From 49c6aedc71b376a8644618cdbb6fd0bff8ee2d7a Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Sun, 15 Jun 2025 23:15:40 +0200 Subject: [PATCH 01/12] DNS server refactor Preparations for TCP serving --- src/dns_server.c | 81 ++++++++++++++++++------------------------------ src/dns_server.h | 8 +++-- src/main.c | 35 +++++++++++++++++---- src/options.c | 4 +++ src/options.h | 2 +- 5 files changed, 70 insertions(+), 60 deletions(-) diff --git a/src/dns_server.c b/src/dns_server.c index 9f150e8..c14f0b3 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -1,60 +1,39 @@ #include // NOLINT(llvmlibc-restrict-system-libc-headers) #include // NOLINT(llvmlibc-restrict-system-libc-headers) -#include // NOLINT(llvmlibc-restrict-system-libc-headers) -#include // NOLINT(llvmlibc-restrict-system-libc-headers) #include #include // NOLINT(llvmlibc-restrict-system-libc-headers) -#include // NOLINT(llvmlibc-restrict-system-libc-headers) #include // NOLINT(llvmlibc-restrict-system-libc-headers) #include "dns_server.h" #include "logging.h" -enum { -REQUEST_MAX = 1500 // A default MTU. We don't do TCP so any bigger is likely a waste -}; - - // Creates and bind a listening UDP socket for incoming requests. -static int get_listen_sock(const char *listen_addr, int listen_port, - unsigned int *addrlen) { - struct addrinfo *ai = NULL; - struct addrinfo hints; - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - memset(&hints, 0, sizeof(struct addrinfo)); - /* prevent DNS lookups if leakage is our worry */ - hints.ai_flags = AI_NUMERICHOST; - - int res = getaddrinfo(listen_addr, NULL, &hints, &ai); - if(res != 0) { - FLOG("Error parsing listen address %s:%d (getaddrinfo): %s", listen_addr, listen_port, - gai_strerror(res)); - if(ai) { - freeaddrinfo(ai); - } - return -1; +static int get_listen_sock(struct addrinfo *listen_addrinfo) { + int sock = socket(listen_addrinfo->ai_family, SOCK_DGRAM, 0); + if (sock < 0) { + FLOG("Error creating socket: %s (%d)", strerror(errno), errno); } - struct sockaddr_in *saddr = (struct sockaddr_in*) ai->ai_addr; - - *addrlen = ai->ai_addrlen; - saddr->sin_port = htons(listen_port); - - int sock = socket(ai->ai_family, SOCK_DGRAM, 0); - if (sock < 0) { - FLOG("Error creating socket"); + char ipstr[INET6_ADDRSTRLEN]; + if (listen_addrinfo->ai_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); + } else if (listen_addrinfo->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); + } else { + FLOG("Unknown address family: %d", listen_addrinfo->ai_family); } - res = bind(sock, ai->ai_addr, ai->ai_addrlen); + uint16_t port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); + + int res = bind(sock, listen_addrinfo->ai_addr, listen_addrinfo->ai_addrlen); if (res < 0) { - FLOG("Error binding %s:%d: %s (%d)", listen_addr, listen_port, - strerror(errno), res); + FLOG("Error binding on %s:%d UDP: %s (%d)", ipstr, port, + strerror(errno), errno); } - freeaddrinfo(ai); + ILOG("Listening on %s:%d UDP", ipstr, port); - ILOG("Listening on %s:%d", listen_addr, listen_port); return sock; } @@ -62,14 +41,10 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, ev_io *w, int __attribute__((unused)) revents) { dns_server_t *d = (dns_server_t *)w->data; - char *buf = (char *)calloc(1, REQUEST_MAX + 1); - if (buf == NULL) { - FLOG("Out of mem"); - } - struct sockaddr_storage raddr; - /* recvfrom can write to addrlen */ - socklen_t tmp_addrlen = d->addrlen; - ssize_t len = recvfrom(w->fd, buf, REQUEST_MAX, 0, (struct sockaddr*)&raddr, + char tmp_buf[UINT16_MAX]; // stack buffer for largest UDP packet to support EDNS + struct sockaddr_storage tmp_raddr; + socklen_t tmp_addrlen = d->addrlen; // recvfrom can write to addrlen + ssize_t len = recvfrom(w->fd, tmp_buf, UINT16_MAX, 0, (struct sockaddr*)&tmp_raddr, &tmp_addrlen); if (len < 0) { ELOG("recvfrom failed: %s", strerror(errno)); @@ -81,15 +56,21 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, return; } - uint16_t tx_id = ntohs(*((uint16_t*)buf)); - d->cb(d, d->cb_data, (struct sockaddr*)&raddr, tx_id, buf, len); + char *dns_req = (char *)malloc(len); // To free buffer after https request is complete. + if (dns_req == NULL) { + FLOG("Out of mem"); + } + memcpy(dns_req, tmp_buf, len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + + d->cb(d, d->cb_data, (struct sockaddr*)&tmp_raddr, dns_req, len); } void dns_server_init(dns_server_t *d, struct ev_loop *loop, - const char *listen_addr, int listen_port, + struct addrinfo *listen_addrinfo, dns_req_received_cb cb, void *data) { d->loop = loop; - d->sock = get_listen_sock(listen_addr, listen_port, &d->addrlen); + d->sock = get_listen_sock(listen_addrinfo); + d->addrlen = listen_addrinfo->ai_addrlen; d->cb = cb; d->cb_data = data; diff --git a/src/dns_server.h b/src/dns_server.h index 9982285..aa54846 100644 --- a/src/dns_server.h +++ b/src/dns_server.h @@ -1,6 +1,9 @@ #ifndef _DNS_SERVER_H_ #define _DNS_SERVER_H_ +#include +#include +#include #include #include #include @@ -8,8 +11,7 @@ struct dns_server_s; typedef void (*dns_req_received_cb)(struct dns_server_s *dns_server, void *data, - struct sockaddr* addr, uint16_t tx_id, - char *dns_req, size_t dns_req_len); + struct sockaddr* addr, char *dns_req, size_t dns_req_len); typedef struct dns_server_s { struct ev_loop *loop; @@ -21,7 +23,7 @@ typedef struct dns_server_s { } dns_server_t; void dns_server_init(dns_server_t *d, struct ev_loop *loop, - const char *listen_addr, int listen_port, + struct addrinfo *listen_addrinfo, dns_req_received_cb cb, void *data); // Sends a DNS response 'buf' of length 'blen' to 'raddr'. diff --git a/src/main.c b/src/main.c index 2e9f5f2..bf95b81 100644 --- a/src/main.c +++ b/src/main.c @@ -105,10 +105,11 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { } static void dns_server_cb(dns_server_t *dns_server, void *data, - struct sockaddr* addr, uint16_t tx_id, + struct sockaddr* tmp_remote_addr, char *dns_req, size_t dns_req_len) { app_state_t *app = (app_state_t *)data; + uint16_t tx_id = ntohs(*((uint16_t*)dns_req)); DLOG("Received request for id: %hX, len: %d", tx_id, dns_req_len); // If we're not yet bootstrapped, don't answer. libcurl will fall back to @@ -125,9 +126,9 @@ static void dns_server_cb(dns_server_t *dns_server, void *data, FLOG("%04hX: Out of mem", tx_id); } req->tx_id = tx_id; - memcpy(&req->raddr, addr, dns_server->addrlen); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memcpy(&req->raddr, tmp_remote_addr, dns_server->addrlen); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) req->dns_server = dns_server; - req->dns_req = dns_req; // To free buffer after https request is complete. + req->dns_req = dns_req; // To free buffer after https request is complete. req->start_tstamp = ev_now(dns_server->loop); req->stat = app->stat; @@ -135,7 +136,7 @@ static void dns_server_cb(dns_server_t *dns_server, void *data, stat_request_begin(app->stat, dns_req_len); } https_client_fetch(app->https_client, app->resolver_url, - dns_req, dns_req_len, app->resolv, req->tx_id, https_resp_cb, req); + req->dns_req, dns_req_len, app->resolv, req->tx_id, https_resp_cb, req); } static int addr_list_reduced(const char* full_list, const char* list) { @@ -205,6 +206,23 @@ static int proxy_supports_name_resolution(const char *proxy) return 0; } +static struct addrinfo * get_listen_address(const char *listen_addr) { + struct addrinfo *ai = NULL; + struct addrinfo hints; + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memset(&hints, 0, sizeof(struct addrinfo)); + /* prevent DNS lookups if leakage is our worry */ + hints.ai_flags = AI_NUMERICHOST; + + int res = getaddrinfo(listen_addr, NULL, &hints, &ai); + if (res != 0) { + FLOG("Error parsing listen address %s, getaddrinfo error: %s", + listen_addr, gai_strerror(res)); + } + + return ai; +} + static const char * sw_version(void) { #ifdef SW_VERSION return SW_VERSION; @@ -300,9 +318,14 @@ int main(int argc, char *argv[]) { app.using_dns_poller = 0; app.stat = (opt.stats_interval ? &stat : NULL); + struct addrinfo *listen_addrinfo = get_listen_address(opt.listen_addr); + ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons(opt.listen_port); + dns_server_t dns_server; - dns_server_init(&dns_server, loop, opt.listen_addr, opt.listen_port, - dns_server_cb, &app); + dns_server_init(&dns_server, loop, listen_addrinfo, dns_server_cb, &app); + + freeaddrinfo(listen_addrinfo); + listen_addrinfo = NULL; if (opt.gid != (uid_t)-1 && setgroups(1, &opt.gid)) { FLOG("Failed to set groups"); diff --git a/src/options.c b/src/options.c index e432e93..52a9ad8 100644 --- a/src/options.c +++ b/src/options.c @@ -183,6 +183,10 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char * printf("Flight recorder limit must be between 100 and 100000.\n"); return OPR_OPTION_ERROR; } + if (opt->listen_port < 0 || opt->listen_port > UINT16_MAX) { + printf("Listen port must be between 0 and %u.\n", UINT16_MAX); + return OPR_OPTION_ERROR; + } return OPR_SUCCESS; } diff --git a/src/options.h b/src/options.h index b303ac0..c0a36ad 100644 --- a/src/options.h +++ b/src/options.h @@ -6,7 +6,7 @@ struct Options { const char *listen_addr; - uint16_t listen_port; + int listen_port; // Logfile. const char *logfile; From df97ceeb493df59e3437a79fbcc260e28a10d616 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:38:12 +0200 Subject: [PATCH 02/12] Added DNS server with TCP support - added TCP client limit option, also can be used to disable TCP DNS server in case of issues - readme and help pages updated - combined API with UDP DNS server - using minimally necessary public header - added TCP only statistic counters - added TCP functional tests - basic querries with valgrind - fragmented TCP request processing - testing 4k large response support - started to use stricter compiler warnings --- .gitignore | 1 + CMakeLists.txt | 8 +- README.md | 6 +- munin/https_dns_proxy.plugin | 17 +- src/dns_server.c | 4 +- src/dns_server.h | 2 +- src/dns_server_tcp.c | 371 +++++++++++++++++++++++++++++ src/dns_server_tcp.h | 19 ++ src/logging.c | 2 +- src/main.c | 42 +++- src/options.c | 17 +- src/options.h | 2 + src/stat.c | 40 +++- src/stat.h | 14 +- tests/robot/DnsTcpClient.py | 65 +++++ tests/robot/functional_tests.robot | 69 +++++- 16 files changed, 637 insertions(+), 42 deletions(-) create mode 100644 src/dns_server_tcp.c create mode 100755 src/dns_server_tcp.h create mode 100644 tests/robot/DnsTcpClient.py diff --git a/.gitignore b/.gitignore index 49ddd59..967c5bd 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ output.xml report.html custom_curl/ valgrind-*.log +tests/robot/__pycache__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 55152ec..20549c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,12 +25,14 @@ if (NOT CMAKE_INSTALL_BINDIR) set(CMAKE_INSTALL_BINDIR bin) endif() -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra --pedantic -Wno-strict-aliasing -Wno-variadic-macros") +# TODO: add flags: -Wconversion -Wsign-conversion -Wfloat-conversion -Wcast-align -Wimplicit-fallthrough +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wformat=2 -Wunused -Wno-variadic-macros -Wnull-dereference -Wshadow") set(CMAKE_C_FLAGS_DEBUG "-gdwarf-4 -DDEBUG") set(CMAKE_C_FLAGS_RELEASE "-O2") -if ((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9) OR - (CMAKE_C_COMPILER_ID MATCHES Clang AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10)) +if (((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9) AND + (CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_LESS 14)) OR + ( CMAKE_C_COMPILER_ID MATCHES Clang AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10)) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-gnu-zero-variadic-macro-arguments -Wno-gnu-folding-constant") endif() diff --git a/README.md b/README.md index 14313c8..8f0ad31 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `https_dns_proxy` is a light-weight DNS<-->HTTPS, non-caching translation proxy for the [RFC 8484][rfc-8484] DNS-over-HTTPS standard. It receives -regular (UDP) DNS requests and issues them via DoH. +regular (UDP or TCP) DNS requests and issues them via DoH. [Google's DNS-over-HTTPS][google-doh] service is default, but [Cloudflare's service][cloudflare-doh] also works with trivial commandline flag @@ -158,7 +158,7 @@ docker run --name "https-dns-proxy" -p 5053:5053/udp \ Just run it as a daemon and point traffic at it. Commandline flags are: ``` -Usage: ./https_dns_proxy [-a ] [-p ] +Usage: ./https_dns_proxy [-a ] [-p ] [-T ] [-b ] [-i ] [-4] [-r ] [-t ] [-x] [-q] [-C ] [-c ] [-d] [-u ] [-g ] @@ -167,6 +167,8 @@ Usage: ./https_dns_proxy [-a ] [-p ] DNS server -a listen_addr Local IPv4/v6 address to bind to. (Default: 127.0.0.1) -p listen_port Local port to bind to. (Default: 5053) + -T tcp_client_limit Number of TCP clients to serve. + (Default: 20, Disabled: 0, Min: 1, Max: 200) DNS client -b dns_servers Comma-separated IPv4/v6 addresses and ports (addr:port) diff --git a/munin/https_dns_proxy.plugin b/munin/https_dns_proxy.plugin index 654450e..6f4663f 100755 --- a/munin/https_dns_proxy.plugin +++ b/munin/https_dns_proxy.plugin @@ -11,6 +11,8 @@ graph_scale no graph_args --base 1000 --lower-limit 0 requests.label Requests responses.label Responses +tcprequests.label TcpRequests +tcpresponses.label TcpResponses multigraph https_dns_proxy_latency graph_title HTTPS DNS proxy - latency @@ -19,6 +21,7 @@ graph_category network graph_scale no graph_args --base 1000 --lower-limit 0 latency.label Latency +tcplatency.label TcpLatency multigraph https_dns_proxy_connections graph_title HTTPS DNS proxy - connections @@ -40,7 +43,7 @@ EOM esac log_lines=$(journalctl --unit https_dns_proxy.service --output cat --since '6 minutes ago') -pattern='stat\.c:[0-9]+ ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$' +pattern='stat\.c:[0-9]+ ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$' # match log lines with pattern (last match will be used) IFS=' @@ -53,18 +56,26 @@ for line in $log_lines; do fi done -latency='U' if [ -n "${stat[3]}" ] && \ [ -n "${stat[2]}" ] && \ [ "${stat[2]}" -gt "0" ]; then latency=$((${stat[3]} / ${stat[2]})) fi +if [ -n "${stat[11]}" ] && \ + [ -n "${stat[10]}" ] && \ + [ "${stat[10]}" -gt "0" ]; then + tcplatency=$((${stat[11]} / ${stat[10]})) +fi + echo "multigraph https_dns_proxy_count" echo "requests.value ${stat[1]:-U}" echo "responses.value ${stat[2]:-U}" +echo "tcprequests.value ${stat[9]:-U}" +echo "tcpresponses.value ${stat[10]:-U}" echo "multigraph https_dns_proxy_latency" -echo "latency.value ${latency}" +echo "latency.value ${latency:-0}" +echo "tcplatency.value ${tcplatency:-0}" echo "multigraph https_dns_proxy_connections" echo "opened.value ${stat[6]:-U}" echo "closed.value ${stat[7]:-U}" diff --git a/src/dns_server.c b/src/dns_server.c index c14f0b3..361ba75 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -52,7 +52,7 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, } if (len < (int)sizeof(uint16_t)) { - WLOG("Malformed request received (too short)."); + WLOG("Malformed request received, too short: %d", len); return; } @@ -62,7 +62,7 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, } memcpy(dns_req, tmp_buf, len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - d->cb(d, d->cb_data, (struct sockaddr*)&tmp_raddr, dns_req, len); + d->cb(d, 0, d->cb_data, (struct sockaddr*)&tmp_raddr, dns_req, len); } void dns_server_init(dns_server_t *d, struct ev_loop *loop, diff --git a/src/dns_server.h b/src/dns_server.h index aa54846..9699c6c 100644 --- a/src/dns_server.h +++ b/src/dns_server.h @@ -10,7 +10,7 @@ struct dns_server_s; -typedef void (*dns_req_received_cb)(struct dns_server_s *dns_server, void *data, +typedef void (*dns_req_received_cb)(void *dns_server, uint8_t is_tcp, void *data, struct sockaddr* addr, char *dns_req, size_t dns_req_len); typedef struct dns_server_s { diff --git a/src/dns_server_tcp.c b/src/dns_server_tcp.c new file mode 100644 index 0000000..0a25d04 --- /dev/null +++ b/src/dns_server_tcp.c @@ -0,0 +1,371 @@ +//NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) +#define _GNU_SOURCE // needed for having accept4() + +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) + +#include "dns_server_tcp.h" +#include "logging.h" + +// the following macros require to have client pointer to tcp_client_s structure +// else: compilation failure will occur +#define LOG_CLIENT(level, format, args...) LOG(level, "C-%u: " format, client->id, ## args) +#define DLOG_CLIENT(format, args...) DLOG("C-%u: " format, client->id, ## args) +#define ILOG_CLIENT(format, args...) ILOG("C-%u: " format, client->id, ## args) +#define WLOG_CLIENT(format, args...) WLOG("C-%u: " format, client->id, ## args) +#define ELOG_CLIENT(format, args...) ELOG("C-%u: " format, client->id, ## args) +#define FLOG_CLIENT(format, args...) FLOG("C-%u: " format, client->id, ## args) + +enum { + LISTEN_BACKLOG = 5, + MIN_DNS_LENGTH = 12, // RFC1035 4.1.1 header size + IDLE_TIMEOUT_S = 120, // "two minutes" according to RFC1035 4.2.2 + RESEND_DELAY_US = 500, // 0.0005 sec +}; + +struct tcp_client_s { + struct dns_server_tcp_s * d; + + uint64_t id; + int sock; + + struct sockaddr_storage raddr; + socklen_t addr_len; + + char * input_buffer; + uint32_t input_buffer_size; + uint32_t input_buffer_used; + + ev_io read_watcher; + ev_timer timer_watcher; + + struct tcp_client_s * next; +} __attribute__((packed)) __attribute__((aligned(128))); + +struct dns_server_tcp_s { + struct ev_loop *loop; + + dns_req_received_cb cb; + void *cb_data; + + int sock; + socklen_t addrlen; + ev_io accept_watcher; + + uint64_t client_id; + uint16_t client_count; + uint16_t client_limit; + struct tcp_client_s * clients; +} __attribute__((packed)) __attribute__((aligned(128))); + + +static void remove_client(struct tcp_client_s * client) { + dns_server_tcp_t *d = client->d; + + DLOG_CLIENT("Removing client, socket %d", client->sock); + + if (d->client_count == d->client_limit) { + ev_io_start(d->loop, &d->accept_watcher); // continue accepting new client connections + } + d->client_count--; + + ev_io_stop(d->loop, &client->read_watcher); + ev_timer_stop(d->loop, &client->timer_watcher); + + free(client->input_buffer); + + close(client->sock); + + if (d->clients == client) { + d->clients = client->next; + } + else { + for (struct tcp_client_s * cur = d->clients; cur != NULL; cur = cur->next) { + if (cur->next == client) { + cur->next = client->next; + break; + } + } + } + + free(client); +} + +static int get_dns_request(struct tcp_client_s *client, + char ** dns_req, uint16_t * req_size) { + // check if whole request is available + *req_size = ntohs(*((uint16_t*)client->input_buffer)); + uint16_t data_size = sizeof(uint16_t) + *req_size; + if (data_size > client->input_buffer_used) { + return 0; // Partial request + } + // copy whole request + *dns_req = (char *)malloc(*req_size); // To free buffer after https request is complete. + if (*dns_req == NULL) { + FLOG_CLIENT("Out of mem"); + } + memcpy(*dns_req, client->input_buffer + sizeof(uint16_t), *req_size); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + // move down data of next request(s) if any + client->input_buffer_used -= data_size; + memmove(client->input_buffer, client->input_buffer + data_size, client->input_buffer_used); // NOLINT(clang-diagnostic-format-nonliteral,clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + return 1; +} + +static void read_cb(struct ev_loop __attribute__((unused)) *loop, + ev_io *w, int __attribute__((unused)) revents) { + struct tcp_client_s *client = (struct tcp_client_s *)w->data; + dns_server_tcp_t *d = client->d; + + // Receive data + char buf[UINT16_MAX]; // stack buffer for largest DNS request + ssize_t len = recv(w->fd, &buf, UINT16_MAX, 0); + if (len <= 0) { + if (len == 0 || errno == ECONNRESET) { + DLOG_CLIENT("Connection closed"); + } else if (errno == EAGAIN && errno == EWOULDBLOCK) { + return; + } else { + WLOG_CLIENT("Read error: %s", strerror(errno)); + } + remove_client(client); + return; + } + + // Append data into input buffer + const uint32_t free_space = client->input_buffer_size - client->input_buffer_used; + const uint32_t needed_space = client->input_buffer_used + (uint32_t)len; + DLOG_CLIENT("Received %d byte, free: %u", len, free_space); + if (free_space < len) { + for (client->input_buffer_size = 64; // lower value does not make much sense + client->input_buffer_size < needed_space; + client->input_buffer_size *= 2) { + if (client->input_buffer_size > 2*UINT16_MAX) { + FLOG_CLIENT("Unrealistic input buffer size: %u", client->input_buffer_size); + } + } + DLOG_CLIENT("Resize input buffer to %u", client->input_buffer_size); + client->input_buffer = (char *) realloc((void*) client->input_buffer, // NOLINT(bugprone-suspicious-realloc-usage) if realloc fails, program stops + client->input_buffer_size); + if (client->input_buffer == NULL) { + FLOG_CLIENT("Out of mem"); + } + } + memcpy(client->input_buffer + client->input_buffer_used, buf, (size_t)len); // NOLINT(clang-diagnostic-format-nonliteral,clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + client->input_buffer_used = needed_space; + + // Split requests + char *dns_req = NULL; + uint16_t req_size = 0; + uint8_t request_received = 0; + while (get_dns_request(client, &dns_req, &req_size)) { + if (req_size < MIN_DNS_LENGTH) { + WLOG_CLIENT("Malformed request received, too short: %u", req_size); + free(dns_req); + remove_client(client); + return; + } + + d->cb(d, 1, d->cb_data, (struct sockaddr*)&client->raddr, dns_req, req_size); + request_received = 1; + } + + if (request_received) { + ev_timer_again(d->loop, &client->timer_watcher); + } +} + +static void timer_cb(struct ev_loop __attribute__((unused)) *loop, + ev_timer *w, int __attribute__((unused)) revents) { + struct tcp_client_s *client = (struct tcp_client_s *)w->data; + DLOG_CLIENT("TCP client timeouted"); + remove_client(client); +} + +static void accept_cb(struct ev_loop __attribute__((unused)) *loop, + ev_io *w, int __attribute__((unused)) revents) { + dns_server_tcp_t *d = (dns_server_tcp_t *)w->data; + + struct sockaddr_storage client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + int client_sock = accept4(w->fd, (struct sockaddr *)&client_addr, + &client_addr_len, SOCK_NONBLOCK); + if (client_sock == -1 && errno != EAGAIN && errno != EWOULDBLOCK) { + ELOG("Failed to accept TCP client: %s", strerror(errno)); + return; + } + + d->client_id++; + d->client_count++; + if (d->client_count == d->client_limit) { + ev_io_stop(d->loop, &d->accept_watcher); // suspend accepting new client connections + } + + struct tcp_client_s *client = (struct tcp_client_s *)calloc(1, sizeof(struct tcp_client_s)); + if (client == NULL) { + FLOG("Out of mem"); + } + client->d = d; + client->id = d->client_id; + client->sock = client_sock; + memcpy(&client->raddr, &client_addr, client_addr_len); // NOLINT(clang-diagnostic-format-nonliteral,clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + client->addr_len = client_addr_len; + client->input_buffer = NULL; + client->next = d->clients; + d->clients = client; + + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + ev_io_init(&client->read_watcher, read_cb, client->sock, EV_READ); + client->read_watcher.data = client; + ev_io_start(d->loop, &client->read_watcher); + + ev_init(&client->timer_watcher, timer_cb); + client->timer_watcher.repeat = IDLE_TIMEOUT_S; + client->timer_watcher.data = client; + ev_timer_again(d->loop, &client->timer_watcher); + + DLOG_CLIENT("Accepted client %u of %u, socket %d", d->client_count, d->client_limit, client->sock); +} + +// Creates and bind a listening non-blocking TCP socket for incoming requests. +static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) { + int sock = socket(listen_addrinfo->ai_family, SOCK_STREAM, 0); + if (sock < 0) { + FLOG("Error creating TCP socket: %s (%d)", strerror(errno), errno); + } + + int yes = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { + ELOG("Reuse address failed: %s (%d)", strerror(errno), errno); + } + + char ipstr[INET6_ADDRSTRLEN]; + if (listen_addrinfo->ai_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); + } else if (listen_addrinfo->ai_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); + } else { + FLOG("Unknown address family: %d", listen_addrinfo->ai_family); + } + + uint16_t port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); + + int res = bind(sock, listen_addrinfo->ai_addr, listen_addrinfo->ai_addrlen); + if (res < 0) { + FLOG("Error binding on %s:%d TCP: %s (%d)", ipstr, port, + strerror(errno), errno); + } + + if (listen(sock, LISTEN_BACKLOG) == -1) { + FLOG("Error listaning on %s:%d TCP: %s (%d)", ipstr, port, + strerror(errno), errno); + } + + int flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) { + FLOG("Error getting TCP socket flags: %s (%d)", ipstr, port, + strerror(errno), errno); + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + FLOG("Error setting TCP socket to non-blocking: %s (%d)", ipstr, port, + strerror(errno), errno); + } + + ILOG("Listening on %s:%d TCP", ipstr, port); + + return sock; +} + +dns_server_tcp_t * dns_server_tcp_create( + struct ev_loop *loop, struct addrinfo *listen_addrinfo, + dns_req_received_cb cb, void *data, uint16_t tcp_client_limit) { + dns_server_tcp_t * d = (dns_server_tcp_t *) malloc(sizeof(dns_server_tcp_t)); + if (d == NULL) { + FLOG("Out of mem"); + } + d->loop = loop; + d->cb = cb; + d->cb_data = data; + d->sock = get_tcp_listen_sock(listen_addrinfo); + d->addrlen = listen_addrinfo->ai_addrlen; + d->client_id = 0; + d->client_count = 0; + d->client_limit = tcp_client_limit; + d->clients = NULL; + + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + ev_io_init(&d->accept_watcher, accept_cb, d->sock, EV_READ); + d->accept_watcher.data = d; + ev_io_start(d->loop, &d->accept_watcher); + + return d; +} + +void dns_server_tcp_respond(dns_server_tcp_t *d, + struct sockaddr *raddr, char *resp, size_t resp_len) +{ + if (resp_len < MIN_DNS_LENGTH || resp_len > UINT16_MAX) { + WLOG("Malformed request received, invalid length: %u", resp_len); + return; + } + + // find client data + struct tcp_client_s *client = NULL; + for (struct tcp_client_s * cur = d->clients; cur != NULL; cur = cur->next) { + if (memcmp(raddr, &(cur->raddr), cur->addr_len) == 0) { + client = cur; + break; + } + } + if (client == NULL) { + uint16_t response_id = ntohs(*((uint16_t*)resp)); + WLOG("Could not find client, can not send DNS response: %04hX", response_id); + return; + } + + DLOG_CLIENT("Sending %u bytes", resp_len); + + // send length of response + uint16_t resp_size = htons((uint16_t)resp_len); + ssize_t len = send(client->sock, &resp_size, sizeof(uint16_t), MSG_MORE | MSG_NOSIGNAL); + if (len != sizeof(uint16_t)) { + WLOG_CLIENT("Send error: %s, len: %d", strerror(errno), len); + remove_client(client); + return; + } + + // send the response + ssize_t sent = 0; + for (uint8_t i = 0; i < UINT8_MAX; ++i) // endless loop guard + { + len = send(client->sock, resp + sent, resp_len - (size_t)sent, MSG_NOSIGNAL); + if (len < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + WLOG_CLIENT("Send error: %s", strerror(errno)); + remove_client(client); + return; + } + } + sent += len; + + if (sent == (ssize_t)resp_len) { + break; + } + + usleep(RESEND_DELAY_US); + } + + ev_timer_again(d->loop, &client->timer_watcher); +} + +void dns_server_tcp_stop(dns_server_tcp_t *d) { + while (d->clients) { + remove_client(d->clients); //NOLINT(clang-analyzer-unix.Malloc) false use after free detection + } + ev_io_stop(d->loop, &d->accept_watcher); +} + +void dns_server_tcp_cleanup(dns_server_tcp_t *d) { + close(d->sock); +} diff --git a/src/dns_server_tcp.h b/src/dns_server_tcp.h new file mode 100755 index 0000000..3fb32a9 --- /dev/null +++ b/src/dns_server_tcp.h @@ -0,0 +1,19 @@ +#ifndef _DNS_SERVER_TCP_H_ +#define _DNS_SERVER_TCP_H_ + +#include "dns_server.h" + +typedef struct dns_server_tcp_s dns_server_tcp_t; + +dns_server_tcp_t * dns_server_tcp_create( + struct ev_loop *loop, struct addrinfo *listen_addrinfo, + dns_req_received_cb cb, void *data, uint16_t tcp_client_limit); + +void dns_server_tcp_respond(dns_server_tcp_t *d, + struct sockaddr *raddr, char *resp, size_t resp_len); + +void dns_server_tcp_stop(dns_server_tcp_t *d); + +void dns_server_tcp_cleanup(dns_server_tcp_t *d); + +#endif // _DNS_SERVER_H_ diff --git a/src/logging.c b/src/logging.c index a85a45f..23030e7 100644 --- a/src/logging.c +++ b/src/logging.c @@ -125,7 +125,7 @@ void _log(const char *file, int line, int severity, const char *fmt, ...) { va_list args; va_start(args, fmt); - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + // NOLINTNEXTLINE(clang-diagnostic-format-nonliteral,clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) chars = vsnprintf(buff + buff_pos, LOG_LINE_SIZE - buff_pos, fmt, args); va_end(args); diff --git a/src/main.c b/src/main.c index bf95b81..04cbabe 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include "dns_poller.h" #include "dns_server.h" +#include "dns_server_tcp.h" #include "https_client.h" #include "logging.h" #include "options.h" @@ -24,11 +25,13 @@ typedef struct { const char *resolver_url; stat_t *stat; uint8_t using_dns_poller; + socklen_t addrlen; } app_state_t; // NOLINTNEXTLINE(altera-struct-pack-align) typedef struct { - dns_server_t *dns_server; + void *dns_server; + uint8_t is_tcp; char* dns_req; stat_t *stat; ev_tstamp start_tstamp; @@ -94,9 +97,13 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { WLOG("DNS request and response IDs are not matching: %hX != %hX", req->tx_id, response_id); } else { - dns_server_respond(req->dns_server, (struct sockaddr*)&req->raddr, buf, buflen); + if (req->is_tcp) { + dns_server_tcp_respond((dns_server_tcp_t *)req->dns_server, (struct sockaddr*)&req->raddr, buf, buflen); + } else { + dns_server_respond((dns_server_t *)req->dns_server, (struct sockaddr*)&req->raddr, buf, buflen); + } if (req->stat) { - stat_request_end(req->stat, buflen, ev_now(req->dns_server->loop) - req->start_tstamp); + stat_request_end(req->stat, buflen, ev_now(req->stat->loop) - req->start_tstamp, req->is_tcp); } } } @@ -104,7 +111,7 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { free(req); } -static void dns_server_cb(dns_server_t *dns_server, void *data, +static void dns_server_cb(void *dns_server, uint8_t is_tcp, void *data, struct sockaddr* tmp_remote_addr, char *dns_req, size_t dns_req_len) { app_state_t *app = (app_state_t *)data; @@ -126,14 +133,15 @@ static void dns_server_cb(dns_server_t *dns_server, void *data, FLOG("%04hX: Out of mem", tx_id); } req->tx_id = tx_id; - memcpy(&req->raddr, tmp_remote_addr, dns_server->addrlen); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memcpy(&req->raddr, tmp_remote_addr, app->addrlen); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) req->dns_server = dns_server; + req->is_tcp = is_tcp; req->dns_req = dns_req; // To free buffer after https request is complete. - req->start_tstamp = ev_now(dns_server->loop); req->stat = app->stat; if (req->stat) { - stat_request_begin(app->stat, dns_req_len); + req->start_tstamp = ev_now(app->stat->loop); + stat_request_begin(app->stat, dns_req_len, is_tcp); } https_client_fetch(app->https_client, app->resolver_url, req->dns_req, dns_req_len, app->resolv, req->tx_id, https_resp_cb, req); @@ -311,19 +319,25 @@ int main(int argc, char *argv[]) { https_client_t https_client; https_client_init(&https_client, &opt, (opt.stats_interval ? &stat : NULL), loop); + struct addrinfo *listen_addrinfo = get_listen_address(opt.listen_addr); + ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons(opt.listen_port); + app_state_t app; app.https_client = &https_client; app.resolv = NULL; app.resolver_url = opt.resolver_url; app.using_dns_poller = 0; app.stat = (opt.stats_interval ? &stat : NULL); - - struct addrinfo *listen_addrinfo = get_listen_address(opt.listen_addr); - ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons(opt.listen_port); + app.addrlen = listen_addrinfo->ai_addrlen; dns_server_t dns_server; dns_server_init(&dns_server, loop, listen_addrinfo, dns_server_cb, &app); + dns_server_tcp_t * dns_server_tcp = NULL; + if (opt.tcp_client_limit > 0) { + dns_server_tcp = dns_server_tcp_create(loop, listen_addrinfo, dns_server_cb, &app, opt.tcp_client_limit); + } + freeaddrinfo(listen_addrinfo); listen_addrinfo = NULL; @@ -390,6 +404,9 @@ int main(int argc, char *argv[]) { ev_signal_stop(loop, &sigint); ev_signal_stop(loop, &sigpipe); dns_server_stop(&dns_server); + if (dns_server_tcp != NULL) { + dns_server_tcp_stop(dns_server_tcp); + } stat_stop(&stat); DLOG("re-entering loop"); @@ -397,6 +414,11 @@ int main(int argc, char *argv[]) { DLOG("loop finished all events"); dns_server_cleanup(&dns_server); + if (dns_server_tcp != NULL) { + dns_server_tcp_cleanup(dns_server_tcp); + free(dns_server_tcp); + dns_server_tcp = NULL; + } https_client_cleanup(&https_client); stat_cleanup(&stat); diff --git a/src/options.c b/src/options.c index 52a9ad8..13cc7f7 100644 --- a/src/options.c +++ b/src/options.c @@ -16,12 +16,14 @@ #endif enum { -DEFAULT_HTTP_VERSION = 2 +DEFAULT_HTTP_VERSION = 2, +MAX_TCP_CLIENTS = 200 }; void options_init(struct Options *opt) { opt->listen_addr = "127.0.0.1"; opt->listen_port = 5053; + opt->tcp_client_limit = 20; opt->logfile = "-"; opt->logfd = STDOUT_FILENO; opt->loglevel = LOG_ERROR; @@ -46,7 +48,7 @@ void options_init(struct Options *opt) { enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char **argv) { int c = 0; - while ((c = getopt(argc, argv, "a:c:p:du:g:b:i:4r:e:t:l:vxqm:s:C:F:hV")) != -1) { + while ((c = getopt(argc, argv, "a:c:p:T:du:g:b:i:4r:e:t:l:vxqm:s:C:F:hV")) != -1) { switch (c) { case 'a': // listen_addr opt->listen_addr = optarg; @@ -57,6 +59,9 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char * case 'p': // listen_port opt->listen_port = atoi(optarg); break; + case 'T': // tcp_client_limit + opt->tcp_client_limit = atoi(optarg); + break; case 'd': // daemonize opt->daemonize = 1; break; @@ -187,13 +192,17 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char * printf("Listen port must be between 0 and %u.\n", UINT16_MAX); return OPR_OPTION_ERROR; } + if (opt->tcp_client_limit < 0 || opt->tcp_client_limit > MAX_TCP_CLIENTS) { + printf("TCP client limit must be between 0 and %u.\n", MAX_TCP_CLIENTS); + return OPR_OPTION_ERROR; + } return OPR_SUCCESS; } void options_show_usage(int __attribute__((unused)) argc, char **argv) { struct Options defaults; options_init(&defaults); - printf("Usage: %s [-a ] [-p ]\n", argv[0]); + printf("Usage: %s [-a ] [-p ] [-T ]\n", argv[0]); printf(" [-b ] [-i ] [-4]\n"); printf(" [-r ] [-t ] [-x] [-q] [-C ] [-c ]\n"); printf(" [-d] [-u ] [-g ] \n"); @@ -203,6 +212,8 @@ void options_show_usage(int __attribute__((unused)) argc, char **argv) { defaults.listen_addr); printf(" -p listen_port Local port to bind to. (Default: %d)\n", defaults.listen_port); + printf(" -T tcp_client_limit Number of TCP clients to serve. (Default: %d, Disabled: 0, Min: 1, Max: %d)\n", + defaults.tcp_client_limit, MAX_TCP_CLIENTS); printf("\n DNS client\n"); printf(" -b dns_servers Comma-separated IPv4/v6 addresses and ports (addr:port)\n"); printf(" of DNS servers to resolve resolver host (e.g. dns.google).\n"\ diff --git a/src/options.h b/src/options.h index c0a36ad..9dd5c81 100644 --- a/src/options.h +++ b/src/options.h @@ -8,6 +8,8 @@ struct Options { const char *listen_addr; int listen_port; + int tcp_client_limit; + // Logfile. const char *logfile; int logfd; diff --git a/src/stat.c b/src/stat.c index af3c921..bddf758 100644 --- a/src/stat.c +++ b/src/stat.c @@ -7,17 +7,26 @@ static void reset_counters(stat_t *s) { s->requests = 0; s->responses = 0; s->query_times_sum = 0; + s->connections_opened = 0; s->connections_closed = 0; s->connections_reused = 0; + + s->tcp_requests_size = 0; + s->tcp_responses_size = 0; + s->tcp_requests = 0; + s->tcp_responses = 0; + s->tcp_query_times_sum = 0; } static void stat_print(stat_t *s) { - SLOG("%llu %llu %llu %zu %zu %llu %llu %llu", + SLOG("%llu %llu %llu %zu %zu %llu %llu %llu %llu %llu %llu %zu %zu", s->requests, s->responses, s->query_times_sum, s->requests_size, s->responses_size, s->connections_opened, s->connections_closed, - s->connections_reused); + s->connections_reused, + s->tcp_requests, s->tcp_responses, s->tcp_query_times_sum, + s->tcp_requests_size, s->tcp_responses_size); reset_counters(s); } @@ -40,23 +49,36 @@ void stat_init(stat_t *s, struct ev_loop *loop, int stats_interval) { ev_timer_start(loop, &s->stats_timer); SLOG("RequestsCount ResponsesCount LatencyMilisecondsSummary " "RequestsSize ResponsesSize ConnectionsOpened ConnectionsClosed " - "ConnectionsReused"); + "ConnectionsReused TcpRequestsCount TcpResponsesCount " + "TcpLatencyMilisecondsSummary TcpRequestsSize TcpResponsesSize"); } } -void stat_request_begin(stat_t *s, size_t req_len) +void stat_request_begin(stat_t *s, size_t req_len, uint8_t is_tcp) { + if (is_tcp) { + s->tcp_requests_size += req_len; + s->tcp_requests++; + } else { s->requests_size += req_len; s->requests++; + } } -void stat_request_end(stat_t *s, size_t resp_len, ev_tstamp latency) +void stat_request_end(stat_t *s, size_t resp_len, ev_tstamp latency, uint8_t is_tcp) { if (resp_len) { - s->responses_size += resp_len; - s->responses++; - // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - s->query_times_sum += (latency * 1000); + if (is_tcp) { + s->tcp_responses_size += resp_len; + s->tcp_responses++; + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + s->tcp_query_times_sum += (uint64_t)(latency * 1000); + } else { + s->responses_size += resp_len; + s->responses++; + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + s->query_times_sum += (uint64_t)(latency * 1000); + } } } diff --git a/src/stat.h b/src/stat.h index f96674c..9c1f4bc 100644 --- a/src/stat.h +++ b/src/stat.h @@ -19,22 +19,30 @@ typedef struct { struct ev_loop *loop; int stats_interval; + ev_timer stats_timer; + size_t requests_size; size_t responses_size; uint64_t requests; uint64_t responses; uint64_t query_times_sum; + uint64_t connections_opened; uint64_t connections_closed; uint64_t connections_reused; - ev_timer stats_timer; + + size_t tcp_requests_size; + size_t tcp_responses_size; + uint64_t tcp_requests; + uint64_t tcp_responses; + uint64_t tcp_query_times_sum; } stat_t; void stat_init(stat_t *s, struct ev_loop *loop, int stats_interval); -void stat_request_begin(stat_t *s, size_t req_len); +void stat_request_begin(stat_t *s, size_t req_len, uint8_t is_tcp); -void stat_request_end(stat_t *s, size_t resp_len, ev_tstamp latency); +void stat_request_end(stat_t *s, size_t resp_len, ev_tstamp latency, uint8_t is_tcp); void stat_connection_opened(stat_t *s); diff --git a/tests/robot/DnsTcpClient.py b/tests/robot/DnsTcpClient.py new file mode 100644 index 0000000..35ba0b2 --- /dev/null +++ b/tests/robot/DnsTcpClient.py @@ -0,0 +1,65 @@ +import socket + + +class DnsTcpClient: + + ROBOT_LIBRARY_SCOPE = 'GLOBAL' + + google_dns_request_byte_parts = [ + b'\x00\x1C', # 2 byte: DNS request length: 28 byte + b'\x00\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00', # 12 byte + b'\x06google\x03com\x00', # 12 byte + b'\x00\x01\x00\x01' # 4 byte + ] + + def __init__(self): + self.client_socket = None + + def open_tcp_client_connection(self, host, port): + try: + self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.client_socket.settimeout(10) + self.client_socket.connect((host, int(port))) + print(f"Successfully connected to {host}:{port}") + except Exception as e: + raise Exception(f"Failed to open TCP connection: {e}") + + def send_tcp_request_parts(self, *parts): + if not self.client_socket: + raise Exception("No TCP connection open. Call 'Open Tcp Client Connection' first.") + try: + msg = b'' + for part in parts: + msg += DnsTcpClient.google_dns_request_byte_parts[int(part) - 1] + self.client_socket.sendall(msg) + print(f"Sent parts {' '.join(parts)}, bytes: {len(msg)}") + except Exception as e: + raise Exception(f"Failed to send message: {e}") + + def receive_tcp_response(self): + if not self.client_socket: + raise Exception("No TCP connection open. Call 'Open Tcp Client Connection' first.") + msg = b'' + run = True + dnslen = 0 + while run: + try: + data = self.client_socket.recv(1024) + if not data: + raise Exception(f"Connection closed!") + print(f"Received {len(data)} bytes") + msg += data + if not dnslen: + dnslen = msg[0] * 256 + msg[1] + print(f"DNS response length: {dnslen} bytes") + if len(msg) == dnslen + 2: + run = False + except Exception as e: + raise Exception(f"Failed to receive message: {e}") from e + return msg[2:] + + def close_tcp_client_connection(self): + if self.client_socket: + self.client_socket.close() + self.client_socket = None + print("TCP connection closed.") diff --git a/tests/robot/functional_tests.robot b/tests/robot/functional_tests.robot index 7d11f3b..d55ad6f 100644 --- a/tests/robot/functional_tests.robot +++ b/tests/robot/functional_tests.robot @@ -3,6 +3,7 @@ Documentation Simple functional tests for https_dns_proxy Library OperatingSystem Library Process Library Collections +Library DnsTcpClient.py *** Variables *** @@ -18,6 +19,7 @@ Test Teardown Stop Proxy Common Test Setup Set Test Variable &{expected_logs} loop destroyed=1 # last log line Set Test Variable @{error_logs} [F] # any fatal error + Set Test Variable @{dig_options} +notcp # UDP only Start Proxy [Arguments] @{args} @@ -36,7 +38,7 @@ Start Proxy With Valgrind @{default_args} = Create List --track-fds=yes --time-stamp=yes --log-file=valgrind-%p.log --suppressions=valgrind.supp ... --gen-suppressions=all --tool=memcheck --leak-check=full --leak-resolution=high ... --show-leak-kinds=all --track-origins=yes --keep-stacktraces=alloc-and-free - ... ${BINARY_PATH} -v -v -v -F 100 -4 -p ${PORT} # using flight recorder with smallest possible buffer size to test memory leak + ... ${BINARY_PATH} -v -v -v -F 100 -4 -p ${PORT} # using flight recorder with smallest possible buffer size to test memory leak @{proces_args} = Combine Lists ${default_args} ${args} ${proxy} = Start Process valgrind @{proces_args} ... stderr=STDOUT alias=proxy @@ -49,6 +51,9 @@ Start Proxy With Valgrind Stop Proxy Send Signal To Process SIGINT ${proxy} ${result} = Wait For Process ${proxy} timeout=15 secs + IF $result is None + ${result} = Terminate Process ${proxy} kill=true + END Log ${result.rc} Log ${result.stdout} Log ${result.stderr} @@ -64,21 +69,23 @@ Stop Proxy Start Dig [Arguments] ${domain}=google.com - ${handle} = Start Process dig +timeout\=${dig_timeout} +retry\=${dig_retry} @127.0.0.1 -p ${PORT} ${domain} + ${handle} = Start Process dig +timeout\=${dig_timeout} +retry\=${dig_retry} @{dig_options} @127.0.0.1 -p ${PORT} ${domain} ... stderr=STDOUT alias=dig RETURN ${handle} Stop Dig [Arguments] ${handle} - ${result} = Wait For Process ${handle} timeout=15 secs + ${result} = Wait For Process ${handle} timeout=20 secs Log ${result.stdout} Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} ANSWER SECTION + RETURN ${result.stdout} Run Dig [Arguments] ${domain}=google.com ${handle} = Start Dig ${domain} - Stop Dig ${handle} + ${dig_output} = Stop Dig ${handle} + RETURN ${dig_output} Run Dig Parallel ${dig_handles} = Create List @@ -90,6 +97,12 @@ Run Dig Parallel Stop Dig ${handle} END +Large Response Test + [Documentation] https://dnscheck.tools/#more + Set Test Variable @{dig_options} @{dig_options} -t txt # ask for TXT response + ${dig_output} = Run Dig txtfill4096.test.dnscheck.tools + Should Contain ${dig_output} MSG SIZE \ rcvd: 4185 # expecting more than 4k large response + *** Test Cases *** Handle Unbound Server Does Not Support HTTP/1.1 @@ -107,4 +120,50 @@ Reuse HTTP/2 Connection Valgrind Resource Leak Check Start Proxy With Valgrind Run Dig Parallel - \ No newline at end of file + +Valgrind Resource Leak Check TCP + Start Proxy With Valgrind + Set Test Variable @{dig_options} +tcp # TCP only + Run Dig Parallel + +Large Response UDP + Start Proxy + Large Response Test + +Large Response TCP + Start Proxy + Set Test Variable @{dig_options} +tcp # TCP only + Large Response Test + +Send TCP Requests Fragmented + [Documentation] Check manually the debug logs of dns_server_tcp.c file! + Start Proxy + Open Tcp Client Connection 127.0.0.1 ${PORT} + + # send 1st request and start 2nd one + Send Tcp Request Parts 1 + Sleep 0.01 + Send Tcp Request Parts 2 + Sleep 0.01 + Send Tcp Request Parts 3 + Sleep 0.01 + Send Tcp Request Parts 4 1 + ${dns_reply} = Receive Tcp Response + Log ${dns_reply} + Should Contain ${dns_reply} google + + # send 2nd request and start 3rd one + Send Tcp Request Parts 2 + Sleep 0.01 + Send Tcp Request Parts 3 4 1 2 + ${dns_reply} = Receive Tcp Response + Log ${dns_reply} + Should Contain ${dns_reply} google + + # finish 3rd request + Send Tcp Request Parts 3 4 + ${dns_reply} = Receive Tcp Response + Log ${dns_reply} + Should Contain ${dns_reply} google + + Close Tcp Client Connection From 866a916e8ef0e41c619fb7873dc56bc21c0a990c Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Tue, 1 Jul 2025 23:23:53 +0200 Subject: [PATCH 03/12] UDP truncation feature --- src/dns_server.c | 135 +++++++++++++++++++++++++++-- src/dns_server.h | 9 +- src/dns_server_tcp.c | 5 +- src/main.c | 13 +-- tests/robot/functional_tests.robot | 46 ++++++++-- 5 files changed, 184 insertions(+), 24 deletions(-) diff --git a/src/dns_server.c b/src/dns_server.c index 361ba75..f696472 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -1,8 +1,9 @@ -#include // NOLINT(llvmlibc-restrict-system-libc-headers) -#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) #include -#include // NOLINT(llvmlibc-restrict-system-libc-headers) -#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#include // NOLINT(llvmlibc-restrict-system-libc-headers) #include "dns_server.h" #include "logging.h" @@ -51,7 +52,7 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, return; } - if (len < (int)sizeof(uint16_t)) { + if (len < DNS_HEADER_LENGTH) { WLOG("Malformed request received, too short: %d", len); return; } @@ -80,9 +81,127 @@ void dns_server_init(dns_server_t *d, struct ev_loop *loop, ev_io_start(d->loop, &d->watcher); } -void dns_server_respond(dns_server_t *d, struct sockaddr *raddr, char *buf, - size_t blen) { - ssize_t len = sendto(d->sock, buf, blen, 0, raddr, d->addrlen); +static uint16_t get_edns_udp_size(const char *dns_req, const size_t dns_req_len) { + ares_dns_record_t *dnsrec = NULL; + ares_status_t parse_status = ares_dns_parse((const unsigned char *)dns_req, dns_req_len, 0, &dnsrec); + if (parse_status != ARES_SUCCESS) { + WLOG("Failed to parse DNS request: %s", ares_strerror(parse_status)); + return DNS_SIZE_LIMIT; + } + const uint16_t tx_id = ares_dns_record_get_id(dnsrec); + uint16_t udp_size = 0; + const size_t record_count = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL); + for (size_t i = 0; i < record_count; ++i) { + const ares_dns_rr_t *rr = ares_dns_record_rr_get(dnsrec, ARES_SECTION_ADDITIONAL, i); + if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) { + udp_size = ares_dns_rr_get_u16(rr, ARES_RR_OPT_UDP_SIZE); + if (udp_size > 0) { + DLOG("%04hX: Found EDNS0 UDP buffer size: %u", tx_id, udp_size); + } + break; + } + } + ares_dns_record_destroy(dnsrec); + if (udp_size < DNS_SIZE_LIMIT) { + DLOG("%04hX: EDNS0 UDP buffer size %u overruled to %d", tx_id, udp_size, DNS_SIZE_LIMIT); + return DNS_SIZE_LIMIT; // RFC6891 4.3 "Values lower than 512 MUST be treated as equal to 512." + } + return udp_size; +} + +static void truncate_dns_response(char *buf, size_t *buflen, const uint16_t size_limit) { + const size_t old_size = *buflen; + buf[2] |= 0x02; // anyway: set truncation flag + + ares_dns_record_t *dnsrec = NULL; + ares_status_t status = ares_dns_parse((const unsigned char *)buf, *buflen, 0, &dnsrec); + if (status != ARES_SUCCESS) { + WLOG("Failed to parse DNS response: %s", ares_strerror(status)); + return; + } + const uint16_t tx_id = ares_dns_record_get_id(dnsrec); + + // NOTE: according to current c-ares implementation, removing first or last elements are the fastest! + + // remove every additional and authority record + while (ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL) > 0) { + status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_ADDITIONAL, 0); + if (status != ARES_SUCCESS) { + WLOG("%04hX: Could not remove additional record: %s", tx_id, ares_strerror(status)); + } + } + while (ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY) > 0) { + status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_AUTHORITY, 0); + if (status != ARES_SUCCESS) { + WLOG("%04hX: Could not remove authority record: %s", tx_id, ares_strerror(status)); + } + } + + // rough estimate to reach size limit + size_t answers = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); + size_t answers_to_keep = (size_limit - DNS_HEADER_LENGTH) / (old_size / answers); + answers_to_keep = answers_to_keep > 0 ? answers_to_keep : 1; // try to keep 1 answer + + // remove answer records until fit size limit or running out of answers + unsigned char *new_resp = NULL; + size_t new_resp_len = 0; + for (uint8_t g = 0; g < UINT8_MAX; ++g) { // endless loop guard + status = ares_dns_write(dnsrec, &new_resp, &new_resp_len); + if (status != ARES_SUCCESS) { + WLOG("%04hX: Failed to create truncated DNS response: %s", tx_id, ares_strerror(status)); + new_resp = NULL; // just to be sure + break; + } + if (new_resp_len < size_limit || answers == 0) { + break; + } + if (new_resp_len >= old_size) { + WLOG("%04hX: Truncated DNS response size larger or equal to original: %u >= %u", + tx_id, new_resp_len, old_size); // impossible? + } + ares_free_string(new_resp); + new_resp = NULL; + + DLOG("%04hX: DNS response size truncated from %u to %u but to keep %u limit reducing answers from %u to %u", + tx_id, old_size, new_resp_len, size_limit, answers, answers_to_keep); + + while (answers > answers_to_keep) { + status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_ANSWER, answers - 1); + if (status != ARES_SUCCESS) { + WLOG("%04hX: Could not remove answer record: %s", tx_id, ares_strerror(status)); + break; + } + --answers; + } + answers = ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER); // update to be sure! + answers_to_keep /= 2; + } + ares_dns_record_destroy(dnsrec); + + if (new_resp != NULL && new_resp_len < old_size) { + memcpy(buf, new_resp, new_resp_len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + *buflen = new_resp_len; + buf[2] |= 0x02; // set truncation flag + ILOG("%04hX: DNS response size truncated from %u to %u to keep %u limit", + tx_id, old_size, new_resp_len, size_limit); + ares_free_string(new_resp); + } +} + +void dns_server_respond(dns_server_t *d, struct sockaddr *raddr, + const char *dns_req, const size_t dns_req_len, char *dns_resp, size_t dns_resp_len) { + if (dns_resp_len > DNS_SIZE_LIMIT) { + const uint16_t udp_size = get_edns_udp_size(dns_req, dns_req_len); + if (dns_resp_len > udp_size) { + truncate_dns_response(dns_resp, &dns_resp_len, udp_size); + } else { + uint16_t tx_id = ntohs(*((uint16_t*)dns_req)); + DLOG("%04hX: DNS response size %u larger than %d but EDNS0 UDP buffer size %u allows it", + tx_id, dns_resp_len, DNS_SIZE_LIMIT, udp_size); + } + } + + ssize_t len = sendto(d->sock, dns_resp, dns_resp_len, 0, raddr, d->addrlen); if(len == -1) { DLOG("sendto failed: %s", strerror(errno)); } diff --git a/src/dns_server.h b/src/dns_server.h index 9699c6c..4c5cbfb 100644 --- a/src/dns_server.h +++ b/src/dns_server.h @@ -8,6 +8,11 @@ #include #include +enum { + DNS_HEADER_LENGTH = 12, // RFC1035 4.1.1 header size + DNS_SIZE_LIMIT = 512 +}; + struct dns_server_s; typedef void (*dns_req_received_cb)(void *dns_server, uint8_t is_tcp, void *data, @@ -27,8 +32,8 @@ void dns_server_init(dns_server_t *d, struct ev_loop *loop, dns_req_received_cb cb, void *data); // Sends a DNS response 'buf' of length 'blen' to 'raddr'. -void dns_server_respond(dns_server_t *d, struct sockaddr *raddr, char *buf, - size_t blen); +void dns_server_respond(dns_server_t *d, struct sockaddr *raddr, + const char *dns_req, const size_t dns_req_len, char *dns_resp, size_t dns_resp_len); void dns_server_stop(dns_server_t *d); diff --git a/src/dns_server_tcp.c b/src/dns_server_tcp.c index 0a25d04..f9947b6 100644 --- a/src/dns_server_tcp.c +++ b/src/dns_server_tcp.c @@ -19,7 +19,6 @@ enum { LISTEN_BACKLOG = 5, - MIN_DNS_LENGTH = 12, // RFC1035 4.1.1 header size IDLE_TIMEOUT_S = 120, // "two minutes" according to RFC1035 4.2.2 RESEND_DELAY_US = 500, // 0.0005 sec }; @@ -159,7 +158,7 @@ static void read_cb(struct ev_loop __attribute__((unused)) *loop, uint16_t req_size = 0; uint8_t request_received = 0; while (get_dns_request(client, &dns_req, &req_size)) { - if (req_size < MIN_DNS_LENGTH) { + if (req_size < DNS_HEADER_LENGTH) { WLOG_CLIENT("Malformed request received, too short: %u", req_size); free(dns_req); remove_client(client); @@ -305,7 +304,7 @@ dns_server_tcp_t * dns_server_tcp_create( void dns_server_tcp_respond(dns_server_tcp_t *d, struct sockaddr *raddr, char *resp, size_t resp_len) { - if (resp_len < MIN_DNS_LENGTH || resp_len > UINT16_MAX) { + if (resp_len < DNS_HEADER_LENGTH || resp_len > UINT16_MAX) { WLOG("Malformed request received, invalid length: %u", resp_len); return; } diff --git a/src/main.c b/src/main.c index 04cbabe..46c44be 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,7 @@ typedef struct { void *dns_server; uint8_t is_tcp; char* dns_req; + size_t dns_req_len; stat_t *stat; ev_tstamp start_tstamp; uint16_t tx_id; @@ -87,12 +88,11 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { if (req == NULL) { FLOG("%04hX: data NULL", req->tx_id); } - free((void*)req->dns_req); if (buf != NULL) { // May be NULL for timeout, DNS failure, or something similar. - if (buflen < (int)sizeof(uint16_t)) { - WLOG("%04hX: Malformed response received (too short)", req->tx_id); + if (buflen < DNS_HEADER_LENGTH) { + WLOG("%04hX: Malformed response received, too short: %u", req->tx_id, buflen); } else { - uint16_t response_id = ntohs(*((uint16_t*)buf)); + const uint16_t response_id = ntohs(*((uint16_t*)buf)); if (req->tx_id != response_id) { WLOG("DNS request and response IDs are not matching: %hX != %hX", req->tx_id, response_id); @@ -100,7 +100,8 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { if (req->is_tcp) { dns_server_tcp_respond((dns_server_tcp_t *)req->dns_server, (struct sockaddr*)&req->raddr, buf, buflen); } else { - dns_server_respond((dns_server_t *)req->dns_server, (struct sockaddr*)&req->raddr, buf, buflen); + dns_server_respond((dns_server_t *)req->dns_server, (struct sockaddr*)&req->raddr, + req->dns_req, req->dns_req_len, buf, buflen); } if (req->stat) { stat_request_end(req->stat, buflen, ev_now(req->stat->loop) - req->start_tstamp, req->is_tcp); @@ -108,6 +109,7 @@ static void https_resp_cb(void *data, char *buf, size_t buflen) { } } } + free((void*)req->dns_req); free(req); } @@ -137,6 +139,7 @@ static void dns_server_cb(void *dns_server, uint8_t is_tcp, void *data, req->dns_server = dns_server; req->is_tcp = is_tcp; req->dns_req = dns_req; // To free buffer after https request is complete. + req->dns_req_len = dns_req_len; req->stat = app->stat; if (req->stat) { diff --git a/tests/robot/functional_tests.robot b/tests/robot/functional_tests.robot index d55ad6f..7241f5c 100644 --- a/tests/robot/functional_tests.robot +++ b/tests/robot/functional_tests.robot @@ -31,6 +31,7 @@ Start Proxy Set Test Variable ${dig_timeout} 2 Set Test Variable ${dig_retry} 0 Sleep 0.5 + Is Process Running ${proxy} Common Test Setup Start Proxy With Valgrind @@ -46,6 +47,7 @@ Start Proxy With Valgrind Set Test Variable ${dig_timeout} 10 Set Test Variable ${dig_retry} 2 Sleep 6 # wait for valgrind to fire up the proxy + Is Process Running ${proxy} Common Test Setup Stop Proxy @@ -74,17 +76,18 @@ Start Dig RETURN ${handle} Stop Dig - [Arguments] ${handle} + [Arguments] ${handle} ${expect}=${None} ${result} = Wait For Process ${handle} timeout=20 secs Log ${result.stdout} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} ANSWER SECTION + ${expect}= Set Variable If $expect is None ANSWER SECTION ${expect} + Should Contain ${result.stdout} ${expect} RETURN ${result.stdout} Run Dig - [Arguments] ${domain}=google.com + [Arguments] ${domain}=google.com ${expect}=${None} ${handle} = Start Dig ${domain} - ${dig_output} = Stop Dig ${handle} + ${dig_output} = Stop Dig ${handle} ${expect} RETURN ${dig_output} Run Dig Parallel @@ -97,11 +100,24 @@ Run Dig Parallel Stop Dig ${handle} END + Large Response Test [Documentation] https://dnscheck.tools/#more - Set Test Variable @{dig_options} @{dig_options} -t txt # ask for TXT response + # use large buffer not to fragment UDP response, and ask for TXT response + Set Test Variable @{dig_options} @{dig_options} +bufsize=5000 -t txt ${dig_output} = Run Dig txtfill4096.test.dnscheck.tools - Should Contain ${dig_output} MSG SIZE \ rcvd: 4185 # expecting more than 4k large response + Should Match Regexp ${dig_output} MSG SIZE\\s+rcvd: 4\\d{3}$ # expecting more than 4k large response + +Verify Truncation + [Arguments] ${domain} ${udp_buffer_size} ${result_bytes_min} ${result_bytes_max} ${expect}=${None} + # ask for TXT response + Set Test Variable @{dig_options} +notcp +ignore +bufsize=${udp_buffer_size} -t txt + ${dig_output} = Run Dig ${domain} ${expect} + Should Contain ${dig_output} flags: qr tc + # expecting response to be ${result_bytes_min} byte (could be flaky) + @{res} = Should Match Regexp ${dig_output} MSG SIZE\\s+rcvd: (\\d+)$ # expecting more than 4k large response + Should Be True ${res}[1] >= ${result_bytes_min} + Should Be True ${res}[1] <= ${result_bytes_max} *** Test Cases *** @@ -167,3 +183,21 @@ Send TCP Requests Fragmented Should Contain ${dns_reply} google Close Tcp Client Connection + +Truncate UDP Small + Start Proxy + Wait Until Keyword Succeeds 5x 200ms + # too small buffer will be overridden to 512, so expecting more than 300 bytes + ... Verify Truncation microsoft.com 256 300 512 + +Truncate UDP Large + Start Proxy + Wait Until Keyword Succeeds 5x 200ms + # expecting more than 1500 byte large response + ... Verify Truncation microsoft.com 2000 1500 2000 + +Truncate UDP Impossible + Start Proxy + Wait Until Keyword Succeeds 5x 200ms + # the only TXT answer record has to be dropped to met limit + ... Verify Truncation txtfill4096.test.dnscheck.tools 4096 12 100 ANSWER: 0 From 083d8d4abd4840fd02654acbdba58c5a65a7fbd0 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Sun, 6 Jul 2025 23:03:45 +0200 Subject: [PATCH 04/12] Enable more compiler warnings To ensure code quality and make life harder. Notes: - choosed to use enum or int (e.g. ares_status_t) - CURLINFO_SSL_VERIFYRESULT was wrong - https_fetch_ctx_cleanup's curl_result_code parameter - int options range checked in options.c --- CMakeLists.txt | 5 ++--- src/dns_poller.c | 16 ++++++++-------- src/dns_poller.h | 2 +- src/dns_server.c | 18 +++++++++--------- src/https_client.c | 28 ++++++++++++++-------------- src/logging.c | 4 ++-- src/main.c | 15 +++++++++------ src/options.h | 2 +- 8 files changed, 46 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20549c6..8e4ea7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,8 +25,7 @@ if (NOT CMAKE_INSTALL_BINDIR) set(CMAKE_INSTALL_BINDIR bin) endif() -# TODO: add flags: -Wconversion -Wsign-conversion -Wfloat-conversion -Wcast-align -Wimplicit-fallthrough -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wformat=2 -Wunused -Wno-variadic-macros -Wnull-dereference -Wshadow") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wformat=2 -Wunused -Wno-variadic-macros -Wnull-dereference -Wshadow -Wconversion -Wsign-conversion -Wfloat-conversion -Wimplicit-fallthrough") set(CMAKE_C_FLAGS_DEBUG "-gdwarf-4 -DDEBUG") set(CMAKE_C_FLAGS_RELEASE "-O2") @@ -97,7 +96,7 @@ if(USE_CLANG_TIDY) message(STATUS "clang-tidy not found.") else() message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-fix" "-fix-errors" "-checks=*,-cert-err34-c,-readability-identifier-length,-altera-unroll-loops,-bugprone-easily-swappable-parameters,-concurrency-mt-unsafe,-*magic-numbers,-hicpp-signed-bitwise,-readability-function-cognitive-complexity,-altera-id-dependent-backward-branch,-google-readability-todo,-misc-include-cleaner") + set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-fix" "-fix-errors" "-checks=*,-cert-err34-c,-readability-identifier-length,-altera-unroll-loops,-bugprone-easily-swappable-parameters,-concurrency-mt-unsafe,-*magic-numbers,-hicpp-signed-bitwise,-readability-function-cognitive-complexity,-altera-id-dependent-backward-branch,-google-readability-todo,-misc-include-cleaner,-cast-align") endif() else() message(STATUS "Not using clang-tidy.") diff --git a/src/dns_poller.c b/src/dns_poller.c index ce5cdd2..1acf2f4 100644 --- a/src/dns_poller.c +++ b/src/dns_poller.c @@ -12,7 +12,7 @@ static void sock_cb(struct ev_loop __attribute__((unused)) *loop, } static struct ev_io * get_io_event(dns_poller_t *d, int sock) { - for (int i = 0; i < d->io_events_count; i++) { + for (unsigned i = 0; i < d->io_events_count; i++) { if (d->io_events[i].fd == sock) { return &d->io_events[i]; } @@ -35,7 +35,7 @@ static void sock_state_cb(void *data, int fd, int read, int write) { // reserve and start new event on unused slot io_event_ptr = get_io_event(d, 0); if (!io_event_ptr) { - FLOG("c-ares needed more IO event handler, than the number of provided nameservers: %d", d->io_events_count); + FLOG("c-ares needed more IO event handler, than the number of provided nameservers: %u", d->io_events_count); } DLOG("Reserved new io event: %p", io_event_ptr); // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) @@ -52,7 +52,7 @@ static char *get_addr_listing(char** addr_list, const int af) { } for (int i = 0; addr_list[i]; i++) { const char *res = ares_inet_ntop(af, addr_list[i], pos, - list + POLLER_ADDR_LIST_SIZE - 1 - pos); + (ares_socklen_t)(list + POLLER_ADDR_LIST_SIZE - 1 - pos)); if (res != NULL) { pos += strlen(pos); *pos = ','; @@ -97,8 +97,8 @@ static ev_tstamp get_timeout(dns_poller_t *d) struct timeval tv; struct timeval *tvp = ares_timeout(d->ares, &max_tv, &tv); // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) - ev_tstamp after = tvp->tv_sec + tvp->tv_usec * 1e-6; - return after ? after : 0.1; + ev_tstamp after = (double)tvp->tv_sec + (double)tvp->tv_usec * 1e-6; + return after > 0.1 ? after : 0.1; } static void timer_cb(struct ev_loop __attribute__((unused)) *loop, @@ -170,8 +170,8 @@ void dns_poller_init(dns_poller_t *d, struct ev_loop *loop, d->timer.data = d; ev_timer_start(d->loop, &d->timer); - int nameservers = 1; - for (int i = 0; bootstrap_dns[i]; i++) { + unsigned nameservers = 1; + for (unsigned i = 0; bootstrap_dns[i]; i++) { if (bootstrap_dns[i] == ',') { nameservers++; } @@ -181,7 +181,7 @@ void dns_poller_init(dns_poller_t *d, struct ev_loop *loop, if (!d->io_events) { FLOG("Out of mem"); } - for (int i = 0; i < nameservers; i++) { + for (unsigned i = 0; i < nameservers; i++) { d->io_events[i].data = d; } d->io_events_count = nameservers; diff --git a/src/dns_poller.h b/src/dns_poller.h index e4f63fd..e257220 100644 --- a/src/dns_poller.h +++ b/src/dns_poller.h @@ -30,7 +30,7 @@ typedef struct { ev_timer timer; ev_io *io_events; - int io_events_count; + unsigned io_events_count; } dns_poller_t; // Initializes c-ares and starts a timer for periodic DNS resolution on the diff --git a/src/dns_server.c b/src/dns_server.c index f696472..a775759 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -57,13 +57,13 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, return; } - char *dns_req = (char *)malloc(len); // To free buffer after https request is complete. + char *dns_req = (char *)malloc((size_t)len); // To free buffer after https request is complete. if (dns_req == NULL) { FLOG("Out of mem"); } - memcpy(dns_req, tmp_buf, len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + memcpy(dns_req, tmp_buf, (size_t)len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - d->cb(d, 0, d->cb_data, (struct sockaddr*)&tmp_raddr, dns_req, len); + d->cb(d, 0, d->cb_data, (struct sockaddr*)&tmp_raddr, dns_req, (size_t)len); } void dns_server_init(dns_server_t *d, struct ev_loop *loop, @@ -85,7 +85,7 @@ static uint16_t get_edns_udp_size(const char *dns_req, const size_t dns_req_len) ares_dns_record_t *dnsrec = NULL; ares_status_t parse_status = ares_dns_parse((const unsigned char *)dns_req, dns_req_len, 0, &dnsrec); if (parse_status != ARES_SUCCESS) { - WLOG("Failed to parse DNS request: %s", ares_strerror(parse_status)); + WLOG("Failed to parse DNS request: %s", ares_strerror((int)parse_status)); return DNS_SIZE_LIMIT; } const uint16_t tx_id = ares_dns_record_get_id(dnsrec); @@ -116,7 +116,7 @@ static void truncate_dns_response(char *buf, size_t *buflen, const uint16_t size ares_dns_record_t *dnsrec = NULL; ares_status_t status = ares_dns_parse((const unsigned char *)buf, *buflen, 0, &dnsrec); if (status != ARES_SUCCESS) { - WLOG("Failed to parse DNS response: %s", ares_strerror(status)); + WLOG("Failed to parse DNS response: %s", ares_strerror((int)status)); return; } const uint16_t tx_id = ares_dns_record_get_id(dnsrec); @@ -127,13 +127,13 @@ static void truncate_dns_response(char *buf, size_t *buflen, const uint16_t size while (ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL) > 0) { status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_ADDITIONAL, 0); if (status != ARES_SUCCESS) { - WLOG("%04hX: Could not remove additional record: %s", tx_id, ares_strerror(status)); + WLOG("%04hX: Could not remove additional record: %s", tx_id, ares_strerror((int)status)); } } while (ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY) > 0) { status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_AUTHORITY, 0); if (status != ARES_SUCCESS) { - WLOG("%04hX: Could not remove authority record: %s", tx_id, ares_strerror(status)); + WLOG("%04hX: Could not remove authority record: %s", tx_id, ares_strerror((int)status)); } } @@ -148,7 +148,7 @@ static void truncate_dns_response(char *buf, size_t *buflen, const uint16_t size for (uint8_t g = 0; g < UINT8_MAX; ++g) { // endless loop guard status = ares_dns_write(dnsrec, &new_resp, &new_resp_len); if (status != ARES_SUCCESS) { - WLOG("%04hX: Failed to create truncated DNS response: %s", tx_id, ares_strerror(status)); + WLOG("%04hX: Failed to create truncated DNS response: %s", tx_id, ares_strerror((int)status)); new_resp = NULL; // just to be sure break; } @@ -168,7 +168,7 @@ static void truncate_dns_response(char *buf, size_t *buflen, const uint16_t size while (answers > answers_to_keep) { status = ares_dns_record_rr_del(dnsrec, ARES_SECTION_ANSWER, answers - 1); if (status != ARES_SUCCESS) { - WLOG("%04hX: Could not remove answer record: %s", tx_id, ares_strerror(status)); + WLOG("%04hX: Could not remove answer record: %s", tx_id, ares_strerror((int)status)); break; } --answers; diff --git a/src/https_client.c b/src/https_client.c index 7b21227..c61efcc 100644 --- a/src/https_client.c +++ b/src/https_client.c @@ -138,7 +138,7 @@ static int closesocket_callback(void __attribute__((unused)) *clientp, curl_sock return 0; } -static void https_log_data(enum LogSeverity level, struct https_fetch_ctx *ctx, +static void https_log_data(int level, struct https_fetch_ctx *ctx, const char * prefix, char *ptr, size_t size) { const size_t width = 0x10; @@ -156,14 +156,14 @@ static void https_log_data(enum LogSeverity level, struct https_fetch_ctx *ctx, for (size_t c = 0; c < width; c++) { if (i+c < size) { // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - hex_off += snprintf(hex + hex_off, sizeof(hex) - hex_off, - "%02x ", (unsigned char)ptr[i+c]); + hex_off += (size_t)snprintf(hex + hex_off, sizeof(hex) - hex_off, + "%02x ", (unsigned char)ptr[i+c]); // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - str_off += snprintf(str + str_off, sizeof(str) - str_off, - "%c", isprint(ptr[i+c]) ? ptr[i+c] : '.'); + str_off += (size_t)snprintf(str + str_off, sizeof(str) - str_off, + "%c", isprint(ptr[i+c]) ? ptr[i+c] : '.'); } else { // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - hex_off += snprintf(hex + hex_off, sizeof(hex) - hex_off, " "); + hex_off += (size_t)snprintf(hex + hex_off, sizeof(hex) - hex_off, " "); } } @@ -249,7 +249,7 @@ static void https_set_request_version(https_client_t *client, switch (client->opt->use_http_version) { case 1: http_version_int = CURL_HTTP_VERSION_1_1; - // fallthrough + __attribute__((fallthrough)); case 2: break; case 3: @@ -338,7 +338,7 @@ static void https_fetch_ctx_init(https_client_t *client, static int https_fetch_ctx_process_response(https_client_t *client, struct https_fetch_ctx *ctx, - int curl_result_code) + CURLcode curl_result_code) { CURLcode res = 0; long long_resp = 0; @@ -419,15 +419,15 @@ static int https_fetch_ctx_process_response(https_client_t *client, res = curl_easy_getinfo(ctx->curl, CURLINFO_SSL_VERIFYRESULT, &long_resp); if (res != CURLE_OK) { ELOG_REQ("CURLINFO_SSL_VERIFYRESULT: %s", curl_easy_strerror(res)); - } else if (long_resp != CURLE_OK) { - WLOG_REQ("CURLINFO_SSL_VERIFYRESULT: %s", curl_easy_strerror(long_resp)); + } else if (long_resp != 0) { + WLOG_REQ("CURLINFO_SSL_VERIFYRESULT: certificate verification failure %d", long_resp); } res = curl_easy_getinfo(ctx->curl, CURLINFO_OS_ERRNO, &long_resp); if (res != CURLE_OK) { ELOG_REQ("CURLINFO_OS_ERRNO: %s", curl_easy_strerror(res)); } else if (long_resp != 0) { - WLOG_REQ("CURLINFO_OS_ERRNO: %d %s", long_resp, strerror(long_resp)); + WLOG_REQ("CURLINFO_OS_ERRNO: %d %s", long_resp, strerror((int)long_resp)); if (long_resp == ENETUNREACH && !client->opt->ipv4) { // this can't be fixed here with option overwrite because of dns_poller WLOG("Try to run application with -4 argument!"); @@ -510,7 +510,7 @@ static void https_fetch_ctx_cleanup(https_client_t *client, if (curl_result_code < 0) { WLOG_REQ("Request was aborted"); drop_reply = 1; - } else if (https_fetch_ctx_process_response(client, ctx, curl_result_code) != 0) { + } else if (https_fetch_ctx_process_response(client, ctx, (CURLcode)curl_result_code) != 0) { ILOG_REQ("Response was faulty, skipping DNS reply"); drop_reply = 1; } @@ -540,7 +540,7 @@ static void check_multi_info(https_client_t *c) { struct https_fetch_ctx *cur = c->fetches; while (cur) { if (cur->curl == msg->easy_handle) { - https_fetch_ctx_cleanup(c, prev, cur, msg->data.result); + https_fetch_ctx_cleanup(c, prev, cur, (int)msg->data.result); break; } prev = cur; @@ -642,7 +642,7 @@ static int multi_timer_cb(CURLM __attribute__((unused)) *multi, ev_timer_stop(c->loop, &c->timer); if (timeout_ms >= 0) { // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - ev_timer_init(&c->timer, timer_cb, timeout_ms / 1000.0, 0); + ev_timer_init(&c->timer, timer_cb, (double)timeout_ms / 1000.0, 0); ev_timer_start(c->loop, &c->timer); } return 0; diff --git a/src/logging.c b/src/logging.c index 23030e7..944c96b 100644 --- a/src/logging.c +++ b/src/logging.c @@ -121,7 +121,7 @@ void _log(const char *file, int line, int severity, const char *fmt, ...) { if (chars < 0 || chars >= LOG_LINE_SIZE/2) { abort(); // must be impossible } - buff_pos += chars; + buff_pos += (uint32_t)chars; va_list args; va_start(args, fmt); @@ -132,7 +132,7 @@ void _log(const char *file, int line, int severity, const char *fmt, ...) { if (chars < 0) { abort(); // must be impossible } - buff_pos += chars; + buff_pos += (uint32_t)chars; if (buff_pos >= LOG_LINE_SIZE) { buff_pos = LOG_LINE_SIZE - 1; buff[buff_pos - 1] = '$'; // indicate truncation diff --git a/src/main.c b/src/main.c index 46c44be..d9a49cb 100644 --- a/src/main.c +++ b/src/main.c @@ -156,7 +156,7 @@ static int addr_list_reduced(const char* full_list, const char* list) { while (pos < end) { char current[50]; const char *comma = strchr(pos, ','); - size_t ip_len = comma ? comma - pos : end - pos; + size_t ip_len = (size_t)(comma ? comma - pos : end - pos); strncpy(current, pos, ip_len); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) current[ip_len] = '\0'; @@ -180,7 +180,10 @@ static void dns_poll_cb(const char* hostname, void *data, memset(buf, 0, sizeof(buf)); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) if (strlen(hostname) > 254) { FLOG("Hostname too long."); } int ip_start = snprintf(buf, sizeof(buf) - 1, "%s:443:", hostname); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - (void)snprintf(buf + ip_start, sizeof(buf) - 1 - ip_start, "%s", addr_list); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + if (ip_start < 0) { + abort(); // must be impossible + } + (void)snprintf(buf + ip_start, sizeof(buf) - 1 - (uint32_t)ip_start, "%s", addr_list); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) if (app->resolv && app->resolv->data) { char * old_addr_list = strstr(app->resolv->data, ":443:"); if (old_addr_list) { @@ -272,7 +275,7 @@ int main(int argc, char *argv[]) { } case OPR_PARSING_ERROR: printf("Failed to parse options!\n"); - // fallthrough + __attribute__((fallthrough)); case OPR_OPTION_ERROR: printf("\n"); options_show_usage(argc, argv); @@ -281,7 +284,7 @@ int main(int argc, char *argv[]) { abort(); // must not happen } - logging_init(opt.logfd, opt.loglevel, opt.flight_recorder_size); + logging_init(opt.logfd, opt.loglevel, (uint32_t)opt.flight_recorder_size); ILOG("Version: %s", sw_version()); ILOG("Built: " __DATE__ " " __TIME__); @@ -323,7 +326,7 @@ int main(int argc, char *argv[]) { https_client_init(&https_client, &opt, (opt.stats_interval ? &stat : NULL), loop); struct addrinfo *listen_addrinfo = get_listen_address(opt.listen_addr); - ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons(opt.listen_port); + ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons((uint16_t)opt.listen_port); app_state_t app; app.https_client = &https_client; @@ -338,7 +341,7 @@ int main(int argc, char *argv[]) { dns_server_tcp_t * dns_server_tcp = NULL; if (opt.tcp_client_limit > 0) { - dns_server_tcp = dns_server_tcp_create(loop, listen_addrinfo, dns_server_cb, &app, opt.tcp_client_limit); + dns_server_tcp = dns_server_tcp_create(loop, listen_addrinfo, dns_server_cb, &app, (uint16_t)opt.tcp_client_limit); } freeaddrinfo(listen_addrinfo); diff --git a/src/options.h b/src/options.h index 9dd5c81..11d06d9 100644 --- a/src/options.h +++ b/src/options.h @@ -57,7 +57,7 @@ struct Options { const char *ca_info; // Number of logs to be kept by flight recorder - uint32_t flight_recorder_size; + int flight_recorder_size; } __attribute__((aligned(128))); typedef struct Options options_t; From 7c5445cfee5c23bdd4568a4681fc10b704a57222 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:50:38 +0200 Subject: [PATCH 05/12] Replacing c-ares deprecated functions --- src/dns_poller.c | 61 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/dns_poller.c b/src/dns_poller.c index 1acf2f4..2ff30d8 100644 --- a/src/dns_poller.c +++ b/src/dns_poller.c @@ -7,8 +7,8 @@ static void sock_cb(struct ev_loop __attribute__((unused)) *loop, ev_io *w, int revents) { dns_poller_t *d = (dns_poller_t *)w->data; - ares_process_fd(d->ares, (revents & EV_READ) ? w->fd : ARES_SOCKET_BAD, - (revents & EV_WRITE) ? w->fd : ARES_SOCKET_BAD); + ares_process_fd(d->ares, (revents & EV_READ) ? w->fd : ARES_SOCKET_BAD, + (revents & EV_WRITE) ? w->fd : ARES_SOCKET_BAD); } static struct ev_io * get_io_event(dns_poller_t *d, int sock) { @@ -44,21 +44,43 @@ static void sock_state_cb(void *data, int fd, int read, int write) { ev_io_start(d->loop, io_event_ptr); } -static char *get_addr_listing(char** addr_list, const int af) { +static char *get_addr_listing(struct ares_addrinfo_node * nodes) { char *list = (char *)calloc(1, POLLER_ADDR_LIST_SIZE); - char *pos = list; if (list == NULL) { FLOG("Out of mem"); } - for (int i = 0; addr_list[i]; i++) { - const char *res = ares_inet_ntop(af, addr_list[i], pos, - (ares_socklen_t)(list + POLLER_ADDR_LIST_SIZE - 1 - pos)); + char *pos = list; + unsigned ipv4 = 0; + unsigned ipv6 = 0; + + for (struct ares_addrinfo_node *node = nodes; node != NULL; node = node->ai_next) { + const char *res = NULL; + + if (node->ai_family == AF_INET) { + res = ares_inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)node->ai_addr)->sin_addr, + pos, (ares_socklen_t)(list + POLLER_ADDR_LIST_SIZE - 1 - pos)); + ipv4++; + } else if (node->ai_family == AF_INET6) { + res = ares_inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)node->ai_addr)->sin6_addr, + pos, (ares_socklen_t)(list + POLLER_ADDR_LIST_SIZE - 1 - pos)); + ipv6++; + } else { + WLOG("Unhandled address family: %d", node->ai_family); + continue; + } + if (res != NULL) { pos += strlen(pos); *pos = ','; pos++; + } else { + DLOG("Not enough space left for further IP addresses"); // test with POLLER_ADDR_LIST_SIZE = 10 value + break; } } + + DLOG("Received %u IPv4 and %u IPv6 addresses", ipv4, ipv6); + if (pos == list) { free((void*)list); list = NULL; @@ -69,19 +91,20 @@ static char *get_addr_listing(char** addr_list, const int af) { } static void ares_cb(void *arg, int status, int __attribute__((unused)) timeouts, - struct hostent *h) { + struct ares_addrinfo *result) { dns_poller_t *d = (dns_poller_t *)arg; d->request_ongoing = 0; ev_tstamp interval = 5; // retry by default after some time if (status != ARES_SUCCESS) { WLOG("DNS lookup of '%s' failed: %s", d->hostname, ares_strerror(status)); - } else if (!h || h->h_length < 1) { + } else if (!result || result->nodes == NULL) { WLOG("No hosts found for '%s'", d->hostname); } else { interval = d->polling_interval; - d->cb(d->hostname, d->cb_data, get_addr_listing(h->h_addr_list, h->h_addrtype)); + d->cb(d->hostname, d->cb_data, get_addr_listing(result->nodes)); } + ares_freeaddrinfo(result); if (status != ARES_EDESTRUCTION) { DLOG("DNS poll interval changed to: %.0lf", interval); @@ -106,9 +129,14 @@ static void timer_cb(struct ev_loop __attribute__((unused)) *loop, dns_poller_t *d = (dns_poller_t *)w->data; if (d->request_ongoing) { - // process query timeouts - DLOG("Processing DNS queries"); + DLOG("Processing DNS query timeouts"); +#if ARES_VERSION_MAJOR >= 1 && ARES_VERSION_MINOR >= 34 + ares_process_fds(d->ares, NULL, 0, ARES_PROCESS_FLAG_NONE); +#elif ARES_VERSION_MAJOR >= 1 && ARES_VERSION_MINOR >= 28 + ares_process_fd(d->ares, ARES_SOCKET_BAD, ARES_SOCKET_BAD); +#else ares_process(d->ares, NULL, NULL); +#endif } else { DLOG("Starting DNS query"); // Cancel any pending queries before making new ones. c-ares can't be depended on to @@ -117,7 +145,14 @@ static void timer_cb(struct ev_loop __attribute__((unused)) *loop, // free memory tied up by any "zombie" queries. ares_cancel(d->ares); d->request_ongoing = 1; - ares_gethostbyname(d->ares, d->hostname, d->family, ares_cb, d); + + struct ares_addrinfo_hints hints; + memset(&hints, 0, sizeof(hints)); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + hints.ai_flags = ARES_AI_CANONNAME; + hints.ai_family = d->family; + hints.ai_socktype = SOCK_STREAM; + + ares_getaddrinfo(d->ares, d->hostname, "https", &hints, ares_cb, d); } if (d->request_ongoing) { // need to re-check, it might change! From 58bad2e8432dbeab7dc50e3fe5556c4a80667d54 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Sat, 12 Jul 2025 22:35:47 +0200 Subject: [PATCH 06/12] Reset HTTPS client on timeouts If there are not any successful request. --- README.md | 3 +++ src/https_client.c | 59 +++++++++++++++++++++++++++++++++------------- src/https_client.h | 2 ++ src/options.c | 15 +++++++++++- src/options.h | 2 ++ 5 files changed, 64 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 8f0ad31..7736d29 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,9 @@ Usage: ./https_dns_proxy [-a ] [-p ] [-T connections--; + if (client->connections <= 0 && ev_is_active(&client->reset_timer)) { + ILOG("Client reset timer cancelled, since all connection closed"); + ev_timer_stop(client->loop, &client->reset_timer); + } + if (client->stat) { stat_connection_closed(client->stat); } @@ -353,6 +358,12 @@ static int https_fetch_ctx_process_response(https_client_t *client, case CURLE_WRITE_ERROR: WLOG_REQ("curl request failed with write error (probably response content was too large)"); break; + case CURLE_OPERATION_TIMEDOUT: + if (!ev_is_active(&client->reset_timer)) { + ILOG_REQ("Client reset timer started"); + ev_timer_start(client->loop, &client->reset_timer); + } + __attribute__((fallthrough)); default: WLOG_REQ("curl request failed with %d: %s", curl_result_code, curl_easy_strerror(curl_result_code)); if (ctx->curl_errbuf[0] != 0) { @@ -465,7 +476,7 @@ static int https_fetch_ctx_process_response(https_client_t *client, res = curl_easy_getinfo(ctx->curl, CURLINFO_SCHEME, &str_resp); if (res != CURLE_OK) { ELOG_REQ("CURLINFO_SCHEME: %s", curl_easy_strerror(res)); - } else if (strcasecmp(str_resp, "https") != 0) { + } else if (str_resp != NULL && strcasecmp(str_resp, "https") != 0) { DLOG_REQ("CURLINFO_SCHEME: %s", str_resp); } @@ -648,15 +659,31 @@ static int multi_timer_cb(CURLM __attribute__((unused)) *multi, return 0; } +static void https_client_multi_init(https_client_t *c, struct curl_slist *header_list) { + c->curlm = curl_multi_init(); // if fails, first setopt will fail + c->header_list = header_list; + + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, HTTPS_CONNECTION_LIMIT); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_HOST_CONNECTIONS, HTTPS_CONNECTION_LIMIT); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETDATA, c); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETFUNCTION, multi_sock_cb); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERDATA, c); + ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERFUNCTION, multi_timer_cb); +} + +static void reset_timer_cb(struct ev_loop __attribute__((unused)) *loop, + ev_timer *w, int __attribute__((unused)) revents) { + GET_PTR(https_client_t, c, w->data); + ILOG("Client reset timer timeouted"); + https_client_reset(c); +} + void https_client_init(https_client_t *c, options_t *opt, stat_t *stat, struct ev_loop *loop) { // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) memset(c, 0, sizeof(*c)); c->loop = loop; - c->curlm = curl_multi_init(); // if fails, first setopt will fail - c->header_list = curl_slist_append(curl_slist_append(NULL, - "Accept: " DOH_CONTENT_TYPE), - "Content-Type: " DOH_CONTENT_TYPE); c->fetches = NULL; c->timer.data = c; for (int i = 0; i < HTTPS_SOCKET_LIMIT; i++) { @@ -665,13 +692,13 @@ void https_client_init(https_client_t *c, options_t *opt, c->opt = opt; c->stat = stat; - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, HTTPS_CONNECTION_LIMIT); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_HOST_CONNECTIONS, HTTPS_CONNECTION_LIMIT); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETDATA, c); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETFUNCTION, multi_sock_cb); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERDATA, c); - ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERFUNCTION, multi_timer_cb); + ev_timer_init(&c->reset_timer, reset_timer_cb, (double)opt->conn_loss_time, 0); + c->reset_timer.data = c; + + struct curl_slist *header_list = curl_slist_append(curl_slist_append(NULL, + "Accept: " DOH_CONTENT_TYPE), + "Content-Type: " DOH_CONTENT_TYPE); + https_client_multi_init(c, header_list); } void https_client_fetch(https_client_t *c, const char *url, @@ -687,11 +714,10 @@ void https_client_fetch(https_client_t *c, const char *url, } void https_client_reset(https_client_t *c) { - options_t *opt = c->opt; - stat_t *stat = c->stat; - struct ev_loop *loop = c->loop; + struct curl_slist *header_list = c->header_list; + c->header_list = NULL; https_client_cleanup(c); - https_client_init(c, opt, stat, loop); + https_client_multi_init(c, header_list); } void https_client_cleanup(https_client_t *c) { @@ -700,4 +726,5 @@ void https_client_cleanup(https_client_t *c) { } curl_slist_free_all(c->header_list); curl_multi_cleanup(c->curlm); + ev_timer_stop(c->loop, &c->reset_timer); } diff --git a/src/https_client.h b/src/https_client.h index 7c5ea08..de60bbb 100644 --- a/src/https_client.h +++ b/src/https_client.h @@ -43,6 +43,8 @@ typedef struct { options_t *opt; stat_t *stat; + + ev_timer reset_timer; } https_client_t; void https_client_init(https_client_t *c, options_t *opt, diff --git a/src/options.c b/src/options.c index 13cc7f7..51f8193 100644 --- a/src/options.c +++ b/src/options.c @@ -41,6 +41,7 @@ void options_init(struct Options *opt) { opt->curl_proxy = NULL; opt->use_http_version = DEFAULT_HTTP_VERSION; opt->max_idle_time = 118; + opt->conn_loss_time = 15; opt->stats_interval = 0; opt->ca_info = NULL; opt->flight_recorder_size = 0; @@ -48,7 +49,7 @@ void options_init(struct Options *opt) { enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char **argv) { int c = 0; - while ((c = getopt(argc, argv, "a:c:p:T:du:g:b:i:4r:e:t:l:vxqm:s:C:F:hV")) != -1) { + while ((c = getopt(argc, argv, "a:c:p:T:du:g:b:i:4r:e:t:l:vxqm:L:s:C:F:hV")) != -1) { switch (c) { case 'a': // listen_addr opt->listen_addr = optarg; @@ -107,6 +108,9 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char * case 'm': opt->max_idle_time = atoi(optarg); break; + case 'L': + opt->conn_loss_time = atoi(optarg); + break; case 's': // stats interval opt->stats_interval = atoi(optarg); break; @@ -179,6 +183,11 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char * printf("Maximum idle time must be between 0 and 3600.\n"); return OPR_OPTION_ERROR; } + if (opt->conn_loss_time < 5 || + opt->conn_loss_time > 60) { + printf("Connection loss time must be between 5 and 60.\n"); + return OPR_OPTION_ERROR; + } if (opt->stats_interval < 0 || opt->stats_interval > 3600) { printf("Statistic interval must be between 0 and 3600.\n"); return OPR_OPTION_ERROR; @@ -238,6 +247,10 @@ void options_show_usage(int __attribute__((unused)) argc, char **argv) { printf(" -m max_idle_time Maximum idle time in seconds allowed for reusing a HTTPS connection.\n"\ " (Default: %d, Min: 0, Max: 3600)\n", defaults.max_idle_time); + printf(" -L conn_loss_time Time in seconds to tolerate connection timeouts of reused connections.\n"\ + " This option mitigates half-open TCP connection issue (e.g. WAN IP change).\n"\ + " (Default: %d, Min: 5, Max: 60)\n", + defaults.conn_loss_time); printf(" -C ca_path Optional file containing CA certificates.\n"); printf(" -c dscp_codepoint Optional DSCP codepoint to set on upstream HTTPS server\n"); printf(" connections. (Min: 0, Max: 63)\n"); diff --git a/src/options.h b/src/options.h index 11d06d9..9320b50 100644 --- a/src/options.h +++ b/src/options.h @@ -50,6 +50,8 @@ struct Options { int max_idle_time; + int conn_loss_time; + // Print statistic interval int stats_interval; From 7626a98e993fe41a3f1c76e3d57c82a3ee1d5d0f Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:44:06 +0200 Subject: [PATCH 07/12] systemd service improvements Goal is to indicate service readyness after bootstrap has finished. --- .github/workflows/cmake.yml | 2 +- CMakeLists.txt | 15 ++++++++++++++- README.md | 3 ++- https_dns_proxy.service.in | 6 ++++-- src/main.c | 30 ++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b62051e..caea824 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,7 +22,7 @@ jobs: run: sudo apt-get update - name: Setup Dependencies - run: sudo apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev build-essential clang-tidy dnsutils python3-pip python3-venv valgrind ${{ matrix.compiler }} + run: sudo apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essential clang-tidy dnsutils python3-pip python3-venv valgrind ${{ matrix.compiler }} - name: Setup Python Virtual Environment run: python3 -m venv ${{github.workspace}}/venv diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4ea7e..5c6fe60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.7) project(HttpsDnsProxy C) +include(CheckIncludeFile) + # FUNCTIONS # source: https://stackoverflow.com/a/27990434 @@ -35,6 +37,9 @@ if (((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GRE set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-gnu-zero-variadic-macro-arguments -Wno-gnu-folding-constant") endif() +set(SERVICE_EXTRA_OPTIONS "") +set(SERVICE_TYPE "simple") + # VERSION # It is possible to define external default value like: cmake -DSW_VERSION=1.2-custom @@ -82,6 +87,15 @@ include_directories( ${LIBCARES_INCLUDE_DIR} ${LIBCURL_INCLUDE_DIR} ${LIBEV_INCLUDE_DIR} src) +check_include_file("systemd/sd-daemon.h" HAVE_SD_DAEMON_H) + +if(HAVE_SD_DAEMON_H) + message(STATUS "Using libsystemd") + add_definitions(-DHAS_LIBSYSTEMD=1) + set(LIBS ${LIBS} systemd) + set(SERVICE_TYPE "notify") +endif() + # CLANG TIDY option(USE_CLANG_TIDY "Use clang-tidy during compilation" ON) @@ -133,7 +147,6 @@ endif() install(TARGETS ${TARGET_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) -set(SERVICE_EXTRA_OPTIONS "") if(IS_DIRECTORY "/etc/munin/plugins" AND IS_DIRECTORY "/etc/munin/plugin-conf.d") set(SERVICE_EXTRA_OPTIONS "-s 300") diff --git a/README.md b/README.md index 7736d29..08573af 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Depends on `c-ares (>=1.11.0)`, `libcurl (>=7.66.0)`, `libev (>=4.25)`. On Debian-derived systems those are libc-ares-dev, libcurl4-{openssl,nss,gnutls}-dev and libev-dev respectively. On Redhat-derived systems those are c-ares-devel, libcurl-devel and libev-devel. +On systems with systemd it is recommended to have libsystemd development package installed. On MacOS, you may run into issues with curl headers. Others have had success when first installing curl with brew. ``` @@ -57,7 +58,7 @@ brew link curl --force On Ubuntu ``` -apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev build-essential +apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essential ``` If all pre-requisites are met, you should be able to build with: diff --git a/https_dns_proxy.service.in b/https_dns_proxy.service.in index cab76b6..bec7753 100644 --- a/https_dns_proxy.service.in +++ b/https_dns_proxy.service.in @@ -6,11 +6,13 @@ Before=nss-lookup.target After=network.target [Service] -Type=simple +Type=${SERVICE_TYPE} DynamicUser=yes -Restart=on-failure ExecStart=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/https_dns_proxy \ -v -v ${SERVICE_EXTRA_OPTIONS} +Restart=on-failure +RestartSec=5 +TimeoutStartSec=20 TimeoutStopSec=10 [Install] diff --git a/src/main.c b/src/main.c index d9a49cb..1a73af7 100644 --- a/src/main.c +++ b/src/main.c @@ -9,6 +9,10 @@ #include // NOLINT(llvmlibc-restrict-system-libc-headers) #include // NOLINT(llvmlibc-restrict-system-libc-headers) +#if HAS_LIBSYSTEMD == 1 +#include // NOLINT(llvmlibc-restrict-system-libc-headers) +#endif + #include "dns_poller.h" #include "dns_server.h" #include "dns_server_tcp.h" @@ -150,6 +154,27 @@ static void dns_server_cb(void *dns_server, uint8_t is_tcp, void *data, req->dns_req, dns_req_len, app->resolv, req->tx_id, https_resp_cb, req); } +static void systemd_notify_ready(void) { +#if HAS_LIBSYSTEMD == 1 + static uint8_t called_once = 0; + if (called_once != 0) { + DLOG("Systemd notify already called once!"); + return; + } + called_once = 1; + const int result = sd_notify(0, "READY=1"); + if (result > 0) { + DLOG("Systemd notify succeeded, service is ready!"); + } else if (result == 0) { + WLOG("Systemd notify called, but NOTIFY_SOCKET not set. Running manually?"); + } else { + ELOG("Systemd notify failed with: %s", strerror(result)); + } +#else + DLOG("Systemd notify skipped, not compiled with libsystemd!"); +#endif +} + static int addr_list_reduced(const char* full_list, const char* list) { const char *pos = list; const char *end = list + strlen(list); @@ -184,6 +209,9 @@ static void dns_poll_cb(const char* hostname, void *data, abort(); // must be impossible } (void)snprintf(buf + ip_start, sizeof(buf) - 1 - (uint32_t)ip_start, "%s", addr_list); // NOLINT(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + if (app->resolv == NULL) { + systemd_notify_ready(); + } if (app->resolv && app->resolv->data) { char * old_addr_list = strstr(app->resolv->data, ":443:"); if (old_addr_list) { @@ -394,6 +422,8 @@ int main(int argc, char *argv[]) { } else { ILOG("Resolver prefix '%s' doesn't appear to contain a " "hostname. DNS polling disabled.", opt.resolver_url); + + systemd_notify_ready(); } } From f10d561ed344bac2b97625cbc4510fdf17ea9d88 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Thu, 10 Jul 2025 22:53:25 +0200 Subject: [PATCH 08/12] Version bump Because of larger feature pack --- src/https_client.c | 2 +- src/main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/https_client.c b/src/https_client.c index f0d969f..b26ac0f 100644 --- a/src/https_client.c +++ b/src/https_client.c @@ -315,7 +315,7 @@ static void https_fetch_ctx_init(https_client_t *client, ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_WRITEDATA, ctx); ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_MAXAGE_CONN, client->opt->max_idle_time); ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_PIPEWAIT, client->opt->use_http_version > 1); - ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_USERAGENT, "https_dns_proxy/0.3"); + ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_USERAGENT, "https_dns_proxy/0.4"); ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_FOLLOWLOCATION, 0); ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_NOSIGNAL, 0); ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TIMEOUT, client->connections > 0 ? 5 : 10 /* seconds */); diff --git a/src/main.c b/src/main.c index 1a73af7..82db28a 100644 --- a/src/main.c +++ b/src/main.c @@ -269,7 +269,7 @@ static const char * sw_version(void) { #ifdef SW_VERSION return SW_VERSION; #else - return "2025.5.10-atLeast"; // update date sometimes, like 1-2 times a year + return "2025.8.26-atLeast"; // update date sometimes, like 1-2 times a year #endif } From 887635f1131287b30919be6e368124d2273b0062 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Mon, 25 Aug 2025 10:11:05 +0200 Subject: [PATCH 09/12] fixup! DNS server refactor --- src/dns_server.c | 9 +++++---- src/main.c | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/dns_server.c b/src/dns_server.c index a775759..2f213d8 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -16,17 +16,18 @@ static int get_listen_sock(struct addrinfo *listen_addrinfo) { FLOG("Error creating socket: %s (%d)", strerror(errno), errno); } + uint16_t port; char ipstr[INET6_ADDRSTRLEN]; if (listen_addrinfo->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); + port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); + inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); } else if (listen_addrinfo->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); + port = ntohs(((struct sockaddr_in6*) listen_addrinfo->ai_addr)->sin6_port); + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); } else { FLOG("Unknown address family: %d", listen_addrinfo->ai_family); } - uint16_t port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); - int res = bind(sock, listen_addrinfo->ai_addr, listen_addrinfo->ai_addrlen); if (res < 0) { FLOG("Error binding on %s:%d UDP: %s (%d)", ipstr, port, diff --git a/src/main.c b/src/main.c index 82db28a..c81d5a5 100644 --- a/src/main.c +++ b/src/main.c @@ -354,7 +354,12 @@ int main(int argc, char *argv[]) { https_client_init(&https_client, &opt, (opt.stats_interval ? &stat : NULL), loop); struct addrinfo *listen_addrinfo = get_listen_address(opt.listen_addr); - ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons((uint16_t)opt.listen_port); + + if (listen_addrinfo->ai_family == AF_INET) { + ((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port = htons((uint16_t)opt.listen_port); + } else if (listen_addrinfo->ai_family == AF_INET6) { + ((struct sockaddr_in6*) listen_addrinfo->ai_addr)->sin6_port = htons((uint16_t)opt.listen_port); + } app_state_t app; app.https_client = &https_client; From acdcaf7302187a7f1df2f34d381bf1ab370f0f34 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Mon, 25 Aug 2025 10:07:12 +0200 Subject: [PATCH 10/12] fixup! Added DNS server with TCP support --- src/dns_server_tcp.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dns_server_tcp.c b/src/dns_server_tcp.c index f9947b6..f52d491 100644 --- a/src/dns_server_tcp.c +++ b/src/dns_server_tcp.c @@ -118,11 +118,11 @@ static void read_cb(struct ev_loop __attribute__((unused)) *loop, // Receive data char buf[UINT16_MAX]; // stack buffer for largest DNS request - ssize_t len = recv(w->fd, &buf, UINT16_MAX, 0); + ssize_t len = recv(w->fd, buf, UINT16_MAX, 0); if (len <= 0) { if (len == 0 || errno == ECONNRESET) { DLOG_CLIENT("Connection closed"); - } else if (errno == EAGAIN && errno == EWOULDBLOCK) { + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { return; } else { WLOG_CLIENT("Read error: %s", strerror(errno)); @@ -239,17 +239,18 @@ static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) { ELOG("Reuse address failed: %s (%d)", strerror(errno), errno); } + uint16_t port; char ipstr[INET6_ADDRSTRLEN]; if (listen_addrinfo->ai_family == AF_INET) { - inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); + port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); + inet_ntop(AF_INET, &((struct sockaddr_in *)listen_addrinfo->ai_addr)->sin_addr, ipstr, sizeof(ipstr)); } else if (listen_addrinfo->ai_family == AF_INET6) { - inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); + port = ntohs(((struct sockaddr_in6*) listen_addrinfo->ai_addr)->sin6_port); + inet_ntop(AF_INET6, &((struct sockaddr_in6 *)listen_addrinfo->ai_addr)->sin6_addr, ipstr, sizeof(ipstr)); } else { FLOG("Unknown address family: %d", listen_addrinfo->ai_family); } - uint16_t port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); - int res = bind(sock, listen_addrinfo->ai_addr, listen_addrinfo->ai_addrlen); if (res < 0) { FLOG("Error binding on %s:%d TCP: %s (%d)", ipstr, port, From dab40d4b263ca589eb331c9889ee345a82d79ed1 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:01:00 +0200 Subject: [PATCH 11/12] fixup! DNS server refactor --- src/dns_server.c | 13 +++++++++---- src/dns_server.h | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/dns_server.c b/src/dns_server.c index 2f213d8..2fc8f19 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -16,7 +16,7 @@ static int get_listen_sock(struct addrinfo *listen_addrinfo) { FLOG("Error creating socket: %s (%d)", strerror(errno), errno); } - uint16_t port; + uint16_t port = 0; char ipstr[INET6_ADDRSTRLEN]; if (listen_addrinfo->ai_family == AF_INET) { port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); @@ -43,15 +43,20 @@ static void watcher_cb(struct ev_loop __attribute__((unused)) *loop, ev_io *w, int __attribute__((unused)) revents) { dns_server_t *d = (dns_server_t *)w->data; - char tmp_buf[UINT16_MAX]; // stack buffer for largest UDP packet to support EDNS + char tmp_buf[DNS_REQUEST_BUFFER_SIZE]; struct sockaddr_storage tmp_raddr; socklen_t tmp_addrlen = d->addrlen; // recvfrom can write to addrlen - ssize_t len = recvfrom(w->fd, tmp_buf, UINT16_MAX, 0, (struct sockaddr*)&tmp_raddr, - &tmp_addrlen); + ssize_t len = recvfrom(w->fd, tmp_buf, DNS_REQUEST_BUFFER_SIZE, MSG_TRUNC, + (struct sockaddr*)&tmp_raddr, &tmp_addrlen); if (len < 0) { ELOG("recvfrom failed: %s", strerror(errno)); return; } + if (len > DNS_REQUEST_BUFFER_SIZE) { + WLOG("Unsupported request received, too large: %d. Limit is: %d", + len, DNS_REQUEST_BUFFER_SIZE); + return; + } if (len < DNS_HEADER_LENGTH) { WLOG("Malformed request received, too short: %d", len); diff --git a/src/dns_server.h b/src/dns_server.h index 4c5cbfb..0d87165 100644 --- a/src/dns_server.h +++ b/src/dns_server.h @@ -10,7 +10,8 @@ enum { DNS_HEADER_LENGTH = 12, // RFC1035 4.1.1 header size - DNS_SIZE_LIMIT = 512 + DNS_SIZE_LIMIT = 512, + DNS_REQUEST_BUFFER_SIZE = 4096 // EDNS default before DNS Flag Day 2020 }; struct dns_server_s; From 3f356266a3bde8840e5d9fd49cf0efd8618eb8a0 Mon Sep 17 00:00:00 2001 From: baranyaib90 <5031516+baranyaib90@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:01:29 +0200 Subject: [PATCH 12/12] fixup! Added DNS server with TCP support --- src/dns_server_tcp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dns_server_tcp.c b/src/dns_server_tcp.c index f52d491..97d03e4 100644 --- a/src/dns_server_tcp.c +++ b/src/dns_server_tcp.c @@ -117,8 +117,8 @@ static void read_cb(struct ev_loop __attribute__((unused)) *loop, dns_server_tcp_t *d = client->d; // Receive data - char buf[UINT16_MAX]; // stack buffer for largest DNS request - ssize_t len = recv(w->fd, buf, UINT16_MAX, 0); + char buf[DNS_REQUEST_BUFFER_SIZE]; // if there would be more data, callback will be called again + ssize_t len = recv(w->fd, buf, DNS_REQUEST_BUFFER_SIZE, 0); if (len <= 0) { if (len == 0 || errno == ECONNRESET) { DLOG_CLIENT("Connection closed"); @@ -239,7 +239,7 @@ static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) { ELOG("Reuse address failed: %s (%d)", strerror(errno), errno); } - uint16_t port; + uint16_t port = 0; char ipstr[INET6_ADDRSTRLEN]; if (listen_addrinfo->ai_family == AF_INET) { port = ntohs(((struct sockaddr_in*) listen_addrinfo->ai_addr)->sin_port); @@ -306,7 +306,7 @@ void dns_server_tcp_respond(dns_server_tcp_t *d, struct sockaddr *raddr, char *resp, size_t resp_len) { if (resp_len < DNS_HEADER_LENGTH || resp_len > UINT16_MAX) { - WLOG("Malformed request received, invalid length: %u", resp_len); + WLOG("Malformed response received, invalid length: %u", resp_len); return; }