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((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((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((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((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();