From 59fe16a7b076526983e97773a89735adf0be0b88 Mon Sep 17 00:00:00 2001 From: xream Date: Mon, 2 Sep 2024 16:29:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Surge=20Hysteria2=20=E4=B8=8E=20TUIC=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=94=AF=E6=8C=81=E7=AB=AF=E5=8F=A3=E8=B7=B3?= =?UTF-8?q?=E8=B7=83;=20Hysteria2=20URI=20=E7=9A=84=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=94=AF=E6=8C=81=20=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=E8=B7=B3=E8=B7=83=20=E7=9A=84=E3=80=8C=E5=A4=9A=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E5=9C=B0=E5=9D=80=E6=A0=BC=E5=BC=8F=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- backend/package.json | 2 +- backend/src/core/proxy-utils/index.js | 8 ++- backend/src/core/proxy-utils/parsers/index.js | 68 +++++++++++++++++-- .../core/proxy-utils/parsers/peggy/surge.js | 8 ++- .../core/proxy-utils/parsers/peggy/surge.peg | 8 ++- .../src/core/proxy-utils/producers/surge.js | 18 +++++ 7 files changed, 100 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8373c86..d7263fd 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.

-[![Build](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/sub-store-org/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/sub-store-org/Sub-Store) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/Peng-Ym/Sub-Store) ![Lines of code](https://img.shields.io/tokei/lines/github/sub-store-org/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/sub-store-org/Sub-Store) +[![Build](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/sub-store-org/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/sub-store-org/Sub-Store) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/Peng-Ym/Sub-Store) ![Lines of code](https://img.shields.io/tokei/lines/github/sub-store-org/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/sub-store-org/Sub-Store) sub-store-org%2FSub-Store | Trendshift [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/PengYM) - + Core functionalities: 1. Conversion among various formats. @@ -21,7 +21,7 @@ Core functionalities: 3. Collect multiple subscriptions in one URL. > The following descriptions of features may not be updated in real-time. Please refer to the actual available features for accurate information. - + ## 1. Subscription Conversion ### Supported Input Formats @@ -98,7 +98,7 @@ or esbuild(experimental) ``` -pnpm run --parallel "/^dev:.*/" +SUB_STORE_BACKEND_API_PORT=3000 pnpm run --parallel "/^dev:.*/" ``` ## LICENSE @@ -111,7 +111,6 @@ This project is under the GPL V3 LICENSE. [![Star History Chart](https://api.star-history.com/svg?repos=sub-store-org/sub-store&type=Date)](https://star-history.com/#sub-store-org/sub-store&Date) - ## Acknowledgements - Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work! diff --git a/backend/package.json b/backend/package.json index 3fc0179..bf38065 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.368", + "version": "2.14.370", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/index.js b/backend/src/core/proxy-utils/index.js index 5bc7bfe..eb6d212 100644 --- a/backend/src/core/proxy-utils/index.js +++ b/backend/src/core/proxy-utils/index.js @@ -424,8 +424,12 @@ function lastParse(proxy) { proxy[`${proxy.network}-opts`].path = [transportPath]; } } - if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) { - delete proxy.ports; + if (['hysteria', 'hysteria2'].includes(proxy.type)) { + if (proxy.ports) { + proxy.ports = proxy.ports.replace(/\//g, ','); + } else { + delete proxy.ports; + } } if ( ['hysteria2'].includes(proxy.type) && diff --git a/backend/src/core/proxy-utils/parsers/index.js b/backend/src/core/proxy-utils/parsers/index.js index 3d88471..45b2390 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -5,6 +5,7 @@ import { isPresent, isNotBlank, getIfPresent, + getRandomPort, } from '@/utils'; import getSurgeParser from './peggy/surge'; import getLoonParser from './peggy/loon'; @@ -13,6 +14,19 @@ import getTrojanURIParser from './peggy/trojan-uri'; import { Base64 } from 'js-base64'; +function surge_port_hopping(raw) { + const [parts, port_hopping] = + raw.match( + /,\s*?port-hopping\s*?=\s*?["']?\s*?((\d+(-\d+)?)([,;]\d+(-\d+)?)*)\s*?["']?\s*?/, + ) || []; + return { + port_hopping: port_hopping + ? port_hopping.replace(/;/g, ',') + : undefined, + line: parts ? raw.replace(parts, '') : raw, + }; +} + // Parse SS URI format (only supports new SIP002, legacy format is depreciated). // reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme function URI_SS() { @@ -545,13 +559,42 @@ function URI_Hysteria2() { }; const parse = (line) => { line = line.split(/(hysteria2|hy2):\/\//)[2]; - // eslint-disable-next-line no-unused-vars - let [__, password, server, ___, port, ____, addons = '', name] = - /^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line); - port = parseInt(`${port}`, 10); - if (isNaN(port)) { + // 端口跳跃有两种写法: + // 1. 服务器的地址和可选端口。如果省略端口,则默认为 443。 + // 端口部分支持 端口跳跃 的「多端口地址格式」。 + // https://hysteria.network/zh/docs/advanced/Port-Hopping + // 2. 参数 mport + let ports; + /* eslint-disable no-unused-vars */ + let [ + __, + password, + server, + ___, + port, + ____, + _____, + ______, + _______, + ________, + addons = '', + name, + ] = /^(.*?)@(.*?)(:((\d+(-\d+)?)([,;]\d+(-\d+)?)*))?\/?(\?(.*?))?(?:#(.*?))?$/.exec( + line, + ); + /* eslint-enable no-unused-vars */ + if (/^\d+$/.test(port)) { + port = parseInt(`${port}`, 10); + if (isNaN(port)) { + port = 443; + } + } else if (port) { + ports = port; + port = getRandomPort(ports); + } else { port = 443; } + password = decodeURIComponent(password); if (name != null) { name = decodeURIComponent(name); @@ -563,6 +606,7 @@ function URI_Hysteria2() { name, server, port, + ports, password, }; @@ -1295,7 +1339,12 @@ function Surge_Tuic() { const test = (line) => { return /^.*=\s*tuic(-v5)?/.test(line.split(',')[0]); }; - const parse = (line) => getSurgeParser().parse(line); + const parse = (raw) => { + const { port_hopping, line } = surge_port_hopping(raw); + const proxy = getSurgeParser().parse(line); + proxy['ports'] = port_hopping; + return proxy; + }; return { name, test, parse }; } function Surge_WireGuard() { @@ -1312,7 +1361,12 @@ function Surge_Hysteria2() { const test = (line) => { return /^.*=\s*hysteria2/.test(line.split(',')[0]); }; - const parse = (line) => getSurgeParser().parse(line); + const parse = (raw) => { + const { port_hopping, line } = surge_port_hopping(raw); + const proxy = getSurgeParser().parse(line); + proxy['ports'] = port_hopping; + return proxy; + }; return { name, test, parse }; } diff --git a/backend/src/core/proxy-utils/parsers/peggy/surge.js b/backend/src/core/proxy-utils/parsers/peggy/surge.js index 0cf03af..49c8c5f 100644 --- a/backend/src/core/proxy-utils/parsers/peggy/surge.js +++ b/backend/src/core/proxy-utils/parsers/peggy/surge.js @@ -91,11 +91,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_ } handleShadowTLS(); } -tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "tuic"; handleShadowTLS(); } -tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "tuic"; proxy.version = 5; handleShadowTLS(); @@ -104,7 +104,7 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under proxy.type = "wireguard-surge"; handleShadowTLS(); } -hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "hysteria2"; handleShadowTLS(); } @@ -151,6 +151,8 @@ port = digits:[0-9]+ { } } +port_hopping_interval = comma "port-hopping-interval" equals match:$[0-9]+ { proxy["hop-interval"] = parseInt(match.trim()); } + username = & { let j = peg$currPos; let start, end; diff --git a/backend/src/core/proxy-utils/parsers/peggy/surge.peg b/backend/src/core/proxy-utils/parsers/peggy/surge.peg index 2ebf743..40da6bc 100644 --- a/backend/src/core/proxy-utils/parsers/peggy/surge.peg +++ b/backend/src/core/proxy-utils/parsers/peggy/surge.peg @@ -89,11 +89,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_ } handleShadowTLS(); } -tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +tuic = tag equals "tuic" address (alpn/token/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "tuic"; handleShadowTLS(); } -tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/sni/fast_open/tfo/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "tuic"; proxy.version = 5; handleShadowTLS(); @@ -102,7 +102,7 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under proxy.type = "wireguard-surge"; handleShadowTLS(); } -hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* { +hysteria2 = tag equals "hysteria2" address (no_error_alert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/sni/tls_verification/passwordk/tls_fingerprint/download_bandwidth/ecn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/port_hopping_interval/others)* { proxy.type = "hysteria2"; handleShadowTLS(); } @@ -149,6 +149,8 @@ port = digits:[0-9]+ { } } +port_hopping_interval = comma "port-hopping-interval" equals match:$[0-9]+ { proxy["hop-interval"] = parseInt(match.trim()); } + username = & { let j = peg$currPos; let start, end; diff --git a/backend/src/core/proxy-utils/producers/surge.js b/backend/src/core/proxy-utils/producers/surge.js index af3401b..41ac3b7 100644 --- a/backend/src/core/proxy-utils/producers/surge.js +++ b/backend/src/core/proxy-utils/producers/surge.js @@ -675,6 +675,15 @@ function tuic(proxy) { 'alpn', ); + if (isPresent(proxy, 'ports')) { + result.append(`,port-hopping=${proxy.ports.replace(/,/g, ';')}`); + } + + result.appendIfPresent( + `,port-hopping-interval=${proxy['hop-interval']}`, + 'hop-interval', + ); + const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version']; result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version'); @@ -935,6 +944,15 @@ function hysteria2(proxy) { result.appendIfPresent(`,password=${proxy.password}`, 'password'); + if (isPresent(proxy, 'ports')) { + result.append(`,port-hopping=${proxy.ports.replace(/,/g, ';')}`); + } + + result.appendIfPresent( + `,port-hopping-interval=${proxy['hop-interval']}`, + 'hop-interval', + ); + const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version']; result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');