Skip to content

Commit

Permalink
fix: proper handling of EDNS0 opt records incl. padding
Browse files Browse the repository at this point in the history
  • Loading branch information
wmalinowski committed Oct 23, 2024
1 parent fbcf96b commit b832567
Show file tree
Hide file tree
Showing 6 changed files with 559 additions and 25 deletions.
11 changes: 10 additions & 1 deletion lib/dns-message.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, test, expect } from "vitest";
import type { DnsOptRecord } from "./dns-message";
import {
createDnsQuery,
serializeDnsQuery,
Expand Down Expand Up @@ -82,9 +83,17 @@ describe("parseAnswer", () => {
// it will add random padding to the message
// so we cannot compare the buffers directly
const serialized = serializeDnsQuery(query);

const message = parseDnsMessage(serialized);

// we need cheat a bit and add padding to the expected message
const optRecords = message.additionalRecords.filter(
(ar) => ar.type === 41,
) as DnsOptRecord[];
const paddingRecord = optRecords.filter(
(ar) => ar.type === 41 && ar.rdata[0]?.optionCode === 12,
)[0];
query.additionalRecords.push(paddingRecord);

expect(message).toStrictEqual(query);
});
});
58 changes: 41 additions & 17 deletions lib/dns-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ export function createDnsQuery(questions: DnsQuestion[]): DnsMessage {
rdata: [],
} as DnsOptRecord);

// leave space for EDNS0 padding
message.arcount += 1;

return message;
}

Expand Down Expand Up @@ -273,8 +276,14 @@ function serializeResourceRecord(

if (record.type === 41) {
// OPT record
if (record.rdlength !== 0) {
throw new Error("EDNS0 OPT records not supported");
for (const opt of (<DnsOptRecord>record).rdata) {
message.setUint16(offset, opt.optionCode);
offset += 2;
message.setUint16(offset, opt.optionLength);
offset += 2;
for (const byte of opt.optionData) {
message.setUint8(offset++, byte);
}
}
} else {
for (const byte of (<DnsResourceRecord>record).rdata) {
Expand All @@ -290,18 +299,27 @@ function serializeEDNS0Padding(message: DataView, offset: number) {

// padding header of 4 bytes must be included
const length = offset + 4;
const padding = Math.ceil(length / 128) * 128 - length;
const paddingLength = Math.ceil(length / 128) * 128 - length;

const padding = Array.from({ length: paddingLength }, () =>
Math.floor(Math.random() * 0xff),
);

// RFC 7830
message.setUint16(offset, 12); // OPTION-CODE for EDNS0 padding
offset += 2;
message.setUint16(offset, padding); // padding length
offset += 2;
for (let i = 0; i < padding; i++) {
// we do not need to generate cryptographically secure random numbers
// simple random numbers are enough to obfuscate the message
message.setUint8(offset++, Math.floor(Math.random() * 0xff));
}
const paddingRecord: DnsOptRecord = {
name: [],
type: 41,
maxPayloadSize: 0,
extendedRcode: 0,
version: 0,
do: 0,
z: 0,
rdlength: padding.length + 4,
rdata: [
{ optionCode: 12, optionLength: padding.length, optionData: padding },
],
};
offset = serializeResourceRecord(message, offset, paddingRecord);

return offset;
}
Expand Down Expand Up @@ -395,7 +413,7 @@ export function parseDnsMessage(

if (offset !== raw.byteLength) {
// TODO: implement support for parsing padding and replace the warning with error
console.warn(
throw new Error(
`Unexpected end of message (offset: ${offset}, length: ${raw.byteLength})`,
);
}
Expand Down Expand Up @@ -490,13 +508,19 @@ function parseOptRecordBody(

record.rdlength = rawView.getUint16(offset);
offset += 2;
for (let j = 0; j < record.rdlength; j++) {

const rdEnd = offset + record.rdlength;

while (offset < rdEnd) {
const optionCode = rawView.getUint16(offset);
offset += 2;
const optionLength = rawView.getUint16(offset);
const opt = {
optionCode: rawView.getUint16(offset),
optionLength: rawView.getUint16(offset + 2),
optionCode,
optionLength,
optionData: [],
} as Opt;
offset += 4;
offset += 2;
for (let k = 0; k < opt.optionLength; k++) {
opt.optionData.push(rawView.getUint8(offset++));
}
Expand Down
12 changes: 7 additions & 5 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "./dns-message";
import { domainToAscii } from "./punycode";

export function resolve(name: string, type: string) {
export function resolve(server: string, name: string, type: string) {
const qualifiedName = domainToAscii(name);

const query = createDnsQuery([
Expand All @@ -22,7 +22,9 @@ export function resolve(name: string, type: string) {

const buffer = serializeDnsQuery(query);

const url = new URL("https://1.1.1.1/dns-query");
const url = new URL(server);
url.pathname = "/dns-query";

return fetch(url, {
method: "POST",
mode: "cors",
Expand All @@ -34,9 +36,9 @@ export function resolve(name: string, type: string) {
})
.then((response) => {
if (!response.ok) {
throw new Error(
`Failed to fetch: ${response.status} ${response.statusText}`,
);
return response.text().then((text) => {
throw new Error(`HTTP ${response.status}: ${text}`);
});
}
return response.arrayBuffer();
})
Expand Down
Loading

0 comments on commit b832567

Please sign in to comment.