* [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy
@ 2025-09-18 18:47 Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration Alviro Iskandar Setiawan
` (8 more replies)
0 siblings, 9 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
Happy Friday everyone,
I hope you all are doing well!
This is the v12 revision of the new DNS resolver feature that does not
rely on getaddrinfo(). This revision is based on the previous v11
series with significant changes. This series is also available at:
https://github.com/alviroiskandar/gwproxy.git #new-dns-resolver
One of the concerns in using getaddrinfo() is that it will
block the entire thread while waiting for the DNS resolution to
complete. As a result, the naive workaround is to have dedicated worker
threads that solely handle DNS resolution. Such workaround is not so
efficient as it needs to communicate across multiple threads using
mutexes and condition variables, which adds overhead. Moreover, each
getaddrinfo() call will create its own socket and close it after the
resolution is done, which is not efficient either. Another concern is
that getaddrinfo() does not allow us to specify the DNS server to use,
so it relies on the system's DNS configuration, which may not be
desirable in some scenarios.
There was an attempt to batch DNS resolutions using
getaddrinfo_a(), but it's not widely available in all libc
implementations. And it's still not pollable. Behind the scenes, it
still uses multiple threads to handle the DNS resolutions, so it still
has the same concerns as above. Even worse, it needs to clone() the
entire process to handle a single DNS query. So if we batch 100 DNS
queries, it will execute close() syscall 100 times, which is not
efficient and scalable at all. The cancellation of pending DNS queries
is also very complicated.
To address that concern, this series introduces a new
experimental DNS resolver feature that does not rely on getaddrinfo().
Instead, it uses a single reusable UDP socket per thread to communicate
with the DNS server directly. It also allows the event loop to poll the
UDP socket for incoming DNS responses, so it does not block the entire
thread. The DNS server to use can also be specified via the new
--dns-server option. Currently, only one DNS server can be specified.
There are 8 patches in this series. One patch is not relevant
to the DNS feature, which is the patch that removes the unused struct
gwp_dns_query declaration. The other 7 patches are related to the DNS
feature, which are:
* Ahmad Gani: Introduce __unused macro to silence unused variable warnings.
* Ahmad Gani: Add DNS parser code.
* me: Add DNS resolver code.
* me: Add DNS resolution interface APIs.
* me: Introduce --dns-server and --raw-dns options.
* me: Integrate the raw DNS feature to epoll.
* me: Introduce --use-new-dns-resolver configure option.
I tried not to be too invasive in this series. I don't touch dns.c at
all. Instead, I added a new file dns_resolver.c which will only be
compiled if --use-new-dns-resolver is enabled during the configuration
time. The new DNS resolver feature is disabled by default as it's still
experimental. I also added a new option --dns-server to specify the DNS
server to use, and --raw-dns=1|0 to enable the new raw DNS feature.
This series slowly constructs the new DNS resolver feature step
by step. Patch 2-5 are preparatory patches. Patch 6 is where the data
structures are embedded into gwproxy common data structures and the
initialization happens. The actual integration to epoll happens in
patch 7. And then the last patch, patch 8, adds the configure option
to enable the new DNS resolver feature.
How to test this feature:
./configure --cc=clang --use-new-dns-resolver;
make -j$(nproc);
./gwproxy --as-socks5=1 --raw-dns=1 --nr-workers=1 --log-level=4 --bind=[::]:1080 --dns-server=1.1.1.1:53;
Then in another terminal, you can use curl to test it:
curl --proxy socks5h://[::1]:1080 http://example.com;
You will see something like this:
$ ./gwproxy --as-socks5=1 --raw-dns=1 --nr-workers=4 --log-level=4 --bind=[::]:1080 --dns-server=1.1.1.1:53
[2025-09-19 01:17:55][debug ][00940294]: Using event loop: epoll
[2025-09-19 01:17:55][debug ][00940294]: Initializing SOCKS5 context
[2025-09-19 01:17:55][debug ][00940294]: SOCKS5 context initialized without auth file
[2025-09-19 01:17:55][info ][00940294]: Worker 0 is listening on [::]:1080 (fd=3)
[2025-09-19 01:17:55][debug ][00940294]: Worker 0 initialized raw DNS resolver: 1.1.1.1:53 (fd=4)
[2025-09-19 01:17:55][debug ][00940294]: Worker 0 registered raw DNS UDP socket to epoll (fd=4)
[2025-09-19 01:17:55][debug ][00940294]: Worker 0 epoll (ep_fd=5, ev_fd=6)
[2025-09-19 01:17:55][info ][00940294]: Worker 1 is listening on [::]:1080 (fd=7)
[2025-09-19 01:17:55][debug ][00940294]: Worker 1 initialized raw DNS resolver: 1.1.1.1:53 (fd=8)
[2025-09-19 01:17:55][debug ][00940294]: Worker 1 registered raw DNS UDP socket to epoll (fd=8)
[2025-09-19 01:17:55][debug ][00940294]: Worker 1 epoll (ep_fd=9, ev_fd=10)
[2025-09-19 01:17:55][info ][00940294]: Worker 2 is listening on [::]:1080 (fd=11)
[2025-09-19 01:17:55][debug ][00940294]: Worker 2 initialized raw DNS resolver: 1.1.1.1:53 (fd=12)
[2025-09-19 01:17:55][debug ][00940294]: Worker 2 registered raw DNS UDP socket to epoll (fd=12)
[2025-09-19 01:17:55][debug ][00940294]: Worker 2 epoll (ep_fd=13, ev_fd=14)
[2025-09-19 01:17:55][info ][00940294]: Worker 3 is listening on [::]:1080 (fd=15)
[2025-09-19 01:17:55][debug ][00940294]: Worker 3 initialized raw DNS resolver: 1.1.1.1:53 (fd=16)
[2025-09-19 01:17:55][debug ][00940294]: Worker 3 registered raw DNS UDP socket to epoll (fd=16)
[2025-09-19 01:17:55][debug ][00940294]: Worker 3 epoll (ep_fd=17, ev_fd=18)
[2025-09-19 01:17:55][info ][00940295]: Worker 1 started (epoll)
[2025-09-19 01:17:55][info ][00940296]: Worker 2 started (epoll)
[2025-09-19 01:17:55][info ][00940297]: Worker 3 started (epoll)
[2025-09-19 01:17:55][info ][00940294]: Worker 0 started (epoll)
[2025-09-19 01:17:55][debug ][00940296]: Increased connection slot capacity to 16
[2025-09-19 01:17:55][debug ][00940296]: New connection from [::1]:45444 (fd=19)
[2025-09-19 01:17:55][debug ][00940296]: Resolved DNS query for example.com to 23.215.0.138:80 (gcp_idx=0)
[2025-09-19 01:17:55][info ][00940296]: New connection pair created (idx=0, cfd=19, tfd=20, ca=[::1]:45444, ta=23.215.0.138:80)
[2025-09-19 01:17:56][info ][00940296]: Target socket connected (fd=20, idx=0, ca=[::1]:45444, ta=23.215.0.138:80)
[2025-09-19 01:17:56][info ][00940296]: Closing connection pair (idx=0, cfd=19, tfd=20, ca=[::1]:45444, ta=23.215.0.138:80)
[2025-09-19 01:17:56][debug ][00940296]: Connection slot capacity shrunk to 0
Interesting points from that log:
* The DNS server 1.1.1.1:53 is used.
* Each thread has its own DNS UDP socket registered to epoll.
* The DNS query for example.com is resolved properly.
* gcp_idx=0 indicates that the DNS query uses txid=0, which is
also the index in the sess_map array.
Future works to be done:
* Support multiple DNS servers.
* Support /etc/hosts file parsing.
* Add timeout handling for DNS queries.
* Integrate io_uring support for the new DNS feature.
* Split DNS parser unit tests into a separate test suite.
* Integrate GitHub Actions to build and test the new DNS feature.
Link to v11: https://lore.gnuweeb.org/gwml/20250914050943.184934-1-reyuki@gnuweeb.org
Makefile | 6 +
configure | 8 +
src/gwproxy/common.h | 4 +
src/gwproxy/dns_parser.c | 583 +++++++++++++++++++++++++++++++++++++
src/gwproxy/dns_parser.h | 192 ++++++++++++
src/gwproxy/dns_resolver.c | 377 ++++++++++++++++++++++++
src/gwproxy/dns_resolver.h | 49 ++++
src/gwproxy/ev/epoll.c | 143 ++++++++-
src/gwproxy/gwproxy.c | 199 ++++++++++++-
src/gwproxy/gwproxy.h | 28 +-
10 files changed, 1570 insertions(+), 19 deletions(-)
create mode 100644 src/gwproxy/dns_parser.c
create mode 100644 src/gwproxy/dns_parser.h
create mode 100644 src/gwproxy/dns_resolver.c
create mode 100644 src/gwproxy/dns_resolver.h
base-commit: 60c6c822cf8ab14d80800776435417238ea371b0
--
Alviro Iskandar Setiawan
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 2/8] gwproxy: Introduce __unused macro Alviro Iskandar Setiawan
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
This struct is not defined anywhere and gdq is not used. Kill it.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/gwproxy.h | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h
index 095c0ce..445846f 100644
--- a/src/gwproxy/gwproxy.h
+++ b/src/gwproxy/gwproxy.h
@@ -133,8 +133,6 @@ struct gwp_conn {
uint32_t ep_mask;
};
-struct gwp_dns_query;
-
enum {
/*
* Don't close the file descriptor when freeing the connection pair.
@@ -174,7 +172,6 @@ struct gwp_conn_pair {
struct gwp_socks5_conn *s5_conn;
struct gwp_http_conn *http_conn;
};
- struct gwp_dns_query *gdq;
struct gwp_dns_entry *gde;
struct gwp_sockaddr client_addr;
struct gwp_sockaddr target_addr;
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 2/8] gwproxy: Introduce __unused macro
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 3/8] Add DNS parser code Alviro Iskandar Setiawan
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Ahmad Gani, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
From: Ahmad Gani <reyuki@gnuweeb.org>
Introduce __unused macro in preparation to add a new experimental
feature. When the experimental feature is disabled, there will be
several empty functions where their parameters are not used. It'll
be useful to silence the unused variable warnings.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
Co-authored-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/common.h | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/gwproxy/common.h b/src/gwproxy/common.h
index 8ae6ab4..adbd2c2 100644
--- a/src/gwproxy/common.h
+++ b/src/gwproxy/common.h
@@ -12,6 +12,10 @@
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
+#ifndef __unused
+#define __unused __attribute__((__unused__))
+#endif
+
#ifndef __cold
#define __cold __attribute__((__cold__))
#endif
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 3/8] Add DNS parser code
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 2/8] gwproxy: Introduce __unused macro Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 4/8] Add DNS resolver code Alviro Iskandar Setiawan
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Ahmad Gani, GNU/Weeb Mailing List, Alviro Iskandar Setiawan
From: Ahmad Gani <reyuki@gnuweeb.org>
Introduce a DNS parser in preparation to add a new DNS resolver feature
that does not rely on getaddrinfo(). The new feature will create its
own UDP sockets to communicate with DNS servers directly.
Signed-off-by: Ahmad Gani <reyuki@gnuweeb.org>
Co-authored-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/dns_parser.c | 583 +++++++++++++++++++++++++++++++++++++++
src/gwproxy/dns_parser.h | 192 +++++++++++++
2 files changed, 775 insertions(+)
create mode 100644 src/gwproxy/dns_parser.c
create mode 100644 src/gwproxy/dns_parser.h
diff --git a/src/gwproxy/dns_parser.c b/src/gwproxy/dns_parser.c
new file mode 100644
index 0000000..02a5e96
--- /dev/null
+++ b/src/gwproxy/dns_parser.c
@@ -0,0 +1,583 @@
+#define _DEFAULT_SOURCE
+#include <endian.h>
+#include <stdbool.h>
+#include <gwproxy/dns_parser.h>
+#include <gwproxy/net.h>
+
+static ssize_t construct_qname(uint8_t *dst, size_t dst_len, const char *qname)
+{
+ const uint8_t *p = (const uint8_t *)qname;
+ uint8_t *lp = dst; /* Length position. */
+ uint8_t *sp = lp + 1; /* String position. */
+ size_t total = 0;
+ uint16_t l = 0;
+
+ while (1) {
+ uint8_t c = *p++;
+
+ total++;
+ if (total >= dst_len)
+ return -ENAMETOOLONG;
+
+ if (c == '.' || c == '\0') {
+ if (l < 1 || l > DOMAIN_LABEL_LIMIT)
+ return -EINVAL;
+
+ *lp = (uint8_t)l;
+ lp = sp++;
+ l = 0;
+ if (!c)
+ break;
+ } else {
+ l++;
+ *sp = c;
+ sp++;
+ }
+ }
+
+ return total;
+}
+
+static int calculate_question_len(uint8_t *in, size_t in_len)
+{
+ const uint8_t *p = in;
+ int tot_len, advance_len;
+
+ tot_len = 0;
+ while (true) {
+ if (*p == 0x0) {
+ tot_len++;
+ break;
+ }
+
+ if (tot_len >= (int)in_len)
+ return -ENOBUFS;
+
+ advance_len = *p + 1;
+ tot_len += advance_len;
+ p += advance_len;
+ }
+
+ if (tot_len > DOMAIN_NAME_LIMIT)
+ return -ENAMETOOLONG;
+
+ tot_len += 4;
+ if (tot_len >= (int)in_len)
+ return -ENOBUFS;
+
+ return tot_len;
+}
+
+static int serialize_answ(uint16_t txid, uint8_t *in, size_t in_len, gwdns_answ_data *out)
+{
+ gwdns_header_pkt *hdr;
+ uint16_t raw_flags;
+ size_t idx, i;
+ void *ptr;
+ int ret;
+
+ idx = sizeof(*hdr);
+ if (idx >= in_len)
+ return -EAGAIN;
+
+ hdr = (void *)in;
+ if (memcmp(&txid, &hdr->id, sizeof(txid)))
+ return -EINVAL;
+
+ memcpy(&raw_flags, &hdr->flags, sizeof(raw_flags));
+ raw_flags = ntohs(raw_flags);
+ /* QR MUST 1 = response from dns server */
+ if (!DNS_QR(raw_flags))
+ return -EINVAL;
+
+ /* OPCODE MUST 0 = standard query */
+ if (DNS_OPCODE(raw_flags))
+ return -EINVAL;
+
+ /* RCODE MUST 0 = No error */
+ if (DNS_RCODE(raw_flags))
+ return -EPROTO;
+
+ // is it safe or recommended to alter the in buffer directly?
+ hdr->ancount = ntohs(hdr->ancount);
+ if (!hdr->ancount)
+ return -ENODATA;
+
+ /*
+ * Check the sizes upfront.
+ *
+ * 1 bytes for variable-length
+ * in[idx] for the length of first name
+ * 1 bytes for null terminator
+ * 2 bytes for qtype
+ * 2 bytes for qclass
+ */
+ if ((size_t)(1 + in[idx] + 1 + 2 + 2) >= in_len)
+ return -EINVAL;
+
+ ret = calculate_question_len(&in[idx], in_len - idx);
+ if (ret <= 0)
+ return -EINVAL;
+
+ idx += ret;
+ if (idx >= in_len)
+ return -EAGAIN;
+
+ out->hdr.ancount = 0;
+ ptr = malloc(hdr->ancount * sizeof(uint8_t *));
+ if (!ptr)
+ return -ENOMEM;
+
+ out->rr_answ = ptr;
+ for (i = 0; i < hdr->ancount; i++) {
+ uint16_t is_compressed, rdlength;
+ gwdns_serialized_answ *item = malloc(sizeof(gwdns_serialized_answ));
+ if (!item) {
+ ret = -ENOMEM;
+ goto exit_free;
+ }
+
+ memcpy(&is_compressed, &in[idx], sizeof(is_compressed));
+ is_compressed = ntohs(is_compressed);
+ is_compressed = DNS_IS_COMPRESSED(is_compressed);
+ assert(is_compressed);
+ idx += 2; // NAME
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ free(item);
+ goto exit_free;
+ }
+
+ memcpy(&item->rr_type, &in[idx], 2);
+ item->rr_type = ntohs(item->rr_type);
+ idx += 2; // TYPE
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ free(item);
+ goto exit_free;
+ }
+ memcpy(&item->rr_class, &in[idx], 2);
+ item->rr_class = ntohs(item->rr_class);
+ idx += 2; // CLASS
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ free(item);
+ goto exit_free;
+ }
+ memcpy(&item->ttl, &in[idx], 4);
+ item->ttl = be32toh(item->ttl);
+ idx += 4; // TTL
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ free(item);
+ goto exit_free;
+ }
+
+ memcpy(&rdlength, &in[idx], sizeof(rdlength));
+ rdlength = ntohs(rdlength);
+ switch (item->rr_type) {
+ case TYPE_AAAA:
+ if (rdlength != sizeof(struct in6_addr)) {
+ ret = -EINVAL;
+ free(item);
+ goto exit_free;
+ }
+ break;
+ case TYPE_A:
+ if (rdlength != sizeof(struct in_addr)) {
+ ret = -EINVAL;
+ free(item);
+ goto exit_free;
+ }
+ break;
+ case TYPE_CNAME:
+ idx += 2 + rdlength;
+ free(item);
+ continue;
+
+ default:
+ ret = -EINVAL;
+ free(item);
+ goto exit_free;
+ break;
+ }
+
+ item->rdlength = rdlength;
+ idx += 2;
+ if (idx >= in_len) {
+ ret = -EAGAIN;
+ free(item);
+ goto exit_free;
+ }
+
+ ptr = malloc(rdlength);
+ if (!ptr) {
+ ret = -ENOMEM;
+ free(item);
+ goto exit_free;
+ }
+
+ memcpy(ptr, &in[idx], rdlength);
+ idx += rdlength;
+ if (idx > in_len) {
+ ret = -EINVAL;
+ free(item);
+ free(ptr);
+ goto exit_free;
+ }
+
+ item->rdata = ptr;
+ out->rr_answ[out->hdr.ancount] = item;
+ out->hdr.ancount++;
+ }
+
+ if (!out->hdr.ancount) {
+ free(out->rr_answ);
+ return -ENODATA;
+ }
+
+ return 0;
+exit_free:
+ for (i = 0; i < out->hdr.ancount; i++) {
+ free(out->rr_answ[i]->rdata);
+ free(out->rr_answ[i]);
+ }
+ free(out->rr_answ);
+ return ret;
+}
+
+static void free_serialize_answ(gwdns_answ_data *answ)
+{
+ size_t i;
+ for (i = 0; i < answ->hdr.ancount; i++) {
+ free(answ->rr_answ[i]->rdata);
+ free(answ->rr_answ[i]);
+ }
+ free(answ->rr_answ);
+}
+
+int gwdns_parse_query(uint16_t txid, const char *service,
+ uint8_t *in, size_t in_len,
+ struct gwdns_addrinfo_node **ai)
+{
+ struct gwdns_addrinfo_node *results, *tail;
+ gwdns_answ_data raw_answ;
+ int r, port;
+ size_t i;
+
+ port = atoi(service);
+ if (port < 0)
+ return -EINVAL;
+ port = htons(port);
+
+ r = serialize_answ(txid, in, in_len, &raw_answ);
+ if (r)
+ return r;
+
+ results = tail = NULL;
+ for (i = 0; i < raw_answ.hdr.ancount; i++) {
+ struct gwdns_addrinfo_node *new_node;
+ gwdns_serialized_answ *answ;
+ struct sockaddr_in6 *i6;
+ struct sockaddr_in *i4;
+
+ answ = raw_answ.rr_answ[i];
+ new_node = malloc(sizeof(*new_node));
+ if (!new_node) {
+ r = -ENOMEM;
+ goto exit_free;
+ }
+ new_node->ai_next = NULL;
+
+ if (answ->rr_type == TYPE_AAAA) {
+ i6 = &new_node->ai_addr.i6;
+ new_node->ai_family = AF_INET6;
+ new_node->ai_addrlen = sizeof(i6);
+ i6->sin6_port = port;
+ i6->sin6_family = AF_INET6;
+ assert(sizeof(i6->sin6_addr) == answ->rdlength);
+ memcpy(&i6->sin6_addr, answ->rdata, answ->rdlength);
+ } else {
+ i4 = &new_node->ai_addr.i4;
+ new_node->ai_family = AF_INET;
+ new_node->ai_addrlen = sizeof(i4);
+ i4->sin_port = port;
+ i4->sin_family = AF_INET;
+ assert(sizeof(i4->sin_addr) == answ->rdlength);
+ memcpy(&i4->sin_addr, answ->rdata, answ->rdlength);
+ new_node->ai_ttl = answ->ttl;
+ }
+
+ if (!tail)
+ results = new_node;
+ else
+ tail->ai_next = new_node;
+ tail = new_node;
+ }
+
+ *ai = results;
+ r = 0;
+exit_free:
+ if (r && results)
+ gwdns_free_parsed_query(results);
+ free_serialize_answ(&raw_answ);
+ return r;
+}
+
+void gwdns_free_parsed_query(struct gwdns_addrinfo_node *ai)
+{
+ struct gwdns_addrinfo_node *tmp, *node = ai;
+ while (node) {
+ tmp = node->ai_next;
+ free(node);
+ node = tmp;
+ }
+}
+
+static ssize_t construct_question(gwdns_question_part *question)
+{
+ gwdns_header_pkt *hdr;
+ gwdns_query_pkt pkt;
+ uint16_t qtype, qclass;
+ size_t required_len;
+ ssize_t bw;
+
+ switch (question->type) {
+ case AF_INET6:
+ question->type = TYPE_AAAA;
+ break;
+ case AF_INET:
+ question->type = TYPE_A;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ hdr = &pkt.hdr;
+ /*
+ * the memset implicitly set opcode to OPCODE_QUERY
+ */
+ memset(hdr, 0, sizeof(*hdr));
+ /*
+ * no need to htons, so no ntohs for comparison in serialize_answ.
+ */
+ hdr->id = question->txid;
+ DNS_SET_RD(hdr->flags, true);
+ hdr->flags = htons(hdr->flags);
+ hdr->qdcount = htons(1);
+
+ /*
+ * pkt.body is interpreted as question section
+ * for layout and format, see RFC 1035 4.1.2. Question section format
+ */
+ bw = construct_qname(pkt.body, sizeof(pkt.body) - 3, question->domain);
+ if (bw < 0)
+ return bw;
+
+ pkt.body[bw++] = 0x0;
+ qtype = htons(question->type);
+ qclass = htons(CLASS_IN);
+ memcpy(&pkt.body[bw], &qtype, 2);
+ bw += 2;
+ memcpy(&pkt.body[bw], &qclass, 2);
+ bw += 2;
+
+ required_len = sizeof(pkt.hdr) + bw;
+ if (question->dst_len < required_len)
+ return -ENOBUFS;
+
+ memcpy(question->dst_buffer, &pkt, required_len);
+
+ return required_len;
+}
+
+ssize_t gwdns_build_query(uint16_t txid, const char *name, int family, uint8_t *out, size_t out_len)
+{
+ gwdns_question_part q;
+
+ q.domain = name;
+ q.type = family;
+ q.txid = txid;
+ q.dst_buffer = out;
+ q.dst_len = out_len;
+ return construct_question(&q);
+}
+
+#ifdef RUNTEST
+
+void test_parse_ipv4(void)
+{
+ struct gwdns_addrinfo_node *d, *node;
+ uint16_t txid;
+
+ uint8_t recv_pkt[] = {
+ 0x23, 0xc6, 0x81, 0x80, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x8a, 0xc0, 0x0c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x66,
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04,
+ 0x4a, 0x7d, 0x18, 0x64, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x8b, 0xc0, 0x0c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04, 0x4a, 0x7d, 0x18, 0x65,
+ 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x00, 0x04,
+ 0x4a, 0x7d, 0x18, 0x71
+ };
+
+ memcpy(&txid, recv_pkt, 2);
+ d = NULL;
+ assert(!gwdns_parse_query(txid, "80", recv_pkt, sizeof(recv_pkt), &d));
+ assert(d);
+ node = d;
+ while (node) {
+ struct gwdns_addrinfo_node *tmp;
+ char buff[FULL_ADDRSTRLEN];
+
+ tmp = node->ai_next;
+ assert(node->ai_family == AF_INET);
+ convert_ssaddr_to_str(buff, &node->ai_addr);
+ printf("IPv4: %s\n", buff);
+ node = tmp;
+ }
+
+ gwdns_free_parsed_query(d);
+}
+
+void test_parse_ipv6(void)
+{
+ struct gwdns_addrinfo_node *d, *node;
+ uint16_t txid;
+
+ uint8_t recv_pkt[] = {
+ 0x45, 0x67,
+ 0x81, 0x80,
+ 0x00, 0x01,
+ 0x00, 0x04,
+ 0x00, 0x00,
+ 0x00, 0x00,
+
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d,
+ 0x00,
+ 0x00, 0x1c,
+ 0x00, 0x01,
+
+ 0xc0, 0x0c,
+ 0x00, 0x1c,
+ 0x00, 0x01,
+ 0x00, 0x00, 0x09, 0x06,
+ 0x00, 0x10,
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71,
+
+ 0xc0, 0x0c,
+ 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x09, 0x06,
+ 0x00, 0x10,
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a,
+
+ 0xc0, 0x0c,
+ 0x00, 0x1c,
+ 0x00, 0x01,
+ 0x00, 0x00, 0x09, 0x06,
+ 0x00, 0x10,
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65,
+
+ 0xc0, 0x0c,
+ 0x00, 0x1c,
+ 0x00, 0x01,
+ 0x00, 0x00, 0x0c, 0x16,
+ 0x00, 0x10,
+ 0x24, 0x04, 0x68, 0x00, 0x40, 0x03, 0x0c, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71
+ };
+
+ memcpy(&txid, recv_pkt, sizeof(txid));
+
+ node = NULL;
+ assert(!gwdns_parse_query(txid, "80", recv_pkt, sizeof(recv_pkt), &d));
+ assert(d);
+ node = d;
+ while (node) {
+ struct gwdns_addrinfo_node *tmp;
+ char buff[FULL_ADDRSTRLEN];
+
+ tmp = node->ai_next;
+ assert(node->ai_family == AF_INET6);
+ convert_ssaddr_to_str(buff, &node->ai_addr);
+ printf("IPv6: %s\n", buff);
+ node = tmp;
+ }
+
+ gwdns_free_parsed_query(d);
+}
+
+void test_build_ipv4(void)
+{
+ uint8_t buff[UDP_MSG_LIMIT];
+ gwdns_header_pkt *hdr;
+ uint16_t c;
+ ssize_t r;
+
+ c = 0xFFFF;
+ r = gwdns_build_query(c, "google.com", AF_INET, buff, sizeof(buff));
+ assert(r > 0);
+
+ hdr = (void *)buff;
+ assert(ntohs(hdr->qdcount) == 1);
+ assert(!hdr->nscount);
+ assert(!DNS_QR(hdr->flags));
+ assert(DNS_OPCODE(hdr->flags) == OPCODE_QUERY);
+ c = htons(TYPE_A);
+ assert(!memcmp(buff + 12 + 12, &c, 2));
+}
+
+void test_build_ipv6(void)
+{
+ uint8_t buff[UDP_MSG_LIMIT];
+ gwdns_header_pkt *hdr;
+ uint16_t c;
+ ssize_t r;
+
+ c = 0xFFFF;
+ r = gwdns_build_query(c, "google.com", AF_INET6, buff, sizeof(buff));
+ assert(r > 0);
+
+ hdr = (void *)buff;
+ assert(ntohs(hdr->qdcount) == 1);
+ assert(!hdr->nscount);
+ assert(!DNS_QR(hdr->flags));
+ assert(DNS_OPCODE(hdr->flags) == OPCODE_QUERY);
+ c = htons(TYPE_AAAA);
+ assert(!memcmp(buff + 12 + 12, &c, 2));
+}
+
+/*
+ * test mock data of recv in both IPv4 and IPv6
+ *
+ * the mock data are produced by this script:
+ * https://gist.github.com/realyukii/d7b450b4ddc305c66a2d8cd5600f23c4
+ */
+void run_all_tests(void)
+{
+ /*
+ * We can't use serialize_answ to parse multiple response at once.
+ * The caller MUST call serialize_answ more than one time if there's
+ * more than one response, because txid is passed to only verify one
+ * response.
+ */
+ fprintf(stderr, "test constructing DNS standard query packet for TYPE_A!\n");
+ test_build_ipv4();
+ fprintf(stderr, "test constructing DNS standard query packet for TYPE_AAAA!\n");
+ test_build_ipv6();
+ fprintf(stderr, "test parsing DNS standard query packet for TYPE_A!\n");
+ test_parse_ipv4();
+ fprintf(stderr, "test parsing DNS standard query packet for TYPE_AAAA!\n");
+ test_parse_ipv6();
+ fprintf(stderr, "all tests passed!\n");
+}
+
+int main(void)
+{
+ run_all_tests();
+ return 0;
+}
+
+#endif
diff --git a/src/gwproxy/dns_parser.h b/src/gwproxy/dns_parser.h
new file mode 100644
index 0000000..6d5769c
--- /dev/null
+++ b/src/gwproxy/dns_parser.h
@@ -0,0 +1,192 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <gwproxy/net.h>
+
+#ifndef GWP_DNS_PARSER_H
+#define GWP_DNS_PARSER_H
+
+#ifndef __packed
+#define __packed __attribute__((__packed__))
+#endif
+
+/*
+ * 4. MESSAGES
+ * 4.1. Format
+ *
+ * All communications inside of the domain protocol are carried in a single
+ * format called a message. The top-level format of a message is divided
+ * into 5 sections (some of which may be empty in certain cases), shown below:
+ *
+ * +---------------------+
+ * | Header |
+ * +---------------------+
+ * | Question | the question for the name server
+ * +---------------------+
+ * | Answer | RRs answering the question
+ * +---------------------+
+ * | Authority | RRs pointing toward an authority
+ * +---------------------+
+ * | Additional | RRs holding additional information
+ * +---------------------+
+ *
+ * These sections are defined in RFC 1035 §4.1. The Header section is always
+ * present and includes fields that specify which of the other sections follow,
+ * as well as metadata such as whether the message is a query or response,
+ * the opcode, etc.
+ */
+
+/* Flag bit position in little-endian machine */
+#define DNS_QR_BIT 0xF
+#define DNS_OPCODE_BIT 0xB // 4-bit field
+#define DNS_AA_BIT 0xA
+#define DNS_TC_BIT 0x9
+#define DNS_RD_BIT 0x8
+#define DNS_RA_BIT 0x7
+#define DNS_Z_BIT 0x4 // 3-bit field
+#define DNS_RCODE_BIT 0x0 // 4-bit field
+#define DNS_COMPRESSION_BIT (0x3 << 0xE)
+
+/* Flag extraction macros for listtle-endian machine */
+#define DNS_QR(flags) (((flags) >> DNS_QR_BIT) & 0x1)
+#define DNS_OPCODE(flags) (((flags) >> DNS_OPCODE_BIT) & 0xF)
+#define DNS_RCODE(flags) ((flags) & 0xF)
+#define DNS_IS_COMPRESSED(mask) ((mask) & DNS_COMPRESSION_BIT)
+
+/* Flag construction macros for little-endian machine */
+#define DNS_SET_RD(flags, val) (flags) = ((flags) & ~(1 << DNS_RD_BIT)) | ((!!(val)) << DNS_RD_BIT)
+
+/* as per RFC 1035 §2.3.4. Size limits */
+#define DOMAIN_LABEL_LIMIT 63
+#define DOMAIN_NAME_LIMIT 255
+#define UDP_MSG_LIMIT 512
+
+typedef enum {
+ OPCODE_QUERY = 0, // Standard query (QUERY)
+} gwdns_op;
+
+typedef enum {
+ TYPE_A = 1, // IPv4 host address
+ TYPE_CNAME = 5, // the canonical name for an alias
+ TYPE_AAAA = 28, // IPv6 host address
+} gwdns_type;
+
+typedef enum {
+ CLASS_IN = 1, // Internet
+} gwdns_class;
+
+typedef struct {
+ uint16_t id;
+ uint16_t flags;
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+} __packed gwdns_header_pkt;
+
+typedef struct {
+ uint8_t question[UDP_MSG_LIMIT];
+ char answr[UDP_MSG_LIMIT];
+} gwdns_question_buffer;
+
+typedef struct {
+ uint8_t *dst_buffer;
+ uint16_t txid;
+ uint16_t type;
+ size_t dst_len;
+ const char *domain;
+} gwdns_question_part;
+
+/*
+ * 4.1.3. Resource record format
+ *
+ * The answer, authority, and additional sections all share the same
+ * format: a variable number of resource records, where the number of
+ * records is specified in the corresponding count field in the header.
+ */
+typedef struct {
+ uint8_t *name; // DOMAIN NAME: variable‑length sequence of labels (length-byte followed by label, ending in 0), possibly compressed
+ uint16_t rr_type; // TYPE: two-octet code identifying the RR type (see gwdns_type)
+ uint16_t rr_class; // CLASS: two-octet code identifying the RR class (see gwdns_class)
+ uint32_t ttl; // TTL: 32-bit unsigned, time to live in seconds
+ uint16_t rdlength; // RDLENGTH: length in octets of RDATA
+ uint8_t *rdata; // RDATA: variable-length data, format depends on TYPE and CLASS
+} gwdns_serialized_rr;
+
+typedef struct {
+ char qname[DOMAIN_NAME_LIMIT];
+ uint16_t qtype;
+ uint16_t qclass;
+} gwdns_serialized_question;
+
+typedef gwdns_serialized_rr gwdns_serialized_answ;
+
+typedef struct {
+ gwdns_header_pkt hdr;
+ uint8_t body[UDP_MSG_LIMIT];
+} gwdns_query_pkt;
+
+typedef struct {
+ gwdns_header_pkt hdr;
+ gwdns_serialized_question question;
+ gwdns_serialized_answ **rr_answ;
+} gwdns_answ_data;
+
+struct gwdns_addrinfo_node {
+ int ai_family;
+ int ai_ttl;
+ socklen_t ai_addrlen;
+ struct gwp_sockaddr ai_addr;
+ struct gwdns_addrinfo_node *ai_next;
+};
+
+/*
+ * Build standard query for domain name lookup.
+ *
+ * The caller may need to check for potential transaction ID collisions.
+ *
+ * Possible errors are:
+ * - ENAMETOOLONG name is too long.
+ * - ENOBUFS length specified by out_len is not sufficient.
+ * - EINVAL malformed name or unsupported value of family.
+ *
+ * @param txid transaction id
+ * @param name domain name
+ * @param family choose request for IPv4 or IPv6
+ * @param out destination buffer for constructed packet
+ * @param out_len available capacity of destination buffer
+ * @return length of bytes written into dst_buffer on success,
+ * or a negative integer on failure.
+ */
+ssize_t gwdns_build_query(uint16_t txid, const char *name, int family, uint8_t *out, size_t out_len);
+
+/*
+ * Parse name server's answer
+ *
+ * Possible errors are:
+ * -EAGAIN in buffer is not sufficient, no bytes are processed, need more data.
+ * -EINVAL the content of in buffer is not valid.
+ * -ENOMEM failed to allocate dynamic memory.
+ * -ENODATA the packet didn't contain any answers.
+ * -EPROTO the DNS server can't understand your question
+ *
+ * @param txid transaction id of question
+ * @param service port number in ascii
+ * @param in a pointer to buffer that need to be parsed
+ * @param in_len a pointer to buffer that need to be parsed
+ * @param ai a pointer to address info
+ * @return zero on success or a negative number on failure
+ */
+int gwdns_parse_query(uint16_t txid, const char *service,
+ uint8_t *in, size_t in_len,
+ struct gwdns_addrinfo_node **ai);
+void gwdns_free_parsed_query(struct gwdns_addrinfo_node *addrinfo);
+
+#endif /* #ifndef GWP_DNS_PARSER_H */
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 4/8] Add DNS resolver code
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (2 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 3/8] Add DNS parser code Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs Alviro Iskandar Setiawan
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
In preparation to add a new DNS feature that does not rely on
getaddrinfo(), create the initial data structure that contains udp_fd,
server addr, sess_map, and stack to accomodate the new DNS resolver
feature. The stack and sess_map will be needed to map DNS queries to
their corresponding active client session.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/dns_resolver.c | 252 +++++++++++++++++++++++++++++++++++++
src/gwproxy/dns_resolver.h | 19 +++
2 files changed, 271 insertions(+)
create mode 100644 src/gwproxy/dns_resolver.c
create mode 100644 src/gwproxy/dns_resolver.h
diff --git a/src/gwproxy/dns_resolver.c b/src/gwproxy/dns_resolver.c
new file mode 100644
index 0000000..4d6924f
--- /dev/null
+++ b/src/gwproxy/dns_resolver.c
@@ -0,0 +1,252 @@
+/*
+ * A new experimental DNS resolver that does not rely on getaddrinfo().
+ */
+
+#include <gwproxy/dns_resolver.h>
+#include <gwproxy/syscall.h>
+#include <gwproxy/net.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct gwp_dns_resolver_map {
+ uint32_t cap;
+ uint32_t sp;
+ uint16_t *stack;
+ struct gwp_conn_pair **sess_map;
+};
+
+static int alloc_res_map(struct gwp_dns_resolver_map **map, uint32_t cap)
+{
+ struct gwp_dns_resolver_map *m;
+ uint32_t i, local_cap = 2;
+
+ /*
+ * Keep the capacity a power of 2 for easier doubling to
+ * max size of 65536.
+ */
+ while (local_cap < cap)
+ local_cap *= 2;
+
+ if (local_cap > 65536)
+ return -EINVAL;
+
+ m = malloc(sizeof(*m));
+ if (!m)
+ return -ENOMEM;
+
+ m->sess_map = calloc(local_cap, sizeof(*m->sess_map));
+ if (!m->sess_map)
+ goto err_m;
+
+ m->stack = malloc(local_cap * sizeof(*m->stack));
+ if (!m->stack)
+ goto err_sess;
+
+ m->cap = local_cap;
+ m->sp = 0;
+
+ /*
+ * Fill the stack with all available indexes.
+ */
+ i = local_cap;
+ while (i--)
+ m->stack[m->sp++] = i;
+
+ *map = m;
+ return 0;
+
+err_sess:
+ free(m->sess_map);
+err_m:
+ free(m);
+ return -ENOMEM;
+}
+
+static void free_res_map(struct gwp_dns_resolver_map *map)
+{
+ if (!map)
+ return;
+
+ free(map->stack);
+ free(map->sess_map);
+ free(map);
+}
+
+static int expand_res_map(struct gwp_dns_resolver_map *map)
+{
+ uint32_t new_cap, i, old_cap = map->cap;
+ struct gwp_conn_pair **new_sess_map;
+ uint16_t *new_stack;
+
+ new_cap = old_cap * 2;
+ if (new_cap > 65536)
+ return -ENOSPC;
+
+ new_sess_map = realloc(map->sess_map, new_cap * sizeof(*map->sess_map));
+ if (!new_sess_map)
+ return -ENOMEM;
+ map->sess_map = new_sess_map;
+ memset(&map->sess_map[old_cap], 0, old_cap * sizeof(*map->sess_map));
+
+ new_stack = realloc(map->stack, new_cap * sizeof(*map->stack));
+ if (!new_stack)
+ return -ENOMEM;
+ map->stack = new_stack;
+
+ i = new_cap;
+ while (i-- > old_cap)
+ map->stack[map->sp++] = i;
+
+ map->cap = new_cap;
+ return 0;
+}
+
+static int insert_map(struct gwp_dns_resolver_map *map,
+ struct gwp_conn_pair *pair, uint16_t *txid)
+{
+ uint32_t x;
+ int r;
+
+ if (!map->sp) {
+ r = expand_res_map(map);
+ if (r)
+ return r;
+ }
+
+ x = map->stack[--map->sp];
+ map->sess_map[x] = pair;
+ *txid = x;
+ return 0;
+}
+
+static struct gwp_conn_pair *lookup_map(struct gwp_dns_resolver_map *map,
+ uint16_t txid)
+{
+ if (txid >= map->cap)
+ return NULL;
+
+ return map->sess_map[txid];
+}
+
+static void try_reset_map(struct gwp_dns_resolver_map *map)
+{
+ struct gwp_conn_pair **new_sess_map;
+ uint32_t i, new_cap = 16;
+ uint16_t *new_stack;
+
+ if (map->cap <= 16)
+ return;
+
+ /*
+ * Try to allocate a smaller map first before freeing the
+ * existing one so we don't lose the existing map if
+ * one of reallocations fail.
+ */
+ new_sess_map = calloc(new_cap, sizeof(*new_sess_map));
+ if (!new_sess_map)
+ return;
+
+ new_stack = malloc(new_cap * sizeof(*new_stack));
+ if (!new_stack) {
+ free(new_sess_map);
+ return;
+ }
+
+ /*
+ * Ok, we are good to go, free the old map and
+ * replace with the new one.
+ */
+ free(map->sess_map);
+ free(map->stack);
+ map->sess_map = new_sess_map;
+ map->stack = new_stack;
+ map->cap = new_cap;
+ map->sp = 0;
+
+ i = new_cap;
+ while (i--)
+ map->stack[map->sp++] = i;
+}
+
+static void delete_map(struct gwp_dns_resolver_map *map, uint16_t txid)
+{
+ if (txid >= map->cap)
+ return;
+
+ if (!map->sess_map[txid])
+ return;
+
+ map->sess_map[txid] = NULL;
+ map->stack[map->sp++] = txid;
+
+ /*
+ * If all slots are free, try to shrink the map.
+ */
+ if (map->sp == map->cap)
+ try_reset_map(map);
+}
+
+static int init_udp_sock(struct gwp_dns_resolver *gdr, const char *srv_addr)
+{
+ static const int type = SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC;
+ struct gwp_sockaddr addr;
+ int f, r;
+
+ r = convert_str_to_ssaddr(srv_addr, &addr, 53);
+ if (r)
+ return r;
+
+ f = __sys_socket(addr.sa.sa_family, type, 0);
+ if (f < 0)
+ return f;
+
+ r = __sys_connect(f, &addr.sa, sizeof(addr));
+ if (r < 0) {
+ __sys_close(f);
+ return r;
+ }
+
+ gdr->udp_fd = f;
+ return 0;
+}
+
+int gwp_dns_res_init(struct gwp_ctx *ctx, struct gwp_dns_resolver *gdr,
+ const char *srv_addr)
+{
+ int r;
+
+ r = alloc_res_map(&gdr->sess_map, 16);
+ if (r)
+ return r;
+
+ gdr->srv_addr = strdup(srv_addr);
+ if (!gdr->srv_addr) {
+ r = -ENOMEM;
+ goto err_map;
+ }
+
+ r = init_udp_sock(gdr, gdr->srv_addr);
+ if (r)
+ goto err_srv;
+
+ (void)ctx;
+ return 0;
+
+err_srv:
+ free(gdr->srv_addr);
+err_map:
+ free_res_map(gdr->sess_map);
+ return r;
+}
+
+void gwp_dns_res_free(struct gwp_dns_resolver *res)
+{
+ if (!res)
+ return;
+
+ free_res_map(res->sess_map);
+ free(res->srv_addr);
+ __sys_close(res->udp_fd);
+ memset(res, 0, sizeof(*res));
+}
diff --git a/src/gwproxy/dns_resolver.h b/src/gwproxy/dns_resolver.h
new file mode 100644
index 0000000..d4545f1
--- /dev/null
+++ b/src/gwproxy/dns_resolver.h
@@ -0,0 +1,19 @@
+#ifndef GWPROXY__DNS_RESOLVER_H
+#define GWPROXY__DNS_RESOLVER_H
+
+#include <gwproxy/gwproxy.h>
+
+struct gwp_ctx;
+struct gwp_dns_resolver_map;
+
+struct gwp_dns_resolver {
+ int udp_fd;
+ char *srv_addr;
+ struct gwp_dns_resolver_map *sess_map;
+};
+
+int gwp_dns_res_init(struct gwp_ctx *ctx, struct gwp_dns_resolver *gdr,
+ const char *srv_addr);
+void gwp_dns_res_free(struct gwp_dns_resolver *gdr);
+
+#endif /* #ifndef GWPROXY__DNS_RESOLVER_H */
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (3 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 4/8] Add DNS resolver code Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 23:16 ` Ammar Faizi
2025-09-18 18:47 ` [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns Alviro Iskandar Setiawan
` (3 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
Provide an interface for DNS resolution to be used by other components
without including the socket handling code. It allows other components
to handle the socket I/O as they see fit, flexibly. For instance, using
epoll vs using io_uring has different ways of handling the socket I/O.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/dns_resolver.c | 125 +++++++++++++++++++++++++++++++++++++
src/gwproxy/dns_resolver.h | 30 +++++++++
2 files changed, 155 insertions(+)
diff --git a/src/gwproxy/dns_resolver.c b/src/gwproxy/dns_resolver.c
index 4d6924f..22fac18 100644
--- a/src/gwproxy/dns_resolver.c
+++ b/src/gwproxy/dns_resolver.c
@@ -250,3 +250,128 @@ void gwp_dns_res_free(struct gwp_dns_resolver *res)
__sys_close(res->udp_fd);
memset(res, 0, sizeof(*res));
}
+
+int gwp_dns_res_prep_query(struct gwp_dns_resolver *res,
+ struct gwp_dns_packet *gdp)
+{
+ int af, ret;
+ ssize_t tmp;
+
+ assert(gdp->gcp);
+
+ if (!gdp->__in_fallback_attempt) {
+ ret = insert_map(res->sess_map, gdp->gcp, &gdp->txid);
+ if (ret)
+ return ret;
+ } else {
+ assert(gdp->gcp == lookup_map(res->sess_map, gdp->txid));
+ }
+
+ ret = 0;
+ if (gdp->restyp == GWP_DNS_RESTYP_PREFER_IPV6) {
+ af = AF_INET6;
+ gdp->restyp = GWP_DNS_RESTYP_IPV4_ONLY;
+ gdp->__in_fallback_attempt = true;
+ } else if (gdp->restyp == GWP_DNS_RESTYP_PREFER_IPV4) {
+ af = AF_INET;
+ gdp->restyp = GWP_DNS_RESTYP_IPV6_ONLY;
+ gdp->__in_fallback_attempt = true;
+ } else if (gdp->restyp == GWP_DNS_RESTYP_IPV6_ONLY) {
+ ret = 0;
+ af = AF_INET6;
+ } else if (gdp->restyp == GWP_DNS_RESTYP_IPV4_ONLY) {
+ ret = 0;
+ af = AF_INET;
+ } else {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ tmp = gwdns_build_query(gdp->txid, gdp->host, af, gdp->buf, gdp->buf_len);
+ if (tmp < 0) {
+ ret = (int)tmp;
+ goto err;
+ }
+
+ gdp->buf_len = (uint16_t)tmp;
+ return ret;
+
+err:
+ delete_map(res->sess_map, gdp->txid);
+ return ret;
+}
+
+void gwp_dns_res_drop_query(struct gwp_dns_resolver *res,
+ struct gwp_conn_pair *gcp, uint16_t txid)
+{
+ assert(gcp);
+ assert(gcp == lookup_map(res->sess_map, txid));
+ delete_map(res->sess_map, txid);
+ (void)gcp;
+}
+
+int gwp_dns_res_fetch_gcp_by_payload(struct gwp_dns_resolver *res,
+ const uint8_t buf[UDP_MSG_LIMIT],
+ uint16_t len,
+ struct gwp_conn_pair **gcp_p)
+{
+ uint16_t txid;
+
+ if (len < 38)
+ return -EINVAL;
+
+ memcpy(&txid, buf, 2);
+ if (txid >= res->sess_map->cap)
+ return -EINVAL;
+
+ *gcp_p = lookup_map(res->sess_map, txid);
+ if (!gcp_p)
+ return -ENOENT;
+
+ return 0;
+}
+
+int gwp_dns_res_complete_query(struct gwp_dns_resolver *res,
+ struct gwp_dns_packet *gdp,
+ uint8_t buf[UDP_MSG_LIMIT],
+ uint16_t len,
+ struct gwp_sockaddr *addr)
+{
+ struct gwdns_addrinfo_node *ai = NULL;
+ char port[sizeof("65535")];
+ uint16_t txid;
+ ssize_t r;
+
+ if (len < 38)
+ return -EINVAL;
+
+ memcpy(&txid, buf, 2);
+ if (txid >= res->sess_map->cap)
+ return -EINVAL;
+
+ if (gdp->txid != txid)
+ return -EINVAL;
+
+ if (gdp->gcp != res->sess_map->sess_map[txid])
+ return -EINVAL;
+
+ snprintf(port, sizeof(port), "%hu", gdp->port);
+ r = gwdns_parse_query(txid, port, buf, len, &ai);
+ if (!r) {
+ *addr = ai->ai_addr;
+ gwdns_free_parsed_query(ai);
+ delete_map(res->sess_map, txid);
+ return 0;
+ }
+
+ if (gdp->__in_fallback_attempt) {
+ int tmp = gwp_dns_res_prep_query(res, gdp);
+ if (!tmp)
+ return -EAGAIN;
+
+ r = tmp;
+ }
+
+ delete_map(res->sess_map, txid);
+ return (int)r;
+}
diff --git a/src/gwproxy/dns_resolver.h b/src/gwproxy/dns_resolver.h
index d4545f1..1e46efd 100644
--- a/src/gwproxy/dns_resolver.h
+++ b/src/gwproxy/dns_resolver.h
@@ -1,7 +1,9 @@
#ifndef GWPROXY__DNS_RESOLVER_H
#define GWPROXY__DNS_RESOLVER_H
+#include <gwproxy/dns_parser.h>
#include <gwproxy/gwproxy.h>
+#include <gwproxy/dns.h>
struct gwp_ctx;
struct gwp_dns_resolver_map;
@@ -12,8 +14,36 @@ struct gwp_dns_resolver {
struct gwp_dns_resolver_map *sess_map;
};
+struct gwp_dns_packet {
+ bool __in_fallback_attempt;
+ uint8_t restyp;
+ uint16_t txid;
+ uint16_t buf_len;
+ uint16_t port;
+ uint8_t buf[UDP_MSG_LIMIT];
+ char *host;
+ struct gwp_conn_pair *gcp;
+};
+
int gwp_dns_res_init(struct gwp_ctx *ctx, struct gwp_dns_resolver *gdr,
const char *srv_addr);
void gwp_dns_res_free(struct gwp_dns_resolver *gdr);
+int gwp_dns_res_prep_query(struct gwp_dns_resolver *res,
+ struct gwp_dns_packet *gdp);
+
+void gwp_dns_res_drop_query(struct gwp_dns_resolver *res,
+ struct gwp_conn_pair *gcp, uint16_t txid);
+
+int gwp_dns_res_fetch_gcp_by_payload(struct gwp_dns_resolver *res,
+ const uint8_t buf[UDP_MSG_LIMIT],
+ uint16_t len,
+ struct gwp_conn_pair **gcp_p);
+
+int gwp_dns_res_complete_query(struct gwp_dns_resolver *res,
+ struct gwp_dns_packet *gdp,
+ uint8_t buf[UDP_MSG_LIMIT],
+ uint16_t len,
+ struct gwp_sockaddr *addr);
+
#endif /* #ifndef GWPROXY__DNS_RESOLVER_H */
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (4 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 22:54 ` Ammar Faizi
2025-09-18 18:47 ` [PATCH gwproxy v12 7/8] epoll: Intregrate the raw DNS feature to epoll Alviro Iskandar Setiawan
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
This is the initial integration of the new DNS resolver feature that
does not rely on getaddrinfo(). Introduce two new options in order to
utilize it.
1) --dns-server to be filled with DNS servers, for now it only supports
a single host. In the futures, it may accept multiple hosts
separated by commas.
2) --raw-dns to be filled with either 0 or 1. 0 to disable, 1 to
enable.
While in there, plug the new DNS resolver data structure into struct
gwp_wrk and initialize it.
Note that this feature is guarded with CONFIG_NEW_DNS_RESOLVER, which
is disabled by default as it's still experimental. A future patch will
add a configure option to enable it.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/gwproxy.c | 199 +++++++++++++++++++++++++++++++++++++++---
src/gwproxy/gwproxy.h | 25 +++++-
2 files changed, 213 insertions(+), 11 deletions(-)
diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c
index 3d277f7..623519d 100644
--- a/src/gwproxy/gwproxy.c
+++ b/src/gwproxy/gwproxy.c
@@ -67,6 +67,10 @@ static const struct option long_opts[] = {
{ "log-level", required_argument, NULL, 'm' },
{ "log-file", required_argument, NULL, 'f' },
{ "pid-file", required_argument, NULL, 'p' },
+#ifdef CONFIG_NEW_DNS_RESOLVER
+ { "dns-server", required_argument, NULL, 'j' },
+ { "raw-dns", required_argument, NULL, 'r' },
+#endif
{ NULL, 0, NULL, 0 }
};
@@ -77,6 +81,7 @@ static const struct gwp_cfg default_opts = {
.as_socks5 = false,
.as_http = false,
.socks5_prefer_ipv6 = false,
+ .use_raw_dns = false,
.protocol_timeout = 10,
.socks5_auth_file = NULL,
.socks5_dns_cache_secs = 0,
@@ -94,6 +99,7 @@ static const struct gwp_cfg default_opts = {
.log_level = 3,
.log_file = "/dev/stdout",
.pid_file = NULL,
+ .dns_servers = "1.1.1.1"
};
__cold
@@ -127,6 +133,10 @@ static void show_help(const char *app)
printf(" -m, --log-level=level Set log level (0=none, 1=error, 2=warning, 3=info, 4=debug, default: %d)\n", default_opts.log_level);
printf(" -f, --log-file=file Log to the specified file (default: %s)\n", default_opts.log_file);
printf(" -p, --pid-file=file Write PID to the specified file (default is no pid file)\n");
+#ifdef CONFIG_NEW_DNS_RESOLVER
+ printf(" -j, --dns-server=addr:port DNS server address (default: system resolver)\n");
+ printf(" -r, --raw-dns=0|1 Use raw DNS for SOCKS5 (default: %d)\n", default_opts.use_raw_dns);
+#endif
printf("\n");
}
@@ -228,6 +238,12 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg)
case 'p':
cfg->pid_file = optarg;
break;
+ case 'j':
+ cfg->dns_servers = optarg;
+ break;
+ case 'r':
+ cfg->use_raw_dns = !!atoi(optarg);
+ break;
default:
fprintf(stderr, "Unknown option: %c\n", c);
show_help(argv[0]);
@@ -275,6 +291,138 @@ einval:
return -EINVAL;
}
+#ifdef CONFIG_NEW_DNS_RESOLVER
+static int gwp_ctx_init_raw_dns(struct gwp_wrk *w)
+{
+ struct gwp_ctx *ctx = w->ctx;
+ int r;
+
+ w->dns = calloc(1, sizeof(*w->dns));
+ if (!w->dns)
+ return -ENOMEM;
+
+ /*
+ * TODO(Viro_SSFS): Support multiple DNS servers by splitting
+ * ctx->cfg.dns_servers by comma.
+ *
+ * For now, support only one DNS server.
+ */
+ w->dns->nr = 1;
+ w->dns->resolvers = calloc(w->dns->nr, sizeof(*w->dns->resolvers));
+ if (!w->dns->resolvers) {
+ r = -ENOMEM;
+ goto err_dns;
+ }
+
+ r = gwp_dns_res_init(ctx, &w->dns->resolvers[0], ctx->cfg.dns_servers);
+ if (r < 0)
+ goto err_resolvers;
+
+ pr_dbg(&ctx->lh, "Worker %u initialized raw DNS resolver: %s (fd=%d)",
+ w->idx, ctx->cfg.dns_servers, w->dns->resolvers[0].udp_fd);
+ return 0;
+
+err_resolvers:
+ free(w->dns->resolvers);
+ w->dns->resolvers = NULL;
+err_dns:
+ free(w->dns);
+ w->dns = NULL;
+ return r;
+}
+
+static void gwp_ctx_free_raw_dns(struct gwp_wrk *w)
+{
+ size_t i;
+
+ if (!w->dns)
+ return;
+
+ if (w->dns->resolvers) {
+ for (i = 0; i < w->dns->nr; i++)
+ gwp_dns_res_free(&w->dns->resolvers[i]);
+ free(w->dns->resolvers);
+ w->dns->resolvers = NULL;
+ }
+
+ free(w->dns);
+ w->dns = NULL;
+}
+
+static int gwp_alloc_dns_packet(struct gwp_conn_pair *gcp)
+{
+ gcp->gdp = calloc(1, sizeof(*gcp->gdp));
+ return gcp->gdp ? 0 : -ENOMEM;
+}
+
+static void gwp_free_dns_packet(struct gwp_conn_pair *gcp)
+{
+ if (gcp->gdp) {
+ free(gcp->gdp->host);
+ free(gcp->gdp);
+ gcp->gdp = NULL;
+ }
+}
+
+static int gwp_raw_dns_resolve(struct gwp_wrk *w,
+ struct gwp_conn_pair *gcp,
+ const char *host,
+ const char *port)
+{
+ struct gwp_dns_resolver *res;
+ struct gwp_dns_packet *gdp;
+ int r;
+
+ assert(w->dns);
+
+ res = &w->dns->resolvers[0];
+ r = gwp_alloc_dns_packet(gcp);
+ if (r < 0)
+ return r;
+
+ gdp = gcp->gdp;
+ gdp->host = strdup(host);
+ if (!gdp->host) {
+ r = -ENOMEM;
+ goto out_err;
+ }
+
+ gdp->restyp = w->ctx->cfg.socks5_prefer_ipv6 ?
+ GWP_DNS_RESTYP_PREFER_IPV6 :
+ GWP_DNS_RESTYP_PREFER_IPV4;
+
+ gdp->port = (uint16_t)atoi(port);
+ gdp->buf_len = sizeof(gdp->buf);
+ gdp->gcp = gcp;
+ r = gwp_dns_res_prep_query(res, gdp);
+ if (r < 0)
+ goto out_err;
+
+ return -EINPROGRESS;
+
+out_err:
+ gwp_free_dns_packet(gcp);
+ return r;
+}
+#else /* #ifdef CONFIG_NEW_DNS_RESOLVER */
+static int gwp_ctx_init_raw_dns(struct gwp_wrk __unused *w)
+{
+ return -ENOSYS;
+}
+
+static void gwp_ctx_free_raw_dns(struct gwp_wrk __unused *w)
+{
+}
+
+static int gwp_raw_dns_resolve(struct gwp_wrk __unused *w,
+ struct gwp_conn_pair __unused *gcp,
+ const char __unused *host,
+ const char __unused *port)
+{
+ return -ENOSYS;
+}
+#endif /* #ifdef CONFIG_NEW_DNS_RESOLVER */
+
#define FULL_ADDRSTRLEN (INET6_ADDRSTRLEN + sizeof(":65535[]") - 1)
__hot
@@ -451,6 +599,7 @@ static int gwp_ctx_init_thread(struct gwp_wrk *w,
const struct gwp_sockaddr *bind_addr)
{
struct gwp_ctx *ctx = w->ctx;
+ struct gwp_cfg *cfg = &ctx->cfg;
int r;
r = gwp_ctx_init_thread_sock(w, bind_addr);
@@ -459,13 +608,29 @@ static int gwp_ctx_init_thread(struct gwp_wrk *w,
return r;
}
+
+ if (cfg->use_raw_dns) {
+ r = gwp_ctx_init_raw_dns(w);
+ if (r < 0) {
+ pr_err(&ctx->lh, "Failed to initialize raw DNS: %s", strerror(-r));
+ goto out_err;
+ }
+ }
+
r = gwp_ctx_init_thread_event(w);
if (r < 0) {
pr_err(&ctx->lh, "gwp_ctx_init_thread_event: %s\n", strerror(-r));
- gwp_ctx_free_thread_sock(w);
+ goto out_err_raw_dns;
}
return r;
+
+out_err_raw_dns:
+ if (cfg->use_raw_dns)
+ gwp_ctx_free_raw_dns(w);
+out_err:
+ gwp_ctx_free_thread_sock(w);
+ return r;
}
static void free_conn(struct gwp_conn *conn);
@@ -529,6 +694,11 @@ static void gwp_ctx_signal_all_workers(struct gwp_ctx *ctx)
__cold
static void gwp_ctx_free_thread(struct gwp_wrk *w)
{
+ struct gwp_cfg *cfg = &w->ctx->cfg;
+
+ if (cfg->use_raw_dns)
+ gwp_ctx_free_raw_dns(w);
+
gwp_ctx_free_thread_sock_pairs(w);
gwp_ctx_free_thread_sock(w);
gwp_ctx_free_thread_event(w);
@@ -695,7 +865,7 @@ static int gwp_ctx_init_dns(struct gwp_ctx *ctx)
};
int r;
- if (!cfg->as_socks5 && !cfg->as_http) {
+ if ((!cfg->as_socks5 && !cfg->as_http) || cfg->use_raw_dns) {
ctx->dns = NULL;
return 0;
}
@@ -966,6 +1136,7 @@ __hot
int gwp_free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
{
struct gwp_conn_slot *gcs = &w->conn_slot;
+ struct gwp_cfg *cfg = &w->ctx->cfg;
struct gwp_conn_pair *tmp;
uint32_t i = gcp->idx;
@@ -990,7 +1161,9 @@ 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)
+ if (cfg->use_raw_dns && gcp->gdp)
+ free(gcp->gdp);
+ else if (gcp->gde)
gwp_dns_entry_put(gcp->gde);
switch (gcp->prot_type) {
@@ -1207,16 +1380,21 @@ static int prepare_target_addr_domain(struct gwp_wrk *w,
const char *host, const char *port)
{
struct gwp_ctx *ctx = w->ctx;
+ struct gwp_cfg *cfg = &ctx->cfg;
int r;
- r = gwp_dns_cache_lookup(ctx->dns, host, port, &gcp->target_addr);
- if (!r) {
- pr_dbg(&ctx->lh, "Found %s:%s in DNS cache %s", host, port,
- ip_to_str(&gcp->target_addr));
- return 0;
- }
+ if (cfg->use_raw_dns) {
+ return gwp_raw_dns_resolve(w, gcp, host, port);
+ } else {
+ r = gwp_dns_cache_lookup(ctx->dns, host, port, &gcp->target_addr);
+ if (!r) {
+ pr_dbg(&ctx->lh, "Found %s:%s in DNS cache %s", host, port,
+ ip_to_str(&gcp->target_addr));
+ return 0;
+ }
- return queue_dns_resolution(w, gcp, host, port);
+ return queue_dns_resolution(w, gcp, host, port);
+ }
}
static int socks5_prepare_target_addr_domain(struct gwp_wrk *w,
@@ -1546,6 +1724,7 @@ static void *gwp_ctx_thread_entry(void *arg)
r = -EINVAL;
break;
}
+
ctx->stop = true;
gwp_ctx_signal_all_workers(ctx);
pr_info(&ctx->lh, "Worker %u stopped", w->idx);
diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h
index 445846f..82bd8d8 100644
--- a/src/gwproxy/gwproxy.h
+++ b/src/gwproxy/gwproxy.h
@@ -18,6 +18,10 @@
#include <liburing.h>
#endif
+#ifdef CONFIG_NEW_DNS_RESOLVER
+#include <gwproxy/dns_resolver.h>
+#endif
+
#include <gwproxy/http1.h>
struct gwp_cfg {
@@ -27,6 +31,7 @@ struct gwp_cfg {
bool as_socks5;
bool as_http;
bool socks5_prefer_ipv6;
+ bool use_raw_dns;
int protocol_timeout;
const char *socks5_auth_file;
int socks5_dns_cache_secs;
@@ -44,6 +49,7 @@ struct gwp_cfg {
int log_level;
const char *log_file;
const char *pid_file;
+ const char *dns_servers;
};
struct gwp_ctx;
@@ -59,6 +65,7 @@ enum {
EV_BIT_SOCKS5_AUTH_FILE = (8ull << 48ull),
EV_BIT_HTTP_CONN = (18ull << 48ull),
+ EV_BIT_RAW_DNS_QUERY = (19ull << 48ull),
/*
* This ev_bit is used for user_data masking during protocol
@@ -153,6 +160,8 @@ struct gwp_http_conn {
struct gwnet_http_req_hdr req_hdr;
};
+struct gwp_dns_packet;
+
struct gwp_conn_pair {
struct gwp_conn target;
struct gwp_conn client;
@@ -172,7 +181,10 @@ struct gwp_conn_pair {
struct gwp_socks5_conn *s5_conn;
struct gwp_http_conn *http_conn;
};
- struct gwp_dns_entry *gde;
+ union {
+ struct gwp_dns_entry *gde;
+ struct gwp_dns_packet *gdp;
+ };
struct gwp_sockaddr client_addr;
struct gwp_sockaddr target_addr;
};
@@ -192,6 +204,13 @@ struct iou {
};
#endif
+struct gwp_dns_resolver;
+
+struct gwp_wrk_dns {
+ struct gwp_dns_resolver *resolvers;
+ uint32_t nr;
+};
+
struct gwp_wrk {
int tcp_fd;
struct gwp_conn_slot conn_slot;
@@ -218,6 +237,10 @@ struct gwp_wrk {
struct gwp_ctx *ctx;
uint32_t idx;
pthread_t thread;
+
+#ifdef CONFIG_NEW_DNS_RESOLVER
+ struct gwp_wrk_dns *dns;
+#endif
};
enum {
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 7/8] epoll: Intregrate the raw DNS feature to epoll
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (5 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 8/8] Makefile: Introduce --use-new-dns-resolver configure option Alviro Iskandar Setiawan
2025-09-18 18:55 ` [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
Integrate the new DNS feature to the epoll event loop.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
src/gwproxy/ev/epoll.c | 143 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 138 insertions(+), 5 deletions(-)
diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c
index d46568a..143786a 100644
--- a/src/gwproxy/ev/epoll.c
+++ b/src/gwproxy/ev/epoll.c
@@ -17,6 +17,130 @@
#include <limits.h>
#include <sys/inotify.h>
+
+static int arm_poll_for_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp);
+
+#ifdef CONFIG_NEW_DNS_RESOLVER
+#include <gwproxy/dns_resolver.h>
+static int register_dns_to_epoll(struct gwp_wrk *w)
+{
+ struct gwp_wrk_dns *dns = w->dns;
+ uint32_t i;
+
+ if (!dns)
+ return 0;
+
+ for (i = 0; i < dns->nr; i++) {
+ struct gwp_dns_resolver *res = &dns->resolvers[i];
+ struct epoll_event ev;
+ int r;
+
+ if (res->udp_fd < 0)
+ continue;
+
+ ev.events = EPOLLIN;
+ ev.data.u64 = 0;
+ ev.data.ptr = res;
+ ev.data.u64 |= EV_BIT_RAW_DNS_QUERY;
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, res->udp_fd, &ev);
+ if (r < 0) {
+ pr_err(&w->ctx->lh,
+ "Failed to add raw DNS UDP socket to epoll: %s\n",
+ strerror(-r));
+ return r;
+ }
+
+ pr_dbg(&w->ctx->lh,
+ "Worker %u registered raw DNS UDP socket to epoll (fd=%d)",
+ w->idx, res->udp_fd);
+ }
+
+ return 0;
+}
+
+static int send_dns_payload(struct gwp_dns_resolver *res,
+ struct gwp_conn_pair *gcp)
+{
+ struct gwp_dns_packet *gdp = gcp->gdp;
+ const void *b = gdp->buf;
+ size_t l = gdp->buf_len;
+ ssize_t sr;
+
+ sr = __sys_sendto(res->udp_fd, b, l, MSG_NOSIGNAL, NULL, 0);
+ if (unlikely(sr < 0))
+ return (int)sr;
+
+ return 0;
+}
+
+static int chk_handle_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
+{
+ struct gwp_cfg *cfg = &w->ctx->cfg;
+
+ if (cfg->use_raw_dns) {
+ struct gwp_dns_resolver *res = &w->dns->resolvers[0];
+ return send_dns_payload(res, gcp);
+ }
+
+ return arm_poll_for_dns_query(w, gcp);
+}
+
+static int prep_and_send_socks5_rep_connect(struct gwp_wrk *w,
+ struct gwp_conn_pair *gcp,
+ int err);
+
+static int handle_connect(struct gwp_wrk *w, struct gwp_conn_pair *gcp);
+
+static int handle_ev_raw_dns_query(struct gwp_wrk *w)
+{
+ struct gwp_dns_resolver *res = &w->dns->resolvers[0];
+ struct gwp_conn_pair *gcp = NULL;
+ struct gwp_ctx *ctx = w->ctx;
+ uint8_t buf[UDP_MSG_LIMIT];
+ uint16_t len;
+ ssize_t ret;
+ int r;
+
+ ret = __sys_recv(res->udp_fd, buf, sizeof(buf), 0);
+ if (unlikely(ret < 0))
+ return (int)ret;
+
+ len = (uint16_t)ret;
+ ret = gwp_dns_res_fetch_gcp_by_payload(res, buf, len, &gcp);
+ if (unlikely(ret < 0))
+ return 0;
+
+ ret = gwp_dns_res_complete_query(res, gcp->gdp, buf, len,
+ &gcp->target_addr);
+ if (likely(!ret)) {
+ pr_dbg(&ctx->lh, "Resolved DNS query for %s to %s (gcp_idx=%u)",
+ gcp->gdp->host, ip_to_str(&gcp->target_addr), gcp->idx);
+ r = handle_connect(w, gcp);
+ } else {
+ if (gcp->conn_state == CONN_STATE_SOCKS5_DNS_QUERY)
+ r = prep_and_send_socks5_rep_connect(w, gcp, (int)ret);
+ else
+ r = -EIO;
+ }
+ return r;
+}
+#else
+static int register_dns_to_epoll(struct gwp_wrk __unused *w)
+{
+ return 0;
+}
+
+static int chk_handle_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
+{
+ return arm_poll_for_dns_query(w, gcp);
+}
+
+static int handle_ev_raw_dns_query(struct gwp_wrk __unused *w)
+{
+ return -ENOSYS;
+}
+#endif
+
__cold
int gwp_ctx_init_thread_epoll(struct gwp_wrk *w)
{
@@ -73,6 +197,10 @@ int gwp_ctx_init_thread_epoll(struct gwp_wrk *w)
goto out_free_events;
}
+ r = register_dns_to_epoll(w);
+ if (r)
+ goto out_free_events;
+
pr_dbg(&w->ctx->lh, "Worker %u epoll (ep_fd=%d, ev_fd=%d)", w->idx,
ep_fd, ev_fd);
return 0;
@@ -154,10 +282,12 @@ static int free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp)
int nr_fd_closed = 0;
int r;
- if (gde) {
- r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
- if (unlikely(r))
- return r;
+ if (!w->ctx->cfg.use_raw_dns) {
+ if (gde) {
+ r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL);
+ if (unlikely(r))
+ return r;
+ }
}
if (gcp->client.fd >= 0) {
@@ -890,7 +1020,7 @@ static bool is_ev_bit_conn_pair(uint64_t ev_bit)
static int chk_socks5(struct gwp_wrk *w, struct gwp_conn_pair *gcp, int r)
{
if (r == -EINPROGRESS && gcp->conn_state == CONN_STATE_SOCKS5_DNS_QUERY)
- return arm_poll_for_dns_query(w, gcp);
+ return chk_handle_dns_query(w, gcp);
if (r == 0 && gcp->conn_state == CONN_STATE_SOCKS5_CONNECT)
return handle_connect(w, gcp);
@@ -1057,6 +1187,9 @@ static int handle_event(struct gwp_wrk *w, struct epoll_event *ev)
case EV_BIT_SOCKS5_AUTH_FILE:
r = handle_ev_socks5_auth_file(w);
break;
+ case EV_BIT_RAW_DNS_QUERY:
+ r = handle_ev_raw_dns_query(w);
+ break;
default:
pr_err(&w->ctx->lh, "Unknown event bit: %" PRIu64, ev_bit);
return -EINVAL;
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH gwproxy v12 8/8] Makefile: Introduce --use-new-dns-resolver configure option
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (6 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 7/8] epoll: Intregrate the raw DNS feature to epoll Alviro Iskandar Setiawan
@ 2025-09-18 18:47 ` Alviro Iskandar Setiawan
2025-09-18 18:55 ` [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:47 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Alviro Iskandar Setiawan, Ahmad Gani, GNU/Weeb Mailing List
Add a configure option to enable the new DNS resolver feature.
By default it is disabled as it's still experimental.
Signed-off-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org>
---
Makefile | 6 ++++++
configure | 8 ++++++++
2 files changed, 14 insertions(+)
diff --git a/Makefile b/Makefile
index 3ac35f0..a2d1e71 100644
--- a/Makefile
+++ b/Makefile
@@ -83,6 +83,12 @@ endif
-include config.make
LIBS=$(LIB_LDFLAGS)
+ifeq ($(CONFIG_NEW_DNS_RESOLVER),y)
+GWPROXY_CC_SOURCES += \
+ $(GWPROXY_DIR)/dns_parser.c \
+ $(GWPROXY_DIR)/dns_resolver.c
+endif
+
ifeq ($(CONFIG_IO_URING),y)
GWPROXY_CC_SOURCES += $(GWPROXY_DIR)/ev/io_uring.c
ALL_GWPROXY_OBJECTS += $(LIBURING_TARGET)
diff --git a/configure b/configure
index 44c49da..6daaeb6 100755
--- a/configure
+++ b/configure
@@ -37,6 +37,9 @@ for opt do
--use-io-uring)
use_io_uring="yes";
;;
+ --use-new-dns-resolver)
+ use_new_dns_resolver="yes";
+ ;;
--sanitize)
use_sanitize="yes";
;;
@@ -88,6 +91,7 @@ Options: [defaults in brackets after descriptions]
--debug Build with debug enabled
--use-io-uring Enable io_uring support (default: no)
--sanitize Enable sanitizers (default: no)
+ --use-new-dns-resolver Use the new DNS resolver feature (default: no)
EOF
exit 0;
fi
@@ -346,6 +350,10 @@ if test "${use_sanitize}" = "yes"; then
CXXFLAGS="${CXXFLAGS} -fsanitize=address -fsanitize=undefined";
fi;
+if test "${use_new_dns_resolver}" = "yes"; then
+ add_config "CONFIG_NEW_DNS_RESOLVER";
+fi;
+
add_config "CONFIG_LINUX";
if test "${use_debug}" = "yes"; then
--
Alviro Iskandar Setiawan
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
` (7 preceding siblings ...)
2025-09-18 18:47 ` [PATCH gwproxy v12 8/8] Makefile: Introduce --use-new-dns-resolver configure option Alviro Iskandar Setiawan
@ 2025-09-18 18:55 ` Alviro Iskandar Setiawan
8 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 18:55 UTC (permalink / raw)
To: Ammar Faiz; +Cc: Ahmad Gani, GNU/Weeb Mailing List
On Fri, Sep 19, 2025 at 1:47 AM Alviro Iskandar Setiawan wrote:
> queries, it will execute close() syscall 100 times, which is not
s/close/clone/
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns
2025-09-18 18:47 ` [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns Alviro Iskandar Setiawan
@ 2025-09-18 22:54 ` Ammar Faizi
2025-09-18 23:07 ` Alviro Iskandar Setiawan
0 siblings, 1 reply; 13+ messages in thread
From: Ammar Faizi @ 2025-09-18 22:54 UTC (permalink / raw)
To: Alviro Iskandar Setiawan; +Cc: Ahmad Gani, GNU/Weeb Mailing List
On Fri, Sep 19, 2025 at 01:47:28AM +0700, Alviro Iskandar Setiawan wrote:
> While in there, plug the new DNS resolver data structure into struct
> gwp_wrk and initialize it.
I don't see any guard against the io_uring usage. Note that you only
add this feature support for epoll. Did you test and verify that your
feature addition doesn't break the io_uring event loop?
> @@ -228,6 +238,12 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg)
> case 'p':
> cfg->pid_file = optarg;
> break;
> + case 'j':
> + cfg->dns_servers = optarg;
> + break;
> + case 'r':
> + cfg->use_raw_dns = !!atoi(optarg);
> + break;
> default:
> fprintf(stderr, "Unknown option: %c\n", c);
> show_help(argv[0]);
Maybe you can add:
if (cfg->use_raw_dns && !strcmp(cfg->event_loop, "io_uring)) {
fprintf(stderr, ERR_WRAP "Error: The raw DNS feature is currently not supported with the io_uring event loop\n" ERR_WRAP);
goto einval;
}
or something similar to avoid io_uring being run with the experimental
feature that's not available for it yet.
> +
> + if (cfg->use_raw_dns) {
> + r = gwp_ctx_init_raw_dns(w);
> + if (r < 0) {
> + pr_err(&ctx->lh, "Failed to initialize raw DNS: %s", strerror(-r));
> + goto out_err;
> + }
> + }
The problem with this initialization is that, it does not take care of
the io_uring case. It'll probably crash as the io_uring code still
thinks it's allowed to use ctx->dns.
--
Ammar Faizi
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns
2025-09-18 22:54 ` Ammar Faizi
@ 2025-09-18 23:07 ` Alviro Iskandar Setiawan
0 siblings, 0 replies; 13+ messages in thread
From: Alviro Iskandar Setiawan @ 2025-09-18 23:07 UTC (permalink / raw)
To: Ammar Faizi; +Cc: Ahmad Gani, GNU/Weeb Mailing List
On Fri, Sep 19, 2025 at 5:54 AM Ammar Faizi wrote:
> I don't see any guard against the io_uring usage. Note that you only
> add this feature support for epoll. Did you test and verify that your
> feature addition doesn't break the io_uring event loop?
Oh, that was an oversight.
> Maybe you can add:
>
> if (cfg->use_raw_dns && !strcmp(cfg->event_loop, "io_uring)) {
> fprintf(stderr, ERR_WRAP "Error: The raw DNS feature is currently not supported with the io_uring event loop\n" ERR_WRAP);
> goto einval;
> }
>
> or something similar to avoid io_uring being run with the experimental
> feature that's not available for it yet.
I'll fold that addition in for v13.
tq
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs
2025-09-18 18:47 ` [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs Alviro Iskandar Setiawan
@ 2025-09-18 23:16 ` Ammar Faizi
0 siblings, 0 replies; 13+ messages in thread
From: Ammar Faizi @ 2025-09-18 23:16 UTC (permalink / raw)
To: Alviro Iskandar Setiawan; +Cc: Ahmad Gani, GNU/Weeb Mailing List
On Fri, Sep 19, 2025 at 01:47:27AM +0700, Alviro Iskandar Setiawan wrote:
> +void gwp_dns_res_drop_query(struct gwp_dns_resolver *res,
> + struct gwp_conn_pair *gcp, uint16_t txid)
> +{
> + assert(gcp);
> + assert(gcp == lookup_map(res->sess_map, txid));
> + delete_map(res->sess_map, txid);
> + (void)gcp;
> +}
It seems this function, gwp_dns_res_drop_query(), is not called
anywhere. Based on the function naming, I guess it's supposed to be
called from gwp_dns_res_complete_query() where you're expected to
clear an index in the map after the corresponding DNS query has been
completed?
--
Ammar Faizi
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-09-18 23:16 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-18 18:47 [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 1/8] gwproxy: Remove 'struct gwp_dns_query' declaration Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 2/8] gwproxy: Introduce __unused macro Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 3/8] Add DNS parser code Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 4/8] Add DNS resolver code Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 5/8] dns_resolver: Add DNS resolution interface APIs Alviro Iskandar Setiawan
2025-09-18 23:16 ` Ammar Faizi
2025-09-18 18:47 ` [PATCH gwproxy v12 6/8] gwproxy: Introduce --dns-server and --raw-dns Alviro Iskandar Setiawan
2025-09-18 22:54 ` Ammar Faizi
2025-09-18 23:07 ` Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 7/8] epoll: Intregrate the raw DNS feature to epoll Alviro Iskandar Setiawan
2025-09-18 18:47 ` [PATCH gwproxy v12 8/8] Makefile: Introduce --use-new-dns-resolver configure option Alviro Iskandar Setiawan
2025-09-18 18:55 ` [PATCH gwproxy v12 0/8] Initial work on integration of DNS parser lib in gwproxy Alviro Iskandar Setiawan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox