public inbox for gwml@vger.gnuweeb.org
 help / color / mirror / Atom feed
From: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
To: Ammar Faiz <ammarfaizi2@gnuweeb.org>
Cc: Ahmad Gani <reyuki@gnuweeb.org>,
	GNU/Weeb Mailing List <gwml@vger.gnuweeb.org>,
	Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
Subject: [PATCH gwproxy v13 3/8] Add DNS parser code
Date: Tue, 30 Sep 2025 15:54:23 +0700	[thread overview]
Message-ID: <20250930085428.717195-4-alviro.iskandar@gnuweeb.org> (raw)
In-Reply-To: <20250930085428.717195-1-alviro.iskandar@gnuweeb.org>

From: Ahmad Gani <reyuki@gnuweeb.org>

Introduce a DNS parser in preparation to add a new DNS resolver feature
that does not rely on getaddrinfo(). The new feature will create its
own UDP sockets to communicate with DNS servers directly.

Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
Co-authored-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
 src/gwproxy/dns_parser.c | 583 +++++++++++++++++++++++++++++++++++++++
 src/gwproxy/dns_parser.h | 192 +++++++++++++
 2 files changed, 775 insertions(+)
 create mode 100644 src/gwproxy/dns_parser.c
 create mode 100644 src/gwproxy/dns_parser.h

diff --git a/src/gwproxy/dns_parser.c b/src/gwproxy/dns_parser.c
new file mode 100644
index 0000000..02a5e96
--- /dev/null
+++ b/src/gwproxy/dns_parser.c
@@ -0,0 +1,583 @@
+#define _DEFAULT_SOURCE
+#include <endian.h>
+#include <stdbool.h>
+#include <gwproxy/dns_parser.h>
+#include <gwproxy/net.h>
+
+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 = 0;
+
+	while (1) {
+		uint8_t c = *p++;
+
+		total++;
+		if (total >= dst_len)
+			return -ENAMETOOLONG;
+
+		if (c == '.' || c == '\0') {
+			if (l < 1 || l > DOMAIN_LABEL_LIMIT)
+				return -EINVAL;
+
+			*lp = (uint8_t)l;
+			lp = sp++;
+			l = 0;
+			if (!c)
+				break;
+		} else {
+			l++;
+			*sp = c;
+			sp++;
+		}
+	}
+
+	return total;
+}
+
+static int calculate_question_len(uint8_t *in, size_t in_len)
+{
+	const uint8_t *p = in;
+	int tot_len, advance_len;
+
+	tot_len = 0;
+	while (true) {
+		if (*p == 0x0) {
+			tot_len++;
+			break;
+		}
+
+		if (tot_len >= (int)in_len)
+			return -ENOBUFS;
+
+		advance_len = *p + 1;
+		tot_len += advance_len;
+		p += advance_len;
+	}
+
+	if (tot_len > DOMAIN_NAME_LIMIT)
+		return -ENAMETOOLONG;
+
+	tot_len += 4;
+	if (tot_len >= (int)in_len)
+		return -ENOBUFS;
+
+	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 = ntohs(is_compressed);
+		is_compressed = DNS_IS_COMPRESSED(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);
+		switch (item->rr_type) {
+		case TYPE_AAAA:
+			if (rdlength != sizeof(struct in6_addr)) {
+				ret = -EINVAL;
+				free(item);
+				goto exit_free;
+			}
+			break;
+		case TYPE_A:
+			if (rdlength != sizeof(struct in_addr)) {
+				ret = -EINVAL;
+				free(item);
+				goto exit_free;
+			}
+			break;
+		case TYPE_CNAME:
+			idx += 2 + rdlength;
+			free(item);
+			continue;
+
+		default:
+			ret = -EINVAL;
+			free(item);
+			goto exit_free;
+			break;
+		}
+
+		item->rdlength = rdlength;
+		idx += 2;
+		if (idx >= in_len) {
+			ret = -EAGAIN;
+			free(item);
+			goto exit_free;
+		}
+
+		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[out->hdr.ancount] = item;
+		out->hdr.ancount++;
+	}
+
+	if (!out->hdr.ancount) {
+		free(out->rr_answ);
+		return -ENODATA;
+	}
+
+	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;
+
+	results = 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:
+	if (r && results)
+		gwdns_free_parsed_query(results);
+	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/dns_parser.h b/src/gwproxy/dns_parser.h
new file mode 100644
index 0000000..6d5769c
--- /dev/null
+++ b/src/gwproxy/dns_parser.h
@@ -0,0 +1,192 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <gwproxy/net.h>
+
+#ifndef GWP_DNS_PARSER_H
+#define GWP_DNS_PARSER_H
+
+#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)
+} gwdns_op;
+
+typedef enum {
+	TYPE_A		= 1,	// IPv4 host address
+	TYPE_CNAME	= 5,	// the canonical name for an alias
+	TYPE_AAAA	= 28,	// IPv6 host address
+} gwdns_type;
+
+typedef enum {
+	CLASS_IN	= 1,	// Internet
+} 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);
+
+#endif /* #ifndef GWP_DNS_PARSER_H */
-- 
Alviro Iskandar Setiawan


  parent reply	other threads:[~2025-09-30  8:54 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-30  8:54 [PATCH gwproxy v13 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 2/8] gwproxy: Introduce __unused macro Alviro Iskandar Setiawan
2025-09-30  8:54 ` Alviro Iskandar Setiawan [this message]
2025-09-30  8:54 ` [PATCH gwproxy v13 4/8] Add DNS resolver code Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 5/8] dns_resolver: Add DNS resolution interface APIs Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 6/8] gwproxy: Introduce --dns-server and --raw-dns Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 7/8] epoll: Intregrate the raw DNS feature to epoll Alviro Iskandar Setiawan
2025-09-30  8:54 ` [PATCH gwproxy v13 8/8] Makefile: Introduce --use-new-dns-resolver configure option Alviro Iskandar Setiawan
2025-10-02  7:05 ` [PATCH gwproxy v13 0/8] Initial work on integration of DNS parser lib in gwproxy Ammar Faizi
2025-10-02  7:20   ` Alviro Iskandar Setiawan
2025-10-02  8:07     ` Ammar Faizi
2025-10-02  8:19       ` Alviro Iskandar Setiawan
2025-10-26 21:10         ` Ammar Faizi
2025-10-29 16:33           ` Alviro Iskandar Setiawan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250930085428.717195-4-alviro.iskandar@gnuweeb.org \
    --to=alviro.iskandar@gnuweeb.org \
    --cc=ammarfaizi2@gnuweeb.org \
    --cc=gwml@vger.gnuweeb.org \
    --cc=reyuki@gnuweeb.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox