]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - testcode/dohclient.c
Vendor import of Unbound 1.12.0.
[FreeBSD/FreeBSD.git] / testcode / dohclient.c
1 /*
2  * testcode/dohclient.c - debug program. Perform multiple DNS queries using DoH.
3  *
4  * Copyright (c) 2020, NLnet Labs. All rights reserved.
5  *
6  * This software is open source.
7  * 
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 
12  * Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  * 
15  * Redistributions in binary form must reproduce the above copyright notice,
16  * this list of conditions and the following disclaimer in the documentation
17  * and/or other materials provided with the distribution.
18  * 
19  * Neither the name of the NLNET LABS nor the names of its contributors may
20  * be used to endorse or promote products derived from this software without
21  * specific prior written permission.
22  * 
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35
36 /** 
37  * \file
38  *
39  * Simple DNS-over-HTTPS client. For testing and debugging purposes.
40  * No authentication of TLS cert.
41  */
42
43 #include "config.h"
44 #ifdef HAVE_GETOPT_H
45 #include <getopt.h>
46 #endif
47 #include "sldns/wire2str.h"
48 #include "sldns/sbuffer.h"
49 #include "sldns/str2wire.h"
50 #include "sldns/parseutil.h"
51 #include "util/data/msgencode.h"
52 #include "util/data/msgreply.h"
53 #include "util/data/msgparse.h"
54 #include "util/net_help.h"
55 #include <openssl/ssl.h>
56 #include <openssl/err.h>
57 #ifdef HAVE_NGHTTP2
58 #include <nghttp2/nghttp2.h>
59
60 struct http2_session {
61         nghttp2_session* session;
62         SSL* ssl;
63         int fd;
64         int query_count;
65         /* Use POST :method if 1 */
66         int post;
67         int block_select;
68         const char* authority;
69         const char* endpoint;
70         const char* content_type;
71 };
72
73 struct http2_stream {
74         int32_t stream_id;
75         int res_status;
76         struct sldns_buffer* buf;
77         char* path;
78 };
79
80 static void usage(char* argv[])
81 {
82         printf("usage: %s [options] name type class ...\n", argv[0]);
83         printf("        sends the name-type-class queries over "
84                         "DNS-over-HTTPS.\n");
85         printf("-s server       IP address to send the queries to, "
86                         "default: 127.0.0.1\n");
87         printf("-p              Port to connect to, default: %d\n",
88                 UNBOUND_DNS_OVER_HTTPS_PORT);
89         printf("-P              Use POST method instead of default GET\n");
90         printf("-e              HTTP endpoint, default: /dns-query\n");
91         printf("-c              Content-type in request, default: "
92                 "application/dns-message\n");
93         printf("-h              This help text\n");
94         exit(1);
95 }
96
97 /** open TCP socket to svr */
98 static int
99 open_svr(const char* svr, int port)
100 {
101         struct sockaddr_storage addr;
102         socklen_t addrlen;
103         int fd = -1;
104         int r;
105         if(!ipstrtoaddr(svr, port, &addr, &addrlen)) {
106                 printf("fatal: bad server specs '%s'\n", svr);
107                 exit(1);
108         }
109
110         fd = socket(addr_is_ip6(&addr, addrlen)?PF_INET6:PF_INET,
111                 SOCK_STREAM, 0);
112         if(fd == -1) {
113                 perror("socket() error");
114                 exit(1);
115         }
116         r = connect(fd, (struct sockaddr*)&addr, addrlen);
117         if(r < 0 && r != EINPROGRESS) {
118                 perror("connect() error");
119                 exit(1);
120         }
121         return fd;
122 }
123
124 static ssize_t http2_submit_request_read_cb(
125         nghttp2_session* ATTR_UNUSED(session),
126         int32_t ATTR_UNUSED(stream_id), uint8_t* buf, size_t length,
127         uint32_t* data_flags, nghttp2_data_source* source,
128         void* ATTR_UNUSED(cb_arg))
129 {
130         if(length > sldns_buffer_remaining(source->ptr))
131                 length = sldns_buffer_remaining(source->ptr);
132
133         memcpy(buf, sldns_buffer_current(source->ptr), length);
134         sldns_buffer_skip(source->ptr, length);
135
136         if(sldns_buffer_remaining(source->ptr) == 0) {
137                 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
138         }
139
140         return length;
141 }
142
143 static void
144 submit_query(struct http2_session* h2_session, struct sldns_buffer* buf)
145 {
146         int32_t stream_id;
147         struct http2_stream* h2_stream;
148         nghttp2_nv headers[5];
149         char* qb64;
150         size_t qb64_size;
151         size_t qb64_expected_size;
152         size_t i;
153         nghttp2_data_provider data_prd;
154
155         h2_stream = calloc(1,  sizeof(*h2_stream));
156         if(!h2_stream)
157                 fatal_exit("could not malloc http2 stream");
158         h2_stream->buf = buf;
159
160         if(h2_session->post) {
161                 data_prd.source.ptr = buf;
162                 data_prd.read_callback = http2_submit_request_read_cb;
163                 h2_stream->path = (char*)h2_session->endpoint;
164         } else {
165                 qb64_expected_size = sldns_b64_ntop_calculate_size(
166                                 sldns_buffer_remaining(buf));
167                 qb64 = malloc(qb64_expected_size);
168                 if(!qb64) fatal_exit("out of memory");
169                 qb64_size = sldns_b64url_ntop(sldns_buffer_begin(buf),
170                         sldns_buffer_remaining(buf), qb64, qb64_expected_size);
171                 h2_stream->path = malloc(strlen(
172                         h2_session->endpoint)+strlen("?dns=")+qb64_size+1);
173                 if(!h2_stream->path) fatal_exit("out of memory");
174                 snprintf(h2_stream->path, strlen(h2_session->endpoint)+
175                         strlen("?dns=")+qb64_size+1, "%s?dns=%s",
176                         h2_session->endpoint, qb64);
177                 free(qb64);
178         }
179
180         headers[0].name = (uint8_t*)":method";
181         if(h2_session->post)
182                 headers[0].value = (uint8_t*)"POST";
183         else
184                 headers[0].value = (uint8_t*)"GET";
185         headers[1].name = (uint8_t*)":path";
186         headers[1].value = (uint8_t*)h2_stream->path;
187         headers[2].name = (uint8_t*)":scheme";
188         headers[2].value = (uint8_t*)"https";
189         headers[3].name = (uint8_t*)":authority";
190         headers[3].value = (uint8_t*)h2_session->authority;
191         headers[4].name = (uint8_t*)"content-type";
192         headers[4].value = (uint8_t*)h2_session->content_type;
193
194         printf("Request headers\n");
195         for(i=0; i<sizeof(headers)/sizeof(headers[0]); i++) {
196                 headers[i].namelen = strlen((char*)headers[i].name);
197                 headers[i].valuelen = strlen((char*)headers[i].value);
198                 headers[i].flags = NGHTTP2_NV_FLAG_NONE;
199                 printf("%s: %s\n", headers[i].name, headers[i].value);
200         }
201
202         stream_id = nghttp2_submit_request(h2_session->session, NULL, headers,
203                 sizeof(headers)/sizeof(headers[0]),
204                 (h2_session->post) ? &data_prd : NULL, h2_stream);
205         if(stream_id < 0) {
206                 printf("Failed to submit nghttp2 request");
207                 exit(1);
208         }
209         h2_session->query_count++;
210         h2_stream->stream_id = stream_id;
211 }
212
213 static sldns_buffer*
214 make_query(char* qname, char* qtype, char* qclass)
215 {
216         struct query_info qinfo;
217         struct edns_data edns;
218         sldns_buffer* buf = sldns_buffer_new(65553);
219         if(!buf) fatal_exit("out of memory");
220         qinfo.qname = sldns_str2wire_dname(qname, &qinfo.qname_len);
221         if(!qinfo.qname) {
222                 printf("cannot parse query name: '%s'\n", qname);
223                 exit(1);
224         }
225
226         qinfo.qtype = sldns_get_rr_type_by_name(qtype);
227         qinfo.qclass = sldns_get_rr_class_by_name(qclass);
228         qinfo.local_alias = NULL;
229
230         qinfo_query_encode(buf, &qinfo); /* flips buffer */
231         free(qinfo.qname);
232         sldns_buffer_write_u16_at(buf, 0, 0x0000);
233         sldns_buffer_write_u16_at(buf, 2, BIT_RD);
234         memset(&edns, 0, sizeof(edns));
235         edns.edns_present = 1;
236         edns.bits = EDNS_DO;
237         edns.udp_size = 4096;
238         if(sldns_buffer_capacity(buf) >=
239                 sldns_buffer_limit(buf)+calc_edns_field_size(&edns))
240                 attach_edns_record(buf, &edns);
241         return buf;
242 }
243
244 static ssize_t http2_recv_cb(nghttp2_session* ATTR_UNUSED(session),
245         uint8_t* buf, size_t len, int ATTR_UNUSED(flags), void* cb_arg)
246 {
247         struct http2_session* h2_session = (struct http2_session*)cb_arg;
248         int r;
249         struct timeval tv, *waittv;
250         fd_set rfd;
251         ERR_clear_error();
252
253         memset(&tv, 0, sizeof(tv));
254
255         if(h2_session->block_select && h2_session->query_count <= 0) {
256                 return NGHTTP2_ERR_WOULDBLOCK;
257         }
258         if(h2_session->block_select)
259                 waittv = NULL;
260         else
261                 waittv = &tv;
262         memset(&rfd, 0, sizeof(rfd));
263         FD_ZERO(&rfd);
264         FD_SET(h2_session->fd, &rfd);
265         r = select(h2_session->fd+1, &rfd, NULL, NULL, waittv);
266         if(r <= 0) {
267                 return NGHTTP2_ERR_WOULDBLOCK;
268         }
269
270         r = SSL_read(h2_session->ssl, buf, len);
271         if(r <= 0) {
272                 int want = SSL_get_error(h2_session->ssl, r);
273                 if(want == SSL_ERROR_ZERO_RETURN) {
274                         return NGHTTP2_ERR_EOF;
275                 }
276                 log_crypto_err("could not SSL_read");
277                 return NGHTTP2_ERR_EOF;
278         }
279         return r;
280 }
281
282 static ssize_t http2_send_cb(nghttp2_session* ATTR_UNUSED(session),
283         const uint8_t* buf, size_t len, int ATTR_UNUSED(flags), void* cb_arg)
284 {
285         struct http2_session* h2_session = (struct http2_session*)cb_arg;
286
287         int r;
288         ERR_clear_error();
289         r = SSL_write(h2_session->ssl, buf, len);
290         if(r <= 0) {
291                 int want = SSL_get_error(h2_session->ssl, r);
292                 if(want == SSL_ERROR_ZERO_RETURN) {
293                         return NGHTTP2_ERR_CALLBACK_FAILURE;
294                 }
295                 log_crypto_err("could not SSL_write");
296                 return NGHTTP2_ERR_CALLBACK_FAILURE;
297         }
298         return r;
299 }
300
301 static int http2_stream_close_cb(nghttp2_session* ATTR_UNUSED(session),
302         int32_t ATTR_UNUSED(stream_id),
303         nghttp2_error_code ATTR_UNUSED(error_code), void *cb_arg)
304 {
305         struct http2_session* h2_session = (struct http2_session*)cb_arg;
306         struct http2_stream* h2_stream;
307         if(!(h2_stream = nghttp2_session_get_stream_user_data(
308                 h2_session->session, stream_id))) {
309                 return 0;
310         }
311         h2_session->query_count--;
312         sldns_buffer_free(h2_stream->buf);
313         if(!h2_session->post)
314                 free(h2_stream->path);
315         free(h2_stream);
316         h2_stream = NULL;
317         return 0;
318 }
319
320 static int http2_data_chunk_recv_cb(nghttp2_session* ATTR_UNUSED(session),
321         uint8_t ATTR_UNUSED(flags), int32_t stream_id, const uint8_t* data,
322         size_t len, void* cb_arg)
323 {
324         struct http2_session* h2_session = (struct http2_session*)cb_arg;
325         struct http2_stream* h2_stream;
326
327         if(!(h2_stream = nghttp2_session_get_stream_user_data(
328                 h2_session->session, stream_id))) {
329                 return 0;
330         }
331
332         if(sldns_buffer_remaining(h2_stream->buf) < len) {
333                 log_err("received data chunck does not fit into buffer");
334                 return NGHTTP2_ERR_CALLBACK_FAILURE;
335         }
336
337         sldns_buffer_write(h2_stream->buf, data, len);
338
339         return 0;
340 }
341
342 static int http2_frame_recv_cb(nghttp2_session *session,
343         const nghttp2_frame *frame, void* ATTR_UNUSED(cb_arg))
344 {
345         struct http2_stream* h2_stream;
346
347         if(!(h2_stream = nghttp2_session_get_stream_user_data(
348                 session, frame->hd.stream_id)))
349                 return 0;
350         if(frame->hd.type == NGHTTP2_HEADERS &&
351                 frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
352                 sldns_buffer_clear(h2_stream->buf);
353         }
354         if(((frame->hd.type != NGHTTP2_DATA &&
355                 frame->hd.type != NGHTTP2_HEADERS) ||
356                 frame->hd.flags & NGHTTP2_FLAG_END_STREAM) &&
357                         h2_stream->res_status == 200) {
358                         char* pktstr;
359                         sldns_buffer_flip(h2_stream->buf);
360                         pktstr = sldns_wire2str_pkt(
361                                 sldns_buffer_begin(h2_stream->buf),
362                                 sldns_buffer_limit(h2_stream->buf));
363                         printf("%s\n", pktstr);
364                         free(pktstr);
365                         return 0;
366         }
367         return 0;
368 }
369 static int http2_header_cb(nghttp2_session* ATTR_UNUSED(session),
370         const nghttp2_frame* frame, const uint8_t* name, size_t namelen,
371         const uint8_t* value, size_t ATTR_UNUSED(valuelen),
372         uint8_t ATTR_UNUSED(flags), void* cb_arg)
373 {
374         struct http2_stream* h2_stream;
375         struct http2_session* h2_session = (struct http2_session*)cb_arg;
376         printf("%s %s\n", name, value);
377         if(namelen == 7 && memcmp(":status", name, namelen) == 0) {
378                 if(!(h2_stream = nghttp2_session_get_stream_user_data(
379                         h2_session->session, frame->hd.stream_id))) {
380                         return 0;
381                 }
382                 h2_stream->res_status = atoi((char*)value);
383         }
384         return 0;
385 }
386
387 static struct http2_session*
388 http2_session_create()
389 {
390         struct http2_session* h2_session = calloc(1,
391                 sizeof(struct http2_session));
392         nghttp2_session_callbacks* callbacks;
393         if(!h2_session)
394                 fatal_exit("out of memory");
395
396         if(nghttp2_session_callbacks_new(&callbacks) == NGHTTP2_ERR_NOMEM) {
397                 log_err("failed to initialize nghttp2 callback");
398                 return NULL;
399         }
400         nghttp2_session_callbacks_set_recv_callback(callbacks, http2_recv_cb);
401         nghttp2_session_callbacks_set_send_callback(callbacks, http2_send_cb);
402         nghttp2_session_callbacks_set_on_stream_close_callback(callbacks,
403                 http2_stream_close_cb);
404         nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks,
405                 http2_data_chunk_recv_cb);
406         nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
407                 http2_frame_recv_cb);
408         nghttp2_session_callbacks_set_on_header_callback(callbacks,
409                 http2_header_cb);
410         nghttp2_session_client_new(&h2_session->session, callbacks, h2_session);
411         nghttp2_session_callbacks_del(callbacks);
412         return h2_session;
413 }
414
415 static void
416 http2_session_delete(struct http2_session* h2_session)
417 {
418         nghttp2_session_del(h2_session->session);
419         free(h2_session);
420 }
421
422 static void
423 http2_submit_setting(struct http2_session* h2_session)
424 {
425         int ret;
426         nghttp2_settings_entry settings[1] = {
427                 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
428                  100}};
429
430         ret = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE,
431                 settings, 1);
432         if(ret) {
433                 printf("http2: submit_settings failed, "
434                         "error: %s\n", nghttp2_strerror(ret));
435                 exit(1);
436         }
437 }
438
439 static void
440 http2_write(struct http2_session* h2_session)
441 {
442         if(nghttp2_session_want_write(h2_session->session)) {
443                 if(nghttp2_session_send(h2_session->session)) {
444                         printf("nghttp2 session send failed\n");
445                         exit(1);
446                 }
447         }
448 }
449
450 static void
451 http2_read(struct http2_session* h2_session)
452 {
453         if(nghttp2_session_want_read(h2_session->session)) {
454                 if(nghttp2_session_recv(h2_session->session)) {
455                         printf("nghttp2 session mem_recv failed\n");
456                         exit(1);
457                 }
458         }
459 }
460
461 static void
462 run(struct http2_session* h2_session, int port, int count, char** q)
463 {
464         int i;
465         SSL_CTX* ctx = NULL;
466         SSL* ssl = NULL;
467         int fd;
468         struct sldns_buffer* buf = NULL;
469
470         fd = open_svr(h2_session->authority, port);
471         h2_session->fd = fd;
472
473         ctx = connect_sslctx_create(NULL, NULL, NULL, 0);
474         if(!ctx) fatal_exit("cannot create ssl ctx");
475         SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3);
476         ssl = outgoing_ssl_fd(ctx, fd);
477         if(!ssl) {
478                 printf("cannot create ssl\n");
479                 exit(1);
480         }
481         h2_session->ssl = ssl;
482         while(1) {
483                 int r;
484                 ERR_clear_error();
485                 if( (r=SSL_do_handshake(ssl)) == 1)
486                         break;
487                 r = SSL_get_error(ssl, r);
488                 if(r != SSL_ERROR_WANT_READ &&
489                         r != SSL_ERROR_WANT_WRITE) {
490                         log_crypto_err("could not ssl_handshake");
491                         exit(1);
492                 }
493         }
494
495         http2_submit_setting(h2_session);
496         http2_write(h2_session);
497         http2_read(h2_session); /* Read setting from remote peer */
498
499         h2_session->block_select = 1;
500
501         /* hande query */
502         for(i=0; i<count; i+=3) {
503                 buf = make_query(q[i], q[i+1], q[i+2]);
504                 submit_query(h2_session, buf);
505         }
506         http2_write(h2_session);
507         while(h2_session->query_count) {
508                 http2_read(h2_session);
509                 http2_write(h2_session);
510         }
511
512         /* shutdown */
513         http2_session_delete(h2_session);
514         SSL_shutdown(ssl);
515         SSL_free(ssl);
516         SSL_CTX_free(ctx);
517         close(fd);
518 }
519
520 /** getopt global, in case header files fail to declare it. */
521 extern int optind;
522 /** getopt global, in case header files fail to declare it. */
523 extern char* optarg;
524 int main(int argc, char** argv)
525 {
526         int c;
527         int port = UNBOUND_DNS_OVER_HTTPS_PORT;
528         struct http2_session* h2_session = http2_session_create();
529         if(!h2_session) fatal_exit("out of memory");
530
531         if(argc == 1) {
532                 usage(argv);
533         }
534         
535         h2_session->authority = "127.0.0.1";
536         h2_session->post = 0;
537         h2_session->endpoint = "/dns-query";
538         h2_session->content_type = "application/dns-message";
539
540         while((c=getopt(argc, argv, "c:e:hs:p:P")) != -1) {
541                 switch(c) {
542                         case 'c':
543                                 h2_session->content_type = optarg;
544                                 break;
545                         case 'e':
546                                 h2_session->endpoint = optarg;
547                                 break;
548                         case 'p':
549                                 if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) {
550                                         printf("error parsing port, "
551                                             "number expected: %s\n", optarg);
552                                         return 1;
553                                 }
554                                 port = atoi(optarg);
555                                 break;
556                         case 'P':
557                                 h2_session->post = 1;
558                                 break;
559                         case 's':
560                                 h2_session->authority = optarg;
561                                 break;
562                         case 'h':
563                         case '?':
564                         default:
565                                 usage(argv);
566                 }
567         }
568         argc -= optind;
569         argv += optind;
570         if(argc%3!=0) {
571                 printf("Invalid input. Specify qname, qtype, and qclass.\n");
572                 return 1;
573         }
574
575
576         run(h2_session, port, argc, argv);
577
578         return 0;
579 }
580 #else
581 int main(int ATTR_UNUSED(argc), char** ATTR_UNUSED(argv))
582 {
583         printf("Compiled without nghttp2, cannot run test.\n");
584         return 1;
585 }
586 #endif /*  HAVE_NGHTTP2 */