From b5edb2cd39af4691f192fc6b3a8d66da5a90bab9 Mon Sep 17 00:00:00 2001 From: Tianling Shen Date: Sat, 19 Aug 2023 19:23:30 +0800 Subject: [PATCH] refactor(view): improve tuic support Signed-off-by: Tianling Shen --- .../resources/view/homeproxy/client.js | 2 +- .../resources/view/homeproxy/node.js | 107 +++++++++++------- .../resources/view/homeproxy/server.js | 72 ++++++------ root/etc/homeproxy/scripts/generate_client.uc | 12 +- root/etc/homeproxy/scripts/generate_server.uc | 18 +-- .../homeproxy/scripts/update_subscriptions.uc | 27 +++++ 6 files changed, 150 insertions(+), 88 deletions(-) diff --git a/htdocs/luci-static/resources/view/homeproxy/client.js b/htdocs/luci-static/resources/view/homeproxy/client.js index 64a2eef5..d6d06264 100644 --- a/htdocs/luci-static/resources/view/homeproxy/client.js +++ b/htdocs/luci-static/resources/view/homeproxy/client.js @@ -262,7 +262,7 @@ return view.extend({ so = ss.option(form.ListValue, 'tcpip_stack', _('TCP/IP stack'), _('TCP/IP stack.')); if (features.with_gvisor) { - so.value('mixed', _('Mixed')) + so.value('mixed', _('Mixed')); so.value('gvisor', _('gVisor')); } if (features.with_lwip) diff --git a/htdocs/luci-static/resources/view/homeproxy/node.js b/htdocs/luci-static/resources/view/homeproxy/node.js index e54abcb4..6e014b7c 100644 --- a/htdocs/luci-static/resources/view/homeproxy/node.js +++ b/htdocs/luci-static/resources/view/homeproxy/node.js @@ -210,6 +210,30 @@ function parseShareLink(uri, features) { break; } + break; + case 'tuic': + /* https://github.com/daeuniverse/dae/discussions/182 */ + var url = new URL('http://' + uri[1]); + var params = url.searchParams; + + /* Check if uuid exists */ + if (!url.username) + return null; + + config = { + label: url.hash ? decodeURIComponent(url.hash.slice(1)) : null, + type: 'tuic', + address: url.hostname, + port: url.port || '80', + uuid: url.username, + password: url.password ? decodeURIComponent(url.password) : null, + tuic_congestion_control: params.get('congestion_control'), + tuic_udp_relay_mode: params.get('udp_relay_mode'), + tls: '1', + tls_sni: params.get('sni'), + tls_alpn: params.get('alpn') ? decodeURIComponent(params.get('alpn')).split(',') : null + }; + break; case 'vless': /* https://github.com/XTLS/Xray-core/discussions/716 */ @@ -490,13 +514,14 @@ return view.extend({ so.value('http', _('HTTP')); if (features.with_quic) so.value('hysteria', _('Hysteria')); - so.value('tuic', _('Tuic')); so.value('shadowsocks', _('Shadowsocks')); if (features.with_shadowsocksr) so.value('shadowsocksr', _('ShadowsocksR')); so.value('shadowtls', _('ShadowTLS')); so.value('socks', _('Socks')); so.value('trojan', _('Trojan')); + if (features.with_quic) + so.value('tuic', _('Tuic')); if (features.with_wireguard) so.value('wireguard', _('WireGuard')); so.value('vless', _('VLESS')); @@ -524,14 +549,14 @@ return view.extend({ so.depends('type', 'shadowsocks'); so.depends('type', 'shadowsocksr'); so.depends('type', 'trojan'); + so.depends('type', 'tuic'); so.depends({'type': 'shadowtls', 'shadowtls_version': '2'}); so.depends({'type': 'shadowtls', 'shadowtls_version': '3'}); so.depends({'type': 'socks', 'socks_version': '5'}); - so.depends('type', 'tuic'); so.validate = function(section_id, value) { if (section_id) { var type = this.map.lookupOption('type', section_id)[0].formvalue(section_id); - var required_type = [ 'shadowsocks', 'shadowsocksr', 'shadowtls', 'trojan', 'tuic' ]; + var required_type = [ 'shadowsocks', 'shadowsocksr', 'shadowtls', 'trojan' ]; if (required_type.includes(type)) { if (type === 'shadowsocks') { @@ -749,14 +774,49 @@ return view.extend({ so.rmempty = false; so.modalonly = true; - /* VMess / VLESS config start */ + /* TUIC config start */ so = ss.option(form.Value, 'uuid', _('UUID')); + so.depends('type', 'tuic'); so.depends('type', 'vless'); so.depends('type', 'vmess'); - so.depends('type', 'tuic'); so.validate = hp.validateUUID; so.modalonly = true; + so = ss.option(form.ListValue, 'tuic_congestion_control', _('Congestion control algorithm'), + _('QUIC congestion control algorithm.')); + so.value('cubic', _('CUBIC')); + so.value('new_reno', _('New Reno')); + so.value('bbr', _('BBR')); + so.default = 'cubic'; + so.depends('type', 'tuic'); + so.rmempty = false; + so.modalonly = true; + + so = ss.option(form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'), + _('UDP packet relay mode.')); + so.value('native', _('Native')); + so.value('quic', _('QUIC')); + so.default = 'native'; + so.depends('type', 'tuic'); + so.rmempty = false; + so.modalonly = true; + + so = ss.option(form.Flag, 'tuic_enable_zero_rtt', _('Enable 0-RTT handshake'), + _('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed.
' + + 'Disabling this is highly recommended, as it is vulnerable to replay attacks.')); + so.default = so.disabled; + so.depends('type', 'tuic'); + so.modalonly = true; + + so = ss.option(form.Value, 'tuic_heartbeat', _('Heartbeat'), + _('Interval for sending heartbeat packets for keeping the connection alive (in seconds).')); + so.datatype = 'uinteger'; + so.default = '10'; + so.depends('type', 'tuic'); + so.modalonly = true; + /* Tuic config end */ + + /* VMess / VLESS config start */ so = ss.option(form.ListValue, 'vless_flow', _('Flow')); so.value('', _('None')); so.value('xtls-rprx-vision'); @@ -794,37 +854,6 @@ return view.extend({ so.modalonly = true; /* VMess config end */ - /* Tuic config start */ - so = ss.option(form.ListValue, 'tuic_congestion_control', _('Congestion control'), - _('QUIC congestion control algorithm.')); - so.value('cubic'); - so.value('new_reno'); - so.value('bbr'); - so.default = 'cubic'; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.ListValue, 'tuic_udp_relay_mode', _('UDP relay mode'), - _('UDP packet relay mode.')); - so.value('native'); - so.value('quic'); - so.default = 'native'; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.Flag, 'tuic_zero_rtt_handshake', _('Zero RTT handshake'), - _('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed. Disabling this is highly recommended, as it is vulnerable to replay attacks.')); - so.default = so.disabled; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.Value, 'tuic_heartbeat', _('Heartbeat'), - _('Interval for sending heartbeat packets for keeping the connection alive.')); - so.default = '10s'; - so.depends('type', 'tuic'); - so.modalonly = true; - /* Tuic config end */ - /* Transport config start */ so = ss.option(form.ListValue, 'transport', _('Transport'), _('No TCP transport, plain HTTP is merged into the HTTP transport.')); @@ -895,7 +924,7 @@ return view.extend({ so.modalonly = true; so = ss.option(form.Value, 'http_idle_timeout', _('Idle timeout'), - _('Specifies the period of time after which a health check will be performed using a ping frame if no frames have been received on the connection.
' + + _('Specifies the period of time (in seconds) after which a health check will be performed using a ping frame if no frames have been received on the connection.
' + 'Please note that a ping response is considered a received frame, so if there is no other traffic on the connection, the health check will be executed every interval.')); so.datatype = 'uinteger'; so.depends('transport', 'grpc'); @@ -903,7 +932,7 @@ return view.extend({ so.modalonly = true; so = ss.option(form.Value, 'http_ping_timeout', _('Ping timeout'), - _('Specifies the timeout duration after sending a PING frame, within which a response must be received.
' + + _('Specifies the timeout (in seconds) duration after sending a PING frame, within which a response must be received.
' + 'If a response to the PING frame is not received within the specified timeout duration, the connection will be closed.')); so.datatype = 'uinteger'; so.depends('transport', 'grpc'); @@ -1033,9 +1062,9 @@ return view.extend({ so.depends('type', 'hysteria'); so.depends('type', 'shadowtls'); so.depends('type', 'trojan'); + so.depends('type', 'tuic'); so.depends('type', 'vless'); so.depends('type', 'vmess'); - so.depends('type', 'tuic'); so.validate = function(section_id, value) { if (section_id) { var type = this.map.lookupOption('type', section_id)[0].formvalue(section_id); diff --git a/htdocs/luci-static/resources/view/homeproxy/server.js b/htdocs/luci-static/resources/view/homeproxy/server.js index 2d033145..91c32e1b 100644 --- a/htdocs/luci-static/resources/view/homeproxy/server.js +++ b/htdocs/luci-static/resources/view/homeproxy/server.js @@ -105,11 +105,12 @@ return view.extend({ if (features.with_quic) { o.value('hysteria', _('Hysteria')); o.value('naive', _('NaïveProxy')); - o.value('tuic', _('Tuic')); } o.value('shadowsocks', _('Shadowsocks')); o.value('socks', _('Socks')); o.value('trojan', _('Trojan')); + if (features.with_quic) + o.value('tuic', _('Tuic')); o.value('vless', _('VLESS')); o.value('vmess', _('VMess')); o.rmempty = false; @@ -232,14 +233,46 @@ return view.extend({ o.depends('type', 'shadowsocks'); o.modalonly = true; - /* VLESS / VMess config start */ + /* Tuic config start */ o = s.option(form.Value, 'uuid', _('UUID')); + o.depends('type', 'tuic'); o.depends('type', 'vless'); o.depends('type', 'vmess'); - o.depends('type', 'tuic'); o.validate = hp.validateUUID; o.modalonly = true; + o = s.option(form.ListValue, 'tuic_congestion_control', _('Congestion control algorithm'), + _('QUIC congestion control algorithm.')); + o.value('cubic'); + o.value('new_reno'); + o.value('bbr'); + o.default = 'cubic'; + o.depends('type', 'tuic'); + o.modalonly = true; + + o = s.option(form.ListValue, 'tuic_auth_timeout', _('Auth timeout'), + _('How long the server should wait for the client to send the authentication command (in seconds).')); + o.datatype = 'uinteger'; + o.default = '3'; + o.depends('type', 'tuic'); + o.modalonly = true; + + o = s.option(form.Flag, 'tuic_enable_zero_rtt', _('Enable 0-RTT handshake'), + _('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed.
' + + 'Disabling this is highly recommended, as it is vulnerable to replay attacks.')); + o.default = o.disabled; + o.depends('type', 'tuic'); + o.modalonly = true; + + o = s.option(form.Value, 'tuic_heartbeat', _('Heartbeat'), + _('Interval for sending heartbeat packets for keeping the connection alive (in seconds).')); + o.datatype = 'uinteger'; + o.default = '10'; + o.depends('type', 'tuic'); + o.modalonly = true; + /* Tuic config end */ + + /* VLESS / VMess config start */ o = s.option(form.ListValue, 'vless_flow', _('Flow')); o.value('', _('None')); o.value('xtls-rprx-vision'); @@ -253,35 +286,6 @@ return view.extend({ o.modalonly = true; /* VMess config end */ - /* Tuic config start */ - so = ss.option(form.ListValue, 'tuic_congestion_control', _('Congestion control'), - _('QUIC congestion control algorithm.')); - so.value('cubic'); - so.value('new_reno'); - so.value('bbr'); - so.default = 'cubic'; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.ListValue, 'tuic_auth_timeout', _('Auth timeout'), - _('How long the server should wait for the client to send the authentication command.')); - so.default = '3s'; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.Flag, 'tuic_zero_rtt_handshake', _('Zero RTT handshake'), - _('Enable 0-RTT QUIC connection handshake on the client side. This is not impacting much on the performance, as the protocol is fully multiplexed. Disabling this is highly recommended, as it is vulnerable to replay attacks.')); - so.default = so.disabled; - so.depends('type', 'tuic'); - so.modalonly = true; - - so = ss.option(form.Value, 'tuic_heartbeat', _('Heartbeat'), - _('Interval for sending heartbeat packets for keeping the connection alive.')); - so.default = '10s'; - so.depends('type', 'tuic'); - so.modalonly = true; - /* Tuic config end */ - /* Transport config start */ o = s.option(form.ListValue, 'transport', _('Transport'), _('No TCP transport, plain HTTP is merged into the HTTP transport.')); @@ -334,7 +338,7 @@ return view.extend({ o.modalonly = true; o = s.option(form.Value, 'http_idle_timeout', _('Idle timeout'), - _('Specifies the time until idle clients should be closed with a GOAWAY frame. PING frames are not considered as activity.')); + _('Specifies the time (in seconds) until idle clients should be closed with a GOAWAY frame. PING frames are not considered as activity.')); o.datatype = 'uinteger'; o.depends('transport', 'grpc'); o.depends({'transport': 'http', 'tls': '1'}); @@ -342,7 +346,7 @@ return view.extend({ if (features.with_grpc) { o = s.option(form.Value, 'http_ping_timeout', _('Ping timeout'), - _('The timeout that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.')); + _('The timeout (in seconds) that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.')); o.datatype = 'uinteger'; o.depends('transport', 'grpc'); o.modalonly = true; diff --git a/root/etc/homeproxy/scripts/generate_client.uc b/root/etc/homeproxy/scripts/generate_client.uc index 4af2280a..78198cef 100755 --- a/root/etc/homeproxy/scripts/generate_client.uc +++ b/root/etc/homeproxy/scripts/generate_client.uc @@ -153,8 +153,13 @@ function generate_outbound(node) { obfs_param: node.shadowsocksr_obfs_param, /* ShadowTLS / Socks */ version: (node.type === 'shadowtls') ? strToInt(node.shadowtls_version) : ((node.type === 'socks') ? node.socks_version : null), - /* VLESS / VMess / Tuic */ + /* Tuic */ uuid: node.uuid, + congestion_control: node.tuic_congestion_control, + udp_relay_mode: node.tuic_udp_relay_mode, + zero_rtt_handshake: (node.tuic_enable_zero_rtt === '1') || null, + heartbeat: node.tuic_heartbeat ? (node.tuic_heartbeat + 's') : null, + /* VLESS / VMess */ flow: node.vless_flow, alter_id: strToInt(node.vmess_alterid), security: node.vmess_encrypt, @@ -170,11 +175,6 @@ function generate_outbound(node) { pre_shared_key: node.wireguard_pre_shared_key, reserved: parse_port(node.wireguard_reserved), mtu: node.wireguard_mtu, - /* Tuic */ - congestion_control: node.tuic_congestion_control, - udp_relay_mode: node.tuic_udp_relay_mode, - zero_rtt_handshake: (node.tuic_zero_rtt_handshake === '1') || null, - heartbeat: node.tuic_heartbeat, multiplex: (node.multiplex === '1') ? { enabled: true, diff --git a/root/etc/homeproxy/scripts/generate_server.uc b/root/etc/homeproxy/scripts/generate_server.uc index 7b8be722..768a619b 100755 --- a/root/etc/homeproxy/scripts/generate_server.uc +++ b/root/etc/homeproxy/scripts/generate_server.uc @@ -64,17 +64,17 @@ uci.foreach(uciconfig, uciserver, (cfg) => { max_conn_client: strToInt(cfg.hysteria_max_conn_client), disable_mtu_discovery: (cfg.hysteria_disable_mtu_discovery === '1') || null, - /* Tuic */ - congestion_control: cfg.tuic_congestion_control, - auth_timeout: cfg.tuic_auth_timeout, - zero_rtt_handshake: (cfg.tuic_zero_rtt_handshake === '1') || null, - heartbeat: cfg.tuic_heartbeat, - /* Shadowsocks */ method: (cfg.type === 'shadowsocks') ? cfg.shadowsocks_encrypt_method : null, password: (cfg.type in ['shadowsocks', 'shadowtls']) ? cfg.password : null, - /* HTTP / Hysteria / Socks / Trojan / VLESS / VMess / Tuic */ + /* Tuic */ + congestion_control: cfg.tuic_congestion_control, + auth_timeout: cfg.tuic_auth_timeout ? (cfg.tuic_auth_timeout + 's') : null, + zero_rtt_handshake: (cfg.tuic_enable_zero_rtt === '1') || null, + heartbeat: cfg.tuic_heartbeat ? (cfg.tuic_heartbeat + 's') : null, + + /* HTTP / Hysteria / Socks / Trojan / Tuic / VLESS / VMess */ users: (cfg.type !== 'shadowsocks') ? [ { name: !(cfg.type in ['http', 'socks']) ? 'cfg-' + cfg['.name'] + '-server' : null, @@ -85,8 +85,10 @@ uci.foreach(uciconfig, uciserver, (cfg) => { auth: (cfg.hysteria_auth_type === 'base64') ? cfg.hysteria_auth_payload : null, auth_str: (cfg.hysteria_auth_type === 'string') ? cfg.hysteria_auth_payload : null, - /* VLESS / VMess / Tuic */ + /* Tuic */ uuid: cfg.uuid, + + /* VLESS / VMess */ flow: cfg.vless_flow, alterId: strToInt(cfg.vmess_alterid) } diff --git a/root/etc/homeproxy/scripts/update_subscriptions.uc b/root/etc/homeproxy/scripts/update_subscriptions.uc index 56d4283d..71c90ccd 100755 --- a/root/etc/homeproxy/scripts/update_subscriptions.uc +++ b/root/etc/homeproxy/scripts/update_subscriptions.uc @@ -273,6 +273,33 @@ function parse_uri(uri) { break; } + break; + case 'tuic': + /* https://github.com/daeuniverse/dae/discussions/182 */ + const tuic_url = parseURL('http://' + uri[1]), + tuic_params = tuic_url.searchParams || {}; + + if (!sing_features.with_quic) { + log(sprintf('Skipping unsupported %s node: %s.', 'tuic', urldecode(tuic_url.hash) || tuic_url.hostname)); + log(sprintf('Please rebuild sing-box with %s support!', 'QUIC')); + + return null; + } + + config = { + label: tuic_url.hash ? urldecode(tuic_url.hash) : null, + type: 'tuic', + address: tuic_url.hostname, + port: tuic_url.port, + uuid: tuic_url.username, + password: tuic_url.password ? urldecode(tuic_url.password) : null, + tuic_congestion_control: tuic_params.congestion_control, + tuic_udp_relay_mode: tuic_params.udp_relay_mode, + tls: '1', + tls_sni: tuic_params.sni, + tls_alpn: tuic_params.alpn ? split(urldecode(tuic_params.alpn), ',') : null, + }; + break; case 'vless': /* https://github.com/XTLS/Xray-core/discussions/716 */