From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server-vie001.gnuweeb.org X-Spam-Level: X-Spam-Status: No, score=-0.2 required=5.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,PDS_OTHER_BAD_TLD, URIBL_DBL_BLOCKED_OPENDNS,URIBL_ZEN_BLOCKED_OPENDNS autolearn=no autolearn_force=no version=3.4.6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gnuweeb.org; s=new2025; t=1757501043; bh=TNRK862s7KznZn91ew6kjTMvdOimmd99bny2/meXlg4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding:Message-ID:Date:From: Reply-To:Subject:To:Cc:In-Reply-To:References:Resent-Date: Resent-From:Resent-To:Resent-Cc:User-Agent:Content-Type: Content-Transfer-Encoding; b=s3JHXl7/fYYkUd03C5HyIqxxlFfLPSmNGHMBE74WsHji2jIhK/u8Nmzmbe8tX4ISR AaqM5C4SpSdb0DJDbVe4t/fh3xxhuIMVFLTXJLg+4jLUC15pd3awd6fc51s/P3E98f Uyt7Wq18lv/nVlPsNEma+Gyd0jFrhtbC7ke9UEw7ueSzmUOpAZXTyG8lVXSt/xSGad 2n8jR/yxtnR0T9CFpTnXvxBi7wqPXMs1KKUFUhWKZ+6Ke+hSCZ9U8Z1kU5sc/QtY/L uyoVXLBm4aXfqJw9J6huW1sSOIeqeFMC8RodiInINmKJXW8POAp5qb+qn/pFGsNo8C FtC0+6ZLmgIuA== Received: from zero (unknown [182.253.228.107]) by server-vie001.gnuweeb.org (Postfix) with ESMTPSA id 8F99831279E7; Wed, 10 Sep 2025 10:43:50 +0000 (UTC) From: Ahmad Gani To: Ammar Faizi Cc: Ahmad Gani , Alviro Iskandar Setiawan , GNU/Weeb Mailing List Subject: [PATCH gwproxy v10 2/2] gwproxy: refactor code base to add experimental raw DNS backend Date: Wed, 10 Sep 2025 17:43:25 +0700 Message-ID: <20250910104326.580778-3-reyuki@gnuweeb.org> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250910104326.580778-1-reyuki@gnuweeb.org> References: <20250910104326.580778-1-reyuki@gnuweeb.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: 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. Add fallback mechanism for raw DNS If *_PREFER_* is used in the restyp, the raw DNS backend will attempt to retry DNS query with different address family. Add DNS server option (remove hard-coded DNS server) Introduce --dns-server gwproxy' cmdline option instead of hard-coded value. Signed-off-by: Ahmad Gani --- Makefile | 2 +- configure | 8 ++ src/gwproxy/common.h | 4 + src/gwproxy/dns.c | 254 ++++++++++++++++++++++++++++++++++------ src/gwproxy/dns.h | 58 ++++++++- src/gwproxy/dnsparser.c | 2 + src/gwproxy/ev/epoll.c | 128 ++++++++++++++++++-- src/gwproxy/gwproxy.c | 213 +++++++++++++++++++++++++++++++-- src/gwproxy/gwproxy.h | 89 +++++++++++++- 9 files changed, 691 insertions(+), 67 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/configure b/configure index 44c49da20de4..1cc61e7691da 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_flag "-fsanitize=address"; then diff --git a/src/gwproxy/common.h b/src/gwproxy/common.h index 8ae6ab4a31f5..aae4446a6875 100644 --- a/src/gwproxy/common.h +++ b/src/gwproxy/common.h @@ -12,6 +12,10 @@ #define unlikely(x) __builtin_expect(!!(x), 0) #endif +#ifndef __maybe_unused +#define __maybe_unused __attribute__((__unused__)) +#endif + #ifndef __cold #define __cold __attribute__((__cold__)) #endif diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c index cdc62a5bcf78..07a9332a31e6 100644 --- a/src/gwproxy/dns.c +++ b/src/gwproxy/dns.c @@ -23,8 +23,7 @@ #include #include - -struct gwp_dns_ctx; +#include struct gwp_dns_wrk { struct gwp_dns_ctx *ctx; @@ -32,20 +31,6 @@ struct gwp_dns_wrk { pthread_t thread; }; -struct gwp_dns_ctx { - 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) { struct gwp_dns_entry *e, *next; @@ -182,13 +167,181 @@ int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name, return found ? 0 : -EHOSTUNREACH; } +#ifdef CONFIG_RAW_DNS + +static void _gwp_dns_entry_free(struct gwp_dns_entry *e) +{ + assert(e); + free(e->name); + free(e); +} + +void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +{ + struct gwp_dns_entry *new_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); +} + +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; +} + +static int attempt_fallback(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +{ + int af, r; + + /* 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: + assert(0); + return -EINVAL; + } + + r = (int)gwdns_build_query(e->txid, + e->name, af, e->payload, sizeof(e->payload)); + if (r > 0) { + e->payloadlen = r; + r = -EAGAIN; + } + + return r; +} + +int gwp_dns_process(uint8_t buff[UDP_MSG_LIMIT], int bufflen, struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +{ + struct gwdns_addrinfo_node *ai; + ssize_t r; + + r = gwdns_parse_query(e->txid, e->service, buff, bufflen, &ai); + if (r) { + if (r == -ENODATA) + r = attempt_fallback(ctx, e); + goto exit; + } + + e->addr = ai->ai_addr; + + gwdns_free_parsed_query(ai); +exit: + return (int)r; +} + +struct gwp_dns_entry *gwp_raw_dns_queue(uint16_t txid, struct gwp_dns_ctx *ctx, + const char *name, const char *service) +{ + struct gwp_dns_entry *e; + size_t nl, sl; + ssize_t r; + int af; + + e = malloc(sizeof(*e)); + if (!e) + return NULL; + + if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx)) + return NULL; + + switch (ctx->cfg.restyp) { + case GWP_DNS_RESTYP_PREFER_IPV4: + case GWP_DNS_RESTYP_IPV4_ONLY: + case GWP_DNS_RESTYP_DEFAULT: + af = AF_INET; + break; + case GWP_DNS_RESTYP_PREFER_IPV6: + case GWP_DNS_RESTYP_IPV6_ONLY: + af = AF_INET6; + break; + default: + assert(0); + goto out_free_e; + } + + r = gwdns_build_query(txid, + name, af, 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 + * avoid multiple allocations. + * + * The format is: "\0\0" where both name and + * service are null-terminated strings. + */ + nl = strlen(name); + sl = service ? strlen(service) : 0; + e->name = malloc(nl + 1 + sl + 1); + if (!e->name) + goto out_free_e; + + e->service = e->name + nl + 1; + memcpy(e->name, name, nl + 1); + if (service) + memcpy(e->service, service, sl + 1); + else + e->service[0] = '\0'; + + e->res = 0; + e->idx = ctx->nr_entries++; + ctx->entries[e->idx] = e; + + return e; +out_free_e: + free(e); + return NULL; +} +#else +static void free_all_queued_entries(__maybe_unused struct gwp_dns_ctx *ctx) +{ +} +#endif /* #ifdef 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); + __sys_close(e->ev_fd); free(e->name); free(e); } @@ -719,33 +872,51 @@ 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; + if (cfg->use_raw_dns) { +#ifdef CONFIG_RAW_DNS + 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->entry_cap = DEFAULT_ENTRIES_CAP; + ctx->entries = malloc(ctx->entry_cap * sizeof(*ctx->entries)); + if (!ctx->entries) { + __sys_close(r); + r = -ENOMEM; + 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->nr_sleeping = 0; - ctx->nr_entries = 0; - ctx->workers = NULL; - ctx->head = NULL; - ctx->tail = NULL; ctx->should_stop = false; ctx->last_scan = time(NULL); - r = init_workers(ctx); - if (r) - goto out_free_cache; *ctx_p = ctx; return 0; -out_free_cache: - free_cache(ctx->cache); out_destroy_cond: - pthread_cond_destroy(&ctx->cond); + if (!cfg->use_raw_dns) + pthread_cond_destroy(&ctx->cond); out_destroy_mutex: pthread_mutex_destroy(&ctx->lock); out_free_ctx: @@ -762,10 +933,14 @@ static void put_all_queued_entries(struct gwp_dns_ctx *ctx) void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx) { - free_workers(ctx); + if (ctx->cfg.use_raw_dns) { + free_all_queued_entries(ctx); + } else { + free_workers(ctx); + pthread_cond_destroy(&ctx->cond); + put_all_queued_entries(ctx); + } pthread_mutex_destroy(&ctx->lock); - pthread_cond_destroy(&ctx->cond); - put_all_queued_entries(ctx); free_cache(ctx->cache); free(ctx); } @@ -811,7 +986,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_close_fd; e->service = e->name + nl + 1; memcpy(e->name, name, nl + 1); @@ -820,13 +995,14 @@ 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; + atomic_init(&e->refcnt, 2); push_queue(ctx, e); + return e; -out_close_ev_fd: - close(e->ev_fd); +out_close_fd: + __sys_close(e->ev_fd); out_free_e: free(e); return NULL; diff --git a/src/gwproxy/dns.h b/src/gwproxy/dns.h index 10c7cea2ebe4..049505997e37 100644 --- a/src/gwproxy/dns.h +++ b/src/gwproxy/dns.h @@ -9,11 +9,20 @@ #include #include #include +#include #include - -struct gwp_dns_wrk; +#include +#include struct gwp_dns_entry { +#ifdef CONFIG_RAW_DNS + uint32_t idx; + int payloadlen; + union { + uint16_t txid; + uint8_t payload[UDP_MSG_LIMIT]; + }; +#endif char *name; char *service; _Atomic(int) refcnt; @@ -31,13 +40,37 @@ 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; uint32_t restyp; + bool use_raw_dns; +#ifdef CONFIG_RAW_DNS + const char *ns_addr_str; +#endif }; -struct gwp_dns_ctx; +struct gwp_dns_ctx { +#ifdef CONFIG_RAW_DNS + uint32_t entry_cap; + struct gwp_dns_entry **entries; + struct gwp_sockaddr ns_addr; + socklen_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; +}; /** * Initialize the DNS context. Stores the context in `*ctx_p`. When @@ -87,6 +120,25 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, */ bool gwp_dns_entry_put(struct gwp_dns_entry *entry); +#ifdef CONFIG_RAW_DNS +struct gwp_dns_entry *gwp_raw_dns_queue(uint16_t txid, struct gwp_dns_ctx *ctx, + const char *name, const char *service); + +void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e); + +int gwp_dns_process(uint8_t buff[UDP_MSG_LIMIT], int bufflen, struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e); +#else +static inline struct gwp_dns_entry *gwp_raw_dns_queue(__maybe_unused uint16_t txid, __maybe_unused struct gwp_dns_ctx *ctx, + __maybe_unused const char *name, __maybe_unused const char *service) +{ + return NULL; +} + +static inline void gwp_dns_raw_entry_free(__maybe_unused struct gwp_dns_ctx *ctx, __maybe_unused struct gwp_dns_entry *e) +{ +} +#endif + /** * Lookup a DNS entry in the cache. If the entry is found, it fills the * `addr` structure with the resolved address and returns 0. If the entry is diff --git a/src/gwproxy/dnsparser.c b/src/gwproxy/dnsparser.c index cb2a090dfbb0..973a189bed19 100644 --- a/src/gwproxy/dnsparser.c +++ b/src/gwproxy/dnsparser.c @@ -318,6 +318,8 @@ int gwdns_parse_query(uint16_t txid, const char *service, *ai = results; r = 0; exit_free: + if (r && results) + gwdns_free_parsed_query(results); free_serialize_answ(&raw_answ); return r; } diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c index d46568a6a2b1..48398442c5a7 100644 --- a/src/gwproxy/ev/epoll.c +++ b/src/gwproxy/ev/epoll.c @@ -39,6 +39,16 @@ int gwp_ctx_init_thread_epoll(struct gwp_wrk *w) goto out_close_ep_fd; } + if (w->ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + ev.events = EPOLLIN; + ev.data.u64 = EV_BIT_DNS_QUERY; + r = __sys_epoll_ctl(ep_fd, EPOLL_CTL_ADD, w->dns_resolver.udp_fd, &ev); + if (unlikely(r)) + return (int)r; +#endif + } + w->evsz = 512; events = calloc(w->evsz, sizeof(*events)); if (!events) { @@ -778,6 +788,79 @@ static int handle_connect(struct gwp_wrk *w, struct gwp_conn_pair *gcp) return 0; } +#ifdef CONFIG_RAW_DNS +static int send_raw_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) +{ + struct gwp_dns_entry *gde = gcp->gde; + struct gwp_dns_ctx *dctx; + ssize_t r; + + dctx = w->ctx->dns; + r = __sys_sendto( + w->dns_resolver.udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL, + &dctx->ns_addr.sa, dctx->ns_addrlen + ); + if (r < 0) + return (int)r; + pr_dbg(&w->ctx->lh, "standard DNS query for %s has been sent", gde->name); + + return 0; +} +static int read_raw_dns(struct gwp_wrk *w, struct gwp_conn_pair **pgcp, struct gwp_dns_entry **pgde) +{ + uint8_t buff[UDP_MSG_LIMIT]; + struct gwp_conn_pair *gcp; + struct gwp_dns_entry *gde; + struct gwp_dns_ctx *dctx; + uint16_t txid; + int r; + + dctx = w->ctx->dns; + r = (int)__sys_recvfrom( + w->dns_resolver.udp_fd, buff, sizeof(buff), 0, + &dctx->ns_addr.sa, &dctx->ns_addrlen + ); + if (r < 0) + return (int)r; + if (r < 38) + return -EINVAL; + + memcpy(&txid, buff, 2); + gcp = w->dns_resolver.sess_map[txid]; + if (!gcp) + return -ENOENT; + + gde = gcp->gde; + pr_dbg(&w->ctx->lh, "proxy session for DNS query %s was found!", gde->name); + r = gwp_dns_process(buff, r, dctx, gde); + if (r == -EAGAIN) { + pr_dbg(&w->ctx->lh, "DNS Fallback"); + return send_raw_dns_query(w, gcp); + } else if (r) + gde->res = r; + + r = return_txid(w, gde); + assert(!r); + if (r) + return r; + + *pgcp = gcp; + *pgde = gde; + return 0; +} +#else +static int send_raw_dns_query(__maybe_unused struct gwp_wrk *w, __maybe_unused struct gwp_conn_pair *gcp) +{ + return 0; +} +static int read_raw_dns(__maybe_unused struct gwp_wrk *w, + __maybe_unused struct gwp_conn_pair **pgcp, + __maybe_unused struct gwp_dns_entry **pgde) +{ + return 0; +} +#endif + static int arm_poll_for_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) { @@ -786,16 +869,22 @@ static int arm_poll_for_dns_query(struct gwp_wrk *w, int r; assert(gde); - assert(gde->ev_fd >= 0); 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); - if (unlikely(r)) - return r; + if (w->ctx->cfg.use_raw_dns) { + r = send_raw_dns_query(w, gcp); + if (r) + return r; + } else { + assert(gde->ev_fd >= 0); + r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->ev_fd, &ev); + if (unlikely(r)) + return r; + } return 0; } @@ -822,18 +911,26 @@ static void log_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp, __hot static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) { - struct gwp_dns_entry *gde = gcp->gde; - int r, ct = gcp->conn_state; + struct gwp_dns_entry *gde = NULL; + int r, ct; - assert(gde); - assert(gde->ev_fd >= 0); + if (w->ctx->cfg.use_raw_dns) { + r = read_raw_dns(w, &gcp, &gde); + if (r) + return r; + } else { + gde = gcp->gde; + assert(gde); + assert(gde->ev_fd >= 0); + r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL); + if (unlikely(r)) + return r; + } + + ct = gcp->conn_state; 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; - log_dns_query(w, gcp, gde); if (likely(!gde->res)) { gcp->target_addr = gde->addr; @@ -845,7 +942,12 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) r = -EIO; } - gwp_dns_entry_put(gde); + if (w->ctx->cfg.use_raw_dns) { + pr_dbg(&w->ctx->lh, "removing proxy session from the %s's txid map", gde->name); + gwp_dns_raw_entry_free(w->ctx->dns, gde); + } else { + gwp_dns_entry_put(gde); + } gcp->gde = NULL; return r; } diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c index 3d277f7ee6f8..8271ae95143a 100644 --- a/src/gwproxy/gwproxy.c +++ b/src/gwproxy/gwproxy.c @@ -44,6 +44,11 @@ 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' }, + { "session-map-cap", required_argument, NULL, 's' }, +#endif { "event-loop", required_argument, NULL, 'e' }, { "bind", required_argument, NULL, 'b' }, { "target", required_argument, NULL, 't' }, @@ -54,7 +59,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' }, @@ -74,6 +78,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, @@ -81,7 +86,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, @@ -94,6 +98,10 @@ 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", + .sess_map_cap = 16 +#endif }; __cold @@ -104,6 +112,10 @@ 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); +#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); @@ -114,7 +126,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); @@ -159,6 +170,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; @@ -189,9 +203,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; @@ -228,6 +239,14 @@ 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; + case 's': + cfg->sess_map_cap = atoi(optarg); + break; +#endif default: fprintf(stderr, "Unknown option: %c\n", c); show_help(argv[0]); @@ -446,6 +465,143 @@ static void gwp_ctx_free_thread_event(struct gwp_wrk *w) } } +#ifdef CONFIG_RAW_DNS +static inline int realloc_stack(struct dns_resolver *resolv) +{ + struct gwp_conn_pair **cp; + uint16_t *stack; + size_t newcap; + uint16_t j, idx; + + newcap = resolv->sess_map_cap * 2; + if (newcap > 65536) + return -ENOSPC; + + cp = realloc(resolv->sess_map, sizeof(*cp) * newcap); + if (!cp) + return -ENOMEM; + + resolv->sess_map = cp; + stack = realloc(resolv->stack.arr, sizeof(*stack) * newcap); + if (!stack) + return -ENOMEM; + resolv->stack.arr = stack; + + j = (uint16_t)newcap - 1; + for (idx = 0; j > resolv->sess_map_cap; idx++,j--) + stack[idx] = j; + + resolv->sess_map_cap = newcap; + + return 0; +} + +static inline int pop_txid(struct dns_resolver *resolv) +{ + int r; + if (resolv->stack.top == 0) { + r = realloc_stack(resolv); + if (r) + return -EAGAIN; + } + + return resolv->stack.arr[--resolv->stack.top]; +} + +static struct gwp_dns_entry *generate_gde(struct gwp_wrk *w, struct gwp_conn_pair *gcp, const char *host, const char *port) +{ + struct gwp_dns_entry *gde; + struct gwp_dns_ctx *dns = w->ctx->dns; + int txid; + + txid = pop_txid(&w->dns_resolver); + if (txid < 0) + return NULL; + + gde = gwp_raw_dns_queue((uint16_t)txid, dns, host, port); + if (!gde) { + return_txid(w, gde); + return NULL; + } + pr_dbg(&w->ctx->lh, "constructing standard DNS query packet for %s", host); + w->dns_resolver.sess_map[txid] = gcp; + + return gde; +} + +static int gwp_ctx_init_dns_resolver(struct gwp_wrk *w) +{ + struct dns_resolver *resolv; + struct gwp_dns_ctx *dns; + struct gwp_cfg *cfg; + struct gwp_ctx *ctx; + int udp_fd, r; + void *ptr; + + ctx = w->ctx; + cfg = &ctx->cfg; + dns = ctx->dns; + udp_fd = __sys_socket(dns->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0); + if (udp_fd < 0) { + pr_err(&w->ctx->lh, "Failed to create udp_fd: %s\n", strerror(-udp_fd)); + return udp_fd; + } + + resolv = &w->dns_resolver; + ptr = calloc(cfg->sess_map_cap, sizeof(*resolv->stack.arr)); + if (!ptr) { + r = -ENOMEM; + goto exit_close; + } + resolv->stack.arr = ptr; + ptr = calloc(cfg->sess_map_cap, sizeof(ptr)); + if (!ptr) { + r = -ENOMEM; + goto exit_free; + } + resolv->sess_map = ptr; + + resolv->udp_fd = udp_fd; + resolv->sess_map_cap = cfg->sess_map_cap; + + r = cfg->sess_map_cap; + resolv->stack.top = r--; + for (; r <= 0; r--) + resolv->stack.arr[r] = r; + + return 0; +exit_free: + free(resolv->stack.arr); +exit_close: + __sys_close(udp_fd); + return r; +} + +static void gwp_ctx_free_raw_dns(struct gwp_wrk *w) +{ + struct dns_resolver *resolv = &w->dns_resolver; + __sys_close(resolv->udp_fd); + free(resolv->sess_map); + free(resolv->stack.arr); +} +#else +static struct gwp_dns_entry *generate_gde(__maybe_unused struct gwp_wrk *w, + __maybe_unused struct gwp_conn_pair *gcp, + __maybe_unused const char *host, + __maybe_unused const char *port) +{ + return NULL; +} +static int gwp_ctx_init_dns_resolver(__maybe_unused struct gwp_wrk *w) +{ + return 0; +} + +static void gwp_ctx_free_raw_dns(__maybe_unused struct gwp_wrk *w) +{ +} +#endif /* #ifdef CONFIG_RAW_DNS */ + __cold static int gwp_ctx_init_thread(struct gwp_wrk *w, const struct gwp_sockaddr *bind_addr) @@ -459,6 +615,14 @@ static int gwp_ctx_init_thread(struct gwp_wrk *w, return r; } + if (ctx->cfg.use_raw_dns) { + r = gwp_ctx_init_dns_resolver(w); + if (r) { + pr_err(&ctx->lh, "gwp_ctx_init_thread_event: %s\n", strerror(-r)); + gwp_ctx_free_thread_sock(w); + } + } + r = gwp_ctx_init_thread_event(w); if (r < 0) { pr_err(&ctx->lh, "gwp_ctx_init_thread_event: %s\n", strerror(-r)); @@ -529,6 +693,8 @@ static void gwp_ctx_signal_all_workers(struct gwp_ctx *ctx) __cold static void gwp_ctx_free_thread(struct gwp_wrk *w) { + if (w->ctx->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); @@ -689,12 +855,23 @@ 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 = cfg->nr_dns_workers + .nr_workers = 1, +#ifdef CONFIG_RAW_DNS + .ns_addr_str = cfg->ns_addr_str +#endif }; 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; @@ -733,6 +910,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 { @@ -990,8 +1171,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_put(gcp->gde); + if (gcp->gde) { + if (w->ctx->cfg.use_raw_dns) { + pr_dbg(&w->ctx->lh, "client disconnected before query for %s resolved", gcp->gde->name); + return_txid(w, gcp->gde); + gwp_dns_raw_entry_free(w->ctx->dns, gcp->gde); + } else { + gwp_dns_entry_put(gcp->gde); + } + } switch (gcp->prot_type) { case GWP_PROT_TYPE_SOCKS5: @@ -1192,7 +1380,12 @@ static int queue_dns_resolution(struct gwp_wrk *w, struct gwp_conn_pair *gcp, struct gwp_dns_ctx *dns = w->ctx->dns; struct gwp_dns_entry *gde; - gde = gwp_dns_queue(dns, host, port); + if (w->ctx->cfg.use_raw_dns) { + gde = generate_gde(w, gcp, host, port); + } else { + gde = gwp_dns_queue(dns, host, port); + } + if (unlikely(!gde)) { pr_err(&w->ctx->lh, "Failed to allocate DNS entry for %s:%s", host, port); return -ENOMEM; diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h index 095c0ce700c3..31d8b56ad80f 100644 --- a/src/gwproxy/gwproxy.h +++ b/src/gwproxy/gwproxy.h @@ -9,6 +9,7 @@ #define _GNU_SOURCE #endif +#include #include #include #include @@ -24,6 +25,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; @@ -31,7 +33,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; @@ -44,6 +45,10 @@ struct gwp_cfg { int log_level; const char *log_file; const char *pid_file; +#ifdef CONFIG_RAW_DNS + const char *ns_addr_str; + int sess_map_cap; +#endif }; struct gwp_ctx; @@ -195,8 +200,24 @@ struct iou { }; #endif +struct stack_u16 { + uint16_t top; + uint16_t *arr; +}; + +struct dns_resolver { + struct stack_u16 stack; + struct gwp_conn_pair **sess_map; + uint16_t sess_map_cap; + int udp_fd; +}; + struct gwp_wrk { int tcp_fd; +#ifdef CONFIG_RAW_DNS + /* For mapping DNS queries to the corresponding proxy session */ + struct dns_resolver dns_resolver; +#endif struct gwp_conn_slot conn_slot; union { @@ -251,6 +272,72 @@ int gwp_create_timer(int fd, int sec, int nsec); void gwp_setup_cli_sock_options(struct gwp_wrk *w, int fd); const char *ip_to_str(const struct gwp_sockaddr *gs); +#ifdef CONFIG_RAW_DNS +static inline void shrink_stack(struct gwp_wrk *w) +{ + struct dns_resolver *resolv; + struct gwp_cfg *cfg; + uint16_t *p1; + void *p2; + int i; + + cfg = &w->ctx->cfg; + resolv = &w->dns_resolver; + p1 = realloc(resolv->stack.arr, cfg->sess_map_cap * sizeof(*resolv->stack.arr)); + if (!p1) + return; + resolv->stack.arr = p1; + + p2 = realloc(resolv->sess_map, cfg->sess_map_cap * sizeof(*resolv->sess_map)); + if (!p2) + return; + resolv->sess_map = p2; + + memset(p2, 0, cfg->sess_map_cap * sizeof(*resolv->sess_map)); + + i = cfg->sess_map_cap; + resolv->stack.top = i--; + for (; i <= 0; i--) + p1[i] = i; + + resolv->sess_map_cap = cfg->sess_map_cap; +} + +static inline int return_txid(struct gwp_wrk *w, struct gwp_dns_entry *gde) +{ + struct dns_resolver *resolv; + struct gwp_cfg *cfg; + uint16_t idx; + + resolv = &w->dns_resolver; + cfg = &w->ctx->cfg; + /* the program shouldn't return more than its pop */ + if (resolv->stack.top >= resolv->sess_map_cap) { + assert(0); + return -EINVAL; + } + + idx = resolv->stack.top++; + /* shrinks it when there is no active query + * and the allocated size is larger than the default size. + */ + if (idx == resolv->sess_map_cap && + resolv->sess_map_cap > cfg->sess_map_cap) { + shrink_stack(w); + } else { + resolv->stack.arr[idx] = gde->txid; + resolv->sess_map[idx] = NULL; + } + + return 0; +} +#else +static inline int return_txid(__maybe_unused struct gwp_wrk *w, __maybe_unused struct gwp_dns_entry *gde) +{ + return 0; +} +#endif + static inline void gwp_conn_buf_advance(struct gwp_conn *conn, size_t len) { assert(len <= conn->len); -- Ahmad Gani