Files
3x-ui/frontend/src/schemas/protocols/stream/finalmask.ts
T
MHSanaei 32f96298f8 feat(finalmask): sync transport with upstream Xray core changes
Consolidate the eight legacy mKCP/header UDP mask types into a single mkcp-legacy type ({header, value}), simplify xicmp to {dgram, ips}, and add the new realm UDP mask type, matching the updated Xray-core wire format. Update the FinalMask schema enum, the transport form, the mKCP seeding default, and the backend KCP share-link translation. Refresh golden fixtures/snapshots and add backend coverage for the mapping.
2026-06-01 10:12:51 +02:00

89 lines
3.6 KiB
TypeScript

import { z } from 'zod';
// FinalMask is xray-core's late-layer obfuscation wrapper applied AFTER
// the network/security layers. It models per-type masks on TCP and UDP
// plus optional QUIC tuning. The `settings` sub-object is polymorphic on
// `type`; we model the wire-faithful shape with a permissive
// record-of-unknown for `settings` and leave per-type tightening to
// Step 6 — there are 8 UDP mask types plus 3 TCP mask types, each with
// distinct setting fields, and modeling them all as discriminated unions
// here would dwarf the rest of the stream module without buying anything
// the safety net doesn't already cover.
export const TcpMaskTypeSchema = z.enum(['fragment', 'sudoku', 'header-custom']);
export type TcpMaskType = z.infer<typeof TcpMaskTypeSchema>;
export const TcpMaskSchema = z.object({
type: TcpMaskTypeSchema,
settings: z.record(z.string(), z.unknown()).optional(),
});
export type TcpMask = z.infer<typeof TcpMaskSchema>;
export const UdpMaskTypeSchema = z.enum([
'salamander',
'mkcp-legacy',
'header-custom',
'xdns',
'xicmp',
'noise',
'sudoku',
'realm',
]);
export type UdpMaskType = z.infer<typeof UdpMaskTypeSchema>;
export const UdpMaskSchema = z.object({
type: UdpMaskTypeSchema,
settings: z.record(z.string(), z.unknown()).optional(),
});
export type UdpMask = z.infer<typeof UdpMaskSchema>;
export const QuicCongestionSchema = z.enum(['reno', 'bbr', 'brutal', 'force-brutal']);
export type QuicCongestion = z.infer<typeof QuicCongestionSchema>;
export const BbrProfileSchema = z.enum(['conservative', 'standard', 'aggressive']);
export type BbrProfile = z.infer<typeof BbrProfileSchema>;
// udpHop randomizes the QUIC port between a range every `interval` seconds
// to dodge port-based blocking. Both fields are dash-range strings on the
// wire (e.g. '20000-50000', '5-10'). preprocess coerces legacy DB rows
// where interval was stored as a number (UI bug — see B19 in commit history).
const StringRangeSchema = z.preprocess(
(v) => (typeof v === 'number' ? String(v) : v),
z.string(),
);
export const QuicUdpHopSchema = z.object({
ports: StringRangeSchema.default('20000-50000'),
interval: StringRangeSchema.default('5-10'),
});
export type QuicUdpHop = z.infer<typeof QuicUdpHopSchema>;
export const QuicParamsSchema = z.object({
congestion: QuicCongestionSchema.default('bbr'),
bbrProfile: BbrProfileSchema.optional(),
debug: z.boolean().optional(),
brutalUp: z.string().optional(),
brutalDown: z.string().optional(),
udpHop: QuicUdpHopSchema.optional(),
initStreamReceiveWindow: z.number().int().min(0).optional(),
maxStreamReceiveWindow: z.number().int().min(0).optional(),
initConnectionReceiveWindow: z.number().int().min(0).optional(),
maxConnectionReceiveWindow: z.number().int().min(0).optional(),
maxIdleTimeout: z.number().int().min(4).max(120).optional(),
keepAlivePeriod: z.number().int().min(2).max(60).optional(),
disablePathMTUDiscovery: z.boolean().optional(),
maxIncomingStreams: z.number().int().min(8).optional(),
});
export type QuicParams = z.infer<typeof QuicParamsSchema>;
// `tcp` and `udp` are omitted from the wire entirely when their arrays
// are empty (legacy toJson() drops them). Our default([]) here mirrors
// the parsed-in shape; the shadow harness already treats empty arrays as
// equivalent to absence so both pipelines converge.
export const FinalMaskStreamSettingsSchema = z.object({
tcp: z.array(TcpMaskSchema).default([]),
udp: z.array(UdpMaskSchema).default([]),
quicParams: QuicParamsSchema.optional(),
});
export type FinalMaskStreamSettings = z.infer<typeof FinalMaskStreamSettingsSchema>;