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 */ 587