mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-28 08:45:01 +03:00
feat(warp): register WARP over MASQUE from the WARP modal
xray-core's masque transport reaches Cloudflare WARP with an enrolled ECDSA P-256 key, the endpoint's public key and the assigned tunnel addresses, which only a WARP enrollment hands out. A new regMasque action on /panel/api/xray/warp/:action registers a separate WARP device and enrolls a secp256r1 key for the masque tunnel (the API revision and client version the WARP apps enroll MASQUE keys with). Enrolling replaces a device's WireGuard key, so it never touches the stored WireGuard registration. It returns the PEM key pair, the addresses and the endpoint, and the WARP modal turns them into a warp-masque outbound (HTTP/3, Cloudflare's MASQUE SNI), or refreshes the one already there.
This commit is contained in:
@@ -2138,7 +2138,7 @@ export const sections: readonly Section[] = [
|
||||
name: 'action',
|
||||
in: 'path',
|
||||
type: 'string',
|
||||
desc: 'data — return Warp stats. del — delete Warp data. config — return current config. reg — register (sends keys). changeIp — rotate the endpoint. license — set a Warp+ key. interval — set automatic rotation in hours.',
|
||||
desc: 'data — return Warp stats. del — delete Warp data. config — return current config. reg — register (sends keys). regMasque — register a separate device enrolled for MASQUE and return its warp key pair, addresses and endpoint. changeIp — rotate the endpoint. license — set a Warp+ key. interval — set automatic rotation in hours.',
|
||||
},
|
||||
{
|
||||
name: 'privateKey',
|
||||
|
||||
@@ -93,6 +93,31 @@ export function buildWarpOutbound(
|
||||
};
|
||||
}
|
||||
|
||||
interface WarpMasqueRegistration {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
address: string[];
|
||||
endpoint: string;
|
||||
}
|
||||
|
||||
// Cloudflare serves WARP over MASQUE on HTTP/3 behind this SNI; the enrolled key and
|
||||
// endpoint key replace user/pass, which xray-core refuses next to a warp block.
|
||||
export function buildWarpMasqueOutbound(reg: WarpMasqueRegistration): Record<string, unknown> {
|
||||
return {
|
||||
tag: 'warp-masque',
|
||||
protocol: 'masque',
|
||||
settings: { address: reg.endpoint, port: 443 },
|
||||
streamSettings: {
|
||||
network: 'masque',
|
||||
security: 'tls',
|
||||
tlsSettings: { serverName: 'consumer-masque.cloudflareclient.com', alpn: ['h3'] },
|
||||
masqueSettings: {
|
||||
warp: { privateKey: reg.privateKey, publicKey: reg.publicKey, address: reg.address },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeWarpRotation(
|
||||
existing: Record<string, unknown> | undefined,
|
||||
data: WarpData | null,
|
||||
@@ -310,6 +335,21 @@ export default function WarpModal({
|
||||
}
|
||||
}
|
||||
|
||||
async function addMasqueOutbound() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const msg = await HttpUtil.post<string>('/panel/api/xray/warp/regMasque');
|
||||
if (!msg?.success || !msg.obj) return;
|
||||
const outbound = buildWarpMasqueOutbound(JSON.parse(msg.obj) as WarpMasqueRegistration);
|
||||
const index = templateSettings?.outbounds?.findIndex((o) => o?.tag === 'warp-masque') ?? -1;
|
||||
if (index >= 0) onResetOutbound({ index, outbound });
|
||||
else onAddOutbound(outbound);
|
||||
onClose();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function addOutbound() {
|
||||
if (!stagedOutbound) {
|
||||
messageApi.warning(t('pages.xray.warp.fetchFirst'));
|
||||
@@ -544,6 +584,18 @@ export default function WarpModal({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider className="my-10">MASQUE</Divider>
|
||||
<Alert type="info" showIcon title={t('pages.xray.warp.masqueDesc')} />
|
||||
<Button
|
||||
type="primary"
|
||||
className="mt-8"
|
||||
loading={loading}
|
||||
icon={<PlusOutlined />}
|
||||
onClick={addMasqueOutbound}
|
||||
>
|
||||
{t('pages.xray.warp.addMasqueOutbound')}
|
||||
</Button>
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user