From 47a95e5a3dac0d6d2ffbea6214fa07b953ebef03 Mon Sep 17 00:00:00 2001 From: xream Date: Sun, 13 Aug 2023 15:54:04 +0800 Subject: [PATCH] feat: Added support for tuic and some compatibility adjustments --- backend/package.json | 2 +- backend/src/core/proxy-utils/parsers/index.js | 11 +++++ .../core/proxy-utils/parsers/peggy/surge.js | 14 +++++- .../core/proxy-utils/parsers/peggy/surge.peg | 14 +++++- .../src/core/proxy-utils/producers/clash.js | 26 ++++++++-- .../src/core/proxy-utils/producers/stash.js | 17 +++++++ .../src/core/proxy-utils/producers/surge.js | 49 +++++++++++++++++++ 7 files changed, 125 insertions(+), 8 deletions(-) diff --git a/backend/package.json b/backend/package.json index 36534fd..b9a7361 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.4", + "version": "2.14.5", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/parsers/index.js b/backend/src/core/proxy-utils/parsers/index.js index 0a0fac2..13afd0b 100644 --- a/backend/src/core/proxy-utils/parsers/index.js +++ b/backend/src/core/proxy-utils/parsers/index.js @@ -270,6 +270,7 @@ function Clash_All() { 'http', 'snell', 'trojan', + 'tuic', ].includes(proxy.type) ) { throw new Error( @@ -474,6 +475,15 @@ function Surge_Snell() { return { name, test, parse }; } +function Surge_Tuic() { + const name = 'Surge Tuic Parser'; + const test = (line) => { + return /^.*=\s*tuic(-v5)??/.test(line.split(',')[0]); + }; + const parse = (line) => getSurgeParser().parse(line); + return { name, test, parse }; +} + export default [ URI_SS(), URI_SSR(), @@ -485,6 +495,7 @@ export default [ Surge_Trojan(), Surge_Http(), Surge_Snell(), + Surge_Tuic(), Surge_Socks5(), Loon_SS(), Loon_SSR(), diff --git a/backend/src/core/proxy-utils/parsers/peggy/surge.js b/backend/src/core/proxy-utils/parsers/peggy/surge.js index f1e4727..3adf0d3 100644 --- a/backend/src/core/proxy-utils/parsers/peggy/surge.js +++ b/backend/src/core/proxy-utils/parsers/peggy/surge.js @@ -29,7 +29,7 @@ const grammars = String.raw` } } -start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) { +start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) { return proxy; } @@ -73,6 +73,13 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_ $set(proxy, "obfs-opts.path", obfs.path); } } +tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* { + proxy.type = "tuic"; +} +tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* { + proxy.type = "tuic"; + proxy.version = 5; +} socks5 = tag equals "socks5" address (username password)? (fast_open/others)* { proxy.type = "socks5"; } @@ -175,6 +182,11 @@ uri = $[^,]+ udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } +tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; } +ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); } +token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); } +alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); } +uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); } tag = match:[^=,]* { proxy.name = match.join("").trim(); } comma = _ "," _ diff --git a/backend/src/core/proxy-utils/parsers/peggy/surge.peg b/backend/src/core/proxy-utils/parsers/peggy/surge.peg index a478f71..d22cd66 100644 --- a/backend/src/core/proxy-utils/parsers/peggy/surge.peg +++ b/backend/src/core/proxy-utils/parsers/peggy/surge.peg @@ -27,7 +27,7 @@ } } -start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls) { +start = (shadowsocks/vmess/trojan/https/http/snell/socks5/socks5_tls/tuic/tuic_v5) { return proxy; } @@ -71,6 +71,13 @@ snell = tag equals "snell" address (snell_version/snell_psk/obfs/obfs_host/obfs_ $set(proxy, "obfs-opts.path", obfs.path); } } +tuic = tag equals "tuic" address (alpn/token/ip_version/tls_verification/sni/fast_open/tfo/others)* { + proxy.type = "tuic"; +} +tuic_v5 = tag equals "tuic-v5" address (alpn/passwordk/uuidk/ip_version/tls_verification/sni/fast_open/tfo/others)* { + proxy.type = "tuic"; + proxy.version = 5; +} socks5 = tag equals "socks5" address (username password)? (fast_open/others)* { proxy.type = "socks5"; } @@ -173,6 +180,11 @@ uri = $[^,]+ udp_relay = comma "udp" equals flag:bool { proxy.udp = flag; } fast_open = comma "fast-open" equals flag:bool { proxy.tfo = flag; } +tfo = comma "tfo" equals flag:bool { proxy.tfo = flag; } +ip_version = comma "ip-version" equals match:[^,]+ { proxy["ip-version"] = match.join(""); } +token = comma "token" equals match:[^,]+ { proxy.token = match.join(""); } +alpn = comma "alpn" equals match:[^,]+ { proxy.alpn = match.join(""); } +uuidk = comma "uuid" equals match:[^,]+ { proxy.uuid = match.join(""); } tag = match:[^=,]* { proxy.name = match.join("").trim(); } comma = _ "," _ diff --git a/backend/src/core/proxy-utils/producers/clash.js b/backend/src/core/proxy-utils/producers/clash.js index 886d8c0..cb319bd 100644 --- a/backend/src/core/proxy-utils/producers/clash.js +++ b/backend/src/core/proxy-utils/producers/clash.js @@ -4,11 +4,27 @@ export default function Clash_Producer() { const type = 'ALL'; const produce = (proxies) => { // filter unsupported proxies - proxies = proxies.filter((proxy) => - ['ss', 'ssr', 'vmess', 'socks', 'http', 'snell', 'trojan'].includes( - proxy.type, - ), - ); + proxies = proxies.filter((proxy) => { + if ( + ![ + 'ss', + 'ssr', + 'vmess', + 'socks', + 'http', + 'snell', + 'trojan', + ].includes(proxy.type) + ) { + return false; + } else if ( + proxy.type === 'snell' && + String(proxy.version) === '4' + ) { + return false; + } + return true; + }); return ( 'proxies:\n' + proxies diff --git a/backend/src/core/proxy-utils/producers/stash.js b/backend/src/core/proxy-utils/producers/stash.js index 234c837..1c03db7 100644 --- a/backend/src/core/proxy-utils/producers/stash.js +++ b/backend/src/core/proxy-utils/producers/stash.js @@ -6,6 +6,15 @@ export default function Stash_Producer() { return ( 'proxies:\n' + proxies + .filter((proxy) => { + if ( + proxy.type === 'snell' && + String(proxy.version) === '4' + ) { + return false; + } + return true; + }) .map((proxy) => { if (proxy.type === 'vmess') { // handle vmess aead @@ -19,6 +28,14 @@ export default function Stash_Producer() { proxy.servername = proxy.sni; delete proxy.sni; } + } else if (proxy.type === 'tuic') { + if (isPresent(proxy, 'alpn')) { + proxy.alpn = Array.isArray(proxy.alpn) + ? proxy.alpn + : [proxy.alpn]; + } else { + proxy.alpn = ['h3']; + } } delete proxy['tls-fingerprint']; diff --git a/backend/src/core/proxy-utils/producers/surge.js b/backend/src/core/proxy-utils/producers/surge.js index 7267636..f6de9f7 100644 --- a/backend/src/core/proxy-utils/producers/surge.js +++ b/backend/src/core/proxy-utils/producers/surge.js @@ -4,6 +4,14 @@ import $ from '@/core/app'; const targetPlatform = 'Surge'; +const ipVersions = { + dual: 'dual', + ipv4: 'v4-only', + ipv6: 'v6-only', + 'ipv4-prefer': 'prefer-v4', + 'ipv6-prefer': 'prefer-v6', +}; + export default function Surge_Producer() { const produce = (proxy) => { switch (proxy.type) { @@ -19,6 +27,8 @@ export default function Surge_Producer() { return socks5(proxy); case 'snell': return snell(proxy); + case 'tuic': + return tuic(proxy); } throw new Error( `Platform ${targetPlatform} does not support proxy type: ${proxy.type}`, @@ -239,6 +249,45 @@ function snell(proxy) { return result.toString(); } +function tuic(proxy) { + const result = new Result(proxy); + let type = proxy.type; + if (proxy.password && proxy.uuid) { + type = 'tuic-v5'; + } + result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`); + + result.appendIfPresent(`,uuid=${proxy.uuid}`, 'uuid'); + result.appendIfPresent(`,password=${proxy.password}`, 'password'); + result.appendIfPresent(`,token=${proxy.token}`, 'token'); + + result.appendIfPresent( + `,alpn=${Array.isArray(proxy.alpn) ? proxy.alpn[0] : proxy.alpn}`, + 'alpn', + ); + + result.appendIfPresent( + `,ip-version=${ipVersions[proxy['ip-version']] || proxy['ip-version']}`, + 'ip-version', + ); + + // tls verification + result.appendIfPresent(`,sni=${proxy.sni}`, 'sni'); + result.appendIfPresent( + `,skip-cert-verify=${proxy['skip-cert-verify']}`, + 'skip-cert-verify', + ); + + // tfo + result.appendIfPresent(`,tfo=${proxy.tfo}`, 'fast-open'); + result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo'); + + // test-url + result.appendIfPresent(`,test-url=${proxy['test-url']}`, 'test-url'); + + return result.toString(); +} + function handleTransport(result, proxy) { if (isPresent(proxy, 'network')) { if (proxy.network === 'ws') {