import * as plugins from './plugins.js'; interface IDnsServerOptions { httpsKey: string; httpsCert: string; httpsPort: number; udpPort: number; } interface IDnsHandler { domainPattern: string; recordTypes: string[]; handler: (question: plugins.dnsPacket.Question) => plugins.dnsPacket.Answer | null; } export class DnsServer { private udpServer: plugins.dgram.Socket; private httpsServer: plugins.https.Server; private handlers: IDnsHandler[] = []; constructor(private options: IDnsServerOptions) {} public registerHandler( domainPattern: string, recordTypes: string[], handler: (question: plugins.dnsPacket.Question) => plugins.dnsPacket.Answer | null ): void { this.handlers.push({ domainPattern, recordTypes, handler }); } private processDnsRequest(request: plugins.dnsPacket.Packet): plugins.dnsPacket.Packet { const response: plugins.dnsPacket.Packet = { type: 'response', id: request.id, flags: plugins.dnsPacket.RECURSION_DESIRED | plugins.dnsPacket.RECURSION_AVAILABLE, questions: request.questions, answers: [], }; for (const question of request.questions) { console.log(`Query for ${question.name} of type ${question.type}`); let answered = false; for (const handlerEntry of this.handlers) { if ( plugins.minimatch.minimatch(question.name, handlerEntry.domainPattern) && handlerEntry.recordTypes.includes(question.type) ) { const answer = handlerEntry.handler(question); if (answer) { response.answers.push(answer); answered = true; break; } } } if (!answered) { console.log(`No handler found for ${question.name} of type ${question.type}`); } } return response; } private handleHttpsRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { if (req.method === 'POST' && req.url === '/dns-query') { let body: Buffer[] = []; req.on('data', chunk => { body.push(chunk); }).on('end', () => { const msg = Buffer.concat(body); const request = plugins.dnsPacket.decode(msg); const response = this.processDnsRequest(request); const responseData = plugins.dnsPacket.encode(response); res.writeHead(200, { 'Content-Type': 'application/dns-message' }); res.end(responseData); }); } else { res.writeHead(404); res.end(); } } public async start(): Promise { this.httpsServer = plugins.https.createServer( { key: this.options.httpsKey, cert: this.options.httpsCert, }, this.handleHttpsRequest.bind(this) ); this.udpServer = plugins.dgram.createSocket('udp4'); this.udpServer.on('message', (msg, rinfo) => { const request = plugins.dnsPacket.decode(msg); const response = this.processDnsRequest(request); const responseData = plugins.dnsPacket.encode(response); this.udpServer.send(responseData, rinfo.port, rinfo.address); }); this.udpServer.on('error', (err) => { console.error(`UDP Server error:\n${err.stack}`); this.udpServer.close(); }); const udpListeningDeferred = plugins.smartpromise.defer(); const httpsListeningDeferred = plugins.smartpromise.defer(); try { this.udpServer.bind(this.options.udpPort, '0.0.0.0', () => { console.log(`UDP DNS server running on port ${this.options.udpPort}`); udpListeningDeferred.resolve(); }); this.httpsServer.listen(this.options.httpsPort, () => { console.log(`HTTPS DNS server running on port ${this.options.httpsPort}`); httpsListeningDeferred.resolve(); }); } catch (err) { console.error('Error starting DNS server:', err); process.exit(1); } await Promise.all([udpListeningDeferred.promise, httpsListeningDeferred.promise]); } public async stop(): Promise { const doneUdp = plugins.smartpromise.defer(); const doneHttps = plugins.smartpromise.defer(); this.udpServer.close(() => { console.log('UDP DNS server stopped'); this.udpServer.unref(); this.udpServer = null; doneUdp.resolve(); }); this.httpsServer.close(() => { console.log('HTTPS DNS server stopped'); this.httpsServer.unref(); this.httpsServer = null; doneHttps.resolve(); }); await Promise.all([doneUdp.promise, doneHttps.promise]); } }