green-tunnel 2.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/core/config.d.ts +13 -0
  2. package/dist/core/config.d.ts.map +1 -0
  3. package/dist/core/config.js +87 -0
  4. package/dist/core/config.js.map +1 -0
  5. package/dist/core/dns/decode.d.ts +12 -0
  6. package/dist/core/dns/decode.d.ts.map +1 -0
  7. package/dist/core/dns/decode.js +23 -0
  8. package/dist/core/dns/decode.js.map +1 -0
  9. package/dist/core/dns/doh.d.ts +19 -0
  10. package/dist/core/dns/doh.d.ts.map +1 -0
  11. package/dist/core/dns/doh.js +54 -0
  12. package/dist/core/dns/doh.js.map +1 -0
  13. package/dist/core/dns/dot.d.ts +13 -0
  14. package/dist/core/dns/dot.d.ts.map +1 -0
  15. package/dist/core/dns/dot.js +79 -0
  16. package/dist/core/dns/dot.js.map +1 -0
  17. package/dist/core/dns/index.d.ts +8 -0
  18. package/dist/core/dns/index.d.ts.map +1 -0
  19. package/dist/core/dns/index.js +18 -0
  20. package/dist/core/dns/index.js.map +1 -0
  21. package/dist/core/dns/plain.d.ts +17 -0
  22. package/dist/core/dns/plain.d.ts.map +1 -0
  23. package/dist/core/dns/plain.js +45 -0
  24. package/dist/core/dns/plain.js.map +1 -0
  25. package/dist/core/dns/resolver.d.ts +27 -0
  26. package/dist/core/dns/resolver.d.ts.map +1 -0
  27. package/dist/core/dns/resolver.js +81 -0
  28. package/dist/core/dns/resolver.js.map +1 -0
  29. package/dist/core/errors.d.ts +32 -0
  30. package/dist/core/errors.d.ts.map +1 -0
  31. package/dist/core/errors.js +37 -0
  32. package/dist/core/errors.js.map +1 -0
  33. package/dist/core/http/head.d.ts +40 -0
  34. package/dist/core/http/head.d.ts.map +1 -0
  35. package/dist/core/http/head.js +122 -0
  36. package/dist/core/http/head.js.map +1 -0
  37. package/dist/core/http/rewriter.d.ts +20 -0
  38. package/dist/core/http/rewriter.d.ts.map +1 -0
  39. package/dist/core/http/rewriter.js +184 -0
  40. package/dist/core/http/rewriter.js.map +1 -0
  41. package/dist/core/index.d.ts +28 -0
  42. package/dist/core/index.d.ts.map +1 -0
  43. package/dist/core/index.js +27 -0
  44. package/dist/core/index.js.map +1 -0
  45. package/dist/core/logger.d.ts +38 -0
  46. package/dist/core/logger.d.ts.map +1 -0
  47. package/dist/core/logger.js +92 -0
  48. package/dist/core/logger.js.map +1 -0
  49. package/dist/core/net/socket.d.ts +30 -0
  50. package/dist/core/net/socket.d.ts.map +1 -0
  51. package/dist/core/net/socket.js +98 -0
  52. package/dist/core/net/socket.js.map +1 -0
  53. package/dist/core/proxy/connect-tunnel.d.ts +14 -0
  54. package/dist/core/proxy/connect-tunnel.d.ts.map +1 -0
  55. package/dist/core/proxy/connect-tunnel.js +44 -0
  56. package/dist/core/proxy/connect-tunnel.js.map +1 -0
  57. package/dist/core/proxy/connection.d.ts +12 -0
  58. package/dist/core/proxy/connection.d.ts.map +1 -0
  59. package/dist/core/proxy/connection.js +88 -0
  60. package/dist/core/proxy/connection.js.map +1 -0
  61. package/dist/core/proxy/context.d.ts +15 -0
  62. package/dist/core/proxy/context.d.ts.map +1 -0
  63. package/dist/core/proxy/context.js +2 -0
  64. package/dist/core/proxy/context.js.map +1 -0
  65. package/dist/core/proxy/index.d.ts +33 -0
  66. package/dist/core/proxy/index.d.ts.map +1 -0
  67. package/dist/core/proxy/index.js +162 -0
  68. package/dist/core/proxy/index.js.map +1 -0
  69. package/dist/core/proxy/pipe.d.ts +12 -0
  70. package/dist/core/proxy/pipe.d.ts.map +1 -0
  71. package/dist/core/proxy/pipe.js +42 -0
  72. package/dist/core/proxy/pipe.js.map +1 -0
  73. package/dist/core/proxy/plain-tunnel.d.ts +12 -0
  74. package/dist/core/proxy/plain-tunnel.d.ts.map +1 -0
  75. package/dist/core/proxy/plain-tunnel.js +29 -0
  76. package/dist/core/proxy/plain-tunnel.js.map +1 -0
  77. package/dist/core/system-proxy/darwin.d.ts +32 -0
  78. package/dist/core/system-proxy/darwin.d.ts.map +1 -0
  79. package/dist/core/system-proxy/darwin.js +146 -0
  80. package/dist/core/system-proxy/darwin.js.map +1 -0
  81. package/dist/core/system-proxy/driver.d.ts +21 -0
  82. package/dist/core/system-proxy/driver.d.ts.map +1 -0
  83. package/dist/core/system-proxy/driver.js +2 -0
  84. package/dist/core/system-proxy/driver.js.map +1 -0
  85. package/dist/core/system-proxy/exec.d.ts +21 -0
  86. package/dist/core/system-proxy/exec.d.ts.map +1 -0
  87. package/dist/core/system-proxy/exec.js +62 -0
  88. package/dist/core/system-proxy/exec.js.map +1 -0
  89. package/dist/core/system-proxy/index.d.ts +45 -0
  90. package/dist/core/system-proxy/index.d.ts.map +1 -0
  91. package/dist/core/system-proxy/index.js +151 -0
  92. package/dist/core/system-proxy/index.js.map +1 -0
  93. package/dist/core/system-proxy/linux.d.ts +18 -0
  94. package/dist/core/system-proxy/linux.d.ts.map +1 -0
  95. package/dist/core/system-proxy/linux.js +100 -0
  96. package/dist/core/system-proxy/linux.js.map +1 -0
  97. package/dist/core/system-proxy/recovery.d.ts +23 -0
  98. package/dist/core/system-proxy/recovery.d.ts.map +1 -0
  99. package/dist/core/system-proxy/recovery.js +147 -0
  100. package/dist/core/system-proxy/recovery.js.map +1 -0
  101. package/dist/core/system-proxy/windows.d.ts +15 -0
  102. package/dist/core/system-proxy/windows.d.ts.map +1 -0
  103. package/dist/core/system-proxy/windows.js +96 -0
  104. package/dist/core/system-proxy/windows.js.map +1 -0
  105. package/dist/core/tls/fragment.d.ts +28 -0
  106. package/dist/core/tls/fragment.d.ts.map +1 -0
  107. package/dist/core/tls/fragment.js +81 -0
  108. package/dist/core/tls/fragment.js.map +1 -0
  109. package/dist/core/types.d.ts +138 -0
  110. package/dist/core/types.d.ts.map +1 -0
  111. package/dist/core/types.js +7 -0
  112. package/dist/core/types.js.map +1 -0
  113. package/dist/index.d.ts +11 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +11 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/main.d.ts +3 -0
  118. package/dist/main.d.ts.map +1 -0
  119. package/dist/main.js +155 -0
  120. package/dist/main.js.map +1 -0
  121. package/dist/options.d.ts +25 -0
  122. package/dist/options.d.ts.map +1 -0
  123. package/dist/options.js +144 -0
  124. package/dist/options.js.map +1 -0
  125. package/dist/ui.d.ts +17 -0
  126. package/dist/ui.d.ts.map +1 -0
  127. package/dist/ui.js +89 -0
  128. package/dist/ui.js.map +1 -0
  129. package/dist/version.d.ts +3 -0
  130. package/dist/version.d.ts.map +1 -0
  131. package/dist/version.js +13 -0
  132. package/dist/version.js.map +1 -0
  133. package/package.json +31 -30
  134. package/LICENSE +0 -21
  135. package/README.md +0 -172
  136. package/bin/gt.js +0 -170
  137. package/src/config.js +0 -15
  138. package/src/dns/base.js +0 -47
  139. package/src/dns/https.js +0 -30
  140. package/src/dns/tls.js +0 -15
  141. package/src/dns/unencrypted.js +0 -25
  142. package/src/handlers/http.js +0 -65
  143. package/src/handlers/https.js +0 -78
  144. package/src/handlers/request.js +0 -32
  145. package/src/http/request.js +0 -45
  146. package/src/http/response.js +0 -45
  147. package/src/http/utils.js +0 -86
  148. package/src/index.cjs +0 -7
  149. package/src/index.js +0 -4
  150. package/src/logger.js +0 -11
  151. package/src/proxy.js +0 -74
  152. package/src/scripts/windows/wininet-reset-settings.ps1 +0 -14
  153. package/src/utils/analytics.js +0 -59
  154. package/src/utils/buffer.js +0 -35
  155. package/src/utils/socket.js +0 -32
  156. package/src/utils/system-proxy.js +0 -153
