Skip to content

Commit 0e074b4

Browse files
authored
Merge pull request #182 from baranyaib90/master
Fixes 14
2 parents 484bd15 + fd62a31 commit 0e074b4

File tree

13 files changed

+386
-120
lines changed

13 files changed

+386
-120
lines changed

README.md

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ only makes sense if you trust your DoH provider.
4343

4444
## Build
4545

46-
Depends on `c-ares (>=1.11.0)`, `libcurl (>=7.64.0)`, `libev (>=4.25)`.
46+
Depends on `c-ares (>=1.11.0)`, `libcurl (>=7.65.0)`, `libev (>=4.25)`.
4747

4848
On Debian-derived systems those are libc-ares-dev,
4949
libcurl4-{openssl,nss,gnutls}-dev and libev-dev respectively.
@@ -159,40 +159,55 @@ Just run it as a daemon and point traffic at it. Commandline flags are:
159159

160160
```
161161
Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>]
162-
[-d] [-u <user>] [-g <group>] [-b <dns_servers>]
163-
[-i <polling_interval>] [-4] [-r <resolver_url>]
164-
[-t <proxy_server>] [-l <logfile>] [-c <dscp_codepoint>]
165-
[-x] [-q] [-s <statistic_interval>] [-v]+ [-V] [-h]
162+
[-b <dns_servers>] [-i <polling_interval>] [-4]
163+
[-r <resolver_url>] [-t <proxy_server>] [-x] [-q] [-C <ca_path>] [-c <dscp_codepoint>]
164+
[-d] [-u <user>] [-g <group>]
165+
[-v]+ [-l <logfile>] [-s <statistic_interval>] [-F <log_limit>] [-V] [-h]
166166
167-
-a listen_addr Local IPv4/v6 address to bind to. (127.0.0.1)
168-
-p listen_port Local port to bind to. (5053)
169-
-d Daemonize.
170-
-u user Optional user to drop to if launched as root.
171-
-g group Optional group to drop to if launched as root.
167+
DNS server
168+
-a listen_addr Local IPv4/v6 address to bind to. (Default: 127.0.0.1)
169+
-p listen_port Local port to bind to. (Default: 5053)
170+
171+
DNS client
172172
-b dns_servers Comma-separated IPv4/v6 addresses and ports (addr:port)
173173
of DNS servers to resolve resolver host (e.g. dns.google).
174174
When specifying a port for IPv6, enclose the address in [].
175-
(8.8.8.8,1.1.1.1,8.8.4.4,1.0.0.1,145.100.185.15,145.100.185.16,185.49.141.37)
175+
(Default: 8.8.8.8,1.1.1.1,8.8.4.4,1.0.0.1,145.100.185.15,145.100.185.16,185.49.141.37)
176176
-i polling_interval Optional polling interval of DNS servers.
177177
(Default: 120, Min: 5, Max: 3600)
178178
-4 Force IPv4 hostnames for DNS resolvers non IPv6 networks.
179-
-r resolver_url The HTTPS path to the resolver URL. Default: https://dns.google/dns-query
179+
180+
HTTPS client
181+
-r resolver_url The HTTPS path to the resolver URL. (Default: https://dns.google/dns-query)
180182
-t proxy_server Optional HTTP proxy. e.g. socks5://127.0.0.1:1080
181183
Remote name resolution will be used if the protocol
182184
supports it (http, https, socks4a, socks5h), otherwise
183185
initial DNS resolution will still be done via the
184186
bootstrap DNS servers.
185-
-l logfile Path to file to log to. ("-")
186-
-c dscp_codepoint Optional DSCP codepoint[0-63] to set on upstream DNS server
187-
connections.
188187
-x Use HTTP/1.1 instead of HTTP/2. Useful with broken
189-
or limited builds of libcurl. (false)
190-
-q Use HTTP/3 (QUIC) only. (false)
191-
-s statistic_interval Optional statistic printout interval.
192-
(Default: 0, Disabled: 0, Min: 1, Max: 3600)
188+
or limited builds of libcurl.
189+
-q Use HTTP/3 (QUIC) only.
190+
-m max_idle_time Maximum idle time in seconds allowed for reusing a HTTPS connection.
191+
(Default: 118, Min: 0, Max: 3600)
192+
-C ca_path Optional file containing CA certificates.
193+
-c dscp_codepoint Optional DSCP codepoint to set on upstream HTTPS server
194+
connections. (Min: 0, Max: 63)
195+
196+
Process
197+
-d Daemonize.
198+
-u user Optional user to drop to if launched as root.
199+
-g group Optional group to drop to if launched as root.
200+
201+
Logging
193202
-v Increase logging verbosity. (Default: error)
194203
Levels: fatal, stats, error, warning, info, debug
195204
Request issues are logged on warning level.
205+
-l logfile Path to file to log to. (Default: standard output)
206+
-s statistic_interval Optional statistic printout interval.
207+
(Default: 0, Disabled: 0, Min: 1, Max: 3600)
208+
-F log_limit Flight recorder: storing desired amount of logs from all levels
209+
in memory and dumping them on fatal error or on SIGUSR2 signal.
210+
(Default: 0, Disabled: 0, Min: 100, Max: 100000)
196211
-V Print version and exit.
197212
-h Print help and exit.
198213
```

