@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,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/dns-message` — a DNS query for one A or AAAA question written into
|
|
3
|
+
* the caller's buffer, and the reply to it read back into addresses.
|
|
4
|
+
*
|
|
5
|
+
* A stub resolver sends one question over UDP and reads whatever datagram
|
|
6
|
+
* comes back to its port, so every byte of a reply is the peer's: a name
|
|
7
|
+
* compressed into a loop, a count of 65,535 answers in a 40-byte message, an A
|
|
8
|
+
* record five bytes long. This module reads a reply against the query it
|
|
9
|
+
* answers and refuses each of those with its own code:
|
|
10
|
+
*
|
|
11
|
+
* import { DnsReply, DNS_NOERROR, DNS_QUERY_MAX_BYTES, DNS_TYPE_A, dnsBuildQuery, dnsParseReply } from "nish/net/dns-message"
|
|
12
|
+
*
|
|
13
|
+
* const query: u8[] = new Array<u8>(DNS_QUERY_MAX_BYTES)
|
|
14
|
+
* const qlen: i32 = dnsBuildQuery(query, id, "example.com", DNS_TYPE_A) // < 0: a DNS_ERR_* code
|
|
15
|
+
* // ... send query[0, qlen), read the datagram into msg[0, mlen) ...
|
|
16
|
+
* const reply: DnsReply = new DnsReply()
|
|
17
|
+
* const status: i32 = dnsParseReply(msg, mlen, query, qlen, 443, reply)
|
|
18
|
+
* if (status === DNS_NOERROR && !reply.truncated) {
|
|
19
|
+
* // reply.addresses[i * DNS_ADDRESS_BYTES, +18) for i < reply.count, each with reply.ttls[i]
|
|
20
|
+
* } // DNS_NXDOMAIN, DNS_SERVFAIL, DNS_REFUSED: the server's answer; < 0: a DNS_ERR_* code
|
|
21
|
+
*
|
|
22
|
+
* **Nothing is allocated per message.** The query is written into the
|
|
23
|
+
* caller's array and the reply read into a `DnsReply` the caller made once and
|
|
24
|
+
* passes again; `dnsParseReply` resets it first. A window outside its array is
|
|
25
|
+
* a bug in the caller and panics; nothing a peer sends does, and the work is
|
|
26
|
+
* linear in the reply's length.
|
|
27
|
+
*
|
|
28
|
+
* **A reply must answer the query.** Its ID, its QR bit, opcode 0, exactly one
|
|
29
|
+
* question, and that question's name (compared without case, as RFC 4343 asks),
|
|
30
|
+
* type and class must all be the query's, or the reply is refused before any
|
|
31
|
+
* record is read: it is not an answer to this question.
|
|
32
|
+
*
|
|
33
|
+
* **Names are bounded.** A compression pointer must point before the start of
|
|
34
|
+
* the run of labels that holds it, so every hop moves backwards and a loop is
|
|
35
|
+
* impossible; at most `DNS_MAX_POINTER_HOPS` (16) hops are followed, a name
|
|
36
|
+
* is at most 255 bytes in wire form, and the label types RFC 6891 retired
|
|
37
|
+
* (`0x40`, `0x80`) are refused.
|
|
38
|
+
*
|
|
39
|
+
* **Only the question's name is answered.** Records are read in message
|
|
40
|
+
* order, and the chain starts at the question's name: a CNAME whose owner is
|
|
41
|
+
* the chain's end moves the end to its target, and an A or AAAA record of the
|
|
42
|
+
* asked type whose owner is the chain's end is kept. Every other record is
|
|
43
|
+
* read past, so an answer for a name nobody asked about never becomes an
|
|
44
|
+
* address. The first `DNS_MAX_ANSWERS` (8) addresses are kept and the rest
|
|
45
|
+
* counted in `dropped`.
|
|
46
|
+
*
|
|
47
|
+
* **Addresses are the 18-byte form** of `docs/LANGUAGE.md`: sixteen bytes of
|
|
48
|
+
* IPv6, an IPv4 address as `::ffff:a.b.c.d`, then the caller's port big-endian,
|
|
49
|
+
* so one can be handed to a connect as it stands.
|
|
50
|
+
*
|
|
51
|
+
* The record of what this module refuses and why is `docs/security/dns.md`.
|
|
52
|
+
*/
|
|
53
|
+
import { getU16BE, getU32BE, setU16BE } from "nish/bytes"
|
|
54
|
+
|
|
55
|
+
/** The record type of an IPv4 address (RFC 1035 §3.2.2). */
|
|
56
|
+
export const DNS_TYPE_A: i32 = 1
|
|
57
|
+
/** The record type of an alias (RFC 1035 §3.2.2). */
|
|
58
|
+
export const DNS_TYPE_CNAME: i32 = 5
|
|
59
|
+
/** The record type of an IPv6 address (RFC 3596 §2.1). */
|
|
60
|
+
export const DNS_TYPE_AAAA: i32 = 28
|
|
61
|
+
/** The Internet class, the only one asked or kept. */
|
|
62
|
+
export const DNS_CLASS_IN: i32 = 1
|
|
63
|
+
|
|
64
|
+
/** The bytes of one address: sixteen of IPv6 and two of port. */
|
|
65
|
+
export const DNS_ADDRESS_BYTES: i32 = 18
|
|
66
|
+
/** How many addresses one reply keeps; the rest are counted in `DnsReply.dropped`. */
|
|
67
|
+
export const DNS_MAX_ANSWERS: i32 = 8
|
|
68
|
+
/** How many compression pointers one name may follow. */
|
|
69
|
+
export const DNS_MAX_POINTER_HOPS: i32 = 16
|
|
70
|
+
/** The longest name in wire form, its root byte included (RFC 1035 §2.3.4). */
|
|
71
|
+
export const DNS_MAX_NAME_BYTES: i32 = 255
|
|
72
|
+
/** The longest label (RFC 1035 §2.3.4). */
|
|
73
|
+
export const DNS_MAX_LABEL_BYTES: i32 = 63
|
|
74
|
+
/** The longest query `dnsBuildQuery` writes: the header, the longest name, the type and class. */
|
|
75
|
+
export const DNS_QUERY_MAX_BYTES: i32 = 12 + 255 + 4
|
|
76
|
+
|
|
77
|
+
/** The server answered: the name exists, and its addresses, if any, are in the reply. */
|
|
78
|
+
export const DNS_NOERROR: i32 = 0
|
|
79
|
+
/** The server could not answer (RCODE 2). */
|
|
80
|
+
export const DNS_SERVFAIL: i32 = 2
|
|
81
|
+
/** The name does not exist (RCODE 3). */
|
|
82
|
+
export const DNS_NXDOMAIN: i32 = 3
|
|
83
|
+
/** The server will not answer (RCODE 5). */
|
|
84
|
+
export const DNS_REFUSED: i32 = 5
|
|
85
|
+
|
|
86
|
+
/** `out` has no room for the query; nothing is written past `out.length`. */
|
|
87
|
+
export const DNS_ERR_SPACE: i32 = -1
|
|
88
|
+
/** The name to ask is empty, or holds an empty label (`a..b`, `.a`). */
|
|
89
|
+
export const DNS_ERR_EMPTY_LABEL: i32 = -2
|
|
90
|
+
/** A label of the name to ask is longer than 63 bytes. */
|
|
91
|
+
export const DNS_ERR_LABEL_LONG: i32 = -3
|
|
92
|
+
/** A name is longer than 255 bytes in wire form: the one to ask, or one in the reply. */
|
|
93
|
+
export const DNS_ERR_NAME_LONG: i32 = -4
|
|
94
|
+
/** The name to ask holds a byte that is not printable ASCII (a control byte, a space, or above 0x7E). */
|
|
95
|
+
export const DNS_ERR_NAME_BYTE: i32 = -5
|
|
96
|
+
/** The type to ask is not `DNS_TYPE_A` or `DNS_TYPE_AAAA`. */
|
|
97
|
+
export const DNS_ERR_QTYPE: i32 = -6
|
|
98
|
+
/** The reply is shorter than its 12-byte header. */
|
|
99
|
+
export const DNS_ERR_HEADER: i32 = -7
|
|
100
|
+
/** The reply's QR bit is clear: it is a query, not a reply. */
|
|
101
|
+
export const DNS_ERR_NOT_REPLY: i32 = -8
|
|
102
|
+
/** The reply's opcode is not 0 (a standard query). */
|
|
103
|
+
export const DNS_ERR_OPCODE: i32 = -9
|
|
104
|
+
/** The reply's ID is not the query's. */
|
|
105
|
+
export const DNS_ERR_ID: i32 = -10
|
|
106
|
+
/** The reply does not hold exactly one question, or not the query's name, type and class. */
|
|
107
|
+
export const DNS_ERR_QUESTION: i32 = -11
|
|
108
|
+
/** A name or a record's fixed fields run past the end of the reply. */
|
|
109
|
+
export const DNS_ERR_SHORT: i32 = -12
|
|
110
|
+
/** The reply's counts promise more records than its remaining bytes can hold. */
|
|
111
|
+
export const DNS_ERR_COUNT: i32 = -13
|
|
112
|
+
/** A compression pointer points at or after the run of labels that holds it, or into the header. */
|
|
113
|
+
export const DNS_ERR_POINTER: i32 = -14
|
|
114
|
+
/** A name follows more than `DNS_MAX_POINTER_HOPS` compression pointers. */
|
|
115
|
+
export const DNS_ERR_HOPS: i32 = -15
|
|
116
|
+
/** A label's length byte starts with the bits `01` or `10`, types RFC 6891 retired. */
|
|
117
|
+
export const DNS_ERR_LABEL_TYPE: i32 = -16
|
|
118
|
+
/** An A record's RDLENGTH is not 4, or an AAAA record's is not 16. */
|
|
119
|
+
export const DNS_ERR_RDLENGTH: i32 = -17
|
|
120
|
+
/** A record's RDATA runs past the end of the reply, or a CNAME's target does not fill it. */
|
|
121
|
+
export const DNS_ERR_RDATA: i32 = -18
|
|
122
|
+
/** The reply's RCODE is none of NOERROR, SERVFAIL, NXDOMAIN and REFUSED (FORMERR, NOTIMP, or one undefined). */
|
|
123
|
+
export const DNS_ERR_RCODE: i32 = -19
|
|
124
|
+
|
|
125
|
+
const DNS_HEADER_BYTES: i32 = 12
|
|
126
|
+
const DNS_FLAG_QR: i32 = 0x8000
|
|
127
|
+
const DNS_FLAG_TC: i32 = 0x0200
|
|
128
|
+
const DNS_FLAG_RD: i32 = 0x0100
|
|
129
|
+
/** The fewest bytes a record can take: a root name and its ten fixed bytes. */
|
|
130
|
+
const DNS_MIN_RECORD_BYTES: i32 = 11
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* One reply read by `dnsParseReply`, made once and reused.
|
|
134
|
+
*
|
|
135
|
+
* The arrays are sized for `DNS_MAX_ANSWERS`, and `owner` and `chain` hold a
|
|
136
|
+
* name each in lowercase wire form, so reading a reply writes only into what
|
|
137
|
+
* the constructor made.
|
|
138
|
+
*/
|
|
139
|
+
export class DnsReply {
|
|
140
|
+
/** `count` addresses of `DNS_ADDRESS_BYTES` each, from offset 0. */
|
|
141
|
+
addresses: u8[]
|
|
142
|
+
/** The TTL of each kept address, in seconds; one above 2^31 - 1 reads as 0 (RFC 2181 §8). */
|
|
143
|
+
ttls: i32[]
|
|
144
|
+
/** How many addresses were kept. */
|
|
145
|
+
count: i32 = 0
|
|
146
|
+
/** How many addresses past `DNS_MAX_ANSWERS` were read and not kept. */
|
|
147
|
+
dropped: i32 = 0
|
|
148
|
+
/** The reply's RCODE, once its header was read. */
|
|
149
|
+
rcode: i32 = 0
|
|
150
|
+
/** The TC bit: the reply was cut to fit a datagram, and no record of it was read. */
|
|
151
|
+
truncated: boolean = false
|
|
152
|
+
/** The last record's owner name, lowercase, in wire form. */
|
|
153
|
+
owner: u8[]
|
|
154
|
+
/** The end of the alias chain, lowercase, in wire form: the question's name until a CNAME moves it. */
|
|
155
|
+
chain: u8[]
|
|
156
|
+
/** The question's name as the query asked it, lowercase, in wire form. */
|
|
157
|
+
asked: u8[]
|
|
158
|
+
|
|
159
|
+
constructor() {
|
|
160
|
+
this.addresses = new Array<u8>(DNS_MAX_ANSWERS * DNS_ADDRESS_BYTES)
|
|
161
|
+
this.ttls = new Array<i32>(DNS_MAX_ANSWERS)
|
|
162
|
+
this.owner = new Array<u8>(DNS_MAX_NAME_BYTES)
|
|
163
|
+
this.chain = new Array<u8>(DNS_MAX_NAME_BYTES)
|
|
164
|
+
this.asked = new Array<u8>(DNS_MAX_NAME_BYTES)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Panics unless `[0, len)` lies inside `bytes`. */
|
|
169
|
+
const dnsCheckLength = (bytes: u8[], len: i32, who: string): void => {
|
|
170
|
+
if (len < 0 || len > toI32(bytes.length)) {
|
|
171
|
+
panic(who + ": the length is outside the array")
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** `b` with A to Z read as a to z, and every other byte as it stands. */
|
|
176
|
+
const dnsLower = (b: i32): i32 => (b >= 65 && b <= 90 ? b + 32 : b)
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Writes the query for `name` and `qtype` into `out` from offset 0: the ID,
|
|
180
|
+
* RD set, one question of class IN, and no EDNS record. Answers the query's
|
|
181
|
+
* length, or a `DNS_ERR_*` code with `out` partly written.
|
|
182
|
+
*
|
|
183
|
+
* `name` is ASCII, its labels split on `.`, and one trailing `.` is read as the
|
|
184
|
+
* root it names. `id` is the caller's: a resolver draws it at random, so that a
|
|
185
|
+
* spoofed reply must guess it, and passes the same query to `dnsParseReply`.
|
|
186
|
+
*/
|
|
187
|
+
export const dnsBuildQuery = (out: u8[], id: i32, name: string, qtype: i32): i32 => {
|
|
188
|
+
if (id < 0 || id > 0xffff) {
|
|
189
|
+
panic(`dnsBuildQuery: the id ${id} is not 16 bits`)
|
|
190
|
+
}
|
|
191
|
+
if (qtype !== DNS_TYPE_A && qtype !== DNS_TYPE_AAAA) {
|
|
192
|
+
return DNS_ERR_QTYPE
|
|
193
|
+
}
|
|
194
|
+
let n: i32 = toI32(name.length)
|
|
195
|
+
if (n > 0 && name.charCodeAt(n - 1) === 46) {
|
|
196
|
+
n -= 1
|
|
197
|
+
}
|
|
198
|
+
if (n === 0) {
|
|
199
|
+
return DNS_ERR_EMPTY_LABEL
|
|
200
|
+
}
|
|
201
|
+
// n bytes of name become n + 1 of labels and one root byte.
|
|
202
|
+
if (n + 2 > DNS_MAX_NAME_BYTES) {
|
|
203
|
+
return DNS_ERR_NAME_LONG
|
|
204
|
+
}
|
|
205
|
+
const total: i32 = DNS_HEADER_BYTES + n + 2 + 4
|
|
206
|
+
if (total > toI32(out.length)) {
|
|
207
|
+
return DNS_ERR_SPACE
|
|
208
|
+
}
|
|
209
|
+
setU16BE(out, 0, toU16(id))
|
|
210
|
+
setU16BE(out, 2, toU16(DNS_FLAG_RD))
|
|
211
|
+
setU16BE(out, 4, toU16(1))
|
|
212
|
+
setU16BE(out, 6, toU16(0))
|
|
213
|
+
setU16BE(out, 8, toU16(0))
|
|
214
|
+
setU16BE(out, 10, toU16(0))
|
|
215
|
+
// `lengthAt` is the length byte of the label being written.
|
|
216
|
+
let lengthAt: i32 = DNS_HEADER_BYTES
|
|
217
|
+
let i: i32 = 0
|
|
218
|
+
while (i <= n) {
|
|
219
|
+
const b: i32 = i < n ? toI32(name.charCodeAt(i)) : 46
|
|
220
|
+
if (b === 46) {
|
|
221
|
+
const label: i32 = DNS_HEADER_BYTES + i - lengthAt
|
|
222
|
+
if (label === 0) {
|
|
223
|
+
return DNS_ERR_EMPTY_LABEL
|
|
224
|
+
}
|
|
225
|
+
if (label > DNS_MAX_LABEL_BYTES) {
|
|
226
|
+
return DNS_ERR_LABEL_LONG
|
|
227
|
+
}
|
|
228
|
+
if (lengthAt < 0 || lengthAt >= toI32(out.length)) {
|
|
229
|
+
return DNS_ERR_SPACE
|
|
230
|
+
}
|
|
231
|
+
out[lengthAt] = toU8(label)
|
|
232
|
+
lengthAt = DNS_HEADER_BYTES + i + 1
|
|
233
|
+
} else if (b < 33 || b > 126) {
|
|
234
|
+
return DNS_ERR_NAME_BYTE
|
|
235
|
+
} else {
|
|
236
|
+
out[DNS_HEADER_BYTES + i + 1] = toU8(b)
|
|
237
|
+
}
|
|
238
|
+
i += 1
|
|
239
|
+
}
|
|
240
|
+
out[DNS_HEADER_BYTES + n + 1] = toU8(0)
|
|
241
|
+
setU16BE(out, DNS_HEADER_BYTES + n + 2, toU16(qtype))
|
|
242
|
+
setU16BE(out, DNS_HEADER_BYTES + n + 4, toU16(DNS_CLASS_IN))
|
|
243
|
+
return total
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Reads the name at `msg[off]`, following compression pointers, into `out` in
|
|
248
|
+
* lowercase wire form, and answers the offset just past the name where it
|
|
249
|
+
* stands in the message (past its first pointer, when it has one), or a
|
|
250
|
+
* `DNS_ERR_*` code.
|
|
251
|
+
*
|
|
252
|
+
* A pointer must point before `runStart`, the offset the labels holding it
|
|
253
|
+
* started at, and after the header: each hop then moves strictly backwards,
|
|
254
|
+
* which is what makes a loop impossible rather than merely bounded.
|
|
255
|
+
*/
|
|
256
|
+
const dnsReadName = (msg: u8[], len: i32, off: i32, out: u8[]): i32 => {
|
|
257
|
+
let pos: i32 = off
|
|
258
|
+
let runStart: i32 = off
|
|
259
|
+
let after: i32 = -1
|
|
260
|
+
let hops: i32 = 0
|
|
261
|
+
let w: i32 = 0
|
|
262
|
+
const msgLen: i32 = toI32(msg.length)
|
|
263
|
+
const outLen: i32 = toI32(out.length)
|
|
264
|
+
while (true) {
|
|
265
|
+
if (pos < 0 || pos >= len || pos >= msgLen) {
|
|
266
|
+
return DNS_ERR_SHORT
|
|
267
|
+
}
|
|
268
|
+
const b: i32 = toI32(msg[pos])
|
|
269
|
+
if (w < 0 || w >= outLen) {
|
|
270
|
+
return DNS_ERR_NAME_LONG
|
|
271
|
+
}
|
|
272
|
+
if (b === 0) {
|
|
273
|
+
out[w] = toU8(0)
|
|
274
|
+
return after >= 0 ? after : pos + 1
|
|
275
|
+
}
|
|
276
|
+
const kind: i32 = b & 0xc0
|
|
277
|
+
if (kind === 0xc0) {
|
|
278
|
+
if (pos + 1 >= len || pos + 1 >= msgLen) {
|
|
279
|
+
return DNS_ERR_SHORT
|
|
280
|
+
}
|
|
281
|
+
const target: i32 = ((b & 0x3f) << 8) | toI32(msg[pos + 1])
|
|
282
|
+
if (target >= runStart || target < DNS_HEADER_BYTES) {
|
|
283
|
+
return DNS_ERR_POINTER
|
|
284
|
+
}
|
|
285
|
+
hops += 1
|
|
286
|
+
if (hops > DNS_MAX_POINTER_HOPS) {
|
|
287
|
+
return DNS_ERR_HOPS
|
|
288
|
+
}
|
|
289
|
+
if (after < 0) {
|
|
290
|
+
after = pos + 2
|
|
291
|
+
}
|
|
292
|
+
pos = target
|
|
293
|
+
runStart = target
|
|
294
|
+
} else if (kind !== 0) {
|
|
295
|
+
return DNS_ERR_LABEL_TYPE
|
|
296
|
+
} else {
|
|
297
|
+
if (b > len - pos - 1) {
|
|
298
|
+
return DNS_ERR_SHORT
|
|
299
|
+
}
|
|
300
|
+
// The label, its length byte and the root byte still to come.
|
|
301
|
+
if (w + b + 2 > DNS_MAX_NAME_BYTES) {
|
|
302
|
+
return DNS_ERR_NAME_LONG
|
|
303
|
+
}
|
|
304
|
+
out[w] = toU8(b)
|
|
305
|
+
let k: i32 = 1
|
|
306
|
+
while (k <= b) {
|
|
307
|
+
out[w + k] = toU8(dnsLower(toI32(msg[pos + k])))
|
|
308
|
+
k += 1
|
|
309
|
+
}
|
|
310
|
+
w += b + 1
|
|
311
|
+
pos += b + 1
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Whether two names `dnsReadName` wrote are the same name. */
|
|
317
|
+
const dnsSameName = (a: u8[], b: u8[]): boolean => {
|
|
318
|
+
let i: i32 = 0
|
|
319
|
+
const aLen: i32 = toI32(a.length)
|
|
320
|
+
const bLen: i32 = toI32(b.length)
|
|
321
|
+
while (true) {
|
|
322
|
+
if (i < 0 || i >= aLen || i >= bLen) {
|
|
323
|
+
return false
|
|
324
|
+
}
|
|
325
|
+
const n: i32 = toI32(a[i])
|
|
326
|
+
if (n !== toI32(b[i])) {
|
|
327
|
+
return false
|
|
328
|
+
}
|
|
329
|
+
if (n === 0) {
|
|
330
|
+
return true
|
|
331
|
+
}
|
|
332
|
+
let k: i32 = 1
|
|
333
|
+
while (k <= n) {
|
|
334
|
+
if (a[i + k] !== b[i + k]) {
|
|
335
|
+
return false
|
|
336
|
+
}
|
|
337
|
+
k += 1
|
|
338
|
+
}
|
|
339
|
+
i += n + 1
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Copies `name`, a name `dnsReadName` wrote, into `into`. */
|
|
344
|
+
const dnsCopyName = (name: u8[], into: u8[]): void => {
|
|
345
|
+
let i: i32 = 0
|
|
346
|
+
const nameLen: i32 = toI32(name.length)
|
|
347
|
+
while (true) {
|
|
348
|
+
if (i < 0 || i >= nameLen) {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
const n: i32 = toI32(name[i])
|
|
352
|
+
let k: i32 = 0
|
|
353
|
+
while (k <= n) {
|
|
354
|
+
into[i + k] = name[i + k]
|
|
355
|
+
k += 1
|
|
356
|
+
}
|
|
357
|
+
if (n === 0) {
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
i += n + 1
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Keeps the RDATA at `msg[at]`, `size` bytes, as the next address of `reply`, with `port` and `ttl`. */
|
|
365
|
+
const dnsKeepAddress = (msg: u8[], at: i32, size: i32, port: i32, ttl: i32, reply: DnsReply): void => {
|
|
366
|
+
const base: i32 = reply.count * DNS_ADDRESS_BYTES
|
|
367
|
+
const lead: i32 = 16 - size
|
|
368
|
+
let k: i32 = 0
|
|
369
|
+
while (k < lead) {
|
|
370
|
+
reply.addresses[base + k] = toU8(0)
|
|
371
|
+
k += 1
|
|
372
|
+
}
|
|
373
|
+
if (size === 4) {
|
|
374
|
+
reply.addresses[base + 10] = toU8(255)
|
|
375
|
+
reply.addresses[base + 11] = toU8(255)
|
|
376
|
+
}
|
|
377
|
+
k = 0
|
|
378
|
+
while (k < size) {
|
|
379
|
+
reply.addresses[base + lead + k] = msg[at + k]
|
|
380
|
+
k += 1
|
|
381
|
+
}
|
|
382
|
+
setU16BE(reply.addresses, base + 16, toU16(port))
|
|
383
|
+
reply.ttls[reply.count] = ttl
|
|
384
|
+
reply.count += 1
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Reads `msg[0, len)` as the reply to `query[0, queryLen)`, a query
|
|
389
|
+
* `dnsBuildQuery` wrote, into `reply`, with `port` written into every address.
|
|
390
|
+
*
|
|
391
|
+
* Answers `DNS_NOERROR`, `DNS_NXDOMAIN`, `DNS_SERVFAIL` or `DNS_REFUSED` for a
|
|
392
|
+
* reply that answers the query, or a `DNS_ERR_*` code for one that is
|
|
393
|
+
* malformed or answers another. The answer records are read only under
|
|
394
|
+
* NOERROR and with TC clear; with TC set, `reply.truncated` says so,
|
|
395
|
+
* `reply.count` is 0 whatever the counts claim, and the caller asks again
|
|
396
|
+
* over TCP. The authority and
|
|
397
|
+
* additional sections are counted against the bytes left and never read.
|
|
398
|
+
*/
|
|
399
|
+
export const dnsParseReply = (
|
|
400
|
+
msg: u8[],
|
|
401
|
+
len: i32,
|
|
402
|
+
query: u8[],
|
|
403
|
+
queryLen: i32,
|
|
404
|
+
port: i32,
|
|
405
|
+
reply: DnsReply
|
|
406
|
+
): i32 => {
|
|
407
|
+
dnsCheckLength(msg, len, "dnsParseReply")
|
|
408
|
+
dnsCheckLength(query, queryLen, "dnsParseReply")
|
|
409
|
+
if (port < 0 || port > 0xffff) {
|
|
410
|
+
panic(`dnsParseReply: the port ${port} is not 16 bits`)
|
|
411
|
+
}
|
|
412
|
+
reply.count = 0
|
|
413
|
+
reply.dropped = 0
|
|
414
|
+
reply.rcode = 0
|
|
415
|
+
reply.truncated = false
|
|
416
|
+
if (len < DNS_HEADER_BYTES) {
|
|
417
|
+
return DNS_ERR_HEADER
|
|
418
|
+
}
|
|
419
|
+
const flags: i32 = toI32(getU16BE(msg, 2))
|
|
420
|
+
if ((flags & DNS_FLAG_QR) === 0) {
|
|
421
|
+
return DNS_ERR_NOT_REPLY
|
|
422
|
+
}
|
|
423
|
+
if (((flags >> 11) & 15) !== 0) {
|
|
424
|
+
return DNS_ERR_OPCODE
|
|
425
|
+
}
|
|
426
|
+
if (queryLen < DNS_HEADER_BYTES || msg[0] !== query[0] || msg[1] !== query[1]) {
|
|
427
|
+
return DNS_ERR_ID
|
|
428
|
+
}
|
|
429
|
+
if (toI32(getU16BE(msg, 4)) !== 1) {
|
|
430
|
+
return DNS_ERR_QUESTION
|
|
431
|
+
}
|
|
432
|
+
const asked: i32 = dnsReadName(query, queryLen, DNS_HEADER_BYTES, reply.asked)
|
|
433
|
+
if (asked < 0 || asked + 4 > queryLen) {
|
|
434
|
+
panic("dnsParseReply: the query is not one dnsBuildQuery wrote")
|
|
435
|
+
}
|
|
436
|
+
const named: i32 = dnsReadName(msg, len, DNS_HEADER_BYTES, reply.chain)
|
|
437
|
+
if (named < 0) {
|
|
438
|
+
return named
|
|
439
|
+
}
|
|
440
|
+
if (named + 4 > len) {
|
|
441
|
+
return DNS_ERR_SHORT
|
|
442
|
+
}
|
|
443
|
+
if (
|
|
444
|
+
!dnsSameName(reply.chain, reply.asked) ||
|
|
445
|
+
msg[named] !== query[asked] ||
|
|
446
|
+
msg[named + 1] !== query[asked + 1] ||
|
|
447
|
+
msg[named + 2] !== query[asked + 2] ||
|
|
448
|
+
msg[named + 3] !== query[asked + 3]
|
|
449
|
+
) {
|
|
450
|
+
return DNS_ERR_QUESTION
|
|
451
|
+
}
|
|
452
|
+
const qtype: i32 = toI32(getU16BE(query, asked))
|
|
453
|
+
reply.truncated = (flags & DNS_FLAG_TC) !== 0
|
|
454
|
+
const rcode: i32 = flags & 15
|
|
455
|
+
reply.rcode = rcode
|
|
456
|
+
if (rcode !== DNS_NOERROR && rcode !== DNS_SERVFAIL && rcode !== DNS_NXDOMAIN && rcode !== DNS_REFUSED) {
|
|
457
|
+
return DNS_ERR_RCODE
|
|
458
|
+
}
|
|
459
|
+
// A truncated reply's counts describe the records the server could not fit,
|
|
460
|
+
// so they are not held to the bytes that arrived.
|
|
461
|
+
if (reply.truncated) {
|
|
462
|
+
return rcode
|
|
463
|
+
}
|
|
464
|
+
let pos: i32 = named + 4
|
|
465
|
+
const records: i32 = toI32(getU16BE(msg, 6)) + toI32(getU16BE(msg, 8)) + toI32(getU16BE(msg, 10))
|
|
466
|
+
if (records > (len - pos) / DNS_MIN_RECORD_BYTES) {
|
|
467
|
+
return DNS_ERR_COUNT
|
|
468
|
+
}
|
|
469
|
+
if (rcode !== DNS_NOERROR) {
|
|
470
|
+
return rcode
|
|
471
|
+
}
|
|
472
|
+
const answers: i32 = toI32(getU16BE(msg, 6))
|
|
473
|
+
let i: i32 = 0
|
|
474
|
+
while (i < answers) {
|
|
475
|
+
const fixed: i32 = dnsReadName(msg, len, pos, reply.owner)
|
|
476
|
+
if (fixed < 0) {
|
|
477
|
+
return fixed
|
|
478
|
+
}
|
|
479
|
+
if (fixed + 10 > len) {
|
|
480
|
+
return DNS_ERR_SHORT
|
|
481
|
+
}
|
|
482
|
+
const type: i32 = toI32(getU16BE(msg, fixed))
|
|
483
|
+
const klass: i32 = toI32(getU16BE(msg, fixed + 2))
|
|
484
|
+
const rawTtl: i32 = toI32(getU32BE(msg, fixed + 4))
|
|
485
|
+
const size: i32 = toI32(getU16BE(msg, fixed + 8))
|
|
486
|
+
const data: i32 = fixed + 10
|
|
487
|
+
if (size > len - data) {
|
|
488
|
+
return DNS_ERR_RDATA
|
|
489
|
+
}
|
|
490
|
+
if (klass === DNS_CLASS_IN) {
|
|
491
|
+
if ((type === DNS_TYPE_A && size !== 4) || (type === DNS_TYPE_AAAA && size !== 16)) {
|
|
492
|
+
return DNS_ERR_RDLENGTH
|
|
493
|
+
}
|
|
494
|
+
if (dnsSameName(reply.owner, reply.chain)) {
|
|
495
|
+
if (type === DNS_TYPE_CNAME) {
|
|
496
|
+
const target: i32 = dnsReadName(msg, data + size, data, reply.owner)
|
|
497
|
+
if (target < 0) {
|
|
498
|
+
return target
|
|
499
|
+
}
|
|
500
|
+
if (target !== data + size) {
|
|
501
|
+
return DNS_ERR_RDATA
|
|
502
|
+
}
|
|
503
|
+
dnsCopyName(reply.owner, reply.chain)
|
|
504
|
+
} else if (type === qtype) {
|
|
505
|
+
if (reply.count < DNS_MAX_ANSWERS) {
|
|
506
|
+
dnsKeepAddress(msg, data, size, port, rawTtl < 0 ? 0 : rawTtl, reply)
|
|
507
|
+
} else {
|
|
508
|
+
reply.dropped += 1
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
pos = data + size
|
|
514
|
+
i += 1
|
|
515
|
+
}
|
|
516
|
+
return DNS_NOERROR
|
|
517
|
+
}
|