Skip to content

Commit 58bad2e

Browse files
committed
Reset HTTPS client on timeouts
If there are not any successful request.
1 parent 7c5445c commit 58bad2e

File tree

5 files changed

+64
-17
lines changed

5 files changed

+64
-17
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>] [-T <tcp_client_l
191191
-q Use HTTP/3 (QUIC) only.
192192
-m max_idle_time Maximum idle time in seconds allowed for reusing a HTTPS connection.
193193
(Default: 118, Min: 0, Max: 3600)
194+
-L conn_loss_time Time in seconds to tolerate connection timeouts of reused connections.
195+
This option mitigates half-open TCP connection issue (e.g. WAN IP change).
196+
(Default: 15, Min: 5, Max: 60)
194197
-C ca_path Optional file containing CA certificates.
195198
-c dscp_codepoint Optional DSCP codepoint to set on upstream HTTPS server
196199
connections. (Min: 0, Max: 63)

src/https_client.c

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ static int closesocket_callback(void __attribute__((unused)) *clientp, curl_sock
131131
DLOG("curl closed socket: %d", sock);
132132
client->connections--;
133133

134+
if (client->connections <= 0 && ev_is_active(&client->reset_timer)) {
135+
ILOG("Client reset timer cancelled, since all connection closed");
136+
ev_timer_stop(client->loop, &client->reset_timer);
137+
}
138+
134139
if (client->stat) {
135140
stat_connection_closed(client->stat);
136141
}
@@ -353,6 +358,12 @@ static int https_fetch_ctx_process_response(https_client_t *client,
353358
case CURLE_WRITE_ERROR:
354359
WLOG_REQ("curl request failed with write error (probably response content was too large)");
355360
break;
361+
case CURLE_OPERATION_TIMEDOUT:
362+
if (!ev_is_active(&client->reset_timer)) {
363+
ILOG_REQ("Client reset timer started");
364+
ev_timer_start(client->loop, &client->reset_timer);
365+
}
366+
__attribute__((fallthrough));
356367
default:
357368
WLOG_REQ("curl request failed with %d: %s", curl_result_code, curl_easy_strerror(curl_result_code));
358369
if (ctx->curl_errbuf[0] != 0) {
@@ -465,7 +476,7 @@ static int https_fetch_ctx_process_response(https_client_t *client,
465476
res = curl_easy_getinfo(ctx->curl, CURLINFO_SCHEME, &str_resp);
466477
if (res != CURLE_OK) {
467478
ELOG_REQ("CURLINFO_SCHEME: %s", curl_easy_strerror(res));
468-
} else if (strcasecmp(str_resp, "https") != 0) {
479+
} else if (str_resp != NULL && strcasecmp(str_resp, "https") != 0) {
469480
DLOG_REQ("CURLINFO_SCHEME: %s", str_resp);
470481
}
471482

@@ -648,15 +659,31 @@ static int multi_timer_cb(CURLM __attribute__((unused)) *multi,
648659
return 0;
649660
}
650661

662+
static void https_client_multi_init(https_client_t *c, struct curl_slist *header_list) {
663+
c->curlm = curl_multi_init(); // if fails, first setopt will fail
664+
c->header_list = header_list;
665+
666+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX);
667+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
668+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_HOST_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
669+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETDATA, c);
670+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETFUNCTION, multi_sock_cb);
671+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERDATA, c);
672+
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
673+
}
674+
675+
static void reset_timer_cb(struct ev_loop __attribute__((unused)) *loop,
676+
ev_timer *w, int __attribute__((unused)) revents) {
677+
GET_PTR(https_client_t, c, w->data);
678+
ILOG("Client reset timer timeouted");
679+
https_client_reset(c);
680+
}
681+
651682
void https_client_init(https_client_t *c, options_t *opt,
652683
stat_t *stat, struct ev_loop *loop) {
653684
// NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
654685
memset(c, 0, sizeof(*c));
655686
c->loop = loop;
656-
c->curlm = curl_multi_init(); // if fails, first setopt will fail
657-
c->header_list = curl_slist_append(curl_slist_append(NULL,
658-
"Accept: " DOH_CONTENT_TYPE),
659-
"Content-Type: " DOH_CONTENT_TYPE);
660687
c->fetches = NULL;
661688
c->timer.data = c;
662689
for (int i = 0; i < HTTPS_SOCKET_LIMIT; i++) {
@@ -665,13 +692,13 @@ void https_client_init(https_client_t *c, options_t *opt,
665692
c->opt = opt;
666693
c->stat = stat;
667694

668-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX);
669-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
670-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_MAX_HOST_CONNECTIONS, HTTPS_CONNECTION_LIMIT);
671-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETDATA, c);
672-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_SOCKETFUNCTION, multi_sock_cb);
673-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERDATA, c);
674-
ASSERT_CURL_MULTI_SETOPT(c->curlm, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
695+
ev_timer_init(&c->reset_timer, reset_timer_cb, (double)opt->conn_loss_time, 0);
696+
c->reset_timer.data = c;
697+
698+
struct curl_slist *header_list = curl_slist_append(curl_slist_append(NULL,
699+
"Accept: " DOH_CONTENT_TYPE),
700+
"Content-Type: " DOH_CONTENT_TYPE);
701+
https_client_multi_init(c, header_list);
675702
}
676703

