]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Integrate PROXYv2 support into dig
authorArtem Boldariev <artem@boldariev.com>
Mon, 16 Oct 2023 19:54:13 +0000 (22:54 +0300)
committerArtem Boldariev <artem@boldariev.com>
Wed, 6 Dec 2023 13:15:25 +0000 (15:15 +0200)
This commit adds PROXYv2 support into dig by the means of adding
+[no]proxy and +[no]proxy-plain options. Since this commit dig
supports sending PROXYv2 headers on all supported DNS-transports.

The support for PROXYv2 is modelled after that one in kdig.

bin/dig/dig.c
bin/dig/dighost.c
bin/dig/dighost.h

index 54f7f81e33ddbb0115444411d9c6ee88030cbc3c..54d1425ea56db2e1f7624df172c08d36eea82f60 100644 (file)
@@ -252,6 +252,14 @@ help(void) {
               "request)\n"
               "                 +padding=###        (Set padding block size "
               "[0])\n"
+              "                 "
+              "+[no]proxy[=src_addr[#src_port]-dst_addr[#dst_port]]        "
+              "(Add PROXYv2 headers to the queries. If addresses are omitted, "
+              "LOCAL PROXYv2 headers are added)\n"
+              "                 "
+              "+[no]proxy-plain[=src_addr[#src_port]-dst_addr[#dst_port]]  "
+              "(The same as '+[no]proxy', but send PROXYv2 headers ahead of "
+              "any encryption if an encrypted transport is used)\n"
               "                 +qid=###            (Specify the query ID to "
               "use when sending queries)\n"
               "                 +[no]qr             (Print question before "
@@ -365,6 +373,39 @@ received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) {
                }
                printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg,
                       proto);
+
+               if (query->lookup->proxy_mode) {
+                       printf(";; CLIENT PROXY HEADER");
+
+                       if ((dig_lookup_is_tls(query->lookup) ||
+                            (query->lookup->https_mode &&
+                             !query->lookup->http_plain)) &&
+                           query->lookup->proxy_plain)
+                       {
+                               printf(" (plain)");
+                       }
+
+                       printf(": ");
+
+                       if (!query->lookup->proxy_local) {
+                               char src_buf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+                               char dst_buf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+
+                               isc_sockaddr_format(
+                                       &query->lookup->proxy_src_addr, src_buf,
+                                       sizeof(src_buf));
+
+                               isc_sockaddr_format(
+                                       &query->lookup->proxy_dst_addr, dst_buf,
+                                       sizeof(dst_buf));
+                               printf("source: %s, destination: %s", src_buf,
+                                      dst_buf);
+                       } else {
+                               printf("LOCAL");
+                       }
+
+                       printf("\n");
+               }
                time(&tnow);
                (void)localtime_r(&tnow, &tmnow);
 
@@ -1052,6 +1093,317 @@ printgreeting(int argc, char **argv, dig_lookup_t *lookup) {
                        goto invalid_option;                             \
        } while (0)
 
