diff --git a/apps/emqx/i18n/emqx_schema_i18n.conf b/apps/emqx/i18n/emqx_schema_i18n.conf index 39d5b2828..a3aebe796 100644 --- a/apps/emqx/i18n/emqx_schema_i18n.conf +++ b/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 { desc { en: """The maximum number of concurrent connections allowed by the listener.""" diff --git a/apps/emqx/src/emqx_listeners.erl b/apps/emqx/src/emqx_listeners.erl index fedf583e2..6982b3dea 100644 --- a/apps/emqx/src/emqx_listeners.erl +++ b/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)}, {idle_timeout_ms, maps:get(idle_timeout, Opts, 0)}, {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)} ] ++ case maps:get(cacertfile, SSLOpts, undefined) of undefined -> []; CaCertFile -> [{cacertfile, binary_to_list(CaCertFile)}] - end, + end ++ + optional_quic_listener_opts(Opts), ConnectionOpts = #{ 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), listener => {quic, ListenerName}, limiter => limiter(Opts) @@ -726,3 +727,61 @@ get_ssl_options(Conf) -> error -> maps:get(<<"ssl_options">>, Conf, undefined) 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 + ]. diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index 8d24e6937..7000ffe0a 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -120,6 +120,9 @@ -elvis([{elvis_style, god_modules, disable}]). +-define(BIT(Bits), (1 bsl (Bits))). +-define(MAX_UINT(Bits), (?BIT(Bits) - 1)). + namespace() -> broker. tags() -> @@ -862,6 +865,79 @@ fields("mqtt_quic_listener") -> } )}, {"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", sc( duration_ms(), @@ -870,6 +946,12 @@ fields("mqtt_quic_listener") -> 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", sc( duration_ms(), @@ -878,6 +960,12 @@ fields("mqtt_quic_listener") -> 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", sc( duration_ms(), @@ -886,6 +974,100 @@ fields("mqtt_quic_listener") -> 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", sc( ref("listener_quic_ssl_opts"), @@ -2638,3 +2820,30 @@ parse_port(Port) -> _:_ -> throw("bad_port_number") 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 + } + ). diff --git a/apps/emqx/test/emqx_common_test_helpers.erl b/apps/emqx/test/emqx_common_test_helpers.erl index 5149b8b8a..dd88b013d 100644 --- a/apps/emqx/test/emqx_common_test_helpers.erl +++ b/apps/emqx/test/emqx_common_test_helpers.erl @@ -44,6 +44,7 @@ client_ssl_twoway/1, ensure_mnesia_stopped/0, ensure_quic_listener/2, + ensure_quic_listener/3, is_all_tcp_servers_available/1, is_tcp_server_available/2, 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. 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), Conf = #{ acceptors => 16, @@ -533,7 +537,7 @@ ensure_quic_listener(Name, UdpPort) -> mountpoint => <<>>, 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 ok -> ok; {error, {already_started, _Pid}} -> ok diff --git a/apps/emqx/test/emqx_quic_multistreams_SUITE.erl b/apps/emqx/test/emqx_quic_multistreams_SUITE.erl index 17ba85da7..a95597f07 100644 --- a/apps/emqx/test/emqx_quic_multistreams_SUITE.erl +++ b/apps/emqx/test/emqx_quic_multistreams_SUITE.erl @@ -32,7 +32,8 @@ all() -> [ {group, mstream}, {group, shutdown}, - {group, misc} + {group, misc}, + t_listener_with_lowlevel_settings ]. groups() -> @@ -1892,6 +1893,60 @@ t_multi_streams_sub_0_rtt_stream_data_cont(Config) -> ok = emqtt:disconnect(C), 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 %%-------------------------------------------------------------------- diff --git a/changes/ce/feat-10019.en.md b/changes/ce/feat-10019.en.md new file mode 100644 index 000000000..b6cc0381c --- /dev/null +++ b/changes/ce/feat-10019.en.md @@ -0,0 +1 @@ +Add low level tuning settings for QUIC listeners. diff --git a/changes/ce/feat-10019.zh.md b/changes/ce/feat-10019.zh.md new file mode 100644 index 000000000..9ef671b3d --- /dev/null +++ b/changes/ce/feat-10019.zh.md @@ -0,0 +1 @@ +为 QUIC 侦听器添加更多底层调优选项。