feat(dnsserver): Return multiple matching records, improve DNSSEC RRset signing, add client resolution strategy and localhost handling, update tests

This commit is contained in:
2025-09-12 17:32:03 +00:00
parent afd1c18496
commit f29962a6dc
8 changed files with 281 additions and 42 deletions

View File

@@ -15,6 +15,8 @@ export interface IDnsServerOptions {
manualHttpsMode?: boolean;
// Primary nameserver for SOA records (defaults to ns1.{dnssecZone})
primaryNameserver?: string;
// Local handling for RFC 6761 localhost (default: true)
enableLocalhostHandling?: boolean;
}
export interface DnsAnswer {
@@ -569,6 +571,7 @@ export class DnsServer {
console.log(`Query for ${question.name} of type ${question.type}`);
let answered = false;
let shouldSignRrset = true; // skip DNSSEC signing for synthetic/local answers
const recordsForQuestion: DnsAnswer[] = [];
// Handle DNSKEY queries if DNSSEC is requested
@@ -582,9 +585,59 @@ export class DnsServer {
};
recordsForQuestion.push(dnskeyAnswer);
answered = true;
// DNSKEY is signable, keep shouldSignRrset true
} else {
// Built-in handling for localhost and reverse localhost (RFC 6761)
const enableLocal = this.options.enableLocalhostHandling !== false;
if (enableLocal) {
const qnameLower = (question.name || '').toLowerCase();
const qnameTrimmed = qnameLower.endsWith('.') ? qnameLower.slice(0, -1) : qnameLower;
// localhost forward lookups
if (qnameTrimmed === 'localhost') {
if (question.type === 'A') {
recordsForQuestion.push({
name: question.name,
type: 'A',
class: 'IN',
ttl: 0,
data: '127.0.0.1',
});
answered = true;
shouldSignRrset = false;
} else if (question.type === 'AAAA') {
recordsForQuestion.push({
name: question.name,
type: 'AAAA',
class: 'IN',
ttl: 0,
data: '::1',
});
answered = true;
shouldSignRrset = false;
}
}
// Reverse lookup for 127.0.0.1
if (!answered) {
const reverseLocalhostV4 = '1.0.0.127.in-addr.arpa';
if (qnameTrimmed === reverseLocalhostV4 && question.type === 'PTR') {
recordsForQuestion.push({
name: question.name,
type: 'PTR',
class: 'IN',
ttl: 0,
data: 'localhost.',
});
answered = true;
shouldSignRrset = false;
}
}
}
// Collect all matching records from handlers
for (const handlerEntry of this.handlers) {
if (!answered) {
for (const handlerEntry of this.handlers) {
if (
plugins.minimatch.minimatch(question.name, handlerEntry.domainPattern) &&
handlerEntry.recordTypes.includes(question.type)
@@ -602,6 +655,7 @@ export class DnsServer {
// Continue processing other handlers to allow multiple records
}
}
}
}
}
@@ -612,7 +666,7 @@ export class DnsServer {
}
// Group records by type for DNSSEC signing
if (dnssecRequested) {
if (dnssecRequested && shouldSignRrset) {
const rrsetKey = `${question.name}:${question.type}`;
rrsetMap.set(rrsetKey, recordsForQuestion);
}
@@ -1019,7 +1073,9 @@ export class DnsServer {
}
private nameToBuffer(name: string): Buffer {
const labels = name.split('.');
// Trim trailing dot to avoid double-root
const trimmed = name.endsWith('.') ? name.slice(0, -1) : name;
const labels = trimmed.split('.').filter(l => l.length > 0);
const buffers = labels.map(label => {
const len = Buffer.byteLength(label, 'utf8');
const buf = Buffer.alloc(1 + len);
@@ -1027,6 +1083,7 @@ export class DnsServer {
buf.write(label, 1);
return buf;
});
return Buffer.concat([...buffers, Buffer.from([0])]); // Add root label
// Append exactly one root label
return Buffer.concat([...buffers, Buffer.from([0])]);
}
}
}