@amritk/nish-x86_64-linux 0.24.0 → 0.26.0
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.
- package/bin/nish +0 -0
- package/docs/AI.md +45 -4
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/host.d.ts +12 -0
- package/runtime/core/host.mjs +74 -2
- package/runtime/nish.d.ts +148 -0
- package/runtime/nish.h +59 -0
- package/runtime/nish.mjs +25 -0
- package/runtime/node.mjs +136 -6
- package/runtime/runtime-net.c +44 -3
- package/runtime/runtime-os.c +351 -18
- package/scripts/build.sh +13 -3
- package/std/README.md +27 -10
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +132 -20
- package/std/compress/deflate.ts +638 -248
- package/std/compress/gzip.ts +20 -6
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +7 -2
- package/std/net/http1.ts +9 -9
- package/std/net/postgres.ts +259 -26
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/dns-resolver` — a DNS stub resolver with no I/O of its own: it
|
|
3
|
+
* says which bytes to send to which server, reads the bytes that came back,
|
|
4
|
+
* and answers each lookup's addresses, while the program's own loop owns the
|
|
5
|
+
* sockets and the clock.
|
|
6
|
+
*
|
|
7
|
+
* const resolver = new DnsResolver(config, hosts, 4);
|
|
8
|
+
* const lookup = resolver.start("example.com", 443, now);
|
|
9
|
+
* // in the loop: send what nextOutgoing gives, feed replies to receive,
|
|
10
|
+
* // call tick(now) when nextDeadlineNanos() passes, then read result.
|
|
11
|
+
*
|
|
12
|
+
* `nish/net/dns` drives it over `nish:net` sockets and adds a blocking
|
|
13
|
+
* `resolveHost`; this module never imports `nish:net`, never reads a clock
|
|
14
|
+
* and never reads a file, so it compiles for `wasm32` and a test drives it
|
|
15
|
+
* with scripted packets.
|
|
16
|
+
*
|
|
17
|
+
* A lookup asks A and AAAA at once, one query each. Each query tries the
|
|
18
|
+
* servers of its `DnsConfig` in turn, `attempts` rounds of `timeout` seconds
|
|
19
|
+
* a try, follows a CNAME the reply leaves unanswered with a query of its own,
|
|
20
|
+
* at most `DNS_RESOLVER_MAX_CNAME_HOPS` of them, and asks again over TCP when
|
|
21
|
+
* a reply comes back truncated. A name goes through the search list as
|
|
22
|
+
* resolv.conf(5) describes. `localhost`, an address literal and a hosts entry
|
|
23
|
+
* answer at once with no packet.
|
|
24
|
+
*
|
|
25
|
+
* A reply from any address or port but the server's, or with another ID or
|
|
26
|
+
* question, is dropped and counted, and the query keeps waiting for the true
|
|
27
|
+
* one: a spoofer can lose a race, never end a lookup. The ID of each send is
|
|
28
|
+
* the caller's, given to `nextOutgoing`: `nish/net/dns` draws it from
|
|
29
|
+
* `crypto.getRandomValues`, which a `wasm32` build cannot reach, so this
|
|
30
|
+
* module asks for none and a test can script its IDs.
|
|
31
|
+
*
|
|
32
|
+
* Every buffer is made in the constructor; a lookup allocates only the
|
|
33
|
+
* sixteen transient bytes `dnsParseAddress` takes to test a name for an
|
|
34
|
+
* address literal.
|
|
35
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
36
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
DNS_ADDRESS_BYTES,
|
|
41
|
+
DNS_ERR_ID,
|
|
42
|
+
DNS_ERR_QUESTION,
|
|
43
|
+
DNS_ERR_RCODE,
|
|
44
|
+
DNS_MAX_ANSWERS,
|
|
45
|
+
DNS_MAX_LABEL_BYTES,
|
|
46
|
+
DNS_MAX_NAME_BYTES,
|
|
47
|
+
DNS_NOERROR,
|
|
48
|
+
DNS_NXDOMAIN,
|
|
49
|
+
DNS_QUERY_MAX_BYTES,
|
|
50
|
+
DNS_TYPE_A,
|
|
51
|
+
DNS_TYPE_AAAA,
|
|
52
|
+
DnsReply,
|
|
53
|
+
dnsBuildQuery,
|
|
54
|
+
dnsParseReply,
|
|
55
|
+
} from "nish/net/dns-message"
|
|
56
|
+
import { setU16BE } from "nish/bytes"
|
|
57
|
+
import { DnsConfig, DnsHosts, dnsHostsFind, dnsParseAddress } from "nish/net/dns-config"
|
|
58
|
+
|
|
59
|
+
/** `result` for a lookup that has not finished. */
|
|
60
|
+
export const DNS_RESOLVE_PENDING: i32 = -40
|
|
61
|
+
/** The name does not exist, or has no address: NXDOMAIN or NODATA from every candidate. */
|
|
62
|
+
export const DNS_RESOLVE_NOT_FOUND: i32 = -41
|
|
63
|
+
/** No server gave an answer: each timed out, failed or was refused, `attempts` rounds over. */
|
|
64
|
+
export const DNS_RESOLVE_NO_ANSWER: i32 = -42
|
|
65
|
+
/**
|
|
66
|
+
* The CNAME chain loops across queries back to a name already asked, or needs
|
|
67
|
+
* more than `DNS_RESOLVER_MAX_CNAME_HOPS` queries. A loop closed inside one
|
|
68
|
+
* reply is `DNS_RESOLVE_NOT_FOUND`, as that reply names no address.
|
|
69
|
+
*/
|
|
70
|
+
export const DNS_RESOLVE_CNAME: i32 = -43
|
|
71
|
+
/** `start` found every lookup slot in use. */
|
|
72
|
+
export const DNS_RESOLVE_BUSY: i32 = -44
|
|
73
|
+
/** `resolveHost`'s time ran out before the lookup finished. */
|
|
74
|
+
export const DNS_RESOLVE_TIMEOUT: i32 = -45
|
|
75
|
+
|
|
76
|
+
/** The most queries a CNAME chain may add to one query before it is `DNS_RESOLVE_CNAME`. */
|
|
77
|
+
export const DNS_RESOLVER_MAX_CNAME_HOPS: i32 = 8
|
|
78
|
+
/** The longest reply read over TCP; a longer length prefix fails the server. */
|
|
79
|
+
export const DNS_TCP_MAX_REPLY: i32 = 16384
|
|
80
|
+
/** The most addresses one lookup answers: `DNS_MAX_ANSWERS` A and as many AAAA. */
|
|
81
|
+
export const DNS_RESOLVE_MAX_ADDRESSES: i32 = 2 * DNS_MAX_ANSWERS
|
|
82
|
+
/** The most lookups one resolver holds at once. */
|
|
83
|
+
export const DNS_RESOLVER_MAX_LOOKUPS: i32 = 64
|
|
84
|
+
/** `nextOutgoing`'s `meta[0]`: send `out` as one datagram to `to` from a new socket. */
|
|
85
|
+
export const DNS_SEND_UDP: i32 = 1
|
|
86
|
+
/** `nextOutgoing`'s `meta[0]`: connect a new TCP socket to `to` and write `out`. */
|
|
87
|
+
export const DNS_SEND_TCP: i32 = 2
|
|
88
|
+
/** `nextOutgoing`'s `meta[0]`: close the socket the query held, if any. */
|
|
89
|
+
export const DNS_CLOSE: i32 = 3
|
|
90
|
+
/** The room `nextOutgoing` needs in `out`: a query and TCP's two-byte length. */
|
|
91
|
+
export const DNS_OUTGOING_MAX_BYTES: i32 = DNS_QUERY_MAX_BYTES + 2
|
|
92
|
+
|
|
93
|
+
// A query's state. A lookup's: free, running, or finished with its result held.
|
|
94
|
+
const DNS_Q_IDLE: i32 = 0
|
|
95
|
+
const DNS_Q_UDP: i32 = 1
|
|
96
|
+
const DNS_Q_TCP: i32 = 2
|
|
97
|
+
const DNS_Q_DONE: i32 = 3
|
|
98
|
+
const DNS_L_FREE: i32 = 0
|
|
99
|
+
const DNS_L_RUNNING: i32 = 1
|
|
100
|
+
const DNS_L_DONE: i32 = 2
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A stub resolver of `maxLookups` concurrent lookups over `config`'s servers,
|
|
104
|
+
* answering from `hosts` first when it is given. Query `q` belongs to lookup
|
|
105
|
+
* `q / 2`, and asks A when `q` is even and AAAA when it is odd.
|
|
106
|
+
*/
|
|
107
|
+
export class DnsResolver {
|
|
108
|
+
config: DnsConfig
|
|
109
|
+
hosts: DnsHosts | null
|
|
110
|
+
/** Each query's bytes, from offset 0: the header, the name and the question's type and class. */
|
|
111
|
+
queries: u8[][]
|
|
112
|
+
/** Each query's name in wire form, labels and root, as asked; and that name lowercased, the start of its chain. */
|
|
113
|
+
names: u8[][]
|
|
114
|
+
starts: u8[][]
|
|
115
|
+
/** Each query's TCP reply as it arrives, without its two-byte length. */
|
|
116
|
+
streams: u8[][]
|
|
117
|
+
/** Each query's addresses and their TTLs, once it has its answer. */
|
|
118
|
+
answers: u8[][]
|
|
119
|
+
answerTtls: i32[][]
|
|
120
|
+
/** Each lookup's name in wire form, without its root byte, as `start` gave it. */
|
|
121
|
+
bases: u8[][]
|
|
122
|
+
/** Each lookup's answer, A first. */
|
|
123
|
+
lookupAddresses: u8[][]
|
|
124
|
+
lookupTtls: i32[][]
|
|
125
|
+
reply: DnsReply
|
|
126
|
+
scratch: u8[]
|
|
127
|
+
queryLength: i32[]
|
|
128
|
+
nameLength: i32[]
|
|
129
|
+
state: i32[]
|
|
130
|
+
/** The outcome of a finished query: its address count, or `DNS_RESOLVE_*`. */
|
|
131
|
+
outcome: i32[]
|
|
132
|
+
/** The action `nextOutgoing` owes the driver for a query: 0 or a `DNS_SEND_*`/`DNS_CLOSE`. */
|
|
133
|
+
pending: i32[]
|
|
134
|
+
server: i32[]
|
|
135
|
+
round: i32[]
|
|
136
|
+
hops: i32[]
|
|
137
|
+
deadline: i64[]
|
|
138
|
+
/** Bytes of the TCP reply read so far, its two-byte length included, and that length once read. */
|
|
139
|
+
streamHave: i32[]
|
|
140
|
+
streamWant: i32[]
|
|
141
|
+
baseLength: i32[]
|
|
142
|
+
/** Whether a finished query's name was NXDOMAIN, which moves the search on, rather than NODATA, which ends it. */
|
|
143
|
+
nxdomain: boolean[]
|
|
144
|
+
/** Whether a lookup's name ended in a dot, so no search suffix applies. */
|
|
145
|
+
absolute: boolean[]
|
|
146
|
+
/** The candidate a lookup is asking, by `dnsResolverCandidate`'s order, and how many it has. */
|
|
147
|
+
candidate: i32[]
|
|
148
|
+
candidates: i32[]
|
|
149
|
+
/** The error a lookup ends with if no later candidate answers: a family that reached no server while the other was NXDOMAIN, or 0. */
|
|
150
|
+
fallback: i32[]
|
|
151
|
+
dots: i32[]
|
|
152
|
+
lookupState: i32[]
|
|
153
|
+
lookupResult: i32[]
|
|
154
|
+
lookupPort: i32[]
|
|
155
|
+
maxLookups: i32 = 0
|
|
156
|
+
/** Where `nextOutgoing` looks first, so no query waits behind another forever. */
|
|
157
|
+
cursor: i32 = 0
|
|
158
|
+
/** Datagrams dropped for coming from another address or port than the server's. */
|
|
159
|
+
droppedSource: i32 = 0
|
|
160
|
+
/** Replies dropped for another ID. */
|
|
161
|
+
droppedId: i32 = 0
|
|
162
|
+
/** Replies dropped for another question. */
|
|
163
|
+
droppedQuestion: i32 = 0
|
|
164
|
+
/** Datagrams dropped as malformed, or arriving for a query not waiting on UDP. */
|
|
165
|
+
droppedMalformed: i32 = 0
|
|
166
|
+
/** Tries that timed out. */
|
|
167
|
+
timeouts: i32 = 0
|
|
168
|
+
/** Tries that ended in SERVFAIL, REFUSED, a bad TCP reply or a socket failure. */
|
|
169
|
+
serverFailures: i32 = 0
|
|
170
|
+
/** Truncated replies asked again over TCP. */
|
|
171
|
+
tcpRetries: i32 = 0
|
|
172
|
+
/** Queries a CNAME chain added. */
|
|
173
|
+
cnameQueries: i32 = 0
|
|
174
|
+
/** Queries handed to the driver, UDP and TCP. */
|
|
175
|
+
sent: i32 = 0
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A resolver with room for `maxLookups` lookups at once, every buffer made
|
|
179
|
+
* now. Panics unless `maxLookups` is 1 to `DNS_RESOLVER_MAX_LOOKUPS`.
|
|
180
|
+
* `config` and `hosts` are read at each `start`, so a program may parse
|
|
181
|
+
* them again between lookups.
|
|
182
|
+
*/
|
|
183
|
+
constructor(config: DnsConfig, hosts: DnsHosts | null, maxLookups: i32) {
|
|
184
|
+
if (maxLookups < 1 || maxLookups > DNS_RESOLVER_MAX_LOOKUPS) {
|
|
185
|
+
panic(`DnsResolver: ${maxLookups} lookups is outside 1 to ${DNS_RESOLVER_MAX_LOOKUPS}`)
|
|
186
|
+
}
|
|
187
|
+
this.config = config
|
|
188
|
+
this.hosts = hosts
|
|
189
|
+
this.maxLookups = maxLookups
|
|
190
|
+
const n: i32 = maxLookups * 2
|
|
191
|
+
this.queries = []
|
|
192
|
+
this.names = []
|
|
193
|
+
this.starts = []
|
|
194
|
+
this.streams = []
|
|
195
|
+
this.answers = []
|
|
196
|
+
this.answerTtls = []
|
|
197
|
+
this.bases = []
|
|
198
|
+
this.lookupAddresses = []
|
|
199
|
+
this.lookupTtls = []
|
|
200
|
+
let q: i32 = 0
|
|
201
|
+
while (q < n) {
|
|
202
|
+
this.queries.push(new Array<u8>(DNS_QUERY_MAX_BYTES))
|
|
203
|
+
this.names.push(new Array<u8>(DNS_MAX_NAME_BYTES))
|
|
204
|
+
this.starts.push(new Array<u8>(DNS_MAX_NAME_BYTES))
|
|
205
|
+
this.streams.push(new Array<u8>(DNS_TCP_MAX_REPLY))
|
|
206
|
+
this.answers.push(new Array<u8>(DNS_MAX_ANSWERS * DNS_ADDRESS_BYTES))
|
|
207
|
+
this.answerTtls.push(new Array<i32>(DNS_MAX_ANSWERS))
|
|
208
|
+
q += 1
|
|
209
|
+
}
|
|
210
|
+
let l: i32 = 0
|
|
211
|
+
while (l < maxLookups) {
|
|
212
|
+
this.bases.push(new Array<u8>(DNS_MAX_NAME_BYTES))
|
|
213
|
+
this.lookupAddresses.push(new Array<u8>(DNS_RESOLVE_MAX_ADDRESSES * DNS_ADDRESS_BYTES))
|
|
214
|
+
this.lookupTtls.push(new Array<i32>(DNS_RESOLVE_MAX_ADDRESSES))
|
|
215
|
+
l += 1
|
|
216
|
+
}
|
|
217
|
+
this.reply = new DnsReply()
|
|
218
|
+
this.scratch = new Array<u8>(DNS_QUERY_MAX_BYTES)
|
|
219
|
+
this.queryLength = new Array<i32>(n)
|
|
220
|
+
this.nameLength = new Array<i32>(n)
|
|
221
|
+
this.state = new Array<i32>(n)
|
|
222
|
+
this.outcome = new Array<i32>(n)
|
|
223
|
+
this.pending = new Array<i32>(n)
|
|
224
|
+
this.server = new Array<i32>(n)
|
|
225
|
+
this.round = new Array<i32>(n)
|
|
226
|
+
this.hops = new Array<i32>(n)
|
|
227
|
+
this.deadline = new Array<i64>(n)
|
|
228
|
+
this.streamHave = new Array<i32>(n)
|
|
229
|
+
this.streamWant = new Array<i32>(n)
|
|
230
|
+
this.baseLength = new Array<i32>(maxLookups)
|
|
231
|
+
this.nxdomain = new Array<boolean>(n)
|
|
232
|
+
this.absolute = new Array<boolean>(maxLookups)
|
|
233
|
+
this.candidate = new Array<i32>(maxLookups)
|
|
234
|
+
this.candidates = new Array<i32>(maxLookups)
|
|
235
|
+
this.fallback = new Array<i32>(maxLookups)
|
|
236
|
+
this.dots = new Array<i32>(maxLookups)
|
|
237
|
+
this.lookupState = new Array<i32>(maxLookups)
|
|
238
|
+
this.lookupResult = new Array<i32>(maxLookups)
|
|
239
|
+
this.lookupPort = new Array<i32>(maxLookups)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Begins a lookup of `name`, whose addresses will carry `port`, at
|
|
244
|
+
* `nowNanos` on the caller's monotonic clock, and answers its id, or a
|
|
245
|
+
* negative code: `DNS_RESOLVE_BUSY` when every slot is in use, or the
|
|
246
|
+
* `DNS_ERR_*` `dnsBuildQuery` gives a name that is not one.
|
|
247
|
+
*
|
|
248
|
+
* `localhost` and any name under it answer 127.0.0.1 and ::1 (RFC 6761
|
|
249
|
+
* §6.3), an address literal answers itself, and a name in `hosts` answers
|
|
250
|
+
* its entries, each at once and with no packet: `result` has them now.
|
|
251
|
+
* Panics when `port` is not 16 bits.
|
|
252
|
+
*/
|
|
253
|
+
start(name: string, port: i32, nowNanos: i64): i32 {
|
|
254
|
+
if (port < 0 || port > 0xffff) {
|
|
255
|
+
panic(`DnsResolver.start: the port ${port} is not 16 bits`)
|
|
256
|
+
}
|
|
257
|
+
let l: i32 = 0
|
|
258
|
+
while (l < this.maxLookups && this.lookupState[l] !== DNS_L_FREE) {
|
|
259
|
+
l += 1
|
|
260
|
+
}
|
|
261
|
+
if (l === this.maxLookups) {
|
|
262
|
+
return DNS_RESOLVE_BUSY
|
|
263
|
+
}
|
|
264
|
+
this.state[l * 2] = DNS_Q_IDLE
|
|
265
|
+
this.state[l * 2 + 1] = DNS_Q_IDLE
|
|
266
|
+
const at: u8[] = this.lookupAddresses[l]
|
|
267
|
+
const ttls: i32[] = this.lookupTtls[l]
|
|
268
|
+
if (dnsResolverIsLocalhost(name)) {
|
|
269
|
+
dnsResolverLoopback(at, port)
|
|
270
|
+
ttls[0] = 0
|
|
271
|
+
ttls[1] = 0
|
|
272
|
+
return dnsResolverFinishNow(this, l, 2)
|
|
273
|
+
}
|
|
274
|
+
if (dnsResolverLiteral(this.scratch, name, at, port)) {
|
|
275
|
+
ttls[0] = 0
|
|
276
|
+
return dnsResolverFinishNow(this, l, 1)
|
|
277
|
+
}
|
|
278
|
+
const built: i32 = dnsBuildQuery(this.scratch, 0, name, DNS_TYPE_A)
|
|
279
|
+
if (built < 0) {
|
|
280
|
+
return built
|
|
281
|
+
}
|
|
282
|
+
const hosts: DnsHosts | null = this.hosts
|
|
283
|
+
if (hosts !== null) {
|
|
284
|
+
const v4: i32 = dnsHostsFind(hosts, name, DNS_TYPE_A, port, this.answers[l * 2], DNS_MAX_ANSWERS)
|
|
285
|
+
const v6: i32 = dnsHostsFind(hosts, name, DNS_TYPE_AAAA, port, this.answers[l * 2 + 1], DNS_MAX_ANSWERS)
|
|
286
|
+
if (v4 + v6 > 0) {
|
|
287
|
+
dnsResolverGather(this, l, v4, v6, false)
|
|
288
|
+
return dnsResolverFinishNow(this, l, v4 + v6)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// The wire name is the query's bytes after the header, less the root byte
|
|
292
|
+
// and the question's type and class.
|
|
293
|
+
const base: u8[] = this.bases[l]
|
|
294
|
+
const baseLength: i32 = built - 12 - 5
|
|
295
|
+
let i: i32 = 0
|
|
296
|
+
let dots: i32 = 0
|
|
297
|
+
while (i < baseLength && i < toI32(base.length)) {
|
|
298
|
+
base[i] = this.scratch[12 + i]
|
|
299
|
+
i += 1
|
|
300
|
+
}
|
|
301
|
+
// Every length byte past the first is a dot of the name.
|
|
302
|
+
let p: i32 = 0
|
|
303
|
+
while (p >= 0 && p < baseLength && p < toI32(base.length)) {
|
|
304
|
+
p += toI32(base[p]) + 1
|
|
305
|
+
if (p < baseLength) {
|
|
306
|
+
dots += 1
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.baseLength[l] = baseLength
|
|
310
|
+
this.absolute[l] = toI32(name.charCodeAt(toI32(name.length) - 1)) === 46
|
|
311
|
+
this.dots[l] = dots
|
|
312
|
+
this.candidate[l] = 0
|
|
313
|
+
this.fallback[l] = 0
|
|
314
|
+
this.candidates[l] = this.absolute[l] ? 1 : this.config.searchCount + 1
|
|
315
|
+
this.lookupPort[l] = port
|
|
316
|
+
this.lookupState[l] = DNS_L_RUNNING
|
|
317
|
+
this.lookupResult[l] = DNS_RESOLVE_PENDING
|
|
318
|
+
if (!dnsResolverNextCandidate(this, l, nowNanos)) {
|
|
319
|
+
dnsResolverFinish(this, l, DNS_RESOLVE_NOT_FOUND)
|
|
320
|
+
}
|
|
321
|
+
return l
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The next thing the driver must do, or -1 when there is nothing: the
|
|
326
|
+
* query it is for, with `meta[0]` one of `DNS_SEND_UDP`, `DNS_SEND_TCP` or
|
|
327
|
+
* `DNS_CLOSE`. A send writes `meta[1]` bytes to `out` from offset 0 (TCP's
|
|
328
|
+
* with their two-byte length) and the server's address to `to`, and draws
|
|
329
|
+
* the query `id`, which must be new and random for each send since it is
|
|
330
|
+
* half of what keeps an off-path spoofer out; it also replaces whatever
|
|
331
|
+
* socket the query held, so the driver closes that one first. Panics when
|
|
332
|
+
* `out` is shorter than `DNS_OUTGOING_MAX_BYTES`, `to` shorter than 18
|
|
333
|
+
* bytes or `meta` than 2, or when `id` is not 16 bits.
|
|
334
|
+
*/
|
|
335
|
+
nextOutgoing(out: u8[], to: u8[], meta: i32[], id: i32): i32 {
|
|
336
|
+
if (id < 0 || id > 0xffff) {
|
|
337
|
+
panic(`DnsResolver.nextOutgoing: the id ${id} is not 16 bits`)
|
|
338
|
+
}
|
|
339
|
+
if (
|
|
340
|
+
toI32(out.length) < DNS_OUTGOING_MAX_BYTES ||
|
|
341
|
+
toI32(to.length) < DNS_ADDRESS_BYTES ||
|
|
342
|
+
toI32(meta.length) < 2
|
|
343
|
+
) {
|
|
344
|
+
panic("DnsResolver.nextOutgoing: out, to or meta is too short")
|
|
345
|
+
}
|
|
346
|
+
const n: i32 = this.maxLookups * 2
|
|
347
|
+
let k: i32 = 0
|
|
348
|
+
while (k < n) {
|
|
349
|
+
const q: i32 = (this.cursor + k) % n
|
|
350
|
+
const kind: i32 = this.pending[q]
|
|
351
|
+
if (kind !== 0) {
|
|
352
|
+
this.pending[q] = 0
|
|
353
|
+
this.cursor = (q + 1) % n
|
|
354
|
+
meta[0] = kind
|
|
355
|
+
meta[1] = 0
|
|
356
|
+
if (kind === DNS_CLOSE) {
|
|
357
|
+
return q
|
|
358
|
+
}
|
|
359
|
+
const query: u8[] = this.queries[q]
|
|
360
|
+
setU16BE(query, 0, toU16(id))
|
|
361
|
+
const length: i32 = this.queryLength[q]
|
|
362
|
+
const skip: i32 = kind === DNS_SEND_TCP ? 2 : 0
|
|
363
|
+
if (skip === 2) {
|
|
364
|
+
setU16BE(out, 0, toU16(length))
|
|
365
|
+
}
|
|
366
|
+
let i: i32 = 0
|
|
367
|
+
while (i < length && i < toI32(query.length)) {
|
|
368
|
+
out[skip + i] = query[i]
|
|
369
|
+
i += 1
|
|
370
|
+
}
|
|
371
|
+
meta[1] = skip + length
|
|
372
|
+
const from: i32 = this.server[q] * DNS_ADDRESS_BYTES
|
|
373
|
+
i = 0
|
|
374
|
+
while (i < DNS_ADDRESS_BYTES && i < toI32(to.length)) {
|
|
375
|
+
to[i] = this.config.servers[from + i]
|
|
376
|
+
i += 1
|
|
377
|
+
}
|
|
378
|
+
this.sent = dnsResolverCount(this.sent)
|
|
379
|
+
return q
|
|
380
|
+
}
|
|
381
|
+
k += 1
|
|
382
|
+
}
|
|
383
|
+
return -1
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* A datagram of `len` bytes from `buf[0]` that arrived from `from` on query
|
|
388
|
+
* `q`'s socket at `nowNanos`. One from another address or port than the
|
|
389
|
+
* server's, with another ID or question, or malformed, is dropped and
|
|
390
|
+
* counted, and the query keeps waiting. One that answers the query with an
|
|
391
|
+
* RCODE the resolver does not read, FORMERR or NOTIMP, fails the server. Panics when `q` is not a query or
|
|
392
|
+
* `len` is outside `buf`.
|
|
393
|
+
*/
|
|
394
|
+
receive(q: i32, buf: u8[], len: i32, from: u8[], nowNanos: i64): void {
|
|
395
|
+
dnsResolverCheckQuery(this, q, "receive")
|
|
396
|
+
if (len < 0 || len > toI32(buf.length) || toI32(from.length) < DNS_ADDRESS_BYTES) {
|
|
397
|
+
panic("DnsResolver.receive: len is outside buf, or from is shorter than 18 bytes")
|
|
398
|
+
}
|
|
399
|
+
if (this.state[q] !== DNS_Q_UDP) {
|
|
400
|
+
this.droppedMalformed = dnsResolverCount(this.droppedMalformed)
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
const at: i32 = this.server[q] * DNS_ADDRESS_BYTES
|
|
404
|
+
let i: i32 = 0
|
|
405
|
+
while (i < DNS_ADDRESS_BYTES && i < toI32(from.length)) {
|
|
406
|
+
if (from[i] !== this.config.servers[at + i]) {
|
|
407
|
+
this.droppedSource = dnsResolverCount(this.droppedSource)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
i += 1
|
|
411
|
+
}
|
|
412
|
+
const status: i32 = dnsParseReply(
|
|
413
|
+
buf,
|
|
414
|
+
len,
|
|
415
|
+
this.queries[q],
|
|
416
|
+
this.queryLength[q],
|
|
417
|
+
this.lookupPort[q / 2],
|
|
418
|
+
this.reply
|
|
419
|
+
)
|
|
420
|
+
if (status === DNS_ERR_ID) {
|
|
421
|
+
this.droppedId = dnsResolverCount(this.droppedId)
|
|
422
|
+
} else if (status === DNS_ERR_QUESTION) {
|
|
423
|
+
this.droppedQuestion = dnsResolverCount(this.droppedQuestion)
|
|
424
|
+
} else if (status === DNS_ERR_RCODE) {
|
|
425
|
+
dnsResolverServerFailed(this, q, nowNanos)
|
|
426
|
+
} else if (status < 0) {
|
|
427
|
+
this.droppedMalformed = dnsResolverCount(this.droppedMalformed)
|
|
428
|
+
} else if (this.reply.truncated) {
|
|
429
|
+
this.tcpRetries = dnsResolverCount(this.tcpRetries)
|
|
430
|
+
dnsResolverSend(this, q, DNS_Q_TCP, nowNanos)
|
|
431
|
+
} else {
|
|
432
|
+
dnsResolverAnswer(this, q, status, nowNanos)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* `len` bytes from `buf[0]` read from query `q`'s TCP socket at
|
|
438
|
+
* `nowNanos`. A length over `DNS_TCP_MAX_REPLY`, a reply that is malformed
|
|
439
|
+
* or answers another question, or one still truncated fails the server,
|
|
440
|
+
* since a stream cannot carry a second, true reply after it. Panics as
|
|
441
|
+
* `receive` does.
|
|
442
|
+
*/
|
|
443
|
+
receiveStream(q: i32, buf: u8[], len: i32, nowNanos: i64): void {
|
|
444
|
+
dnsResolverCheckQuery(this, q, "receiveStream")
|
|
445
|
+
if (len < 0 || len > toI32(buf.length)) {
|
|
446
|
+
panic("DnsResolver.receiveStream: len is outside buf")
|
|
447
|
+
}
|
|
448
|
+
if (this.state[q] !== DNS_Q_TCP) {
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
const stream: u8[] = this.streams[q]
|
|
452
|
+
let i: i32 = 0
|
|
453
|
+
while (i < len) {
|
|
454
|
+
const have: i32 = this.streamHave[q]
|
|
455
|
+
if (have < 2) {
|
|
456
|
+
this.streamWant[q] = (this.streamWant[q] << 8) | toI32(buf[i])
|
|
457
|
+
if (have === 1 && (this.streamWant[q] < 12 || this.streamWant[q] > DNS_TCP_MAX_REPLY)) {
|
|
458
|
+
dnsResolverServerFailed(this, q, nowNanos)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
} else if (have - 2 < this.streamWant[q]) {
|
|
462
|
+
stream[have - 2] = buf[i]
|
|
463
|
+
}
|
|
464
|
+
this.streamHave[q] = have + 1
|
|
465
|
+
i += 1
|
|
466
|
+
}
|
|
467
|
+
const want: i32 = this.streamWant[q]
|
|
468
|
+
if (this.streamHave[q] < 2 || this.streamHave[q] - 2 < want) {
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
const status: i32 = dnsParseReply(
|
|
472
|
+
stream,
|
|
473
|
+
want,
|
|
474
|
+
this.queries[q],
|
|
475
|
+
this.queryLength[q],
|
|
476
|
+
this.lookupPort[q / 2],
|
|
477
|
+
this.reply
|
|
478
|
+
)
|
|
479
|
+
if (status < 0 || this.reply.truncated) {
|
|
480
|
+
dnsResolverServerFailed(this, q, nowNanos)
|
|
481
|
+
} else {
|
|
482
|
+
dnsResolverAnswer(this, q, status, nowNanos)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The driver's word that query `q`'s socket failed at `nowNanos`: it could
|
|
488
|
+
* not be opened or written, the connection was refused, or the stream
|
|
489
|
+
* ended before the reply did. The query moves to the next server. Panics
|
|
490
|
+
* when `q` is not a query.
|
|
491
|
+
*/
|
|
492
|
+
fail(q: i32, nowNanos: i64): void {
|
|
493
|
+
dnsResolverCheckQuery(this, q, "fail")
|
|
494
|
+
if (this.state[q] === DNS_Q_UDP || this.state[q] === DNS_Q_TCP) {
|
|
495
|
+
dnsResolverServerFailed(this, q, nowNanos)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Moves every query whose try has run past its deadline at `nowNanos` to the next server. */
|
|
500
|
+
tick(nowNanos: i64): void {
|
|
501
|
+
const n: i32 = this.maxLookups * 2
|
|
502
|
+
let q: i32 = 0
|
|
503
|
+
while (q < n) {
|
|
504
|
+
if ((this.state[q] === DNS_Q_UDP || this.state[q] === DNS_Q_TCP) && this.deadline[q] <= nowNanos) {
|
|
505
|
+
this.timeouts = dnsResolverCount(this.timeouts)
|
|
506
|
+
dnsResolverServerFailed(this, q, nowNanos)
|
|
507
|
+
}
|
|
508
|
+
q += 1
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** The earliest deadline of any try in flight, on the caller's clock, or -1 when none is. */
|
|
513
|
+
nextDeadlineNanos(): i64 {
|
|
514
|
+
const n: i32 = this.maxLookups * 2
|
|
515
|
+
let soonest: i64 = -1
|
|
516
|
+
let q: i32 = 0
|
|
517
|
+
while (q < n) {
|
|
518
|
+
if (
|
|
519
|
+
(this.state[q] === DNS_Q_UDP || this.state[q] === DNS_Q_TCP) &&
|
|
520
|
+
(soonest < 0 || this.deadline[q] < soonest)
|
|
521
|
+
) {
|
|
522
|
+
soonest = this.deadline[q]
|
|
523
|
+
}
|
|
524
|
+
q += 1
|
|
525
|
+
}
|
|
526
|
+
return soonest
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Lookup `lookup`'s answer: `DNS_RESOLVE_PENDING` while it runs, or, once
|
|
531
|
+
* it has finished, its address count with the addresses written to `out`
|
|
532
|
+
* (A first, then AAAA, 18 bytes each, with the port `start` was given) and
|
|
533
|
+
* their TTLs in seconds to `ttls`, or a negative `DNS_RESOLVE_*` code. A
|
|
534
|
+
* finished lookup's slot is free again once this has answered it. Panics
|
|
535
|
+
* when `lookup` is not a slot that `start` gave out, or when `out` or
|
|
536
|
+
* `ttls` has no room for `DNS_RESOLVE_MAX_ADDRESSES`.
|
|
537
|
+
*/
|
|
538
|
+
result(lookup: i32, out: u8[], ttls: i32[]): i32 {
|
|
539
|
+
if (lookup < 0 || lookup >= this.maxLookups || this.lookupState[lookup] === DNS_L_FREE) {
|
|
540
|
+
panic(`DnsResolver.result: ${lookup} is not a lookup in progress`)
|
|
541
|
+
}
|
|
542
|
+
if (
|
|
543
|
+
toI32(out.length) < DNS_RESOLVE_MAX_ADDRESSES * DNS_ADDRESS_BYTES ||
|
|
544
|
+
toI32(ttls.length) < DNS_RESOLVE_MAX_ADDRESSES
|
|
545
|
+
) {
|
|
546
|
+
panic("DnsResolver.result: out or ttls has no room for DNS_RESOLVE_MAX_ADDRESSES")
|
|
547
|
+
}
|
|
548
|
+
if (this.lookupState[lookup] !== DNS_L_DONE) {
|
|
549
|
+
return DNS_RESOLVE_PENDING
|
|
550
|
+
}
|
|
551
|
+
const count: i32 = this.lookupResult[lookup]
|
|
552
|
+
let i: i32 = 0
|
|
553
|
+
while (i < count * DNS_ADDRESS_BYTES && i < toI32(out.length)) {
|
|
554
|
+
out[i] = this.lookupAddresses[lookup][i]
|
|
555
|
+
i += 1
|
|
556
|
+
}
|
|
557
|
+
i = 0
|
|
558
|
+
while (i < count && i < toI32(ttls.length)) {
|
|
559
|
+
ttls[i] = this.lookupTtls[lookup][i]
|
|
560
|
+
i += 1
|
|
561
|
+
}
|
|
562
|
+
this.lookupState[lookup] = DNS_L_FREE
|
|
563
|
+
return count
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Ends `lookup` now, whether or not it has finished, and frees its slot;
|
|
568
|
+
* the sockets of its queries are owed a `DNS_CLOSE`. Panics as `result`.
|
|
569
|
+
*/
|
|
570
|
+
cancel(lookup: i32): void {
|
|
571
|
+
if (lookup < 0 || lookup >= this.maxLookups || this.lookupState[lookup] === DNS_L_FREE) {
|
|
572
|
+
panic(`DnsResolver.cancel: ${lookup} is not a lookup in progress`)
|
|
573
|
+
}
|
|
574
|
+
dnsResolverStop(this, lookup * 2)
|
|
575
|
+
dnsResolverStop(this, lookup * 2 + 1)
|
|
576
|
+
this.lookupState[lookup] = DNS_L_FREE
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** How many lookups are in progress or hold an answer not yet read. */
|
|
580
|
+
active(): i32 {
|
|
581
|
+
let count: i32 = 0
|
|
582
|
+
let l: i32 = 0
|
|
583
|
+
while (l < this.maxLookups) {
|
|
584
|
+
if (this.lookupState[l] !== DNS_L_FREE) {
|
|
585
|
+
count += 1
|
|
586
|
+
}
|
|
587
|
+
l += 1
|
|
588
|
+
}
|
|
589
|
+
return count
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** Panics unless `q` names one of `r`'s queries. */
|
|
594
|
+
const dnsResolverCheckQuery = (r: DnsResolver, q: i32, who: string): void => {
|
|
595
|
+
if (q < 0 || q >= r.maxLookups * 2) {
|
|
596
|
+
panic(`DnsResolver.${who}: ${q} is not a query`)
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** `n` plus one, held at the i32 maximum, so a counter a peer drives never overflows. */
|
|
601
|
+
const dnsResolverCount = (n: i32): i32 => (n < 2147483647 ? n + 1 : n)
|
|
602
|
+
|
|
603
|
+
/** Whether `name` is `localhost` or a name under it, in any case, with or without the root's dot, and with no empty label before `localhost`. */
|
|
604
|
+
const dnsResolverIsLocalhost = (name: string): boolean => {
|
|
605
|
+
let n: i32 = toI32(name.length)
|
|
606
|
+
if (n > 0 && toI32(name.charCodeAt(n - 1)) === 46) {
|
|
607
|
+
n -= 1
|
|
608
|
+
}
|
|
609
|
+
const word: string = "localhost"
|
|
610
|
+
if (n < 9 || (n > 9 && toI32(name.charCodeAt(n - 10)) !== 46)) {
|
|
611
|
+
return false
|
|
612
|
+
}
|
|
613
|
+
// `.localhost` and `x..localhost` have an empty label, so are no name.
|
|
614
|
+
if (n === 10 || (n > 10 && toI32(name.charCodeAt(n - 11)) === 46)) {
|
|
615
|
+
return false
|
|
616
|
+
}
|
|
617
|
+
let i: i32 = 0
|
|
618
|
+
while (i < 9 && i < toI32(word.length)) {
|
|
619
|
+
let b: i32 = toI32(name.charCodeAt(n - 9 + i))
|
|
620
|
+
if (b >= 65 && b <= 90) {
|
|
621
|
+
b += 32
|
|
622
|
+
}
|
|
623
|
+
if (b !== toI32(word.charCodeAt(i))) {
|
|
624
|
+
return false
|
|
625
|
+
}
|
|
626
|
+
i += 1
|
|
627
|
+
}
|
|
628
|
+
return true
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** Writes 127.0.0.1 then ::1, both with `port`, to `out`. */
|
|
632
|
+
const dnsResolverLoopback = (out: u8[], port: i32): void => {
|
|
633
|
+
let i: i32 = 0
|
|
634
|
+
while (i < 2 * DNS_ADDRESS_BYTES && i < toI32(out.length)) {
|
|
635
|
+
out[i] = toU8(0)
|
|
636
|
+
i += 1
|
|
637
|
+
}
|
|
638
|
+
out[10] = toU8(255)
|
|
639
|
+
out[11] = toU8(255)
|
|
640
|
+
out[12] = toU8(127)
|
|
641
|
+
out[15] = toU8(1)
|
|
642
|
+
setU16BE(out, 16, toU16(port))
|
|
643
|
+
out[DNS_ADDRESS_BYTES + 15] = toU8(1)
|
|
644
|
+
setU16BE(out, DNS_ADDRESS_BYTES + 16, toU16(port))
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Whether `name` is an IPv4 or IPv6 literal, which is then written to `out`
|
|
649
|
+
* with `port`. `scratch` holds the name's bytes for the parser.
|
|
650
|
+
*/
|
|
651
|
+
const dnsResolverLiteral = (scratch: u8[], name: string, out: u8[], port: i32): boolean => {
|
|
652
|
+
const n: i32 = toI32(name.length)
|
|
653
|
+
if (n === 0 || n > toI32(scratch.length)) {
|
|
654
|
+
return false
|
|
655
|
+
}
|
|
656
|
+
let i: i32 = 0
|
|
657
|
+
while (i < n) {
|
|
658
|
+
const b: i32 = toI32(name.charCodeAt(i))
|
|
659
|
+
if (b > 127) {
|
|
660
|
+
return false
|
|
661
|
+
}
|
|
662
|
+
scratch[i] = toU8(b)
|
|
663
|
+
i += 1
|
|
664
|
+
}
|
|
665
|
+
return dnsParseAddress(scratch, 0, n, out, 0, port)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Finishes lookup `l` before any query was sent, with `count` addresses already in place. */
|
|
669
|
+
const dnsResolverFinishNow = (r: DnsResolver, l: i32, count: i32): i32 => {
|
|
670
|
+
r.lookupState[l] = DNS_L_DONE
|
|
671
|
+
r.lookupResult[l] = count
|
|
672
|
+
return l
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Ends lookup `l` with `result`, and owes the driver a close for each of its queries still holding a socket. */
|
|
676
|
+
const dnsResolverFinish = (r: DnsResolver, l: i32, result: i32): void => {
|
|
677
|
+
dnsResolverStop(r, l * 2)
|
|
678
|
+
dnsResolverStop(r, l * 2 + 1)
|
|
679
|
+
r.lookupState[l] = DNS_L_DONE
|
|
680
|
+
r.lookupResult[l] = result
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** Stops query `q`, and owes the driver a close if a socket may be open for it. */
|
|
684
|
+
const dnsResolverStop = (r: DnsResolver, q: i32): void => {
|
|
685
|
+
if (r.state[q] === DNS_Q_UDP || r.state[q] === DNS_Q_TCP || r.pending[q] !== 0) {
|
|
686
|
+
r.pending[q] = DNS_CLOSE
|
|
687
|
+
}
|
|
688
|
+
if (r.state[q] !== DNS_Q_IDLE) {
|
|
689
|
+
r.state[q] = DNS_Q_DONE
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Copies `v4` A and `v6` AAAA addresses from lookup `l`'s two queries into
|
|
695
|
+
* its answer, A first. From a server, every address of a type gets the
|
|
696
|
+
* smallest TTL among them, the TTL of the RRset (RFC 2181 §5.2); from the
|
|
697
|
+
* hosts file, `fromServer` false, each gets 0, since nothing says how long
|
|
698
|
+
* the file holds.
|
|
699
|
+
*/
|
|
700
|
+
const dnsResolverGather = (r: DnsResolver, l: i32, v4: i32, v6: i32, fromServer: boolean): void => {
|
|
701
|
+
const out: u8[] = r.lookupAddresses[l]
|
|
702
|
+
const ttls: i32[] = r.lookupTtls[l]
|
|
703
|
+
let k: i32 = 0
|
|
704
|
+
let half: i32 = 0
|
|
705
|
+
while (half < 2) {
|
|
706
|
+
const q: i32 = l * 2 + half
|
|
707
|
+
const count: i32 = half === 0 ? v4 : v6
|
|
708
|
+
let least: i32 = 0
|
|
709
|
+
let j: i32 = 0
|
|
710
|
+
while (fromServer && j < count) {
|
|
711
|
+
const t: i32 = r.answerTtls[q][j]
|
|
712
|
+
if (j === 0 || t < least) {
|
|
713
|
+
least = t
|
|
714
|
+
}
|
|
715
|
+
j += 1
|
|
716
|
+
}
|
|
717
|
+
let i: i32 = 0
|
|
718
|
+
while (i < count && k >= 0 && k < toI32(ttls.length)) {
|
|
719
|
+
let b: i32 = 0
|
|
720
|
+
while (b < DNS_ADDRESS_BYTES) {
|
|
721
|
+
out[k * DNS_ADDRESS_BYTES + b] = r.answers[q][i * DNS_ADDRESS_BYTES + b]
|
|
722
|
+
b += 1
|
|
723
|
+
}
|
|
724
|
+
ttls[k] = least
|
|
725
|
+
k += 1
|
|
726
|
+
i += 1
|
|
727
|
+
}
|
|
728
|
+
half += 1
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Starts lookup `l`'s current candidate name, skipping any the search list
|
|
734
|
+
* makes too long, and answers whether there was one to start.
|
|
735
|
+
*
|
|
736
|
+
* The order is resolv.conf(5)'s: a name with a trailing dot is only itself; a
|
|
737
|
+
* name with at least `ndots` dots is itself and then each search suffix; any
|
|
738
|
+
* other name is each suffix and then itself.
|
|
739
|
+
*/
|
|
740
|
+
const dnsResolverNextCandidate = (r: DnsResolver, l: i32, nowNanos: i64): boolean => {
|
|
741
|
+
while (r.candidate[l] < r.candidates[l]) {
|
|
742
|
+
const c: i32 = r.candidate[l]
|
|
743
|
+
const search: i32 = r.config.searchCount
|
|
744
|
+
let suffix: i32 = -1
|
|
745
|
+
if (!r.absolute[l]) {
|
|
746
|
+
if (r.dots[l] >= r.config.ndots) {
|
|
747
|
+
suffix = c - 1
|
|
748
|
+
} else {
|
|
749
|
+
suffix = c < search ? c : -1
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const a: i32 = l * 2
|
|
753
|
+
const length: i32 = dnsResolverCandidate(r, l, suffix, r.names[a])
|
|
754
|
+
if (length > 0) {
|
|
755
|
+
dnsResolverBegin(r, a, length, DNS_TYPE_A, nowNanos)
|
|
756
|
+
let i: i32 = 0
|
|
757
|
+
while (i < length) {
|
|
758
|
+
r.names[a + 1][i] = r.names[a][i]
|
|
759
|
+
i += 1
|
|
760
|
+
}
|
|
761
|
+
dnsResolverBegin(r, a + 1, length, DNS_TYPE_AAAA, nowNanos)
|
|
762
|
+
return true
|
|
763
|
+
}
|
|
764
|
+
r.candidate[l] = c + 1
|
|
765
|
+
}
|
|
766
|
+
return false
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Writes lookup `l`'s name, with search suffix `suffix` appended unless it
|
|
771
|
+
* is -1, into `out` in wire form with its root byte, and answers its length,
|
|
772
|
+
* or 0 when the suffix is not a name of labels or makes the name over 255
|
|
773
|
+
* bytes.
|
|
774
|
+
*/
|
|
775
|
+
const dnsResolverCandidate = (r: DnsResolver, l: i32, suffix: i32, out: u8[]): i32 => {
|
|
776
|
+
const base: u8[] = r.bases[l]
|
|
777
|
+
let n: i32 = r.baseLength[l]
|
|
778
|
+
let i: i32 = 0
|
|
779
|
+
while (i < n && i < toI32(out.length) && i < toI32(base.length)) {
|
|
780
|
+
out[i] = base[i]
|
|
781
|
+
i += 1
|
|
782
|
+
}
|
|
783
|
+
if (suffix >= 0) {
|
|
784
|
+
const start: i32 = r.config.searchStart[suffix]
|
|
785
|
+
const length: i32 = r.config.searchLength[suffix]
|
|
786
|
+
// The suffix's text becomes labels: each dot, and its end, closes the
|
|
787
|
+
// label whose length byte sits at `n`.
|
|
788
|
+
let label: i32 = 0
|
|
789
|
+
let k: i32 = 0
|
|
790
|
+
while (k <= length) {
|
|
791
|
+
const b: i32 = k < length ? toI32(r.config.search[start + k]) : 46
|
|
792
|
+
if (b === 46) {
|
|
793
|
+
if (label === 0 || label > DNS_MAX_LABEL_BYTES || n < 0 || n >= toI32(out.length)) {
|
|
794
|
+
return 0
|
|
795
|
+
}
|
|
796
|
+
out[n] = toU8(label)
|
|
797
|
+
n += label + 1
|
|
798
|
+
label = 0
|
|
799
|
+
} else if (b < 33 || b > 126 || n + 1 + label > DNS_MAX_NAME_BYTES - 2) {
|
|
800
|
+
return 0
|
|
801
|
+
} else {
|
|
802
|
+
out[n + 1 + label] = toU8(b)
|
|
803
|
+
label += 1
|
|
804
|
+
}
|
|
805
|
+
k += 1
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (n + 1 > DNS_MAX_NAME_BYTES) {
|
|
809
|
+
return 0
|
|
810
|
+
}
|
|
811
|
+
out[n] = toU8(0)
|
|
812
|
+
return n + 1
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Makes query `q` ask `qtype` for the `length`-byte wire name in
|
|
817
|
+
* `r.names[q]`, as the start of a new chain, and sends it to the first
|
|
818
|
+
* server.
|
|
819
|
+
*/
|
|
820
|
+
const dnsResolverBegin = (r: DnsResolver, q: i32, length: i32, qtype: i32, nowNanos: i64): void => {
|
|
821
|
+
r.hops[q] = 0
|
|
822
|
+
r.nameLength[q] = length
|
|
823
|
+
let i: i32 = 0
|
|
824
|
+
while (i < length) {
|
|
825
|
+
const b: i32 = toI32(r.names[q][i])
|
|
826
|
+
r.starts[q][i] = toU8(b >= 65 && b <= 90 ? b + 32 : b)
|
|
827
|
+
i += 1
|
|
828
|
+
}
|
|
829
|
+
dnsResolverAsk(r, q, qtype, nowNanos)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/** Writes query `q`'s bytes for its current name and `qtype`, ID 0 until it is sent, and sends it to the first server. */
|
|
833
|
+
const dnsResolverAsk = (r: DnsResolver, q: i32, qtype: i32, nowNanos: i64): void => {
|
|
834
|
+
const query: u8[] = r.queries[q]
|
|
835
|
+
const length: i32 = r.nameLength[q]
|
|
836
|
+
// The bytes are dnsBuildQuery's: the ID, RD, one question, then the name, type and class IN.
|
|
837
|
+
setU16BE(query, 0, toU16(0))
|
|
838
|
+
setU16BE(query, 2, toU16(0x0100))
|
|
839
|
+
setU16BE(query, 4, toU16(1))
|
|
840
|
+
setU16BE(query, 6, toU16(0))
|
|
841
|
+
setU16BE(query, 8, toU16(0))
|
|
842
|
+
setU16BE(query, 10, toU16(0))
|
|
843
|
+
let i: i32 = 0
|
|
844
|
+
while (i < length && 12 + i < toI32(query.length)) {
|
|
845
|
+
query[12 + i] = r.names[q][i]
|
|
846
|
+
i += 1
|
|
847
|
+
}
|
|
848
|
+
setU16BE(query, 12 + length, toU16(qtype))
|
|
849
|
+
setU16BE(query, 14 + length, toU16(1))
|
|
850
|
+
r.queryLength[q] = 16 + length
|
|
851
|
+
// Only this name's reply may say NXDOMAIN, not an earlier candidate's or lookup's.
|
|
852
|
+
r.nxdomain[q] = false
|
|
853
|
+
r.server[q] = 0
|
|
854
|
+
r.round[q] = 0
|
|
855
|
+
dnsResolverSend(r, q, DNS_Q_UDP, nowNanos)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Owes the driver a send of query `q` to its current server over `how`, with a deadline `timeout` seconds after `nowNanos`. */
|
|
859
|
+
const dnsResolverSend = (r: DnsResolver, q: i32, how: i32, nowNanos: i64): void => {
|
|
860
|
+
r.state[q] = how
|
|
861
|
+
r.pending[q] = how === DNS_Q_TCP ? DNS_SEND_TCP : DNS_SEND_UDP
|
|
862
|
+
r.streamHave[q] = 0
|
|
863
|
+
r.streamWant[q] = 0
|
|
864
|
+
r.deadline[q] = nowNanos + toI64(r.config.timeout) * toI64(1000000000)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Query `q`'s try failed: it moves to the next server, and after the last
|
|
869
|
+
* server to the next round, and after `attempts` rounds it is
|
|
870
|
+
* `DNS_RESOLVE_NO_ANSWER`.
|
|
871
|
+
*/
|
|
872
|
+
const dnsResolverServerFailed = (r: DnsResolver, q: i32, nowNanos: i64): void => {
|
|
873
|
+
r.serverFailures = dnsResolverCount(r.serverFailures)
|
|
874
|
+
r.server[q] = r.server[q] + 1
|
|
875
|
+
if (r.server[q] >= r.config.serverCount) {
|
|
876
|
+
r.server[q] = 0
|
|
877
|
+
r.round[q] = r.round[q] + 1
|
|
878
|
+
}
|
|
879
|
+
if (r.round[q] >= r.config.attempts) {
|
|
880
|
+
dnsResolverDone(r, q, DNS_RESOLVE_NO_ANSWER, nowNanos)
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
dnsResolverSend(r, q, DNS_Q_UDP, nowNanos)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Query `q`'s reply, whose status `dnsParseReply` answered and which is in
|
|
888
|
+
* `r.reply`: its addresses, NXDOMAIN, NODATA, a CNAME to ask after, or a
|
|
889
|
+
* failure of the server.
|
|
890
|
+
*/
|
|
891
|
+
const dnsResolverAnswer = (r: DnsResolver, q: i32, status: i32, nowNanos: i64): void => {
|
|
892
|
+
const reply: DnsReply = r.reply
|
|
893
|
+
r.nxdomain[q] = status === DNS_NXDOMAIN
|
|
894
|
+
if (status === DNS_NXDOMAIN) {
|
|
895
|
+
dnsResolverDone(r, q, DNS_RESOLVE_NOT_FOUND, nowNanos)
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
if (status !== DNS_NOERROR) {
|
|
899
|
+
dnsResolverServerFailed(r, q, nowNanos)
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
if (reply.count > 0) {
|
|
903
|
+
let i: i32 = 0
|
|
904
|
+
while (i < reply.count * DNS_ADDRESS_BYTES) {
|
|
905
|
+
r.answers[q][i] = reply.addresses[i]
|
|
906
|
+
i += 1
|
|
907
|
+
}
|
|
908
|
+
i = 0
|
|
909
|
+
while (i < reply.count) {
|
|
910
|
+
r.answerTtls[q][i] = reply.ttls[i]
|
|
911
|
+
i += 1
|
|
912
|
+
}
|
|
913
|
+
dnsResolverDone(r, q, reply.count, nowNanos)
|
|
914
|
+
return
|
|
915
|
+
}
|
|
916
|
+
if (dnsResolverSameName(reply.chain, reply.asked)) {
|
|
917
|
+
dnsResolverDone(r, q, DNS_RESOLVE_NOT_FOUND, nowNanos)
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
// The chain left the reply without an address: ask for its end.
|
|
921
|
+
if (r.hops[q] >= DNS_RESOLVER_MAX_CNAME_HOPS || dnsResolverSameName(reply.chain, r.starts[q])) {
|
|
922
|
+
dnsResolverDone(r, q, DNS_RESOLVE_CNAME, nowNanos)
|
|
923
|
+
return
|
|
924
|
+
}
|
|
925
|
+
r.hops[q] = r.hops[q] + 1
|
|
926
|
+
r.cnameQueries = dnsResolverCount(r.cnameQueries)
|
|
927
|
+
const length: i32 = dnsResolverNameLength(reply.chain)
|
|
928
|
+
let i: i32 = 0
|
|
929
|
+
while (i < length) {
|
|
930
|
+
r.names[q][i] = reply.chain[i]
|
|
931
|
+
i += 1
|
|
932
|
+
}
|
|
933
|
+
r.nameLength[q] = length
|
|
934
|
+
dnsResolverAsk(r, q, (q & 1) === 0 ? DNS_TYPE_A : DNS_TYPE_AAAA, nowNanos)
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/** The length of the wire name in `name`, its root byte included, read label by label since a label may hold a 0 byte. */
|
|
938
|
+
const dnsResolverNameLength = (name: u8[]): i32 => {
|
|
939
|
+
let i: i32 = 0
|
|
940
|
+
while (i >= 0 && i < toI32(name.length) && name[i] !== toU8(0)) {
|
|
941
|
+
i += toI32(name[i]) + 1
|
|
942
|
+
}
|
|
943
|
+
return i + 1
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** Whether the lowercase wire names `a` and `b` are the same name. */
|
|
947
|
+
const dnsResolverSameName = (a: u8[], b: u8[]): boolean => {
|
|
948
|
+
const n: i32 = dnsResolverNameLength(a)
|
|
949
|
+
let i: i32 = 0
|
|
950
|
+
while (i < n && i < toI32(a.length) && i < toI32(b.length)) {
|
|
951
|
+
if (a[i] !== b[i]) {
|
|
952
|
+
return false
|
|
953
|
+
}
|
|
954
|
+
i += 1
|
|
955
|
+
}
|
|
956
|
+
return true
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Query `q` has finished with `outcome`, its address count or a
|
|
961
|
+
* `DNS_RESOLVE_*` code; once its lookup's other query has too, the lookup
|
|
962
|
+
* finishes, or moves to its next candidate name when both found nothing.
|
|
963
|
+
*
|
|
964
|
+
* Addresses from either query make the answer. NXDOMAIN names the name,
|
|
965
|
+
* not a type, so NXDOMAIN from either query with the other reaching no server
|
|
966
|
+
* or breaking its CNAME chain sends the search to its next candidate, keeping
|
|
967
|
+
* that error as the lookup's if no later candidate answers: a network that
|
|
968
|
+
* drops AAAA queries still finds a name under a later search domain. Without
|
|
969
|
+
* an NXDOMAIN, such an error is the lookup's, since the name may well exist.
|
|
970
|
+
* Otherwise the name has no address: NXDOMAIN from both moves the search on,
|
|
971
|
+
* and NODATA ends it, as musl's resolver does, since the name exists and only
|
|
972
|
+
* lacks the type asked.
|
|
973
|
+
*/
|
|
974
|
+
const dnsResolverDone = (r: DnsResolver, q: i32, outcome: i32, nowNanos: i64): void => {
|
|
975
|
+
if (r.state[q] === DNS_Q_UDP || r.state[q] === DNS_Q_TCP) {
|
|
976
|
+
r.pending[q] = DNS_CLOSE
|
|
977
|
+
}
|
|
978
|
+
r.state[q] = DNS_Q_DONE
|
|
979
|
+
r.outcome[q] = outcome
|
|
980
|
+
const l: i32 = q / 2
|
|
981
|
+
const a: i32 = l * 2
|
|
982
|
+
if (r.state[a] !== DNS_Q_DONE || r.state[a + 1] !== DNS_Q_DONE) {
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
const v4: i32 = r.outcome[a]
|
|
986
|
+
const v6: i32 = r.outcome[a + 1]
|
|
987
|
+
if (v4 > 0 || v6 > 0) {
|
|
988
|
+
const four: i32 = v4 > 0 ? v4 : 0
|
|
989
|
+
const six: i32 = v6 > 0 ? v6 : 0
|
|
990
|
+
dnsResolverGather(r, l, four, six, true)
|
|
991
|
+
dnsResolverFinish(r, l, four + six)
|
|
992
|
+
return
|
|
993
|
+
}
|
|
994
|
+
let error: i32 = 0
|
|
995
|
+
if (v4 === DNS_RESOLVE_NO_ANSWER || v4 === DNS_RESOLVE_CNAME) {
|
|
996
|
+
error = v4
|
|
997
|
+
} else if (v6 === DNS_RESOLVE_NO_ANSWER || v6 === DNS_RESOLVE_CNAME) {
|
|
998
|
+
error = v6
|
|
999
|
+
}
|
|
1000
|
+
const named: boolean = r.nxdomain[a] || r.nxdomain[a + 1]
|
|
1001
|
+
if (error !== 0 && !named) {
|
|
1002
|
+
dnsResolverFinish(r, l, error)
|
|
1003
|
+
return
|
|
1004
|
+
}
|
|
1005
|
+
if (error !== 0 && r.fallback[l] === 0) {
|
|
1006
|
+
r.fallback[l] = error
|
|
1007
|
+
}
|
|
1008
|
+
if (error !== 0 || (r.nxdomain[a] && r.nxdomain[a + 1])) {
|
|
1009
|
+
r.candidate[l] = r.candidate[l] + 1
|
|
1010
|
+
if (dnsResolverNextCandidate(r, l, nowNanos)) {
|
|
1011
|
+
return
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
dnsResolverFinish(r, l, r.fallback[l] !== 0 ? r.fallback[l] : DNS_RESOLVE_NOT_FOUND)
|
|
1015
|
+
}
|