From 5cd390f7b1b872a74f7c5f9922c783ecd216338a Mon Sep 17 00:00:00 2001 From: Piper Pentagram Date: Fri, 18 Jul 2025 16:25:26 -0700 Subject: [PATCH] parse a DNS header! --- CMakeLists.txt | 1 + include/dns_packet_buffer.h | 4 +- include/dns_packets.h | 114 ++++++++++++++++++------------------ include/dns_request.h | 2 +- src/dns_packet_buffer.c | 6 +- src/dns_request.c | 56 +++++++++++++++++- src/udp_server.c | 39 ++++++++---- 7 files changed, 146 insertions(+), 76 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e5144e..23a4478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 4.0) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_BUILD_TYPE Debug) project(pupdns VERSION 0.1) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/include/dns_packet_buffer.h b/include/dns_packet_buffer.h index 4539e02..5884dad 100644 --- a/include/dns_packet_buffer.h +++ b/include/dns_packet_buffer.h @@ -5,7 +5,7 @@ #define PB_OUT_OF_BOUNDS 9999; struct DNSPacketBuffer { - char buf[PB_SIZE]; + unsigned char buf[PB_SIZE]; size_t pos; }; @@ -13,4 +13,4 @@ struct DNSPacketBuffer *new_dns_packet_buffer(); struct DNSPacketHeader *dns_header(struct DNSPacketBuffer); -char pb_read_byte(struct DNSPacketBuffer *); +unsigned char pb_read_byte(struct DNSPacketBuffer *); diff --git a/include/dns_packets.h b/include/dns_packets.h index f4a1ac8..b80b66c 100644 --- a/include/dns_packets.h +++ b/include/dns_packets.h @@ -1,6 +1,7 @@ #pragma once #include +#define DNS_HEADER_BYTES 12 struct DNSPacketHeader { /* @@ -8,14 +9,34 @@ struct DNSPacketHeader { * a random ID is assigned to query packets. * Response packets must contain the same ID as the query. */ - size_t packet_id : 16; + unsigned int packet_id : 16; + + /* + * RD (Recursion Desired) + * if set by the client, the server should + * attempt to resolve the query recursively + * + * the server may not allow recursive resolution. + * (see RA / Recursion Available) + */ + unsigned int recursion_desired : 1; /* - * QR (Query/Response) - * 0 if the packet is a query - * 1 if the packet is a response + * TC (Truncated) + * if true, this message was truncated. + * usually happens when the response payload + * is larger than the max UDP packet size + * + * if set, the query may be re-issued over TCP for the full payload. */ - size_t is_response : 1; + unsigned int is_truncated : 1; + + /* + * AA (Authoritative Answer) + * + * 1 if the responding server is authoritative for the queried domain + */ + unsigned int is_authoritative : 1; /* * OPCODE (Operation Code) @@ -27,51 +48,14 @@ struct DNSPacketHeader { * * almost always will be 0 */ - size_t opcode : 4; + unsigned int opcode : 4; /* - * AA (Authoritative Answer) - * - * 1 if the responding server is authoritative for the queried domain + * QR (Query/Response) + * 0 if the packet is a query + * 1 if the packet is a response */ - size_t is_authoritative : 1; - - /* - * TC (Truncated) - * if true, this message was truncated. - * usually happens when the response payload - * is larger than the max UDP packet size - * - * if set, the query may be re-issued over TCP for the full payload. - */ - size_t is_truncated : 1; - - /* - * RD (Recursion Desired) - * if set by the client, the server should - * attempt to resolve the query recursively - * - * the server may not allow recursive resolution. - * (see RA / Recursion Available) - */ - size_t recursion_desired : 1; - - - /* - * RA (Recursion Available) - * if set by the server, incicates that the server allows - * recursive queries. - */ - size_t recursion_available : 1; - - /* - * Z (Reserved) - * in the original spec, this is reserved for future use. - * - * in later RFCs, it is used for DNSSEC queries. - * (to be implemented) - */ - size_t z : 3; + unsigned int is_response : 1; /* * RCODE (Response Code) @@ -86,31 +70,47 @@ struct DNSPacketHeader { * 5 - Refused - the nameserver refuses to perform the requested operation. for example, it may refuse a zone transfer. * 6-15 - Reserved for future use */ - size_t response_code : 4; + unsigned int response_code : 4; + + /* + * Z (Reserved) + * in the original spec, this is reserved for future use. + * + * in later RFCs, it is used for DNSSEC queries. + * (to be implemented) + */ + unsigned int z : 3; + + /* + * RA (Recursion Available) + * if set by the server, incicates that the server allows + * recursive queries. + */ + unsigned int recursion_available : 1; /* * QDCOUNT (Question Count) * the number of entries in the question section */ - size_t question_count : 16; + unsigned int question_count : 16; /* * ANCOUNT (Answer Count) * the number of entries in the answer section */ - size_t answer_count : 16; + unsigned int answer_count : 16; /* * NSCOUNT (Nameserver Count) * the number of entries in the authority records section */ - size_t ns_count : 16; + unsigned int ns_count : 16; /* * ARCOUNT (Additional Records Count) * the number of entries in the additional records section */ - size_t ar_count : 16; + unsigned int ar_count : 16; }; @@ -125,26 +125,26 @@ struct RecordPreamble { * TYPE * the record type */ - size_t type : 16; + unsigned int type : 16; /* * CLASS * specifies the class of the data in the RDATA field. * in practice, always set to 1 */ - size_t class : 16; + unsigned int class : 16; /* * TTL (Time-To-Live) * how long a record can be cached before it should be requeried. */ - size_t ttl : 32; + unsigned int ttl : 32; /* * RDLENGTH (Record Data Length) * length of the record-type-specific data. */ - size_t rd_data_length: 16; + unsigned int rd_data_length: 16; }; /* @@ -153,5 +153,5 @@ struct RecordPreamble { */ struct ARecord { struct RecordPreamble preamble; - size_t ip : 32; + unsigned int ip : 32; }; diff --git a/include/dns_request.h b/include/dns_request.h index e5705ff..393c01b 100644 --- a/include/dns_request.h +++ b/include/dns_request.h @@ -7,4 +7,4 @@ struct DNSRequest { struct DNSPacketHeader *header; }; -struct DNSRequest *new_dns_request(struct DNSPacketBuffer *pb); +struct DNSRequest *parse_dns_query(struct DNSPacketBuffer *pb); diff --git a/src/dns_packet_buffer.c b/src/dns_packet_buffer.c index 7d7cd69..eb84f12 100644 --- a/src/dns_packet_buffer.c +++ b/src/dns_packet_buffer.c @@ -16,9 +16,9 @@ struct DNSPacketHeader *dns_header(struct DNSPacketBuffer) // read a single byte and move the cursor one byte forward -char pb_read_byte(struct DNSPacketBuffer *pb) +unsigned char pb_read_byte(struct DNSPacketBuffer *pb) { - char b = pb->buf[pb->pos]; + unsigned char b = pb->buf[pb->pos]; pb->pos++; if (pb->pos >= PB_SIZE) { @@ -32,7 +32,7 @@ char pb_read_byte(struct DNSPacketBuffer *pb) return b; } -char pb_get_byte(struct DNSPacketBuffer *pb) +unsigned char pb_get_byte(struct DNSPacketBuffer *pb) { if (pb->pos >= PB_SIZE) { printf( diff --git a/src/dns_request.c b/src/dns_request.c index b960d9d..034359e 100644 --- a/src/dns_request.c +++ b/src/dns_request.c @@ -1,14 +1,66 @@ #include +#include #include "dns_request.h" +/* + * get k'th bit from byte c + * because of how the DNS spec works, this starts counting from the LEFTMOST bit. + */ +// unsigned int get_bit(unsigned char c, unsigned int k) +// { +// unsigned int flag = 1U; // flag = 0000.....00001 +// +// flag = flag << (8U-k); // flag = 0000...010...000 (shifted k positions) +// +// return c & flag; +// } +/* + * get n bits from byte c starting at position k + * because of how the DNS spec works, this starts counting from the LEFTMOST bit. + */ +// unsigned int get_bits(unsigned char c, unsigned int n, unsigned int k) +// { +// unsigned int ret = 0; +// unsigned int flag = 1U; // flag = 0000.....00001 +// +// for (int j = 0; j < n; j++) { +// unsigned int bit_pos = 8U - k + j; +// ret = ret | get_bit(c, k+j) << bit_pos; +// } +// +// return ret; +// } -struct DNSRequest *new_dns_request(struct DNSPacketBuffer *pb) +struct DNSRequest *parse_dns_query(struct DNSPacketBuffer *pb) { struct DNSRequest *req; req = malloc(sizeof(struct DNSRequest)); - req->header = (struct DNSPacketHeader *) pb->buf; + + /* + * casting the buffer directly into the packet header struct like this + * works okay, but because of the nature of the DNS protocol, it needs + * a lot of manual tweaking. + * + * the struct fields are defined in a weird order (compared to the spec) + * because C wants to read each byte from right to left, + * but DNS fields go left to right. + * + * so for each set of fields adding up to 8 bits of data, i reversed the + * order of the fields in the struct definition so that the fields end up + * with the correct bits off the wire. + * + * DNS also has multi-bit (but less than a byte) fields such as + * opcode and response code. fortunately we can skip these for now, + * since opcode is practically always zero for our purposes, + * and response_code only makes sense in the server response. + * + * but we do need to take care of all the network-order 16-bit fields. + */ + req->header->packet_id = ntohs(req->header->packet_id); + req->header->question_count = ntohs(req->header->question_count); + return req; } diff --git a/src/udp_server.c b/src/udp_server.c index 04de262..0027214 100644 --- a/src/udp_server.c +++ b/src/udp_server.c @@ -2,10 +2,10 @@ #include "dns_packet_buffer.h" #include "dns_request.h" +#include #include #include #include -#include #include #include #include @@ -37,20 +37,37 @@ int start_server() return -1; } - socklen_t len = 0; + while (true) { + socklen_t len = 0; - int n = recvfrom(sockfd, (char *)pb->buf, sizeof(pb->buf), MSG_WAITALL, 0, &len); + int n = recvfrom(sockfd, (char *)pb->buf, sizeof(pb->buf), MSG_WAITALL, 0, &len); - printf("received %d bytes\n", n); + printf("===== NEW REQUEST =====\n"); + printf("received %d bytes\n", n); - struct DNSRequest *req = new_dns_request(pb); + struct DNSRequest *req = parse_dns_query(pb); - printf("packet_id: %x\n", req->header->packet_id); - printf("is_response: %d\n", req->header->is_response); - printf("opcode: %d\n", req->header->opcode); - printf("is_authoritative: %d\n", req->header->is_authoritative); - printf("is_truncated: %d\n", req->header->is_truncated); - printf("recursion_desired: %d\n", req->header->recursion_desired); + printf("packet_id: %x\n", req->header->packet_id); + printf("is_response: %d\n", req->header->is_response); + printf("opcode: %d\n", req->header->opcode); + printf("is_authoritative: %d\n", req->header->is_authoritative); + printf("is_truncated: %d\n", req->header->is_truncated); + printf("recursion_desired: %d\n", req->header->recursion_desired); + printf("recursion_available: %d\n", req->header->recursion_available); + printf("reserved: %d\n", req->header->z); + printf("response_code: %d\n", req->header->response_code); + printf("question_count: %d\n", req->header->question_count); + printf("answer_count: %d\n", req->header->answer_count); + printf("ns_count: %d\n", req->header->ns_count); + printf("ar_count: %d\n", req->header->ar_count); + + // for (int i=0; ibuf[i]); + // } + + free(pb); + free(req); + } close(sockfd); return 0;