677704
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,
687714
}
688715

689716
void https_client_reset(https_client_t *c) {
690-
options_t *opt = c->opt;
691-
stat_t *stat = c->stat;
692-
struct ev_loop *loop = c->loop;
717+
struct curl_slist *header_list = c->header_list;
718+
c->header_list = NULL;
693719
https_client_cleanup(c);
694-
https_client_init(c, opt, stat, loop);
720+
https_client_multi_init(c, header_list);
695721
}
696722

697723
void https_client_cleanup(https_client_t *c) {
@@ -700,4 +726,5 @@ void https_client_cleanup(https_client_t *c) {
700726
}
701727
curl_slist_free_all(c->header_list);
702728
curl_multi_cleanup(c->curlm);
729+
ev_timer_stop(c->loop, &c->reset_timer);
703730
}

src/https_client.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ typedef struct {
4343

4444
options_t *opt;
4545
stat_t *stat;
46+
47+
ev_timer reset_timer;
4648
} https_client_t;
4749

4850
void https_client_init(https_client_t *c, options_t *opt,

src/options.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ void options_init(struct Options *opt) {
4141
opt->curl_proxy = NULL;
4242
opt->use_http_version = DEFAULT_HTTP_VERSION;
4343
opt->max_idle_time = 118;
44+
opt->conn_loss_time = 15;
4445
opt->stats_interval = 0;
4546
opt->ca_info = NULL;
4647
opt->flight_recorder_size = 0;
4748
}
4849

4950
enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char **argv) {
5051
int c = 0;
51-
while ((c = getopt(argc, argv, "a:c:p:T:du:g:b:i:4r:e:t:l:vxqm:s:C:F:hV")) != -1) {
52+
while ((c = getopt(argc, argv, "a:c:p:T:du:g:b:i:4r:e:t:l:vxqm:L:s:C:F:hV")) != -1) {
5253
switch (c) {
5354
case 'a': // listen_addr
5455
opt->listen_addr = optarg;
@@ -107,6 +108,9 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char *
107108
case 'm':
108109
opt->max_idle_time = atoi(optarg);
109110
break;
111+
case 'L':
112+
opt->conn_loss_time = atoi(optarg);
113+
break;
110114
case 's': // stats interval
111115
opt->stats_interval = atoi(optarg);
112116
break;
@@ -179,6 +183,11 @@ enum OptionsParseResult options_parse_args(struct Options *opt, int argc, char *
179183
printf("Maximum idle time must be between 0 and 3600.\n");
180184
return OPR_OPTION_ERROR;
181185
}
186+
if (opt->conn_loss_time < 5 ||
187+
opt->conn_loss_time > 60) {
188+
printf("Connection loss time must be between 5 and 60.\n");
189+
return OPR_OPTION_ERROR;
190+
}
182191
if (opt->stats_interval < 0 || opt->stats_interval > 3600) {
183192
printf("Statistic interval must be between 0 and 3600.\n");
184193
return OPR_OPTION_ERROR;
@@ -238,6 +247,10 @@ void options_show_usage(int __attribute__((unused)) argc, char **argv) {
238247
printf(" -m max_idle_time Maximum idle time in seconds allowed for reusing a HTTPS connection.\n"\
239248
" (Default: %d, Min: 0, Max: 3600)\n",
240249
defaults.max_idle_time);
250+
printf(" -L conn_loss_time Time in seconds to tolerate connection timeouts of reused connections.\n"\
251+
" This option mitigates half-open TCP connection issue (e.g. WAN IP change).\n"\
252+
" (Default: %d, Min: 5, Max: 60)\n",
253+
defaults.conn_loss_time);
241254
printf(" -C ca_path Optional file containing CA certificates.\n");
242255
printf(" -c dscp_codepoint Optional DSCP codepoint to set on upstream HTTPS server\n");
243256
printf(" connections. (Min: 0, Max: 63)\n");

src/options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ struct Options {
5050

5151
int max_idle_time;
5252

53+
int conn_loss_time;
54+
5355
// Print statistic interval
5456
int stats_interval;
5557

0 commit comments

Comments
 (0)