2 * testcode/dohclient.c - debug program. Perform multiple DNS queries using DoH.
4 * Copyright (c) 2020, NLnet Labs. All rights reserved.
6 * This software is open source.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
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.
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.
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.
39 * Simple DNS-over-HTTPS client. For testing and debugging purposes.
40 * No authentication of TLS cert.
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>
58 #include <nghttp2/nghttp2.h>
60 struct http2_session {
61 nghttp2_session* session;
65 /* Use POST :method if 1 */
68 const char* authority;
70 const char* content_type;
76 struct sldns_buffer* buf;
80 static void usage(char* argv[])
82 printf("usage: %s [options] name type class ...\n", argv[0]);
83 printf(" sends the name-type-class queries over "
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");
97 /** open TCP socket to svr */
99 open_svr(const char* svr, int port)
101 struct sockaddr_storage addr;
105 if(!ipstrtoaddr(svr, port, &addr, &addrlen)) {
106 printf("fatal: bad server specs '%s'\n", svr);
110 fd = socket(addr_is_ip6(&addr, addrlen)?PF_INET6:PF_INET,
113 perror("socket() error");
116 r = connect(fd, (struct sockaddr*)&addr, addrlen);
117 if(r < 0 && r != EINPROGRESS) {
118 perror("connect() error");
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))
130 if(length > sldns_buffer_remaining(source->ptr))
131 length = sldns_buffer_remaining(source->ptr);
133 memcpy(buf, sldns_buffer_current(source->ptr), length);
134 sldns_buffer_skip(source->ptr, length);
136 if(sldns_buffer_remaining(source->ptr) == 0) {
137 *data_flags |= NGHTTP2_DATA_FLAG_EOF;
144 submit_query(struct http2_session* h2_session, struct sldns_buffer* buf)
147 struct http2_stream* h2_stream;
148 nghttp2_nv headers[5];
151 size_t qb64_expected_size;
153 nghttp2_data_provider data_prd;
155 h2_stream = calloc(1, sizeof(*h2_stream));
157 fatal_exit("could not malloc http2 stream");
158 h2_stream->buf = buf;
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;
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);
180 headers[0].name = (uint8_t*)":method";
182 headers[0].value = (uint8_t*)"POST";
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;
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);
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);
206 printf("Failed to submit nghttp2 request");
209 h2_session->query_count++;
210 h2_stream->stream_id = stream_id;
214 make_query(char* qname, char* qtype, char* qclass)
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);
222 printf("cannot parse query name: '%s'\n", qname);
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;
230 qinfo_query_encode(buf, &qinfo); /* flips buffer */
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;
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);
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)
247 struct http2_session* h2_session = (struct http2_session*)cb_arg;
249 struct timeval tv, *waittv;
253 memset(&tv, 0, sizeof(tv));
255 if(h2_session->block_select && h2_session->query_count <= 0) {
256 return NGHTTP2_ERR_WOULDBLOCK;
258 if(h2_session->block_select)
262 memset(&rfd, 0, sizeof(rfd));
264 FD_SET(h2_session->fd, &rfd);
265 r = select(h2_session->fd+1, &rfd, NULL, NULL, waittv);
267 return NGHTTP2_ERR_WOULDBLOCK;
270 r = SSL_read(h2_session->ssl, buf, len);
272 int want = SSL_get_error(h2_session->ssl, r);
273 if(want == SSL_ERROR_ZERO_RETURN) {
274 return NGHTTP2_ERR_EOF;
276 log_crypto_err("could not SSL_read");
277 return NGHTTP2_ERR_EOF;
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)
285 struct http2_session* h2_session = (struct http2_session*)cb_arg;
289 r = SSL_write(h2_session->ssl, buf, len);
291 int want = SSL_get_error(h2_session->ssl, r);
292 if(want == SSL_ERROR_ZERO_RETURN) {
293 return NGHTTP2_ERR_CALLBACK_FAILURE;
295 log_crypto_err("could not SSL_write");
296 return NGHTTP2_ERR_CALLBACK_FAILURE;
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)
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))) {
311 h2_session->query_count--;
312 sldns_buffer_free(h2_stream->buf);
313 if(!h2_session->post)
314 free(h2_stream->path);
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)
324 struct http2_session* h2_session = (struct http2_session*)cb_arg;
325 struct http2_stream* h2_stream;
327 if(!(h2_stream = nghttp2_session_get_stream_user_data(
328 h2_session->session, stream_id))) {
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;
337 sldns_buffer_write(h2_stream->buf, data, len);
342 static int http2_frame_recv_cb(nghttp2_session *session,
343 const nghttp2_frame *frame, void* ATTR_UNUSED(cb_arg))
345 struct http2_stream* h2_stream;
347 if(!(h2_stream = nghttp2_session_get_stream_user_data(
348 session, frame->hd.stream_id)))
350 if(frame->hd.type == NGHTTP2_HEADERS &&
351 frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
352 sldns_buffer_clear(h2_stream->buf);
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) {
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);
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)
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))) {
382 h2_stream->res_status = atoi((char*)value);
387 static struct http2_session*
388 http2_session_create()
390 struct http2_session* h2_session = calloc(1,
391 sizeof(struct http2_session));
392 nghttp2_session_callbacks* callbacks;
394 fatal_exit("out of memory");
396 if(nghttp2_session_callbacks_new(&callbacks) == NGHTTP2_ERR_NOMEM) {
397 log_err("failed to initialize nghttp2 callback");
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,
410 nghttp2_session_client_new(&h2_session->session, callbacks, h2_session);
411 nghttp2_session_callbacks_del(callbacks);
416 http2_session_delete(struct http2_session* h2_session)
418 nghttp2_session_del(h2_session->session);
423 http2_submit_setting(struct http2_session* h2_session)
426 nghttp2_settings_entry settings[1] = {
427 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
430 ret = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE,
433 printf("http2: submit_settings failed, "
434 "error: %s\n", nghttp2_strerror(ret));
440 http2_write(struct http2_session* h2_session)
442 if(nghttp2_session_want_write(h2_session->session)) {
443 if(nghttp2_session_send(h2_session->session)) {
444 printf("nghttp2 session send failed\n");
451 http2_read(struct http2_session* h2_session)
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");
462 run(struct http2_session* h2_session, int port, int count, char** q)
468 struct sldns_buffer* buf = NULL;
470 fd = open_svr(h2_session->authority, port);
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);
478 printf("cannot create ssl\n");
481 h2_session->ssl = ssl;
485 if( (r=SSL_do_handshake(ssl)) == 1)
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");
495 http2_submit_setting(h2_session);
496 http2_write(h2_session);
497 http2_read(h2_session); /* Read setting from remote peer */
499 h2_session->block_select = 1;
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);
506 http2_write(h2_session);
507 while(h2_session->query_count) {
508 http2_read(h2_session);
509 http2_write(h2_session);
513 http2_session_delete(h2_session);
520 /** getopt global, in case header files fail to declare it. */
522 /** getopt global, in case header files fail to declare it. */
524 int main(int argc, char** argv)
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");
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";
540 while((c=getopt(argc, argv, "c:e:hs:p:P")) != -1) {
543 h2_session->content_type = optarg;
546 h2_session->endpoint = optarg;
549 if(atoi(optarg)==0 && strcmp(optarg,"0")!=0) {
550 printf("error parsing port, "
551 "number expected: %s\n", optarg);
557 h2_session->post = 1;
560 h2_session->authority = optarg;
571 printf("Invalid input. Specify qname, qtype, and qclass.\n");
576 run(h2_session, port, argc, argv);
581 int main(int ATTR_UNUSED(argc), char** ATTR_UNUSED(argv))
583 printf("Compiled without nghttp2, cannot run test.\n");
586 #endif /* HAVE_NGHTTP2 */