@@ -0,0 +1,13 @@
1
+ import type { DnsSettings, FragmentSettings, ProxyOptions, ProxySettings } from './types.js';
2
+ export declare const DEFAULT_DNS: DnsSettings;
3
+ export declare const DEFAULT_FRAGMENT: FragmentSettings;
4
+ export declare const DEFAULT_SETTINGS: ProxySettings;
5
+ /**
6
+ * Merge user overrides onto the defaults. Explicit and shallow-per-section on
7
+ * purpose: a hand-written resolver keeps `exactOptionalPropertyTypes` honest
8
+ * and makes it obvious which keys exist.
9
+ */
10
+ export declare function resolveSettings(options?: ProxyOptions): ProxySettings;
11
+ /** Throws `RangeError` on values that would produce a broken proxy. */
12
+ export declare function assertValidSettings(settings: ProxySettings): void;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7F,eAAO,MAAM,WAAW,EAAE,WAWzB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,gBAK9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAQ9B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,YAAiB,GAAG,aAAa,CA0BzE;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CA0BjE"}
@@ -0,0 +1,87 @@
1
+ export const DEFAULT_DNS = {
2
+ mode: 'doh',
3
+ dohUrl: 'https://cloudflare-dns.com/dns-query',
4
+ dotHost: '1.1.1.1',
5
+ dotPort: 853,
6
+ plainServers: [],
7
+ family: 'ipv4-first',
8
+ cacheSize: 1000,
9
+ minTtlSeconds: 30,
10
+ maxTtlSeconds: 3600,
11
+ timeoutMs: 5000,
12
+ };
13
+ export const DEFAULT_FRAGMENT = {
14
+ enabled: true,
15
+ size: 40,
16
+ tlsRecords: false,
17
+ delayMs: 0,
18
+ };
19
+ export const DEFAULT_SETTINGS = {
20
+ host: '127.0.0.1',
21
+ port: 8000,
22
+ httpsOnly: false,
23
+ connectTimeoutMs: 10_000,
24
+ idleTimeoutMs: 120_000,
25
+ fragment: DEFAULT_FRAGMENT,
26
+ dns: DEFAULT_DNS,
27
+ };
28
+ /**
29
+ * Merge user overrides onto the defaults. Explicit and shallow-per-section on
30
+ * purpose: a hand-written resolver keeps `exactOptionalPropertyTypes` honest
31
+ * and makes it obvious which keys exist.
32
+ */
33
+ export function resolveSettings(options = {}) {
34
+ return {
35
+ host: options.host ?? DEFAULT_SETTINGS.host,
36
+ port: options.port ?? DEFAULT_SETTINGS.port,
37
+ httpsOnly: options.httpsOnly ?? DEFAULT_SETTINGS.httpsOnly,
38
+ connectTimeoutMs: options.connectTimeoutMs ?? DEFAULT_SETTINGS.connectTimeoutMs,
39
+ idleTimeoutMs: options.idleTimeoutMs ?? DEFAULT_SETTINGS.idleTimeoutMs,
40
+ fragment: {
41
+ enabled: options.fragment?.enabled ?? DEFAULT_FRAGMENT.enabled,
42
+ size: options.fragment?.size ?? DEFAULT_FRAGMENT.size,
43
+ tlsRecords: options.fragment?.tlsRecords ?? DEFAULT_FRAGMENT.tlsRecords,
44
+ delayMs: options.fragment?.delayMs ?? DEFAULT_FRAGMENT.delayMs,
45
+ },
46
+ dns: {
47
+ mode: options.dns?.mode ?? DEFAULT_DNS.mode,
48
+ dohUrl: options.dns?.dohUrl ?? DEFAULT_DNS.dohUrl,
49
+ dotHost: options.dns?.dotHost ?? DEFAULT_DNS.dotHost,
50
+ dotPort: options.dns?.dotPort ?? DEFAULT_DNS.dotPort,
51
+ plainServers: options.dns?.plainServers ?? DEFAULT_DNS.plainServers,
52
+ family: options.dns?.family ?? DEFAULT_DNS.family,
53
+ cacheSize: options.dns?.cacheSize ?? DEFAULT_DNS.cacheSize,
54
+ minTtlSeconds: options.dns?.minTtlSeconds ?? DEFAULT_DNS.minTtlSeconds,
55
+ maxTtlSeconds: options.dns?.maxTtlSeconds ?? DEFAULT_DNS.maxTtlSeconds,
56
+ timeoutMs: options.dns?.timeoutMs ?? DEFAULT_DNS.timeoutMs,
57
+ },
58
+ };
59
+ }
60
+ /** Throws `RangeError` on values that would produce a broken proxy. */
61
+ export function assertValidSettings(settings) {
62
+ if (!Number.isInteger(settings.port) || settings.port < 0 || settings.port > 65_535) {
63
+ throw new RangeError(`port must be an integer in 0..65535, got ${String(settings.port)}`);
64
+ }
65
+ if (!Number.isInteger(settings.fragment.size) || settings.fragment.size < 1) {
66
+ throw new RangeError(`fragment.size must be a positive integer, got ${String(settings.fragment.size)}`);
67
+ }
68
+ if (settings.fragment.delayMs < 0) {
69
+ throw new RangeError(`fragment.delayMs must be >= 0, got ${String(settings.fragment.delayMs)}`);
70
+ }
71
+ if (settings.dns.minTtlSeconds > settings.dns.maxTtlSeconds) {
72
+ throw new RangeError('dns.minTtlSeconds must be <= dns.maxTtlSeconds');
73
+ }
74
+ if (settings.dns.mode === 'doh') {
75
+ let url;
76
+ try {
77
+ url = new URL(settings.dns.dohUrl);
78
+ }
79
+ catch {
80
+ throw new RangeError(`dns.dohUrl is not a valid URL: ${settings.dns.dohUrl}`);
81
+ }
82
+ if (url.protocol !== 'https:') {
83
+ throw new RangeError(`dns.dohUrl must use https, got ${url.protocol}`);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type dnsPacket from 'dns-packet';
2
+ import type { DnsRecord } from '../types.js';
3
+ import { type QueryType } from './resolver.js';
4
+ /**
5
+ * Pull the address records out of a decoded DNS response.
6
+ *
7
+ * The two explicit literal comparisons are what narrow `Answer` (a union of
8
+ * ~18 record shapes) down to the string-valued one — comparing against the
9
+ * `type` variable alone does not discriminate the union.
10
+ */
11
+ export declare function extractAddresses(packet: dnsPacket.Packet, type: QueryType): DnsRecord[];
12
+ //# sourceMappingURL=decode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../../src/core/dns/decode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,EAAE,CAavF"}
@@ -0,0 +1,23 @@
1
+ import { familyOf } from './resolver.js';
2
+ /**
3
+ * Pull the address records out of a decoded DNS response.
4
+ *
5
+ * The two explicit literal comparisons are what narrow `Answer` (a union of
6
+ * ~18 record shapes) down to the string-valued one — comparing against the
7
+ * `type` variable alone does not discriminate the union.
8
+ */
9
+ export function extractAddresses(packet, type) {
10
+ const family = familyOf(type);
11
+ const records = [];
12
+ for (const answer of packet.answers ?? []) {
13
+ if (answer.type !== 'A' && answer.type !== 'AAAA')
14
+ continue;
15
+ // CNAMEs in the chain are skipped: the upstream resolver already followed
16
+ // them and returned the addresses they point at.
17
+ if (answer.type !== type)
18
+ continue;
19
+ records.push({ address: answer.data, family, ttl: answer.ttl ?? 0 });
20
+ }
21
+ return records;
22
+ }
23
+ //# sourceMappingURL=decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode.js","sourceRoot":"","sources":["../../../src/core/dns/decode.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAwB,EAAE,IAAe;IACxE,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QAC5D,0EAA0E;QAC1E,iDAAiD;QACjD,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;YAAE,SAAS;QACnC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { DnsRecord, DnsSettings } from '../types.js';
2
+ import { CachingDnsResolver, type QueryType } from './resolver.js';
3
+ /**
4
+ * DNS over HTTPS using the RFC 8484 wire format.
5
+ *
6
+ * v2 spoke the Google/Cloudflare JSON API, which meant it only worked with the
7
+ * handful of servers that implement it. Wire format works with essentially
8
+ * every DoH resolver (Cloudflare, Google, AdGuard, NextDNS, self-hosted …).
9
+ *
10
+ * We use the GET form rather than POST: it is cacheable, and it is what
11
+ * HTTP/1.1-only clients get served. Node's `fetch` does not speak HTTP/2, so a
12
+ * server that mandates it (Quad9 answers 505 to everything else) is out of
13
+ * reach — use `--dns dot --dot-host 9.9.9.9` for those.
14
+ */
15
+ export declare class DohResolver extends CachingDnsResolver {
16
+ protected query(hostname: string, type: QueryType): Promise<DnsRecord[]>;
17
+ }
18
+ export declare function createDohResolver(settings: DnsSettings): DohResolver;
19
+ //# sourceMappingURL=doh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doh.d.ts","sourceRoot":"","sources":["../../../src/core/dns/doh.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEnE;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,kBAAkB;cACxB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAoCxF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CAEpE"}
@@ -0,0 +1,54 @@
1
+ import dnsPacket from 'dns-packet';
2
+ import { DnsError, toError } from '../errors.js';
3
+ import { extractAddresses } from './decode.js';
4
+ import { CachingDnsResolver } from './resolver.js';
5
+ /**
6
+ * DNS over HTTPS using the RFC 8484 wire format.
7
+ *
8
+ * v2 spoke the Google/Cloudflare JSON API, which meant it only worked with the
9
+ * handful of servers that implement it. Wire format works with essentially
10
+ * every DoH resolver (Cloudflare, Google, AdGuard, NextDNS, self-hosted …).
11
+ *
12
+ * We use the GET form rather than POST: it is cacheable, and it is what
13
+ * HTTP/1.1-only clients get served. Node's `fetch` does not speak HTTP/2, so a
14
+ * server that mandates it (Quad9 answers 505 to everything else) is out of
15
+ * reach — use `--dns dot --dot-host 9.9.9.9` for those.
16
+ */
17
+ export class DohResolver extends CachingDnsResolver {
18
+ async query(hostname, type) {
19
+ const request = dnsPacket.encode({
20
+ type: 'query',
21
+ id: 0, // RFC 8484 §4.1 — id 0 keeps responses cacheable by intermediaries.
22
+ flags: dnsPacket.RECURSION_DESIRED,
23
+ questions: [{ type, name: hostname }],
24
+ });
25
+ const url = new URL(this.settings.dohUrl);
26
+ url.searchParams.set('dns', request.toString('base64url'));
27
+ let response;
28
+ try {
29
+ response = await fetch(url, {
30
+ headers: { accept: 'application/dns-message' },
31
+ signal: AbortSignal.timeout(this.settings.timeoutMs),
32
+ });
33
+ }
34
+ catch (cause) {
35
+ throw new DnsError(hostname, `DoH request failed: ${toError(cause).message}`, { cause });
36
+ }
37
+ if (!response.ok) {
38
+ throw new DnsError(hostname, `DoH server returned ${String(response.status)}`);
39
+ }
40
+ const body = Buffer.from(await response.arrayBuffer());
41
+ let decoded;
42
+ try {
43
+ decoded = dnsPacket.decode(body);
44
+ }
45
+ catch (cause) {
46
+ throw new DnsError(hostname, 'malformed DoH response', { cause });
47
+ }
48
+ return extractAddresses(decoded, type);
49
+ }
50
+ }
51
+ export function createDohResolver(settings) {
52
+ return new DohResolver(settings);
53
+ }
54
+ //# sourceMappingURL=doh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doh.js","sourceRoot":"","sources":["../../../src/core/dns/doh.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAkB,MAAM,eAAe,CAAC;AAEnE;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAY,SAAQ,kBAAkB;IAC9B,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,IAAe;QAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC;YAC/B,IAAI,EAAE,OAAO;YACb,EAAE,EAAE,CAAC,EAAE,oEAAoE;YAC3E,KAAK,EAAE,SAAS,CAAC,iBAAiB;YAClC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;SACtC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QAE3D,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC1B,OAAO,EAAE,EAAE,MAAM,EAAE,yBAAyB,EAAE;gBAC9C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;aACrD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QAEvD,IAAI,OAAgC,CAAC;QACrC,IAAI,CAAC;YACH,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAqB;IACrD,OAAO,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { DnsRecord, DnsSettings } from '../types.js';
2
+ import { CachingDnsResolver, type QueryType } from './resolver.js';
3
+ /**
4
+ * DNS over TLS (RFC 7858). One short-lived TLS connection per uncached lookup —
5
+ * the resolver cache means that is a handful of connections per session, and it
6
+ * avoids the stale-socket handling a pooled connection would need.
7
+ */
8
+ export declare class DotResolver extends CachingDnsResolver {
9
+ #private;
10
+ protected query(hostname: string, type: QueryType): Promise<DnsRecord[]>;
11
+ }
12
+ export declare function createDotResolver(settings: DnsSettings): DotResolver;
13
+ //# sourceMappingURL=dot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../../../src/core/dns/dot.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEnE;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,kBAAkB;;cACxB,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAwExF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CAEpE"}
@@ -0,0 +1,79 @@
1
+ import { isIP } from 'node:net';
2
+ import tls from 'node:tls';
3
+ import dnsPacket from 'dns-packet';
4
+ import { DnsError, toError } from '../errors.js';
5
+ import { extractAddresses } from './decode.js';
6
+ import { CachingDnsResolver } from './resolver.js';
7
+ /**
8
+ * DNS over TLS (RFC 7858). One short-lived TLS connection per uncached lookup —
9
+ * the resolver cache means that is a handful of connections per session, and it
10
+ * avoids the stale-socket handling a pooled connection would need.
11
+ */
12
+ export class DotResolver extends CachingDnsResolver {
13
+ async query(hostname, type) {
14
+ const request = dnsPacket.streamEncode({
15
+ type: 'query',
16
+ id: 0,
17
+ flags: dnsPacket.RECURSION_DESIRED,
18
+ questions: [{ type, name: hostname }],
19
+ });
20
+ const response = await this.#exchange(hostname, request);
21
+ let decoded;
22
+ try {
23
+ decoded = dnsPacket.streamDecode(response);
24
+ }
25
+ catch (cause) {
26
+ throw new DnsError(hostname, 'malformed DoT response', { cause });
27
+ }
28
+ return extractAddresses(decoded, type);
29
+ }
30
+ #exchange(hostname, request) {
31
+ const { dotHost, dotPort, timeoutMs } = this.settings;
32
+ return new Promise((resolve, reject) => {
33
+ const socket = tls.connect({
34
+ host: dotHost,
35
+ port: dotPort,
36
+ ALPNProtocols: ['dot'],
37
+ // Only send SNI when the server is addressed by name; certificates for
38
+ // IP-addressed resolvers (1.1.1.1, 9.9.9.9) carry IP SANs instead.
39
+ ...(isIP(dotHost) === 0 ? { servername: dotHost } : {}),
40
+ });
41
+ let chunks = Buffer.alloc(0);
42
+ const finish = (error, value) => {
43
+ socket.destroy();
44
+ if (error)
45
+ reject(error);
46
+ else if (value)
47
+ resolve(value);
48
+ };
49
+ socket.setTimeout(timeoutMs, () => {
50
+ finish(new DnsError(hostname, `DoT query timed out after ${String(timeoutMs)}ms`));
51
+ });
52
+ socket.once('secureConnect', () => {
53
+ socket.write(request);
54
+ });
55
+ socket.on('data', (chunk) => {
56
+ chunks = Buffer.concat([chunks, chunk]);
57
+ // Messages are framed with a two-byte big-endian length prefix.
58
+ if (chunks.length < 2)
59
+ return;
60
+ const expected = chunks.readUInt16BE(0);
61
+ if (chunks.length >= expected + 2) {
62
+ finish(undefined, chunks.subarray(0, expected + 2));
63
+ }
64
+ });
65
+ socket.once('error', (cause) => {
66
+ finish(new DnsError(hostname, `DoT connection failed: ${toError(cause).message}`, { cause }));
67
+ });
68
+ socket.once('close', () => {
69
+ if (chunks.length === 0) {
70
+ finish(new DnsError(hostname, 'DoT connection closed before a response arrived'));
71
+ }
72
+ });
73
+ });
74
+ }
75
+ }
76
+ export function createDotResolver(settings) {
77
+ return new DotResolver(settings);
78
+ }
79
+ //# sourceMappingURL=dot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dot.js","sourceRoot":"","sources":["../../../src/core/dns/dot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { DnsSettings } from '../types.js';
2
+ import type { DnsResolver } from './resolver.js';
3
+ export { CachingDnsResolver, type DnsResolver, type QueryType } from './resolver.js';
4
+ export { DohResolver } from './doh.js';
5
+ export { DotResolver } from './dot.js';
6
+ export { PlainResolver } from './plain.js';
7
+ export declare function createDnsResolver(settings: DnsSettings): DnsResolver;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/dns/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CASpE"}
@@ -0,0 +1,18 @@
1
+ import { createDohResolver } from './doh.js';
2
+ import { createDotResolver } from './dot.js';
3
+ import { createPlainResolver } from './plain.js';
4
+ export { CachingDnsResolver } from './resolver.js';
5
+ export { DohResolver } from './doh.js';
6
+ export { DotResolver } from './dot.js';
7
+ export { PlainResolver } from './plain.js';
8
+ export function createDnsResolver(settings) {
9
+ switch (settings.mode) {
10
+ case 'doh':
11
+ return createDohResolver(settings);
12
+ case 'dot':
13
+ return createDotResolver(settings);
14
+ case 'plain':
15
+ return createPlainResolver(settings);
16
+ }
17
+ }
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/dns/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGjD,OAAO,EAAE,kBAAkB,EAAoC,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,iBAAiB,CAAC,QAAqB;IACrD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,KAAK;YACR,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACrC,KAAK,KAAK;YACR,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACrC,KAAK,OAAO;YACV,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { DnsRecord, DnsSettings } from '../types.js';
2
+ import { CachingDnsResolver, type QueryType } from './resolver.js';
3
+ /**
4
+ * Unencrypted DNS over the platform resolver.
5
+ *
6
+ * v2 pulled in `dns-socket` and kept one module-level socket alive forever,
7
+ * then read `answers[0].data` without checking it existed. Node's own
8
+ * `dns.Resolver` does the same job with TTLs, retries and no dependency.
9
+ */
10
+ export declare class PlainResolver extends CachingDnsResolver {
11
+ #private;
12
+ constructor(settings: DnsSettings);
13
+ protected query(hostname: string, type: QueryType): Promise<DnsRecord[]>;
14
+ close(): void;
15
+ }
16
+ export declare function createPlainResolver(settings: DnsSettings): PlainResolver;
17
+ //# sourceMappingURL=plain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain.d.ts","sourceRoot":"","sources":["../../../src/core/dns/plain.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAY,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7E;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,kBAAkB;;gBAGvC,QAAQ,EAAE,WAAW;cAQR,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAiB9E,KAAK,IAAI,IAAI;CAIvB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,CAExE"}
@@ -0,0 +1,45 @@
1
+ import { Resolver } from 'node:dns/promises';
2
+ import { DnsError, toError } from '../errors.js';
3
+ import { CachingDnsResolver, familyOf } from './resolver.js';
4
+ /**
5
+ * Unencrypted DNS over the platform resolver.
6
+ *
7
+ * v2 pulled in `dns-socket` and kept one module-level socket alive forever,
8
+ * then read `answers[0].data` without checking it existed. Node's own
9
+ * `dns.Resolver` does the same job with TTLs, retries and no dependency.
10
+ */
11
+ export class PlainResolver extends CachingDnsResolver {
12
+ #resolver;
13
+ constructor(settings) {
14
+ super(settings);
15
+ this.#resolver = new Resolver({ timeout: settings.timeoutMs, tries: 2 });
16
+ if (settings.plainServers.length > 0) {
17
+ this.#resolver.setServers([...settings.plainServers]);
18
+ }
19
+ }
20
+ async query(hostname, type) {
21
+ const family = familyOf(type);
22
+ try {
23
+ const answers = type === 'A'
24
+ ? await this.#resolver.resolve4(hostname, { ttl: true })
25
+ : await this.#resolver.resolve6(hostname, { ttl: true });
26
+ return answers.map((answer) => ({ address: answer.address, family, ttl: answer.ttl }));
27
+ }
28
+ catch (cause) {
29
+ const code = cause.code;
30
+ // "No records of this type" is a normal answer, not a failure — let the
31
+ // caller fall back to the other address family.
32
+ if (code === 'ENODATA' || code === 'ENOTFOUND')
33
+ return [];
34
+ throw new DnsError(hostname, toError(cause).message, { cause });
35
+ }
36
+ }
37
+ close() {
38
+ this.#resolver.cancel();
39
+ super.close();
40
+ }
41
+ }
42
+ export function createPlainResolver(settings) {
43
+ return new PlainResolver(settings);
44
+ }
45
+ //# sourceMappingURL=plain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain.js","sourceRoot":"","sources":["../../../src/core/dns/plain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AAE7E;;;;;;GAMG;AACH,MAAM,OAAO,aAAc,SAAQ,kBAAkB;IAC1C,SAAS,CAAW;IAE7B,YAAY,QAAqB;QAC/B,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,IAAI,CAAC,SAAS,GAAG,IAAI,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACzE,IAAI,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAEkB,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,IAAe;QAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,OAAO,GACX,IAAI,KAAK,GAAG;gBACV,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;gBACxD,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7D,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;YACnD,wEAAwE;YACxE,gDAAgD;YAChD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW;gBAAE,OAAO,EAAE,CAAC;YAC1D,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAEQ,KAAK;QACZ,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QACxB,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAqB;IACvD,OAAO,IAAI,aAAa,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { DnsRecord, DnsSettings } from '../types.js';
2
+ export type QueryType = 'A' | 'AAAA';
3
+ export interface DnsResolver {
4
+ /** Resolve to a single IP. Always rejects on failure — never returns undefined. */
5
+ resolve(hostname: string): Promise<string>;
6
+ close(): void;
7
+ }
8
+ /**
9
+ * Shared caching + family-preference logic.
10
+ *
11
+ * Two v2 bugs are fixed here:
12
+ * 1. `lookup()` caught every error and returned `undefined`, which the socket
13
+ * layer then passed to `net.connect` — silently dialling localhost.
14
+ * 2. The cache never expired, so a rotated IP stayed wrong for the whole
15
+ * session. Entries now expire on the record's own TTL (clamped).
16
+ */
17
+ export declare abstract class CachingDnsResolver implements DnsResolver {
18
+ #private;
19
+ protected readonly settings: DnsSettings;
20
+ constructor(settings: DnsSettings);
21
+ protected abstract query(hostname: string, type: QueryType): Promise<DnsRecord[]>;
22
+ resolve(hostname: string): Promise<string>;
23
+ close(): void;
24
+ }
25
+ /** Family that matches a query type, for building `DnsRecord`s. */
26
+ export declare function familyOf(type: QueryType): 4 | 6;
27
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../../src/core/dns/resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,8BAAsB,kBAAmB,YAAW,WAAW;;IAC7D,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;gBAI7B,QAAQ,EAAE,WAAW;IAQjC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAE3E,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAehD,KAAK,IAAI,IAAI;CAyBd;AAoBD,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,GAAG,CAAC,CAE/C"}
@@ -0,0 +1,81 @@
1
+ import { isIP } from 'node:net';
2
+ import { LRUCache } from 'lru-cache';
3
+ import { DnsError } from '../errors.js';
4
+ /**
5
+ * Shared caching + family-preference logic.
6
+ *
7
+ * Two v2 bugs are fixed here:
8
+ * 1. `lookup()` caught every error and returned `undefined`, which the socket
9
+ * layer then passed to `net.connect` — silently dialling localhost.
10
+ * 2. The cache never expired, so a rotated IP stayed wrong for the whole
11
+ * session. Entries now expire on the record's own TTL (clamped).
12
+ */
13
+ export class CachingDnsResolver {
14
+ settings;
15
+ #cache;
16
+ #inflight = new Map();
17
+ constructor(settings) {
18
+ this.settings = settings;
19
+ this.#cache = new LRUCache({
20
+ max: Math.max(1, settings.cacheSize),
21
+ ttl: settings.maxTtlSeconds * 1000,
22
+ });
23
+ }
24
+ async resolve(hostname) {
25
+ if (isIP(hostname) !== 0)
26
+ return hostname;
27
+ const cached = this.#cache.get(hostname);
28
+ if (cached !== undefined)
29
+ return cached;
30
+ // Collapse concurrent lookups for the same name into one query.
31
+ const existing = this.#inflight.get(hostname);
32
+ if (existing)
33
+ return existing;
34
+ const pending = this.#lookup(hostname).finally(() => this.#inflight.delete(hostname));
35
+ this.#inflight.set(hostname, pending);
36
+ return pending;
37
+ }
38
+ close() {
39
+ this.#cache.clear();
40
+ this.#inflight.clear();
41
+ }
42
+ async #lookup(hostname) {
43
+ const [primary, fallback] = queryOrder(this.settings.family);
44
+ let record = await this.#tryQuery(hostname, primary);
45
+ if (!record && fallback) {
46
+ record = await this.#tryQuery(hostname, fallback);
47
+ }
48
+ if (!record) {
49
+ throw new DnsError(hostname, `no ${primary}${fallback ? `/${fallback}` : ''} records`);
50
+ }
51
+ const ttlSeconds = clamp(record.ttl, this.settings.minTtlSeconds, this.settings.maxTtlSeconds);
52
+ this.#cache.set(hostname, record.address, { ttl: ttlSeconds * 1000 });
53
+ return record.address;
54
+ }
55
+ async #tryQuery(hostname, type) {
56
+ const records = await this.query(hostname, type);
57
+ return records.find((record) => isIP(record.address) !== 0);
58
+ }
59
+ }
60
+ function queryOrder(family) {
61
+ switch (family) {
62
+ case 'ipv4':
63
+ return ['A'];
64
+ case 'ipv6':
65
+ return ['AAAA'];
66
+ case 'ipv6-first':
67
+ return ['AAAA', 'A'];
68
+ default:
69
+ return ['A', 'AAAA'];
70
+ }
71
+ }
72
+ function clamp(value, min, max) {
73
+ if (!Number.isFinite(value) || value <= 0)
74
+ return min;
75
+ return Math.min(Math.max(value, min), max);
76
+ }
77
+ /** Family that matches a query type, for building `DnsRecord`s. */
78
+ export function familyOf(type) {
79
+ return type === 'A' ? 4 : 6;
80
+ }
81
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../../src/core/dns/resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Typed errors. v2 swallowed nearly every failure into a debug log, which is
3
+ * why a broken DNS lookup silently became a connection to 127.0.0.1. Everything
4
+ * here is meant to reach the caller.
5
+ */
6
+ export declare class GreenTunnelError extends Error {
7
+ readonly name: string;
8
+ }
9
+ export declare class DnsError extends GreenTunnelError {
10
+ readonly name = "DnsError";
11
+ readonly hostname: string;
12
+ constructor(hostname: string, message: string, options?: {
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ export declare class ConnectError extends GreenTunnelError {
17
+ readonly name = "ConnectError";
18
+ readonly host: string;
19
+ readonly port: number;
20
+ constructor(host: string, port: number, message: string, options?: {
21
+ cause?: unknown;
22
+ });
23
+ }
24
+ export declare class ProtocolError extends GreenTunnelError {
25
+ readonly name = "ProtocolError";
26
+ }
27
+ export declare class SystemProxyError extends GreenTunnelError {
28
+ readonly name = "SystemProxyError";
29
+ }
30
+ /** Narrow an unknown catch binding to something with a message. */
31
+ export declare function toError(value: unknown): Error;
32
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,EAAE,MAAM,CAAsB;CACrD;AAED,qBAAa,QAAS,SAAQ,gBAAgB;IAC5C,SAAkB,IAAI,cAAc;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI7E;AAED,qBAAa,YAAa,SAAQ,gBAAgB;IAChD,SAAkB,IAAI,kBAAkB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKvF;AAED,qBAAa,aAAc,SAAQ,gBAAgB;IACjD,SAAkB,IAAI,mBAAmB;CAC1C;AAED,qBAAa,gBAAiB,SAAQ,gBAAgB;IACpD,SAAkB,IAAI,sBAAsB;CAC7C;AAED,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE7C"}