@nictool/dns-resource-record 1.4.0 → 1.6.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/CHANGELOG.md +30 -1
- package/README.md +49 -42
- package/lib/readme.js +84 -0
- package/lib/tinydns.js +20 -10
- package/package.json +3 -2
- package/rr/a.js +13 -5
- package/rr/aaaa.js +19 -11
- package/rr/apl.js +49 -2
- package/rr/caa.js +30 -23
- package/rr/cert.js +44 -7
- package/rr/cname.js +22 -5
- package/rr/dhcid.js +17 -2
- package/rr/dname.js +15 -5
- package/rr/dnskey.js +31 -8
- package/rr/ds.js +23 -6
- package/rr/hinfo.js +28 -10
- package/rr/hip.js +43 -2
- package/rr/https.js +38 -9
- package/rr/ipseckey.js +20 -16
- package/rr/key.js +26 -17
- package/rr/kx.js +18 -2
- package/rr/loc.js +24 -14
- package/rr/mx.js +16 -6
- package/rr/naptr.js +39 -20
- package/rr/ns.js +23 -5
- package/rr/nsec.js +21 -6
- package/rr/nsec3.js +71 -7
- package/rr/nsec3param.js +52 -22
- package/rr/nxt.js +22 -7
- package/rr/openpgpkey.js +28 -8
- package/rr/ptr.js +23 -5
- package/rr/rp.js +23 -2
- package/rr/rrsig.js +106 -10
- package/rr/sig.js +111 -13
- package/rr/smimea.js +23 -6
- package/rr/soa.js +36 -6
- package/rr/spf.js +17 -3
- package/rr/srv.js +28 -11
- package/rr/sshfp.js +24 -8
- package/rr/svcb.js +37 -10
- package/rr/tlsa.js +26 -9
- package/rr/tsig.js +155 -13
- package/rr/txt.js +46 -12
- package/rr/uri.js +20 -8
- package/rr/wks.js +42 -2
- package/rr.js +109 -7
package/rr/txt.js
CHANGED
|
@@ -16,25 +16,39 @@ export default class TXT extends RR {
|
|
|
16
16
|
return 'Text'
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
getTags() {
|
|
20
|
+
return ['common']
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
getRdataFields(arg) {
|
|
20
24
|
return ['data']
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
getRFCs() {
|
|
24
|
-
return [1035]
|
|
28
|
+
return [1035, 4408, 7208, 6376]
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
getTypeId() {
|
|
28
32
|
return 16
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
getCanonical() {
|
|
36
|
+
return {
|
|
37
|
+
owner: 'example.com.',
|
|
38
|
+
ttl: 3600,
|
|
39
|
+
class: 'IN',
|
|
40
|
+
type: 'TXT',
|
|
41
|
+
data: 'v=spf1 mx -all',
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
/****** IMPORTERS *******/
|
|
32
|
-
fromTinydns(
|
|
33
|
-
const str =
|
|
46
|
+
fromTinydns({ tinyline }) {
|
|
47
|
+
const str = tinyline
|
|
34
48
|
let fqdn, rdata, s, ttl, ts, loc
|
|
35
49
|
// 'fqdn:s:ttl:timestamp:lo
|
|
36
50
|
if (str[0] === "'") {
|
|
37
|
-
;[fqdn, s, ttl, ts, loc] = str.
|
|
51
|
+
;[fqdn, s, ttl, ts, loc] = str.slice(1).split(':')
|
|
38
52
|
rdata = TINYDNS.octalToChar(s)
|
|
39
53
|
} else {
|
|
40
54
|
;[fqdn, rdata, ttl, ts, loc] = this.fromTinydnsGeneric(str)
|
|
@@ -46,14 +60,14 @@ export default class TXT extends RR {
|
|
|
46
60
|
type: 'TXT',
|
|
47
61
|
data: rdata,
|
|
48
62
|
timestamp: ts,
|
|
49
|
-
location: loc
|
|
63
|
+
location: loc?.trim() || '',
|
|
50
64
|
})
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
fromTinydnsGeneric(str) {
|
|
54
68
|
// generic: :fqdn:n:rdata:ttl:timestamp:location
|
|
55
69
|
// eslint-disable-next-line prefer-const
|
|
56
|
-
let [fqdn, n, rdata, ttl, ts, loc] = str.
|
|
70
|
+
let [fqdn, n, rdata, ttl, ts, loc] = str.slice(1).split(':')
|
|
57
71
|
if (n != 16) this.throwHelp('TXT fromTinydns, invalid n')
|
|
58
72
|
|
|
59
73
|
rdata = TINYDNS.octalToChar(rdata)
|
|
@@ -61,19 +75,18 @@ export default class TXT extends RR {
|
|
|
61
75
|
let len = rdata[0].charCodeAt(0)
|
|
62
76
|
let pos = 1
|
|
63
77
|
while (pos < rdata.length) {
|
|
64
|
-
s += rdata.
|
|
78
|
+
s += rdata.slice(pos, +(len + pos))
|
|
65
79
|
pos = len + pos
|
|
66
80
|
len = rdata.charCodeAt(pos + 1)
|
|
67
81
|
}
|
|
68
82
|
return [fqdn, s, ttl, ts, loc]
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
fromBind(
|
|
85
|
+
fromBind({ bindline }) {
|
|
72
86
|
// test.example.com 3600 IN TXT "..."
|
|
73
|
-
const regex =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!match) this.throwHelp(`unable to parse TXT: ${opts.bindline}`)
|
|
87
|
+
const regex = /^(?<owner>\S{1,255})\s+(?<ttl>\d{1,10})\s+(?<cls>IN)\s+(?<type>\w{3})\s+(?<rdata>\S.*)$/i
|
|
88
|
+
const match = bindline.trim().match(regex)
|
|
89
|
+
if (!match) this.throwHelp(`unable to parse TXT: ${bindline}`)
|
|
77
90
|
|
|
78
91
|
const { owner, ttl, cls, type, rdata } = match.groups
|
|
79
92
|
|
|
@@ -99,6 +112,12 @@ export default class TXT extends RR {
|
|
|
99
112
|
return `${this.get('owner')}\t+${this.get('ttl')}\t${this.get('type')}\t'${data}' ~\n`
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
getWireRdata() {
|
|
116
|
+
let data = this.get('data')
|
|
117
|
+
if (Array.isArray(data)) data = data.join('')
|
|
118
|
+
return packStringWire(data)
|
|
119
|
+
}
|
|
120
|
+
|
|
102
121
|
toTinydns() {
|
|
103
122
|
let data = this.get('data')
|
|
104
123
|
if (Array.isArray(data)) data = data.join('')
|
|
@@ -128,3 +147,18 @@ function asQuotedStrings(data) {
|
|
|
128
147
|
|
|
129
148
|
return data
|
|
130
149
|
}
|
|
150
|
+
|
|
151
|
+
function packStringWire(str) {
|
|
152
|
+
const parts = str.match(/(.{1,255})/g)
|
|
153
|
+
let len = 0
|
|
154
|
+
for (const part of parts) len += part.length + 1
|
|
155
|
+
|
|
156
|
+
const buf = Buffer.allocUnsafe(len)
|
|
157
|
+
let offset = 0
|
|
158
|
+
for (const part of parts) {
|
|
159
|
+
buf.writeUInt8(part.length, offset++)
|
|
160
|
+
buf.write(part, offset, 'ascii')
|
|
161
|
+
offset += part.length
|
|
162
|
+
}
|
|
163
|
+
return buf
|
|
164
|
+
}
|
package/rr/uri.js
CHANGED
|
@@ -27,26 +27,26 @@ export default class URI extends RR {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/****** IMPORTERS *******/
|
|
30
|
-
fromTinydns(
|
|
30
|
+
fromTinydns({ tinyline }) {
|
|
31
31
|
// URI via generic, :fqdn:n:rdata:ttl:timestamp:lo
|
|
32
|
-
const [fqdn, n, rdata, ttl, ts, loc] =
|
|
32
|
+
const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
|
|
33
33
|
if (n != 256) this.throwHelp('URI fromTinydns, invalid n')
|
|
34
34
|
|
|
35
35
|
return new URI({
|
|
36
36
|
type: 'URI',
|
|
37
37
|
owner: this.fullyQualify(fqdn),
|
|
38
|
-
priority: TINYDNS.octalToUInt16(rdata.
|
|
39
|
-
weight: TINYDNS.octalToUInt16(rdata.
|
|
40
|
-
target: TINYDNS.octalToChar(rdata.
|
|
38
|
+
priority: TINYDNS.octalToUInt16(rdata.slice(0, 8)),
|
|
39
|
+
weight: TINYDNS.octalToUInt16(rdata.slice(8, 16)),
|
|
40
|
+
target: TINYDNS.octalToChar(rdata.slice(16)),
|
|
41
41
|
ttl: parseInt(ttl, 10),
|
|
42
42
|
timestamp: ts,
|
|
43
|
-
location: loc
|
|
43
|
+
location: loc?.trim() ?? '',
|
|
44
44
|
})
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
fromBind(
|
|
47
|
+
fromBind({ bindline }) {
|
|
48
48
|
// test.example.com 3600 IN URI priority, weight, target
|
|
49
|
-
const [owner, ttl, c, type, priority, weight, target] =
|
|
49
|
+
const [owner, ttl, c, type, priority, weight, target] = bindline.split(/\s+/)
|
|
50
50
|
return new URI({
|
|
51
51
|
class: c,
|
|
52
52
|
type: type,
|
|
@@ -75,6 +75,18 @@ export default class URI extends RR {
|
|
|
75
75
|
return 256
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
getCanonical() {
|
|
79
|
+
return {
|
|
80
|
+
owner: 'www.example.com.',
|
|
81
|
+
ttl: 3600,
|
|
82
|
+
class: 'IN',
|
|
83
|
+
type: 'URI',
|
|
84
|
+
priority: 10,
|
|
85
|
+
weight: 10,
|
|
86
|
+
target: 'http://www.example.com/',
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
getQuotedFields() {
|
|
79
91
|
return ['target']
|
|
80
92
|
}
|
package/rr/wks.js
CHANGED
|
@@ -29,6 +29,10 @@ export default class WKS extends RR {
|
|
|
29
29
|
return 'Well Known Service'
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
getTags() {
|
|
33
|
+
return ['obsolete']
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
getRdataFields(arg) {
|
|
33
37
|
return ['address', 'protocol', 'bit map']
|
|
34
38
|
}
|
|
@@ -41,11 +45,23 @@ export default class WKS extends RR {
|
|
|
41
45
|
return 11
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
getCanonical() {
|
|
49
|
+
return {
|
|
50
|
+
owner: 'host.example.com.',
|
|
51
|
+
ttl: 3600,
|
|
52
|
+
class: 'IN',
|
|
53
|
+
type: 'WKS',
|
|
54
|
+
address: '192.0.2.1',
|
|
55
|
+
protocol: 'TCP',
|
|
56
|
+
'bit map': 'ftp smtp',
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
/****** IMPORTERS *******/
|
|
45
61
|
|
|
46
|
-
fromBind(
|
|
62
|
+
fromBind({ bindline }) {
|
|
47
63
|
// test.example.com 3600 IN WKS 192.168.1.1 TCP ftp smtp
|
|
48
|
-
const parts =
|
|
64
|
+
const parts = bindline.split(/\s+/)
|
|
49
65
|
const [owner, ttl, c, type, address, protocol] = parts
|
|
50
66
|
return new WKS({
|
|
51
67
|
owner,
|
|
@@ -58,6 +74,30 @@ export default class WKS extends RR {
|
|
|
58
74
|
})
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
fromTinydns({ tinyline }) {
|
|
78
|
+
const [owner, _typeId, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
|
|
79
|
+
|
|
80
|
+
const binary = Buffer.from(TINYDNS.octalToChar(rdata), 'binary')
|
|
81
|
+
const address = [binary.readUInt8(0), binary.readUInt8(1), binary.readUInt8(2), binary.readUInt8(3)].join(
|
|
82
|
+
'.',
|
|
83
|
+
)
|
|
84
|
+
const protoNum = binary.readUInt8(4)
|
|
85
|
+
const protoMap = { 6: 'TCP', 17: 'UDP' }
|
|
86
|
+
const protocol = protoMap[protoNum] ?? protoNum
|
|
87
|
+
const bitmap = binary.slice(5).toString()
|
|
88
|
+
|
|
89
|
+
return new WKS({
|
|
90
|
+
owner: this.fullyQualify(owner),
|
|
91
|
+
ttl: parseInt(ttl, 10),
|
|
92
|
+
type: 'WKS',
|
|
93
|
+
address,
|
|
94
|
+
protocol,
|
|
95
|
+
'bit map': bitmap,
|
|
96
|
+
timestamp: ts,
|
|
97
|
+
location: loc?.trim() ?? '',
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
61
101
|
/****** EXPORTERS *******/
|
|
62
102
|
|
|
63
103
|
toTinydns() {
|
package/rr.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import * as TINYDNS from './lib/tinydns.js'
|
|
2
|
+
|
|
1
3
|
export default class RR extends Map {
|
|
2
4
|
constructor(opts) {
|
|
3
5
|
super()
|
|
4
6
|
|
|
5
7
|
if (opts === null) return
|
|
6
8
|
|
|
7
|
-
if (opts
|
|
9
|
+
if (opts?.default) this.default = opts.default
|
|
8
10
|
|
|
9
|
-
if (opts
|
|
10
|
-
if (opts
|
|
11
|
+
if (opts?.bindline) return this.fromBind(opts)
|
|
12
|
+
if (opts?.tinyline) return this.fromTinydns(opts)
|
|
11
13
|
|
|
12
14
|
// tinydns specific
|
|
13
15
|
this.setLocation(opts?.location)
|
|
@@ -21,10 +23,10 @@ export default class RR extends Map {
|
|
|
21
23
|
for (const f of this.getFields('rdata')) {
|
|
22
24
|
const fnName = `set${this.ucFirst(f)}`
|
|
23
25
|
if (this[fnName] === undefined) this.throwHelp(`Missing ${fnName} in class ${this.get('type')}`)
|
|
24
|
-
this[fnName](opts[f])
|
|
26
|
+
this[fnName](opts?.[f])
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
if (opts
|
|
29
|
+
if (opts?.comment) this.set('comment', opts.comment)
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
ucFirst(str) {
|
|
@@ -91,7 +93,7 @@ export default class RR extends Map {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
setTtl(t) {
|
|
94
|
-
|
|
96
|
+
t = t ?? this.default?.ttl
|
|
95
97
|
if (t === undefined) {
|
|
96
98
|
if (['SOA', 'SSHPF'].includes(this.get('type'))) return
|
|
97
99
|
this.throwHelp('TTL is required, no default available')
|
|
@@ -157,7 +159,7 @@ export default class RR extends Map {
|
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
getEmpty(prop) {
|
|
160
|
-
return this.get(prop)
|
|
162
|
+
return this.get(prop) ?? ''
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
getComment(prop) {
|
|
@@ -184,6 +186,10 @@ export default class RR extends Map {
|
|
|
184
186
|
return []
|
|
185
187
|
}
|
|
186
188
|
|
|
189
|
+
getTags() {
|
|
190
|
+
return []
|
|
191
|
+
}
|
|
192
|
+
|
|
187
193
|
getFields(arg) {
|
|
188
194
|
const commonFields = ['owner', 'ttl', 'class', 'type']
|
|
189
195
|
Object.freeze(commonFields)
|
|
@@ -307,6 +313,80 @@ export default class RR extends Map {
|
|
|
307
313
|
)
|
|
308
314
|
}
|
|
309
315
|
|
|
316
|
+
compressIPv6(val) {
|
|
317
|
+
// * RFC 5952
|
|
318
|
+
// * 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
|
|
319
|
+
// * 4.2.1 The use of the symbol "::" MUST be used to its maximum capability.
|
|
320
|
+
// * 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
|
|
321
|
+
// * 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
|
|
322
|
+
// * 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
|
|
323
|
+
|
|
324
|
+
// 4.3 Lowercase and 4.1 remove leading zeros per segment
|
|
325
|
+
const segments = val
|
|
326
|
+
.toLowerCase()
|
|
327
|
+
.split(':')
|
|
328
|
+
.map((s) => s.replace(/^0+/, '') || '0')
|
|
329
|
+
|
|
330
|
+
let bestStart = -1
|
|
331
|
+
let bestLen = 0
|
|
332
|
+
let curStart = -1
|
|
333
|
+
let curLen = 0
|
|
334
|
+
|
|
335
|
+
// 4.2.1 & 4.2.3 Find the longest consecutive run of '0'
|
|
336
|
+
for (let i = 0; i < segments.length; i++) {
|
|
337
|
+
if (segments[i] === '0') {
|
|
338
|
+
if (curStart === -1) curStart = i
|
|
339
|
+
curLen++
|
|
340
|
+
if (curLen > bestLen) {
|
|
341
|
+
bestLen = curLen
|
|
342
|
+
bestStart = curStart
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
curStart = -1
|
|
346
|
+
curLen = 0
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 4.2.2 Don't shorten a single 16-bit 0 field
|
|
351
|
+
if (bestLen < 2) {
|
|
352
|
+
return segments.join(':')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const head = segments.slice(0, bestStart).join(':')
|
|
356
|
+
const tail = segments.slice(bestStart + bestLen).join(':')
|
|
357
|
+
|
|
358
|
+
return `${head}::${tail}`
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
octalToBuffer(octalStr) {
|
|
362
|
+
return Buffer.from(TINYDNS.octalToChar(octalStr), 'binary')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
wirePackDomain(fqdn) {
|
|
366
|
+
return packDomainNameWire(fqdn)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
getWireRdata() {
|
|
370
|
+
const line = this.toTinydns()
|
|
371
|
+
if (!line.startsWith(':'))
|
|
372
|
+
throw new Error(`${this.get('type')}: override getWireRdata() — non-generic tinydns format`)
|
|
373
|
+
// line: :fqdn:typeId:rdata:ttl:ts:loc\n
|
|
374
|
+
const rdata = line.split(':')[3]
|
|
375
|
+
return this.octalToBuffer(rdata ?? '')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
toWire() {
|
|
379
|
+
const rdata = this.getWireRdata()
|
|
380
|
+
const owner = this.wirePackDomain(this.get('owner'))
|
|
381
|
+
const classMap = { IN: 1, CS: 2, CH: 3, HS: 4, NONE: 254, ANY: 255 }
|
|
382
|
+
const meta = Buffer.alloc(10)
|
|
383
|
+
meta.writeUInt16BE(this.getTypeId(), 0)
|
|
384
|
+
meta.writeUInt16BE(classMap[this.get('class')] ?? 1, 2)
|
|
385
|
+
meta.writeUInt32BE(this.get('ttl'), 4)
|
|
386
|
+
meta.writeUInt16BE(rdata.length, 8)
|
|
387
|
+
return Buffer.concat([owner, meta, rdata])
|
|
388
|
+
}
|
|
389
|
+
|
|
310
390
|
toBind(zone_opts) {
|
|
311
391
|
return `${this.getPrefix(zone_opts)}\t${this.getRdataFields()
|
|
312
392
|
.map((f) => this.getQuoted(f))
|
|
@@ -331,3 +411,25 @@ export default class RR extends Map {
|
|
|
331
411
|
.join(' ')}' ~\n`
|
|
332
412
|
}
|
|
333
413
|
}
|
|
414
|
+
|
|
415
|
+
function packDomainNameWire(fqdn) {
|
|
416
|
+
if (fqdn === '.') return Buffer.from([0])
|
|
417
|
+
const parts = fqdn.split('.')
|
|
418
|
+
let len = 0
|
|
419
|
+
for (const part of parts) {
|
|
420
|
+
if (part.length > 0) len += part.length + 1
|
|
421
|
+
}
|
|
422
|
+
len += 1 // for the final \0
|
|
423
|
+
|
|
424
|
+
const buf = Buffer.allocUnsafe(len)
|
|
425
|
+
let offset = 0
|
|
426
|
+
for (const part of parts) {
|
|
427
|
+
if (part.length > 0) {
|
|
428
|
+
buf.writeUInt8(part.length, offset++)
|
|
429
|
+
buf.write(part, offset, 'ascii')
|
|
430
|
+
offset += part.length
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
buf.writeUInt8(0, offset)
|
|
434
|
+
return buf
|
|
435
|
+
}
|