development_build_with_http3.sh

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,54 @@ echo "WARNING !!!"
77
echo
88
echo "Use only for development and testing!"
99
echo "It is highly highly not recommended, to use in production!"
10-
echo "This script was based on: https://github.com/curl/curl/blob/curl-7_82_0/docs/HTTP3.md"
10+
echo "This script was based on: https://github.com/curl/curl/blob/curl-8_12_1/docs/HTTP3.md"
11+
echo
12+
echo "Extra packages suggested to be installed: autoconf libtool"
1113
echo
1214

1315
sleep 5
1416

17+
set -x
18+
1519
INSTALL_DIR=$PWD/custom_curl/install
1620
mkdir -p $INSTALL_DIR
1721
cd custom_curl
1822

1923
###
2024

21-
git clone --depth 1 -b openssl-3.0.0+quic https://github.com/quictls/openssl
25+
git clone --depth 1 -b openssl-3.1.4+quic https://github.com/quictls/openssl
2226
cd openssl
2327
./config enable-tls1_3 --prefix=$INSTALL_DIR
2428
make -j build_libs
2529
make install_dev
2630
cd ..
2731

28-
git clone --depth 1 -b v0.3.0 https://github.com/ngtcp2/nghttp3
32+
git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3
2933
cd nghttp3
34+
git submodule update --init
3035
autoreconf -fi
3136
./configure --prefix=$INSTALL_DIR --enable-lib-only
3237
make -j
3338
make install
3439
cd ..
3540

36-
git clone --depth 1 -b v0.3.1 https://github.com/ngtcp2/ngtcp2
41+
git clone --depth 1 -b v1.2.0 https://github.com/ngtcp2/ngtcp2
3742
cd ngtcp2
3843
autoreconf -fi
3944
./configure PKG_CONFIG_PATH=$INSTALL_DIR/lib64/pkgconfig:$INSTALL_DIR/lib64/pkgconfig LDFLAGS="-Wl,-rpath,$INSTALL_DIR/lib64" --prefix=$INSTALL_DIR --enable-lib-only --with-openssl
4045
make -j
4146
make install
4247
cd ..
4348

44-
git clone --depth 1 -b v1.47.0 https://github.com/nghttp2/nghttp2
49+
git clone --depth 1 -b v1.64.0 https://github.com/nghttp2/nghttp2
4550
cd nghttp2
4651
autoreconf -fi
4752
./configure PKG_CONFIG_PATH=$INSTALL_DIR/lib64/pkgconfig:$INSTALL_DIR/lib64/pkgconfig LDFLAGS="-Wl,-rpath,$INSTALL_DIR/lib64" --prefix=$INSTALL_DIR --enable-lib-only --with-openssl
4853
make -j
4954
make install
5055
cd ..
5156

52-
git clone --depth 1 -b curl-7_82_0 https://github.com/curl/curl
57+
git clone --depth 1 -b curl-8_12_1 https://github.com/curl/curl
5358
cd curl
5459
autoreconf -fi
5560
LDFLAGS="-Wl,-rpath,$INSTALL_DIR/lib64" ./configure --with-openssl=$INSTALL_DIR --with-nghttp2=$INSTALL_DIR --with-nghttp3=$INSTALL_DIR --with-ngtcp2=$INSTALL_DIR --prefix=$INSTALL_DIR
@@ -60,5 +65,5 @@ cd ..
6065
###
6166

