@amritk/nish-x86_64-linux 0.23.1 → 0.25.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 +50 -23
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/arrays.d.ts +0 -2
- package/runtime/core/arrays.mjs +0 -2
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/core/numbers.mjs +2 -3
- package/runtime/nish.d.ts +100 -4
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +23 -5
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +45 -4
- package/runtime/runtime-os.c +115 -12
- package/runtime/runtime-term.c +2 -2
- package/runtime/runtime.c +1 -1
- package/scripts/build.sh +13 -3
- package/std/README.md +44 -7
- 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 +290 -0
- package/std/compress/deflate.ts +2790 -0
- package/std/compress/gzip.ts +761 -0
- package/std/compress/zlib.ts +168 -0
- 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/sha256.ts +2 -3
- package/std/crypto/x25519.ts +3 -4
- 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/http-compress.ts +393 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +101 -1
- package/std/net/http1.ts +9 -9
- 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
package/std/net/dns.ts
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/dns` — the stub resolver of `nish/net/dns-resolver` driven over
|
|
3
|
+
* `nish:net` sockets on the caller's own readiness loop, and `resolveHost`,
|
|
4
|
+
* which runs a loop of its own for a program that has none.
|
|
5
|
+
*
|
|
6
|
+
* const client = new DnsClient(new DnsResolver(config, hosts, 4), loop, 1000);
|
|
7
|
+
* const lookup = client.start("example.com", 443, monotonicNanos());
|
|
8
|
+
* // in the loop: pollWait(loop, ready, client.timeoutMs(now, own)); hand
|
|
9
|
+
* // each token client.owns to client.ready, call client.tick(now), and
|
|
10
|
+
* // read client.result(lookup, out, ttls) until it is not PENDING.
|
|
11
|
+
*
|
|
12
|
+
* `DnsClient` opens a fresh UDP socket on a kernel-chosen port for every UDP
|
|
13
|
+
* try and a TCP connection for every TCP one, watches each under a token of
|
|
14
|
+
* the range it was given, and closes it when the try ends. It never waits,
|
|
15
|
+
* never reads the clock and never reads a file: the caller's loop owns all
|
|
16
|
+
* three, so a lookup costs a server nothing while it waits. Each send's
|
|
17
|
+
* query ID is drawn from `crypto.getRandomValues`.
|
|
18
|
+
*
|
|
19
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
20
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { DNS_ADDRESS_BYTES } from "nish/net/dns-message"
|
|
24
|
+
import { DnsConfig, DnsHosts, dnsParseHosts, dnsParseResolvConf } from "nish/net/dns-config"
|
|
25
|
+
import {
|
|
26
|
+
DNS_CLOSE,
|
|
27
|
+
DNS_OUTGOING_MAX_BYTES,
|
|
28
|
+
DNS_RESOLVE_MAX_ADDRESSES,
|
|
29
|
+
DNS_RESOLVE_PENDING,
|
|
30
|
+
DNS_RESOLVE_TIMEOUT,
|
|
31
|
+
DNS_SEND_TCP,
|
|
32
|
+
DnsResolver,
|
|
33
|
+
} from "nish/net/dns-resolver"
|
|
34
|
+
import {
|
|
35
|
+
connectResult,
|
|
36
|
+
netClose,
|
|
37
|
+
netRead,
|
|
38
|
+
netWrite,
|
|
39
|
+
pollAdd,
|
|
40
|
+
pollCreate,
|
|
41
|
+
pollModify,
|
|
42
|
+
pollWait,
|
|
43
|
+
tcpConnect,
|
|
44
|
+
udpBind,
|
|
45
|
+
udpRecvFrom,
|
|
46
|
+
udpSendTo,
|
|
47
|
+
} from "nish:net"
|
|
48
|
+
import { readFileBytesSync } from "nish:fs"
|
|
49
|
+
import { monotonicNanos } from "nish:process"
|
|
50
|
+
|
|
51
|
+
/** The longest datagram a UDP try reads; a longer one is cut, fails to parse and is dropped. */
|
|
52
|
+
export const DNS_UDP_MAX_REPLY: i32 = 4096
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The most datagrams one `ready` reads from a query's socket. The loop is
|
|
56
|
+
* level-triggered, so the rest wait for the next `pollWait`, and a flood on
|
|
57
|
+
* the port cannot hold the caller's loop inside the client.
|
|
58
|
+
*/
|
|
59
|
+
export const DNS_CLIENT_MAX_DATAGRAMS: i32 = 32
|
|
60
|
+
|
|
61
|
+
/** What a query's socket is doing: none open, a UDP try, a TCP try connecting or writing, one reading. */
|
|
62
|
+
const DNS_FD_NONE: i32 = 0
|
|
63
|
+
const DNS_FD_UDP: i32 = 1
|
|
64
|
+
const DNS_FD_TCP_WRITE: i32 = 2
|
|
65
|
+
const DNS_FD_TCP_READ: i32 = 3
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A `DnsResolver` driven on the caller's loop. Its sockets are watched under
|
|
69
|
+
* tokens `tokenBase` to `tokenBase + tokens() - 1`, one per query, and the
|
|
70
|
+
* caller hands every ready pair in that range to `ready`.
|
|
71
|
+
*/
|
|
72
|
+
export class DnsClient {
|
|
73
|
+
/** The core; `start`, `result` and `cancel` go through the client so its sockets follow. */
|
|
74
|
+
resolver: DnsResolver
|
|
75
|
+
/** Each query's socket, or -1. */
|
|
76
|
+
fds: i32[]
|
|
77
|
+
/** What each query's socket is doing, a `DNS_FD_*`. */
|
|
78
|
+
fdState: i32[]
|
|
79
|
+
/** Each query's TCP bytes, length prefix first, kept until they are all written. */
|
|
80
|
+
outputs: u8[][]
|
|
81
|
+
outputLength: i32[]
|
|
82
|
+
outputSent: i32[]
|
|
83
|
+
/** Scratch for `nextOutgoing`, a read, a sender's address and the ID. */
|
|
84
|
+
out: u8[]
|
|
85
|
+
to: u8[]
|
|
86
|
+
meta: i32[]
|
|
87
|
+
buf: u8[]
|
|
88
|
+
from: u8[]
|
|
89
|
+
recvMeta: i32[]
|
|
90
|
+
idBytes: u8[]
|
|
91
|
+
/** Whether `idBytes` holds an ID no send has used yet. */
|
|
92
|
+
idFresh: boolean = false
|
|
93
|
+
loop: i32 = 0
|
|
94
|
+
tokenBase: i32 = 0
|
|
95
|
+
/** Sockets that could not be opened, written or connected, each a failed try. */
|
|
96
|
+
socketFailures: i32 = 0
|
|
97
|
+
/** Sockets opened, UDP and TCP, one per try. */
|
|
98
|
+
socketsOpened: i32 = 0
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A client for `resolver` whose sockets join the loop `loop` under tokens
|
|
102
|
+
* from `tokenBase`. Panics when `tokenBase` is negative.
|
|
103
|
+
*/
|
|
104
|
+
constructor(resolver: DnsResolver, loop: i32, tokenBase: i32) {
|
|
105
|
+
if (tokenBase < 0) {
|
|
106
|
+
panic(`DnsClient: a token base of ${tokenBase} is negative`)
|
|
107
|
+
}
|
|
108
|
+
const n: i32 = resolver.maxLookups * 2
|
|
109
|
+
this.resolver = resolver
|
|
110
|
+
this.loop = loop
|
|
111
|
+
this.tokenBase = tokenBase
|
|
112
|
+
this.fds = new Array<i32>(n)
|
|
113
|
+
this.fds.fill(-1)
|
|
114
|
+
this.fdState = new Array<i32>(n)
|
|
115
|
+
this.outputs = []
|
|
116
|
+
let q: i32 = 0
|
|
117
|
+
while (q < n) {
|
|
118
|
+
this.outputs.push(new Array<u8>(DNS_OUTGOING_MAX_BYTES))
|
|
119
|
+
q += 1
|
|
120
|
+
}
|
|
121
|
+
this.outputLength = new Array<i32>(n)
|
|
122
|
+
this.outputSent = new Array<i32>(n)
|
|
123
|
+
this.out = new Array<u8>(DNS_OUTGOING_MAX_BYTES)
|
|
124
|
+
this.to = new Array<u8>(DNS_ADDRESS_BYTES)
|
|
125
|
+
this.meta = new Array<i32>(2)
|
|
126
|
+
this.buf = new Array<u8>(DNS_UDP_MAX_REPLY)
|
|
127
|
+
this.from = new Array<u8>(DNS_ADDRESS_BYTES)
|
|
128
|
+
this.recvMeta = new Array<i32>(2)
|
|
129
|
+
this.idBytes = new Array<u8>(2)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** How many tokens, from `tokenBase`, the client's sockets use. */
|
|
133
|
+
tokens(): i32 {
|
|
134
|
+
return this.resolver.maxLookups * 2
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Whether `token` is one of the client's. */
|
|
138
|
+
owns(token: i32): boolean {
|
|
139
|
+
return token >= this.tokenBase && token - this.tokenBase < this.tokens()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `DnsResolver.start`, with its first packets sent: the lookup, or a negative error. */
|
|
143
|
+
start(name: string, port: i32, nowNanos: i64): i32 {
|
|
144
|
+
const lookup: i32 = this.resolver.start(name, port, nowNanos)
|
|
145
|
+
this.pump(nowNanos)
|
|
146
|
+
return lookup
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** `DnsResolver.result`; a finished lookup's sockets are already closed. */
|
|
150
|
+
result(lookup: i32, out: u8[], ttls: i32[]): i32 {
|
|
151
|
+
return this.resolver.result(lookup, out, ttls)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** `DnsResolver.cancel`, closing the lookup's sockets. */
|
|
155
|
+
cancel(lookup: i32, nowNanos: i64): void {
|
|
156
|
+
this.resolver.cancel(lookup)
|
|
157
|
+
this.pump(nowNanos)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The pair `token`, `events` that `pollWait` reported at `nowNanos`, for a
|
|
162
|
+
* token `owns` answers true for: reads the datagrams (at most
|
|
163
|
+
* `DNS_CLIENT_MAX_DATAGRAMS`) or stream bytes that are waiting, finishes a
|
|
164
|
+
* connection or a write, and sends whatever the core asks next. Panics for a token that is not the client's.
|
|
165
|
+
*/
|
|
166
|
+
ready(token: i32, events: i32, nowNanos: i64): void {
|
|
167
|
+
if (!this.owns(token)) {
|
|
168
|
+
panic(`DnsClient.ready: the token ${token} is not the client's`)
|
|
169
|
+
}
|
|
170
|
+
const q: i32 = token - this.tokenBase
|
|
171
|
+
const fd: i32 = this.fds[q]
|
|
172
|
+
if (fd < 0) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
const state: i32 = this.fdState[q]
|
|
176
|
+
if (state === DNS_FD_UDP) {
|
|
177
|
+
dnsClientReadDatagrams(this, q, fd, nowNanos)
|
|
178
|
+
} else if (state === DNS_FD_TCP_WRITE) {
|
|
179
|
+
dnsClientWrite(this, q, fd, nowNanos)
|
|
180
|
+
} else if (state === DNS_FD_TCP_READ) {
|
|
181
|
+
dnsClientReadStream(this, q, fd, (events & 4) !== 0, nowNanos)
|
|
182
|
+
}
|
|
183
|
+
this.pump(nowNanos)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** `DnsResolver.tick`, then whatever it asks to send. */
|
|
187
|
+
tick(nowNanos: i64): void {
|
|
188
|
+
this.resolver.tick(nowNanos)
|
|
189
|
+
this.pump(nowNanos)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The `pollWait` timeout for a caller who would otherwise wait `ownMs`
|
|
194
|
+
* (negative for ever): the smaller of it and the milliseconds, rounded up,
|
|
195
|
+
* to the core's next deadline after `nowNanos`.
|
|
196
|
+
*/
|
|
197
|
+
timeoutMs(nowNanos: i64, ownMs: i32): i32 {
|
|
198
|
+
const deadline: i64 = this.resolver.nextDeadlineNanos()
|
|
199
|
+
if (deadline < 0) {
|
|
200
|
+
return ownMs
|
|
201
|
+
}
|
|
202
|
+
let wait: i64 = 0
|
|
203
|
+
if (deadline > nowNanos) {
|
|
204
|
+
wait = (deadline - nowNanos + 999999) / 1000000
|
|
205
|
+
}
|
|
206
|
+
if (ownMs >= 0 && toI64(ownMs) < wait) {
|
|
207
|
+
return ownMs
|
|
208
|
+
}
|
|
209
|
+
return toI32(wait)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Carries out everything the core has queued: opens, sends and closes sockets. */
|
|
213
|
+
pump(nowNanos: i64): void {
|
|
214
|
+
let q: i32 = this.nextOutgoing()
|
|
215
|
+
while (q >= 0 && q < toI32(this.fds.length)) {
|
|
216
|
+
dnsClientClose(this, q)
|
|
217
|
+
const kind: i32 = this.meta[0]
|
|
218
|
+
if (kind === DNS_SEND_TCP) {
|
|
219
|
+
dnsClientOpenTcp(this, q, nowNanos)
|
|
220
|
+
} else if (kind !== DNS_CLOSE) {
|
|
221
|
+
dnsClientSendUdp(this, q, nowNanos)
|
|
222
|
+
}
|
|
223
|
+
q = this.nextOutgoing()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The core's next piece of work, with a random ID no send has used: one is
|
|
229
|
+
* drawn only after the last went out, so an empty call or a close costs no
|
|
230
|
+
* `getRandomValues`.
|
|
231
|
+
*/
|
|
232
|
+
nextOutgoing(): i32 {
|
|
233
|
+
if (!this.idFresh) {
|
|
234
|
+
crypto.getRandomValues(this.idBytes)
|
|
235
|
+
this.idFresh = true
|
|
236
|
+
}
|
|
237
|
+
const id: i32 = (toI32(this.idBytes[0]) << 8) | toI32(this.idBytes[1])
|
|
238
|
+
const q: i32 = this.resolver.nextOutgoing(this.out, this.to, this.meta, id)
|
|
239
|
+
if (q >= 0 && this.meta.length > 0 && this.meta[0] !== DNS_CLOSE) {
|
|
240
|
+
this.idFresh = false
|
|
241
|
+
}
|
|
242
|
+
return q
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Closes query `q`'s socket, if it has one. */
|
|
247
|
+
const dnsClientClose = (c: DnsClient, q: i32): void => {
|
|
248
|
+
const fd: i32 = c.fds[q]
|
|
249
|
+
if (fd >= 0) {
|
|
250
|
+
netClose(fd)
|
|
251
|
+
c.fds[q] = -1
|
|
252
|
+
c.fdState[q] = DNS_FD_NONE
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Fails query `q`'s try because its socket did: closes it, if it opened, and tells the core. */
|
|
257
|
+
const dnsClientFail = (c: DnsClient, q: i32, nowNanos: i64): void => {
|
|
258
|
+
c.socketFailures = dnsClientCount(c.socketFailures)
|
|
259
|
+
dnsClientClose(c, q)
|
|
260
|
+
c.resolver.fail(q, nowNanos)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Whether the 18-byte address `to` is IPv4, `::ffff:a.b.c.d`. */
|
|
264
|
+
const dnsClientIsV4 = (to: u8[]): boolean => {
|
|
265
|
+
let i: i32 = 0
|
|
266
|
+
while (i < 10 && i < toI32(to.length)) {
|
|
267
|
+
if (to[i] !== 0) {
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
i += 1
|
|
271
|
+
}
|
|
272
|
+
return to.length >= 12 && to[10] === 0xff && to[11] === 0xff
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** `n` plus one, held at the i32 maximum, so a counter never overflows. */
|
|
276
|
+
const dnsClientCount = (n: i32): i32 => (n < 2147483647 ? n + 1 : n)
|
|
277
|
+
|
|
278
|
+
/** Sends `c.out` to `c.to` from a new socket on a port the kernel picks, watched for reading. */
|
|
279
|
+
const dnsClientSendUdp = (c: DnsClient, q: i32, nowNanos: i64): void => {
|
|
280
|
+
const fd: i32 = udpBind(dnsClientIsV4(c.to) ? "0.0.0.0" : "::", 0, 0)
|
|
281
|
+
if (fd < 0) {
|
|
282
|
+
dnsClientFail(c, q, nowNanos)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
c.socketsOpened = dnsClientCount(c.socketsOpened)
|
|
286
|
+
c.fds[q] = fd
|
|
287
|
+
c.fdState[q] = DNS_FD_UDP
|
|
288
|
+
const length: i32 = c.meta[1]
|
|
289
|
+
if (
|
|
290
|
+
udpSendTo(fd, c.out, 0, length, c.to, 0, 0) !== length ||
|
|
291
|
+
pollAdd(c.loop, fd, 1, c.tokenBase + q) !== 0
|
|
292
|
+
) {
|
|
293
|
+
dnsClientFail(c, q, nowNanos)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Starts a connection to `c.to`, watched for writing, and keeps `c.out` to write once it is made. */
|
|
298
|
+
const dnsClientOpenTcp = (c: DnsClient, q: i32, nowNanos: i64): void => {
|
|
299
|
+
const fd: i32 = tcpConnect(c.to)
|
|
300
|
+
if (fd < 0) {
|
|
301
|
+
dnsClientFail(c, q, nowNanos)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
c.socketsOpened = dnsClientCount(c.socketsOpened)
|
|
305
|
+
c.fds[q] = fd
|
|
306
|
+
c.fdState[q] = DNS_FD_TCP_WRITE
|
|
307
|
+
const length: i32 = c.meta[1]
|
|
308
|
+
const output: u8[] = c.outputs[q]
|
|
309
|
+
let i: i32 = 0
|
|
310
|
+
while (i < length && i < toI32(output.length) && i < toI32(c.out.length)) {
|
|
311
|
+
output[i] = c.out[i]
|
|
312
|
+
i += 1
|
|
313
|
+
}
|
|
314
|
+
c.outputLength[q] = length
|
|
315
|
+
c.outputSent[q] = 0
|
|
316
|
+
if (pollAdd(c.loop, fd, 2, c.tokenBase + q) !== 0) {
|
|
317
|
+
dnsClientFail(c, q, nowNanos)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The datagrams waiting on query `q`'s socket, to the core, until none is,
|
|
323
|
+
* the try ends or `DNS_CLIENT_MAX_DATAGRAMS` are read.
|
|
324
|
+
*/
|
|
325
|
+
const dnsClientReadDatagrams = (c: DnsClient, q: i32, fd: i32, nowNanos: i64): void => {
|
|
326
|
+
let read: i32 = 0
|
|
327
|
+
while (c.fds[q] === fd && read < DNS_CLIENT_MAX_DATAGRAMS) {
|
|
328
|
+
const n: i32 = udpRecvFrom(fd, c.buf, 0, DNS_UDP_MAX_REPLY, c.from, c.recvMeta)
|
|
329
|
+
if (n < 0) {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
read += 1
|
|
333
|
+
c.resolver.receive(q, c.buf, n, c.from, nowNanos)
|
|
334
|
+
c.pump(nowNanos)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Query `q`'s connection is writable: on the first time, whether it was
|
|
340
|
+
* made; then as much of the query as the socket takes, and once all of it
|
|
341
|
+
* is written, the socket watched for reading instead.
|
|
342
|
+
*/
|
|
343
|
+
const dnsClientWrite = (c: DnsClient, q: i32, fd: i32, nowNanos: i64): void => {
|
|
344
|
+
if (c.outputSent[q] === 0 && connectResult(fd) !== 0) {
|
|
345
|
+
dnsClientFail(c, q, nowNanos)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
while (c.outputSent[q] < c.outputLength[q]) {
|
|
349
|
+
const n: i32 = netWrite(fd, c.outputs[q], c.outputSent[q], c.outputLength[q] - c.outputSent[q])
|
|
350
|
+
if (n === -11) {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
if (n <= 0) {
|
|
354
|
+
dnsClientFail(c, q, nowNanos)
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
c.outputSent[q] = c.outputSent[q] + n
|
|
358
|
+
}
|
|
359
|
+
c.fdState[q] = DNS_FD_TCP_READ
|
|
360
|
+
if (pollModify(c.loop, fd, 1, c.tokenBase + q) !== 0) {
|
|
361
|
+
dnsClientFail(c, q, nowNanos)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* The bytes waiting on query `q`'s connection, to the core, until none are or
|
|
367
|
+
* the try ends. The end of the stream, or a hang-up with nothing left to
|
|
368
|
+
* read, before the reply is whole fails the try. A try the core ended stops
|
|
369
|
+
* the reading, though the next one may open its socket on the same fd.
|
|
370
|
+
*/
|
|
371
|
+
const dnsClientReadStream = (c: DnsClient, q: i32, fd: i32, hungUp: boolean, nowNanos: i64): void => {
|
|
372
|
+
while (c.fds[q] === fd && c.fdState[q] === DNS_FD_TCP_READ) {
|
|
373
|
+
const n: i32 = netRead(fd, c.buf, 0, DNS_UDP_MAX_REPLY)
|
|
374
|
+
if (n === -11 && !hungUp) {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
if (n <= 0) {
|
|
378
|
+
dnsClientFail(c, q, nowNanos)
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
c.resolver.receiveStream(q, c.buf, n, nowNanos)
|
|
382
|
+
c.pump(nowNanos)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The addresses of `name`, looked up as `/etc/resolv.conf` and `/etc/hosts`
|
|
388
|
+
* say (either missing means the defaults), each 18 bytes with `port` into
|
|
389
|
+
* `out` and its TTL in seconds into `ttls`: the count, or a negative
|
|
390
|
+
* `DNS_RESOLVE_*` code, `DNS_RESOLVE_TIMEOUT` once `timeoutMs` has passed,
|
|
391
|
+
* or the negative errno of a poll loop that cannot be made or waited on.
|
|
392
|
+
* It waits for both the A and the AAAA answer, so a family that answered is
|
|
393
|
+
* not returned on its own once `timeoutMs` passes: it is `DNS_RESOLVE_TIMEOUT`.
|
|
394
|
+
*
|
|
395
|
+
* **It blocks the thread** until the lookup ends, on a loop of its own, so
|
|
396
|
+
* it is for a CLI or a program's start-up; a server resolves with a
|
|
397
|
+
* `DnsClient` on its own loop. `out` and `ttls` need room for
|
|
398
|
+
* `DNS_RESOLVE_MAX_ADDRESSES`, or it panics.
|
|
399
|
+
*/
|
|
400
|
+
export const resolveHost = (name: string, timeoutMs: i32, port: i32, out: u8[], ttls: i32[]): i32 => {
|
|
401
|
+
const config: DnsConfig = new DnsConfig()
|
|
402
|
+
const conf: u8[] | null = readFileBytesSync("/etc/resolv.conf")
|
|
403
|
+
if (conf !== null) {
|
|
404
|
+
dnsParseResolvConf(conf, 0, toI32(conf.length), config)
|
|
405
|
+
} else {
|
|
406
|
+
dnsParseResolvConf(new Array<u8>(0), 0, 0, config)
|
|
407
|
+
}
|
|
408
|
+
const hosts: DnsHosts = new DnsHosts()
|
|
409
|
+
const hostsFile: u8[] | null = readFileBytesSync("/etc/hosts")
|
|
410
|
+
if (hostsFile !== null) {
|
|
411
|
+
dnsParseHosts(hostsFile, 0, toI32(hostsFile.length), hosts)
|
|
412
|
+
}
|
|
413
|
+
return dnsLookupWith(config, hosts, name, timeoutMs, port, out, ttls)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* `resolveHost` with the configuration given rather than read: one lookup
|
|
418
|
+
* of `name` on a loop of its own, for at most `timeoutMs`. Blocks as
|
|
419
|
+
* `resolveHost` does.
|
|
420
|
+
*/
|
|
421
|
+
export const dnsLookupWith = (
|
|
422
|
+
config: DnsConfig,
|
|
423
|
+
hosts: DnsHosts | null,
|
|
424
|
+
name: string,
|
|
425
|
+
timeoutMs: i32,
|
|
426
|
+
port: i32,
|
|
427
|
+
out: u8[],
|
|
428
|
+
ttls: i32[]
|
|
429
|
+
): i32 => {
|
|
430
|
+
if (
|
|
431
|
+
toI32(out.length) < DNS_RESOLVE_MAX_ADDRESSES * DNS_ADDRESS_BYTES ||
|
|
432
|
+
toI32(ttls.length) < DNS_RESOLVE_MAX_ADDRESSES
|
|
433
|
+
) {
|
|
434
|
+
panic("resolveHost: out or ttls has no room for DNS_RESOLVE_MAX_ADDRESSES")
|
|
435
|
+
}
|
|
436
|
+
const loop: i32 = pollCreate()
|
|
437
|
+
if (loop < 0) {
|
|
438
|
+
return loop
|
|
439
|
+
}
|
|
440
|
+
const client: DnsClient = new DnsClient(new DnsResolver(config, hosts, 1), loop, 0)
|
|
441
|
+
const begun: i64 = monotonicNanos()
|
|
442
|
+
const end: i64 = begun + toI64(timeoutMs) * 1000000
|
|
443
|
+
const lookup: i32 = client.start(name, port, begun)
|
|
444
|
+
if (lookup < 0) {
|
|
445
|
+
netClose(loop)
|
|
446
|
+
return lookup
|
|
447
|
+
}
|
|
448
|
+
const ready: i32[] = new Array<i32>(8)
|
|
449
|
+
let status: i32 = client.result(lookup, out, ttls)
|
|
450
|
+
while (status === DNS_RESOLVE_PENDING) {
|
|
451
|
+
const now: i64 = monotonicNanos()
|
|
452
|
+
if (now >= end) {
|
|
453
|
+
client.cancel(lookup, now)
|
|
454
|
+
status = DNS_RESOLVE_TIMEOUT
|
|
455
|
+
break
|
|
456
|
+
}
|
|
457
|
+
const own: i32 = toI32((end - now + 999999) / 1000000)
|
|
458
|
+
const count: i32 = pollWait(loop, ready, client.timeoutMs(now, own))
|
|
459
|
+
if (count < 0) {
|
|
460
|
+
// A signal answers 0, so a negative count is the loop failing: waiting again would spin.
|
|
461
|
+
client.cancel(lookup, now)
|
|
462
|
+
status = count
|
|
463
|
+
break
|
|
464
|
+
}
|
|
465
|
+
const after: i64 = monotonicNanos()
|
|
466
|
+
let k: i32 = 0
|
|
467
|
+
while (k < count && 2 * k + 1 < toI32(ready.length)) {
|
|
468
|
+
if (client.owns(ready[2 * k])) {
|
|
469
|
+
client.ready(ready[2 * k], ready[2 * k + 1], after)
|
|
470
|
+
}
|
|
471
|
+
k += 1
|
|
472
|
+
}
|
|
473
|
+
client.tick(after)
|
|
474
|
+
status = client.result(lookup, out, ttls)
|
|
475
|
+
}
|
|
476
|
+
netClose(loop)
|
|
477
|
+
return status
|
|
478
|
+
}
|