Parcourir la source

Merge pull request #10019 from qzhuyan/dev/william/quic-hidden-low-level-tunings

230222 feat(quic): add hidden low level settings for listeners.
Zaiming (Stone) Shi il y a 3 ans
Parent
commit
df7e9db057

+ 341 - 0
apps/emqx/i18n/emqx_schema_i18n.conf

@@ -1901,6 +1901,347 @@ base_listener_acceptors {
     }
     }
 }
 }
 
 
+fields_mqtt_quic_listener_max_bytes_per_key {
+    desc {
+        en: "Maximum number of bytes to encrypt with a single 1-RTT encryption key before initiating key update. Default: 274877906944"
+        zh: "在启动密钥更新之前,用单个 1-RTT 加密密钥加密的最大字节数。默认值:274877906944"
+    }
+    label {
+        en: "Max bytes per key"
+        zh: "每个密钥的最大字节数"
+    }
+}
+
+fields_mqtt_quic_listener_handshake_idle_timeout_ms {
+    desc {
+        en: "How long a handshake can idle before it is discarded. Default: 10 000"
+        zh: "一个握手在被丢弃之前可以空闲多长时间。 默认值:10 000"
+    }
+    label {
+        en: "Handshake idle timeout ms"
+        zh: "握手空闲超时毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_tls_server_max_send_buffer {
+    desc {
+        en: "How much Server TLS data to buffer. Default: 8192"
+        zh: "缓冲多少TLS数据。 默认值:8192"
+    }
+    label {
+        en: "TLS server max send buffer"
+        zh: "TLS 服务器最大发送缓冲区"
+    }
+}
+
+fields_mqtt_quic_listener_stream_recv_window_default {
+    desc {
+        en: "Initial stream receive window size. Default: 32678"
+        zh: "初始流接收窗口大小。 默认值:32678"
+    }
+    label {
+        en: "Stream recv window default"
+        zh: "流接收窗口默认"
+    }
+}
+
+fields_mqtt_quic_listener_stream_recv_buffer_default {
+    desc {
+        en: "Stream initial buffer size. Default: 4096"
+        zh: "流的初始缓冲区大小。默认:4096"
+    }
+    label {
+        en: "Stream recv buffer default"
+        zh: "流媒体接收缓冲区默认值"
+    }
+}
+
+fields_mqtt_quic_listener_conn_flow_control_window {
+    desc {
+        en: "Connection-wide flow control window. Default: 16777216"
+        zh: "连接的流控窗口。默认:16777216"
+    }
+    label {
+        en: "Conn flow control window"
+        zh: "流控窗口"
+    }
+}
+
+fields_mqtt_quic_listener_max_stateless_operations {
+    desc {
+        en: "The maximum number of stateless operations that may be queued on a worker at any one time. Default: 16"
+        zh: "无状态操作的最大数量,在任何时候都可以在一个工作者上排队。默认值:16"
+    }
+    label {
+        en: "Max stateless operations"
+        zh: "最大无状态操作数"
+    }
+}
+
+fields_mqtt_quic_listener_initial_window_packets {
+    desc {
+        en: "The size (in packets) of the initial congestion window for a connection. Default: 10"
+        zh: "一个连接的初始拥堵窗口的大小(以包为单位)。默认值:10"
+    }
+    label {
+        en: "Initial window packets"
+        zh: "初始窗口数据包"
+    }
+}
+
+fields_mqtt_quic_listener_send_idle_timeout_ms {
+    desc {
+        en: "Reset congestion control after being idle for amount of time. Default: 1000"
+        zh: "在闲置一定时间后重置拥堵控制。默认值:1000"
+    }
+    label {
+        en: "Send idle timeout ms"
+        zh: "发送空闲超时毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_initial_rtt_ms {
+    desc {
+        en: "Initial RTT estimate."
+        zh: "初始RTT估计"
+    }
+    label {
+        en: "Initial RTT ms"
+        zh: "Initial RTT 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_max_ack_delay_ms {
+    desc {
+        en: "How long to wait after receiving data before sending an ACK. Default: 25"
+        zh: "在收到数据后要等待多长时间才能发送一个ACK。默认值:25"
+    }
+    label {
+        en: "Max ack delay ms"
+        zh: "最大应答延迟 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_disconnect_timeout_ms {
+    desc {
+        en: "How long to wait for an ACK before declaring a path dead and disconnecting. Default: 16000"
+        zh: "在判定路径无效和断开连接之前,要等待多长时间的ACK。默认:16000"
+    }
+    label {
+        en: "Disconnect timeout ms"
+        zh: "断开连接超时 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_idle_timeout_ms {
+    desc {
+        en: "How long a connection can go idle before it is gracefully shut down. 0 to disable timeout"
+        zh: "一个连接在被优雅地关闭之前可以空闲多长时间。0 表示禁用超时"
+    }
+    label {
+        en: "Idle timeout ms"
+        zh: "空闲超时 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_handshake_idle_timeout_ms {
+    desc {
+        en: "How long a handshake can idle before it is discarded"
+        zh: "一个握手在被丢弃之前可以空闲多长时间"
+    }
+    label {
+        en: "Handshake idle timeout ms"
+        zh: "握手空闲超时 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_keep_alive_interval_ms {
+    desc {
+        en: "How often to send PING frames to keep a connection alive."
+        zh: "多长时间发送一次PING帧以保活连接。"
+    }
+    label {
+        en: "Keep alive interval ms"
+        zh: "保持活着的时间间隔 毫秒"
+    }
+}
+
+fields_mqtt_quic_listener_peer_bidi_stream_count {
+    desc {
+        en: "Number of bidirectional streams to allow the peer to open."
+        zh: "允许对端打开的双向流的数量"
+    }
+    label {
+        en: "Peer bidi stream count"
+        zh: "对端双向流的数量"
+    }
+}
+
+fields_mqtt_quic_listener_peer_unidi_stream_count {
+    desc {
+        en: "Number of unidirectional streams to allow the peer to open."
+        zh: "允许对端打开的单向流的数量"
+    }
+    label {
+        en: "Peer unidi stream count"
+        zh: "对端单向流的数量"
+    }
+}
+
+fields_mqtt_quic_listener_retry_memory_limit {
+    desc {
+        en: "The percentage of available memory usable for handshake connections before stateless retry is used. Calculated as `N/65535`. Default: 65"
+        zh: "在使用无状态重试之前,可用于握手连接的可用内存的百分比。计算为`N/65535`。默认值:65"
+    }
+    label {
+        en: "Retry memory limit"
+        zh: "重试内存限制"
+    }
+}
+
+fields_mqtt_quic_listener_load_balancing_mode {
+    desc {
+        en: "0: Disabled, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. default: 0"
+        zh: "0: 禁用, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. 默认: 0"
+    }
+    label {
+        en: "Load balancing mode"
+        zh: "负载平衡模式"
+    }
+}
+
+fields_mqtt_quic_listener_max_operations_per_drain {
+    desc {
+        en: "The maximum number of operations to drain per connection quantum. Default: 16"
+        zh: "每个连接操作的最大耗费操作数。默认:16"
+    }
+    label {
+        en: "Max operations per drain"
+        zh: "每次操作最大操作数"
+    }
+}
+
+fields_mqtt_quic_listener_send_buffering_enabled {
+    desc {
+        en: "Buffer send data instead of holding application buffers until sent data is acknowledged. Default: 1 (Enabled)"
+        zh: "缓冲发送数据,而不是保留应用缓冲区,直到发送数据被确认。默认值:1(启用)"
+    }
+    label {
+        en: "Send buffering enabled"
+        zh: "启用发送缓冲功能"
+    }
+}
+
+fields_mqtt_quic_listener_pacing_enabled {
+    desc {
+        en: "Pace sending to avoid overfilling buffers on the path. Default: 1 (Enabled)"
+        zh: "有节奏的发送,以避免路径上的缓冲区过度填充。默认值:1(已启用)"
+    }
+    label {
+        en: "Pacing enabled"
+        zh: "启用节奏发送"
+    }
+}
+
+fields_mqtt_quic_listener_migration_enabled {
+    desc {
+        en: "Enable clients to migrate IP addresses and tuples. Requires a cooperative load-balancer, or no load-balancer. Default: 1 (Enabled)"
+        zh: "开启客户端地址迁移功能。需要一个支持的负载平衡器,或者没有负载平衡器。默认值:1(已启用)"
+    }
+    label {
+        en: "Migration enabled"
+        zh: "启用地址迁移"
+    }
+}
+
+fields_mqtt_quic_listener_datagram_receive_enabled {
+    desc {
+        en: "Advertise support for QUIC datagram extension. Reserve for the future. Default 0 (FALSE)"
+        zh: "宣传对QUIC Datagram 扩展的支持。为将来保留。默认为0(FALSE)"
+    }
+    label {
+        en: "Datagram receive enabled"
+        zh: "启用 Datagram 接收"
+    }
+}
+
+fields_mqtt_quic_listener_server_resumption_level {
+    desc {
+        en: "Controls resumption tickets and/or 0-RTT server support. Default: 0 (No resumption)"
+        zh: "连接恢复 和/或 0-RTT 服务器支持。默认值:0(无恢复功能)"
+    }
+    label {
+        en: "Server resumption level"
+        zh: "服务端连接恢复支持"
+    }
+}
+
+fields_mqtt_quic_listener_minimum_mtu {
+    desc {
+        en: "The minimum MTU supported by a connection. This will be used as the starting MTU. Default: 1248"
+        zh: "一个连接所支持的最小MTU。这将被作为起始MTU使用。默认值:1248"
+    }
+    label {
+        en: "Minimum MTU"
+        zh: "最小 MTU"
+    }
+}
+
+fields_mqtt_quic_listener_maximum_mtu {
+    desc {
+        en: "The maximum MTU supported by a connection. This will be the maximum probed value. Default: 1500"
+        zh: "一个连接所支持的最大MTU。这将是最大的探测值。默认值:1500"
+    }
+    label {
+        en: "Maximum MTU"
+        zh: "最大 MTU"
+    }
+}
+
+fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us {
+    desc {
+        en: "The time in microseconds to wait before reattempting MTU probing if max was not reached. Default: 600000000"
+        zh: "如果没有达到 max ,在重新尝试 MTU 探测之前要等待的时间,单位是微秒。默认值:600000000"
+    }
+    label {
+        en: "MTU discovery search complete timeout us"
+        zh: ""
+    }
+}
+
+fields_mqtt_quic_listener_mtu_discovery_missing_probe_count {
+    desc {
+        en: "The maximum number of stateless operations that may be queued on a binding at any one time. Default: 3"
+        zh: "在任何时候都可以在一个绑定上排队的无状态操作的最大数量。默认值:3"
+    }
+    label {
+        en: "MTU discovery missing probe count"
+        zh: "MTU发现丢失的探针数量"
+    }
+}
+
+fields_mqtt_quic_listener_max_binding_stateless_operations {
+    desc {
+        en: "The maximum number of stateless operations that may be queued on a binding at any one time. Default: 100"
+        zh: "在任何时候可以在一个绑定上排队的无状态操作的最大数量。默认值:100"
+    }
+    label {
+        en: "Max binding stateless operations"
+        zh: "最大绑定无状态操作"
+    }
+}
+
+fields_mqtt_quic_listener_stateless_operation_expiration_ms {
+    desc {
+        en: "The time limit between operations for the same endpoint, in milliseconds. Default: 100"
+        zh: "同一个对端的操作之间的时间限制,单位是毫秒。 默认:100"
+    }
+    label {
+        en: "Stateless operation expiration ms"
+        zh: "无状态操作过期 毫秒"
+    }
+}
+
 base_listener_max_connections {
 base_listener_max_connections {
     desc {
     desc {
         en: """The maximum number of concurrent connections allowed by the listener."""
         en: """The maximum number of concurrent connections allowed by the listener."""

+ 63 - 4
apps/emqx/src/emqx_listeners.erl

@@ -383,17 +383,18 @@ do_start_listener(quic, ListenerName, #{bind := Bind} = Opts) ->
                     {keep_alive_interval_ms, maps:get(keep_alive_interval, Opts, 0)},
                     {keep_alive_interval_ms, maps:get(keep_alive_interval, Opts, 0)},
                     {idle_timeout_ms, maps:get(idle_timeout, Opts, 0)},
                     {idle_timeout_ms, maps:get(idle_timeout, Opts, 0)},
                     {handshake_idle_timeout_ms, maps:get(handshake_idle_timeout, Opts, 10000)},
                     {handshake_idle_timeout_ms, maps:get(handshake_idle_timeout, Opts, 10000)},
-                    {server_resumption_level, 2},
+                    {server_resumption_level, maps:get(server_resumption_level, Opts, 2)},
                     {verify, maps:get(verify, SSLOpts, verify_none)}
                     {verify, maps:get(verify, SSLOpts, verify_none)}
                 ] ++
                 ] ++
                     case maps:get(cacertfile, SSLOpts, undefined) of
                     case maps:get(cacertfile, SSLOpts, undefined) of
                         undefined -> [];
                         undefined -> [];
                         CaCertFile -> [{cacertfile, binary_to_list(CaCertFile)}]
                         CaCertFile -> [{cacertfile, binary_to_list(CaCertFile)}]
-                    end,
+                    end ++
+                    optional_quic_listener_opts(Opts),
             ConnectionOpts = #{
             ConnectionOpts = #{
                 conn_callback => emqx_quic_connection,
                 conn_callback => emqx_quic_connection,
-                peer_unidi_stream_count => 1,
-                peer_bidi_stream_count => 10,
+                peer_unidi_stream_count => maps:get(peer_unidi_stream_count, Opts, 1),
+                peer_bidi_stream_count => maps:get(peer_bidi_stream_count, Opts, 10),
                 zone => zone(Opts),
                 zone => zone(Opts),
                 listener => {quic, ListenerName},
                 listener => {quic, ListenerName},
                 limiter => limiter(Opts)
                 limiter => limiter(Opts)
@@ -726,3 +727,61 @@ get_ssl_options(Conf) ->
         error ->
         error ->
             maps:get(<<"ssl_options">>, Conf, undefined)
             maps:get(<<"ssl_options">>, Conf, undefined)
     end.
     end.
+
+%% @doc Get QUIC optional settings for low level tunings.
+%% @see quicer:quic_settings()
+-spec optional_quic_listener_opts(map()) -> proplists:proplist().
+optional_quic_listener_opts(Conf) when is_map(Conf) ->
+    maps:to_list(
+        maps:filter(
+            fun(Name, _V) ->
+                lists:member(
+                    Name,
+                    quic_listener_optional_settings()
+                )
+            end,
+            Conf
+        )
+    ).
+
+-spec quic_listener_optional_settings() -> [atom()].
+quic_listener_optional_settings() ->
+    [
+        max_bytes_per_key,
+        %% In conf schema we use handshake_idle_timeout
+        handshake_idle_timeout_ms,
+        %% In conf schema we use idle_timeout
+        idle_timeout_ms,
+        %% not use since we are server
+        %% tls_client_max_send_buffer,
+        tls_server_max_send_buffer,
+        stream_recv_window_default,
+        stream_recv_buffer_default,
+        conn_flow_control_window,
+        max_stateless_operations,
+        initial_window_packets,
+        send_idle_timeout_ms,
+        initial_rtt_ms,
+        max_ack_delay_ms,
+        disconnect_timeout_ms,
+        %% In conf schema,  we use keep_alive_interval
+        keep_alive_interval_ms,
+        %% over written by conn opts
+        peer_bidi_stream_count,
+        %% over written by conn opts
+        peer_unidi_stream_count,
+        retry_memory_limit,
+        load_balancing_mode,
+        max_operations_per_drain,
+        send_buffering_enabled,
+        pacing_enabled,
+        migration_enabled,
+        datagram_receive_enabled,
+        server_resumption_level,
+        minimum_mtu,
+        maximum_mtu,
+        mtu_discovery_search_complete_timeout_us,
+        mtu_discovery_missing_probe_count,
+        max_binding_stateless_operations,
+        stateless_operation_expiration_ms
+    ].

+ 209 - 0
apps/emqx/src/emqx_schema.erl

@@ -120,6 +120,9 @@
 
 
 -elvis([{elvis_style, god_modules, disable}]).
 -elvis([{elvis_style, god_modules, disable}]).
 
 
+-define(BIT(Bits), (1 bsl (Bits))).
+-define(MAX_UINT(Bits), (?BIT(Bits) - 1)).
+
 namespace() -> broker.
 namespace() -> broker.
 
 
 tags() ->
 tags() ->
@@ -862,6 +865,79 @@ fields("mqtt_quic_listener") ->
                 }
                 }
             )},
             )},
         {"ciphers", ciphers_schema(quic)},
         {"ciphers", ciphers_schema(quic)},
+
+        {"max_bytes_per_key",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(64),
+                ?DESC(fields_mqtt_quic_listener_max_bytes_per_key)
+            )},
+        {"handshake_idle_timeout_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(64),
+                ?DESC(fields_mqtt_quic_listener_handshake_idle_timeout)
+            )},
+        {"tls_server_max_send_buffer",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_tls_server_max_send_buffer)
+            )},
+        {"stream_recv_window_default",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_stream_recv_window_default)
+            )},
+        {"stream_recv_buffer_default",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_stream_recv_buffer_default)
+            )},
+        {"conn_flow_control_window",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_conn_flow_control_window)
+            )},
+        {"max_stateless_operations",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_max_stateless_operations)
+            )},
+        {"initial_window_packets",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_initial_window_packets)
+            )},
+        {"send_idle_timeout_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_send_idle_timeout_ms)
+            )},
+        {"initial_rtt_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_initial_rtt_ms)
+            )},
+        {"max_ack_delay_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_max_ack_delay_ms)
+            )},
+        {"disconnect_timeout_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_disconnect_timeout_ms)
+            )},
         {"idle_timeout",
         {"idle_timeout",
             sc(
             sc(
                 duration_ms(),
                 duration_ms(),
@@ -870,6 +946,12 @@ fields("mqtt_quic_listener") ->
                     desc => ?DESC(fields_mqtt_quic_listener_idle_timeout)
                     desc => ?DESC(fields_mqtt_quic_listener_idle_timeout)
                 }
                 }
             )},
             )},
