228 lines
5.5 KiB
TypeScript
228 lines
5.5 KiB
TypeScript
|
import * as plugins from '../ts_server/plugins.js';
|
||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
import { tapNodeTools } from '@git.zone/tstest/tapbundle_node';
|
||
|
import * as dnsPacket from 'dns-packet';
|
||
|
import * as dgram from 'dgram';
|
||
|
|
||
|
import * as smartdns from '../ts_server/index.js';
|
||
|
|
||
|
let dnsServer: smartdns.DnsServer;
|
||
|
|
||
|
// Port management for tests
|
||
|
let nextHttpsPort = 8600;
|
||
|
let nextUdpPort = 8601;
|
||
|
|
||
|
function getUniqueHttpsPort() {
|
||
|
return nextHttpsPort++;
|
||
|
}
|
||
|
|
||
|
function getUniqueUdpPort() {
|
||
|
return nextUdpPort++;
|
||
|
}
|
||
|
|
||
|
// Cleanup function for servers
|
||
|
async function stopServer(server: smartdns.DnsServer | null | undefined) {
|
||
|
if (!server) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
await server.stop();
|
||
|
} catch (e) {
|
||
|
console.log('Handled error when stopping server:', e.message || e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tap.test('SOA records should be returned for non-existent domains', async () => {
|
||
|
const httpsData = await tapNodeTools.createHttpsCert();
|
||
|
const udpPort = getUniqueUdpPort();
|
||
|
|
||
|
dnsServer = new smartdns.DnsServer({
|
||
|
httpsKey: httpsData.key,
|
||
|
httpsCert: httpsData.cert,
|
||
|
httpsPort: getUniqueHttpsPort(),
|
||
|
udpPort: udpPort,
|
||
|
dnssecZone: 'example.com',
|
||
|
});
|
||
|
|
||
|
await dnsServer.start();
|
||
|
|
||
|
const client = dgram.createSocket('udp4');
|
||
|
|
||
|
const query = dnsPacket.encode({
|
||
|
type: 'query',
|
||
|
id: 1,
|
||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||
|
questions: [
|
||
|
{
|
||
|
name: 'nonexistent.example.com',
|
||
|
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, udpPort, 'localhost', (err) => {
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
client.close();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
const dnsResponse = await responsePromise;
|
||
|
|
||
|
console.log('✅ SOA response received');
|
||
|
const soaAnswers = dnsResponse.answers.filter(a => a.type === 'SOA');
|
||
|
expect(soaAnswers.length).toEqual(1);
|
||
|
|
||
|
const soaData = (soaAnswers[0] as any).data;
|
||
|
console.log('✅ SOA mname:', soaData.mname);
|
||
|
console.log('✅ SOA rname:', soaData.rname);
|
||
|
|
||
|
expect(soaData.mname).toEqual('ns1.example.com');
|
||
|
expect(soaData.rname).toEqual('hostmaster.example.com');
|
||
|
|
||
|
await stopServer(dnsServer);
|
||
|
dnsServer = null;
|
||
|
});
|
||
|
|
||
|
tap.test('Primary nameserver should be configurable', async () => {
|
||
|
const httpsData = await tapNodeTools.createHttpsCert();
|
||
|
const udpPort = getUniqueUdpPort();
|
||
|
|
||
|
dnsServer = new smartdns.DnsServer({
|
||
|
httpsKey: httpsData.key,
|
||
|
httpsCert: httpsData.cert,
|
||
|
httpsPort: getUniqueHttpsPort(),
|
||
|
udpPort: udpPort,
|
||
|
dnssecZone: 'example.com',
|
||
|
primaryNameserver: 'custom-ns.example.com',
|
||
|
});
|
||
|
|
||
|
await dnsServer.start();
|
||
|
|
||
|
const client = dgram.createSocket('udp4');
|
||
|
|
||
|
const query = dnsPacket.encode({
|
||
|
type: 'query',
|
||
|
id: 2,
|
||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||
|
questions: [
|
||
|
{
|
||
|
name: 'nonexistent.example.com',
|
||
|
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, udpPort, 'localhost', (err) => {
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
client.close();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
const dnsResponse = await responsePromise;
|
||
|
|
||
|
const soaAnswers = dnsResponse.answers.filter(a => a.type === 'SOA');
|
||
|
expect(soaAnswers.length).toEqual(1);
|
||
|
|
||
|
const soaData = (soaAnswers[0] as any).data;
|
||
|
console.log('✅ Custom primary nameserver:', soaData.mname);
|
||
|
|
||
|
expect(soaData.mname).toEqual('custom-ns.example.com');
|
||
|
|
||
|
await stopServer(dnsServer);
|
||
|
dnsServer = null;
|
||
|
});
|
||
|
|
||
|
tap.test('Default primary nameserver with FQDN', async () => {
|
||
|
const httpsData = await tapNodeTools.createHttpsCert();
|
||
|
const udpPort = getUniqueUdpPort();
|
||
|
|
||
|
dnsServer = new smartdns.DnsServer({
|
||
|
httpsKey: httpsData.key,
|
||
|
httpsCert: httpsData.cert,
|
||
|
httpsPort: getUniqueHttpsPort(),
|
||
|
udpPort: udpPort,
|
||
|
dnssecZone: 'example.com',
|
||
|
primaryNameserver: 'ns.example.com.', // FQDN with trailing dot
|
||
|
});
|
||
|
|
||
|
await dnsServer.start();
|
||
|
|
||
|
const client = dgram.createSocket('udp4');
|
||
|
|
||
|
const query = dnsPacket.encode({
|
||
|
type: 'query',
|
||
|
id: 3,
|
||
|
flags: dnsPacket.RECURSION_DESIRED,
|
||
|
questions: [
|
||
|
{
|
||
|
name: 'nonexistent.example.com',
|
||
|
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, udpPort, 'localhost', (err) => {
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
client.close();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
const dnsResponse = await responsePromise;
|
||
|
|
||
|
const soaAnswers = dnsResponse.answers.filter(a => a.type === 'SOA');
|
||
|
const soaData = (soaAnswers[0] as any).data;
|
||
|
console.log('✅ FQDN primary nameserver:', soaData.mname);
|
||
|
|
||
|
expect(soaData.mname).toEqual('ns.example.com.');
|
||
|
|
||
|
await stopServer(dnsServer);
|
||
|
dnsServer = null;
|
||
|
});
|
||
|
|
||
|
export default tap.start();
|