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;
}
}