6267
cd ..
63-
cmake -D CUSTOM_LIBCURL_INSTALL_PATH=$INSTALL_DIR .
68+
cmake -D CUSTOM_LIBCURL_INSTALL_PATH=$INSTALL_DIR -D CMAKE_BUILD_TYPE=Debug .
6469
make -j

src/dns_poller.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ static void sock_state_cb(void *data, int fd, int read, int write) {
3535
// reserve and start new event on unused slot
3636
io_event_ptr = get_io_event(d, 0);
3737
if (!io_event_ptr) {
38-
FLOG("c-ares needed more event, than nameservers count: %d", d->io_events_count);
38+
FLOG("c-ares needed more IO event handler, than the number of provided nameservers: %d", d->io_events_count);
3939
}
4040
DLOG("Reserved new io event: %p", io_event_ptr);
4141
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
@@ -75,9 +75,9 @@ static void ares_cb(void *arg, int status, int __attribute__((unused)) timeouts,
7575
ev_tstamp interval = 5; // retry by default after some time
7676

7777
if (status != ARES_SUCCESS) {
78-
WLOG("DNS lookup failed: %s", ares_strerror(status));
78+
WLOG("DNS lookup of '%s' failed: %s", d->hostname, ares_strerror(status));
7979
} else if (!h || h->h_length < 1) {
80-
WLOG("No hosts.");
80+
WLOG("No hosts found for '%s'", d->hostname);
8181
} else {
8282
interval = d->polling_interval;
8383
d->cb(d->hostname, d->cb_data, get_addr_listing(h->h_addr_list, h->h_addrtype));

src/https_client.c

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ DOH_MAX_RESPONSE_SIZE = 65535
5050
FLOG("Unexpected NULL pointer for " #var_name "(" #type ")"); \
5151
}
5252

53+
static void https_fetch_ctx_cleanup(https_client_t *client,
54+
struct https_fetch_ctx *prev,
55+
struct https_fetch_ctx *ctx,
56+
int curl_result_code);
57+
5358
static size_t write_buffer(void *buf, size_t size, size_t nmemb, void *userp) {
5459
GET_PTR(struct https_fetch_ctx, ctx, userp);
5560
size_t write_size = size * nmemb;
@@ -76,14 +81,20 @@ static curl_socket_t opensocket_callback(void *clientp, curlsocktype purpose,
7681
struct curl_sockaddr *addr) {
7782
GET_PTR(https_client_t, client, clientp);
7883

84+
if (client->connections >= HTTPS_SOCKET_LIMIT) {
85+
ELOG("curl needed more socket, than the number of maximum sockets: %d", HTTPS_SOCKET_LIMIT);
86+
return CURL_SOCKET_BAD;
87+
}
88+
7989
curl_socket_t sock = socket(addr->family, addr->socktype, addr->protocol);
80-
if (sock != -1) {
81-
DLOG("curl opened socket: %d", sock);
82-
} else {
90+
if (sock == -1) {
8391
ELOG("Could not open curl socket %d:%s", errno, strerror(errno));
8492
return CURL_SOCKET_BAD;
8593
}
8694

95+
DLOG("curl opened socket: %d", sock);
96+
client->connections++;
97+
8798
if (client->stat) {
8899
stat_connection_opened(client->stat);
89100
}
@@ -112,13 +123,14 @@ static int closesocket_callback(void __attribute__((unused)) *clientp, curl_sock
112123
{
113124
GET_PTR(https_client_t, client, clientp);
114125

115-
if (close(sock) == 0) {
116-
DLOG("curl closed socket: %d", sock);
117-
} else {
126+
if (close(sock) != 0) {
118127
ELOG("Could not close curl socket %d:%s", errno, strerror(errno));
119128
return 1;
120129
}
121130

131+
DLOG("curl closed socket: %d", sock);
132+
client->connections--;
133+
122134
if (client->stat) {
123135
stat_connection_closed(client->stat);
124136
}
@@ -265,7 +277,6 @@ static void https_set_request_version(https_client_t *client,
265277
} else if (client->opt->use_http_version == 2) {
266278
ELOG("Try to run application with -x argument! Falling back to HTTP/1.1 version.");
267279
client->opt->use_http_version = 1;
268-
// TODO: consider CURLMOPT_PIPELINING setting??
269280
}
270281
}
271282
}
@@ -293,30 +304,22 @@ static void https_fetch_ctx_init(https_client_t *client,
293304
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_DEBUGFUNCTION, https_curl_debug);
294305
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_DEBUGDATA, ctx);
295306
}
296-
if (logging_debug_enabled() || client->stat || client->opt->dscp) {
297-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
298-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_OPENSOCKETDATA, client);
299-
}
300-
if (logging_debug_enabled() || client->stat) {
301-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
302-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_CLOSESOCKETDATA, client);
303-
}
307+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
308+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_OPENSOCKETDATA, client);
309+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
310+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_CLOSESOCKETDATA, client);
304311
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_URL, url);
305312
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_HTTPHEADER, client->header_list);
306313
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_POSTFIELDSIZE, datalen);
307314
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_POSTFIELDS, data);
308315
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_WRITEFUNCTION, &write_buffer);
309316
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_WRITEDATA, ctx);
310-
#ifdef CURLOPT_MAXAGE_CONN
311-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TCP_KEEPALIVE, 1L);
312-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TCP_KEEPIDLE, 50L);
313-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TCP_KEEPINTVL, 50L);
314-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_MAXAGE_CONN, 300L);
315-
#endif
317+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_MAXAGE_CONN, client->opt->max_idle_time);
318+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_PIPEWAIT, client->opt->use_http_version > 1);
316319
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_USERAGENT, "https_dns_proxy/0.3");
317320
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_FOLLOWLOCATION, 0);
318321
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_NOSIGNAL, 0);
319-
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TIMEOUT, 10 /* seconds */);
322+
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_TIMEOUT, client->connections > 0 ? 5 : 10 /* seconds */);
320323
// We know Google supports this, so force it.
321324
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
322325
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_ERRORBUFFER, ctx->curl_errbuf); // zeroed by calloc
@@ -329,8 +332,13 @@ static void https_fetch_ctx_init(https_client_t *client,
329332
}
330333
CURLMcode multi_code = curl_multi_add_handle(client->curlm, ctx->curl);
331334
if (multi_code != CURLM_OK) {
332-
FLOG_REQ("curl_multi_add_handle error %d: %s",
333-
multi_code, curl_multi_strerror(multi_code));
335+
ELOG_REQ("curl_multi_add_handle error %d: %s", multi_code, curl_multi_strerror(multi_code));
336+
if (multi_code == CURLM_ABORTED_BY_CALLBACK) {
337+
WLOG_REQ("Resetting HTTPS client to recover from faulty state!");
338+
https_client_reset(client);
339+
} else {
340+
https_fetch_ctx_cleanup(client, NULL, client->fetches, -1); // dropping current failed request
341+
}
334342
}
335343
}
336344

