Add basic EDNS(0) OPT RR support
* Add EDNS(0) OPT RR for UDP payload size in query. * Rework PR #34, fixes #36. Add support for options in an OPT pseudo-RR. * Include tweaks from @silverwind
This commit is contained in:
parent
40d8e5657f
commit
f6db3d3d5f
113
index.js
113
index.js
@ -77,6 +77,7 @@ name.decode = function (buf, offset) {
|
|||||||
name.decode.bytes = 0
|
name.decode.bytes = 0
|
||||||
|
|
||||||
name.encodingLength = function (n) {
|
name.encodingLength = function (n) {
|
||||||
|
if (n === '.') return 1
|
||||||
return Buffer.byteLength(n) + 2
|
return Buffer.byteLength(n) + 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,6 +665,84 @@ raaaa.encodingLength = function () {
|
|||||||
return 18
|
return 18
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const roption = exports.option = {}
|
||||||
|
|
||||||
|
roption.encode = function (option, buf, offset) {
|
||||||
|
if (!buf) buf = Buffer.allocUnsafe(roption.encodingLength(option))
|
||||||
|
if (!offset) offset = 0
|
||||||
|
const oldOffset = offset
|
||||||
|
|
||||||
|
buf.writeUInt16BE(option.code, offset)
|
||||||
|
offset += 2
|
||||||
|
buf.writeUInt16BE(option.data.length, offset)
|
||||||
|
offset += 2
|
||||||
|
option.data.copy(buf, offset)
|
||||||
|
offset += option.data.length
|
||||||
|
|
||||||
|
roption.encode.bytes = offset - oldOffset
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
roption.encode.bytes = 0
|
||||||
|
|
||||||
|
roption.decode = function (buf, offset) {
|
||||||
|
if (!offset) offset = 0
|
||||||
|
const option = {}
|
||||||
|
|
||||||
|
option.code = buf.readUInt16BE(offset)
|
||||||
|
const len = buf.readUInt16BE(offset + 2)
|
||||||
|
option.data = buf.slice(offset + 4, offset + 4 + len)
|
||||||
|
|
||||||
|
roption.decode.bytes = len + 4
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
|
||||||
|
roption.decode.bytes = 0
|
||||||
|
|
||||||
|
roption.encodingLength = function (option) {
|
||||||
|
return option.data.length + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
const ropt = exports.opt = {}
|
||||||
|
|
||||||
|
ropt.encode = function (options, buf, offset) {
|
||||||
|
if (!buf) buf = Buffer.allocUnsafe(ropt.encodingLength(options))
|
||||||
|
if (!offset) offset = 0
|
||||||
|
const oldOffset = offset
|
||||||
|
|
||||||
|
const rdlen = encodingLengthList(options, roption)
|
||||||
|
buf.writeUInt16BE(rdlen, offset)
|
||||||
|
offset = encodeList(options, roption, buf, offset + 2)
|
||||||
|
|
||||||
|
ropt.encode.bytes = offset - oldOffset
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
ropt.encode.bytes = 0
|
||||||
|
|
||||||
|
ropt.decode = function (buf, offset) {
|
||||||
|
if (!offset) offset = 0
|
||||||
|
const oldOffset = offset
|
||||||
|
|
||||||
|
const options = []
|
||||||
|
let rdlen = buf.readUInt16BE(offset)
|
||||||
|
offset += 2
|
||||||
|
let o = 0
|
||||||
|
while (rdlen > 0) {
|
||||||
|
options[o++] = roption.decode(buf, offset)
|
||||||
|
offset += roption.decode.bytes
|
||||||
|
rdlen -= roption.decode.bytes
|
||||||
|
}
|
||||||
|
ropt.decode.bytes = offset - oldOffset
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
ropt.decode.bytes = 0
|
||||||
|
|
||||||
|
ropt.encodingLength = function (options) {
|
||||||
|
return 2 + encodingLengthList(options || [], roption)
|
||||||
|
}
|
||||||
|
|
||||||
const renc = exports.record = function (type) {
|
const renc = exports.record = function (type) {
|
||||||
switch (type.toUpperCase()) {
|
switch (type.toUpperCase()) {
|
||||||
case 'A': return ra
|
case 'A': return ra
|
||||||
@ -679,6 +758,7 @@ const renc = exports.record = function (type) {
|
|||||||
case 'NS': return rns
|
case 'NS': return rns
|
||||||
case 'SOA': return rsoa
|
case 'SOA': return rsoa
|
||||||
case 'MX': return rmx
|
case 'MX': return rmx
|
||||||
|
case 'OPT': return ropt
|
||||||
}
|
}
|
||||||
return runknown
|
return runknown
|
||||||
}
|
}
|
||||||
@ -696,15 +776,29 @@ answer.encode = function (a, buf, offset) {
|
|||||||
|
|
||||||
buf.writeUInt16BE(types.toType(a.type), offset)
|
buf.writeUInt16BE(types.toType(a.type), offset)
|
||||||
|
|
||||||
|
if (a.type.toUpperCase() === 'OPT') {
|
||||||
|
if (a.name !== '.') {
|
||||||
|
throw new Error('OPT name must be root.')
|
||||||
|
}
|
||||||
|
buf.writeUInt16BE(a.updPayloadSize || 4096, offset + 2)
|
||||||
|
buf.writeUInt8(a.extendedRcode || 0, offset + 4)
|
||||||
|
buf.writeUInt8(a.ednsVersion || 0, offset + 5)
|
||||||
|
buf.writeUInt16BE(a.flags || 0, offset + 6)
|
||||||
|
|
||||||
|
offset += 8
|
||||||
|
ropt.encode(a.options || [], buf, offset)
|
||||||
|
offset += ropt.encode.bytes
|
||||||
|
} else {
|
||||||
let klass = classes.toClass(a.class === undefined ? 'IN' : a.class)
|
let klass = classes.toClass(a.class === undefined ? 'IN' : a.class)
|
||||||
if (a.flush) klass |= FLUSH_MASK // the 1st bit of the class is the flush bit
|
if (a.flush) klass |= FLUSH_MASK // the 1st bit of the class is the flush bit
|
||||||
buf.writeUInt16BE(klass, offset + 2)
|
buf.writeUInt16BE(klass, offset + 2)
|
||||||
|
|
||||||
buf.writeUInt32BE(a.ttl || 0, offset + 4)
|
buf.writeUInt32BE(a.ttl || 0, offset + 4)
|
||||||
|
|
||||||
|
offset += 8
|
||||||
const enc = renc(a.type)
|
const enc = renc(a.type)
|
||||||
enc.encode(a.data, buf, offset + 8)
|
enc.encode(a.data, buf, offset)
|
||||||
offset += 8 + enc.encode.bytes
|
offset += enc.encode.bytes
|
||||||
|
}
|
||||||
|
|
||||||
answer.encode.bytes = offset - oldOffset
|
answer.encode.bytes = offset - oldOffset
|
||||||
return buf
|
return buf
|
||||||
@ -721,6 +815,15 @@ answer.decode = function (buf, offset) {
|
|||||||
a.name = name.decode(buf, offset)
|
a.name = name.decode(buf, offset)
|
||||||
offset += name.decode.bytes
|
offset += name.decode.bytes
|
||||||
a.type = types.toString(buf.readUInt16BE(offset))
|
a.type = types.toString(buf.readUInt16BE(offset))
|
||||||
|
if (a.type === 'OPT') {
|
||||||
|
a.udpPayloadSize = buf.readUInt16BE(offset + 2)
|
||||||
|
a.extendedRcode = buf.readUInt8(offset + 4)
|
||||||
|
a.ednsVersion = buf.readUInt8(offset + 5)
|
||||||
|
a.flags = buf.readUInt16BE(offset + 6)
|
||||||
|
a.flag_do = ((a.flags >> 15) & 0x1) === 1
|
||||||
|
a.options = ropt.decode(buf, offset + 8)
|
||||||
|
offset += 8 + ropt.decode.bytes
|
||||||
|
} else {
|
||||||
const klass = buf.readUInt16BE(offset + 2)
|
const klass = buf.readUInt16BE(offset + 2)
|
||||||
a.ttl = buf.readUInt32BE(offset + 4)
|
a.ttl = buf.readUInt32BE(offset + 4)
|
||||||
|
|
||||||
@ -730,6 +833,7 @@ answer.decode = function (buf, offset) {
|
|||||||
const enc = renc(a.type)
|
const enc = renc(a.type)
|
||||||
a.data = enc.decode(buf, offset + 8)
|
a.data = enc.decode(buf, offset + 8)
|
||||||
offset += 8 + enc.decode.bytes
|
offset += 8 + enc.decode.bytes
|
||||||
|
}
|
||||||
|
|
||||||
answer.decode.bytes = offset - oldOffset
|
answer.decode.bytes = offset - oldOffset
|
||||||
return a
|
return a
|
||||||
@ -738,7 +842,7 @@ answer.decode = function (buf, offset) {
|
|||||||
answer.decode.bytes = 0
|
answer.decode.bytes = 0
|
||||||
|
|
||||||
answer.encodingLength = function (a) {
|
answer.encodingLength = function (a) {
|
||||||
return name.encodingLength(a.name) + 8 + renc(a.type).encodingLength(a.data)
|
return name.encodingLength(a.name) + 8 + renc(a.type).encodingLength(a.data || a.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const question = exports.question = {}
|
const question = exports.question = {}
|
||||||
@ -798,6 +902,7 @@ exports.RECURSION_DESIRED = 1 << 8
|
|||||||
exports.RECURSION_AVAILABLE = 1 << 7
|
exports.RECURSION_AVAILABLE = 1 << 7
|
||||||
exports.AUTHENTIC_DATA = 1 << 5
|
exports.AUTHENTIC_DATA = 1 << 5
|
||||||
exports.CHECKING_DISABLED = 1 << 4
|
exports.CHECKING_DISABLED = 1 << 4
|
||||||
|
exports.DNSSEC_OK = 1 << 15
|
||||||
|
|
||||||
exports.encode = function (result, buf, offset) {
|
exports.encode = function (result, buf, offset) {
|
||||||
if (!buf) buf = Buffer.allocUnsafe(exports.encodingLength(result))
|
if (!buf) buf = Buffer.allocUnsafe(exports.encodingLength(result))
|
||||||
|
|||||||
38
test.js
38
test.js
@ -321,6 +321,44 @@ tape('stream', function (t) {
|
|||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tape('opt', function (t) {
|
||||||
|
const val = {
|
||||||
|
type: 'query',
|
||||||
|
questions: [{
|
||||||
|
type: 'A',
|
||||||
|
name: 'hello.a.com'
|
||||||
|
}],
|
||||||
|
additionals: [{
|
||||||
|
type: 'OPT',
|
||||||
|
name: '.',
|
||||||
|
udpPayloadSize: 4096
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
testEncoder(t, packet, val)
|
||||||
|
let buf = packet.encode(val)
|
||||||
|
let val2 = packet.decode(buf)
|
||||||
|
const additional1 = val.additionals[0]
|
||||||
|
let additional2 = val2.additionals[0]
|
||||||
|
t.ok(compare(t, additional1.name, additional2.name), 'name matches')
|
||||||
|
t.ok(compare(t, additional1.udpPayloadSize, additional2.udpPayloadSize), 'udp payload size matches')
|
||||||
|
t.ok(compare(t, 0, additional2.flags), 'flags match')
|
||||||
|
additional1.flags = packet.DNSSEC_OK
|
||||||
|
additional1.extendedRcode = 0x80
|
||||||
|
// padding, see RFC 7830
|
||||||
|
additional1.options = [{
|
||||||
|
code: 12,
|
||||||
|
data: Buffer.alloc(31)
|
||||||
|
}]
|
||||||
|
buf = packet.encode(val)
|
||||||
|
val2 = packet.decode(buf)
|
||||||
|
additional2 = val2.additionals[0]
|
||||||
|
t.ok(compare(t, 1 << 15, additional2.flags), 'DO bit set in flags')
|
||||||
|
t.ok(compare(t, true, additional2.flag_do), 'DO bit set')
|
||||||
|
t.ok(compare(t, additional1.extendedRcode, additional2.extendedRcode), 'extended rcode matches')
|
||||||
|
t.ok(compare(t, additional1.options, additional2.options), 'options match')
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
tape('unpack', function (t) {
|
tape('unpack', function (t) {
|
||||||
const buf = Buffer.from([
|
const buf = Buffer.from([
|
||||||
0x00, 0x79,
|
0x00, 0x79,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user