1 /*
2 * Copyright 2019-2021 OARC, Inc.
3 * Copyright 2017-2018 Akamai Technologies
4 * Copyright 2006-2016 Nominum, Inc.
5 * All rights reserved.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19 /*
20 * Based on HTTP/2 module in DNS shotgun by Tomáš Křížek (CZ.NIC)
21 * https://gitlab.nic.cz/knot/shotgun/-/blob/master/replay/dnssim/src/output/dnssim/https2.c
22 *
23 * Initial PoC implementation by Atanas Argirov (PeeriX)
24 * https://github.com/m0rcq
25 */
26
27 #include "config.h"
28
29 #include "net.h"
30 #include "edns.h"
31 #include "parse_uri.h"
32 #include "log.h"
33 #include "strerror.h"
34 #include "util.h"
35 #include "os.h"
36 #include "opt.h"
37
38 #include <errno.h>
39 #include <assert.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <openssl/err.h>
43 #include <sys/socket.h>
44 #include <netinet/in.h>
45 #include <ck_pr.h>
46 #include <nghttp2/nghttp2.h>
47 #include <openssl/bio.h>
48 #include <openssl/evp.h>
49
50 #define DNS_GET_REQUEST_VAR "?dns="
51 #define DNS_MSG_MAX_SIZE 65535
52
53 static SSL_CTX* ssl_ctx = 0;
54 static struct URI doh_uri;
55 enum perf_doh_method {
56 doh_method_get,
57 doh_method_post
58 };
59 static enum perf_doh_method doh_method = doh_method_get;
60 static size_t doh_max_concurr = 100;
61
62 #define self ((struct perf__doh_socket*)sock)
63
64 #define MAKE_NV(NAME, VALUE) \
65 { \
66 (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
67 NGHTTP2_NV_FLAG_NONE \
68 }
69
70 #define MAKE_NV_LEN(NAME, VALUE, VALUELEN) \
71 { \
72 (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \
73 NGHTTP2_NV_FLAG_NONE \
74 }
75
76 typedef struct {
77 uint8_t *buf, *bufp;
78 size_t len, buf_len;
79 } http2_data_provider_t;
80
81 typedef struct {
82 nghttp2_session* session;
83 http2_data_provider_t payload;
84 bool settings_sent;
85 char dnsmsg[DNS_MSG_MAX_SIZE];
86 size_t dnsmsg_at;
87 bool dnsmsg_completed;
88 } http2_session_t;
89
90 struct _doh_stats {
91 size_t code[500];
92 size_t unknown_code;
93 };
94
95 struct perf__doh_socket {
96 struct perf_net_socket base;
97
98 pthread_mutex_t lock;
99 SSL* ssl;
100
101 bool is_ready, is_conn_ready, is_ssl_ready, is_sending, is_post_sending;
102 // bool have_more; TODO
103 bool do_reconnect;
104
105 perf_sockaddr_t server, local;
106 size_t bufsize;
107
108 uint16_t qid;
109
110 uint64_t conn_ts;
111 perf_socket_event_t conn_event, conning_event;
112
113 http2_session_t http2; // http2 session data
114 int http2_code;
115 bool http2_is_dns;
116
117 struct _doh_stats stats;
118 };
119
120 static pthread_mutex_t _nghttp2_lock = PTHREAD_MUTEX_INITIALIZER;
121 static nghttp2_session_callbacks* _nghttp2_callbacks = 0;
122 static nghttp2_option* _nghttp2_option = 0;
123
perf_net_doh_parse_uri(const char * uri)124 void perf_net_doh_parse_uri(const char* uri)
125 {
126 if (parse_uri(&doh_uri, uri)) {
127 perf_log_warning("invalid DNS-over-HTTPS URI");
128 perf_opt_usage();
129 exit(1);
130 }
131 }
132
perf_net_doh_parse_method(const char * method)133 void perf_net_doh_parse_method(const char* method)
134 {
135 if (!strcmp(method, "GET")) {
136 doh_method = doh_method_get;
137 return;
138 } else if (!strcmp(method, "POST")) {
139 doh_method = doh_method_post;
140 return;
141 }
142
143 perf_log_warning("invalid DNS-over-HTTPS method");
144 perf_opt_usage();
145 exit(1);
146 }
147
perf_net_doh_set_max_concurrent_streams(size_t max_concurr)148 void perf_net_doh_set_max_concurrent_streams(size_t max_concurr)
149 {
150 doh_max_concurr = max_concurr;
151 }
152
perf__doh_connect(struct perf_net_socket * sock)153 static void perf__doh_connect(struct perf_net_socket* sock)
154 {
155 int ret;
156
157 nghttp2_session_del(self->http2.session);
158 ret = nghttp2_session_client_new2(&self->http2.session, _nghttp2_callbacks, sock, _nghttp2_option);
159 if (ret < 0) {
160 perf_log_fatal("Failed to initialize http2 session: %s", nghttp2_strerror(ret));
161 }
162
163 int fd = socket(self->server.sa.sa.sa_family, SOCK_STREAM, 0);
164 if (fd == -1) {
165 char __s[256];
166 perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
167 }
168 ck_pr_store_int(&sock->fd, fd);
169
170 if (self->ssl) {
171 SSL_free(self->ssl);
172 }
173 if (!(self->ssl = SSL_new(ssl_ctx))) {
174 perf_log_fatal("SSL_new(): %s", ERR_error_string(ERR_get_error(), 0));
175 }
176 if (!(ret = SSL_set_fd(self->ssl, sock->fd))) {
177 perf_log_fatal("SSL_set_fd(): %s", ERR_error_string(SSL_get_error(self->ssl, ret), 0));
178 }
179
180 if (self->server.sa.sa.sa_family == AF_INET6) {
181 int on = 1;
182
183 if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
184 perf_log_warning("setsockopt(IPV6_V6ONLY) failed");
185 }
186 }
187
188 if (bind(sock->fd, &self->local.sa.sa, self->local.length) == -1) {
189 char __s[256];
190 perf_log_fatal("bind: %s", perf_strerror_r(errno, __s, sizeof(__s)));
191 }
192
193 if (self->bufsize > 0) {
194 ret = setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF,
195 &self->bufsize, sizeof(self->bufsize));
196 if (ret < 0)
197 perf_log_warning("setsockbuf(SO_RCVBUF) failed");
198
199 ret = setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF,
200 &self->bufsize, sizeof(self->bufsize));
201 if (ret < 0)
202 perf_log_warning("setsockbuf(SO_SNDBUF) failed");
203 }
204
205 int flags = fcntl(sock->fd, F_GETFL, 0);
206 if (flags < 0)
207 perf_log_fatal("fcntl(F_GETFL)");
208 ret = fcntl(sock->fd, F_SETFL, flags | O_NONBLOCK);
209 if (ret < 0)
210 perf_log_fatal("fcntl(F_SETFL)");
211
212 self->conn_ts = perf_get_time();
213 if (sock->event) {
214 sock->event(sock, self->conning_event, self->conn_ts);
215 self->conning_event = perf_socket_event_reconnecting;
216 }
217 if (connect(sock->fd, &self->server.sa.sa, self->server.length)) {
218 if (errno == EINPROGRESS) {
219 return;
220 } else {
221 char __s[256];
222 perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s)));
223 }
224 }
225
226 self->is_conn_ready = true;
227 }
228
perf__doh_reconnect(struct perf_net_socket * sock)229 static void perf__doh_reconnect(struct perf_net_socket* sock)
230 {
231 close(sock->fd);
232 // self->have_more = false; TODO
233
234 self->http2.settings_sent = false;
235 self->is_ready = false;
236 self->is_conn_ready = false;
237 self->is_ssl_ready = false;
238 self->is_sending = false;
239 self->is_post_sending = false;
240
241 self->http2.dnsmsg_at = 0;
242 self->http2.dnsmsg_completed = false;
243 self->http2_code = 0;
244 self->http2_is_dns = false;
245
246 perf__doh_connect(sock);
247 }
248
249 // static ssize_t _recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *sock)
250 // {
251 // if (self->http2.dnsmsg_completed) {
252 // return NGHTTP2_ERR_WOULDBLOCK;
253 // }
254 //
255 // ssize_t n = SSL_read(self->ssl, buf, length);
256 // if (!n) {
257 // perf__doh_reconnect(sock);
258 // return NGHTTP2_ERR_WOULDBLOCK;
259 // }
260 // if (n < 0) {
261 // int err = SSL_get_error(self->ssl, n);
262 // switch (err) {
263 // case SSL_ERROR_WANT_READ:
264 // return NGHTTP2_ERR_WOULDBLOCK;
265 // case SSL_ERROR_SYSCALL:
266 // switch (errno) {
267 // case ECONNREFUSED:
268 // case ECONNRESET:
269 // case ENOTCONN:
270 // perf__doh_reconnect(sock);
271 // return NGHTTP2_ERR_WOULDBLOCK;
272 // default:
273 // break;
274 // }
275 // break;
276 // default:
277 // break;
278 // }
279 // return NGHTTP2_ERR_CALLBACK_FAILURE;
280 // }
281 // return n;
282 // }
283
perf__doh_recv(struct perf_net_socket * sock,void * buf,size_t len,int flags)284 static ssize_t perf__doh_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
285 {
286 // read TLS data here instead of nghttp2_recv_callback
287 PERF_LOCK(&self->lock);
288 if (!self->is_ready) {
289 PERF_UNLOCK(&self->lock);
290 errno = EAGAIN;
291 return -1;
292 }
293
294 uint8_t recvbuf[TCP_RECV_BUF_SIZE];
295 ssize_t n = SSL_read(self->ssl, recvbuf, TCP_RECV_BUF_SIZE);
296 if (!n) {
297 perf__doh_reconnect(sock);
298 PERF_UNLOCK(&self->lock);
299 errno = EAGAIN;
300 return -1;
301 }
302 if (n < 0) {
303 int err = SSL_get_error(self->ssl, n);
304 switch (err) {
305 case SSL_ERROR_WANT_READ:
306 errno = EAGAIN;
307 break;
308 case SSL_ERROR_SYSCALL:
309 switch (errno) {
310 case ECONNREFUSED:
311 case ECONNRESET:
312 case ENOTCONN:
313 perf__doh_reconnect(sock);
314 errno = EAGAIN;
315 break;
316 default:
317 break;
318 }
319 break;
320 default:
321 errno = EBADF;
322 break;
323 }
324 PERF_UNLOCK(&self->lock);
325 return -1;
326 }
327
328 // this will be processed by nghttp2 callbacks
329 int ret = nghttp2_session_mem_recv(self->http2.session, recvbuf, n);
330 if (ret < 0) {
331 perf_log_warning("nghttp2_session_mem_recv failed: %s", nghttp2_strerror(ret));
332 PERF_UNLOCK(&self->lock);
333 return -1;
334 }
335 // TODO: handle partial mem_recv
336 if (ret != n) {
337 perf_log_fatal("perf__doh_recv() mem_recv did not take all");
338 }
339
340 // TODO: is this faster then mem_recv?
341 // int ret = nghttp2_session_recv(self->http2.session);
342 // if (ret < 0) {
343 // perf_log_warning("nghttp2_session_recv failed: %s", nghttp2_strerror(ret));
344 // PERF_UNLOCK(&self->lock);
345 // return -1;
346 // }
347
348 if (self->http2.dnsmsg_completed) {
349 if (self->http2_code > 99 && self->http2_code < 600) {
350 self->stats.code[self->http2_code - 100]++;
351 } else {
352 self->stats.unknown_code++;
353 }
354 if (!self->http2_is_dns) {
355 // TODO: store non-dns for stats
356 self->http2.dnsmsg_completed = false;
357 self->http2.dnsmsg_at = 0;
358 self->http2_code = 0;
359 self->http2_is_dns = false;
360 PERF_UNLOCK(&self->lock);
361 errno = EAGAIN;
362 return -1;
363 }
364 if (self->http2_code < 200 || self->http2_code > 299) {
365 // TODO: store return code for stats
366 self->http2.dnsmsg_completed = false;
367 self->http2.dnsmsg_at = 0;
368 self->http2_code = 0;
369 PERF_UNLOCK(&self->lock);
370 errno = EAGAIN;
371 return -1;
372 }
373 if (self->http2.dnsmsg_at < len) {
374 len = self->http2.dnsmsg_at;
375 }
376 memcpy(buf, self->http2.dnsmsg, len);
377 self->http2.dnsmsg_completed = false;
378 self->http2.dnsmsg_at = 0;
379
380 // self->have_more = false; TODO
381 PERF_UNLOCK(&self->lock);
382 return len;
383 }
384
385 // self->have_more = true; TODO
386 PERF_UNLOCK(&self->lock);
387 errno = EAGAIN;
388 return -1;
389 }
390
_submit_dns_query_get(struct perf_net_socket * sock,const void * buf,size_t len)391 static void _submit_dns_query_get(struct perf_net_socket* sock, const void* buf, size_t len)
392 {
393 const size_t path_len = doh_uri.pathlen
394 + sizeof(DNS_GET_REQUEST_VAR) - 1
395 + (4 * ((len + 2) / 3)) + 1;
396 char full_path[path_len];
397 char* p = &full_path[0];
398
399 memcpy(p, doh_uri.path, doh_uri.pathlen);
400 p += doh_uri.pathlen;
401
402 memcpy(p, DNS_GET_REQUEST_VAR, sizeof(DNS_GET_REQUEST_VAR) - 1);
403 p += sizeof(DNS_GET_REQUEST_VAR) - 1;
404
405 EVP_EncodeBlock((unsigned char*)p, buf, len);
406 // RFC8484 requires base64url (RFC4648)
407 // and Padding characters (=) for base64url MUST NOT be included.
408 // base64url alphabet is the same as base64 except + is - and / is _
409 while (*p) {
410 switch (*p) {
411 case '+':
412 *p++ = '-';
413 break;
414 case '/':
415 *p++ = '_';
416 break;
417 case '=':
418 *p = 0;
419 break;
420 default:
421 p++;
422 }
423 }
424
425 const nghttp2_nv hdrs[] = {
426 MAKE_NV(":method", "GET"),
427 MAKE_NV(":scheme", "https"),
428 MAKE_NV_LEN(":authority", doh_uri.hostport, doh_uri.hostportlen),
429 MAKE_NV_LEN(":path", full_path, p - full_path),
430 MAKE_NV("accept", "application/dns-message"),
431 MAKE_NV("user-agent", "dnsperf/" PACKAGE_VERSION " (nghttp2/" NGHTTP2_VERSION ")")
432 };
433
434 int32_t stream_id = nghttp2_submit_request(self->http2.session,
435 NULL,
436 hdrs,
437 sizeof(hdrs) / sizeof(hdrs[0]),
438 NULL,
439 sock);
440 if (stream_id < 0) {
441 perf_log_fatal("Failed to submit HTTP2 request: %s", nghttp2_strerror(stream_id));
442 }
443 }
444
_payload_read_cb(nghttp2_session * session,int32_t stream_id,uint8_t * buf,size_t length,uint32_t * data_flags,nghttp2_data_source * source,void * sock)445 static ssize_t _payload_read_cb(nghttp2_session* session,
446 int32_t stream_id, uint8_t* buf,
447 size_t length, uint32_t* data_flags,
448 nghttp2_data_source* source,
449 void* sock)
450 {
451 http2_data_provider_t* payload = source->ptr;
452
453 ssize_t payload_size = length < payload->len ? length : payload->len;
454
455 memcpy(buf, payload->bufp, payload_size);
456 payload->bufp += payload_size;
457 payload->len -= payload_size;
458 // check for EOF
459 if (payload->len == 0) {
460 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
461 self->is_post_sending = false;
462 }
463
464 return payload_size;
465 }
466
_submit_dns_query_post(struct perf_net_socket * sock,const void * buf,size_t len)467 static void _submit_dns_query_post(struct perf_net_socket* sock, const void* buf, size_t len)
468 {
469 // POST requires DATA flow-controlled payload that local endpoint
470 // can send across without issuing WINDOW_UPDATE
471 // we need to check for this and bounce back the request if the
472 // payload > remote window size
473 // TODO: are below needed? can they be checked on connect?
474 // int remote_window_size = nghttp2_session_get_remote_window_size(self->http2.session);
475 // if (remote_window_size < 0) {
476 // perf_log_fatal("failed to get http2 session remote window size");
477 // }
478 // if (len > remote_window_size) {
479 // perf_log_fatal("remote window size is too small for POST payload");
480 // }
481
482 // compose content-length
483 char payload_size[20];
484 int payload_size_len = snprintf(payload_size, sizeof(payload_size), "%zu", len);
485 // TODO: check snprintf()
486
487 const nghttp2_nv hdrs[] = {
488 MAKE_NV(":method", "POST"),
489 MAKE_NV(":scheme", "https"),
490 MAKE_NV_LEN(":authority", doh_uri.hostport, doh_uri.hostportlen),
491 MAKE_NV_LEN(":path", doh_uri.path, doh_uri.pathlen),
492 MAKE_NV("accept", "application/dns-message"),
493 MAKE_NV("content-type", "application/dns-message"),
494 MAKE_NV_LEN("content-length", payload_size, payload_size_len),
495 MAKE_NV("user-agent", "dnsperf/" PACKAGE_VERSION " (nghttp2/" NGHTTP2_VERSION ")")
496 };
497
498 if (len > self->http2.payload.buf_len) {
499 self->http2.payload.buf_len = ((len / MAX_EDNS_PACKET) + 1) * MAX_EDNS_PACKET;
500 if (!(self->http2.payload.buf = realloc(self->http2.payload.buf, self->http2.payload.buf_len))) {
501 perf_log_fatal("perf_net_doh: out of memory");
502 }
503 }
504 if (self && self->http2.payload.buf && buf) { // fix clang scan-build
505 memcpy(self->http2.payload.buf, buf, len);
506 } else {
507 perf_log_fatal("_submit_dns_query_post(): payload.buf is null");
508 }
509 self->http2.payload.bufp = self->http2.payload.buf;
510 self->http2.payload.len = len;
511 self->is_post_sending = true;
512
513 // we need data provider to pass to submit()
514
515 nghttp2_data_provider data_provider = {
516 .source.ptr = &self->http2.payload,
517 .read_callback = _payload_read_cb
518 };
519 int32_t stream_id = nghttp2_submit_request(self->http2.session,
520 NULL,
521 hdrs,
522 sizeof(hdrs) / sizeof(hdrs[0]),
523 &data_provider,
524 sock);
525 if (stream_id < 0) {
526 perf_log_fatal("Failed to submit HTTP2 request: %s", nghttp2_strerror(stream_id));
527 }
528 }
529
perf__doh_sendto(struct perf_net_socket * sock,uint16_t qid,const void * buf,size_t len,int flags,const struct sockaddr * dest_addr,socklen_t addrlen)530 static ssize_t perf__doh_sendto(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen)
531 {
532 PERF_LOCK(&self->lock);
533
534 if (!self->is_ready) {
535 // TODO: query will be lost here
536 PERF_UNLOCK(&self->lock);
537 errno = EINPROGRESS;
538 return -1;
539 }
540
541 if (self->is_sending) {
542 perf_log_fatal("called when sending");
543 }
544
545 self->qid = qid;
546
547 switch (doh_method) {
548 case doh_method_get:
549 _submit_dns_query_get(sock, buf, len);
550 break;
551 case doh_method_post:
552 _submit_dns_query_post(sock, buf, len);
553 break;
554 }
555
556 int ret = nghttp2_session_send(self->http2.session);
557 if (ret < 0) {
558 // TODO: handle error better, reconnect when needed
559 perf_log_warning("nghttp2_session_send failed: %s", nghttp2_strerror(ret));
560 self->do_reconnect = true;
561 PERF_UNLOCK(&self->lock);
562 errno = EINPROGRESS;
563 return -1;
564 }
565
566 if (self->is_post_sending || nghttp2_session_get_outbound_queue_size(self->http2.session) > 0 || nghttp2_session_want_write(self->http2.session)) {
567 self->is_sending = true;
568 PERF_UNLOCK(&self->lock);
569 errno = EINPROGRESS;
570 return -1;
571 }
572 PERF_UNLOCK(&self->lock);
573
574 return len;
575 }
576
perf__doh_close(struct perf_net_socket * sock)577 static int perf__doh_close(struct perf_net_socket* sock)
578 {
579 // TODO
580 return close(sock->fd);
581 }
582
perf__doh_sockeq(struct perf_net_socket * sock_a,struct perf_net_socket * sock_b)583 static int perf__doh_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
584 {
585 return sock_a->fd == sock_b->fd;
586 }
587
perf__doh_sockready(struct perf_net_socket * sock,int pipe_fd,int64_t timeout)588 static int perf__doh_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
589 {
590 PERF_LOCK(&self->lock);
591
592 if (self->do_reconnect) {
593 perf__doh_reconnect(sock);
594 self->do_reconnect = false;
595 }
596
597 if (self->is_ready) {
598 // do nghttp2 I/O send to flush outstanding frames
599 int ret = nghttp2_session_send(self->http2.session);
600 if (ret != 0) {
601 // TODO: handle error better, reconnect when needed
602 perf_log_warning("nghttp2_session_send failed: %s", nghttp2_strerror(ret));
603 self->do_reconnect = true;
604 PERF_UNLOCK(&self->lock);
605 return 0;
606 }
607
608 bool sent = false;
609 if (self->is_sending) {
610 if (self->is_post_sending || nghttp2_session_get_outbound_queue_size(self->http2.session) > 0 || nghttp2_session_want_write(self->http2.session)) {
611 PERF_UNLOCK(&self->lock);
612 return 0;
613 }
614 self->is_sending = false;
615 sent = true;
616 }
617 PERF_UNLOCK(&self->lock);
618 if (sent && sock->sent) {
619 sock->sent(sock, self->qid);
620 }
621 return 1;
622 }
623
624 if (!self->is_conn_ready) {
625 switch (perf_os_waituntilanywritable(&sock, 1, pipe_fd, timeout)) {
626 case PERF_R_TIMEDOUT:
627 PERF_UNLOCK(&self->lock);
628 return -1;
629 case PERF_R_SUCCESS: {
630 int error = 0;
631 socklen_t len = (socklen_t)sizeof(error);
632
633 getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
634 if (error != 0) {
635 if (error == EINPROGRESS
636 #if EWOULDBLOCK != EAGAIN
637 || error == EWOULDBLOCK
638 #endif
639 || error == EAGAIN) {
640 PERF_UNLOCK(&self->lock);
641 return 0;
642 }
643 // unrecoverable error, reconnect
644 self->do_reconnect = true;
645 PERF_UNLOCK(&self->lock);
646 return 0;
647 }
648 break;
649 }
650 default:
651 PERF_UNLOCK(&self->lock);
652 return -1;
653 }
654 self->is_conn_ready = true;
655 }
656
657 if (!self->is_ssl_ready) {
658 int ret = SSL_connect(self->ssl);
659 if (!ret) {
660 // unrecoverable error, reconnect
661 self->do_reconnect = true;
662 PERF_UNLOCK(&self->lock);
663 return 0;
664 }
665 if (ret < 0) {
666 switch (SSL_get_error(self->ssl, ret)) {
667 case SSL_ERROR_WANT_READ:
668 case SSL_ERROR_WANT_WRITE:
669 break;
670 default:
671 // unrecoverable error, reconnect
672 self->do_reconnect = true;
673 }
674 PERF_UNLOCK(&self->lock);
675 return 0;
676 }
677
678 const uint8_t* alpn = 0;
679 uint32_t alpn_len = 0;
680 #ifndef OPENSSL_NO_NEXTPROTONEG
681 SSL_get0_next_proto_negotiated(self->ssl, &alpn, &alpn_len);
682 #endif /* !OPENSSL_NO_NEXTPROTONEG */
683 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
684 if (!alpn) {
685 SSL_get0_alpn_selected(self->ssl, &alpn, &alpn_len);
686 }
687 #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
688 #if defined(OPENSSL_NO_NEXTPROTONEG) && OPENSSL_VERSION_NUMBER < 0x10002000L
689 #error "OpenSSL has no support for getting alpn"
690 #endif
691 if (!alpn || alpn_len != 2 || memcmp("h2", alpn, 2) != 0) {
692 perf_log_warning("Unable to get ALPN or not \"h2\", reconnecting");
693 self->do_reconnect = true;
694 PERF_UNLOCK(&self->lock);
695 return 0;
696 }
697 self->is_ssl_ready = true;
698 }
699
700 if (!self->http2.settings_sent) {
701 // send settings
702 // TODO: does sending settings need session_send()?
703 nghttp2_settings_entry iv[] = {
704 { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, doh_max_concurr },
705 { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 },
706 { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535 }
707 };
708 int ret = nghttp2_submit_settings(self->http2.session, NGHTTP2_FLAG_NONE, iv,
709 sizeof(iv) / sizeof(*iv));
710 if (ret != 0) {
711 perf_log_warning("Could not submit https2 SETTINGS: %s", nghttp2_strerror(ret));
712 self->do_reconnect = true;
713 PERF_UNLOCK(&self->lock);
714 return 0;
715 }
716 self->http2.settings_sent = true;
717 }
718
719 self->is_ready = true;
720 PERF_UNLOCK(&self->lock);
721
722 if (sock->event) {
723 sock->event(sock, self->conn_event, perf_get_time() - self->conn_ts);
724 self->conn_event = perf_socket_event_reconnected;
725 }
726
727 return 1;
728 }
729
perf__doh_have_more(struct perf_net_socket * sock)730 static bool perf__doh_have_more(struct perf_net_socket* sock)
731 {
732 // return self->have_more; TODO
733 return false;
734 }
735
736 /* nghttp2 callbacks */
737
_http2_send_cb(nghttp2_session * session,const uint8_t * data,size_t length,int flags,void * sock)738 static ssize_t _http2_send_cb(nghttp2_session* session,
739 const uint8_t* data,
740 size_t length,
741 int flags,
742 void* sock)
743 {
744 // TODO: remove once non-experimental
745 if (!PERF_TRYLOCK(&self->lock)) {
746 perf_log_fatal("_http2_send_cb called without lock");
747 }
748
749 if (!self->is_ready) {
750 return NGHTTP2_ERR_CALLBACK_FAILURE;
751 }
752
753 ssize_t n = SSL_write(self->ssl, data, length);
754 if (n < 1) {
755 switch (SSL_get_error(self->ssl, n)) {
756 case SSL_ERROR_SYSCALL:
757 switch (errno) {
758 case ECONNREFUSED:
759 case ECONNRESET:
760 case ENOTCONN:
761 case EPIPE:
762 perf__doh_reconnect(sock);
763 return NGHTTP2_ERR_CALLBACK_FAILURE;
764 default:
765 break;
766 }
767 break;
768 case SSL_ERROR_WANT_READ:
769 case SSL_ERROR_WANT_WRITE:
770 return NGHTTP2_ERR_WOULDBLOCK;
771 default:
772 break;
773 }
774 perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(self->ssl, n), 0));
775 return NGHTTP2_ERR_CALLBACK_FAILURE;
776 }
777
778 return n;
779 }
780
_http2_frame_recv_cb(nghttp2_session * session,const nghttp2_frame * frame,void * sock)781 static int _http2_frame_recv_cb(nghttp2_session* session, const nghttp2_frame* frame, void* sock)
782 {
783 // TODO: remove once non-experimental
784 if (!PERF_TRYLOCK(&self->lock)) {
785 perf_log_fatal("_http2_frame_recv_cb called without lock");
786 }
787
788 switch (frame->hd.type) {
789 case NGHTTP2_DATA:
790 // we are interested in DATA frame which will carry the DNS response
791 // NGHTTP2_FLAG_END_STREAM indicates that we have the data in full
792 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
793 // TODO: what's the point of below code? if dnsmsg_at > max size then it will already done a buffer overflow
794 // if (self->http2.dnsmsg_at > DNS_MSG_MAX_SIZE) {
795 // perf_log_warning("DNS response > DNS message maximum size");
796 // return NGHTTP2_ERR_CALLBACK_FAILURE;
797 // }
798
799 // TODO: need to be able to receive multiple responses at the same time
800 if (self->http2.dnsmsg_completed) {
801 perf_log_fatal("_http2_frame_recv_cb: frame received when already having a dns msg");
802 }
803
804 self->http2.dnsmsg_completed = true;
805 // self->have_more = false; TODO
806 }
807 break;
808 case NGHTTP2_RST_STREAM:
809 case NGHTTP2_GOAWAY:
810 perf__doh_reconnect(sock);
811 break;
812 default:
813 break;
814 }
815
816 return 0;
817 }
818
_http2_on_header_callback(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * sock)819 static int _http2_on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* sock)
820 {
821 switch (frame->hd.type) {
822 case NGHTTP2_HEADERS: {
823 if (!value) {
824 return 0;
825 }
826 if (!strncasecmp(":status:", (const char*)name, namelen)) {
827 self->http2_code = atoi((const char*)value);
828 } else if (!strncasecmp("content-type:", (const char*)name, namelen)) {
829 if (!strncasecmp("application/dns-message", (const char*)value, valuelen)) {
830 self->http2_is_dns = true;
831 }
832 }
833 break;
834 }
835 default:
836 break;
837 }
838 return 0;
839 }
840
_http2_data_chunk_recv_cb(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * sock)841 static int _http2_data_chunk_recv_cb(nghttp2_session* session,
842 uint8_t flags,
843 int32_t stream_id,
844 const uint8_t* data,
845 size_t len, void* sock)
846 {
847 // TODO: remove once non-experimental
848 if (!PERF_TRYLOCK(&self->lock)) {
849 perf_log_fatal("_http2_data_chunk_recv_cb called without lock");
850 }
851
852 if (self->http2.dnsmsg_completed) {
853 perf_log_fatal("_http2_data_chunk_recv_cb: chunk received when already having a dns msg");
854 }
855
856 // TODO: point of nghttp2_session_get_stream_user_data() code?
857 // if (nghttp2_session_get_stream_user_data(session, stream_id)) {
858 if (self->http2.dnsmsg_at + len > DNS_MSG_MAX_SIZE) {
859 perf_log_warning("http2 chunk data exceeds DNS message max size");
860 return NGHTTP2_ERR_CALLBACK_FAILURE;
861 }
862 memcpy(self->http2.dnsmsg + self->http2.dnsmsg_at, data, len);
863 self->http2.dnsmsg_at += len;
864 // }
865
866 return 0;
867 }
868
869 #ifndef OPENSSL_NO_NEXTPROTONEG
870 /* NPN TLS extension check */
select_next_proto_cb(SSL * ssl,unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)871 static int select_next_proto_cb(SSL* ssl, unsigned char** out,
872 unsigned char* outlen, const unsigned char* in,
873 unsigned int inlen, void* arg)
874 {
875 if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
876 perf_log_warning("Server did not advertise %u", NGHTTP2_PROTO_VERSION_ID);
877 return SSL_TLSEXT_ERR_ALERT_WARNING;
878 }
879
880 return SSL_TLSEXT_ERR_OK;
881 }
882 #endif /* !OPENSSL_NO_NEXTPROTONEG */
883
perf_net_doh_opensocket(const perf_sockaddr_t * server,const perf_sockaddr_t * local,size_t bufsize,void * data,perf_net_sent_cb_t sent,perf_net_event_cb_t event)884 struct perf_net_socket* perf_net_doh_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event)
885 {
886 struct perf__doh_socket* tmp = calloc(1, sizeof(struct perf__doh_socket)); // clang scan-build
887 struct perf_net_socket* sock = (struct perf_net_socket*)tmp;
888
889 if (!sock) {
890 perf_log_fatal("perf_net_doh_opensocket(): out of memory");
891 return 0; // needed for clang scan build
892 }
893
894 sock->recv = perf__doh_recv;
895 sock->sendto = perf__doh_sendto;
896 sock->close = perf__doh_close;
897 sock->sockeq = perf__doh_sockeq;
898 sock->sockready = perf__doh_sockready;
899 sock->have_more = perf__doh_have_more;
900
901 sock->data = data;
902 sock->sent = sent;
903 sock->event = event;
904
905 self->server = *server;
906 self->local = *local;
907 self->bufsize = bufsize;
908 if (self->bufsize > 0) {
909 self->bufsize *= 1024;
910 }
911 self->conning_event = perf_socket_event_connecting;
912 self->conn_event = perf_socket_event_connected;
913 PERF_MUTEX_INIT(&self->lock);
914
915 if (!(self->http2.payload.buf = malloc(MAX_EDNS_PACKET))) {
916 perf_log_fatal("perf_net_doh_opensocket(): out of memory");
917 }
918 self->http2.payload.buf_len = MAX_EDNS_PACKET;
919
920 if (!ssl_ctx) {
921 #ifdef HAVE_TLS_METHOD
922 if (!(ssl_ctx = SSL_CTX_new(TLS_method()))) {
923 perf_log_fatal("SSL_CTX_new(): %s", ERR_error_string(ERR_get_error(), 0));
924 }
925 if (!SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION)) {
926 perf_log_fatal("SSL_CTX_set_min_proto_version(TLS1_2_VERSION): %s", ERR_error_string(ERR_get_error(), 0));
927 }
928 #else
929 if (!(ssl_ctx = SSL_CTX_new(SSLv23_client_method()))) {
930 perf_log_fatal("SSL_CTX_new(): %s", ERR_error_string(ERR_get_error(), 0));
931 }
932 #endif
933 SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
934 #ifndef OPENSSL_NO_NEXTPROTONEG
935 SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
936 #endif /* !OPENSSL_NO_NEXTPROTONEG */
937 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
938 SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char*)"\x02h2", 3);
939 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
940 }
941
942 /* setup HTTP/2 callbacks */
943 if (!_nghttp2_callbacks || !_nghttp2_option) {
944 PERF_LOCK(&_nghttp2_lock);
945 if (!_nghttp2_callbacks) {
946 if (nghttp2_session_callbacks_new(&_nghttp2_callbacks)) {
947 perf_log_fatal("Unable to create nghttp2 callbacks: out of memory");
948 }
949 nghttp2_session_callbacks_set_send_callback(_nghttp2_callbacks, _http2_send_cb);
950 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(_nghttp2_callbacks, _http2_data_chunk_recv_cb);
951 nghttp2_session_callbacks_set_on_frame_recv_callback(_nghttp2_callbacks, _http2_frame_recv_cb);
952 nghttp2_session_callbacks_set_on_header_callback(_nghttp2_callbacks, _http2_on_header_callback);
953
954 // nghttp2_session_callbacks_set_recv_callback(_nghttp2_callbacks, _recv_callback);
955 }
956
957 /* setup HTTP/2 options */
958 if (!_nghttp2_option) {
959 if (nghttp2_option_new(&_nghttp2_option)) {
960 perf_log_fatal("Unable to create nghttp2 options: out of memory");
961 }
962 nghttp2_option_set_peer_max_concurrent_streams(_nghttp2_option, doh_max_concurr);
963 }
964 PERF_UNLOCK(&_nghttp2_lock);
965 }
966
967 perf__doh_connect(sock);
968
969 return sock;
970 }
971
972 static struct _doh_stats doh_stats;
973
perf_net_doh_stats_init()974 void perf_net_doh_stats_init()
975 {
976 memset(&doh_stats, 0, sizeof(doh_stats));
977 }
978
perf_net_doh_stats_compile(struct perf_net_socket * sock)979 void perf_net_doh_stats_compile(struct perf_net_socket* sock)
980 {
981 int i;
982 for (i = 0; i < (sizeof(doh_stats.code) / sizeof(doh_stats.code[0])); i++) {
983 doh_stats.code[i] += self->stats.code[i];
984 }
985 doh_stats.unknown_code += self->stats.unknown_code;
986 }
987
perf_net_doh_stats_print()988 void perf_net_doh_stats_print()
989 {
990 printf("DNS-over-HTTPS statistics:\n\n");
991
992 printf(" HTTP/2 return codes: ");
993 bool first_code = true, no_codes = true;
994 int i;
995 for (i = 0; i < (sizeof(doh_stats.code) / sizeof(doh_stats.code[0])); i++) {
996 if (doh_stats.code[i]) {
997 if (first_code)
998 first_code = false;
999 else
1000 printf(", ");
1001 printf("%d: %zu", i + 100, doh_stats.code[i]);
1002 no_codes = false;
1003 }
1004 }
1005 if (doh_stats.unknown_code) {
1006 if (!first_code)
1007 printf(", ");
1008 printf("unknown: %zu", doh_stats.unknown_code);
1009 no_codes = false;
1010 }
1011 if (no_codes) {
1012 printf("none");
1013 }
1014 printf("\n");
1015
1016 printf("\n");
1017 }
1018