Skip to content

Commit 3078e15

Browse files
committed
feat: Loon 支持 tls-profile; sing-box 支持 packet-encoding, global-padding, authenticated-length; 优化 packet-encoding 转换逻辑
1 parent 4cc11dd commit 3078e15

10 files changed

Lines changed: 577 additions & 31 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,16 @@ function lastParse(proxy) {
801801
if (['vless'].includes(proxy.type)) {
802802
proxy.network = proxy.network || 'tcp';
803803
}
804+
if (
805+
['vmess', 'vless'].includes(proxy.type) &&
806+
proxy['packet-encoding'] == null
807+
) {
808+
if (proxy.xudp) {
809+
proxy['packet-encoding'] = 'xudp';
810+
} else if (proxy['packet-addr']) {
811+
proxy['packet-encoding'] = 'packetaddr';
812+
}
813+
}
804814
if (
805815
[
806816
'trojan',

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -855,8 +855,9 @@ function URI_VMess() {
855855
const normalizedTransportHost =
856856
getIfNotBlank(transportHost);
857857
if (proxy.network === 'h2') {
858-
const h2Hosts =
859-
splitURIHostList(normalizedTransportHost);
858+
const h2Hosts = splitURIHostList(
859+
normalizedTransportHost,
860+
);
860861
if (h2Hosts) {
861862
opts.host = h2Hosts;
862863
}
@@ -1820,14 +1821,15 @@ function URI_VLESS() {
18201821
proxy['tls-fingerprint'] = getIfPresent(params.pcs);
18211822
proxy._h2 = /(TRUE)|1/i.test(params.h2);
18221823

1823-
switch (`${params.packetEncoding || ''}`.toLowerCase()) {
1824+
switch (`${params.packetEncoding || ''}`.trim().toLowerCase()) {
18241825
case 'none':
1826+
proxy['packet-encoding'] = '';
18251827
break;
18261828
case 'packet':
1827-
proxy['packet-addr'] = true;
1829+
proxy['packet-encoding'] = 'packetaddr';
18281830
break;
18291831
default:
1830-
proxy.xudp = true;
1832+
proxy['packet-encoding'] = 'xudp';
18311833
break;
18321834
}
18331835

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,35 +54,35 @@ shadowsocks = tag equals "shadowsocks"i address method password (obfs_typev obfs
5454
$set(proxy, "plugin-opts.path", obfs.path);
5555
}
5656
}
57-
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
57+
vmess = tag equals "vmess"i address method uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/vmess_alterId/fast_open/udp_relay/ip_mode/public_key/short_id/block_quic/others)* {
5858
proxy.type = "vmess";
5959
proxy.cipher = proxy.cipher || "none";
6060
proxy.alterId = proxy.alterId || 0;
6161
handleTransport();
6262
}
63-
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
63+
vless = tag equals "vless"i address uuid (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/fast_open/udp_relay/ip_mode/flow/public_key/short_id/block_quic/others)* {
6464
proxy.type = "vless";
6565
handleTransport();
6666
}
67-
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
67+
trojan = tag equals "trojan"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/fast_open/udp_relay/ip_mode/block_quic/others)* {
6868
proxy.type = "trojan";
6969
handleTransport();
7070
}
71-
anytls = tag equals "anytls"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/idle_session_check_interval/idle_session_timeout/min_idle_session/max_stream_count/others)* {
71+
anytls = tag equals "anytls"i address password (transport/transport_host/transport_path/over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/fast_open/udp_relay/ip_mode/block_quic/idle_session_check_interval/idle_session_timeout/min_idle_session/max_stream_count/others)* {
7272
proxy.type = "anytls";
7373
handleTransport();
7474
}
75-
hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/udp_relay/fast_open/download_bandwidth/server_ports/hop_interval/salamander_password/ecn/ip_mode/block_quic/others)* {
75+
hysteria2 = tag equals "hysteria2"i address password (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/udp_relay/fast_open/download_bandwidth/server_ports/hop_interval/salamander_password/ecn/ip_mode/block_quic/others)* {
7676
proxy.type = "hysteria2";
7777
}
78-
https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
78+
https = tag equals "https"i address (username password)? (tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/fast_open/udp_relay/ip_mode/block_quic/others)* {
7979
proxy.type = "http";
8080
proxy.tls = true;
8181
}
8282
http = tag equals "http"i address (username password)? (fast_open/udp_relay/ip_mode/block_quic/others)* {
8383
proxy.type = "http";
8484
}
85-
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/fast_open/udp_relay/ip_mode/block_quic/others)* {
85+
socks5 = tag equals "socks5"i address (username password)? (over_tls/tls_name/sni/tls_verification/tls_cert_sha256/tls_pubkey_sha256/tls_profile/fast_open/udp_relay/ip_mode/block_quic/others)* {
8686
proxy.type = "socks5";
8787
}
8888
@@ -184,6 +184,7 @@ sni = comma "sni" equals match:[^,]+ { proxy.sni = match.join("").replace(/^"(.*
184184
tls_verification = comma "skip-cert-verify" equals flag:bool { proxy["skip-cert-verify"] = flag; }
185185
tls_cert_sha256 = comma "tls-cert-sha256" equals match:[^,]+ { proxy["tls-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
186186
tls_pubkey_sha256 = comma "tls-pubkey-sha256" equals match:[^,]+ { proxy["tls-pubkey-sha256"] = match.join("").replace(/^"(.*)"$/, '$1'); }
187+
tls_profile = comma "tls-profile" equals match:[^,]+ { proxy["client-fingerprint"] = match.join("").replace(/^"(.*)"$/, '$1'); }
187188
188189
flow = comma "flow" equals match:[^,]+ { proxy["flow"] = match.join("").replace(/^"(.*)"$/, '$1'); }
189190
public_key = comma "public-key" equals match:[^,]+ { proxy["reality-opts"] = proxy["reality-opts"] || {}; proxy["reality-opts"]["public-key"] = match.join("").replace(/^"(.*)"$/, '$1'); }

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export default function Loon_Producer() {
6262
return { produce };
6363
}
6464

65+
function appendTlsProfile(result, proxy) {
66+
if (
67+
isPresent(proxy, 'client-fingerprint') &&
68+
`${proxy['client-fingerprint']}`.trim() !== ''
69+
) {
70+
result.append(`,tls-profile=${proxy['client-fingerprint']}`);
71+
}
72+
}
73+
6574
function shadowsocks(proxy) {
6675
const result = new Result(proxy);
6776
if (
@@ -305,6 +314,7 @@ function trojan(proxy) {
305314
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
306315
'skip-cert-verify',
307316
);
317+
appendTlsProfile(result, proxy);
308318

309319
// sni
310320
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
@@ -360,6 +370,7 @@ function anytls(proxy) {
360370
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
361371
'skip-cert-verify',
362372
);
373+
appendTlsProfile(result, proxy);
363374

364375
// sni
365376
result.appendIfPresent(`,tls-name=${proxy.sni}`, 'sni');
@@ -441,6 +452,9 @@ function vmess(proxy) {
441452
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
442453
'skip-cert-verify',
443454
);
455+
if (proxy.tls || isReality) {
456+
appendTlsProfile(result, proxy);
457+
}
444458

445459
if (isReality) {
446460
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
@@ -551,6 +565,9 @@ function vless(proxy) {
551565
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
552566
'skip-cert-verify',
553567
);
568+
if (proxy.tls || isReality || isXtls) {
569+
appendTlsProfile(result, proxy);
570+
}
554571

555572
if (isXtls) {
556573
result.appendIfPresent(`,flow=${proxy.flow}`, 'flow');
@@ -612,6 +629,9 @@ function http(proxy) {
612629
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
613630
'skip-cert-verify',
614631
);
632+
if (proxy.tls) {
633+
appendTlsProfile(result, proxy);
634+
}
615635

616636
// tfo
617637
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
@@ -645,6 +665,9 @@ function socks5(proxy) {
645665
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
646666
'skip-cert-verify',
647667
);
668+
if (proxy.tls) {
669+
appendTlsProfile(result, proxy);
670+
}
648671

649672
// tfo
650673
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');
@@ -774,6 +797,7 @@ function hysteria2(proxy) {
774797
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
775798
'skip-cert-verify',
776799
);
800+
appendTlsProfile(result, proxy);
777801

778802
if (proxy['obfs-password'] && proxy.obfs == 'salamander') {
779803
result.append(`,salamander-password=${proxy['obfs-password']}`);

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,39 @@ const snellParser = (proxy = {}) => {
727727
return parsedProxy;
728728
};
729729

730+
const singBoxPacketEncodings = ['', 'packetaddr', 'xudp'];
731+
732+
const normalizeSingBoxPacketEncoding = (value) => {
733+
const packetEncoding = `${value}`.trim().toLowerCase();
734+
if (singBoxPacketEncodings.includes(packetEncoding)) {
735+
return packetEncoding;
736+
}
737+
return undefined;
738+
};
739+
740+
const vmessVlessPacketEncodingParser = (proxy, parsedProxy) => {
741+
if (proxy['packet-encoding'] != null) {
742+
const packetEncoding = normalizeSingBoxPacketEncoding(
743+
proxy['packet-encoding'],
744+
);
745+
if (packetEncoding != null) parsedProxy.packet_encoding = packetEncoding;
746+
} else if (proxy.xudp) {
747+
parsedProxy.packet_encoding = 'xudp';
748+
} else if (proxy['packet-addr']) {
749+
parsedProxy.packet_encoding = 'packetaddr';
750+
}
751+
};
752+
753+
const vmessProtocolOptionsParser = (proxy, parsedProxy) => {
754+
vmessVlessPacketEncodingParser(proxy, parsedProxy);
755+
if (proxy['global-padding'] != null) {
756+
parsedProxy.global_padding = !!proxy['global-padding'];
757+
}
758+
if (proxy['authenticated-length'] != null) {
759+
parsedProxy.authenticated_length = !!proxy['authenticated-length'];
760+
}
761+
};
762+
730763
const vmessParser = (proxy = {}) => {
731764
const parsedProxy = {
732765
tag: proxy.name,
@@ -751,7 +784,7 @@ const vmessParser = (proxy = {}) => {
751784
parsedProxy.security = 'auto';
752785
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
753786
throw 'invalid port';
754-
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
787+
vmessProtocolOptionsParser(proxy, parsedProxy);
755788
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
756789
if (proxy.network === 'ws') wsParser(proxy, parsedProxy);
757790
if (proxy.network === 'h2') h2Parser(proxy, parsedProxy);
@@ -778,7 +811,7 @@ const vlessParser = (proxy = {}) => {
778811
};
779812
if (parsedProxy.server_port < 0 || parsedProxy.server_port > 65535)
780813
throw 'invalid port';
781-
if (proxy.xudp) parsedProxy.packet_encoding = 'xudp';
814+
vmessVlessPacketEncodingParser(proxy, parsedProxy);
782815
if (proxy['fast-open']) parsedProxy.udp_fragment = true;
783816
// if (['xtls-rprx-vision', ''].includes(proxy.flow)) parsedProxy.flow = proxy.flow;
784817
if (proxy.flow != null) parsedProxy.flow = proxy.flow;

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,9 @@ function getTransportHost(network, transportOpts = {}) {
134134
function normalizeVmessSecurity(security) {
135135
if (
136136
security &&
137-
![
138-
'aes-128-gcm',
139-
'chacha20-poly1305',
140-
'auto',
141-
'none',
142-
'zero',
143-
].includes(security)
137+
!['aes-128-gcm', 'chacha20-poly1305', 'auto', 'none', 'zero'].includes(
138+
security,
139+
)
144140
) {
145141
return 'auto';
146142
}
@@ -749,10 +745,28 @@ function vless(proxy) {
749745
}
750746

751747
let packetEncoding = '';
752-
if (proxy['packet-addr']) {
753-
packetEncoding = '&packetEncoding=packet';
754-
} else if (proxy.udp === true && !proxy.xudp) {
755-
packetEncoding = '&packetEncoding=none';
748+
let canonicalPacketEncoding;
749+
if (proxy['packet-encoding'] != null) {
750+
canonicalPacketEncoding = `${proxy['packet-encoding']}`
751+
.trim()
752+
.toLowerCase();
753+
} else if (proxy.xudp) {
754+
canonicalPacketEncoding = 'xudp';
755+
} else if (proxy['packet-addr']) {
756+
canonicalPacketEncoding = 'packetaddr';
757+
} else if (proxy.udp === true) {
758+
canonicalPacketEncoding = '';
759+
}
760+
switch (canonicalPacketEncoding) {
761+
case '':
762+
packetEncoding = '&packetEncoding=none';
763+
break;
764+
case 'packetaddr':
765+
packetEncoding = '&packetEncoding=packet';
766+
break;
767+
case 'xudp':
768+
packetEncoding = '&packetEncoding=xudp';
769+
break;
756770
}
757771

758772
return `vless://${proxy.uuid}@${proxy.server}:${

backend/src/test/proxy-parsers/pipeline.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,26 @@ FINAL,DIRECT
202202
}
203203
});
204204

205+
it('prefers xudp over packet-addr when normalizing legacy packet fields', function () {
206+
const proxy = parseOne(
207+
JSON.stringify({
208+
name: 'vless-legacy-packet-conflict',
209+
type: 'vless',
210+
server: 'vless.example.com',
211+
port: 443,
212+
uuid: UUID,
213+
xudp: true,
214+
'packet-addr': true,
215+
}),
216+
);
217+
218+
expectSubset(proxy, {
219+
type: 'vless',
220+
name: 'vless-legacy-packet-conflict',
221+
'packet-encoding': 'xudp',
222+
});
223+
});
224+
205225
it('accepts every Clash-supported proxy type as inline objects', function () {
206226
const supportedTypes = [
207227
'tailscale',

0 commit comments

Comments
 (0)