Skip to content

Commit ae531d0

Browse files
committed
feat: URI 输入输出支持 vcn, name-cert-verify
1 parent b63f608 commit ae531d0

7 files changed

Lines changed: 96 additions & 5 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,11 @@ function URI_VLESS() {
18831883
proxy['ech-opts'] = echOpts;
18841884
}
18851885
proxy['tls-fingerprint'] = getIfPresent(params.pcs);
1886+
proxy._vcn = params.vcn
1887+
?.split(',')
1888+
.map((name) => name.trim())
1889+
.filter(Boolean);
1890+
proxy['name-cert-verify'] = proxy._vcn?.[0];
18861891
proxy._h2 = /(TRUE)|1/i.test(params.h2);
18871892

18881893
switch (`${params.packetEncoding || ''}`.trim().toLowerCase()) {

backend/src/core/proxy-utils/parsers/peggy/trojan-uri.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ function parseTrojan(url) {
111111
proxy["tls-fingerprint"] =
112112
params.pcs;
113113

114+
proxy._vcn = params.vcn
115+
?.split(",")
116+
.map((name) => name.trim())
117+
.filter(Boolean);
118+
119+
proxy["name-cert-verify"] =
120+
proxy._vcn?.[0];
121+
114122

115123
if (params.alpn) {
116124
proxy.alpn = params.alpn.split(",");

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,13 @@ function vless(proxy) {
596596
if (proxy['tls-fingerprint']) {
597597
pcs = `&pcs=${encodeURIComponent(proxy['tls-fingerprint'])}`;
598598
}
599+
let vcn = '';
600+
const certNames = Array.isArray(proxy._vcn)
601+
? proxy._vcn.join(',')
602+
: proxy['name-cert-verify'];
603+
if (Array.isArray(proxy._vcn) || certNames) {
604+
vcn = `&vcn=${encodeURIComponent(certNames)}`;
605+
}
599606
let ech = '';
600607
const echConfigList = buildXrayEchConfigListFromMihomo(
601608
proxy['ech-opts'],
@@ -777,7 +784,7 @@ function vless(proxy) {
777784
proxy.port
778785
}?security=${encodeURIComponent(
779786
security,
780-
)}${vlessTransport}${packetEncoding}${alpn}${allowInsecure}${pcs}${ech}${h2}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}${pqv}${encryption}#${encodeURIComponent(
787+
)}${vlessTransport}${packetEncoding}${alpn}${allowInsecure}${pcs}${vcn}${ech}${h2}${sni}${fp}${flow}${sid}${spx}${pbk}${mode}${extra}${pqv}${encryption}#${encodeURIComponent(
781788
proxy.name,
782789
)}`;
783790
}
@@ -1192,6 +1199,13 @@ export default function URI_Producer() {
11921199
proxy['tls-fingerprint'],
11931200
)}`;
11941201
}
1202+
let trojanVcn = '';
1203+
const trojanCertNames = Array.isArray(proxy._vcn)
1204+
? proxy._vcn.join(',')
1205+
: proxy['name-cert-verify'];
1206+
if (Array.isArray(proxy._vcn) || trojanCertNames) {
1207+
trojanVcn = `&vcn=${encodeURIComponent(trojanCertNames)}`;
1208+
}
11951209
let trojanAlpn = '';
11961210
if (proxy.alpn) {
11971211
trojanAlpn = `&alpn=${encodeURIComponent(
@@ -1234,7 +1248,7 @@ export default function URI_Producer() {
12341248
proxy.port
12351249
}?sni=${encodeURIComponent(proxy.sni || proxy.server)}${
12361250
proxy['skip-cert-verify'] ? '&allowInsecure=1' : ''
1237-
}${trojanTransport}${trojanAlpn}${trojanFp}${trojanPcs}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
1251+
}${trojanTransport}${trojanAlpn}${trojanFp}${trojanPcs}${trojanVcn}${trojanSecurity}${trojanSid}${trojanPbk}${trojanSpx}${trojanMode}${trojanExtra}#${encodeURIComponent(
12381252
proxy.name,
12391253
)}`;
12401254
break;

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,18 @@ describe('Proxy URI parser coverage', function () {
716716
});
717717
});
718718

