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:
@ -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) {
|
||||
|
Reference in New Issue
Block a user