From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server-vie001.gnuweeb.org X-Spam-Level: X-Spam-Status: No, score=-1.2 required=5.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,URIBL_DBL_BLOCKED_OPENDNS, URIBL_ZEN_BLOCKED_OPENDNS autolearn=ham autolearn_force=no version=3.4.6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gnuweeb.org; s=new2025; t=1755147199; bh=LwNYYDFbGzUl98Obi67AQynKvpSLwN7YPIlZkJWXHcA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type:Content-Transfer-Encoding:Message-ID: Date:From:Reply-To:Subject:To:Cc:In-Reply-To:References: Resent-Date:Resent-From:Resent-To:Resent-Cc:User-Agent: Content-Type:Content-Transfer-Encoding; b=TKQXSXWAHH1p+IgwyGecN3FDeJ0mfYNhzDg4LD/K39Zg+BJdtwrGFC25StJ1i7wDo nodNeT9NE9pxIFhGsvnmR6+UColHjHVyuTSjaC0EK6MOHONf+AAdPuS/XH4hXmSCgT Ohl/Y5zWFH4ASKkUx/z6E8NkH6XduMP1U/8nYXb3RaZ/D8pIIIoZtnvzKSQeB6uV6j E6Cgt1Bx+PAU8cjYJL0FSwNzlYL/YgSfXzPPovdKAubQYWlZd54NAzXcV580bOKx0j p2MGt+SH1jcuNYd5US9yBl/da4t0ESNN9Psg2xGP7uBrBGokFXbPDsy+qNAhAs2xiW HlQKhXSenPiqQ== Received: from zero (unknown [36.68.224.235]) by server-vie001.gnuweeb.org (Postfix) with ESMTPSA id 09E7B3127F9B; Thu, 14 Aug 2025 04:53:17 +0000 (UTC) From: Ahmad Gani To: Ammar Faizi Cc: Ahmad Gani , Alviro Iskandar Setiawan , GNU/Weeb Mailing List Subject: [PATCH gwproxy v5 2/2] dnsparser: Add dns parser code Date: Thu, 14 Aug 2025 11:46:55 +0700 Message-ID: <20250814044658.252579-3-reyuki@gnuweeb.org> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250814044658.252579-1-reyuki@gnuweeb.org> References: <20250814044658.252579-1-reyuki@gnuweeb.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit List-Id: Introducing dns parser for better flexibility over the program flow, the DNS protocol implementation is limited to standard query (OPCODE_QUERY). Also, Delegate creation of transaction id to the caller, It gives the caller more flexibility in handling DNS requests. A single domain name lookup can have two responses: one for IPv4 and one for IPv6. With this change, the caller may use the same txid for both queries, making it easier to group related DNS responses. Signed-off-by: Ahmad Gani --- src/gwproxy/dnsparser.c | 568 ++++++++++++++++++++++++++++++++++++++++ src/gwproxy/dnsparser.h | 210 +++++++++++++++ 2 files changed, 778 insertions(+) create mode 100644 src/gwproxy/dnsparser.c create mode 100644 src/gwproxy/dnsparser.h diff --git a/src/gwproxy/dnsparser.c b/src/gwproxy/dnsparser.c new file mode 100644 index 000000000000..3a04f37f72aa --- /dev/null +++ b/src/gwproxy/dnsparser.c @@ -0,0 +1,568 @@ +#define _DEFAULT_SOURCE +#include +#include +#include + +static ssize_t construct_qname(uint8_t *dst, size_t dst_len, const char *qname) +{ + const uint8_t *p = (const uint8_t *)qname; + uint8_t *lp = dst; // Length position. + uint8_t *sp = lp + 1; // String position. + size_t total = 0; + uint16_t l; + + l = 0; + while (1) { + uint8_t c = *p++; + + total++; + if (total >= dst_len) + return -ENAMETOOLONG; + + if (c == '.' || c == '\0') { + if (l < 1 || l > 255) + return -EINVAL; + + *lp = (uint8_t)l; + lp = sp++; + l = 0; + if (!c) + break; + } else { + l++; + *sp = c; + sp++; + } + } + + return total; +} + +static ssize_t calculate_question_len(uint8_t *in, size_t in_len) +{ + const uint8_t *p = in; + size_t tot_len, advance_len; + + tot_len = 0; + while (true) { + if (*p == 0x0) { + tot_len++; + break; + } + + if (tot_len >= in_len) + return -ENAMETOOLONG; + + advance_len = *p + 1; + tot_len += advance_len; + p += advance_len; + } + + tot_len += 4; + + return tot_len; +} + +static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *out) +{ + gwdns_header_pkt *hdr; + uint16_t raw_flags; + size_t idx, i; + void *ptr; + int ret; + + idx = sizeof(*hdr); + if (idx >= in_len) + return -EAGAIN; + + hdr = (void *)in; + if (memcmp(&txid, &hdr->id, sizeof(txid))) + return -EINVAL; + + memcpy(&raw_flags, &hdr->flags, sizeof(raw_flags)); + raw_flags = ntohs(raw_flags); + /* QR MUST 1 = response from dns server */ + if (!DNS_QR(raw_flags)) + return -EINVAL; + + /* OPCODE MUST 0 = standard query */ + if (DNS_OPCODE(raw_flags)) + return -EINVAL; + + /* RCODE MUST 0 = No error */ + if (DNS_RCODE(raw_flags)) + return -EPROTO; + + // is it safe or recommended to alter the in buffer directly? + hdr->ancount = ntohs(hdr->ancount); + if (!hdr->ancount) + return -ENODATA; + + /* + * Check the sizes upfront. + * + * 1 bytes for variable-length + * in[idx] for the length of first name + * 1 bytes for null terminator + * 2 bytes for qtype + * 2 bytes for qclass + */ + if ((size_t)(1 + in[idx] + 1 + 2 + 2) >= in_len) + return -EINVAL; + + ret = calculate_question_len(&in[idx], in_len - idx); + if (ret <= 0) + return -EINVAL; + + idx += ret; + if (idx >= in_len) + return -EAGAIN; + + out->hdr.ancount = 0; + ptr = malloc(hdr->ancount * sizeof(uint8_t *)); + if (!ptr) + return -ENOMEM; + + out->rr_answ = ptr; + for (i = 0; i < hdr->ancount; i++) { + uint16_t is_compressed, rdlength; + gwdns_serialized_answ *item = malloc(sizeof(gwdns_serialized_answ)); + if (!item) { + ret = -ENOMEM; + goto exit_free; + } + + memcpy(&is_compressed, &in[idx], sizeof(is_compressed)); + is_compressed = DNS_IS_COMPRESSED(ntohs(is_compressed)); + assert(is_compressed); + idx += 2; // NAME + if (idx >= in_len) { + ret = -EAGAIN; + free(item); + goto exit_free; + } + + memcpy(&item->rr_type, &in[idx], 2); + item->rr_type = ntohs(item->rr_type); + idx += 2; // TYPE + if (idx >= in_len) { + ret = -EAGAIN; + free(item); + goto exit_free; + } + memcpy(&item->rr_class, &in[idx], 2); + item->rr_class = ntohs(item->rr_class); + idx += 2; // CLASS + if (idx >= in_len) { + ret = -EAGAIN; + free(item); + goto exit_free; + } + memcpy(&item->ttl, &in[idx], 4); + item->ttl = be32toh(item->ttl); + idx += 4; // TTL + if (idx >= in_len) { + ret = -EAGAIN; + free(item); + goto exit_free; + } + + memcpy(&rdlength, &in[idx], sizeof(rdlength)); + rdlength = ntohs(rdlength); + if (item->rr_type != TYPE_AAAA && item->rr_type != TYPE_A) { + ret = -EINVAL; + free(item); + goto exit_free; + } + if (item->rr_type == TYPE_AAAA && rdlength != sizeof(struct in6_addr)) { + ret = -EINVAL; + free(item); + goto exit_free; + } + if (item->rr_type == TYPE_A && rdlength != sizeof(struct in_addr)) { + ret = -EINVAL; + free(item); + goto exit_free; + } + item->rdlength = rdlength; + idx += 2; + if (idx >= in_len) { + ret = -EAGAIN; + free(item); + goto exit_free; + } + + /* + * considering if condition above, + * maybe we don't need a malloc and just allocate fixed size + * for rdata? however if this parser want to be expanded for + * other dns operation (e.g OPCODE_IQUERY, etc), rdata maybe + * contain more than sizeof in6_addr. + */ + ptr = malloc(rdlength); + if (!ptr) { + ret = -ENOMEM; + free(item); + goto exit_free; + } + + memcpy(ptr, &in[idx], rdlength); + idx += rdlength; + if (idx > in_len) { + ret = -EINVAL; + free(item); + free(ptr); + goto exit_free; + } + + item->rdata = ptr; + out->rr_answ[i] = item; + out->hdr.ancount++; + } + + return 0; +exit_free: + for (i = 0; i < out->hdr.ancount; i++) { + free(out->rr_answ[i]->rdata); + free(out->rr_answ[i]); + } + free(out->rr_answ); + return ret; +} + +static void free_serialize_answ(gwdns_answ_data *answ) +{ + size_t i; + for (i = 0; i < answ->hdr.ancount; i++) { + free(answ->rr_answ[i]->rdata); + free(answ->rr_answ[i]); + } + free(answ->rr_answ); +} + +int gwdns_parse_query(uint16_t txid, const char *service, + uint8_t *in, size_t in_len, + struct gwdns_addrinfo_node **ai) +{ + struct gwdns_addrinfo_node *results, *tail; + gwdns_answ_data raw_answ; + int r, port; + size_t i; + + port = atoi(service); + if (port < 0) + return -EINVAL; + port = htons(port); + + r = serialize_answ(txid, in, in_len, &raw_answ); + if (r) + return r; + + if (!raw_answ.hdr.ancount) + goto exit_free; + + tail = NULL; + for (i = 0; i < raw_answ.hdr.ancount; i++) { + struct gwdns_addrinfo_node *new_node; + gwdns_serialized_answ *answ; + struct sockaddr_in6 *i6; + struct sockaddr_in *i4; + + answ = raw_answ.rr_answ[i]; + new_node = malloc(sizeof(*new_node)); + if (!new_node) { + r = -ENOMEM; + goto exit_free; + } + new_node->ai_next = NULL; + + if (answ->rr_type == TYPE_AAAA) { + i6 = &new_node->ai_addr.i6; + new_node->ai_family = AF_INET6; + new_node->ai_addrlen = sizeof(i6); + i6->sin6_port = port; + i6->sin6_family = AF_INET6; + assert(sizeof(i6->sin6_addr) == answ->rdlength); + memcpy(&i6->sin6_addr, answ->rdata, answ->rdlength); + } else { + i4 = &new_node->ai_addr.i4; + new_node->ai_family = AF_INET; + new_node->ai_addrlen = sizeof(i4); + i4->sin_port = port; + i4->sin_family = AF_INET; + assert(sizeof(i4->sin_addr) == answ->rdlength); + memcpy(&i4->sin_addr, answ->rdata, answ->rdlength); + new_node->ai_ttl = answ->ttl; + } + + if (!tail) + results = new_node; + else + tail->ai_next = new_node; + tail = new_node; + } + + *ai = results; + r = 0; +exit_free: + free_serialize_answ(&raw_answ); + return r; +} + +void gwdns_free_parsed_query(struct gwdns_addrinfo_node *ai) +{ + struct gwdns_addrinfo_node *tmp, *node = ai; + while (node) { + tmp = node->ai_next; + free(node); + node = tmp; + } +} + +static ssize_t construct_question(gwdns_question_part *question) +{ + gwdns_header_pkt *hdr; + gwdns_query_pkt pkt; + uint16_t qtype, qclass; + size_t required_len; + ssize_t bw; + + switch (question->type) { + case AF_INET6: + question->type = TYPE_AAAA; + break; + case AF_INET: + question->type = TYPE_A; + break; + default: + return -EINVAL; + } + + hdr = &pkt.hdr; + /* + * the memset implicitly set opcode to OPCODE_QUERY + */ + memset(hdr, 0, sizeof(*hdr)); + /* + * no need to htons, so no ntohs for comparison in serialize_answ. + */ + hdr->id = question->txid; + DNS_SET_RD(hdr->flags, true); + hdr->flags = htons(hdr->flags); + hdr->qdcount = htons(1); + + /* + * pkt.body is interpreted as question section + * for layout and format, see RFC 1035 4.1.2. Question section format + */ + bw = construct_qname(pkt.body, sizeof(pkt.body) - 3, question->domain); + if (bw < 0) + return bw; + + pkt.body[bw++] = 0x0; + qtype = htons(question->type); + qclass = htons(CLASS_IN); + memcpy(&pkt.body[bw], &qtype, 2); + bw += 2; + memcpy(&pkt.body[bw], &qclass, 2); + bw += 2; + + required_len = sizeof(pkt.hdr) + bw; + if (question->dst_len < required_len) + return -ENOBUFS; + + memcpy(question->dst_buffer, &pkt, required_len); + + return required_len; +} + +ssize_t gwdns_build_query(uint16_t txid, const char *name, int family, uint8_t *out, size_t out_len) +{ + gwdns_question_part q; + + q.domain = name; + q.type = family; + q.txid = txid; + q.dst_buffer = out; + q.dst_len = out_len; + return construct_question(&q); +} + +#ifdef RUNTEST + +void test_parse_ipv4(void) +{ + struct gwdns_addrinfo_node *d, *node; + uint16_t txid; + + uint8_t recv_pkt[] = { + 0x23, 0xc6, 0x81, 0x80, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, + 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x8a, 0xc0, 0x0c, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x66, + 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, + 0x4a, 0x7d, 0x18, 0x64, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x8b, 0xc0, 0x0c, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x65, + 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, + 0x4a, 0x7d, 0x18, 0x71 + }; + + memcpy(&txid, recv_pkt, 2); + d = NULL; + assert(!gwdns_parse_query(txid, "80", recv_pkt, sizeof(recv_pkt), &d)); + assert(d); + node = d; + while (node) { + struct gwdns_addrinfo_node *tmp; + char buff[FULL_ADDRSTRLEN]; + + tmp = node->ai_next; + assert(node->ai_family == AF_INET); + convert_ssaddr_to_str(buff, &node->ai_addr); + printf("IPv4: %s\n", buff); + node = tmp; + } + + gwdns_free_parsed_query(d); +} + +void test_parse_ipv6(void) +{ + struct gwdns_addrinfo_node *d, *node; + uint16_t txid; + + uint8_t recv_pkt[] = { + 0x45, 0x67, + 0x81, 0x80, + 0x00, 0x01, + 0x00, 0x04, + 0x00, 0x00, + 0x00, 0x00, + + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, + 0x00, + 0x00, 0x1c, + 0x00, 0x01, + + 0xc0, 0x0c, + 0x00, 0x1c, + 0x00, 0x01, + 0x00, 0x00, 0x09, 0x06, + 0x00, 0x10, + 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, + + 0xc0, 0x0c, + 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x09, 0x06, + 0x00, 0x10, + 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, + + 0xc0, 0x0c, + 0x00, 0x1c, + 0x00, 0x01, + 0x00, 0x00, 0x09, 0x06, + 0x00, 0x10, + 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, + + 0xc0, 0x0c, + 0x00, 0x1c, + 0x00, 0x01, + 0x00, 0x00, 0x0c, 0x16, + 0x00, 0x10, + 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71 + }; + + memcpy(&txid, recv_pkt, sizeof(txid)); + + node = NULL; + assert(!gwdns_parse_query(txid, "80", recv_pkt, sizeof(recv_pkt), &d)); + assert(d); + node = d; + while (node) { + struct gwdns_addrinfo_node *tmp; + char buff[FULL_ADDRSTRLEN]; + + tmp = node->ai_next; + assert(node->ai_family == AF_INET6); + convert_ssaddr_to_str(buff, &node->ai_addr); + printf("IPv6: %s\n", buff); + node = tmp; + } + + gwdns_free_parsed_query(d); +} + +void test_build_ipv4(void) +{ + uint8_t buff[UDP_MSG_LIMIT]; + gwdns_header_pkt *hdr; + uint16_t c; + ssize_t r; + + c = 0xFFFF; + r = gwdns_build_query(c, "google.com", AF_INET, buff, sizeof(buff)); + assert(r > 0); + + hdr = (void *)buff; + assert(ntohs(hdr->qdcount) == 1); + assert(!hdr->nscount); + assert(!DNS_QR(hdr->flags)); + assert(DNS_OPCODE(hdr->flags) == OPCODE_QUERY); + c = htons(TYPE_A); + assert(!memcmp(buff + 12 + 12, &c, 2)); +} + +void test_build_ipv6(void) +{ + uint8_t buff[UDP_MSG_LIMIT]; + gwdns_header_pkt *hdr; + uint16_t c; + ssize_t r; + + c = 0xFFFF; + r = gwdns_build_query(c, "google.com", AF_INET6, buff, sizeof(buff)); + assert(r > 0); + + hdr = (void *)buff; + assert(ntohs(hdr->qdcount) == 1); + assert(!hdr->nscount); + assert(!DNS_QR(hdr->flags)); + assert(DNS_OPCODE(hdr->flags) == OPCODE_QUERY); + c = htons(TYPE_AAAA); + assert(!memcmp(buff + 12 + 12, &c, 2)); +} + +/* + * test mock data of recv in both IPv4 and IPv6 + * + * the mock data are produced by this script: + * https://gist.github.com/realyukii/d7b450b4ddc305c66a2d8cd5600f23c4 + */ +void run_all_tests(void) +{ + /* + * We can't use serialize_answ to parse multiple response at once. + * The caller MUST call serialize_answ more than one time if there's + * more than one response, because txid is passed to only verify one + * response. + */ + fprintf(stderr, "test constructing DNS standard query packet for TYPE_A!\n"); + test_build_ipv4(); + fprintf(stderr, "test constructing DNS standard query packet for TYPE_AAAA!\n"); + test_build_ipv6(); + fprintf(stderr, "test parsing DNS standard query packet for TYPE_A!\n"); + test_parse_ipv4(); + fprintf(stderr, "test parsing DNS standard query packet for TYPE_AAAA!\n"); + test_parse_ipv6(); + fprintf(stderr, "all tests passed!\n"); +} + +int main(void) +{ + run_all_tests(); + return 0; +} + +#endif diff --git a/src/gwproxy/dnsparser.h b/src/gwproxy/dnsparser.h new file mode 100644 index 000000000000..c96d3caafc48 --- /dev/null +++ b/src/gwproxy/dnsparser.h @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef __packed +#define __packed __attribute__((__packed__)) +#endif + +/* + * 4. MESSAGES + * 4.1. Format + * + * All communications inside of the domain protocol are carried in a single + * format called a message. The top-level format of a message is divided + * into 5 sections (some of which may be empty in certain cases), shown below: + * + * +---------------------+ + * | Header | + * +---------------------+ + * | Question | the question for the name server + * +---------------------+ + * | Answer | RRs answering the question + * +---------------------+ + * | Authority | RRs pointing toward an authority + * +---------------------+ + * | Additional | RRs holding additional information + * +---------------------+ + * + * These sections are defined in RFC 1035 §4.1. The Header section is always + * present and includes fields that specify which of the other sections follow, + * as well as metadata such as whether the message is a query or response, + * the opcode, etc. + */ + +/* Flag bit position in little-endian machine */ +#define DNS_QR_BIT 0xF +#define DNS_OPCODE_BIT 0xB // 4-bit field +#define DNS_AA_BIT 0xA +#define DNS_TC_BIT 0x9 +#define DNS_RD_BIT 0x8 +#define DNS_RA_BIT 0x7 +#define DNS_Z_BIT 0x4 // 3-bit field +#define DNS_RCODE_BIT 0x0 // 4-bit field +#define DNS_COMPRESSION_BIT (0x3 << 0xE) + +/* Flag extraction macros for listtle-endian machine */ +#define DNS_QR(flags) (((flags) >> DNS_QR_BIT) & 0x1) +#define DNS_OPCODE(flags) (((flags) >> DNS_OPCODE_BIT) & 0xF) +#define DNS_RCODE(flags) ((flags) & 0xF) +#define DNS_IS_COMPRESSED(mask) ((mask) & DNS_COMPRESSION_BIT) + +/* Flag construction macros for little-endian machine */ +#define DNS_SET_RD(flags, val) (flags) = ((flags) & ~(1 << DNS_RD_BIT)) | ((!!(val)) << DNS_RD_BIT) + +/* as per RFC 1035 §2.3.4. Size limits */ +#define DOMAIN_LABEL_LIMIT 63 +#define DOMAIN_NAME_LIMIT 255 +#define UDP_MSG_LIMIT 512 + +typedef enum { + OPCODE_QUERY = 0, // Standard query (QUERY) + OPCODE_IQUERY = 1, // Inverse query (IQUERY) + OPCODE_STATUS = 2, // Server status request (STATUS) + OPCODE_RESERVED_MIN = 3, // Reserved for future use (inclusive) + OPCODE_RESERVED_MAX = 15 // Reserved for future use (inclusive) +} gwdns_op; + +typedef enum { + TYPE_A = 1, // IPv4 host address + TYPE_NS = 2, // an authoritative name server + TYPE_CNAME = 5, // the canonical name for an alias + TYPE_SOA = 6, // marks the start of a zone of authority + TYPE_MB = 7, // a mailbox domain name (EXPERIMENTAL) + TYPE_MG = 8, // a mail group member (EXPERIMENTAL) + TYPE_MR = 9, // a mail rename domain name (EXPERIMENTAL) + TYPE_NULL = 10, // a null RR (EXPERIMENTAL) + TYPE_WKS = 11, // a well known service description + TYPE_PTR = 12, // a domain name pointer + TYPE_HINFO = 13, // host information + TYPE_MINFO = 14, // mailbox or mail list information + TYPE_MX = 15, // mail exchange + TYPE_TXT = 16, // text strings + TYPE_AAAA = 28, // IPv6 host address + QTYPE_AXFR = 252, // A request for a transfer of an entire zone + QTYPE_MAILB = 253, // A request for mailbox-related records (MB, MG or MR) + QTYPE_ALL = 255 // A request for all records +} gwdns_type; + +typedef enum { + CLASS_IN = 1, // Internet + CLASS_CH = 3, // CHAOS class + CLASS_HS = 4, // Hesiod + QCLASS_ANY = 255 // ANY class (matches any class) +} gwdns_class; + +typedef struct { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __packed gwdns_header_pkt; + +typedef struct { + uint8_t question[UDP_MSG_LIMIT]; + char answr[UDP_MSG_LIMIT]; +} gwdns_question_buffer; + +typedef struct { + uint8_t *dst_buffer; + uint16_t txid; + uint16_t type; + size_t dst_len; + const char *domain; +} gwdns_question_part; + +/* + * 4.1.3. Resource record format + * + * The answer, authority, and additional sections all share the same + * format: a variable number of resource records, where the number of + * records is specified in the corresponding count field in the header. + */ +typedef struct { + uint8_t *name; // DOMAIN NAME: variable‑length sequence of labels (length-byte followed by label, ending in 0), possibly compressed + uint16_t rr_type; // TYPE: two-octet code identifying the RR type (see gwdns_type) + uint16_t rr_class; // CLASS: two-octet code identifying the RR class (see gwdns_class) + uint32_t ttl; // TTL: 32-bit unsigned, time to live in seconds + uint16_t rdlength; // RDLENGTH: length in octets of RDATA + uint8_t *rdata; // RDATA: variable-length data, format depends on TYPE and CLASS +} gwdns_serialized_rr; + +typedef struct { + char qname[DOMAIN_NAME_LIMIT]; + uint16_t qtype; + uint16_t qclass; +} gwdns_serialized_question; + +typedef gwdns_serialized_rr gwdns_serialized_answ; + +typedef struct { + gwdns_header_pkt hdr; + uint8_t body[UDP_MSG_LIMIT]; +} gwdns_query_pkt; + +typedef struct { + gwdns_header_pkt hdr; + gwdns_serialized_question question; + gwdns_serialized_answ **rr_answ; +} gwdns_answ_data; + +struct gwdns_addrinfo_node { + int ai_family; + int ai_ttl; + socklen_t ai_addrlen; + struct gwp_sockaddr ai_addr; + struct gwdns_addrinfo_node *ai_next; +}; + +/* + * Build standard query for domain name lookup. + * + * The caller may need to check for potential transaction ID collisions. + * + * Possible errors are: + * - ENAMETOOLONG name is too long. + * - ENOBUFS length specified by out_len is not sufficient. + * - EINVAL malformed name or unsupported value of family. + * + * @param txid transaction id + * @param name domain name + * @param family choose request for IPv4 or IPv6 + * @param out destination buffer for constructed packet + * @param out_len available capacity of destination buffer + * @return length of bytes written into dst_buffer on success, + * or a negative integer on failure. + */ +ssize_t gwdns_build_query(uint16_t txid, const char *name, int family, uint8_t *out, size_t out_len); + +/* + * Parse name server's answer + * + * Possible errors are: + * -EAGAIN in buffer is not sufficient, no bytes are processed, need more data. + * -EINVAL the content of in buffer is not valid. + * -ENOMEM failed to allocate dynamic memory. + * -ENODATA the packet didn't contain any answers. + * -EPROTO the DNS server can't understand your question + * + * @param txid transaction id of question + * @param service port number in ascii + * @param in a pointer to buffer that need to be parsed + * @param in_len a pointer to buffer that need to be parsed + * @param ai a pointer to address info + * @return zero on success or a negative number on failure + */ +int gwdns_parse_query(uint16_t txid, const char *service, + uint8_t *in, size_t in_len, + struct gwdns_addrinfo_node **ai); +void gwdns_free_parsed_query(struct gwdns_addrinfo_node *addrinfo); -- Ahmad Gani