feat(rustdns-client): add Rust DNS client binary and TypeScript IPC bridge to enable UDP and DoH resolution, RDATA decoding, and DNSSEC AD/rcode support
This commit is contained in:
@@ -247,4 +247,115 @@ tap.test('SOA query with DNSSEC should work', async () => {
|
||||
dnsServer = null;
|
||||
});
|
||||
|
||||
tap.test('SOA serialization produces correct wire format', async () => {
|
||||
const httpsData = await tapNodeTools.createHttpsCert();
|
||||
const udpPort = getUniqueUdpPort();
|
||||
|
||||
dnsServer = new smartdns.DnsServer({
|
||||
httpsKey: httpsData.key,
|
||||
httpsCert: httpsData.cert,
|
||||
httpsPort: getUniqueHttpsPort(),
|
||||
udpPort: udpPort,
|
||||
dnssecZone: 'roundtrip.example.com',
|
||||
});
|
||||
|
||||
// Register a handler with specific SOA data we can verify round-trips correctly
|
||||
const expectedSoa = {
|
||||
mname: 'ns1.roundtrip.example.com',
|
||||
rname: 'admin.roundtrip.example.com',
|
||||
serial: 2025020101,
|
||||
refresh: 7200,
|
||||
retry: 1800,
|
||||
expire: 1209600,
|
||||
minimum: 43200,
|
||||
};
|
||||
|
||||
dnsServer.registerHandler('roundtrip.example.com', ['SOA'], (question) => {
|
||||
return {
|
||||
name: question.name,
|
||||
type: 'SOA',
|
||||
class: 'IN',
|
||||
ttl: 3600,
|
||||
data: expectedSoa,
|
||||
};
|
||||
});
|
||||
|
||||
await dnsServer.start();
|
||||
|
||||
const client = dgram.createSocket('udp4');
|
||||
|
||||
// Plain UDP query without DNSSEC to test pure SOA serialization
|
||||
const query = dnsPacket.encode({
|
||||
type: 'query',
|
||||
id: 3,
|
||||
flags: dnsPacket.RECURSION_DESIRED,
|
||||
questions: [
|
||||
{
|
||||
name: 'roundtrip.example.com',
|
||||
type: 'SOA',
|
||||
class: 'IN',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log('Sending plain SOA query for serialization round-trip test');
|
||||
|
||||
const responsePromise = new Promise<dnsPacket.Packet>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
client.close();
|
||||
reject(new Error('Query timed out after 5 seconds'));
|
||||
}, 5000);
|
||||
|
||||
client.on('message', (msg) => {
|
||||
clearTimeout(timeout);
|
||||
try {
|
||||
const dnsResponse = dnsPacket.decode(msg);
|
||||
resolve(dnsResponse);
|
||||
} catch (e) {
|
||||
reject(new Error(`Failed to decode response: ${e.message}`));
|
||||
}
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
client.close();
|
||||
});
|
||||
|
||||
client.send(query, udpPort, 'localhost', (err) => {
|
||||
if (err) {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
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('Round-trip SOA data:', soaData);
|
||||
|
||||
// Verify all 7 SOA fields survived the full round-trip:
|
||||
// handler → Rust encode_soa → wire → dns-packet decode
|
||||
expect(soaData.mname).toEqual(expectedSoa.mname);
|
||||
expect(soaData.rname).toEqual(expectedSoa.rname);
|
||||
expect(soaData.serial).toEqual(expectedSoa.serial);
|
||||
expect(soaData.refresh).toEqual(expectedSoa.refresh);
|
||||
expect(soaData.retry).toEqual(expectedSoa.retry);
|
||||
expect(soaData.expire).toEqual(expectedSoa.expire);
|
||||
expect(soaData.minimum).toEqual(expectedSoa.minimum);
|
||||
} catch (error) {
|
||||
console.error('SOA serialization round-trip test failed:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await stopServer(dnsServer);
|
||||
dnsServer = null;
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user