Skip to content

Commit 387d90c

Browse files
committed
feat: mihomo 输出 snell 支持 shadow-tls(shadowrocket 我也先加了); Loon/Surge/sing-box shadow-tls 支持 apln; Loon shadow-tls 支持 tls-profile
1 parent 45f6606 commit 387d90c

12 files changed

Lines changed: 517 additions & 19 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.32.1",
3+
"version": "2.33.0",
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/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,26 @@ function lastParse(proxy) {
768768
.replace(/^\[/, '')
769769
.replace(/\]$/, '');
770770
}
771+
if (
772+
proxy.type === 'snell' &&
773+
proxy['obfs-opts']?.mode === 'shadow-tls' &&
774+
!proxy.plugin
775+
) {
776+
proxy.plugin = 'shadow-tls';
777+
proxy['plugin-opts'] = {
778+
host: proxy['obfs-opts'].host,
779+
password: proxy['obfs-opts'].password,
780+
version: proxy['obfs-opts'].version,
781+
alpn: proxy['obfs-opts'].alpn,
782+
};
783+
delete proxy['obfs-opts'];
784+
}
785+
if (proxy.plugin === 'shadow-tls' && proxy['plugin-opts']) {
786+
if (proxy.alpn && !proxy['plugin-opts'].alpn) {
787+
proxy['plugin-opts'].alpn = proxy.alpn;
788+
}
789+
delete proxy.alpn;
790+
}
771791
if (proxy.network === 'ws') {
772792
if (!proxy['ws-opts'] && (proxy['ws-path'] || proxy['ws-headers'])) {
773793
proxy['ws-opts'] = {};

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,19 @@ const grammars = String.raw`
3737
3838
function handleShadowTLS() {
3939
if (shadowTLS.password) {
40+
if (shadowTLS.version < 2) {
41+
throw new Error("shadow-tls version " + shadowTLS.version + " is not supported");
42+
}
4043
proxy.plugin = "shadow-tls";
4144
proxy["plugin-opts"] = {
4245
host: shadowTLS.host,
4346
password: shadowTLS.password,
4447
version: shadowTLS.version,
4548
};
49+
if (proxy.alpn) {
50+
$set(proxy, "plugin-opts.alpn", proxy.alpn);
51+
delete proxy.alpn;
52+
}
4653
}
4754
}
4855
@@ -68,13 +75,13 @@ start = (shadowsocksr/shadowsocks/vmess/vless/trojan/https/http/socks5/hysteria2
6875
return proxy;
6976
}
7077
71-
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
78+
shadowsocksr = tag equals "shadowsocksr"i address method password (ssr_protocol/ssr_protocol_param/obfs_ssr/obfs_ssr_param/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/tls_profile/alpn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/others)*{
7279
proxy.type = "ssr";
7380
// handle ssr obfs
7481
proxy.obfs = obfs.type;
7582
handleShadowTLS();
7683
}
77-
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/udp_over_tcp/others)* {
84+
shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs_hostv)? (obfs_ss/obfs_host/obfs_uri/fast_open/udp_relay/udp_port/tls_profile/alpn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/ip_mode/block_quic/udp_over_tcp/others)* {
7885
proxy.type = "ss";
7986
// handle ss obfs
8087
if (obfs.type == "http" || obfs.type === "tls") {

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,19 @@ const grammars = String.raw`
3636
shadowTLS.version = 2;
3737
}
3838
if (shadowTLS.password) {
39+
if (shadowTLS.version < 2) {
40+
throw new Error("shadow-tls version " + shadowTLS.version + " is not supported");
41+
}
3942
proxy.plugin = "shadow-tls";
4043
proxy["plugin-opts"] = {
4144
host: shadowTLS.host,
4245
password: shadowTLS.password,
4346
version: shadowTLS.version,
4447
};
48+
if (proxy.alpn) {
49+
$set(proxy, "plugin-opts.alpn", proxy.alpn);
50+
delete proxy.alpn;
51+
}
4552
}
4653
}
4754
function stripQuotes(value) {
@@ -366,7 +373,7 @@ start = (anytls/shadowsocks/vmess/trojan/h2_connect/https/http/snell/socks5/sock
366373
return proxy;
367374
}
368375
369-
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
376+
shadowsocks = tag equals "ss" address (method/passwordk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/alpn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/udp_port/others)* {
370377
proxy.type = "ss";
371378
// handle obfs
372379
if (obfs.type == "http" || obfs.type === "tls") {
@@ -412,7 +419,7 @@ ssh = tag equals "ssh" address (username password)? (usernamek passwordk)? (serv
412419
proxy.type = "ssh";
413420
handleShadowTLS();
414421
}
415-
snell = tag equals "snell" address (snell_version/snell_mode/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/reuse/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
422+
snell = tag equals "snell" address (snell_version/snell_mode/snell_psk/obfs/obfs_host/obfs_uri/ip_version/underlying_proxy/tos/allow_other_interface/interface/test_url/test_udp/test_timeout/hybrid/no_error_alert/fast_open/tfo/udp_relay/reuse/alpn/shadow_tls_version/shadow_tls_sni/shadow_tls_password/block_quic/others)* {
416423
proxy.type = "snell";
417424
// handle obfs
418425
if (obfs.type == "http" || obfs.type === "tls") {

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,18 @@ export default function ClashMeta_Producer() {
7676
return false;
7777
} else if (
7878
hasMihomoShadowTls(proxy) &&
79-
(proxy.type !== 'ss' ||
79+
(!['ss', 'snell'].includes(proxy.type) ||
8080
!isSupportedMihomoVersion(
8181
getMihomoShadowTlsVersion(proxy),
8282
[1, 2, 3],
8383
))
8484
) {
8585
return false;
86+
} else if (hasMihomoSnellShadowTlsObfsConflict(proxy)) {
87+
$.error(
88+
`Platform Mihomo does not support Snell shadow-tls with obfs for proxy ${proxy.name}. Proxy has been filtered.`,
89+
);
90+
return false;
8691
} else if (['juicity', 'naive'].includes(proxy.type)) {
8792
return false;
8893
} else if (
@@ -255,6 +260,23 @@ export default function ClashMeta_Producer() {
255260
);
256261
}
257262

263+
if (proxy.type === 'snell') {
264+
const shadowTLSOpts = getMihomoShadowTlsOpts(proxy);
265+
if (shadowTLSOpts) {
266+
proxy['obfs-opts'] = {
267+
mode: 'shadow-tls',
268+
host: shadowTLSOpts.host,
269+
password: shadowTLSOpts.password,
270+
version: shadowTLSOpts.version,
271+
};
272+
if (shadowTLSOpts.alpn) {
273+
proxy['obfs-opts'].alpn = shadowTLSOpts.alpn;
274+
}
275+
delete proxy.plugin;
276+
delete proxy['plugin-opts'];
277+
}
278+
}
279+
258280
if (
259281
['vmess', 'vless'].includes(proxy.type) &&
260282
proxy.network === 'http'
@@ -413,11 +435,29 @@ function isSupportedMihomoVersion(version, supportedVersions) {
413435
}
414436

415437
function hasMihomoShadowTls(proxy) {
416-
return proxy?.plugin === 'shadow-tls';
438+
return Boolean(getMihomoShadowTlsOpts(proxy));
439+
}
440+
441+
function hasMihomoSnellShadowTlsObfsConflict(proxy) {
442+
return (
443+
proxy?.type === 'snell' &&
444+
proxy?.plugin === 'shadow-tls' &&
445+
(isPresent(proxy, 'obfs-opts.mode') ||
446+
isPresent(proxy, 'obfs-opts.host') ||
447+
isPresent(proxy, 'obfs-opts.path'))
448+
);
417449
}
418450

419451
function getMihomoShadowTlsVersion(proxy) {
420-
return proxy?.plugin === 'shadow-tls'
421-
? proxy?.['plugin-opts']?.version
422-
: undefined;
452+
return getMihomoShadowTlsOpts(proxy)?.version;
453+
}
454+
455+
function getMihomoShadowTlsOpts(proxy) {
456+
if (proxy?.plugin === 'shadow-tls' && proxy?.['plugin-opts']) {
457+
return proxy['plugin-opts'];
458+
}
459+
if (proxy?.type === 'snell' && proxy?.['obfs-opts']?.mode === 'shadow-tls') {
460+
return proxy['obfs-opts'];
461+
}
462+
return undefined;
423463
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ function appendAlpn(result, proxy) {
6969
if (alpn) result.append(`,alpn="${alpn}"`);
7070
}
7171

72+
function getLoonShadowTLSAlpn(proxy) {
73+
const values = proxy?.['plugin-opts']?.alpn ?? proxy?.alpn;
74+
const normalized = Array.isArray(values) ? values : `${values || ''}`.split(',');
75+
return normalized
76+
.map((item) => `${item}`.trim())
77+
.filter((item) => item !== '')
78+
.join(',');
79+
}
80+
7281
function appendShadowTLS(result, proxy) {
7382
if (proxy.plugin !== 'shadow-tls' || !proxy['plugin-opts']) return;
7483

@@ -85,6 +94,9 @@ function appendShadowTLS(result, proxy) {
8594
}
8695
result.append(`,shadow-tls-version=${version}`);
8796
}
97+
appendTlsProfile(result, proxy);
98+
const alpn = getLoonShadowTLSAlpn(proxy);
99+
if (alpn) result.append(`,alpn="${alpn}"`);
88100
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
89101
}
90102

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export default function Shadowrocket_Producer() {
3333
![1, 2, 3, 4, 5].includes(proxy.version)
3434
) {
3535
return false;
36+
} else if (hasShadowrocketSnellShadowTlsObfsConflict(proxy)) {
37+
$.error(
38+
`Platform Shadowrocket does not support Snell shadow-tls with obfs for proxy ${proxy.name}. Proxy has been filtered.`,
39+
);
40+
return false;
3641
} else if (
3742
[
3843
'tailscale',
@@ -134,8 +139,24 @@ export default function Shadowrocket_Producer() {
134139
proxy['pre-shared-key'] = proxy['preshared-key'];
135140
proxy.ip = getWireGuardAddressWithCIDR(proxy, 'ipv4');
136141
proxy.ipv6 = getWireGuardAddressWithCIDR(proxy, 'ipv6');
137-
} else if (proxy.type === 'snell' && proxy.version < 3) {
138-
delete proxy.udp;
142+
} else if (proxy.type === 'snell') {
143+
if (proxy.version < 3) {
144+
delete proxy.udp;
145+
}
146+
if (proxy.plugin === 'shadow-tls' && proxy['plugin-opts']) {
147+
proxy['obfs-opts'] = {
148+
mode: 'shadow-tls',
149+
host: proxy['plugin-opts'].host,
150+
password: proxy['plugin-opts'].password,
151+
version: proxy['plugin-opts'].version,
152+
};
153+
if (proxy['plugin-opts'].alpn) {
154+
proxy['obfs-opts'].alpn =
155+
proxy['plugin-opts'].alpn;
156+
}
157+
delete proxy.plugin;
158+
delete proxy['plugin-opts'];
159+
}
139160
} else if (proxy.type === 'vless') {
140161
if (isPresent(proxy, 'sni')) {
141162
proxy.servername = proxy.sni;
@@ -278,3 +299,13 @@ export default function Shadowrocket_Producer() {
278299
};
279300
return { type, produce };
280301
}
302+
303+
function hasShadowrocketSnellShadowTlsObfsConflict(proxy) {
304+
return (
305+
proxy?.type === 'snell' &&
306+
proxy?.plugin === 'shadow-tls' &&
307+
(isPresent(proxy, 'obfs-opts.mode') ||
308+
isPresent(proxy, 'obfs-opts.host') ||
309+
isPresent(proxy, 'obfs-opts.path'))
310+
);
311+
}

backend/src/core/proxy-utils/producers/sing-box.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,17 @@ const getShadowTLSPluginOpts = (proxy = {}) => {
529529
if (proxy.plugin === 'shadow-tls' && proxy['plugin-opts']) {
530530
return proxy['plugin-opts'];
531531
}
532+
if (
533+
proxy.type === 'snell' &&
534+
proxy['obfs-opts']?.mode === 'shadow-tls'
535+
) {
536+
return {
537+
host: proxy['obfs-opts'].host,
538+
password: proxy['obfs-opts'].password,
539+
version: proxy['obfs-opts'].version,
540+
alpn: proxy['obfs-opts'].alpn,
541+
};
542+
}
532543
return undefined;
533544
};
534545

@@ -567,7 +578,7 @@ const shadowTLSOutboundParser = (proxy = {}, pluginOpts) => {
567578
}
568579
if (stPart.server_port < 0 || stPart.server_port > 65535)
569580
throw '端口值非法';
570-
const alpn = normalizeALPN(pluginOpts.alpn);
581+
const alpn = normalizeALPN(pluginOpts.alpn) ?? normalizeALPN(proxy.alpn);
571582
if (alpn) stPart.tls.alpn = alpn;
572583
if (proxy['fast-open'] === true) stPart.udp_fragment = true;
573584
tfoParser(proxy, stPart);
@@ -723,9 +734,15 @@ const snellParser = (proxy = {}) => {
723734
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
724735
throw 'invalid port';
725736
if (version != null) parsedProxy.version = version;
726-
if (proxy['obfs-opts']?.mode)
737+
if (
738+
proxy['obfs-opts']?.mode &&
739+
proxy['obfs-opts'].mode !== 'shadow-tls'
740+
)
727741
parsedProxy.obfs_mode = proxy['obfs-opts'].mode;
728-
if (proxy['obfs-opts']?.host)
742+
if (
743+
proxy['obfs-opts']?.host &&
744+
proxy['obfs-opts']?.mode !== 'shadow-tls'
745+
)
729746
parsedProxy.obfs_host = proxy['obfs-opts'].host;
730747
if (proxy.reuse && (version == null || version >= 4))
731748
parsedProxy.reuse = true;

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ function appendAlpn(result, proxy) {
6565
if (alpn) result.append(`,alpn="${alpn}"`);
6666
}
6767

68+
function getShadowTLSAlpn(proxy) {
69+
return formatSurgeAlpn(proxy?.['plugin-opts']?.alpn ?? proxy?.alpn);
70+
}
71+
6872
function appendShadowTLS(result, proxy, includeUdpPort = false) {
6973
if (proxy.plugin !== 'shadow-tls' || !proxy['plugin-opts']) return;
7074

@@ -81,6 +85,8 @@ function appendShadowTLS(result, proxy, includeUdpPort = false) {
8185
}
8286
result.append(`,shadow-tls-version=${version}`);
8387
}
88+
const alpn = getShadowTLSAlpn(proxy);
89+
if (alpn) result.append(`,alpn="${alpn}"`);
8490
if (includeUdpPort) {
8591
result.appendIfPresent(`,udp-port=${proxy['udp-port']}`, 'udp-port');
8692
}
@@ -96,7 +102,9 @@ function appendTlsProxyParams(result, proxy, enabled = true) {
96102
'tls-fingerprint',
97103
);
98104
result.appendIfPresent(`,sni="${proxy.sni}"`, 'sni');
99-
appendAlpn(result, proxy);
105+
if (proxy.plugin !== 'shadow-tls') {
106+
appendAlpn(result, proxy);
107+
}
100108
result.appendIfPresent(
101109
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
102110
'skip-cert-verify',

0 commit comments

Comments
 (0)