@@ -506,10 +514,10 @@ static void https_fetch_ctx_cleanup(https_client_t *client,
506514
}
507515
int drop_reply = 0;
508516
if (curl_result_code < 0) {
509-
WLOG_REQ("Request was aborted.");
517+
WLOG_REQ("Request was aborted");
510518
drop_reply = 1;
511519
} else if (https_fetch_ctx_process_response(client, ctx, curl_result_code) != 0) {
512-
ILOG_REQ("Response was faulty, skipping DNS reply.");
520+
ILOG_REQ("Response was faulty, skipping DNS reply");
513521
drop_reply = 1;
514522
}
515523
if (drop_reply) {
@@ -545,42 +553,62 @@ static void check_multi_info(https_client_t *c) {
545553
cur = cur->next;
546554
}
547555
}
556+
else {
557+
ELOG("Unhandled curl message: %d", msg->msg); // unlikely
558+
}
548559
}
549560
}
550561

551562
static void sock_cb(struct ev_loop __attribute__((unused)) *loop,
552563
struct ev_io *w, int revents) {
553564
GET_PTR(https_client_t, c, w->data);
565+
int ignore = 0;
554566
CURLMcode code = curl_multi_socket_action(
555567
c->curlm, w->fd, (revents & EV_READ ? CURL_CSELECT_IN : 0) |
556568
(revents & EV_WRITE ? CURL_CSELECT_OUT : 0),
557-
&c->still_running);
558-
if (code != CURLM_OK) {
569+
&ignore);
570+
if (code == CURLM_OK) {
571+
check_multi_info(c);
572+
}
573+
else {
559574
FLOG("curl_multi_socket_action error %d: %s", code, curl_multi_strerror(code));
575+
if (code == CURLM_ABORTED_BY_CALLBACK) {
576+
WLOG("Resetting HTTPS client to recover from faulty state!");
577+
https_client_reset(c);
578+
}
560579
}
561-
check_multi_info(c);
562580
}
563581

