fix(dnsserver): Fix SOA record timeout issue by correcting RRSIG field formatting - Fixed RRSIG generation by using correct field name 'signersName' (not 'signerName') - Fixed label count calculation in RRSIG by filtering empty strings - Added SOA records to DNSSEC signing map for proper RRSIG generation - Added error logging and fallback values for RRSIG generation robustness - Updated test expectations to match corrected DNSSEC RRset signing behavior - Added comprehensive SOA test coverage including timeout, debug, and simple test scenarios
271 lines
6.9 KiB
TypeScript
271 lines
6.9 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 = 8900;
|
|
let nextUdpPort = 8901;
|
|
|
|
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 work for all scenarios', 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',
|
|
});
|
|
|
|
// Register SOA handler for the zone
|
|
dnsServer.registerHandler('example.com', ['SOA'], (question) => {
|
|
console.log('SOA handler called for:', question.name);
|
|
return {
|
|
name: question.name,
|
|
type: 'SOA',
|
|
class: 'IN',
|
|
ttl: 3600,
|
|
data: {
|
|
mname: 'ns.example.com',
|
|
rname: 'admin.example.com',
|
|
serial: 2024010101,
|
|
refresh: 3600,
|
|
retry: 600,
|
|
expire: 604800,
|
|
minimum: 86400,
|
|
},
|
|
};
|
|
});
|
|
|
|
// Register some other records
|
|
dnsServer.registerHandler('example.com', ['A'], (question) => {
|
|
return {
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '192.168.1.1',
|
|
};
|
|
});
|
|
|
|
await dnsServer.start();
|
|
|
|
const client = dgram.createSocket('udp4');
|
|
|
|
// Test 1: Direct SOA query
|
|
console.log('\n--- Test 1: Direct SOA query ---');
|
|
const soaQuery = dnsPacket.encode({
|
|
type: 'query',
|
|
id: 1,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [
|
|
{
|
|
name: 'example.com',
|
|
type: 'SOA',
|
|
class: 'IN',
|
|
},
|
|
],
|
|
});
|
|
|
|
let response = await new Promise<dnsPacket.Packet>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
client.close();
|
|
reject(new Error('Query timed out'));
|
|
}, 2000);
|
|
|
|
client.on('message', (msg) => {
|
|
clearTimeout(timeout);
|
|
const dnsResponse = dnsPacket.decode(msg);
|
|
resolve(dnsResponse);
|
|
client.removeAllListeners();
|
|
});
|
|
|
|
client.on('error', (err) => {
|
|
clearTimeout(timeout);
|
|
reject(err);
|
|
client.close();
|
|
});
|
|
|
|
client.send(soaQuery, udpPort, 'localhost');
|
|
});
|
|
|
|
console.log('Direct SOA query response:', response.answers.length, 'answers');
|
|
expect(response.answers.length).toEqual(1);
|
|
expect(response.answers[0].type).toEqual('SOA');
|
|
|
|
// Test 2: Non-existent domain query (should get SOA in authority)
|
|
console.log('\n--- Test 2: Non-existent domain query ---');
|
|
const nxQuery = dnsPacket.encode({
|
|
type: 'query',
|
|
id: 2,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [
|
|
{
|
|
name: 'nonexistent.example.com',
|
|
type: 'A',
|
|
class: 'IN',
|
|
},
|
|
],
|
|
});
|
|
|
|
response = await new Promise<dnsPacket.Packet>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
client.close();
|
|
reject(new Error('Query timed out'));
|
|
}, 2000);
|
|
|
|
client.on('message', (msg) => {
|
|
clearTimeout(timeout);
|
|
const dnsResponse = dnsPacket.decode(msg);
|
|
resolve(dnsResponse);
|
|
client.removeAllListeners();
|
|
});
|
|
|
|
client.send(nxQuery, udpPort, 'localhost');
|
|
});
|
|
|
|
console.log('Non-existent query response:', response.answers.length, 'answers');
|
|
const soaAnswers = response.answers.filter(a => a.type === 'SOA');
|
|
expect(soaAnswers.length).toEqual(1);
|
|
|
|
// Test 3: SOA with DNSSEC
|
|
console.log('\n--- Test 3: SOA query with DNSSEC ---');
|
|
const dnssecQuery = dnsPacket.encode({
|
|
type: 'query',
|
|
id: 3,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [
|
|
{
|
|
name: 'example.com',
|
|
type: 'SOA',
|
|
class: 'IN',
|
|
},
|
|
],
|
|
additionals: [
|
|
{
|
|
name: '.',
|
|
type: 'OPT',
|
|
ttl: 0,
|
|
flags: 0x8000, // DO bit
|
|
data: Buffer.alloc(0),
|
|
} as any,
|
|
],
|
|
});
|
|
|
|
response = await new Promise<dnsPacket.Packet>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
client.close();
|
|
reject(new Error('Query timed out'));
|
|
}, 2000);
|
|
|
|
client.on('message', (msg) => {
|
|
clearTimeout(timeout);
|
|
const dnsResponse = dnsPacket.decode(msg);
|
|
resolve(dnsResponse);
|
|
client.removeAllListeners();
|
|
});
|
|
|
|
client.send(dnssecQuery, udpPort, 'localhost');
|
|
});
|
|
|
|
console.log('DNSSEC SOA query response:', response.answers.length, 'answers');
|
|
console.log('Answer types:', response.answers.map(a => a.type));
|
|
expect(response.answers.length).toEqual(2); // SOA + RRSIG
|
|
expect(response.answers.some(a => a.type === 'SOA')).toEqual(true);
|
|
expect(response.answers.some(a => a.type === 'RRSIG')).toEqual(true);
|
|
|
|
client.close();
|
|
await stopServer(dnsServer);
|
|
dnsServer = null;
|
|
});
|
|
|
|
tap.test('Configurable primary nameserver works correctly', async () => {
|
|
const httpsData = await tapNodeTools.createHttpsCert();
|
|
const udpPort = getUniqueUdpPort();
|
|
|
|
dnsServer = new smartdns.DnsServer({
|
|
httpsKey: httpsData.key,
|
|
httpsCert: httpsData.cert,
|
|
httpsPort: getUniqueHttpsPort(),
|
|
udpPort: udpPort,
|
|
dnssecZone: 'test.com',
|
|
primaryNameserver: 'master.test.com',
|
|
});
|
|
|
|
await dnsServer.start();
|
|
|
|
const client = dgram.createSocket('udp4');
|
|
|
|
const query = dnsPacket.encode({
|
|
type: 'query',
|
|
id: 1,
|
|
flags: dnsPacket.RECURSION_DESIRED,
|
|
questions: [
|
|
{
|
|
name: 'nonexistent.test.com',
|
|
type: 'A',
|
|
class: 'IN',
|
|
},
|
|
],
|
|
});
|
|
|
|
const response = await new Promise<dnsPacket.Packet>((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
client.close();
|
|
reject(new Error('Query timed out'));
|
|
}, 2000);
|
|
|
|
client.on('message', (msg) => {
|
|
clearTimeout(timeout);
|
|
const dnsResponse = dnsPacket.decode(msg);
|
|
resolve(dnsResponse);
|
|
});
|
|
|
|
client.on('error', (err) => {
|
|
clearTimeout(timeout);
|
|
reject(err);
|
|
client.close();
|
|
});
|
|
|
|
client.send(query, udpPort, 'localhost');
|
|
});
|
|
|
|
const soaAnswers = response.answers.filter(a => a.type === 'SOA');
|
|
console.log('✅ Configured primary nameserver:', (soaAnswers[0] as any).data.mname);
|
|
expect((soaAnswers[0] as any).data.mname).toEqual('master.test.com');
|
|
|
|
client.close();
|
|
await stopServer(dnsServer);
|
|
dnsServer = null;
|
|
});
|
|
|
|
export default tap.start(); |