From d7b9756a214030b0022ce791b67b12fb7bceeea0 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 19 Feb 2014 15:51:02 -0800 Subject: [PATCH] [master] ENDS client-subnet in dig 3749. [func] "dig +subnet" sends an EDNS client subnet option containing the specified address/prefix when querying. (Thanks to Wilmer van der Gaast.) [RT #35415] --- CHANGES | 5 ++ README | 2 + bin/dig/dig.c | 18 +++++ bin/dig/dig.docbook | 10 +++ bin/dig/dighost.c | 134 ++++++++++++++++++++++++++++++++++++-- bin/dig/include/dig/dig.h | 4 ++ lib/dns/message.c | 42 ++++++++++++ 7 files changed, 208 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 01db1c2918..f4eaf4ce4f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +3749. [func] "dig +subnet" sends an EDNS client subnet option + containing the specified address/prefix when + querying. (Thanks to Wilmer van der Gaast.) + [RT #35415] + 3748. [test] Use delve to test dns_client interfaces. [RT #35383] 3747. [bug] A race condition could lead to a core dump when diff --git a/README b/README index 604ee31b26..b98de2cb39 100644 --- a/README +++ b/README @@ -127,6 +127,8 @@ BIND 9.10.0 zones. This can simplify the process of rolling DNSSEC keys by guaranteeing that cached signatures will have expired within the specified amount of time. + - "dig +subnet" sends an EDNS client-subnet option when + querying. - New "dnssec-coverage" tool to check DNSSEC key coverage for a zone and report if a lapse in signing coverage has been inadvertently scheduled. diff --git a/bin/dig/dig.c b/bin/dig/dig.c index adafdb660d..dd1946fc90 100644 --- a/bin/dig/dig.c +++ b/bin/dig/dig.c @@ -192,6 +192,7 @@ help(void) { " +domain=### (Set default domainname)\n" " +bufsize=### (Set EDNS0 Max UDP packet size)\n" " +ndots=### (Set NDOTS value)\n" +" +subnet=addr (Set edns-client-subnet option)\n" " +[no]edns[=###] (Set EDNS version) [0]\n" " +[no]search (Set whether to use searchlist)\n" " +[no]showsearch (Search with intermediate results)\n" @@ -1156,6 +1157,23 @@ plus_option(char *option, isc_boolean_t is_batchfile, FULLCHECK("stats"); lookup->stats = state; break; + case 'u': /* subnet */ + FULLCHECK("subnet"); + if (state && value == NULL) + goto need_value; + if (!state) { + if (lookup->ecs_addr != NULL) { + isc_mem_free(mctx, lookup->ecs_addr); + lookup->ecs_addr = NULL; + } + break; + } + if (lookup->edns == -1) + lookup->edns = 0; + result = parse_netprefix(&lookup->ecs_addr, value); + if (result != ISC_R_SUCCESS) + fatal("Couldn't parse client"); + break; default: goto invalid_option; } diff --git a/bin/dig/dig.docbook b/bin/dig/dig.docbook index 2db8a5b9f2..e918822839 100644 --- a/bin/dig/dig.docbook +++ b/bin/dig/dig.docbook @@ -936,6 +936,16 @@ + + + + + Send an EDNS Client Subnet option with the speciifed + IP address or network prefix. + + + + diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index 79381accd2..6eede93221 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -78,14 +78,13 @@ #include #include #include -#ifdef DIG_SIGCHASE #include -#endif #include #include #include #include #include +#include #include #include #include @@ -807,6 +806,7 @@ make_empty_lookup(void) { looknew->new_search = ISC_FALSE; looknew->done_as_is = ISC_FALSE; looknew->need_search = ISC_FALSE; + looknew->ecs_addr = NULL; #ifdef ISC_PLATFORM_USESIT looknew->sitvalue = NULL; #endif @@ -891,6 +891,12 @@ clone_lookup(dig_lookup_t *lookold, isc_boolean_t servers) { looknew->need_search = lookold->need_search; looknew->done_as_is = lookold->done_as_is; + if (lookold->ecs_addr != NULL) { + size_t len = sizeof(isc_sockaddr_t); + looknew->ecs_addr = isc_mem_allocate(mctx, len); + memmove(looknew->ecs_addr, lookold->ecs_addr, len); + } + if (servers) clone_server_list(lookold->my_server_list, &looknew->my_server_list); @@ -1005,6 +1011,65 @@ parse_bits(char *arg, const char *desc, isc_uint32_t max) { return (tmp); } +isc_result_t +parse_netprefix(isc_sockaddr_t **sap, const char *value) { + isc_result_t result = ISC_R_SUCCESS; + isc_sockaddr_t *sa = NULL; + struct in_addr in4; + struct in6_addr in6; + isc_uint32_t netmask = 0; + char *slash = NULL; + isc_boolean_t parsed = ISC_FALSE; + + if ((slash = strchr(value, '/'))) { + *slash = '\0'; + result = isc_parse_uint32(&netmask, slash + 1, 10); + if (result != ISC_R_SUCCESS) { + *slash = '/'; + fatal("invalid prefix length '%s': %s\n", + value, isc_result_totext(result)); + } + } + + sa = isc_mem_allocate(mctx, sizeof(*sa)); + if (inet_pton(AF_INET6, value, &in6) == 1) { + isc_sockaddr_fromin6(sa, &in6, 0); + parsed = ISC_TRUE; + if (netmask == 0 || netmask > 128) + netmask = 128; + } else if (inet_pton(AF_INET, value, &in4) == 1) { + parsed = ISC_TRUE; + isc_sockaddr_fromin(sa, &in4, 0); + if (netmask == 0 || netmask > 32) + netmask = 32; + } else if (netmask != 0) { + char buf[64]; + int i; + + strlcpy(buf, value, sizeof(buf)); + for (i = 0; i < 3; i++) { + strlcat(buf, ".0", sizeof(buf)); + if (inet_pton(AF_INET, buf, &in4) == 1) { + parsed = ISC_TRUE; + isc_sockaddr_fromin(sa, &in4, 0); + break; + } + } + + } + + if (slash != NULL) + *slash = '/'; + + if (!parsed) + fatal("invalid address '%s'", value); + + sa->length = netmask; + *sap = sa; + + return (ISC_R_SUCCESS); +} + /* * Parse HMAC algorithm specification @@ -1394,7 +1459,8 @@ setup_libs(void) { /*% * Add EDNS0 option record to a message. Currently, the only supported - * options are UDP buffer size, the DO bit, and NSID request. + * options are UDP buffer size, the DO bit, and EDNS options + * (e.g., NSID, SIT, client-subnet) */ static void add_opt(dns_message_t *msg, isc_uint16_t udpsize, isc_uint16_t edns, @@ -1568,6 +1634,9 @@ destroy_lookup(dig_lookup_t *lookup) { if (lookup->tsigctx != NULL) dst_context_destroy(&lookup->tsigctx); + if (lookup->ecs_addr != NULL) + isc_mem_free(mctx, lookup->ecs_addr); + isc_mem_free(mctx, lookup); } @@ -2022,6 +2091,10 @@ setup_lookup(dig_lookup_t *lookup) { isc_buffer_t b; dns_compress_t cctx; char store[MXNAME]; + char ecsbuf[20]; +#ifdef ISC_PLATFORM_USESIT + char sitbuf[256]; +#endif #ifdef WITH_IDN idn_result_t mr; char utf8_textname[MXNAME], utf8_origin[MXNAME], idn_textname[MXNAME]; @@ -2273,14 +2346,18 @@ setup_lookup(dig_lookup_t *lookup) { result = dns_message_renderbegin(lookup->sendmsg, &cctx, &lookup->renderbuf); check_result(result, "dns_message_renderbegin"); - if (lookup->udpsize > 0 || lookup->dnssec || lookup->edns > -1) { -#define EDNSOPTS 2 + if (lookup->udpsize > 0 || lookup->dnssec || + lookup->edns > -1 || lookup->ecs_addr != NULL) + { +#define EDNSOPTS 3 dns_ednsopt_t opts[EDNSOPTS]; int i = 0; + if (lookup->udpsize == 0) lookup->udpsize = 4096; if (lookup->edns < 0) lookup->edns = 0; + if (lookup->nsid) { INSIST(i < EDNSOPTS); opts[i].code = DNS_OPT_NSID; @@ -2288,15 +2365,56 @@ setup_lookup(dig_lookup_t *lookup) { opts[i].value = NULL; i++; } + + if (lookup->ecs_addr != NULL) { + isc_uint32_t prefixlen; + struct sockaddr *sa; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + size_t addrl; + isc_buffer_t b; + + sa = &lookup->ecs_addr->type.sa; + prefixlen = lookup->ecs_addr->length; + + /* Round up prefix len to a multiple of 8 */ + addrl = (prefixlen + 7) / 8; + + INSIST(i < EDNSOPTS); + opts[i].code = DNS_OPT_CLIENT_SUBNET; + opts[i].length = addrl + 4; + check_result(result, "isc_buffer_allocate"); + isc_buffer_init(&b, ecsbuf, sizeof(ecsbuf)); + if (sa->sa_family == AF_INET) { + sin = (struct sockaddr_in *) sa; + isc_buffer_putuint16(&b, 1); + isc_buffer_putuint8(&b, prefixlen); + isc_buffer_putuint8(&b, 0); + isc_buffer_putmem(&b, + (isc_uint8_t *) &sin->sin_addr, + addrl); + } else { + sin6 = (struct sockaddr_in6 *) sa; + isc_buffer_putuint16(&b, 2); + isc_buffer_putuint8(&b, prefixlen); + isc_buffer_putuint8(&b, 0); + isc_buffer_putmem(&b, + (isc_uint8_t *) &sin6->sin6_addr, + addrl); + } + + opts[i].value = (isc_uint8_t *) ecsbuf; + i++; + } + #ifdef ISC_PLATFORM_USESIT if (lookup->sit) { INSIST(i < EDNSOPTS); opts[i].code = DNS_OPT_SIT; if (lookup->sitvalue != NULL) { - char bb[256]; isc_buffer_t b; - isc_buffer_init(&b, bb, sizeof(bb)); + isc_buffer_init(&b, sitbuf, sizeof(sitbuf)); result = isc_hex_decodestring(lookup->sitvalue, &b); check_result(result, "isc_hex_decodestring"); @@ -2310,6 +2428,7 @@ setup_lookup(dig_lookup_t *lookup) { i++; } #endif + add_opt(lookup->sendmsg, lookup->udpsize, lookup->edns, lookup->dnssec, opts, i); } @@ -2377,6 +2496,7 @@ setup_lookup(dig_lookup_t *lookup) { ISC_LINK_INIT(query, link); ISC_LIST_ENQUEUE(lookup->q, query, link); } + /* XXX qrflag, print_query, etc... */ if (!ISC_LIST_EMPTY(lookup->q) && qr) { extrabytes = 0; diff --git a/bin/dig/include/dig/dig.h b/bin/dig/include/dig/dig.h index 83cd24d2ec..ad4c7c2ab2 100644 --- a/bin/dig/include/dig/dig.h +++ b/bin/dig/include/dig/dig.h @@ -187,6 +187,7 @@ isc_boolean_t sigchase; isc_buffer_t *querysig; isc_uint32_t msgcounter; dns_fixedname_t fdomain; + isc_sockaddr_t *ecs_addr; #ifdef ISC_PLATFORM_USESIT char *sitvalue; #endif @@ -343,6 +344,9 @@ isc_result_t parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, const char *desc); +isc_result_t +parse_netprefix(isc_sockaddr_t **sap, const char *value); + void parse_hmac(const char *hmacstr); diff --git a/lib/dns/message.c b/lib/dns/message.c index 20358195ca..2b40cc64de 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -3191,6 +3191,43 @@ dns_message_sectiontotext(dns_message_t *msg, dns_section_t section, return (result); } +static isc_result_t +render_ecs(isc_buffer_t *optbuf, isc_buffer_t *target) { + int i; + char addr[16], addr_text[64]; + isc_uint16_t family; + isc_uint8_t addrlen, addrbytes, scopelen; + + INSIST(isc_buffer_remaininglength(optbuf) >= 4); + family = isc_buffer_getuint16(optbuf); + addrlen = isc_buffer_getuint8(optbuf); + scopelen = isc_buffer_getuint8(optbuf); + + addrbytes = (addrlen + 7) / 8; + INSIST(isc_buffer_remaininglength(optbuf) >= addrbytes); + + memset(addr, 0, sizeof(addr)); + for (i = 0; i < addrbytes; i ++) + addr[i] = isc_buffer_getuint8(optbuf); + + if (family == 1) + inet_ntop(AF_INET, addr, addr_text, sizeof(addr_text)); + else if (family == 2) + inet_ntop(AF_INET6, addr, addr_text, sizeof(addr_text)); + else { + snprintf(addr_text, sizeof(addr_text), + "Unsupported family %d", + family); + ADD_STRING(target, addr_text); + return (ISC_R_SUCCESS); + } + + ADD_STRING(target, addr_text); + sprintf(addr_text, "/%d/%d", addrlen, scopelen); + ADD_STRING(target, addr_text); + return (ISC_R_SUCCESS); +} + isc_result_t dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, @@ -3257,6 +3294,11 @@ dns_message_pseudosectiontotext(dns_message_t *msg, ADD_STRING(target, "; NSID"); } else if (optcode == DNS_OPT_SIT) { ADD_STRING(target, "; SIT"); + } else if (optcode == DNS_OPT_CLIENT_SUBNET) { + ADD_STRING(target, "; CLIENT-SUBNET: "); + render_ecs(&optbuf, target); + ADD_STRING(target, "\n"); + break; } else { ADD_STRING(target, "; OPT="); sprintf(buf, "%u", optcode); -- GitLab