* [PATCH gwproxy v3 4/6] dnslookup: Initial work for implementation of C-ares-like getaddrinfo function
2025-08-05 13:04 [PATCH gwproxy v3 0/6] Initial work for DNS lookup implementation Ahmad Gani
` (2 preceding siblings ...)
2025-08-05 13:04 ` [PATCH gwproxy v3 3/6] dnslookup: Allow only port string number Ahmad Gani
@ 2025-08-05 13:04 ` Ahmad Gani
2025-08-05 13:04 ` [PATCH gwproxy v3 5/6] dnsparser: Transaction id creation is delegated to caller Ahmad Gani
` (2 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Ahmad Gani @ 2025-08-05 13:04 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
Introducing glibc's getaddrinfo replacement, the DNS protocol
implementation is limited to standard query (OPCODE_QUERY) as for now,
but may extended later as necessary.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dnslookup.c | 285 ++++++++++++++++++++++++++++
src/gwproxy/dnslookup.h | 118 ++++++++++++
src/gwproxy/dnsparser.c | 408 ++++++++++++++++++++++++++++++++++++++++
src/gwproxy/dnsparser.h | 191 +++++++++++++++++++
4 files changed, 1002 insertions(+)
create mode 100644 src/gwproxy/dnslookup.c
create mode 100644 src/gwproxy/dnslookup.h
create mode 100644 src/gwproxy/dnsparser.c
create mode 100644 src/gwproxy/dnsparser.h
diff --git a/src/gwproxy/dnslookup.c b/src/gwproxy/dnslookup.c
new file mode 100644
index 000000000000..de286fb5ab14
--- /dev/null
+++ b/src/gwproxy/dnslookup.c
@@ -0,0 +1,285 @@
+/*
+ * DNS lookup: an implementation of DNS resolution for IPv4 and IPv6.
+ */
+
+#include <gwproxy/net.h>
+#include <gwproxy/dnslookup.h>
+#include <gwproxy/dnsparser.h>
+#include <gwproxy/syscall.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static int resolve_name(int sockfd, const char *name, uint16_t type, uint16_t nport,
+ struct gw_ares_addrinfo *result, struct gw_addrinfo_node **tail)
+{
+ uint8_t send_buff[UDP_MSG_LIMIT];
+ uint8_t recv_buff[UDP_MSG_LIMIT];
+ gwdns_query_pkt *query_pkt;
+ gwdns_answ_data raw_answ;
+ gwdns_question_part q;
+ ssize_t buff_len;
+ int ret;
+
+ q.domain = name;
+ q.type = type;
+ q.dst_buffer = send_buff;
+ q.dst_len = UDP_MSG_LIMIT;
+ buff_len = construct_question(&q);
+ if (buff_len < 0) {
+ /*
+ * I'm confident that 512 bytes is more than sufficient
+ * to construct single dns query packet.
+ */
+ assert(buff_len != -ENOBUFS);
+ ret = GW_ARES_EINVAL;
+ return ret;
+ }
+ query_pkt = (void *)send_buff;
+
+ ret = __sys_send(sockfd, send_buff, buff_len, MSG_NOSIGNAL);
+ if (ret < 0) {
+ ret = GW_ARES_INTERNAL_ERR;
+ return ret;
+ }
+
+ ret = __sys_recv(sockfd, recv_buff, UDP_MSG_LIMIT, MSG_NOSIGNAL);
+ if (ret < 0) {
+ ret = GW_ARES_INTERNAL_ERR;
+ return ret;
+ }
+
+ /*
+ * TODO(reyuki): even though it's unlikely,
+ * but what todo when the connection is closed?
+ * drop the request or retry?
+ */
+ assert(ret);
+
+ ret = serialize_answ(query_pkt->hdr.id, recv_buff, ret, &raw_answ);
+ if (ret) {
+ /*
+ * TODO(reyuki): the reason of failure can vary,
+ * but what to do in that case? EAGAIN could possibly indicate
+ * short recv, but this is UDP packet, retry from send?
+ * or just drop the request?
+ */
+ }
+ assert(!ret);
+
+ /* TODO(reyuki): hints->ai_family is used to filter results */
+ for (size_t i = 0; i < raw_answ.hdr.ancount; i++) {
+ gwdns_serialized_answ *answ = raw_answ.rr_answ[i];
+ struct gw_addrinfo_node *new_node = malloc(sizeof(*new_node));
+ if (!new_node) {
+ ret = GW_ARES_ENOMEM;
+ goto exit_free;
+ }
+ new_node->ai_next = NULL;
+
+ if (answ->rr_type == TYPE_AAAA) {
+ new_node->ai_family = AF_INET6;
+ new_node->ai_addrlen = sizeof(new_node->ai_addr.i6);
+ new_node->ai_addr.i6.sin6_port = nport;
+ new_node->ai_addr.i6.sin6_family = AF_INET6;
+ /*
+ * no overflow.
+ * it's guaranteed to be true by serialize_answ function
+ */
+ assert(sizeof(new_node->ai_addr.i6.sin6_addr) == answ->rdlength);
+ memcpy(&new_node->ai_addr.i6.sin6_addr, answ->rdata, answ->rdlength);
+ } else {
+ new_node->ai_family = AF_INET;
+ new_node->ai_addrlen = sizeof(new_node->ai_addr.i4);
+ new_node->ai_addr.i4.sin_port = nport;
+ new_node->ai_addr.i4.sin_family = AF_INET;
+ /*
+ * no overflow.
+ * it's guaranteed to be true by serialize_answ function
+ */
+ assert(sizeof(new_node->ai_addr.i4.sin_addr) == answ->rdlength);
+ memcpy(&new_node->ai_addr.i4.sin_addr, answ->rdata, answ->rdlength);
+ new_node->ai_ttl = answ->ttl;
+ }
+
+ if (!*tail)
+ result->nodes = new_node;
+ else
+ (*tail)->ai_next = new_node;
+ *tail = new_node;
+ }
+
+ ret = GW_ARES_SUCCESS;
+exit_free:
+ free_serialize_answ(&raw_answ);
+ return ret;
+}
+
+void gw_ares_getaddrinfo(gw_ares_channel_t *channel,
+ const char *name, const char *service,
+ const struct gw_ares_addrinfo_hints *hints,
+ gw_ares_addrinfo_callback callback, void *arg)
+{
+ struct gw_ares_addrinfo *result;
+ struct gw_addrinfo_node *tail;
+ struct gwp_sockaddr *addr;
+ socklen_t addrlen;
+ int ret, sockfd;
+ uint16_t nport;
+ uint8_t mask;
+
+ switch (hints->ai_family) {
+ case AF_UNSPEC:
+ mask = I6_BIT | I4_BIT;
+ break;
+ case AF_INET:
+ mask = I4_BIT;
+ break;
+ case AF_INET6:
+ mask = I6_BIT;
+ break;
+ default:
+ ret = GW_ARES_EINVAL;
+ goto error;
+ }
+
+ nport = (uint16_t)atoi(service);
+ if (!nport) {
+ ret = GW_ARES_EINVAL;
+ goto error;
+ }
+ nport = htons(nport);
+
+ result = malloc(sizeof(*result));
+ if (!result) {
+ ret = GW_ARES_ENOMEM;
+ goto error;
+ }
+
+ addr = &channel->servers[0];
+ sockfd = __sys_socket(addr->sa.sa_family, SOCK_DGRAM, 0);
+ if (sockfd < 0) {
+ ret = GW_ARES_INTERNAL_ERR;
+ goto error_free;
+ }
+
+ addrlen = addr->sa.sa_family == AF_INET ? sizeof(addr->i4) : sizeof(addr->i6);
+ ret = __sys_connect(sockfd, &addr->sa, addrlen);
+ if (ret < 0) {
+ ret = GW_ARES_INTERNAL_ERR;
+ goto error_close;
+ }
+
+ tail = NULL;
+ if (IS_I6(mask)) {
+ ret = resolve_name(sockfd, name, TYPE_AAAA, nport, result, &tail);
+ if (ret)
+ goto error_close;
+ }
+
+ if (IS_I4(mask)) {
+ ret = resolve_name(sockfd, name, TYPE_A, nport, result, &tail);
+ if (ret)
+ goto error_close;
+ }
+
+ callback(arg, GW_ARES_SUCCESS, result);
+ return;
+error_close:
+ __sys_close(sockfd);
+error_free:
+ free(result);
+error:
+ callback(arg, ret, result);
+}
+
+void gw_ares_freeaddrinfo(struct gw_ares_addrinfo *ai)
+{
+ struct gw_addrinfo_node *tmp, *node = ai->nodes;
+ while (node) {
+ tmp = node->ai_next;
+ free(node);
+ node = tmp;
+ }
+
+ free(ai);
+}
+
+int gw_ares_init(gw_ares_channel_t **channel, struct gw_ares_options *opts)
+{
+ gw_ares_channel_t *c;
+ int ret;
+
+ if (!opts->nr_server)
+ return -EINVAL;
+
+ c = malloc(sizeof(*c));
+ if (!c)
+ return -ENOMEM;
+
+ *channel = c;
+ c->nr_server = opts->nr_server;
+ c->servers = malloc(c->nr_server * sizeof(*c->servers));
+ if (!c->servers) {
+ free(c);
+ return -ENOMEM;
+ }
+ /*
+ * TODO(reyuki): validate flags and
+ * for now use it to control recursion desired (RD) bit?
+ */
+ c->flags = opts->flags;
+ for (int i = 0; i < c->nr_server; i++) {
+ ret = convert_str_to_ssaddr(opts->servers[i], &c->servers[i], DEFAULT_DOMAIN_PORT);
+ if (ret) {
+ free(c->servers);
+ free(c);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void gw_ares_deinit(gw_ares_channel_t *channel)
+{
+ free(channel->servers);
+ free(channel);
+}
+
+static void gw_ares_cb(void *arg, int status, struct gw_ares_addrinfo *result)
+{
+ struct gw_addrinfo_node *node;
+ char buf[FULL_ADDRSTRLEN];
+
+ (void)arg;
+
+ assert(!status);
+ node = result->nodes;
+ while (node) {
+ int r = convert_ssaddr_to_str(buf, &node->ai_addr);
+ assert(!r);
+ printf("%s: %s\n", node->ai_family == AF_INET6 ? "IPv6" : "IPv4", buf);
+ node = node->ai_next;
+ }
+ gw_ares_freeaddrinfo(result);
+}
+
+int main(void)
+{
+ gw_ares_channel_t *channel;
+ struct gw_ares_addrinfo_hints hints = {
+ .ai_family = AF_UNSPEC
+ };
+ const char *servers[] = {"1.1.1.1", "8.8.8.8"};
+ struct gw_ares_options opts = {
+ .flags = 0,
+ .nr_server = 1,
+ .servers = servers
+ };
+ int ret;
+ ret = gw_ares_init(&channel, &opts);
+ if (ret)
+ return -EXIT_FAILURE;
+ gw_ares_getaddrinfo(channel, "google.com", "80", &hints, gw_ares_cb, NULL);
+ gw_ares_deinit(channel);
+}
diff --git a/src/gwproxy/dnslookup.h b/src/gwproxy/dnslookup.h
new file mode 100644
index 000000000000..a4ed02cab5f1
--- /dev/null
+++ b/src/gwproxy/dnslookup.h
@@ -0,0 +1,118 @@
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <gwproxy/net.h>
+
+#define DEFAULT_DOMAIN_PORT 53
+#define I6_BIT (1u << 0)
+#define I4_BIT (1u << 1)
+#define IS_I6(X) (((X) & I6_BIT) != 0)
+#define IS_I4(X) (((X) & I4_BIT) != 0)
+
+enum {
+ GW_ARES_SUCCESS = 0,
+ GW_ARES_ENOMEM = 1,
+ GW_ARES_EINVAL = 2,
+
+ /*
+ * internal error can be interpreted as system call failure,
+ * however, the cause of it can be vary.
+ */
+ GW_ARES_INTERNAL_ERR = 3
+};
+
+struct gw_ares_options {
+ int flags;
+ int nr_server;
+
+ /*
+ * the string format is ip:port, the ip may be encapsulated with square
+ * brackets ([]), and must be if using IPv6.
+ */
+ const char **servers;
+};
+
+struct gw_ares_channeldata {
+ int flags;
+ int nr_server;
+ /* currently only index 0 is used, and others are ignored. */
+ struct gwp_sockaddr *servers;
+};
+
+typedef struct gw_ares_channeldata gw_ares_channel_t;
+
+struct gw_ares_addrinfo_hints {
+ int ai_family;
+};
+
+/*
+ * gw_addrinfo_node structure is similar to RFC 3493 addrinfo,
+ * but without canonname and with extra ttl field.
+ *
+ * - https://c-ares.org/docs/ares_getaddrinfo.html
+ */
+struct gw_addrinfo_node {
+ int ai_family;
+ int ai_ttl;
+ socklen_t ai_addrlen;
+ struct gwp_sockaddr ai_addr;
+ struct gw_addrinfo_node *ai_next;
+};
+
+struct gw_ares_addrinfo {
+ struct gw_addrinfo_node *nodes;
+ char *name;
+};
+
+/*
+ * result is only initialized if status == GW_ARES_SUCCESS.
+ */
+typedef void (*gw_ares_addrinfo_callback)(void *arg, int status,
+ struct gw_ares_addrinfo *result);
+
+/*
+ * Initiate a host query by name and service
+ *
+ * Description:
+ * the gw_getaddrinfo function initiate a host query
+ * by @name on the name service channel identified by @channel
+ *
+ * @param channel
+ * @param name
+ * @param service
+ * @param hints
+ * @param callback
+ * @param arg
+ */
+void gw_ares_getaddrinfo(gw_ares_channel_t *channel,
+ const char *name, const char *service,
+ const struct gw_ares_addrinfo_hints *hints,
+ gw_ares_addrinfo_callback callback, void *arg);
+
+/*
+ * Free the resources allocated by gw_ares_getaddrinfo.
+ *
+ * @param ai
+ */
+void gw_ares_freeaddrinfo(struct gw_ares_addrinfo *ai);
+
+/*
+ * Initialize name service communication channel.
+ *
+ * Description:
+ * the gw_ares_init function initialize a communication channel for name
+ * service lookups.
+ *
+ * It is recommended for an application to have at most one channel and use this
+ * for all DNS queries for the life of the application.
+ *
+ * gw_ares_init can return any of the following values when an error occured:
+ * -EINVAL invalid options
+ * -ENOMEM insufficient memory
+ *
+ * @param channel pointer to initialize
+ * @param opts controlling the behavior of the resolver
+ * @return zero on success and negative integer on error
+ */
+int gw_ares_init(gw_ares_channel_t **channel, struct gw_ares_options *opts);
+void gw_ares_deinit(gw_ares_channel_t *channel);
diff --git a/src/gwproxy/dnsparser.c b/src/gwproxy/dnsparser.c
new file mode 100644
index 000000000000..744dc61581ef
--- /dev/null
+++ b/src/gwproxy/dnsparser.c
@@ -0,0 +1,408 @@
+#define _DEFAULT_SOURCE
+#include <endian.h>
+#include <gwproxy/dnsparser.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;
+
+ 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;
+}
+
+int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *out)
+{
+ size_t idx;
+ gwdns_header_pkt *hdr;
+ uint16_t raw_flags;
+ 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;
+ out->rr_answ = malloc(hdr->ancount * sizeof(uint8_t *));
+ if (!out->rr_answ)
+ return -ENOMEM;
+
+ for (size_t 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;
+ }
+
+ out->rr_answ[i] = item;
+
+ 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;
+ 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;
+ 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;
+ goto exit_free;
+ }
+ memcpy(&item->ttl, &in[idx], 4);
+ item->ttl = be32toh(item->ttl);
+ idx += 4; // TTL
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ 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;
+ 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.
+ */
+ item->rdata = malloc(rdlength);
+ if (!item->rdata) {
+ ret = -ENOMEM;
+ free(item);
+ goto exit_free;
+ }
+ memcpy(item->rdata, &in[idx], rdlength);
+ idx += rdlength;
+ if (idx > in_len) {
+ ret = -EINVAL;
+ goto exit_free;
+ }
+ out->hdr.ancount++;
+ }
+
+ return 0;
+exit_free:
+ for (size_t i = 0; i < out->hdr.ancount; i++) {
+ free(out->rr_answ[i]->rdata);
+ free(out->rr_answ[i]);
+ }
+ free(out->rr_answ);
+ return ret;
+}
+
+void free_serialize_answ(gwdns_answ_data *answ)
+{
+ for (size_t i = 0; i < answ->hdr.ancount; i++) {
+ free(answ->rr_answ[i]->rdata);
+ free(answ->rr_answ[i]);
+ }
+ free(answ->rr_answ);
+}
+
+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;
+
+ if (question->type != TYPE_AAAA && question->type != TYPE_A)
+ return -EINVAL;
+
+ hdr = &pkt.hdr;
+ /*
+ * the memset implicitly set opcode to OPCODE_QUERY
+ */
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->id = htons((uint16_t)rand());
+ 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;
+}
+
+#ifdef RUNTEST
+
+void test_parse_ipv4(void)
+{
+ gwdns_answ_data d;
+ 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);
+ assert(!serialize_answ(txid, recv_pkt, sizeof(recv_pkt), &d));
+ for (size_t i = 0; i < d.hdr.ancount; i++) {
+ struct gwp_sockaddr gs;
+ gwdns_serialized_answ *answ;
+ char buff[FULL_ADDRSTRLEN];
+
+ memset(&gs, 0, sizeof(gs));
+ answ = d.rr_answ[i];
+ assert(answ->rdlength == 4);
+ gs.sa.sa_family = AF_INET;
+ memcpy(&gs.i4.sin_addr, answ->rdata, 4);
+ convert_ssaddr_to_str(buff, &gs);
+ printf("IPv4: %s\n", buff);
+ }
+ free_serialize_answ(&d);
+}
+
+void test_parse_ipv6(void)
+{
+ gwdns_answ_data d;
+ uint16_t txid;
+ int ret;
+
+ 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));
+
+ ret = serialize_answ(txid, recv_pkt, sizeof(recv_pkt), &d);
+ assert(!ret);
+ for (size_t i = 0; i < d.hdr.ancount; i++) {
+ struct gwp_sockaddr gs;
+ gwdns_serialized_answ *answ;
+ char buff[FULL_ADDRSTRLEN];
+
+ memset(&gs, 0, sizeof(gs));
+ answ = d.rr_answ[i];
+ assert(answ->rdlength == 16);
+ gs.sa.sa_family = AF_INET6;
+ memcpy(&gs.i6.sin6_addr, answ->rdata, 16);
+ convert_ssaddr_to_str(buff, &gs);
+ printf("IPv6: %s\n", buff);
+ }
+ free_serialize_answ(&d);
+}
+
+/*
+ * 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.
+ */
+ test_parse_ipv4();
+ 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..41048568240b
--- /dev/null
+++ b/src/gwproxy/dnsparser.h
@@ -0,0 +1,191 @@
+#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 <liburing.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)
+ 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 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;
+
+/*
+ * Construct question packet
+ *
+ * The caller may need to check for potential transaction ID collisions.
+ *
+ * possible error are:
+ * - ENAMETOOLONG domain name in question.name is too long.
+ * - ENOBUFS length in question.dst_len is not sufficient.
+ * - EINVAL malformed or unsupported value in question data
+ *
+ * @param prepared question
+ * @return length of bytes written into dst_buffer on success,
+ * or a negative integer on failure.
+ */
+ssize_t construct_question(gwdns_question_part *question);
+
+/*
+ * Serialize name server's answer
+ *
+ * possible error 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 in a pointer to buffer that want to be parsed
+ * @param out a pointer to serialized buffer of answer to question
+ * @return zero on success or a negative number on failure
+ */
+int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *out);
+void free_serialize_answ(gwdns_answ_data *answ);
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH gwproxy v3 6/6] dnslookup: Make gw_ares_getaddrinfo asynchronous
2025-08-05 13:04 [PATCH gwproxy v3 0/6] Initial work for DNS lookup implementation Ahmad Gani
` (4 preceding siblings ...)
2025-08-05 13:04 ` [PATCH gwproxy v3 5/6] dnsparser: Transaction id creation is delegated to caller Ahmad Gani
@ 2025-08-05 13:04 ` Ahmad Gani
2025-08-06 3:31 ` [PATCH gwproxy v3 0/6] Initial work for DNS lookup implementation Ammar Faizi
6 siblings, 0 replies; 14+ messages in thread
From: Ahmad Gani @ 2025-08-05 13:04 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
The previous gw_ares_getaddrinfo version is blocking and no different
with glibc's getaddrinfo, this changes introduce asynchronous version of
gw_ares_getaddrinfo function.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dnslookup.c | 367 ++++++++++++++++++++++++++++------------
src/gwproxy/dnslookup.h | 99 ++++++++---
src/gwproxy/dnsparser.c | 14 +-
3 files changed, 337 insertions(+), 143 deletions(-)
diff --git a/src/gwproxy/dnslookup.c b/src/gwproxy/dnslookup.c
index de286fb5ab14..91d38b4e64d2 100644
--- a/src/gwproxy/dnslookup.c
+++ b/src/gwproxy/dnslookup.c
@@ -6,48 +6,45 @@
#include <gwproxy/dnslookup.h>
#include <gwproxy/dnsparser.h>
#include <gwproxy/syscall.h>
-#include <sys/syscall.h>
+#include <sys/epoll.h>
#include <unistd.h>
-static int resolve_name(int sockfd, const char *name, uint16_t type, uint16_t nport,
- struct gw_ares_addrinfo *result, struct gw_addrinfo_node **tail)
+int gw_ares_process_event(gw_ares_channel_t *channel)
{
- uint8_t send_buff[UDP_MSG_LIMIT];
uint8_t recv_buff[UDP_MSG_LIMIT];
- gwdns_query_pkt *query_pkt;
+ struct gw_ares_request *req;
gwdns_answ_data raw_answ;
- gwdns_question_part q;
- ssize_t buff_len;
+ socklen_t addrlen;
int ret;
- q.domain = name;
- q.type = type;
- q.dst_buffer = send_buff;
- q.dst_len = UDP_MSG_LIMIT;
- buff_len = construct_question(&q);
- if (buff_len < 0) {
- /*
- * I'm confident that 512 bytes is more than sufficient
- * to construct single dns query packet.
- */
- assert(buff_len != -ENOBUFS);
- ret = GW_ARES_EINVAL;
- return ret;
- }
- query_pkt = (void *)send_buff;
-
- ret = __sys_send(sockfd, send_buff, buff_len, MSG_NOSIGNAL);
+ addrlen = sizeof(*channel->addr);
+ ret = __sys_recvfrom(channel->sockfd, recv_buff, UDP_MSG_LIMIT,
+ MSG_NOSIGNAL, &channel->addr->sa, &addrlen);
if (ret < 0) {
- ret = GW_ARES_INTERNAL_ERR;
+ /* GW_ARES_WAITING is unused for now. */
+ if (ret == -EAGAIN)
+ ret = GW_ARES_WAITING;
+ else
+ ret = GW_ARES_INTERNAL_ERR;
return ret;
}
- ret = __sys_recv(sockfd, recv_buff, UDP_MSG_LIMIT, MSG_NOSIGNAL);
- if (ret < 0) {
- ret = GW_ARES_INTERNAL_ERR;
- return ret;
+ if (ret < 2)
+ return GW_ARES_EINVAL;
+
+ printf("%d bytes received\n", ret);
+ req = NULL;
+ int idx;
+ for (idx = 0; idx < channel->req_nr; idx++) {
+ if (!memcmp(&channel->reqs[idx]->txid, recv_buff, 2)) {
+ req = channel->reqs[idx];
+ break;
+ }
}
+ if (!req)
+ return GW_ARES_NOENT;
+
/*
* TODO(reyuki): even though it's unlikely,
* but what todo when the connection is closed?
@@ -55,7 +52,8 @@ static int resolve_name(int sockfd, const char *name, uint16_t type, uint16_t np
*/
assert(ret);
- ret = serialize_answ(query_pkt->hdr.id, recv_buff, ret, &raw_answ);
+ memset(&raw_answ, 0, sizeof(raw_answ));
+ ret = serialize_answ(req->txid, recv_buff, ret, &raw_answ);
if (ret) {
/*
* TODO(reyuki): the reason of failure can vary,
@@ -63,13 +61,20 @@ static int resolve_name(int sockfd, const char *name, uint16_t type, uint16_t np
* short recv, but this is UDP packet, retry from send?
* or just drop the request?
*/
+ assert(ret == -ENODATA);
}
- assert(!ret);
- /* TODO(reyuki): hints->ai_family is used to filter results */
- for (size_t i = 0; i < raw_answ.hdr.ancount; i++) {
- gwdns_serialized_answ *answ = raw_answ.rr_answ[i];
- struct gw_addrinfo_node *new_node = malloc(sizeof(*new_node));
+ printf("ancount=%u\n", raw_answ.hdr.ancount);
+
+ size_t i;
+ for (i = 0; i < raw_answ.hdr.ancount; i++) {
+ struct gw_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) {
ret = GW_ARES_ENOMEM;
goto exit_free;
@@ -77,119 +82,193 @@ static int resolve_name(int sockfd, const char *name, uint16_t type, uint16_t np
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(new_node->ai_addr.i6);
- new_node->ai_addr.i6.sin6_port = nport;
- new_node->ai_addr.i6.sin6_family = AF_INET6;
+ new_node->ai_addrlen = sizeof(i6);
+ i6->sin6_port = req->dst_port;
+ i6->sin6_family = AF_INET6;
/*
* no overflow.
* it's guaranteed to be true by serialize_answ function
*/
- assert(sizeof(new_node->ai_addr.i6.sin6_addr) == answ->rdlength);
- memcpy(&new_node->ai_addr.i6.sin6_addr, answ->rdata, answ->rdlength);
+ 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(new_node->ai_addr.i4);
- new_node->ai_addr.i4.sin_port = nport;
- new_node->ai_addr.i4.sin_family = AF_INET;
+ new_node->ai_addrlen = sizeof(i4);
+ i4->sin_port = req->dst_port;
+ i4->sin_family = AF_INET;
/*
* no overflow.
* it's guaranteed to be true by serialize_answ function
*/
- assert(sizeof(new_node->ai_addr.i4.sin_addr) == answ->rdlength);
- memcpy(&new_node->ai_addr.i4.sin_addr, answ->rdata, answ->rdlength);
+ assert(sizeof(i4->sin_addr) == answ->rdlength);
+ memcpy(&i4->sin_addr, answ->rdata, answ->rdlength);
new_node->ai_ttl = answ->ttl;
}
- if (!*tail)
- result->nodes = new_node;
+ if (!req->tail)
+ req->results->nodes = new_node;
else
- (*tail)->ai_next = new_node;
- *tail = new_node;
+ req->tail->ai_next = new_node;
+ req->tail = new_node;
}
ret = GW_ARES_SUCCESS;
exit_free:
free_serialize_answ(&raw_answ);
+ req->refcnt--;
+ assert(req->refcnt >= 0);
+ if (!req->refcnt) {
+ channel->getaddrinfo_cb(
+ req->callback_args, GW_ARES_SUCCESS, req->results
+ );
+ free(req);
+ channel->req_nr--;
+ req = channel->reqs[channel->req_nr];
+ channel->reqs[idx] = req;
+ channel->reqs[channel->req_nr] = NULL;
+ }
return ret;
}
+static int resolve_name(gw_ares_channel_t *channel, const char *name,
+ uint16_t type, uint16_t txid)
+{
+ uint8_t send_buff[UDP_MSG_LIMIT];
+ gwdns_question_part q;
+ ssize_t buff_len;
+ int ret;
+
+ q.domain = name;
+ q.type = type;
+ q.txid = txid;
+ q.dst_buffer = send_buff;
+ q.dst_len = UDP_MSG_LIMIT;
+ buff_len = construct_question(&q);
+ if (buff_len < 0) {
+ /*
+ * I'm confident that 512 bytes is more than sufficient
+ * to construct single dns query packet.
+ */
+ assert(buff_len != -ENOBUFS);
+ ret = GW_ARES_EINVAL;
+ return ret;
+ }
+
+ ret = __sys_sendto(
+ channel->sockfd, send_buff, buff_len, MSG_NOSIGNAL,
+ &channel->addr->sa, channel->addrlen
+ );
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int resize_reqs(gw_ares_channel_t *channel)
+{
+ struct gw_ares_request **ptr;
+ int new_cap;
+
+ new_cap = channel->req_cap * 2;
+ ptr = realloc(channel->reqs, new_cap);
+ if (!ptr)
+ return -ENOMEM;
+
+ memset(ptr[channel->req_nr], 0, channel->req_nr * sizeof(*channel->reqs));
+ channel->reqs = ptr;
+ channel->req_cap = new_cap;
+
+ return 0;
+}
+
void gw_ares_getaddrinfo(gw_ares_channel_t *channel,
const char *name, const char *service,
- const struct gw_ares_addrinfo_hints *hints,
- gw_ares_addrinfo_callback callback, void *arg)
+ const struct gw_ares_addrinfo_hints *hints, void *arg)
{
- struct gw_ares_addrinfo *result;
- struct gw_addrinfo_node *tail;
- struct gwp_sockaddr *addr;
- socklen_t addrlen;
- int ret, sockfd;
+ struct gw_ares_addrinfo *results;
+ struct gw_ares_request *req;
uint16_t nport;
uint8_t mask;
+ int ret;
+
+ if (channel->req_nr == channel->req_cap) {
+ ret = resize_reqs(channel);
+ if (ret) {
+ ret = GW_ARES_ENOMEM;
+ goto error;
+ }
+ }
+ assert(channel->req_nr < channel->req_cap);
+
+ req = malloc(sizeof(*req));
+ if (!req) {
+ ret = GW_ARES_ENOMEM;
+ goto error;
+ }
+ channel->reqs[channel->req_nr] = req;
+ channel->req_nr++;
+
+ req->tail = NULL;
+ req->txid = (uint16_t)rand();
switch (hints->ai_family) {
case AF_UNSPEC:
mask = I6_BIT | I4_BIT;
+ req->refcnt = 2;
break;
case AF_INET:
+ req->refcnt = 1;
mask = I4_BIT;
break;
case AF_INET6:
+ req->refcnt = 1;
mask = I6_BIT;
break;
default:
ret = GW_ARES_EINVAL;
- goto error;
+ goto error_free_req;
}
nport = (uint16_t)atoi(service);
if (!nport) {
ret = GW_ARES_EINVAL;
- goto error;
+ goto error_free_req;
}
nport = htons(nport);
- result = malloc(sizeof(*result));
- if (!result) {
+ results = malloc(sizeof(*results));
+ if (!results) {
ret = GW_ARES_ENOMEM;
- goto error;
- }
-
- addr = &channel->servers[0];
- sockfd = __sys_socket(addr->sa.sa_family, SOCK_DGRAM, 0);
- if (sockfd < 0) {
- ret = GW_ARES_INTERNAL_ERR;
- goto error_free;
+ goto error_free_req;
}
- addrlen = addr->sa.sa_family == AF_INET ? sizeof(addr->i4) : sizeof(addr->i6);
- ret = __sys_connect(sockfd, &addr->sa, addrlen);
- if (ret < 0) {
- ret = GW_ARES_INTERNAL_ERR;
- goto error_close;
- }
+ req->dst_port = nport;
+ req->results = results;
- tail = NULL;
if (IS_I6(mask)) {
- ret = resolve_name(sockfd, name, TYPE_AAAA, nport, result, &tail);
+ ret = resolve_name(channel, name, TYPE_AAAA, req->txid);
if (ret)
- goto error_close;
+ goto error_free_all;
}
if (IS_I4(mask)) {
- ret = resolve_name(sockfd, name, TYPE_A, nport, result, &tail);
+ ret = resolve_name(channel, name, TYPE_A, req->txid);
if (ret)
- goto error_close;
+ goto error_free_all;
}
- callback(arg, GW_ARES_SUCCESS, result);
return;
-error_close:
- __sys_close(sockfd);
-error_free:
- free(result);
+error_free_all:
+ free(results);
+error_free_req:
+ free(req);
+ channel->req_nr--;
+ channel->reqs[channel->req_nr] = NULL;
error:
- callback(arg, ret, result);
+ channel->getaddrinfo_cb(arg, ret, results);
}
void gw_ares_freeaddrinfo(struct gw_ares_addrinfo *ai)
@@ -218,68 +297,136 @@ int gw_ares_init(gw_ares_channel_t **channel, struct gw_ares_options *opts)
*channel = c;
c->nr_server = opts->nr_server;
+
+ ret = -ENOMEM;
+
+ c->reqs = calloc(DEFAULT_REQ_CAP, sizeof(*c->reqs));
+ if (!c->reqs)
+ goto error_free_c;
+
c->servers = malloc(c->nr_server * sizeof(*c->servers));
- if (!c->servers) {
- free(c);
- return -ENOMEM;
- }
+ if (!c->servers)
+ goto error_free_reqs;
+
/*
- * TODO(reyuki): validate flags and
- * for now use it to control recursion desired (RD) bit?
+ * TODO(reyuki): validate flags
+ * For now flags is unused but it may be used to control
+ * recursion desired (RD) bit and other things in the future.
*/
c->flags = opts->flags;
- for (int i = 0; i < c->nr_server; i++) {
- ret = convert_str_to_ssaddr(opts->servers[i], &c->servers[i], DEFAULT_DOMAIN_PORT);
- if (ret) {
- free(c->servers);
- free(c);
- return ret;
- }
+ int i;
+ for (i = 0; i < c->nr_server; i++) {
+ ret = convert_str_to_ssaddr(
+ opts->servers[i], &c->servers[i], DEFAULT_DOMAIN_PORT
+ );
+ if (ret)
+ goto error_free_all;
}
+ ret = __sys_socket(c->servers[0].sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ if (ret < 0)
+ goto error_free_all;
+
+ opts->sockstate_cb(opts->sockstate_data, ret);
+ c->req_cap = DEFAULT_REQ_CAP;
+ c->req_nr = 0;
+ c->getaddrinfo_cb = opts->getaddrinfo_cb;
+ c->sockfd = ret;
+ c->addr = &c->servers[0];
+ c->addrlen = c->addr->sa.sa_family == AF_INET ?
+ sizeof(c->addr->i4) : sizeof(c->addr->i6);
+
return 0;
+error_free_all:
+ free(c->servers);
+error_free_reqs:
+ free(c->reqs);
+error_free_c:
+ free(c);
+ return ret;
}
void gw_ares_deinit(gw_ares_channel_t *channel)
{
free(channel->servers);
+ free(channel->reqs);
free(channel);
}
-static void gw_ares_cb(void *arg, int status, struct gw_ares_addrinfo *result)
+static void sockstate_cb(void *data, int socket)
+{
+ int epfd = *(int *)data;
+ struct epoll_event ev = {
+ .events = EPOLLIN
+ };
+
+ __sys_epoll_ctl(epfd, EPOLL_CTL_ADD, socket, &ev);
+}
+
+static void getaddrinfo_cb(void *arg, int status, struct gw_ares_addrinfo *result)
{
struct gw_addrinfo_node *node;
char buf[FULL_ADDRSTRLEN];
(void)arg;
+ if (status)
+ printf("status=%d\n", status);
assert(!status);
node = result->nodes;
while (node) {
int r = convert_ssaddr_to_str(buf, &node->ai_addr);
assert(!r);
- printf("%s: %s\n", node->ai_family == AF_INET6 ? "IPv6" : "IPv4", buf);
+ printf(
+ "%s: %s\n",
+ node->ai_family == AF_INET6 ? "IPv6" : "IPv4", buf
+ );
node = node->ai_next;
}
+
gw_ares_freeaddrinfo(result);
}
int main(void)
{
+ struct gw_ares_addrinfo_hints hints;
+ struct gw_ares_options opts;
gw_ares_channel_t *channel;
- struct gw_ares_addrinfo_hints hints = {
- .ai_family = AF_UNSPEC
- };
+ struct epoll_event ev;
+ int ret, epfd;
+
const char *servers[] = {"1.1.1.1", "8.8.8.8"};
- struct gw_ares_options opts = {
- .flags = 0,
- .nr_server = 1,
- .servers = servers
- };
- int ret;
+
+ epfd = __sys_epoll_create1(0);
+ if (epfd < 0)
+ return -EXIT_FAILURE;
+
+ opts.flags = 0;
+ opts.nr_server = 1;
+ opts.servers = servers;
+ opts.sockstate_data = &epfd;
+ opts.sockstate_cb = sockstate_cb;
+ opts.getaddrinfo_cb = getaddrinfo_cb;
ret = gw_ares_init(&channel, &opts);
if (ret)
return -EXIT_FAILURE;
- gw_ares_getaddrinfo(channel, "google.com", "80", &hints, gw_ares_cb, NULL);
+
+ hints.ai_family = AF_UNSPEC;
+ gw_ares_getaddrinfo(channel, "google.com", "80", &hints, NULL);
+ gw_ares_getaddrinfo(channel, "facebook.com", "80", &hints, NULL);
+ gw_ares_getaddrinfo(channel, "github.com", "80", &hints, NULL);
+
+ while (true) {
+ if (!channel->req_nr)
+ break;
+
+ ret = __sys_epoll_wait(epfd, &ev, 1024, -1);
+ if (ret < 0)
+ return -EXIT_FAILURE;
+
+ if (ev.events & EPOLLIN)
+ gw_ares_process_event(channel);
+ }
+
gw_ares_deinit(channel);
}
diff --git a/src/gwproxy/dnslookup.h b/src/gwproxy/dnslookup.h
index a4ed02cab5f1..a98ed6cff23c 100644
--- a/src/gwproxy/dnslookup.h
+++ b/src/gwproxy/dnslookup.h
@@ -3,6 +3,7 @@
#include <errno.h>
#include <gwproxy/net.h>
+#define DEFAULT_REQ_CAP 50
#define DEFAULT_DOMAIN_PORT 53
#define I6_BIT (1u << 0)
#define I4_BIT (1u << 1)
@@ -18,31 +19,35 @@ enum {
* internal error can be interpreted as system call failure,
* however, the cause of it can be vary.
*/
- GW_ARES_INTERNAL_ERR = 3
+ GW_ARES_INTERNAL_ERR = 3,
+ GW_ARES_WAITING = 4,
+ GW_ARES_NOENT = 5
};
-struct gw_ares_options {
- int flags;
- int nr_server;
+typedef void (*gw_ares_sockstate_cb)(void *data, int socket);
- /*
- * the string format is ip:port, the ip may be encapsulated with square
- * brackets ([]), and must be if using IPv6.
- */
- const char **servers;
+struct gw_ares_addrinfo {
+ struct gw_addrinfo_node *nodes;
+ char *name;
};
-struct gw_ares_channeldata {
+/*
+ * result is only initialized if status == GW_ARES_SUCCESS.
+ */
+typedef void (*gw_ares_addrinfo_cb)(void *arg, int status,
+ struct gw_ares_addrinfo *result);
+
+struct gw_ares_options {
int flags;
int nr_server;
- /* currently only index 0 is used, and others are ignored. */
- struct gwp_sockaddr *servers;
-};
-
-typedef struct gw_ares_channeldata gw_ares_channel_t;
-
-struct gw_ares_addrinfo_hints {
- int ai_family;
+ /*
+ * the string format is ip:port, the ip may be encapsulated with square
+ * brackets ([]), and must be if using IPv6.
+ */
+ const char **servers;
+ gw_ares_sockstate_cb sockstate_cb;
+ gw_ares_addrinfo_cb getaddrinfo_cb;
+ void *sockstate_data;
};
/*
@@ -59,16 +64,36 @@ struct gw_addrinfo_node {
struct gw_addrinfo_node *ai_next;
};
-struct gw_ares_addrinfo {
- struct gw_addrinfo_node *nodes;
- char *name;
+struct gw_ares_request {
+ int refcnt;
+ uint16_t dst_port;
+ uint16_t txid;
+ struct gw_ares_addrinfo *results;
+ struct gw_addrinfo_node *tail;
+ void *callback_args;
};
-/*
- * result is only initialized if status == GW_ARES_SUCCESS.
- */
-typedef void (*gw_ares_addrinfo_callback)(void *arg, int status,
- struct gw_ares_addrinfo *result);
+struct gw_ares_channeldata {
+ int flags;
+ int nr_server;
+ /* currently only index 0 is used, and others are ignored. */
+ struct gwp_sockaddr *servers;
+ gw_ares_addrinfo_cb getaddrinfo_cb;
+
+ int sockfd;
+ struct gwp_sockaddr *addr;
+ socklen_t addrlen;
+
+ struct gw_ares_request **reqs;
+ int req_nr;
+ int req_cap;
+};
+
+typedef struct gw_ares_channeldata gw_ares_channel_t;
+
+struct gw_ares_addrinfo_hints {
+ int ai_family;
+};
/*
* Initiate a host query by name and service
@@ -86,8 +111,7 @@ typedef void (*gw_ares_addrinfo_callback)(void *arg, int status,
*/
void gw_ares_getaddrinfo(gw_ares_channel_t *channel,
const char *name, const char *service,
- const struct gw_ares_addrinfo_hints *hints,
- gw_ares_addrinfo_callback callback, void *arg);
+ const struct gw_ares_addrinfo_hints *hints, void *arg);
/*
* Free the resources allocated by gw_ares_getaddrinfo.
@@ -115,4 +139,23 @@ void gw_ares_freeaddrinfo(struct gw_ares_addrinfo *ai);
* @return zero on success and negative integer on error
*/
int gw_ares_init(gw_ares_channel_t **channel, struct gw_ares_options *opts);
+
+/*
+ * Free the resources allocated by gw_ares_init.
+ *
+ * @param channel
+ */
void gw_ares_deinit(gw_ares_channel_t *channel);
+
+/*
+ * Process event
+ *
+ * Description:
+ * the gw_ares_process_event function performing socket-related operation,
+ * you MUST call this when certain I/O in DNS socket file descriptor
+ * is ready to use.
+ *
+ * @param channel
+ * @return zero on success and negative integer on error
+ */
+int gw_ares_process_event(gw_ares_channel_t *channel);
diff --git a/src/gwproxy/dnsparser.c b/src/gwproxy/dnsparser.c
index 2d8af1da2893..442ea5558174 100644
--- a/src/gwproxy/dnsparser.c
+++ b/src/gwproxy/dnsparser.c
@@ -122,7 +122,8 @@ int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *o
if (!out->rr_answ)
return -ENOMEM;
- for (size_t i = 0; i < hdr->ancount; i++) {
+ size_t i;
+ for (i = 0; i < hdr->ancount; i++) {
uint16_t is_compressed, rdlength;
gwdns_serialized_answ *item = malloc(sizeof(gwdns_serialized_answ));
if (!item) {
@@ -211,7 +212,7 @@ int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *o
return 0;
exit_free:
- for (size_t i = 0; i < out->hdr.ancount; i++) {
+ for (i = 0; i < out->hdr.ancount; i++) {
free(out->rr_answ[i]->rdata);
free(out->rr_answ[i]);
}
@@ -221,7 +222,8 @@ exit_free:
void free_serialize_answ(gwdns_answ_data *answ)
{
- for (size_t i = 0; i < answ->hdr.ancount; i++) {
+ size_t i;
+ for (i = 0; i < answ->hdr.ancount; i++) {
free(answ->rr_answ[i]->rdata);
free(answ->rr_answ[i]);
}
@@ -300,7 +302,8 @@ void test_parse_ipv4(void)
memcpy(&txid, recv_pkt, 2);
assert(!serialize_answ(txid, recv_pkt, sizeof(recv_pkt), &d));
- for (size_t i = 0; i < d.hdr.ancount; i++) {
+ size_t i;
+ for (i = 0; i < d.hdr.ancount; i++) {
struct gwp_sockaddr gs;
gwdns_serialized_answ *answ;
char buff[FULL_ADDRSTRLEN];
@@ -367,7 +370,8 @@ void test_parse_ipv6(void)
ret = serialize_answ(txid, recv_pkt, sizeof(recv_pkt), &d);
assert(!ret);
- for (size_t i = 0; i < d.hdr.ancount; i++) {
+ size_t i;
+ for (i = 0; i < d.hdr.ancount; i++) {
struct gwp_sockaddr gs;
gwdns_serialized_answ *answ;
char buff[FULL_ADDRSTRLEN];
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 14+ messages in thread