Skip to content

Commit 07a86e5

Browse files
committed
fix: RFC correctness drive updates
- fix(packet): Name decode rejects pointer cycles (RFC 1035) - fix(packet): EDNS exposes extendedRcode/version/doFlag; udpPayloadSize configurable (RFC 6891) - fix(packet): Header initializes ancount; AD/CD bits split from Z (RFC 4035) - fix(packet): ECS encoder truncates address to ceil(prefix/8) octets, adds IPv6 family (RFC 7871)
1 parent 19f93e4 commit 07a86e5

3 files changed

Lines changed: 50 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
44

55
### Unreleased
66

7-
- fix(packet): Name decode rejects pointer cycles (RFC 1035)
8-
- fix(packet): EDNS exposes extendedRcode/version/doFlag; udpPayloadSize configurable (RFC 6891)
9-
- fix(packet): Header initializes ancount; AD/CD bits split from Z (RFC 4035)
10-
- fix(packet): ECS encoder truncates address to ceil(prefix/8) octets, adds IPv6 family (RFC 7871)
7+
- fix(packet): IPv6 `::` compression for leading-zero address #123
8+
- fix(packet): Name decode rejects pointer cycles (RFC 1035) #124
9+
- fix(packet): EDNS exposes extendedRcode/version/doFlag #124
10+
- fix(packet): Header initializes ancount; AD/CD bits split from Z (RFC 4035) #124
11+
- fix(packet): ECS encoder truncates address, adds IPv6 (RFC 7871) #124
1112
- feat(server): PROXY protocol v1/v2 support #122
1213

1314
### 2.2.1 - 2026-05-25

packet.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,29 @@ const BufferWriter = require('./lib/writer');
55

66
const debug = debuglog('dns2');
77

8-
const toIPv6 = buffer => buffer
9-
.map(part => (part > 0 ? part.toString(16) : '0'))
10-
.join(':')
11-
.replace(/\b(?:0+:){1,}/, ':');
8+
// Canonical IPv6 text form per RFC 5952:
9+
// - lower case hex, no leading zeros per group (handled by toString(16))
10+
// - the longest run of >= 2 zero groups is replaced with "::"
11+
// - on ties, the first such run is chosen
12+
// - a single zero group is NOT compressed
13+
const toIPv6 = buffer => {
14+
const segments = buffer.map(part => (part > 0 ? part.toString(16) : '0'));
15+
let bestStart = -1; let bestLen = 0;
16+
let curStart = -1; let curLen = 0;
17+
for (let i = 0; i < segments.length; i++) {
18+
if (segments[i] === '0') {
19+
if (curLen === 0) curStart = i;
20+
curLen++;
21+
if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; }
22+
} else {
23+
curLen = 0;
24+
}
25+
}
26+
if (bestLen < 2) return segments.join(':');
27+
const before = segments.slice(0, bestStart).join(':');
28+
const after = segments.slice(bestStart + bestLen).join(':');
29+
return `${before}::${after}`;
30+
};
1231

1332
const fromIPv6 = (address) => {
1433
const digits = address.split(':');

test/packet.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,28 @@ test('Package#toIPv6', function() {
8181
assert.equal(Packet.toIPv6([ 9734, 18176, 12552, 0, 0, 0, 44098, 10984 ]), '2606:4700:3108::ac42:2ae8');
8282
});
8383

84+
test('Package#toIPv6 RFC 5952 — leading-zero addresses', function() {
85+
assert.equal(Packet.toIPv6([ 0, 0, 0, 0, 0, 0, 0, 1 ]), '::1');
86+
assert.equal(Packet.toIPv6([ 0, 0, 0, 0, 0, 0, 0, 0 ]), '::');
87+
assert.equal(Packet.toIPv6([ 0, 0, 0, 0, 0, 0xffff, 0xc000, 0x0201 ]), '::ffff:c000:201');
88+
});
89+
90+
test('Package#toIPv6 RFC 5952 — trailing-zero addresses', function() {
91+
assert.equal(Packet.toIPv6([ 1, 0, 0, 0, 0, 0, 0, 0 ]), '1::');
92+
assert.equal(Packet.toIPv6([ 0x2001, 0xdb8, 0, 0, 0, 0, 0, 0 ]), '2001:db8::');
93+
});
94+
95+
test('Package#toIPv6 RFC 5952 — single zero group is not compressed', function() {
96+
// §4.2.2: "::" MUST NOT be used to shorten just one 16-bit 0 field.
97+
assert.equal(Packet.toIPv6([ 1, 0, 1, 1, 1, 1, 1, 1 ]), '1:0:1:1:1:1:1:1');
98+
});
99+
100+
test('Package#toIPv6 RFC 5952 — first run wins on tie', function() {
101+
// §4.2.3: when there is more than one run of equal maximum length,
102+
// the first is shortened.
103+
assert.equal(Packet.toIPv6([ 1, 0, 0, 1, 0, 0, 1, 1 ]), '1::1:0:0:1:1');
104+
});
105+
84106
test('Package#fromIPv6', function() {
85107
assert.deepEqual(Packet.fromIPv6('2a04:4e42:200::323'), [
86108
'2a04', '4e42', '0200', '0', '0', '0', '0', '0323' ]);

0 commit comments

Comments
 (0)