mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-02 20:22:10 +03:00
ae0da4c51f
Object-form DNS server entries always received port: 53, because DnsServerObjectInnerSchema defaulted the port unconditionally and the DnsServerModal wire adapter always wrote it. Per Xray-core, encrypted schemes must not carry a port field; a non-standard port is embedded in the URL instead. Default the port to 53 only for non-encrypted addresses and omit it for the encrypted DNS schemes Xray dispatches without a port - https, https+local, h2c, h2c+local and quic+local - both in the Zod schema and in the modal's valuesToWire adapter. Schemes are matched case-insensitively to mirror Xray-core's EqualFold comparison. A shared isEncryptedDnsAddress helper backs both paths. Fixes #5920 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
import { PortSchema } from '@/schemas/primitives';
|
|
|
|
export const DnsQueryStrategySchema = z.enum([
|
|
'UseIP',
|
|
'UseIPv4',
|
|
'UseIPv6',
|
|
'UseSystem',
|
|
]);
|
|
export type DnsQueryStrategy = z.infer<typeof DnsQueryStrategySchema>;
|
|
|
|
const DnsHostValueSchema = z.union([z.string(), z.array(z.string())]);
|
|
export const DnsHostsSchema = z.record(z.string(), DnsHostValueSchema);
|
|
export type DnsHosts = z.infer<typeof DnsHostsSchema>;
|
|
|
|
export function isEncryptedDnsAddress(address: string): boolean {
|
|
return /^(https|https\+local|h2c|h2c\+local|quic\+local):\/\//i.test(address);
|
|
}
|
|
|
|
export const DnsServerObjectInnerSchema = z.object({
|
|
address: z.string(),
|
|
port: PortSchema.optional(),
|
|
domains: z.array(z.string()).optional(),
|
|
expectedIPs: z.array(z.string()).optional(),
|
|
unexpectedIPs: z.array(z.string()).optional(),
|
|
skipFallback: z.boolean().optional(),
|
|
finalQuery: z.boolean().optional(),
|
|
tag: z.string().optional(),
|
|
clientIP: z.string().optional(),
|
|
queryStrategy: DnsQueryStrategySchema.optional(),
|
|
disableCache: z.boolean().optional(),
|
|
timeoutMs: z.number().int().min(0).default(4000),
|
|
serveStale: z.boolean().optional(),
|
|
serveExpiredTTL: z.number().int().min(0).optional(),
|
|
});
|
|
|
|
export const DnsServerObjectSchema = z.preprocess(
|
|
(val) => {
|
|
if (typeof val !== 'object' || val === null || Array.isArray(val)) return val;
|
|
const v = val as Record<string, unknown>;
|
|
if (v.expectIPs && !v.expectedIPs) {
|
|
return { ...v, expectedIPs: v.expectIPs };
|
|
}
|
|
return val;
|
|
},
|
|
DnsServerObjectInnerSchema,
|
|
).transform((v) => {
|
|
if (v.port === undefined && !isEncryptedDnsAddress(v.address)) {
|
|
return { ...v, port: 53 };
|
|
}
|
|
return v;
|
|
});
|
|
export type DnsServerObject = z.infer<typeof DnsServerObjectSchema>;
|
|
|
|
export const DnsServerEntrySchema = z.union([z.string(), DnsServerObjectSchema]);
|
|
export type DnsServerEntry = z.infer<typeof DnsServerEntrySchema>;
|
|
|
|
export const DnsObjectSchema = z.object({
|
|
tag: z.string().optional(),
|
|
hosts: DnsHostsSchema.optional(),
|
|
servers: z.array(DnsServerEntrySchema).optional(),
|
|
clientIp: z.string().optional(),
|
|
queryStrategy: DnsQueryStrategySchema.default('UseIP'),
|
|
disableCache: z.boolean().default(false),
|
|
disableFallback: z.boolean().default(false),
|
|
disableFallbackIfMatch: z.boolean().default(false),
|
|
enableParallelQuery: z.boolean().default(false),
|
|
useSystemHosts: z.boolean().default(false),
|
|
serveStale: z.boolean().default(false),
|
|
serveExpiredTTL: z.number().int().min(0).default(0),
|
|
});
|
|
export type DnsObject = z.infer<typeof DnsObjectSchema>;
|