From 12a9aa7c628e2d7dcd34949603848a3fb53fce9c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 8 Jun 2021 22:10:55 +0100 Subject: [PATCH] Major rewrite of the DNS server and domain handling code. This should be largely transparent, but it drastically improves performance and reduces memory foot-print when configuring large numbers domains of the form local=/adserver.com/ or local=/adserver.com/# Lookup times now grow as log-to-base-2 of the number of domains, rather than greater than linearly, as before. The change makes multiple addresses associated with a domain work address=/example.com/1.2.3.4 address=/example.com/5.6.7.8 It also handles multiple upstream servers for a domain better; using the same try/retry alogrithms as non domain-specific servers. This also applies to DNSSEC-generated queries. Finally, some of the oldest and gnarliest code in dnsmasq has had a significant clean-up. It's far from perfect, but it _is_ better. --- CHANGELOG | 21 +- Makefile | 2 +- bld/Android.mk | 3 +- src/cache.c | 30 +- src/dbus.c | 2 +- src/dnsmasq.h | 69 +- src/domain-match.c | 379 +++++++++ src/forward.c | 1883 ++++++++++++++++++++------------------------ src/loop.c | 22 +- src/network.c | 332 ++++---- src/option.c | 269 ++++--- src/rfc1035.c | 80 +- 12 files changed, 1691 insertions(+), 1401 deletions(-) create mode 100644 src/domain-match.c diff --git a/CHANGELOG b/CHANGELOG index 87adaf7..8eaf294 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,8 +20,25 @@ version 2.86 which the bug manifests itself, and then working out exactly what was going on. - - + Major rewrite of the DNS server and domain handling code. + This should be largely transparent, but it drastically + improves performance and reduces memory foot-print when + configuring large numbers domains of the form + local=/adserver.com/ + or + local=/adserver.com/# + Lookup times now grow as log-to-base-2 of the number of domains, + rather than greater than linearly, as before. + The change makes multiple addresses associated with a domain work + address=/example.com/1.2.3.4 + address=/example.com/5.6.7.8 + It also handles multiple upstream servers for a domain better; using + the same try/retry alogrithms as non domain-specific servers. This + also applies to DNSSEC-generated queries. + Finally, some of the oldest and gnarliest code in dnsmasq has had + a significant clean-up. It's far from perfect, but it _is_ better. + + version 2.85 Fix problem with DNS retries in 2.83/2.84. The new logic in 2.83/2.84 which merges distinct requests diff --git a/Makefile b/Makefile index e4c3f5c..9148ed5 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \ - metrics.o hash_questions.o + metrics.o hash_questions.o domain-match.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h ip6addr.h metrics.h diff --git a/bld/Android.mk b/bld/Android.mk index f924be9..601d89b 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -11,7 +11,8 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ radv.c slaac.c auth.c ipset.c domain.c \ dnssec.c dnssec-openssl.c blockdata.c tables.c \ loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \ - crypto.c dump.c ubus.c metrics.c hash_questions.c + crypto.c dump.c ubus.c metrics.c hash_questions.c \ + domain-match.c LOCAL_MODULE := dnsmasq diff --git a/src/cache.c b/src/cache.c index 7988a3e..3f59cd3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1605,16 +1605,13 @@ int cache_make_stat(struct txt_record *t) serv->flags &= ~SERV_COUNTED; for (serv = daemon->servers; serv; serv = serv->next) - if (!(serv->flags & - (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND))) + if (!(serv->flags & SERV_COUNTED)) { char *new, *lenp; int port, newlen, bytes_avail, bytes_needed; unsigned int queries = 0, failed_queries = 0; for (serv1 = serv; serv1; serv1 = serv1->next) - if (!(serv1->flags & - (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) && - sockaddr_isequal(&serv->addr, &serv1->addr)) + if (!(serv1->flags & SERV_COUNTED) && sockaddr_isequal(&serv->addr, &serv1->addr)) { serv1->flags |= SERV_COUNTED; queries += serv1->queries; @@ -1689,15 +1686,12 @@ void dump_cache(time_t now) serv->flags &= ~SERV_COUNTED; for (serv = daemon->servers; serv; serv = serv->next) - if (!(serv->flags & - (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND))) + if (!(serv->flags & SERV_COUNTED)) { int port; unsigned int queries = 0, failed_queries = 0; for (serv1 = serv; serv1; serv1 = serv1->next) - if (!(serv1->flags & - (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) && - sockaddr_isequal(&serv->addr, &serv1->addr)) + if (!(serv1->flags & SERV_COUNTED) && sockaddr_isequal(&serv->addr, &serv1->addr)) { serv1->flags |= SERV_COUNTED; queries += serv1->queries; @@ -1885,14 +1879,14 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) { unsigned int rcode = addr->log.rcode; - if (rcode == SERVFAIL) - dest = "SERVFAIL"; - else if (rcode == REFUSED) - dest = "REFUSED"; - else if (rcode == NOTIMP) - dest = "not implemented"; - else - sprintf(daemon->addrbuff, "%u", rcode); + if (rcode == SERVFAIL) + dest = "SERVFAIL"; + else if (rcode == REFUSED) + dest = "REFUSED"; + else if (rcode == NOTIMP) + dest = "not implemented"; + else + sprintf(daemon->addrbuff, "%u", rcode); } else inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, diff --git a/src/dbus.c b/src/dbus.c index 2516483..0a67e3c 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -377,7 +377,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) /* 0.0.0.0 for server address == NULL, for Dbus */ if (addr.in.sin_family == AF_INET && addr.in.sin_addr.s_addr == 0) - flags |= SERV_NO_ADDR; + flags |= SERV_LITERAL_ADDRESS; if (strings) { diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 95dc8ae..b64b22a 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -498,7 +498,7 @@ struct crec { #define F_NO_RR (1u<<25) #define F_IPSET (1u<<26) #define F_NOEXTRA (1u<<27) -#define F_SERVFAIL (1u<<28) /* currently unused. */ +#define F_DOMAINSRV (1u<<28) #define F_RCODE (1u<<29) #define F_SRV (1u<<30) @@ -524,19 +524,20 @@ union mysockaddr { #define IFACE_PERMANENT 4 -#define SERV_FROM_RESOLV 1 /* 1 for servers from resolv, 0 for command line. */ -#define SERV_NO_ADDR 2 /* no server, this domain is local only */ -#define SERV_LITERAL_ADDRESS 4 /* addr is the answer, not the server */ -#define SERV_HAS_DOMAIN 8 /* server for one domain only */ +/* The actual values here matter, since we sort on them to get records in the order + IPv6 addr, IPv4 addr, all zero return, no-data return, send upstream. */ +#define SERV_LITERAL_ADDRESS 1 /* addr is the answer, or NoDATA is the answer, depending on the next three flags */ +#define SERV_ALL_ZEROS 2 /* return all zeros for A and AAAA */ +#define SERV_4ADDR 4 /* addr is IPv4 */ +#define SERV_6ADDR 8 /* addr is IPv6 */ #define SERV_HAS_SOURCE 16 /* source address defined */ #define SERV_FOR_NODOTS 32 /* server for names with no domain part only */ #define SERV_WARNED_RECURSIVE 64 /* avoid warning spam */ #define SERV_FROM_DBUS 128 /* 1 if source is DBus */ #define SERV_MARK 256 /* for mark-and-delete */ -#define SERV_TYPE (SERV_HAS_DOMAIN | SERV_FOR_NODOTS) #define SERV_COUNTED 512 /* workspace for log code */ #define SERV_USE_RESOLV 1024 /* forward this domain in the normal way */ -#define SERV_NO_REBIND 2048 /* inhibit dns-rebind protection */ +#define SERV_FROM_RESOLV 2048 /* 1 for servers from resolv, 0 for command line. */ #define SERV_FROM_FILE 4096 /* read from --servers-file */ #define SERV_LOOP 8192 /* server causes forwarding loop */ #define SERV_DO_DNSSEC 16384 /* Validate DNSSEC when using this server */ @@ -561,19 +562,46 @@ struct randfd_list { struct randfd_list *next; }; + struct server { + int flags; + char *domain; + struct server *next; + int serial, arrayposn; + int last_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; + int tcpfd, edns_pktsz; time_t pktsz_reduced; unsigned int queries, failed_queries; + time_t forwardtime; + int forwardcount; #ifdef HAVE_LOOP u32 uid; #endif - struct server *next; +}; + +/* First three fields must match struct server in next three definitions.. */ +struct serv_addr4 { + int flags; + char *domain; + struct server *next; + struct in_addr addr; +}; + +struct serv_addr6 { + int flags; + char *domain; + struct server *next; + struct in6_addr addr; +}; + +struct serv_local { + int flags; + char *domain; + struct server *next; }; struct ipsets { @@ -662,6 +690,7 @@ struct hostsfile { #define STAT_SECURE_WILDCARD 7 #define STAT_OK 8 #define STAT_ABANDONED 9 +#define STAT_INPROGRESS 10 #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 @@ -1054,7 +1083,8 @@ extern struct daemon { char *lease_change_command; struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces; struct bogus_addr *bogus_addr, *ignore_addr; - struct server *servers; + struct server *servers, *local_domains, **serverarray, *no_rebind; + int serverarraysz; struct ipsets *ipsets; int log_fac; /* log facility */ char *log_file; /* optional log file */ @@ -1126,9 +1156,6 @@ extern struct daemon { struct serverfd *sfds; struct irec *interfaces; struct listener *listeners; - struct server *last_server; - time_t forwardtime; - int forwardcount; struct server *srv_save; /* Used for resend on DoD */ size_t packet_len; /* " " */ int fd_save; /* " " */ @@ -1244,9 +1271,7 @@ unsigned char *skip_questions(struct dns_header *header, size_t plen); unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen); unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep); -size_t setup_reply(struct dns_header *header, size_t qlen, - union all_addr *addrp, unsigned int flags, - unsigned long ttl); +void setup_reply(struct dns_header *header, unsigned int flags); int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored); @@ -1695,3 +1720,13 @@ int do_arp_script_run(void); void dump_init(void); void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst); #endif + +/* domain-match.c */ +void build_server_array(void); +int lookup_domain(char *qdomain, int flags, int *lowout, int *highout); +int filter_servers(int seed, int flags, int *lowout, int *highout); +int is_local_answer(time_t now, int first, char *name); +#ifdef HAVE_DNSSEC +int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp); +#endif + diff --git a/src/domain-match.c b/src/domain-match.c new file mode 100644 index 0000000..8a478eb --- /dev/null +++ b/src/domain-match.c @@ -0,0 +1,379 @@ +/* dnsmasq is Copyright (c) 2000-2021 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "dnsmasq.h" + +static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv); +static int order_qsort(const void *a, const void *b); +static int order_servers(struct server *s, struct server *s2); + +void build_server_array(void) +{ + struct server *serv; + int count = 0; + + for (serv = daemon->servers; serv; serv = serv->next) + count++; + + for (serv = daemon->local_domains; serv; serv = serv->next) + count++; + + if (count > daemon->serverarraysz) + { + struct server **new; + + if ((new = whine_malloc(count * sizeof(struct server *)))) + { + if (daemon->serverarray) + free(daemon->serverarray); + + daemon->serverarray = new; + daemon->serverarraysz = count; + } + } + + count = 0; + + for (serv = daemon->servers; serv; serv = serv->next, count++) + { + daemon->serverarray[count] = serv; + serv->serial = count; + serv->last_server = -1; + } + + for (serv = daemon->local_domains; serv; serv = serv->next, count++) + daemon->serverarray[count] = serv; + + qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort); + + /* servers need the location in the array to find all the whole + set of equivalent servers from a pointer to a single one. */ + for (count = 0; count < daemon->serverarraysz; count++) + if (!(daemon->serverarray[count]->flags & SERV_LITERAL_ADDRESS)) + daemon->serverarray[count]->arrayposn = count; +} + +/* we're looking for the server whose domain is the longest exact match + to the RH end of qdomain, or a local address if the flags match. + Add '.' to the LHS of the query string so + server=/.example.com/ works. + + A flag of F_SERVER returns an upstream server only. + A flag of F_DNSSECOK returns a DNSSEC capable server only and + also disables NODOTS servers from consideration. + A flag of F_DOMAINSRV returns a domain-specific server only. + return 0 if nothing found, 1 otherwise. +*/ +int lookup_domain(char *qdomain, int flags, int *lowout, int *highout) +{ + int rc, nodots, leading_dot = 1; + ssize_t qlen, maxlen; + int try, high, low = 0; + int nlow = 0, nhigh = 0; + char *cp; + + /* may be no configured servers. */ + if (daemon->serverarraysz == 0) + return 0; + + maxlen = strlen(daemon->serverarray[0]->domain); + + /* find query length and presence of '.' */ + for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++) + if (*cp == '.') + nodots = 0; + + /* Handle empty name, and searches for DNSSEC queries without + diverting to NODOTS servers. */ + if (qlen == 0 || flags & F_DNSSECOK) + nodots = 0; + + /* No point trying to match more than the largest server domain */ + if (qlen > maxlen) + { + qdomain += qlen - maxlen; + qlen = maxlen; + leading_dot = 0; + } + + /* Search shorter and shorter RHS substrings for a match */ + while (qlen >= 0) + { + /* Note that when we chop off a character, all the possible matches + MUST be at a larger index than the nearest failing match with one more + character, since the array is sorted longest to smallest. Hence + we don't reset low here. */ + high = daemon->serverarraysz; + + /* binary search */ + do + { + try = (low + high)/2; + + if ((rc = order(qdomain, leading_dot, qlen, daemon->serverarray[try])) == 0) + break; + + if (rc < 0) + { + if (high == try) + break; + high = try; + } + else + { + if (low == try) + break; + low = try; + } + } + while (low != high); + + if (rc == 0) + { + /* We've matched a setting which says to use servers without a domain. + Continue the search with empty query (the last character gets stripped + by the loop. */ + if (daemon->serverarray[try]->flags & SERV_USE_RESOLV) + { + qdomain += qlen - 1; + qlen = 1; + } + else + { + /* We have a match, but it may only be (say) an IPv6 address, and + if the query wasn't for an AAAA record, it's no good, and we need + to continue generalising */ + if (filter_servers(try, flags, &nlow, &nhigh)) + break; + } + } + + if (leading_dot) + leading_dot = 0; + else + { + qlen--; + qdomain++; + } + } + + /* domain has no dots, and we have at least one server configured to handle such, + These servers always sort to the very end of the array. + A configured server eg server=/lan/ will take precdence. */ + if (nodots && + (daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) && + (nlow == nhigh || strlen(daemon->serverarray[nlow]->domain) == 0)) + filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh); + + /* F_DOMAINSRV returns only domain-specific servers, so if we got to a + general server, return empty set. */ + if (nlow != nhigh && (flags & F_DOMAINSRV) && strlen(daemon->serverarray[nlow]->domain) == 0) + nlow = nhigh; + + if (lowout) + *lowout = nlow; + + if (highout) + *highout = nhigh; + + if (nlow == nhigh) + return 0; + + return 1; +} + + +int filter_servers(int seed, int flags, int *lowout, int *highout) +{ + int nlow = seed, nhigh = seed; + int i; + + /* expand nlow and nhigh to cover all the records with the same domain + nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers, + which can happen below. */ + while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0) + nlow--; + + while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0) + nhigh++; + + nhigh++; + + /* Now the servers are on order between low and high, in the order + return zero for both, IPv6 addr, IPv4 addr, no-data return, send upstream. + + See which of those match our query in that priority order and narrow (low, high) */ + + for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++); + + if (i != nlow && (flags & F_IPV6)) + nhigh = i; + else + { + nlow = i; + + for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++); + + if (i != nlow && (flags & F_IPV4)) + nhigh = i; + else + { + nlow = i; + + for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++); + + if (i != nlow && (flags & (F_IPV4 | F_IPV6))) + nhigh = i; + else + { + nlow = i; + + for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++); + + /* --local=/domain/, only return if we don't need a server. */ + if (i != nlow && !(flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))) + nhigh = i; + else + { + nlow = i; + /* If we want a server that can do DNSSEC, and this one can't, + return nothing. */ + if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC)) + nlow = nhigh; + } + } + } + } + + *lowout = nlow; + *highout = nhigh; + + return (nlow != nhigh); +} + +int is_local_answer(time_t now, int first, char *name) +{ + int flags = 0; + int rc = 0; + + if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS) + { + if (flags & SERV_4ADDR) + rc = F_IPV4; + else if (flags & SERV_6ADDR) + rc = F_IPV6; + else if (flags & SERV_ALL_ZEROS) + rc = F_IPV4 | F_IPV6; + else + rc = check_for_local_domain(name, now) ? F_NOERR : F_NXDOMAIN; + } + + return rc; +} + +#ifdef HAVE_DNSSEC +int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp) +{ + int first, last, index; + + /* Find server to send DNSSEC query to. This will normally be the + same as for the original query, but may be another if + servers for domains are involved. */ + if (!lookup_domain(keyname, F_DNSSECOK, &first, &last)) + return -1; + + for (index = first; index != last; index++) + if (daemon->serverarray[index] == server) + break; + + /* No match to server used for original query. + Use newly looked up set. */ + if (index == last) + index = daemon->serverarray[first]->last_server == -1 ? + first : daemon->serverarray[first]->last_server; + + if (firstp) + *firstp = first; + + if (lastp) + *lastp = last; + + return index; +} +#endif + +/* order by size, then by dictionary order */ +static int order(char *qdomain, int leading_dot, size_t qlen, struct server *serv) +{ + size_t dlen = 0; + int rc; + + /* servers for dotless names always sort last + searched for name is never dotless. */ + if (serv->flags & SERV_FOR_NODOTS) + return -1; + + if (leading_dot) + qlen++; + + dlen = strlen(serv->domain); + + if (qlen < dlen) + return 1; + + if (qlen > dlen) + return -1; + + if (leading_dot && (rc = '.' - serv->domain[0]) != 0) + return rc; + + return strcmp(qdomain, leading_dot ? &serv->domain[1] : serv->domain); +} + +static int order_servers(struct server *s1, struct server *s2) +{ + size_t dlen = strlen(s1->domain); + + /* need full comparison of dotless servers in + order_qsort() and filter_servers() */ + if (s1->flags & SERV_FOR_NODOTS) + return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1; + + return order(s1->domain, 0, dlen, s2); +} + +static int order_qsort(const void *a, const void *b) +{ + int rc; + + struct server *s1 = *((struct server **)a); + struct server *s2 = *((struct server **)b); + + rc = order_servers(s1, s2); + + /* Sort all literal NODATA and local IPV4 or IPV6 responses together, + in a very specific order. */ + if (rc == 0) + rc = (s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) - + (s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)); + + /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */ + if (rc == 0) + if (!(s1->flags & SERV_LITERAL_ADDRESS)) + rc = s1->serial - s2->serial; + + return rc; +} diff --git a/src/forward.c b/src/forward.c index 3502b3e..bb356c0 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, void *hash); +static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp); static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); static unsigned short get_id(void); @@ -106,146 +106,6 @@ int send_from(int fd, int nowild, char *packet, size_t len, return 1; } -static unsigned int search_servers(time_t now, union all_addr **addrpp, unsigned int qtype, - char *qdomain, int *type, char **domain, int *norebind) - -{ - /* If the query ends in the domain in one of our servers, set - domain to point to that name. We find the largest match to allow both - domain.org and sub.domain.org to exist. */ - - unsigned int namelen = strlen(qdomain); - unsigned int matchlen = 0; - struct server *serv; - unsigned int flags = 0; - static union all_addr zero; - - for (serv = daemon->servers; serv; serv=serv->next) - if (qtype == F_DNSSECOK && !(serv->flags & SERV_DO_DNSSEC)) - continue; - /* domain matches take priority over NODOTS matches */ - else if ((serv->flags & SERV_FOR_NODOTS) && *type != SERV_HAS_DOMAIN && !strchr(qdomain, '.') && namelen != 0) - { - unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; - *type = SERV_FOR_NODOTS; - if ((serv->flags & SERV_NO_REBIND) && norebind) - *norebind = 1; - else if (serv->flags & SERV_NO_ADDR) - flags = F_NXDOMAIN; - else if (serv->flags & SERV_LITERAL_ADDRESS) - { - /* literal address = '#' -> return all-zero address for IPv4 and IPv6 */ - if ((serv->flags & SERV_USE_RESOLV) && (qtype & (F_IPV6 | F_IPV4))) - { - memset(&zero, 0, sizeof(zero)); - flags = qtype; - *addrpp = &zero; - } - else if (sflag & qtype) - { - flags = sflag; - if (serv->addr.sa.sa_family == AF_INET) - *addrpp = (union all_addr *)&serv->addr.in.sin_addr; - else - *addrpp = (union all_addr *)&serv->addr.in6.sin6_addr; - } - else if (!flags || (flags & F_NXDOMAIN)) - flags = F_NOERR; - } - } - else if (serv->flags & SERV_HAS_DOMAIN) - { - unsigned int domainlen = strlen(serv->domain); - char *matchstart = qdomain + namelen - domainlen; - if (namelen >= domainlen && - hostname_isequal(matchstart, serv->domain) && - (domainlen == 0 || namelen == domainlen || *(matchstart-1) == '.' )) - { - if ((serv->flags & SERV_NO_REBIND) && norebind) - *norebind = 1; - else - { - unsigned int sflag = serv->addr.sa.sa_family == AF_INET ? F_IPV4 : F_IPV6; - /* implement priority rules for --address and --server for same domain. - --address wins if the address is for the correct AF - --server wins otherwise. */ - if (domainlen != 0 && domainlen == matchlen) - { - if ((serv->flags & SERV_LITERAL_ADDRESS)) - { - if (!(sflag & qtype) && flags == 0) - continue; - } - else - { - if (flags & (F_IPV4 | F_IPV6)) - continue; - } - } - - if (domainlen >= matchlen) - { - *type = serv->flags & (SERV_HAS_DOMAIN | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_DO_DNSSEC); - *domain = serv->domain; - matchlen = domainlen; - if (serv->flags & SERV_NO_ADDR) - flags = F_NXDOMAIN; - else if (serv->flags & SERV_LITERAL_ADDRESS) - { - /* literal address = '#' -> return all-zero address for IPv4 and IPv6 */ - if ((serv->flags & SERV_USE_RESOLV) && (qtype & (F_IPV6 | F_IPV4))) - { - memset(&zero, 0, sizeof(zero)); - flags = qtype; - *addrpp = &zero; - } - else if (sflag & qtype) - { - flags = sflag; - if (serv->addr.sa.sa_family == AF_INET) - *addrpp = (union all_addr *)&serv->addr.in.sin_addr; - else - *addrpp = (union all_addr *)&serv->addr.in6.sin6_addr; - } - else if (!flags || (flags & F_NXDOMAIN)) - flags = F_NOERR; - } - else - flags = 0; - } - } - } - } - - if (flags == 0 && !(qtype & (F_QUERY | F_DNSSECOK)) && - option_bool(OPT_NODOTS_LOCAL) && !strchr(qdomain, '.') && namelen != 0) - /* don't forward A or AAAA queries for simple names, except the empty name */ - flags = F_NOERR; - - if (flags == F_NXDOMAIN && check_for_local_domain(qdomain, now)) - flags = F_NOERR; - - if (flags) - { - if (flags == F_NXDOMAIN || flags == F_NOERR) - log_query(flags | qtype | F_NEG | F_CONFIG | F_FORWARD, qdomain, NULL, NULL); - else - { - /* handle F_IPV4 and F_IPV6 set on ANY query to 0.0.0.0/:: domain. */ - if (flags & F_IPV4) - log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, qdomain, *addrpp, NULL); - if (flags & F_IPV6) - log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, qdomain, *addrpp, NULL); - } - } - else if ((*type) & SERV_USE_RESOLV) - { - *type = 0; /* use normal servers for this domain */ - *domain = NULL; - } - return flags; -} - #ifdef HAVE_CONNTRACK static void set_outgoing_mark(struct frec *forward, int fd) { @@ -285,33 +145,87 @@ static void server_send_log(struct server *server, int fd, } #endif -static int server_test_type(const struct server *server, - const char *domain, int type, int extratype) +static int domain_no_rebind(char *domain) { - return (type == (server->flags & (SERV_TYPE | extratype)) && - (type != SERV_HAS_DOMAIN || hostname_isequal(domain, server->domain)) && - !(server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))); + struct server *serv; + int dlen = (int)strlen(domain); + + /* flags is misused to hold length of domain. */ + for (serv = daemon->no_rebind; serv; serv = serv->next) + if (dlen >= serv->flags && strcmp(serv->domain, &domain[dlen - serv->flags]) == 0) + return 1; + + return 0; +} + +size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, int first, int last) +{ + int trunc = 0; + unsigned char *p; + int start; + union all_addr addr; + + if (flags & (F_NXDOMAIN | F_NOERR)) + log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL); + + setup_reply(header, flags); + + if (!(p = skip_questions(header, size))) + return 0; + + if (flags & gotname & F_IPV4) + for (start = first; start != last; start++) + { + struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start]; + + if (srv->flags & SERV_ALL_ZEROS) + memset(&addr, 0, sizeof(addr)); + else + addr.addr4 = srv->addr; + + header->ancount = htons(ntohs(header->ancount) + 1); + add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr); + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL); + } + + if (flags & gotname & F_IPV6) + for (start = first; start != last; start++) + { + struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start]; + + if (srv->flags & SERV_ALL_ZEROS) + memset(&addr, 0, sizeof(addr)); + else + addr.addr6 = srv->addr; + + header->ancount = htons(ntohs(header->ancount) + 1); + add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr); + log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL); + } + + if (trunc) + header->hb3 |= HB3_TC; + + return p - (unsigned char *)header; } static int forward_query(int udpfd, union mysockaddr *udpaddr, union all_addr *dst_addr, unsigned int dst_iface, - struct dns_header *header, size_t plen, time_t now, + struct dns_header *header, size_t plen, char *limit, time_t now, struct frec *forward, int ad_reqd, int do_bit) { - char *domain = NULL; - int type = SERV_DO_DNSSEC, norebind = 0; - union all_addr *addrp = NULL; unsigned int flags = 0; unsigned int fwd_flags = 0; - struct server *start = NULL; - void *hash = hash_questions(header, plen, daemon->namebuff); -#ifdef HAVE_DNSSEC - int do_dnssec = 0; -#endif + int is_dnssec = forward && (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)); + struct server *master; unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + void *hash = hash_questions(header, plen, daemon->namebuff); unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); int old_src = 0; - + int first, last, start = 0; + int subnet, cacheable, forwarded = 0; + size_t edns0_len; + unsigned char *pheader; (void)do_bit; if (header->hb4 & HB4_CD) @@ -357,7 +271,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (!daemon->free_frec_src) { query_full(now); - goto frec_err; + goto reply; } src = daemon->free_frec_src; @@ -380,269 +294,286 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } /* retry existing query */ - if (forward) + 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. - If that generates an answer, it will become the new default - for this server */ - forward->flags |= FREC_TEST_PKTSZ; + /* new query */ + /* don't forward A or AAAA queries for simple names, except the empty name */ + if (option_bool(OPT_NODOTS_LOCAL) && + (gotname & (F_IPV4 | F_IPV6)) && + !strchr(daemon->namebuff, '.') && + strlen(daemon->namebuff) != 0) + { + flags = F_NOERR; + goto reply; + } + + flags = 0; + + /* no available server. */ + if (!lookup_domain(daemon->namebuff, gotname, &first, &last)) + goto reply; + + /* Configured answer. */ + if ((flags = is_local_answer(now, first, daemon->namebuff))) + goto reply; + + master = daemon->serverarray[first]; + + if (!(forward = get_new_frec(now, NULL, NULL))) + goto reply; + /* table full - flags == 0, return REFUSED */ + + forward->frec_src.source = *udpaddr; + forward->frec_src.orig_id = ntohs(header->id); + 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(); + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; + forward->flags = fwd_flags; + if (domain_no_rebind(daemon->namebuff)) + forward->flags |= FREC_NOREBIND; + if (header->hb4 & HB4_CD) + forward->flags |= FREC_CHECKING_DISABLED; + if (ad_reqd) + forward->flags |= FREC_AD_QUESTION; +#ifdef HAVE_DNSSEC + forward->work_counter = DNSSEC_WORK; + if (do_bit) + forward->flags |= FREC_DO_QUESTION; +#endif + + start = first; + + if (option_bool(OPT_ALL_SERVERS)) + forward->forwardall = 1; + + if (!option_bool(OPT_ORDER)) + { + if (master->forwardcount++ > FORWARD_TEST || + difftime(now, master->forwardtime) > FORWARD_TIME || + master->last_server == -1) + { + master->forwardtime = now; + master->forwardcount = 0; + forward->forwardall = 1; + } + else + start = master->last_server; + } + } + else + { + /* retry on existing query, from original source. Send to all available servers */ #ifdef HAVE_DNSSEC /* If we've already got an answer to this query, but we're awaiting keys for validation, there's no point retrying the query, retry the key query instead...... */ if (forward->blocking_query) { - int fd, is_sign; + int is_sign; unsigned char *pheader; - forward->flags &= ~FREC_TEST_PKTSZ; - while (forward->blocking_query) forward = forward->blocking_query; blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); plen = forward->stash_len; - - forward->flags |= FREC_TEST_PKTSZ; + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) PUTSHORT(SAFE_PKTSZ, pheader); - if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) - server_send_log(forward->sentto, fd, header, plen, - DUMP_SEC_QUERY, - F_NOEXTRA | F_DNSSEC, "retry", "dnssec"); - - return 1; - } -#endif - - /* retry on existing query, from original source. Send to all available servers */ - domain = forward->sentto->domain; - forward->sentto->failed_queries++; - if (!option_bool(OPT_ORDER) && old_src) - { + /* Find suitable servers: should never fail. */ + if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last)) + return 0; + + is_dnssec = 1; forward->forwardall = 1; - daemon->last_server = NULL; } - type = forward->sentto->flags & SERV_TYPE; -#ifdef HAVE_DNSSEC - do_dnssec = forward->sentto->flags & SERV_DO_DNSSEC; + else #endif - - if (!(start = forward->sentto->next)) - start = daemon->servers; /* at end of list, recycle */ - header->id = htons(forward->new_id); - } - else - { - /* new query */ - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - -#ifdef HAVE_DNSSEC - do_dnssec = type & SERV_DO_DNSSEC; -#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 */ - - if (forward) { - forward->frec_src.source = *udpaddr; - forward->frec_src.orig_id = ntohs(header->id); - 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(); - memcpy(forward->hash, hash, HASH_SIZE); - forward->forwardall = 0; - forward->flags = fwd_flags; - if (norebind) - forward->flags |= FREC_NOREBIND; - if (header->hb4 & HB4_CD) - forward->flags |= FREC_CHECKING_DISABLED; - if (ad_reqd) - forward->flags |= FREC_AD_QUESTION; -#ifdef HAVE_DNSSEC - forward->work_counter = DNSSEC_WORK; - if (do_bit) - forward->flags |= FREC_DO_QUESTION; -#endif + /* retry on existing query, from original source. Send to all available servers */ + forward->sentto->failed_queries++; - header->id = htons(forward->new_id); + if (!filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last)) + goto reply; - /* In strict_order mode, always try servers in the order - specified in resolv.conf, if a domain is given - always try all the available servers, - otherwise, use the one last known to work. */ + master = daemon->serverarray[first]; - if (type == 0) - { - if (option_bool(OPT_ORDER)) - start = daemon->servers; - else if (!(start = daemon->last_server) || - daemon->forwardcount++ > FORWARD_TEST || - difftime(now, daemon->forwardtime) > FORWARD_TIME) - { - start = daemon->servers; - forward->forwardall = 1; - daemon->forwardcount = 0; - daemon->forwardtime = now; - } - } + /* Forward to all available servers on retry of query from same host. */ + if (!option_bool(OPT_ORDER) && old_src) + forward->forwardall = 1; else { - start = daemon->servers; - if (!option_bool(OPT_ORDER)) - forward->forwardall = 1; - } + start = forward->sentto->arrayposn; + + if (option_bool(OPT_ORDER)) + { + /* In strict order mode, there must be a server later in the list + left to send to, otherwise without the forwardall mechanism, + code further on will cycle around the list forwever if they + all return REFUSED. If at the last, give up. */ + if (++start == last) + goto reply; + } + } } - } - - /* check for send errors here (no route to host) - if we fail to send to all nameservers, send back an error - packet straight away (helps modem users when offline) */ - - if (!flags && forward) - { - struct server *firstsentto = start; - int subnet, cacheable, forwarded = 0; - size_t edns0_len; - unsigned char *pheader; + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. + If that generates an answer, it will become the new default + for this server */ + forward->flags |= FREC_TEST_PKTSZ; + } + + /* We may be resending a DNSSEC query here, for which the below processing is not necessary. */ + if (!is_dnssec) + { /* If a query is retried, use the log_id for the retry when logging the answer. */ forward->frec_src.log_id = daemon->log_id; + header->id = htons(forward->new_id); + 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) + if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC)) { plen = add_do_bit(header, plen, ((unsigned char *) header) + PACKETSZ); - + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, this allows it to select auth servers when one is returning bad data. */ if (option_bool(OPT_DNSSEC_DEBUG)) header->hb4 |= HB4_CD; - + } #endif - + if (find_pseudoheader(header, plen, &edns0_len, &pheader, NULL, NULL)) { /* If there wasn't a PH before, and there is now, we added it. */ if (!oph) forward->flags |= FREC_ADDED_PHEADER; - + /* If we're sending an EDNS0 with any options, we can't recreate the query from a reply. */ if (edns0_len > 11) forward->flags |= FREC_HAS_EXTRADATA; - + /* Reduce udp size on retransmits. */ if (forward->flags & FREC_TEST_PKTSZ) PUTSHORT(SAFE_PKTSZ, pheader); } + } + + if (forward->forwardall) + start = first; + + forwarded = 0; + + /* check for send errors here (no route to host) + if we fail to send to all nameservers, send back an error + packet straight away (helps modem users when offline) */ + + while (1) + { + int fd; + struct server *srv = daemon->serverarray[start]; - 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 ((fd = allocate_rfd(&forward->rfds, srv)) != -1) + { - if (server_test_type(start, domain, type, 0) && - ((fd = allocate_rfd(&forward->rfds, start)) != -1)) - { - #ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - set_outgoing_mark(forward, fd); + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + set_outgoing_mark(forward, fd); +#endif + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) + { + /* Difficult one here. If our client didn't send EDNS0, we will have set the UDP + packet size to 512. But that won't provide space for the RRSIGS in many cases. + The RRSIGS will be stripped out before the answer goes back, so the packet should + shrink again. So, if we added a do-bit, bump the udp packet size to the value + known to be OK for this server. We check returned size after stripping and set + the truncated bit if it's still too big. */ + unsigned char *pheader; + int is_sign; + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(srv->edns_pktsz, pheader); + } +#endif + + if (retry_send(sendto(fd, (char *)header, plen, 0, + &srv->addr.sa, + sa_len(&srv->addr)))) + continue; + + if (errno == 0) + { +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &srv->addr); #endif -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) + /* Keep info in case we want to re-send this packet */ + daemon->srv_save = srv; + daemon->packet_len = plen; + daemon->fd_save = fd; + + if (!(forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))) { - /* Difficult one here. If our client didn't send EDNS0, we will have set the UDP - packet size to 512. But that won't provide space for the RRSIGS in many cases. - The RRSIGS will be stripped out before the answer goes back, so the packet should - shrink again. So, if we added a do-bit, bump the udp packet size to the value - known to be OK for this server. We check returned size after stripping and set - the truncated bit if it's still too big. */ - unsigned char *pheader; - int is_sign; - if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) - PUTSHORT(start->edns_pktsz, pheader); - } -#endif - - if (retry_send(sendto(fd, (char *)header, plen, 0, - &start->addr.sa, - sa_len(&start->addr)))) - continue; - - if (errno == 0) - { -#ifdef HAVE_DUMPFILE - dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &start->addr); -#endif - - /* 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"); log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, - &start->addr, NULL); - start->queries++; - forwarded = 1; - forward->sentto = start; - if (!forward->forwardall) - break; - forward->forwardall++; + &srv->addr, NULL); } +#ifdef HAVE_DNSSEC + else + log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, daemon->namebuff, &srv->addr, + querystr("dnssec-retry", (forward->flags & FREC_DNSKEY_QUERY) ? T_DNSKEY : T_DS)); +#endif + + srv->queries++; + forwarded = 1; + forward->sentto = srv; + if (!forward->forwardall) + break; + forward->forwardall++; } - - if (!(start = start->next)) - start = daemon->servers; - - if (start == firstsentto) - break; } - if (forwarded) - return 1; - - /* could not send on, prepare to return */ - header->id = htons(forward->frec_src.orig_id); - free_frec(forward); /* cancel */ - } + if (++start == last) + break; + } - /* could not send on, return empty answer or address if known for whole domain */ - frec_err: + if (forwarded || is_dnssec) + return 1; + + /* could not send on, prepare to return */ + header->id = htons(forward->frec_src.orig_id); + free_frec(forward); /* cancel */ + + reply: if (udpfd != -1) { - plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); + if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, first, last))) + return 0; + if (oph) - plen = add_pseudoheader(header, plen, ((unsigned char *) header) + PACKETSZ, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); } - + return 0; } @@ -734,7 +665,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* RFC 4035 sect 4.6 para 3 */ if (!is_sign && !option_bool(OPT_DNSSEC_PROXY)) header->hb4 &= ~HB4_AD; - + + header->hb4 |= HB4_RA; /* recursion if available */ + if (OPCODE(header) != QUERY) return resize_packet(header, n, pheader, plen); @@ -831,6 +764,158 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server return resize_packet(header, n, pheader, plen); } +#ifdef HAVE_DNSSEC +static int dnssec_validate(struct frec **forwardp, struct dns_header *header, + ssize_t plen, struct server *server, time_t now) +{ + int status = 0; + struct frec *forward = *forwardp; + + /* We've had a reply already, which we're validating. Ignore this duplicate */ + if (forward->blocking_query) + return STAT_INPROGRESS; + + /* Truncated answer can't be validated. + If this is an answer to a DNSSEC-generated query, we still + need to get the client to retry over TCP, so return + an answer with the TC bit set, even if the actual answer fits. + */ + if (header->hb3 & HB3_TC) + status = STAT_TRUNCATED; + + /* If all replies to a query are REFUSED, give up. */ + if (RCODE(header) == REFUSED) + status = STAT_ABANDONED; + + while (1) + { + /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise + would invite infinite loops, since the answers to DNSKEY and DS queries + will not be cached, so they'll be repeated. */ + if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) + { + if (forward->flags & FREC_DNSKEY_QUERY) + status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + else if (forward->flags & FREC_DS_QUERY) + status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); + else + status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, + !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), + NULL, NULL, NULL); +#ifdef HAVE_DUMPFILE + if (status == STAT_BOGUS) + dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, + header, (size_t)plen, &server->addr, NULL); +#endif + } + + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (status == STAT_NEED_DS || status == STAT_NEED_KEY) + { + struct frec *new = NULL, *orig; + int serverind; + + /* Free any saved query */ + if (forward->stash) + blockdata_free(forward->stash); + + /* Now save reply pending receipt of key data */ + if (!(forward->stash = blockdata_alloc((char *)header, plen))) + return STAT_ABANDONED; + forward->stash_len = plen; + + /* Find the original query that started it all.... */ + for (orig = forward; orig->dependent; orig = orig->dependent); + + /* Make sure we don't expire and free the orig frec during the + allocation of a new one. */ + if (--orig->work_counter == 0 || + !(new = get_new_frec(now, NULL, orig)) || + (serverind = dnssec_server(server, daemon->keyname, NULL, NULL)) == -1) + { + status = STAT_ABANDONED; + if (new) + free_frec(new); + } + else + { + int querytype, fd; + struct frec *next = new->next; + size_t nn; + + server = daemon->serverarray[serverind]; + + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->blocking_query = NULL; + + new->sentto = server; + new->rfds = NULL; + new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); + new->forwardall = 0; + + new->dependent = forward; /* to find query awaiting new one. */ + forward->blocking_query = new; /* for garbage cleaning */ + /* validate routines leave name of required record in daemon->keyname */ + if (status == STAT_NEED_KEY) + { + new->flags |= FREC_DNSKEY_QUERY; + querytype = T_DNSKEY; + } + else + { + new->flags |= FREC_DS_QUERY; + querytype = T_DS; + } + + nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, querytype, server->edns_pktsz); + + memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ + new->stash = blockdata_alloc((char *)header, nn); + new->stash_len = nn; + + /* Don't resend this. */ + daemon->srv_save = NULL; + + if ((fd = allocate_rfd(&new->rfds, server)) != -1) + { +#ifdef HAVE_CONNTRACK + if (option_bool(OPT_CONNTRACK)) + set_outgoing_mark(orig, fd); +#endif + server_send_log(server, fd, header, nn, DUMP_SEC_QUERY, + F_NOEXTRA | F_DNSSEC, daemon->keyname, + querystr("dnssec-query", querytype)); + server->queries++; + } + } + return STAT_INPROGRESS; + } + + /* Validated original answer, all done. */ + if (!forward->dependent) + break; + + /* validated subsidiary query, (and cached result) + pop that and return to the previous query we were working on. */ + struct frec *prev = forward->dependent; + free_frec(forward); + *forwardp = forward = prev; + forward->blocking_query = NULL; /* already gone */ + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + plen = forward->stash_len; + } + + return status; +} +#endif + /* sets new last_server */ void reply_query(int fd, time_t now) { @@ -844,7 +929,9 @@ void reply_query(int fd, time_t now) size_t nn; struct server *server; void *hash; - + int first, last, c; + int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -856,25 +943,33 @@ void reply_query(int fd, time_t now) if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR)) return; + + hash = hash_questions(header, n, daemon->namebuff); - /* spoof check: answer must come from known server, */ - for (server = daemon->servers; server; server = server->next) - if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && - sockaddr_isequal(&server->addr, &serveraddr)) + if (!(forward = lookup_frec(ntohs(header->id), fd, hash, &first, &last))) + return; + + /* spoof check: answer must come from known server, also + we may have sent the same query to multiple servers from + the same local socket, and would like to know which one has answered. */ + for (c = first; c != last; c++) + if (sockaddr_isequal(&daemon->serverarray[c]->addr, &serveraddr)) break; - if (!server) + if (c == last) return; + server = daemon->serverarray[c]; + + if (RCODE(header) != REFUSED) + daemon->serverarray[first]->last_server = c; + else if (daemon->serverarray[first]->last_server == c) + daemon->serverarray[first]->last_server = -1; + /* If sufficient time has elapsed, try and expand UDP buffer size again. */ if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) server->edns_pktsz = daemon->edns_pktsz; - hash = hash_questions(header, n, daemon->namebuff); - - if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) - return; - #ifdef HAVE_DUMPFILE dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_REPLY : DUMP_UP_REPLY, (void *)header, n, &serveraddr, NULL); @@ -896,381 +991,188 @@ void reply_query(int fd, time_t now) !(forward->flags & FREC_HAS_EXTRADATA)) /* for broken servers, attempt to send to another one. */ { - unsigned char *pheader; + unsigned char *pheader, *udpsz; + unsigned short udp_size = PACKETSZ; /* default if no EDNS0 */ size_t plen; int is_sign; #ifdef HAVE_DNSSEC + /* DNSSEC queries have a copy of the original query stashed. */ if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) { - struct server *start; - blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); - plen = forward->stash_len; - - forward->forwardall = 2; /* only retry once */ - start = forward->sentto; - - /* for non-domain specific servers, see if we can find another to try. */ - if ((forward->sentto->flags & SERV_TYPE) == 0) - while (1) - { - if (!(start = start->next)) - start = daemon->servers; - if (start == forward->sentto) - break; - - if ((start->flags & SERV_TYPE) == 0 && - (start->flags & SERV_DO_DNSSEC)) - break; - } - - - if ((fd = allocate_rfd(&forward->rfds, start)) != -1) - server_send_log(start, fd, header, plen, - DUMP_SEC_QUERY, - F_NOEXTRA | F_DNSSEC, "retry", "dnssec"); - return; + nn = forward->stash_len; + udp_size = daemon->edns_pktsz; } + else #endif - - /* In strict order mode, there must be a server later in the chain - left to send to, otherwise without the forwardall mechanism, - code further on will cycle around the list forwever if they - all return REFUSED. Note that server is always non-NULL before - this executes. */ - if (option_bool(OPT_ORDER)) - for (server = forward->sentto->next; server; server = server->next) - if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR | SERV_LOOP))) - break; - - /* recreate query from reply */ - pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign, NULL); - if (!is_sign && server) { + nn = 0; + + /* recreate query from reply */ + if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL))) + GETSHORT(udp_size, udpsz); + + /* If the client provides an EDNS0 UDP size, use that to limit our reply. + (bounded by the maximum configured). If no EDNS0, then it + defaults to 512 */ + if (udp_size > daemon->edns_pktsz) + udp_size = daemon->edns_pktsz; + else if (udp_size < PACKETSZ) + udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ + + if (!is_sign && + (nn = resize_packet(header, (size_t)n, pheader, plen)) && + (forward->flags & FREC_DO_QUESTION)) + add_do_bit(header, nn, (unsigned char *)pheader + plen); + header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); - if ((nn = resize_packet(header, (size_t)n, pheader, plen))) - { - header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC); - header->hb4 &= ~(HB4_RA | HB4_RCODE | HB4_CD | HB4_AD); - if (forward->flags & FREC_CHECKING_DISABLED) - header->hb4 |= HB4_CD; - if (forward->flags & FREC_AD_QUESTION) - header->hb4 |= HB4_AD; - if (forward->flags & FREC_DO_QUESTION) - add_do_bit(header, nn, (unsigned char *)pheader + plen); - forward_query(-1, NULL, NULL, 0, header, nn, now, forward, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION); - return; - } + header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC); + header->hb4 &= ~(HB4_RA | HB4_RCODE | HB4_CD | HB4_AD); + if (forward->flags & FREC_CHECKING_DISABLED) + header->hb4 |= HB4_CD; + if (forward->flags & FREC_AD_QUESTION) + header->hb4 |= HB4_AD; } - } - - server = forward->sentto; - if ((forward->sentto->flags & SERV_TYPE) == 0) - { - if (RCODE(header) == REFUSED) - server = NULL; - else + + if (nn) { - struct server *last_server; - - /* find good server by address if possible, otherwise assume the last one we sent to */ - for (last_server = daemon->servers; last_server; last_server = last_server->next) - if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) && - sockaddr_isequal(&last_server->addr, &serveraddr)) - { - server = last_server; - break; - } - } - if (!option_bool(OPT_ALL_SERVERS)) - daemon->last_server = server; - } - - /* We tried resending to this server with a smaller maximum size and got an answer. - Make that permanent. To avoid reduxing the packet size for a single dropped packet, - only do this when we get a truncated answer, or one larger than the safe size. */ - if (forward->sentto->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && - ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) - { - forward->sentto->edns_pktsz = SAFE_PKTSZ; - forward->sentto->pktsz_reduced = now; - (void)prettyprint_addr(&forward->sentto->addr, daemon->addrbuff); - my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ); + forward_query(-1, NULL, NULL, 0, header, nn, ((char *) header) + udp_size, now, forward, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION); + return; + } } - /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ - if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != REFUSED) + + /* decrement count of replies recieved if we sent to more than one server. */ + if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED) + return; + + /* We tried resending to this server with a smaller maximum size and got an answer. + Make that permanent. To avoid reduxing the packet size for a single dropped packet, + only do this when we get a truncated answer, or one larger than the safe size. */ + if (server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && + ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) { - int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; - - if (option_bool(OPT_NO_REBIND)) - check_rebind = !(forward->flags & FREC_NOREBIND); - - /* Don't cache replies where DNSSEC validation was turned off, either - the upstream server told us so, or the original query specified it. */ - if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED)) - no_cache_dnssec = 1; - -#ifdef HAVE_DNSSEC - if ((forward->sentto->flags & SERV_DO_DNSSEC) && - option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) - { - int status = 0; - - /* We've had a reply already, which we're validating. Ignore this duplicate */ - if (forward->blocking_query) - return; - - /* Truncated answer can't be validated. - If this is an answer to a DNSSEC-generated query, we still - need to get the client to retry over TCP, so return - an answer with the TC bit set, even if the actual answer fits. - */ - if (header->hb3 & HB3_TC) - status = STAT_TRUNCATED; - - while (1) - { - /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise - would invite infinite loops, since the answers to DNSKEY and DS queries - will not be cached, so they'll be repeated. */ - if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED) - { - if (forward->flags & FREC_DNSKEY_QUERY) - status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); - else if (forward->flags & FREC_DS_QUERY) - status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); - else - status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, - !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), - NULL, NULL, NULL); -#ifdef HAVE_DUMPFILE - if (status == STAT_BOGUS) - dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, - header, (size_t)n, &serveraddr, NULL); -#endif - } - - /* Can't validate, as we're missing key data. Put this - answer aside, whilst we get that. */ - if (status == STAT_NEED_DS || status == STAT_NEED_KEY) - { - struct frec *new, *orig; - - /* Free any saved query */ - if (forward->stash) - blockdata_free(forward->stash); - - /* Now save reply pending receipt of key data */ - if (!(forward->stash = blockdata_alloc((char *)header, n))) - return; - forward->stash_len = n; - - /* Find the original query that started it all.... */ - for (orig = forward; orig->dependent; orig = orig->dependent); - - /* Make sure we don't expire and free the orig frec during the - allocation of a new one. */ - if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, orig))) - status = STAT_ABANDONED; - else - { - int querytype, fd, type = SERV_DO_DNSSEC; - struct frec *next = new->next; - char *domain; - - *new = *forward; /* copy everything, then overwrite */ - new->next = next; - new->blocking_query = NULL; - - /* Find server to forward to. This will normally be the - same as for the original query, but may be another if - servers for domains are involved. */ - if (search_servers(now, NULL, F_DNSSECOK, daemon->keyname, &type, &domain, NULL) == 0) - { - struct server *start, *new_server = NULL; - start = server = forward->sentto; - - while (1) - { - if (server_test_type(start, domain, type, SERV_DO_DNSSEC)) - { - new_server = start; - if (server == start) - { - new_server = NULL; - break; - } - } - - if (!(start = start->next)) - start = daemon->servers; - if (start == server) - break; - } - - if (new_server) - server = new_server; - } - - new->sentto = server; - new->rfds = NULL; - new->frec_src.next = NULL; - new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); - new->forwardall = 0; - - new->dependent = forward; /* to find query awaiting new one. */ - forward->blocking_query = new; /* for garbage cleaning */ - /* validate routines leave name of required record in daemon->keyname */ - if (status == STAT_NEED_KEY) - { - new->flags |= FREC_DNSKEY_QUERY; - querytype = T_DNSKEY; - } - else - { - new->flags |= FREC_DS_QUERY; - querytype = T_DS; - } - - nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, - daemon->keyname, forward->class, querytype, server->edns_pktsz); - - memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); - new->new_id = get_id(); - header->id = htons(new->new_id); - /* Save query for retransmission */ - new->stash = blockdata_alloc((char *)header, nn); - new->stash_len = nn; - - /* Don't resend this. */ - daemon->srv_save = NULL; - - if ((fd = allocate_rfd(&new->rfds, server)) != -1) - { -#ifdef HAVE_CONNTRACK - if (option_bool(OPT_CONNTRACK)) - set_outgoing_mark(orig, fd); -#endif - server_send_log(server, fd, header, nn, DUMP_SEC_QUERY, - F_NOEXTRA | F_DNSSEC, daemon->keyname, - querystr("dnssec-query", querytype)); - server->queries++; - } - } - return; - } - - /* Validated original answer, all done. */ - if (!forward->dependent) - break; - - /* validated subsidiary query, (and cached result) - pop that and return to the previous query we were working on. */ - struct frec *prev = forward->dependent; - free_frec(forward); - forward = prev; - forward->blocking_query = NULL; /* already gone */ - blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); - n = forward->stash_len; - } - - - no_cache_dnssec = 0; - - if (status == STAT_TRUNCATED) - header->hb3 |= HB3_TC; - else - { - char *result, *domain = "result"; - - if (status == STAT_ABANDONED) - { - result = "ABANDONED"; - status = STAT_BOGUS; - } - else - result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); - - if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) - domain = daemon->namebuff; - - log_query(F_SECSTAT, domain, NULL, result); - } - - if (status == STAT_SECURE) - cache_secure = 1; - else if (status == STAT_BOGUS) - { - no_cache_dnssec = 1; - bogusanswer = 1; - } - } - -#endif - - /* restore CD bit to the value in the query */ - if (forward->flags & FREC_CHECKING_DISABLED) - 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, - forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) - { - struct frec_src *src; - - header->id = htons(forward->frec_src.orig_id); - header->hb4 |= HB4_RA; /* recursion if available */ -#ifdef HAVE_DNSSEC - /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size - greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0 - header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */ - if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ)) - { - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - header->hb3 |= HB3_TC; - nn = resize_packet(header, nn, NULL, 0); - } -#endif - - for (src = &forward->frec_src; src; src = src->next) - { - header->id = htons(src->orig_id); - -#ifdef HAVE_DUMPFILE - dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); -#endif - - 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) - { - daemon->log_display_id = src->log_id; - daemon->log_source_addr = &src->source; - log_query(F_UPSTREAM, "query", NULL, "duplicate"); - } - } - } - - free_frec(forward); /* cancel */ + server->edns_pktsz = SAFE_PKTSZ; + server->pktsz_reduced = now; + (void)prettyprint_addr(&server->addr, daemon->addrbuff); + my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ); } + + /* Don't cache replies where DNSSEC validation was turned off, either + the upstream server told us so, or the original query specified it. */ + if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED)) + no_cache_dnssec = 1; + +#ifdef HAVE_DNSSEC + if ((forward->sentto->flags & SERV_DO_DNSSEC) && + option_bool(OPT_DNSSEC_VALID) && + !(forward->flags & FREC_CHECKING_DISABLED)) + { + /* Note that the value of forward may change here: + we can start with a DNSSEC query reply and + return with a query that was suspended pending + that DNSSEC query. */ + int status = dnssec_validate(&forward, header, n, server, now); + + if (status == STAT_INPROGRESS) + return; + + no_cache_dnssec = 0; + + if (status == STAT_TRUNCATED) + header->hb3 |= HB3_TC; + else + { + char *result, *domain = "result"; + + if (status == STAT_ABANDONED) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_SECSTAT, domain, NULL, result); + } + + if (status == STAT_SECURE) + cache_secure = 1; + else if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + } +#endif + + if (option_bool(OPT_NO_REBIND)) + check_rebind = !(forward->flags & FREC_NOREBIND); + + /* restore CD bit to the value in the query */ + if (forward->flags & FREC_CHECKING_DISABLED) + 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, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) + { + struct frec_src *src; + + header->id = htons(forward->frec_src.orig_id); +#ifdef HAVE_DNSSEC + /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size + greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0 + header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */ + if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ)) + { + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + header->hb3 |= HB3_TC; + nn = resize_packet(header, nn, NULL, 0); + } +#endif + + for (src = &forward->frec_src; src; src = src->next) + { + header->id = htons(src->orig_id); + +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); +#endif + + 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) + { + daemon->log_display_id = src->log_id; + daemon->log_source_addr = &src->source; + log_query(F_UPSTREAM, "query", NULL, "duplicate"); + } + } + } + + free_frec(forward); /* cancel */ } @@ -1594,33 +1496,135 @@ void receive_query(struct listener *listen, time_t now) daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; } else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, - header, (size_t)n, now, NULL, ad_reqd, do_bit)) + header, (size_t)n, ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit)) daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++; else daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; } } +/* Send query in packet, qsize to a server determined by first,last,start and + get the reply. return reply size. */ +static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, size_t qsize, + int have_mark, unsigned int mark, struct server **servp) +{ + int firstsendto = -1; + u16 *length = (u16 *)packet; + unsigned char *payload = &packet[2]; + struct dns_header *header = (struct dns_header *)payload; + unsigned char c1, c2; + unsigned char hash[HASH_SIZE]; + unsigned int rsize; + + (void)mark; + (void)have_mark; + + memcpy(hash, hash_questions(header, (unsigned int)qsize, daemon->namebuff), HASH_SIZE); + while (1) + { + int data_sent = 0; + struct server *serv; + + if (firstsendto == -1) + firstsendto = start; + else + { + start++; + + if (start == last) + start = first; + + if (start == firstsendto) + break; + } + + serv = daemon->serverarray[start]; + + retry: + *length = htons(qsize); + + if (serv->tcpfd == -1) + { + if ((serv->tcpfd = socket(serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) + continue; + +#ifdef HAVE_CONNTRACK + /* Copy connection mark of incoming query to outgoing connection. */ + if (have_mark) + setsockopt(serv->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +#endif + + if ((!local_bind(serv->tcpfd, &serv->source_addr, serv->interface, 0, 1))) + { + close(serv->tcpfd); + serv->tcpfd = -1; + continue; + } + +#ifdef MSG_FASTOPEN + server_send(serv, serv->tcpfd, packet, qsize + sizeof(u16), MSG_FASTOPEN); + + if (errno == 0) + data_sent = 1; +#endif + + if (!data_sent && connect(serv->tcpfd, &serv->addr.sa, sa_len(&serv->addr)) == -1) + { + close(serv->tcpfd); + serv->tcpfd = -1; + continue; + } + + daemon->serverarray[first]->last_server = start; + serv->flags &= ~SERV_GOT_TCP; + } + + if ((!data_sent && !read_write(serv->tcpfd, packet, qsize + sizeof(u16), 0)) || + !read_write(serv->tcpfd, &c1, 1, 1) || + !read_write(serv->tcpfd, &c2, 1, 1) || + !read_write(serv->tcpfd, payload, (rsize = (c1 << 8) | c2), 1)) + { + close(serv->tcpfd); + serv->tcpfd = -1; + /* We get data then EOF, reopen connection to same server, + else try next. This avoids DoS from a server which accepts + connections and then closes them. */ + if (serv->flags & SERV_GOT_TCP) + goto retry; + else + continue; + } + + /* If the hash of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. + Try another server, or give up */ + if (memcmp(hash, hash_questions(header, rsize, daemon->namebuff), HASH_SIZE) != 0) + continue; + + serv->flags |= SERV_GOT_TCP; + + *servp = serv; + return rsize; + } + + return 0; +} + #ifdef HAVE_DNSSEC -/* Recurse up the key hierarchy */ +/* Recurse down the key hierarchy */ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, int class, char *name, char *keyname, struct server *server, int have_mark, unsigned int mark, int *keycount) { - int new_status; + int first, last, start, new_status; unsigned char *packet = NULL; - unsigned char *payload = NULL; struct dns_header *new_header = NULL; - u16 *length = NULL; - + while (1) { - int type = SERV_DO_DNSSEC; - char *domain; size_t m; - unsigned char c1, c2; - struct server *firstsendto = NULL; - + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ if (--(*keycount) == 0) new_status = STAT_ABANDONED; @@ -1641,9 +1645,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si if (!packet) { packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); - payload = &packet[2]; - new_header = (struct dns_header *)payload; - length = (u16 *)packet; + new_header = (struct dns_header *)&packet[2]; } if (!packet) @@ -1655,100 +1657,17 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz); - *length = htons(m); - - /* Find server to forward to. This will normally be the - same as for the original query, but may be another if - servers for domains are involved. */ - if (search_servers(now, NULL, F_DNSSECOK, keyname, &type, &domain, NULL) != 0) + if ((start = dnssec_server(server, daemon->keyname, &first, &last)) == -1 || + (m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0) { new_status = STAT_ABANDONED; break; } - - while (1) - { - int data_sent = 0; - - if (!firstsendto) - firstsendto = server; - else - { - if (!(server = server->next)) - server = daemon->servers; - if (server == firstsendto) - { - /* can't find server to accept our query. */ - new_status = STAT_ABANDONED; - break; - } - } - - if (!server_test_type(server, domain, type, SERV_DO_DNSSEC)) - continue; - - retry: - /* may need to make new connection. */ - if (server->tcpfd == -1) - { - if ((server->tcpfd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) - continue; /* No good, next server */ - -#ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (have_mark) - setsockopt(server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); -#endif - - if (!local_bind(server->tcpfd, &server->source_addr, server->interface, 0, 1)) - { - close(server->tcpfd); - server->tcpfd = -1; - continue; /* No good, next server */ - } - -#ifdef MSG_FASTOPEN - server_send(server, server->tcpfd, packet, m + sizeof(u16), MSG_FASTOPEN); - - if (errno == 0) - data_sent = 1; -#endif - - if (!data_sent && connect(server->tcpfd, &server->addr.sa, sa_len(&server->addr)) == -1) - { - close(server->tcpfd); - server->tcpfd = -1; - continue; /* No good, next server */ - } - - server->flags &= ~SERV_GOT_TCP; - } - - if ((!data_sent && !read_write(server->tcpfd, packet, m + sizeof(u16), 0)) || - !read_write(server->tcpfd, &c1, 1, 1) || - !read_write(server->tcpfd, &c2, 1, 1) || - !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) - { - close(server->tcpfd); - server->tcpfd = -1; - /* We get data then EOF, reopen connection to same server, - else try next. This avoids DoS from a server which accepts - connections and then closes them. */ - if (server->flags & SERV_GOT_TCP) - goto retry; - else - continue; - } - - log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr, - querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); - - server->flags |= SERV_GOT_TCP; - - m = (c1 << 8) | c2; - new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); - break; - } + + log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr, + querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); + + new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); if (new_status != STAT_OK) break; @@ -1770,7 +1689,7 @@ unsigned char *tcp_request(int confd, time_t now, union mysockaddr *local_addr, struct in_addr netmask, int auth_dns) { size_t size = 0; - int norebind = 0; + int norebind; #ifdef HAVE_AUTH int local_auth = 0; #endif @@ -1779,14 +1698,14 @@ unsigned char *tcp_request(int confd, time_t now, size_t m; unsigned short qtype; unsigned int gotname; - unsigned char c1, c2; /* Max TCP packet + slop + size */ unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16)); unsigned char *payload = &packet[2]; + unsigned char c1, c2; /* largest field in header is 16-bits, so this is still sufficiently aligned */ struct dns_header *header = (struct dns_header *)payload; u16 *length = (u16 *)packet; - struct server *last_server; + struct server *serv; struct in_addr dst_addr_4; union mysockaddr peer_addr; socklen_t peer_len = sizeof(union mysockaddr); @@ -1794,10 +1713,9 @@ unsigned char *tcp_request(int confd, time_t now, unsigned char *pheader; unsigned int mark = 0; int have_mark = 0; - - (void)mark; - (void)have_mark; - + int first, last; + unsigned int flags = 0; + if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1) return packet; @@ -1896,6 +1814,8 @@ unsigned char *tcp_request(int confd, time_t now, #endif } + norebind = domain_no_rebind(daemon->namebuff); + if (local_addr->sa.sa_family == AF_INET) dst_addr_4 = local_addr->in.sin_addr; else @@ -1936,216 +1856,129 @@ unsigned char *tcp_request(int confd, time_t now, if (m == 0) { - unsigned int flags = 0; - union all_addr *addrp = NULL; - int type = SERV_DO_DNSSEC; - 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, &cacheable); - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); - -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && (type & SERV_DO_DNSSEC)) + struct server *master; + int start; + + /* don't forward A or AAAA queries for simple names, except the empty name */ + if (option_bool(OPT_NODOTS_LOCAL) && + (gotname & (F_IPV4 | F_IPV6)) && + !strchr(daemon->namebuff, '.') && + strlen(daemon->namebuff) != 0) { - size = add_do_bit(header, size, ((unsigned char *) header) + 65536); + flags = F_NOERR; + break;; + } + + /* Configured answer or no available server. */ + if (lookup_domain(daemon->namebuff, gotname, &first, &last) && !(flags = is_local_answer(now, first, daemon->namebuff))) + { + master = daemon->serverarray[first]; + + if (option_bool(OPT_ORDER) || master->last_server == -1) + start = first; + else + start = master->last_server; + + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); - /* For debugging, set Checking Disabled, otherwise, have the upstream check too, - this allows it to select auth servers when one is returning bad data. */ - if (option_bool(OPT_DNSSEC_DEBUG)) - header->hb4 |= HB4_CD; - } -#endif - - /* Check if we added a pheader on forwarding - may need to - strip it from the reply. */ - if (!oph && find_pseudoheader(header, size, NULL, NULL, NULL, NULL)) - added_pheader = 1; - - type &= ~SERV_DO_DNSSEC; - - if (type != 0 || option_bool(OPT_ORDER) || !daemon->last_server) - last_server = daemon->servers; - else - last_server = daemon->last_server; - - if (!flags && last_server) - { - struct server *firstsendto = NULL; - unsigned char hash[HASH_SIZE]; - memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE); - - /* Loop round available servers until we succeed in connecting to one. - Note that this code subtly ensures that consecutive queries on this connection - which can go to the same server, do so. */ - while (1) - { - int data_sent = 0; - - if (!firstsendto) - firstsendto = last_server; - else - { - if (!(last_server = last_server->next)) - last_server = daemon->servers; - - if (last_server == firstsendto) - break; - } - - /* server for wrong domain */ - if (!server_test_type(last_server, domain, type, 0)) - continue; - - retry: - *length = htons(size); - - if (last_server->tcpfd == -1) - { - if ((last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) == -1) - continue; - -#ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (have_mark) - setsockopt(last_server->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); -#endif - - if ((!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, 0, 1))) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - continue; - } - -#ifdef MSG_FASTOPEN - server_send(last_server, last_server->tcpfd, packet, size + sizeof(u16), MSG_FASTOPEN); - - if (errno == 0) - data_sent = 1; -#endif - - if (!data_sent && connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - continue; - } - - last_server->flags &= ~SERV_GOT_TCP; - } - - /* get query name again for logging - may have been overwritten */ - if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) - strcpy(daemon->namebuff, "query"); - - if ((!data_sent && !read_write(last_server->tcpfd, packet, size + sizeof(u16), 0)) || - !read_write(last_server->tcpfd, &c1, 1, 1) || - !read_write(last_server->tcpfd, &c2, 1, 1) || - !read_write(last_server->tcpfd, payload, (c1 << 8) | c2, 1)) - { - close(last_server->tcpfd); - last_server->tcpfd = -1; - /* We get data then EOF, reopen connection to same server, - else try next. This avoids DoS from a server which accepts - connections and then closes them. */ - if (last_server->flags & SERV_GOT_TCP) - goto retry; - else - continue; - } - - last_server->flags |= SERV_GOT_TCP; - - m = (c1 << 8) | c2; - - log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, - &last_server->addr, NULL); - #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC)) - { - int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ - int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, - last_server, have_mark, mark, &keycount); - char *result, *domain = "result"; - - if (status == STAT_ABANDONED) - { - result = "ABANDONED"; - status = STAT_BOGUS; - } - else - result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); - - if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) - domain = daemon->namebuff; - - log_query(F_SECSTAT, domain, NULL, result); - - if (status == STAT_BOGUS) - { - no_cache_dnssec = 1; - bogusanswer = 1; - } - - if (status == STAT_SECURE) - cache_secure = 1; - } -#endif - - /* restore CD bit to the value in the query */ - if (checking_disabled) + if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC)) + { + size = add_do_bit(header, size, ((unsigned char *) header) + 65536); + + /* For debugging, set Checking Disabled, otherwise, have the upstream check too, + this allows it to select auth servers when one is returning bad data. */ + if (option_bool(OPT_DNSSEC_DEBUG)) header->hb4 |= HB4_CD; - else - header->hb4 &= ~HB4_CD; - - /* There's no point in updating the cache, since this process will exit and - lose the information after a few queries. We make this call for the alias and - bogus-nxdomain side-effects. */ - /* If the crc of the question section doesn't match the crc we sent, then - someone might be attempting to insert bogus values into the cache by - sending replies containing questions and bogus answers. */ - if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0) - { - m = 0; - 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); - - break; } - } - - /* In case of local answer or no connections made. */ - if (m == 0) - { - m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); - if (have_pseudoheader) - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); +#endif + + /* Check if we added a pheader on forwarding - may need to + strip it from the reply. */ + if (!have_pseudoheader && find_pseudoheader(header, size, NULL, NULL, NULL, NULL)) + added_pheader = 1; + + /* Loop round available servers until we succeed in connecting to one. */ + if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0) + break; + + /* get query name again for logging - may have been overwritten */ + if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) + strcpy(daemon->namebuff, "query"); + log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL); + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC)) + { + int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ + int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, + serv, have_mark, mark, &keycount); + char *result, *domain = "result"; + + if (status == STAT_ABANDONED) + { + result = "ABANDONED"; + status = STAT_BOGUS; + } + else + result = (status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS")); + + if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) + domain = daemon->namebuff; + + log_query(F_SECSTAT, domain, NULL, result); + + if (status == STAT_BOGUS) + { + no_cache_dnssec = 1; + bogusanswer = 1; + } + + if (status == STAT_SECURE) + cache_secure = 1; + } +#endif + + /* restore CD bit to the value in the query */ + if (checking_disabled) + 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 (!cacheable) + no_cache_dnssec = 1; + + m = process_reply(header, now, serv, (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); } } } + + /* In case of local answer or no connections made. */ + if (m == 0) + { + if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff, first, last))) + break; + if (have_pseudoheader) + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + } + check_log_writer(1); *length = htons(m); - - if (m == 0 || !read_write(confd, packet, m + sizeof(u16), 0)) - return packet; + + if (!read_write(confd, packet, m + sizeof(u16), 0)) + break; } + + return packet; } + static struct frec *allocate_frec(time_t now) { struct frec *f; @@ -2484,17 +2317,19 @@ static void query_full(time_t now) } -static struct frec *lookup_frec(unsigned short id, int fd, void *hash) +static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp) { struct frec *f; struct server *s; - int type; + int first, last; 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)) { + filter_servers(f->sentto->arrayposn, F_SERVER, firstp, lastp); + /* sent from random port */ for (fdl = f->rfds; fdl; fdl = fdl->next) if (fdl->rfd->fd == fd) @@ -2503,15 +2338,12 @@ static struct frec *lookup_frec(unsigned short id, int fd, void *hash) /* 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 (server_test_type(s, f->sentto->domain, type, 0) && - s->sfd && s->sfd->fd == fd) - return f; - - s = s->next ? s->next : daemon->servers; - } while (s != f->sentto); + for (first = *firstp, last = *lastp; first != last; first++) + { + s = daemon->serverarray[first]; + if (s->sfd && s->sfd->fd == fd) + return f; + } } return NULL; @@ -2563,9 +2395,6 @@ void server_gone(struct server *server) 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 758674b..56b5287 100644 --- a/src/loop.c +++ b/src/loop.c @@ -30,8 +30,8 @@ void loop_send_probes() /* Loop through all upstream servers not for particular domains, and send a query to that server which is identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */ for (serv = daemon->servers; serv; serv = serv->next) - if (!(serv->flags & - (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP))) + if (strlen(serv->domain) == 0 && + !(serv->flags & (SERV_FOR_NODOTS | SERV_LOOP))) { ssize_t len = loop_make_probe(serv->uid); int fd; @@ -96,15 +96,15 @@ int detect_loop(char *query, int type) uid = strtol(query, NULL, 16); for (serv = daemon->servers; serv; serv = serv->next) - if (!(serv->flags & - (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_LOOP)) && - uid == serv->uid) - { - serv->flags |= SERV_LOOP; - check_servers(); /* log new state */ - return 1; - } - + if (strlen(serv->domain) == 0 && + !(serv->flags & SERV_LOOP) && + uid == serv->uid) + { + serv->flags |= SERV_LOOP; + check_servers(); /* log new state */ + return 1; + } + return 0; } diff --git a/src/network.c b/src/network.c index be35128..e0b3ec8 100644 --- a/src/network.c +++ b/src/network.c @@ -31,7 +31,7 @@ int indextoname(int fd, int index, char *name) safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE); - return 1; + return 1; } @@ -1479,8 +1479,7 @@ void pre_allocate_sfds(void) } 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, srv->ifindex) && + if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && errno != 0 && option_bool(OPT_NOWILD)) { @@ -1509,6 +1508,10 @@ void mark_servers(int flag) serv->flags &= ~SERV_LOOP; #endif } + + for (serv = daemon->local_domains; serv; serv = serv->next) + if (serv->flags & flag) + serv->flags |= SERV_MARK; } void cleanup_servers(void) @@ -1523,14 +1526,27 @@ void cleanup_servers(void) { server_gone(serv); *up = serv->next; - if (serv->domain) - free(serv->domain); + free(serv->domain); free(serv); } else up = &serv->next; } + + for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp) + { + tmp = serv->next; + if (serv->flags & SERV_MARK) + { + *up = serv->next; + free(serv->domain); + free(serv); + } + else + up = &serv->next; + } + #ifdef HAVE_LOOP /* Now we have a new set of servers, test for loops. */ loop_send_probes(); @@ -1543,76 +1559,80 @@ void add_update_server(int flags, const char *interface, const char *domain) { - struct server *serv, *next = NULL; - char *domain_str = NULL; + struct server *serv; + char *domain_str; + + if (!domain) + domain = ""; + + /* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. + NOTE that we can get local=/domain/ here, but NOT address=/domain/1.2.3.4 */ +#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS) /* See if there is a suitable candidate, and unmark */ - for (serv = daemon->servers; serv; serv = serv->next) - if (serv->flags & SERV_MARK) - { - if (domain) - { - if (!(serv->flags & SERV_HAS_DOMAIN) || !hostname_isequal(domain, serv->domain)) - continue; - } - else - { - if (serv->flags & SERV_HAS_DOMAIN) - continue; - } - - break; - } - + for (serv = (flags & SERV_IS_LOCAL) ? daemon->local_domains : daemon->servers; serv; serv = serv->next) + if ((serv->flags & SERV_MARK) && hostname_isequal(domain, serv->domain)) + break; + if (serv) - { - domain_str = serv->domain; - next = serv->next; - } + domain_str = serv->domain; else if ((serv = whine_malloc(sizeof (struct server)))) { /* Not found, create a new one. */ - if (domain && !(domain_str = whine_malloc(strlen(domain)+1))) + if (!(domain_str = whine_malloc(strlen(domain)+1))) { free(serv); serv = NULL; } else - { - struct server *s; - /* Add to the end of the chain, for order */ - if (!daemon->servers) - daemon->servers = serv; + { + strcpy(domain_str, domain); + + if (flags & SERV_IS_LOCAL) + { + serv->next = daemon->local_domains; + daemon->local_domains = serv; + } else { - for (s = daemon->servers; s->next; s = s->next); - s->next = serv; + struct server *s; + /* Add to the end of the chain, for order */ + if (!daemon->servers) + daemon->servers = serv; + else + { + for (s = daemon->servers; s->next; s = s->next); + s->next = serv; + } + + serv->next = NULL; } - if (domain) - strcpy(domain_str, domain); } } if (serv) { - memset(serv, 0, sizeof(struct server)); + if (!(flags & SERV_IS_LOCAL)) + memset(serv, 0, sizeof(struct server)); + serv->flags = flags; serv->domain = domain_str; - serv->next = next; - serv->queries = serv->failed_queries = 0; -#ifdef HAVE_LOOP - serv->uid = rand32(); -#endif - if (domain) - serv->flags |= SERV_HAS_DOMAIN; - - if (interface) - safe_strncpy(serv->interface, interface, sizeof(serv->interface)); - if (addr) - serv->addr = *addr; - if (source_addr) - serv->source_addr = *source_addr; + + if (!(flags & SERV_IS_LOCAL)) + { + serv->queries = serv->failed_queries = 0; +#ifdef HAVE_LOOP + serv->uid = rand32(); +#endif + + if (interface) + safe_strncpy(serv->interface, interface, sizeof(serv->interface)); + if (addr) + serv->addr = *addr; + if (source_addr) + serv->source_addr = *source_addr; + } } } @@ -1623,7 +1643,7 @@ void check_servers(void) struct serverfd *sfd, *tmp, **up; int port = 0, count; int locals = 0; - + /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) enumerate_interfaces(0); @@ -1634,114 +1654,117 @@ void check_servers(void) for (count = 0, serv = daemon->servers; serv; serv = serv->next) { - if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND))) - { - /* Init edns_pktsz for newly created server records. */ - if (serv->edns_pktsz == 0) - serv->edns_pktsz = daemon->edns_pktsz; - + /* Init edns_pktsz for newly created server records. */ + if (serv->edns_pktsz == 0) + serv->edns_pktsz = daemon->edns_pktsz; + #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID)) - { - if (!(serv->flags & SERV_FOR_NODOTS)) - serv->flags |= SERV_DO_DNSSEC; + if (option_bool(OPT_DNSSEC_VALID)) + { + if (!(serv->flags & SERV_FOR_NODOTS)) + serv->flags |= SERV_DO_DNSSEC; + + /* Disable DNSSEC validation when using server=/domain/.... servers + unless there's a configured trust anchor. */ + if (strlen(serv->domain) != 0) + { + struct ds_config *ds; + char *domain = serv->domain; - /* Disable DNSSEC validation when using server=/domain/.... servers - unless there's a configured trust anchor. */ - if (serv->flags & SERV_HAS_DOMAIN) - { - struct ds_config *ds; - char *domain = serv->domain; - - /* .example.com is valid */ - while (*domain == '.') - domain++; - - for (ds = daemon->ds; ds; ds = ds->next) - if (ds->name[0] != 0 && hostname_isequal(domain, ds->name)) - break; - - if (!ds) - serv->flags &= ~SERV_DO_DNSSEC; - } + /* .example.com is valid */ + while (*domain == '.') + domain++; + + for (ds = daemon->ds; ds; ds = ds->next) + if (ds->name[0] != 0 && hostname_isequal(domain, ds->name)) + break; + + if (!ds) + serv->flags &= ~SERV_DO_DNSSEC; } + } #endif - - port = prettyprint_addr(&serv->addr, daemon->namebuff); - - /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ - if (serv->addr.sa.sa_family == AF_INET && - serv->addr.in.sin_addr.s_addr == 0) - { - serv->flags |= SERV_MARK; - continue; - } - - for (iface = daemon->interfaces; iface; iface = iface->next) - if (sockaddr_isequal(&serv->addr, &iface->addr)) - break; - if (iface) - { - my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff); - serv->flags |= SERV_MARK; - continue; - } - - /* Do we need a socket set? */ - if (!serv->sfd && - !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && - errno != 0) - { - my_syslog(LOG_WARNING, - _("ignoring nameserver %s - cannot make/bind socket: %s"), - daemon->namebuff, strerror(errno)); - serv->flags |= SERV_MARK; - continue; - } - - if (serv->sfd) - serv->sfd->used = 1; + + port = prettyprint_addr(&serv->addr, daemon->namebuff); + + /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */ + if (serv->addr.sa.sa_family == AF_INET && + serv->addr.in.sin_addr.s_addr == 0) + { + serv->flags |= SERV_MARK; + continue; } - if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS)) + for (iface = daemon->interfaces; iface; iface = iface->next) + if (sockaddr_isequal(&serv->addr, &iface->addr)) + break; + if (iface) { - if (++count > SERVERS_LOGGED) - continue; - - if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) - { - char *s1, *s2, *s3 = ""; -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC)) - s3 = _("(no DNSSEC)"); -#endif - if (!(serv->flags & SERV_HAS_DOMAIN)) - s1 = _("unqualified"), s2 = _("names"); - else if (strlen(serv->domain) == 0) - s1 = _("default"), s2 = ""; - else - s1 = _("domain"), s2 = serv->domain; - - if (serv->flags & SERV_NO_ADDR) - { - count--; - if (++locals <= LOCALS_LOGGED) - my_syslog(LOG_INFO, _("using only locally-known addresses for %s %s"), s1, s2); - } - else if (serv->flags & SERV_USE_RESOLV) - my_syslog(LOG_INFO, _("using standard nameservers for %s %s"), s1, s2); - else - my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3); - } -#ifdef HAVE_LOOP - else if (serv->flags & SERV_LOOP) - my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port); -#endif - else if (serv->interface[0] != 0) - my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface); - else - my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); + my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff); + serv->flags |= SERV_MARK; + continue; } + + /* Do we need a socket set? */ + if (!serv->sfd && + !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && + errno != 0) + { + my_syslog(LOG_WARNING, + _("ignoring nameserver %s - cannot make/bind socket: %s"), + daemon->namebuff, strerror(errno)); + serv->flags |= SERV_MARK; + continue; + } + + if (serv->sfd) + serv->sfd->used = 1; + + if (++count > SERVERS_LOGGED) + continue; + + if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS)) + { + char *s1, *s2, *s3 = ""; + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC)) + s3 = _("(no DNSSEC)"); +#endif + if (serv->flags & SERV_FOR_NODOTS) + s1 = _("unqualified"), s2 = _("names"); + else if (strlen(serv->domain) == 0) + s1 = _("default"), s2 = ""; + else + s1 = _("domain"), s2 = serv->domain; + + my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s %s"), daemon->namebuff, port, s1, s2, s3); + } +#ifdef HAVE_LOOP + else if (serv->flags & SERV_LOOP) + my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port); +#endif + else if (serv->interface[0] != 0) + my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface); + else + my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); + + } + + for (count = 0, serv = daemon->local_domains; serv; serv = serv->next) + { + if (++count > SERVERS_LOGGED) + continue; + + if ((serv->flags & SERV_LITERAL_ADDRESS) && + !(serv->flags & (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS))) + { + count--; + if (++locals <= LOCALS_LOGGED) + my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain); + } + else if (serv->flags & SERV_USE_RESOLV) + my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain); } if (locals > LOCALS_LOGGED) @@ -1764,6 +1787,7 @@ void check_servers(void) } cleanup_servers(); + build_server_array(); } /* Return zero if no servers found, in that case we keep polling. diff --git a/src/option.c b/src/option.c index 23cf058..cacfaa6 100644 --- a/src/option.c +++ b/src/option.c @@ -644,6 +644,9 @@ static char *canonicalise_opt(char *s) if (!s) return 0; + if (strlen(s) == 0) + return ""; + unhide_metas(s); if (!(ret = canonicalise(s, &nomem)) && nomem) { @@ -816,14 +819,14 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a char *interface_opt = NULL; int scope_index = 0; char *scope_id; - - if (!arg || strlen(arg) == 0) + + if (strcmp(arg, "#") == 0) { - *flags |= SERV_NO_ADDR; - *interface = 0; + if (flags) + *flags |= SERV_USE_RESOLV; return NULL; } - + if ((source = split_chr(arg, '@')) && /* is there a source. */ (portno = split_chr(source, '#')) && !atoi_check16(portno, &source_port)) @@ -921,7 +924,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a static struct server *add_rev4(struct in_addr addr, int msize) { struct server *serv = opt_malloc(sizeof(struct server)); - in_addr_t a = ntohl(addr.s_addr); + in_addr_t a = ntohl(addr.s_addr); char *p; memset(serv, 0, sizeof(struct server)); @@ -949,10 +952,6 @@ static struct server *add_rev4(struct in_addr addr, int msize) p += sprintf(p, "in-addr.arpa"); - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; - return serv; } @@ -973,10 +972,6 @@ static struct server *add_rev6(struct in6_addr *addr, int msize) } p += sprintf(p, "ip6.arpa"); - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; - return serv; } @@ -1664,16 +1659,6 @@ void reset_option_bool(unsigned int opt) option_var(opt) &= ~(option_val(opt)); } -static void server_list_free(struct server *list) -{ - while (list) - { - struct server *tmp = list; - list = list->next; - free(tmp); - } -} - static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only) { int i; @@ -2309,15 +2294,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (!serv) ret_err_free(_("bad prefix"), new); - serv->flags |= SERV_NO_ADDR; - + serv->flags |= SERV_LITERAL_ADDRESS; + serv->next = daemon->local_domains; + daemon->local_domains = serv; + /* local=// */ serv = opt_malloc(sizeof(struct server)); memset(serv, 0, sizeof(struct server)); serv->domain = d; - serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; - serv->next = daemon->servers; - daemon->servers = serv; + serv->flags = SERV_LITERAL_ADDRESS; + serv->next = daemon->local_domains; + daemon->local_domains = serv; } } } @@ -2352,15 +2339,17 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma /* generate the equivalent of local=/xxx.yyy.zzz.ip6.arpa/ */ struct server *serv = add_rev6(&new->start6, msize); - serv->flags |= SERV_NO_ADDR; + serv->flags |= SERV_LITERAL_ADDRESS; + serv->next = daemon->local_domains; + daemon->local_domains = serv; /* local=// */ serv = opt_malloc(sizeof(struct server)); memset(serv, 0, sizeof(struct server)); serv->domain = d; - serv->flags = SERV_HAS_DOMAIN | SERV_NO_ADDR; - serv->next = daemon->servers; - daemon->servers = serv; + serv->flags = SERV_LITERAL_ADDRESS; + serv->next = daemon->local_domains; + daemon->local_domains = serv; } } } @@ -2615,98 +2604,166 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } while (arg); break; + case LOPT_NO_REBIND: /* --rebind-domain-ok */ + { + struct server *new; + + unhide_metas(arg); + + if (*arg == '/') + arg++; + + do { + comma = split_chr(arg, '/'); + new = opt_malloc(sizeof(struct serv_local)); + new->domain = opt_string_alloc(arg); + new->flags = strlen(arg); + new->next = daemon->no_rebind; + daemon->no_rebind = new; + arg = comma; + } while (arg && *arg); + + break; + } + case 'S': /* --server */ case LOPT_LOCAL: /* --local */ case 'A': /* --address */ - case LOPT_NO_REBIND: /* --rebind-domain-ok */ { - struct server *serv, *newlist = NULL; - + struct server *new; + size_t size; + char *lastdomain = NULL, *domain = ""; + char *alloc_domain; + int flags = 0; + char *err; + struct in_addr addr4; + struct in6_addr addr6; + unhide_metas(arg); - if (arg && (*arg == '/' || option == LOPT_NO_REBIND)) + /* split the domain args, if any and skip to the end of them. */ + if (arg && *arg == '/') { - int rebind = !(*arg == '/'); - char *end = NULL; - if (!rebind) - arg++; - while (rebind || (end = split_chr(arg, '/'))) + char *last; + + arg++; + domain = lastdomain = arg; + + while ((last = split_chr(arg, '/'))) { - char *domain = NULL; - /* elide leading dots - they are implied in the search algorithm */ - while (*arg == '.') arg++; - /* # matches everything and becomes a zero length domain string */ - if (strcmp(arg, "#") == 0) - domain = ""; - else if (strlen (arg) != 0 && !(domain = canonicalise_opt(arg))) - ret_err(gen_err); - serv = opt_malloc(sizeof(struct server)); - memset(serv, 0, sizeof(struct server)); - serv->next = newlist; - newlist = serv; - serv->domain = domain; - serv->flags = domain ? SERV_HAS_DOMAIN : SERV_FOR_NODOTS; - arg = end; - if (rebind) - break; + lastdomain = arg; + arg = last; } - if (!newlist) - ret_err(gen_err); - } - else - { - newlist = opt_malloc(sizeof(struct server)); - memset(newlist, 0, sizeof(struct server)); -#ifdef HAVE_LOOP - newlist->uid = rand32(); -#endif } if (servers_only && option == 'S') - newlist->flags |= SERV_FROM_FILE; - - if (option == 'A') - { - newlist->flags |= SERV_LITERAL_ADDRESS; - if (!(newlist->flags & SERV_TYPE)) - { - server_list_free(newlist); - ret_err(gen_err); - } - } - else if (option == LOPT_NO_REBIND) - newlist->flags |= SERV_NO_REBIND; + flags |= SERV_FROM_FILE; if (!arg || !*arg) + flags = SERV_LITERAL_ADDRESS; + else if (option == 'A') { - if (!(newlist->flags & SERV_NO_REBIND)) - newlist->flags |= SERV_NO_ADDR; /* no server */ + /* # as literal address means return zero address for 4 and 6 */ + if (strcmp(arg, "#") == 0) + flags |= SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS; + else if (inet_pton(AF_INET, arg, &addr4) > 0) + flags |= SERV_4ADDR | SERV_LITERAL_ADDRESS; + else if (inet_pton(AF_INET6, arg, &addr6) > 0) + flags |= SERV_6ADDR | SERV_LITERAL_ADDRESS; + else + ret_err(_("Bad address in --address")); } - else if (strcmp(arg, "#") == 0) - newlist->flags |= SERV_USE_RESOLV; /* treat in ordinary way */ + if (!(alloc_domain = canonicalise_opt(domain))) + ret_err(gen_err); + + + if (flags & SERV_LITERAL_ADDRESS) + { + if (flags & SERV_6ADDR) + { + size = sizeof(struct serv_addr6); + new = opt_malloc(sizeof(struct serv_addr6)); + ((struct serv_addr6*)new)->addr = addr6; + } + else if (flags & SERV_4ADDR) + { + size = sizeof(struct serv_addr4); + new = opt_malloc(sizeof(struct serv_addr4)); + ((struct serv_addr4*)new)->addr = addr4; + } + else + { + size = sizeof(struct serv_local); + new = opt_malloc(sizeof(struct serv_local)); + } + + new->next = daemon->local_domains; + daemon->local_domains = new; + } else { - char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); - if (err) + size = sizeof(struct server); + new = opt_malloc(sizeof(struct server)); + +#ifdef HAVE_LOOP + new->uid = rand32(); +#endif + if ((err = parse_server(arg, &new->addr, &new->source_addr, new->interface, &flags))) { - server_list_free(newlist); - ret_err(err); + free(new); + ret_err(err); + } + + /* Since domains that use standard servers don't have the + network stuff, it's easier to treat them as local. */ + if (flags & SERV_USE_RESOLV) + { + new->next = daemon->local_domains; + daemon->local_domains = new; + } + else + { + new->next = daemon->servers; + daemon->servers = new; } } - serv = newlist; - while (serv->next) - { - serv->next->flags |= serv->flags & ~(SERV_HAS_DOMAIN | SERV_FOR_NODOTS); - serv->next->addr = serv->addr; - serv->next->source_addr = serv->source_addr; - strcpy(serv->next->interface, serv->interface); - serv = serv->next; - } - serv->next = daemon->servers; - daemon->servers = newlist; - break; + new->domain = alloc_domain; + + /* server=//1.2.3.4 is special. */ + if (strlen(domain) == 0 && lastdomain) + flags |= SERV_FOR_NODOTS; + + new->flags = flags; + + /* If we have more than one domain, copy and iterate */ + if (lastdomain) + while (domain != lastdomain) + { + struct server *last = new; + + domain += strlen(domain) + 1; + + if (!(alloc_domain = canonicalise_opt(domain))) + ret_err(gen_err); + + new = opt_malloc(size); + memcpy(new, last, size); + new->domain = alloc_domain; + if (flags & (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)) + { + new->next = daemon->local_domains; + daemon->local_domains = new; + } + else + { + new->next = daemon->servers; + daemon->servers = new; + } + } + + break; } case LOPT_REV_SERV: /* --rev-server */ @@ -2731,9 +2788,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma serv = add_rev4(addr4, size); if (!serv) ret_err(_("bad prefix")); + serv->next = daemon->servers; + daemon->servers = serv; } else if (inet_pton(AF_INET6, arg, &addr6)) - serv = add_rev6(&addr6, size); + { + serv = add_rev6(&addr6, size); + serv->next = daemon->servers; + daemon->servers = serv; + } else ret_err(gen_err); diff --git a/src/rfc1035.c b/src/rfc1035.c index 5a961b8..9bc5ef2 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -894,6 +894,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, if (typep) *typep = 0; + *name = 0; /* return empty name if no query found. */ + if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY) return 0; /* must be exactly one query. */ @@ -926,14 +928,8 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, return F_QUERY; } -size_t setup_reply(struct dns_header *header, size_t qlen, - union all_addr *addrp, unsigned int flags, unsigned long ttl) +void setup_reply(struct dns_header *header, unsigned int flags) { - unsigned char *p; - - if (!(p = skip_questions(header, qlen))) - return 0; - /* clear authoritative and truncated flags, set QR flag */ header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR; /* clear AD flag, set RA flag */ @@ -946,30 +942,10 @@ size_t setup_reply(struct dns_header *header, size_t qlen, SET_RCODE(header, NOERROR); /* empty domain */ else if (flags == F_NXDOMAIN) SET_RCODE(header, NXDOMAIN); - else if (flags == F_SERVFAIL) - { - union all_addr a; - a.log.rcode = SERVFAIL; - log_query(F_CONFIG | F_RCODE, "error", &a, NULL); - SET_RCODE(header, SERVFAIL); - } else if (flags & ( F_IPV4 | F_IPV6)) { - if (flags & F_IPV4) - { /* we know the address */ - SET_RCODE(header, NOERROR); - header->ancount = htons(1); - header->hb3 |= HB3_AA; - add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_A, C_IN, "4", addrp); - } - - if (flags & F_IPV6) - { - SET_RCODE(header, NOERROR); - header->ancount = htons(ntohs(header->ancount) + 1); - header->hb3 |= HB3_AA; - add_resource_record(header, NULL, NULL, sizeof(struct dns_header), &p, ttl, NULL, T_AAAA, C_IN, "6", addrp); - } + SET_RCODE(header, NOERROR); + header->hb3 |= HB3_AA; } else /* nowhere to forward to */ { @@ -978,8 +954,6 @@ size_t setup_reply(struct dns_header *header, size_t qlen, log_query(F_CONFIG | F_RCODE, "error", &a, NULL); SET_RCODE(header, REFUSED); } - - return p - (unsigned char *)header; } /* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */ @@ -1553,43 +1527,17 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, anscount++; } } - else if (option_bool(OPT_BOGUSPRIV) && ( - (is_arpa == F_IPV6 && private_net6(&addr.addr6)) || - (is_arpa == F_IPV4 && private_net(addr.addr4, 1)))) + else if (option_bool(OPT_BOGUSPRIV) && + ((is_arpa == F_IPV6 && private_net6(&addr.addr6)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) && + !lookup_domain(name, F_DOMAINSRV, NULL, NULL)) { - struct server *serv; - unsigned int namelen = strlen(name); - char *nameend = name + namelen; - - /* see if have rev-server set */ - for (serv = daemon->servers; serv; serv = serv->next) - { - unsigned int domainlen; - char *matchstart; - - if ((serv->flags & (SERV_HAS_DOMAIN | SERV_NO_ADDR)) != SERV_HAS_DOMAIN) - continue; - - domainlen = strlen(serv->domain); - if (domainlen == 0 || domainlen > namelen) - continue; - - matchstart = nameend - domainlen; - if (hostname_isequal(matchstart, serv->domain) && - (namelen == domainlen || *(matchstart-1) == '.' )) - break; - } - /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */ - if (!serv) - { - ans = 1; - sec_data = 0; - nxdomain = 1; - if (!dryrun) - log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN, - name, &addr, NULL); - } + ans = 1; + sec_data = 0; + nxdomain = 1; + if (!dryrun) + log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN, + name, &addr, NULL); } }