+        {"idle_timeout_ms",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(64),
+                ?DESC(fields_mqtt_quic_listener_idle_timeout_ms)
+            )},
         {"handshake_idle_timeout",
         {"handshake_idle_timeout",
             sc(
             sc(
                 duration_ms(),
                 duration_ms(),
@@ -878,6 +960,12 @@ fields("mqtt_quic_listener") ->
                     desc => ?DESC(fields_mqtt_quic_listener_handshake_idle_timeout)
                     desc => ?DESC(fields_mqtt_quic_listener_handshake_idle_timeout)
                 }
                 }
             )},
             )},
+        {"handshake_idle_timeout_ms",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(64),
+                ?DESC(fields_mqtt_quic_listener_handshake_idle_timeout_ms)
+            )},
         {"keep_alive_interval",
         {"keep_alive_interval",
             sc(
             sc(
                 duration_ms(),
                 duration_ms(),
@@ -886,6 +974,100 @@ fields("mqtt_quic_listener") ->
                     desc => ?DESC(fields_mqtt_quic_listener_keep_alive_interval)
                     desc => ?DESC(fields_mqtt_quic_listener_keep_alive_interval)
                 }
                 }
             )},
             )},
+        {"keep_alive_interval_ms",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(32),
+                ?DESC(fields_mqtt_quic_listener_keep_alive_interval_ms)
+            )},
+        {"peer_bidi_stream_count",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_peer_bidi_stream_count)
+            )},
+        {"peer_unidi_stream_count",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_peer_unidi_stream_count)
+            )},
+        {"retry_memory_limit",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_retry_memory_limit)
+            )},
+        {"load_balancing_mode",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_load_balancing_mode)
+            )},
+        {"max_operations_per_drain",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(8),
+                ?DESC(fields_mqtt_quic_listener_max_operations_per_drain)
+            )},
+        {"send_buffering_enabled",
+            quic_feature_toggle(
+                ?DESC(fields_mqtt_quic_listener_send_buffering_enabled)
+            )},
+        {"pacing_enabled",
+            quic_feature_toggle(
+                ?DESC(fields_mqtt_quic_listener_pacing_enabled)
+            )},
+        {"migration_enabled",
+            quic_feature_toggle(
+                ?DESC(fields_mqtt_quic_listener_migration_enabled)
+            )},
+        {"datagram_receive_enabled",
+            quic_feature_toggle(
+                ?DESC(fields_mqtt_quic_listener_datagram_receive_enabled)
+            )},
+        {"server_resumption_level",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(8),
+                ?DESC(fields_mqtt_quic_listener_server_resumption_level)
+            )},
+        {"minimum_mtu",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_minimum_mtu)
+            )},
+        {"maximum_mtu",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_maximum_mtu)
+            )},
+        {"mtu_discovery_search_complete_timeout_us",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(64),
+                ?DESC(fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us)
+            )},
+        {"mtu_discovery_missing_probe_count",
+            quic_lowlevel_settings_uint(
+                1,
+                ?MAX_UINT(8),
+                ?DESC(fields_mqtt_quic_listener_mtu_discovery_missing_probe_count)
+            )},
+        {"max_binding_stateless_operations",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_max_binding_stateless_operations)
+            )},
+        {"stateless_operation_expiration_ms",
+            quic_lowlevel_settings_uint(
+                0,
+                ?MAX_UINT(16),
+                ?DESC(fields_mqtt_quic_listener_stateless_operation_expiration_ms)
+            )},
         {"ssl_options",
         {"ssl_options",
             sc(
             sc(
                 ref("listener_quic_ssl_opts"),
                 ref("listener_quic_ssl_opts"),
@@ -2638,3 +2820,30 @@ parse_port(Port) ->
         _:_ ->
         _:_ ->
             throw("bad_port_number")
             throw("bad_port_number")
     end.
     end.
+
+quic_feature_toggle(Desc) ->
+    sc(
+        %% true, false are for user facing
+        %% 0, 1 are for internal represtation
+        typerefl:alias("boolean", typerefl:union([true, false, 0, 1])),
+        #{
+            desc => Desc,
+            hidden => true,
+            required => false,
+            converter => fun
+                (true) -> 1;
+                (false) -> 0;
+                (Other) -> Other
+            end
+        }
+    ).
+
+quic_lowlevel_settings_uint(Low, High, Desc) ->
+    sc(
+        range(Low, High),
+        #{
+            required => false,
+            hidden => true,
+            desc => Desc
+        }
+    ).

+ 5 - 1
apps/emqx/test/emqx_common_test_helpers.erl

@@ -44,6 +44,7 @@
     client_ssl_twoway/1,
     client_ssl_twoway/1,
     ensure_mnesia_stopped/0,
     ensure_mnesia_stopped/0,
     ensure_quic_listener/2,
     ensure_quic_listener/2,
+    ensure_quic_listener/3,
     is_all_tcp_servers_available/1,
     is_all_tcp_servers_available/1,
     is_tcp_server_available/2,
     is_tcp_server_available/2,
     is_tcp_server_available/3,
     is_tcp_server_available/3,
@@ -511,6 +512,9 @@ ensure_dashboard_listeners_started(_App) ->
 
 
 -spec ensure_quic_listener(Name :: atom(), UdpPort :: inet:port_number()) -> ok.
 -spec ensure_quic_listener(Name :: atom(), UdpPort :: inet:port_number()) -> ok.
 ensure_quic_listener(Name, UdpPort) ->
 ensure_quic_listener(Name, UdpPort) ->
+    ensure_quic_listener(Name, UdpPort, #{}).
+-spec ensure_quic_listener(Name :: atom(), UdpPort :: inet:port_number(), map()) -> ok.
+ensure_quic_listener(Name, UdpPort, ExtraSettings) ->
     application:ensure_all_started(quicer),
     application:ensure_all_started(quicer),
     Conf = #{
     Conf = #{
         acceptors => 16,
         acceptors => 16,
@@ -533,7 +537,7 @@ ensure_quic_listener(Name, UdpPort) ->
         mountpoint => <<>>,
         mountpoint => <<>>,
         zone => default
         zone => default
     },
     },
-    emqx_config:put([listeners, quic, Name], Conf),
+    emqx_config:put([listeners, quic, Name], maps:merge(Conf, ExtraSettings)),
     case emqx_listeners:start_listener(quic, Name, Conf) of
     case emqx_listeners:start_listener(quic, Name, Conf) of
         ok -> ok;
         ok -> ok;
         {error, {already_started, _Pid}} -> ok
         {error, {already_started, _Pid}} -> ok

+ 56 - 1
apps/emqx/test/emqx_quic_multistreams_SUITE.erl

@@ -32,7 +32,8 @@ all() ->
     [
     [
         {group, mstream},
         {group, mstream},
         {group, shutdown},
         {group, shutdown},
-        {group, misc}
+        {group, misc},
+        t_listener_with_lowlevel_settings
     ].
     ].
 
 
 groups() ->
 groups() ->
@@ -1892,6 +1893,60 @@ t_multi_streams_sub_0_rtt_stream_data_cont(Config) ->
     ok = emqtt:disconnect(C),
     ok = emqtt:disconnect(C),
     ok = emqtt:disconnect(C0).
     ok = emqtt:disconnect(C0).
 
 
+t_listener_with_lowlevel_settings(_Config) ->
+    LPort = 24567,
+    LowLevelTunings = #{
+        max_bytes_per_key => 274877906,
+        %% In conf schema we use handshake_idle_timeout
+        handshake_idle_timeout_ms => 2000,
+        %% In conf schema we use idle_timeout
+        idle_timeout_ms => 20000,
+        %% not use since we are server
+        %% tls_client_max_send_buffer,
+        tls_server_max_send_buffer => 10240,
+        stream_recv_window_default => 1024,
+        stream_recv_buffer_default => 1024,
+        conn_flow_control_window => 1024,
+        max_stateless_operations => 16,
+        initial_window_packets => 1300,
+        send_idle_timeout_ms => 12000,
+        initial_rtt_ms => 300,
+        max_ack_delay_ms => 6000,
+        disconnect_timeout_ms => 60000,
+        %% In conf schema,  we use keep_alive_interval
+        keep_alive_interval_ms => 12000,
+        %% over written by conn opts
+        peer_bidi_stream_count => 100,
+        %% over written by conn opts
+        peer_unidi_stream_count => 100,
+        retry_memory_limit => 640,
+        load_balancing_mode => 1,
+        max_operations_per_drain => 32,
+        send_buffering_enabled => 1,
+        pacing_enabled => 0,
+        migration_enabled => 0,
+        datagram_receive_enabled => 1,
+        server_resumption_level => 0,
+        minimum_mtu => 1250,
+        maximum_mtu => 1600,
+        mtu_discovery_search_complete_timeout_us => 500000000,
+        mtu_discovery_missing_probe_count => 6,
+        max_binding_stateless_operations => 200,
+        stateless_operation_expiration_ms => 200
+    },
+    ?assertEqual(
+        ok, emqx_common_test_helpers:ensure_quic_listener(?FUNCTION_NAME, LPort, LowLevelTunings)
+    ),
+    timer:sleep(1000),
+    {ok, C} = emqtt:start_link([{proto_ver, v5}, {port, LPort}]),
+    {ok, _} = emqtt:quic_connect(C),
+    {ok, _, _} = emqtt:subscribe(C, <<"test/1/2">>, qos2),
+    {ok, _, [_SubQos]} = emqtt:subscribe_via(C, {new_data_stream, []}, #{}, [
+        {<<"test/1/3">>, [{qos, 2}]}
+    ]),
+    ok = emqtt:disconnect(C),
+    emqx_listeners:stop_listener(emqx_listeners:listener_id(quic, ?FUNCTION_NAME)).
+
 %%--------------------------------------------------------------------
 %%--------------------------------------------------------------------
 %% Helper functions
 %% Helper functions
 %%--------------------------------------------------------------------
 %%--------------------------------------------------------------------

+ 1 - 0
changes/ce/feat-10019.en.md

@@ -0,0 +1 @@
+Add low level tuning settings for QUIC listeners.

+ 1 - 0
changes/ce/feat-10019.zh.md

@@ -0,0 +1 @@
+为 QUIC 侦听器添加更多底层调优选项。