diff --git a/backport-0001-Handle-caching-with-EDNS-options-better.patch b/backport-0001-Handle-caching-with-EDNS-options-better.patch new file mode 100644 index 0000000000000000000000000000000000000000..506458019b0a94fb11bc8ab325a79210e541eb9a --- /dev/null +++ b/backport-0001-Handle-caching-with-EDNS-options-better.patch @@ -0,0 +1,361 @@ +From 25e63f1e56f5acdcf91893a1b92ad1e0f2f552d8 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 25 Nov 2020 21:17:52 +0000 +Subject: [PATCH] Handle caching with EDNS options better. + +If we add the EDNS client subnet option, or the client's +MAC address, then the reply we get back may very depending on +that. Since the cache is ignorant of such things, it's not safe to +cache such replies. This patch determines when a dangerous EDNS +option is being added and disables caching. + +Note that for much the same reason, we can't combine multiple +queries for the same question when dangerous EDNS options are +being added, and the code now handles that in the same way. This +query combining is required for security against cache poisoning, +so disabling the cache has a security function as well as a +correctness one. +--- + man/dnsmasq.8 | 4 ++-- + src/dnsmasq.h | 3 ++- + src/edns0.c | 75 +++++++++++++++++++++++++++++++++++++---------------------- + src/forward.c | 41 +++++++++++++++++++++----------- + 4 files changed, 78 insertions(+), 45 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index 7c6b405..ac7c9fa 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -692,8 +692,8 @@ still marks the request so that no upstream nameserver will add client + address information either. The default is zero for both IPv4 and + IPv6. Note that upstream nameservers may be configured to return + different results based on this information, but the dnsmasq cache +-does not take account. If a dnsmasq instance is configured such that +-different results may be encountered, caching should be disabled. ++does not take account. Caching is therefore disabled for such replies, ++unless the subnet address being added is constant. + + For example, + .B --add-subnet=24,96 +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index fa8c5ea..9f74c7a 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -655,6 +655,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 */ + +@@ -1658,7 +1659,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/edns0.c b/src/edns0.c +index d75d3cc..53cfe24 100644 +--- a/src/edns0.c ++++ b/src/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); +- +- if (!(p = skip_name(pseudoheader, header, plen, 10))) +- return 1; +- +- p += 8; /* skip UDP length and RCODE */ ++ calc_len = calc_subnet_opt(&opt, peer, NULL); + +- 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/forward.c b/src/forward.c +index d9b32b3..70b84d7 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -352,13 +352,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 && +@@ -455,18 +452,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) + { +@@ -650,7 +650,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. */ +@@ -1260,6 +1260,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, +@@ -1821,7 +1826,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; +@@ -1992,7 +1997,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); +@@ -2171,6 +2176,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); +@@ -2435,10 +2445,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 && +-- +1.8.3.1 + diff --git a/backport-0002-Fix-to-75e2f0aec33e58ef5b8d4d107d821c215a52827c.patch b/backport-0002-Fix-to-75e2f0aec33e58ef5b8d4d107d821c215a52827c.patch new file mode 100644 index 0000000000000000000000000000000000000000..3a233ffb15defe6ae70900c49ea32c105a1dcb8a --- /dev/null +++ b/backport-0002-Fix-to-75e2f0aec33e58ef5b8d4d107d821c215a52827c.patch @@ -0,0 +1,24 @@ +From 12af2b171de0d678d98583e2190789e544440e02 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 22 Jan 2021 18:24:03 +0000 +Subject: [PATCH] Fix to 75e2f0aec33e58ef5b8d4d107d821c215a52827c + +--- + src/forward.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/forward.c b/src/forward.c +index 43d0ae7..1def931 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -378,6 +378,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + new->dest = *dst_addr; + new->log_id = daemon->log_id; + new->iface = dst_iface; ++ forward->frec_src.fd = udpfd; + } + + return 1; +-- +1.8.3.1 + diff --git a/backport-0003-Fix-for-12af2b171de0d678d98583e2190789e544440e02.patch b/backport-0003-Fix-for-12af2b171de0d678d98583e2190789e544440e02.patch new file mode 100644 index 0000000000000000000000000000000000000000..f5fb4cd72fdc41e5d9d04cc301697155a6ef8501 --- /dev/null +++ b/backport-0003-Fix-for-12af2b171de0d678d98583e2190789e544440e02.patch @@ -0,0 +1,25 @@ +From 3f535da79e7a42104543ef5c7b5fa2bed819a78b Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 22 Jan 2021 22:26:25 +0000 +Subject: [PATCH] Fix for 12af2b171de0d678d98583e2190789e544440e02 + +--- + src/forward.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/forward.c b/src/forward.c +index 1def931..5c9cbbb 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -378,7 +378,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + new->dest = *dst_addr; + new->log_id = daemon->log_id; + new->iface = dst_iface; +- forward->frec_src.fd = udpfd; ++ new->fd = udpfd; + } + + return 1; +-- +1.8.3.1 + diff --git a/backport-0004-Fix-problem-with-DNS-retries-in-2.83-2.84.patch b/backport-0004-Fix-problem-with-DNS-retries-in-2.83-2.84.patch new file mode 100644 index 0000000000000000000000000000000000000000..d2b29df729a62ab58325afaf8992c08851b4ba0a --- /dev/null +++ b/backport-0004-Fix-problem-with-DNS-retries-in-2.83-2.84.patch @@ -0,0 +1,126 @@ +From 141a26f979b4bc959d8e866a295e24f8cf456920 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 17 Feb 2021 23:56:32 +0000 +Subject: [PATCH] Fix problem with DNS retries in 2.83/2.84. + +The new logic in 2.83/2.84 which merges distinct requests for the +same domain causes problems with clients which do retries as distinct +requests (differing IDs and/or source ports.) The retries just get +piggy-backed on the first, failed, request. + +The logic is now changed so that distinct requests for repeated +queries still get merged into a single ID/source port, but they now +always trigger a re-try upstream. + +Thanks to Nicholas Mu for his analysis. +--- + src/forward.c | 79 ++++++++++++++++++++++++++++++++--------------------------- + 1 file changed, 43 insertions(+), 36 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index 8fb0327..e82e14a 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -278,8 +278,46 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + fwd_flags |= FREC_DO_QUESTION; + #endif + +- /* may be no servers available. */ +- if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) ++ /* Check for retry on existing query from same source */ ++ if (!forward && (!(forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) ++ { ++ /* Maybe 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.*/ ++ ++ if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && ++ (forward = lookup_frec_by_query(hash, fwd_flags))) ++ { ++ struct frec_src *new; ++ ++ /* Note whine_malloc() zeros memory. */ ++ if (!daemon->free_frec_src && ++ daemon->frec_src_count < daemon->ftabsize && ++ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) ++ { ++ daemon->frec_src_count++; ++ daemon->free_frec_src->next = NULL; ++ } ++ ++ /* If we've been spammed with many duplicates, just drop the query. */ ++ if (!daemon->free_frec_src) ++ return 0; ++ ++ new = daemon->free_frec_src; ++ daemon->free_frec_src = new->next; ++ new->next = forward->frec_src.next; ++ forward->frec_src.next = new; ++ new->orig_id = ntohs(header->id); ++ new->source = *udpaddr; ++ new->dest = *dst_addr; ++ new->log_id = daemon->log_id; ++ new->iface = dst_iface; ++ new->fd = udpfd; ++ } ++ } ++ ++ /* retry existing query */ ++ if (forward) + { + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. +@@ -350,40 +388,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + } + else + { +- /* 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.*/ +- +- 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 && +- daemon->frec_src_count < daemon->ftabsize && +- (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) +- { +- daemon->frec_src_count++; +- daemon->free_frec_src->next = NULL; +- } +- +- /* If we've been spammed with many duplicates, just drop the query. */ +- if (daemon->free_frec_src) +- { +- struct frec_src *new = daemon->free_frec_src; +- daemon->free_frec_src = new->next; +- new->next = forward->frec_src.next; +- forward->frec_src.next = new; +- new->orig_id = ntohs(header->id); +- new->source = *udpaddr; +- new->dest = *dst_addr; +- new->log_id = daemon->log_id; +- new->iface = dst_iface; +- new->fd = udpfd; +- } +- +- return 1; +- } +- ++ /* new query */ ++ + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + +@@ -392,6 +398,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + #endif + type &= ~SERV_DO_DNSSEC; + ++ /* may be no servers available. */ + if (daemon->servers && !flags) + forward = get_new_frec(now, NULL, NULL); + /* table full - flags == 0, return REFUSED */ +-- +1.8.3.1 + diff --git a/backport-0005-Simplify-preceding-fix.patch b/backport-0005-Simplify-preceding-fix.patch new file mode 100644 index 0000000000000000000000000000000000000000..5f65aa0a22805145b84b874c39e74b49c54c9081 --- /dev/null +++ b/backport-0005-Simplify-preceding-fix.patch @@ -0,0 +1,114 @@ +From 305cb79c5754d5554729b18a2c06fe7ce699687a Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Thu, 18 Feb 2021 21:35:09 +0000 +Subject: [PATCH] Simplify preceding fix. + +Remove distinction between retry with same QID/SP and +retry for same query with different QID/SP. If the +QID/SP are the same as an existing one, simply retry, +if a new QID/SP is seen, add to the list to be replied to. +--- + src/forward.c | 64 ++++++++++++++++++++--------------------------------------- + 1 file changed, 22 insertions(+), 42 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index e82e14a..6bbf8a4 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -17,9 +17,6 @@ + #include "dnsmasq.h" + + static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); +-static struct frec *lookup_frec_by_sender(unsigned short id, +- union mysockaddr *addr, +- void *hash); + static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); + + static unsigned short get_id(void); +@@ -278,18 +275,20 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + fwd_flags |= FREC_DO_QUESTION; + #endif + +- /* Check for retry on existing query from same source */ +- if (!forward && (!(forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) ++ /* Check for retry on existing query */ ++ if (!forward && (forward = lookup_frec_by_query(hash, fwd_flags))) + { +- /* Maybe 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.*/ +- +- if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && +- (forward = lookup_frec_by_query(hash, fwd_flags))) +- { +- struct frec_src *new; ++ struct frec_src *src; ++ ++ for (src = &forward->frec_src; src; src = src->next) ++ if (src->orig_id == ntohs(header->id) && ++ sockaddr_isequal(&src->source, udpaddr)) ++ break; + ++ /* Existing query, but from new source, just add this ++ client to the list that will get the reply.*/ ++ if (!src) ++ { + /* Note whine_malloc() zeros memory. */ + if (!daemon->free_frec_src && + daemon->frec_src_count < daemon->ftabsize && +@@ -303,16 +302,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (!daemon->free_frec_src) + return 0; + +- new = daemon->free_frec_src; +- daemon->free_frec_src = new->next; +- new->next = forward->frec_src.next; +- forward->frec_src.next = new; +- new->orig_id = ntohs(header->id); +- new->source = *udpaddr; +- new->dest = *dst_addr; +- new->log_id = daemon->log_id; +- new->iface = dst_iface; +- new->fd = udpfd; ++ src = daemon->free_frec_src; ++ daemon->free_frec_src = src->next; ++ src->next = forward->frec_src.next; ++ forward->frec_src.next = src; ++ src->orig_id = ntohs(header->id); ++ src->source = *udpaddr; ++ src->dest = *dst_addr; ++ src->log_id = daemon->log_id; ++ src->iface = dst_iface; ++ src->fd = udpfd; + } + } + +@@ -2433,25 +2432,6 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has + return NULL; + } + +-static struct frec *lookup_frec_by_sender(unsigned short id, +- union mysockaddr *addr, +- void *hash) +-{ +- struct frec *f; +- struct frec_src *src; +- +- for (f = daemon->frec_list; f; f = f->next) +- if (f->sentto && +- !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && +- memcmp(hash, f->hash, HASH_SIZE) == 0) +- for (src = &f->frec_src; src; src = src->next) +- if (src->orig_id == id && +- sockaddr_isequal(&src->source, addr)) +- return f; +- +- return NULL; +-} +- + static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) + { + struct frec *f; +-- +1.8.3.1 + diff --git a/backport-0006-Update-to-new-struct-frec-fields-in-conntrack-code.patch b/backport-0006-Update-to-new-struct-frec-fields-in-conntrack-code.patch new file mode 100644 index 0000000000000000000000000000000000000000..fe02f16d9faa69dfca7115a9ee52454d4df92d6a --- /dev/null +++ b/backport-0006-Update-to-new-struct-frec-fields-in-conntrack-code.patch @@ -0,0 +1,34 @@ +From cc0b4489c782f6b90ca118abb18e716a7a831289 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 15 Jan 2021 22:21:52 +0000 +Subject: [PATCH] Update to new struct frec fields in conntrack code. + +--- + src/forward.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index f94c4cf..7a95ddf 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -538,7 +538,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; +- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) ++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } + #endif +@@ -1193,7 +1193,7 @@ void reply_query(int fd, int family, time_t now) + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; +- if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark)) ++ if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } + #endif +-- +1.8.3.1 + diff --git a/backport-0007-Use-the-values-of-min-port-and-max-port-in-TCP-conne.patch b/backport-0007-Use-the-values-of-min-port-and-max-port-in-TCP-conne.patch new file mode 100644 index 0000000000000000000000000000000000000000..a24a23c8ccedaeaa9e501f49b10d0dbc2c2ed867 --- /dev/null +++ b/backport-0007-Use-the-values-of-min-port-and-max-port-in-TCP-conne.patch @@ -0,0 +1,84 @@ +From a2a7e040b128a8ec369ba8f22beca2705435b85b Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Sat, 12 Dec 2020 23:26:45 +0000 +Subject: [PATCH] Use the values of --min-port and --max-port in TCP + connections. + +Rather that letting the kernel pick source ports, do it ourselves +so that the --min-port and --max-port parameters are be obeyed. +--- + CHANGELOG | 5 +++++ + src/network.c | 37 +++++++++++++++++++++++++++++++++---- + 2 files changed, 38 insertions(+), 4 deletions(-) + +diff --git a/CHANGELOG b/CHANGELOG +index e6a2231..34ad22e 100644 +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -1,3 +1,8 @@ ++version 2.83 ++ Use the values of --min-port and --max-port in outgoing ++ TCP connections to upstream DNS servers. ++ ++ + version 2.82 + Improve behaviour in the face of network interfaces which come + and go and change index. Thanks to Petr Mensik for the patch. +diff --git a/src/network.c b/src/network.c +index c7d002b..7cf2546 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -1262,17 +1262,46 @@ int random_sock(int family) + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) + { + union mysockaddr addr_copy = *addr; ++ unsigned short port; ++ int tries = 1, done = 0; ++ unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; ++ ++ if (addr_copy.sa.sa_family == AF_INET) ++ port = addr_copy.in.sin_port; ++ else ++ port = addr_copy.in6.sin6_port; + + /* cannot set source _port_ for TCP connections. */ + if (is_tcp) ++ port = 0; ++ ++ /* Bind a random port within the range given by min-port and max-port */ ++ if (port == 0) ++ { ++ tries = ports_avail < 30 ? 3 * ports_avail : 100; ++ port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); ++ } ++ ++ while (tries--) + { + if (addr_copy.sa.sa_family == AF_INET) +- addr_copy.in.sin_port = 0; ++ addr_copy.in.sin_port = port; + else +- addr_copy.in6.sin6_port = 0; ++ addr_copy.in6.sin6_port = port; ++ ++ if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1) ++ { ++ done = 1; ++ break; ++ } ++ ++ if (errno != EADDRINUSE && errno != EACCES) ++ return 0; ++ ++ port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); + } +- +- if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1) ++ ++ if (!done) + return 0; + + if (!is_tcp && ifindex > 0) +-- +1.8.3.1 + diff --git a/backport-0008-Correct-occasional-bind-dynamic-synchronization-brea.patch b/backport-0008-Correct-occasional-bind-dynamic-synchronization-brea.patch new file mode 100644 index 0000000000000000000000000000000000000000..4aca129a95a8ea8cbc7eb76a45cd70ed3223f6ad --- /dev/null +++ b/backport-0008-Correct-occasional-bind-dynamic-synchronization-brea.patch @@ -0,0 +1,252 @@ +From 4c0aecc68524c5fd74053244a605e905dc644228 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= +Date: Tue, 2 Mar 2021 18:21:32 +0000 +Subject: [PATCH] Correct occasional --bind-dynamic synchronization break + +Request only one re-read of addresses and/or routes + +Previous implementation re-reads systemd addresses exactly the same +number of time equal number of notifications received. +This is not necessary, we need just notification of change, then re-read +the current state and adapt listeners. Repeated re-reading slows netlink +processing and highers CPU usage on mass interface changes. + +Continue reading multicast events from netlink, even when ENOBUFS +arrive. Broadcasts are not trusted anyway and refresh would be done in +iface_enumerate. Save queued events sent again. + +Remove sleeping on netlink ENOBUFS + +With reduced number of written events netlink should receive ENOBUFS +rarely. It does not make sense to wait if it is received. It is just a +signal some packets got missing. Fast reading all pending packets is required, +seq checking ensures it already. Finishes changes by +commit 1d07667ac77c55b9de56b1b2c385167e0e0ec27a. + +Move restart from iface_enumerate to enumerate_interfaces + +When ENOBUFS is received, restart of reading addresses is done. But +previously found addresses might not have been found this time. In order +to catch this, restart both IPv4 and IPv6 enumeration with clearing +found interfaces first. It should deliver up-to-date state also after +ENOBUFS. + +Read all netlink messages before netlink restart + +Before writing again into netlink socket, try fetching all pending +messages. They would be ignored, only might trigger new address +synchronization. Should ensure new try has better chance to succeed. + +ENOBUFS error handling was improved. Netlink is correctly drained before +sending a new request again. It seems ENOBUFS supression is no longer +necessary or wanted. Let kernel tell us when it failed and handle it a +good way. +--- + src/netlink.c | 72 +++++++++++++++++++++++++++++++++++++++-------------------- + src/network.c | 14 ++++++++---- + 2 files changed, 58 insertions(+), 28 deletions(-) + +diff --git a/src/netlink.c b/src/netlink.c +index 0494070..3ad18a6 100644 +--- a/src/netlink.c ++++ b/src/netlink.c +@@ -41,19 +41,26 @@ + + #ifndef NDA_RTA + # define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +-#endif ++#endif ++ ++/* Used to request refresh of addresses or routes just once, ++ * when multiple changes might be announced. */ ++enum async_states { ++ STATE_NEWADDR = (1 << 0), ++ STATE_NEWROUTE = (1 << 1), ++}; + + + static struct iovec iov; + static u32 netlink_pid; + +-static void nl_async(struct nlmsghdr *h); ++static unsigned nl_async(struct nlmsghdr *h, unsigned state); ++static void nl_multicast_state(unsigned state); + + char *netlink_init(void) + { + struct sockaddr_nl addr; + socklen_t slen = sizeof(addr); +- int opt = 1; + + addr.nl_family = AF_NETLINK; + addr.nl_pad = 0; +@@ -92,10 +99,6 @@ char *netlink_init(void) + iov.iov_len = 100; + iov.iov_base = safe_malloc(iov.iov_len); + +- if (daemon->kernel_version >= KERNEL_VERSION(2,6,30) && +- setsockopt(daemon->netlinkfd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(opt)) == -1) +- return _("warning: failed to set NETLINK_NO_ENOBUFS on netlink socket"); +- + return NULL; + } + +@@ -151,7 +154,9 @@ static ssize_t netlink_recv(void) + + + /* family = AF_UNSPEC finds ARP table entries. +- family = AF_LOCAL finds MAC addresses. */ ++ family = AF_LOCAL finds MAC addresses. ++ returns 0 on failure, 1 on success, -1 when restart is required ++*/ + int iface_enumerate(int family, void *parm, int (*callback)()) + { + struct sockaddr_nl addr; +@@ -159,6 +164,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + ssize_t len; + static unsigned int seq = 0; + int callback_ok = 1; ++ unsigned state = 0; + + struct { + struct nlmsghdr nlh; +@@ -170,7 +176,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + + addr.nl_family = AF_NETLINK; + +- again: + if (family == AF_UNSPEC) + req.nlh.nlmsg_type = RTM_GETNEIGH; + else if (family == AF_LOCAL) +@@ -197,8 +202,8 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + { + if (errno == ENOBUFS) + { +- sleep(1); +- goto again; ++ nl_multicast_state(state); ++ return -1; + } + return 0; + } +@@ -207,7 +212,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) + { + /* May be multicast arriving async */ +- nl_async(h); ++ state = nl_async(h, state); + } + else if (h->nlmsg_seq != seq) + { +@@ -341,26 +346,36 @@ int iface_enumerate(int family, void *parm, int (*callback)()) + } + } + +-void netlink_multicast(void) ++static void nl_multicast_state(unsigned state) + { + ssize_t len; + struct nlmsghdr *h; + int flags; +- +- /* don't risk blocking reading netlink messages here. */ ++ + if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || + fcntl(daemon->netlinkfd, F_SETFL, flags | O_NONBLOCK) == -1) + return; ++ ++ do { ++ /* don't risk blocking reading netlink messages here. */ ++ while ((len = netlink_recv()) != -1) + +- if ((len = netlink_recv()) != -1) +- for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) +- nl_async(h); +- ++ for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) ++ state = nl_async(h, state); ++ } while (errno == ENOBUFS); ++ + /* restore non-blocking status */ + fcntl(daemon->netlinkfd, F_SETFL, flags); + } + +-static void nl_async(struct nlmsghdr *h) ++void netlink_multicast(void) ++{ ++ unsigned state = 0; ++ nl_multicast_state(state); ++} ++ ++ ++static unsigned nl_async(struct nlmsghdr *h, unsigned state) + { + if (h->nlmsg_type == NLMSG_ERROR) + { +@@ -368,7 +383,8 @@ static void nl_async(struct nlmsghdr *h) + if (err->error != 0) + my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); + } +- else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) ++ else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE && ++ (state & STATE_NEWROUTE)==0) + { + /* We arrange to receive netlink multicast messages whenever the network route is added. + If this happens and we still have a DNS packet in the buffer, we re-send it. +@@ -380,10 +396,18 @@ static void nl_async(struct nlmsghdr *h) + if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && + (rtm->rtm_table == RT_TABLE_MAIN || + rtm->rtm_table == RT_TABLE_LOCAL)) +- queue_event(EVENT_NEWROUTE); ++ { ++ queue_event(EVENT_NEWROUTE); ++ state |= STATE_NEWROUTE; ++ } ++ } ++ else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) && ++ (state & STATE_NEWADDR)==0) ++ { ++ queue_event(EVENT_NEWADDR); ++ state |= STATE_NEWADDR; + } +- else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) +- queue_event(EVENT_NEWADDR); ++ return state; + } + #endif + +diff --git a/src/network.c b/src/network.c +index 6cf15a9..77eb6ad 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -638,7 +638,8 @@ int enumerate_interfaces(int reset) + + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; +- ++ ++again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) + iface->found = 0; +@@ -690,9 +691,14 @@ int enumerate_interfaces(int reset) + param.spare = spare; + + ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); +- +- if (ret) +- ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ if (ret < 0) ++ goto again; ++ else if (ret) ++ { ++ ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); ++ if (ret < 0) ++ goto again; ++ } + + errsave = errno; + close(param.fd); +-- +1.8.3.1 + diff --git a/backport-0009-Move-fd-into-frec_src-fixes-15b60ddf935a531269bb8c68.patch b/backport-0009-Move-fd-into-frec_src-fixes-15b60ddf935a531269bb8c68.patch new file mode 100644 index 0000000000000000000000000000000000000000..fac271d2857d9cd9f7355844ce56e7db1664ae9b --- /dev/null +++ b/backport-0009-Move-fd-into-frec_src-fixes-15b60ddf935a531269bb8c68.patch @@ -0,0 +1,65 @@ +From 04490bf622ac84891aad6f2dd2edf83725decdee Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 22 Jan 2021 16:49:12 +0000 +Subject: [PATCH] Move fd into frec_src, fixes + 15b60ddf935a531269bb8c68198de012a4967156 + +If identical queries from IPv4 and IPv6 sources are combined by the +new code added in 15b60ddf935a531269bb8c68198de012a4967156 then replies +can end up being sent via the wrong family of socket. The ->fd +should be per query, not per-question. + +In bind-interfaces mode, this could also result in replies being sent +via the wrong socket even when IPv4/IPV6 issues are not in play. +--- + src/dnsmasq.h | 3 ++- + src/forward.c | 4 ++-- + 2 files changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 914f469..360c226 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -664,6 +664,7 @@ struct frec { + union mysockaddr source; + union all_addr dest; + unsigned int iface, log_id; ++ int fd; + unsigned short orig_id; + struct frec_src *next; + } frec_src; +@@ -671,7 +672,7 @@ struct frec { + struct randfd *rfd4; + struct randfd *rfd6; + unsigned short new_id; +- int fd, forwardall, flags; ++ int forwardall, flags; + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +diff --git a/src/forward.c b/src/forward.c +index 7a95ddf..43d0ae7 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -402,8 +402,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; + forward->frec_src.next = NULL; ++ forward->frec_src.fd = udpfd; + forward->new_id = get_id(); +- forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; + forward->flags = fwd_flags; +@@ -1300,7 +1300,7 @@ void reply_query(int fd, int family, time_t now) + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); + #endif + +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, ++ send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) +-- +1.8.3.1 + diff --git a/backport-0010-CVE-2021-3448.patch b/backport-0010-CVE-2021-3448.patch new file mode 100644 index 0000000000000000000000000000000000000000..00b1d6ef3ef73573686f76da006074fa1dfb4584 --- /dev/null +++ b/backport-0010-CVE-2021-3448.patch @@ -0,0 +1,1098 @@ +From 74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Mon, 15 Mar 2021 21:59:51 +0000 +Subject: [PATCH] Use random source ports where possible if source + addresses/interfaces in use. + +CVE-2021-3448 applies. + +It's possible to specify the source address or interface to be +used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4 +or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of +these have, until now, used a single socket, bound to a fixed +port. This was originally done to allow an error (non-existent +interface, or non-local address) to be detected at start-up. This +means that any upstream servers specified in such a way don't use +random source ports, and are more susceptible to cache-poisoning +attacks. + +We now use random ports where possible, even when the +source is specified, so server=8.8.8.8@1.2.3.4 or +server=8.8.8.8@eth0 will use random source +ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will +use the explicitly configured port, and should only be done with +understanding of the security implications. +Note that this change changes non-existing interface, or non-local +source address errors from fatal to run-time. The error will be +logged and communiction with the server not possible. +--- + man/dnsmasq.8 | 4 +- + src/dnsmasq.c | 31 +++-- + src/dnsmasq.h | 26 ++-- + src/forward.c | 392 +++++++++++++++++++++++++++++++++++----------------------- + src/loop.c | 20 ++- + src/network.c | 110 +++++----------- + src/option.c | 3 +- + src/tftp.c | 6 +- + src/util.c | 2 +- + 10 files changed, 344 insertions(+), 272 deletions(-) + +diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 +index 6dee5a4..4910bd2 100644 +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -431,7 +431,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots + or domain parts, to upstream nameservers. If the name is not known + from /etc/hosts or DHCP then a "not found" answer is returned. + .TP +-.B \-S, --local, --server=[/[]/[domain/]][[#]][@|[#]] ++.B \-S, --local, --server=[/[]/[domain/]][[#]][@][@[#]] + Specify IP address of upstream servers directly. Setting this flag does + not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more + optional domains are given, that server is used only for those domains +@@ -492,7 +492,7 @@ source address specified but the port may be specified directly as + part of the source address. Forcing queries to an interface is not + implemented on all platforms supported by dnsmasq. + .TP +-.B --rev-server=/[,][#][@|[#]] ++.B --rev-server=/[,][#][@][@[#]] + This is functionally the same as + .B --server, + but provides some syntactic sugar to make specifying address-to-name queries easier. For example +diff --git a/src/dnsmasq.c b/src/dnsmasq.c +index a2e1796..d1806fc 100644 +--- a/src/dnsmasq.c ++++ b/src/dnsmasq.c +@@ -1672,6 +1672,7 @@ static int set_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int wait = 0, i; + + #ifdef HAVE_TFTP +@@ -1692,11 +1693,14 @@ static int set_dns_listeners(time_t now) + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + poll_listen(serverfdp->fd, POLLIN); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0) +- poll_listen(daemon->randomsocks[i].fd, POLLIN); +- ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0) ++ poll_listen(daemon->randomsocks[i].fd, POLLIN); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ poll_listen(rfl->rfd->fd, POLLIN); ++ + for (listener = daemon->listeners; listener; listener = listener->next) + { + /* only listen for queries if we have resources */ +@@ -1733,18 +1737,23 @@ static void check_dns_listeners(time_t now) + { + struct serverfd *serverfdp; + struct listener *listener; ++ struct randfd_list *rfl; + int i; + int pipefd[2]; + + for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) + if (poll_check(serverfdp->fd, POLLIN)) +- reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); ++ reply_query(serverfdp->fd, now); + +- if (daemon->port != 0 && !daemon->osport) +- for (i = 0; i < RANDOM_SOCKS; i++) +- if (daemon->randomsocks[i].refcount != 0 && +- poll_check(daemon->randomsocks[i].fd, POLLIN)) +- reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && ++ poll_check(daemon->randomsocks[i].fd, POLLIN)) ++ reply_query(daemon->randomsocks[i].fd, now); ++ ++ /* Check overflow random sockets too. */ ++ for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) ++ if (poll_check(rfl->rfd->fd, POLLIN)) ++ reply_query(rfl->rfd->fd, now); + + /* Races. The child process can die before we read all of the data from the + pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index e201b7a..b0080d0 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -549,13 +549,20 @@ struct serverfd { + }; + + struct randfd { ++ struct server *serv; + int fd; +- unsigned short refcount, family; ++ unsigned short refcount; /* refcount == 0xffff means overflow record. */ + }; +- ++ ++struct randfd_list { ++ struct randfd *rfd; ++ struct randfd_list *next; ++}; ++ + struct server { + union mysockaddr addr, source_addr; + char interface[IF_NAMESIZE+1]; ++ unsigned int ifindex; /* corresponding to interface, above */ + struct serverfd *sfd; + char *domain; /* set if this server only handles a domain. */ + int flags, tcpfd, edns_pktsz; +@@ -679,8 +686,7 @@ struct frec { + struct frec_src *next; + } frec_src; + struct server *sentto; /* NULL means free */ +- struct randfd *rfd4; +- struct randfd *rfd6; ++ struct randfd_list *rfds; + unsigned short new_id; + int forwardall, flags; + time_t time; +@@ -1120,11 +1126,12 @@ extern struct daemon { + int forwardcount; + struct server *srv_save; /* Used for resend on DoD */ + size_t packet_len; /* " " */ +- struct randfd *rfd_save; /* " " */ ++ int fd_save; /* " " */ + pid_t tcp_pids[MAX_PROCS]; + int tcp_pipes[MAX_PROCS]; + int pipe_to_parent; + struct randfd randomsocks[RANDOM_SOCKS]; ++ struct randfd_list *rfl_spare, *rfl_poll; + int v6pktinfo; + struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ + int log_id, log_display_id; /* ids of transactions for logging */ +@@ -1296,7 +1303,7 @@ void safe_strncpy(char *dest, const char *src, size_t size); + void safe_pipe(int *fd, int read_noblock); + void *whine_malloc(size_t size); + int sa_len(union mysockaddr *addr); +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); + int hostname_isequal(const char *a, const char *b); + int hostname_issubdomain(char *a, char *b); + time_t dnsmasq_time(void); +@@ -1347,7 +1354,7 @@ char *parse_server(char *arg, union mysockaddr *addr, + int option_read_dynfile(char *file, int flags); + + /* forward.c */ +-void reply_query(int fd, int family, time_t now); ++void reply_query(int fd, time_t now); + void receive_query(struct listener *listen, time_t now); + unsigned char *tcp_request(int confd, time_t now, + union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); +@@ -1357,13 +1364,12 @@ int send_from(int fd, int nowild, char *packet, size_t len, + union mysockaddr *to, union all_addr *source, + unsigned int iface); + void resend_query(void); +-struct randfd *allocate_rfd(int family); +-void free_rfd(struct randfd *rfd); ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv); ++void free_rfds(struct randfd_list **fdlp); + + /* network.c */ + int indextoname(int fd, int index, char *name); + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); +-int random_sock(int family); + void pre_allocate_sfds(void); + int reload_servers(char *fname); + void mark_servers(int flag); +diff --git a/src/forward.c b/src/forward.c +index 6bbf8a4..a15ac10 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -16,7 +16,7 @@ + + #include "dnsmasq.h" + +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash); + static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); + + static unsigned short get_id(void); +@@ -344,26 +344,18 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(SAFE_PKTSZ, pheader); + +- if (forward->sentto->addr.sa.sa_family == AF_INET) +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); +- else +- log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); +- +- +- if (forward->sentto->sfd) +- fd = forward->sentto->sfd->fd; +- else ++ if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) + { +- if (forward->sentto->addr.sa.sa_family == AF_INET6) +- fd = forward->rfd6->fd; ++ if (forward->sentto->addr.sa.sa_family == AF_INET) ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); + else +- fd = forward->rfd4->fd; ++ log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); ++ ++ while (retry_send(sendto(fd, (char *)header, plen, 0, ++ &forward->sentto->addr.sa, ++ sa_len(&forward->sentto->addr)))); + } + +- while (retry_send(sendto(fd, (char *)header, plen, 0, +- &forward->sentto->addr.sa, +- sa_len(&forward->sentto->addr)))); +- + return 1; + } + #endif +@@ -508,48 +500,27 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + + while (1) + { ++ int fd; ++ + /* only send to servers dealing with our domain. + domain may be NULL, in which case server->domain + must be NULL also. */ + + if (type == (start->flags & SERV_TYPE) && + (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && +- !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) ++ !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ ((fd = allocate_rfd(&forward->rfds, start)) != -1)) + { +- int fd; +- +- /* find server socket to use, may need to get random one. */ +- if (start->sfd) +- fd = start->sfd->fd; +- else +- { +- if (start->addr.sa.sa_family == AF_INET6) +- { +- if (!forward->rfd6 && +- !(forward->rfd6 = allocate_rfd(AF_INET6))) +- break; +- daemon->rfd_save = forward->rfd6; +- fd = forward->rfd6->fd; +- } +- else +- { +- if (!forward->rfd4 && +- !(forward->rfd4 = allocate_rfd(AF_INET))) +- break; +- daemon->rfd_save = forward->rfd4; +- fd = forward->rfd4->fd; +- } +- ++ + #ifdef HAVE_CONNTRACK +- /* Copy connection mark of incoming query to outgoing connection. */ +- if (option_bool(OPT_CONNTRACK)) +- { +- unsigned int mark; +- if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) +- setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +- } +-#endif ++ /* Copy connection mark of incoming query to outgoing connection. */ ++ if (option_bool(OPT_CONNTRACK)) ++ { ++ unsigned int mark; ++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) ++ setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } ++#endif + + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) +@@ -581,6 +552,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + /* Keep info in case we want to re-send this packet */ + daemon->srv_save = start; + daemon->packet_len = plen; ++ daemon->fd_save = fd; + + if (!gotname) + strcpy(daemon->namebuff, "query"); +@@ -597,7 +569,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + break; + forward->forwardall++; + } +- } ++ } + + if (!(start = start->next)) + start = daemon->servers; +@@ -812,7 +784,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server + } + + /* sets new last_server */ +-void reply_query(int fd, int family, time_t now) ++void reply_query(int fd, time_t now) + { + /* packet from peer server, extract data for cache, and send to + original requester */ +@@ -827,9 +799,9 @@ void reply_query(int fd, int family, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + /* Determine the address of the server replying so that we can mark that as good */ +- if ((serveraddr.sa.sa_family = family) == AF_INET6) ++ if (serveraddr.sa.sa_family == AF_INET6) + serveraddr.in6.sin6_flowinfo = 0; + + header = (struct dns_header *)daemon->packet; +@@ -852,7 +824,7 @@ void reply_query(int fd, int family, time_t now) + + hash = hash_questions(header, n, daemon->namebuff); + +- if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) ++ if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) + return; + + #ifdef HAVE_DUMPFILE +@@ -906,28 +878,7 @@ void reply_query(int fd, int family, time_t now) + } + + +- fd = -1; +- +- if (start->sfd) +- fd = start->sfd->fd; +- else +- { +- if (start->addr.sa.sa_family == AF_INET6) +- { +- /* may have changed family */ +- if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6))) +- fd = forward->rfd6->fd; +- } +- else +- { +- /* may have changed family */ +- if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET))) +- fd = forward->rfd4->fd; +- } +- } +- +- /* Can't get socket. */ +- if (fd == -1) ++ if ((fd = allocate_rfd(&forward->rfds, start)) == -1) + return; + + #ifdef HAVE_DUMPFILE +@@ -1136,8 +1087,7 @@ void reply_query(int fd, int family, time_t now) + } + + new->sentto = server; +- new->rfd4 = NULL; +- new->rfd6 = NULL; ++ new->rfds = NULL; + new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); + new->forwardall = 0; +@@ -1176,24 +1126,7 @@ void reply_query(int fd, int family, time_t now) + /* Don't resend this. */ + daemon->srv_save = NULL; + +- if (server->sfd) +- fd = server->sfd->fd; +- else +- { +- fd = -1; +- if (server->addr.sa.sa_family == AF_INET6) +- { +- if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) +- fd = new->rfd6->fd; +- } +- else +- { +- if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) +- fd = new->rfd4->fd; +- } +- } +- +- if (fd != -1) ++ if ((fd = allocate_rfd(&new->rfds, server)) != -1) + { + #ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ +@@ -1360,7 +1293,7 @@ void receive_query(struct listener *listen, time_t now) + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +- ++ + dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0; + netmask.s_addr = 0; + +@@ -2228,9 +2161,8 @@ static struct frec *allocate_frec(time_t now) + f->next = daemon->frec_list; + f->time = now; + f->sentto = NULL; +- f->rfd4 = NULL; ++ f->rfds = NULL; + f->flags = 0; +- f->rfd6 = NULL; + #ifdef HAVE_DNSSEC + f->dependent = NULL; + f->blocking_query = NULL; +@@ -2242,46 +2174,192 @@ static struct frec *allocate_frec(time_t now) + return f; + } + +-struct randfd *allocate_rfd(int family) ++/* return a UDP socket bound to a random port, have to cope with straying into ++ occupied port nos and reserved ones. */ ++static int random_sock(struct server *s) + { +- static int finger = 0; +- int i; ++ int fd; ++ ++ if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) ++ { ++ if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) ++ return fd; + ++ if (s->interface[0] == 0) ++ (void)prettyprint_addr(&s->source_addr, daemon->namebuff); ++ else ++ strcpy(daemon->namebuff, s->interface); ++ ++ my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), ++ daemon->namebuff, strerror(errno)); ++ close(fd); ++ } ++ ++ return -1; ++} ++ ++/* compare source addresses and interface, serv2 can be null. */ ++static int server_isequal(const struct server *serv1, ++ const struct server *serv2) ++{ ++ return (serv2 && ++ serv2->ifindex == serv1->ifindex && ++ sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) && ++ strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0); ++} ++ ++/* fdlp points to chain of randomfds already in use by transaction. ++ If there's already a suitable one, return it, else allocate a ++ new one and add it to the list. ++ ++ Not leaking any resources in the face of allocation failures ++ is rather convoluted here. ++ ++ Note that rfd->serv may be NULL, when a server goes away. ++*/ ++int allocate_rfd(struct randfd_list **fdlp, struct server *serv) ++{ ++ static int finger = 0; ++ int i, j = 0; ++ struct randfd_list *rfl; ++ struct randfd *rfd = NULL; ++ int fd = 0; ++ ++ /* If server has a pre-allocated fd, use that. */ ++ if (serv->sfd) ++ return serv->sfd->fd; ++ ++ /* existing suitable random port socket linked to this transaction? */ ++ for (rfl = *fdlp; rfl; rfl = rfl->next) ++ if (server_isequal(serv, rfl->rfd->serv)) ++ return rfl->rfd->fd; ++ ++ /* No. need new link. */ ++ if ((rfl = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl->next; ++ else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) ++ return -1; ++ + /* limit the number of sockets we have open to avoid starvation of + (eg) TFTP. Once we have a reasonable number, randomness should be OK */ +- + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount == 0) + { +- if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) +- break; +- +- daemon->randomsocks[i].refcount = 1; +- daemon->randomsocks[i].family = family; +- return &daemon->randomsocks[i]; ++ if ((fd = random_sock(serv)) != -1) ++ { ++ rfd = &daemon->randomsocks[i]; ++ rfd->serv = serv; ++ rfd->fd = fd; ++ rfd->refcount = 1; ++ } ++ break; + } +- ++ + /* No free ones or cannot get new socket, grab an existing one */ +- for (i = 0; i < RANDOM_SOCKS; i++) ++ if (!rfd) ++ for (j = 0; j < RANDOM_SOCKS; j++) ++ { ++ i = (j + finger) % RANDOM_SOCKS; ++ if (daemon->randomsocks[i].refcount != 0 && ++ server_isequal(serv, daemon->randomsocks[i].serv) && ++ daemon->randomsocks[i].refcount != 0xfffe) ++ { ++ finger = i + 1; ++ rfd = &daemon->randomsocks[i]; ++ rfd->refcount++; ++ break; ++ } ++ } ++ ++ if (j == RANDOM_SOCKS) + { +- int j = (i+finger) % RANDOM_SOCKS; +- if (daemon->randomsocks[j].refcount != 0 && +- daemon->randomsocks[j].family == family && +- daemon->randomsocks[j].refcount != 0xffff) ++ struct randfd_list *rfl_poll; ++ ++ /* there are no free slots, and non with the same parameters we can piggy-back on. ++ We're going to have to allocate a new temporary record, distinguished by ++ refcount == 0xffff. This will exist in the frec randfd list, never be shared, ++ and be freed when no longer in use. It will also be held on ++ the daemon->rfl_poll list so the poll system can find it. */ ++ ++ if ((rfl_poll = daemon->rfl_spare)) ++ daemon->rfl_spare = rfl_poll->next; ++ else ++ rfl_poll = whine_malloc(sizeof(struct randfd_list)); ++ ++ if (!rfl_poll || ++ !(rfd = whine_malloc(sizeof(struct randfd))) || ++ (fd = random_sock(serv)) == -1) + { +- finger = j; +- daemon->randomsocks[j].refcount++; +- return &daemon->randomsocks[j]; ++ ++ /* Don't leak anything we may already have */ ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ ++ if (rfl_poll) ++ { ++ rfl_poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl_poll; ++ } ++ ++ if (rfd) ++ free(rfd); ++ ++ return -1; /* doom */ + } +- } + +- return NULL; /* doom */ ++ /* Note rfd->serv not set here, since it's not reused */ ++ rfd->fd = fd; ++ rfd->refcount = 0xffff; /* marker for temp record */ ++ ++ rfl_poll->rfd = rfd; ++ rfl_poll->next = daemon->rfl_poll; ++ daemon->rfl_poll = rfl_poll; ++ } ++ ++ rfl->rfd = rfd; ++ rfl->next = *fdlp; ++ *fdlp = rfl; ++ ++ return rfl->rfd->fd; + } + +-void free_rfd(struct randfd *rfd) ++void free_rfds(struct randfd_list **fdlp) + { +- if (rfd && --(rfd->refcount) == 0) +- close(rfd->fd); ++ struct randfd_list *tmp, *rfl, *poll, *next, **up; ++ ++ for (rfl = *fdlp; rfl; rfl = tmp) ++ { ++ if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0) ++ close(rfl->rfd->fd); ++ ++ /* temporary overflow record */ ++ if (rfl->rfd->refcount == 0xffff) ++ { ++ free(rfl->rfd); ++ ++ /* go through the link of all these by steam to delete. ++ This list is expected to be almost always empty. */ ++ for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next) ++ { ++ next = poll->next; ++ ++ if (poll->rfd == rfl->rfd) ++ { ++ *up = poll->next; ++ poll->next = daemon->rfl_spare; ++ daemon->rfl_spare = poll; ++ } ++ else ++ up = &poll->next; ++ } ++ } ++ ++ tmp = rfl->next; ++ rfl->next = daemon->rfl_spare; ++ daemon->rfl_spare = rfl; ++ } ++ ++ *fdlp = NULL; + } + + static void free_frec(struct frec *f) +@@ -2297,12 +2375,9 @@ static void free_frec(struct frec *f) + } + + f->frec_src.next = NULL; +- free_rfd(f->rfd4); +- f->rfd4 = NULL; ++ free_rfds(&f->rfds); + f->sentto = NULL; + f->flags = 0; +- free_rfd(f->rfd6); +- f->rfd6 = NULL; + + #ifdef HAVE_DNSSEC + if (f->stash) +@@ -2409,26 +2484,39 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force) + return f; /* OK if malloc fails and this is NULL */ + } + +-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) ++static struct frec *lookup_frec(unsigned short id, int fd, void *hash) + { + struct frec *f; +- ++ struct server *s; ++ int type; ++ struct randfd_list *fdl; ++ + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ +- if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) +- return f; +- +- if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) +- return f; +- +- /* sent to upstream from bound socket. */ +- if (f->sentto->sfd && f->sentto->sfd->fd == fd) ++ for (fdl = f->rfds; fdl; fdl = fdl->next) ++ if (fdl->rfd->fd == fd) + return f; ++ ++ /* Sent to upstream from socket associated with a server. ++ Note we have to iterate over all the possible servers, since they may ++ have different bound sockets. */ ++ type = f->sentto->flags & SERV_TYPE; ++ s = f->sentto; ++ do { ++ if ((type == (s->flags & SERV_TYPE)) && ++ (type != SERV_HAS_DOMAIN || ++ (s->domain && hostname_isequal(f->sentto->domain, s->domain))) && ++ !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && ++ s->sfd && s->sfd->fd == fd) ++ return f; ++ ++ s = s->next ? s->next : daemon->servers; ++ } while (s != f->sentto); + } +- ++ + return NULL; + } + +@@ -2458,34 +2546,30 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) + void resend_query() + { + if (daemon->srv_save) +- { +- int fd; +- +- if (daemon->srv_save->sfd) +- fd = daemon->srv_save->sfd->fd; +- else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) +- fd = daemon->rfd_save->fd; +- else +- return; +- +- while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, +- &daemon->srv_save->addr.sa, +- sa_len(&daemon->srv_save->addr)))); +- } ++ while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0, ++ &daemon->srv_save->addr.sa, ++ sa_len(&daemon->srv_save->addr)))); + } + + /* A server record is going away, remove references to it */ + void server_gone(struct server *server) + { + struct frec *f; ++ int i; + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->sentto == server) + free_frec(f); ++ ++ /* If any random socket refers to this server, NULL the reference. ++ No more references to the socket will be created in the future. */ ++ for (i = 0; i < RANDOM_SOCKS; i++) ++ if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) ++ daemon->randomsocks[i].serv = NULL; + + if (daemon->last_server == server) + daemon->last_server = NULL; +- ++ + if (daemon->srv_save == server) + daemon->srv_save = NULL; + } +diff --git a/src/loop.c b/src/loop.c +index 15e34c7..758674b 100644 +--- a/src/loop.c ++++ b/src/loop.c +@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid); + void loop_send_probes() + { + struct server *serv; ++ struct randfd_list *rfds = NULL; + + if (!option_bool(OPT_LOOP_DETECT)) + return; +@@ -34,29 +35,22 @@ void loop_send_probes() + { + ssize_t len = loop_make_probe(serv->uid); + int fd; +- struct randfd *rfd = NULL; + +- if (serv->sfd) +- fd = serv->sfd->fd; +- else +- { +- if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) +- continue; +- fd = rfd->fd; +- } +- ++ if ((fd = allocate_rfd(&rfds, serv)) == -1) ++ continue; ++ + while (retry_send(sendto(fd, daemon->packet, len, 0, + &serv->addr.sa, sa_len(&serv->addr)))); +- +- free_rfd(rfd); + } ++ ++ free_rfds(&rfds); + } + + static ssize_t loop_make_probe(u32 uid) + { + struct dns_header *header = (struct dns_header *)daemon->packet; + unsigned char *p = (unsigned char *)(header+1); +- ++ + /* packet buffer overwritten */ + daemon->srv_save = NULL; + +diff --git a/src/network.c b/src/network.c +index 5286c61..cca6ff2 100644 +--- a/src/network.c ++++ b/src/network.c +@@ -696,7 +696,8 @@ int enumerate_interfaces(int reset) + #ifdef HAVE_AUTH + struct auth_zone *zone; + #endif +- ++ struct server *serv; ++ + /* Do this max once per select cycle - also inhibits netlink socket use + in TCP child processes. */ + +@@ -714,6 +715,20 @@ int enumerate_interfaces(int reset) + if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + return 0; + ++ /* iface indexes can change when interfaces are created/destroyed. ++ We use them in the main forwarding control path, when the path ++ to a server is specified by an interface, so cache them. ++ Update the cache here. */ ++ for (serv = daemon->servers; serv; serv = serv->next) ++ if (strlen(serv->interface) != 0) ++ { ++ struct ifreq ifr; ++ ++ safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE); ++ if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1) ++ serv->ifindex = ifr.ifr_ifindex; ++ } ++ + again: + /* Mark interfaces for garbage collection */ + for (iface = daemon->interfaces; iface; iface = iface->next) +@@ -808,7 +823,7 @@ again: + + errno = errsave; + spare = param.spare; +- ++ + return ret; + } + +@@ -948,10 +963,10 @@ int tcp_interface(int fd, int af) + /* use mshdr so that the CMSDG_* macros are available */ + msg.msg_control = daemon->packet; + msg.msg_controllen = len = daemon->packet_buff_sz; +- ++ + /* we overwrote the buffer... */ +- daemon->srv_save = NULL; +- ++ daemon->srv_save = NULL; ++ + if (af == AF_INET) + { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && +@@ -1287,59 +1302,6 @@ void join_multicast(int dienow) + } + #endif + +-/* return a UDP socket bound to a random port, have to cope with straying into +- occupied port nos and reserved ones. */ +-int random_sock(int family) +-{ +- int fd; +- +- if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) +- { +- union mysockaddr addr; +- unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; +- int tries = ports_avail < 30 ? 3 * ports_avail : 100; +- +- memset(&addr, 0, sizeof(addr)); +- addr.sa.sa_family = family; +- +- /* don't loop forever if all ports in use. */ +- +- if (fix_fd(fd)) +- while(tries--) +- { +- unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); +- +- if (family == AF_INET) +- { +- addr.in.sin_addr.s_addr = INADDR_ANY; +- addr.in.sin_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in.sin_len = sizeof(struct sockaddr_in); +-#endif +- } +- else +- { +- addr.in6.sin6_addr = in6addr_any; +- addr.in6.sin6_port = port; +-#ifdef HAVE_SOCKADDR_SA_LEN +- addr.in6.sin6_len = sizeof(struct sockaddr_in6); +-#endif +- } +- +- if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) +- return fd; +- +- if (errno != EADDRINUSE && errno != EACCES) +- break; +- } +- +- close(fd); +- } +- +- return -1; +-} +- +- + int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) + { + union mysockaddr addr_copy = *addr; +@@ -1355,10 +1317,9 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind + /* cannot set source _port_ for TCP connections. */ + if (is_tcp) + port = 0; +- +- /* Bind a random port within the range given by min-port and max-port */ +- if (port == 0) ++ else if (port == 0) + { ++ /* Bind a random port within the range given by min-port and max-port */ + tries = ports_avail < 30 ? 3 * ports_avail : 100; + port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); + } +@@ -1413,38 +1374,33 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind + return 1; + } + +-static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) ++static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex) + { + struct serverfd *sfd; +- unsigned int ifindex = 0; + int errsave; + int opt = 1; + + /* when using random ports, servers which would otherwise use +- the INADDR_ANY/port0 socket have sfd set to NULL */ +- if (!daemon->osport && intname[0] == 0) ++ the INADDR_ANY/port0 socket have sfd set to NULL, this is ++ anything without an explictly set source port. */ ++ if (!daemon->osport) + { + errno = 0; + + if (addr->sa.sa_family == AF_INET && +- addr->in.sin_addr.s_addr == INADDR_ANY && + addr->in.sin_port == htons(0)) + return NULL; + + if (addr->sa.sa_family == AF_INET6 && +- memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && + addr->in6.sin6_port == htons(0)) + return NULL; + } + +- if (intname && strlen(intname) != 0) +- ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ +- + /* may have a suitable one already */ + for (sfd = daemon->sfds; sfd; sfd = sfd->next ) +- if (sockaddr_isequal(&sfd->source_addr, addr) && +- strcmp(intname, sfd->interface) == 0 && +- ifindex == sfd->ifindex) ++ if (ifindex == sfd->ifindex && ++ sockaddr_isequal(&sfd->source_addr, addr) && ++ strcmp(intname, sfd->interface) == 0) + return sfd; + + /* need to make a new one. */ +@@ -1495,7 +1451,7 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in.sin_len = sizeof(struct sockaddr_in); + #endif +- if ((sfd = allocate_sfd(&addr, ""))) ++ if ((sfd = allocate_sfd(&addr, "", 0))) + sfd->preallocated = 1; + + memset(&addr, 0, sizeof(addr)); +@@ -1505,13 +1461,13 @@ void pre_allocate_sfds(void) + #ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); + #endif +- if ((sfd = allocate_sfd(&addr, ""))) ++ if ((sfd = allocate_sfd(&addr, "", 0))) + sfd->preallocated = 1; + } + + for (srv = daemon->servers; srv; srv = srv->next) + if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && +- !allocate_sfd(&srv->source_addr, srv->interface) && ++ !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && + errno != 0 && + option_bool(OPT_NOWILD)) + { +@@ -1720,7 +1676,7 @@ void check_servers(void) + + /* Do we need a socket set? */ + if (!serv->sfd && +- !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && ++ !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && + errno != 0) + { + my_syslog(LOG_WARNING, +diff --git a/src/option.c b/src/option.c +index bfda212..159d1d4 100644 +--- a/src/option.c ++++ b/src/option.c +@@ -819,7 +819,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a + if (interface_opt) + { + #if defined(SO_BINDTODEVICE) +- safe_strncpy(interface, interface_opt, IF_NAMESIZE); ++ safe_strncpy(interface, source, IF_NAMESIZE); ++ source = interface_opt; + #else + return _("interface binding not supported"); + #endif +diff --git a/src/tftp.c b/src/tftp.c +index 846b32e..ea2aa84 100644 +--- a/src/tftp.c ++++ b/src/tftp.c +@@ -94,7 +94,7 @@ void tftp_request(struct listener *listen, time_t now) + + if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) + return; +- ++ + /* Can always get recvd interface for IPv6 */ + if (!check_dest) + { +@@ -587,7 +587,7 @@ void check_tftp_listeners(time_t now) + daemon->srv_save = NULL; + handle_tftp(now, transfer, recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)); + } +- ++ + for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) + { + tmp = transfer->next; +@@ -602,7 +602,7 @@ void check_tftp_listeners(time_t now) + + /* we overwrote the buffer... */ + daemon->srv_save = NULL; +- ++ + if ((len = get_block(daemon->packet, transfer)) == -1) + { + len = tftp_err_oops(daemon->packet, transfer->file->filename); +diff --git a/src/util.c b/src/util.c +index 972c60b..6176dc9 100644 +--- a/src/util.c ++++ b/src/util.c +@@ -316,7 +316,7 @@ void *whine_malloc(size_t size) + return ret; + } + +-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) ++int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) + { + if (s1->sa.sa_family == s2->sa.sa_family) + { +-- +1.8.3.1 + diff --git a/backport-Add-missing-check-for-NULL-return-from-allocate_rfd.patch b/backport-Add-missing-check-for-NULL-return-from-allocate_rfd.patch new file mode 100644 index 0000000000000000000000000000000000000000..9de07a4e82fbb52372fbfcbb9acb876fcd4b5560 --- /dev/null +++ b/backport-Add-missing-check-for-NULL-return-from-allocate_rfd.patch @@ -0,0 +1,71 @@ +From 824461192ca5098043f9ca4ddeba7df1f65b30ba Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Sun, 15 Nov 2020 22:13:25 +0000 +Subject: [PATCH] Add missing check for NULL return from allocate_rfd(). + +Conflict:NA +Reference:https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=824461192ca5098043f9ca4ddeba7df1f65b30ba +--- + src/forward.c | 18 ++++++++++-------- + 1 file changed, 10 insertions(+), 8 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index 4f9a963..50da095 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -823,7 +823,6 @@ void reply_query(int fd, int family, time_t now) + int is_sign; + + #ifdef HAVE_DNSSEC +- /* For DNSSEC originated queries, just retry the query to the same server. */ + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) + { + struct server *start; +@@ -849,6 +848,8 @@ void reply_query(int fd, int family, time_t now) + } + + ++ fd = -1; ++ + if (start->sfd) + fd = start->sfd->fd; + else +@@ -856,19 +857,21 @@ void reply_query(int fd, int family, time_t now) + if (start->addr.sa.sa_family == AF_INET6) + { + /* may have changed family */ +- if (!forward->rfd6) +- forward->rfd6 = allocate_rfd(AF_INET6); +- fd = forward->rfd6->fd; ++ if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6))) ++ fd = forward->rfd6->fd; + } + else + { + /* may have changed family */ +- if (!forward->rfd4) +- forward->rfd4 = allocate_rfd(AF_INET); +- fd = forward->rfd4->fd; ++ if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET))) ++ fd = forward->rfd4->fd; + } + } + ++ /* Can't get socket. */ ++ if (fd == -1) ++ return; ++ + #ifdef HAVE_DUMPFILE + dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr); + #endif +@@ -2311,7 +2314,6 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force) + return f; /* OK if malloc fails and this is NULL */ + } + +-/* crc is all-ones if not known. */ + static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) + { + struct frec *f; +-- +2.23.0 + diff --git a/backport-Fix-DNS-reply-when-asking-for-DNSSEC-and-a-validated.patch b/backport-Fix-DNS-reply-when-asking-for-DNSSEC-and-a-validated.patch new file mode 100644 index 0000000000000000000000000000000000000000..95738b6a66c0d2edce37ea12e3c5f5dba67f1cd4 --- /dev/null +++ b/backport-Fix-DNS-reply-when-asking-for-DNSSEC-and-a-validated.patch @@ -0,0 +1,28 @@ +From 1eb6cedb03cb335071fda22ee7c623b2298d3729 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Sat, 14 Nov 2020 15:29:34 +0000 +Subject: [PATCH] Fix DNS reply when asking for DNSSEC and a validated CNAME is + already cached. + +Conflict:NA +Reference:https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=1eb6cedb03cb335071fda22ee7c623b2298d3729 +--- + src/rfc1035.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/rfc1035.c b/src/rfc1035.c +index a8cdc6e..79af53f 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -1359,6 +1359,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, + } + + } ++ else ++ return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */ + + strcpy(name, cname_target); + } +-- +2.23.0 + diff --git a/dnsmasq.spec b/dnsmasq.spec index 8e153835c005249f83ed9b702c42a9a6bac055f7..69762aad25e0e6f3694333cd5dcc331808c6d036 100644 --- a/dnsmasq.spec +++ b/dnsmasq.spec @@ -1,6 +1,6 @@ Name: dnsmasq Version: 2.82 -Release: 4 +Release: 5 Summary: Dnsmasq provides network infrastructure for small networks License: GPLv2 or GPLv3 URL: http://www.thekelleys.org.uk/dnsmasq/ @@ -19,6 +19,18 @@ Patch8: backport-CVE-2020-25685_2.patch Patch9: backport-CVE-2020-25686_1.patch Patch10: backport-CVE-2020-25686_2.patch Patch11: backport-fix-regression-in-s_config_in_context-method.patch +Patch12: backport-Add-missing-check-for-NULL-return-from-allocate_rfd.patch +Patch13: backport-Fix-DNS-reply-when-asking-for-DNSSEC-and-a-validated.patch +Patch14: backport-0001-Handle-caching-with-EDNS-options-better.patch +Patch15: backport-0002-Fix-to-75e2f0aec33e58ef5b8d4d107d821c215a52827c.patch +Patch16: backport-0003-Fix-for-12af2b171de0d678d98583e2190789e544440e02.patch +Patch17: backport-0004-Fix-problem-with-DNS-retries-in-2.83-2.84.patch +Patch18: backport-0005-Simplify-preceding-fix.patch +Patch19: backport-0006-Update-to-new-struct-frec-fields-in-conntrack-code.patch +Patch20: backport-0007-Use-the-values-of-min-port-and-max-port-in-TCP-conne.patch +Patch21: backport-0008-Correct-occasional-bind-dynamic-synchronization-brea.patch +Patch22: backport-0009-Move-fd-into-frec_src-fixes-15b60ddf935a531269bb8c68.patch +Patch23: backport-0010-CVE-2021-3448.patch BuildRequires: dbus-devel pkgconfig libidn2-devel nettle-devel systemd Requires: nettle >= 3.4 @@ -111,6 +123,23 @@ install -Dpm644 %{SOURCE2} $RPM_BUILD_ROOT%{_sysusersdir}/dnsmasq.conf %{_mandir}/man8/dnsmasq* %changelog +* Wed Apr 21 2021 gaihuiying - 2.82-5 +- Type:bugfix +- Id:NA +- SUG:NA +- DESC: Add missing check for NULL return from allocate_rfd + Fix DNS reply when asking for DNSSEC and a validated + Handle caching with EDNS options better + Fix to 75e2f0aec33e58ef5b8d4d107d821c215a52827c + Fix for 12af2b171de0d678d98583e2190789e544440e02 + Fix problem with DNS retries in 2.83 2.84 version + Simplify preceding fix + Update to new struct frec fields in conntrack code + Use the values of min port and max port in TCP connection + Correct occasional bind dynamic synchronization + Move fd into frec_src fixes 15b60ddf935a531269bb8c68 + fix CVE-2021-3448 + * Thu Jan 21 2021 zhujh - 2.82-4 - Type:bugfix - Id:NA