diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 15097a043..dbc78939e 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -1571,7 +1571,8 @@ "type": "string" }, "vlessRoute": { - "description": "VlessRoute is a free-form port/range routing spec (e.g. \"53,443,1000-2000\");\nstored verbatim, format-validated on the frontend.", + "description": "Single VLESS route value (0-65535) baked into the subscription UUID's 3rd\ngroup (bytes 6-7), which xray reads via net.PortFromBytes(id[6:8]). Empty = none.", + "example": "443", "type": "string" } }, @@ -8130,7 +8131,7 @@ ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" } ] } @@ -8222,7 +8223,7 @@ ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" } } } @@ -8317,7 +8318,7 @@ ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" } ] } @@ -8457,7 +8458,7 @@ ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" } } } @@ -8569,7 +8570,7 @@ ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" } } } diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 7febe65cc..3cc91538d 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -340,7 +340,7 @@ export const EXAMPLES: Record = { ], "updatedAt": 0, "verifyPeerCertByName": "", - "vlessRoute": "" + "vlessRoute": "443" }, "Inbound": { "clientStats": [ diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 6a95b66a2..96b7d875f 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -1545,7 +1545,8 @@ export const SCHEMAS: Record = { "type": "string" }, "vlessRoute": { - "description": "VlessRoute is a free-form port/range routing spec (e.g. \"53,443,1000-2000\");\nstored verbatim, format-validated on the frontend.", + "description": "Single VLESS route value (0-65535) baked into the subscription UUID's 3rd\ngroup (bytes 6-7), which xray reads via net.PortFromBytes(id[6:8]). Empty = none.", + "example": "443", "type": "string" } }, diff --git a/frontend/src/lib/hosts/host-link.ts b/frontend/src/lib/hosts/host-link.ts index 1a3b0e92b..ebbb47492 100644 --- a/frontend/src/lib/hosts/host-link.ts +++ b/frontend/src/lib/hosts/host-link.ts @@ -17,6 +17,7 @@ export type HostLinkInput = Pick< | 'echConfigList' | 'overrideSniFromAddress' | 'keepSniBlank' + | 'vlessRoute' >; // hostToExternalProxyEntry projects a host onto the ExternalProxyEntry shape the @@ -48,5 +49,6 @@ export function hostToExternalProxyEntry(host: HostLinkInput): ExternalProxyEntr host.pinnedPeerCertSha256 && host.pinnedPeerCertSha256.length > 0 ? host.pinnedPeerCertSha256 : undefined, verifyPeerCertByName: host.verifyPeerCertByName || undefined, echConfigList: host.echConfigList || undefined, + vlessRoute: host.vlessRoute || undefined, }; } diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 2734de0da..c15bb2a8c 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -326,6 +326,18 @@ export interface GenVlessLinkInput { externalProxy?: ExternalProxyEntry | null; } +// Mirror of the Go applyVlessRoute: bake a single 0-65535 value into the UUID's +// 3rd group (bytes 6-7), which xray reads as the vless route. Empty/invalid/non- +// UUID input is returned unchanged. +export function applyVlessRoute(id: string, route: string | undefined): string { + const r = (route ?? '').trim(); + if (r === '' || !/^\d{1,5}$/.test(r)) return id; + const n = Number(r); + if (n > 65535) return id; + if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id)) return id; + return id.slice(0, 14) + n.toString(16).padStart(4, '0') + id.slice(18); +} + // VLESS share link: vless://@:?#. The // query carries network type, encryption, network-specific knobs, and // security-specific knobs (TLS fingerprint/alpn/sni or Reality @@ -437,7 +449,7 @@ export function genVlessLink(input: GenVlessLinkInput): string { params.set('flow', flow); } - const url = new URL(`vless://${clientId}@${formatUrlHost(address)}:${port}`); + const url = new URL(`vless://${applyVlessRoute(clientId, externalProxy?.vlessRoute)}@${formatUrlHost(address)}:${port}`); for (const [key, value] of params) url.searchParams.set(key, value); url.hash = encodeURIComponent(remark); return url.toString(); diff --git a/frontend/src/pages/hosts/HostFormModal.tsx b/frontend/src/pages/hosts/HostFormModal.tsx index e8d7df9dd..d68e8c5ee 100644 --- a/frontend/src/pages/hosts/HostFormModal.tsx +++ b/frontend/src/pages/hosts/HostFormModal.tsx @@ -260,7 +260,7 @@ export default function HostFormModal({ open, mode, host, inboundOptions, save, - +