2024-06-02 13:34:19 +00:00
|
|
|
import * as plugins from './plugins.js';
|
|
|
|
|
|
|
|
interface IDnsServerOptions {
|
|
|
|
httpsKey: string;
|
|
|
|
httpsCert: string;
|
|
|
|
httpsPort: number;
|
|
|
|
udpPort: number;
|
|
|
|
}
|
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
interface IDnsHandler {
|
|
|
|
domainPattern: string;
|
|
|
|
recordTypes: string[];
|
|
|
|
handler: (question: plugins.dnsPacket.Question) => plugins.dnsPacket.Answer | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class DnsServer {
|
2024-06-02 13:34:19 +00:00
|
|
|
private udpServer: plugins.dgram.Socket;
|
|
|
|
private httpsServer: plugins.https.Server;
|
2024-09-18 17:28:28 +00:00
|
|
|
private handlers: IDnsHandler[] = [];
|
2024-06-02 13:34:19 +00:00
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
constructor(private options: IDnsServerOptions) {}
|
2024-06-02 13:34:19 +00:00
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
public registerHandler(
|
|
|
|
domainPattern: string,
|
|
|
|
recordTypes: string[],
|
|
|
|
handler: (question: plugins.dnsPacket.Question) => plugins.dnsPacket.Answer | null
|
|
|
|
): void {
|
|
|
|
this.handlers.push({ domainPattern, recordTypes, handler });
|
2024-06-02 13:34:19 +00:00
|
|
|
}
|
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2024-06-02 13:34:19 +00:00
|
|
|
}
|
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
if (!answered) {
|
|
|
|
console.log(`No handler found for ${question.name} of type ${question.type}`);
|
|
|
|
}
|
|
|
|
}
|
2024-06-02 13:34:19 +00:00
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
return response;
|
2024-06-02 13:34:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-09-18 17:28:28 +00:00
|
|
|
const response = this.processDnsRequest(request);
|
2024-06-02 13:34:19 +00:00
|
|
|
const responseData = plugins.dnsPacket.encode(response);
|
|
|
|
res.writeHead(200, { 'Content-Type': 'application/dns-message' });
|
|
|
|
res.end(responseData);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res.writeHead(404);
|
|
|
|
res.end();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
public async start(): Promise<void> {
|
|
|
|
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);
|
2024-06-02 13:34:19 +00:00
|
|
|
});
|
2024-09-18 17:28:28 +00:00
|
|
|
|
|
|
|
this.udpServer.on('error', (err) => {
|
|
|
|
console.error(`UDP Server error:\n${err.stack}`);
|
|
|
|
this.udpServer.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
const udpListeningDeferred = plugins.smartpromise.defer<void>();
|
|
|
|
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}`);
|
|
|
|
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]);
|
2024-06-02 13:34:19 +00:00
|
|
|
}
|
|
|
|
|
2024-09-18 17:28:28 +00:00
|
|
|
public async stop(): Promise<void> {
|
|
|
|
const doneUdp = plugins.smartpromise.defer<void>();
|
|
|
|
const doneHttps = plugins.smartpromise.defer<void>();
|
2024-06-02 13:34:19 +00:00
|
|
|
this.udpServer.close(() => {
|
|
|
|
console.log('UDP DNS server stopped');
|
2024-09-18 17:28:28 +00:00
|
|
|
this.udpServer.unref();
|
|
|
|
this.udpServer = null;
|
|
|
|
doneUdp.resolve();
|
2024-06-02 13:34:19 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
this.httpsServer.close(() => {
|
|
|
|
console.log('HTTPS DNS server stopped');
|
2024-09-18 17:28:28 +00:00
|
|
|
this.httpsServer.unref();
|
|
|
|
this.httpsServer = null;
|
|
|
|
doneHttps.resolve();
|
2024-06-02 13:34:19 +00:00
|
|
|
});
|
2024-09-18 17:28:28 +00:00
|
|
|
|
|
|
|
await Promise.all([doneUdp.promise, doneHttps.promise]);
|
2024-06-02 13:34:19 +00:00
|
|
|
}
|
2024-09-18 17:28:28 +00:00
|
|
|
}
|