xdns-mask.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. type Raw = Record<string, unknown>;
  2. /** The EDNS0 payload the pre-26.9.30 xdns always negotiated; without it answers cap at 512. */
  3. export const XDNS_LEGACY_EDNS0 = 1232;
  4. const LEGACY_RECORD_TYPES: Record<string, number> = { '': 16, txt: 16, a: 1, aaaa: 28 };
  5. function legacyDomain(spec: string): Raw | null {
  6. let name = spec.trim();
  7. let method = '';
  8. const colon = name.lastIndexOf(':');
  9. if (colon >= 0) {
  10. method = name.slice(colon + 1).toLowerCase();
  11. name = name.slice(0, colon);
  12. }
  13. name = name.replace(/^\.+|\.+$/g, '');
  14. const type = LEGACY_RECORD_TYPES[method];
  15. if (!name || type === undefined) return null;
  16. return { name, types: [type], edns0: XDNS_LEGACY_EDNS0 };
  17. }
  18. // xray-core 26.9.30 (#6718) parses xdns domains/resolvers only as objects. Mirrors
  19. // internal/util/maskcompat: a bare name becomes TXT, entries the old core refused are dropped.
  20. export function upgradeLegacyXdnsSettings(settings: Raw): { next: Raw; changed: boolean } {
  21. const rawDomains = Array.isArray(settings.domains) ? (settings.domains as unknown[]) : [];
  22. const rawResolvers = Array.isArray(settings.resolvers) ? (settings.resolvers as unknown[]) : [];
  23. const isLegacy = (v: unknown) => typeof v === 'string';
  24. if (!rawDomains.some(isLegacy) && !rawResolvers.some(isLegacy)) {
  25. return { next: settings, changed: false };
  26. }
  27. const domains: unknown[] = [];
  28. const listed = new Set<string>();
  29. const addDomain = (domain: Raw) => {
  30. const key = String(domain.name ?? '').toLowerCase();
  31. if (key && listed.has(key)) return;
  32. listed.add(key);
  33. domains.push(domain);
  34. };
  35. for (const entry of rawDomains) {
  36. if (typeof entry === 'string') {
  37. const domain = legacyDomain(entry);
  38. if (domain) addDomain(domain);
  39. } else if (entry && typeof entry === 'object') {
  40. addDomain(entry as Raw);
  41. }
  42. }
  43. const resolvers: unknown[] = [];
  44. for (const entry of rawResolvers) {
  45. if (typeof entry !== 'string') {
  46. resolvers.push(entry);
  47. continue;
  48. }
  49. const sep = entry.indexOf('+udp://');
  50. const addr = sep >= 0 ? entry.slice(sep + '+udp://'.length).trim() : '';
  51. const domain = sep >= 0 ? legacyDomain(entry.slice(0, sep)) : null;
  52. if (!addr || !domain) continue;
  53. addDomain(domain);
  54. resolvers.push({ type: 'udp', settings: { addr } });
  55. }
  56. const next: Raw = { ...settings, domains };
  57. if (resolvers.length > 0) next.resolvers = resolvers;
  58. else delete next.resolvers;
  59. return { next, changed: true };
  60. }
  61. /** Applies upgradeLegacyXdnsSettings to every xdns entry of a finalmask.udp list. */
  62. export function upgradeLegacyXdnsMasks(udp: unknown[]): { next: unknown[]; changed: boolean } {
  63. let changed = false;
  64. const next = udp.map((entry) => {
  65. const mask = entry as Raw | null;
  66. if (!mask || typeof mask !== 'object' || String(mask.type).toLowerCase() !== 'xdns') {
  67. return entry;
  68. }
  69. if (!mask.settings || typeof mask.settings !== 'object') return entry;
  70. const upgraded = upgradeLegacyXdnsSettings(mask.settings as Raw);
  71. if (!upgraded.changed) return entry;
  72. changed = true;
  73. return { ...mask, settings: upgraded.next };
  74. });
  75. return { next, changed };
  76. }