DNS chanllenge: Stuck in write question section stage

I’m stuck on Stage #BF2.

I read RFC documentation and code examples. The response buffer looks fine, but I always get the error “dns: bad data”. I have spent two days, but I still have no idea. Please help me out.

The buffer before sending to the client:

[0] = {unsigned char} 210 '\322'
[1] = {unsigned char} 4 '\004'
[2] = {unsigned char} 0 '\000'
[3] = {unsigned char} 128 '\200'
[4] = {unsigned char} 1 '\001'
[5] = {unsigned char} 0 '\000'
[6] = {unsigned char} 0 '\000'
[7] = {unsigned char} 0 '\000'
[8] = {unsigned char} 0 '\000'
[9] = {unsigned char} 0 '\000'
[10] = {unsigned char} 0 '\000'
[11] = {unsigned char} 0 '\000'
[12] = {unsigned char} 12 '\f'
[13] = {unsigned char} 99 'c'
[14] = {unsigned char} 111 'o'
[15] = {unsigned char} 100 'd'
[16] = {unsigned char} 101 'e'
[17] = {unsigned char} 99 'c'
[18] = {unsigned char} 114 'r'
[19] = {unsigned char} 97 'a'
[20] = {unsigned char} 102 'f'
[21] = {unsigned char} 116 't'
[22] = {unsigned char} 101 'e'
[23] = {unsigned char} 114 'r'
[24] = {unsigned char} 115 's'
[25] = {unsigned char} 2 '\002'
[26] = {unsigned char} 105 'i'
[27] = {unsigned char} 111 'o'
[28] = {unsigned char} 0 '\000'
[29] = {unsigned char} 1 '\001'
[30] = {unsigned char} 0 '\000'
[31] = {unsigned char} 1 '\001'
[32] = {unsigned char} 0 '\000'

and remain empty octet ('\000') to fill up 512 bytes

Here are my logs:

[tester::#BF2] Running tests for Stage #BF2 (Write question section)
[tester::#BF2] Starting DNS server on 127.0.0.1:2053
[tester::#BF2] Running program
[tester::#BF2] DNS resolver listening on 127.0.0.1:5354
[tester::#BF2] Connecting to 127.0.0.1:2053 using UDP
[tester::#BF2] Querying `A` record for codecrafters.io.
[tester::#BF2] Sending Request: (Messages with >>> prefix are part of this log)
[tester::#BF2] >>> ;; opcode: QUERY, status: NOERROR, id: 1234
[tester::#BF2] >>> ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
[tester::#BF2] >>> 
[tester::#BF2] >>> ;; QUESTION SECTION:
[tester::#BF2] >>> ;codecrafters.io.    IN       A
[tester::#BF2] >>> 
[tester::#BF2] dns: bad rdata
[tester::#BF2] Bad rdata error can happen because of a lot of reasons.
[tester::#BF2] If you are seeing this in an early stage that probably indicates that the packet you sent was not properly parsed.
[tester::#BF2] Test failed
[tester::#BF2] Terminating program
[tester::#BF2] Shutting down DNS resolver server...
[tester::#BF2] Program terminated successfully

And here’s a snippet of my code:

std::array<unsigned char, dns::LIMIT_NAME_LEN> domain_name{};
if (!dns::encodeDomainName("codecrafters.io", domain_name).has_value()) {
    std::cerr << "Error encode domain name: " << strerror(errno) << std::endl;
}

const dns::header_t header = {
    .id = 1234,
    .flags   = { .bits = 1 << 15 },
    .qdcount = 1,
    .ancount = 0,
    .nscount = 0,
    .arcount = 0,
};
const dns::question_t question = {
    .name  = domain_name,
    .type  = dns::record_type::A,
    .clazz = dns::record_class::IN,
};
const dns::packet packet = { .header = header, .question = question };

unsigned char buff[dns::LIMIT_UDP_LEN];
if (!buffering_dns_packet(packet, buff).has_value()) {
    std::cerr << "Error when packing DNS package: " << strerror(errno) << std::endl;
}

if (sendto(udp_socket,
   buff,
   sizeof(buff),
   0,
   reinterpret_cast<sockaddr *>(&client_address),
   sizeof(client_address)) == -1) {
    std::cerr << "Failed to send response: " << strerror(errno) << std::endl;
}

Other helper functions

bool validLenOctet(const unsigned char& len) { return (len & 0xC0) == 0; }

std::optional<size_t> encodeDomainName(const std::string& name, std::array<unsigned char, LIMIT_NAME_LEN>& out) {
    size_t write_pos = 0, read_pos = 0;
    while (read_pos < name.size()) {
        size_t end = name.find('.', read_pos);
        if (end == std::string::npos) end = name.size();
        const size_t len = end - read_pos;
        if (!validLenOctet(len)) return std::nullopt;
        out[write_pos++] = static_cast<unsigned char>(len);
        memcpy(&out[write_pos], &name[read_pos], len);
        write_pos += len;
        read_pos = end + 1;
    }
    out[write_pos++] = 0;
    return write_pos;
}

std::optional<size_t> buffering_dns_packet(const packet& p, unsigned char buff[]) {
    size_t index = 0;

    auto add_byte = [&](const uint8_t byte) -> bool {
        if (index >= LIMIT_UDP_LEN) return false;
        buff[index++] = byte;
        return true;
    };

    try {
        if (!add_byte(p.header.id & 0xff) || !add_byte(p.header.id >> 8) ||
            !add_byte(p.header.flags.bits & 0xff) || !add_byte(p.header.flags.bits >> 8) ||
            !add_byte(p.header.qdcount & 0xff) || !add_byte(p.header.qdcount >> 8) ||
            !add_byte(p.header.ancount & 0xff) || !add_byte(p.header.ancount >> 8) ||
            !add_byte(p.header.nscount & 0xff) || !add_byte(p.header.nscount >> 8) ||
            !add_byte(p.header.arcount & 0xff) || !add_byte(p.header.arcount >> 8)) {
            return std::nullopt;
        }

        for (const uint8_t c : p.question.name) {
            if (!add_byte(c)) return std::nullopt;
            if (c == 0) break;
        }

        if (!add_byte(p.question.type & 0xff) || !add_byte(p.question.type >> 8) ||
            !add_byte(p.question.clazz & 0xff) || !add_byte(p.question.clazz >> 8)) {
            return std::nullopt;
        }

        return index;
    } catch (...) {
        return std::nullopt;
    }
}

Hi @darwineee, I tried running your code against the previous stages, but it’s no longer passing the second stage.

Suggestions:

  1. Use our CLI to test against previous stages by running:
codecrafters test --previous
  1. Focus on fixing the early stages first, as later stages depend on them.

Thank you. The --previous option is helpful.

Feel free to reach out again if you are still stuck.

1 Like

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