+/*
+ * Parse source and destination addresses in the same format as used by "kdig":
+ *
+ * SRC_ADDR[#SRC_PORT]-DST_ADDR[#DST_PORT]
+ *
+ * This can be described (pretty closely for our purpose) using the
+ * following EBNF grammar:
+ *
+ * S = proxy-addrs. (* start rule *)
+ * proxy-addrs = addr "-" addr EOF.
+ * addr = addr-char { addr-char } ["#" port ].
+ * port = digit { digit }.
+ * addr-char = <aby but "#", "-", EOF >.
+ * EOF = '\0'.
+ */
+#define MATCH(ch)     (st->str[0] == (ch))
+#define MATCH_DIGIT() isdigit((unsigned char)(st->str[0]))
+#define ADVANCE()     st->str++
+#define GETP()       (st->str)
+
+typedef struct isc_proxy_addrs_parser_state {
+       const char *str;
+
+       const char *last_addr_start;
+       size_t last_addr_len;
+
+       const char *last_port_start;
+       size_t last_port_len;
+
+       const char *src_addr_start;
+       size_t src_addr_len;
+
+       const char *src_port_start;
+       size_t src_port_len;
+
+       const char *dst_addr_start;
+       size_t dst_addr_len;
+
+       const char *dst_port_start;
+       size_t dst_port_len;
+} isc_proxy_addrs_parser_state_t;
+
+static bool
+rule_proxy_addrs(isc_proxy_addrs_parser_state_t *st);
+
+static bool
+rule_addr(isc_proxy_addrs_parser_state_t *st);
+
+static bool
+rule_port(isc_proxy_addrs_parser_state_t *st);
+
+static bool
+rule_addr_char(isc_proxy_addrs_parser_state_t *st);
+
+static void
+proxy_handle_port_string(const char *port_start, const size_t port_len,
+                        in_port_t *pport) {
+       char buf[512] = { 0 }; /* max */
+       size_t string_size = 0, max_string_bytes = 0;
+
+       string_size = port_len + 1;
+       max_string_bytes = string_size > sizeof(buf) ? sizeof(buf)
+                                                    : string_size;
+
+       (void)strlcpy(buf, port_start, max_string_bytes);
+       *pport = (in_port_t)atoi(buf);
+}
+
+static isc_result_t
+proxy_handle_addr_string(const char *addr_start, const size_t addr_len,
+                        const in_port_t addr_port, isc_sockaddr_t *addr) {
+       isc_result_t result = ISC_R_FAILURE;
+       char buf[512] = { 0 }; /* max */
+       size_t string_size = 0, max_string_bytes = 0;
+       struct in_addr ipv4 = { 0 };
+       struct in6_addr ipv6 = { 0 };
+       int ret = 0;
+
+       string_size = addr_len + 1;
+       max_string_bytes = string_size > sizeof(buf) ? sizeof(buf)
+                                                    : string_size;
+
+       (void)strlcpy(buf, addr_start, max_string_bytes);
+
+       ret = inet_pton(AF_INET, buf, &ipv4);
+       if (ret == 1) {
+               isc_sockaddr_fromin(addr, &ipv4, addr_port);
+               result = ISC_R_SUCCESS;
+       } else {
+               ret = inet_pton(AF_INET6, buf, &ipv6);
+               if (ret == 1) {
+                       isc_sockaddr_fromin6(addr, &ipv6, addr_port);
+                       result = ISC_R_SUCCESS;
+               }
+       }
+
+       return (result);
+}
+
+static bool
+parse_proxy_addresses(const char *addrs, isc_sockaddr_t *psrc,
+                     isc_sockaddr_t *pdst) {
+       isc_result_t result = ISC_R_FAILURE;
+       isc_sockaddr_t src = { 0 }, dst = { 0 };
+       isc_proxy_addrs_parser_state_t st = { 0 };
+       in_port_t src_port = 0, dst_port = 53; /* Follow kdig footsteps */
+
+       REQUIRE(addrs != NULL && *addrs != '\0');
+       REQUIRE(psrc != NULL);
+       REQUIRE(pdst != NULL);
+
+       st.str = addrs;
+
+       /* start syntax analysis and verification */
+       if (!rule_proxy_addrs(&st)) {
+               warn("PROXY source and destination addresses cannot be parsed");
+               return (false);
+       }
+
+       /* get port numeric values */
+       if (st.src_port_len > 0) {
+               INSIST(st.src_port_start != NULL);
+               proxy_handle_port_string(st.src_port_start, st.src_port_len,
+                                        &src_port);
+       }
+
+       if (st.dst_port_len > 0) {
+               INSIST(st.dst_port_start != NULL);
+               proxy_handle_port_string(st.dst_port_start, st.dst_port_len,
+                                        &dst_port);
+       }
+
+       /* get addresses */
+       INSIST(st.src_addr_len > 0);
+       INSIST(st.src_addr_start != NULL);
+       INSIST(st.dst_addr_len > 0);
+       INSIST(st.dst_addr_start != NULL);
+
+       result = proxy_handle_addr_string(st.src_addr_start, st.src_addr_len,
+                                         src_port, &src);
+       if (result != ISC_R_SUCCESS) {
+               warn("Cannot get PROXY source address: %s",
+                    isc_result_totext(result));
+               return (false);
+       }
+
+       result = proxy_handle_addr_string(st.dst_addr_start, st.dst_addr_len,
+                                         dst_port, &dst);
+       if (result != ISC_R_SUCCESS) {
+               warn("Cannot get PROXY destination address: %s",
+                    isc_result_totext(result));
+               return (false);
+       }
+
+       /* addresses should be of the same type */
+       if (isc_sockaddr_pf(&src) != isc_sockaddr_pf(&dst)) {
+               warn("PROXY source and destination addresses must be of the "
+                    "same type");
+               return (false);
+       }
+
+       *psrc = src;
+       *pdst = dst;
+
+       return (true);
+}
+
+static bool
+rule_proxy_addrs(isc_proxy_addrs_parser_state_t *st) {
+       if (!rule_addr(st)) {
+               return (false);
+       }
+
+       st->src_addr_start = st->last_addr_start;
+       st->src_addr_len = st->last_addr_len;
+       st->src_port_start = st->last_port_start;
+       st->src_port_len = st->last_port_len;
+
+       if (!MATCH('-')) {
+               return (false);
+       }
+
+       ADVANCE();
+
+       if (!rule_addr(st)) {
+               return (false);
+       }
+
+       st->dst_addr_start = st->last_addr_start;
+       st->dst_addr_len = st->last_addr_len;
+       st->dst_port_start = st->last_port_start;
+       st->dst_port_len = st->last_port_len;
+
+       if (!MATCH('\0')) {
+               return (false);
+       }
+
+       return (true);
+}
+
+static bool
+rule_addr(isc_proxy_addrs_parser_state_t *st) {
+       const char *start = GETP();
+       if (!rule_addr_char(st)) {
+               return (false);
+       }
+
+       while (rule_addr_char(st)) {
+               /* skip */
+       }
+
+       st->last_addr_start = start;
+       st->last_addr_len = GETP() - start;
+
+       if (MATCH('#')) {
+               ADVANCE();
+
+               if (!rule_port(st)) {
+                       return (false);
+               }
+       }
+
+       return (true);
+}
+
+static bool
+rule_port(isc_proxy_addrs_parser_state_t *st) {
+       const char *start = GETP();
+       if (!MATCH_DIGIT()) {
+               return (false);
+       }
+
+       ADVANCE();
+
+       while (MATCH_DIGIT()) {
+               ADVANCE();
+       }
+
+       st->last_port_start = start;
+       st->last_port_len = GETP() - start;
+
+       return (true);
+}
+
+static bool
+rule_addr_char(isc_proxy_addrs_parser_state_t *st) {
+       if (MATCH('#') || MATCH('-') || MATCH('\0')) {
+               return (false);
+       }
+
+       ADVANCE();
+
+       return (true);
+}
+
+#undef GETP
+#undef ADVANCE
+#undef MATCH_DIGIT
+#undef MATCH
+
+static bool
+plus_proxy_handle_addresses(const char *value, const bool state,
+                           dig_lookup_t *lookup) {
+       lookup->proxy_mode = state;
+       if (!state) {
+               /*
+                * We are not interested in the option value in that
+                * case
+                */
+               return (true);
+       }
+
+       if (value == NULL || *value == '\0') {
+               lookup->proxy_local = true;
+               return (true);
+       }
+
+       if (!parse_proxy_addresses(value, &lookup->proxy_src_addr,
+                                  &lookup->proxy_dst_addr))
+       {
+               return (false);
+       }
+       return (true);
+}
+
+static bool
+plus_proxy_options(const char *cmd, const char *value, const bool state,
+                  dig_lookup_t *lookup) {
+       switch (cmd[5]) {
+       case '-':
+               FULLCHECK("proxy-plain");
+               lookup->proxy_plain = state;
+               if (!plus_proxy_handle_addresses(value, state, lookup)) {
+                       goto invalid_option;
+               }
+               break;
+       case '\0':
+               FULLCHECK("proxy");
+               if (!plus_proxy_handle_addresses(value, state, lookup)) {
+                       goto invalid_option;
+               }
+               break;
+       default:
+               goto invalid_option;
+       }
+       return (true);
+
+invalid_option:
+       return (false);
+}
+
 static bool
 plus_tls_options(const char *cmd, const char *value, const bool state,
                 dig_lookup_t *lookup) {
@@ -1797,19 +2149,30 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
                }
                break;
        case 'p':
