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