mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-22 22:05:02 +03:00
fix(sub): carry REALITY ML-KEM hint in VLESS links (#6712)
* fix(sub): carry REALITY ML-KEM hint in VLESS links Keep raw share links in parity with Clash subscriptions for Xray 26.9.8+. Preserve the URI hint through Go and frontend imports, expose it in the outbound editor, and update the documentation tooling. * fix(link): accept REALITY ML-KEM boolean aliases * test(frontend): isolate Happ preset notifications * fix(link): keep the ML-KEM hint out of Xray REALITY settings support-x25519mlkem768 is a Mihomo reality-opts option; xray-core's REALITYConfig (infra/conf/transport_security.go) has no such field and its JSON loader drops unknown keys silently. The PR also stored it as realitySettings.supportX25519Mlkem768 in Xray outbounds (form switch, Go and TS link import, docs outbound builders) and as an inbound settings default that is stripped before Xray and read by no link generator. The outbound switch therefore did nothing, and imported links carried a dead key into the JSON subscription. The share-link hint itself stays: Go, frontend and docs still emit support-x25519mlkem768=true on VLESS REALITY links and drop it on a TLS host override. --------- Co-authored-by: libmur-dev <333915961+libmur-dev@users.noreply.github.com> Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -453,6 +453,7 @@ export function genVlessLink(input: GenVlessLinkInput): string {
|
||||
applyExternalProxyTLSParams(externalProxy, params, security);
|
||||
} else if (security === 'reality') {
|
||||
params.set('security', 'reality');
|
||||
params.set('support-x25519mlkem768', 'true');
|
||||
if (stream.security === 'reality') {
|
||||
const reality = stream.realitySettings;
|
||||
params.set('pbk', reality.settings.publicKey);
|
||||
|
||||
@@ -8,7 +8,7 @@ exports[`genInboundLinks orchestrator > shadowsocks-tcp-2022: byte-stable 1`] =
|
||||
|
||||
exports[`genInboundLinks orchestrator > trojan-ws-tls: byte-stable 1`] = `"trojan://trojan-test-pw-XYZ@override.test:443?type=ws&path=%2Ftrojan&host=trojan.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=trojan.example.test#parity-test"`;
|
||||
|
||||
exports[`genInboundLinks orchestrator > vless-tcp-reality: byte-stable 1`] = `"vless://22222222-3333-4444-9555-666666666666@override.test:443?type=tcp&encryption=none&security=reality&pbk=Tx5yj1bRcOPHkdvT2pIAQ2zh0gQ8m4OPdnzqXJxxV3o&fp=chrome&sni=yahoo.com&sid=a3f1&spx=%2Fdafd018f50a389b&flow=xtls-rprx-vision#parity-test"`;
|
||||
exports[`genInboundLinks orchestrator > vless-tcp-reality: byte-stable 1`] = `"vless://22222222-3333-4444-9555-666666666666@override.test:443?type=tcp&encryption=none&security=reality&support-x25519mlkem768=true&pbk=Tx5yj1bRcOPHkdvT2pIAQ2zh0gQ8m4OPdnzqXJxxV3o&fp=chrome&sni=yahoo.com&sid=a3f1&spx=%2Fdafd018f50a389b&flow=xtls-rprx-vision#parity-test"`;
|
||||
|
||||
exports[`genInboundLinks orchestrator > vless-ws-tls: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@override.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test#parity-test"`;
|
||||
|
||||
@@ -36,7 +36,7 @@ exports[`genShadowsocksLink > shadowsocks-tcp-2022: byte-stable 1`] = `"ss://202
|
||||
|
||||
exports[`genTrojanLink > trojan-ws-tls: byte-stable 1`] = `"trojan://trojan-test-pw-XYZ@example.test:443?type=ws&path=%2Ftrojan&host=trojan.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=trojan.example.test#parity-test"`;
|
||||
|
||||
exports[`genVlessLink > vless-tcp-reality: byte-stable 1`] = `"vless://22222222-3333-4444-9555-666666666666@example.test:443?type=tcp&encryption=none&security=reality&pbk=Tx5yj1bRcOPHkdvT2pIAQ2zh0gQ8m4OPdnzqXJxxV3o&fp=chrome&sni=yahoo.com&sid=a3f1&spx=%2Fd08ed99bd9afc60&flow=xtls-rprx-vision#parity-test"`;
|
||||
exports[`genVlessLink > vless-tcp-reality: byte-stable 1`] = `"vless://22222222-3333-4444-9555-666666666666@example.test:443?type=tcp&encryption=none&security=reality&support-x25519mlkem768=true&pbk=Tx5yj1bRcOPHkdvT2pIAQ2zh0gQ8m4OPdnzqXJxxV3o&fp=chrome&sni=yahoo.com&sid=a3f1&spx=%2Fd08ed99bd9afc60&flow=xtls-rprx-vision#parity-test"`;
|
||||
|
||||
exports[`genVlessLink > vless-ws-tls: byte-stable 1`] = `"vless://8c14d6f7-2e3b-4a91-9d24-3f7a6b8c1e02@example.test:443?type=ws&encryption=none&path=%2Fws&host=cdn.example.test&security=tls&fp=chrome&alpn=h2%2Chttp%2F1.1&sni=cdn.example.test#parity-test"`;
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { message } from 'antd';
|
||||
|
||||
import { AllSetting } from '@/models/setting';
|
||||
import HappSettingsContent from '@/pages/settings/HappSettingsContent';
|
||||
|
||||
import { renderWithProviders } from './test-utils';
|
||||
|
||||
vi.spyOn(message, 'success').mockImplementation(() => undefined as never);
|
||||
|
||||
const chinaProfile = {
|
||||
Name: 'Bypass-CN',
|
||||
GlobalProxy: 'true',
|
||||
|
||||
@@ -86,6 +86,22 @@ describe('genVlessLink', () => {
|
||||
const fixtures = fixturesForProtocol('vless');
|
||||
expect(fixtures.length, 'need at least one vless full-inbound fixture').toBeGreaterThan(0);
|
||||
|
||||
it('enables X25519MLKEM768 in REALITY share links', () => {
|
||||
const entry = fixtures.find(([name]) => name === 'vless-tcp-reality');
|
||||
expect(entry, 'need a VLESS REALITY fixture').toBeDefined();
|
||||
const [, raw] = entry!;
|
||||
const typed = InboundSchema.parse(raw);
|
||||
const client = (raw as { settings: { clients: Array<{ id: string }> } }).settings.clients[0];
|
||||
|
||||
const link = genVlessLink({
|
||||
inbound: typed,
|
||||
address: 'example.test',
|
||||
clientId: client.id,
|
||||
});
|
||||
|
||||
expect(new URL(link).searchParams.get('support-x25519mlkem768')).toBe('true');
|
||||
});
|
||||
|
||||
for (const [name, raw] of fixtures) {
|
||||
it(`${name}: byte-stable`, () => {
|
||||
const typed = InboundSchema.parse(raw);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
parseHysteria2Link,
|
||||
parseWireguardLink,
|
||||
} from '@/lib/xray/outbound-link-parser';
|
||||
import { formValuesToWirePayload, rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
|
||||
import { Base64 } from '@/utils';
|
||||
|
||||
// Focused acceptance tests for the share-link parsers — one happy-path
|
||||
@@ -248,6 +249,7 @@ describe('parseVlessLink', () => {
|
||||
const link =
|
||||
'vless://11111111-2222-4333-8444-555555555555@srv.example:443' +
|
||||
'?type=tcp&security=reality&pbk=pubkey&sid=abcd&fp=chrome&sni=cloudflare.com&flow=xtls-rprx-vision' +
|
||||
'&support-x25519mlkem768=true' +
|
||||
'#imported-vless';
|
||||
const out = parseVlessLink(link);
|
||||
expect(out?.protocol).toBe('vless');
|
||||
@@ -263,6 +265,14 @@ describe('parseVlessLink', () => {
|
||||
expect(reality.publicKey).toBe('pubkey');
|
||||
expect(reality.shortId).toBe('abcd');
|
||||
expect(reality.serverName).toBe('cloudflare.com');
|
||||
// The hint is for Mihomo; xray-core's REALITYConfig has no such field.
|
||||
expect(reality).not.toHaveProperty('supportX25519Mlkem768');
|
||||
|
||||
const form = rawOutboundToFormValues(out!);
|
||||
const saved = formValuesToWirePayload(form);
|
||||
const savedReality = (saved.streamSettings as Record<string, unknown>)
|
||||
.realitySettings as Record<string, unknown>;
|
||||
expect(savedReality).not.toHaveProperty('supportX25519Mlkem768');
|
||||
});
|
||||
|
||||
it('parses encryption + pqv (post-quantum) into settings and mldsa65Verify', () => {
|
||||
|
||||
Reference in New Issue
Block a user