Files
3x-ui/frontend/src/schemas/protocols/stream/finalmask.ts
T
MHSanaei 814cda3fb4 feat(xray): update xray-core to v26.7.11 and adapt panel
Bump xtls/xray-core to 50231eaf (v26.7.11) and the three binary pins
(DockerInit.sh, release.yml x2) in lockstep.

Adapt the panel to the upstream changes:

- Shadowsocks "none"/"plain" and VMess "none"/"zero" were removed from
  the core. A migration rewrites stored none/plain SS methods to a
  supported cipher and none/zero VMess security to "auto" (on both the
  clients column and inbound settings JSON); the SS build-time heal does
  the same so a row injected after boot cannot brick startup. The removed
  values are dropped from every frontend option list, schema and adapter,
  and coerced to "auto" at the Go link/sub/Clash emit sites and both link
  importers. Fix the CipherType_NONE sentinel that no longer compiles.

- Unencrypted vless/trojan outbounds to a public address are now refused
  by the core. Validate outbounds through the vendored config loader when
  saving the xray template and when storing/merging outbound
  subscriptions, so one such outbound cannot keep the core from starting.

- New TCP finalmask type "xmc" (Minecraft mimicry): add it to the sub
  link allowlist, the frontend enum and the FinalMask form (hostname,
  usernames, required password), and document it.

- streamSettings gained a "method" alias for "network"; canonicalize it
  to "network" at inbound save time and in the form adapters/schema so a
  method-keyed config keeps its transport.

- New root "env" config key is passed through xray.Config, compared in
  Equals, and forces a restart in the hot diff.

- REALITY now defaults minClientVer to 26.3.27; update the form
  placeholder.
2026-07-12 00:30:47 +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 4 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', 'xmc']);
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>;