feat(smartdns): Add DNS Server and DNSSEC tools with comprehensive unit tests
This commit is contained in:
parent
2cfecab96f
commit
5c06ae1edb
95
changelog.md
Normal file
95
changelog.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2024-09-18 - 6.1.0 - feat(smartdns)
|
||||||
|
Add DNS Server and DNSSEC tools with comprehensive unit tests
|
||||||
|
|
||||||
|
- Updated package dependencies to the latest versions
|
||||||
|
- Introduced DnsServer class for handling DNS requests over both HTTPS and UDP with support for custom handlers
|
||||||
|
- Added DnsSec class for generating and managing DNSSEC keys and DS records
|
||||||
|
- Implemented unit tests for DnsServer and Smartdns classes
|
||||||
|
|
||||||
|
## 2024-06-02 - 6.0.0 - server/client
|
||||||
|
Main description here
|
||||||
|
|
||||||
|
- **Breaking Change:** Move from client only to server + client exports.
|
||||||
|
|
||||||
|
## 2024-03-30 - 5.0.4 - maintenance
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Switch to new org scheme
|
||||||
|
|
||||||
|
## 2023-04-08 - 5.0.4 - core
|
||||||
|
Main description here
|
||||||
|
|
||||||
|
- Core update
|
||||||
|
- Fixes applied to the system
|
||||||
|
|
||||||
|
## 2022-07-27 - 5.0.0 - core
|
||||||
|
Update contains relevant changes
|
||||||
|
|
||||||
|
- **Breaking Change:** Major update and core changes
|
||||||
|
- Fixes and updates applied
|
||||||
|
|
||||||
|
## 2022-07-27 - 4.0.11 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- **Breaking Change:** Core update and changes applied
|
||||||
|
|
||||||
|
## 2021-08-24 - 4.0.10 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Fixes applied to the core functionalities
|
||||||
|
|
||||||
|
## 2021-01-23 - 4.0.8 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Updates and fixes to the core components
|
||||||
|
|
||||||
|
## 2020-08-05 - 4.0.4 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Multiple core fixes applied
|
||||||
|
|
||||||
|
## 2020-02-15 - 4.0.0 - core
|
||||||
|
Main description here
|
||||||
|
|
||||||
|
- Core updates
|
||||||
|
- Fixes applied across the system
|
||||||
|
|
||||||
|
## 2020-02-15 - 3.0.8 - core
|
||||||
|
Core updates with major changes
|
||||||
|
|
||||||
|
- **Breaking Change:** Now uses Google DNS HTTPS API and handles DNSSEC validation
|
||||||
|
|
||||||
|
## 2019-01-07 - 3.0.6 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Fixes and updates applied to the core
|
||||||
|
|
||||||
|
## 2018-05-13 - 3.0.4 - core
|
||||||
|
Range contains relevant changes
|
||||||
|
|
||||||
|
- Fixes applied, including `fix .checkUntilAvailable` error
|
||||||
|
|
||||||
|
## 2018-05-13 - 3.0.0 - ci
|
||||||
|
Main description here
|
||||||
|
|
||||||
|
- CI changes and updates to the access level and global packages
|
||||||
|
|
||||||
|
## 2017-07-31 - 2.0.10 - package
|
||||||
|
Update to new package name and improved record retrieval
|
||||||
|
|
||||||
|
- **Breaking Change:** Package name update and record retrieval improvements
|
||||||
|
|
||||||
|
## 2017-01-27 - 2.0.1 - maintenance
|
||||||
|
Multiple fixes and merges
|
||||||
|
|
||||||
|
## 2017-01-27 - 2.0.0 - core
|
||||||
|
Fix typings and update to better API
|
||||||
|
|
||||||
|
## 2016-11-15 - 1.0.7 - initial
|
||||||
|
Initial setup and improvements
|
||||||
|
|
||||||
|
- Initial deployment
|
||||||
|
- README improvements
|
||||||
|
|
17
package.json
17
package.json
@ -36,18 +36,21 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartdelay": "^3.0.1",
|
"@push.rocks/smartdelay": "^3.0.1",
|
||||||
"@push.rocks/smartenv": "^5.0.5",
|
"@push.rocks/smartenv": "^5.0.5",
|
||||||
"@push.rocks/smartpromise": "^4.0.2",
|
"@push.rocks/smartpromise": "^4.0.4",
|
||||||
"@push.rocks/smartrequest": "^2.0.15",
|
"@push.rocks/smartrequest": "^2.0.15",
|
||||||
"@tsclass/tsclass": "^4.0.54",
|
"@tsclass/tsclass": "^4.1.2",
|
||||||
"@types/dns-packet": "^5.6.5",
|
"@types/dns-packet": "^5.6.5",
|
||||||
"dns-packet": "^5.6.1"
|
"@types/elliptic": "^6.4.18",
|
||||||
|
"dns-packet": "^5.6.1",
|
||||||
|
"elliptic": "^6.5.7",
|
||||||
|
"minimatch": "^10.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.66",
|
"@git.zone/tsbuild": "^2.1.84",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^1.2.49",
|
||||||
"@git.zone/tstest": "^1.0.77",
|
"@git.zone/tstest": "^1.0.77",
|
||||||
"@push.rocks/tapbundle": "^5.0.8",
|
"@push.rocks/tapbundle": "^5.2.0",
|
||||||
"@types/node": "^20.13.0"
|
"@types/node": "^22.5.5"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
878
pnpm-lock.yaml
generated
878
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,163 @@
|
|||||||
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
|
import { tapNodeTools } from '@push.rocks/tapbundle/node';
|
||||||
|
|
||||||
|
import * as dnsPacket from 'dns-packet';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as dgram from 'dgram';
|
||||||
|
|
||||||
|
import * as smartdns from '../ts_server/index.js';
|
||||||
|
|
||||||
|
let dnsServer: smartdns.DnsServer;
|
||||||
|
|
||||||
|
tap.test('should create an instance of DnsServer', async () => {
|
||||||
|
// Use valid options
|
||||||
|
const httpsData = await tapNodeTools.createHttpsCert();
|
||||||
|
dnsServer = new smartdns.DnsServer({
|
||||||
|
httpsKey: httpsData.key,
|
||||||
|
httpsCert: httpsData.cert,
|
||||||
|
httpsPort: 8080,
|
||||||
|
udpPort: 8081,
|
||||||
|
});
|
||||||
|
expect(dnsServer).toBeInstanceOf(smartdns.DnsServer);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should start the server', async () => {
|
||||||
|
await dnsServer.start();
|
||||||
|
// @ts-ignore
|
||||||
|
expect(dnsServer.httpsServer).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('lets add a handler', async () => {
|
||||||
|
dnsServer.registerHandler('*.bleu.de', ['A'], (question) => {
|
||||||
|
return {
|
||||||
|
name: question.name,
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
ttl: 300,
|
||||||
|
data: '127.0.0.1',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const response = dnsServer.processDnsRequest({
|
||||||
|
type: 'query',
|
||||||
|
id: 1,
|
||||||
|
flags: 0,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answers: [],
|
||||||
|
});
|
||||||
|
expect(response.answers[0]).toEqual({
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
ttl: 300,
|
||||||
|
data: '127.0.0.1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('lets query over https', async () => {
|
||||||
|
const query = dnsPacket.encode({
|
||||||
|
type: 'query',
|
||||||
|
id: 2,
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('https://localhost:8080/dns-query', {
|
||||||
|
method: 'POST',
|
||||||
|
body: query,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/dns-message',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
|
||||||
|
const responseData = await response.arrayBuffer();
|
||||||
|
const dnsResponse = dnsPacket.decode(Buffer.from(responseData));
|
||||||
|
|
||||||
|
console.log(dnsResponse.answers[0]);
|
||||||
|
|
||||||
|
expect(dnsResponse.answers[0]).toEqual({
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
ttl: 300,
|
||||||
|
flush: false,
|
||||||
|
data: '127.0.0.1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('lets query over udp', async () => {
|
||||||
|
const client = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
const query = dnsPacket.encode({
|
||||||
|
type: 'query',
|
||||||
|
id: 3,
|
||||||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const responsePromise = new Promise<dnsPacket.Packet>((resolve, reject) => {
|
||||||
|
client.on('message', (msg) => {
|
||||||
|
const dnsResponse = dnsPacket.decode(msg);
|
||||||
|
resolve(dnsResponse);
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
client.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.send(query, 8081, 'localhost', (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const dnsResponse = await responsePromise;
|
||||||
|
|
||||||
|
console.log(dnsResponse.answers[0]);
|
||||||
|
|
||||||
|
expect(dnsResponse.answers[0]).toEqual({
|
||||||
|
name: 'dnsly_a.bleu.de',
|
||||||
|
type: 'A',
|
||||||
|
class: 'IN',
|
||||||
|
ttl: 300,
|
||||||
|
flush: false,
|
||||||
|
data: '127.0.0.1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should run for a while', async (toolsArg) => {
|
||||||
|
await toolsArg.delayFor(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should stop the server', async () => {
|
||||||
|
await dnsServer.stop();
|
||||||
|
// @ts-ignore
|
||||||
|
expect(dnsServer.httpsServer).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
await tap.start();
|
@ -24,7 +24,7 @@ export const makeNodeProcessUseDnsProvider = (providerArg: TDnsProvider) => {
|
|||||||
|
|
||||||
export interface ISmartDnsConstructorOptions {}
|
export interface ISmartDnsConstructorOptions {}
|
||||||
|
|
||||||
export interface IGoogleDNSHTTPSResponse {
|
export interface IDnsJsonResponse {
|
||||||
Status: number;
|
Status: number;
|
||||||
TC: boolean;
|
TC: boolean;
|
||||||
RD: boolean;
|
RD: boolean;
|
||||||
@ -135,14 +135,14 @@ export class Smartdns {
|
|||||||
): Promise<plugins.tsclass.network.IDnsRecord[]> {
|
): Promise<plugins.tsclass.network.IDnsRecord[]> {
|
||||||
const requestUrl = `https://cloudflare-dns.com/dns-query?name=${recordNameArg}&type=${recordTypeArg}&do=1`;
|
const requestUrl = `https://cloudflare-dns.com/dns-query?name=${recordNameArg}&type=${recordTypeArg}&do=1`;
|
||||||
const returnArray: plugins.tsclass.network.IDnsRecord[] = [];
|
const returnArray: plugins.tsclass.network.IDnsRecord[] = [];
|
||||||
const getResponseBody = async (counterArg = 0): Promise<IGoogleDNSHTTPSResponse> => {
|
const getResponseBody = async (counterArg = 0): Promise<IDnsJsonResponse> => {
|
||||||
const response = await plugins.smartrequest.request(requestUrl, {
|
const response = await plugins.smartrequest.request(requestUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/dns-json',
|
accept: 'application/dns-json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const responseBody: IGoogleDNSHTTPSResponse = response.body;
|
const responseBody: IDnsJsonResponse = response.body;
|
||||||
if (responseBody?.Status !== 0 && counterArg < retriesCounterArg) {
|
if (responseBody?.Status !== 0 && counterArg < retriesCounterArg) {
|
||||||
await plugins.smartdelay.delayFor(500);
|
await plugins.smartdelay.delayFor(500);
|
||||||
return getResponseBody(counterArg++);
|
return getResponseBody(counterArg++);
|
||||||
|
@ -16,7 +16,3 @@ export { smartdelay, smartenv, smartpromise, smartrequest };
|
|||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
export { tsclass };
|
export { tsclass };
|
||||||
|
|
||||||
// third party scope
|
|
||||||
const dns2 = smartenvInstance.getSafeNodeModule('dns2');
|
|
||||||
export { dns2 };
|
|
||||||
|
@ -7,59 +7,61 @@ interface IDnsServerOptions {
|
|||||||
udpPort: number;
|
udpPort: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DnsServer {
|
interface IDnsHandler {
|
||||||
private udpServer: plugins.dgram.Socket;
|
domainPattern: string;
|
||||||
private httpsServer: plugins.https.Server;
|
recordTypes: string[];
|
||||||
|
handler: (question: plugins.dnsPacket.Question) => plugins.dnsPacket.Answer | null;
|
||||||
constructor(private options: IDnsServerOptions) {
|
|
||||||
this.udpServer = plugins.dgram.createSocket('udp4');
|
|
||||||
this.setupUdpServer();
|
|
||||||
|
|
||||||
this.httpsServer = plugins.https.createServer(
|
|
||||||
{
|
|
||||||
key: plugins.fs.readFileSync(options.httpsKey),
|
|
||||||
cert: plugins.fs.readFileSync(options.httpsCert)
|
|
||||||
},
|
|
||||||
this.handleHttpsRequest.bind(this)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupUdpServer(): void {
|
export class DnsServer {
|
||||||
this.udpServer.on('message', (msg, rinfo) => {
|
private udpServer: plugins.dgram.Socket;
|
||||||
const request = plugins.dnsPacket.decode(msg);
|
private httpsServer: plugins.https.Server;
|
||||||
const response = {
|
private handlers: IDnsHandler[] = [];
|
||||||
type: 'response' as const,
|
|
||||||
|
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,
|
id: request.id,
|
||||||
flags: plugins.dnsPacket.RECURSION_DESIRED | plugins.dnsPacket.RECURSION_AVAILABLE,
|
flags: plugins.dnsPacket.RECURSION_DESIRED | plugins.dnsPacket.RECURSION_AVAILABLE,
|
||||||
questions: request.questions,
|
questions: request.questions,
|
||||||
answers: [] as plugins.dnsPacket.Answer[]
|
answers: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const question = request.questions[0];
|
for (const question of request.questions) {
|
||||||
console.log(`UDP query for ${question.name} of type ${question.type}`);
|
console.log(`Query for ${question.name} of type ${question.type}`);
|
||||||
|
|
||||||
if (question.type === 'A') {
|
let answered = false;
|
||||||
response.answers.push({
|
|
||||||
name: question.name,
|
for (const handlerEntry of this.handlers) {
|
||||||
type: 'A',
|
if (
|
||||||
class: 'IN',
|
plugins.minimatch.minimatch(question.name, handlerEntry.domainPattern) &&
|
||||||
ttl: 300,
|
handlerEntry.recordTypes.includes(question.type)
|
||||||
data: '127.0.0.1'
|
) {
|
||||||
});
|
const answer = handlerEntry.handler(question);
|
||||||
|
if (answer) {
|
||||||
|
response.answers.push(answer);
|
||||||
|
answered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = plugins.dnsPacket.encode(response);
|
if (!answered) {
|
||||||
this.udpServer.send(responseData, rinfo.port, rinfo.address);
|
console.log(`No handler found for ${question.name} of type ${question.type}`);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.udpServer.on('error', (err) => {
|
return response;
|
||||||
console.error(`UDP Server error:\n${err.stack}`);
|
|
||||||
this.udpServer.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.udpServer.bind(this.options.udpPort, '0.0.0.0', () => {
|
|
||||||
console.log(`UDP DNS server running on port ${this.options.udpPort}`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleHttpsRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
|
private handleHttpsRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
|
||||||
@ -71,27 +73,7 @@ class DnsServer {
|
|||||||
}).on('end', () => {
|
}).on('end', () => {
|
||||||
const msg = Buffer.concat(body);
|
const msg = Buffer.concat(body);
|
||||||
const request = plugins.dnsPacket.decode(msg);
|
const request = plugins.dnsPacket.decode(msg);
|
||||||
const response = {
|
const response = this.processDnsRequest(request);
|
||||||
type: 'response' as const,
|
|
||||||
id: request.id,
|
|
||||||
flags: plugins.dnsPacket.RECURSION_DESIRED | plugins.dnsPacket.RECURSION_AVAILABLE,
|
|
||||||
questions: request.questions,
|
|
||||||
answers: [] as plugins.dnsPacket.Answer[]
|
|
||||||
};
|
|
||||||
|
|
||||||
const question = request.questions[0];
|
|
||||||
console.log(`DoH query for ${question.name} of type ${question.type}`);
|
|
||||||
|
|
||||||
if (question.type === 'A') {
|
|
||||||
response.answers.push({
|
|
||||||
name: question.name,
|
|
||||||
type: 'A',
|
|
||||||
class: 'IN',
|
|
||||||
ttl: 300,
|
|
||||||
data: '127.0.0.1'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = plugins.dnsPacket.encode(response);
|
const responseData = plugins.dnsPacket.encode(response);
|
||||||
res.writeHead(200, { 'Content-Type': 'application/dns-message' });
|
res.writeHead(200, { 'Content-Type': 'application/dns-message' });
|
||||||
res.end(responseData);
|
res.end(responseData);
|
||||||
@ -102,19 +84,64 @@ class DnsServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): void {
|
public async start(): Promise<void> {
|
||||||
this.httpsServer.listen(this.options.httpsPort, () => {
|
this.httpsServer = plugins.https.createServer(
|
||||||
console.log(`DoH server running on port ${this.options.httpsPort}`);
|
{
|
||||||
|
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<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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public async stop(): Promise<void> {
|
||||||
|
const doneUdp = plugins.smartpromise.defer<void>();
|
||||||
|
const doneHttps = plugins.smartpromise.defer<void>();
|
||||||
this.udpServer.close(() => {
|
this.udpServer.close(() => {
|
||||||
console.log('UDP DNS server stopped');
|
console.log('UDP DNS server stopped');
|
||||||
|
this.udpServer.unref();
|
||||||
|
this.udpServer = null;
|
||||||
|
doneUdp.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.httpsServer.close(() => {
|
this.httpsServer.close(() => {
|
||||||
console.log('HTTPS DNS server stopped');
|
console.log('HTTPS DNS server stopped');
|
||||||
|
this.httpsServer.unref();
|
||||||
|
this.httpsServer = null;
|
||||||
|
doneHttps.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Promise.all([doneUdp.promise, doneHttps.promise]);
|
||||||
}
|
}
|
||||||
}
|
}
|
83
ts_server/classes.dnstools.ts
Normal file
83
ts_server/classes.dnstools.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
interface DnssecZone {
|
||||||
|
zone: string;
|
||||||
|
algorithm: string;
|
||||||
|
keySize: number;
|
||||||
|
days: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DnssecKeyPair {
|
||||||
|
private: string;
|
||||||
|
public: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DnsSec {
|
||||||
|
private zone: DnssecZone;
|
||||||
|
private keyPair: DnssecKeyPair;
|
||||||
|
private ec: any; // declare the ec instance
|
||||||
|
|
||||||
|
constructor(zone: DnssecZone) {
|
||||||
|
this.zone = zone;
|
||||||
|
this.ec = new plugins.elliptic.ec('secp256k1'); // Create an instance of the secp256k1 curve
|
||||||
|
this.keyPair = this.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateKeyPair(): DnssecKeyPair {
|
||||||
|
const key = this.ec.genKeyPair();
|
||||||
|
const privatePem = key.getPrivate().toString('hex'); // get private key in hex format
|
||||||
|
// @ts-ignore
|
||||||
|
const publicPem = key.getPublic().toString('hex'); // get public key in hex format
|
||||||
|
|
||||||
|
return {
|
||||||
|
private: privatePem,
|
||||||
|
public: publicPem
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatPEM(pem: string, type: string): string {
|
||||||
|
const start = `-----BEGIN ${type}-----`;
|
||||||
|
const end = `-----END ${type}-----`;
|
||||||
|
|
||||||
|
const formatted = [start];
|
||||||
|
for (let i = 0; i < pem.length; i += 64) {
|
||||||
|
formatted.push(pem.slice(i, i + 64));
|
||||||
|
}
|
||||||
|
formatted.push(end);
|
||||||
|
return formatted.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDSRecord(): string {
|
||||||
|
const publicPem = this.keyPair.public;
|
||||||
|
const publicKey = this.ec.keyFromPublic(publicPem); // Create a public key from the publicPEM
|
||||||
|
|
||||||
|
const digest = publicKey.getPublic(); // get public point
|
||||||
|
return `DS {id} 8 {algorithm} {digest} {hash-algorithm}\n`
|
||||||
|
.replace('{id}', '256') // zone hash
|
||||||
|
.replace('{algorithm}', this.getAlgorithm())
|
||||||
|
.replace('{digest}', `0x${digest.getX()}${digest.getY()}`)
|
||||||
|
.replace('{hash-algorithm}', '2');
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAlgorithm(): string {
|
||||||
|
switch (this.zone.algorithm) {
|
||||||
|
case 'ECDSA':
|
||||||
|
return '8';
|
||||||
|
case 'ED25519':
|
||||||
|
return '15';
|
||||||
|
case 'RSA':
|
||||||
|
return '1';
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeyPair(): DnssecKeyPair {
|
||||||
|
return this.keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDsAndKeyPair(): [DnssecKeyPair, string] {
|
||||||
|
const dsRecord = this.getDSRecord();
|
||||||
|
return [this.keyPair, dsRecord];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './classes.dnsserver.js';
|
@ -1,3 +1,4 @@
|
|||||||
|
// node native
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
@ -10,8 +11,20 @@ export {
|
|||||||
dgram,
|
dgram,
|
||||||
}
|
}
|
||||||
|
|
||||||
import * as dnsPacket from 'dns-packet';
|
// @push.rocks scope
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
dnsPacket
|
smartpromise,
|
||||||
|
}
|
||||||
|
|
||||||
|
// third party
|
||||||
|
import * as elliptic from 'elliptic';
|
||||||
|
import * as dnsPacket from 'dns-packet';
|
||||||
|
import * as minimatch from 'minimatch';
|
||||||
|
|
||||||
|
export {
|
||||||
|
dnsPacket,
|
||||||
|
elliptic,
|
||||||
|
minimatch,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user