Skip to content

Commit 8a53dfe

Browse files
committed
add support of DHCID record type
Related-to RFC-4701
1 parent c66cbbf commit 8a53dfe

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ This is a list adopted from <https://github.com/miekg/dns/blob/master/README.md>
8686
- [x] 4509 - SHA256 Hash in DS
8787
- [x] 4592 - Wildcards in the DNS
8888
- [x] 4635 - HMAC SHA TSIG
89-
- [ ] 4701 - DHCID
89+
- [x] 4701 - DHCID
9090
- [x] 4892 - id.server
9191
- [x] 5001 - NSID
9292
- [x] 5155 - NSEC3 record

src/records/dhcid.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from "chai";
2+
import { parseRecord } from ".";
3+
import { RR } from "../rr";
4+
import { RRType } from "../types";
5+
6+
describe("test DHCID", () => {
7+
// https://datatracker.ietf.org/doc/html/rfc4701#section-3.6
8+
const examples = [
9+
// Example 1: DHCPv6 client identifier with FQDN "chi6.example.com"
10+
[
11+
`chi6.example.com. 3600 IN DHCID ( AAIBY2/AuCccgoJbsaxcQc9TUapptP69l
12+
OjxfNuVAA2kjEA= )`,
13+
`chi6.example.com.\t\t3600\tIN\tDHCID\tAAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=`,
14+
],
15+
// Example 2: DHCP client-identifier option (IPv4 client) with FQDN "chi.example.com"
16+
[
17+
`chi.example.com. 3600 IN DHCID ( AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdW
18+
L3b/NaiUDlW2No= )`,
19+
`chi.example.com.\t\t3600\tIN\tDHCID\tAAEBOSD+XR3Os/0LozeXVqcNc7FwCfQdWL3b/NaiUDlW2No=`,
20+
],
21+
// Example 3: MAC address as identity with FQDN "client.example.com"
22+
[
23+
`client.example.com. 3600 IN DHCID ( AAABxLmlskllE0MVjd57zHcWmEH3pCQ6V
24+
ytcKD//7es/deY= )`,
25+
`client.example.com.\t\t3600\tIN\tDHCID\tAAABxLmlskllE0MVjd57zHcWmEH3pCQ6VytcKD//7es/deY=`,
26+
],
27+
];
28+
29+
for (const i in examples) {
30+
let record: RR | undefined;
31+
32+
it(`should parse DHCID example ${+i + 1}`, () => {
33+
record = parseRecord(examples[i][0]);
34+
expect(record.header.type).to.equal(RRType.DHCID);
35+
});
36+
37+
it(`should present DHCID example ${+i + 1}`, () => {
38+
const out = record?.present();
39+
expect(out).to.equal(examples[i][1]);
40+
});
41+
}
42+
});

src/records/dhcid.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { stringToBinary, binaryToString } from "../encoding";
2+
import { ParseError } from "../error";
3+
import { Slice } from "../packet";
4+
import { RR } from "../rr";
5+
import { Writer } from "../buffer";
6+
import { CharacterString } from "../char";
7+
import { Uint16, Uint8 } from "../types";
8+
9+
/**
10+
* The DHCID (DHCP Identifier) record is used to associate a DHCP client identity with a domain name.
11+
*
12+
* The record contains a digest generated from the client's identifier and FQDN,
13+
* ensuring only the rightful DHCP client can update the DNS entry.
14+
*
15+
* RDATA format (RFC 4701):
16+
* ```
17+
* 0 1 2 3
18+
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
19+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20+
* | identifier type (16 bits) | digest type | |
21+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
22+
* | digest (binary) |
23+
* | |
24+
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25+
* ```
26+
*
27+
* Specified by {@link https://datatracker.ietf.org/doc/html/rfc4701 | RFC 4701}
28+
*/
29+
export class DHCID extends RR {
30+
identifierType!: Uint16; // 2 bytes
31+
digestType!: Uint8; // 1 byte
32+
digest!: Uint8Array; // variable length
33+
34+
unpackRdata(rdata: Slice): void {
35+
this.identifierType = rdata.readUint16();
36+
this.digestType = rdata.readUint8();
37+
this.digest = rdata.readUint8Array();
38+
}
39+
40+
packRdata(buf: Writer): number {
41+
let n = buf.writeUint16(this.identifierType);
42+
n += buf.writeUint8(this.digestType);
43+
n += buf.write(this.digest);
44+
return n;
45+
}
46+
47+
parseRdata(rdata: CharacterString[]): void {
48+
if (rdata.length === 0) {
49+
throw new ParseError("Missing fields in DHCID RDATA");
50+
}
51+
52+
const str = rdata.reduce((v, cur) => v += cur.raw(), "");
53+
const data = stringToBinary(str, "base64");
54+
55+
// At lease has 3 bytes, the first two bytes is the identifier type and
56+
// the third one is digest type.
57+
if (data.byteLength < 3) {
58+
throw new ParseError("Invalid DHCID RDATA");
59+
}
60+
61+
this.identifierType = (data[0] << 8) | data[1];
62+
this.digestType = data[2];
63+
64+
this.digest = data.slice(3);
65+
}
66+
67+
presentRdata(): string {
68+
const data = new Uint8Array(3 + this.digest.byteLength);
69+
data[0] = this.identifierType >>> 8;
70+
data[1] = this.identifierType & 0xff;
71+
data[2] = this.digestType;
72+
data.set(this.digest, 3);
73+
return binaryToString(data, "base64");
74+
}
75+
}

src/records/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Lexer, scanHeader, scanRdata } from "../scan";
2929
import { BufferReader, CharReader } from "../buffer";
3030
import { APL } from "./apl";
3131
import { IPSECKEY } from "./ipseckey";
32+
import { DHCID } from "./dhcid";
3233
import { Unknown } from "./unknown";
3334

3435
export { A, isA } from "./a";
@@ -55,6 +56,7 @@ export { NSAP, NSAPPTR } from "./nsap";
5556
export { NAPTR } from "./naptr";
5657
export { APL } from "./apl";
5758
export { IPSECKEY } from "./ipseckey";
59+
export { DHCID } from "./dhcid";
5860
export { Unknown } from "./unknown";
5961

6062
/**
@@ -207,6 +209,9 @@ function initRecord(h: Header): RR {
207209
record = new IPSECKEY(h);
208210
break;
209211
}
212+
case RRType.DHCID:
213+
record = new DHCID(h);
214+
break;
210215
default:
211216
record = new Unknown(h);
212217
break;

0 commit comments

Comments
 (0)