]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add preliminary DoH client support to dig
authorOndřej Surý <ondrej@isc.org>
Wed, 27 Jan 2021 14:49:27 +0000 (15:49 +0100)
committerArtem Boldariev <artem@boldariev.com>
Fri, 5 Mar 2021 11:28:17 +0000 (13:28 +0200)
add options "+https", "+https-get" and "+http-plain" to
allow dig to connect over HTTP/2 channels.

bin/dig/dig.c
bin/dig/dig.rst
bin/dig/dighost.c
bin/dig/dighost.h
doc/man/dig.1in

index 76c491f073b0326f05f5247180369b6e5b324483..c4f75c4fddf96d0a011450240964360b016b18b7 100644 (file)
@@ -228,6 +228,10 @@ help(void) {
               "SERVFAIL)\n"
               "                 +[no]header-only    (Send query without a "
               "question section)\n"
+              "                 +[no]https[=###]    (DNS over HTTPS mode) "
+              "[/]\n"
+              "                 +[no]https-get      (Use GET instead of "
+              "default POST method\n"
               "                 +[no]identify       (ID responders in short "
               "answers)\n"
 #ifdef HAVE_LIBIDN2
@@ -348,12 +352,18 @@ received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) {
                }
                if (query->lookup->tls_mode) {
                        proto = "TLS";
+               } else if (query->lookup->https_mode) {
+                       if (query->lookup->http_plain) {
+                               proto = "HTTP";
+                       } else {
+                               proto = "HTTPS";
+                       }
                } else if (query->lookup->tcp_mode) {
                        proto = "TCP";
                } else {
                        proto = "UDP";
                }
-               printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->servname,
+               printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg,
                       proto);
                time(&tnow);
                (void)localtime_r(&tnow, &tmnow);
@@ -1066,6 +1076,17 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
                    (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0))   \
                        goto invalid_option;                             \
        } while (0)