-               FULLCHECK("padding");
-               if (state && lookup->edns == -1) {
-                       lookup->edns = DEFAULT_EDNS_VERSION;
-               }
-               if (value == NULL) {
-                       goto need_value;
-               }
-               result = parse_uint(&num, value, 512, "padding");
-               if (result != ISC_R_SUCCESS) {
-                       warn("Couldn't parse padding");
-                       goto exit_or_usage;
+               switch (cmd[1]) {
+               case 'a':
+                       FULLCHECK("padding");
+                       if (state && lookup->edns == -1) {
+                               lookup->edns = DEFAULT_EDNS_VERSION;
+                       }
+                       if (value == NULL) {
+                               goto need_value;
+                       }
+                       result = parse_uint(&num, value, 512, "padding");
+                       if (result != ISC_R_SUCCESS) {
+                               warn("Couldn't parse padding");
+                               goto exit_or_usage;
+                       }
+                       lookup->padding = (uint16_t)num;
+                       break;
+               case 'r':
+                       if (!plus_proxy_options(cmd, value, state, lookup)) {
+                               goto invalid_option;
+                       }
+                       break;
+               default:
+                       goto invalid_option;
                }
-               lookup->padding = (uint16_t)num;
                break;
        case 'q':
                switch (cmd[1]) {
index 901e7837c8d4a3aa404ed4bcb56bddaf0c00c7d3..571614c07b6d373495aa146ea06a3a9ca09c0674 100644 (file)
@@ -794,6 +794,11 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
        looknew->rrcomments = lookold->rrcomments;
        looknew->fuzzing = lookold->fuzzing;
        looknew->fuzztime = lookold->fuzztime;
+       looknew->proxy_mode = lookold->proxy_mode;
+       looknew->proxy_plain = lookold->proxy_plain;
+       looknew->proxy_local = lookold->proxy_local;
+       looknew->proxy_src_addr = lookold->proxy_src_addr;
+       looknew->proxy_dst_addr = lookold->proxy_dst_addr;
 
        if (lookold->ecs_addr != NULL) {
                looknew->ecs_addr = isc_mem_get(mctx,
@@ -2921,6 +2926,9 @@ start_tcp(dig_query_t *query) {
        bool tls_mode = false;
        isc_tlsctx_client_session_cache_t *sess_cache = NULL;
        int local_timeout;
+       isc_nm_proxy_type_t proxy_type = ISC_NM_PROXY_NONE;
+       isc_nm_proxyheader_info_t proxy_info = { 0 };
+       isc_nm_proxyheader_info_t *ppi = NULL;
 
        REQUIRE(DIG_VALID_QUERY(query));
 
@@ -3012,6 +3020,22 @@ start_tcp(dig_query_t *query) {
                }
        }
 
+       if (query->lookup->proxy_mode) {
+               proxy_type = ISC_NM_PROXY_PLAIN;
+               if ((tls_mode || (query->lookup->https_mode &&
+                                 !query->lookup->http_plain)) &&
+                   !query->lookup->proxy_plain)
+               {
+                       proxy_type = ISC_NM_PROXY_ENCRYPTED;
+               }
+               if (!query->lookup->proxy_local) {
+                       isc_nm_proxyheader_info_init(
+                               &proxy_info, &query->lookup->proxy_src_addr,
+                               &query->lookup->proxy_dst_addr, NULL);
+                       ppi = &proxy_info;
+               }
+       }
+
        REQUIRE(query != NULL);
 
        query_attach(query, &connectquery);
@@ -3025,7 +3049,7 @@ start_tcp(dig_query_t *query) {
                isc_nm_streamdnsconnect(netmgr, &localaddr, &query->sockaddr,
                                        tcp_connected, connectquery,
                                        local_timeout, tlsctx, sess_cache,
-                                       ISC_NM_PROXY_NONE, NULL);
+                                       proxy_type, ppi);
 #if HAVE_LIBNGHTTP2
        } else if (query->lookup->https_mode) {
                char uri[4096] = { 0 };
@@ -3045,13 +3069,13 @@ start_tcp(dig_query_t *query) {
                isc_nm_httpconnect(netmgr, &localaddr, &query->sockaddr, uri,
                                   !query->lookup->https_get, tcp_connected,
                                   connectquery, tlsctx, sess_cache,
-                                  local_timeout, ISC_NM_PROXY_NONE, NULL);
+                                  local_timeout, proxy_type, ppi);
 #endif
        } else {
                isc_nm_streamdnsconnect(netmgr, &localaddr, &query->sockaddr,
                                        tcp_connected, connectquery,
-                                       local_timeout, NULL, NULL,
-                                       ISC_NM_PROXY_NONE, NULL);
+                                       local_timeout, NULL, NULL, proxy_type,
+                                       ppi);
        }
 
        return;
@@ -3301,9 +3325,24 @@ start_udp(dig_query_t *query) {
        }
 
        query_attach(query, &connectquery);
-       isc_nm_udpconnect(netmgr, &localaddr, &query->sockaddr, udp_ready,
-                         connectquery,
-                         (timeout ? timeout : UDP_TIMEOUT) * 1000);
+       if (query->lookup->proxy_mode) {
+               isc_nm_proxyheader_info_t proxy_info = { 0 };
+               isc_nm_proxyheader_info_t *ppi = NULL;
+               if (!query->lookup->proxy_local) {
+                       isc_nm_proxyheader_info_init(
+                               &proxy_info, &query->lookup->proxy_src_addr,
+                               &query->lookup->proxy_dst_addr, NULL);
+                       ppi = &proxy_info;
+               }
+               isc_nm_proxyudpconnect(netmgr, &localaddr, &query->sockaddr,
+                                      udp_ready, connectquery,
+                                      (timeout ? timeout : UDP_TIMEOUT) * 1000,
+                                      ppi);
+       } else {
+               isc_nm_udpconnect(netmgr, &localaddr, &query->sockaddr,
+                                 udp_ready, connectquery,
+                                 (timeout ? timeout : UDP_TIMEOUT) * 1000);
+       }
 }
 
 /*%
index af4379ce4cc6e9ba131d96d23d2312e06d5e36a7..799fb4a7d831bfd9d3572b692fea197d3514c368 100644 (file)
@@ -187,6 +187,13 @@ struct dig_lookup {
                char *tls_key_file;
                isc_tlsctx_cache_t *tls_ctx_cache;
        };
+       struct {
+               bool proxy_mode;
+               bool proxy_plain;
+               bool proxy_local;
+               isc_sockaddr_t proxy_src_addr;
+               isc_sockaddr_t proxy_dst_addr;
+       };
        isc_stdtime_t fuzztime;
 };