* [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy
@ 2025-08-28 14:34 Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 01/11] gwproxy: Fix syntax error inside assertion Ahmad Gani
` (12 more replies)
0 siblings, 13 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
Hi Chief,
This is revision v6 of the initial work on the integration of the DNS
parser lib in gwproxy. This is an RFC draft; the patches themselves
aren't final.
This series has 11 patches; you can skip the rest and focus on
the highlighted one:
- add DNS server option (remove hard-coded DNS server)
- add fallback mechanism for raw DNS
- fix DNS parser
- mark this feature as experimental and disabled by default
- fix minor errors from master branch
Thanks for taking a look.
# Changelog
v1 -> v2:
- use existing convert_str_to_ssaddr instead of init_addr
- fix memory leak when init_addr (now it's replaced) failed
- modify convert_str_to_ssaddr to support default port
- bring back __cold attribute on convert_ssaddr_to_str
- don't fill a dangling pointer as Sir Alviro said
- for now it's blocking, attempt_retry label is not needed
v2 -> v3:
- remove TODO(reyuki): hints->ai_family is used to filter results
- make gw_ares_getaddrinfo asynchronous by change UDP socket to non-blocking
- move socket creation to library initialization
- make UDP socket unconnected
- restructure internal struct and program execution flow to support async
- update unit test of dns parser
- transaction id creation is delegated to caller
- fix logic bug in serialize_answ that can lead to memory error: invalid read
- rename parameter prt and split commit message
- move variable declaration in for loop according to the Sir Alviro coding-style
v3 -> v4:
- add else block in the convert_str_to_ssaddr function
- use format specifier %hu for uint16_t in printf
- update base commit to branch master from upstream remote repository
v4 -> v5:
- squash commits to eliminate changes unrelated to the commit subject
- change commit subject: use imperative form for the subject
- update base commit to branch master from upstream remote repository
- changing the email subject
- drop the c-ares style thing
- restructure dnsparser.c and dnsparser.h
v5 -> v6:
- mark this feature as experimental and disabled by default
- fix minor issues from master branch
- fix dns parser
- add dns server as gwproxy' cmdline option
- add fallback mechanism for raw dns backend
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
Ahmad Gani (11):
gwproxy: Fix syntax error inside assertion
gwproxy: Fix socks5 failure on debug mode
dnsparser: Add dns parser code
dnsparser: remove unused constant
dnsparser: Ignore CNAME if any
dns: Remove code block related to the usage of glibc's getaddrinfo_a
function
dns: refactor dns.c to integrate the dns parser
dns: revert removed DNS code and disable raw DNS by default
test: revert DNS test-case
gwproxy: Add DNS server option
dns: Add fallback mechanism for raw DNS
Makefile | 2 +-
configure | 8 +
src/gwproxy/dns.c | 240 +++++++++++++++--
src/gwproxy/dns.h | 29 +-
src/gwproxy/dnsparser.c | 581 ++++++++++++++++++++++++++++++++++++++++
src/gwproxy/dnsparser.h | 192 +++++++++++++
src/gwproxy/ev/epoll.c | 69 ++++-
src/gwproxy/gwproxy.c | 57 +++-
src/gwproxy/gwproxy.h | 5 +-
9 files changed, 1132 insertions(+), 51 deletions(-)
create mode 100644 src/gwproxy/dnsparser.c
create mode 100644 src/gwproxy/dnsparser.h
base-commit: 2a53c0e3694a88331fc6fd9a6793c01dc37c27f9
--
Ahmad Gani
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 01/11] gwproxy: Fix syntax error inside assertion
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 02/11] gwproxy: Fix socks5 failure on debug mode Ahmad Gani
` (11 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
The default configure script didn't catch this error, but when --debug
added as configure option, and it failed to compile.
Fixes: e941a3354eb3 ("gwproxy: Split socks5 and http initialization")
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/gwproxy.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index 86bae757555a..ac2bbc515c4e 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -667,7 +667,7 @@ out_err:
static void gwp_ctx_free_socks5(struct gwp_ctx *ctx)
{
- assert(ctx->cfg->as_socks5);
+ assert(ctx->cfg.as_socks5);
gwp_socks5_ctx_free(ctx->socks5);
ctx->socks5 = NULL;
pr_dbg(&ctx->lh, "SOCKS5 context freed");
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 02/11] gwproxy: Fix socks5 failure on debug mode
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 01/11] gwproxy: Fix syntax error inside assertion Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 03/11] dnsparser: Add dns parser code Ahmad Gani
` (10 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
Why use assert when the caller already use if statement for the same value?
this assert cause socks5 to always failed on runtime when debug mode is enabled
Fixes: 9c5669f11ec6 ("gwproxy: Unify HTTP and SOCKS5 common paths")
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/gwproxy.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index ac2bbc515c4e..7a90609dd5f1 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -1246,7 +1246,6 @@ int gwp_socks5_prepare_target_addr(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
struct gwp_socks5_addr *dst;
assert(sc);
- assert(sc->state == CONN_STATE_SOCKS5_CONNECT);
dst = &sc->dst_addr;
memset(ta, 0, sizeof(*ta));
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 03/11] dnsparser: Add dns parser code
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 01/11] gwproxy: Fix syntax error inside assertion Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 02/11] gwproxy: Fix socks5 failure on debug mode Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 04/11] dnsparser: remove unused constant Ahmad Gani
` (9 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
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 <reyuki@gnuweeb.org>
---
src/gwproxy/dnsparser.c | 568 ++++++++++++++++++++++++++++++++++++++++
src/gwproxy/dnsparser.h | 214 +++++++++++++++
2 files changed, 782 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..7b3a3c981ea7
--- /dev/null
+++ b/src/gwproxy/dnsparser.c
@@ -0,0 +1,568 @@
+#define _DEFAULT_SOURCE
+#include <endian.h>
+#include <stdbool.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 = 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 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 -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..dc181aa0a2f1
--- /dev/null
+++ b/src/gwproxy/dnsparser.h
@@ -0,0 +1,214 @@
+#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)
+ 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);
+
+#endif /* #ifndef GWP_DNS_PARSER_H */
\ No newline at end of file
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 04/11] dnsparser: remove unused constant
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (2 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 03/11] dnsparser: Add dns parser code Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 05/11] dnsparser: Ignore CNAME if any Ahmad Gani
` (8 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
The parser will likely only be targeted for a standard query,
specifically to resolve a domain name to IPv4 or IPv6.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dnsparser.h | 24 +-----------------------
1 file changed, 1 insertion(+), 23 deletions(-)
diff --git a/src/gwproxy/dnsparser.h b/src/gwproxy/dnsparser.h
index dc181aa0a2f1..b4c4b88b1c21 100644
--- a/src/gwproxy/dnsparser.h
+++ b/src/gwproxy/dnsparser.h
@@ -69,39 +69,17 @@
#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)
+ OPCODE_QUERY = 0, // Standard query (QUERY)
} 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 {
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 05/11] dnsparser: Ignore CNAME if any
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (3 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 04/11] dnsparser: remove unused constant Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 06/11] dns: Remove code block related to the usage of glibc's getaddrinfo_a function Ahmad Gani
` (7 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
Previously the parser will abort the program if it detects a type other
than A or AAAA record.
and other minor changes
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dnsparser.c | 63 +++++++++++++++++++++++++----------------
1 file changed, 38 insertions(+), 25 deletions(-)
diff --git a/src/gwproxy/dnsparser.c b/src/gwproxy/dnsparser.c
index 7b3a3c981ea7..cb2a090dfbb0 100644
--- a/src/gwproxy/dnsparser.c
+++ b/src/gwproxy/dnsparser.c
@@ -20,7 +20,7 @@ static ssize_t construct_qname(uint8_t *dst, size_t dst_len, const char *qname)
return -ENAMETOOLONG;
if (c == '.' || c == '\0') {
- if (l < 1 || l > 255)
+ if (l < 1 || l > DOMAIN_LABEL_LIMIT)
return -EINVAL;
*lp = (uint8_t)l;
@@ -51,14 +51,19 @@ static int calculate_question_len(uint8_t *in, size_t in_len)
}
if (tot_len >= (int)in_len)
- return -ENAMETOOLONG;
+ 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;
}
@@ -133,7 +138,8 @@ static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_
}
memcpy(&is_compressed, &in[idx], sizeof(is_compressed));
- is_compressed = DNS_IS_COMPRESSED(ntohs(is_compressed));
+ is_compressed = ntohs(is_compressed);
+ is_compressed = DNS_IS_COMPRESSED(is_compressed);
assert(is_compressed);
idx += 2; // NAME
if (idx >= in_len) {
@@ -169,21 +175,33 @@ static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_
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;
+ 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);
- goto exit_free;
- }
- if (item->rr_type == TYPE_A && rdlength != sizeof(struct in_addr)) {
+ continue;
+
+ default:
ret = -EINVAL;
free(item);
goto exit_free;
+ break;
}
+
item->rdlength = rdlength;
idx += 2;
if (idx >= in_len) {
@@ -192,13 +210,6 @@ static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_
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;
@@ -216,10 +227,15 @@ static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_
}
item->rdata = ptr;
- out->rr_answ[i] = item;
+ 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++) {
@@ -258,10 +274,7 @@ int gwdns_parse_query(uint16_t txid, const char *service,
if (r)
return r;
- if (!raw_answ.hdr.ancount)
- goto exit_free;
-
- tail = NULL;
+ results = tail = NULL;
for (i = 0; i < raw_answ.hdr.ancount; i++) {
struct gwdns_addrinfo_node *new_node;
gwdns_serialized_answ *answ;
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 06/11] dns: Remove code block related to the usage of glibc's getaddrinfo_a function
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (4 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 05/11] dnsparser: Ignore CNAME if any Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 07/11] dns: refactor dns.c to integrate the dns parser Ahmad Gani
` (6 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
This change is required for preparation in migration to no dedicated DNS
threads alternative.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
configure | 20 -----
src/gwproxy/dns.c | 220 ----------------------------------------------
2 files changed, 240 deletions(-)
diff --git a/configure b/configure
index cef07b1cb652..19baa1585237 100755
--- a/configure
+++ b/configure
@@ -355,26 +355,6 @@ else
CXXFLAGS="${CXXFLAGS} -DNDEBUG";
fi;
-# ------ Check for getaddrinfo_a() ------
-cat > $tmp_c << EOF
-#include <netdb.h>
-int main(void)
-{
- struct sigevent ev;
- struct gaicb *gc = (void *)0;
- getaddrinfo_a(GAI_NOWAIT, &gc, 1, &ev);
- return 0;
-}
-EOF
-l="Do we have getaddrinfo_a()?";
-if compile_cc "" "-lanl" "getaddrinfo_a()"; then
- add_config "CONFIG_HAVE_GETADDRINFO_A";
- print_supp "yes" "${l}";
- LIB_LDFLAGS="${LIB_LDFLAGS} -lanl";
-else
- print_supp "no" "${l}";
-fi;
-
add_make_var "CC" "${cc}";
add_make_var "CXX" "${cxx}";
add_make_var "CFLAGS" "$(echo "${CFLAGS} ${USER_CFLAGS}" | awk '{$1=$1};1')";
diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c
index cdc62a5bcf78..1e271a388493 100644
--- a/src/gwproxy/dns.c
+++ b/src/gwproxy/dns.c
@@ -234,204 +234,6 @@ static void wait_for_queue_entry(struct gwp_dns_ctx *ctx)
cond_scan_cache(ctx);
}
-#ifdef CONFIG_HAVE_GETADDRINFO_A
-/*
- * Must be called with ctx->lock held.
- */
-static struct gwp_dns_entry *unplug_queue_list(struct gwp_dns_ctx *ctx)
-{
- struct gwp_dns_entry *head = ctx->head;
-
- ctx->head = ctx->tail = NULL;
- ctx->nr_entries = 0;
- return head;
-}
-
-struct dbq_entry {
- struct gwp_dns_entry *e;
- struct gaicb cb;
-};
-
-struct dns_batch_query {
- struct dbq_entry *entries;
- struct gaicb **reqs;
- struct addrinfo hints;
- uint32_t nr_entries;
- uint32_t cap;
-};
-
-static void dbq_free(struct dns_batch_query *dbq)
-{
- uint32_t i;
-
- if (!dbq)
- return;
-
- if (dbq->reqs) {
- for (i = 0; i < dbq->nr_entries; i++) {
- if (dbq->reqs[i]->ar_result)
- freeaddrinfo(dbq->reqs[i]->ar_result);
- }
- }
-
- free(dbq->entries);
- free(dbq->reqs);
- free(dbq);
-}
-
-static int dbq_add_entry(struct dns_batch_query *dbq, struct gwp_dns_entry *e)
-{
- struct dbq_entry *de;
-
- if (dbq->nr_entries >= dbq->cap) {
- uint32_t new_cap = dbq->cap ? dbq->cap * 2 : 16;
- struct dbq_entry *nentries;
-
- nentries = realloc(dbq->entries, new_cap * sizeof(*nentries));
- if (!nentries)
- return -ENOMEM;
- dbq->entries = nentries;
- dbq->cap = new_cap;
- }
-
- de = &dbq->entries[dbq->nr_entries];
- de->e = e;
- memset(&de->cb, 0, sizeof(de->cb));
- de->cb.ar_name = e->name;
- de->cb.ar_service = e->service;
- de->cb.ar_request = &dbq->hints;
- de->cb.ar_result = NULL;
- dbq->nr_entries++;
-
- return 0;
-}
-
-static int collect_active_queries(struct gwp_dns_ctx *ctx,
- struct gwp_dns_entry **head_p,
- struct dns_batch_query **dbq_p)
-{
- struct gwp_dns_entry *e, *next, *prev = NULL, *head = *head_p;
- struct dns_batch_query *dbq;
-
- dbq = calloc(1, sizeof(*dbq));
- if (!dbq)
- return -ENOMEM;
-
- assert(head);
- prep_hints(&dbq->hints, ctx->cfg.restyp);
- for (e = head; e; e = next) {
- int x = atomic_load(&e->refcnt);
- next = e->next;
- if (x > 1) {
-
- if (dbq_add_entry(dbq, e)) {
- dbq_free(dbq);
- return -ENOMEM;
- }
-
- prev = e;
- continue;
- }
-
- assert(x == 1);
- /*
- * If the refcnt is 1, it means we are the last reference
- * to this entry. The client no longer cares about the
- * result. We can free it immediately. No need to resolve
- * the query nor to signal the eventfd.
- */
- if (prev)
- prev->next = next;
- else
- head = next;
-
- gwp_dns_entry_free(e);
- }
-
- *head_p = head;
- *dbq_p = dbq;
- return 0;
-}
-
-static void dispatch_batch_result(int r, struct gwp_dns_ctx *ctx,
- struct dns_batch_query *dbq,
- uint32_t restyp)
-{
- struct gwp_dns_entry *e;
- struct addrinfo *ai;
- uint32_t i;
-
- for (i = 0; i < dbq->nr_entries; i++) {
- e = dbq->entries[i].e;
- ai = dbq->reqs[i]->ar_result;
-
- if (!r) {
- e->res = gai_error(dbq->reqs[i]);
- if (!e->res) {
- if (!iterate_addr_list(ai, &e->addr, restyp))
- e->res = -EHOSTUNREACH;
- }
- } else {
- e->res = r;
- }
-
- eventfd_write(e->ev_fd, 1);
- if (!e->res)
- try_pass_result_to_cache(ctx, e->name, ai);
- }
-}
-
-/*
- * Filling dbq->reqs[n] cannot be done in dbq_add_entry() because
- * the reallocation of dbq->entries may change the address of
- * dbq->entries[n].cb.
- */
-static int prep_reqs(struct dns_batch_query *dbq)
-{
- uint32_t i;
-
- dbq->reqs = malloc(dbq->nr_entries * sizeof(*dbq->reqs));
- if (!dbq->reqs)
- return -ENOMEM;
-
- for (i = 0; i < dbq->nr_entries; i++)
- dbq->reqs[i] = &dbq->entries[i].cb;
-
- return 0;
-}
-
-/*
- * Must be called with ctx->lock held. May release the lock, but
- * it will reacquire it before returning.
- */
-static void process_queue_entry_batch(struct gwp_dns_ctx *ctx)
-{
- struct gwp_dns_entry *head = unplug_queue_list(ctx);
- struct dns_batch_query *dbq = NULL;
-
- if (!head)
- return;
-
- pthread_mutex_unlock(&ctx->lock);
-
- if (!collect_active_queries(ctx, &head, &dbq)) {
- if (!prep_reqs(dbq)) {
- struct sigevent ev;
- int r;
-
- memset(&ev, 0, sizeof(ev));
- ev.sigev_notify = SIGEV_NONE;
- r = getaddrinfo_a(GAI_WAIT, dbq->reqs, dbq->nr_entries, &ev);
- dispatch_batch_result(r, ctx, dbq, ctx->cfg.restyp);
- }
- }
-
- dbq_free(dbq);
- put_all_entries(head);
- pthread_mutex_lock(&ctx->lock);
-}
-#endif /* #ifdef CONFIG_HAVE_GETADDRINFO_A */
-
/*
* Must be called with ctx->lock held. May release the lock, but
* it will reacquire it before returning.
@@ -477,28 +279,6 @@ out:
*/
static void process_queue_entry(struct gwp_dns_ctx *ctx)
{
- /*
- * There are two cases here:
- *
- * 1. All of the DNS threads are busy, and there are still a lot
- * of queued entries. Process them in batch via getaddrinfo_a().
- *
- * 2. The number of threads is sufficient to handle the queued
- * entries, so process them one by one.
- *
- * Why not always getaddrinfo_a()? Because getaddrinfo_a() has
- * a higher overhead than processing entries individually as it
- * will spawn a new thread for each query. Don't bother invoking
- * clone() for each entry if we can process them in the current
- * thread.
- */
-#ifdef CONFIG_HAVE_GETADDRINFO_A
- if (ctx->nr_entries > (ctx->nr_sleeping + 16)) {
- process_queue_entry_batch(ctx);
- return;
- }
-#endif
-
process_queue_entry_single(ctx);
}
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 07/11] dns: refactor dns.c to integrate the dns parser
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (5 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 06/11] dns: Remove code block related to the usage of glibc's getaddrinfo_a function Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 08/11] dns: revert removed DNS code and disable raw DNS by default Ahmad Gani
` (5 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
remove threads support and eventfd in dns.c
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
Makefile | 2 +-
src/gwproxy/dns.c | 394 ++++++++++++--------------------------
src/gwproxy/dns.h | 33 ++--
src/gwproxy/ev/epoll.c | 32 +++-
src/gwproxy/ev/io_uring.c | 6 +-
src/gwproxy/gwproxy.c | 11 +-
src/gwproxy/gwproxy.h | 1 -
src/gwproxy/tests/dns.c | 110 +++++------
8 files changed, 222 insertions(+), 367 deletions(-)
diff --git a/Makefile b/Makefile
index 3ac35f052793..eb750e0fc31c 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ LIBGWPSOCKS5_TEST_CC_SOURCES = $(GWPROXY_DIR)/tests/socks5.c
LIBGWPSOCKS5_TEST_OBJECTS = $(LIBGWPSOCKS5_TEST_CC_SOURCES:%.c=%.c.o)
LIBGWDNS_TARGET = libgwdns.so
-LIBGWDNS_CC_SOURCES = $(GWPROXY_DIR)/dns.c $(GWPROXY_DIR)/dns_cache.c
+LIBGWDNS_CC_SOURCES = $(GWPROXY_DIR)/dns.c $(GWPROXY_DIR)/dnsparser.c $(GWPROXY_DIR)/dns_cache.c $(GWPROXY_DIR)/net.c
LIBGWDNS_OBJECTS = $(LIBGWDNS_CC_SOURCES:%.c=%.c.o)
LIBGWDNS_TEST_TARGET = $(GWPROXY_DIR)/tests/dns.t
LIBGWDNS_TEST_CC_SOURCES = $(GWPROXY_DIR)/tests/dns.c
diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c
index 1e271a388493..fea81109ca6d 100644
--- a/src/gwproxy/dns.c
+++ b/src/gwproxy/dns.c
@@ -23,46 +23,38 @@
#include <pthread.h>
#include <sys/eventfd.h>
+#include <gwproxy/dnsparser.h>
struct gwp_dns_ctx;
-struct gwp_dns_wrk {
- struct gwp_dns_ctx *ctx;
- uint32_t id;
- pthread_t thread;
-};
-
struct gwp_dns_ctx {
+ int nr_entries;
+ int entry_cap;
+ struct gwp_dns_entry **entries;
+ int sockfd;
+ int ns_family;
+ struct gwp_sockaddr ns_addr;
+ uint8_t ns_addrlen;
volatile bool should_stop;
pthread_mutex_t lock;
- pthread_cond_t cond;
- uint32_t nr_sleeping;
- uint32_t nr_entries;
- struct gwp_dns_entry *head;
- struct gwp_dns_entry *tail;
- struct gwp_dns_wrk *workers;
struct gwp_dns_cache *cache;
time_t last_scan;
struct gwp_dns_cfg cfg;
};
-static void put_all_entries(struct gwp_dns_entry *head)
+void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen)
{
- struct gwp_dns_entry *e, *next;
-
- for (e = head; e; e = next) {
- next = e->next;
- gwp_dns_entry_put(e);
- }
+ *addr = ctx->ns_addr;
+ *addrlen = ctx->ns_addrlen;
}
-static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
+__attribute__((unused))
+static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockaddr *gs,
uint32_t rt)
{
- struct addrinfo *ai;
+ struct gwdns_addrinfo_node *ai;
- if (!res)
- return false;
+ assert(res);
/*
* Handle IPV4_ONLY and IPV6_ONLY cases together.
@@ -75,9 +67,9 @@ static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
if (ai->ai_family != fm)
continue;
if (fm == AF_INET)
- gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
+ gs->i4 = ai->ai_addr.i4;
else
- gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
+ gs->i6 = ai->ai_addr.i6;
return true;
}
return false;
@@ -91,19 +83,19 @@ static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
int prm = (rt == GWP_DNS_RESTYP_PREFER_IPV6) ? AF_INET6
: AF_INET;
int sec = (prm == AF_INET6) ? AF_INET : AF_INET6;
- struct sockaddr *fallback = NULL;
+ struct gwp_sockaddr *fallback = NULL;
for (ai = res; ai; ai = ai->ai_next) {
if (ai->ai_family != prm) {
if (ai->ai_family == sec && !fallback)
- fallback = ai->ai_addr;
+ fallback = &ai->ai_addr;
continue;
}
if (prm == AF_INET)
- gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
+ gs->i4 = ai->ai_addr.i4;
else
- gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
+ gs->i6 = ai->ai_addr.i6;
return true;
}
@@ -111,9 +103,9 @@ static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
return false;
if (sec == AF_INET)
- gs->i4 = *(struct sockaddr_in *)fallback;
+ gs->i4 = fallback->i4;
else
- gs->i6 = *(struct sockaddr_in6 *)fallback;
+ gs->i6 = fallback->i6;
return true;
}
@@ -123,10 +115,10 @@ static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
*/
for (ai = res; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET) {
- gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
+ gs->i4 = ai->ai_addr.i4;
return true;
} else if (ai->ai_family == AF_INET6) {
- gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
+ gs->i6 = ai->ai_addr.i6;
return true;
}
}
@@ -134,19 +126,7 @@ static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
return false;
}
-static void prep_hints(struct addrinfo *hints, uint32_t restyp)
-{
- memset(hints, 0, sizeof(*hints));
- hints->ai_family = AF_UNSPEC;
- hints->ai_socktype = SOCK_STREAM;
- hints->ai_flags = AI_ADDRCONFIG;
-
- if (restyp == GWP_DNS_RESTYP_IPV4_ONLY)
- hints->ai_family = AF_INET;
- else if (restyp == GWP_DNS_RESTYP_IPV6_ONLY)
- hints->ai_family = AF_INET6;
-}
-
+__attribute__((unused))
static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
const struct addrinfo *ai)
{
@@ -159,38 +139,40 @@ static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
gwp_dns_cache_insert(ctx->cache, name, ai, time(NULL) + x);
}
-int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name,
- const char *service, struct gwp_sockaddr *addr,
- uint32_t restyp)
-{
- struct addrinfo *res = NULL, hints;
- bool found;
- int r;
-
- prep_hints(&hints, restyp);
- r = getaddrinfo(name, service, &hints, &res);
- if (r)
- return -r;
+// static int gwp_dns_find_preferred_addr(struct gwp_dns_ctx *ctx, struct gwdns_addrinfo_node *ai, const char *name,
+// struct gwp_sockaddr *addr, uint32_t restyp)
+// {
+// bool found;
- found = iterate_addr_list(res, addr, restyp);
- if (found)
- try_pass_result_to_cache(ctx, name, res);
+// found = iterate_addr_list(ai, addr, restyp);
+// if (found)
+// try_pass_result_to_cache(ctx, name, ai);
- if (res)
- freeaddrinfo(res);
+// return found ? 0 : -EHOSTUNREACH;
+// }
- return found ? 0 : -EHOSTUNREACH;
+static void _gwp_dns_entry_free(struct gwp_dns_entry *e)
+{
+ assert(e);
+ assert(e->udp_fd >= 0);
+ close(e->udp_fd);
+ free(e->name);
+ free(e);
}
-static void gwp_dns_entry_free(struct gwp_dns_entry *e)
+void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
{
- if (!e)
- return;
+ struct gwp_dns_entry *new_e;
- assert(e->ev_fd >= 0);
- close(e->ev_fd);
- free(e->name);
- free(e);
+ assert(e);
+
+ new_e = ctx->entries[--ctx->nr_entries];
+ assert(ctx->nr_entries == new_e->idx);
+ new_e->idx = e->idx;
+ ctx->entries[e->idx] = new_e;
+ ctx->entries[ctx->nr_entries] = NULL;
+
+ _gwp_dns_entry_free(e);
}
/*
@@ -203,6 +185,7 @@ static void gwp_dns_entry_free(struct gwp_dns_entry *e)
* cache to find expired entries and delete them.
* - Save the current time as the last scan time.
*/
+__attribute__((unused))
static void cond_scan_cache(struct gwp_dns_ctx *ctx)
{
if (!ctx->cache)
@@ -223,146 +206,6 @@ static void cond_scan_cache(struct gwp_dns_ctx *ctx)
ctx->last_scan = time(NULL);
}
-/*
- * Must be called with ctx->lock held.
- */
-static void wait_for_queue_entry(struct gwp_dns_ctx *ctx)
-{
- ctx->nr_sleeping++;
- pthread_cond_wait(&ctx->cond, &ctx->lock);
- ctx->nr_sleeping--;
- cond_scan_cache(ctx);
-}
-
-/*
- * Must be called with ctx->lock held. May release the lock, but
- * it will reacquire it before returning.
- */
-static void process_queue_entry_single(struct gwp_dns_ctx *ctx)
-{
- struct gwp_dns_entry *e = ctx->head;
-
- if (!e)
- return;
-
- e = ctx->head;
- ctx->head = e->next;
- if (!ctx->head) {
- ctx->tail = NULL;
- assert(ctx->nr_entries == 1);
- }
-
- ctx->nr_entries--;
- pthread_mutex_unlock(&ctx->lock);
-
- if (atomic_load(&e->refcnt) == 1) {
- /*
- * If the refcnt is 1, it means we are the last reference
- * to this entry. The client no longer cares about the
- * result. We can free it immediately. No need to resolve
- * the query nor to signal the eventfd.
- */
- gwp_dns_entry_free(e);
- goto out;
- }
-
- e->res = gwp_dns_resolve(ctx, e->name, e->service, &e->addr, ctx->cfg.restyp);
- eventfd_write(e->ev_fd, 1);
- gwp_dns_entry_put(e);
-out:
- pthread_mutex_lock(&ctx->lock);
-}
-
-/*
- * Must be called with ctx->lock held. May release the lock, but
- * it will reacquire it before returning.
- */
-static void process_queue_entry(struct gwp_dns_ctx *ctx)
-{
- process_queue_entry_single(ctx);
-}
-
-static void *gwp_dns_thread_entry(void *arg)
-{
- struct gwp_dns_wrk *w = arg;
- struct gwp_dns_ctx *ctx = w->ctx;
-
- pthread_mutex_lock(&ctx->lock);
- while (!ctx->should_stop) {
- if (ctx->head)
- process_queue_entry(ctx);
- else
- wait_for_queue_entry(ctx);
- }
- pthread_mutex_unlock(&ctx->lock);
-
- return NULL;
-}
-
-static void free_worker(struct gwp_dns_wrk *w)
-{
- struct gwp_dns_ctx *ctx;
-
- if (!w)
- return;
-
- ctx = w->ctx;
- pthread_mutex_lock(&ctx->lock);
- ctx->should_stop = true;
- pthread_cond_broadcast(&ctx->cond);
- pthread_mutex_unlock(&ctx->lock);
- pthread_join(w->thread, NULL);
-}
-
-static void free_workers(struct gwp_dns_ctx *ctx)
-{
- uint32_t i;
-
- if (!ctx->workers)
- return;
-
- for (i = 0; i < ctx->cfg.nr_workers; i++)
- free_worker(&ctx->workers[i]);
-
- free(ctx->workers);
- ctx->workers = NULL;
-}
-
-static int init_workers(struct gwp_dns_ctx *ctx)
-{
- struct gwp_dns_wrk *workers, *w;
- uint32_t i;
- int r;
-
- if (ctx->cfg.nr_workers == 0)
- return -EINVAL;
-
- workers = calloc(ctx->cfg.nr_workers, sizeof(*workers));
- if (!workers)
- return -ENOMEM;
-
- ctx->workers = workers;
- for (i = 0; i < ctx->cfg.nr_workers; i++) {
- w = &workers[i];
- w->ctx = ctx;
- w->id = i;
- r = pthread_create(&w->thread, NULL, gwp_dns_thread_entry, w);
- if (r) {
- r = -r;
- goto out_err;
- }
- }
-
- return 0;
-
-out_err:
- while (i--)
- free_worker(&workers[i]);
- free(workers);
- ctx->workers = NULL;
- return r;
-}
-
static bool fetch_i4(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr,
uint16_t port)
{
@@ -499,33 +342,26 @@ int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg)
goto out_free_ctx;
}
- r = pthread_cond_init(&ctx->cond, NULL);
- if (r) {
- r = -r;
- goto out_destroy_mutex;
- }
-
r = init_cache(ctx);
if (r)
- goto out_destroy_cond;
+ goto out_destroy_mutex;
- ctx->nr_sleeping = 0;
- ctx->nr_entries = 0;
- ctx->workers = NULL;
- ctx->head = NULL;
- ctx->tail = NULL;
+ r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53);
+ if (r)
+ goto out_destroy_mutex;
+ ctx->ns_addrlen = ctx->ns_addr.sa.sa_family == AF_INET
+ ? sizeof(ctx->ns_addr.i4)
+ : sizeof(ctx->ns_addr.i6);
ctx->should_stop = false;
ctx->last_scan = time(NULL);
- r = init_workers(ctx);
- if (r)
- goto out_free_cache;
+ ctx->nr_entries = 0;
+ ctx->entry_cap = DEFAULT_ENTRIES_CAP;
+ ctx->entries = malloc(ctx->entry_cap * sizeof(*ctx->entries));
+ if (!ctx->entries)
+ goto out_destroy_mutex;
*ctx_p = ctx;
return 0;
-out_free_cache:
- free_cache(ctx->cache);
-out_destroy_cond:
- pthread_cond_destroy(&ctx->cond);
out_destroy_mutex:
pthread_mutex_destroy(&ctx->lock);
out_free_ctx:
@@ -534,51 +370,67 @@ out_free_ctx:
return r;
}
-static void put_all_queued_entries(struct gwp_dns_ctx *ctx)
+static void free_all_queued_entries(struct gwp_dns_ctx *ctx)
{
- put_all_entries(ctx->head);
- ctx->head = ctx->tail = NULL;
+ int i;
+ for (i = 0; i < ctx->nr_entries; i++) {
+ struct gwp_dns_entry *e = ctx->entries[i];
+ _gwp_dns_entry_free(e);
+ }
+
+ free(ctx->entries);
}
void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx)
{
- free_workers(ctx);
pthread_mutex_destroy(&ctx->lock);
- pthread_cond_destroy(&ctx->cond);
- put_all_queued_entries(ctx);
+ free_all_queued_entries(ctx);
free_cache(ctx->cache);
free(ctx);
}
-static void push_queue(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
+static bool realloc_entries(struct gwp_dns_ctx *ctx)
{
- pthread_mutex_lock(&ctx->lock);
- if (ctx->tail)
- ctx->tail->next = e;
- else
- ctx->head = e;
- ctx->tail = e;
- e->next = NULL;
+ struct gwp_dns_entry **tmp;
+ int new_cap;
- ctx->nr_entries++;
- if (ctx->nr_sleeping)
- pthread_cond_signal(&ctx->cond);
- pthread_mutex_unlock(&ctx->lock);
+ new_cap = ctx->entry_cap * 2;
+ tmp = realloc(ctx->entries, new_cap * sizeof(*tmp));
+ if (!tmp)
+ return 1;
+
+ ctx->entries = tmp;
+ ctx->entry_cap = new_cap;
+
+ return 0;
}
struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
const char *name, const char *service)
{
struct gwp_dns_entry *e;
+ uint16_t txid;
size_t nl, sl;
+ ssize_t r;
+
+ if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx))
+ return NULL;
e = malloc(sizeof(*e));
if (!e)
return NULL;
- e->ev_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
- if (e->ev_fd < 0)
+ r = __sys_socket(ctx->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ if (r < 0)
+ goto out_free_e;
+ e->udp_fd = (int)r;
+
+ txid = (uint16_t)rand();
+ // TODO(reyuki): avoid hard-coded AF_INET and use restyp instead
+ r = gwdns_build_query(txid, name, AF_INET, e->payload, sizeof(e->payload));
+ if (r < 0)
goto out_free_e;
+ e->payloadlen = (int)r;
/*
* Merge name and service into a single allocated string to
@@ -591,7 +443,7 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
sl = service ? strlen(service) : 0;
e->name = malloc(nl + 1 + sl + 1);
if (!e->name)
- goto out_close_ev_fd;
+ goto out_free_e;
e->service = e->name + nl + 1;
memcpy(e->name, name, nl + 1);
@@ -600,31 +452,39 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
else
e->service[0] = '\0';
- atomic_init(&e->refcnt, 2);
e->res = 0;
- push_queue(ctx, e);
+ e->idx = ctx->nr_entries++;
+ ctx->entries[e->idx] = e;
+
return e;
-out_close_ev_fd:
- close(e->ev_fd);
out_free_e:
free(e);
return NULL;
}
-bool gwp_dns_entry_put(struct gwp_dns_entry *e)
+int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
{
- int x;
-
- if (!e)
- return false;
+ struct gwdns_addrinfo_node *ai;
+ uint8_t buff[UDP_MSG_LIMIT];
+ ssize_t r;
+
+ r = __sys_recvfrom(
+ e->udp_fd, buff, sizeof(buff), 0,
+ &ctx->ns_addr.sa, (socklen_t *)&ctx->ns_addrlen
+ );
+ if (r <= 0)
+ return (int)r;
+
+ r = gwdns_parse_query(e->txid, e->service, buff, r, &ai);
+ if (r)
+ goto exit_free_ai;
- x = atomic_fetch_sub(&e->refcnt, 1);
- assert(x > 0);
- if (x == 1) {
- gwp_dns_entry_free(e);
- return true;
- }
+ e->addr = ai->ai_addr;
+ // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp);
- return false;
+exit_free_ai:
+ gwdns_free_parsed_query(ai);
+ return (int)r;
}
+
diff --git a/src/gwproxy/dns.h b/src/gwproxy/dns.h
index 10c7cea2ebe4..12d21552c92e 100644
--- a/src/gwproxy/dns.h
+++ b/src/gwproxy/dns.h
@@ -10,17 +10,21 @@
#include <stdbool.h>
#include <netinet/in.h>
#include <gwproxy/net.h>
-
-struct gwp_dns_wrk;
+#include <gwproxy/dnsparser.h>
+#include <gwproxy/syscall.h>
struct gwp_dns_entry {
+ int idx;
char *name;
char *service;
- _Atomic(int) refcnt;
int res;
- int ev_fd;
+ int udp_fd;
struct gwp_sockaddr addr;
- struct gwp_dns_entry *next;
+ int payloadlen;
+ union {
+ uint16_t txid;
+ uint8_t payload[UDP_MSG_LIMIT];
+ };
};
enum {
@@ -31,9 +35,12 @@ enum {
GWP_DNS_RESTYP_PREFER_IPV6 = 4,
};
+#define DEFAULT_ENTRIES_CAP 255
+
struct gwp_dns_cfg {
int cache_expiry; /* In seconds. <= 0 to disable cache. */
uint32_t nr_workers;
+ const char *ns_addr_str;
uint32_t restyp;
};
@@ -64,7 +71,7 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx);
/**
* Queue a DNS resolution request. It returns a pointer to a gwp_dns_entry
* with eventfd set to a valid file descriptor that can be used to wait for
- * the resolution result. The caller's responsible to call gwp_dns_entry_put()
+ * the resolution result. The caller's responsible to call gwp_dns_entry_free()
* to release the entry when it is no longer needed.
*
* The returned eventfd file descriptor is non-blocking.
@@ -77,15 +84,11 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx);
struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
const char *name, const char *service);
-/**
- * Release a DNS entry. This function decrements the reference count of the
- * entry. If the reference count reaches zero, the entry is freed.
- *
- * @param entry Pointer to the DNS entry to release. If the entry is
- * NULL, this function does nothing.
- * @return True if the entry was freed, false otherwise.
- */
-bool gwp_dns_entry_put(struct gwp_dns_entry *entry);
+void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen);
+
+void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e);
+
+int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e);
/**
* Lookup a DNS entry in the cache. If the entry is found, it fills the
diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c
index d46568a6a2b1..5e117be2ae8f 100644
--- a/src/gwproxy/ev/epoll.c
+++ b/src/gwproxy/ev/epoll.c
@@ -155,7 +155,7 @@ static int free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
int r;
if (gde) {
- r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->udp_fd, NULL);
if (unlikely(r))
return r;
}
@@ -782,22 +782,36 @@ static int arm_poll_for_dns_query(struct gwp_wrk *w,
struct gwp_conn_pair *gcp)
{
struct gwp_dns_entry *gde = gcp->gde;
+ struct gwp_sockaddr addr;
+ struct gwp_dns_ctx *dctx;
struct epoll_event ev;
- int r;
+ uint8_t addrlen;
+ ssize_t r;
assert(gde);
- assert(gde->ev_fd >= 0);
+ dctx = w->ctx->dns;
+
+ cp_nsaddr(dctx, &addr, &addrlen);
+ r = __sys_sendto(
+ gde->udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL,
+ &addr.sa, addrlen
+ );
+ if (unlikely(r < 0))
+ goto exit_close;
ev.events = EPOLLIN;
ev.data.u64 = 0;
ev.data.ptr = gcp;
ev.data.u64 |= EV_BIT_DNS_QUERY;
- r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->ev_fd, &ev);
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->udp_fd, &ev);
if (unlikely(r))
- return r;
+ goto exit_close;
return 0;
+exit_close:
+ close(gde->udp_fd);
+ return (int)r;
}
static void log_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp,
@@ -830,9 +844,9 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
assert(ct == CONN_STATE_SOCKS5_DNS_QUERY ||
ct == CONN_STATE_HTTP_DNS_QUERY);
- r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
- if (unlikely(r))
- return r;
+ r = gwp_dns_process(w->ctx->dns, gde);
+ if (r)
+ gde->res = r;
log_dns_query(w, gcp, gde);
if (likely(!gde->res)) {
@@ -845,7 +859,7 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
r = -EIO;
}
- gwp_dns_entry_put(gde);
+ gwp_dns_entry_free(w->ctx->dns, gde);
gcp->gde = NULL;
return r;
}
diff --git a/src/gwproxy/ev/io_uring.c b/src/gwproxy/ev/io_uring.c
index 78440935383b..9cb06b1214ca 100644
--- a/src/gwproxy/ev/io_uring.c
+++ b/src/gwproxy/ev/io_uring.c
@@ -675,13 +675,13 @@ static int prep_domain_resolution(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
assert(gde);
s = get_sqe_nofail(w);
- io_uring_prep_poll_add(s, gde->ev_fd, POLLIN);
+ io_uring_prep_poll_add(s, gde->udp_fd, POLLIN);
io_uring_sqe_set_data(s, gcp);
s->user_data |= EV_BIT_IOU_DNS_QUERY;
get_gcp(gcp);
pr_dbg(&ctx->lh,
"Prepared DNS query for domain '%s' (fd=%d, idx=%u, ref_cnt=%d)",
- gde->name, gde->ev_fd, gcp->idx, gcp->ref_cnt);
+ gde->name, gde->udp_fd, gcp->idx, gcp->ref_cnt);
return 0;
}
@@ -741,7 +741,7 @@ static int handle_ev_dns_query(struct gwp_wrk *w, void *udata)
gde->name, ip_to_str(&gcp->target_addr), gcp->target.fd,
gcp->idx);
- gwp_dns_entry_put(gde);
+ gwp_dns_entry_free(ctx->dns, gde);
gcp->gde = NULL;
return handle_socks5_connect_target(w, gcp);
}
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index 7a90609dd5f1..d0a010ab4b27 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -54,7 +54,6 @@ static const struct option long_opts[] = {
{ "socks5-auth-file", required_argument, NULL, 'A' },
{ "socks5-dns-cache-secs", required_argument, NULL, 'L' },
{ "nr-workers", required_argument, NULL, 'w' },
- { "nr-dns-workers", required_argument, NULL, 'W' },
{ "connect-timeout", required_argument, NULL, 'c' },
{ "target-buf-size", required_argument, NULL, 'T' },
{ "client-buf-size", required_argument, NULL, 'C' },
@@ -81,7 +80,6 @@ static const struct gwp_cfg default_opts = {
.socks5_auth_file = NULL,
.socks5_dns_cache_secs = 0,
.nr_workers = 4,
- .nr_dns_workers = 4,
.connect_timeout = 5,
.target_buf_size = 2048,
.client_buf_size = 2048,
@@ -114,7 +112,6 @@ static void show_help(const char *app)
printf(" -L, --socks5-dns-cache-secs=sec SOCKS5 DNS cache duration in seconds (default: %d)\n", default_opts.socks5_dns_cache_secs);
printf(" Set to 0 or a negative number to disable DNS caching.\n");
printf(" -w, --nr-workers=nr Number of worker threads (default: %d)\n", default_opts.nr_workers);
- printf(" -W, --nr-dns-workers=nr Number of DNS worker threads for SOCKS5 (default: %d)\n", default_opts.nr_dns_workers);
printf(" -c, --connect-timeout=sec Connection to target timeout in seconds (default: %d)\n", default_opts.connect_timeout);
printf(" -T, --target-buf-size=nr Target buffer size in bytes (default: %d)\n", default_opts.target_buf_size);
printf(" -C, --client-buf-size=nr Client buffer size in bytes (default: %d)\n", default_opts.client_buf_size);
@@ -189,9 +186,6 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg)
case 'w':
cfg->nr_workers = atoi(optarg);
break;
- case 'W':
- cfg->nr_dns_workers = atoi(optarg);
- break;
case 'c':
cfg->connect_timeout = atoi(optarg);
break;
@@ -691,7 +685,8 @@ static int gwp_ctx_init_dns(struct gwp_ctx *ctx)
const struct gwp_dns_cfg dns_cfg = {
.cache_expiry = cfg->socks5_dns_cache_secs,
.restyp = cfg->socks5_prefer_ipv6 ? GWP_DNS_RESTYP_PREFER_IPV6 : 0,
- .nr_workers = cfg->nr_dns_workers
+ .nr_workers = 1,
+ .ns_addr_str = "1.1.1.1"
};
int r;
@@ -991,7 +986,7 @@ int gwp_free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
__sys_close(gcp->timer_fd);
if (gcp->gde)
- gwp_dns_entry_put(gcp->gde);
+ gwp_dns_entry_free(w->ctx->dns, gcp->gde);
switch (gcp->prot_type) {
case GWP_PROT_TYPE_SOCKS5:
diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h
index 095c0ce700c3..0206e68e7481 100644
--- a/src/gwproxy/gwproxy.h
+++ b/src/gwproxy/gwproxy.h
@@ -31,7 +31,6 @@ struct gwp_cfg {
const char *socks5_auth_file;
int socks5_dns_cache_secs;
int nr_workers;
- int nr_dns_workers;
int connect_timeout;
int target_buf_size;
int client_buf_size;
diff --git a/src/gwproxy/tests/dns.c b/src/gwproxy/tests/dns.c
index 7735d24afdd2..130ccfec59b6 100644
--- a/src/gwproxy/tests/dns.c
+++ b/src/gwproxy/tests/dns.c
@@ -20,25 +20,30 @@ struct req_template {
const char *domain, *service;
};
+struct poll_map {
+ struct gwp_dns_entry *e;
+ int fd;
+};
+
static const struct req_template req_template[] = {
- { "localhost", "80" },
{ "facebook.com", "80" },
- { "google.com", "443" },
- { "github.com", "443" },
- { "example.com", "80" },
- { "twitter.com", "443" },
- { "reddit.com", "80" },
- { "youtube.com", "443" },
- { "wikipedia.org", "80" },
- { "stackoverflow.com", "443" },
- { "amazon.com", "80" },
- { "microsoft.com", "443" },
- { "apple.com", "80" },
- { "linkedin.com", "443" },
- { "bing.com", "80" },
};
-static int poll_all_in(struct pollfd *pfd, int n, int timeout)
+static struct gwp_dns_entry *find_item(struct poll_map *map, int n, int fd)
+{
+ struct gwp_dns_entry *e;
+ int i;
+
+ e = NULL;
+ for (i = 0; i < n; i++) {
+ if (map[i].fd == fd)
+ e = map[i].e;
+ }
+
+ return e;
+}
+
+static int poll_all_in(struct gwp_dns_ctx *ctx, struct poll_map *map, struct pollfd *pfd, int n, int timeout)
{
int ret, i, t = 0;
@@ -54,7 +59,12 @@ static int poll_all_in(struct pollfd *pfd, int n, int timeout)
}
for (i = 0; i < n; i++) {
- if (pfd[i].revents & (POLLIN | POLLERR | POLLHUP)) {
+ struct gwp_dns_entry *e;
+ if (pfd[i].revents & POLLIN) {
+ e = find_item(map, n, pfd[i].fd);
+ assert(e);
+ ret = gwp_dns_process(ctx, e);
+ assert(!ret);
pfd[i].events = 0;
t++;
}
@@ -67,12 +77,14 @@ static int poll_all_in(struct pollfd *pfd, int n, int timeout)
static void test_basic_dns_multiple_requests(void)
{
- struct gwp_dns_cfg cfg = { .nr_workers = 1 };
- struct gwp_dns_entry *earr[ARRAY_SIZE(req_template)];
+ struct gwp_dns_cfg cfg = { .nr_workers = 1, .ns_addr_str = "1.1.1.1" };
+ struct poll_map pollfd_map[ARRAY_SIZE(req_template)];
struct pollfd pfd[ARRAY_SIZE(req_template)];
+ struct gwp_sockaddr addr;
struct gwp_dns_ctx *ctx;
+ uint8_t addrlen;
+ ssize_t r;
int i, n;
- int r;
r = gwp_dns_ctx_init(&ctx, &cfg);
assert(!r);
@@ -81,65 +93,37 @@ static void test_basic_dns_multiple_requests(void)
n = (int)ARRAY_SIZE(req_template);
for (i = 0; i < n; i++) {
const struct req_template *rt = &req_template[i];
- earr[i] = gwp_dns_queue(ctx, rt->domain, rt->service);
- assert(earr[i]);
- assert(earr[i]->ev_fd >= 0);
- pfd[i].fd = earr[i]->ev_fd;
+ struct gwp_dns_entry *e;
+ e = gwp_dns_queue(ctx, rt->domain, rt->service);
+ assert(e);
+ assert(e->udp_fd >= 0);
+ pfd[i].fd = e->udp_fd;
pfd[i].events = POLLIN;
+ cp_nsaddr(ctx, &addr, &addrlen);
+ r = __sys_sendto(
+ e->udp_fd, e->payload, e->payloadlen, MSG_NOSIGNAL,
+ &addr.sa, addrlen
+ );
+ assert(r > 0);
+ pollfd_map[i].fd = e->udp_fd;
+ pollfd_map[i].e = e;
}
- r = poll_all_in(pfd, n, 5000);
+ r = poll_all_in(ctx, pollfd_map, pfd, n, 5000);
assert(!r);
for (i = 0; i < n; i++) {
- assert(earr[i]->res == 0);
- r = earr[i]->addr.sa.sa_family;
+ assert(pollfd_map[i].e->res == 0);
+ r = pollfd_map[i].e->addr.sa.sa_family;
assert(r == AF_INET || r == AF_INET6);
}
- for (i = 0; i < n; i++)
- gwp_dns_entry_put(earr[i]);
- gwp_dns_ctx_free(ctx);
-}
-
-static void test_dns_cache(void)
-{
- struct gwp_dns_cfg cfg = { .nr_workers = 1, .cache_expiry = 10 };
- struct gwp_sockaddr addr;
- struct gwp_dns_ctx *ctx;
- struct gwp_dns_entry *e;
- struct pollfd pfd;
- int r;
-
- r = gwp_dns_ctx_init(&ctx, &cfg);
- assert(!r);
- assert(ctx != NULL);
-
- e = gwp_dns_queue(ctx, "localhost", "80");
- assert(e != NULL);
- assert(e->ev_fd >= 0);
- pfd.fd = e->ev_fd;
- pfd.events = POLLIN;
- r = poll_all_in(&pfd, 1, 5000);
- assert(r == 0);
- assert(e->res == 0);
- r = e->addr.sa.sa_family;
- assert(r == AF_INET || r == AF_INET6);
- gwp_dns_entry_put(e);
-
- r = gwp_dns_cache_lookup(ctx, "localhost", "80", &addr);
- assert(!r);
- r = addr.sa.sa_family;
- assert(r == AF_INET || r == AF_INET6);
- r = gwp_dns_cache_lookup(ctx, "aaaa.com", "80", &addr);
- assert(r == -ENOENT);
gwp_dns_ctx_free(ctx);
}
int main(void)
{
test_basic_dns_multiple_requests();
- test_dns_cache();
printf("All tests passed.\n");
return 0;
}
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 08/11] dns: revert removed DNS code and disable raw DNS by default
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (6 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 07/11] dns: refactor dns.c to integrate the dns parser Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 09/11] test: revert DNS test-case Ahmad Gani
` (4 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
The raw DNS backend is now marked as experimental and disabled by default.
It can be enabled at build time with the configure option --use-raw-dns,
and at runtime with the gwproxy option -r 1.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
configure | 28 ++
src/gwproxy/dns.c | 688 +++++++++++++++++++++++++++++++++-----
src/gwproxy/dns.h | 36 +-
src/gwproxy/ev/epoll.c | 62 ++--
src/gwproxy/ev/io_uring.c | 6 +-
src/gwproxy/gwproxy.c | 31 +-
src/gwproxy/gwproxy.h | 1 +
7 files changed, 730 insertions(+), 122 deletions(-)
diff --git a/configure b/configure
index 19baa1585237..539610916149 100755
--- a/configure
+++ b/configure
@@ -34,6 +34,9 @@ for opt do
--cxx=*)
cxx="$optarg";
;;
+ --use-raw-dns)
+ use_raw_dns="yes";
+ ;;
--use-io-uring)
use_io_uring="yes";
;;
@@ -87,6 +90,7 @@ Options: [defaults in brackets after descriptions]
--cxx=CMD Use CMD as the C++ compiler
--debug Build with debug enabled
--use-io-uring Enable io_uring support (default: no)
+ --use-raw-dns Enable experimental raw DNS backend (default: no)
--sanitize Enable sanitizers (default: no)
EOF
exit 0;
@@ -333,6 +337,10 @@ if test "${use_io_uring}" = "yes"; then
CXXFLAGS="${CXXFLAGS} -I./src/liburing/src/include";
fi;
+if test "${use_raw_dns}" = "yes"; then
+ add_config "CONFIG_RAW_DNS";
+fi;
+
if test "${use_sanitize}" = "yes"; then
add_config "CONFIG_SANITIZE";
if ! add_c_and_cxx_flag "-fsanitize=address"; then
@@ -355,6 +363,26 @@ else
CXXFLAGS="${CXXFLAGS} -DNDEBUG";
fi;
+# ------ Check for getaddrinfo_a() ------
+cat > $tmp_c << EOF
+#include <netdb.h>
+int main(void)
+{
+ struct sigevent ev;
+ struct gaicb *gc = (void *)0;
+ getaddrinfo_a(GAI_NOWAIT, &gc, 1, &ev);
+ return 0;
+}
+EOF
+l="Do we have getaddrinfo_a()?";
+if compile_cc "" "-lanl" "getaddrinfo_a()"; then
+ add_config "CONFIG_HAVE_GETADDRINFO_A";
+ print_supp "yes" "${l}";
+ LIB_LDFLAGS="${LIB_LDFLAGS} -lanl";
+else
+ print_supp "no" "${l}";
+fi;
+
add_make_var "CC" "${cc}";
add_make_var "CXX" "${cxx}";
add_make_var "CFLAGS" "$(echo "${CFLAGS} ${USER_CFLAGS}" | awk '{$1=$1};1')";
diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c
index fea81109ca6d..9bc67b083b03 100644
--- a/src/gwproxy/dns.c
+++ b/src/gwproxy/dns.c
@@ -27,34 +27,51 @@
struct gwp_dns_ctx;
+struct gwp_dns_wrk {
+ struct gwp_dns_ctx *ctx;
+ uint32_t id;
+ pthread_t thread;
+};
+
struct gwp_dns_ctx {
- int nr_entries;
- int entry_cap;
+#ifdef CONFIG_RAW_DNS
+ uint32_t entry_cap;
struct gwp_dns_entry **entries;
int sockfd;
int ns_family;
struct gwp_sockaddr ns_addr;
uint8_t ns_addrlen;
+#endif
volatile bool should_stop;
pthread_mutex_t lock;
+ pthread_cond_t cond;
+ uint32_t nr_sleeping;
+ uint32_t nr_entries;
+ struct gwp_dns_entry *head;
+ struct gwp_dns_entry *tail;
+ struct gwp_dns_wrk *workers;
struct gwp_dns_cache *cache;
time_t last_scan;
struct gwp_dns_cfg cfg;
};
-void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen)
+static void put_all_entries(struct gwp_dns_entry *head)
{
- *addr = ctx->ns_addr;
- *addrlen = ctx->ns_addrlen;
+ struct gwp_dns_entry *e, *next;
+
+ for (e = head; e; e = next) {
+ next = e->next;
+ gwp_dns_entry_put(e);
+ }
}
-__attribute__((unused))
-static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockaddr *gs,
+static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs,
uint32_t rt)
{
- struct gwdns_addrinfo_node *ai;
+ struct addrinfo *ai;
- assert(res);
+ if (!res)
+ return false;
/*
* Handle IPV4_ONLY and IPV6_ONLY cases together.
@@ -67,9 +84,9 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad
if (ai->ai_family != fm)
continue;
if (fm == AF_INET)
- gs->i4 = ai->ai_addr.i4;
+ gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
else
- gs->i6 = ai->ai_addr.i6;
+ gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
return true;
}
return false;
@@ -83,19 +100,19 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad
int prm = (rt == GWP_DNS_RESTYP_PREFER_IPV6) ? AF_INET6
: AF_INET;
int sec = (prm == AF_INET6) ? AF_INET : AF_INET6;
- struct gwp_sockaddr *fallback = NULL;
+ struct sockaddr *fallback = NULL;
for (ai = res; ai; ai = ai->ai_next) {
if (ai->ai_family != prm) {
if (ai->ai_family == sec && !fallback)
- fallback = &ai->ai_addr;
+ fallback = ai->ai_addr;
continue;
}
if (prm == AF_INET)
- gs->i4 = ai->ai_addr.i4;
+ gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
else
- gs->i6 = ai->ai_addr.i6;
+ gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
return true;
}
@@ -103,9 +120,9 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad
return false;
if (sec == AF_INET)
- gs->i4 = fallback->i4;
+ gs->i4 = *(struct sockaddr_in *)fallback;
else
- gs->i6 = fallback->i6;
+ gs->i6 = *(struct sockaddr_in6 *)fallback;
return true;
}
@@ -115,10 +132,10 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad
*/
for (ai = res; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET) {
- gs->i4 = ai->ai_addr.i4;
+ gs->i4 = *(struct sockaddr_in *)ai->ai_addr;
return true;
} else if (ai->ai_family == AF_INET6) {
- gs->i6 = ai->ai_addr.i6;
+ gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr;
return true;
}
}
@@ -126,7 +143,19 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad
return false;
}
-__attribute__((unused))
+static void prep_hints(struct addrinfo *hints, uint32_t restyp)
+{
+ memset(hints, 0, sizeof(*hints));
+ hints->ai_family = AF_UNSPEC;
+ hints->ai_socktype = SOCK_STREAM;
+ hints->ai_flags = AI_ADDRCONFIG;
+
+ if (restyp == GWP_DNS_RESTYP_IPV4_ONLY)
+ hints->ai_family = AF_INET;
+ else if (restyp == GWP_DNS_RESTYP_IPV6_ONLY)
+ hints->ai_family = AF_INET6;
+}
+
static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
const struct addrinfo *ai)
{
@@ -139,17 +168,36 @@ static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name,
gwp_dns_cache_insert(ctx->cache, name, ai, time(NULL) + x);
}
-// static int gwp_dns_find_preferred_addr(struct gwp_dns_ctx *ctx, struct gwdns_addrinfo_node *ai, const char *name,
-// struct gwp_sockaddr *addr, uint32_t restyp)
-// {
-// bool found;
+int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name,
+ const char *service, struct gwp_sockaddr *addr,
+ uint32_t restyp)
+{
+ struct addrinfo *res = NULL, hints;
+ bool found;
+ int r;
+
+ prep_hints(&hints, restyp);
+ r = getaddrinfo(name, service, &hints, &res);
+ if (r)
+ return -r;
+
+ found = iterate_addr_list(res, addr, restyp);
+ if (found)
+ try_pass_result_to_cache(ctx, name, res);
+
+ if (res)
+ freeaddrinfo(res);
+
+ return found ? 0 : -EHOSTUNREACH;
+}
-// found = iterate_addr_list(ai, addr, restyp);
-// if (found)
-// try_pass_result_to_cache(ctx, name, ai);
+#ifdef CONFIG_RAW_DNS
-// return found ? 0 : -EHOSTUNREACH;
-// }
+void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen)
+{
+ *addr = ctx->ns_addr;
+ *addrlen = ctx->ns_addrlen;
+}
static void _gwp_dns_entry_free(struct gwp_dns_entry *e)
{
@@ -160,7 +208,7 @@ static void _gwp_dns_entry_free(struct gwp_dns_entry *e)
free(e);
}
-void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
+void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
{
struct gwp_dns_entry *new_e;
@@ -175,6 +223,70 @@ void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
_gwp_dns_entry_free(e);
}
+static void free_all_queued_entries(struct gwp_dns_ctx *ctx)
+{
+ uint32_t i;
+ for (i = 0; i < ctx->nr_entries; i++) {
+ struct gwp_dns_entry *e = ctx->entries[i];
+ _gwp_dns_entry_free(e);
+ }
+
+ free(ctx->entries);
+}
+
+static bool realloc_entries(struct gwp_dns_ctx *ctx)
+{
+ struct gwp_dns_entry **tmp;
+ int new_cap;
+
+ new_cap = ctx->entry_cap * 2;
+ tmp = realloc(ctx->entries, new_cap * sizeof(*tmp));
+ if (!tmp)
+ return 1;
+
+ ctx->entries = tmp;
+ ctx->entry_cap = new_cap;
+
+ return 0;
+}
+
+int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
+{
+ struct gwdns_addrinfo_node *ai;
+ uint8_t buff[UDP_MSG_LIMIT];
+ ssize_t r;
+
+ r = __sys_recvfrom(
+ e->udp_fd, buff, sizeof(buff), 0,
+ &ctx->ns_addr.sa, (socklen_t *)&ctx->ns_addrlen
+ );
+ if (r <= 0)
+ return (int)r;
+
+ r = gwdns_parse_query(e->txid, e->service, buff, r, &ai);
+ if (r)
+ goto exit_free_ai;
+
+ e->addr = ai->ai_addr;
+ // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp);
+
+exit_free_ai:
+ gwdns_free_parsed_query(ai);
+ return (int)r;
+}
+#endif /* #ifndef CONFIG_RAW_DNS */
+
+static void gwp_dns_entry_free(struct gwp_dns_entry *e)
+{
+ if (!e)
+ return;
+
+ assert(e->ev_fd >= 0);
+ close(e->ev_fd);
+ free(e->name);
+ free(e);
+}
+
/*
* Must be called with ctx->lock held. May release the lock, but it
* will reacquire it before returning.
@@ -185,7 +297,6 @@ void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
* cache to find expired entries and delete them.
* - Save the current time as the last scan time.
*/
-__attribute__((unused))
static void cond_scan_cache(struct gwp_dns_ctx *ctx)
{
if (!ctx->cache)
@@ -206,6 +317,366 @@ static void cond_scan_cache(struct gwp_dns_ctx *ctx)
ctx->last_scan = time(NULL);
}
+/*
+ * Must be called with ctx->lock held.
+ */
+static void wait_for_queue_entry(struct gwp_dns_ctx *ctx)
+{
+ ctx->nr_sleeping++;
+ pthread_cond_wait(&ctx->cond, &ctx->lock);
+ ctx->nr_sleeping--;
+ cond_scan_cache(ctx);
+}
+
+#ifdef CONFIG_HAVE_GETADDRINFO_A
+/*
+ * Must be called with ctx->lock held.
+ */
+static struct gwp_dns_entry *unplug_queue_list(struct gwp_dns_ctx *ctx)
+{
+ struct gwp_dns_entry *head = ctx->head;
+
+ ctx->head = ctx->tail = NULL;
+ ctx->nr_entries = 0;
+ return head;
+}
+
+struct dbq_entry {
+ struct gwp_dns_entry *e;
+ struct gaicb cb;
+};
+
+struct dns_batch_query {
+ struct dbq_entry *entries;
+ struct gaicb **reqs;
+ struct addrinfo hints;
+ uint32_t nr_entries;
+ uint32_t cap;
+};
+
+static void dbq_free(struct dns_batch_query *dbq)
+{
+ uint32_t i;
+
+ if (!dbq)
+ return;
+
+ if (dbq->reqs) {
+ for (i = 0; i < dbq->nr_entries; i++) {
+ if (dbq->reqs[i]->ar_result)
+ freeaddrinfo(dbq->reqs[i]->ar_result);
+ }
+ }
+
+ free(dbq->entries);
+ free(dbq->reqs);
+ free(dbq);
+}
+
+static int dbq_add_entry(struct dns_batch_query *dbq, struct gwp_dns_entry *e)
+{
+ struct dbq_entry *de;
+
+ if (dbq->nr_entries >= dbq->cap) {
+ uint32_t new_cap = dbq->cap ? dbq->cap * 2 : 16;
+ struct dbq_entry *nentries;
+
+ nentries = realloc(dbq->entries, new_cap * sizeof(*nentries));
+ if (!nentries)
+ return -ENOMEM;
+ dbq->entries = nentries;
+ dbq->cap = new_cap;
+ }
+
+ de = &dbq->entries[dbq->nr_entries];
+ de->e = e;
+ memset(&de->cb, 0, sizeof(de->cb));
+ de->cb.ar_name = e->name;
+ de->cb.ar_service = e->service;
+ de->cb.ar_request = &dbq->hints;
+ de->cb.ar_result = NULL;
+ dbq->nr_entries++;
+
+ return 0;
+}
+
+static int collect_active_queries(struct gwp_dns_ctx *ctx,
+ struct gwp_dns_entry **head_p,
+ struct dns_batch_query **dbq_p)
+{
+ struct gwp_dns_entry *e, *next, *prev = NULL, *head = *head_p;
+ struct dns_batch_query *dbq;
+
+ dbq = calloc(1, sizeof(*dbq));
+ if (!dbq)
+ return -ENOMEM;
+
+ assert(head);
+ prep_hints(&dbq->hints, ctx->cfg.restyp);
+ for (e = head; e; e = next) {
+ int x = atomic_load(&e->refcnt);
+ next = e->next;
+ if (x > 1) {
+
+ if (dbq_add_entry(dbq, e)) {
+ dbq_free(dbq);
+ return -ENOMEM;
+ }
+
+ prev = e;
+ continue;
+ }
+
+ assert(x == 1);
+ /*
+ * If the refcnt is 1, it means we are the last reference
+ * to this entry. The client no longer cares about the
+ * result. We can free it immediately. No need to resolve
+ * the query nor to signal the eventfd.
+ */
+ if (prev)
+ prev->next = next;
+ else
+ head = next;
+
+ gwp_dns_entry_free(e);
+ }
+
+ *head_p = head;
+ *dbq_p = dbq;
+ return 0;
+}
+
+static void dispatch_batch_result(int r, struct gwp_dns_ctx *ctx,
+ struct dns_batch_query *dbq,
+ uint32_t restyp)
+{
+ struct gwp_dns_entry *e;
+ struct addrinfo *ai;
+ uint32_t i;
+
+ for (i = 0; i < dbq->nr_entries; i++) {
+ e = dbq->entries[i].e;
+ ai = dbq->reqs[i]->ar_result;
+
+ if (!r) {
+ e->res = gai_error(dbq->reqs[i]);
+ if (!e->res) {
+ if (!iterate_addr_list(ai, &e->addr, restyp))
+ e->res = -EHOSTUNREACH;
+ }
+ } else {
+ e->res = r;
+ }
+
+ eventfd_write(e->ev_fd, 1);
+ if (!e->res)
+ try_pass_result_to_cache(ctx, e->name, ai);
+ }
+}
+
+/*
+ * Filling dbq->reqs[n] cannot be done in dbq_add_entry() because
+ * the reallocation of dbq->entries may change the address of
+ * dbq->entries[n].cb.
+ */
+static int prep_reqs(struct dns_batch_query *dbq)
+{
+ uint32_t i;
+
+ dbq->reqs = malloc(dbq->nr_entries * sizeof(*dbq->reqs));
+ if (!dbq->reqs)
+ return -ENOMEM;
+
+ for (i = 0; i < dbq->nr_entries; i++)
+ dbq->reqs[i] = &dbq->entries[i].cb;
+
+ return 0;
+}
+
+/*
+ * Must be called with ctx->lock held. May release the lock, but
+ * it will reacquire it before returning.
+ */
+static void process_queue_entry_batch(struct gwp_dns_ctx *ctx)
+{
+ struct gwp_dns_entry *head = unplug_queue_list(ctx);
+ struct dns_batch_query *dbq = NULL;
+
+ if (!head)
+ return;
+
+ pthread_mutex_unlock(&ctx->lock);
+
+ if (!collect_active_queries(ctx, &head, &dbq)) {
+ if (!prep_reqs(dbq)) {
+ struct sigevent ev;
+ int r;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.sigev_notify = SIGEV_NONE;
+ r = getaddrinfo_a(GAI_WAIT, dbq->reqs, dbq->nr_entries, &ev);
+ dispatch_batch_result(r, ctx, dbq, ctx->cfg.restyp);
+ }
+ }
+
+ dbq_free(dbq);
+ put_all_entries(head);
+ pthread_mutex_lock(&ctx->lock);
+}
+#endif /* #ifdef CONFIG_HAVE_GETADDRINFO_A */
+
+/*
+ * Must be called with ctx->lock held. May release the lock, but
+ * it will reacquire it before returning.
+ */
+static void process_queue_entry_single(struct gwp_dns_ctx *ctx)
+{
+ struct gwp_dns_entry *e = ctx->head;
+
+ if (!e)
+ return;
+
+ e = ctx->head;
+ ctx->head = e->next;
+ if (!ctx->head) {
+ ctx->tail = NULL;
+ assert(ctx->nr_entries == 1);
+ }
+
+ ctx->nr_entries--;
+ pthread_mutex_unlock(&ctx->lock);
+
+ if (atomic_load(&e->refcnt) == 1) {
+ /*
+ * If the refcnt is 1, it means we are the last reference
+ * to this entry. The client no longer cares about the
+ * result. We can free it immediately. No need to resolve
+ * the query nor to signal the eventfd.
+ */
+ gwp_dns_entry_free(e);
+ goto out;
+ }
+
+ e->res = gwp_dns_resolve(ctx, e->name, e->service, &e->addr, ctx->cfg.restyp);
+ eventfd_write(e->ev_fd, 1);
+ gwp_dns_entry_put(e);
+out:
+ pthread_mutex_lock(&ctx->lock);
+}
+
+/*
+ * Must be called with ctx->lock held. May release the lock, but
+ * it will reacquire it before returning.
+ */
+static void process_queue_entry(struct gwp_dns_ctx *ctx)
+{
+ /*
+ * There are two cases here:
+ *
+ * 1. All of the DNS threads are busy, and there are still a lot
+ * of queued entries. Process them in batch via getaddrinfo_a().
+ *
+ * 2. The number of threads is sufficient to handle the queued
+ * entries, so process them one by one.
+ *
+ * Why not always getaddrinfo_a()? Because getaddrinfo_a() has
+ * a higher overhead than processing entries individually as it
+ * will spawn a new thread for each query. Don't bother invoking
+ * clone() for each entry if we can process them in the current
+ * thread.
+ */
+#ifdef CONFIG_HAVE_GETADDRINFO_A
+ if (ctx->nr_entries > (ctx->nr_sleeping + 16)) {
+ process_queue_entry_batch(ctx);
+ return;
+ }
+#endif
+
+ process_queue_entry_single(ctx);
+}
+
+static void *gwp_dns_thread_entry(void *arg)
+{
+ struct gwp_dns_wrk *w = arg;
+ struct gwp_dns_ctx *ctx = w->ctx;
+
+ pthread_mutex_lock(&ctx->lock);
+ while (!ctx->should_stop) {
+ if (ctx->head)
+ process_queue_entry(ctx);
+ else
+ wait_for_queue_entry(ctx);
+ }
+ pthread_mutex_unlock(&ctx->lock);
+
+ return NULL;
+}
+
+static void free_worker(struct gwp_dns_wrk *w)
+{
+ struct gwp_dns_ctx *ctx;
+
+ if (!w)
+ return;
+
+ ctx = w->ctx;
+ pthread_mutex_lock(&ctx->lock);
+ ctx->should_stop = true;
+ pthread_cond_broadcast(&ctx->cond);
+ pthread_mutex_unlock(&ctx->lock);
+ pthread_join(w->thread, NULL);
+}
+
+static void free_workers(struct gwp_dns_ctx *ctx)
+{
+ uint32_t i;
+
+ if (!ctx->workers)
+ return;
+
+ for (i = 0; i < ctx->cfg.nr_workers; i++)
+ free_worker(&ctx->workers[i]);
+
+ free(ctx->workers);
+ ctx->workers = NULL;
+}
+
+static int init_workers(struct gwp_dns_ctx *ctx)
+{
+ struct gwp_dns_wrk *workers, *w;
+ uint32_t i;
+ int r;
+
+ if (ctx->cfg.nr_workers == 0)
+ return -EINVAL;
+
+ workers = calloc(ctx->cfg.nr_workers, sizeof(*workers));
+ if (!workers)
+ return -ENOMEM;
+
+ ctx->workers = workers;
+ for (i = 0; i < ctx->cfg.nr_workers; i++) {
+ w = &workers[i];
+ w->ctx = ctx;
+ w->id = i;
+ r = pthread_create(&w->thread, NULL, gwp_dns_thread_entry, w);
+ if (r) {
+ r = -r;
+ goto out_err;
+ }
+ }
+
+ return 0;
+
+out_err:
+ while (i--)
+ free_worker(&workers[i]);
+ free(workers);
+ ctx->workers = NULL;
+ return r;
+}
+
static bool fetch_i4(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr,
uint16_t port)
{
@@ -342,26 +813,49 @@ int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg)
goto out_free_ctx;
}
- r = init_cache(ctx);
- if (r)
- goto out_destroy_mutex;
-
- r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53);
+ if (cfg->use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ // r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53);
+ r = convert_str_to_ssaddr("1.1.1.1", &ctx->ns_addr, 53);
if (r)
goto out_destroy_mutex;
ctx->ns_addrlen = ctx->ns_addr.sa.sa_family == AF_INET
? sizeof(ctx->ns_addr.i4)
: sizeof(ctx->ns_addr.i6);
- ctx->should_stop = false;
- ctx->last_scan = time(NULL);
- ctx->nr_entries = 0;
ctx->entry_cap = DEFAULT_ENTRIES_CAP;
ctx->entries = malloc(ctx->entry_cap * sizeof(*ctx->entries));
if (!ctx->entries)
goto out_destroy_mutex;
+#endif
+ } else {
+ r = pthread_cond_init(&ctx->cond, NULL);
+ if (r) {
+ r = -r;
+ goto out_destroy_mutex;
+ }
+
+ ctx->nr_sleeping = 0;
+ ctx->workers = NULL;
+ ctx->head = NULL;
+ ctx->tail = NULL;
+ r = init_workers(ctx);
+ if (r)
+ goto out_destroy_cond;
+ }
+ ctx->nr_entries = 0;
+
+ r = init_cache(ctx);
+ if (r)
+ goto out_destroy_cond;
+
+ ctx->should_stop = false;
+ ctx->last_scan = time(NULL);
*ctx_p = ctx;
return 0;
+out_destroy_cond:
+ if (!cfg->use_raw_dns)
+ pthread_cond_destroy(&ctx->cond);
out_destroy_mutex:
pthread_mutex_destroy(&ctx->lock);
out_free_ctx:
@@ -370,56 +864,63 @@ out_free_ctx:
return r;
}
-static void free_all_queued_entries(struct gwp_dns_ctx *ctx)
+static void put_all_queued_entries(struct gwp_dns_ctx *ctx)
{
- int i;
- for (i = 0; i < ctx->nr_entries; i++) {
- struct gwp_dns_entry *e = ctx->entries[i];
- _gwp_dns_entry_free(e);
- }
-
- free(ctx->entries);
+ put_all_entries(ctx->head);
+ ctx->head = ctx->tail = NULL;
}
void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx)
{
- pthread_mutex_destroy(&ctx->lock);
+ if (ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
free_all_queued_entries(ctx);
+#endif
+ } else {
+ free_workers(ctx);
+ pthread_cond_destroy(&ctx->cond);
+ put_all_queued_entries(ctx);
+ }
+ pthread_mutex_destroy(&ctx->lock);
free_cache(ctx->cache);
free(ctx);
}
-static bool realloc_entries(struct gwp_dns_ctx *ctx)
+static void push_queue(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
{
- struct gwp_dns_entry **tmp;
- int new_cap;
-
- new_cap = ctx->entry_cap * 2;
- tmp = realloc(ctx->entries, new_cap * sizeof(*tmp));
- if (!tmp)
- return 1;
-
- ctx->entries = tmp;
- ctx->entry_cap = new_cap;
+ pthread_mutex_lock(&ctx->lock);
+ if (ctx->tail)
+ ctx->tail->next = e;
+ else
+ ctx->head = e;
+ ctx->tail = e;
+ e->next = NULL;
- return 0;
+ ctx->nr_entries++;
+ if (ctx->nr_sleeping)
+ pthread_cond_signal(&ctx->cond);
+ pthread_mutex_unlock(&ctx->lock);
}
struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
const char *name, const char *service)
{
struct gwp_dns_entry *e;
- uint16_t txid;
size_t nl, sl;
+#ifdef CONFIG_RAW_DNS
+ uint16_t txid;
ssize_t r;
-
- if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx))
- return NULL;
+#endif
e = malloc(sizeof(*e));
if (!e)
return NULL;
+ if (ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx))
+ return NULL;
+
r = __sys_socket(ctx->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0);
if (r < 0)
goto out_free_e;
@@ -431,6 +932,12 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
if (r < 0)
goto out_free_e;
e->payloadlen = (int)r;
+#endif
+ } else {
+ e->ev_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (e->ev_fd < 0)
+ goto out_free_e;
+ }
/*
* Merge name and service into a single allocated string to
@@ -443,7 +950,7 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
sl = service ? strlen(service) : 0;
e->name = malloc(nl + 1 + sl + 1);
if (!e->name)
- goto out_free_e;
+ goto out_close_fd;
e->service = e->name + nl + 1;
memcpy(e->name, name, nl + 1);
@@ -453,38 +960,43 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
e->service[0] = '\0';
e->res = 0;
- e->idx = ctx->nr_entries++;
- ctx->entries[e->idx] = e;
-
+ if (ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ e->idx = ctx->nr_entries++;
+ ctx->entries[e->idx] = e;
+#endif
+ } else {
+ atomic_init(&e->refcnt, 2);
+ push_queue(ctx, e);
+ }
return e;
+out_close_fd:
+ if (ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ close(e->udp_fd);
+#endif
+ } else {
+ close(e->ev_fd);
+ }
out_free_e:
free(e);
return NULL;
}
-int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
+bool gwp_dns_entry_put(struct gwp_dns_entry *e)
{
- struct gwdns_addrinfo_node *ai;
- uint8_t buff[UDP_MSG_LIMIT];
- ssize_t r;
-
- r = __sys_recvfrom(
- e->udp_fd, buff, sizeof(buff), 0,
- &ctx->ns_addr.sa, (socklen_t *)&ctx->ns_addrlen
- );
- if (r <= 0)
- return (int)r;
+ int x;
- r = gwdns_parse_query(e->txid, e->service, buff, r, &ai);
- if (r)
- goto exit_free_ai;
+ if (!e)
+ return false;
- e->addr = ai->ai_addr;
- // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp);
+ x = atomic_fetch_sub(&e->refcnt, 1);
+ assert(x > 0);
+ if (x == 1) {
+ gwp_dns_entry_free(e);
+ return true;
+ }
-exit_free_ai:
- gwdns_free_parsed_query(ai);
- return (int)r;
+ return false;
}
-
diff --git a/src/gwproxy/dns.h b/src/gwproxy/dns.h
index 12d21552c92e..410989f6414c 100644
--- a/src/gwproxy/dns.h
+++ b/src/gwproxy/dns.h
@@ -14,17 +14,22 @@
#include <gwproxy/syscall.h>
struct gwp_dns_entry {
- int idx;
- char *name;
- char *service;
- int res;
+#ifdef CONFIG_RAW_DNS
+ uint32_t idx;
int udp_fd;
- struct gwp_sockaddr addr;
int payloadlen;
union {
uint16_t txid;
uint8_t payload[UDP_MSG_LIMIT];
};
+#endif
+ char *name;
+ char *service;
+ _Atomic(int) refcnt;
+ int res;
+ int ev_fd;
+ struct gwp_sockaddr addr;
+ struct gwp_dns_entry *next;
};
enum {
@@ -40,8 +45,11 @@ enum {
struct gwp_dns_cfg {
int cache_expiry; /* In seconds. <= 0 to disable cache. */
uint32_t nr_workers;
- const char *ns_addr_str;
uint32_t restyp;
+ bool use_raw_dns;
+#ifdef CONFIG_RAW_DNS
+ const char *ns_addr_str;
+#endif
};
struct gwp_dns_ctx;
@@ -71,7 +79,7 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx);
/**
* Queue a DNS resolution request. It returns a pointer to a gwp_dns_entry
* with eventfd set to a valid file descriptor that can be used to wait for
- * the resolution result. The caller's responsible to call gwp_dns_entry_free()
+ * the resolution result. The caller's responsible to call gwp_dns_entry_put()
* to release the entry when it is no longer needed.
*
* The returned eventfd file descriptor is non-blocking.
@@ -83,12 +91,24 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx);
*/
struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
const char *name, const char *service);
+/**
+ * Release a DNS entry. This function decrements the reference count of the
+ * entry. If the reference count reaches zero, the entry is freed.
+ *
+ * @param entry Pointer to the DNS entry to release. If the entry is
+ * NULL, this function does nothing.
+ * @return True if the entry was freed, false otherwise.
+ */
+bool gwp_dns_entry_put(struct gwp_dns_entry *entry);
+
+#ifdef CONFIG_RAW_DNS
void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen);
-void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e);
+void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e);
int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e);
+#endif
/**
* Lookup a DNS entry in the cache. If the entry is found, it fills the
diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c
index 5e117be2ae8f..a4ea80359236 100644
--- a/src/gwproxy/ev/epoll.c
+++ b/src/gwproxy/ev/epoll.c
@@ -155,7 +155,7 @@ static int free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
int r;
if (gde) {
- r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->udp_fd, NULL);
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
if (unlikely(r))
return r;
}
@@ -782,36 +782,43 @@ static int arm_poll_for_dns_query(struct gwp_wrk *w,
struct gwp_conn_pair *gcp)
{
struct gwp_dns_entry *gde = gcp->gde;
- struct gwp_sockaddr addr;
- struct gwp_dns_ctx *dctx;
struct epoll_event ev;
- uint8_t addrlen;
ssize_t r;
assert(gde);
- dctx = w->ctx->dns;
+ ev.events = EPOLLIN;
+ ev.data.u64 = 0;
+ ev.data.ptr = gcp;
+ ev.data.u64 |= EV_BIT_DNS_QUERY;
+
+ if (w->ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ struct gwp_dns_ctx *dctx;
+ struct gwp_sockaddr addr;
+ uint8_t addrlen;
+
+ dctx = w->ctx->dns;
cp_nsaddr(dctx, &addr, &addrlen);
r = __sys_sendto(
gde->udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL,
&addr.sa, addrlen
);
if (unlikely(r < 0))
- goto exit_close;
-
- ev.events = EPOLLIN;
- ev.data.u64 = 0;
- ev.data.ptr = gcp;
- ev.data.u64 |= EV_BIT_DNS_QUERY;
+ return (int)r;
r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->udp_fd, &ev);
if (unlikely(r))
- goto exit_close;
+ return (int)r;
+#endif
+ } else {
+ assert(gde->ev_fd >= 0);
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->ev_fd, &ev);
+ if (unlikely(r))
+ return (int)r;
+ }
return 0;
-exit_close:
- close(gde->udp_fd);
- return (int)r;
}
static void log_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp,
@@ -844,9 +851,18 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
assert(ct == CONN_STATE_SOCKS5_DNS_QUERY ||
ct == CONN_STATE_HTTP_DNS_QUERY);
- r = gwp_dns_process(w->ctx->dns, gde);
- if (r)
- gde->res = r;
+ if (w->ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ r = gwp_dns_process(w->ctx->dns, gde);
+ if (r)
+ gde->res = r;
+#endif
+ } else {
+ assert(gde->ev_fd >= 0);
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
+ if (unlikely(r))
+ return r;
+ }
log_dns_query(w, gcp, gde);
if (likely(!gde->res)) {
@@ -858,8 +874,14 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
else
r = -EIO;
}
-
- gwp_dns_entry_free(w->ctx->dns, gde);
+
+ if (w->ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ gwp_dns_raw_entry_free(w->ctx->dns, gde);
+#endif
+ } else {
+ gwp_dns_entry_put(gde);
+ }
gcp->gde = NULL;
return r;
}
diff --git a/src/gwproxy/ev/io_uring.c b/src/gwproxy/ev/io_uring.c
index 9cb06b1214ca..78440935383b 100644
--- a/src/gwproxy/ev/io_uring.c
+++ b/src/gwproxy/ev/io_uring.c
@@ -675,13 +675,13 @@ static int prep_domain_resolution(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
assert(gde);
s = get_sqe_nofail(w);
- io_uring_prep_poll_add(s, gde->udp_fd, POLLIN);
+ io_uring_prep_poll_add(s, gde->ev_fd, POLLIN);
io_uring_sqe_set_data(s, gcp);
s->user_data |= EV_BIT_IOU_DNS_QUERY;
get_gcp(gcp);
pr_dbg(&ctx->lh,
"Prepared DNS query for domain '%s' (fd=%d, idx=%u, ref_cnt=%d)",
- gde->name, gde->udp_fd, gcp->idx, gcp->ref_cnt);
+ gde->name, gde->ev_fd, gcp->idx, gcp->ref_cnt);
return 0;
}
@@ -741,7 +741,7 @@ static int handle_ev_dns_query(struct gwp_wrk *w, void *udata)
gde->name, ip_to_str(&gcp->target_addr), gcp->target.fd,
gcp->idx);
- gwp_dns_entry_free(ctx->dns, gde);
+ gwp_dns_entry_put(gde);
gcp->gde = NULL;
return handle_socks5_connect_target(w, gcp);
}
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index d0a010ab4b27..11efc21f4c5c 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -44,6 +44,7 @@
static const struct option long_opts[] = {
{ "help", no_argument, NULL, 'h' },
+ { "raw-dns", required_argument, NULL, 'r' },
{ "event-loop", required_argument, NULL, 'e' },
{ "bind", required_argument, NULL, 'b' },
{ "target", required_argument, NULL, 't' },
@@ -73,6 +74,7 @@ static const struct gwp_cfg default_opts = {
.event_loop = "epoll",
.bind = "[::]:1080",
.target = NULL,
+ .use_raw_dns = false,
.as_socks5 = false,
.as_http = false,
.socks5_prefer_ipv6 = false,
@@ -102,6 +104,7 @@ static void show_help(const char *app)
printf(" -h, --help Show this help message and exit\n");
printf(" -e, --event-loop=name Specify the event loop to use (default: %s)\n", default_opts.event_loop);
printf(" Available values: epoll, io_uring\n");
+ printf(" -r, --raw-dns=0|1 Use experimental raw DNS as the backend (default: %d)\n", default_opts.use_raw_dns);
printf(" -b, --bind=addr:port Bind to the specified address (default: %s)\n", default_opts.bind);
printf(" -t, --target=addr_port Target address to connect to\n");
printf(" -S, --as-socks5=0|1 Run as a SOCKS5 proxy (default: %d)\n", default_opts.as_socks5);
@@ -156,6 +159,9 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg)
case 'h':
show_help(argv[0]);
exit(0);
+ case 'r':
+ cfg->use_raw_dns = !!atoi(optarg);
+ break;
case 'e':
cfg->event_loop = optarg;
break;
@@ -683,13 +689,21 @@ static int gwp_ctx_init_dns(struct gwp_ctx *ctx)
{
struct gwp_cfg *cfg = &ctx->cfg;
const struct gwp_dns_cfg dns_cfg = {
+ .use_raw_dns = cfg->use_raw_dns,
.cache_expiry = cfg->socks5_dns_cache_secs,
.restyp = cfg->socks5_prefer_ipv6 ? GWP_DNS_RESTYP_PREFER_IPV6 : 0,
.nr_workers = 1,
- .ns_addr_str = "1.1.1.1"
+ // .ns_addr_str = "1.1.1.1"
};
int r;
+ if (cfg->use_raw_dns) {
+#ifndef CONFIG_RAW_DNS
+ pr_err(&ctx->lh, "raw DNS backend is not enabled in this build");
+ return -ENOSYS;
+#endif
+ }
+
if (!cfg->as_socks5 && !cfg->as_http) {
ctx->dns = NULL;
return 0;
@@ -728,6 +742,10 @@ static int gwp_ctx_parse_ev(struct gwp_ctx *ctx)
ctx->ev_used = GWP_EV_EPOLL;
pr_dbg(&ctx->lh, "Using event loop: epoll");
} else if (!strcmp(ev, "io_uring") || !strcmp(ev, "iou")) {
+ if (ctx->cfg.use_raw_dns) {
+ pr_err(&ctx->lh, "raw DNS backend is not supported for io_uring yet");
+ return -ENOSYS;
+ }
ctx->ev_used = GWP_EV_IO_URING;
pr_dbg(&ctx->lh, "Using event loop: io_uring");
} else {
@@ -985,8 +1003,15 @@ int gwp_free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
if (gcp->timer_fd >= 0)
__sys_close(gcp->timer_fd);
- if (gcp->gde)
- gwp_dns_entry_free(w->ctx->dns, gcp->gde);
+ if (gcp->gde) {
+ if (w->ctx->cfg.use_raw_dns) {
+#ifdef CONFIG_RAW_DNS
+ gwp_dns_raw_entry_free(w->ctx->dns, gcp->gde);
+#endif
+ } else {
+ gwp_dns_entry_put(gcp->gde);
+ }
+ }
switch (gcp->prot_type) {
case GWP_PROT_TYPE_SOCKS5:
diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h
index 0206e68e7481..7f276d329e78 100644
--- a/src/gwproxy/gwproxy.h
+++ b/src/gwproxy/gwproxy.h
@@ -24,6 +24,7 @@ struct gwp_cfg {
const char *event_loop;
const char *bind;
const char *target;
+ bool use_raw_dns;
bool as_socks5;
bool as_http;
bool socks5_prefer_ipv6;
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 09/11] test: revert DNS test-case
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (7 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 08/11] dns: revert removed DNS code and disable raw DNS by default Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 10/11] gwproxy: Add DNS server option Ahmad Gani
` (3 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
The raw DNS test case will be added later once we've solved the known problem.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/ev/epoll.c | 1 -
src/gwproxy/tests/dns.c | 110 +++++++++++++++++++++++-----------------
2 files changed, 63 insertions(+), 48 deletions(-)
diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c
index a4ea80359236..cd8fd8752473 100644
--- a/src/gwproxy/ev/epoll.c
+++ b/src/gwproxy/ev/epoll.c
@@ -847,7 +847,6 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
int r, ct = gcp->conn_state;
assert(gde);
- assert(gde->ev_fd >= 0);
assert(ct == CONN_STATE_SOCKS5_DNS_QUERY ||
ct == CONN_STATE_HTTP_DNS_QUERY);
diff --git a/src/gwproxy/tests/dns.c b/src/gwproxy/tests/dns.c
index 130ccfec59b6..7735d24afdd2 100644
--- a/src/gwproxy/tests/dns.c
+++ b/src/gwproxy/tests/dns.c
@@ -20,30 +20,25 @@ struct req_template {
const char *domain, *service;
};
-struct poll_map {
- struct gwp_dns_entry *e;
- int fd;
-};
-
static const struct req_template req_template[] = {
+ { "localhost", "80" },
{ "facebook.com", "80" },
+ { "google.com", "443" },
+ { "github.com", "443" },
+ { "example.com", "80" },
+ { "twitter.com", "443" },
+ { "reddit.com", "80" },
+ { "youtube.com", "443" },
+ { "wikipedia.org", "80" },
+ { "stackoverflow.com", "443" },
+ { "amazon.com", "80" },
+ { "microsoft.com", "443" },
+ { "apple.com", "80" },
+ { "linkedin.com", "443" },
+ { "bing.com", "80" },
};
-static struct gwp_dns_entry *find_item(struct poll_map *map, int n, int fd)
-{
- struct gwp_dns_entry *e;
- int i;
-
- e = NULL;
- for (i = 0; i < n; i++) {
- if (map[i].fd == fd)
- e = map[i].e;
- }
-
- return e;
-}
-
-static int poll_all_in(struct gwp_dns_ctx *ctx, struct poll_map *map, struct pollfd *pfd, int n, int timeout)
+static int poll_all_in(struct pollfd *pfd, int n, int timeout)
{
int ret, i, t = 0;
@@ -59,12 +54,7 @@ static int poll_all_in(struct gwp_dns_ctx *ctx, struct poll_map *map, struct pol
}
for (i = 0; i < n; i++) {
- struct gwp_dns_entry *e;
- if (pfd[i].revents & POLLIN) {
- e = find_item(map, n, pfd[i].fd);
- assert(e);
- ret = gwp_dns_process(ctx, e);
- assert(!ret);
+ if (pfd[i].revents & (POLLIN | POLLERR | POLLHUP)) {
pfd[i].events = 0;
t++;
}
@@ -77,14 +67,12 @@ static int poll_all_in(struct gwp_dns_ctx *ctx, struct poll_map *map, struct pol
static void test_basic_dns_multiple_requests(void)
{
- struct gwp_dns_cfg cfg = { .nr_workers = 1, .ns_addr_str = "1.1.1.1" };
- struct poll_map pollfd_map[ARRAY_SIZE(req_template)];
+ struct gwp_dns_cfg cfg = { .nr_workers = 1 };
+ struct gwp_dns_entry *earr[ARRAY_SIZE(req_template)];
struct pollfd pfd[ARRAY_SIZE(req_template)];
- struct gwp_sockaddr addr;
struct gwp_dns_ctx *ctx;
- uint8_t addrlen;
- ssize_t r;
int i, n;
+ int r;
r = gwp_dns_ctx_init(&ctx, &cfg);
assert(!r);
@@ -93,37 +81,65 @@ static void test_basic_dns_multiple_requests(void)
n = (int)ARRAY_SIZE(req_template);
for (i = 0; i < n; i++) {
const struct req_template *rt = &req_template[i];
- struct gwp_dns_entry *e;
- e = gwp_dns_queue(ctx, rt->domain, rt->service);
- assert(e);
- assert(e->udp_fd >= 0);
- pfd[i].fd = e->udp_fd;
+ earr[i] = gwp_dns_queue(ctx, rt->domain, rt->service);
+ assert(earr[i]);
+ assert(earr[i]->ev_fd >= 0);
+ pfd[i].fd = earr[i]->ev_fd;
pfd[i].events = POLLIN;
- cp_nsaddr(ctx, &addr, &addrlen);
- r = __sys_sendto(
- e->udp_fd, e->payload, e->payloadlen, MSG_NOSIGNAL,
- &addr.sa, addrlen
- );
- assert(r > 0);
- pollfd_map[i].fd = e->udp_fd;
- pollfd_map[i].e = e;
}
- r = poll_all_in(ctx, pollfd_map, pfd, n, 5000);
+ r = poll_all_in(pfd, n, 5000);
assert(!r);
for (i = 0; i < n; i++) {
- assert(pollfd_map[i].e->res == 0);
- r = pollfd_map[i].e->addr.sa.sa_family;
+ assert(earr[i]->res == 0);
+ r = earr[i]->addr.sa.sa_family;
assert(r == AF_INET || r == AF_INET6);
}
+ for (i = 0; i < n; i++)
+ gwp_dns_entry_put(earr[i]);
+ gwp_dns_ctx_free(ctx);
+}
+
+static void test_dns_cache(void)
+{
+ struct gwp_dns_cfg cfg = { .nr_workers = 1, .cache_expiry = 10 };
+ struct gwp_sockaddr addr;
+ struct gwp_dns_ctx *ctx;
+ struct gwp_dns_entry *e;
+ struct pollfd pfd;
+ int r;
+
+ r = gwp_dns_ctx_init(&ctx, &cfg);
+ assert(!r);
+ assert(ctx != NULL);
+
+ e = gwp_dns_queue(ctx, "localhost", "80");
+ assert(e != NULL);
+ assert(e->ev_fd >= 0);
+ pfd.fd = e->ev_fd;
+ pfd.events = POLLIN;
+ r = poll_all_in(&pfd, 1, 5000);
+ assert(r == 0);
+ assert(e->res == 0);
+ r = e->addr.sa.sa_family;
+ assert(r == AF_INET || r == AF_INET6);
+ gwp_dns_entry_put(e);
+
+ r = gwp_dns_cache_lookup(ctx, "localhost", "80", &addr);
+ assert(!r);
+ r = addr.sa.sa_family;
+ assert(r == AF_INET || r == AF_INET6);
+ r = gwp_dns_cache_lookup(ctx, "aaaa.com", "80", &addr);
+ assert(r == -ENOENT);
gwp_dns_ctx_free(ctx);
}
int main(void)
{
test_basic_dns_multiple_requests();
+ test_dns_cache();
printf("All tests passed.\n");
return 0;
}
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 10/11] gwproxy: Add DNS server option
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (8 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 09/11] test: revert DNS test-case Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 11/11] dns: Add fallback mechanism for raw DNS Ahmad Gani
` (2 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
Introduce --dns-server gwproxy' cmdline option instead of hard-coded
value.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dns.c | 3 +--
src/gwproxy/gwproxy.c | 18 +++++++++++++++++-
src/gwproxy/gwproxy.h | 3 +++
3 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c
index 9bc67b083b03..1eb0d9a67229 100644
--- a/src/gwproxy/dns.c
+++ b/src/gwproxy/dns.c
@@ -815,8 +815,7 @@ int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg)
if (cfg->use_raw_dns) {
#ifdef CONFIG_RAW_DNS
- // r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53);
- r = convert_str_to_ssaddr("1.1.1.1", &ctx->ns_addr, 53);
+ r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53);
if (r)
goto out_destroy_mutex;
ctx->ns_addrlen = ctx->ns_addr.sa.sa_family == AF_INET
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index 11efc21f4c5c..36305e65c4cb 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -45,6 +45,9 @@
static const struct option long_opts[] = {
{ "help", no_argument, NULL, 'h' },
{ "raw-dns", required_argument, NULL, 'r' },
+#ifdef CONFIG_RAW_DNS
+ { "dns-server", required_argument, NULL, 'j' },
+#endif
{ "event-loop", required_argument, NULL, 'e' },
{ "bind", required_argument, NULL, 'b' },
{ "target", required_argument, NULL, 't' },
@@ -94,6 +97,9 @@ static const struct gwp_cfg default_opts = {
.log_level = 3,
.log_file = "/dev/stdout",
.pid_file = NULL,
+#ifdef CONFIG_RAW_DNS
+ .ns_addr_str = "1.1.1.1"
+#endif
};
__cold
@@ -105,6 +111,9 @@ static void show_help(const char *app)
printf(" -e, --event-loop=name Specify the event loop to use (default: %s)\n", default_opts.event_loop);
printf(" Available values: epoll, io_uring\n");
printf(" -r, --raw-dns=0|1 Use experimental raw DNS as the backend (default: %d)\n", default_opts.use_raw_dns);
+#ifdef CONFIG_RAW_DNS
+ printf(" -j, --dns-server=addr DNS server address (default: %s)\n", default_opts.ns_addr_str);
+#endif
printf(" -b, --bind=addr:port Bind to the specified address (default: %s)\n", default_opts.bind);
printf(" -t, --target=addr_port Target address to connect to\n");
printf(" -S, --as-socks5=0|1 Run as a SOCKS5 proxy (default: %d)\n", default_opts.as_socks5);
@@ -228,6 +237,11 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg)
case 'p':
cfg->pid_file = optarg;
break;
+#ifdef CONFIG_RAW_DNS
+ case 'j':
+ cfg->ns_addr_str = optarg;
+ break;
+#endif
default:
fprintf(stderr, "Unknown option: %c\n", c);
show_help(argv[0]);
@@ -693,7 +707,9 @@ static int gwp_ctx_init_dns(struct gwp_ctx *ctx)
.cache_expiry = cfg->socks5_dns_cache_secs,
.restyp = cfg->socks5_prefer_ipv6 ? GWP_DNS_RESTYP_PREFER_IPV6 : 0,
.nr_workers = 1,
- // .ns_addr_str = "1.1.1.1"
+#ifdef CONFIG_RAW_DNS
+ .ns_addr_str = cfg->ns_addr_str
+#endif
};
int r;
diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h
index 7f276d329e78..cdc5adf88ad6 100644
--- a/src/gwproxy/gwproxy.h
+++ b/src/gwproxy/gwproxy.h
@@ -44,6 +44,9 @@ struct gwp_cfg {
int log_level;
const char *log_file;
const char *pid_file;
+#ifdef CONFIG_RAW_DNS
+ const char *ns_addr_str;
+#endif
};
struct gwp_ctx;
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH gwproxy v6 11/11] dns: Add fallback mechanism for raw DNS
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (9 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 10/11] gwproxy: Add DNS server option Ahmad Gani
@ 2025-08-28 14:34 ` Ahmad Gani
2025-08-28 21:52 ` [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ammar Faizi
2025-08-28 22:23 ` Ammar Faizi
12 siblings, 0 replies; 16+ messages in thread
From: Ahmad Gani @ 2025-08-28 14:34 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
If *_PREFER_* is used in the restyp, the raw DNS backend will attempt to
retry DNS query with different address family.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
---
src/gwproxy/dns.c | 75 ++++++++++++++++++++++++++++++++----------
src/gwproxy/ev/epoll.c | 40 ++++++++++++++--------
2 files changed, 84 insertions(+), 31 deletions(-)
diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c
index 1eb0d9a67229..9f563e083fd2 100644
--- a/src/gwproxy/dns.c
+++ b/src/gwproxy/dns.c
@@ -264,11 +264,36 @@ int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e)
return (int)r;
r = gwdns_parse_query(e->txid, e->service, buff, r, &ai);
- if (r)
+ if (r) {
+ if (r == -ENODATA) {
+ uint16_t txid;
+ int af;
+
+ /* Fallback to other family if this one yield no results */
+ switch (ctx->cfg.restyp) {
+ case GWP_DNS_RESTYP_PREFER_IPV4:
+ af = AF_INET6;
+ break;
+ case GWP_DNS_RESTYP_PREFER_IPV6:
+ af = AF_INET;
+ break;
+ default:
+ goto exit_free_ai;
+ break;
+ }
+
+ txid = (uint16_t)rand();
+ r = gwdns_build_query(txid, e->name, af, e->payload, sizeof(e->payload));
+ if (r > 0) {
+ e->txid = txid;
+ e->payloadlen = (int)r;
+ r = -EAGAIN;
+ }
+ }
goto exit_free_ai;
+ }
e->addr = ai->ai_addr;
- // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp);
exit_free_ai:
gwdns_free_parsed_query(ai);
@@ -909,6 +934,7 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
#ifdef CONFIG_RAW_DNS
uint16_t txid;
ssize_t r;
+ int af;
#endif
e = malloc(sizeof(*e));
@@ -917,25 +943,38 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx,
if (ctx->cfg.use_raw_dns) {
#ifdef CONFIG_RAW_DNS
- if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx))
- return NULL;
+ if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx))
+ return NULL;
+
+ r = __sys_socket(ctx->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ if (r < 0)
+ goto out_free_e;
+ e->udp_fd = (int)r;
+
+ switch (ctx->cfg.restyp) {
+ case GWP_DNS_RESTYP_PREFER_IPV4:
+ case GWP_DNS_RESTYP_IPV4_ONLY:
+ af = AF_INET;
+ break;
+ case GWP_DNS_RESTYP_PREFER_IPV6:
+ case GWP_DNS_RESTYP_IPV6_ONLY:
+ af = AF_INET6;
+ break;
+ default:
+ assert(0);
+ break;
+ }
- r = __sys_socket(ctx->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0);
- if (r < 0)
- goto out_free_e;
- e->udp_fd = (int)r;
-
- txid = (uint16_t)rand();
- // TODO(reyuki): avoid hard-coded AF_INET and use restyp instead
- r = gwdns_build_query(txid, name, AF_INET, e->payload, sizeof(e->payload));
- if (r < 0)
- goto out_free_e;
- e->payloadlen = (int)r;
+ txid = (uint16_t)rand();
+ r = gwdns_build_query(txid, name, af, e->payload, sizeof(e->payload));
+ if (r < 0)
+ goto out_free_e;
+ e->payloadlen = (int)r;
#endif
} else {
- e->ev_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
- if (e->ev_fd < 0)
- goto out_free_e;
+ e->ev_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (e->ev_fd < 0)
+ goto out_free_e;
}
/*
diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c
index cd8fd8752473..935f7717f926 100644
--- a/src/gwproxy/ev/epoll.c
+++ b/src/gwproxy/ev/epoll.c
@@ -778,6 +778,27 @@ static int handle_connect(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
return 0;
}
+#ifdef CONFIG_RAW_DNS
+static int arm_poll_for_raw_dns_query(struct gwp_wrk *w,
+ struct gwp_conn_pair *gcp)
+{
+ struct gwp_dns_entry *gde = gcp->gde;
+ struct gwp_dns_ctx *dctx;
+ struct gwp_sockaddr addr;
+ uint8_t addrlen;
+ ssize_t r;
+
+ dctx = w->ctx->dns;
+ cp_nsaddr(dctx, &addr, &addrlen);
+ r = __sys_sendto(
+ gde->udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL,
+ &addr.sa, addrlen
+ );
+
+ return (int)r;
+}
+#endif
+
static int arm_poll_for_dns_query(struct gwp_wrk *w,
struct gwp_conn_pair *gcp)
{
@@ -794,18 +815,7 @@ static int arm_poll_for_dns_query(struct gwp_wrk *w,
if (w->ctx->cfg.use_raw_dns) {
#ifdef CONFIG_RAW_DNS
- struct gwp_dns_ctx *dctx;
- struct gwp_sockaddr addr;
- uint8_t addrlen;
-
- dctx = w->ctx->dns;
- cp_nsaddr(dctx, &addr, &addrlen);
- r = __sys_sendto(
- gde->udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL,
- &addr.sa, addrlen
- );
- if (unlikely(r < 0))
- return (int)r;
+ arm_poll_for_raw_dns_query(w, gcp);
r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->udp_fd, &ev);
if (unlikely(r))
@@ -853,7 +863,11 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
if (w->ctx->cfg.use_raw_dns) {
#ifdef CONFIG_RAW_DNS
r = gwp_dns_process(w->ctx->dns, gde);
- if (r)
+ if (r == -EAGAIN) {
+ pr_dbg(&w->ctx->lh, "DNS Fallback\n");
+ arm_poll_for_raw_dns_query(w, gcp);
+ return 0;
+ } else if (r)
gde->res = r;
#endif
} else {
--
Ahmad Gani
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (10 preceding siblings ...)
2025-08-28 14:34 ` [PATCH gwproxy v6 11/11] dns: Add fallback mechanism for raw DNS Ahmad Gani
@ 2025-08-28 21:52 ` Ammar Faizi
2025-08-28 22:23 ` Ammar Faizi
12 siblings, 0 replies; 16+ messages in thread
From: Ammar Faizi @ 2025-08-28 21:52 UTC (permalink / raw)
To: Ahmad Gani; +Cc: Ammar Faizi, Alviro Iskandar Setiawan, GNU/Weeb Mailing List
On Thu, 28 Aug 2025 21:34:22 +0700, Ahmad Gani wrote:
> This is revision v6 of the initial work on the integration of the DNS
> parser lib in gwproxy. This is an RFC draft; the patches themselves
> aren't final.
>
> This series has 11 patches; you can skip the rest and focus on
> the highlighted one:
> - add DNS server option (remove hard-coded DNS server)
> - add fallback mechanism for raw DNS
> - fix DNS parser
> - mark this feature as experimental and disabled by default
> - fix minor errors from master branch
I only take two urgent patches for now. The other are still
problematic. I'll explain it to you shortly.
Applied, thanks!
[01/11] gwproxy: Fix syntax error inside assertion
commit: 68e70946d1c3c80d8b5c40324113b20a6f81ef56
[02/11] gwproxy: Fix socks5 failure on debug mode
commit: 5a5d312aa65324dee6e08a88c5889a60124dac5a
Best regards,
--
Ammar Faizi
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
` (11 preceding siblings ...)
2025-08-28 21:52 ` [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ammar Faizi
@ 2025-08-28 22:23 ` Ammar Faizi
2025-08-29 0:54 ` Ahmad Gani
12 siblings, 1 reply; 16+ messages in thread
From: Ammar Faizi @ 2025-08-28 22:23 UTC (permalink / raw)
To: Ahmad Gani; +Cc: Alviro Iskandar Setiawan, GNU/Weeb Mailing List
On Thu, Aug 28, 2025 at 09:34:22PM +0700, Ahmad Gani wrote:
> Ahmad Gani (11):
> gwproxy: Fix syntax error inside assertion
> gwproxy: Fix socks5 failure on debug mode
> dnsparser: Add dns parser code
> dnsparser: remove unused constant
> dnsparser: Ignore CNAME if any
> dns: Remove code block related to the usage of glibc's getaddrinfo_a
> function
> dns: refactor dns.c to integrate the dns parser
> dns: revert removed DNS code and disable raw DNS by default
> test: revert DNS test-case
> gwproxy: Add DNS server option
> dns: Add fallback mechanism for raw DNS
Stop doing the same mistake again, again, and again. Don't send a patch
to fix your previous patch. I'll add unnecessary noise for the reviewers
and clutter the git history.
For example:
- You add various enums in patch #3, but you remove them in patch #4.
- You remove getaddrinfo_a() in patch #6, but you add it again in patch #8.
- You remove the DNS tests in patch #6, but your bring the tests back
in patch #9.
There are more patterns like that. Something like that should just
be f*ck*ng squashed. No need to bomb the list and reviewers with
unnecessary additions and removals which end up doing nothing because
your addtions are fully undone by your removals and vice-versa.
You should only send fix-and-revert patches to fix upstream commits,
not to fix your local commits.
--
Ammar Faizi
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy
2025-08-28 22:23 ` Ammar Faizi
@ 2025-08-29 0:54 ` Ahmad Gani
2025-08-29 1:59 ` Ammar Faizi
0 siblings, 1 reply; 16+ messages in thread
From: Ahmad Gani @ 2025-08-29 0:54 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Alviro Iskandar Setiawan, GNU/Weeb Mailing List
On Fri, Aug 29, 2025 at 5:23 AM Ammar Faizi wrote:
> Stop doing the same mistake again, again, and again. Don't send a patch
> to fix your previous patch. I'll add unnecessary noise for the reviewers
> and clutter the git history.
>
> For example:
>
> - You add various enums in patch #3, but you remove them in patch #4.
>
> - You remove getaddrinfo_a() in patch #6, but you add it again in patch #8.
>
> - You remove the DNS tests in patch #6, but your bring the tests back
> in patch #9.
>
> There are more patterns like that. Something like that should just
> be f*ck*ng squashed. No need to bomb the list and reviewers with
> unnecessary additions and removals which end up doing nothing because
> your addtions are fully undone by your removals and vice-versa.
>
> You should only send fix-and-revert patches to fix upstream commits,
> not to fix your local commits.
Sorry, I should have clarified and explained it better.
I meant to provide more context and to open a discussion about each commit
in this patch series, not to have the patches applied as-is. But I guess
you're right; if I want to do that I can point to the branch on the GitHub
repository and send a cleaned set of patches.
Now that I understand patch series are intended to be applied, and that
unnecessary context just becomes noise for people who didn't make these
modifications, I will send a cleaned patch series next time.
--
Ahmad Gani
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy
2025-08-29 0:54 ` Ahmad Gani
@ 2025-08-29 1:59 ` Ammar Faizi
0 siblings, 0 replies; 16+ messages in thread
From: Ammar Faizi @ 2025-08-29 1:59 UTC (permalink / raw)
To: Ahmad Gani; +Cc: Alviro Iskandar Setiawan, GNU/Weeb Mailing List
On Fri, Aug 29, 2025 at 07:54:40AM +0700, Ahmad Gani wrote:
> I will send a cleaned patch series next time.
Nice, thanks!
--
Ammar Faizi
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2025-08-29 1:59 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-28 14:34 [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 01/11] gwproxy: Fix syntax error inside assertion Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 02/11] gwproxy: Fix socks5 failure on debug mode Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 03/11] dnsparser: Add dns parser code Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 04/11] dnsparser: remove unused constant Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 05/11] dnsparser: Ignore CNAME if any Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 06/11] dns: Remove code block related to the usage of glibc's getaddrinfo_a function Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 07/11] dns: refactor dns.c to integrate the dns parser Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 08/11] dns: revert removed DNS code and disable raw DNS by default Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 09/11] test: revert DNS test-case Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 10/11] gwproxy: Add DNS server option Ahmad Gani
2025-08-28 14:34 ` [PATCH gwproxy v6 11/11] dns: Add fallback mechanism for raw DNS Ahmad Gani
2025-08-28 21:52 ` [PATCH gwproxy v6 00/11] Initial work on integration of DNS parser lib in gwproxy Ammar Faizi
2025-08-28 22:23 ` Ammar Faizi
2025-08-29 0:54 ` Ahmad Gani
2025-08-29 1:59 ` Ammar Faizi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox