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=-1.2 required=5.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,URIBL_DBL_BLOCKED_OPENDNS, URIBL_ZEN_BLOCKED_OPENDNS autolearn=ham autolearn_force=no version=3.4.6 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gnuweeb.org; s=new2025; t=1756391859; bh=hSPXXgf3TWP4eotna5SiPP+2ACaIP4j1szhamCvKXmw=; 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=BuHtgFOrQxHdoGN89wIamdhtSX9NfWUxGi+VNmnadWlRnDr3CqRT+DlUKd5nGGzDl JomKvjO5X9WHaRRlw48t6FGsz4WiOQJ9CpmFxuObDnBGu6EbwT9mYHoXd+/1kMw7KC znVMt7NdqeAx21DcstL9nkdimN+MPoPIupcTgaldUuBYgMPOYRKhXFXqwAwhZ0tDW6 3ZYJ2TxCt26CraIZAYVegVr4/nV+yrEMtxXYz8SEvdB+vekCHLLThZGvQz1PWcHLqF rHfuD5jh52DqAQm3OgHRtUg5u24JosY3T03JuKBkolAZb8krOvT54pXXo01XKHasy5 VzfZYGhcMAshA== Received: from zero (unknown [182.253.228.107]) by server-vie001.gnuweeb.org (Postfix) with ESMTPSA id BCEBF3127F75; Thu, 28 Aug 2025 14:37:18 +0000 (UTC) From: Ahmad Gani To: Ammar Faizi Cc: Ahmad Gani , Alviro Iskandar Setiawan , GNU/Weeb Mailing List Subject: [PATCH gwproxy v6 08/11] dns: revert removed DNS code and disable raw DNS by default Date: Thu, 28 Aug 2025 21:34:30 +0700 Message-ID: <20250828143444.540247-9-reyuki@gnuweeb.org> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20250828143444.540247-1-reyuki@gnuweeb.org> References: <20250828143444.540247-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. Signed-off-by: Ahmad Gani --- configure | 28 ++ src/gwproxy/dns.c | 688 +++++++++++++++++++++++++++++++++----- src/gwproxy/dns.h | 36 +- src/gwproxy/ev/epoll.c | 62 ++-- src/gwproxy/ev/io_uring.c | 6 +- src/gwproxy/gwproxy.c | 31 +- src/gwproxy/gwproxy.h | 1 + 7 files changed, 730 insertions(+), 122 deletions(-) diff --git a/configure b/configure index 19baa1585237..539610916149 100755 --- a/configure +++ b/configure @@ -34,6 +34,9 @@ for opt do --cxx=*) cxx="$optarg"; ;; + --use-raw-dns) + use_raw_dns="yes"; + ;; --use-io-uring) use_io_uring="yes"; ;; @@ -87,6 +90,7 @@ Options: [defaults in brackets after descriptions] --cxx=CMD Use CMD as the C++ compiler --debug Build with debug enabled --use-io-uring Enable io_uring support (default: no) + --use-raw-dns Enable experimental raw DNS backend (default: no) --sanitize Enable sanitizers (default: no) EOF exit 0; @@ -333,6 +337,10 @@ if test "${use_io_uring}" = "yes"; then CXXFLAGS="${CXXFLAGS} -I./src/liburing/src/include"; fi; +if test "${use_raw_dns}" = "yes"; then + add_config "CONFIG_RAW_DNS"; +fi; + if test "${use_sanitize}" = "yes"; then add_config "CONFIG_SANITIZE"; if ! add_c_and_cxx_flag "-fsanitize=address"; then @@ -355,6 +363,26 @@ else CXXFLAGS="${CXXFLAGS} -DNDEBUG"; fi; +# ------ Check for getaddrinfo_a() ------ +cat > $tmp_c << EOF +#include +int main(void) +{ + struct sigevent ev; + struct gaicb *gc = (void *)0; + getaddrinfo_a(GAI_NOWAIT, &gc, 1, &ev); + return 0; +} +EOF +l="Do we have getaddrinfo_a()?"; +if compile_cc "" "-lanl" "getaddrinfo_a()"; then + add_config "CONFIG_HAVE_GETADDRINFO_A"; + print_supp "yes" "${l}"; + LIB_LDFLAGS="${LIB_LDFLAGS} -lanl"; +else + print_supp "no" "${l}"; +fi; + add_make_var "CC" "${cc}"; add_make_var "CXX" "${cxx}"; add_make_var "CFLAGS" "$(echo "${CFLAGS} ${USER_CFLAGS}" | awk '{$1=$1};1')"; diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c index fea81109ca6d..9bc67b083b03 100644 --- a/src/gwproxy/dns.c +++ b/src/gwproxy/dns.c @@ -27,34 +27,51 @@ struct gwp_dns_ctx; +struct gwp_dns_wrk { + struct gwp_dns_ctx *ctx; + uint32_t id; + pthread_t thread; +}; + struct gwp_dns_ctx { - int nr_entries; - int entry_cap; +#ifdef CONFIG_RAW_DNS + uint32_t entry_cap; struct gwp_dns_entry **entries; int sockfd; int ns_family; struct gwp_sockaddr ns_addr; uint8_t ns_addrlen; +#endif volatile bool should_stop; pthread_mutex_t lock; + pthread_cond_t cond; + uint32_t nr_sleeping; + uint32_t nr_entries; + struct gwp_dns_entry *head; + struct gwp_dns_entry *tail; + struct gwp_dns_wrk *workers; struct gwp_dns_cache *cache; time_t last_scan; struct gwp_dns_cfg cfg; }; -void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen) +static void put_all_entries(struct gwp_dns_entry *head) { - *addr = ctx->ns_addr; - *addrlen = ctx->ns_addrlen; + struct gwp_dns_entry *e, *next; + + for (e = head; e; e = next) { + next = e->next; + gwp_dns_entry_put(e); + } } -__attribute__((unused)) -static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockaddr *gs, +static bool iterate_addr_list(struct addrinfo *res, struct gwp_sockaddr *gs, uint32_t rt) { - struct gwdns_addrinfo_node *ai; + struct addrinfo *ai; - assert(res); + if (!res) + return false; /* * Handle IPV4_ONLY and IPV6_ONLY cases together. @@ -67,9 +84,9 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad if (ai->ai_family != fm) continue; if (fm == AF_INET) - gs->i4 = ai->ai_addr.i4; + gs->i4 = *(struct sockaddr_in *)ai->ai_addr; else - gs->i6 = ai->ai_addr.i6; + gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr; return true; } return false; @@ -83,19 +100,19 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad int prm = (rt == GWP_DNS_RESTYP_PREFER_IPV6) ? AF_INET6 : AF_INET; int sec = (prm == AF_INET6) ? AF_INET : AF_INET6; - struct gwp_sockaddr *fallback = NULL; + struct sockaddr *fallback = NULL; for (ai = res; ai; ai = ai->ai_next) { if (ai->ai_family != prm) { if (ai->ai_family == sec && !fallback) - fallback = &ai->ai_addr; + fallback = ai->ai_addr; continue; } if (prm == AF_INET) - gs->i4 = ai->ai_addr.i4; + gs->i4 = *(struct sockaddr_in *)ai->ai_addr; else - gs->i6 = ai->ai_addr.i6; + gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr; return true; } @@ -103,9 +120,9 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad return false; if (sec == AF_INET) - gs->i4 = fallback->i4; + gs->i4 = *(struct sockaddr_in *)fallback; else - gs->i6 = fallback->i6; + gs->i6 = *(struct sockaddr_in6 *)fallback; return true; } @@ -115,10 +132,10 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad */ for (ai = res; ai; ai = ai->ai_next) { if (ai->ai_family == AF_INET) { - gs->i4 = ai->ai_addr.i4; + gs->i4 = *(struct sockaddr_in *)ai->ai_addr; return true; } else if (ai->ai_family == AF_INET6) { - gs->i6 = ai->ai_addr.i6; + gs->i6 = *(struct sockaddr_in6 *)ai->ai_addr; return true; } } @@ -126,7 +143,19 @@ static bool iterate_addr_list(struct gwdns_addrinfo_node *res, struct gwp_sockad return false; } -__attribute__((unused)) +static void prep_hints(struct addrinfo *hints, uint32_t restyp) +{ + memset(hints, 0, sizeof(*hints)); + hints->ai_family = AF_UNSPEC; + hints->ai_socktype = SOCK_STREAM; + hints->ai_flags = AI_ADDRCONFIG; + + if (restyp == GWP_DNS_RESTYP_IPV4_ONLY) + hints->ai_family = AF_INET; + else if (restyp == GWP_DNS_RESTYP_IPV6_ONLY) + hints->ai_family = AF_INET6; +} + static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name, const struct addrinfo *ai) { @@ -139,17 +168,36 @@ static void try_pass_result_to_cache(struct gwp_dns_ctx *ctx, const char *name, gwp_dns_cache_insert(ctx->cache, name, ai, time(NULL) + x); } -// static int gwp_dns_find_preferred_addr(struct gwp_dns_ctx *ctx, struct gwdns_addrinfo_node *ai, const char *name, -// struct gwp_sockaddr *addr, uint32_t restyp) -// { -// bool found; +int gwp_dns_resolve(struct gwp_dns_ctx *ctx, const char *name, + const char *service, struct gwp_sockaddr *addr, + uint32_t restyp) +{ + struct addrinfo *res = NULL, hints; + bool found; + int r; + + prep_hints(&hints, restyp); + r = getaddrinfo(name, service, &hints, &res); + if (r) + return -r; + + found = iterate_addr_list(res, addr, restyp); + if (found) + try_pass_result_to_cache(ctx, name, res); + + if (res) + freeaddrinfo(res); + + return found ? 0 : -EHOSTUNREACH; +} -// found = iterate_addr_list(ai, addr, restyp); -// if (found) -// try_pass_result_to_cache(ctx, name, ai); +#ifdef CONFIG_RAW_DNS -// return found ? 0 : -EHOSTUNREACH; -// } +void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen) +{ + *addr = ctx->ns_addr; + *addrlen = ctx->ns_addrlen; +} static void _gwp_dns_entry_free(struct gwp_dns_entry *e) { @@ -160,7 +208,7 @@ static void _gwp_dns_entry_free(struct gwp_dns_entry *e) free(e); } -void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) { struct gwp_dns_entry *new_e; @@ -175,6 +223,70 @@ void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) _gwp_dns_entry_free(e); } +static void free_all_queued_entries(struct gwp_dns_ctx *ctx) +{ + uint32_t i; + for (i = 0; i < ctx->nr_entries; i++) { + struct gwp_dns_entry *e = ctx->entries[i]; + _gwp_dns_entry_free(e); + } + + free(ctx->entries); +} + +static bool realloc_entries(struct gwp_dns_ctx *ctx) +{ + struct gwp_dns_entry **tmp; + int new_cap; + + new_cap = ctx->entry_cap * 2; + tmp = realloc(ctx->entries, new_cap * sizeof(*tmp)); + if (!tmp) + return 1; + + ctx->entries = tmp; + ctx->entry_cap = new_cap; + + return 0; +} + +int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +{ + struct gwdns_addrinfo_node *ai; + uint8_t buff[UDP_MSG_LIMIT]; + ssize_t r; + + r = __sys_recvfrom( + e->udp_fd, buff, sizeof(buff), 0, + &ctx->ns_addr.sa, (socklen_t *)&ctx->ns_addrlen + ); + if (r <= 0) + return (int)r; + + r = gwdns_parse_query(e->txid, e->service, buff, r, &ai); + if (r) + goto exit_free_ai; + + e->addr = ai->ai_addr; + // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp); + +exit_free_ai: + gwdns_free_parsed_query(ai); + return (int)r; +} +#endif /* #ifndef CONFIG_RAW_DNS */ + +static void gwp_dns_entry_free(struct gwp_dns_entry *e) +{ + if (!e) + return; + + assert(e->ev_fd >= 0); + close(e->ev_fd); + free(e->name); + free(e); +} + /* * Must be called with ctx->lock held. May release the lock, but it * will reacquire it before returning. @@ -185,7 +297,6 @@ void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) * cache to find expired entries and delete them. * - Save the current time as the last scan time. */ -__attribute__((unused)) static void cond_scan_cache(struct gwp_dns_ctx *ctx) { if (!ctx->cache) @@ -206,6 +317,366 @@ static void cond_scan_cache(struct gwp_dns_ctx *ctx) ctx->last_scan = time(NULL); } +/* + * Must be called with ctx->lock held. + */ +static void wait_for_queue_entry(struct gwp_dns_ctx *ctx) +{ + ctx->nr_sleeping++; + pthread_cond_wait(&ctx->cond, &ctx->lock); + ctx->nr_sleeping--; + cond_scan_cache(ctx); +} + +#ifdef CONFIG_HAVE_GETADDRINFO_A +/* + * Must be called with ctx->lock held. + */ +static struct gwp_dns_entry *unplug_queue_list(struct gwp_dns_ctx *ctx) +{ + struct gwp_dns_entry *head = ctx->head; + + ctx->head = ctx->tail = NULL; + ctx->nr_entries = 0; + return head; +} + +struct dbq_entry { + struct gwp_dns_entry *e; + struct gaicb cb; +}; + +struct dns_batch_query { + struct dbq_entry *entries; + struct gaicb **reqs; + struct addrinfo hints; + uint32_t nr_entries; + uint32_t cap; +}; + +static void dbq_free(struct dns_batch_query *dbq) +{ + uint32_t i; + + if (!dbq) + return; + + if (dbq->reqs) { + for (i = 0; i < dbq->nr_entries; i++) { + if (dbq->reqs[i]->ar_result) + freeaddrinfo(dbq->reqs[i]->ar_result); + } + } + + free(dbq->entries); + free(dbq->reqs); + free(dbq); +} + +static int dbq_add_entry(struct dns_batch_query *dbq, struct gwp_dns_entry *e) +{ + struct dbq_entry *de; + + if (dbq->nr_entries >= dbq->cap) { + uint32_t new_cap = dbq->cap ? dbq->cap * 2 : 16; + struct dbq_entry *nentries; + + nentries = realloc(dbq->entries, new_cap * sizeof(*nentries)); + if (!nentries) + return -ENOMEM; + dbq->entries = nentries; + dbq->cap = new_cap; + } + + de = &dbq->entries[dbq->nr_entries]; + de->e = e; + memset(&de->cb, 0, sizeof(de->cb)); + de->cb.ar_name = e->name; + de->cb.ar_service = e->service; + de->cb.ar_request = &dbq->hints; + de->cb.ar_result = NULL; + dbq->nr_entries++; + + return 0; +} + +static int collect_active_queries(struct gwp_dns_ctx *ctx, + struct gwp_dns_entry **head_p, + struct dns_batch_query **dbq_p) +{ + struct gwp_dns_entry *e, *next, *prev = NULL, *head = *head_p; + struct dns_batch_query *dbq; + + dbq = calloc(1, sizeof(*dbq)); + if (!dbq) + return -ENOMEM; + + assert(head); + prep_hints(&dbq->hints, ctx->cfg.restyp); + for (e = head; e; e = next) { + int x = atomic_load(&e->refcnt); + next = e->next; + if (x > 1) { + + if (dbq_add_entry(dbq, e)) { + dbq_free(dbq); + return -ENOMEM; + } + + prev = e; + continue; + } + + assert(x == 1); + /* + * If the refcnt is 1, it means we are the last reference + * to this entry. The client no longer cares about the + * result. We can free it immediately. No need to resolve + * the query nor to signal the eventfd. + */ + if (prev) + prev->next = next; + else + head = next; + + gwp_dns_entry_free(e); + } + + *head_p = head; + *dbq_p = dbq; + return 0; +} + +static void dispatch_batch_result(int r, struct gwp_dns_ctx *ctx, + struct dns_batch_query *dbq, + uint32_t restyp) +{ + struct gwp_dns_entry *e; + struct addrinfo *ai; + uint32_t i; + + for (i = 0; i < dbq->nr_entries; i++) { + e = dbq->entries[i].e; + ai = dbq->reqs[i]->ar_result; + + if (!r) { + e->res = gai_error(dbq->reqs[i]); + if (!e->res) { + if (!iterate_addr_list(ai, &e->addr, restyp)) + e->res = -EHOSTUNREACH; + } + } else { + e->res = r; + } + + eventfd_write(e->ev_fd, 1); + if (!e->res) + try_pass_result_to_cache(ctx, e->name, ai); + } +} + +/* + * Filling dbq->reqs[n] cannot be done in dbq_add_entry() because + * the reallocation of dbq->entries may change the address of + * dbq->entries[n].cb. + */ +static int prep_reqs(struct dns_batch_query *dbq) +{ + uint32_t i; + + dbq->reqs = malloc(dbq->nr_entries * sizeof(*dbq->reqs)); + if (!dbq->reqs) + return -ENOMEM; + + for (i = 0; i < dbq->nr_entries; i++) + dbq->reqs[i] = &dbq->entries[i].cb; + + return 0; +} + +/* + * Must be called with ctx->lock held. May release the lock, but + * it will reacquire it before returning. + */ +static void process_queue_entry_batch(struct gwp_dns_ctx *ctx) +{ + struct gwp_dns_entry *head = unplug_queue_list(ctx); + struct dns_batch_query *dbq = NULL; + + if (!head) + return; + + pthread_mutex_unlock(&ctx->lock); + + if (!collect_active_queries(ctx, &head, &dbq)) { + if (!prep_reqs(dbq)) { + struct sigevent ev; + int r; + + memset(&ev, 0, sizeof(ev)); + ev.sigev_notify = SIGEV_NONE; + r = getaddrinfo_a(GAI_WAIT, dbq->reqs, dbq->nr_entries, &ev); + dispatch_batch_result(r, ctx, dbq, ctx->cfg.restyp); + } + } + + dbq_free(dbq); + put_all_entries(head); + pthread_mutex_lock(&ctx->lock); +} +#endif /* #ifdef CONFIG_HAVE_GETADDRINFO_A */ + +/* + * Must be called with ctx->lock held. May release the lock, but + * it will reacquire it before returning. + */ +static void process_queue_entry_single(struct gwp_dns_ctx *ctx) +{ + struct gwp_dns_entry *e = ctx->head; + + if (!e) + return; + + e = ctx->head; + ctx->head = e->next; + if (!ctx->head) { + ctx->tail = NULL; + assert(ctx->nr_entries == 1); + } + + ctx->nr_entries--; + pthread_mutex_unlock(&ctx->lock); + + if (atomic_load(&e->refcnt) == 1) { + /* + * If the refcnt is 1, it means we are the last reference + * to this entry. The client no longer cares about the + * result. We can free it immediately. No need to resolve + * the query nor to signal the eventfd. + */ + gwp_dns_entry_free(e); + goto out; + } + + e->res = gwp_dns_resolve(ctx, e->name, e->service, &e->addr, ctx->cfg.restyp); + eventfd_write(e->ev_fd, 1); + gwp_dns_entry_put(e); +out: + pthread_mutex_lock(&ctx->lock); +} + +/* + * Must be called with ctx->lock held. May release the lock, but + * it will reacquire it before returning. + */ +static void process_queue_entry(struct gwp_dns_ctx *ctx) +{ + /* + * There are two cases here: + * + * 1. All of the DNS threads are busy, and there are still a lot + * of queued entries. Process them in batch via getaddrinfo_a(). + * + * 2. The number of threads is sufficient to handle the queued + * entries, so process them one by one. + * + * Why not always getaddrinfo_a()? Because getaddrinfo_a() has + * a higher overhead than processing entries individually as it + * will spawn a new thread for each query. Don't bother invoking + * clone() for each entry if we can process them in the current + * thread. + */ +#ifdef CONFIG_HAVE_GETADDRINFO_A + if (ctx->nr_entries > (ctx->nr_sleeping + 16)) { + process_queue_entry_batch(ctx); + return; + } +#endif + + process_queue_entry_single(ctx); +} + +static void *gwp_dns_thread_entry(void *arg) +{ + struct gwp_dns_wrk *w = arg; + struct gwp_dns_ctx *ctx = w->ctx; + + pthread_mutex_lock(&ctx->lock); + while (!ctx->should_stop) { + if (ctx->head) + process_queue_entry(ctx); + else + wait_for_queue_entry(ctx); + } + pthread_mutex_unlock(&ctx->lock); + + return NULL; +} + +static void free_worker(struct gwp_dns_wrk *w) +{ + struct gwp_dns_ctx *ctx; + + if (!w) + return; + + ctx = w->ctx; + pthread_mutex_lock(&ctx->lock); + ctx->should_stop = true; + pthread_cond_broadcast(&ctx->cond); + pthread_mutex_unlock(&ctx->lock); + pthread_join(w->thread, NULL); +} + +static void free_workers(struct gwp_dns_ctx *ctx) +{ + uint32_t i; + + if (!ctx->workers) + return; + + for (i = 0; i < ctx->cfg.nr_workers; i++) + free_worker(&ctx->workers[i]); + + free(ctx->workers); + ctx->workers = NULL; +} + +static int init_workers(struct gwp_dns_ctx *ctx) +{ + struct gwp_dns_wrk *workers, *w; + uint32_t i; + int r; + + if (ctx->cfg.nr_workers == 0) + return -EINVAL; + + workers = calloc(ctx->cfg.nr_workers, sizeof(*workers)); + if (!workers) + return -ENOMEM; + + ctx->workers = workers; + for (i = 0; i < ctx->cfg.nr_workers; i++) { + w = &workers[i]; + w->ctx = ctx; + w->id = i; + r = pthread_create(&w->thread, NULL, gwp_dns_thread_entry, w); + if (r) { + r = -r; + goto out_err; + } + } + + return 0; + +out_err: + while (i--) + free_worker(&workers[i]); + free(workers); + ctx->workers = NULL; + return r; +} + static bool fetch_i4(struct gwp_dns_cache_entry *e, struct gwp_sockaddr *addr, uint16_t port) { @@ -342,26 +813,49 @@ int gwp_dns_ctx_init(struct gwp_dns_ctx **ctx_p, const struct gwp_dns_cfg *cfg) goto out_free_ctx; } - r = init_cache(ctx); - if (r) - goto out_destroy_mutex; - - r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53); + if (cfg->use_raw_dns) { +#ifdef CONFIG_RAW_DNS + // r = convert_str_to_ssaddr(cfg->ns_addr_str, &ctx->ns_addr, 53); + r = convert_str_to_ssaddr("1.1.1.1", &ctx->ns_addr, 53); if (r) goto out_destroy_mutex; ctx->ns_addrlen = ctx->ns_addr.sa.sa_family == AF_INET ? sizeof(ctx->ns_addr.i4) : sizeof(ctx->ns_addr.i6); - ctx->should_stop = false; - ctx->last_scan = time(NULL); - ctx->nr_entries = 0; ctx->entry_cap = DEFAULT_ENTRIES_CAP; ctx->entries = malloc(ctx->entry_cap * sizeof(*ctx->entries)); if (!ctx->entries) goto out_destroy_mutex; +#endif + } else { + r = pthread_cond_init(&ctx->cond, NULL); + if (r) { + r = -r; + goto out_destroy_mutex; + } + + ctx->nr_sleeping = 0; + ctx->workers = NULL; + ctx->head = NULL; + ctx->tail = NULL; + r = init_workers(ctx); + if (r) + goto out_destroy_cond; + } + ctx->nr_entries = 0; + + r = init_cache(ctx); + if (r) + goto out_destroy_cond; + + ctx->should_stop = false; + ctx->last_scan = time(NULL); *ctx_p = ctx; return 0; +out_destroy_cond: + if (!cfg->use_raw_dns) + pthread_cond_destroy(&ctx->cond); out_destroy_mutex: pthread_mutex_destroy(&ctx->lock); out_free_ctx: @@ -370,56 +864,63 @@ out_free_ctx: return r; } -static void free_all_queued_entries(struct gwp_dns_ctx *ctx) +static void put_all_queued_entries(struct gwp_dns_ctx *ctx) { - int i; - for (i = 0; i < ctx->nr_entries; i++) { - struct gwp_dns_entry *e = ctx->entries[i]; - _gwp_dns_entry_free(e); - } - - free(ctx->entries); + put_all_entries(ctx->head); + ctx->head = ctx->tail = NULL; } void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx) { - pthread_mutex_destroy(&ctx->lock); + if (ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS free_all_queued_entries(ctx); +#endif + } else { + free_workers(ctx); + pthread_cond_destroy(&ctx->cond); + put_all_queued_entries(ctx); + } + pthread_mutex_destroy(&ctx->lock); free_cache(ctx->cache); free(ctx); } -static bool realloc_entries(struct gwp_dns_ctx *ctx) +static void push_queue(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) { - struct gwp_dns_entry **tmp; - int new_cap; - - new_cap = ctx->entry_cap * 2; - tmp = realloc(ctx->entries, new_cap * sizeof(*tmp)); - if (!tmp) - return 1; - - ctx->entries = tmp; - ctx->entry_cap = new_cap; + pthread_mutex_lock(&ctx->lock); + if (ctx->tail) + ctx->tail->next = e; + else + ctx->head = e; + ctx->tail = e; + e->next = NULL; - return 0; + ctx->nr_entries++; + if (ctx->nr_sleeping) + pthread_cond_signal(&ctx->cond); + pthread_mutex_unlock(&ctx->lock); } struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, const char *name, const char *service) { struct gwp_dns_entry *e; - uint16_t txid; size_t nl, sl; +#ifdef CONFIG_RAW_DNS + uint16_t txid; ssize_t r; - - if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx)) - return NULL; +#endif e = malloc(sizeof(*e)); if (!e) return NULL; + if (ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + if (ctx->nr_entries == ctx->entry_cap && realloc_entries(ctx)) + return NULL; + r = __sys_socket(ctx->ns_addr.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK, 0); if (r < 0) goto out_free_e; @@ -431,6 +932,12 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, if (r < 0) goto out_free_e; e->payloadlen = (int)r; +#endif + } else { + e->ev_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (e->ev_fd < 0) + goto out_free_e; + } /* * Merge name and service into a single allocated string to @@ -443,7 +950,7 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, sl = service ? strlen(service) : 0; e->name = malloc(nl + 1 + sl + 1); if (!e->name) - goto out_free_e; + goto out_close_fd; e->service = e->name + nl + 1; memcpy(e->name, name, nl + 1); @@ -453,38 +960,43 @@ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, e->service[0] = '\0'; e->res = 0; - e->idx = ctx->nr_entries++; - ctx->entries[e->idx] = e; - + if (ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + e->idx = ctx->nr_entries++; + ctx->entries[e->idx] = e; +#endif + } else { + atomic_init(&e->refcnt, 2); + push_queue(ctx, e); + } return e; +out_close_fd: + if (ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + close(e->udp_fd); +#endif + } else { + close(e->ev_fd); + } out_free_e: free(e); return NULL; } -int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e) +bool gwp_dns_entry_put(struct gwp_dns_entry *e) { - struct gwdns_addrinfo_node *ai; - uint8_t buff[UDP_MSG_LIMIT]; - ssize_t r; - - r = __sys_recvfrom( - e->udp_fd, buff, sizeof(buff), 0, - &ctx->ns_addr.sa, (socklen_t *)&ctx->ns_addrlen - ); - if (r <= 0) - return (int)r; + int x; - r = gwdns_parse_query(e->txid, e->service, buff, r, &ai); - if (r) - goto exit_free_ai; + if (!e) + return false; - e->addr = ai->ai_addr; - // gwp_dns_find_preferred_addr(ctx, ai, e->name, &e->addr, ctx->cfg.restyp); + x = atomic_fetch_sub(&e->refcnt, 1); + assert(x > 0); + if (x == 1) { + gwp_dns_entry_free(e); + return true; + } -exit_free_ai: - gwdns_free_parsed_query(ai); - return (int)r; + return false; } - diff --git a/src/gwproxy/dns.h b/src/gwproxy/dns.h index 12d21552c92e..410989f6414c 100644 --- a/src/gwproxy/dns.h +++ b/src/gwproxy/dns.h @@ -14,17 +14,22 @@ #include struct gwp_dns_entry { - int idx; - char *name; - char *service; - int res; +#ifdef CONFIG_RAW_DNS + uint32_t idx; int udp_fd; - struct gwp_sockaddr addr; int payloadlen; union { uint16_t txid; uint8_t payload[UDP_MSG_LIMIT]; }; +#endif + char *name; + char *service; + _Atomic(int) refcnt; + int res; + int ev_fd; + struct gwp_sockaddr addr; + struct gwp_dns_entry *next; }; enum { @@ -40,8 +45,11 @@ enum { struct gwp_dns_cfg { int cache_expiry; /* In seconds. <= 0 to disable cache. */ uint32_t nr_workers; - const char *ns_addr_str; uint32_t restyp; + bool use_raw_dns; +#ifdef CONFIG_RAW_DNS + const char *ns_addr_str; +#endif }; struct gwp_dns_ctx; @@ -71,7 +79,7 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx); /** * Queue a DNS resolution request. It returns a pointer to a gwp_dns_entry * with eventfd set to a valid file descriptor that can be used to wait for - * the resolution result. The caller's responsible to call gwp_dns_entry_free() + * the resolution result. The caller's responsible to call gwp_dns_entry_put() * to release the entry when it is no longer needed. * * The returned eventfd file descriptor is non-blocking. @@ -83,12 +91,24 @@ void gwp_dns_ctx_free(struct gwp_dns_ctx *ctx); */ struct gwp_dns_entry *gwp_dns_queue(struct gwp_dns_ctx *ctx, const char *name, const char *service); +/** + * Release a DNS entry. This function decrements the reference count of the + * entry. If the reference count reaches zero, the entry is freed. + * + * @param entry Pointer to the DNS entry to release. If the entry is + * NULL, this function does nothing. + * @return True if the entry was freed, false otherwise. + */ +bool gwp_dns_entry_put(struct gwp_dns_entry *entry); + +#ifdef CONFIG_RAW_DNS void cp_nsaddr(struct gwp_dns_ctx *ctx, struct gwp_sockaddr *addr, uint8_t *addrlen); -void gwp_dns_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e); +void gwp_dns_raw_entry_free(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e); int gwp_dns_process(struct gwp_dns_ctx *ctx, struct gwp_dns_entry *e); +#endif /** * Lookup a DNS entry in the cache. If the entry is found, it fills the diff --git a/src/gwproxy/ev/epoll.c b/src/gwproxy/ev/epoll.c index 5e117be2ae8f..a4ea80359236 100644 --- a/src/gwproxy/ev/epoll.c +++ b/src/gwproxy/ev/epoll.c @@ -155,7 +155,7 @@ static int free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp) int r; if (gde) { - r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->udp_fd, NULL); + r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL); if (unlikely(r)) return r; } @@ -782,36 +782,43 @@ static int arm_poll_for_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) { struct gwp_dns_entry *gde = gcp->gde; - struct gwp_sockaddr addr; - struct gwp_dns_ctx *dctx; struct epoll_event ev; - uint8_t addrlen; ssize_t r; assert(gde); - dctx = w->ctx->dns; + ev.events = EPOLLIN; + ev.data.u64 = 0; + ev.data.ptr = gcp; + ev.data.u64 |= EV_BIT_DNS_QUERY; + + if (w->ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + struct gwp_dns_ctx *dctx; + struct gwp_sockaddr addr; + uint8_t addrlen; + + dctx = w->ctx->dns; cp_nsaddr(dctx, &addr, &addrlen); r = __sys_sendto( gde->udp_fd, gde->payload, gde->payloadlen, MSG_NOSIGNAL, &addr.sa, addrlen ); if (unlikely(r < 0)) - goto exit_close; - - ev.events = EPOLLIN; - ev.data.u64 = 0; - ev.data.ptr = gcp; - ev.data.u64 |= EV_BIT_DNS_QUERY; + return (int)r; r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->udp_fd, &ev); if (unlikely(r)) - goto exit_close; + return (int)r; +#endif + } else { + assert(gde->ev_fd >= 0); + r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_ADD, gde->ev_fd, &ev); + if (unlikely(r)) + return (int)r; + } return 0; -exit_close: - close(gde->udp_fd); - return (int)r; } static void log_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp, @@ -844,9 +851,18 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) assert(ct == CONN_STATE_SOCKS5_DNS_QUERY || ct == CONN_STATE_HTTP_DNS_QUERY); - r = gwp_dns_process(w->ctx->dns, gde); - if (r) - gde->res = r; + if (w->ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + r = gwp_dns_process(w->ctx->dns, gde); + if (r) + gde->res = r; +#endif + } else { + assert(gde->ev_fd >= 0); + r = __sys_epoll_ctl(w->ep_fd, EPOLL_CTL_DEL, gde->ev_fd, NULL); + if (unlikely(r)) + return r; + } log_dns_query(w, gcp, gde); if (likely(!gde->res)) { @@ -858,8 +874,14 @@ static int handle_ev_dns_query(struct gwp_wrk *w, struct gwp_conn_pair *gcp) else r = -EIO; } - - gwp_dns_entry_free(w->ctx->dns, gde); + + if (w->ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + gwp_dns_raw_entry_free(w->ctx->dns, gde); +#endif + } else { + gwp_dns_entry_put(gde); + } gcp->gde = NULL; return r; } diff --git a/src/gwproxy/ev/io_uring.c b/src/gwproxy/ev/io_uring.c index 9cb06b1214ca..78440935383b 100644 --- a/src/gwproxy/ev/io_uring.c +++ b/src/gwproxy/ev/io_uring.c @@ -675,13 +675,13 @@ static int prep_domain_resolution(struct gwp_wrk *w, struct gwp_conn_pair *gcp) assert(gde); s = get_sqe_nofail(w); - io_uring_prep_poll_add(s, gde->udp_fd, POLLIN); + io_uring_prep_poll_add(s, gde->ev_fd, POLLIN); io_uring_sqe_set_data(s, gcp); s->user_data |= EV_BIT_IOU_DNS_QUERY; get_gcp(gcp); pr_dbg(&ctx->lh, "Prepared DNS query for domain '%s' (fd=%d, idx=%u, ref_cnt=%d)", - gde->name, gde->udp_fd, gcp->idx, gcp->ref_cnt); + gde->name, gde->ev_fd, gcp->idx, gcp->ref_cnt); return 0; } @@ -741,7 +741,7 @@ static int handle_ev_dns_query(struct gwp_wrk *w, void *udata) gde->name, ip_to_str(&gcp->target_addr), gcp->target.fd, gcp->idx); - gwp_dns_entry_free(ctx->dns, gde); + gwp_dns_entry_put(gde); gcp->gde = NULL; return handle_socks5_connect_target(w, gcp); } diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c index d0a010ab4b27..11efc21f4c5c 100644 --- a/src/gwproxy/gwproxy.c +++ b/src/gwproxy/gwproxy.c @@ -44,6 +44,7 @@ static const struct option long_opts[] = { { "help", no_argument, NULL, 'h' }, + { "raw-dns", required_argument, NULL, 'r' }, { "event-loop", required_argument, NULL, 'e' }, { "bind", required_argument, NULL, 'b' }, { "target", required_argument, NULL, 't' }, @@ -73,6 +74,7 @@ static const struct gwp_cfg default_opts = { .event_loop = "epoll", .bind = "[::]:1080", .target = NULL, + .use_raw_dns = false, .as_socks5 = false, .as_http = false, .socks5_prefer_ipv6 = false, @@ -102,6 +104,7 @@ static void show_help(const char *app) printf(" -h, --help Show this help message and exit\n"); printf(" -e, --event-loop=name Specify the event loop to use (default: %s)\n", default_opts.event_loop); printf(" Available values: epoll, io_uring\n"); + printf(" -r, --raw-dns=0|1 Use experimental raw DNS as the backend (default: %d)\n", default_opts.use_raw_dns); printf(" -b, --bind=addr:port Bind to the specified address (default: %s)\n", default_opts.bind); printf(" -t, --target=addr_port Target address to connect to\n"); printf(" -S, --as-socks5=0|1 Run as a SOCKS5 proxy (default: %d)\n", default_opts.as_socks5); @@ -156,6 +159,9 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg) case 'h': show_help(argv[0]); exit(0); + case 'r': + cfg->use_raw_dns = !!atoi(optarg); + break; case 'e': cfg->event_loop = optarg; break; @@ -683,13 +689,21 @@ static int gwp_ctx_init_dns(struct gwp_ctx *ctx) { struct gwp_cfg *cfg = &ctx->cfg; const struct gwp_dns_cfg dns_cfg = { + .use_raw_dns = cfg->use_raw_dns, .cache_expiry = cfg->socks5_dns_cache_secs, .restyp = cfg->socks5_prefer_ipv6 ? GWP_DNS_RESTYP_PREFER_IPV6 : 0, .nr_workers = 1, - .ns_addr_str = "1.1.1.1" + // .ns_addr_str = "1.1.1.1" }; int r; + if (cfg->use_raw_dns) { +#ifndef CONFIG_RAW_DNS + pr_err(&ctx->lh, "raw DNS backend is not enabled in this build"); + return -ENOSYS; +#endif + } + if (!cfg->as_socks5 && !cfg->as_http) { ctx->dns = NULL; return 0; @@ -728,6 +742,10 @@ static int gwp_ctx_parse_ev(struct gwp_ctx *ctx) ctx->ev_used = GWP_EV_EPOLL; pr_dbg(&ctx->lh, "Using event loop: epoll"); } else if (!strcmp(ev, "io_uring") || !strcmp(ev, "iou")) { + if (ctx->cfg.use_raw_dns) { + pr_err(&ctx->lh, "raw DNS backend is not supported for io_uring yet"); + return -ENOSYS; + } ctx->ev_used = GWP_EV_IO_URING; pr_dbg(&ctx->lh, "Using event loop: io_uring"); } else { @@ -985,8 +1003,15 @@ int gwp_free_conn_pair(struct gwp_wrk *w, struct gwp_conn_pair *gcp) if (gcp->timer_fd >= 0) __sys_close(gcp->timer_fd); - if (gcp->gde) - gwp_dns_entry_free(w->ctx->dns, gcp->gde); + if (gcp->gde) { + if (w->ctx->cfg.use_raw_dns) { +#ifdef CONFIG_RAW_DNS + gwp_dns_raw_entry_free(w->ctx->dns, gcp->gde); +#endif + } else { + gwp_dns_entry_put(gcp->gde); + } + } switch (gcp->prot_type) { case GWP_PROT_TYPE_SOCKS5: diff --git a/src/gwproxy/gwproxy.h b/src/gwproxy/gwproxy.h index 0206e68e7481..7f276d329e78 100644 --- a/src/gwproxy/gwproxy.h +++ b/src/gwproxy/gwproxy.h @@ -24,6 +24,7 @@ struct gwp_cfg { const char *event_loop; const char *bind; const char *target; + bool use_raw_dns; bool as_socks5; bool as_http; bool socks5_prefer_ipv6; -- Ahmad Gani