feat(dns-server): Improve DNS server interface binding by adding explicit IP validation, configurable UDP/HTTPS binding, and enhanced logging.

This commit is contained in:
2025-05-28 19:03:45 +00:00
parent df209ffa71
commit dd12641fb0
8 changed files with 576 additions and 35 deletions

View File

@ -8,6 +8,8 @@ export interface IDnsServerOptions {
httpsPort: number;
udpPort: number;
dnssecZone: string;
udpBindInterface?: string;
httpsBindInterface?: string;
}
export interface DnsAnswer {
@ -31,17 +33,6 @@ interface DNSKEYData {
key: Buffer;
}
interface RRSIGData {
typeCovered: string; // Changed to string to match dns-packet expectations
algorithm: number;
labels: number;
originalTTL: number;
expiration: number;
inception: number;
keyTag: number;
signerName: string;
signature: Buffer;
}
// Let's Encrypt related interfaces
interface LetsEncryptOptions {
@ -70,7 +61,7 @@ export class DnsServer {
});
// Generate DNSKEY and DS records
const { dsRecord, dnskeyRecord } = this.dnsSec.getDsAndKeyPair();
const { dnskeyRecord } = this.dnsSec.getDsAndKeyPair();
// Parse DNSKEY record into dns-packet format
this.dnskeyRecord = this.parseDNSKEYRecord(dnskeyRecord);
@ -183,7 +174,7 @@ export class DnsServer {
const domain = auth.identifier.value;
// Get DNS challenge
const challenge = auth.challenges.find(c => c.type === 'dns-01');
const challenge = auth.challenges.find((c: any) => c.type === 'dns-01');
if (!challenge) {
throw new Error(`No DNS-01 challenge found for ${domain}`);
}
@ -334,8 +325,9 @@ export class DnsServer {
this.handleHttpsRequest.bind(this)
);
this.httpsServer.listen(this.options.httpsPort, () => {
console.log(`HTTPS DNS server restarted on port ${this.options.httpsPort} with test certificate`);
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
console.log(`HTTPS DNS server restarted on ${httpsInterface}:${this.options.httpsPort} with test certificate`);
resolve();
});
return;
@ -351,8 +343,9 @@ export class DnsServer {
this.handleHttpsRequest.bind(this)
);
this.httpsServer.listen(this.options.httpsPort, () => {
console.log(`HTTPS DNS server restarted on port ${this.options.httpsPort} with new certificate`);
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
console.log(`HTTPS DNS server restarted on ${httpsInterface}:${this.options.httpsPort} with new certificate`);
resolve();
});
} catch (err) {
@ -386,6 +379,25 @@ export class DnsServer {
return authorizedDomains;
}
/**
* Validate if a string is a valid IP address (IPv4 or IPv6)
*/
private isValidIpAddress(ip: string): boolean {
// IPv4 pattern
const ipv4Pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// IPv6 pattern (simplified but more comprehensive)
const ipv6Pattern = /^(::1|::)$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
return ipv4Pattern.test(ip) || ipv6Pattern.test(ip);
}
/**
* Determine if an IP address is IPv6
*/
private isIPv6(ip: string): boolean {
return ip.includes(':');
}
/**
* Check if the server is authoritative for a domain
*/
@ -683,6 +695,18 @@ export class DnsServer {
}
public async start(): Promise<void> {
// Validate interface addresses if provided
const udpInterface = this.options.udpBindInterface || '0.0.0.0';
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
if (this.options.udpBindInterface && !this.isValidIpAddress(this.options.udpBindInterface)) {
throw new Error(`Invalid UDP bind interface: ${this.options.udpBindInterface}`);
}
if (this.options.httpsBindInterface && !this.isValidIpAddress(this.options.httpsBindInterface)) {
throw new Error(`Invalid HTTPS bind interface: ${this.options.httpsBindInterface}`);
}
this.httpsServer = plugins.https.createServer(
{
key: this.options.httpsKey,
@ -691,7 +715,9 @@ export class DnsServer {
this.handleHttpsRequest.bind(this)
);
this.udpServer = plugins.dgram.createSocket('udp4');
// Create appropriate socket type based on interface
const socketType = this.isIPv6(udpInterface) ? 'udp6' : 'udp4';
this.udpServer = plugins.dgram.createSocket(socketType);
this.udpServer.on('message', (msg, rinfo) => {
const request = dnsPacket.decode(msg);
const response = this.processDnsRequest(request);
@ -708,13 +734,13 @@ export class DnsServer {
const httpsListeningDeferred = plugins.smartpromise.defer<void>();
try {
this.udpServer.bind(this.options.udpPort, '0.0.0.0', () => {
console.log(`UDP DNS server running on port ${this.options.udpPort}`);
this.udpServer.bind(this.options.udpPort, udpInterface, () => {
console.log(`UDP DNS server running on ${udpInterface}:${this.options.udpPort}`);
udpListeningDeferred.resolve();
});
this.httpsServer.listen(this.options.httpsPort, () => {
console.log(`HTTPS DNS server running on port ${this.options.httpsPort}`);
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
console.log(`HTTPS DNS server running on ${httpsInterface}:${this.options.httpsPort}`);
httpsListeningDeferred.resolve();
});
} catch (err) {