Skip to content

Commit f490f13

Browse files
committed
QUICk HTTP/3 support
1 parent ed57d7d commit f490f13

File tree

4 files changed

+91
-36
lines changed

4 files changed

+91
-36
lines changed

src/https_client.c

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,60 @@ int https_curl_debug(CURL __attribute__((unused)) * handle, curl_infotype type,
212212
return 0;
213213
}
214214

215+
static const char * http_version_str(const long version) {
216+
switch (version) {
217+
case CURL_HTTP_VERSION_1_0:
218+
return "1.0";
219+
case CURL_HTTP_VERSION_1_1:
220+
return "1.1";
221+
case CURL_HTTP_VERSION_2_0: // fallthrough
222+
case CURL_HTTP_VERSION_2TLS:
223+
return "2";
224+
#ifdef CURL_VERSION_HTTP3
225+
case CURL_HTTP_VERSION_3:
226+
return "3";
227+
#endif
228+
default:
229+
FLOG("Unsupported HTTP version: %d", version);
230+
}
231+
return "UNKNOWN"; // unreachable code
232+
}
233+
234+
static void https_set_request_version(https_client_t *client,
235+
struct https_fetch_ctx *ctx) {
236+
long http_version_int = CURL_HTTP_VERSION_2TLS;
237+
switch (client->opt->use_http_version) {
238+
case 1:
239+
http_version_int = CURL_HTTP_VERSION_1_1;
240+
// fallthrough
241+
case 2:
242+
break;
243+
case 3:
244+
#ifdef CURL_VERSION_HTTP3
245+
http_version_int = CURL_HTTP_VERSION_3;
246+
#endif
247+
break;
248+
default:
249+
FLOG_REQ("Invalid HTTP version: %d", client->opt->use_http_version);
250+
}
251+
DLOG_REQ("Requesting HTTP/%s", http_version_str(http_version_int));
252+
253+
CURLcode easy_code = curl_easy_setopt(ctx->curl, CURLOPT_HTTP_VERSION, http_version_int);
254+
if (easy_code != CURLE_OK) {
255+
ELOG_REQ("Setting HTTP/%s version failed with %d: %s",
256+
http_version_str(http_version_int), easy_code, curl_easy_strerror(easy_code));
257+
258+
if (client->opt->use_http_version == 3) {
259+
ELOG("Try to run application without -q argument!"); // fallback unknown for current request
260+
client->opt->use_http_version = 2;
261+
} else if (client->opt->use_http_version == 2) {
262+
ELOG("Try to run application with -x argument! Falling back to HTTP/1.1 version.");
263+
client->opt->use_http_version = 1;
264+
// TODO: consider CURLMOPT_PIPELINING setting??
265+
}
266+
}
267+
}
268+
215269
static void https_fetch_ctx_init(https_client_t *client,
216270
struct https_fetch_ctx *ctx, const char *url,
217271
const char* data, size_t datalen,
@@ -228,20 +282,7 @@ static void https_fetch_ctx_init(https_client_t *client,
228282

229283
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_RESOLVE, resolv);
230284

231-
DLOG_REQ("Requesting HTTP/1.1: %d", client->opt->use_http_1_1);
232-
CURLcode easy_code = curl_easy_setopt(ctx->curl, CURLOPT_HTTP_VERSION,
233-
client->opt->use_http_1_1 ?
234-
CURL_HTTP_VERSION_1_1 :
235-
CURL_HTTP_VERSION_2_0);
236-
if (easy_code != CURLE_OK) {
237-
// hopefully errors will be logged once
238-
ELOG_REQ("CURLOPT_HTTP_VERSION error %d: %s",
239-
easy_code, curl_easy_strerror(easy_code));
240-
if (!client->opt->use_http_1_1) {
241-
ELOG("Try to run application with -x argument! Forcing HTTP/1.1 version.");
242-
client->opt->use_http_1_1 = 1;
243-
}
244-
}
285+
https_set_request_version(client, ctx);
245286

246287
if (logging_debug_enabled()) {
247288
ASSERT_CURL_EASY_SETOPT(ctx, CURLOPT_VERBOSE, 1L);
@@ -322,9 +363,10 @@ static int https_fetch_ctx_process_response(https_client_t *client,
322363
uploaded_bytes > 0) {
323364
WLOG_REQ("Connecting and sending request to resolver was successful, "
324365
"but no response was sent back");
325-
if (client->opt->use_http_1_1) {
366+
if (client->opt->use_http_version == 1) {
326367
// for example Unbound DoH servers does not support HTTP/1.x, only HTTP/2
327-
WLOG("Resolver may not support current HTTP/1.1 protocol version");
368+
WLOG("Resolver may not support current HTTP/%s protocol version",
369+
http_version_str(client->opt->use_http_version));
328370
}
329371
} else {
330372
// in case of HTTP/1.1 this can happen very often depending on DNS query frequency
@@ -404,20 +446,8 @@ static int https_fetch_ctx_process_response(https_client_t *client,
404446
if ((res = curl_easy_getinfo(
405447
ctx->curl, CURLINFO_HTTP_VERSION, &long_resp)) != CURLE_OK) {
406448
ELOG_REQ("CURLINFO_HTTP_VERSION: %s", curl_easy_strerror(res));
407-
} else {
408-
switch (long_resp) {
409-
case CURL_HTTP_VERSION_1_0:
410-
DLOG_REQ("CURLINFO_HTTP_VERSION: 1.0");
411-
break;
412-
case CURL_HTTP_VERSION_1_1:
413-
DLOG_REQ("CURLINFO_HTTP_VERSION: 1.1");
414-
break;
415-
case CURL_HTTP_VERSION_2_0:
416-
DLOG_REQ("CURLINFO_HTTP_VERSION: 2");
417-
break;
418-
default:
419-
DLOG_REQ("CURLINFO_HTTP_VERSION: %d", long_resp);
420-
}
449+
} else if (long_resp != CURL_HTTP_VERSION_NONE) {
450+
DLOG_REQ("CURLINFO_HTTP_VERSION: %s", http_version_str(long_resp));
421451
}
422452
if ((res = curl_easy_getinfo(
423453
ctx->curl, CURLINFO_PROTOCOL, &long_resp)) != CURLE_OK) {

src/main.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,19 @@ int main(int argc, char *argv[]) {
226226
// through to errors about use of uninitialized values in our code. :(
227227
curl_global_init(CURL_GLOBAL_DEFAULT);
228228

229+
curl_version_info_data *curl_ver = curl_version_info(CURLVERSION_NOW);
230+
if (!(curl_ver->features & CURL_VERSION_HTTP2)) {
231+
WLOG("HTTP/2 is not supported by current libcurl");
232+
}
233+
#ifdef CURL_VERSION_HTTP3
234+
if (!(curl_ver->features & CURL_VERSION_HTTP3))
235+
{
236+
WLOG("HTTP/3 is not supported by current libcurl");
237+
}
238+
#else
239+
WLOG("HTTP/3 was not available at build time, it will not work at all");
240+
#endif
241+
229242
// Note: This calls ev_default_loop(0) which never cleans up.
230243
// valgrind will report a leak. :(
231244
struct ev_loop *loop = EV_DEFAULT;

src/options.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#define O_CLOEXEC 0
1616
#endif
1717

18+
#define DEFAULT_HTTP_VERSION 2
19+
1820
void options_init(struct Options *opt) {
1921
opt->listen_addr = "127.0.0.1";
2022
opt->listen_port = 5053;
@@ -33,13 +35,13 @@ void options_init(struct Options *opt) {
3335
opt->ipv4 = 0;
3436
opt->resolver_url = "https://dns.google/dns-query";
3537
opt->curl_proxy = NULL;
36-
opt->use_http_1_1 = 0;
38+
opt->use_http_version = DEFAULT_HTTP_VERSION;
3739
opt->stats_interval = 0;
3840
}
3941

4042
int options_parse_args(struct Options *opt, int argc, char **argv) {
4143
int c = 0;
42-
while ((c = getopt(argc, argv, "a:c:p:du:g:b:i:4r:e:t:l:vxs:hV")) != -1) {
44+
while ((c = getopt(argc, argv, "a:c:p:du:g:b:i:4r:e:t:l:vxqs:hV")) != -1) {
4345
switch (c) {
4446
case 'a': // listen_addr
4547
opt->listen_addr = optarg;
@@ -82,8 +84,15 @@ int options_parse_args(struct Options *opt, int argc, char **argv) {
8284
opt->loglevel--;
8385
}
8486
break;
85-
case 'x': // http/1.1
86-
opt->use_http_1_1 = 1;
87+
case 'x': // http/1.1 fallthrough
88+
case 'q': // http/3
89+
if (opt->use_http_version == DEFAULT_HTTP_VERSION) {
90+
opt->use_http_version = (c == 'x' ? 1 : 3);
91+
} else {
92+
printf("HTTP version already set to: HTTP/%s\n",
93+
opt->use_http_version == 1 ? "1.1" : "3");
94+
return -1;
95+
}
8796
break;
8897
case 's': // stats interval
8998
opt->stats_interval = atoi(optarg);
@@ -193,6 +202,7 @@ void options_show_usage(int __attribute__((unused)) argc, char **argv) {
193202
printf(" connections.\n");
194203
printf(" -x Use HTTP/1.1 instead of HTTP/2. Useful with broken\n"
195204
" or limited builds of libcurl. (false)\n");
205+
printf(" -q Use HTTP/3 (QUIC) only. (false)\n");
196206
printf(" -s statistic_interval Optional statistic printout interval.\n"\
197207
" (Default: %d, Disabled: 0, Min: 1, Max: 3600)\n",
198208
defaults.stats_interval);

src/options.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ struct Options {
4141
// e.g. "socks5://127.0.0.1:1080"
4242
const char *curl_proxy;
4343

44-
// Hack to fix OpenWRT issues due to dropping of HTTP/2 support from libcurl.
45-
int use_http_1_1;
44+
// 1 = Use only HTTP/1.1 for limited OpenWRT libcurl (which is not built with HTTP/2 support)
45+
// 2 = Use only HTTP/2 default
46+
// 3 = Use only HTTP/3 QUIC
47+
int use_http_version;
4648

4749
// Print statistic interval
4850
int stats_interval;

0 commit comments

Comments
 (0)