Skip to content

Commit 3691485

Browse files
committed
fix(packet): preserve RDLENGTH+RDATA for unknown RR
1 parent 19f5561 commit 3691485

3 files changed

Lines changed: 88 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
66

77
### 2.2.0 - 2026-05-25
88

9+
- fix(packet): preserve RDLENGTH+RDATA for unknown RR types
910
- feat(client): add retryOverTCP option #117
1011
- feat(client): support `dns` argument, fix docs #116
1112
- feat: add resolveSOA #115

packet.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,18 @@ Packet.Resource.encode = function(resource, writer) {
399399
})[0];
400400
if (encoder in Packet.Resource && Packet.Resource[encoder].encode) {
401401
return Packet.Resource[encoder].encode(resource, writer);
402-
} else {
403-
debug('node-dns > unknown encoder %s(%j)', encoder, resource.type);
404402
}
403+
debug('node-dns > unknown encoder %s(%j)', encoder, resource.type);
404+
// Fallback for unknown / decoder-only types: round-trip the raw RDATA the
405+
// decoder preserved as `resource.data`. Without this, RDLENGTH and RDATA
406+
// would be omitted entirely, truncating the wire format and corrupting any
407+
// records that follow.
408+
const data = Buffer.isBuffer(resource.data) ? resource.data : Buffer.alloc(0);
409+
writer.write(data.length, 16);
410+
for (const byte of data) {
411+
writer.write(byte, 8);
412+
}
413+
return writer.toBuffer();
405414
};
406415
/**
407416
* [parse description]

test/packet.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,82 @@ test('Packet.RCODE is preserved through encode/parse round-trip', function() {
671671
}
672672
});
673673

674+
test('Resource encode round-trips unknown type via raw data fallback', function() {
675+
// C1 (AUDIT-RFC.md): the encoder must write RDLENGTH+RDATA for types it
676+
// doesn't know how to serialize, otherwise the wire format is truncated.
677+
// 0xABCD is intentionally not in Packet.TYPE.
678+
const rdata = Buffer.from([ 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01 ]);
679+
const packet = new Packet();
680+
packet.header.qr = 1;
681+
packet.answers.push({
682+
name : 'unknown.example',
683+
type : 0xABCD,
684+
class : Packet.CLASS.IN,
685+
ttl : 60,
686+
data : rdata,
687+
});
688+
const parsed = Packet.parse(packet.toBuffer());
689+
assert.equal(parsed.answers.length, 1);
690+
assert.equal(parsed.answers[0].type, 0xABCD);
691+
assert.equal(parsed.answers[0].class, Packet.CLASS.IN);
692+
assert.equal(parsed.answers[0].ttl, 60);
693+
assert.ok(Buffer.isBuffer(parsed.answers[0].data));
694+
assert.deepEqual(parsed.answers[0].data, rdata);
695+
});
696+
697+
test('Resource encode of unknown type does not corrupt following records', function() {
698+
// The strongest signal for the C1 fix: without it, the missing RDLENGTH
699+
// would make the parser interpret the next record's bytes as RDATA, and
700+
// the A record below would never appear in `answers`.
701+
const packet = new Packet();
702+
packet.header.qr = 1;
703+
packet.answers.push({
704+
name : 'unknown.example',
705+
type : 0xABCD,
706+
class : Packet.CLASS.IN,
707+
ttl : 60,
708+
data : Buffer.from([ 0x01, 0x02, 0x03 ]),
709+
});
710+
packet.answers.push({
711+
name : 'after.example',
712+
type : Packet.TYPE.A,
713+
class : Packet.CLASS.IN,
714+
ttl : 30,
715+
address : '203.0.113.9',
716+
});
717+
const parsed = Packet.parse(packet.toBuffer());
718+
assert.equal(parsed.answers.length, 2);
719+
assert.equal(parsed.answers[0].type, 0xABCD);
720+
assert.equal(parsed.answers[1].type, Packet.TYPE.A);
721+
assert.equal(parsed.answers[1].name, 'after.example');
722+
assert.equal(parsed.answers[1].address, '203.0.113.9');
723+
});
724+
725+
test('Resource encode of unknown type with no data writes empty RDATA', function() {
726+
// When an unknown-type record has no `data`, encode should still emit a
727+
// valid RDLENGTH=0 block so the packet remains parseable.
728+
const packet = new Packet();
729+
packet.header.qr = 1;
730+
packet.answers.push({
731+
name : 'bare.example',
732+
type : 0xABCD,
733+
class : Packet.CLASS.IN,
734+
ttl : 0,
735+
});
736+
packet.answers.push({
737+
name : 'follow.example',
738+
type : Packet.TYPE.A,
739+
class : Packet.CLASS.IN,
740+
ttl : 30,
741+
address : '198.51.100.1',
742+
});
743+
const parsed = Packet.parse(packet.toBuffer());
744+
assert.equal(parsed.answers.length, 2);
745+
assert.equal(parsed.answers[0].type, 0xABCD);
746+
assert.equal(parsed.answers[0].data.length, 0);
747+
assert.equal(parsed.answers[1].address, '198.51.100.1');
748+
});
749+
674750
test('Packet.parse tolerates multiple questions', function() {
675751
const request = new Packet();
676752
request.header.id = 0x9999;

0 commit comments

Comments
 (0)