Skip to content

Commit 374001c

Browse files
committed
feat: Egern 支持 Snell 1/2/3/4/5, SOCKS5 over TLS, VMess/VLESS 新增 gRPC, Trojan/SOCKS5 over TLS/AnyTLS/HTTPS/gRPC(VMess/VLESS) 支持 REALITY
1 parent 6db17e7 commit 374001c

3 files changed

Lines changed: 406 additions & 51 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.24.11",
3+
"version": "2.24.12",
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/producers/egern.js

Lines changed: 142 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default function Egern_Producer() {
2626
'wireguard',
2727
'anytls',
2828
'ssh',
29+
'snell',
2930
].includes(proxy.type) ||
3031
(proxy.type === 'ss' &&
3132
((proxy.plugin === 'obfs' &&
@@ -63,16 +64,20 @@ export default function Egern_Producer() {
6364
'2022-blake3-aes-256-gcm',
6465
].includes(proxy.cipher))) ||
6566
(proxy.type === 'vmess' &&
66-
!['h2', 'http', 'ws', 'tcp'].includes(proxy.network) &&
67-
proxy.network) ||
67+
((!['h2', 'http', 'ws', 'tcp', 'grpc'].includes(
68+
proxy.network,
69+
) &&
70+
proxy.network) ||
71+
!isEgernGrpcGun(proxy))) ||
6872
(proxy.type === 'trojan' &&
6973
!['http', 'ws', 'tcp'].includes(proxy.network) &&
7074
proxy.network) ||
7175
(proxy.type === 'vless' &&
72-
((!['h2', 'http', 'ws', 'tcp'].includes(
76+
((!['h2', 'http', 'ws', 'tcp', 'grpc'].includes(
7377
proxy.network,
7478
) &&
7579
proxy.network) ||
80+
!isEgernGrpcGun(proxy) ||
7681
(typeof proxy.flow !== 'undefined' &&
7782
!['xtls-rprx-vision', ''].includes(
7883
proxy.flow,
@@ -82,12 +87,15 @@ export default function Egern_Producer() {
8287
proxy.token.length !== 0)
8388
) {
8489
return false;
90+
} else if (
91+
proxy.type === 'snell' &&
92+
normalizeSnellVersion(proxy.version) === null
93+
) {
94+
return false;
8595
} else if (
8696
['anytls'].includes(proxy.type) &&
8797
proxy.network &&
88-
(!['tcp'].includes(proxy.network) ||
89-
(['tcp'].includes(proxy.network) &&
90-
proxy['reality-opts']))
98+
!['tcp'].includes(proxy.network)
9199
) {
92100
return false;
93101
} else if (
@@ -126,12 +134,13 @@ export default function Egern_Producer() {
126134
headers: proxy.headers,
127135
}
128136
: {}),
129-
tfo: proxy.tfo || proxy['fast-open'],
137+
tfo: getTfo(proxy),
130138
...(proxy.tls
131139
? {
132140
sni: proxy.sni,
133141
skip_tls_verify:
134142
proxy['skip-cert-verify'],
143+
reality: getReality(proxy),
135144
}
136145
: {}),
137146
};
@@ -148,21 +157,29 @@ export default function Egern_Producer() {
148157
headers: proxy.headers,
149158
}
150159
: {}),
151-
tfo: proxy.tfo || proxy['fast-open'],
160+
tfo: getTfo(proxy),
152161
sni: proxy.sni,
153162
skip_tls_verify: proxy['skip-cert-verify'],
163+
reality: getReality(proxy),
154164
};
155165
} else if (proxy.type === 'socks5') {
156166
proxy = {
157-
type: 'socks5',
167+
type: proxy.tls ? 'socks5_tls' : 'socks5',
158168
name: proxy.name,
159169
server: proxy.server,
160170
port: proxy.port,
161171
username: proxy.username,
162172
password: proxy.password,
163-
tfo: proxy.tfo || proxy['fast-open'],
164-
udp_relay:
165-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
173+
tfo: getTfo(proxy),
174+
udp_relay: getUdpRelay(proxy),
175+
...(proxy.tls
176+
? {
177+
sni: proxy.sni,
178+
skip_tls_verify:
179+
proxy['skip-cert-verify'],
180+
reality: getReality(proxy),
181+
}
182+
: {}),
166183
};
167184
} else if (proxy.type === 'ss') {
168185
proxy = {
@@ -175,9 +192,8 @@ export default function Egern_Producer() {
175192
server: proxy.server,
176193
port: proxy.port,
177194
password: proxy.password,
178-
tfo: proxy.tfo || proxy['fast-open'],
179-
udp_relay:
180-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
195+
tfo: getTfo(proxy),
196+
udp_relay: getUdpRelay(proxy),
181197
};
182198
if (isPresent(original, 'plugin')) {
183199
if (original.plugin === 'obfs') {
@@ -207,9 +223,8 @@ export default function Egern_Producer() {
207223
),
208224
}
209225
: {}),
210-
tfo: proxy.tfo || proxy['fast-open'],
211-
udp_relay:
212-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
226+
tfo: getTfo(proxy),
227+
udp_relay: getUdpRelay(proxy),
213228
sni: proxy.sni,
214229
skip_tls_verify: proxy['skip-cert-verify'],
215230
port_hopping: proxy.ports,
@@ -251,11 +266,11 @@ export default function Egern_Producer() {
251266
server: proxy.server,
252267
port: proxy.port,
253268
password: proxy.password,
254-
tfo: proxy.tfo || proxy['fast-open'],
255-
udp_relay:
256-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
269+
tfo: getTfo(proxy),
270+
udp_relay: getUdpRelay(proxy),
257271
sni: proxy.sni,
258272
skip_tls_verify: proxy['skip-cert-verify'],
273+
reality: getReality(proxy),
259274
websocket: proxy.websocket,
260275
};
261276
} else if (proxy.type === 'anytls') {
@@ -265,11 +280,11 @@ export default function Egern_Producer() {
265280
server: proxy.server,
266281
port: proxy.port,
267282
password: proxy.password,
268-
tfo: proxy.tfo || proxy['fast-open'],
269-
udp_relay:
270-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
283+
tfo: getTfo(proxy),
284+
udp_relay: getUdpRelay(proxy),
271285
sni: proxy.sni,
272286
skip_tls_verify: proxy['skip-cert-verify'],
287+
reality: getReality(proxy),
273288
};
274289
} else if (proxy.type === 'vmess') {
275290
// Egern:传输层,支持 ws/wss/http1/http2/tls,不配置则为 tcp
@@ -319,6 +334,8 @@ export default function Egern_Producer() {
319334
skip_tls_verify: proxy['skip-cert-verify'],
320335
},
321336
};
337+
} else if (proxy.network === 'grpc') {
338+
proxy.transport = getGrpcTransport(proxy);
322339
} else if (
323340
(proxy.network === 'tcp' || !proxy.network) &&
324341
proxy.tls
@@ -332,10 +349,13 @@ export default function Egern_Producer() {
332349
},
333350
};
334351
}
335-
let legacy;
352+
let legacy = false;
336353
if (isPresent(proxy, 'aead') && !proxy.aead) {
337354
legacy = true;
338-
} else if (proxy.alterId !== 0) {
355+
} else if (
356+
isPresent(proxy, 'alterId') &&
357+
proxy.alterId !== 0
358+
) {
339359
legacy = true;
340360
}
341361
proxy = {
@@ -345,10 +365,9 @@ export default function Egern_Producer() {
345365
port: proxy.port,
346366
user_id: proxy.uuid,
347367
security,
348-
tfo: proxy.tfo || proxy['fast-open'],
368+
tfo: getTfo(proxy),
349369
legacy,
350-
udp_relay:
351-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
370+
udp_relay: getUdpRelay(proxy),
352371
transport: proxy.transport,
353372
};
354373
} else if (proxy.type === 'vless') {
@@ -401,25 +420,16 @@ export default function Egern_Producer() {
401420
skip_tls_verify: proxy['skip-cert-verify'],
402421
},
403422
};
423+
} else if (proxy.network === 'grpc') {
424+
proxy.transport = getGrpcTransport(proxy);
404425
} else if (proxy.network === 'tcp' || !proxy.network) {
405-
let reality;
406-
if (
407-
proxy['reality-opts']?.['short-id'] ||
408-
proxy['reality-opts']?.['public-key']
409-
) {
410-
reality = {
411-
short_id: proxy['reality-opts']['short-id'],
412-
public_key:
413-
proxy['reality-opts']['public-key'],
414-
};
415-
}
416426
proxy.transport = {
417427
[proxy.tls ? 'tls' : 'tcp']: {
418428
sni: proxy.tls ? proxy.sni : undefined,
419429
skip_tls_verify: proxy.tls
420430
? proxy['skip-cert-verify']
421431
: undefined,
422-
reality,
432+
reality: getReality(proxy),
423433
},
424434
};
425435
flow = proxy.flow;
@@ -432,9 +442,8 @@ export default function Egern_Producer() {
432442
port: proxy.port,
433443
user_id: proxy.uuid,
434444
security: proxy.cipher,
435-
tfo: proxy.tfo || proxy['fast-open'],
436-
udp_relay:
437-
proxy.udp || proxy.udp_relay || proxy.udp_relay,
445+
tfo: getTfo(proxy),
446+
udp_relay: getUdpRelay(proxy),
438447
transport: proxy.transport,
439448
flow,
440449
};
@@ -500,7 +509,31 @@ export default function Egern_Producer() {
500509
private_key: proxy['private-key'],
501510
// private_key_passphrase: proxy['private-key-passphrase'],
502511
host_keys: proxy['host-key'],
503-
tfo: proxy.tfo || proxy['fast-open'],
512+
tfo: getTfo(proxy),
513+
};
514+
} else if (proxy.type === 'snell') {
515+
const snellVersion = normalizeSnellVersion(
516+
proxy.version,
517+
);
518+
proxy = {
519+
type: 'snell',
520+
name: proxy.name,
521+
server: proxy.server,
522+
port: proxy.port,
523+
psk: proxy.psk,
524+
version: snellVersion,
525+
...(snellVersion == null || snellVersion >= 3
526+
? {
527+
udp_relay: getUdpRelay(proxy),
528+
}
529+
: {}),
530+
reuse: proxy.reuse,
531+
obfs: proxy['obfs-opts']?.mode || proxy.obfs,
532+
obfs_host:
533+
proxy['obfs-opts']?.host ||
534+
proxy['obfs-host'] ||
535+
proxy.obfs_host,
536+
tfo: getTfo(proxy),
504537
};
505538
}
506539
if (
@@ -561,6 +594,7 @@ export default function Egern_Producer() {
561594
'hysteria2',
562595
'anytls',
563596
'ssh',
597+
'snell',
564598
].includes(original.type)
565599
) {
566600
if (
@@ -595,11 +629,12 @@ export default function Egern_Producer() {
595629
if (proxy.transport) {
596630
for (const key in proxy.transport) {
597631
if (
598-
Object.keys(proxy.transport[key]).length ===
632+
key !== 'grpc' &&
633+
(Object.keys(proxy.transport[key]).length ===
599634
0 ||
600-
Object.values(proxy.transport[key]).every(
601-
(value) => value == null,
602-
)
635+
Object.values(proxy.transport[key]).every(
636+
(value) => value == null,
637+
))
603638
) {
604639
delete proxy.transport[key];
605640
}
@@ -644,6 +679,62 @@ function hasHeaders(proxy) {
644679
);
645680
}
646681

682+
function getTfo(proxy) {
683+
return proxy.tfo ?? proxy['fast-open'];
684+
}
685+
686+
function getUdpRelay(proxy) {
687+
return proxy.udp ?? proxy.udp_relay;
688+
}
689+
690+
function getNonEmptyValue(value) {
691+
if (value == null) return undefined;
692+
if (typeof value === 'string' && value.length === 0) return undefined;
693+
return value;
694+
}
695+
696+
function getReality(proxy) {
697+
const realityOpts = proxy?.['reality-opts'];
698+
if (!realityOpts) return undefined;
699+
700+
const reality = {};
701+
const publicKey = getNonEmptyValue(realityOpts['public-key']);
702+
const shortId = getNonEmptyValue(realityOpts['short-id']);
703+
if (publicKey != null) reality.public_key = publicKey;
704+
if (shortId != null) reality.short_id = shortId;
705+
706+
return Object.keys(reality).length > 0 ? reality : undefined;
707+
}
708+
709+
function getGrpcTransport(proxy) {
710+
return {
711+
grpc: {
712+
service_name: proxy['grpc-opts']?.['grpc-service-name'],
713+
sni: proxy.sni,
714+
reality: getReality(proxy),
715+
skip_tls_verify: proxy['skip-cert-verify'],
716+
},
717+
};
718+
}
719+
720+
function isEgernGrpcGun(proxy) {
721+
if (proxy.network !== 'grpc') return true;
722+
723+
const grpcType = proxy['grpc-opts']?.['_grpc-type'];
724+
if (grpcType == null) return true;
725+
726+
return `${grpcType}`.trim().toLowerCase() === 'gun';
727+
}
728+
729+
function normalizeSnellVersion(version) {
730+
if (version == null) return undefined;
731+
732+
const normalized = `${version}`.trim();
733+
if (!/^[1-5]$/.test(normalized)) return null;
734+
735+
return parseInt(normalized, 10);
736+
}
737+
647738
function getFirstHeaderValue(headers, ...keys) {
648739
for (const key of keys) {
649740
const value = getFirstValue(headers?.[key]);
@@ -699,14 +790,15 @@ function supportsRootFingerprintSha256(original, proxy) {
699790
['anytls', 'https', 'hysteria2', 'trojan', 'tuic'].includes(
700791
original.type,
701792
) ||
793+
(original.type === 'socks5' && proxy.type === 'socks5_tls') ||
702794
(original.type === 'http' && proxy.type === 'https')
703795
);
704796
}
705797

706798
function addTransportFingerprintSha256(transport, fingerprintSha256) {
707799
if (!transport) return;
708800

709-
for (const key of ['http2', 'tls', 'wss']) {
801+
for (const key of ['grpc', 'http2', 'tls', 'wss']) {
710802
if (transport[key]) {
711803
transport[key].fingerprint_sha256 = fingerprintSha256;
712804
}

0 commit comments

Comments
 (0)