From 5dc14b6e05f39a5ab0dc02e376b1d7da2fda5bc1 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 2 Feb 2023 20:20:13 +0000 Subject: Replace dead link in dnsmasq.conf.example. Thanks to Timo van Roermund for spotting this. --- dnsmasq.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsmasq.conf.example b/dnsmasq.conf.example index 2047630..0cbf572 100644 --- a/dnsmasq.conf.example +++ b/dnsmasq.conf.example @@ -394,7 +394,7 @@ # The following DHCP options set up dnsmasq in the same way as is specified # for the ISC dhcpcd in -# http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt +# https://web.archive.org/web/20040313070105/http://us1.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt # adapted for a typical dnsmasq installation where the host running # dnsmasq is also the host running samba. # you may want to uncomment some or all of them if you use -- cgit v1.2.1 From 137ae2e9cf0dc3596641e7c8b91d15307a35319e Mon Sep 17 00:00:00 2001 From: Taylor R Campbell Date: Sat, 25 Feb 2023 15:00:30 +0000 Subject: Avoid undefined behaviour with the ctype(3) functions. As defined in the C standard: In all cases the argument is an int, the value of which shall be representable as an unsigned char or shall equal the value of the macro EOF. If the argument has any other value, the behavior is undefined. This is because they're designed to work with the int values returned by getc or fgetc; they need extra work to handle a char value. If EOF is -1 (as it almost always is), with 8-bit bytes, the allowed inputs to the ctype(3) functions are: {-1, 0, 1, 2, 3, ..., 255}. However, on platforms where char is signed, such as x86 with the usual ABI, code like char *arg = ...; ... isspace(*arg) ... may pass in values in the range: {-128, -127, -126, ..., -2, -1, 0, 1, ..., 127}. This has two problems: 1. Inputs in the set {-128, -127, -126, ..., -2} are forbidden. 2. The non-EOF byte 0xff is conflated with the value EOF = -1, so even though the input is not forbidden, it may give the wrong answer. Casting char to int first before passing the result to ctype(3) doesn't help: inputs like -128 are unchanged by this cast. It is necessary to cast char inputs to unsigned char first; you can then cast to int if you like but there's no need because the functions will always convert the argument to int by definition. So the above fragment needs to be: char *arg = ...; ... isspace((unsigned char)*arg) ... This patch inserts unsigned char casts where necessary, and changes int casts to unsigned char casts where the input is char. I left alone int casts where the input is unsigned char already -- they're not immediately harmful, although they would have the effect of suppressing some compiler warnings if the input is ever changed to be char instead of unsigned char, so it might be better to remove those casts too. I also left alone calls where the input is int to begin with because it came from getc; casting to unsigned char here would be wrong, of course. --- src/dhcp-common.c | 6 +++--- src/dhcp.c | 6 +++--- src/loop.c | 2 +- src/option.c | 8 ++++---- src/rfc1035.c | 2 +- src/rfc2131.c | 2 +- src/tftp.c | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 84081ce..b4d255e 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -838,7 +838,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, for (i = 0, j = 0; i < opt_len && j < buf_len ; i++) { char c = val[i]; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } #ifdef HAVE_DHCP6 @@ -852,7 +852,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, for (k = i + 1; k < opt_len && k < l && j < buf_len ; k++) { char c = val[k]; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } i = l; @@ -873,7 +873,7 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, for (k = 0; k < len && j < buf_len; k++) { char c = *p++; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } i += len +2; diff --git a/src/dhcp.c b/src/dhcp.c index 42d819f..e578391 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -916,14 +916,14 @@ void dhcp_read_ethers(void) lineno++; - while (strlen(buff) > 0 && isspace((int)buff[strlen(buff)-1])) + while (strlen(buff) > 0 && isspace((unsigned char)buff[strlen(buff)-1])) buff[strlen(buff)-1] = 0; if ((*buff == '#') || (*buff == '+') || (*buff == 0)) continue; - for (ip = buff; *ip && !isspace((int)*ip); ip++); - for(; *ip && isspace((int)*ip); ip++) + for (ip = buff; *ip && !isspace((unsigned char)*ip); ip++); + for(; *ip && isspace((unsigned char)*ip); ip++) *ip = 0; if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN) { diff --git a/src/loop.c b/src/loop.c index cd4855e..19bfae0 100644 --- a/src/loop.c +++ b/src/loop.c @@ -92,7 +92,7 @@ int detect_loop(char *query, int type) return 0; for (i = 0; i < 8; i++) - if (!isxdigit(query[i])) + if (!isxdigit((unsigned char)query[i])) return 0; uid = strtol(query, NULL, 16); diff --git a/src/option.c b/src/option.c index e4810fd..1090bca 100644 --- a/src/option.c +++ b/src/option.c @@ -2751,7 +2751,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err(gen_err); for (p = arg; *p; p++) - if (!isxdigit((int)*p)) + if (!isxdigit((unsigned char)*p)) ret_err(gen_err); set_option_bool(OPT_UMBRELLA_DEVID); @@ -4836,7 +4836,7 @@ err: new->target = target; new->ttl = ttl; - for (arg += arglen+1; *arg && isspace(*arg); arg++); + for (arg += arglen+1; *arg && isspace((unsigned char)*arg); arg++); } break; @@ -5227,7 +5227,7 @@ err: unhide_metas(keyhex); /* 4034: "Whitespace is allowed within digits" */ for (cp = keyhex; *cp; ) - if (isspace(*cp)) + if (isspace((unsigned char)*cp)) for (cp1 = cp; *cp1; cp1++) *cp1 = *(cp1+1); else @@ -5315,7 +5315,7 @@ static void read_file(char *file, FILE *f, int hard_opt, int from_script) memmove(p, p+1, strlen(p+1)+1); } - if (isspace(*p)) + if (isspace((unsigned char)*p)) { *p = ' '; white = 1; diff --git a/src/rfc1035.c b/src/rfc1035.c index 5c0df56..1693253 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -519,7 +519,7 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name, /* make counted string zero-term and sanitise */ for (i = 0; i < len; i++) { - if (!isprint((int)*(p3+1))) + if (!isprint((unsigned char)*(p3+1))) break; *p3 = *(p3+1); p3++; diff --git a/src/rfc2131.c b/src/rfc2131.c index 17e97b5..5190982 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1678,7 +1678,7 @@ static int sanitise(unsigned char *opt, char *buf) for (i = option_len(opt); i > 0; i--) { char c = *p++; - if (isprint((int)c)) + if (isprint((unsigned char)c)) *buf++ = c; } *buf = 0; /* add terminator */ diff --git a/src/tftp.c b/src/tftp.c index 0861f37..8e1dc4a 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -405,7 +405,7 @@ void tftp_request(struct listener *listen, time_t now) if (*p == '\\') *p = '/'; else if (option_bool(OPT_TFTP_LC)) - *p = tolower(*p); + *p = tolower((unsigned char)*p); strcpy(daemon->namebuff, "/"); if (prefix) -- cgit v1.2.1 From 7d6b68c5d7016aca5372f12e9f0c25f0a108644d Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 6 Mar 2023 13:06:03 +0000 Subject: Document suppressing deafult options in --dhcp-option. --- man/dnsmasq.8 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 3d1d96a..41e2e04 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1297,7 +1297,15 @@ and to set the time-server address to 192.168.0.4, do or .B --dhcp-option = option:ntp-server, 192.168.0.4 The special address 0.0.0.0 is taken to mean "the address of the -machine running dnsmasq". +machine running dnsmasq". + +An option without data is valid, and includes just the option without data. +(There is only one option with a zero length data field currently defined for DHCPv4, 80:rapid commit, so this feature is not very useful in practice). Options for which dnsmasq normally +provides default values can be ommitted by defining the option with no data. These are +netmask, broadcast, router, DNS server, domainname and hostname. Thus, for DHCPv4 +.B --dhcp-option = option:router +will result in no router option being sent, rather than the default of the host on which dnsmasq is running. For DHCPv6, the same is true of the options DNS server and refresh time. + Data types allowed are comma separated dotted-quad IPv4 addresses, []-wrapped IPv6 addresses, a decimal number, colon-separated hex digits -- cgit v1.2.1 From 997982f78bd3f8c311b9557e1ef825555e7290bb Mon Sep 17 00:00:00 2001 From: Dominik Derigs Date: Fri, 3 Mar 2023 18:05:26 +0100 Subject: Fix --rev-server option. It was broken in 1db9943c6879c160a5fbef885d5ceadd3668b74d when resolving upstream servers by name was extended to --rev-server without accounting for the fact that re-using one and the same upstream server for each of the x.y.z.in-addr.arpa is actually a wanted feature Signed-off-by: DL6ER --- src/option.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/option.c b/src/option.c index 1090bca..2e208ba 100644 --- a/src/option.c +++ b/src/option.c @@ -1159,6 +1159,9 @@ static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int } else { + /* Always reset server as valid here, so we can add the same upstream + server address multiple times for each x.y.z.in-addr.arpa */ + sdetails.valid = 1; while (parse_server_next(&sdetails)) { if ((string = parse_server_addr(&sdetails))) @@ -1244,6 +1247,9 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in } else { + /* Always reset server as valid here, so we can add the same upstream + server address multiple times for each x.y.z.ip6.arpa */ + sdetails.valid = 1; while (parse_server_next(&sdetails)) { if ((string = parse_server_addr(&sdetails))) -- cgit v1.2.1 From f5ef0f064c3f06b250a9eeda36dc239227658b00 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 6 Mar 2023 23:00:58 +0000 Subject: Fix possible SEGV when no servers defined. If there exists a --address=// or --server=//# configuration but no upstream server config unqualified by domain then when a query which doesnt match the domain is recieved it will use the qualfied server config and in the process possibly make an out-of-bounds memory access. Thanks to Daniel Danzberger for spotting the bug. --- CHANGELOG | 11 +++++++++++ src/domain-match.c | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index de9c5e0..3af20cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +version 2.90 + Fix reversion in --rev-server introduced in 2.88 which + caused breakage if the prefix length is not exactly divisible + by 8 (IPv4) or 4 (IPv6). + + Fix possible SEGV when there server(s) for a particular + domain are configured, but no server which is not qualified + for a particular domain. Thanks to Daniel Danzberger for + spotting this bug. + + version 2.89 Fix bug introduced in 2.88 (commit fe91134b) which can result in corruption of the DNS cache internal data structures and diff --git a/src/domain-match.c b/src/domain-match.c index fe8e25a..9cc51e6 100644 --- a/src/domain-match.c +++ b/src/domain-match.c @@ -253,9 +253,10 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout) if (highout) *highout = nhigh; - if (nlow == nhigh) + /* qlen == -1 when we failed to match even an empty query, if there are no default servers. */ + if (nlow == nhigh || qlen == -1) return 0; - + return 1; } -- cgit v1.2.1 From 9a698434dd9cc0f3abbf98f9b266c491d322d20f Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 6 Mar 2023 23:30:36 +0000 Subject: Bump version in Debian changelog. --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index a1cf600..b903774 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +dnsmasq (2.90-1) unstable; urgency=low + + * New upstream. + + -- Simon Kelley Mon, 6 Mar 2023 23:22:06 +0000 + dnsmasq (2.89-1) unstable; urgency=low * New upstream. -- cgit v1.2.1 From eb92fb32b746f2104b0f370b5b295bb8dd4bd5e5 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 7 Mar 2023 22:07:46 +0000 Subject: Set the default maximum DNS UDP packet size to 1232. http://www.dnsflagday.net/2020/ refers. Thanks to Xiang Li for the prompt. --- CHANGELOG | 9 ++++++++- man/dnsmasq.8 | 3 ++- src/config.h | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3af20cf..52d8678 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,8 +7,15 @@ version 2.90 domain are configured, but no server which is not qualified for a particular domain. Thanks to Daniel Danzberger for spotting this bug. - + Set the default maximum DNS UDP packet sice to 1232. This + has been the recommended value since 2020 because it's the + largest value that avoid fragmentation, and fragmentation + is just not reliable on the modern internet, especially + for IPv6. It's still possible to override this with + --edns-packet-max for special circumstances. + + version 2.89 Fix bug introduced in 2.88 (commit fe91134b) which can result in corruption of the DNS cache internal data structures and diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 41e2e04..5acb935 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -183,7 +183,8 @@ to zero completely disables DNS function, leaving only DHCP and/or TFTP. .TP .B \-P, --edns-packet-max= Specify the largest EDNS.0 UDP packet which is supported by the DNS -forwarder. Defaults to 4096, which is the RFC5625-recommended size. +forwarder. Defaults to 1232, which is the recommended size following the +DNS flag day in 2020. Only increase if you know what you are doing. .TP .B \-Q, --query-port= Send outbound DNS queries from, and listen for their replies on, the diff --git a/src/config.h b/src/config.h index 1e7b30f..37b374e 100644 --- a/src/config.h +++ b/src/config.h @@ -19,7 +19,7 @@ #define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */ #define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */ #define TCP_BACKLOG 32 /* kernel backlog limit for TCP connections */ -#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */ +#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ #define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ #define DNSSEC_WORK 50 /* Max number of queries to validate one question */ -- cgit v1.2.1 From ef8e930e4295265b8f46898a8e166f17d7f8ddc8 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 7 Mar 2023 22:46:44 +0000 Subject: Generalise cached NXDOMAIN replies. We can cache an NXDOMAIN reply to a query for any RRTYPE and reply from a cached NXDOMAIN to any RRTYPE. --- src/rfc1035.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/rfc1035.c b/src/rfc1035.c index 1693253..3d82ad9 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -894,9 +894,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t { flags &= ~(F_IPV4 | F_IPV6 | F_SRV); - /* Can store NXDOMAIN reply to CNAME or ANY query. */ - if (qtype == T_CNAME || qtype == T_ANY) - insert = 1; + /* Can store NXDOMAIN reply for any qtype. */ + insert = 1; } log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0); @@ -2081,7 +2080,22 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, } if (!ans) - return 0; /* failed to answer a question */ + { + /* We may know that the domain doesn't exist for any RRtype. */ + if ((crecp = cache_find_by_name(NULL, name, now, F_NXDOMAIN))) + { + ans = nxdomain = 1; + auth = 0; + + if (!(crecp->flags & F_DNSSECOK)) + sec_data = 0; + + if (!dryrun) + log_query(F_NXDOMAIN | F_NEG, name, NULL, NULL, 0); + } + else + return 0; /* failed to answer a question */ + } } if (dryrun) -- cgit v1.2.1 From ef5aac95d4391fb1290fd76a3826b2851e589bbc Mon Sep 17 00:00:00 2001 From: Clayton Craft Date: Wed, 8 Mar 2023 15:35:05 +0000 Subject: Allow configuring filter-A/AAAA via dbus. --- dbus/DBus-interface | 8 ++++++++ src/dbus.c | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/dbus/DBus-interface b/dbus/DBus-interface index 59b41b1..00b4465 100644 --- a/dbus/DBus-interface +++ b/dbus/DBus-interface @@ -44,6 +44,14 @@ SetFilterWin2KOption -------------------- Takes boolean, sets or resets the --filterwin2k option. +SetFilterA +------------------------ +Takes boolean, sets or resets the --filter-A option. + +SetFilterAAAA +------------------------ +Takes boolean, sets or resets the --filter-AAAA option. + SetBogusPrivOption ------------------ Takes boolean, sets or resets the --bogus-priv option. diff --git a/src/dbus.c b/src/dbus.c index fd5d1ca..4366b7e 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -52,6 +52,12 @@ const char* introspection_xml_template = " \n" " \n" " \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" " \n" " \n" " \n" @@ -817,6 +823,14 @@ DBusHandlerResult message_handler(DBusConnection *connection, { reply = dbus_set_bool(message, OPT_FILTER, "filterwin2k"); } + else if (strcmp(method, "SetFilterA") == 0) + { + reply = dbus_set_bool(message, OPT_FILTER_A, "filter-A"); + } + else if (strcmp(method, "SetFilterAAAA") == 0) + { + reply = dbus_set_bool(message, OPT_FILTER_AAAA, "filter-AAAA"); + } else if (strcmp(method, "SetLocaliseQueriesOption") == 0) { reply = dbus_set_bool(message, OPT_LOCALISE, "localise-queries"); -- cgit v1.2.1 From 00be8b39e240934e404533deda08cbae2aae25a8 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Wed, 15 Mar 2023 21:12:55 +0000 Subject: Fix DHCPv6 "use multicast" response which previously failed to set the message type correctly. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Petr Menšík for spotting the problem. --- src/rfc3315.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rfc3315.c b/src/rfc3315.c index 8754481..477df91 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -353,7 +353,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu put_opt6_short(DHCP6USEMULTI); put_opt6_string("Use multicast"); end_opt6(o1); - return 1; + goto done; } /* match vendor and user class options */ @@ -1277,12 +1277,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu } + log_tags(tagif, state->xid); + + done: /* Fill in the message type. Note that we store the offset, not a direct pointer, since the packet memory may have been reallocated. */ ((unsigned char *)(daemon->outpacket.iov_base))[start_msg] = outmsgtype; - log_tags(tagif, state->xid); log6_opts(0, state->xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1)); return 1; -- cgit v1.2.1 From 946180701191ea7e552fa5a939140a635d47b566 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 16 Mar 2023 15:16:17 +0000 Subject: Remove limitation on --dynamic-host. Dynamic-host was implemented to ignore interface addresses with /32 (or /128 for IPv6) prefix lengths, since they are not useful for synthesising addresses. Due to a bug before 2.88, this didn't work for IPv4, and some have used --dynamic-host=example.com,0.0.0.0,eth0 to do the equivalent of --interface-name for such interfaces. When the bug was fixed in 2.88 these uses broke. Since this behaviour seems to violate the principle of least surprise, and since the 2.88 fix is breaking existing imstallations, this commit removes the check on /32 and /128 prefix lengths to solve both problems. --- src/network.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/network.c b/src/network.c index 1e41a78..a80d096 100644 --- a/src/network.c +++ b/src/network.c @@ -359,13 +359,8 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, struct in_addr newaddr = addr->in.sin_addr; if (int_name->flags & INP4) - { - if (netmask.s_addr == 0xffffffff) - continue; - - newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) | - (int_name->proto4.s_addr & ~netmask.s_addr); - } + newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) | + (int_name->proto4.s_addr & ~netmask.s_addr); /* check for duplicates. */ for (lp = int_name->addr; lp; lp = lp->next) @@ -398,10 +393,6 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, { int i; - /* No sense in doing /128. */ - if (prefixlen == 128) - continue; - for (i = 0; i < 16; i++) { int bits = ((i+1)*8) - prefixlen; -- cgit v1.2.1 From 5a9eae429a7d0680d606f03f2759d7dde0bbe3f0 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 20 Mar 2023 15:16:29 +0000 Subject: Improve cache use with --filter-A and --filter-AAAA If --filter-AAAA is set and we have cached entry for the domain in question fpr any RR type that allows us to return a NODATA reply when --filter-AAAA is set without going upstream. Similarly for --filter-A. --- src/rfc1035.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/rfc1035.c b/src/rfc1035.c index 3d82ad9..5abacbd 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1910,6 +1910,25 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, anscount++; } } + else if (((flag & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flag & F_IPV6) && option_bool(OPT_FILTER_AAAA))) + { + /* We don't have a cached answer and when we get an answer from upstream we're going to + filter it anyway. If we have a cached answer for the domain for another RRtype then + that may be enough to tell us if the answer should be NODATA and save the round trip. + Cached NXDOMAIN has already been handled, so here we look for any record for the domain, + since its existence allows us to return a NODATA answer. Note that we never set the AD flag, + since we didn't authentucate the record. We do set the AA flag since this answer comes from + local config. */ + + if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_SRV)) + { + ans = 1; + sec_data = 0; + + if (!dryrun) + log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + } + } } if (qtype == T_MX || qtype == T_ANY) @@ -1920,6 +1939,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, { ans = found = 1; sec_data = 0; + if (!dryrun) { int offset; -- cgit v1.2.1 From 28429720355c21739c851c28b003b6d0b4aee549 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 20 Mar 2023 17:14:17 +0000 Subject: More --filter-AAAA caching improvements. Cache answers before filtering and filter coming out of the cache. --- src/forward.c | 19 +++++++++---------- src/rfc1035.c | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/forward.c b/src/forward.c index 0f03818..3f3954d 100644 --- a/src/forward.c +++ b/src/forward.c @@ -811,16 +811,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } } - /* Before extract_addresses() */ - if (rcode == NOERROR) - { - if (option_bool(OPT_FILTER_A)) - n = rrfilter(header, n, RRFILTER_A); - - if (option_bool(OPT_FILTER_AAAA)) - n = rrfilter(header, n, RRFILTER_AAAA); - } - switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) { case 1: @@ -839,6 +829,15 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server break; } + if (rcode == NOERROR) + { + if (option_bool(OPT_FILTER_A)) + n = rrfilter(header, n, RRFILTER_A); + + if (option_bool(OPT_FILTER_AAAA)) + n = rrfilter(header, n, RRFILTER_AAAA); + } + if (doctored) cache_secure = 0; } diff --git a/src/rfc1035.c b/src/rfc1035.c index 5abacbd..75481d3 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -880,7 +880,18 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t return 2; } else - log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, NULL, aqtype); + { + int negflag = F_UPSTREAM; + + /* We're filtering this RRtype. It will be removed from the + returned packet in process_reply() but gets cached here anyway + and will be filtered again on the way out of the cache. Here, + we just need to alter the logging. */ + if (((flags & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flags & F_IPV6) && option_bool(OPT_FILTER_AAAA))) + negflag = F_NEG | F_CONFIG; + + log_query(negflag | flags | F_FORWARD | secflag, name, &addr, NULL, aqtype); + } } p1 = endrr; @@ -1863,8 +1874,21 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!(crecp->flags & F_DNSSECOK)) sec_data = 0; - - if (crecp->flags & F_NEG) + + if (!(crecp->flags & (F_HOSTS | F_DHCP))) + auth = 0; + + if ((((flag & F_IPV4) && option_bool(OPT_FILTER_A)) || ((flag & F_IPV6) && option_bool(OPT_FILTER_AAAA))) && + !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG))) + { + /* We have a cached answer but we're filtering it. */ + ans = 1; + sec_data = 0; + + if (!dryrun) + log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + } + else if (crecp->flags & F_NEG) { ans = 1; auth = 0; @@ -1882,9 +1906,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, !is_same_net(crecp->addr.addr4, local_addr, local_netmask)) continue; - if (!(crecp->flags & (F_HOSTS | F_DHCP))) - auth = 0; - ans = 1; if (!dryrun) { @@ -1917,13 +1938,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, that may be enough to tell us if the answer should be NODATA and save the round trip. Cached NXDOMAIN has already been handled, so here we look for any record for the domain, since its existence allows us to return a NODATA answer. Note that we never set the AD flag, - since we didn't authentucate the record. We do set the AA flag since this answer comes from - local config. */ + since we didn't authentucate the record. */ if (cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_SRV)) { ans = 1; - sec_data = 0; + sec_data = auth = 0; if (!dryrun) log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); -- cgit v1.2.1 From 1f0f86a0d0d0759de4c5eb1080333618563ee5a0 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 20 Mar 2023 18:32:14 +0000 Subject: Add EDE "filtered" extended error when --filter-A or --filter-AAAA act. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a NODATA answer is returned instead of actual data for A or AAAA queries because of the existence of --filter-A or --filter-AAAA config options, then mark the replies with an EDE "filtered" tag. Basic patch by Petr Menšík, tweaked by Simon Kelley to apply onto the preceding caching patches. --- src/dnsmasq.h | 4 ++-- src/edns0.c | 2 +- src/forward.c | 64 +++++++++++++++++++++++++++++++++++++++++----------------- src/rfc1035.c | 14 ++++++++++--- src/rrfilter.c | 46 +++++++++++++++++++++-------------------- 5 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index fe9aa07..292a402 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1366,7 +1366,7 @@ void report_addresses(struct dns_header *header, size_t len, u32 mark); size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct in_addr local_addr, struct in_addr local_netmask, time_t now, int ad_reqd, int do_bit, int have_pseudoheader, - int *stale); + int *stale, int *filtered); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, time_t now); int check_for_ignored_address(struct dns_header *header, size_t qlen); @@ -1811,7 +1811,7 @@ void poll_listen(int fd, short event); int do_poll(int timeout); /* rrfilter.c */ -size_t rrfilter(struct dns_header *header, size_t plen, int mode); +size_t rrfilter(struct dns_header *header, size_t *plen, int mode); u16 *rrfilter_desc(int type); int expand_workspace(unsigned char ***wkspc, int *szp, int new); /* modes. */ diff --git a/src/edns0.c b/src/edns0.c index c498eb1..567101b 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -178,7 +178,7 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l memcpy(buff, datap, rdlen); /* now, delete OPT RR */ - plen = rrfilter(header, plen, RRFILTER_EDNS0); + rrfilter(header, &plen, RRFILTER_EDNS0); /* Now, force addition of a new one */ p = NULL; diff --git a/src/forward.c b/src/forward.c index 3f3954d..d79cc56 100644 --- a/src/forward.c +++ b/src/forward.c @@ -721,7 +721,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if (added_pheader) { /* client didn't send EDNS0, we added one, strip it off before returning answer. */ - n = rrfilter(header, n, RRFILTER_EDNS0); + rrfilter(header, &n, RRFILTER_EDNS0); pheader = NULL; } else @@ -831,11 +831,16 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if (rcode == NOERROR) { + size_t modified = 0; + if (option_bool(OPT_FILTER_A)) - n = rrfilter(header, n, RRFILTER_A); + modified = rrfilter(header, &n, RRFILTER_A); if (option_bool(OPT_FILTER_AAAA)) - n = rrfilter(header, n, RRFILTER_AAAA); + modified += rrfilter(header, &n, RRFILTER_AAAA); + + if (modified > 0) + ede = EDE_FILTERED; } if (doctored) @@ -859,7 +864,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ if (!do_bit) - n = rrfilter(header, n, RRFILTER_DNSSEC); + rrfilter(header, &n, RRFILTER_DNSSEC); } #endif @@ -1807,7 +1812,7 @@ void receive_query(struct listener *listen, time_t now) #endif else { - int stale; + int stale, filtered; int ad_reqd = do_bit; u16 hb3 = header->hb3, hb4 = header->hb4; int fd = listen->fd; @@ -1817,17 +1822,28 @@ void receive_query(struct listener *listen, time_t now) ad_reqd = 1; m = answer_request(header, ((char *) header) + udp_size, (size_t)n, - dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale); + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale, &filtered); if (m >= 1) { - if (stale && have_pseudoheader) + if (have_pseudoheader) { - u16 swap = htons(EDE_STALE); + int ede = EDE_UNSET; - m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, - EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + if (filtered) + ede = EDE_FILTERED; + else if (stale) + ede = EDE_STALE; + + if (ede != EDE_UNSET) + { + u16 swap = htons(ede); + + m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + } } + #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_REPLY, daemon->packet, m, NULL, &source_addr, listen->fd); #endif @@ -2097,7 +2113,7 @@ unsigned char *tcp_request(int confd, time_t now, unsigned char *pheader; unsigned int mark = 0; int have_mark = 0; - int first, last, stale, do_stale = 0; + int first, last, filtered, stale, do_stale = 0; unsigned int flags = 0; u16 hb3, hb4; @@ -2291,7 +2307,7 @@ unsigned char *tcp_request(int confd, time_t now, else /* m > 0 if answered from cache */ m = answer_request(header, ((char *) header) + 65536, (size_t)size, - dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale); + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale, &filtered); /* Do this by steam now we're not in the select() loop */ check_log_writer(1); @@ -2430,13 +2446,23 @@ unsigned char *tcp_request(int confd, time_t now, m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); } } - else if (stale) - { - u16 swap = htons((u16)EDE_STALE); - - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); - } - + else + { + ede = EDE_UNSET; + + if (filtered) + ede = EDE_FILTERED; + else if (stale) + ede = EDE_STALE; + + if (ede != EDE_UNSET) + { + u16 swap = htons((u16)ede); + + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + } + } + check_log_writer(1); *length = htons(m); diff --git a/src/rfc1035.c b/src/rfc1035.c index 75481d3..ea21ffa 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1419,7 +1419,7 @@ static int cache_validated(const struct crec *crecp) size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct in_addr local_addr, struct in_addr local_netmask, time_t now, int ad_reqd, int do_bit, int have_pseudoheader, - int *stale) + int *stale, int *filtered) { char *name = daemon->namebuff; unsigned char *p, *ansp; @@ -1437,6 +1437,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (stale) *stale = 0; + + if (filtered) + *filtered = 0; /* never answer queries with RD unset, to avoid cache snooping. */ if (ntohs(header->ancount) != 0 || @@ -1705,8 +1708,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) continue; - - + if (!(crecp->flags & F_DNSSECOK)) sec_data = 0; @@ -1887,6 +1889,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + + if (filtered) + *filtered = 1; } else if (crecp->flags & F_NEG) { @@ -1947,6 +1952,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) log_query(F_NEG | F_CONFIG | flag, name, NULL, NULL, 0); + + if (filtered) + *filtered = 1; } } } diff --git a/src/rrfilter.c b/src/rrfilter.c index 42d9c21..3a5547a 100644 --- a/src/rrfilter.c +++ b/src/rrfilter.c @@ -156,41 +156,43 @@ static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, i } -/* mode may be remove EDNS0 or DNSSEC RRs or remove A or AAAA from answer section. */ -size_t rrfilter(struct dns_header *header, size_t plen, int mode) +/* mode may be remove EDNS0 or DNSSEC RRs or remove A or AAAA from answer section. + * returns number of modified records. */ +size_t rrfilter(struct dns_header *header, size_t *plen, int mode) { static unsigned char **rrs = NULL; static int rr_sz = 0; unsigned char *p = (unsigned char *)(header+1); - int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar; + size_t rr_found = 0; + int i, rdlen, qtype, qclass, chop_an, chop_ns, chop_ar; if (ntohs(header->qdcount) != 1 || - !(p = skip_name(p, header, plen, 4))) - return plen; + !(p = skip_name(p, header, *plen, 4))) + return 0; GETSHORT(qtype, p); GETSHORT(qclass, p); /* First pass, find pointers to start and end of all the records we wish to elide: records added for DNSSEC, unless explicitly queried for */ - for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; + for (chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) { unsigned char *pstart = p; int type, class; - if (!(p = skip_name(p, header, plen, 10))) - return plen; + if (!(p = skip_name(p, header, *plen, 10))) + return rr_found; GETSHORT(type, p); GETSHORT(class, p); p += 4; /* TTL */ GETSHORT(rdlen, p); - if (!ADD_RDLEN(header, p, plen, rdlen)) - return plen; + if (!ADD_RDLEN(header, p, *plen, rdlen)) + return rr_found; if (mode == RRFILTER_EDNS0) /* EDNS */ { @@ -225,7 +227,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode) } if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) - return plen; + return rr_found; rrs[rr_found++] = pstart; rrs[rr_found++] = p; @@ -240,7 +242,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode) /* Nothing to do. */ if (rr_found == 0) - return plen; + return rr_found; /* Second pass, look for pointers in names in the records we're keeping and make sure they don't point to records we're going to elide. This is theoretically possible, but unlikely. If @@ -248,38 +250,38 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode) p = (unsigned char *)(header+1); /* question first */ - if (!check_name(&p, header, plen, 0, rrs, rr_found)) - return plen; + if (!check_name(&p, header, *plen, 0, rrs, rr_found)) + return rr_found; p += 4; /* qclass, qtype */ /* Now answers and NS */ - if (!check_rrs(p, header, plen, 0, rrs, rr_found)) - return plen; + if (!check_rrs(p, header, *plen, 0, rrs, rr_found)) + return rr_found; /* Third pass, actually fix up pointers in the records */ p = (unsigned char *)(header+1); - check_name(&p, header, plen, 1, rrs, rr_found); + check_name(&p, header, *plen, 1, rrs, rr_found); p += 4; /* qclass, qtype */ - check_rrs(p, header, plen, 1, rrs, rr_found); + check_rrs(p, header, *plen, 1, rrs, rr_found); /* Fourth pass, elide records */ - for (p = rrs[0], i = 1; i < rr_found; i += 2) + for (p = rrs[0], i = 1; (unsigned)i < rr_found; i += 2) { unsigned char *start = rrs[i]; - unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen; + unsigned char *end = ((unsigned)i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + *plen; memmove(p, start, end-start); p += end-start; } - plen = p - (unsigned char *)header; + *plen = p - (unsigned char *)header; header->ancount = htons(ntohs(header->ancount) - chop_an); header->nscount = htons(ntohs(header->nscount) - chop_ns); header->arcount = htons(ntohs(header->arcount) - chop_ar); - return plen; + return rr_found; } /* This is used in the DNSSEC code too, hence it's exported */ -- cgit v1.2.1 From ff28a485cf8b592a6c21b39bbb8e42f5bc390685 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 20 Mar 2023 22:22:46 +0000 Subject: Close Debian bug. --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index b903774..8996f88 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ dnsmasq (2.90-1) unstable; urgency=low - * New upstream. + * New upstream. (closes: #1033165) -- Simon Kelley Mon, 6 Mar 2023 23:22:06 +0000 -- cgit v1.2.1