+#define FULLCHECK6(A, B, C, D, E, F)                                     \
+       do {                                                             \
+               size_t _l = strlen(cmd);                                 \
+               if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \
+                   (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0) && \
+                   (_l >= sizeof(C) || strncasecmp(cmd, C, _l) != 0) && \
+                   (_l >= sizeof(D) || strncasecmp(cmd, D, _l) != 0) && \
+                   (_l >= sizeof(E) || strncasecmp(cmd, E, _l) != 0) && \
+                   (_l >= sizeof(F) || strncasecmp(cmd, F, _l) != 0))   \
+                       goto invalid_option;                             \
+       } while (0)
 
        switch (cmd[0]) {
        case 'a':
@@ -1412,8 +1433,78 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
                lookup->servfail_stops = state;
                break;
        case 'h':
-               FULLCHECK("header-only");
-               lookup->header_only = state;
+               switch (cmd[1]) {
+               case 'e': /* header-only */
+                       FULLCHECK("header-only");
+                       lookup->header_only = state;
+                       break;
+               case 't':
+                       FULLCHECK6("https", "https-get", "https-post",
+                                  "http-plain", "http-plain-get",
+                                  "http-plain-post");
+                       if (lookup->https_path != NULL) {
+                               isc_mem_free(mctx, lookup->https_path);
+                               lookup->https_path = NULL;
+                       }
+                       if (!state) {
+                               lookup->https_mode = false;
+                               break;
+                       }
+                       lookup->https_mode = true;
+                       if (cmd[4] == '-') {
+                               lookup->http_plain = true;
+                               switch (cmd[10]) {
+                               case '\0':
+                                       FULLCHECK("http-plain");
+                                       break;
+                               case '-':
+                                       switch (cmd[6]) {
+                                       case 'p':
+                                               FULLCHECK("https-plain-post");
+                                               break;
+                                       case 'g':
+                                               FULLCHECK("https-plain-get");
+                                               lookup->https_get = true;
+                                               break;
+                                       }
+                                       break;
+                               default:
+                                       goto invalid_option;
+                               }
+                       } else {
+                               switch (cmd[5]) {
+                               case '\0':
+                                       FULLCHECK("https");
+                                       break;
+                               case '-':
+                                       switch (cmd[6]) {
+                                       case 'p':
+                                               FULLCHECK("https-post");
+                                               break;
+                                       case 'g':
+                                               FULLCHECK("https-get");
+                                               lookup->https_get = true;
+                                               break;
+                                       }
+                                       break;
+                               default:
+                                       goto invalid_option;
+                               }
+                       }
+                       if (!lookup->tcp_mode_set) {
+                               lookup->tcp_mode = state;
+                       }
+                       if (value == NULL) {
+                               lookup->https_path = isc_mem_strdup(
+                                       mctx, DEFAULT_HTTPS_PATH);
+                       } else {
+                               lookup->https_path = isc_mem_strdup(mctx,
+                                                                   value);
+                       }
+                       break;
+               default:
+                       goto invalid_option;
+               }
                break;
        case 'i':
                switch (cmd[1]) {
index 9df9ef2c1ec78b0c16925f292933239c306987f8..c28b51e4adfd085f14dda2132e666d5dca72d4e5 100644 (file)
@@ -349,11 +349,38 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to
    default is to add a question section. The query type and query name
    are ignored when this is set.
 
+``+[no]https[=value]``
+   This option indicates whether to use DNS-over-HTTPS (DoH) when querying
+   name servers.  When this option is in use, the port number defaults to 443.
+   The HTTP POST request mode is used when sending the query.
+
+   If ``value`` is specified, it will be used as the HTTP endpoint in the
+   query URI; the default is ``/dns-query``. So, for example, ``dig
+   @example.com +https`` will use the URI ``https://example.com/dns-query``.
+
+``+[no]https-get[=value]``
+   Similar to ``+https``, except that the HTTP GET request mode is used
+   when sending the query.
+
+``+[no]https-post[=value]``
+   Same as ``+https``.
+
+``+[no]http-plain[=value]``
+   Similar to ``+https``, except that HTTP queries will be sent over a
+   non-encrypted channel. When this option is in use, the port number
+   defaults to 80 and the HTTP request mode is POST.
+
+``+[no]http-plain-get[=value]``
+   Similar to ``+http-plain``, except that the HTTP request mode is GET.
+
+``+[no]http-plain-post[=value]``
+   Same as ``+http-plain``.
+
 ``+[no]identify``
-   This option shows [or does not show] the IP address and port number that supplied
-   the answer, when the ``+short`` option is enabled. If short form
-   answers are requested, the default is not to show the source address
-   and port number of the server that provided the answer.
+   This option shows [or does not show] the IP address and port number that
+   supplied the answer, when the ``+short`` option is enabled. If short
+   form answers are requested, the default is not to show the source
+   address and port number of the server that provided the answer.
 
 ``+[no]idnin``
    This option processes [or does not process] IDN domain names on input. This requires
@@ -519,8 +546,9 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to
    5 seconds. An attempt to set ``T`` to less than 1 is silently set to 1.
 
 ``+[no]tls``
-   This option indicates whether to use DNS over TLS (DoT) when querying
-   name servers.
+   This option indicates whether to use DNS-over-TLS (DoT) when querying
+   name servers. When this option is in use, the port number defaults
+   to 853.
 
 ``+[no]topdown``
    This feature is related to ``dig +sigchase``, which is obsolete and
index ff72aea4a559578c41c027ffd94abe6745a3f285..54bb63e5d1b196376caca26757414cca85740167 100644 (file)
@@ -603,102 +603,43 @@ clone_server_list(dig_serverlist_t src, dig_serverlist_t *dest) {
 dig_lookup_t *
 make_empty_lookup(void) {
        dig_lookup_t *looknew;
+#ifdef HAVE_LIBIDN2
+       bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false;
+#endif /* HAVE_LIBIDN2 */
 
        debug("make_empty_lookup()");
 
        INSIST(!free_now);
 
-       looknew = isc_mem_allocate(mctx, sizeof(struct dig_lookup));
-       looknew->pending = true;
-       looknew->textname[0] = 0;
-       looknew->cmdline[0] = 0;
-       looknew->rdtype = dns_rdatatype_a;
-       looknew->qrdtype = dns_rdatatype_a;
-       looknew->rdclass = dns_rdataclass_in;
-       looknew->rdtypeset = false;
-       looknew->rdclassset = false;
-       looknew->sendspace = NULL;
-       looknew->sendmsg = NULL;
-       looknew->name = NULL;
-       looknew->oname = NULL;
-       looknew->xfr_q = NULL;
-       looknew->current_query = NULL;
-       looknew->doing_xfr = false;
-       looknew->ixfr_serial = 0;
-       looknew->trace = false;
-       looknew->trace_root = false;
-       looknew->identify = false;
-       looknew->identify_previous_line = false;
-       looknew->ignore = false;
-       looknew->servfail_stops = true;
-       looknew->besteffort = true;
-       looknew->dns64prefix = false;
-       looknew->dnssec = false;
-       looknew->ednsflags = 0;
-       looknew->opcode = dns_opcode_query;
-       looknew->expire = false;
-       looknew->nsid = false;
-       looknew->tcp_keepalive = false;
-       looknew->padding = 0;
-       looknew->header_only = false;
-       looknew->sendcookie = false;
-       looknew->seenbadcookie = false;
-       looknew->badcookie = true;
-       looknew->multiline = false;
-       looknew->nottl = false;
-       looknew->noclass = false;
-       looknew->onesoa = false;
-       looknew->use_usec = false;
-       looknew->nocrypto = false;
-       looknew->ttlunits = false;
-       looknew->expandaaaa = false;
-       looknew->qr = false;
+       looknew = isc_mem_allocate(mctx, sizeof(*looknew));
+       *looknew = (dig_lookup_t){
+               .pending = true,
+               .rdtype = dns_rdatatype_a,
+               .qrdtype = dns_rdatatype_a,
+               .rdclass = dns_rdataclass_in,
+               .servfail_stops = true,
+               .besteffort = true,
+               .opcode = dns_opcode_query,
+               .badcookie = true,
 #ifdef HAVE_LIBIDN2
-       looknew->idnin = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false;
-       looknew->idnout = looknew->idnin;
-#else  /* ifdef HAVE_LIBIDN2 */
-       looknew->idnin = false;
-       looknew->idnout = false;
+               .idnin = idn_allowed,
+               .idnout = idn_allowed,
 #endif /* HAVE_LIBIDN2 */
-       looknew->udpsize = -1;
-       looknew->edns = -1;
-       looknew->recurse = true;
-       looknew->aaonly = false;
-       looknew->adflag = false;
-       looknew->cdflag = false;
-       looknew->raflag = false;
-       looknew->tcflag = false;
-       looknew->print_unknown_format = false;
-       looknew->zflag = false;
-       looknew->setqid = false;
-       looknew->qid = 0;
-       looknew->ns_search_only = false;
-       looknew->origin = NULL;
-       looknew->tsigctx = NULL;
-       looknew->querysig = NULL;
-       looknew->retries = tries;
-       looknew->nsfound = 0;
-       looknew->tcp_mode = false;
-       looknew->tcp_mode_set = false;
-       looknew->tls_mode = false;
-       looknew->comments = true;
-       looknew->stats = true;
-       looknew->section_question = true;
-       looknew->section_answer = true;
-       looknew->section_authority = true;
-       looknew->section_additional = true;
-       looknew->new_search = false;
-       looknew->done_as_is = false;
-       looknew->need_search = false;
-       looknew->ecs_addr = NULL;
-       looknew->cookie = NULL;
-       looknew->ednsopts = NULL;
-       looknew->ednsoptscnt = 0;
-       looknew->ednsneg = true;
-       looknew->mapped = true;
-       looknew->dscp = -1;
-       looknew->rrcomments = 0;
-       looknew->eoferr = 0;
+               .udpsize = -1,
+               .edns = -1,
+               .recurse = true,
+               .retries = tries,
+               .comments = true,
+               .stats = true,
+               .section_question = true,
+               .section_answer = true,
+               .section_authority = true,
+               .section_additional = true,
+               .ednsneg = true,
+               .mapped = true,
+               .dscp = -1,
+       };
+
        dns_fixedname_init(&looknew->fdomain);
        ISC_LINK_INIT(looknew, link);
        ISC_LIST_INIT(looknew->q);
@@ -787,6 +728,12 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
        looknew->nsid = lookold->nsid;
        looknew->tcp_keepalive = lookold->tcp_keepalive;
        looknew->header_only = lookold->header_only;
+       looknew->https_mode = lookold->https_mode;
+       if (lookold->https_path != NULL) {
+               looknew->https_path = isc_mem_strdup(mctx, lookold->https_path);
+       }
+       looknew->https_get = lookold->https_get;
+       looknew->http_plain = lookold->http_plain;
        looknew->sendcookie = lookold->sendcookie;
        looknew->seenbadcookie = lookold->seenbadcookie;
        looknew->badcookie = lookold->badcookie;
@@ -1638,6 +1585,10 @@ _destroy_lookup(dig_lookup_t *lookup) {
                isc_mem_free(mctx, lookup->ednsopts);
        }
 
+       if (lookup->https_path) {
+               isc_mem_free(mctx, lookup->https_path);
+       }
+
        isc_mem_free(mctx, lookup);
 }
 
@@ -2760,7 +2711,20 @@ start_tcp(dig_query_t *query) {
         * For TLS connections, we want to override the default
         * port number.
         */
-       port = port_set ? port : (query->lookup->tls_mode ? 853 : 53);
+       if (!port_set) {
+               if (query->lookup->tls_mode) {
+                       port = 853;
+               } else if (query->lookup->https_mode &&
+                          !query->lookup->http_plain) {
+                       port = 443;
+               } else if (query->lookup->https_mode) {
+                       port = 80;
+               } else {
+                       port = 53;
+               }
+       }
+
+       debug("query->servname = %s\n", query->servname);
 
        result = get_address(query->servname, port, &query->sockaddr);
        if (result != ISC_R_SUCCESS) {
@@ -2835,7 +2799,27 @@ start_tcp(dig_query_t *query) {
                                (isc_nmiface_t *)&query->sockaddr,
                                tcp_connected, query, local_timeout, 0,
                                query->tlsctx);
-                       check_result(result, "isc_nm_tcpdnsconnect");
+                       check_result(result, "isc_nm_tlsdnsconnect");
+               } else if (query->lookup->https_mode) {
+                       char uri[4096] = { 0 };
+                       snprintf(uri, sizeof(uri), "https://%s:%u%s",
+                                query->userarg, (uint16_t)port,
+                                query->lookup->https_path);
+
+                       if (!query->lookup->http_plain) {
+                               result =
+                                       isc_tlsctx_createclient(&query->tlsctx);
+                               RUNTIME_CHECK(result == ISC_R_SUCCESS);
+                               isc_tlsctx_enable_http2client_alpn(
+                                       query->tlsctx);
+                       }
+
+                       result = isc_nm_httpconnect(
+                               netmgr, (isc_nmiface_t *)&localaddr,
+                               (isc_nmiface_t *)&query->sockaddr, uri,
+                               !query->lookup->https_get, tcp_connected, query,
+                               query->tlsctx, local_timeout, 0);
+                       check_result(result, "isc_nm_httpconnect");
                } else {
                        result = isc_nm_tcpdnsconnect(
                                netmgr, (isc_nmiface_t *)&localaddr,
@@ -3211,6 +3195,7 @@ launch_next_query(dig_query_t *query) {
                        }
                }
        }
+
        lookup_detach(&l);
        return;
 }
@@ -3582,8 +3567,7 @@ recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
        isc_sockaddr_t peer;
 
        REQUIRE(DIG_VALID_QUERY(query));
-       INSIST(query->readhandle != NULL);
-       INSIST(handle == query->readhandle);
+       REQUIRE(query->readhandle != NULL);
        INSIST(!free_now);
 
        debug("recv_done(%p, %s, %p, %p)", handle, isc_result_totext(eresult),
index b719b4856e5f7e1ebab3baaa01c74fa39543ab61..0dfeb56da5e32c2b08b6f67d6609029111349cd8 100644 (file)
@@ -76,6 +76,9 @@
 #define DEFAULT_EDNS_VERSION 0
 #define DEFAULT_EDNS_BUFSIZE 1232
 
+#define DEFAULT_HTTPS_PATH  "/dns-query"
+#define DEFAULT_HTTPS_QUERY "?dns="
+
 /*%
  * Lookup_limit is just a limiter, keeping too many lookups from being
  * created.  It's job is mainly to prevent the program from running away
@@ -168,6 +171,12 @@ struct dig_lookup {
        int rrcomments;
        unsigned int eoferr;
        uint16_t qid;
+       struct {
+               bool http_plain;
+               bool https_mode;
+               bool https_get;
+               char *https_path;
+       };
 };
 
 /*% The dig_query structure */
index 9b78ada40fb9ff51f827c50e46c726472ceb8af9..e5820f5f4232d9917fd4aa56aab6b399a35890c4 100644 (file)
@@ -361,11 +361,38 @@ This option sends a query with a DNS header without a question section. The
 default is to add a question section. The query type and query name
 are ignored when this is set.
 .TP
+.B \fB+[no]https[=value]\fP
+This option indicates whether to use DNS\-over\-HTTPS (DoH) when querying
+name servers.  When this option is in use, the port number defaults to 443.
+The HTTP POST request mode is used when sending the query.
+.sp
+If \fBvalue\fP is specified, it will be used as the HTTP endpoint in the
+query URI; the default is \fB/dns\-query\fP\&. So, for example, \fBdig
+@example.com +https\fP will use the URI \fBhttps://example.com/dns\-query\fP\&.
+.TP
+.B \fB+[no]https\-get[=value]\fP
+Similar to \fB+https\fP, except that the HTTP GET request mode is used
+when sending the query.
+.TP
+.B \fB+[no]https\-post[=value]\fP
+Same as \fB+https\fP\&.
+.TP
+.B \fB+[no]http\-plain[=value]\fP
+Similar to \fB+https\fP, except that HTTP queries will be sent over a
+non\-encrypted channel. When this option is in use, the port number
+defaults to 80 and the HTTP request mode is POST.
+.TP
+.B \fB+[no]http\-plain\-get[=value]\fP
+Similar to \fB+http\-plain\fP, except that the HTTP request mode is GET.
+.TP
+.B \fB+[no]http\-plain\-post[=value]\fP
+Same as \fB+http\-plain\fP\&.
+.TP
 .B \fB+[no]identify\fP
-This option shows [or does not show] the IP address and port number that supplied
-the answer, when the \fB+short\fP option is enabled. If short form
-answers are requested, the default is not to show the source address
-and port number of the server that provided the answer.
+This option shows [or does not show] the IP address and port number that
+supplied the answer, when the \fB+short\fP option is enabled. If short
+form answers are requested, the default is not to show the source
+address and port number of the server that provided the answer.
 .TP
 .B \fB+[no]idnin\fP
 This option processes [or does not process] IDN domain names on input. This requires
@@ -531,8 +558,9 @@ This option sets the timeout for a query to \fBT\fP seconds. The default timeout
 5 seconds. An attempt to set \fBT\fP to less than 1 is silently set to 1.
 .TP
 .B \fB+[no]tls\fP
-This option indicates whether to use DNS over TLS (DoT) when querying
-name servers.
+This option indicates whether to use DNS\-over\-TLS (DoT) when querying
+name servers. When this option is in use, the port number defaults
+to 853.
 .TP
 .B \fB+[no]topdown\fP
 This feature is related to \fBdig +sigchase\fP, which is obsolete and