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 = 8400; let nextUdpPort = 8401; 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('Multiple NS records should work 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: 'example.com', }); // Register multiple NS record handlers dnsServer.registerHandler('example.com', ['NS'], (question) => { return { name: question.name, type: 'NS', class: 'IN', ttl: 3600, data: 'ns1.example.com', }; }); dnsServer.registerHandler('example.com', ['NS'], (question) => { return { name: question.name, type: 'NS', class: 'IN', ttl: 3600, data: 'ns2.example.com', }; }); await dnsServer.start(); const client = dgram.createSocket('udp4'); const query = dnsPacket.encode({ type: 'query', id: 1, flags: dnsPacket.RECURSION_DESIRED, questions: [ { name: 'example.com', type: 'NS', class: 'IN', }, ], }); const responsePromise = new Promise((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('✅ NS records returned:', dnsResponse.answers.length); console.log('✅ NS records:', dnsResponse.answers.map(a => (a as any).data)); // SUCCESS: Multiple NS records are now returned expect(dnsResponse.answers.length).toEqual(2); expect(dnsResponse.answers.map(a => (a as any).data).sort()).toEqual(['ns1.example.com', 'ns2.example.com']); await stopServer(dnsServer); dnsServer = null; }); tap.test('Multiple A records for round-robin DNS', 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', }); // Register multiple A records const ips = ['10.0.0.1', '10.0.0.2', '10.0.0.3']; for (const ip of ips) { dnsServer.registerHandler('www.example.com', ['A'], (question) => { return { name: question.name, type: 'A', class: 'IN', ttl: 300, data: ip, }; }); } await dnsServer.start(); const client = dgram.createSocket('udp4'); const query = dnsPacket.encode({ type: 'query', id: 2, flags: dnsPacket.RECURSION_DESIRED, questions: [ { name: 'www.example.com', type: 'A', class: 'IN', }, ], }); const responsePromise = new Promise((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('✅ A records returned:', dnsResponse.answers.length); console.log('✅ A records:', dnsResponse.answers.map(a => (a as any).data)); // SUCCESS: All A records for round-robin DNS expect(dnsResponse.answers.length).toEqual(3); expect(dnsResponse.answers.map(a => (a as any).data).sort()).toEqual(['10.0.0.1', '10.0.0.2', '10.0.0.3']); await stopServer(dnsServer); dnsServer = null; }); tap.test('Multiple TXT records', 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', }); // Register multiple TXT records const txtRecords = [ ['v=spf1 include:_spf.example.com ~all'], ['v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNA...'], ['google-site-verification=1234567890abcdef'] ]; for (const data of txtRecords) { dnsServer.registerHandler('example.com', ['TXT'], (question) => { return { name: question.name, type: 'TXT', class: 'IN', ttl: 3600, data: data, }; }); } await dnsServer.start(); const client = dgram.createSocket('udp4'); const query = dnsPacket.encode({ type: 'query', id: 3, flags: dnsPacket.RECURSION_DESIRED, questions: [ { name: 'example.com', type: 'TXT', class: 'IN', }, ], }); const responsePromise = new Promise((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('✅ TXT records returned:', dnsResponse.answers.length); // SUCCESS: All TXT records are returned expect(dnsResponse.answers.length).toEqual(3); const txtData = dnsResponse.answers.map(a => (a as any).data[0].toString()); expect(txtData.some(d => d.includes('spf1'))).toEqual(true); expect(txtData.some(d => d.includes('DKIM1'))).toEqual(true); expect(txtData.some(d => d.includes('google-site-verification'))).toEqual(true); await stopServer(dnsServer); dnsServer = null; }); export default tap.start();