@nictool/dns-resource-record 1.6.0 → 1.7.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 +45 -6
- package/README.md +328 -198
- package/dist/dns-rr.min.js +26 -0
- package/dist/dns-rr.min.js.map +7 -0
- package/index.js +12 -11
- package/lib/binary.js +108 -0
- package/lib/bind.js +94 -0
- package/lib/dns-query.js +67 -0
- package/lib/tinydns.js +128 -44
- package/lib/wire.js +519 -0
- package/package.json +29 -10
- package/rr/TEMPLATE.js +124 -0
- package/rr/a.js +8 -48
- package/rr/aaaa.js +13 -80
- package/rr/apl.js +123 -27
- package/rr/caa.js +27 -50
- package/rr/cert.js +58 -87
- package/rr/cname.js +7 -63
- package/rr/dhcid.js +20 -30
- package/rr/dname.js +9 -36
- package/rr/dnskey.js +38 -32
- package/rr/ds.js +43 -57
- package/rr/hinfo.js +21 -40
- package/rr/hip.js +88 -26
- package/rr/https.js +28 -40
- package/rr/ipseckey.js +97 -18
- package/rr/key.js +39 -29
- package/rr/kx.js +16 -27
- package/rr/loc.js +42 -12
- package/rr/mx.js +16 -51
- package/rr/naptr.js +58 -33
- package/rr/ns.js +8 -36
- package/rr/nsec.js +89 -18
- package/rr/nsec3.js +62 -26
- package/rr/nsec3param.js +56 -53
- package/rr/nxt.js +57 -18
- package/rr/openpgpkey.js +24 -30
- package/rr/ptr.js +7 -47
- package/rr/rp.js +17 -32
- package/rr/rrsig.js +80 -85
- package/rr/sig.js +70 -45
- package/rr/smimea.js +33 -33
- package/rr/soa.js +31 -49
- package/rr/spf.js +10 -17
- package/rr/srv.js +22 -62
- package/rr/sshfp.js +25 -53
- package/rr/svcb.js +27 -40
- package/rr/tlsa.js +34 -34
- package/rr/tsig.js +87 -24
- package/rr/txt.js +40 -59
- package/rr/uri.js +19 -29
- package/rr/wks.js +154 -22
- package/rr.js +290 -109
- package/lib/readme.js +0 -84
package/rr/TEMPLATE.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import RR from '../rr.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DNS Resource Record Template
|
|
5
|
+
*
|
|
6
|
+
* Use this as a starting point for creating a new Resource Record (RR) class.
|
|
7
|
+
* Replace 'TEMPLATE' with the actual record type (e.g., 'MX', 'SRV').
|
|
8
|
+
*
|
|
9
|
+
* ## Mandates
|
|
10
|
+
* - Every new RR MUST extend the `RR` class.
|
|
11
|
+
* - 100% test coverage is required for all new PRs.
|
|
12
|
+
* - Follow established patterns for naming and implementation.
|
|
13
|
+
* - **Registration**: You MUST update `index.js` to import and export the new class and add it to the `classes` array.
|
|
14
|
+
* - **Tests**: You MUST create a corresponding test file in `test/rr/<type>.js` (mirroring an existing test file like `test/rr/a.js`).
|
|
15
|
+
*
|
|
16
|
+
* ## `rdataFields` and Avoiding Boilerplate
|
|
17
|
+
* The `rdataFields` static property defines the fields in the RDATA section.
|
|
18
|
+
* It is an array where each element can be:
|
|
19
|
+
* - A string: The field name.
|
|
20
|
+
* - An array: `[fieldName, type]`
|
|
21
|
+
*
|
|
22
|
+
* Supported types for `setTypedValue`:
|
|
23
|
+
* - `u8`, `u16`, `u32`: Unsigned integers of 8, 16, or 32 bits.
|
|
24
|
+
* - `fqdn`: A fully qualified domain name.
|
|
25
|
+
* - `qstr`: Quoted string field (BIND quoting behavior).
|
|
26
|
+
* - `charstr`: DNS length-prefixed character-string (max 255 bytes).
|
|
27
|
+
* - `qcharstr`: Quoted DNS length-prefixed character-string.
|
|
28
|
+
* - `charstrs`: Concatenated DNS character-strings.
|
|
29
|
+
* - `svcparams`: SVCB/HTTPS params string.
|
|
30
|
+
* - `base64`: Base64 encoded data.
|
|
31
|
+
* - `hex`: Hexadecimal encoded data.
|
|
32
|
+
* - `str`: A simple string.
|
|
33
|
+
* - `ipv4`: An IPv4 address.
|
|
34
|
+
* - `ipv6`: An IPv6 address.
|
|
35
|
+
*
|
|
36
|
+
* If you provide a type in `rdataFields`, the base `RR` class automatically
|
|
37
|
+
* handles validation and setting of that field. You only need to implement
|
|
38
|
+
* a custom `set<FieldName>` method if you need more complex validation
|
|
39
|
+
* logic than what the default types provide.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export default class TEMPLATE extends RR {
|
|
43
|
+
// The DNS record type name (e.g., 'A', 'MX', 'TXT')
|
|
44
|
+
static typeName = 'TEMPLATE'
|
|
45
|
+
static typeId = 0
|
|
46
|
+
static RFCs = []
|
|
47
|
+
static tags = []
|
|
48
|
+
|
|
49
|
+
// The character used in tinydns format (optional)
|
|
50
|
+
static tinydnsType = 'T'
|
|
51
|
+
|
|
52
|
+
// Define the RDATA fields.
|
|
53
|
+
// Using types here avoids the need for manual setters in many cases.
|
|
54
|
+
static rdataFields = [
|
|
55
|
+
['field1', 'u16'],
|
|
56
|
+
['field2', 'fqdn'],
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
constructor(opts) {
|
|
60
|
+
super(opts)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/****** Resource record specific setters *******/
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
// Example of a custom setter. Only implement if default validation
|
|
67
|
+
// from `rdataFields` is insufficient.
|
|
68
|
+
setField1(val) {
|
|
69
|
+
if (val === undefined) val = this?.default?.field1
|
|
70
|
+
if (val === undefined) this.throwHelp('TEMPLATE: field1 is required')
|
|
71
|
+
this.is16bitInt('TEMPLATE', 'field1', val)
|
|
72
|
+
this.set('field1', parseInt(val, 10))
|
|
73
|
+
}
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/****** Metadata (Required) *******/
|
|
77
|
+
|
|
78
|
+
getDescription() {
|
|
79
|
+
return 'Short description of the TEMPLATE record'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getCanonical() {
|
|
83
|
+
// Returns a sample object representing a valid record.
|
|
84
|
+
// This is used for documentation and testing.
|
|
85
|
+
return {
|
|
86
|
+
owner: 'example.com.',
|
|
87
|
+
ttl: 3600,
|
|
88
|
+
class: 'IN',
|
|
89
|
+
type: 'TEMPLATE',
|
|
90
|
+
field1: 10,
|
|
91
|
+
field2: 'target.example.com.',
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/****** IMPORTERS / EXPORTERS (Recommended) *******/
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
// Optional: Override if generic implementation in RR.js is insufficient
|
|
99
|
+
fromTinydns({ tinyline }) {
|
|
100
|
+
// Implementation...
|
|
101
|
+
}
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
// Optional: Override if generic implementation in RR.js is insufficient
|
|
106
|
+
toTinydns() {
|
|
107
|
+
// Implementation...
|
|
108
|
+
}
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
getWireRdata() {
|
|
112
|
+
// MUST be implemented for binary/wire format support.
|
|
113
|
+
// Returns a Uint8Array.
|
|
114
|
+
// Example:
|
|
115
|
+
/*
|
|
116
|
+
const domain = this.wirePackDomain(this.get('field2'))
|
|
117
|
+
const result = new Uint8Array(2 + domain.length)
|
|
118
|
+
new DataView(result.buffer).setUint16(0, this.get('field1'))
|
|
119
|
+
result.set(domain, 2)
|
|
120
|
+
return result
|
|
121
|
+
*/
|
|
122
|
+
throw new Error('getWireRdata not implemented')
|
|
123
|
+
}
|
|
124
|
+
}
|
package/rr/a.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import RR from '../rr.js'
|
|
2
2
|
|
|
3
3
|
export default class A extends RR {
|
|
4
|
+
static typeName = 'A'
|
|
5
|
+
static typeId = 1
|
|
6
|
+
static RFCs = [1035]
|
|
7
|
+
static tinydnsType = '+'
|
|
8
|
+
static rdataFields = [['address', 'ipv4']]
|
|
9
|
+
static tags = ['common']
|
|
10
|
+
|
|
4
11
|
constructor(opts) {
|
|
5
12
|
super(opts)
|
|
6
13
|
}
|
|
@@ -16,22 +23,6 @@ export default class A extends RR {
|
|
|
16
23
|
return 'Address'
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
getTags() {
|
|
20
|
-
return ['common']
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getRdataFields(arg) {
|
|
24
|
-
return ['address']
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getRFCs() {
|
|
28
|
-
return [1035]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getTypeId() {
|
|
32
|
-
return 1
|
|
33
|
-
}
|
|
34
|
-
|
|
35
26
|
getCanonical() {
|
|
36
27
|
return {
|
|
37
28
|
owner: 'host.example.com.',
|
|
@@ -42,39 +33,8 @@ export default class A extends RR {
|
|
|
42
33
|
}
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
/****** IMPORTERS *******/
|
|
46
|
-
fromTinydns({ tinyline }) {
|
|
47
|
-
// +fqdn:ip:ttl:timestamp:lo
|
|
48
|
-
const [owner, ip, ttl, ts, loc] = tinyline.slice(1).split(':')
|
|
49
|
-
|
|
50
|
-
return new A({
|
|
51
|
-
owner: this.fullyQualify(owner),
|
|
52
|
-
type: 'A',
|
|
53
|
-
address: ip,
|
|
54
|
-
ttl: parseInt(ttl, 10),
|
|
55
|
-
timestamp: ts,
|
|
56
|
-
location: loc?.trim() ?? '',
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fromBind({ bindline }) {
|
|
61
|
-
// test.example.com 3600 IN A 192.0.2.127
|
|
62
|
-
const [owner, ttl, c, type, address] = bindline.split(/\s+/)
|
|
63
|
-
return new A({
|
|
64
|
-
owner,
|
|
65
|
-
ttl: parseInt(ttl, 10),
|
|
66
|
-
class: c,
|
|
67
|
-
type,
|
|
68
|
-
address,
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
36
|
/****** EXPORTERS *******/
|
|
73
37
|
getWireRdata() {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
toTinydns() {
|
|
78
|
-
return `+${this.getTinyFQDN('owner')}:${this.get('address')}:${this.getTinydnsPostamble()}\n`
|
|
38
|
+
return new Uint8Array(this.get('address').split('.').map(Number))
|
|
79
39
|
}
|
|
80
40
|
}
|
package/rr/aaaa.js
CHANGED
|
@@ -2,6 +2,12 @@ import RR from '../rr.js'
|
|
|
2
2
|
import * as TINYDNS from '../lib/tinydns.js'
|
|
3
3
|
|
|
4
4
|
export default class AAAA extends RR {
|
|
5
|
+
static typeName = 'AAAA'
|
|
6
|
+
static typeId = 28
|
|
7
|
+
static RFCs = [3596, 5952]
|
|
8
|
+
static rdataFields = [['address', 'ipv6']]
|
|
9
|
+
static tags = ['common']
|
|
10
|
+
|
|
5
11
|
constructor(opts) {
|
|
6
12
|
super(opts)
|
|
7
13
|
}
|
|
@@ -11,33 +17,17 @@ export default class AAAA extends RR {
|
|
|
11
17
|
if (!val) this.throwHelp('AAAA: address is required')
|
|
12
18
|
if (!this.isIPv6(val)) this.throwHelp(`AAAA: address must be IPv6 (${val})`)
|
|
13
19
|
|
|
14
|
-
this.set('address', this.
|
|
20
|
+
this.set('address', this.expandIPv6(val.toLowerCase())) // lower case: RFC 5952
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
getCompressed(val) {
|
|
18
|
-
return this.
|
|
24
|
+
return this.compressIPv6(val ?? this.get('address'))
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
getDescription() {
|
|
22
28
|
return 'Address IPv6'
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
getTags() {
|
|
26
|
-
return ['common']
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
getRdataFields(arg) {
|
|
30
|
-
return ['address']
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getRFCs() {
|
|
34
|
-
return [3596, 5952]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getTypeId() {
|
|
38
|
-
return 28
|
|
39
|
-
}
|
|
40
|
-
|
|
41
31
|
getCanonical() {
|
|
42
32
|
return {
|
|
43
33
|
owner: 'host.example.com.',
|
|
@@ -81,69 +71,12 @@ export default class AAAA extends RR {
|
|
|
81
71
|
})
|
|
82
72
|
}
|
|
83
73
|
|
|
84
|
-
fromBind({ bindline }) {
|
|
85
|
-
// test.example.com 3600 IN AAAA ...
|
|
86
|
-
const [owner, ttl, c, type, ip] = bindline.split(/\s+/)
|
|
87
|
-
return new AAAA({
|
|
88
|
-
owner,
|
|
89
|
-
ttl: parseInt(ttl, 10),
|
|
90
|
-
class: c,
|
|
91
|
-
type,
|
|
92
|
-
address: this.expand(ip),
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
compress(val) {
|
|
97
|
-
/*
|
|
98
|
-
* RFC 5952
|
|
99
|
-
* 4.1. Leading zeros MUST be suppressed...A single 16-bit 0000 field MUST be represented as 0.
|
|
100
|
-
* 4.2.1 The use of the symbol "::" MUST be used to its maximum capability.
|
|
101
|
-
* 4.2.2 The symbol "::" MUST NOT be used to shorten just one 16-bit 0 field.
|
|
102
|
-
* 4.2.3 When choosing placement of a "::", the longest run...MUST be shortened
|
|
103
|
-
* 4.3 The characters a-f in an IPv6 address MUST be represented in lowercase.
|
|
104
|
-
*/
|
|
105
|
-
let r = val
|
|
106
|
-
.replace(/0000/g, '0') // 4.1 0000 -> 0
|
|
107
|
-
.replace(/:0+([1-9a-fA-F])/g, ':$1') // 4.1 remove leading zeros
|
|
108
|
-
|
|
109
|
-
const mostConsecutiveZeros = [
|
|
110
|
-
new RegExp(/0?(?::0){6,}:0?/),
|
|
111
|
-
new RegExp(/0?(?::0){5,}:0?/),
|
|
112
|
-
new RegExp(/0?(?::0){4,}:0?/),
|
|
113
|
-
new RegExp(/0?(?::0){3,}:0?/),
|
|
114
|
-
new RegExp(/0?(?::0){2,}:0?/),
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
for (const re of mostConsecutiveZeros) {
|
|
118
|
-
if (re.test(r)) {
|
|
119
|
-
r = r.replace(re, '::') // 4.2
|
|
120
|
-
break
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return r.toLowerCase() // 4.3
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
expand(val, delimiter) {
|
|
128
|
-
if (delimiter === undefined) delimiter = ':'
|
|
129
|
-
|
|
130
|
-
const colons = val.match(/:/g)
|
|
131
|
-
if (colons?.length < 7) {
|
|
132
|
-
// console.log(`AAAA: restoring compressed colons`)
|
|
133
|
-
val = val.replace(/::/, ':'.repeat(9 - colons.length))
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// restore compressed leading zeros
|
|
137
|
-
return val
|
|
138
|
-
.split(':')
|
|
139
|
-
.map((s) => s.padStart(4, 0))
|
|
140
|
-
.join(delimiter)
|
|
141
|
-
.toLowerCase()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
74
|
/****** EXPORTERS *******/
|
|
145
75
|
getWireRdata() {
|
|
146
|
-
|
|
76
|
+
const hex = this.expandIPv6(this.get('address'), '')
|
|
77
|
+
const arr = new Uint8Array(hex.length / 2)
|
|
78
|
+
for (let i = 0; i < arr.length; i++) arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
79
|
+
return arr
|
|
147
80
|
}
|
|
148
81
|
|
|
149
82
|
toBind(zone_opts) {
|
|
@@ -152,7 +85,7 @@ export default class AAAA extends RR {
|
|
|
152
85
|
|
|
153
86
|
toTinydns() {
|
|
154
87
|
// from AAAA notation (8 groups of 4 hex digits) to 16 escaped octals
|
|
155
|
-
const rdata = TINYDNS.packHex(this.
|
|
88
|
+
const rdata = TINYDNS.packHex(this.expandIPv6(this.get('address'), ''))
|
|
156
89
|
return this.getTinydnsGeneric(rdata)
|
|
157
90
|
}
|
|
158
91
|
}
|
package/rr/apl.js
CHANGED
|
@@ -3,6 +3,11 @@ import RR from '../rr.js'
|
|
|
3
3
|
import * as TINYDNS from '../lib/tinydns.js'
|
|
4
4
|
|
|
5
5
|
export default class APL extends RR {
|
|
6
|
+
static typeName = 'APL'
|
|
7
|
+
static typeId = 42
|
|
8
|
+
static RFCs = [3123]
|
|
9
|
+
static rdataFields = ['apl rdata']
|
|
10
|
+
|
|
6
11
|
constructor(opts) {
|
|
7
12
|
super(opts)
|
|
8
13
|
}
|
|
@@ -10,8 +15,6 @@ export default class APL extends RR {
|
|
|
10
15
|
/****** Resource record specific setters *******/
|
|
11
16
|
setAplRdata(val) {
|
|
12
17
|
if (!val) this.throwHelp('APL: apl rdata is required')
|
|
13
|
-
// apl rdata is a list of address prefix list items, e.g.:
|
|
14
|
-
// 1:192.0.2.0/24 !1:192.0.2.64/28 2:2001:db8::/32
|
|
15
18
|
this.set('apl rdata', val)
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -19,25 +22,13 @@ export default class APL extends RR {
|
|
|
19
22
|
return 'Address Prefix List'
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
getRdataFields(arg) {
|
|
23
|
-
return ['apl rdata']
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getRFCs() {
|
|
27
|
-
return [3123]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
getTypeId() {
|
|
31
|
-
return 42
|
|
32
|
-
}
|
|
33
|
-
|
|
34
25
|
getCanonical() {
|
|
35
26
|
return {
|
|
36
27
|
owner: 'example.com.',
|
|
37
28
|
ttl: 3600,
|
|
38
29
|
class: 'IN',
|
|
39
30
|
type: 'APL',
|
|
40
|
-
'apl rdata': '1:192.0.2.
|
|
31
|
+
'apl rdata': '1:192.0.2.1/24 !1:192.0.2.64/28 2:2001:db8::1/128',
|
|
41
32
|
}
|
|
42
33
|
}
|
|
43
34
|
|
|
@@ -47,32 +38,33 @@ export default class APL extends RR {
|
|
|
47
38
|
const [fqdn, n, rdata, ttl, ts, loc] = tinyline.slice(1).split(':')
|
|
48
39
|
if (n != 42) this.throwHelp('APL fromTinydns, invalid n')
|
|
49
40
|
|
|
50
|
-
const bytes =
|
|
41
|
+
const bytes = Uint8Array.from(TINYDNS.octalToChar(rdata), (c) => c.charCodeAt(0))
|
|
51
42
|
const items = []
|
|
52
43
|
let pos = 0
|
|
53
44
|
|
|
54
45
|
while (pos < bytes.length) {
|
|
55
|
-
const afi = bytes
|
|
46
|
+
const afi = (bytes[pos] << 8) | bytes[pos + 1]
|
|
56
47
|
pos += 2
|
|
57
|
-
const prefix = bytes
|
|
48
|
+
const prefix = bytes[pos]
|
|
58
49
|
pos++
|
|
59
|
-
const adfLenByte = bytes
|
|
50
|
+
const adfLenByte = bytes[pos]
|
|
60
51
|
pos++
|
|
61
52
|
const neg = (adfLenByte & 0x80) !== 0
|
|
62
53
|
const addrLen = adfLenByte & 0x7f
|
|
63
|
-
const addrBytes = bytes.
|
|
54
|
+
const addrBytes = bytes.subarray(pos, pos + addrLen)
|
|
64
55
|
pos += addrLen
|
|
65
56
|
|
|
66
57
|
let addr
|
|
67
58
|
if (afi === 1) {
|
|
68
|
-
const padded =
|
|
69
|
-
|
|
59
|
+
const padded = new Uint8Array(4)
|
|
60
|
+
padded.set(addrBytes)
|
|
70
61
|
addr = [...padded].join('.')
|
|
71
62
|
} else {
|
|
72
|
-
const padded =
|
|
73
|
-
|
|
63
|
+
const padded = new Uint8Array(16)
|
|
64
|
+
padded.set(addrBytes)
|
|
65
|
+
const paddedDv = new DataView(padded.buffer)
|
|
74
66
|
const groups = []
|
|
75
|
-
for (let i = 0; i < 16; i += 2) groups.push(
|
|
67
|
+
for (let i = 0; i < 16; i += 2) groups.push(paddedDv.getUint16(i).toString(16).padStart(4, '0'))
|
|
76
68
|
addr = this.compressIPv6(groups.join(':'))
|
|
77
69
|
}
|
|
78
70
|
|
|
@@ -102,6 +94,43 @@ export default class APL extends RR {
|
|
|
102
94
|
})
|
|
103
95
|
}
|
|
104
96
|
|
|
97
|
+
fromWire({ owner, cls, ttl, rdata }) {
|
|
98
|
+
const items = []
|
|
99
|
+
let pos = 0
|
|
100
|
+
while (pos < rdata.length) {
|
|
101
|
+
const afi = (rdata[pos] << 8) | rdata[pos + 1]
|
|
102
|
+
pos += 2
|
|
103
|
+
const prefix = rdata[pos++]
|
|
104
|
+
const adfLenByte = rdata[pos++]
|
|
105
|
+
const neg = (adfLenByte & 0x80) !== 0
|
|
106
|
+
const addrLen = adfLenByte & 0x7f
|
|
107
|
+
const addrBytes = rdata.subarray(pos, pos + addrLen)
|
|
108
|
+
pos += addrLen
|
|
109
|
+
|
|
110
|
+
let addr
|
|
111
|
+
if (afi === 1) {
|
|
112
|
+
const padded = new Uint8Array(4)
|
|
113
|
+
padded.set(addrBytes)
|
|
114
|
+
addr = [...padded].join('.')
|
|
115
|
+
} else {
|
|
116
|
+
const padded = new Uint8Array(16)
|
|
117
|
+
padded.set(addrBytes)
|
|
118
|
+
const dv = new DataView(padded.buffer)
|
|
119
|
+
const groups = []
|
|
120
|
+
for (let i = 0; i < 16; i += 2) groups.push(dv.getUint16(i).toString(16).padStart(4, '0'))
|
|
121
|
+
addr = this.compressIPv6(groups.join(':'))
|
|
122
|
+
}
|
|
123
|
+
items.push(`${neg ? '!' : ''}${afi}:${addr}/${prefix}`)
|
|
124
|
+
}
|
|
125
|
+
return new APL({
|
|
126
|
+
owner,
|
|
127
|
+
ttl,
|
|
128
|
+
class: cls,
|
|
129
|
+
type: 'APL',
|
|
130
|
+
'apl rdata': items.join(' '),
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
105
134
|
/****** EXPORTERS *******/
|
|
106
135
|
toTinydns() {
|
|
107
136
|
return this.getTinydnsGeneric(
|
|
@@ -119,7 +148,7 @@ export default class APL extends RR {
|
|
|
119
148
|
|
|
120
149
|
let addrBytes
|
|
121
150
|
if (afi === 1) {
|
|
122
|
-
addrBytes =
|
|
151
|
+
addrBytes = new Uint8Array(addr.split('.').map((n) => parseInt(n, 10)))
|
|
123
152
|
} else {
|
|
124
153
|
const dblIdx = addr.indexOf('::')
|
|
125
154
|
let groups
|
|
@@ -136,7 +165,10 @@ export default class APL extends RR {
|
|
|
136
165
|
} else {
|
|
137
166
|
groups = addr.split(':')
|
|
138
167
|
}
|
|
139
|
-
|
|
168
|
+
const hexStr = groups.map((g) => g.padStart(4, '0')).join('')
|
|
169
|
+
addrBytes = Uint8Array.from({ length: hexStr.length / 2 }, (_, i) =>
|
|
170
|
+
parseInt(hexStr.slice(i * 2, i * 2 + 2), 16),
|
|
171
|
+
)
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
let len = addrBytes.length
|
|
@@ -152,4 +184,68 @@ export default class APL extends RR {
|
|
|
152
184
|
.join(''),
|
|
153
185
|
)
|
|
154
186
|
}
|
|
187
|
+
|
|
188
|
+
getWireRdata() {
|
|
189
|
+
const items = this.get('apl rdata').split(/\s+/)
|
|
190
|
+
const rdata = []
|
|
191
|
+
|
|
192
|
+
for (const item of items) {
|
|
193
|
+
const neg = item.startsWith('!')
|
|
194
|
+
const bare = neg ? item.slice(1) : item
|
|
195
|
+
const colonIdx = bare.indexOf(':')
|
|
196
|
+
const afi = parseInt(bare.slice(0, colonIdx), 10)
|
|
197
|
+
const rest = bare.slice(colonIdx + 1)
|
|
198
|
+
const slashIdx = rest.lastIndexOf('/')
|
|
199
|
+
const addr = rest.slice(0, slashIdx)
|
|
200
|
+
const prefix = parseInt(rest.slice(slashIdx + 1), 10)
|
|
201
|
+
|
|
202
|
+
let addrBytes
|
|
203
|
+
if (afi === 1) {
|
|
204
|
+
addrBytes = new Uint8Array(addr.split('.').map((n) => parseInt(n, 10)))
|
|
205
|
+
} else {
|
|
206
|
+
const dblIdx = addr.indexOf('::')
|
|
207
|
+
let groups
|
|
208
|
+
if (dblIdx !== -1) {
|
|
209
|
+
const left = addr
|
|
210
|
+
.slice(0, dblIdx)
|
|
211
|
+
.split(':')
|
|
212
|
+
.filter((s) => s !== '')
|
|
213
|
+
const right = addr
|
|
214
|
+
.slice(dblIdx + 2)
|
|
215
|
+
.split(':')
|
|
216
|
+
.filter((s) => s !== '')
|
|
217
|
+
groups = [...left, ...Array(8 - left.length - right.length).fill('0000'), ...right]
|
|
218
|
+
} else {
|
|
219
|
+
groups = addr.split(':')
|
|
220
|
+
}
|
|
221
|
+
const hexStr = groups.map((g) => g.padStart(4, '0')).join('')
|
|
222
|
+
addrBytes = Uint8Array.from({ length: hexStr.length / 2 }, (_, i) =>
|
|
223
|
+
parseInt(hexStr.slice(i * 2, i * 2 + 2), 16),
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let len = addrBytes.length
|
|
228
|
+
while (len > 0 && addrBytes[len - 1] === 0) len--
|
|
229
|
+
const afdPart = addrBytes.slice(0, len)
|
|
230
|
+
|
|
231
|
+
const itemBytes = new Uint8Array(4 + afdPart.length)
|
|
232
|
+
const dv = new DataView(itemBytes.buffer, itemBytes.byteOffset)
|
|
233
|
+
dv.setUint16(0, afi)
|
|
234
|
+
itemBytes[2] = prefix
|
|
235
|
+
itemBytes[3] = (neg ? 0x80 : 0) | afdPart.length
|
|
236
|
+
itemBytes.set(afdPart, 4)
|
|
237
|
+
|
|
238
|
+
rdata.push(itemBytes)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const totalLen = rdata.reduce((sum, r) => sum + r.length, 0)
|
|
242
|
+
const bytes = new Uint8Array(totalLen)
|
|
243
|
+
let pos = 0
|
|
244
|
+
for (const r of rdata) {
|
|
245
|
+
bytes.set(r, pos)
|
|
246
|
+
pos += r.length
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return bytes
|
|
250
|
+
}
|
|
155
251
|
}
|
package/rr/caa.js
CHANGED
|
@@ -2,6 +2,16 @@ import RR from '../rr.js'
|
|
|
2
2
|
import * as TINYDNS from '../lib/tinydns.js'
|
|
3
3
|
|
|
4
4
|
export default class CAA extends RR {
|
|
5
|
+
static typeName = 'CAA'
|
|
6
|
+
static typeId = 257
|
|
7
|
+
static RFCs = [6844, 8659, 9619]
|
|
8
|
+
static rdataFields = [
|
|
9
|
+
['flags', 'u8'],
|
|
10
|
+
['tag', 'charstr'],
|
|
11
|
+
['value', 'qstr'],
|
|
12
|
+
]
|
|
13
|
+
static tags = ['security']
|
|
14
|
+
|
|
5
15
|
constructor(opts) {
|
|
6
16
|
super(opts)
|
|
7
17
|
}
|
|
@@ -62,26 +72,6 @@ export default class CAA extends RR {
|
|
|
62
72
|
return 'Certification Authority Authorization'
|
|
63
73
|
}
|
|
64
74
|
|
|
65
|
-
getTags() {
|
|
66
|
-
return ['security']
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
getQuotedFields() {
|
|
70
|
-
return ['value']
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
getRdataFields(arg) {
|
|
74
|
-
return ['flags', 'tag', 'value']
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
getRFCs() {
|
|
78
|
-
return [6844, 8659]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
getTypeId() {
|
|
82
|
-
return 257
|
|
83
|
-
}
|
|
84
|
-
|
|
85
75
|
getCanonical() {
|
|
86
76
|
return {
|
|
87
77
|
owner: 'example.com.',
|
|
@@ -97,8 +87,8 @@ export default class CAA extends RR {
|
|
|
97
87
|
/****** IMPORTERS *******/
|
|
98
88
|
fromTinydns({ tinyline }) {
|
|
99
89
|
// CAA via generic, :fqdn:n:rdata:ttl:timestamp:lo
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
90
|
+
const { owner, typeId, rdata, ttl, timestamp, location } = this.parseTinydnsLine(tinyline)
|
|
91
|
+
if (typeId != this.getTypeId()) this.throwHelp('CAA fromTinydns, invalid typeId')
|
|
102
92
|
|
|
103
93
|
const flags = TINYDNS.octalToUInt8(rdata.slice(0, 4))
|
|
104
94
|
const taglen = TINYDNS.octalToUInt8(rdata.slice(4, 8))
|
|
@@ -108,49 +98,36 @@ export default class CAA extends RR {
|
|
|
108
98
|
const fingerprint = unescaped.slice(taglen)
|
|
109
99
|
|
|
110
100
|
return new CAA({
|
|
111
|
-
owner
|
|
112
|
-
ttl
|
|
101
|
+
owner,
|
|
102
|
+
ttl,
|
|
113
103
|
type: 'CAA',
|
|
114
104
|
flags,
|
|
115
105
|
tag,
|
|
116
106
|
value: fingerprint,
|
|
117
|
-
timestamp
|
|
118
|
-
location
|
|
107
|
+
timestamp,
|
|
108
|
+
location,
|
|
119
109
|
})
|
|
120
110
|
}
|
|
121
111
|
|
|
122
|
-
|
|
123
|
-
// test.example.com 3600 IN CAA flags, tags, value
|
|
124
|
-
const regex =
|
|
125
|
-
/^(?<owner>\S+)\s+(?<ttl>\d{1,10})\s+(?<class>IN)\s+(?<type>CAA)\s+(?<flags>\d+)\s+(?<tag>\w+)\s+(?:"(?<quotedValue>[^"]+)"|(?<unquotedValue>\S+))$/i
|
|
126
|
-
|
|
127
|
-
const match = bindline.trim().match(regex)
|
|
128
|
-
|
|
129
|
-
if (!match) {
|
|
130
|
-
this.throwHelp(`unable to parse CAA: ${bindline}`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const { owner, ttl, class: c, type, flags, tag, quotedValue, unquotedValue } = match.groups
|
|
112
|
+
/****** EXPORTERS *******/
|
|
134
113
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
114
|
+
getWireRdata() {
|
|
115
|
+
const tag = new TextEncoder().encode(this.get('tag'))
|
|
116
|
+
const value = new TextEncoder().encode(this.get('value'))
|
|
117
|
+
const result = new Uint8Array(2 + tag.length + value.length)
|
|
118
|
+
result[0] = this.get('flags')
|
|
119
|
+
result[1] = tag.length
|
|
120
|
+
result.set(tag, 2)
|
|
121
|
+
result.set(value, 2 + tag.length)
|
|
122
|
+
return result
|
|
144
123
|
}
|
|
145
124
|
|
|
146
|
-
/****** EXPORTERS *******/
|
|
147
|
-
|
|
148
125
|
toTinydns() {
|
|
149
126
|
return this.getTinydnsGeneric(
|
|
150
127
|
TINYDNS.UInt8toOctal(this.get('flags')) +
|
|
151
128
|
TINYDNS.UInt8toOctal(this.get('tag').length) +
|
|
152
129
|
TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('tag')) +
|
|
153
|
-
TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.
|
|
130
|
+
TINYDNS.escapeOctal(/[\r\n\t:\\/]/, this.get('value')),
|
|
154
131
|
)
|
|
155
132
|
}
|
|
156
133
|
}
|