564582
static void timer_cb(struct ev_loop __attribute__((unused)) *loop,
565583
struct ev_timer *w, int __attribute__((unused)) revents) {
566584
GET_PTR(https_client_t, c, w->data);
585+
int ignore = 0;
567586
CURLMcode code = curl_multi_socket_action(c->curlm, CURL_SOCKET_TIMEOUT, 0,
568-
&c->still_running);
587+
&ignore);
569588
if (code != CURLM_OK) {
570-
FLOG("curl_multi_socket_action error %d: %s", code, curl_multi_strerror(code));
589+
ELOG("curl_multi_socket_action error %d: %s", code, curl_multi_strerror(code));
571590
}
572591
check_multi_info(c);
573592
}
574593

575594
static struct ev_io * get_io_event(struct ev_io io_events[], curl_socket_t sock) {
576-
for (int i = 0; i < MAX_TOTAL_CONNECTIONS; i++) {
595+
for (int i = 0; i < HTTPS_SOCKET_LIMIT; i++) {
577596
if (io_events[i].fd == sock) {
578597
return &io_events[i];
579598
}
580599
}
581600
return NULL;
582601
}
583602

603+
static void dump_io_events(struct ev_io io_events[]) {
604+
for (int i = 0; i < HTTPS_SOCKET_LIMIT; i++) {
605+
ILOG("IO event #%d: fd=%d, events=%d/%s%s",
606+
i+1, io_events[i].fd, io_events[i].events,
607+
(io_events[i].events & EV_READ ? "R" : ""),
608+
(io_events[i].events & EV_WRITE ? "W" : ""));
609+
}
610+
}
611+
584612
static int multi_sock_cb(CURL *curl, curl_socket_t sock, int what,
585613
void *userp, void __attribute__((unused)) *sockp) {
586614
GET_PTR(https_client_t, c, userp);
@@ -600,7 +628,10 @@ static int multi_sock_cb(CURL *curl, curl_socket_t sock, int what,
600628
// reserve and start new event on unused slot
601629
io_event_ptr = get_io_event(c->io_events, 0);
602630
if (!io_event_ptr) {
603-
FLOG("curl needed more event, than max connections: %d", MAX_TOTAL_CONNECTIONS);
631+
ELOG("curl needed more IO event handler, than the number of maximum sockets: %d", HTTPS_SOCKET_LIMIT);
632+
dump_io_events(c->io_events);
633+
logging_flight_recorder_dump();
634+
return -1;
604635
}
605636
DLOG("Reserved new io event: %p", io_event_ptr);
606637
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
@@ -634,14 +665,15 @@ void https_client_init(https_client_t *c, options_t *opt,
634665
"Content-Type: " DOH_CONTENT_TYPE);
635666
c->fetches = NULL;
636667
c->timer.data = c;
637-
for (int i = 0; i < MAX_TOTAL_CONNECTIONS; i++) {
668+
for (int i = 0; i < HTTPS_SOCKET_LIMIT; i++) {
638669
c->io_events[i].data = c;
639670
}
640671
c->opt = opt;
641672
c->stat = stat;
642673

643674
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX);
644-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, MAX_TOTAL_CONNECTIONS);
675+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
676+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_HOST_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
645677
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETDATA, c);
646678
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETFUNCTION, multi_sock_cb);
647679
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERDATA, c);

0 commit comments

Comments
 (0)