Skip to content

Commit 0111766

Browse files
committed
feat: Surge HTTP/2 CONNECT 和 TrustTunnel 支持 max-streams
1 parent 27e7fa3 commit 0111766

5 files changed

Lines changed: 168 additions & 3 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.23.13",
3+
"version": "2.23.14",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"packageManager": "pnpm@11.0.9",

backend/src/core/proxy-utils/parsers/peggy/surge.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ https = tag equals "https" address (username password)? (usernamek passwordk)? (
348348
proxy.tls = true;
349349
handleShadowTLS();
350350
}
351-
h2_connect = tag equals "h2-connect" address (username password)? (usernamek passwordk)? (headers/sni/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
351+
h2_connect = tag equals "h2-connect" address (username password)? (usernamek passwordk)? (headers/max_streams/sni/tls_fingerprint/tls_verification/client_cert/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
352352
proxy.type = "h2-connect";
353353
proxy.tls = true;
354354
handleShadowTLS();
@@ -401,7 +401,7 @@ anytls = tag equals "anytls" address (passwordk/reuse/ip_version/underlying_prox
401401
proxy.type = "anytls";
402402
proxy.tls = true;
403403
}
404-
trust_tunnel = tag equals "trust-tunnel" address (usernamek/passwordk/headers/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/fast_open/tfo/block_quic/others)* {
404+
trust_tunnel = tag equals "trust-tunnel" address (usernamek/passwordk/headers/max_streams/reuse/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/tls_fingerprint/tls_verification/client_cert/sni/fast_open/tfo/block_quic/others)* {
405405
proxy.type = "trusttunnel";
406406
proxy.tls = true;
407407
}
@@ -533,6 +533,8 @@ download_bandwidth = comma "download-bandwidth" equals match:[^,]+ { proxy.down
533533
test_url = comma "test-url" equals match:[^,]+ { proxy["test-url"] = match.join(""); }
534534
test_udp = comma "test-udp" equals match:[^,]+ { proxy["test-udp"] = match.join(""); }
535535
test_timeout = comma "test-timeout" equals match:$[0-9]+ { proxy["test-timeout"] = parseInt(match.trim()); }
536+
max_streams = comma "max-streams" equals match:quoted_integer { proxy["max-streams"] = match; }
537+
quoted_integer = '"' match:$[0-9]+ '"' { return parseInt(match.trim()); } / "'" match:$[0-9]+ "'" { return parseInt(match.trim()); } / match:$[0-9]+ { return parseInt(match.trim()); }
536538
tos = comma "tos" equals match:$[0-9]+ { proxy.tos = parseInt(match.trim()); }
537539
interface = comma "interface" equals match:[^,]+ { proxy.interface = match.join(""); }
538540
allow_other_interface = comma "allow-other-interface" equals flag:bool { proxy["allow-other-interface"] = flag; }

backend/src/core/proxy-utils/producers/surge.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ function appendSshPrivateKey(result, proxy) {
6868
}
6969
}
7070

71+
function warnMaxStreamsIfNeeded(proxy) {
72+
if (!isPresent(proxy, 'max-streams')) return;
73+
74+
const maxStreams = Number(stripSurgeQuotes(proxy['max-streams']));
75+
if (!Number.isInteger(maxStreams) || maxStreams <= 3) return;
76+
77+
$.warn(
78+
`Surge ${proxy.type} proxy ${proxy.name}: max-streams=${maxStreams} is greater than 3. Too many streams sharing one TCP connection may hurt performance.`,
79+
);
80+
}
81+
7182
export default function Surge_Producer() {
7283
const produce = (proxy, type, opts = {}) => {
7384
if (
@@ -434,6 +445,11 @@ function trusttunnel(proxy) {
434445
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
435446
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
436447
appendHeaders(result, proxy);
448+
warnMaxStreamsIfNeeded(proxy);
449+
result.appendIfPresent(
450+
`,max-streams=${proxy['max-streams']}`,
451+
'max-streams',
452+
);
437453

438454
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
439455
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');
@@ -502,6 +518,11 @@ function h2Connect(proxy) {
502518
result.appendIfPresent(`,username="${proxy.username}"`, 'username');
503519
result.appendIfPresent(`,password="${proxy.password}"`, 'password');
504520
appendHeaders(result, proxy);
521+
warnMaxStreamsIfNeeded(proxy);
522+
result.appendIfPresent(
523+
`,max-streams=${proxy['max-streams']}`,
524+
'max-streams',
525+
);
505526

506527
const ip_version = ipVersions[proxy['ip-version']] || proxy['ip-version'];
507528
result.appendIfPresent(`,ip-version=${ip_version}`, 'ip-version');

backend/src/test/proxy-parsers/v2ray-and-platforms.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,38 @@ describe('Platform raw-format parser coverage', function () {
28772877
reuse: true,
28782878
},
28792879
},
2880+
{
2881+
title: 'parses trust-tunnel lines with double-quoted max-streams',
2882+
input: 'Surge TrustTunnel Max Streams = trust-tunnel,surge-trust.example.com,443,username=user,password=secret,headers=X-Client:Surge,max-streams="3",sni=sni.example.com,skip-cert-verify=true,reuse=true',
2883+
expected: {
2884+
type: 'trusttunnel',
2885+
name: 'Surge TrustTunnel Max Streams',
2886+
server: 'surge-trust.example.com',
2887+
port: 443,
2888+
username: 'user',
2889+
password: 'secret',
2890+
tls: true,
2891+
headers: {
2892+
'X-Client': 'Surge',
2893+
},
2894+
'max-streams': 3,
2895+
sni: 'sni.example.com',
2896+
'skip-cert-verify': true,
2897+
reuse: true,
2898+
},
2899+
},
2900+
{
2901+
title: 'parses trust-tunnel lines with single-quoted max-streams',
2902+
input: "Surge TrustTunnel Single Max Streams = trust-tunnel,surge-trust-single.example.com,443,max-streams='2'",
2903+
expected: {
2904+
type: 'trusttunnel',
2905+
name: 'Surge TrustTunnel Single Max Streams',
2906+
server: 'surge-trust-single.example.com',
2907+
port: 443,
2908+
tls: true,
2909+
'max-streams': 2,
2910+
},
2911+
},
28802912
{
28812913
title: 'parses h2-connect lines with dynamic headers',
28822914
input: 'Surge H2 = h2-connect,h2.example.com,443,headers=X-Padding:<random-string(16-32)>,sni=sni.example.com,skip-cert-verify=true',
@@ -2893,6 +2925,23 @@ describe('Platform raw-format parser coverage', function () {
28932925
'skip-cert-verify': true,
28942926
},
28952927
},
2928+
{
2929+
title: 'parses h2-connect lines with max-streams',
2930+
input: 'Surge H2 Max Streams = h2-connect,h2.example.com,443,headers=X-Padding:<random-string(16-32)>,max-streams=1,sni=sni.example.com,skip-cert-verify=true',
2931+
expected: {
2932+
type: 'h2-connect',
2933+
name: 'Surge H2 Max Streams',
2934+
server: 'h2.example.com',
2935+
port: 443,
2936+
tls: true,
2937+
headers: {
2938+
'X-Padding': '<random-string(16-32)>',
2939+
},
2940+
'max-streams': 1,
2941+
sni: 'sni.example.com',
2942+
'skip-cert-verify': true,
2943+
},
2944+
},
28962945
{
28972946
title: 'keeps quoted Surge headers with semicolons inside User-Agent values',
28982947
input: '1=http,163.177.17.6,443,headers="Host:153.3.236.22:443;X-T5-Auth:683556433;Connection:Keep-Alive;User-Agent:okhttp/3.11.0 Dalvik/2.1.0 (Linux; U; Android 11; Redmi K30 5G Build/RKQ1.200826.002) baiduboxapp/11.0.5.12 (Baidu; P1 11)"',

backend/src/test/proxy-producers/text.spec.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,99 @@ describe('Proxy text producers', function () {
545545
]);
546546
});
547547

548+
it('produces Surge max-streams for HTTP/2 CONNECT and TrustTunnel', function () {
549+
const output = ProxyUtils.produce(
550+
[
551+
{
552+
type: 'h2-connect',
553+
name: 'Surge H2 Max Streams',
554+
server: 'h2.example.com',
555+
port: 443,
556+
tls: true,
557+
sni: 'sni.example.com',
558+
headers: {
559+
'X-Padding': '<random-string(16-32)>',
560+
},
561+
'max-streams': 1,
562+
},
563+
{
564+
type: 'trusttunnel',
565+
name: 'Surge Trust Max Streams',
566+
server: 'trust.example.com',
567+
port: 443,
568+
username: 'user',
569+
password: 'pass',
570+
headers: {
571+
'X-Client': 'Surge',
572+
},
573+
'max-streams': 3,
574+
sni: 'sni.example.com',
575+
},
576+
],
577+
'Surge',
578+
'external',
579+
);
580+
581+
expect(output.split('\n')).to.deep.equal([
582+
'Surge H2 Max Streams=h2-connect,h2.example.com,443,headers="X-Padding:"<random-string(16-32)>"",max-streams=1,sni="sni.example.com"',
583+
'Surge Trust Max Streams=trust-tunnel,trust.example.com,443,username="user",password="pass",headers="X-Client:"Surge"",max-streams=3,sni="sni.example.com"',
584+
]);
585+
});
586+
587+
it('warns when Surge max-streams is greater than 3', function () {
588+
const { result: output, warnings } = captureWarns(() =>
589+
ProxyUtils.produce(
590+
[
591+
{
592+
type: 'h2-connect',
593+
name: 'Surge H2 High Max Streams',
594+
server: 'h2.example.com',
595+
port: 443,
596+
tls: true,
597+
'max-streams': 4,
598+
},
599+
{
600+
type: 'trusttunnel',
601+
name: 'Surge Trust High Max Streams',
602+
server: 'trust.example.com',
603+
port: 443,
604+
tls: true,
605+
'max-streams': 5,
606+
},
607+
],
608+
'Surge',
609+
'external',
610+
),
611+
);
612+
613+
expect(output.split('\n')).to.deep.equal([
614+
'Surge H2 High Max Streams=h2-connect,h2.example.com,443,max-streams=4',
615+
'Surge Trust High Max Streams=trust-tunnel,trust.example.com,443,max-streams=5',
616+
]);
617+
expect(warnings).to.have.length(2);
618+
expect(warnings[0]).to.include('max-streams=4');
619+
expect(warnings[0]).to.include('greater than 3');
620+
expect(warnings[0]).to.include('performance');
621+
expect(warnings[1]).to.include('max-streams=5');
622+
expect(warnings[1]).to.include('greater than 3');
623+
expect(warnings[1]).to.include('performance');
624+
});
625+
626+
it('round-trips Surge max-streams for HTTP/2 CONNECT and TrustTunnel', function () {
627+
const proxies = ProxyUtils.parse(
628+
[
629+
'Surge H2 Round Trip = h2-connect,h2.example.com,443,headers=X-Padding:<random-string(16-32)>,max-streams=1,sni=sni.example.com',
630+
'Surge Trust Round Trip = trust-tunnel,trust.example.com,443,username=user,password=pass,headers=X-Client:Surge,max-streams="3",sni=sni.example.com',
631+
].join('\n'),
632+
);
633+
const output = ProxyUtils.produce(proxies, 'Surge', 'external');
634+
635+
expect(output.split('\n')).to.deep.equal([
636+
'Surge H2 Round Trip=h2-connect,h2.example.com,443,headers="X-Padding:"<random-string(16-32)>"",max-streams=1,sni="sni.example.com"',
637+
'Surge Trust Round Trip=trust-tunnel,trust.example.com,443,username="user",password="pass",headers="X-Client:"Surge"",max-streams=3,sni="sni.example.com"',
638+
]);
639+
});
640+
548641
it('round-trips Surge root headers with nested quotes and separators', function () {
549642
const output = ProxyUtils.produce(
550643
[

0 commit comments

Comments
 (0)