Files
3x-ui/frontend/src/schemas/protocols/stream/sockopt.ts
nima1024m 6ed6f57b5c fix(panel): normalize XHTTP/sockopt/Reality wire output and validate REALITY target (#4988)
* fix(panel): normalize XHTTP/sockopt/Reality wire output and validate REALITY target

Strip mode-specific XHTTP fields for stream-one, reset harmful sockopt defaults
to 0, split server/client Reality fields on save, validate target host:port in
the inbound form, and expose Happy Eyeballs for the direct freedom outbound.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(panel): keep REALITY public key on the wire, guard freedom noises

The REALITY server/client wire split deleted realitySettings.settings on save, but the panel stores the REALITY public key there and every share-link / subscription generator reads it back from that path (frontend inbound-link.ts, Go subService/subJsonService/subClashService). Stripping it produced empty pbk= links, breaking client connectivity after save+reload.

Revert the reality normalization (drop normalizeRealityForWire and the key sets), restore the inbound REALITY form fields (uTLS, spiderX, publicKey, mldsa65Verify) while keeping the new validated target field, and restore the mldsa65Verify clear handler.

Also guard freedomToWire against undefined noises/finalRules (same defensive treatment as the existing fragment guard, issue #4686) which the new freedom-outbound test surfaced as a crash. Tests now assert the public key is preserved.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
2026-06-06 02:40:32 +02:00

79 lines
2.9 KiB
TypeScript

import { z } from 'zod';
export const SockoptDomainStrategySchema = z.enum([
'AsIs',
'UseIP',
'UseIPv6v4',
'UseIPv6',
'UseIPv4v6',
'UseIPv4',
'ForceIP',
'ForceIPv6v4',
'ForceIPv6',
'ForceIPv4v6',
'ForceIPv4',
]);
export type SockoptDomainStrategy = z.infer<typeof SockoptDomainStrategySchema>;
export const TcpCongestionSchema = z.enum(['bbr', 'cubic', 'reno']);
export type TcpCongestion = z.infer<typeof TcpCongestionSchema>;
export const TproxyModeSchema = z.enum(['off', 'redirect', 'tproxy']);
export type TproxyMode = z.infer<typeof TproxyModeSchema>;
export const AddressPortStrategySchema = z.enum([
'none',
'SrvPortOnly',
'SrvAddressOnly',
'SrvPortAndAddress',
'TxtPortOnly',
'TxtAddressOnly',
'TxtPortAndAddress',
]);
export type AddressPortStrategy = z.infer<typeof AddressPortStrategySchema>;
export const HappyEyeballsSchema = z.object({
tryDelayMs: z.number().int().min(0).default(0),
prioritizeIPv6: z.boolean().default(false),
interleave: z.number().int().min(1).default(1),
maxConcurrentTry: z.number().int().min(0).default(4),
});
export type HappyEyeballs = z.infer<typeof HappyEyeballsSchema>;
export const CustomSockoptSchema = z.object({
system: z.enum(['linux', 'windows', 'darwin']).optional(),
type: z.enum(['int', 'str']),
level: z.string().default('6'),
opt: z.string(),
value: z.union([z.string(), z.number()]),
});
export type CustomSockopt = z.infer<typeof CustomSockoptSchema>;
export const SockoptStreamSettingsSchema = z.object({
acceptProxyProtocol: z.boolean().default(false),
tcpFastOpen: z.union([z.boolean(), z.number().int()]).default(false),
mark: z.number().int().default(0),
tproxy: TproxyModeSchema.default('off'),
tcpMptcp: z.boolean().default(false),
penetrate: z.boolean().default(false),
domainStrategy: SockoptDomainStrategySchema.default('AsIs'),
// 0 = omit on the wire; xray-core skips sockopt fields <= 0 and uses OS defaults.
// Non-zero defaults here previously came from the xray docs *example* (clamp 600,
// maxSeg 1440, userTimeout 10000) and were written into every config when the
// panel sockopt switch was enabled, throttling long-haul links.
tcpMaxSeg: z.number().int().min(0).default(0),
dialerProxy: z.string().default(''),
tcpKeepAliveInterval: z.number().int().min(0).default(0),
tcpKeepAliveIdle: z.number().int().min(0).default(0),
tcpUserTimeout: z.number().int().min(0).default(0),
tcpcongestion: TcpCongestionSchema.default('bbr'),
V6Only: z.boolean().default(false),
tcpWindowClamp: z.number().int().min(0).default(0),
interface: z.string().default(''),
trustedXForwardedFor: z.array(z.string()).default([]),
addressPortStrategy: AddressPortStrategySchema.default('none'),
happyEyeballs: HappyEyeballsSchema.optional(),
customSockopt: z.array(CustomSockoptSchema).default([]),
});
export type SockoptStreamSettings = z.infer<typeof SockoptStreamSettingsSchema>;