Skip to content

Commit 0ba0525

Browse files
authored
Some architectural cleanup (#204)
* Extract DoH proxy core; unify UDP/TCP listeners behind one interface The per-request lifecycle (allocate state, hand to libcurl, route response back to the originating listener, free) lived as static functions in main.c glued together via app_state_t and an is_tcp tag in request_t. The two listener implementations had no shared interface, so dispatch branched on is_tcp at every step. - dns_listener.h: transport-agnostic interface (respond/stop/destroy function pointers + transport tag for metrics). - dns_listener_udp.{c,h}, dns_listener_tcp.{c,h}: adapters. - doh_proxy.{c,h}: owns the request lifecycle, curl resolve list, and bootstrap-await/on-ready handshake. Listeners and dns_poller call into the proxy, not main.c. - dns_common.h: shared DNS protocol constants. main.c shrinks to assembly. The is_tcp boolean is gone. * Extract DNS response truncation into its own module ~110 lines of EDNS0/c-ares juggling (parse OPT, drop additional+authority, drop answers until under the limit, set TC) was buried as static functions in the UDP listener — only reachable by booting a real UDP server. Move to dns_truncate.{c,h} with one entry point: dns_truncate_for_udp(). The UDP listener's respond path collapses to validate-length, truncate-for-fit, sendto. The module is now unit-testable with synthetic byte buffers.
1 parent 8018812 commit 0ba0525

13 files changed

Lines changed: 645 additions & 393 deletions

src/dns_common.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef _DNS_COMMON_H_
2+
#define _DNS_COMMON_H_
3+
4+
// Constants from the DNS wire format, shared by both UDP and TCP listeners
5+
// and by the DoH proxy core.
6+
7+
enum {
8+
DNS_HEADER_LENGTH = 12, // RFC1035 4.1.1 header size
9+
DNS_SIZE_LIMIT = 512, // RFC1035 4.2.1 traditional UDP payload limit
10+
DNS_REQUEST_BUFFER_SIZE = 4096 // EDNS default before DNS Flag Day 2020
11+
};
12+
13+
#endif // _DNS_COMMON_H_

src/dns_listener.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#ifndef _DNS_LISTENER_H_
2+
#define _DNS_LISTENER_H_
3+
4+
#include <sys/socket.h>
5+
#include <sys/types.h>
6+
7+
// A DNS listener accepts requests on some transport (UDP, TCP, ...) and routes
8+
// responses back to the originating peer. The proxy core never branches on
9+
// transport — all listener-specific behaviour (datagram-vs-stream framing,
10+
// EDNS-aware truncation, per-client state) lives behind the function pointers
11+
// on dns_listener.
12+
13+
typedef struct dns_listener dns_listener_t;
14+
15+
// Transport classification, exposed for callers that need to bin metrics or
16+
// log per-transport — not used for dispatch (dispatch is via the function
17+
// pointers on dns_listener itself).
18+
typedef enum {
19+
DNS_TRANSPORT_UDP,
20+
DNS_TRANSPORT_TCP,
21+
} dns_transport_t;
22+
23+
// Invoked once per fully-received DNS request. `dns_req` is heap-allocated
24+
// and ownership transfers to the callee. `listener` is a back-pointer the
25+
// callee uses later to deliver the matching response.
26+
typedef void (*dns_request_fn)(void *ctx, dns_listener_t *listener,
27+
struct sockaddr *raddr,
28+
char *dns_req, size_t dns_req_len);
29+
30+
struct dns_listener {
31+
// Send `dns_resp` to `raddr`. UDP listeners may EDNS-truncate the response
32+
// in place using `dns_req`; TCP listeners ignore the request bytes.
33+
void (*respond)(dns_listener_t *self, struct sockaddr *raddr,
34+
const char *dns_req, size_t dns_req_len,
35+
char *dns_resp, size_t dns_resp_len);
36+
// Stop accepting new requests. Existing per-client state (TCP) is retained
37+
// so any in-flight DoH responses can still be delivered during graceful
38+
// drain.
39+
void (*stop)(dns_listener_t *self);
40+
// Free the listener and any owned resources.
41+
void (*destroy)(dns_listener_t *self);
42+
43+
dns_transport_t transport;
44+
};
45+
46+
#endif // _DNS_LISTENER_H_
Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
//NOLINTNEXTLINE(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
22
#define _GNU_SOURCE // needed for having accept4()
33

4+
#include <arpa/inet.h>
45
#include <errno.h>
56
#include <fcntl.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#include <sys/socket.h>
610
#include <unistd.h>
711

8-
#include "dns_server_tcp.h"
12+
#include "dns_common.h"
13+
#include "dns_listener_tcp.h"
914
#include "logging.h"
1015

1116
// Platform compatibility
@@ -33,8 +38,10 @@ enum {
3338
TCP_DNS_MAX_PAYLOAD = UINT16_MAX - sizeof(uint16_t), // Max after 2-byte length prefix
3439
};
3540

41+
typedef struct dns_listener_tcp_s dns_listener_tcp_t;
42+
3643
struct tcp_client_s {
37-
struct dns_server_tcp_s * d;
44+
dns_listener_tcp_t * d;
3845

3946
uint64_t id;
4047
int sock;
@@ -52,10 +59,12 @@ struct tcp_client_s {
5259
struct tcp_client_s * next;
5360
} __attribute__((packed)) __attribute__((aligned(128)));
5461

55-
struct dns_server_tcp_s {
62+
struct dns_listener_tcp_s {
63+
dns_listener_t base;
64+
5665
struct ev_loop *loop;
5766

58-
dns_req_received_cb cb;
67+
dns_request_fn cb;
5968
void *cb_data;
6069

6170
int sock;
@@ -70,7 +79,7 @@ struct dns_server_tcp_s {
7079

7180

7281
static void remove_client(struct tcp_client_s * client) {
73-
dns_server_tcp_t *d = client->d;
82+
dns_listener_tcp_t *d = client->d;
7483

7584
DLOG_CLIENT("Removing client, socket %d", client->sock);
7685

@@ -114,7 +123,7 @@ static int get_dns_request(struct tcp_client_s *client,
114123
return 0; // Partial request
115124
}
116125
// copy whole request
117-
*dns_req = (char *)malloc(*req_size); // To free buffer after https request is complete.
126+
*dns_req = (char *)malloc(*req_size); // freed when DoH request completes
118127
if (*dns_req == NULL) {
119128
FLOG_CLIENT("Out of mem");
120129
}
@@ -128,7 +137,7 @@ static int get_dns_request(struct tcp_client_s *client,
128137
static void read_cb(struct ev_loop __attribute__((unused)) *loop,
129138
ev_io *w, int __attribute__((unused)) revents) {
130139
struct tcp_client_s *client = (struct tcp_client_s *)w->data;
131-
dns_server_tcp_t *d = client->d;
140+
dns_listener_tcp_t *d = client->d;
132141

133142
// Receive data
134143
char buf[DNS_REQUEST_BUFFER_SIZE]; // if there would be more data, callback will be called again
@@ -191,7 +200,7 @@ static void read_cb(struct ev_loop __attribute__((unused)) *loop,
191200
return;
192201
}
193202

194-
d->cb(d, 1, d->cb_data, (struct sockaddr*)&client->raddr, dns_req, req_size);
203+
d->cb(d->cb_data, &d->base, (struct sockaddr*)&client->raddr, dns_req, req_size);
195204
request_received = 1;
196205
}
197206

@@ -209,7 +218,7 @@ static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
209218

210219
static void accept_cb(struct ev_loop __attribute__((unused)) *loop,
211220
ev_io *w, int __attribute__((unused)) revents) {
212-
dns_server_tcp_t *d = (dns_server_tcp_t *)w->data;
221+
dns_listener_tcp_t *d = (dns_listener_tcp_t *)w->data;
213222

214223
struct sockaddr_storage client_addr;
215224
socklen_t client_addr_len = sizeof(client_addr);
@@ -258,7 +267,7 @@ static void accept_cb(struct ev_loop __attribute__((unused)) *loop,
258267
DLOG_CLIENT("Accepted client %u of %u, socket %d", d->client_count, d->client_limit, client->sock);
259268
}
260269

261-
// Creates and bind a listening non-blocking TCP socket for incoming requests.
270+
// Creates and binds a listening non-blocking TCP socket for incoming requests.
262271
static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) {
263272
int sock = socket(listen_addrinfo->ai_family, SOCK_STREAM, 0);
264273
if (sock < 0) {
@@ -308,33 +317,12 @@ static int get_tcp_listen_sock(struct addrinfo *listen_addrinfo) {
308317
return sock;
309318
}
310319

311-
dns_server_tcp_t * dns_server_tcp_create(
312-
struct ev_loop *loop, struct addrinfo *listen_addrinfo,
313-
dns_req_received_cb cb, void *data, uint16_t tcp_client_limit) {
314-
dns_server_tcp_t * d = (dns_server_tcp_t *) malloc(sizeof(dns_server_tcp_t));
315-
if (d == NULL) {
316-
FLOG("Out of mem");
317-
}
318-
d->loop = loop;
319-
d->cb = cb;
320-
d->cb_data = data;
321-
d->sock = get_tcp_listen_sock(listen_addrinfo);
322-
d->addrlen = listen_addrinfo->ai_addrlen;
323-
d->client_id = 0;
324-
d->client_count = 0;
325-
d->client_limit = tcp_client_limit;
326-
d->clients = NULL;
327-
328-
ev_io_init(&d->accept_watcher, accept_cb, d->sock, EV_READ);
329-
d->accept_watcher.data = d;
330-
ev_io_start(d->loop, &d->accept_watcher);
331-
332-
return d;
333-
}
320+
static void tcp_respond(dns_listener_t *self, struct sockaddr *raddr,
321+
const char __attribute__((unused)) *dns_req,
322+
size_t __attribute__((unused)) dns_req_len,
323+
char *resp, size_t resp_len) {
324+
dns_listener_tcp_t *d = (dns_listener_tcp_t *)self;
334325

335-
void dns_server_tcp_respond(dns_server_tcp_t *d,
336-
struct sockaddr *raddr, char *resp, size_t resp_len)
337-
{
338326
// Limit response size to prevent overflow when accounting for the 2-byte
339327
// length prefix. The total on-wire size would be resp_len + sizeof(uint16_t).
340328
if (resp_len < DNS_HEADER_LENGTH || resp_len > TCP_DNS_MAX_PAYLOAD) {
@@ -402,13 +390,42 @@ void dns_server_tcp_respond(dns_server_tcp_t *d,
402390
ev_timer_again(d->loop, &client->timer_watcher);
403391
}
404392

405-
void dns_server_tcp_stop(dns_server_tcp_t *d) {
393+
static void tcp_stop(dns_listener_t *self) {
394+
dns_listener_tcp_t *d = (dns_listener_tcp_t *)self;
406395
while (d->clients) {
407396
remove_client(d->clients); //NOLINT(clang-analyzer-unix.Malloc) false use after free detection
408397
}
409398
ev_io_stop(d->loop, &d->accept_watcher);
410399
}
411400

412-
void dns_server_tcp_cleanup(dns_server_tcp_t *d) {
401+
static void tcp_destroy(dns_listener_t *self) {
402+
dns_listener_tcp_t *d = (dns_listener_tcp_t *)self;
413403
close(d->sock);
404+
free(d);
405+
}
406+
407+
dns_listener_t * dns_tcp_listener_create(struct ev_loop *loop,
408+
struct addrinfo *listen_addrinfo,
409+
uint16_t client_limit,
410+
dns_request_fn cb, void *ctx) {
411+
dns_listener_tcp_t * d = (dns_listener_tcp_t *)calloc(1, sizeof(dns_listener_tcp_t));
412+
if (d == NULL) {
413+
FLOG("Out of mem");
414+
}
415+
d->base.respond = tcp_respond;
416+
d->base.stop = tcp_stop;
417+
d->base.destroy = tcp_destroy;
418+
d->base.transport = DNS_TRANSPORT_TCP;
419+
d->loop = loop;
420+
d->cb = cb;
421+
d->cb_data = ctx;
422+
d->sock = get_tcp_listen_sock(listen_addrinfo);
423+
d->addrlen = listen_addrinfo->ai_addrlen;
424+
d->client_limit = client_limit;
425+
426+
ev_io_init(&d->accept_watcher, accept_cb, d->sock, EV_READ);
427+
d->accept_watcher.data = d;
428+
ev_io_start(d->loop, &d->accept_watcher);
429+
430+
return &d->base;
414431
}

src/dns_listener_tcp.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ifndef _DNS_LISTENER_TCP_H_
2+
#define _DNS_LISTENER_TCP_H_
3+
4+
#include <netdb.h>
5+
#include <stdint.h>
6+
#include <ev.h>
7+
8+
#include "dns_listener.h"
9+
10+
// Create a TCP DNS listener bound to `listen_addrinfo`. The returned listener
11+
// implements the dns_listener_t interface; callers should treat it as such
12+
// and use dns_listener_stop / dns_listener_destroy for lifecycle.
13+
//
14+
// `client_limit` caps the number of concurrent TCP clients. `cb` is invoked
15+
// once per fully-received DNS request from any client.
16+
dns_listener_t * dns_tcp_listener_create(struct ev_loop *loop,
17+
struct addrinfo *listen_addrinfo,
18+
uint16_t client_limit,
19+
dns_request_fn cb, void *ctx);
20+
21+
#endif // _DNS_LISTENER_TCP_H_

0 commit comments

Comments
 (0)