Skip to content

Commit 873e3cd

Browse files
committed
feat: Shadowsocks v2ray-plugin plugin-opts 中 mode 使用真实取值; 增强 V2Ray URI 兼容性; Egern 过滤掉 v2ray-plugin; Shadowsocks v2ray-plugin plugin-opts 的 mode 相关的逻辑调整
1. mihomo 仅支持 mode: websocket 2. stash 仅支持 mode: websocket 3. shadowrocket 仅支持 mode: websocket 或 quic 或 http2 或 mkcp 或 grpc 4. sing-box 仅支持 mode: websocket 或 quic
1 parent 621d619 commit 873e3cd

10 files changed

Lines changed: 150 additions & 22 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.21.96",
3+
"version": "2.21.99",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"scripts": {

backend/src/core/proxy-utils/parsers/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,10 @@ function URI_SS() {
318318
case 'v2ray-plugin':
319319
proxy.plugin = 'v2ray-plugin';
320320
proxy['plugin-opts'] = {
321-
mode: 'websocket',
321+
mode:
322+
getIfNotBlank(params['obfs']) ||
323+
getIfNotBlank(params['mode']) ||
324+
'websocket',
322325
host:
323326
getIfNotBlank(params['obfs-host']) ||
324327
getIfNotBlank(params['host']),

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
isPresent,
33
produceProxyListOutput,
4+
supportsShadowsocksV2rayPluginMode,
45
} from '@/core/proxy-utils/producers/utils';
56

67
const ipVersions = {
@@ -17,7 +18,11 @@ export default function ClashMeta_Producer() {
1718
const list = proxies
1819
.filter((proxy) => {
1920
if (opts['include-unsupported-proxy']) return true;
20-
if (proxy.type === 'snell' && proxy.version >= 4) {
21+
if (
22+
!supportsShadowsocksV2rayPluginMode(proxy, ['websocket'])
23+
) {
24+
return false;
25+
} else if (proxy.type === 'snell' && proxy.version >= 4) {
2126
return false;
2227
} else if (
2328
['tailscale', 'juicity', 'naive'].includes(proxy.type)

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,18 @@ export default function Egern_Producer() {
152152
proxy.udp || proxy.udp_relay || proxy.udp_relay,
153153
next_hop: proxy.next_hop,
154154
};
155-
if (original.plugin === 'obfs') {
156-
proxy.obfs = original['plugin-opts'].mode;
157-
proxy.obfs_host = original['plugin-opts'].host;
158-
proxy.obfs_uri = original['plugin-opts'].path;
155+
if (isPresent(original, 'plugin')) {
156+
if (original.plugin === 'obfs') {
157+
proxy.obfs = original['plugin-opts'].mode;
158+
proxy.obfs_host = original['plugin-opts'].host;
159+
proxy.obfs_uri = original['plugin-opts'].path;
160+
} else if (
161+
!['shadow-tls'].includes(original.plugin)
162+
) {
163+
throw new Error(
164+
`plugin ${original.plugin} is not supported`,
165+
);
166+
}
159167
}
160168
} else if (proxy.type === 'hysteria2') {
161169
proxy = {

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
isPresent,
33
isShadowsocksOverTls,
44
produceProxyListOutput,
5+
supportsShadowsocksV2rayPluginMode,
56
} from '@/core/proxy-utils/producers/utils';
67
import $ from '@/core/app';
78

@@ -11,7 +12,17 @@ export default function Shadowrocket_Producer() {
1112
const list = proxies
1213
.filter((proxy) => {
1314
if (opts['include-unsupported-proxy']) return true;
14-
if (proxy.type === 'snell' && proxy.version >= 4) {
15+
if (
16+
!supportsShadowsocksV2rayPluginMode(proxy, [
17+
'websocket',
18+
'quic',
19+
'http2',
20+
'mkcp',
21+
'grpc',
22+
])
23+
) {
24+
return false;
25+
} else if (proxy.type === 'snell' && proxy.version >= 4) {
1526
return false;
1627
} else if (
1728
[

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
isPresent,
33
produceProxyListOutput,
4+
supportsShadowsocksV2rayPluginMode,
45
} from '@/core/proxy-utils/producers/utils';
56
import $ from '@/core/app';
67

@@ -10,6 +11,7 @@ export default function Stash_Producer() {
1011
// https://stash.wiki/proxy-protocols/proxy-types#shadowsocks
1112
const list = proxies
1213
.filter((proxy) => {
14+
if (opts['include-unsupported-proxy']) return true;
1315
if (
1416
![
1517
'ss',
@@ -51,6 +53,10 @@ export default function Stash_Producer() {
5153
(proxy.type === 'snell' && proxy.version >= 4)
5254
) {
5355
return false;
56+
} else if (
57+
!supportsShadowsocksV2rayPluginMode(proxy, ['websocket'])
58+
) {
59+
return false;
5460
} else if (
5561
['vless'].includes(proxy.type) &&
5662
proxy['reality-opts'] &&

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -548,14 +548,15 @@ export default function URI_Producer() {
548548
break;
549549
case 'v2ray-plugin':
550550
const mux = normalizePluginMuxValue(opts.mux);
551+
// 为了兼容性 多输出 mode 和 host 两个字段
551552
query += encodeURIComponent(
552-
`v2ray-plugin;obfs=${opts.mode}${
553-
opts.host ? ';obfs-host=' + opts.host : ''
554-
}${opts.host ? ';host=' + opts.host : ''}${
555-
opts.path ? ';path=' + opts.path : ''
556-
}${opts.tls ? ';tls' : ''}${
557-
opts.sni ? ';sni=' + opts.sni : ''
558-
}${
553+
`v2ray-plugin;obfs=${opts.mode};mode=${
554+
opts.mode
555+
}${opts.host ? ';obfs-host=' + opts.host : ''}${
556+
opts.host ? ';host=' + opts.host : ''
557+
}${opts.path ? ';path=' + opts.path : ''}${
558+
opts.tls ? ';tls' : ''
559+
}${opts.sni ? ';sni=' + opts.sni : ''}${
559560
opts['skip-cert-verify']
560561
? ';skip-cert-verify=' +
561562
opts['skip-cert-verify']

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ export function normalizePluginMuxValue(mux) {
5454
return mux;
5555
}
5656

57+
export function supportsShadowsocksV2rayPluginMode(proxy, supportedModes) {
58+
if (proxy?.type !== 'ss' || proxy?.plugin !== 'v2ray-plugin') return true;
59+
60+
const normalizedMode =
61+
typeof proxy?.['plugin-opts']?.mode === 'string'
62+
? proxy['plugin-opts'].mode.trim().toLowerCase()
63+
: proxy?.['plugin-opts']?.mode;
64+
65+
return supportedModes.includes(normalizedMode);
66+
}
67+
5768
export function produceProxyListOutput(list, type, opts = {}) {
5869
if (type === 'internal') return list;
5970

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

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,89 @@ describe('Proxy structured producers', function () {
9999
});
100100
});
101101

102+
it('keeps only websocket shadowsocks v2ray-plugin modes for Mihomo and Stash by default', function () {
103+
const buildProxy = (name, mode) => ({
104+
type: 'ss',
105+
name,
106+
server: 'ss.example.com',
107+
port: 8388,
108+
cipher: 'aes-128-gcm',
109+
password: 'secret',
110+
plugin: 'v2ray-plugin',
111+
'plugin-opts': {
112+
mode,
113+
host: 'cdn.example.com',
114+
path: '/socket',
115+
tls: true,
116+
},
117+
});
118+
119+
const proxies = [
120+
buildProxy('WS', 'websocket'),
121+
buildProxy('QUIC', 'quic'),
122+
];
123+
124+
for (const platform of ['Mihomo', 'Stash']) {
125+
const internal = produceInternal(platform, proxies);
126+
const external = loadProducedYaml(platform, proxies, {
127+
'include-unsupported-proxy': true,
128+
});
129+
130+
expect(internal, platform).to.have.length(1);
131+
expect(internal[0].name, platform).to.equal('WS');
132+
expect(external.proxies.map((proxy) => proxy.name), platform).to
133+
.deep.equal(['WS', 'QUIC']);
134+
}
135+
});
136+
137+
it('keeps only supported shadowsocks v2ray-plugin modes for Shadowrocket by default', function () {
138+
const buildProxy = (name, mode) => ({
139+
type: 'ss',
140+
name,
141+
server: 'ss.example.com',
142+
port: 8388,
143+
cipher: 'aes-128-gcm',
144+
password: 'secret',
145+
plugin: 'v2ray-plugin',
146+
'plugin-opts': {
147+
mode,
148+
host: 'cdn.example.com',
149+
path: '/socket',
150+
tls: true,
151+
},
152+
});
153+
154+
const proxies = [
155+
buildProxy('WS', 'websocket'),
156+
buildProxy('QUIC', 'quic'),
157+
buildProxy('HTTP2', 'http2'),
158+
buildProxy('MKCP', 'mkcp'),
159+
buildProxy('GRPC', 'grpc'),
160+
buildProxy('TLS', 'tls'),
161+
];
162+
163+
const internal = produceInternal('Shadowrocket', proxies);
164+
const external = loadProducedYaml('Shadowrocket', proxies, {
165+
'include-unsupported-proxy': true,
166+
});
167+
168+
expect(internal.map((proxy) => proxy.name)).to.deep.equal([
169+
'WS',
170+
'QUIC',
171+
'HTTP2',
172+
'MKCP',
173+
'GRPC',
174+
]);
175+
expect(external.proxies.map((proxy) => proxy.name)).to.deep.equal([
176+
'WS',
177+
'QUIC',
178+
'HTTP2',
179+
'MKCP',
180+
'GRPC',
181+
'TLS',
182+
]);
183+
});
184+
102185
it('adds Clash.Meta reality defaults and preserves websocket early data', function () {
103186
const proxy = {
104187
type: 'vless',

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ describe('Proxy text producers', function () {
589589

590590
it('produces URI shadowsocks links with v2ray-plugin mux and tls flags', function () {
591591
const plugin = encodeURIComponent(
592-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;sni=sni.example.com;skip-cert-verify=true;mux=0',
592+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;sni=sni.example.com;skip-cert-verify=true;mux=0',
593593
);
594594
const output = produceExternal('URI', {
595595
type: 'ss',
@@ -619,10 +619,10 @@ describe('Proxy text producers', function () {
619619

620620
it('normalizes boolean v2ray-plugin mux values to integers in URI links', function () {
621621
const muxOnPlugin = encodeURIComponent(
622-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
622+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
623623
);
624624
const muxOffPlugin = encodeURIComponent(
625-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
625+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
626626
);
627627

628628
const muxOnOutput = produceExternal('URI', {
@@ -701,10 +701,10 @@ describe('Proxy text producers', function () {
701701
const output = produceExternal('URI', proxies);
702702
const userInfo = Base64.encode('aes-128-gcm:secret');
703703
const muxOnPlugin = encodeURIComponent(
704-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
704+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
705705
);
706706
const muxOffPlugin = encodeURIComponent(
707-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
707+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
708708
);
709709

710710
expect(output).to.equal(
@@ -747,10 +747,10 @@ describe('Proxy text producers', function () {
747747
const output = produceExternal('URI', proxies);
748748
const userInfo = Base64.encode('aes-128-gcm:secret');
749749
const muxOnPlugin = encodeURIComponent(
750-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
750+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;tls;mux=1',
751751
);
752752
const muxOffPlugin = encodeURIComponent(
753-
'v2ray-plugin;obfs=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
753+
'v2ray-plugin;obfs=websocket;mode=websocket;obfs-host=cdn.example.com;host=cdn.example.com;path=/socket;mux=0',
754754
);
755755

756756
expect(output).to.equal(

0 commit comments

Comments
 (0)