diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index dddb615a..e0a5e387 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -657,6 +657,7 @@ struct hostsfile { #define FREC_TEST_PKTSZ 256 #define FREC_HAS_EXTRADATA 512 #define FREC_HAS_PHEADER 1024 +#define FREC_NO_CACHE 2048 #define HASH_SIZE 32 /* SHA-256 digest size */ @@ -1674,7 +1675,7 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace); size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit); size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, - union mysockaddr *source, time_t now, int *check_subnet); + union mysockaddr *source, time_t now, int *check_subnet, int *cacheable); int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); /* arp.c */ diff --git a/src/dnsmasq/edns0.c b/src/dnsmasq/edns0.c index d75d3cc1..53cfe240 100644 --- a/src/dnsmasq/edns0.c +++ b/src/dnsmasq/edns0.c @@ -264,7 +264,8 @@ static void encoder(unsigned char *in, char *out) out[3] = char64(in[2]); } -static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *l3, time_t now, int *cacheablep) { int maclen, replace = 2; /* can't get mac address, just delete any incoming. */ unsigned char mac[DHCP_CHADDR_MAX]; @@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch if ((maclen = find_mac(l3, mac, 1, now)) == 6) { replace = 1; + *cacheablep = 0; if (option_bool(OPT_MAC_HEX)) print_mac(encode, mac, maclen); @@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch } -static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) +static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, + union mysockaddr *l3, time_t now, int *cacheablep) { int maclen; unsigned char mac[DHCP_CHADDR_MAX]; if ((maclen = find_mac(l3, mac, 1, now)) != 0) - plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); - + { + *cacheablep = 0; + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); + } + return plen; } @@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr *addr, const short family) return &addr->in.sin_addr; } -static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) +static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep) { /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ int len; void *addrp = NULL; int sa_family = source->sa.sa_family; - + int cacheable = 0; + opt->source_netmask = 0; opt->scope_netmask = 0; - + if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6) { opt->source_netmask = daemon->add_subnet6->mask; @@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) { sa_family = daemon->add_subnet6->addr.sa.sa_family; addrp = get_addrp(&daemon->add_subnet6->addr, sa_family); + cacheable = 1; } else addrp = &source->in6.sin6_addr; @@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) { sa_family = daemon->add_subnet4->addr.sa.sa_family; addrp = get_addrp(&daemon->add_subnet4->addr, sa_family); + cacheable = 1; /* Address is constant */ } else addrp = &source->in.sin_addr; @@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) opt->family = htons(sa_family == AF_INET6 ? 2 : 1); - len = 0; - if (addrp && opt->source_netmask != 0) { len = ((opt->source_netmask - 1) >> 3) + 1; @@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) if (opt->source_netmask & 7) opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); } + else + { + cacheable = 1; /* No address ever supplied. */ + len = 0; + } + + if (cacheablep) + *cacheablep = cacheable; return len + 4; } -static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source) +static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable) { /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ int len; struct subnet_opt opt; - len = calc_subnet_opt(&opt, source); + len = calc_subnet_opt(&opt, source, cacheable); return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0); } @@ -383,18 +398,18 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe unsigned char *p; int code, i, rdlen; - calc_len = calc_subnet_opt(&opt, peer); + calc_len = calc_subnet_opt(&opt, peer, NULL); - if (!(p = skip_name(pseudoheader, header, plen, 10))) - return 1; - - p += 8; /* skip UDP length and RCODE */ - - GETSHORT(rdlen, p); - if (!CHECK_LEN(header, p, plen, rdlen)) - return 1; /* bad packet */ - - /* check if option there */ + if (!(p = skip_name(pseudoheader, header, plen, 10))) + return 1; + + p += 8; /* skip UDP length and RCODE */ + + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return 1; /* bad packet */ + + /* check if option there */ for (i = 0; i + 4 < rdlen; i += len + 4) { GETSHORT(code, p); @@ -412,24 +427,28 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe return 1; } +/* Set *check_subnet if we add a client subnet option, which needs to checked + in the reply. Set *cacheable to zero if we add an option which the answer + may depend on. */ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, - union mysockaddr *source, time_t now, int *check_subnet) + union mysockaddr *source, time_t now, int *check_subnet, int *cacheable) { *check_subnet = 0; - + *cacheable = 1; + if (option_bool(OPT_ADD_MAC)) - plen = add_mac(header, plen, limit, source, now); + plen = add_mac(header, plen, limit, source, now, cacheable); if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) - plen = add_dns_client(header, plen, limit, source, now); - + plen = add_dns_client(header, plen, limit, source, now, cacheable); + if (daemon->dns_client_id) plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); if (option_bool(OPT_CLIENT_SUBNET)) { - plen = add_source_addr(header, plen, limit, source); + plen = add_source_addr(header, plen, limit, source, cacheable); *check_subnet = 1; } diff --git a/src/dnsmasq/forward.c b/src/dnsmasq/forward.c index 6810d0d3..7ac35ec8 100644 --- a/src/dnsmasq/forward.c +++ b/src/dnsmasq/forward.c @@ -365,13 +365,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, { /* Query from new source, but the same query may be in progress from another source. If so, just add this client to the - list that will get the reply. + list that will get the reply.*/ - Note that is the EDNS client subnet option is in use, we can't do this, - as the clients (and therefore query EDNS options) will be different - for each query. The EDNS subnet code has checks to avoid - attacks in this case. */ - if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags))) + if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && + (forward = lookup_frec_by_query(hash, fwd_flags))) { /* Note whine_malloc() zeros memory. */ if (!daemon->free_frec_src && @@ -468,18 +465,21 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (!flags && forward) { struct server *firstsentto = start; - int subnet, forwarded = 0; + int subnet, cacheable, forwarded = 0; size_t edns0_len; unsigned char *pheader; /* If a query is retried, use the log_id for the retry when logging the answer. */ forward->frec_src.log_id = daemon->log_id; - plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet); + plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable); if (subnet) forward->flags |= FREC_HAS_SUBNET; - + + if (!cacheable) + forward->flags |= FREC_NO_CACHE; + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) { @@ -671,7 +671,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } } #endif - + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) { /* Get extended RCODE. */ @@ -1297,6 +1297,11 @@ void reply_query(int fd, int family, time_t now) header->hb4 |= HB4_CD; else header->hb4 &= ~HB4_CD; + + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (forward->flags & FREC_NO_CACHE) + no_cache_dnssec = 1; if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, @@ -1895,7 +1900,7 @@ unsigned char *tcp_request(int confd, time_t now, int local_auth = 0; #endif int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; - int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; unsigned short qtype; unsigned int gotname; @@ -2099,7 +2104,7 @@ unsigned char *tcp_request(int confd, time_t now, char *domain = NULL; unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); - size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); @@ -2287,6 +2292,11 @@ unsigned char *tcp_request(int confd, time_t now, break; } + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (!cacheable) + no_cache_dnssec = 1; + m = process_reply(header, now, last_server, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); @@ -2554,10 +2564,13 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) struct frec *f; /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below - ensures that no frec created for internal DNSSEC query can be returned here. */ + ensures that no frec created for internal DNSSEC query can be returned here. + + Similarly FREC_NO_CACHE is never set in flags, so a query which is + contigent on a particular source address EDNS0 option will never be matched. */ #define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ - | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY) + | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) for(f = daemon->frec_list; f; f = f->next) if (f->sentto &&