719+
it('parses Trojan vcn values into mihomo and sidecar fields', function () {
720+
const proxy = parseOne(
721+
'trojan://trojan-pass@trojan.example.com:443?vcn=first.example.com%2Csecond.example.com#Trojan%20VCN',
722+
);
723+
724+
expect(proxy['name-cert-verify']).to.equal('first.example.com');
725+
expect(proxy._vcn).to.deep.equal([
726+
'first.example.com',
727+
'second.example.com',
728+
]);
729+
});
730+
719731
it('parses Trojan URIs with grpc reality metadata', function () {
720732
const proxy = parseOne(
721733
'trojan://trojan-pass@trojan-grpc.example.com?type=grpc&serviceName=grpc-service&authority=grpc.example.com&mode=multi&security=reality&pbk=pubkey==&sid=08&spx=%2Fspider&extra=%7B%22x%22%3A1%7D&udp=1&tfo=1#Trojan%20Reality',
@@ -757,9 +769,7 @@ describe('Proxy URI parser coverage', function () {
757769

758770
it('rejects colon-containing Trojan hosts that are not IPv6', function () {
759771
expect(
760-
parseAll(
761-
'trojan://trojan-pass@host:123:443#Invalid%20Host',
762-
),
772+
parseAll('trojan://trojan-pass@host:123:443#Invalid%20Host'),
763773
).to.deep.equal([]);
764774
});
765775

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,20 @@ describe('VMess and VLESS parser coverage', function () {
577577
});
578578
});
579579

580+
it('parses the first VLESS vcn as mihomo name-cert-verify', function () {
581+
const proxy = parseOne(
582+
`vless://${UUID}@vless-ws.example.com:443?type=ws&security=tls&vcn=${encodeURIComponent(
583+
'first.example.com, second.example.com',
584+
)}#VLESS%20WS%20VCN`,
585+
);
586+
587+
expect(proxy['name-cert-verify']).to.equal('first.example.com');
588+
expect(proxy._vcn).to.deep.equal([
589+
'first.example.com',
590+
'second.example.com',
591+
]);
592+
});
593+
580594
it('parses VLESS share ECH config into mihomo ech opts', function () {
581595
const proxy = parseOne(
582596
`vless://${UUID}@vless-ws.example.com:443?type=ws&security=tls&host=cdn.example.com&path=%2Fws&ech=${encodeURIComponent(

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,6 +2506,27 @@ describe('Proxy text producers', function () {
25062506
);
25072507
});
25082508

2509+
it('produces URI VLESS links with vcn from name-cert-verify', function () {
2510+
const output = produceExternal('URI', {
2511+
type: 'vless',
2512+
name: 'URI WS VCN',
2513+
server: 'vless.example.com',
2514+
port: 443,
2515+
uuid: UUID,
2516+
tls: true,
2517+
'name-cert-verify': 'edited.example.com',
2518+
_vcn: ['cert.example.com', 'backup.example.com'],
2519+
network: 'ws',
2520+
'ws-opts': {
2521+
path: '/ws',
2522+
},
2523+
});
2524+
2525+
expect(output).to.equal(
2526+
`vless://${UUID}@vless.example.com:443?security=tls&type=ws&path=%2Fws&vcn=cert.example.com%2Cbackup.example.com#URI%20WS%20VCN`,
2527+
);
2528+
});
2529+
25092530
it('produces URI VLESS links with ech from mihomo ech opts config', function () {
25102531
const output = produceExternal('URI', {
25112532
type: 'vless',
@@ -5064,6 +5085,24 @@ describe('Proxy text producers', function () {
50645085
);
50655086
});
50665087

5088+
it('produces URI Trojan links with vcn from the sidecar', function () {
5089+
const output = produceExternal('URI', {
5090+
type: 'trojan',
5091+
name: 'URI Trojan VCN',
5092+
server: 'trojan.example.com',
5093+
port: 443,
5094+
password: 'secret',
5095+
tls: true,
5096+
sni: 'sni.example.com',
5097+
'name-cert-verify': 'edited.example.com',
5098+
_vcn: ['cert.example.com', 'backup.example.com'],
5099+
});
5100+
5101+
expect(output).to.equal(
5102+
'trojan://secret@trojan.example.com:443?sni=sni.example.com&vcn=cert.example.com%2Cbackup.example.com#URI%20Trojan%20VCN',
5103+
);
5104+
});
5105+
50675106
it('produces URI Trojan websocket links with early data metadata', function () {
50685107
const output = produceExternal('URI', {
50695108
type: 'trojan',

scripts/demo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function operator(proxies = [], targetPlatform, context) {
5656
// 31. `shadow-tls-password`/`shadow-tls-sni`/`shadow-tls-version` 这套旧字段已废弃. 请使用 `plugin: 'shadow-tls'` 和 `plugin-opts: { password, host, version }`
5757
// 32. mihomo 中 Snell shadow-tls 字段与 ss shadow-tls 字段不同, 使用的是 obfs-opts 而不是 plugin+plugin-opts, 不能与 obfs http/tls 共存. Sub-Store 内部有字段转换, 建议直接使用单行 Surge 格式 `1=snell,a.com,443,version=4,psk="1",obfs=http,obfs-host=a.com,shadow-tls-password="1",shadow-tls-sni=a.com,shadow-tls-version=3,alpn="http/1.1,h2",reuse=true` . 若想使用 JSON/JSON5/YAML 单行格式输入, 可使用 `{ "name": "1", "server": "a.com", "port": 443, "psk": "1", "version": 4, "reuse": true, "type": "snell", "obfs-opts": { "mode": "http", "host": "a.com" }, "plugin": "shadow-tls", "plugin-opts": { "host": "a.com", "password": "1", "version": 3, "alpn": [ "http/1.1", "h2" ] } }`
5858
// 33. sing-box Snell 出站默认允许 version 4/5/6, 其中 version 5 会按 sing-box 行为输出成 version 4. 开启“含不支持的协议”时保留 version 1/2/3/4/5/6. 节点上的 `_userkey` 会输出为 sing-box 的 `userkey`
59+
// 34. VLESS/Trojan URI 的 `vcn` 对应 mihomo 的 `name-cert-verify`. Xray-core 支持用逗号分隔多个 name, mihomo 只支持一个, 因此输入时取第一个有效值, 同时用 `_vcn` 数组保留全部有效值; 输出 URI 时优先用 `_vcn` 还原为 `vcn`, 没有 `_vcn` 时才使用 `name-cert-verify`. 若想设置为其他值, 可使用脚本操作设置, 例如 `$server["name-cert-verify"] = $server._vcn?.[1] || $server._vcn?.[0]`
5960

6061
// require 为 Node.js 的 require, 在 Node.js 运行环境下 可以用来引入模块
6162
// 例如在 Node.js 环境下, 将文件内容写入 /tmp/1.txt 文件

0 commit comments

Comments
 (0)