feat: Surge Hysteria2 与 TUIC 协议支持端口跳跃; Hysteria2 URI 的端口部分支持 端口跳跃 的「多端口地址格式」

This commit is contained in:
xream 2024-09-02 16:29:05 +08:00
parent 562d349629
commit 59fe16a7b0
No known key found for this signature in database
GPG Key ID: 1D2C5225471789F9
7 changed files with 100 additions and 21 deletions

View File

@ -98,7 +98,7 @@ or
esbuild(experimental) esbuild(experimental)
``` ```
pnpm run --parallel "/^dev:.*/" SUB_STORE_BACKEND_API_PORT=3000 pnpm run --parallel "/^dev:.*/"
``` ```
## LICENSE ## 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) [![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 ## 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! - Special thanks to @KOP-XIAO for his awesome resource-parser. Please give a [star](https://github.com/KOP-XIAO/QuantumultX) for his great work!

View File

@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.14.368", "version": "2.14.370",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {

View File

@ -424,9 +424,13 @@ function lastParse(proxy) {
proxy[`${proxy.network}-opts`].path = [transportPath]; proxy[`${proxy.network}-opts`].path = [transportPath];
} }
} }
if (['hysteria', 'hysteria2'].includes(proxy.type) && !proxy.ports) { if (['hysteria', 'hysteria2'].includes(proxy.type)) {
if (proxy.ports) {
proxy.ports = proxy.ports.replace(/\//g, ',');
} else {
delete proxy.ports; delete proxy.ports;
} }
}
if ( if (
['hysteria2'].includes(proxy.type) && ['hysteria2'].includes(proxy.type) &&
proxy.obfs && proxy.obfs &&

View File

@ -5,6 +5,7 @@ import {
isPresent, isPresent,
isNotBlank, isNotBlank,
getIfPresent, getIfPresent,
getRandomPort,
} from '@/utils'; } from '@/utils';
import getSurgeParser from './peggy/surge'; import getSurgeParser from './peggy/surge';
import getLoonParser from './peggy/loon'; import getLoonParser from './peggy/loon';
@ -13,6 +14,19 @@ import getTrojanURIParser from './peggy/trojan-uri';
import { Base64 } from 'js-base64'; 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). // Parse SS URI format (only supports new SIP002, legacy format is depreciated).
// reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme // reference: https://github.com/shadowsocks/shadowsocks-org/wiki/SIP002-URI-Scheme
function URI_SS() { function URI_SS() {
@ -545,13 +559,42 @@ function URI_Hysteria2() {
}; };
const parse = (line) => { const parse = (line) => {
line = line.split(/(hysteria2|hy2):\/\//)[2]; line = line.split(/(hysteria2|hy2):\/\//)[2];
// eslint-disable-next-line no-unused-vars // 端口跳跃有两种写法:
let [__, password, server, ___, port, ____, addons = '', name] = // 1. 服务器的地址和可选端口。如果省略端口,则默认为 443。
/^(.*?)@(.*?)(:(\d+))?\/?(\?(.*?))?(?:#(.*?))?$/.exec(line); // 端口部分支持 端口跳跃 的「多端口地址格式」。
// 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); port = parseInt(`${port}`, 10);
if (isNaN(port)) { if (isNaN(port)) {
port = 443; port = 443;
} }
} else if (port) {
ports = port;
port = getRandomPort(ports);
} else {
port = 443;
}
password = decodeURIComponent(password); password = decodeURIComponent(password);
if (name != null) { if (name != null) {
name = decodeURIComponent(name); name = decodeURIComponent(name);
@ -563,6 +606,7 @@ function URI_Hysteria2() {
name, name,
server, server,
port, port,
ports,
password, password,
}; };
@ -1295,7 +1339,12 @@ function Surge_Tuic() {
const test = (line) => { const test = (line) => {
return /^.*=\s*tuic(-v5)?/.test(line.split(',')[0]); 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 }; return { name, test, parse };
} }
function Surge_WireGuard() { function Surge_WireGuard() {
@ -1312,7 +1361,12 @@ function Surge_Hysteria2() {
const test = (line) => { const test = (line) => {
return /^.*=\s*hysteria2/.test(line.split(',')[0]); 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 }; return { name, test, parse };
} }

View File

@ -91,11 +91,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
} }
handleShadowTLS(); 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"; proxy.type = "tuic";
handleShadowTLS(); 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.type = "tuic";
proxy.version = 5; proxy.version = 5;
handleShadowTLS(); handleShadowTLS();
@ -104,7 +104,7 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
proxy.type = "wireguard-surge"; proxy.type = "wireguard-surge";
handleShadowTLS(); 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"; proxy.type = "hysteria2";
handleShadowTLS(); 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 = & { username = & {
let j = peg$currPos; let j = peg$currPos;
let start, end; let start, end;

View File

@ -89,11 +89,11 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_
} }
handleShadowTLS(); 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"; proxy.type = "tuic";
handleShadowTLS(); 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.type = "tuic";
proxy.version = 5; proxy.version = 5;
handleShadowTLS(); handleShadowTLS();
@ -102,7 +102,7 @@ wireguard = tag equals "wireguard" (section_name/no_error_alert/ip_version/under
proxy.type = "wireguard-surge"; proxy.type = "wireguard-surge";
handleShadowTLS(); 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"; proxy.type = "hysteria2";
handleShadowTLS(); 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 = & { username = & {
let j = peg$currPos; let j = peg$currPos;
let start, end; let start, end;

View File

@ -675,6 +675,15 @@ function tuic(proxy) {
'alpn', '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']; const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version'); result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
@ -935,6 +944,15 @@ function hysteria2(proxy) {
result.appendIfPresent(`,password=${proxy.password}`, 'password'); 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']; const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version'); result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');