Last Step of the challenge - timeout with multiple queries

Dealing with multiple queries has been very challening for me. I haven’t been able to make it work with tthe following timeout error I get on #YC9 (after #GT1 passes the test)

remote: [tester::#YC9] Running tests for Stage #YC9 (Parse compressed packet)
remote: [tester::#YC9] Starting DNS server on 127.0.0.1:2053
remote: [tester::#YC9] Running program
remote: [tester::#YC9] DNS resolver listening on 127.0.0.1:5354
remote: [tester::#YC9] Connecting to 127.0.0.1:2053 using UDP
remote: [tester::#YC9] Querying the following in the same request (Messages with >> prefix are part of this log)
remote: [tester::#YC9] >> ;abc.longassdomainname.com.	IN	 A
remote: [tester::#YC9] >> ;def.longassdomainname.com.	IN	 A
remote: [tester::#YC9] Sending Request: (Messages with >>> prefix are part of this log)
remote: [tester::#YC9] >>> ;; opcode: QUERY, status: NOERROR, id: 38476
remote: [tester::#YC9] >>> ;; flags: rd; QUERY: 2, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
remote: [tester::#YC9] >>>
remote: [tester::#YC9] >>> ;; QUESTION SECTION:
remote: [tester::#YC9] >>> ;abc.longassdomainname.com.	IN	 A
remote: [tester::#YC9] >>> ;def.longassdomainname.com.	IN	 A
remote: [tester::#YC9] >>>
remote: [tester::#YC9] DNS query failed: read udp 127.0.0.1:39437->127.0.0.1:2053: i/o timeout.
remote: [tester::#YC9] If you are seeing this after a while then it is likely that your server is not responding with appropriate id
remote: [tester::#YC9] Test failed
remote: [tester::#YC9] Terminating program
remote: [tester::#YC9] Shutting down DNS resolver server...
remote: [tester::#YC9] Program terminated successfully

This is my code. How can I improve it so I don’t get that timeout error?

import * as dgram from "dgram";
import { DNSMessageHeader } from "./dnsMessageHeader";

const args = process.argv.slice(2);
const resolverArg = args.find(arg => arg === args[1]);
const [resolverIp, resolverPortStr] = resolverArg?.split(':') ?? [];

const udpSocket = dgram.createSocket("udp4");
const forwardSocket = dgram.createSocket("udp4");

udpSocket.bind(2053, "127.0.0.1");

function parseDNSQuestion(data: Buffer, offset: number): [Buffer, number] {
    const labels = [];
    let currentOffset = offset;
    
    while (true) {
        const length = data[currentOffset];
        if (length === 0) {
            labels.push(Buffer.from([0]));
            currentOffset++;
            break;
        }
        
        if ((length & 0xc0) === 0xc0) {
            const pointer = ((length & 0x3f) << 8) | data[currentOffset + 1];
            const [compressedLabel] = parseDNSQuestion(data, pointer);
            labels.push(compressedLabel.slice(0, -5));
            currentOffset += 2;
            const typeClass = data.slice(currentOffset, currentOffset + 4);
            currentOffset += 4;
            return [Buffer.concat([...labels, Buffer.from([0]), typeClass]), currentOffset];
        }
        
        labels.push(data.slice(currentOffset, currentOffset + length + 1));
        currentOffset += length + 1;
    }
    
    const typeClass = data.slice(currentOffset, currentOffset + 4);
    currentOffset += 4;
    
    return [Buffer.concat([...labels, typeClass]), currentOffset];
}

udpSocket.on("message", (data: Buffer, remoteInfo: dgram.RemoteInfo) => {
    const queryId = data.readUInt16BE(0);
    const qdcount = data.readUInt16BE(4);

    if (resolverArg) {
        if (qdcount > 1) {
            let offset = 12;
            const responses: Buffer[] = [];
            
            for (let i = 0; i < qdcount; i++) {
                const [question, newOffset] = parseDNSQuestion(data, offset);
                
                const singleHeader = Buffer.from([
                    ...data.slice(0, 2),
                    data[2], data[3],
                    0x00, 0x01,
                    0x00, 0x00,
                    0x00, 0x00,
                    0x00, 0x00
                ]);

                const singlePacket = Buffer.concat([singleHeader, question]);
                
                forwardSocket.send(singlePacket, Number(resolverPortStr), resolverIp);
                forwardSocket.once("message", (response) => {
                    responses.push(response);
                    
                    if (responses.length === qdcount) {
                        const combinedHeader = Buffer.from([
                            ...data.slice(0, 2),
                            responses[0][2], responses[0][3],
                            0x00, qdcount,
                            0x00, qdcount,
                            0x00, 0x00,
                            0x00, 0x00
                        ]);

                        const questions = [];
                        const answers = [];
                        
                        for (let j = 0; j < responses.length; j++) {
                            const resp = responses[j];
                            const [question, qOffset] = parseDNSQuestion(data, 12 + (j * 16));
                            questions.push(question);
                            const answer = resp.slice(qOffset);
                            answers.push(answer);
                        }

                        const finalResponse = Buffer.concat([
                            combinedHeader,
                            ...questions,
                            ...answers
                        ]);

                        udpSocket.send(finalResponse, remoteInfo.port, remoteInfo.address);
                    }
                });
                
                offset = newOffset;
            }
        } else {
            forwardSocket.send(data, Number(resolverPortStr), resolverIp);
            forwardSocket.once("message", (response) => {
                udpSocket.send(response, remoteInfo.port, remoteInfo.address);
            });
        }
        return;
    }

    const header = new DNSMessageHeader();
    header.setId(queryId);
    header.setQR(1);
    header.setQDCount(qdcount);
    header.setANCount(qdcount);
    header.setRD(1);

    let offset = 12;
    const questions = [];
    const answers = [];

    for (let i = 0; i < qdcount; i++) {
        const [question, newOffset] = parseDNSQuestion(data, offset);
        questions.push(question);

        const answer = Buffer.concat([
            question.slice(0, -4),
            Buffer.from([
                0x00, 0x01,
                0x00, 0x01,
                0x00, 0x00, 0x00, 0x3c,
                0x00, 0x04,
                151, 101, 65, 140
            ])
        ]);
        
        answers.push(answer);
        offset = newOffset;
    }

    const response = Buffer.concat([header.toBuffer(), ...questions, ...answers]);
    udpSocket.send(response, remoteInfo.port, remoteInfo.address);
});

Hey @baristaGeek, there might be multiple issues at play. The first thing I noticed is that the order of questions and answers in the response doesn’t match the expected specification.

The order now looks like this:

| ------------------------------------------ |
| Header                                     |
| ------------------------------------------ |
| Question 1                                 |
| ------------------------------------------ |
| Answer 1                                   |
| ------------------------------------------ |
| Question 2                                 |
| ------------------------------------------ |
| Answer 2                                   |
| ------------------------------------------ |

Let me know if you’d like further clarification!

Thanks! That was exactly what I had wrong!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.