diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/arp.c | 4 | ||||
-rw-r--r-- | src/auth.c | 44 | ||||
-rw-r--r-- | src/blockdata.c | 3 | ||||
-rw-r--r-- | src/bpf.c | 4 | ||||
-rw-r--r-- | src/cache.c | 125 | ||||
-rw-r--r-- | src/config.h | 21 | ||||
-rw-r--r-- | src/conntrack.c | 7 | ||||
-rw-r--r-- | src/crypto.c | 111 | ||||
-rw-r--r-- | src/dbus.c | 20 | ||||
-rw-r--r-- | src/dhcp-common.c | 67 | ||||
-rw-r--r-- | src/dhcp-protocol.h | 2 | ||||
-rw-r--r-- | src/dhcp.c | 21 | ||||
-rw-r--r-- | src/dhcp6-protocol.h | 3 | ||||
-rw-r--r-- | src/dhcp6.c | 6 | ||||
-rw-r--r-- | src/dns-protocol.h | 35 | ||||
-rw-r--r-- | src/dnsmasq.c | 211 | ||||
-rw-r--r-- | src/dnsmasq.h | 242 | ||||
-rw-r--r-- | src/dnssec.c | 235 | ||||
-rw-r--r-- | src/domain-match.c | 698 | ||||
-rw-r--r-- | src/domain.c | 70 | ||||
-rw-r--r-- | src/dump.c | 2 | ||||
-rw-r--r-- | src/edns0.c | 65 | ||||
-rw-r--r-- | src/forward.c | 2756 | ||||
-rw-r--r-- | src/hash-questions.c (renamed from src/hash_questions.c) | 55 | ||||
-rw-r--r-- | src/helper.c | 20 | ||||
-rw-r--r-- | src/inotify.c | 3 | ||||
-rw-r--r-- | src/ip6addr.h | 3 | ||||
-rw-r--r-- | src/lease.c | 11 | ||||
-rw-r--r-- | src/log.c | 12 | ||||
-rw-r--r-- | src/loop.c | 44 | ||||
-rw-r--r-- | src/metrics.c | 2 | ||||
-rw-r--r-- | src/metrics.h | 2 | ||||
-rw-r--r-- | src/netlink.c | 91 | ||||
-rw-r--r-- | src/network.c | 649 | ||||
-rw-r--r-- | src/option.c | 784 | ||||
-rw-r--r-- | src/outpacket.c | 2 | ||||
-rw-r--r-- | src/pattern.c | 386 | ||||
-rw-r--r-- | src/poll.c | 2 | ||||
-rw-r--r-- | src/radv-protocol.h | 5 | ||||
-rw-r--r-- | src/radv.c | 2 | ||||
-rw-r--r-- | src/rfc1035.c | 915 | ||||
-rw-r--r-- | src/rfc2131.c | 103 | ||||
-rw-r--r-- | src/rfc3315.c | 62 | ||||
-rw-r--r-- | src/rrfilter.c | 2 | ||||
-rw-r--r-- | src/slaac.c | 2 | ||||
-rw-r--r-- | src/tables.c | 2 | ||||
-rw-r--r-- | src/tftp.c | 40 | ||||
-rw-r--r-- | src/ubus.c | 267 | ||||
-rw-r--r-- | src/util.c | 16 |
49 files changed, 5183 insertions, 3051 deletions
@@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -230,5 +230,3 @@ int do_arp_script_run(void) return 0; } - - @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -105,7 +105,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n int nameoffset, axfroffset = 0; int q, anscount = 0, authcount = 0; struct crec *crecp; - int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0; + int auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0, out_of_zone = 0; struct auth_zone *zone = NULL; struct addrlist *subnet = NULL; char *cut; @@ -146,6 +146,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (qclass != C_IN) { auth = 0; + out_of_zone = 1; continue; } @@ -159,6 +160,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (!zone) { + out_of_zone = 1; auth = 0; continue; } @@ -253,6 +255,17 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } while ((crecp = cache_find_by_addr(crecp, &addr, now, flag))); + if (!found && is_rev_synth(flag, &addr, name) && (local_query || in_zone(zone, name, NULL))) + { + log_query(F_CONFIG | F_REVERSE | flag, name, &addr, NULL); + found = 1; + + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, + T_PTR, C_IN, "d", name)) + anscount++; + } + if (found) nxdomain = 0; else @@ -273,6 +286,7 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n if (!zone) { + out_of_zone = 1; auth = 0; continue; } @@ -400,6 +414,17 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n anscount++; } } + + if (!found && is_name_synthetic(flag, name, &addr) ) + { + found = 1; + nxdomain = 0; + + log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL); + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, + daemon->auth_ttl, NULL, qtype, C_IN, qtype == T_A ? "4" : "6", &addr)) + anscount++; + } if (!cut) { @@ -855,10 +880,22 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n SET_RCODE(header, NXDOMAIN); else SET_RCODE(header, NOERROR); /* no error */ + header->ancount = htons(anscount); header->nscount = htons(authcount); header->arcount = htons(0); + if (!local_query && out_of_zone) + { + SET_RCODE(header, REFUSED); + header->ancount = htons(0); + header->nscount = htons(0); + addr.log.rcode = REFUSED; + addr.log.ede = EDE_NOT_AUTH; + log_query(F_UPSTREAM | F_RCODE, "error", &addr, NULL); + return resize_packet(header, ansp - (unsigned char *)header, NULL, 0); + } + /* Advertise our packet size limit in our reply */ if (have_pseudoheader) return add_pseudoheader(header, ansp - (unsigned char *)header, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); @@ -867,6 +904,3 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n } #endif - - - diff --git a/src/blockdata.c b/src/blockdata.c index f33b28e..f7740b5 100644 --- a/src/blockdata.c +++ b/src/blockdata.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -174,4 +174,3 @@ struct blockdata *blockdata_read(int fd, size_t len) { return blockdata_alloc_real(fd, NULL, len); } - @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -440,5 +440,3 @@ void route_sock(void) } #endif /* HAVE_BSD_NETWORK */ - - diff --git a/src/cache.c b/src/cache.c index 2f2c519..8add610 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -488,8 +488,6 @@ struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class else #endif { - /* Don't log DNSSEC records here, done elsewhere */ - log_query(flags | F_UPSTREAM, name, addr, NULL); if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl) ttl = daemon->max_cache_ttl; if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl) @@ -1602,21 +1600,18 @@ int cache_make_stat(struct txt_record *t) case TXT_STAT_SERVERS: /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) - serv->flags &= ~SERV_COUNTED; + serv->flags &= ~SERV_MARK; 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_MARK)) { 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_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr)) { - serv1->flags |= SERV_COUNTED; + serv1->flags |= SERV_MARK; queries += serv1->queries; failed_queries += serv1->failed_queries; } @@ -1644,6 +1639,7 @@ int cache_make_stat(struct txt_record *t) } t->txt = (unsigned char *)buff; t->len = p - buff; + return 1; } @@ -1686,27 +1682,24 @@ void dump_cache(time_t now) /* sum counts from different records for same server */ for (serv = daemon->servers; serv; serv = serv->next) - serv->flags &= ~SERV_COUNTED; + serv->flags &= ~SERV_MARK; 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_MARK)) { 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_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr)) { - serv1->flags |= SERV_COUNTED; + serv1->flags |= SERV_MARK; queries += serv1->queries; failed_queries += serv1->failed_queries; } port = prettyprint_addr(&serv->addr, daemon->addrbuff); my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->addrbuff, port, queries, failed_queries); } - + if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG)) { struct crec *cache ; @@ -1860,47 +1853,93 @@ char *querystr(char *desc, unsigned short type) if (types) sprintf(buff, "<%s>", types); else - sprintf(buff, "type=%d", type); + sprintf(buff, "<type=%d>", type); } } return buff ? buff : ""; } +static char *edestr(int ede) +{ + switch (ede) + { + case EDE_OTHER: return "other"; + case EDE_USUPDNSKEY: return "unsupported DNSKEY algorithm"; + case EDE_USUPDS: return "unsupported DS digest"; + case EDE_STALE: return "stale answer"; + case EDE_FORGED: return "forged"; + case EDE_DNSSEC_IND: return "DNSSEC indeterminate"; + case EDE_DNSSEC_BOGUS: return "DNSSEC bogus"; + case EDE_SIG_EXP: return "DNSSEC signature expired"; + case EDE_SIG_NYV: return "DNSSEC sig not yet valid"; + case EDE_NO_DNSKEY: return "DNSKEY missing"; + case EDE_NO_RRSIG: return "RRSIG missing"; + case EDE_NO_ZONEKEY: return "no zone key bit set"; + case EDE_NO_NSEC: return "NSEC(3) missing"; + case EDE_CACHED_ERR: return "cached error"; + case EDE_NOT_READY: return "not ready"; + case EDE_BLOCKED: return "blocked"; + case EDE_CENSORED: return "censored"; + case EDE_FILTERED: return "filtered"; + case EDE_PROHIBITED: return "prohibited"; + case EDE_STALE_NXD: return "stale NXDOMAIN"; + case EDE_NOT_AUTH: return "not authoritative"; + case EDE_NOT_SUP: return "not supported"; + case EDE_NO_AUTH: return "no reachable authority"; + case EDE_NETERR: return "network error"; + case EDE_INVALID_DATA: return "invalid data"; + default: return "unknown"; + } +} + void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) { - char *source, *dest = daemon->addrbuff; + char *source, *dest = arg; char *verb = "is"; + char *extra = ""; if (!option_bool(OPT_LOG)) return; + +#ifdef HAVE_DNSSEC + if ((flags & F_DNSSECOK) && option_bool(OPT_EXTRALOG)) + extra = " (DNSSEC signed)"; +#endif name = sanitise(name); if (addr) { + dest = daemon->addrbuff; + if (flags & F_KEYTAG) sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest); else if (flags & F_RCODE) { 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); + + if (addr->log.ede != EDE_UNSET) + { + extra = daemon->addrbuff; + sprintf(extra, " (EDE: %s)", edestr(addr->log.ede)); + } } - else + else if (flags & (F_IPV4 | F_IPV6)) inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6, addr, daemon->addrbuff, ADDRSTRLEN); - + else + dest = arg; } - else - dest = arg; if (flags & F_REVERSE) { @@ -1938,7 +1977,15 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) else if (flags & F_UPSTREAM) source = "reply"; else if (flags & F_SECSTAT) - source = "validation"; + { + if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG)) + { + extra = daemon->addrbuff; + sprintf(extra, " (EDE: %s)", edestr(addr->log.ede)); + } + source = "validation"; + dest = arg; + } else if (flags & F_AUTH) source = "auth"; else if (flags & F_SERVER) @@ -1971,14 +2018,14 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg) if (option_bool(OPT_EXTRALOG)) { - int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2); if (flags & F_NOEXTRA) - my_syslog(LOG_INFO, "* %s/%u %s %s %s %s", daemon->addrbuff2, port, source, name, verb, dest); + my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra); else - my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest); + { + int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2); + my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra); + } } else - my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest); + my_syslog(LOG_INFO, "%s %s %s %s%s", source, name, verb, dest, extra); } - - diff --git a/src/config.h b/src/config.h index 665754c..ba4f2f8 100644 --- a/src/config.h +++ b/src/config.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -25,13 +25,12 @@ #define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ #define DNSSEC_WORK 50 /* Max number of queries to validate one question */ -#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ +#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TIME 20 /* or 20 seconds */ #define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */ #define SERVERS_LOGGED 30 /* Only log this many servers when logging state */ #define LOCALS_LOGGED 8 /* Only log this many local addresses when logging state */ -#define RANDOM_SOCKS 64 /* max simultaneous random ports */ #define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */ #define CACHESIZ 150 /* default cache size */ #define TTL_FLOOR_LIMIT 3600 /* don't allow --min-cache-ttl to raise TTL above this under any circumstances */ @@ -122,8 +121,8 @@ HAVE_AUTH define this to include the facility to act as an authoritative DNS server for one or more zones. -HAVE_NETTLEHASH - include just hash function from nettle, but no DNSSEC. +HAVE_CRYPTOHASH + include just hash function from crypto library, but no DNSSEC. HAVE_DNSSEC include DNSSEC validator. @@ -146,6 +145,7 @@ NO_SCRIPT NO_LARGEFILE NO_AUTH NO_DUMPFILE +NO_LOOP NO_INOTIFY these are available to explicitly disable compile time options which would otherwise be enabled automatically or which are enabled by default @@ -192,7 +192,7 @@ RESOLVFILE /* #define HAVE_IDN */ /* #define HAVE_LIBIDN2 */ /* #define HAVE_CONNTRACK */ -/* #define HAVE_NETTLEHASH */ +/* #define HAVE_CRYPTOHASH */ /* #define HAVE_DNSSEC */ @@ -426,10 +426,10 @@ static char *compile_opts = "no-" #endif "auth " -#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC) +#if !defined(HAVE_CRYPTOHASH) && !defined(HAVE_DNSSEC) "no-" #endif -"nettlehash " +"cryptohash " #ifndef HAVE_DNSSEC "no-" #endif @@ -450,7 +450,4 @@ static char *compile_opts = #endif "dumpfile"; -#endif - - - +#endif /* defined(HAVE_DHCP) */ diff --git a/src/conntrack.c b/src/conntrack.c index 33f5ceb..745a2a3 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -82,7 +82,4 @@ static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, vo return NFCT_CB_CONTINUE; } -#endif - - - +#endif /* HAVE_CONNTRACK */ diff --git a/src/crypto.c b/src/crypto.c index 09525d2..4009569 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -16,21 +16,34 @@ #include "dnsmasq.h" -#ifdef HAVE_DNSSEC +#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) +/* Minimal version of nettle */ + +/* bignum.h includes version.h and works on + earlier releases of nettle which don't have version.h */ +#include <nettle/bignum.h> +#if !defined(NETTLE_VERSION_MAJOR) +# define NETTLE_VERSION_MAJOR 2 +# define NETTLE_VERSION_MINOR 0 +#endif +#define MIN_VERSION(major, minor) ((NETTLE_VERSION_MAJOR == (major) && NETTLE_VERSION_MINOR >= (minor)) || \ + (NETTLE_VERSION_MAJOR > (major))) + +#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */ + +#if defined(HAVE_DNSSEC) #include <nettle/rsa.h> #include <nettle/ecdsa.h> #include <nettle/ecc-curve.h> +#if MIN_VERSION(3, 1) #include <nettle/eddsa.h> -#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 -# include <nettle/gostdsa.h> #endif +#if MIN_VERSION(3, 6) +# include <nettle/gostdsa.h> #endif -#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) -#include <nettle/nettle-meta.h> -#include <nettle/bignum.h> - +#if MIN_VERSION(3, 1) /* Implement a "hash-function" to the nettle API, which simply returns the input data, concatenated into a single, statically maintained, buffer. @@ -84,7 +97,6 @@ static void null_hash_update(void *ctxv, size_t length, const uint8_t *src) ctx->len += length; } - static void null_hash_digest(void *ctx, size_t length, uint8_t *dst) { (void)length; @@ -103,35 +115,7 @@ static struct nettle_hash null_hash = { (nettle_hash_digest_func *) null_hash_digest }; -/* Find pointer to correct hash function in nettle library */ -const struct nettle_hash *hash_find(char *name) -{ - if (!name) - return NULL; - - /* We provide a "null" hash which returns the input data as digest. */ - if (strcmp(null_hash.name, name) == 0) - return &null_hash; - - /* libnettle >= 3.4 provides nettle_lookup_hash() which avoids nasty ABI - incompatibilities if sizeof(nettle_hashes) changes between library - versions. It also #defines nettle_hashes, so use that to tell - if we have the new facilities. */ - -#ifdef nettle_hashes - return nettle_lookup_hash(name); -#else - { - int i; - - for (i = 0; nettle_hashes[i]; i++) - if (strcmp(nettle_hashes[i]->name, name) == 0) - return nettle_hashes[i]; - } - - return NULL; -#endif -} +#endif /* MIN_VERSION(3, 1) */ /* expand ctx and digest memory allocations if necessary and init hash function */ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp) @@ -171,10 +155,6 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges return 1; } -#endif - -#ifdef HAVE_DNSSEC - static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) { @@ -238,7 +218,7 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len static struct ecc_point *key_256 = NULL, *key_384 = NULL; static mpz_t x, y; static struct dsa_signature *sig_struct; -#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR < 4 +#if !MIN_VERSION(3, 4) #define nettle_get_secp_256r1() (&nettle_secp_256r1) #define nettle_get_secp_384r1() (&nettle_secp_384r1) #endif @@ -301,7 +281,7 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len return nettle_ecdsa_verify(key, digest_len, digest, sig_struct); } -#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 +#if MIN_VERSION(3, 6) static int dnsmasq_gostdsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) @@ -342,6 +322,7 @@ static int dnsmasq_gostdsa_verify(struct blockdata *key_data, unsigned int key_l } #endif +#if MIN_VERSION(3, 1) static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) @@ -368,7 +349,7 @@ static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len ((struct null_hash_digest *)digest)->buff, sig); -#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 +#if MIN_VERSION(3, 6) case 16: if (key_len != ED448_KEY_SIZE || sig_len != ED448_SIGNATURE_SIZE) @@ -384,6 +365,7 @@ static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len return 0; } +#endif static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, unsigned char *digest, size_t digest_len, int algo) @@ -399,16 +381,18 @@ static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key case 5: case 7: case 8: case 10: return dnsmasq_rsa_verify; -#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6 +#if MIN_VERSION(3, 6) case 12: return dnsmasq_gostdsa_verify; #endif case 13: case 14: return dnsmasq_ecdsa_verify; - + +#if MIN_VERSION(3, 1) case 15: case 16: return dnsmasq_eddsa_verify; +#endif } return NULL; @@ -479,4 +463,37 @@ char *nsec3_digest_name(int digest) } } +#endif /* defined(HAVE_DNSSEC) */ + +#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) +/* Find pointer to correct hash function in nettle library */ +const struct nettle_hash *hash_find(char *name) +{ + if (!name) + return NULL; + +#if MIN_VERSION(3,1) && defined(HAVE_DNSSEC) + /* We provide a "null" hash which returns the input data as digest. */ + if (strcmp(null_hash.name, name) == 0) + return &null_hash; #endif + + /* libnettle >= 3.4 provides nettle_lookup_hash() which avoids nasty ABI + incompatibilities if sizeof(nettle_hashes) changes between library + versions. */ +#if MIN_VERSION(3, 4) + return nettle_lookup_hash(name); +#else + { + int i; + + for (i = 0; nettle_hashes[i]; i++) + if (strcmp(nettle_hashes[i]->name, name) == 0) + return nettle_hashes[i]; + } + + return NULL; +#endif +} + +#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */ @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -215,7 +215,7 @@ static void dbus_read_servers(DBusMessage *message) domain = NULL; if (!skip) - add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain); + add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain, NULL); } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); } @@ -276,7 +276,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) { const char *str = NULL; union mysockaddr addr, source_addr; - int flags = 0; + u16 flags = 0; char interface[IF_NAMESIZE]; char *str_addr, *str_domain = NULL; @@ -361,10 +361,6 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) strcpy(str_addr, str); } - memset(&addr, 0, sizeof(addr)); - memset(&source_addr, 0, sizeof(source_addr)); - memset(&interface, 0, sizeof(interface)); - /* parse the IP address */ if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags))) { @@ -377,7 +373,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) { @@ -392,7 +388,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) else p = NULL; - add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain); + add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL); } while ((str_domain = p)); } else @@ -407,7 +403,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) dbus_message_iter_get_basic(&string_iter, &str); dbus_message_iter_next (&string_iter); - add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str); + add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str, NULL); } while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING); } @@ -416,7 +412,7 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) } cleanup_servers(); - + if (dup) free(dup); @@ -715,7 +711,7 @@ DBusHandlerResult message_handler(DBusConnection *connection, if (new_servers) { my_syslog(LOG_INFO, _("setting upstream servers from DBus")); - check_servers(); + check_servers(0); if (option_bool(OPT_RELOAD)) clear_cache = 1; } diff --git a/src/dhcp-common.c b/src/dhcp-common.c index eae9886..1f91ca0 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -79,13 +79,46 @@ ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) return (msg->msg_flags & MSG_TRUNC) ? -1 : new_sz; } +/* like match_netid() except that the check can have a trailing * for wildcard */ +/* started as a direct copy of match_netid() */ +int match_netid_wild(struct dhcp_netid *check, struct dhcp_netid *pool) +{ + struct dhcp_netid *tmp1; + + for (; check; check = check->next) + { + const int check_len = strlen(check->net); + const int is_wc = (check->net[check_len - 1] == '*'); + + /* '#' for not is for backwards compat. */ + if (check->net[0] != '!' && check->net[0] != '#') + { + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (is_wc ? (strncmp(check->net, tmp1->net, check_len-1) == 0) : + (strcmp(check->net, tmp1->net) == 0)) + break; + if (!tmp1) + return 0; + } + else + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (is_wc ? (strncmp((check->net)+1, tmp1->net, check_len-2) == 0) : + (strcmp((check->net)+1, tmp1->net) == 0)) + return 0; + } + return 1; +} + struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) { struct tag_if *exprs; struct dhcp_netid_list *list; + /* this now uses match_netid_wild() above so that tag_if can + * be used to set a 'group of interfaces' tag. + */ for (exprs = daemon->tag_if; exprs; exprs = exprs->next) - if (match_netid(exprs->tag, tags, 1)) + if (match_netid_wild(exprs->tag, tags)) for (list = exprs->set; list; list = list->next) { list->list->next = tags; @@ -280,31 +313,29 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config { if (!context) /* called via find_config() from lease_update_from_configs() */ return 1; + + if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) + return 1; #ifdef HAVE_DHCP6 if (context->flags & CONTEXT_V6) { struct addrlist *addr_list; - if (!(config->flags & CONFIG_ADDR6)) - return 1; - - for (; context; context = context->current) - for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) - { - if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) - return 1; - - if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix)) - return 1; - } + if (config->flags & CONFIG_ADDR6) + for (; context; context = context->current) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + { + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + return 1; + + if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix)) + return 1; + } } else #endif { - if (!(config->flags & CONFIG_ADDR)) - return 1; - for (; context; context = context->current) if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) return 1; @@ -622,6 +653,8 @@ static const struct opttab_t { { "client-arch", 93, 2 | OT_DEC }, { "client-interface-id", 94, 0 }, { "client-machine-id", 97, 0 }, + { "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */ + { "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */ { "subnet-select", 118, OT_INTERNAL }, { "domain-search", 119, OT_RFC1035_NAME }, { "sip-server", 120, 0 }, diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h index e9007c8..6ff3ffa 100644 --- a/src/dhcp-protocol.h +++ b/src/dhcp-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -468,8 +468,11 @@ void dhcp_packet(time_t now, int pxe_fd) /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */ if (errno != 0) - my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"), - inet_ntoa(dest.sin_addr), strerror(errno)); + { + inet_ntop(AF_INET, &dest.sin_addr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"), + daemon->addrbuff, strerror(errno)); + } } /* check against secondary interface addresses */ @@ -521,10 +524,11 @@ static void guess_range_netmask(struct in_addr addr, struct in_addr netmask) !(is_same_net(addr, context->start, netmask) && is_same_net(addr, context->end, netmask))) { - strcpy(daemon->dhcp_buff, inet_ntoa(context->start)); - strcpy(daemon->dhcp_buff2, inet_ntoa(context->end)); + inet_ntop(AF_INET, &context->start, daemon->dhcp_buff, DHCP_BUFF_SZ); + inet_ntop(AF_INET, &context->end, daemon->dhcp_buff2, DHCP_BUFF_SZ); + inet_ntop(AF_INET, &netmask, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"), - daemon->dhcp_buff, daemon->dhcp_buff2, inet_ntoa(netmask)); + daemon->dhcp_buff, daemon->dhcp_buff2, daemon->addrbuff); } context->netmask = netmask; } @@ -922,7 +926,7 @@ void dhcp_read_ethers(void) if (!*cp) { - if ((addr.s_addr = inet_addr(ip)) == (in_addr_t)-1) + if (inet_pton(AF_INET, ip, &addr.s_addr) < 1) { my_syslog(MS_DHCP | LOG_ERR, _("bad address at %s line %d"), ETHERSFILE, lineno); continue; @@ -1097,7 +1101,8 @@ static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, if (option_bool(OPT_LOG_OPTS)) { inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN); - my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr4)); + inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2); } /* Save this for replies */ diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h index 48b027b..ebe8dfd 100644 --- a/src/dhcp6-protocol.h +++ b/src/dhcp6-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -72,4 +72,3 @@ #define DHCP6NOBINDING 3 #define DHCP6NOTONLINK 4 #define DHCP6USEMULTI 5 - diff --git a/src/dhcp6.c b/src/dhcp6.c index 096e2e1..2be877f 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -825,6 +825,4 @@ void dhcp_construct_contexts(time_t now) } } -#endif - - +#endif /* HAVE_DHCP6 */ diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 8edb9f3..496a4bb 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -80,8 +80,41 @@ #define EDNS0_OPTION_MAC 65001 /* dyndns.org temporary assignment */ #define EDNS0_OPTION_CLIENT_SUBNET 8 /* IANA */ +#define EDNS0_OPTION_EDE 15 /* IANA - RFC 8914 */ #define EDNS0_OPTION_NOMDEVICEID 65073 /* Nominum temporary assignment */ #define EDNS0_OPTION_NOMCPEID 65074 /* Nominum temporary assignment */ +#define EDNS0_OPTION_UMBRELLA 20292 /* Cisco Umbrella temporary assignment */ + +/* RFC-8914 extended errors, negative values are our definitions */ +#define EDE_UNSET -1 /* No extended DNS error available */ +#define EDE_OTHER 0 /* Other */ +#define EDE_USUPDNSKEY 1 /* Unsupported DNSKEY algo */ +#define EDE_USUPDS 2 /* Unsupported DS Digest */ +#define EDE_STALE 3 /* Stale answer */ +#define EDE_FORGED 4 /* Forged answer */ +#define EDE_DNSSEC_IND 5 /* DNSSEC Indeterminate */ +#define EDE_DNSSEC_BOGUS 6 /* DNSSEC Bogus */ +#define EDE_SIG_EXP 7 /* Signature Expired */ +#define EDE_SIG_NYV 8 /* Signature Not Yet Valid */ +#define EDE_NO_DNSKEY 9 /* DNSKEY missing */ +#define EDE_NO_RRSIG 10 /* RRSIGs missing */ +#define EDE_NO_ZONEKEY 11 /* No Zone Key Bit Set */ +#define EDE_NO_NSEC 12 /* NSEC Missing */ +#define EDE_CACHED_ERR 13 /* Cached Error */ +#define EDE_NOT_READY 14 /* Not Ready */ +#define EDE_BLOCKED 15 /* Blocked */ +#define EDE_CENSORED 16 /* Censored */ +#define EDE_FILTERED 17 /* Filtered */ +#define EDE_PROHIBITED 18 /* Prohibited */ +#define EDE_STALE_NXD 19 /* Stale NXDOMAIN */ +#define EDE_NOT_AUTH 20 /* Not Authoritative */ +#define EDE_NOT_SUP 21 /* Not Supported */ +#define EDE_NO_AUTH 22 /* No Reachable Authority */ +#define EDE_NETERR 23 /* Network error */ +#define EDE_INVALID_DATA 24 /* Invalid Data */ + + + struct dns_header { u16 id; diff --git a/src/dnsmasq.c b/src/dnsmasq.c index 2306c48..602daed 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -24,7 +24,7 @@ struct daemon *daemon; static volatile pid_t pid = 0; static volatile int pipewrite; -static int set_dns_listeners(time_t now); +static void set_dns_listeners(void); static void check_dns_listeners(time_t now); static void sig_handler(int sig); static void async_event(int pipe, time_t now); @@ -109,7 +109,6 @@ int main (int argc, char **argv) daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ; daemon->packet = safe_malloc(daemon->packet_buff_sz); - daemon->addrbuff = safe_malloc(ADDRSTRLEN); if (option_bool(OPT_EXTRALOG)) daemon->addrbuff2 = safe_malloc(ADDRSTRLEN); @@ -135,6 +134,13 @@ int main (int argc, char **argv) } #endif +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) + /* CONNTRACK UBUS code uses this buffer, so if not allocated above, + we need to allocate it here. */ + if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename) + daemon->workspacename = safe_malloc(MAXDNAME); +#endif + #ifdef HAVE_DHCP if (!daemon->lease_file) { @@ -205,8 +211,13 @@ int main (int argc, char **argv) #endif #ifdef HAVE_CONNTRACK - if (option_bool(OPT_CONNTRACK) && (daemon->query_port != 0 || daemon->osport)) - die (_("cannot use --conntrack AND --query-port"), NULL, EC_BADCONF); + if (option_bool(OPT_CONNTRACK)) + { + if (daemon->query_port != 0 || daemon->osport) + die (_("cannot use --conntrack AND --query-port"), NULL, EC_BADCONF); + + need_cap_net_admin = 1; + } #else if (option_bool(OPT_CONNTRACK)) die(_("conntrack support not available: set HAVE_CONNTRACK in src/config.h"), NULL, EC_BADCONF); @@ -237,9 +248,16 @@ int main (int argc, char **argv) die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF); #endif + /* Handle only one of min_port/max_port being set. */ + if (daemon->min_port != 0 && daemon->max_port == 0) + daemon->max_port = MAX_PORT; + + if (daemon->max_port != 0 && daemon->min_port == 0) + daemon->min_port = MIN_PORT; + if (daemon->max_port < daemon->min_port) die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF); - + now = dnsmasq_time(); if (daemon->auth_zones) @@ -390,8 +408,16 @@ int main (int argc, char **argv) if (daemon->port != 0) { cache_init(); - blockdata_init(); + hash_questions_init(); + + /* Scale random socket pool by ftabsize, but + limit it based on available fds. */ + daemon->numrrand = daemon->ftabsize/2; + if (daemon->numrrand > max_fd/3) + daemon->numrrand = max_fd/3; + /* safe_malloc returns zero'd memory */ + daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd)); } #ifdef HAVE_INOTIFY @@ -415,8 +441,6 @@ int main (int argc, char **argv) #ifdef HAVE_DBUS { char *err; - daemon->dbus = NULL; - daemon->watches = NULL; if ((err = dbus_init())) die(_("DBus error: %s"), err, EC_MISC); } @@ -427,8 +451,9 @@ int main (int argc, char **argv) if (option_bool(OPT_UBUS)) #ifdef HAVE_UBUS { - daemon->ubus = NULL; - ubus_init(); + char *err; + if ((err = ubus_init())) + die(_("UBus error: %s"), err, EC_MISC); } #else die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF); @@ -980,7 +1005,7 @@ int main (int argc, char **argv) a single file will be sent to may clients (the file only needs one fd). */ - max_fd -= 30; /* use other than TFTP */ + max_fd -= 30 + daemon->numrrand; /* use other than TFTP */ if (max_fd < 0) max_fd = 5; @@ -1010,7 +1035,7 @@ int main (int argc, char **argv) close(err_pipe[1]); if (daemon->port != 0) - check_servers(); + check_servers(0); pid = getpid(); @@ -1025,16 +1050,10 @@ int main (int argc, char **argv) while (1) { - int t, timeout = -1; + int timeout = -1; poll_reset(); - /* if we are out of resources, find how long we have to wait - for some to come free, we'll loop around then and restart - listening for queries */ - if ((t = set_dns_listeners(now)) != 0) - timeout = t * 1000; - /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */ if (daemon->tftp_trans || (option_bool(OPT_DBUS) && !daemon->dbus)) @@ -1044,15 +1063,18 @@ int main (int argc, char **argv) else if (is_dad_listeners()) timeout = 1000; + set_dns_listeners(); + #ifdef HAVE_DBUS - set_dbus_listeners(); + if (option_bool(OPT_DBUS)) + set_dbus_listeners(); #endif - + #ifdef HAVE_UBUS if (option_bool(OPT_UBUS)) set_ubus_listeners(); #endif - + #ifdef HAVE_DHCP if (daemon->dhcp || daemon->relay4) { @@ -1172,28 +1194,44 @@ int main (int argc, char **argv) #ifdef HAVE_DBUS /* if we didn't create a DBus connection, retry now. */ - if (option_bool(OPT_DBUS) && !daemon->dbus) + if (option_bool(OPT_DBUS)) { - char *err; - if ((err = dbus_init())) - my_syslog(LOG_WARNING, _("DBus error: %s"), err); - if (daemon->dbus) - my_syslog(LOG_INFO, _("connected to system DBus")); + if (!daemon->dbus) + { + char *err = dbus_init(); + + if (daemon->dbus) + my_syslog(LOG_INFO, _("connected to system DBus")); + else if (err) + { + my_syslog(LOG_ERR, _("DBus error: %s"), err); + reset_option_bool(OPT_DBUS); /* fatal error, stop trying. */ + } + } + + check_dbus_listeners(); } - check_dbus_listeners(); #endif #ifdef HAVE_UBUS + /* if we didn't create a UBus connection, retry now. */ if (option_bool(OPT_UBUS)) - { - /* if we didn't create a UBus connection, retry now. */ - if (!daemon->ubus) - { - ubus_init(); - } + { + if (!daemon->ubus) + { + char *err = ubus_init(); - check_ubus_listeners(); - } + if (daemon->ubus) + my_syslog(LOG_INFO, _("connected to system UBus")); + else if (err) + { + my_syslog(LOG_ERR, _("UBus error: %s"), err); + reset_option_bool(OPT_UBUS); /* fatal error, stop trying. */ + } + } + + check_ubus_listeners(); + } #endif check_dns_listeners(now); @@ -1426,7 +1464,7 @@ static void async_event(int pipe, time_t now) } if (check) - check_servers(); + check_servers(0); } #ifdef HAVE_DHCP @@ -1625,7 +1663,7 @@ static void poll_resolv(int force, int do_reload, time_t now) { my_syslog(LOG_INFO, _("reading %s"), latest->name); warned = 0; - check_servers(); + check_servers(0); if (option_bool(OPT_RELOAD) && do_reload) clear_cache_and_reload(now); } @@ -1668,11 +1706,12 @@ void clear_cache_and_reload(time_t now) #endif } -static int set_dns_listeners(time_t now) +static void set_dns_listeners(void) { struct serverfd *serverfdp; struct listener *listener; - int wait = 0, i; + struct randfd_list *rfl; + int i; #ifdef HAVE_TFTP int tftp = 0; @@ -1685,66 +1724,68 @@ static int set_dns_listeners(time_t now) } #endif - /* will we be able to get memory? */ - if (daemon->port != 0) - get_new_frec(now, &wait, NULL); - for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) poll_listen(serverfdp->fd, POLLIN); - if (daemon->port != 0 && !daemon->osport) - for (i = 0; i < RANDOM_SOCKS; i++) - if (daemon->randomsocks[i].refcount != 0) - poll_listen(daemon->randomsocks[i].fd, POLLIN); - + for (i = 0; i < daemon->numrrand; i++) + if (daemon->randomsocks[i].refcount != 0) + poll_listen(daemon->randomsocks[i].fd, POLLIN); + + /* Check overflow random sockets too. */ + for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) + poll_listen(rfl->rfd->fd, POLLIN); + + /* check to see if we have free tcp process slots. */ + for (i = MAX_PROCS - 1; i >= 0; i--) + if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) + break; + for (listener = daemon->listeners; listener; listener = listener->next) { - /* only listen for queries if we have resources */ - if (listener->fd != -1 && wait == 0) + if (listener->fd != -1) poll_listen(listener->fd, POLLIN); - - /* death of a child goes through the select loop, so - we don't need to explicitly arrange to wake up here */ - if (listener->tcpfd != -1) - for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) - { - poll_listen(listener->tcpfd, POLLIN); - break; - } - + + /* Only listen for TCP connections when a process slot + is available. Death of a child goes through the select loop, so + we don't need to explicitly arrange to wake up here, + we'll be called again when a slot becomes available. */ + if (listener->tcpfd != -1 && i >= 0) + poll_listen(listener->tcpfd, POLLIN); + #ifdef HAVE_TFTP /* tftp == 0 in single-port mode. */ if (tftp <= daemon->tftp_max && listener->tftpfd != -1) poll_listen(listener->tftpfd, POLLIN); #endif - } if (!option_bool(OPT_DEBUG)) for (i = 0; i < MAX_PROCS; i++) if (daemon->tcp_pipes[i] != -1) poll_listen(daemon->tcp_pipes[i], POLLIN); - - return wait; } static void check_dns_listeners(time_t now) { struct serverfd *serverfdp; struct listener *listener; + struct randfd_list *rfl; int i; int pipefd[2]; for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) if (poll_check(serverfdp->fd, POLLIN)) - reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); + reply_query(serverfdp->fd, now); - if (daemon->port != 0 && !daemon->osport) - for (i = 0; i < RANDOM_SOCKS; i++) - if (daemon->randomsocks[i].refcount != 0 && - poll_check(daemon->randomsocks[i].fd, POLLIN)) - reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); + for (i = 0; i < daemon->numrrand; i++) + if (daemon->randomsocks[i].refcount != 0 && + poll_check(daemon->randomsocks[i].fd, POLLIN)) + reply_query(daemon->randomsocks[i].fd, now); + + /* Check overflow random sockets too. */ + for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) + if (poll_check(rfl->rfd->fd, POLLIN)) + reply_query(rfl->rfd->fd, now); /* Races. The child process can die before we read all of the data from the pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the @@ -1773,7 +1814,16 @@ static void check_dns_listeners(time_t now) tftp_request(listener, now); #endif - if (listener->tcpfd != -1 && poll_check(listener->tcpfd, POLLIN)) + /* check to see if we have a free tcp process slot. + Note that we can't assume that because we had + at least one a poll() time, that we still do. + There may be more waiting connections after + poll() returns then free process slots. */ + for (i = MAX_PROCS - 1; i >= 0; i--) + if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) + break; + + if (listener->tcpfd != -1 && i >= 0 && poll_check(listener->tcpfd, POLLIN)) { int confd, client_ok = 1; struct irec *iface = NULL; @@ -1863,7 +1913,6 @@ static void check_dns_listeners(time_t now) close(pipefd[0]); else { - int i; #ifdef HAVE_LINUX_NETWORK /* The child process inherits the netlink socket, which it never uses, but when the parent (us) @@ -1883,13 +1932,9 @@ static void check_dns_listeners(time_t now) read_write(pipefd[0], &a, 1, 1); #endif - for (i = 0; i < MAX_PROCS; i++) - if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1) - { - daemon->tcp_pids[i] = p; - daemon->tcp_pipes[i] = pipefd[0]; - break; - } + /* i holds index of free slot */ + daemon->tcp_pids[i] = p; + daemon->tcp_pipes[i] = pipefd[0]; } close(confd); @@ -2068,7 +2113,7 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id) poll_reset(); if (fd != -1) poll_listen(fd, POLLIN); - set_dns_listeners(now); + set_dns_listeners(); set_log_writer(); #ifdef HAVE_DHCP6 diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 914f469..8674823 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -14,7 +14,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#define COPYRIGHT "Copyright (c) 2000-2020 Simon Kelley" +#define COPYRIGHT "Copyright (c) 2000-2021 Simon Kelley" /* We do defines that influence behavior of stdio.h, so complain if included too early. */ @@ -95,11 +95,7 @@ typedef unsigned long long u64; #if defined(HAVE_SOLARIS_NETWORK) # include <sys/sockio.h> #endif -#if defined(HAVE_POLL_H) -# include <poll.h> -#else -# include <sys/poll.h> -#endif +#include <poll.h> #include <sys/wait.h> #include <sys/time.h> #include <sys/un.h> @@ -111,6 +107,7 @@ typedef unsigned long long u64; #endif #include <unistd.h> #include <stdio.h> +#include <stdint.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> @@ -157,7 +154,11 @@ extern int capget(cap_user_header_t header, cap_user_data_t data); #include <priv.h> #endif -#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) +/* Backwards compat with 2.83 */ +#if defined(HAVE_NETTLEHASH) +# define HAVE_CRYPTOHASH +#endif +#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) # include <nettle/nettle-meta.h> #endif @@ -269,7 +270,12 @@ struct event_desc { #define OPT_IGNORE_CLID 59 #define OPT_SINGLE_PORT 60 #define OPT_LEASE_RENEW 61 -#define OPT_LAST 62 +#define OPT_LOG_DEBUG 62 +#define OPT_UMBRELLA 63 +#define OPT_UMBRELLA_DEVID 64 +#define OPT_CMARK_ALST_EN 65 +#define OPT_QUIET_TFTP 66 +#define OPT_LAST 67 #define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) @@ -277,11 +283,13 @@ struct event_desc { #define option_val(x) ((1u) << ((x) % OPTION_BITS)) #define option_bool(x) (option_var(x) & option_val(x)) -/* extra flags for my_syslog, we use a couple of facilities since they are known - not to occupy the same bits as priorities, no matter how syslog.h is set up. */ +/* extra flags for my_syslog, we use facilities since they are known + not to occupy the same bits as priorities, no matter how syslog.h is set up. + MS_DEBUG messages are suppressed unless --log-debug is set. */ #define MS_TFTP LOG_USER #define MS_DHCP LOG_DAEMON #define MS_SCRIPT LOG_MAIL +#define MS_DEBUG LOG_NEWS /* Note that this is used widely as a container for IPv4/IPv6 addresses, so for that reason, was well as to avoid wasting memory in almost every @@ -317,12 +325,14 @@ union all_addr { /* for log_query */ struct { unsigned short keytag, algo, digest, rcode; + int ede; } log; }; struct bogus_addr { - struct in_addr addr; + int is6, prefix; + union all_addr addr; struct bogus_addr *next; }; @@ -423,10 +433,17 @@ struct host_record { struct host_record *next; }; +#define IN4 1 +#define IN6 2 +#define INP4 4 +#define INP6 8 + struct interface_name { char *name; /* domain name */ char *intr; /* interface name */ - int family; /* AF_INET, AF_INET6 or zero for both */ + int flags; + struct in_addr proto4; + struct in6_addr proto6; struct addrlist *addr; struct interface_name *next; }; @@ -486,7 +503,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) @@ -512,19 +529,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_MARK 256 /* for mark-and-delete and log code */ +#define SERV_WILDCARD 512 /* domain has leading '*' */ #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 */ @@ -539,22 +557,56 @@ struct serverfd { }; struct randfd { + struct server *serv; int fd; - unsigned short refcount, family; + unsigned short refcount; /* refcount == 0xffff means overflow record. */ }; - + +struct randfd_list { + struct randfd *rfd; + struct randfd_list *next; +}; + + struct server { + u16 flags, domain_len; + 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 four fields must match struct server in next three definitions.. */ +struct serv_addr4 { + u16 flags, domain_len; + char *domain; + struct server *next; + struct in_addr addr; +}; + +struct serv_addr6 { + u16 flags, domain_len; + char *domain; + struct server *next; + struct in6_addr addr; +}; + +struct serv_local { + u16 flags, domain_len; + char *domain; + struct server *next; }; struct ipsets { @@ -563,6 +615,12 @@ struct ipsets { struct ipsets *next; }; +struct allowlist { + u32 mark, mask; + char **patterns; + struct allowlist *next; +}; + struct irec { union mysockaddr addr; struct in_addr netmask; /* only valid for IPv4 */ @@ -632,17 +690,28 @@ struct hostsfile { #define DUMP_BOGUS 0x0040 #define DUMP_SEC_BOGUS 0x0080 - /* DNSSEC status values. */ -#define STAT_SECURE 1 -#define STAT_INSECURE 2 -#define STAT_BOGUS 3 -#define STAT_NEED_DS 4 -#define STAT_NEED_KEY 5 -#define STAT_TRUNCATED 6 -#define STAT_SECURE_WILDCARD 7 -#define STAT_OK 8 -#define STAT_ABANDONED 9 +#define STAT_SECURE 0x10000 +#define STAT_INSECURE 0x20000 +#define STAT_BOGUS 0x30000 +#define STAT_NEED_DS 0x40000 +#define STAT_NEED_KEY 0x50000 +#define STAT_TRUNCATED 0x60000 +#define STAT_SECURE_WILDCARD 0x70000 +#define STAT_OK 0x80000 +#define STAT_ABANDONED 0x90000 + +#define DNSSEC_FAIL_NYV 0x0001 /* key not yet valid */ +#define DNSSEC_FAIL_EXP 0x0002 /* key expired */ +#define DNSSEC_FAIL_INDET 0x0004 /* indetermined */ +#define DNSSEC_FAIL_NOKEYSUP 0x0008 /* no supported key algo. */ +#define DNSSEC_FAIL_NOSIG 0x0010 /* No RRsigs */ +#define DNSSEC_FAIL_NOZONE 0x0020 /* No Zone bit set */ +#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ +#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ +#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ + +#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) #define FREC_NOREBIND 1 #define FREC_CHECKING_DISABLED 2 @@ -664,14 +733,14 @@ struct frec { union mysockaddr source; union all_addr dest; unsigned int iface, log_id; + int fd; unsigned short orig_id; struct frec_src *next; } frec_src; struct server *sentto; /* NULL means free */ - struct randfd *rfd4; - struct randfd *rfd6; + struct randfd_list *rfds; unsigned short new_id; - int fd, forwardall, flags; + int forwardall, flags; time_t time; unsigned char *hash[HASH_SIZE]; #ifdef HAVE_DNSSEC @@ -679,6 +748,7 @@ struct frec { struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ + struct frec *next_dependent; /* list of above. */ struct frec *blocking_query; /* Query which is blocking us. */ #endif struct frec *next; @@ -891,10 +961,10 @@ struct dhcp_bridge { }; struct cond_domain { - char *domain, *prefix; + char *domain, *prefix; /* prefix is text-prefix on domain name */ struct in_addr start, end; struct in6_addr start6, end6; - int is6, indexed; + int is6, indexed, prefixlen; struct cond_domain *next; }; @@ -1035,8 +1105,12 @@ 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 server_has_wildcard; + int serverarraysz, serverarrayhwm; struct ipsets *ipsets; + u32 allowlist_mask; + struct allowlist *allowlists; int log_fac; /* log facility */ char *log_file; /* optional log file */ int max_logs; /* queue limit */ @@ -1044,6 +1118,9 @@ extern struct daemon { int port, query_port, min_port, max_port; unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl; char *dns_client_id; + u32 umbrella_org; + u32 umbrella_asset; + u8 umbrella_device[8]; struct hostsfile *addn_hosts; struct dhcp_context *dhcp, *dhcp6; struct ra_interface *ra_interfaces; @@ -1104,16 +1181,15 @@ 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; /* " " */ - struct randfd *rfd_save; /* " " */ + int fd_save; /* " " */ pid_t tcp_pids[MAX_PROCS]; int tcp_pipes[MAX_PROCS]; int pipe_to_parent; - struct randfd randomsocks[RANDOM_SOCKS]; + int numrrand; + struct randfd *randomsocks; + struct randfd_list *rfl_spare, *rfl_poll; int v6pktinfo; struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ int log_id, log_display_id; /* ids of transactions for logging */ @@ -1220,18 +1296,19 @@ 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 ede); 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); +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) +void report_addresses(struct dns_header *header, size_t len, u32 mark); +#endif size_t answer_request(struct dns_header *header, char *limit, size_t qlen, struct in_addr local_addr, struct in_addr local_netmask, time_t now, int ad_reqd, int do_bit, int have_pseudoheader); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, - struct bogus_addr *baddr, time_t now); -int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); + time_t now); +int check_for_ignored_address(struct dns_header *header, size_t qlen); int check_for_local_domain(char *name, time_t now); size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen); @@ -1250,6 +1327,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); #endif /* dnssec.c */ +#ifdef HAVE_DNSSEC size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); @@ -1258,8 +1336,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); size_t filter_rrsigs(struct dns_header *header, size_t plen); int setup_timestamp(void); +int errflags_to_ede(int status); +#endif /* hash_questions.c */ +void hash_questions_init(void); unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name); /* crypto.c */ @@ -1284,12 +1365,13 @@ void safe_strncpy(char *dest, const char *src, size_t size); void safe_pipe(int *fd, int read_noblock); void *whine_malloc(size_t size); int sa_len(union mysockaddr *addr); -int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); +int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); int hostname_isequal(const char *a, const char *b); int hostname_issubdomain(char *a, char *b); time_t dnsmasq_time(void); int netmask_length(struct in_addr mask); int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask); +int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix); int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen); u64 addr6part(struct in6_addr *addr); void setaddr6part(struct in6_addr *addr, u64 host); @@ -1331,37 +1413,28 @@ void set_option_bool(unsigned int opt); void reset_option_bool(unsigned int opt); struct hostsfile *expand_filelist(struct hostsfile *list); char *parse_server(char *arg, union mysockaddr *addr, - union mysockaddr *source_addr, char *interface, int *flags); + union mysockaddr *source_addr, char *interface, u16 *flags); int option_read_dynfile(char *file, int flags); /* forward.c */ -void reply_query(int fd, int family, time_t now); +void reply_query(int fd, time_t now); void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); void server_gone(struct server *server); -struct frec *get_new_frec(time_t now, int *wait, struct frec *force); int send_from(int fd, int nowild, char *packet, size_t len, union mysockaddr *to, union all_addr *source, unsigned int iface); void resend_query(void); -struct randfd *allocate_rfd(int family); -void free_rfd(struct randfd *rfd); +int allocate_rfd(struct randfd_list **fdlp, struct server *serv); +void free_rfds(struct randfd_list **fdlp); /* network.c */ int indextoname(int fd, int index, char *name); int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); -int random_sock(int family); void pre_allocate_sfds(void); int reload_servers(char *fname); -void mark_servers(int flag); -void cleanup_servers(void); -void add_update_server(int flags, - union mysockaddr *addr, - union mysockaddr *source_addr, - const char *interface, - const char *domain); -void check_servers(void); +void check_servers(int no_loop_call); int enumerate_interfaces(int reset); void create_wildcard_listeners(void); void create_bound_listeners(int dienow); @@ -1494,10 +1567,14 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname); /* ubus.c */ #ifdef HAVE_UBUS -void ubus_init(void); +char *ubus_init(void); void set_ubus_listeners(void); void check_ubus_listeners(void); void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface); +# ifdef HAVE_CONNTRACK +void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name); +void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl); +# endif #endif /* ipset.c */ @@ -1506,6 +1583,13 @@ void ipset_init(void); int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove); #endif +/* pattern.c */ +#ifdef HAVE_CONNTRACK +int is_valid_dns_name(const char *value); +int is_valid_dns_name_pattern(const char *value); +int is_dns_name_matching_pattern(const char *name, const char *pattern); +#endif + /* helper.c */ #if defined(HAVE_SCRIPT) int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd); @@ -1671,3 +1755,23 @@ 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); +size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, + char *name, char *limit, int first, int last, int ede); +int server_samegroup(struct server *a, struct server *b); +#ifdef HAVE_DNSSEC +int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp); +#endif +void mark_servers(int flag); +void cleanup_servers(void); +int add_update_server(int flags, + union mysockaddr *addr, + union mysockaddr *source_addr, + const char *interface, + const char *domain, + union all_addr *local_addr); diff --git a/src/dnssec.c b/src/dnssec.c index 93cc7bf..153cac4 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -215,14 +215,6 @@ static int is_check_date(unsigned long curtime) return !daemon->dnssec_no_time_check; } -/* Check whether today/now is between date_start and date_end */ -static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end) -{ - /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ - return serial_compare_32(curtime, date_start) == SERIAL_GT - && serial_compare_32(curtime, date_end) == SERIAL_LT; -} - /* Return bytes of canonicalised rrdata one by one. Init state->ip with the RR, and state->end with the end of same. Init state->op to NULL. @@ -334,37 +326,64 @@ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) return rrsetidx; /* short packet */ state2.end = state2.ip + rdlen2; - - while (1) + + /* If the RR has no names in it then canonicalisation + is the identity function and we can compare + the RRs directly. If not we compare the + canonicalised RRs one byte at a time. */ + if (*rr_desc == (u16)-1) { - int ok1, ok2; + int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1; + int cmp = memcmp(state1.ip, state2.ip, rdmin); - ok1 = get_rdata(header, plen, &state1); - ok2 = get_rdata(header, plen, &state2); - - if (!ok1 && !ok2) + if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = 1; + } + else if (cmp == 0 && (rdlen1 == rdlen2)) { /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ for (j = i+1; j < rrsetidx-1; j++) rrset[j] = rrset[j+1]; rrsetidx--; i--; - break; } - else if (ok1 && (!ok2 || *state1.op > *state2.op)) - { - unsigned char *tmp = rrset[i+1]; - rrset[i+1] = rrset[i]; - rrset[i] = tmp; - swap = 1; - break; - } - else if (ok2 && (!ok1 || *state2.op > *state1.op)) - break; - - /* arrive here when bytes are equal, go round the loop again - and compare the next ones. */ } + else + /* Comparing canonicalised RRs, byte-at-a-time. */ + while (1) + { + int ok1, ok2; + + ok1 = get_rdata(header, plen, &state1); + ok2 = get_rdata(header, plen, &state2); + + if (!ok1 && !ok2) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; + break; + } + else if (ok1 && (!ok2 || *state1.op > *state2.op)) + { + unsigned char *tmp = rrset[i+1]; + rrset[i+1] = rrset[i]; + rrset[i] = tmp; + swap = 1; + break; + } + else if (ok2 && (!ok1 || *state2.op > *state1.op)) + break; + + /* arrive here when bytes are equal, go round the loop again + and compare the next ones. */ + } } } while (swap); @@ -507,7 +526,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in struct crec *crecp = NULL; u16 *rr_desc = rrfilter_desc(type); u32 sig_expiration, sig_inception; - + int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP; + unsigned long curtime = time(0); int time_check = is_check_date(curtime); @@ -530,6 +550,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in void *ctx; char *name_start; u32 nsigttl, ttl, orig_ttl; + + failflags &= ~DNSSEC_FAIL_NOSIG; p = sigs[j]; GETLONG(ttl, p); @@ -547,12 +569,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in if (!extract_name(header, plen, &p, keyname, 1, 0)) return STAT_BOGUS; - if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) || - labels > name_labels || - !(hash = hash_find(algo_digest_name(algo))) || + if (!time_check) + failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP); + else + { + /* We must explicitly check against wanted values, because of SERIAL_UNDEF */ + if (serial_compare_32(curtime, sig_inception) == SERIAL_LT) + continue; + else + failflags &= ~DNSSEC_FAIL_NYV; + + if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) + continue; + else + failflags &= ~DNSSEC_FAIL_EXP; + } + + if (!(hash = hash_find(algo_digest_name(algo)))) + continue; + else + failflags &= ~DNSSEC_FAIL_NOKEYSUP; + + if (labels > name_labels || !hash_init(hash, &ctx, &digest)) continue; - + /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY))) return STAT_NEED_KEY; @@ -703,7 +744,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in } } - return STAT_BOGUS; + /* If we reach this point, no verifying key was found */ + return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY; } @@ -724,17 +766,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch unsigned long ttl, sig_ttl; struct blockdata *key; union all_addr a; + int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY; if (ntohs(header->qdcount) != 1 || RCODE(header) == SERVFAIL || RCODE(header) == REFUSED || !extract_name(header, plen, &p, name, 1, 4)) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; GETSHORT(qtype, p); GETSHORT(qclass, p); if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; /* See if we have cached a DS record which validates this key */ if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) @@ -768,14 +811,17 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch GETSHORT(flags, p); if (*p++ != 3) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NOKEY; algo = *p++; keytag = dnskey_keytag(algo, flags, p, rdlen - 4); key = NULL; /* key must have zone key flag set */ if (flags & 0x100) - key = blockdata_alloc((char*)p, rdlen - 4); + { + key = blockdata_alloc((char*)p, rdlen - 4); + failflags &= ~DNSSEC_FAIL_NOZONE; + } p = psave; @@ -796,15 +842,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch unsigned char *digest, *ds_digest; const struct nettle_hash *hash; int sigcnt, rrcnt; - + int wire_len; + if (recp1->addr.ds.algo == algo && recp1->addr.ds.keytag == keytag && - recp1->uid == (unsigned int)class && - (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) && - hash_init(hash, &ctx, &digest)) - + recp1->uid == (unsigned int)class) { - int wire_len = to_wire(name); + failflags &= ~DNSSEC_FAIL_NOKEY; + + if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) + continue; + else + failflags &= ~DNSSEC_FAIL_NODSSUP; + + if (!hash_init(hash, &ctx, &digest)) + continue; + + wire_len = to_wire(name); /* Note that digest may be different between DSs, so we can't move this outside the loop. */ @@ -819,12 +873,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) && - sigcnt != 0 && rrcnt != 0 && - validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, - NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE) + rrcnt != 0) { - valid = 1; - break; + if (sigcnt == 0) + continue; + else + failflags &= ~DNSSEC_FAIL_NOSIG; + + rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, + NULL, key, rdlen - 4, algo, keytag, &sig_ttl); + + failflags &= rc; + + if (STAT_ISEQUAL(rc, STAT_SECURE)) + { + valid = 1; + break; + } } } } @@ -909,7 +974,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch } log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); - return STAT_BOGUS; + return STAT_BOGUS | failflags; } /* The DNS packet is expected to contain the answer to a DS query @@ -944,10 +1009,11 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char else rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); - if (rc == STAT_INSECURE) + if (STAT_ISEQUAL(rc, STAT_INSECURE)) { my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name); - rc = STAT_BOGUS; + log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure"); + return STAT_BOGUS | DNSSEC_FAIL_INDET; } p = (unsigned char *)(header+1); @@ -957,13 +1023,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char /* If the key needed to validate the DS is on the same domain as the DS, we'll loop getting nowhere. Stop that now. This can happen of the DS answer comes from the DS's zone, and not the parent zone. */ - if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname))) + if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname)) { log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); return STAT_BOGUS; } - if (rc != STAT_SECURE) + if (!STAT_ISEQUAL(rc, STAT_SECURE)) return rc; if (!neganswer) @@ -1429,7 +1495,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns if (!(p = skip_name(nsecs[i], header, plen, 15))) return 0; /* bad packet */ - p += 10; /* type, class, TTL, rdlen */ + p += 10; /* type, class, TTL, rdlen */ algo = *p++; if ((hash = hash_find(nsec3_digest_name(algo)))) @@ -1827,7 +1893,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch /* Find all the targets we're looking for answers to. The zeroth array element is for the query, subsequent ones - for CNAME targets, unless the query is for a CNAME. */ + for CNAME targets, unless the query is for a CNAME or ANY. */ if (!expand_workspace(&targets, &target_sz, 0)) return STAT_BOGUS; @@ -1846,7 +1912,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (qtype == T_RRSIG) return STAT_INSECURE; - if (qtype != T_CNAME) + if (qtype != T_CNAME && qtype != T_ANY) for (j = ntohs(header->ancount); j != 0; j--) { if (!(p1 = skip_name(p1, header, plen, 10))) @@ -1932,15 +1998,15 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch if (check_unsigned && i < ntohs(header->ancount)) { rc = zone_status(name, class1, keyname, now); - if (rc == STAT_SECURE) - rc = STAT_BOGUS; + if (STAT_ISEQUAL(rc, STAT_SECURE)) + rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG; if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ } else rc = STAT_INSECURE; - if (rc != STAT_INSECURE) + if (!STAT_ISEQUAL(rc, STAT_INSECURE)) return rc; } } @@ -1951,7 +2017,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch strcpy(daemon->workspacename, keyname); rc = zone_status(daemon->workspacename, class1, keyname, now); - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) { if (class) *class = class1; /* Class for NEED_DS or NEED_KEY */ @@ -1959,13 +2025,13 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch } /* Zone is insecure, don't need to validate RRset */ - if (rc == STAT_SECURE) + if (STAT_ISEQUAL(rc, STAT_SECURE)) { unsigned long sig_ttl; rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); - if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) + if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) { if (class) *class = class1; /* Class for DS or DNSKEY */ @@ -1998,21 +2064,21 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch Note that we may not yet have validated the NSEC/NSEC3 RRsets. That's not a problem since if the RRsets later fail we'll return BOGUS then. */ - if (rc == STAT_SECURE_WILDCARD && + if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; rc = STAT_SECURE; } } } - if (rc == STAT_INSECURE) + if (STAT_ISEQUAL(rc, STAT_INSECURE)) secure = STAT_INSECURE; } /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */ - if (secure == STAT_SECURE) + if (STAT_ISEQUAL(secure, STAT_SECURE)) for (j = 0; j <targetidx; j++) if ((p2 = targets[j])) { @@ -2030,16 +2096,16 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch { /* Empty DS without NSECS */ if (qtype == T_DS) - return STAT_BOGUS; + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; - if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) + if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) { if (class) *class = qclass; /* Class for NEED_DS or NEED_KEY */ return rc; } - return STAT_BOGUS; /* signed zone, no NSECs */ + return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */ } } @@ -2103,4 +2169,31 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char return ret; } +int errflags_to_ede(int status) +{ + /* We can end up with more than one flag set for some errors, + so this encodes a rough priority so the (eg) No sig is reported + before no-unexpired-sig. */ + + if (status & DNSSEC_FAIL_NYV) + return EDE_SIG_NYV; + else if (status & DNSSEC_FAIL_EXP) + return EDE_SIG_EXP; + else if (status & DNSSEC_FAIL_NOKEYSUP) + return EDE_USUPDNSKEY; + else if (status & DNSSEC_FAIL_NOZONE) + return EDE_NO_ZONEKEY; + else if (status & DNSSEC_FAIL_NOKEY) + return EDE_NO_DNSKEY; + else if (status & DNSSEC_FAIL_NODSSUP) + return EDE_USUPDS; + else if (status & DNSSEC_FAIL_NONSEC) + return EDE_NO_NSEC; + else if (status & DNSSEC_FAIL_INDET) + return EDE_DNSSEC_IND; + else if (status & DNSSEC_FAIL_NOSIG) + return EDE_NO_RRSIG; + else + return EDE_UNSET; +} #endif /* HAVE_DNSSEC */ diff --git a/src/domain-match.c b/src/domain-match.c new file mode 100644 index 0000000..f8e4796 --- /dev/null +++ b/src/domain-match.c @@ -0,0 +1,698 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +static int order(char *qdomain, 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); + +/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */ +#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS) + +void build_server_array(void) +{ + struct server *serv; + int count = 0; + + for (serv = daemon->servers; serv; serv = serv->next) +#ifdef HAVE_LOOP + if (!(serv->flags & SERV_LOOP)) +#endif + { + count++; + if (serv->flags & SERV_WILDCARD) + daemon->server_has_wildcard = 1; + } + + for (serv = daemon->local_domains; serv; serv = serv->next) + { + count++; + if (serv->flags & SERV_WILDCARD) + daemon->server_has_wildcard = 1; + } + + daemon->serverarraysz = count; + + if (count > daemon->serverarrayhwm) + { + struct server **new; + + count += 10; /* A few extra without re-allocating. */ + + if ((new = whine_malloc(count * sizeof(struct server *)))) + { + if (daemon->serverarray) + free(daemon->serverarray); + + daemon->serverarray = new; + daemon->serverarrayhwm = count; + } + } + + count = 0; + + for (serv = daemon->servers; serv; serv = serv->next) +#ifdef HAVE_LOOP + if (!(serv->flags & SERV_LOOP)) +#endif + { + daemon->serverarray[count] = serv; + serv->serial = count; + serv->last_server = -1; + count++; + } + + 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_IS_LOCAL)) + 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. + A flag of F_CONFIG returns anything that generates a local + reply of IPv4 or IPV6. + return 0 if nothing found, 1 otherwise. +*/ +int lookup_domain(char *domain, int flags, int *lowout, int *highout) +{ + int rc, crop_query, nodots; + ssize_t qlen; + int try, high, low = 0; + int nlow = 0, nhigh = 0; + char *cp, *qdomain = domain; + + /* may be no configured servers. */ + if (daemon->serverarraysz == 0) + return 0; + + /* 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; + + /* Search shorter and shorter RHS substrings for a match */ + while (qlen >= 0) + { + /* Note that when we chop off a label, 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 to zero here, we can go further below and crop the + search string to the size of the largest remaining server + when this match fails. */ + high = daemon->serverarraysz; + crop_query = 1; + + /* binary search */ + while (1) + { + try = (low + high)/2; + + if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0) + break; + + if (rc < 0) + { + if (high == try) + { + /* qdomain is longer or same length as longest domain, and try == 0 + crop the query to the longest domain. */ + crop_query = qlen - daemon->serverarray[try]->domain_len; + break; + } + high = try; + } + else + { + if (low == try) + { + /* try now points to the last domain that sorts before the query, so + we know that a substring of the query shorter than it is required to match, so + find the largest domain that's shorter than try. Note that just going to + try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point + to aaa, since ccc sorts after bbb, but the first domain that has a chance to + match is bb. So find the length of the first domain later than try which is + is shorter than it. + There's a nasty edge case when qdomain sorts before _any_ of the + server domains, where try _doesn't point_ to the last domain that sorts + before the query, since no such domain exists. In that case, the loop + exits via the rc < 0 && high == try path above and this code is + not executed. */ + ssize_t len, old = daemon->serverarray[try]->domain_len; + while (++try != daemon->serverarraysz) + { + if (old != (len = daemon->serverarray[try]->domain_len)) + { + crop_query = qlen - len; + break; + } + } + break; + } + low = try; + } + }; + + if (rc == 0) + { + int found = 1; + + if (daemon->server_has_wildcard) + { + /* if we have example.com and *example.com we need to check against *example.com, + but the binary search may have found either. Use the fact that example.com is sorted before *example.com + We favour example.com in the case that both match (ie www.example.com) */ + while (try != 0 && order(qdomain, qlen, daemon->serverarray[try-1]) == 0) + try--; + + if (!(qdomain == domain || *qdomain == 0 || *(qdomain-1) == '.')) + { + while (try < daemon->serverarraysz-1 && order(qdomain, qlen, daemon->serverarray[try+1]) == 0) + try++; + + if (!(daemon->serverarray[try]->flags & SERV_WILDCARD)) + found = 0; + } + } + + if (found) + { + /* We've matched a setting which says to use servers without a domain. + Continue the search with empty query */ + if (daemon->serverarray[try]->flags & SERV_USE_RESOLV) + crop_query = qlen; + else if (filter_servers(try, flags, &nlow, &nhigh)) + /* 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 */ + break; + } + } + + /* crop_query must be at least one always. */ + if (crop_query == 0) + crop_query = 1; + + /* strip chars off the query based on the largest possible remaining match, + then continue to the start of the next label unless we have a wildcard + domain somewhere, in which case we have to go one at a time. */ + qlen -= crop_query; + qdomain += crop_query; + if (!daemon->server_has_wildcard) + while (qlen > 0 && (*(qdomain-1) != '.')) + 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 || daemon->serverarray[nlow]->domain_len == 0)) + filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh); + + if (lowout) + *lowout = nlow; + + if (highout) + *highout = nhigh; + + if (nlow == nhigh) + return 0; + + return 1; +} + +/* Return first server in group of equivalent servers; this is the "master" record. */ +int server_samegroup(struct server *a, struct server *b) +{ + return order_servers(a, b) == 0; +} + +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++; + +#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS) + + if (flags & F_CONFIG) + { + /* We're just lookin for any matches that return an RR. */ + for (i = nlow; i < nhigh; i++) + if (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS) + break; + + /* failed, return failure. */ + if (i == nhigh) + nhigh = nlow; + } + else + { + /* Now the servers are on order between low and high, in the order + IPv6 addr, IPv4 addr, return zero for both, send upstream, no-data return. + + 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; + + /* now look for a server */ + for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++); + + if (i != nlow) + { + /* If we want a server that can do DNSSEC, and this one can't, + return nothing, similarly if were looking only for a server + for a particular domain. */ + if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC)) + nlow = nhigh; + else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0) + nlow = nhigh; + else + nhigh = i; + } + else + { + /* --local=/domain/, only return if we don't need a server. */ + if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER)) + nhigh = i; + } + } + } + } + } + + *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 + { + /* argument first is the first struct server which matches the query type; + now roll back to the server which is just the same domain, to check if that + provides an answer of a different type. */ + + for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--); + + if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) || + check_for_local_domain(name, now)) + rc = F_NOERR; + else + rc = F_NXDOMAIN; + } + } + + return rc; +} + +size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede) +{ + 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, ede); + + 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, limit, &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, limit, &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; +} + +#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, size_t qlen, struct server *serv) +{ + size_t dlen = 0; + + /* servers for dotless names always sort last + searched for name is never dotless. */ + if (serv->flags & SERV_FOR_NODOTS) + return -1; + + dlen = serv->domain_len; + + if (qlen < dlen) + return 1; + + if (qlen > dlen) + return -1; + + return strcmp(qdomain, serv->domain); +} + +static int order_servers(struct server *s1, struct server *s2) +{ + int rc; + + /* 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; + + if ((rc = order(s1->domain, s1->domain_len, s2)) != 0) + return rc; + + /* For identical domains, sort wildcard ones first */ + if (s1->flags & SERV_WILDCARD) + return (s2->flags & SERV_WILDCARD) ? 0 : 1; + + return (s2->flags & SERV_WILDCARD) ? -1 : 0; +} + +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. We flip the SERV_LITERAL_ADDRESS bit + so the order is IPv6 literal, IPv4 literal, all-zero literal, + upstream server, NXDOMAIN literal. */ + if (rc == 0) + rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) - + ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS); + + /* 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; +} + +void mark_servers(int flag) +{ + struct server *serv; + + /* mark everything with argument flag */ + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->flags & flag) + serv->flags |= SERV_MARK; + else + serv->flags &= ~SERV_MARK; + + for (serv = daemon->local_domains; serv; serv = serv->next) + if (serv->flags & flag) + serv->flags |= SERV_MARK; + else + serv->flags &= ~SERV_MARK; +} + +void cleanup_servers(void) +{ + struct server *serv, *tmp, **up; + + /* unlink and free anything still marked. */ + for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) + { + tmp = serv->next; + if (serv->flags & SERV_MARK) + { + server_gone(serv); + *up = serv->next; + 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; + } +} + +int add_update_server(int flags, + union mysockaddr *addr, + union mysockaddr *source_addr, + const char *interface, + const char *domain, + union all_addr *local_addr) +{ + struct server *serv = NULL; + char *alloc_domain; + + if (!domain) + domain = ""; + + /* .domain == domain, for historical reasons. */ + if (*domain == '.') + while (*domain == '.') domain++; + else if (*domain == '*') + { + domain++; + if (*domain != 0) + flags |= SERV_WILDCARD; + } + + if (*domain == 0) + alloc_domain = whine_malloc(1); + else if (!(alloc_domain = canonicalise((char *)domain, NULL))) + return 0; + + /* See if there is a suitable candidate, and unmark + only do this for forwarding servers, not + address or local, to avoid delays on large numbers. */ + if (flags & SERV_IS_LOCAL) + for (serv = daemon->servers; serv; serv = serv->next) + if ((serv->flags & SERV_MARK) && + hostname_isequal(alloc_domain, serv->domain)) + break; + + if (serv) + { + free(alloc_domain); + alloc_domain = serv->domain; + } + else + { + size_t size; + + if (flags & SERV_LITERAL_ADDRESS) + { + if (flags & SERV_6ADDR) + size = sizeof(struct serv_addr6); + else if (flags & SERV_4ADDR) + size = sizeof(struct serv_addr4); + else + size = sizeof(struct serv_local); + } + else + size = sizeof(struct server); + + if (!(serv = whine_malloc(size))) + return 0; + + if (flags & SERV_IS_LOCAL) + { + serv->next = daemon->local_domains; + daemon->local_domains = serv; + } + else + { + 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 (!(flags & SERV_IS_LOCAL)) + memset(serv, 0, sizeof(struct server)); + + serv->flags = flags; + serv->domain = alloc_domain; + serv->domain_len = strlen(alloc_domain); + + if (flags & SERV_4ADDR) + ((struct serv_addr4*)serv)->addr = local_addr->addr4; + + if (flags & SERV_6ADDR) + ((struct serv_addr6*)serv)->addr = local_addr->addr6; + + if (!(flags & SERV_IS_LOCAL)) + { +#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; + } + + return 1; +} + diff --git a/src/domain.c b/src/domain.c index 3dc96ab..91e0f22 100644 --- a/src/domain.c +++ b/src/domain.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -18,8 +18,9 @@ static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c); +static int match_domain(struct in_addr addr, struct cond_domain *c); static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c); - +static int match_domain6(struct in6_addr *addr, struct cond_domain *c); int is_name_synthetic(int flags, char *name, union all_addr *addr) { @@ -135,28 +136,9 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr) } if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr)) - { - if (prot == AF_INET) - { - if (!c->is6 && - ntohl(addr->addr4.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr->addr4.s_addr) <= ntohl(c->end.s_addr)) - found = 1; - } - else - { - u64 addrpart = addr6part(&addr->addr6); - - if (c->is6 && - is_same_net6(&addr->addr6, &c->start6, 64) && - addrpart >= addr6part(&c->start6) && - addrpart <= addr6part(&c->end6)) - found = 1; - } - } - + found = (prot == AF_INET) ? match_domain(addr->addr4, c) : match_domain6(&addr->addr6, c); } - + /* restore name */ for (p = tail; *p; p++) if (*p == '.' || *p == ':') @@ -246,14 +228,22 @@ int is_rev_synth(int flag, union all_addr *addr, char *name) } +static int match_domain(struct in_addr addr, struct cond_domain *c) +{ + if (!c->is6 && + ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && + ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) + return 1; + + return 0; +} + static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c) { for (; c; c = c->next) - if (!c->is6 && - ntohl(addr.s_addr) >= ntohl(c->start.s_addr) && - ntohl(addr.s_addr) <= ntohl(c->end.s_addr)) + if (match_domain(addr, c)) return c; - + return NULL; } @@ -267,16 +257,30 @@ char *get_domain(struct in_addr addr) return daemon->domain_suffix; } - -static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c) +static int match_domain6(struct in6_addr *addr, struct cond_domain *c) { u64 addrpart = addr6part(addr); + if (c->is6) + { + if (c->prefixlen >= 64) + { + if (is_same_net6(addr, &c->start6, 64) && + addrpart >= addr6part(&c->start6) && + addrpart <= addr6part(&c->end6)) + return 1; + } + else if (is_same_net6(addr, &c->start6, c->prefixlen)) + return 1; + } + + return 0; +} + +static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c) +{ for (; c; c = c->next) - if (c->is6 && - is_same_net6(addr, &c->start6, 64) && - addrpart >= addr6part(&c->start6) && - addrpart <= addr6part(&c->end6)) + if (match_domain6(addr, c)) return c; return NULL; @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/edns0.c b/src/edns0.c index 53cfe24..7bd26b8 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -427,6 +427,66 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe return 1; } +/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for + * detailed information on packet formating. + */ +#define UMBRELLA_VERSION 1 +#define UMBRELLA_TYPESZ 2 + +#define UMBRELLA_ASSET 0x0004 +#define UMBRELLA_ASSETSZ sizeof(daemon->umbrella_asset) +#define UMBRELLA_ORG 0x0008 +#define UMBRELLA_ORGSZ sizeof(daemon->umbrella_org) +#define UMBRELLA_IPV4 0x0010 +#define UMBRELLA_IPV6 0x0020 +#define UMBRELLA_DEVICE 0x0040 +#define UMBRELLA_DEVICESZ sizeof(daemon->umbrella_device) + +struct umbrella_opt { + u8 magic[4]; + u8 version; + u8 flags; + /* We have 4 possible fields since we'll never send both IPv4 and + * IPv6, so using the larger of the two to calculate max buffer size. + * Each field also has a type header. So the following accounts for + * the type headers and each field size to get a max buffer size. + */ + u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ]; +}; + +static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable) +{ + *cacheable = 0; + + struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {}}; + u8 *u = &opt.fields[0]; + + if (daemon->umbrella_org) { + PUTSHORT(UMBRELLA_ORG, u); + PUTLONG(daemon->umbrella_org, u); + } + + int family = source->sa.sa_family; + PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u); + int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ; + memcpy(u, get_addrp(source, family), size); + u += size; + + if (option_bool(OPT_UMBRELLA_DEVID)) { + PUTSHORT(UMBRELLA_DEVICE, u); + memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ); + u += UMBRELLA_DEVICESZ; + } + + if (daemon->umbrella_asset) { + PUTSHORT(UMBRELLA_ASSET, u); + PUTLONG(daemon->umbrella_asset, u); + } + + int len = u - &opt.magic[0]; + return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, len, 0, 1); +} + /* Set *check_subnet if we add a client subnet option, which needs to checked in the reply. Set *cacheable to zero if we add an option which the answer may depend on. */ @@ -445,6 +505,9 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l if (daemon->dns_client_id) plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); + + if (option_bool(OPT_UMBRELLA)) + plen = add_umbrella_opt(header, plen, limit, source, cacheable); if (option_bool(OPT_CLIENT_SUBNET)) { diff --git a/src/forward.c b/src/forward.c index 7a95ddf..3d638e4 100644 --- a/src/forward.c +++ b/src/forward.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -16,14 +16,15 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); -static struct frec *lookup_frec_by_sender(unsigned short id, - union mysockaddr *addr, - void *hash); -static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); +static struct frec *get_new_frec(time_t now, struct server *serv, int force); +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, unsigned int flagmask); static unsigned short get_id(void); static void free_frec(struct frec *f); +static void query_full(time_t now, char *domain); + +static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status); /* Send a UDP packet with its source address set as "source" unless nowild is true, when we just send it with the kernel default */ @@ -108,163 +109,75 @@ 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) - +#ifdef HAVE_CONNTRACK +static void set_outgoing_mark(struct frec *forward, int fd) +{ + /* Copy connection mark of incoming query to outgoing connection. */ + unsigned int mark; + if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); +} +#endif + +static void log_query_mysockaddr(unsigned int flags, char *name, union mysockaddr *addr, char *arg) +{ + if (addr->sa.sa_family == AF_INET) + log_query(flags | F_IPV4, name, (union all_addr *)&addr->in.sin_addr, arg); + else + log_query(flags | F_IPV6, name, (union all_addr *)&addr->in6.sin6_addr, arg); +} + +static void server_send(struct server *server, int fd, + const void *header, size_t plen, int flags) +{ + while (retry_send(sendto(fd, header, plen, flags, + &server->addr.sa, + sa_len(&server->addr)))); +} + +#ifdef HAVE_DNSSEC +static void server_send_log(struct server *server, int fd, + const void *header, size_t plen, int dumpflags, + unsigned int logflags, char *name, char *arg) +{ +#ifdef HAVE_DUMPFILE + dump_packet(dumpflags, (void *)header, (size_t)plen, NULL, &server->addr); +#endif + log_query_mysockaddr(logflags, name, &server->addr, arg); + server_send(server, fd, header, plen, 0); +} +#endif + +static int domain_no_rebind(char *domain) { - /* 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; + int dlen = (int)strlen(domain); - 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; + for (serv = daemon->no_rebind; serv; serv = serv->next) + if (dlen >= serv->domain_len && strcmp(serv->domain, &domain[dlen - serv->domain_len]) == 0) + return 1; - 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; + return 0; } 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; + int ede = EDE_UNSET; (void)do_bit; if (header->hb4 & HB4_CD) @@ -278,85 +191,32 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, fwd_flags |= FREC_DO_QUESTION; #endif - /* may be no servers available. */ - if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) + /* Check for retry on existing query. + FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below + ensures that no frec created for internal DNSSEC query can be returned here. + + Similarly FREC_NO_CACHE is never set in flags, so a query which is + contigent on a particular source address EDNS0 option will never be matched. */ + if (forward) + old_src = 1; + else if ((forward = lookup_frec_by_query(hash, fwd_flags, + FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION | + FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE))) { - /* 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; + struct frec_src *src; -#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) + for (src = &forward->frec_src; src; src = src->next) + if (src->orig_id == ntohs(header->id) && + sockaddr_isequal(&src->source, udpaddr)) + break; + + if (src) + old_src = 1; + else { - int fd, 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); + /* Existing query, but from new source, just add this + client to the list that will get the reply.*/ - if (forward->sentto->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); - - - if (forward->sentto->sfd) - fd = forward->sentto->sfd->fd; - else - { - if (forward->sentto->addr.sa.sa_family == AF_INET6) - fd = forward->rfd6->fd; - else - fd = forward->rfd4->fd; - } - - while (retry_send(sendto(fd, (char *)header, plen, 0, - &forward->sentto->addr.sa, - sa_len(&forward->sentto->addr)))); - - return 1; - } -#endif - - /* retry on existing query, send to all available servers */ - domain = forward->sentto->domain; - forward->sentto->failed_queries++; - if (!option_bool(OPT_ORDER)) - { - forward->forwardall = 1; - daemon->last_server = NULL; - } - type = forward->sentto->flags & SERV_TYPE; -#ifdef HAVE_DNSSEC - do_dnssec = forward->sentto->flags & SERV_DO_DNSSEC; -#endif - - if (!(start = forward->sentto->next)) - start = daemon->servers; /* at end of list, recycle */ - header->id = htons(forward->new_id); - } - else - { - /* Query from new source, but the same query may be in progress - from another source. If so, just add this client to the - list that will get the reply.*/ - - if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && - (forward = lookup_frec_by_query(hash, fwd_flags))) - { /* Note whine_malloc() zeros memory. */ if (!daemon->free_frec_src && daemon->frec_src_count < daemon->ftabsize && @@ -366,269 +226,345 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, daemon->free_frec_src->next = NULL; } - /* If we've been spammed with many duplicates, just drop the query. */ - if (daemon->free_frec_src) + /* If we've been spammed with many duplicates, return REFUSED. */ + if (!daemon->free_frec_src) { - struct frec_src *new = daemon->free_frec_src; - daemon->free_frec_src = new->next; - new->next = forward->frec_src.next; - forward->frec_src.next = new; - new->orig_id = ntohs(header->id); - new->source = *udpaddr; - new->dest = *dst_addr; - new->log_id = daemon->log_id; - new->iface = dst_iface; + query_full(now, NULL); + goto reply; } - return 1; + src = daemon->free_frec_src; + daemon->free_frec_src = src->next; + src->next = forward->frec_src.next; + forward->frec_src.next = src; + src->orig_id = ntohs(header->id); + src->source = *udpaddr; + src->dest = *dst_addr; + src->log_id = daemon->log_id; + src->iface = dst_iface; + src->fd = udpfd; + + /* closely spaced identical queries cannot be a try and a retry, so + it's safe to wait for the reply from the first without + forwarding the second. */ + if (difftime(now, forward->time) < 2) + return 0; } - - if (gotname) - flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + } + + /* new query */ + if (!forward) + { + if (lookup_domain(daemon->namebuff, gotname, &first, &last)) + flags = is_local_answer(now, first, daemon->namebuff); + else + { + /* no available server. */ + ede = EDE_NOT_READY; + flags = 0; + } + + /* don't forward A or AAAA queries for simple names, except the empty name */ + if (!flags && + option_bool(OPT_NODOTS_LOCAL) && + (gotname & (F_IPV4 | F_IPV6)) && + !strchr(daemon->namebuff, '.') && + strlen(daemon->namebuff) != 0) + flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN; -#ifdef HAVE_DNSSEC - do_dnssec = type & SERV_DO_DNSSEC; -#endif - type &= ~SERV_DO_DNSSEC; + /* Configured answer. */ + if (flags || ede == EDE_NOT_READY) + goto reply; - if (daemon->servers && !flags) - forward = get_new_frec(now, NULL, NULL); + master = daemon->serverarray[first]; + + if (!(forward = get_new_frec(now, master, 0))) + goto reply; /* 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 (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)) { - 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->new_id = get_id(); - forward->fd = udpfd; - 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; + 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 - forward->work_counter = DNSSEC_WORK; - if (do_bit) - forward->flags |= FREC_DO_QUESTION; + /* 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 is_sign; + unsigned char *pheader; + + while (forward->blocking_query) + forward = forward->blocking_query; + + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + plen = forward->stash_len; + /* get query for logging. */ + extract_request(header, plen, daemon->namebuff, NULL); + + if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) + PUTSHORT(SAFE_PKTSZ, pheader); + + /* Find suitable servers: should never fail. */ + if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last)) + return 0; + + is_dnssec = 1; + forward->forwardall = 1; + } + else #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) + /* Forward to all available servers on retry of query from same host. */ + if (!option_bool(OPT_ORDER) && old_src) + forward->forwardall = 1; + else { + start = forward->sentto->arrayposn; + 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; + /* 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; } - } - else - { - start = daemon->servers; - if (!option_bool(OPT_ORDER)) - forward->forwardall = 1; - } + } } + + /* 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; } - /* 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) + /* If a query is retried, use the log_id for the retry when logging the answer. */ + forward->frec_src.log_id = daemon->log_id; + + /* We may be resending a DNSSEC query here, for which the below processing is not necessary. */ + if (!is_dnssec) { - struct server *firstsentto = start; - int subnet, cacheable, forwarded = 0; - size_t edns0_len; - unsigned char *pheader; - - /* If a query is retried, use the log_id for the retry when logging the answer. */ - forward->frec_src.log_id = daemon->log_id; + 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); } - - while (1) - { - /* only send to servers dealing with our domain. - domain may be NULL, in which case server->domain - must be NULL also. */ - - if (type == (start->flags & SERV_TYPE) && - (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && - !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) - { - int fd; + } + + if (forward->forwardall) + start = first; - /* find server socket to use, may need to get random one. */ - if (start->sfd) - fd = start->sfd->fd; - else - { - if (start->addr.sa.sa_family == AF_INET6) - { - if (!forward->rfd6 && - !(forward->rfd6 = allocate_rfd(AF_INET6))) - break; - daemon->rfd_save = forward->rfd6; - fd = forward->rfd6->fd; - } - else - { - if (!forward->rfd4 && - !(forward->rfd4 = allocate_rfd(AF_INET))) - break; - daemon->rfd_save = forward->rfd4; - fd = forward->rfd4->fd; - } + 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]; + + if ((fd = allocate_rfd(&forward->rfds, srv)) != -1) + { + #ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - { - unsigned int mark; - if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) - setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); - } + /* 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(start->edns_pktsz, pheader); - } + 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, - &start->addr.sa, - sa_len(&start->addr)))) - continue; - - if (errno == 0) - { + + 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, &start->addr); + dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &srv->addr); #endif - - /* Keep info in case we want to re-send this packet */ - daemon->srv_save = start; - daemon->packet_len = plen; - + + /* 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))) + { if (!gotname) strcpy(daemon->namebuff, "query"); - if (start->addr.sa.sa_family == AF_INET) - log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (union all_addr *)&start->addr.in.sin_addr, NULL); - else - log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (union all_addr *)&start->addr.in6.sin6_addr, NULL); - start->queries++; - forwarded = 1; - forward->sentto = start; - if (!forward->forwardall) - break; - forward->forwardall++; + log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, + &srv->addr, NULL); } - } - - if (!(start = start->next)) - start = daemon->servers; - - if (start == firstsentto) - break; +#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 (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 */ + 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 */ + ede = EDE_NETERR; + + 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, limit, first, last, ede))) + return 0; + if (oph) - plen = add_pseudoheader(header, plen, ((unsigned char *) header) + PACKETSZ, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + { + u16 swap = htons((u16)ede); + + if (ede != EDE_UNSET) + plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + else + plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + } + +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) + if (option_bool(OPT_CMARK_ALST_EN)) + { + unsigned int mark; + int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark); + if (have_mark && ((u32)mark & daemon->allowlist_mask)) + report_addresses(header, plen, mark); + } +#endif + send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface); } - + return 0; } static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader, - int check_subnet, union mysockaddr *query_source) + int check_subnet, union mysockaddr *query_source, unsigned char *limit, int ede) { unsigned char *pheader, *sizep; char **sets = 0; int munged = 0, is_sign; unsigned int rcode = RCODE(header); size_t plen; - + (void)ad_reqd; (void)do_bit; (void)bogusanswer; @@ -676,11 +612,10 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } else { - unsigned short udpsz; - /* If upstream is advertising a larger UDP packet size than we allow, trim it so that we don't get overlarge requests for the client. We can't do this for signed packets. */ + unsigned short udpsz; GETSHORT(udpsz, sizep); if (udpsz > daemon->edns_pktsz) { @@ -707,7 +642,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); @@ -715,6 +652,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server { union all_addr a; a.log.rcode = rcode; + a.log.ede = ede; log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL); return resize_packet(header, n, pheader, plen); @@ -731,28 +669,32 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } if (daemon->bogus_addr && rcode != NXDOMAIN && - check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) + check_for_bogus_wildcard(header, n, daemon->namebuff, now)) { munged = 1; SET_RCODE(header, NXDOMAIN); header->hb3 &= ~HB3_AA; cache_secure = 0; + ede = EDE_BLOCKED; } else { int doctored = 0; if (rcode == NXDOMAIN && - extract_request(header, n, daemon->namebuff, NULL) && - check_for_local_domain(daemon->namebuff, now)) + extract_request(header, n, daemon->namebuff, NULL)) { - /* if we forwarded a query for a locally known name (because it was for - an unknown type) and the answer is NXDOMAIN, convert that to NODATA, - since we know that the domain exists, even if upstream doesn't */ - munged = 1; - header->hb3 |= HB3_AA; - SET_RCODE(header, NOERROR); - cache_secure = 0; + if (check_for_local_domain(daemon->namebuff, now) || + lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)) + { + /* if we forwarded a query for a locally known name (because it was for + an unknown type) and the answer is NXDOMAIN, convert that to NODATA, + since we know that the domain exists, even if upstream doesn't */ + munged = 1; + header->hb3 |= HB3_AA; + SET_RCODE(header, NOERROR); + cache_secure = 0; + } } if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) @@ -760,6 +702,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); munged = 1; cache_secure = 0; + ede = EDE_BLOCKED; } if (doctored) @@ -789,7 +732,6 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ - if (munged) { header->ancount = htons(0); @@ -801,11 +743,189 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide sections of the packet. Find the new length here and put back pseudoheader if it was removed. */ - return resize_packet(header, n, pheader, plen); + n = resize_packet(header, n, pheader, plen); + + if (pheader && ede != EDE_UNSET) + { + u16 swap = htons((u16)ede); + n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1); + } + + return n; +} + +#ifdef HAVE_DNSSEC +static void dnssec_validate(struct frec *forward, struct dns_header *header, + ssize_t plen, int status, time_t now) +{ + daemon->log_display_id = forward->frec_src.log_id; + + /* 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; + + /* If all replies to a query are REFUSED, give up. */ + if (RCODE(header) == REFUSED) + status = STAT_ABANDONED; + + /* 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 (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(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 (STAT_ISEQUAL(status, STAT_BOGUS)) + dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS, + header, (size_t)plen, &forward->sentto->addr, NULL); +#endif + } + + /* Can't validate, as we're missing key data. Put this + answer aside, whilst we get that. */ + if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY)) + { + struct frec *new = NULL; + int serverind; + struct blockdata *stash; + + /* Now save reply pending receipt of key data */ + if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 && + (stash = blockdata_alloc((char *)header, plen))) + { + struct server *server = daemon->serverarray[serverind]; + struct frec *orig; + unsigned int flags; + void *hash; + size_t nn; + + /* validate routines leave name of required record in daemon->keyname */ + nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, + STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz); + + flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY; + hash = hash_questions(header, nn, daemon->namebuff); + + if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY))) + { + forward->next_dependent = new->dependent; + new->dependent = forward; + /* Make consistent, only replace query copy with unvalidated answer + when we set ->blocking_query. */ + if (forward->stash) + blockdata_free(forward->stash); + forward->blocking_query = new; + forward->stash_len = plen; + forward->stash = stash; + return; + } + + /* 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: third arg of get_new_frec() does that. */ + if (--orig->work_counter == 0 || !(new = get_new_frec(now, server, 1))) + blockdata_free(stash); /* don't leak this on failure. */ + else + { + int fd; + struct frec *next = new->next; + + *new = *forward; /* copy everything, then overwrite */ + new->next = next; + new->blocking_query = NULL; + + new->frec_src.log_id = daemon->log_display_id = ++daemon->log_id; + new->sentto = server; + new->rfds = NULL; + new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); + new->flags |= flags; + new->forwardall = 0; + + forward->next_dependent = NULL; + new->dependent = forward; /* to find query awaiting new one. */ + + /* Make consistent, only replace query copy with unvalidated answer + when we set ->blocking_query. */ + forward->blocking_query = new; + if (forward->stash) + blockdata_free(forward->stash); + forward->stash_len = plen; + forward->stash = stash; + + memcpy(new->hash, hash, 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", STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS)); + server->queries++; + } + + return; + } + } + + /* sending DNSSEC query failed. */ + status = STAT_ABANDONED; + } + + /* Validated original answer, all done. */ + if (!forward->dependent) + return_reply(now, forward, header, plen, status); + else + { + /* validated subsidiary query/queries, (and cached result) + pop that and return to the previous query/queries we were working on. */ + struct frec *prev, *nxt = forward->dependent; + + free_frec(forward); + + while ((prev = nxt)) + { + /* ->next_dependent will have changed after return from recursive call below. */ + nxt = prev->next_dependent; + prev->blocking_query = NULL; /* already gone */ + blockdata_retrieve(prev->stash, prev->stash_len, (void *)header); + dnssec_validate(prev, header, prev->stash_len, status, now); + } + } } +#endif /* sets new last_server */ -void reply_query(int fd, int family, time_t now) +void reply_query(int fd, time_t now) { /* packet from peer server, extract data for cache, and send to original requester */ @@ -814,40 +934,48 @@ void reply_query(int fd, int family, time_t now) struct frec *forward; socklen_t addrlen = sizeof(serveraddr); ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen); - size_t nn; struct server *server; void *hash; - + int first, last, c; + /* packet buffer overwritten */ daemon->srv_save = NULL; - + /* Determine the address of the server replying so that we can mark that as good */ - if ((serveraddr.sa.sa_family = family) == AF_INET6) + if (serveraddr.sa.sa_family == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; header = (struct dns_header *)daemon->packet; 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, family, 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); @@ -859,7 +987,7 @@ void reply_query(int fd, int family, time_t now) daemon->log_source_addr = &forward->frec_src.source; if (daemon->ignore_addr && RCODE(header) == NOERROR && - check_for_ignored_address(header, n, daemon->ignore_addr)) + check_for_ignored_address(header, n)) return; /* Note: if we send extra options in the EDNS0 header, we can't recreate @@ -869,453 +997,262 @@ void reply_query(int fd, int family, 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; - + size_t nn = 0; + #ifdef HAVE_DNSSEC - if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) + /* DNSSEC queries have a copy of the original query stashed. + The query MAY have got a good answer, and be awaiting + the results of further queries, in which case + The Stash contains something else and we don't need to retry anyway. */ + if ((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && !forward->blocking_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; - } - - - fd = -1; - - if (start->sfd) - fd = start->sfd->fd; - else - { - if (start->addr.sa.sa_family == AF_INET6) - { - /* may have changed family */ - if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6))) - fd = forward->rfd6->fd; - } - else - { - /* may have changed family */ - if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET))) - fd = forward->rfd4->fd; - } - } - - /* Can't get socket. */ - if (fd == -1) - return; - -#ifdef HAVE_DUMPFILE - dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr); + nn = forward->stash_len; + udp_size = daemon->edns_pktsz; + } + else #endif - - while (retry_send(sendto(fd, (char *)header, plen, 0, - &start->addr.sa, - sa_len(&start->addr)))); + { + /* recreate query from reply */ + if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL))) + GETSHORT(udp_size, udpsz); - if (start->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&start->addr.in.sin_addr, "dnssec"); - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&start->addr.in6.sin6_addr, "dnssec"); + /* 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 */ - return; - } -#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; + 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); - /* recreate query from reply */ - pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign, NULL); - if (!is_sign && server) - { 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; + 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 */ + + /* 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 (forward->sentto->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && + if (server->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); + 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); } - - /* 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) + forward->sentto = server; + +#ifdef HAVE_DNSSEC + if ((forward->sentto->flags & SERV_DO_DNSSEC) && + option_bool(OPT_DNSSEC_VALID) && + !(forward->flags & FREC_CHECKING_DISABLED)) + dnssec_validate(forward, header, n, STAT_OK, now); + else +#endif + return_reply(now, forward, header, n, STAT_OK); +} + +static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status) +{ + int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + size_t nn; + int ede = EDE_UNSET; + + (void)status; + + daemon->log_display_id = forward->frec_src.log_id; + daemon->log_source_addr = &forward->frec_src.source; + + /* 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 (!STAT_ISEQUAL(status, STAT_OK)) { - 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); + /* status is STAT_OK when validation not turned on. */ + no_cache_dnssec = 0; - /* 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)) + if (STAT_ISEQUAL(status, STAT_TRUNCATED)) + header->hb3 |= HB3_TC; + else { - int status = 0; + char *result, *domain = "result"; + union all_addr a; - /* 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 (type == (start->flags & (SERV_TYPE | SERV_DO_DNSSEC)) && - ((type & SERV_TYPE) != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && - !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) - { - 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->rfd4 = NULL; - new->rfd6 = 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); + a.log.ede = ede = errflags_to_ede(status); - if (server->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, daemon->keyname, (union all_addr *)&(server->addr.in.sin_addr), - querystr("dnssec-query", querytype)); - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr), - querystr("dnssec-query", querytype)); - - 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 (server->sfd) - fd = server->sfd->fd; - else - { - fd = -1; - if (server->addr.sa.sa_family == AF_INET6) - { - if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) - fd = new->rfd6->fd; - } - else - { - if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) - fd = new->rfd4->fd; - } - } - - if (fd != -1) - { -#ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - { - unsigned int mark; - if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark)) - setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); - } -#endif - -#ifdef HAVE_DUMPFILE - dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)nn, NULL, &server->addr); -#endif - - while (retry_send(sendto(fd, (char *)header, nn, 0, - &server->addr.sa, - sa_len(&server->addr)))); - 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 + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { - 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); + result = "ABANDONED"; + status = STAT_BOGUS; } + else + result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); - if (status == STAT_SECURE) + if (STAT_ISEQUAL(status, STAT_SECURE)) cache_secure = 1; - else if (status == STAT_BOGUS) + else if (STAT_ISEQUAL(status, STAT_BOGUS)) { no_cache_dnssec = 1; bogusanswer = 1; + + if (extract_request(header, n, daemon->namebuff, NULL)) + domain = daemon->namebuff; } + + log_query(F_SECSTAT, domain, &a, result); } - + } #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 (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, 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, + ((unsigned char *)header) + daemon->edns_pktsz, ede))) + { + struct frec_src *src; - 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 */ + 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)) + /* 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 + +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) + if (option_bool(OPT_CMARK_ALST_EN)) { - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - header->hb3 |= HB3_TC; - nn = resize_packet(header, nn, NULL, 0); + unsigned int mark; + int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark); + if (have_mark && ((u32)mark & daemon->allowlist_mask)) + report_addresses(header, nn, mark); } #endif - - for (src = &forward->frec_src; src; src = src->next) + + 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) { - header->id = htons(src->orig_id); - -#ifdef HAVE_DUMPFILE - dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); -#endif - - send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, - &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"); - } + daemon->log_display_id = src->log_id; + daemon->log_source_addr = &src->source; + log_query(F_UPSTREAM, "query", NULL, "duplicate"); } } - - free_frec(forward); /* cancel */ } + + free_frec(forward); /* cancel */ +} + + +#ifdef HAVE_CONNTRACK +static int is_query_allowed_for_mark(u32 mark, const char *name) +{ + int is_allowable_name, did_validate_name = 0; + struct allowlist *allowlists; + char **patterns_pos; + + for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) + if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask)) + for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++) + { + if (!strcmp(*patterns_pos, "*")) + return 1; + if (!did_validate_name) + { + is_allowable_name = name ? is_valid_dns_name(name) : 0; + did_validate_name = 1; + } + if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos)) + return 1; + } + return 0; } +static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name) +{ + unsigned char *p; + (void)name; + (void)mark; + +#ifdef HAVE_UBUS + if (name) + ubus_event_bcast_connmark_allowlist_refused(mark, name); +#endif + + setup_reply(header, /* flags: */ 0, EDE_BLOCKED); + + if (!(p = skip_questions(header, qlen))) + return 0; + return p - (unsigned char *)header; +} +#endif void receive_query(struct listener *listen, time_t now) { @@ -1328,6 +1265,11 @@ void receive_query(struct listener *listen, time_t now) size_t m; ssize_t n; int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0; +#ifdef HAVE_CONNTRACK + unsigned int mark = 0; + int have_mark = 0; + int is_single_query = 0, allowed = 1; +#endif #ifdef HAVE_AUTH int local_auth = 0; #endif @@ -1353,7 +1295,7 @@ void receive_query(struct listener *listen, time_t now) /* packet buffer overwritten */ daemon->srv_save = NULL; - + dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0; netmask.s_addr = 0; @@ -1556,6 +1498,11 @@ void receive_query(struct listener *listen, time_t now) #ifdef HAVE_DUMPFILE dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL); #endif + +#ifdef HAVE_CONNTRACK + if (option_bool(OPT_CMARK_ALST_EN)) + have_mark = get_incoming_mark(&source_addr, &dst_addr, /* istcp: */ 0, &mark); +#endif if (extract_request(header, (size_t)n, daemon->namebuff, &type)) { @@ -1563,13 +1510,13 @@ void receive_query(struct listener *listen, time_t now) struct auth_zone *zone; #endif char *types = querystr(auth_dns ? "auth" : "query", type); + + log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff, + &source_addr, types); - if (family == AF_INET) - log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (union all_addr *)&source_addr.in.sin_addr, types); - else - log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (union all_addr *)&source_addr.in6.sin6_addr, types); +#ifdef HAVE_CONNTRACK + is_single_query = 1; +#endif #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ @@ -1611,76 +1558,226 @@ void receive_query(struct listener *listen, time_t now) udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ } +#ifdef HAVE_CONNTRACK #ifdef HAVE_AUTH - if (auth_dns) + if (!auth_dns || local_auth) +#endif + if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask)) + allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL); +#endif + + if (0); +#ifdef HAVE_CONNTRACK + else if (!allowed) + { + u16 swap = htons(EDE_BLOCKED); + + m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL); + + if (have_pseudoheader && m != 0) + m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + + if (m >= 1) + { +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr); +#endif + send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), + (char *)header, m, &source_addr, &dst_addr, if_index); + daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; + } + } +#endif +#ifdef HAVE_AUTH + else if (auth_dns) { m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, local_auth, do_bit, have_pseudoheader); if (m >= 1) { +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr); +#endif +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) + if (local_auth) + if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask)) + report_addresses(header, m, mark); +#endif send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, m, &source_addr, &dst_addr, if_index); daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++; } } - else #endif + else { int ad_reqd = do_bit; - /* RFC 6840 5.7 */ + /* RFC 6840 5.7 */ if (header->hb4 & HB4_AD) ad_reqd = 1; - + m = answer_request(header, ((char *) header) + udp_size, (size_t)n, dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader); if (m >= 1) { +#ifdef HAVE_DUMPFILE + dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr); +#endif +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) + if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask)) + report_addresses(header, m, mark); +#endif send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, m, &source_addr, &dst_addr, if_index); 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; - + size_t m; + int log_save; + /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ if (--(*keycount) == 0) new_status = STAT_ABANDONED; - else if (status == STAT_NEED_KEY) + else if (STAT_ISEQUAL(status, STAT_NEED_KEY)) new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class); - else if (status == STAT_NEED_DS) + else if (STAT_ISEQUAL(status, STAT_NEED_DS)) new_status = dnssec_validate_ds(now, header, n, name, keyname, class); else new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), NULL, NULL, NULL); - if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY) + if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY)) break; /* Can't validate because we need a key/DS whose name now in keyname. @@ -1688,9 +1785,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) @@ -1700,112 +1795,26 @@ 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); + STAT_ISEQUAL(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 (type != (server->flags & (SERV_TYPE | SERV_DO_DNSSEC)) || - (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, server->domain)) || - (server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) - 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 - while(retry_send(sendto(server->tcpfd, packet, m + sizeof(u16), - MSG_FASTOPEN, &server->addr.sa, sa_len(&server->addr)))); - - 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_save = daemon->log_display_id; + daemon->log_display_id = ++daemon->log_id; + + log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr, + querystr("dnssec-query", STAT_ISEQUAL(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 (server->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, keyname, (union all_addr *)&(server->addr.in.sin_addr), - querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS)); - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, keyname, (union all_addr *)&(server->addr.in6.sin6_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; - } + daemon->log_display_id = log_save; - if (new_status != STAT_OK) + if (!STAT_ISEQUAL(new_status, STAT_OK)) break; } @@ -1825,7 +1834,10 @@ 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_CONNTRACK + int is_single_query = 0, allowed = 1; +#endif #ifdef HAVE_AUTH int local_auth = 0; #endif @@ -1834,14 +1846,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); @@ -1849,16 +1861,15 @@ 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; #ifdef HAVE_CONNTRACK /* Get connection mark of incoming query to set on outgoing connections. */ - if (option_bool(OPT_CONNTRACK)) + if (option_bool(OPT_CONNTRACK) || option_bool(OPT_CMARK_ALST_EN)) { union all_addr local; @@ -1903,6 +1914,8 @@ unsigned char *tcp_request(int confd, time_t now, while (1) { + int ede = EDE_UNSET; + if (query_count == TCP_MAX_QUERIES || !packet || !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || @@ -1935,12 +1948,12 @@ unsigned char *tcp_request(int confd, time_t now, #endif char *types = querystr(auth_dns ? "auth" : "query", qtype); - if (peer_addr.sa.sa_family == AF_INET) - log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, - (union all_addr *)&peer_addr.in.sin_addr, types); - else - log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, - (union all_addr *)&peer_addr.in6.sin6_addr, types); + log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff, + &peer_addr, types); + +#ifdef HAVE_CONNTRACK + is_single_query = 1; +#endif #ifdef HAVE_AUTH /* find queries for zones we're authoritative for, and answer them directly */ @@ -1955,6 +1968,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 @@ -1973,13 +1988,34 @@ unsigned char *tcp_request(int confd, time_t now, if (flags & 0x8000) do_bit = 1; /* do bit */ } + +#ifdef HAVE_CONNTRACK +#ifdef HAVE_AUTH + if (!auth_dns || local_auth) +#endif + if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask)) + allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL); +#endif + + if (0); +#ifdef HAVE_CONNTRACK + else if (!allowed) + { + u16 swap = htons(EDE_BLOCKED); + m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL); + + if (have_pseudoheader && m != 0) + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + } +#endif #ifdef HAVE_AUTH - if (auth_dns) + else if (auth_dns) m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth, do_bit, have_pseudoheader); - else #endif + else { int ad_reqd = do_bit; /* RFC 6840 5.7 */ @@ -1995,286 +2031,340 @@ 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); + struct server *master; + int start; - 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)) + if (lookup_domain(daemon->namebuff, gotname, &first, &last)) + flags = is_local_answer(now, first, daemon->namebuff); + else { - 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; + /* No configured servers */ + ede = EDE_NOT_READY; + flags = 0; } -#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) + /* don't forward A or AAAA queries for simple names, except the empty name */ + if (!flags && + option_bool(OPT_NODOTS_LOCAL) && + (gotname & (F_IPV4 | F_IPV6)) && + !strchr(daemon->namebuff, '.') && + strlen(daemon->namebuff) != 0) + flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN; + + if (!flags && ede != EDE_NOT_READY) { - 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) + 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); + +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC)) { - 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; - } + size = add_do_bit(header, size, ((unsigned char *) header) + 65536); - /* server for wrong domain */ - if (type != (last_server->flags & SERV_TYPE) || - (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain)) || - (last_server->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) - 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 - while(retry_send(sendto(last_server->tcpfd, packet, size + sizeof(u16), - MSG_FASTOPEN, &last_server->addr.sa, sa_len(&last_server->addr)))); - - 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; - } + /* 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 (!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) + { + ede = EDE_NETERR; + 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"; - /* 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"); + union all_addr a; + a.log.ede = ede = errflags_to_ede(status); - 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)) + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { - 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; + result = "ABANDONED"; + status = STAT_BOGUS; } - - last_server->flags |= SERV_GOT_TCP; - - m = (c1 << 8) | c2; - - if (last_server->addr.sa.sa_family == AF_INET) - log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, - (union all_addr *)&last_server->addr.in.sin_addr, NULL); else - log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, - (union all_addr *)&last_server->addr.in6.sin6_addr, NULL); - -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC)) + result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS")); + + if (STAT_ISEQUAL(status, STAT_SECURE)) + cache_secure = 1; + else if (STAT_ISEQUAL(status, STAT_BOGUS)) { - 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")); + no_cache_dnssec = 1; + bogusanswer = 1; - if (status == STAT_BOGUS && extract_request(header, m, daemon->namebuff, NULL)) + if (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; - - /* 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; + log_query(F_SECSTAT, domain, &a, result); } - } - - /* 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 + + /* 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, ((unsigned char *)header) + 65536, ede); } } } + + /* In case of local answer or no connections made. */ + if (m == 0) + { + if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff, + ((char *) header) + 65536, first, last, ede))) + break; + if (have_pseudoheader) + { + u16 swap = htons((u16)ede); + + if (ede != EDE_UNSET) + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + else + 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 defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) +#ifdef HAVE_AUTH + if (!auth_dns || local_auth) +#endif + if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask)) + report_addresses(header, m, mark); +#endif + if (!read_write(confd, packet, m + sizeof(u16), 0)) + break; } + + return packet; } -static struct frec *allocate_frec(time_t now) +/* return a UDP socket bound to a random port, have to cope with straying into + occupied port nos and reserved ones. */ +static int random_sock(struct server *s) { - struct frec *f; - - if ((f = (struct frec *)whine_malloc(sizeof(struct frec)))) + int fd; + + if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) { - f->next = daemon->frec_list; - f->time = now; - f->sentto = NULL; - f->rfd4 = NULL; - f->flags = 0; - f->rfd6 = NULL; -#ifdef HAVE_DNSSEC - f->dependent = NULL; - f->blocking_query = NULL; - f->stash = NULL; -#endif - daemon->frec_list = f; + if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) + return fd; + + if (s->interface[0] == 0) + (void)prettyprint_addr(&s->source_addr, daemon->namebuff); + else + strcpy(daemon->namebuff, s->interface); + + my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), + daemon->namebuff, strerror(errno)); + close(fd); } + + return -1; +} - return f; +/* compare source addresses and interface, serv2 can be null. */ +static int server_isequal(const struct server *serv1, + const struct server *serv2) +{ + return (serv2 && + serv2->ifindex == serv1->ifindex && + sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) && + strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0); } -struct randfd *allocate_rfd(int family) +/* fdlp points to chain of randomfds already in use by transaction. + If there's already a suitable one, return it, else allocate a + new one and add it to the list. + + Not leaking any resources in the face of allocation failures + is rather convoluted here. + + Note that rfd->serv may be NULL, when a server goes away. +*/ +int allocate_rfd(struct randfd_list **fdlp, struct server *serv) { static int finger = 0; - int i; - + int i, j = 0; + struct randfd_list *rfl; + struct randfd *rfd = NULL; + int fd = 0; + + /* If server has a pre-allocated fd, use that. */ + if (serv->sfd) + return serv->sfd->fd; + + /* existing suitable random port socket linked to this transaction? */ + for (rfl = *fdlp; rfl; rfl = rfl->next) + if (server_isequal(serv, rfl->rfd->serv)) + return rfl->rfd->fd; + + /* No. need new link. */ + if ((rfl = daemon->rfl_spare)) + daemon->rfl_spare = rfl->next; + else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) + return -1; + /* limit the number of sockets we have open to avoid starvation of (eg) TFTP. Once we have a reasonable number, randomness should be OK */ - - for (i = 0; i < RANDOM_SOCKS; i++) + for (i = 0; i < daemon->numrrand; i++) if (daemon->randomsocks[i].refcount == 0) { - if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) - break; - - daemon->randomsocks[i].refcount = 1; - daemon->randomsocks[i].family = family; - return &daemon->randomsocks[i]; + if ((fd = random_sock(serv)) != -1) + { + rfd = &daemon->randomsocks[i]; + rfd->serv = serv; + rfd->fd = fd; + rfd->refcount = 1; + } + break; } - + /* No free ones or cannot get new socket, grab an existing one */ - for (i = 0; i < RANDOM_SOCKS; i++) + if (!rfd) + for (j = 0; j < daemon->numrrand; j++) + { + i = (j + finger) % daemon->numrrand; + if (daemon->randomsocks[i].refcount != 0 && + server_isequal(serv, daemon->randomsocks[i].serv) && + daemon->randomsocks[i].refcount != 0xfffe) + { + finger = i + 1; + rfd = &daemon->randomsocks[i]; + rfd->refcount++; + break; + } + } + + if (j == daemon->numrrand) { - int j = (i+finger) % RANDOM_SOCKS; - if (daemon->randomsocks[j].refcount != 0 && - daemon->randomsocks[j].family == family && - daemon->randomsocks[j].refcount != 0xffff) + struct randfd_list *rfl_poll; + + /* there are no free slots, and non with the same parameters we can piggy-back on. + We're going to have to allocate a new temporary record, distinguished by + refcount == 0xffff. This will exist in the frec randfd list, never be shared, + and be freed when no longer in use. It will also be held on + the daemon->rfl_poll list so the poll system can find it. */ + + if ((rfl_poll = daemon->rfl_spare)) + daemon->rfl_spare = rfl_poll->next; + else + rfl_poll = whine_malloc(sizeof(struct randfd_list)); + + if (!rfl_poll || + !(rfd = whine_malloc(sizeof(struct randfd))) || + (fd = random_sock(serv)) == -1) { - finger = j; - daemon->randomsocks[j].refcount++; - return &daemon->randomsocks[j]; + + /* Don't leak anything we may already have */ + rfl->next = daemon->rfl_spare; + daemon->rfl_spare = rfl; + + if (rfl_poll) + { + rfl_poll->next = daemon->rfl_spare; + daemon->rfl_spare = rfl_poll; + } + + if (rfd) + free(rfd); + + return -1; /* doom */ } - } - return NULL; /* doom */ + /* Note rfd->serv not set here, since it's not reused */ + rfd->fd = fd; + rfd->refcount = 0xffff; /* marker for temp record */ + + rfl_poll->rfd = rfd; + rfl_poll->next = daemon->rfl_poll; + daemon->rfl_poll = rfl_poll; + } + + rfl->rfd = rfd; + rfl->next = *fdlp; + *fdlp = rfl; + + return rfl->rfd->fd; } -void free_rfd(struct randfd *rfd) +void free_rfds(struct randfd_list **fdlp) { - if (rfd && --(rfd->refcount) == 0) - close(rfd->fd); + struct randfd_list *tmp, *rfl, *poll, *next, **up; + + for (rfl = *fdlp; rfl; rfl = tmp) + { + if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0) + close(rfl->rfd->fd); + + /* temporary overflow record */ + if (rfl->rfd->refcount == 0xffff) + { + free(rfl->rfd); + + /* go through the link of all these by steam to delete. + This list is expected to be almost always empty. */ + for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next) + { + next = poll->next; + + if (poll->rfd == rfl->rfd) + { + *up = poll->next; + poll->next = daemon->rfl_spare; + daemon->rfl_spare = poll; + } + else + up = &poll->next; + } + } + + tmp = rfl->next; + rfl->next = daemon->rfl_spare; + daemon->rfl_spare = rfl; + } + + *fdlp = NULL; } static void free_frec(struct frec *f) @@ -2290,12 +2380,9 @@ static void free_frec(struct frec *f) } f->frec_src.next = NULL; - free_rfd(f->rfd4); - f->rfd4 = NULL; + free_rfds(&f->rfds); f->sentto = NULL; f->flags = 0; - free_rfd(f->rfd6); - f->rfd6 = NULL; #ifdef HAVE_DNSSEC if (f->stash) @@ -2306,160 +2393,150 @@ static void free_frec(struct frec *f) /* Anything we're waiting on is pointless now, too */ if (f->blocking_query) - free_frec(f->blocking_query); + { + struct frec *n, **up; + + /* unlink outselves from the blocking query's dependents list. */ + for (n = f->blocking_query->dependent, up = &f->blocking_query->dependent; n; n = n->next_dependent) + if (n == f) + { + *up = n->next_dependent; + break; + } + else + up = &n->next_dependent; + + /* If we were the only/last dependent, free the blocking query too. */ + if (!f->blocking_query->dependent) + free_frec(f->blocking_query); + } + f->blocking_query = NULL; f->dependent = NULL; + f->next_dependent = NULL; #endif } -/* if wait==NULL return a free or older than TIMEOUT record. - else return *wait zero if one available, or *wait is delay to - when the oldest in-use record will expire. Impose an absolute +/* Impose an absolute limit of 4*TIMEOUT before we wipe things (for random sockets). - If force is non-NULL, always return a result, even if we have - to allocate above the limit, and never free the record pointed - to by the force argument. */ -struct frec *get_new_frec(time_t now, int *wait, struct frec *force) + If force is set, always return a result, even if we have + to allocate above the limit, and don'y free any records. + This is set when allocating for DNSSEC to avoid cutting off + the branch we are sitting on. */ +static struct frec *get_new_frec(time_t now, struct server *master, int force) { struct frec *f, *oldest, *target; int count; - if (wait) - *wait = 0; - - for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next, count++) - if (!f->sentto) - target = f; - else - { + /* look for free records, garbage collect old records and count number in use by our server-group. */ + for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next) + { + if (!f->sentto) + target = f; + else + { #ifdef HAVE_DNSSEC - /* Don't free DNSSEC sub-queries here, as we may end up with - dangling references to them. They'll go when their "real" query - is freed. */ - if (!f->dependent && f != force) + /* Don't free DNSSEC sub-queries here, as we may end up with + dangling references to them. They'll go when their "real" query + is freed. */ + if (!f->dependent && !force) #endif - { - if (difftime(now, f->time) >= 4*TIMEOUT) - { - free_frec(f); - target = f; - } - - - if (!oldest || difftime(f->time, oldest->time) <= 0) - oldest = f; - } - } + { + if (difftime(now, f->time) >= 4*TIMEOUT) + { + free_frec(f); + target = f; + } + else if (!oldest || difftime(f->time, oldest->time) <= 0) + oldest = f; + } + } + + if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master)) + count++; + } - if (target) + if (!force && count >= daemon->ftabsize) { - target->time = now; - return target; + query_full(now, master->domain); + return NULL; } - /* can't find empty one, use oldest if there is one - and it's older than timeout */ - if (!force && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT) + if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT) { - /* keep stuff for twice timeout if we can by allocating a new - record instead */ - if (difftime(now, oldest->time) < 2*TIMEOUT && - count <= daemon->ftabsize && - (f = allocate_frec(now))) - return f; - - if (!wait) - { - free_frec(oldest); - oldest->time = now; - } - return oldest; + /* can't find empty one, use oldest if there is one and it's older than timeout */ + free_frec(oldest); + target = oldest; } - /* none available, calculate time 'till oldest record expires */ - if (!force && count > daemon->ftabsize) + if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec)))) { - static time_t last_log = 0; - - if (oldest && wait) - *wait = oldest->time + (time_t)TIMEOUT - now; - - if ((int)difftime(now, last_log) > 5) - { - last_log = now; - my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize); - } - - return NULL; + target->next = daemon->frec_list; + daemon->frec_list = target; } - - if (!(f = allocate_frec(now)) && wait) - /* wait one second on malloc failure */ - *wait = 1; - return f; /* OK if malloc fails and this is NULL */ + if (target) + target->time = now; + + return target; } -static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) +static void query_full(time_t now, char *domain) { - struct frec *f; + static time_t last_log = 0; + + if ((int)difftime(now, last_log) > 5) + { + last_log = now; + if (!domain || strlen(domain) == 0) + my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize); + else + my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize); + } +} + +static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp) +{ + struct frec *f; + struct server *s; + 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)) { - /* sent from random port */ - if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) - return f; - - if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) - return f; + filter_servers(f->sentto->arrayposn, F_SERVER, firstp, lastp); - /* sent to upstream from bound socket. */ - if (f->sentto->sfd && f->sentto->sfd->fd == fd) + /* sent from random port */ + for (fdl = f->rfds; fdl; fdl = fdl->next) + if (fdl->rfd->fd == fd) return f; + + /* Sent to upstream from socket associated with a server. + Note we have to iterate over all the possible servers, since they may + have different bound sockets. */ + for (first = *firstp, last = *lastp; first != last; first++) + { + s = daemon->serverarray[first]; + if (s->sfd && s->sfd->fd == fd) + return f; + } } - - return NULL; -} - -static struct frec *lookup_frec_by_sender(unsigned short id, - union mysockaddr *addr, - void *hash) -{ - struct frec *f; - struct frec_src *src; - - for (f = daemon->frec_list; f; f = f->next) - if (f->sentto && - !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && - memcmp(hash, f->hash, HASH_SIZE) == 0) - for (src = &f->frec_src; src; src = src->next) - if (src->orig_id == id && - sockaddr_isequal(&src->source, addr)) - return f; return NULL; } -static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask) { struct frec *f; - /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below - ensures that no frec created for internal DNSSEC query can be returned here. - - Similarly FREC_NO_CACHE is never set in flags, so a query which is - contigent on a particular source address EDNS0 option will never be matched. */ - -#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ - | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) - for(f = daemon->frec_list; f; f = f->next) if (f->sentto && - (f->flags & FLAGMASK) == flags && + (f->flags & flagmask) == flags && memcmp(hash, f->hash, HASH_SIZE) == 0) return f; @@ -2470,34 +2547,26 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) void resend_query() { if (daemon->srv_save) - { - int fd; - - if (daemon->srv_save->sfd) - fd = daemon->srv_save->sfd->fd; - else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) - fd = daemon->rfd_save->fd; - else - return; - - while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, - &daemon->srv_save->addr.sa, - sa_len(&daemon->srv_save->addr)))); - } + server_send(daemon->srv_save, daemon->fd_save, + daemon->packet, daemon->packet_len, 0); } /* A server record is going away, remove references to it */ void server_gone(struct server *server) { struct frec *f; + int i; for (f = daemon->frec_list; f; f = f->next) if (f->sentto && f->sentto == server) free_frec(f); - - if (daemon->last_server == server) - daemon->last_server = NULL; + /* If any random socket refers to this server, NULL the reference. + No more references to the socket will be created in the future. */ + for (i = 0; i < daemon->numrrand; i++) + if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) + daemon->randomsocks[i].serv = NULL; + if (daemon->srv_save == server) daemon->srv_save = NULL; } @@ -2521,8 +2590,3 @@ static unsigned short get_id(void) return ret; } } - - - - - diff --git a/src/hash_questions.c b/src/hash-questions.c index 51d88c2..f41023b 100644 --- a/src/hash_questions.c +++ b/src/hash-questions.c @@ -28,28 +28,28 @@ #include "dnsmasq.h" -#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) +#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) + +static const struct nettle_hash *hash; +static void *ctx; +static unsigned char *digest; + +void hash_questions_init(void) +{ + if (!(hash = hash_find("sha256"))) + die(_("Failed to create SHA-256 hash object"), NULL, EC_MISC); + + ctx = safe_malloc(hash->context_size); + digest = safe_malloc(hash->digest_size); +} + unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) { int q; unsigned char *p = (unsigned char *)(header+1); - const struct nettle_hash *hash; - void *ctx; - unsigned char *digest; - - if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest)) - { - /* don't think this can ever happen. */ - static unsigned char dummy[HASH_SIZE]; - static int warned = 0; - - if (!warned) - my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object")); - warned = 1; - - return dummy; - } - + + hash->init(ctx); + for (q = ntohs(header->qdcount); q != 0; q--) { char *cp, c; @@ -74,11 +74,11 @@ unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name return digest; } -#else /* HAVE_DNSSEC */ +#else /* HAVE_DNSSEC || HAVE_CRYPTOHASH */ -#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest -typedef unsigned char BYTE; // 8-bit byte -typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines +#define SHA256_BLOCK_SIZE 32 /* SHA256 outputs a 32 byte digest */ +typedef unsigned char BYTE; /* 8-bit byte */ +typedef unsigned int WORD; /* 32-bit word, change to "long" for 16-bit machines */ typedef struct { BYTE data[64]; @@ -91,6 +91,9 @@ static void sha256_init(SHA256_CTX *ctx); static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); static void sha256_final(SHA256_CTX *ctx, BYTE hash[]); +void hash_questions_init(void) +{ +} unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) { @@ -235,7 +238,7 @@ static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) i = ctx->datalen; - // Pad whatever data is left in the buffer. + /* Pad whatever data is left in the buffer. */ if (ctx->datalen < 56) { ctx->data[i++] = 0x80; @@ -251,7 +254,7 @@ static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) memset(ctx->data, 0, 56); } - // Append to the padding the total message's length in bits and transform. + /* Append to the padding the total message's length in bits and transform. */ ctx->bitlen += ctx->datalen * 8; ctx->data[63] = ctx->bitlen; ctx->data[62] = ctx->bitlen >> 8; @@ -263,8 +266,8 @@ static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) ctx->data[56] = ctx->bitlen >> 56; sha256_transform(ctx, ctx->data); - // Since this implementation uses little endian byte ordering and SHA uses big endian, - // reverse all the bytes when copying the final state to the output hash. + /* Since this implementation uses little endian byte ordering and SHA uses big endian, + reverse all the bytes when copying the final state to the output hash. */ for (i = 0; i < 4; ++i) { hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; diff --git a/src/helper.c b/src/helper.c index 7072cf4..02340a0 100644 --- a/src/helper.c +++ b/src/helper.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -235,7 +235,6 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) } else continue; - /* stringify MAC into dhcp_buff */ p = daemon->dhcp_buff; @@ -433,7 +432,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) buf = grab_extradata_lua(buf, end, "relay_address"); else if (data.giaddr.s_addr != 0) { - lua_pushstring(lua, inet_ntoa(data.giaddr)); + inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN); + lua_pushstring(lua, daemon->addrbuff); lua_setfield(lua, -2, "relay_address"); } @@ -611,8 +611,13 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd) if (is6) buf = grab_extradata(buf, end, "DNSMASQ_RELAY_ADDRESS", &err); - else - my_setenv("DNSMASQ_RELAY_ADDRESS", data.giaddr.s_addr != 0 ? inet_ntoa(data.giaddr) : NULL, &err); + else + { + const char *giaddr = NULL; + if (data.giaddr.s_addr != 0) + giaddr = inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN); + my_setenv("DNSMASQ_RELAY_ADDRESS", giaddr, &err); + } for (i = 0; buf; i++) { @@ -882,7 +887,4 @@ void helper_write(void) } } -#endif - - - +#endif /* HAVE_SCRIPT */ diff --git a/src/inotify.c b/src/inotify.c index d5804cb..5776feb 100644 --- a/src/inotify.c +++ b/src/inotify.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -295,4 +295,3 @@ int inotify_check(time_t now) } #endif /* INOTIFY */ - diff --git a/src/ip6addr.h b/src/ip6addr.h index 352b330..6388c7d 100644 --- a/src/ip6addr.h +++ b/src/ip6addr.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -31,4 +31,3 @@ && ((__const uint32_t *) (a))[1] == 0 \ && ((__const uint32_t *) (a))[2] == 0 \ && ((__const uint32_t *) (a))[3] == 0) - diff --git a/src/lease.c b/src/lease.c index d254e7f..d026d63 100644 --- a/src/lease.c +++ b/src/lease.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -378,7 +378,7 @@ void lease_update_file(time_t now) if (next_event == 0 || difftime(next_event, LEASE_RETRY + now) > 0.0) next_event = LEASE_RETRY + now; - my_syslog(MS_DHCP | LOG_ERR, _("failed to write %s: %s (retry in %us)"), + my_syslog(MS_DHCP | LOG_ERR, _("failed to write %s: %s (retry in %u s)"), daemon->lease_file, strerror(err), (unsigned int)difftime(next_event, now)); } @@ -1034,6 +1034,7 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch } kill_name(lease_tmp); + lease_tmp->flags |= LEASE_CHANGED; /* run script on change */ break; } } @@ -1214,8 +1215,4 @@ void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned } #endif -#endif - - - - +#endif /* HAVE_DHCP */ @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -273,7 +273,7 @@ static void log_write(void) /* priority is one of LOG_DEBUG, LOG_INFO, LOG_NOTICE, etc. See sys/syslog.h. OR'd to priority can be MS_TFTP, MS_DHCP, ... to be able to do log separation between DNS, DHCP and TFTP services. -*/ + If OR'd with MS_DEBUG, the messages are suppressed unless --log-debug is set. */ void my_syslog(int priority, const char *format, ...) { va_list ap; @@ -290,7 +290,13 @@ void my_syslog(int priority, const char *format, ...) func = "-dhcp"; else if ((LOG_FACMASK & priority) == MS_SCRIPT) func = "-script"; - + else if ((LOG_FACMASK & priority) == MS_DEBUG) + { + if (!option_bool(OPT_LOG_DEBUG)) + return; + func = "-debug"; + } + #ifdef LOG_PRI priority = LOG_PRI(priority); #else @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid); void loop_send_probes() { struct server *serv; + struct randfd_list *rfds = NULL; if (!option_bool(OPT_LOOP_DETECT)) return; @@ -29,34 +30,29 @@ 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))) { ssize_t len = loop_make_probe(serv->uid); int fd; - struct randfd *rfd = NULL; - if (serv->sfd) - fd = serv->sfd->fd; - else - { - if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) - continue; - fd = rfd->fd; - } + serv->flags &= ~SERV_LOOP; + if ((fd = allocate_rfd(&rfds, serv)) == -1) + continue; + while (retry_send(sendto(fd, daemon->packet, len, 0, &serv->addr.sa, sa_len(&serv->addr)))); - - free_rfd(rfd); } + + free_rfds(&rfds); } static ssize_t loop_make_probe(u32 uid) { struct dns_header *header = (struct dns_header *)daemon->packet; unsigned char *p = (unsigned char *)(header+1); - + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -102,15 +98,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(1); /* log new state - don't send more probes. */ + return 1; + } + return 0; } diff --git a/src/metrics.c b/src/metrics.c index 79f20b0..fac5b00 100644 --- a/src/metrics.c +++ b/src/metrics.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/metrics.h b/src/metrics.h index ccc92a8..cecf8c8 100644 --- a/src/metrics.h +++ b/src/metrics.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/netlink.c b/src/netlink.c index 8e8431f..7840ef9 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -41,19 +41,26 @@ #ifndef NDA_RTA # define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) -#endif +#endif + +/* Used to request refresh of addresses or routes just once, + * when multiple changes might be announced. */ +enum async_states { + STATE_NEWADDR = (1 << 0), + STATE_NEWROUTE = (1 << 1), +}; static struct iovec iov; static u32 netlink_pid; -static void nl_async(struct nlmsghdr *h); +static unsigned nl_async(struct nlmsghdr *h, unsigned state); +static void nl_multicast_state(unsigned state); char *netlink_init(void) { struct sockaddr_nl addr; socklen_t slen = sizeof(addr); - int opt = 1; addr.nl_family = AF_NETLINK; addr.nl_pad = 0; @@ -92,14 +99,10 @@ char *netlink_init(void) iov.iov_len = 100; iov.iov_base = safe_malloc(iov.iov_len); - if (daemon->kernel_version >= KERNEL_VERSION(2,6,30) && - setsockopt(daemon->netlinkfd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(opt)) == -1) - return _("warning: failed to set NETLINK_NO_ENOBUFS on netlink socket"); - return NULL; } -static ssize_t netlink_recv(void) +static ssize_t netlink_recv(int flags) { struct msghdr msg; struct sockaddr_nl nladdr; @@ -115,7 +118,8 @@ static ssize_t netlink_recv(void) msg.msg_iovlen = 1; msg.msg_flags = 0; - while ((rc = recvmsg(daemon->netlinkfd, &msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); + while ((rc = recvmsg(daemon->netlinkfd, &msg, flags | MSG_PEEK | MSG_TRUNC)) == -1 && + errno == EINTR); /* make buffer big enough */ if (rc != -1 && (msg.msg_flags & MSG_TRUNC)) @@ -132,7 +136,7 @@ static ssize_t netlink_recv(void) /* read it for real */ msg.msg_flags = 0; - while ((rc = recvmsg(daemon->netlinkfd, &msg, 0)) == -1 && errno == EINTR); + while ((rc = recvmsg(daemon->netlinkfd, &msg, flags)) == -1 && errno == EINTR); /* Make sure this is from the kernel */ if (rc == -1 || nladdr.nl_pid == 0) @@ -151,7 +155,9 @@ static ssize_t netlink_recv(void) /* family = AF_UNSPEC finds ARP table entries. - family = AF_LOCAL finds MAC addresses. */ + family = AF_LOCAL finds MAC addresses. + returns 0 on failure, 1 on success, -1 when restart is required +*/ int iface_enumerate(int family, void *parm, int (*callback)()) { struct sockaddr_nl addr; @@ -159,6 +165,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) ssize_t len; static unsigned int seq = 0; int callback_ok = 1; + unsigned state = 0; struct { struct nlmsghdr nlh; @@ -170,7 +177,6 @@ int iface_enumerate(int family, void *parm, int (*callback)()) addr.nl_family = AF_NETLINK; - again: if (family == AF_UNSPEC) req.nlh.nlmsg_type = RTM_GETNEIGH; else if (family == AF_LOCAL) @@ -193,12 +199,12 @@ int iface_enumerate(int family, void *parm, int (*callback)()) while (1) { - if ((len = netlink_recv()) == -1) + if ((len = netlink_recv(0)) == -1) { if (errno == ENOBUFS) { - sleep(1); - goto again; + nl_multicast_state(state); + return -1; } return 0; } @@ -207,7 +213,7 @@ int iface_enumerate(int family, void *parm, int (*callback)()) if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR) { /* May be multicast arriving async */ - nl_async(h); + state = nl_async(h, state); } else if (h->nlmsg_seq != seq) { @@ -341,26 +347,28 @@ int iface_enumerate(int family, void *parm, int (*callback)()) } } -void netlink_multicast(void) +static void nl_multicast_state(unsigned state) { ssize_t len; struct nlmsghdr *h; - int flags; - - /* don't risk blocking reading netlink messages here. */ - if ((flags = fcntl(daemon->netlinkfd, F_GETFL)) == -1 || - fcntl(daemon->netlinkfd, F_SETFL, flags | O_NONBLOCK) == -1) - return; - - if ((len = netlink_recv()) != -1) - for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) - nl_async(h); + + do { + /* don't risk blocking reading netlink messages here. */ + while ((len = netlink_recv(MSG_DONTWAIT)) != -1) - /* restore non-blocking status */ - fcntl(daemon->netlinkfd, F_SETFL, flags); + for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len)) + state = nl_async(h, state); + } while (errno == ENOBUFS); } -static void nl_async(struct nlmsghdr *h) +void netlink_multicast(void) +{ + unsigned state = 0; + nl_multicast_state(state); +} + + +static unsigned nl_async(struct nlmsghdr *h, unsigned state) { if (h->nlmsg_type == NLMSG_ERROR) { @@ -368,7 +376,8 @@ static void nl_async(struct nlmsghdr *h) if (err->error != 0) my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error))); } - else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE) + else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE && + (state & STATE_NEWROUTE)==0) { /* We arrange to receive netlink multicast messages whenever the network route is added. If this happens and we still have a DNS packet in the buffer, we re-send it. @@ -380,11 +389,17 @@ static void nl_async(struct nlmsghdr *h) if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK && (rtm->rtm_table == RT_TABLE_MAIN || rtm->rtm_table == RT_TABLE_LOCAL)) - queue_event(EVENT_NEWROUTE); + { + queue_event(EVENT_NEWROUTE); + state |= STATE_NEWROUTE; + } } - else if (h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) - queue_event(EVENT_NEWADDR); + else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) && + (state & STATE_NEWADDR)==0) + { + queue_event(EVENT_NEWADDR); + state |= STATE_NEWADDR; + } + return state; } -#endif - - +#endif /* HAVE_LINUX_NETWORK */ diff --git a/src/network.c b/src/network.c index 7cf2546..3fc179d 100644 --- a/src/network.c +++ b/src/network.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -31,7 +31,7 @@ int indextoname(int fd, int index, char *name) safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE); - return 1; + return 1; } @@ -232,7 +232,7 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, union mysockaddr *addr, struct in_addr netmask, int prefixlen, int iface_flags) { struct irec *iface; - int mtu = 0, loopback; + int loopback; struct ifreq ifr; int tftp_ok = !!option_bool(OPT_TFTP); int dhcp_ok = 1; @@ -253,9 +253,6 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, if (loopback) dhcp_ok = 0; - if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1) - mtu = ifr.ifr_mtu; - if (!label) label = ifr.ifr_name; else @@ -351,36 +348,109 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, /* Update addresses from interface_names. These are a set independent of the set we're listening on. */ for (int_name = daemon->int_names; int_name; int_name = int_name->next) - if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0 && - (addr->sa.sa_family == int_name->family || int_name->family == 0)) + if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0) { - if (param->spare) + struct addrlist *lp; + + al = NULL; + + if (addr->sa.sa_family == AF_INET && (int_name->flags & (IN4 | INP4))) { - al = param->spare; - param->spare = al->next; + struct in_addr newaddr = addr->in.sin_addr; + + if (int_name->flags & INP4) + { + if (netmask.s_addr == 0xffff) + continue; + + newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) | + (int_name->proto4.s_addr & ~netmask.s_addr); + } + + /* check for duplicates. */ + for (lp = int_name->addr; lp; lp = lp->next) + if (lp->flags == 0 && lp->addr.addr4.s_addr == newaddr.s_addr) + break; + + if (!lp) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->flags = 0; + al->addr.addr4 = newaddr; + } + } + } + + if (addr->sa.sa_family == AF_INET6 && (int_name->flags & (IN6 | INP6))) + { + struct in6_addr newaddr = addr->in6.sin6_addr; + + if (int_name->flags & INP6) + { + int i; + + /* No sense in doing /128. */ + if (prefixlen == 128) + continue; + + for (i = 0; i < 16; i++) + { + int bits = ((i+1)*8) - prefixlen; + + if (bits >= 8) + newaddr.s6_addr[i] = int_name->proto6.s6_addr[i]; + else if (bits >= 0) + { + unsigned char mask = 0xff << bits; + newaddr.s6_addr[i] = + (addr->in6.sin6_addr.s6_addr[i] & mask) | + (int_name->proto6.s6_addr[i] & ~mask); + } + } + } + + /* check for duplicates. */ + for (lp = int_name->addr; lp; lp = lp->next) + if ((lp->flags & ADDRLIST_IPV6) && + IN6_ARE_ADDR_EQUAL(&lp->addr.addr6, &newaddr)) + break; + + if (!lp) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->flags = ADDRLIST_IPV6; + al->addr.addr6 = newaddr; + + /* Privacy addresses and addresses still undergoing DAD and deprecated addresses + don't appear in forward queries, but will in reverse ones. */ + if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) + al->flags |= ADDRLIST_REVONLY; + } + } } - else - al = whine_malloc(sizeof(struct addrlist)); if (al) { al->next = int_name->addr; int_name->addr = al; - - if (addr->sa.sa_family == AF_INET) - { - al->addr.addr4 = addr->in.sin_addr; - al->flags = 0; - } - else - { - al->addr.addr6 = addr->in6.sin6_addr; - al->flags = ADDRLIST_IPV6; - /* Privacy addresses and addresses still undergoing DAD and deprecated addresses - don't appear in forward queries, but will in reverse ones. */ - if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) - al->flags |= ADDRLIST_REVONLY; - } } } } @@ -458,6 +528,11 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, /* add to list */ if ((iface = whine_malloc(sizeof(struct irec)))) { + int mtu = 0; + + if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1) + mtu = ifr.ifr_mtu; + iface->addr = *addr; iface->netmask = netmask; iface->tftp_ok = tftp_ok; @@ -592,7 +667,7 @@ static int release_listener(struct listener *l) int port; port = prettyprint_addr(&l->iface->addr, daemon->addrbuff); - my_syslog(LOG_DEBUG, _("stopped listening on %s(#%d): %s port %d"), + my_syslog(LOG_DEBUG|MS_DEBUG, _("stopped listening on %s(#%d): %s port %d"), l->iface->name, l->iface->index, daemon->addrbuff, port); /* In case it ever returns */ l->iface->done = 0; @@ -621,7 +696,8 @@ int enumerate_interfaces(int reset) #ifdef HAVE_AUTH struct auth_zone *zone; #endif - + struct server *serv; + /* Do this max once per select cycle - also inhibits netlink socket use in TCP child processes. */ @@ -638,7 +714,26 @@ int enumerate_interfaces(int reset) if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) return 0; - + + /* iface indexes can change when interfaces are created/destroyed. + We use them in the main forwarding control path, when the path + to a server is specified by an interface, so cache them. + Update the cache here. */ + for (serv = daemon->servers; serv; serv = serv->next) + if (serv->interface[0] != 0) + { +#ifdef HAVE_LINUX_NETWORK + struct ifreq ifr; + + safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE); + if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1) + serv->ifindex = ifr.ifr_ifindex; +#else + serv->ifindex = if_nametoindex(serv->interface); +#endif + } + +again: /* Mark interfaces for garbage collection */ for (iface = daemon->interfaces; iface; iface = iface->next) iface->found = 0; @@ -690,9 +785,14 @@ int enumerate_interfaces(int reset) param.spare = spare; ret = iface_enumerate(AF_INET6, ¶m, iface_allowed_v6); - - if (ret) - ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); + if (ret < 0) + goto again; + else if (ret) + { + ret = iface_enumerate(AF_INET, ¶m, iface_allowed_v4); + if (ret < 0) + goto again; + } errsave = errno; close(param.fd); @@ -727,7 +827,7 @@ int enumerate_interfaces(int reset) errno = errsave; spare = param.spare; - + return ret; } @@ -867,10 +967,10 @@ int tcp_interface(int fd, int af) /* use mshdr so that the CMSDG_* macros are available */ msg.msg_control = daemon->packet; msg.msg_controllen = len = daemon->packet_buff_sz; - + /* we overwrote the buffer... */ - daemon->srv_save = NULL; - + daemon->srv_save = NULL; + if (af == AF_INET) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && @@ -1045,7 +1145,7 @@ void create_bound_listeners(int dienow) if (!dienow) { int port = prettyprint_addr(&iface->addr, daemon->addrbuff); - my_syslog(LOG_DEBUG, _("listening on %s(#%d): %s port %d"), + my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s(#%d): %s port %d"), iface->name, iface->index, daemon->addrbuff, port); } } @@ -1072,7 +1172,7 @@ void create_bound_listeners(int dienow) if (!dienow) { int port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff); - my_syslog(LOG_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port); + my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port); } } } @@ -1206,66 +1306,13 @@ void join_multicast(int dienow) } #endif -/* return a UDP socket bound to a random port, have to cope with straying into - occupied port nos and reserved ones. */ -int random_sock(int family) -{ - int fd; - - if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) - { - union mysockaddr addr; - unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; - int tries = ports_avail < 30 ? 3 * ports_avail : 100; - - memset(&addr, 0, sizeof(addr)); - addr.sa.sa_family = family; - - /* don't loop forever if all ports in use. */ - - if (fix_fd(fd)) - while(tries--) - { - unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); - - if (family == AF_INET) - { - addr.in.sin_addr.s_addr = INADDR_ANY; - addr.in.sin_port = port; -#ifdef HAVE_SOCKADDR_SA_LEN - addr.in.sin_len = sizeof(struct sockaddr_in); -#endif - } - else - { - addr.in6.sin6_addr = in6addr_any; - addr.in6.sin6_port = port; -#ifdef HAVE_SOCKADDR_SA_LEN - addr.in6.sin6_len = sizeof(struct sockaddr_in6); -#endif - } - - if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) - return fd; - - if (errno != EADDRINUSE && errno != EACCES) - break; - } - - close(fd); - } - - return -1; -} - - int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) { union mysockaddr addr_copy = *addr; unsigned short port; - int tries = 1, done = 0; - unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; - + int tries = 1; + unsigned short ports_avail = 1; + if (addr_copy.sa.sa_family == AF_INET) port = addr_copy.in.sin_port; else @@ -1274,35 +1321,43 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind /* cannot set source _port_ for TCP connections. */ if (is_tcp) port = 0; - - /* Bind a random port within the range given by min-port and max-port */ - if (port == 0) + else if (port == 0 && daemon->max_port != 0) { + /* Bind a random port within the range given by min-port and max-port if either + or both are set. Otherwise use the OS's random ephemeral port allocation by + leaving port == 0 and tries == 1 */ + ports_avail = daemon->max_port - daemon->min_port + 1; tries = ports_avail < 30 ? 3 * ports_avail : 100; - port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); + port = htons(daemon->min_port + (rand16() % ports_avail)); } - while (tries--) + while (1) { + /* elide bind() call if it's to port 0, address 0 */ if (addr_copy.sa.sa_family == AF_INET) - addr_copy.in.sin_port = port; + { + if (port == 0 && addr_copy.in.sin_addr.s_addr == 0) + break; + addr_copy.in.sin_port = port; + } else - addr_copy.in6.sin6_port = port; - - if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1) { - done = 1; - break; + if (port == 0 && IN6_IS_ADDR_UNSPECIFIED(&addr_copy.in6.sin6_addr)) + break; + addr_copy.in6.sin6_port = port; } - if (errno != EADDRINUSE && errno != EACCES) - return 0; + if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1) + break; - port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); - } + if (errno != EADDRINUSE && errno != EACCES) + return 0; - if (!done) - return 0; + if (--tries == 0) + return 0; + + port = htons(daemon->min_port + (rand16() % ports_avail)); + } if (!is_tcp && ifindex > 0) { @@ -1332,38 +1387,33 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind return 1; } -static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) +static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex) { struct serverfd *sfd; - unsigned int ifindex = 0; int errsave; int opt = 1; /* when using random ports, servers which would otherwise use - the INADDR_ANY/port0 socket have sfd set to NULL */ - if (!daemon->osport && intname[0] == 0) + the INADDR_ANY/port0 socket have sfd set to NULL, this is + anything without an explictly set source port. */ + if (!daemon->osport) { errno = 0; if (addr->sa.sa_family == AF_INET && - addr->in.sin_addr.s_addr == INADDR_ANY && addr->in.sin_port == htons(0)) return NULL; if (addr->sa.sa_family == AF_INET6 && - memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && addr->in6.sin6_port == htons(0)) return NULL; } - if (intname && strlen(intname) != 0) - ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ - /* may have a suitable one already */ for (sfd = daemon->sfds; sfd; sfd = sfd->next ) - if (sockaddr_isequal(&sfd->source_addr, addr) && - strcmp(intname, sfd->interface) == 0 && - ifindex == sfd->ifindex) + if (ifindex == sfd->ifindex && + sockaddr_isequal(&sfd->source_addr, addr) && + strcmp(intname, sfd->interface) == 0) return sfd; /* need to make a new one. */ @@ -1414,7 +1464,7 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(struct sockaddr_in); #endif - if ((sfd = allocate_sfd(&addr, ""))) + if ((sfd = allocate_sfd(&addr, "", 0))) sfd->preallocated = 1; memset(&addr, 0, sizeof(addr)); @@ -1424,13 +1474,12 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - if ((sfd = allocate_sfd(&addr, ""))) + if ((sfd = allocate_sfd(&addr, "", 0))) sfd->preallocated = 1; } for (srv = daemon->servers; srv; srv = srv->next) - if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && - !allocate_sfd(&srv->source_addr, srv->interface) && + if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && errno != 0 && option_bool(OPT_NOWILD)) { @@ -1445,136 +1494,23 @@ void pre_allocate_sfds(void) } } -void mark_servers(int flag) -{ - struct server *serv; - - /* mark everything with argument flag */ - for (serv = daemon->servers; serv; serv = serv->next) - { - if (serv->flags & flag) - serv->flags |= SERV_MARK; -#ifdef HAVE_LOOP - /* Give looped servers another chance */ - serv->flags &= ~SERV_LOOP; -#endif - } -} - -void cleanup_servers(void) -{ - struct server *serv, *tmp, **up; - - /* unlink and free anything still marked. */ - for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) - { - tmp = serv->next; - if (serv->flags & SERV_MARK) - { - server_gone(serv); - *up = serv->next; - if (serv->domain) - 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(); -#endif -} - -void add_update_server(int flags, - union mysockaddr *addr, - union mysockaddr *source_addr, - const char *interface, - const char *domain) -{ - struct server *serv, *next = NULL; - char *domain_str = NULL; - - /* 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; - } - - if (serv) - { - domain_str = serv->domain; - next = serv->next; - } - else if ((serv = whine_malloc(sizeof (struct server)))) - { - /* Not found, create a new one. */ - if (domain && !(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; - else - { - for (s = daemon->servers; s->next; s = s->next); - s->next = serv; - } - if (domain) - strcpy(domain_str, domain); - } - } - - if (serv) - { - 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; - } -} - -void check_servers(void) +void check_servers(int no_loop_check) { struct irec *iface; struct server *serv; struct serverfd *sfd, *tmp, **up; int port = 0, count; int locals = 0; + +#ifdef HAVE_LOOP + if (!no_loop_check) + loop_send_probes(); +#endif - /* interface may be new since startup */ + /* clear all marks. */ + mark_servers(0); + + /* interface may be new since startup */ if (!option_bool(OPT_NOWILD)) enumerate_interfaces(0); @@ -1584,114 +1520,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; - - /* 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; - } - } -#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; - } + if (option_bool(OPT_DNSSEC_VALID)) + { + if (!(serv->flags & SERV_FOR_NODOTS)) + serv->flags |= SERV_DO_DNSSEC; - /* Do we need a socket set? */ - if (!serv->sfd && - !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && - errno != 0) + /* Disable DNSSEC validation when using server=/domain/.... servers + unless there's a configured trust anchor. */ + if (strlen(serv->domain) != 0) { - my_syslog(LOG_WARNING, - _("ignoring nameserver %s - cannot make/bind socket: %s"), - daemon->namebuff, strerror(errno)); - serv->flags |= SERV_MARK; - continue; + 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; } - - if (serv->sfd) - serv->sfd->used = 1; } +#endif - if (!(serv->flags & SERV_NO_REBIND) && !(serv->flags & SERV_LITERAL_ADDRESS)) + 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) { - if (++count > SERVERS_LOGGED) - continue; - - if (serv->flags & (SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_USE_RESOLV)) - { - char *s1, *s2, *s3 = ""; + 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; + + if (++count > SERVERS_LOGGED) + continue; + + if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS)) + { + char *s1, *s2, *s3 = "", *s4 = ""; + #ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC)) - s3 = _("(no 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); + if (serv->flags & SERV_FOR_NODOTS) + s1 = _("unqualified"), s2 = _("names"); + else if (strlen(serv->domain) == 0) + s1 = _("default"), s2 = ""; else - my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); + s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : ""; + + my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s%s %s"), daemon->namebuff, port, s1, s4, 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) @@ -1713,7 +1652,8 @@ void check_servers(void) up = &sfd->next; } - cleanup_servers(); + cleanup_servers(); /* remove servers we just deleted. */ + build_server_array(); } /* Return zero if no servers found, in that case we keep polling. @@ -1748,7 +1688,7 @@ int reload_servers(char *fname) memset(&addr, 0, sizeof(addr)); memset(&source_addr, 0, sizeof(source_addr)); - if ((addr.in.sin_addr.s_addr = inet_addr(token)) != (in_addr_t) -1) + if (inet_pton(AF_INET, token, &addr.in.sin_addr) > 0) { #ifdef HAVE_SOCKADDR_SA_LEN source_addr.in.sin_len = addr.in.sin_len = sizeof(source_addr.in); @@ -1786,7 +1726,7 @@ int reload_servers(char *fname) continue; } - add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL); + add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL, NULL); gotone = 1; } @@ -1819,8 +1759,3 @@ void newaddress(time_t now) lease_find_interfaces(now); #endif } - - - - - diff --git a/src/option.c b/src/option.c index 316d9c8..ffce9fc 100644 --- a/src/option.c +++ b/src/option.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -168,6 +168,12 @@ struct myoption { #define LOPT_SINGLE_PORT 359 #define LOPT_SCRIPT_TIME 360 #define LOPT_PXE_VENDOR 361 +#define LOPT_DYNHOST 362 +#define LOPT_LOG_DEBUG 363 +#define LOPT_UMBRELLA 364 +#define LOPT_CMARK_ALST_EN 365 +#define LOPT_CMARK_ALST 366 +#define LOPT_QUIET_TFTP 367 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -321,6 +327,8 @@ static const struct myoption opts[] = { "auth-sec-servers", 1, 0, LOPT_AUTHSFS }, { "auth-peer", 1, 0, LOPT_AUTHPEER }, { "ipset", 1, 0, LOPT_IPSET }, + { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN }, + { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST }, { "synth-domain", 1, 0, LOPT_SYNTH }, { "dnssec", 0, 0, LOPT_SEC_VALID }, { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR }, @@ -341,6 +349,10 @@ static const struct myoption opts[] = { "dumpfile", 1, 0, LOPT_DUMPFILE }, { "dumpmask", 1, 0, LOPT_DUMPMASK }, { "dhcp-ignore-clid", 0, 0, LOPT_IGNORE_CLID }, + { "dynamic-host", 1, 0, LOPT_DYNHOST }, + { "log-debug", 0, 0, LOPT_LOG_DEBUG }, + { "umbrella", 2, 0, LOPT_UMBRELLA }, + { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, { NULL, 0, 0, 0 } }; @@ -491,6 +503,7 @@ static struct { { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_DYNHOST, ARG_DUP, "<name>,[<IPv4>][,<IPv6>],<interface-name>", gettext_noop("Specify host record in interface subnet"), NULL }, { LOPT_CAA, ARG_DUP, "<name>,<flags>,<tag>,<value>", gettext_noop("Specify certification authority authorization record"), NULL }, { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, @@ -501,6 +514,8 @@ static struct { { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL }, { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL }, { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, + { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL }, + { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL }, { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL }, @@ -512,6 +527,7 @@ static struct { { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL }, + { LOPT_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), NULL }, { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL }, { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL }, { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, @@ -521,6 +537,8 @@ static struct { { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL }, { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL }, { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL }, + { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL }, + { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -635,6 +653,9 @@ static char *canonicalise_opt(char *s) if (!s) return 0; + if (strlen(s) == 0) + return opt_string_alloc(""); + unhide_metas(s); if (!(ret = canonicalise(s, &nomem)) && nomem) { @@ -647,7 +668,7 @@ static char *canonicalise_opt(char *s) return ret; } -static int atoi_check(char *a, int *res) +static int numeric_check(char *a) { char *p; @@ -660,10 +681,32 @@ static int atoi_check(char *a, int *res) if (*p < '0' || *p > '9') return 0; + return 1; +} + +static int atoi_check(char *a, int *res) +{ + if (!numeric_check(a)) + return 0; *res = atoi(a); return 1; } +static int strtoul_check(char *a, u32 *res) +{ + unsigned long x; + + if (!numeric_check(a)) + return 0; + x = strtoul(a, NULL, 10); + if (errno || x > UINT32_MAX) { + errno = 0; + return 0; + } + *res = (u32)x; + return 1; +} + static int atoi_check16(char *a, int *res) { if (!(atoi_check(a, res)) || @@ -781,21 +824,23 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr) return NULL; } -char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, int *flags) +char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags) { int source_port = 0, serv_port = NAMESERVER_PORT; char *portno, *source; char *interface_opt = NULL; int scope_index = 0; char *scope_id; - - if (!arg || strlen(arg) == 0) + + *interface = 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)) @@ -813,7 +858,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a if (interface_opt) { #if defined(SO_BINDTODEVICE) - safe_strncpy(interface, interface_opt, IF_NAMESIZE); + safe_strncpy(interface, source, IF_NAMESIZE); + source = interface_opt; #else return _("interface binding not supported"); #endif @@ -889,66 +935,52 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a return NULL; } -static struct server *add_rev4(struct in_addr addr, int msize) +static int domain_rev4(char *domain, struct in_addr addr, int msize) { - struct server *serv = opt_malloc(sizeof(struct server)); - in_addr_t a = ntohl(addr.s_addr); - char *p; - - memset(serv, 0, sizeof(struct server)); - p = serv->domain = opt_malloc(29); /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ - + in_addr_t a = ntohl(addr.s_addr); + + *domain = 0; + switch (msize) { case 32: - p += sprintf(p, "%u.", a & 0xff); + domain += sprintf(domain, "%u.", a & 0xff); /* fall through */ case 24: - p += sprintf(p, "%d.", (a >> 8) & 0xff); + domain += sprintf(domain, "%d.", (a >> 8) & 0xff); /* fall through */ case 16: - p += sprintf(p, "%d.", (a >> 16) & 0xff); + domain += sprintf(domain, "%d.", (a >> 16) & 0xff); /* fall through */ case 8: - p += sprintf(p, "%d.", (a >> 24) & 0xff); + domain += sprintf(domain, "%d.", (a >> 24) & 0xff); break; default: - free(serv->domain); - free(serv); - return NULL; + return 0; } - - p += sprintf(p, "in-addr.arpa"); - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; - - return serv; - + domain += sprintf(domain, "in-addr.arpa"); + + return 1; } -static struct server *add_rev6(struct in6_addr *addr, int msize) +static int domain_rev6(char *domain, struct in6_addr *addr, int msize) { - struct server *serv = opt_malloc(sizeof(struct server)); - char *p; int i; - - memset(serv, 0, sizeof(struct server)); - p = serv->domain = opt_malloc(73); /* strlen("32*<n.>ip6.arpa")+1 */ + + if (msize > 128 || msize%4) + return 0; + *domain = 0; + for (i = msize-1; i >= 0; i -= 4) { int dig = ((unsigned char *)addr)[i>>3]; - p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); + domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4); } - p += sprintf(p, "ip6.arpa"); - - serv->flags = SERV_HAS_DOMAIN; - serv->next = daemon->servers; - daemon->servers = serv; + domain += sprintf(domain, "ip6.arpa"); - return serv; + return 1; } #ifdef HAVE_DHCP @@ -1038,6 +1070,8 @@ static void dhcp_config_free(struct dhcp_config *config) if (config->flags & CONFIG_CLID) free(config->clid); + if (config->flags & CONFIG_NAME) + free(config->hostname); #ifdef HAVE_DHCP6 if (config->flags & CONFIG_ADDR6) @@ -1154,11 +1188,15 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7); new->flags |= DHOPT_VENDOR; + if ((new->flags & DHOPT_ENCAPSULATE) || flags == DHOPT_MATCH) + goto_err(_("inappropriate vendor:")); } else if (strstr(arg, "encap:") == arg) { new->u.encap = atoi(arg+6); new->flags |= DHOPT_ENCAPSULATE; + if ((new->flags & DHOPT_VENDOR) || flags == DHOPT_MATCH) + goto_err(_("inappropriate encap:")); } else if (strstr(arg, "vi-encap:") == arg) { @@ -1633,16 +1671,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; @@ -2203,7 +2231,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma arg = comma; comma = split(arg); daemon->hostmaster = opt_string_alloc(arg); - for (cp = daemon->hostmaster; *cp; cp++) + for (cp = daemon->hostmaster; cp && *cp; cp++) if (*cp == '@') *cp = '.'; @@ -2231,12 +2259,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma set_option_bool(OPT_RESOLV_DOMAIN); else { - char *d; + char *d, *d_raw = arg; comma = split(arg); - if (!(d = canonicalise_opt(arg))) + if (!(d = canonicalise_opt(d_raw))) ret_err(gen_err); else { + free(d); /* allocate this again below. */ if (comma) { struct cond_domain *new = opt_malloc(sizeof(struct cond_domain)); @@ -2244,6 +2273,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->prefix = NULL; new->indexed = 0; + new->prefixlen = 0; unhide_metas(comma); if ((netpart = split_chr(comma, '/'))) @@ -2255,7 +2285,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err_free(gen_err, new); else if (inet_pton(AF_INET, comma, &new->start)) { - int mask = (1 << (32 - msize)) - 1; + int mask; + + if (msize > 32) + ret_err_free(_("bad prefix length"), new); + + mask = (1 << (32 - msize)) - 1; new->is6 = 0; new->start.s_addr = ntohl(htonl(new->start.s_addr) & ~mask); new->end.s_addr = new->start.s_addr | htonl(mask); @@ -2272,41 +2307,39 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err_free(gen_err, new); else { - /* generate the equivalent of - local=/xxx.yyy.zzz.in-addr.arpa/ */ - struct server *serv = add_rev4(new->start, msize); - if (!serv) - ret_err_free(_("bad prefix"), new); - - serv->flags |= SERV_NO_ADDR; - + char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */ + /* local=/xxx.yyy.zzz.in-addr.arpa/ */ + /* domain_rev4 can't fail here, msize checked above. */ + domain_rev4(domain, new->start, msize); + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL); + /* local=/<domain>/ */ - 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; + /* d_raw can't failed to canonicalise here, checked above. */ + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); } } } else if (inet_pton(AF_INET6, comma, &new->start6)) { - u64 mask = (1LLU << (128 - msize)) - 1LLU; - u64 addrpart = addr6part(&new->start6); + u64 mask, addrpart = addr6part(&new->start6); + + if (msize > 128) + ret_err_free(_("bad prefix length"), new); + + mask = (1LLU << (128 - msize)) - 1LLU; + new->is6 = 1; + new->prefixlen = msize; /* prefix==64 overflows the mask calculation above */ - if (msize == 64) + if (msize <= 64) mask = (u64)-1LL; new->end6 = new->start6; setaddr6part(&new->start6, addrpart & ~mask); setaddr6part(&new->end6, addrpart | mask); - if (msize < 64) - ret_err_free(gen_err, new); - else if (arg) + if (arg) { if (option != 's') { @@ -2318,18 +2351,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ret_err_free(gen_err, new); else { + char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */ /* generate the equivalent of local=/xxx.yyy.zzz.ip6.arpa/ */ - struct server *serv = add_rev6(&new->start6, msize); - serv->flags |= SERV_NO_ADDR; - + domain_rev6(domain, &new->start6, msize); + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL); + /* local=/<domain>/ */ - 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; + /* d_raw can't failed to canonicalise here, checked above. */ + add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL); } } } @@ -2369,7 +2399,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } } - new->domain = d; + new->domain = canonicalise_opt(d_raw); if (option == 's') { new->next = daemon->cond_domain; @@ -2378,19 +2408,21 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma else { char *star; - new->next = daemon->synth_domains; - daemon->synth_domains = new; if (new->prefix && (star = strrchr(new->prefix, '*')) && *(star+1) == 0) { *star = 0; new->indexed = 1; + if (new->is6 && new->prefixlen < 64) + ret_err_free(_("prefix length too small"), new); } + new->next = daemon->synth_domains; + daemon->synth_domains = new; } } else if (option == 's') - daemon->domain_suffix = d; + daemon->domain_suffix = canonicalise_opt(d_raw); else ret_err(gen_err); } @@ -2402,6 +2434,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->dns_client_id = opt_string_alloc(arg); break; + case LOPT_UMBRELLA: /* --umbrella */ + set_option_bool(OPT_UMBRELLA); + while (arg) { + comma = split(arg); + if (strstr(arg, "deviceid:")) { + arg += 9; + if (strlen(arg) != 16) + ret_err(gen_err); + for (char *p = arg; *p; p++) { + if (!isxdigit((int)*p)) + ret_err(gen_err); + } + set_option_bool(OPT_UMBRELLA_DEVID); + + u8 *u = daemon->umbrella_device; + char word[3]; + for (u8 i = 0; i < sizeof(daemon->umbrella_device); i++, arg+=2) { + memcpy(word, &(arg[0]), 2); + *u++ = strtoul(word, NULL, 16); + } + } + else if (strstr(arg, "orgid:")) { + if (!strtoul_check(arg+6, &daemon->umbrella_org)) { + ret_err(gen_err); + } + } + else if (strstr(arg, "assetid:")) { + if (!strtoul_check(arg+8, &daemon->umbrella_asset)) { + ret_err(gen_err); + } + } + arg = comma; + } + break; + case LOPT_ADD_MAC: /* --add-mac */ if (!arg) set_option_bool(OPT_ADD_MAC); @@ -2480,27 +2547,49 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case 'B': /* --bogus-nxdomain */ case LOPT_IGNORE_ADDR: /* --ignore-address */ { - struct in_addr addr; + union all_addr addr; + int prefix, is6 = 0; + struct bogus_addr *baddr; + unhide_metas(arg); - if (arg && (inet_pton(AF_INET, arg, &addr) > 0)) + + if (!arg || + ((comma = split_chr(arg, '/')) && !atoi_check(comma, &prefix))) + ret_err(gen_err); + + if (inet_pton(AF_INET6, arg, &addr.addr6) == 1) + is6 = 1; + else if (inet_pton(AF_INET, arg, &addr.addr4) != 1) + ret_err(gen_err); + + if (!comma) { - struct bogus_addr *baddr = opt_malloc(sizeof(struct bogus_addr)); - if (option == 'B') - { - baddr->next = daemon->bogus_addr; - daemon->bogus_addr = baddr; - } + if (is6) + prefix = 128; else - { - baddr->next = daemon->ignore_addr; - daemon->ignore_addr = baddr; - } - baddr->addr = addr; + prefix = 32; + } + + if (prefix > 128 || (!is6 && prefix > 32)) + ret_err(gen_err); + + baddr = opt_malloc(sizeof(struct bogus_addr)); + if (option == 'B') + { + baddr->next = daemon->bogus_addr; + daemon->bogus_addr = baddr; } else - ret_err(gen_err); /* error */ - break; - } + { + baddr->next = daemon->ignore_addr; + daemon->ignore_addr = baddr; + } + + baddr->prefix = prefix; + baddr->is6 = is6; + baddr->addr = addr; + break; + } case 'a': /* --listen-address */ case LOPT_AUTHPEER: /* --auth-peer */ @@ -2544,108 +2633,109 @@ 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->domain_len = 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; - + char *lastdomain = NULL, *domain = ""; + u16 flags = 0; + char *err; + union all_addr addr; + union mysockaddr serv_addr, source_addr; + char interface[IF_NAMESIZE+1]; + 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 *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; - } - 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)) + char *last; + + domain = lastdomain = ++arg; + + while ((last = split_chr(arg, '/'))) { - server_list_free(newlist); - ret_err(gen_err); + lastdomain = arg; + arg = last; } } - else if (option == LOPT_NO_REBIND) - newlist->flags |= SERV_NO_REBIND; 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, &addr.addr4) > 0) + flags = SERV_4ADDR | SERV_LITERAL_ADDRESS; + else if (inet_pton(AF_INET6, arg, &addr.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 */ else { - char *err = parse_server(arg, &newlist->addr, &newlist->source_addr, newlist->interface, &newlist->flags); - if (err) - { - server_list_free(newlist); - ret_err(err); - } + if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags))) + ret_err(err); } + + if (servers_only && option == 'S') + flags |= SERV_FROM_FILE; - serv = newlist; - while (serv->next) + while (1) { - 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; + /* server=//1.2.3.4 is special. */ + if (strlen(domain) == 0 && lastdomain) + flags |= SERV_FOR_NODOTS; + else + flags &= ~SERV_FOR_NODOTS; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr)) + ret_err(gen_err); + + if (!lastdomain || domain == lastdomain) + break; + + domain += strlen(domain) + 1; } - serv->next = daemon->servers; - daemon->servers = newlist; - break; + + break; } case LOPT_REV_SERV: /* --rev-server */ { char *string; int size; - struct server *serv; + u16 flags = 0; + char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */ struct in_addr addr4; struct in6_addr addr6; - + union mysockaddr serv_addr, source_addr; + char interface[IF_NAMESIZE+1]; + unhide_metas(arg); if (!arg) ret_err(gen_err); @@ -2657,22 +2747,27 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (inet_pton(AF_INET, arg, &addr4)) { - serv = add_rev4(addr4, size); - if (!serv) - ret_err(_("bad prefix")); + if (!domain_rev4(domain, addr4, size)) + ret_err(_("bad IPv4 prefix")); } else if (inet_pton(AF_INET6, arg, &addr6)) - serv = add_rev6(&addr6, size); + { + if (!domain_rev6(domain, &addr6, size)) + ret_err(_("bad IPv6 prefix")); + } else ret_err(gen_err); - - string = parse_server(comma, &serv->addr, &serv->source_addr, serv->interface, &serv->flags); - if (string) + if (!comma) + flags |= SERV_LITERAL_ADDRESS; + else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags))) ret_err(string); if (servers_only) - serv->flags |= SERV_FROM_FILE; + flags |= SERV_FROM_FILE; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + ret_err(gen_err); break; } @@ -2743,6 +2838,135 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } #endif + case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */ +#ifndef HAVE_CONNTRACK + ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); + break; +#else + { + u32 mask = UINT32_MAX; + + if (arg) + if (!strtoul_check(arg, &mask) || mask < 1) + ret_err(gen_err); + + set_option_bool(OPT_CMARK_ALST_EN); + daemon->allowlist_mask = mask; + break; + } +#endif + + case LOPT_CMARK_ALST: /* --connmark-allowlist */ +#ifndef HAVE_CONNTRACK + ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives")); + break; +#else + { + struct allowlist *allowlists; + char **patterns, **patterns_pos; + u32 mark, mask = UINT32_MAX; + size_t num_patterns = 0; + + char *c, *m = NULL; + char *separator; + unhide_metas(arg); + if (!arg) + ret_err(gen_err); + c = arg; + if (*c < '0' || *c > '9') + ret_err(gen_err); + while (*c && *c != ',') + { + if (*c == '/') + { + if (m) + ret_err(gen_err); + *c = '\0'; + m = ++c; + } + if (*c < '0' || *c > '9') + ret_err(gen_err); + c++; + } + separator = c; + if (!*separator) + break; + while (c && *c) + { + char *end = strchr(++c, '/'); + if (end) + *end = '\0'; + if (strcmp(c, "*") && !is_valid_dns_name_pattern(c)) + ret_err(gen_err); + if (end) + *end = '/'; + if (num_patterns >= UINT16_MAX - 1) + ret_err(gen_err); + num_patterns++; + c = end; + } + + *separator = '\0'; + if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX) + ret_err(gen_err); + if (m) + if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask)) + ret_err(gen_err); + if (num_patterns) + *separator = ','; + for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) + if (allowlists->mark == mark && allowlists->mask == mask) + ret_err(gen_err); + + patterns = opt_malloc((num_patterns + 1) * sizeof(char *)); + if (!patterns) + goto fail_cmark_allowlist; + patterns_pos = patterns; + c = separator; + while (c && *c) + { + char *end = strchr(++c, '/'); + if (end) + *end = '\0'; + if (!(*patterns_pos++ = opt_string_alloc(c))) + goto fail_cmark_allowlist; + if (end) + *end = '/'; + c = end; + } + *patterns_pos++ = NULL; + + allowlists = opt_malloc(sizeof(struct allowlist)); + if (!allowlists) + goto fail_cmark_allowlist; + memset(allowlists, 0, sizeof(struct allowlist)); + allowlists->mark = mark; + allowlists->mask = mask; + allowlists->patterns = patterns; + allowlists->next = daemon->allowlists; + daemon->allowlists = allowlists; + break; + + fail_cmark_allowlist: + if (patterns) + { + for (patterns_pos = patterns; *patterns_pos; patterns_pos++) + { + free(*patterns_pos); + *patterns_pos = NULL; + } + free(patterns); + patterns = NULL; + } + if (allowlists) + { + free(allowlists); + allowlists = NULL; + } + ret_err(gen_err); + } +#endif + case 'c': /* --cache-size */ { int size; @@ -3389,7 +3613,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma for (configs = daemon->dhcp_conf; configs; configs = configs->next) if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) { - sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); + inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN); + sprintf(errstr, _("duplicate dhcp-host IP address %s"), + daemon->addrbuff); return 0; } } @@ -3960,13 +4186,13 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma comma = split(arg); new->interface = opt_string_alloc(split(comma)); new->iface_index = 0; - if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server)) + if (comma && inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server)) { new->next = daemon->relay4; daemon->relay4 = new; } #ifdef HAVE_DHCP6 - else if (inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server)) + else if (comma && inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server)) { new->next = daemon->relay6; daemon->relay6 = new; @@ -4075,36 +4301,66 @@ err: } case LOPT_INTNAME: /* --interface-name */ + case LOPT_DYNHOST: /* --dynamic-host */ { struct interface_name *new, **up; - char *domain = NULL; - - comma = split(arg); + char *domain = arg; - if (!comma || !(domain = canonicalise_opt(arg))) - ret_err(_("bad interface name")); + arg = split(arg); new = opt_malloc(sizeof(struct interface_name)); - new->next = NULL; - new->addr = NULL; + memset(new, 0, sizeof(struct interface_name)); + new->flags = IN4 | IN6; /* Add to the end of the list, so that first name of an interface is used for PTR lookups. */ for (up = &daemon->int_names; *up; up = &((*up)->next)); *up = new; - new->name = domain; - new->family = 0; - arg = split_chr(comma, '/'); - if (arg) + + while ((comma = split(arg))) + { + if (inet_pton(AF_INET, arg, &new->proto4)) + new->flags |= INP4; + else if (inet_pton(AF_INET6, arg, &new->proto6)) + new->flags |= INP6; + else + break; + + arg = comma; + } + + if ((comma = split_chr(arg, '/'))) { - if (strcmp(arg, "4") == 0) - new->family = AF_INET; - else if (strcmp(arg, "6") == 0) - new->family = AF_INET6; + if (strcmp(comma, "4") == 0) + new->flags &= ~IN6; + else if (strcmp(comma, "6") == 0) + new->flags &= ~IN4; else ret_err_free(gen_err, new); - } - new->intr = opt_string_alloc(comma); + } + + new->intr = opt_string_alloc(arg); + + if (option == LOPT_DYNHOST) + { + if (!(new->flags & (INP4 | INP6))) + ret_err(_("missing address in dynamic host")); + + if (!(new->flags & IN4) || !(new->flags & IN6)) + arg = NULL; /* provoke error below */ + + new->flags &= ~(IN4 | IN6); + } + else + { + if (new->flags & (INP4 | INP6)) + arg = NULL; /* provoke error below */ + } + + if (!domain || !arg || !(new->name = canonicalise_opt(domain))) + ret_err(option == LOPT_DYNHOST ? + _("bad dynamic host") : _("bad interface name")); + break; } @@ -4431,8 +4687,7 @@ err: } else { - int nomem; - char *canon = canonicalise(arg, &nomem); + char *canon = canonicalise_opt(arg); struct name_list *nl; if (!canon) { @@ -4773,15 +5028,33 @@ static int one_file(char *file, int hard_opt) return 1; } +static int file_filter(const struct dirent *ent) +{ + size_t lenfile = strlen(ent->d_name); + + /* ignore emacs backups and dotfiles */ + + if (lenfile == 0 || + ent->d_name[lenfile - 1] == '~' || + (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || + ent->d_name[0] == '.') + return 0; + + return 1; +} /* expand any name which is a directory */ struct hostsfile *expand_filelist(struct hostsfile *list) { unsigned int i; - struct hostsfile *ah; + int entcnt, n; + struct hostsfile *ah, *last, *next, **up; + struct dirent **namelist; /* find largest used index */ for (i = SRC_AH, ah = list; ah; ah = ah->next) { + last = ah; + if (i <= ah->index) i = ah->index + 1; @@ -4797,46 +5070,48 @@ struct hostsfile *expand_filelist(struct hostsfile *list) struct stat buf; if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode)) { - DIR *dir_stream; struct dirent *ent; /* don't read this as a file */ ah->flags |= AH_INACTIVE; - if (!(dir_stream = opendir(ah->fname))) + entcnt = scandir(ah->fname, &namelist, file_filter, alphasort); + if (entcnt < 0) my_syslog(LOG_ERR, _("cannot access directory %s: %s"), ah->fname, strerror(errno)); else { - while ((ent = readdir(dir_stream))) + for (n = 0; n < entcnt; n++) { + ent = namelist[n]; size_t lendir = strlen(ah->fname); size_t lenfile = strlen(ent->d_name); struct hostsfile *ah1; char *path; - /* ignore emacs backups and dotfiles */ - if (lenfile == 0 || - ent->d_name[lenfile - 1] == '~' || - (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || - ent->d_name[0] == '.') - continue; - /* see if we have an existing record. dir is ah->fname file is ent->d_name path to match is ah1->fname */ - for (ah1 = list; ah1; ah1 = ah1->next) + for (up = &list, ah1 = list; ah1; ah1 = next) { + next = ah1->next; + if (lendir < strlen(ah1->fname) && strstr(ah1->fname, ah->fname) == ah1->fname && ah1->fname[lendir] == '/' && strcmp(ah1->fname + lendir + 1, ent->d_name) == 0) { ah1->flags &= ~AH_INACTIVE; + /* If found, remove from list to re-insert at the end. + Unless it's already at the end. */ + if (last != ah1) + *up = next; break; } + + up = &ah1->next; } /* make new record */ @@ -4857,17 +5132,21 @@ struct hostsfile *expand_filelist(struct hostsfile *list) ah1->fname = path; ah1->index = i++; ah1->flags = AH_DIR; - ah1->next = list; - list = ah1; } + + /* Edge case, may be the last in the list anyway */ + if (last != ah1) + last->next = ah1; + ah1->next = NULL; + last = ah1; /* inactivate record if not regular file */ if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 && !S_ISREG(buf.st_mode)) ah1->flags |= AH_INACTIVE; } - closedir(dir_stream); } + free(namelist); } } @@ -4885,9 +5164,9 @@ void read_servers_file(void) } mark_servers(SERV_FROM_FILE); - cleanup_servers(); - read_file(daemon->servers_file, f, LOPT_REV_SERV); + cleanup_servers(); + check_servers(0); } @@ -4903,30 +5182,8 @@ static void clear_dynamic_conf(void) if (configs->flags & CONFIG_BANK) { - struct hwaddr_config *mac, *tmp; - struct dhcp_netid_list *list, *tmplist; - - for (mac = configs->hwaddr; mac; mac = tmp) - { - tmp = mac->next; - free(mac); - } - - if (configs->flags & CONFIG_CLID) - free(configs->clid); - - for (list = configs->netid; list; list = tmplist) - { - free(list->list); - tmplist = list->next; - free(list); - } - - if (configs->flags & CONFIG_NAME) - free(configs->hostname); - - *up = configs->next; - free(configs); + *up = cp; + dhcp_config_free(configs); } else up = &configs->next; @@ -4936,7 +5193,6 @@ static void clear_dynamic_conf(void) static void clear_dynamic_opt(void) { struct dhcp_opt *opts, *cp, **up; - struct dhcp_netid *id, *next; for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp) { @@ -4944,17 +5200,8 @@ static void clear_dynamic_opt(void) if (opts->flags & DHOPT_BANK) { - if ((opts->flags & DHOPT_VENDOR)) - free(opts->u.vendor_class); - free(opts->val); - for (id = opts->netid; id; id = next) - { - next = id->next; - free(id->net); - free(id); - } - *up = opts->next; - free(opts); + *up = cp; + dhcp_opt_free(opts); } else up = &opts->next; @@ -5014,7 +5261,8 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon = opt_malloc(sizeof(struct daemon)); memset(daemon, 0, sizeof(struct daemon)); daemon->namebuff = buff; - + daemon->addrbuff = safe_malloc(ADDRSTRLEN); + /* Set defaults - everything else is zero or NULL */ daemon->cachesize = CACHESIZ; daemon->ftabsize = FTABSIZ; @@ -5034,9 +5282,7 @@ void read_opts(int argc, char **argv, char *compile_opts) daemon->soa_refresh = SOA_REFRESH; daemon->soa_retry = SOA_RETRY; daemon->soa_expiry = SOA_EXPIRY; - daemon->max_port = MAX_PORT; - daemon->min_port = MIN_PORT; - + #ifndef NO_ID add_txt("version.bind", "dnsmasq-" VERSION, 0 ); add_txt("authors.bind", "Simon Kelley", 0); diff --git a/src/outpacket.c b/src/outpacket.c index 50513dc..da6f73c 100644 --- a/src/outpacket.c +++ b/src/outpacket.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/pattern.c b/src/pattern.c new file mode 100644 index 0000000..03e23b9 --- /dev/null +++ b/src/pattern.c @@ -0,0 +1,386 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include "dnsmasq.h" + +#ifdef HAVE_CONNTRACK + +#define LOG(...) \ + do { \ + my_syslog(LOG_DEBUG, __VA_ARGS__); \ + } while (0) + +#define ASSERT(condition) \ + do { \ + if (!(condition)) \ + my_syslog(LOG_ERR, _("[pattern.c:%d] Assertion failure: %s"), __LINE__, #condition); \ + } while (0) + +/** + * Determines whether a given string value matches against a glob pattern + * which may contain zero-or-more-character wildcards denoted by '*'. + * + * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox, + * See https://research.swtch.com/glob + * + * @param value A string value. + * @param num_value_bytes The number of bytes of the string value. + * @param pattern A glob pattern. + * @param num_pattern_bytes The number of bytes of the glob pattern. + * + * @return 1 If the provided value matches against the glob pattern. + * @return 0 Otherwise. + */ +static int is_string_matching_glob_pattern( + const char *value, + size_t num_value_bytes, + const char *pattern, + size_t num_pattern_bytes) +{ + ASSERT(value); + ASSERT(pattern); + + size_t value_index = 0; + size_t next_value_index = 0; + size_t pattern_index = 0; + size_t next_pattern_index = 0; + while (value_index < num_value_bytes || pattern_index < num_pattern_bytes) + { + if (pattern_index < num_pattern_bytes) + { + char pattern_character = pattern[pattern_index]; + if ('a' <= pattern_character && pattern_character <= 'z') + pattern_character -= 'a' - 'A'; + if (pattern_character == '*') + { + /* zero-or-more-character wildcard */ + /* Try to match at value_index, otherwise restart at value_index + 1 next. */ + next_pattern_index = pattern_index; + pattern_index++; + if (value_index < num_value_bytes) + next_value_index = value_index + 1; + else + next_value_index = 0; + continue; + } + else + { + /* ordinary character */ + if (value_index < num_value_bytes) + { + char value_character = value[value_index]; + if ('a' <= value_character && value_character <= 'z') + value_character -= 'a' - 'A'; + if (value_character == pattern_character) + { + pattern_index++; + value_index++; + continue; + } + } + } + } + if (next_value_index) + { + pattern_index = next_pattern_index; + value_index = next_value_index; + continue; + } + return 0; + } + return 1; +} + +/** + * Determines whether a given string value represents a valid DNS name. + * + * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels + * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only + * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. + * + * - A valid name must be fully qualified, i.e., consist of at least two labels. + * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. + * + * - Examples: + * Valid: "example.com" + * Invalid: "ipcamera", "ipcamera.local", "8.8.8.8" + * + * @param value A string value. + * + * @return 1 If the provided string value is a valid DNS name. + * @return 0 Otherwise. + */ +int is_valid_dns_name(const char *value) +{ + ASSERT(value); + + size_t num_bytes = 0; + size_t num_labels = 0; + const char *label = NULL; + int is_label_numeric = 1; + for (const char *c = value;; c++) + { + if (*c && + *c != '-' && *c != '.' && + (*c < '0' || *c > '9') && + (*c < 'A' || *c > 'Z') && + (*c < 'a' || *c > 'z')) + { + LOG(_("Invalid DNS name: Invalid character %c."), *c); + return 0; + } + if (*c) + num_bytes++; + if (!label) + { + if (!*c || *c == '.') + { + LOG(_("Invalid DNS name: Empty label.")); + return 0; + } + if (*c == '-') + { + LOG(_("Invalid DNS name: Label starts with hyphen.")); + return 0; + } + label = c; + } + if (*c && *c != '.') + { + if (*c < '0' || *c > '9') + is_label_numeric = 0; + } + else + { + if (c[-1] == '-') + { + LOG(_("Invalid DNS name: Label ends with hyphen.")); + return 0; + } + size_t num_label_bytes = (size_t) (c - label); + if (num_label_bytes > 63) + { + LOG(_("Invalid DNS name: Label is too long (%zu)."), num_label_bytes); + return 0; + } + num_labels++; + if (!*c) + { + if (num_labels < 2) + { + LOG(_("Invalid DNS name: Not enough labels (%zu)."), num_labels); + return 0; + } + if (is_label_numeric) + { + LOG(_("Invalid DNS name: Final label is fully numeric.")); + return 0; + } + if (num_label_bytes == 5 && + (label[0] == 'l' || label[0] == 'L') && + (label[1] == 'o' || label[1] == 'O') && + (label[2] == 'c' || label[2] == 'C') && + (label[3] == 'a' || label[3] == 'A') && + (label[4] == 'l' || label[4] == 'L')) + { + LOG(_("Invalid DNS name: \"local\" pseudo-TLD.")); + return 0; + } + if (num_bytes < 1 || num_bytes > 253) + { + LOG(_("DNS name has invalid length (%zu)."), num_bytes); + return 0; + } + return 1; + } + label = NULL; + is_label_numeric = 1; + } + } +} + +/** + * Determines whether a given string value represents a valid DNS name pattern. + * + * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels + * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only + * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. + * + * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to + * twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot + * (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com"). + * + * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels. + * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. + * A pattern must end with at least two literal (non-wildcard) labels. + * + * - Examples: + * Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com" + * Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8" + * + * @param value A string value. + * + * @return 1 If the provided string value is a valid DNS name pattern. + * @return 0 Otherwise. + */ +int is_valid_dns_name_pattern(const char *value) +{ + ASSERT(value); + + size_t num_bytes = 0; + size_t num_labels = 0; + const char *label = NULL; + int is_label_numeric = 1; + size_t num_wildcards = 0; + int previous_label_has_wildcard = 1; + for (const char *c = value;; c++) + { + if (*c && + *c != '*' && /* Wildcard. */ + *c != '-' && *c != '.' && + (*c < '0' || *c > '9') && + (*c < 'A' || *c > 'Z') && + (*c < 'a' || *c > 'z')) + { + LOG(_("Invalid DNS name pattern: Invalid character %c."), *c); + return 0; + } + if (*c && *c != '*') + num_bytes++; + if (!label) + { + if (!*c || *c == '.') + { + LOG(_("Invalid DNS name pattern: Empty label.")); + return 0; + } + if (*c == '-') + { + LOG(_("Invalid DNS name pattern: Label starts with hyphen.")); + return 0; + } + label = c; + } + if (*c && *c != '.') + { + if (*c < '0' || *c > '9') + is_label_numeric = 0; + if (*c == '*') + { + if (num_wildcards >= 2) + { + LOG(_("Invalid DNS name pattern: Wildcard character used more than twice per label.")); + return 0; + } + num_wildcards++; + } + } + else + { + if (c[-1] == '-') + { + LOG(_("Invalid DNS name pattern: Label ends with hyphen.")); + return 0; + } + size_t num_label_bytes = (size_t) (c - label) - num_wildcards; + if (num_label_bytes > 63) + { + LOG(_("Invalid DNS name pattern: Label is too long (%zu)."), num_label_bytes); + return 0; + } + num_labels++; + if (!*c) + { + if (num_labels < 2) + { + LOG(_("Invalid DNS name pattern: Not enough labels (%zu)."), num_labels); + return 0; + } + if (num_wildcards != 0 || previous_label_has_wildcard) + { + LOG(_("Invalid DNS name pattern: Wildcard within final two labels.")); + return 0; + } + if (is_label_numeric) + { + LOG(_("Invalid DNS name pattern: Final label is fully numeric.")); + return 0; + } + if (num_label_bytes == 5 && + (label[0] == 'l' || label[0] == 'L') && + (label[1] == 'o' || label[1] == 'O') && + (label[2] == 'c' || label[2] == 'C') && + (label[3] == 'a' || label[3] == 'A') && + (label[4] == 'l' || label[4] == 'L')) + { + LOG(_("Invalid DNS name pattern: \"local\" pseudo-TLD.")); + return 0; + } + if (num_bytes < 1 || num_bytes > 253) + { + LOG(_("DNS name pattern has invalid length after removing wildcards (%zu)."), num_bytes); + return 0; + } + return 1; + } + label = NULL; + is_label_numeric = 1; + previous_label_has_wildcard = num_wildcards != 0; + num_wildcards = 0; + } + } +} + +/** + * Determines whether a given DNS name matches against a DNS name pattern. + * + * @param name A valid DNS name. + * @param pattern A valid DNS name pattern. + * + * @return 1 If the provided DNS name matches against the DNS name pattern. + * @return 0 Otherwise. + */ +int is_dns_name_matching_pattern(const char *name, const char *pattern) +{ + ASSERT(name); + ASSERT(is_valid_dns_name(name)); + ASSERT(pattern); + ASSERT(is_valid_dns_name_pattern(pattern)); + + const char *n = name; + const char *p = pattern; + + do { + const char *name_label = n; + while (*n && *n != '.') + n++; + const char *pattern_label = p; + while (*p && *p != '.') + p++; + if (!is_string_matching_glob_pattern( + name_label, (size_t) (n - name_label), + pattern_label, (size_t) (p - pattern_label))) + break; + if (*n) + n++; + if (*p) + p++; + } while (*n && *p); + + return !*n && !*p; +} + +#endif @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/radv-protocol.h b/src/radv-protocol.h index edc1532..8314e8a 100644 --- a/src/radv-protocol.h +++ b/src/radv-protocol.h @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -53,6 +53,3 @@ struct prefix_opt { #define ICMP6_OPT_RT_INFO 24 #define ICMP6_OPT_RDNSS 25 #define ICMP6_OPT_DNSSL 31 - - - @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/rfc1035.c b/src/rfc1035.c index 3b404a0..b501580 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -364,7 +364,7 @@ int private_net(struct in_addr addr, int ban_localhost) return (((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost) /* 127.0.0.0/8 (loopback) */ || - ((ip_addr & 0xFF000000) == 0x00000000) /* RFC 5735 section 3. "here" network */ || + (((ip_addr & 0xFF000000) == 0x00000000) && ban_localhost) /* RFC 5735 section 3. "here" network */ || ((ip_addr & 0xFF000000) == 0x0A000000) /* 10.0.0.0/8 (private) */ || ((ip_addr & 0xFFF00000) == 0xAC100000) /* 172.16.0.0/12 (private) */ || ((ip_addr & 0xFFFF0000) == 0xC0A80000) /* 192.168.0.0/16 (private) */ || @@ -375,28 +375,32 @@ int private_net(struct in_addr addr, int ban_localhost) ((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF) /* 255.255.255.255/32 (broadcast)*/ ; } -static int private_net6(struct in6_addr *a) +static int private_net6(struct in6_addr *a, int ban_localhost) { - return - IN6_IS_ADDR_UNSPECIFIED(a) || /* RFC 6303 4.3 */ - IN6_IS_ADDR_LOOPBACK(a) || /* RFC 6303 4.3 */ + /* Block IPv4-mapped IPv6 addresses in private IPv4 address space */ + if (IN6_IS_ADDR_V4MAPPED(a)) + { + struct in_addr v4; + v4.s_addr = ((const uint32_t *) (a))[3]; + return private_net(v4, ban_localhost); + } + + return + (IN6_IS_ADDR_UNSPECIFIED(a) && ban_localhost) || /* RFC 6303 4.3 */ + (IN6_IS_ADDR_LOOPBACK(a) && ban_localhost) || /* RFC 6303 4.3 */ IN6_IS_ADDR_LINKLOCAL(a) || /* RFC 6303 4.5 */ + IN6_IS_ADDR_SITELOCAL(a) || ((unsigned char *)a)[0] == 0xfd || /* RFC 6303 4.4 */ ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */ } -static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, char *name, int *doctored) +static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored) { int i, qtype, qclass, rdlen; for (i = count; i != 0; i--) { - if (name && option_bool(OPT_LOG)) - { - if (!extract_name(header, qlen, &p, name, 1, 10)) - return 0; - } - else if (!(p = skip_name(p, header, qlen, 10))) + if (!(p = skip_name(p, header, qlen, 10))) return 0; /* bad packet */ GETSHORT(qtype, p); @@ -435,34 +439,6 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * break; } } - else if (qtype == T_TXT && name && option_bool(OPT_LOG)) - { - unsigned char *p1 = p; - if (!CHECK_LEN(header, p1, qlen, rdlen)) - return 0; - while ((p1 - p) < rdlen) - { - unsigned int i, len = *p1; - unsigned char *p2 = p1; - if ((p1 + len - p) >= rdlen) - return 0; /* bad packet */ - /* make counted string zero-term and sanitise */ - for (i = 0; i < len; i++) - { - if (!isprint((int)*(p2+1))) - break; - - *p2 = *(p2+1); - p2++; - } - *p2 = 0; - my_syslog(LOG_INFO, "reply %s is %s", name, p1); - /* restore */ - memmove(p1 + 1, p1, i); - *p1 = len; - p1 += len+1; - } - } if (!ADD_RDLEN(header, p, qlen, rdlen)) return 0; /* bad packet */ @@ -471,7 +447,7 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header * return p; } -static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doctored) +static int find_soa(struct dns_header *header, size_t qlen, int *doctored) { unsigned char *p; int qtype, qclass, rdlen; @@ -480,7 +456,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc /* first move to NS section and find TTL from any SOA section */ if (!(p = skip_questions(header, qlen)) || - !(p = do_doctor(p, ntohs(header->ancount), header, qlen, name, doctored))) + !(p = do_doctor(p, ntohs(header->ancount), header, qlen, doctored))) return 0; /* bad packet */ for (i = ntohs(header->nscount); i != 0; i--) @@ -516,7 +492,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc } /* rewrite addresses in additional section too */ - if (!do_doctor(p, ntohs(header->arcount), header, qlen, NULL, doctored)) + if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored)) return 0; if (!found_soa) @@ -525,6 +501,40 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc return minttl; } +/* Print TXT reply to log */ +static int print_txt(struct dns_header *header, const size_t qlen, char *name, + unsigned char *p, const int ardlen, int secflag) +{ + unsigned char *p1 = p; + if (!CHECK_LEN(header, p1, qlen, ardlen)) + return 0; + /* Loop over TXT payload */ + while ((p1 - p) < ardlen) + { + unsigned int i, len = *p1; + unsigned char *p3 = p1; + if ((p1 + len - p) >= ardlen) + return 0; /* bad packet */ + + /* make counted string zero-term and sanitise */ + for (i = 0; i < len; i++) + { + if (!isprint((int)*(p3+1))) + break; + *p3 = *(p3+1); + p3++; + } + + *p3 = 0; + log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1); + /* restore */ + memmove(p1 + 1, p1, i); + *p1 = len; + p1 += len+1; + } + return 1; +} + /* Note that the following code can create CNAME chains that don't point to a real record, either because of lack of memory, or lack of SOA records. These are treated by the cache code as expired and cleaned out that way. @@ -534,7 +544,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t int secure, int *doctored) { unsigned char *p, *p1, *endrr, *namep; - int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; + int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; unsigned long ttl = 0; union all_addr addr; #ifdef HAVE_IPSET @@ -542,15 +552,21 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t #else (void)ipsets; /* unused */ #endif - + int found = 0, cname_count = CNAME_CHAIN; + struct crec *cpp = NULL; + int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; +#ifdef HAVE_DNSSEC + int cname_short = 0; +#endif + unsigned long cttl = ULONG_MAX, attl; cache_start_insert(); - /* find_soa is needed for dns_doctor and logging side-effects, so don't call it lazily if there are any. */ - if (daemon->doctors || option_bool(OPT_LOG) || option_bool(OPT_DNSSEC_VALID)) + /* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */ + if (daemon->doctors || option_bool(OPT_DNSSEC_VALID)) { searched_soa = 1; - ttl = find_soa(header, qlen, name, doctored); + ttl = find_soa(header, qlen, doctored); if (*doctored) { @@ -558,308 +574,329 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t return 0; #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID)) - for (i = 0; i < ntohs(header->ancount); i++) - if (daemon->rr_status[i] != 0) + for (j = 0; j < ntohs(header->ancount); j++) + if (daemon->rr_status[j] != 0) return 0; #endif } } - /* go through the questions. */ - p = (unsigned char *)(header+1); + namep = p = (unsigned char *)(header+1); - for (i = ntohs(header->qdcount); i != 0; i--) - { - int found = 0, cname_count = CNAME_CHAIN; - struct crec *cpp = NULL; - int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0; -#ifdef HAVE_DNSSEC - int cname_short = 0; -#endif - unsigned long cttl = ULONG_MAX, attl; - - namep = p; - if (!extract_name(header, qlen, &p, name, 1, 4)) - return 0; /* bad packet */ - - GETSHORT(qtype, p); - GETSHORT(qclass, p); + if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4)) + return 0; /* bad packet */ + + GETSHORT(qtype, p); + GETSHORT(qclass, p); + + if (qclass != C_IN) + return 0; + + /* PTRs: we chase CNAMEs here, since we have no way to + represent them in the cache. */ + if (qtype == T_PTR) + { + int insert = 1, name_encoding = in_arpa_name_2_addr(name, &addr); - if (qclass != C_IN) - continue; - - /* PTRs: we chase CNAMEs here, since we have no way to - represent them in the cache. */ - if (qtype == T_PTR) - { - int name_encoding = in_arpa_name_2_addr(name, &addr); + if (!(flags & F_NXDOMAIN)) + { + cname_loop: + if (!(p1 = skip_questions(header, qlen))) + return 0; - if (!name_encoding) - continue; - - if (!(flags & F_NXDOMAIN)) + for (j = 0; j < ntohs(header->ancount); j++) { - cname_loop: - if (!(p1 = skip_questions(header, qlen))) - return 0; + int secflag = 0; + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ - for (j = 0; j < ntohs(header->ancount); j++) + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) { - int secflag = 0; - unsigned char *tmp = namep; - /* the loop body overwrites the original name, so get it back here. */ - if (!extract_name(header, qlen, &tmp, name, 1, 0) || - !(res = extract_name(header, qlen, &p1, name, 0, 10))) - return 0; /* bad packet */ - - GETSHORT(aqtype, p1); - GETSHORT(aqclass, p1); - GETLONG(attl, p1); - if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); + } + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + /* TTL of record is minimum of CNAMES and PTR */ + if (attl < cttl) + cttl = attl; + + if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR)) + { +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) { - (p1) -= 4; - PUTLONG(daemon->max_ttl, p1); + /* validated RR anywhere in CNAME chain, don't cache. */ + if (cname_short || aqtype == T_CNAME) + insert = 0; + + secflag = F_DNSSECOK; + /* limit TTL based on signature. */ + if (daemon->rr_status[j] < cttl) + cttl = daemon->rr_status[j]; } - GETSHORT(ardlen, p1); - endrr = p1+ardlen; - - /* TTL of record is minimum of CNAMES and PTR */ - if (attl < cttl) - cttl = attl; - - if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR)) - { - if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 0; -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) - { - /* validated RR anywhere in CNAME chain, don't cache. */ - if (cname_short || aqtype == T_CNAME) - return 0; - - secflag = F_DNSSECOK; - /* limit TTL based on signature. */ - if (daemon->rr_status[j] < cttl) - cttl = daemon->rr_status[j]; - } #endif - if (aqtype == T_CNAME) - { - if (!cname_count--) - return 0; /* looped CNAMES, we can't cache. */ + if (aqtype == T_CNAME) + log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL); + + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + + if (aqtype == T_CNAME) + { + if (!cname_count--) + return 0; /* looped CNAMES, we can't cache. */ #ifdef HAVE_DNSSEC - cname_short = 1; + cname_short = 1; #endif - goto cname_loop; - } - - cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE); - found = 1; + goto cname_loop; } - p1 = endrr; - if (!CHECK_LEN(header, p1, qlen, 0)) - return 0; /* bad packet */ + found = 1; + + if (!name_encoding) + log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype)); + else + { + log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL); + if (insert) + cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE); + } } + + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ + } + } + + if (!found && !option_bool(OPT_NO_NEG)) + { + if (!searched_soa) + { + searched_soa = 1; + ttl = find_soa(header, qlen, doctored); } - if (!found && !option_bool(OPT_NO_NEG)) + flags |= F_NEG | (secure ? F_DNSSECOK : 0); + if (name_encoding && ttl) { - if (!searched_soa) - { - searched_soa = 1; - ttl = find_soa(header, qlen, NULL, doctored); - } - if (ttl) - cache_insert(NULL, &addr, C_IN, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ? F_DNSSECOK : 0)); + flags |= F_REVERSE | name_encoding; + cache_insert(NULL, &addr, C_IN, now, ttl, flags); } + + log_query(flags | F_UPSTREAM, name, &addr, NULL); + } + } + else + { + /* everything other than PTR */ + struct crec *newc; + int addrlen = 0, insert = 1; + + if (qtype == T_A) + { + addrlen = INADDRSZ; + flags |= F_IPV4; + } + else if (qtype == T_AAAA) + { + addrlen = IN6ADDRSZ; + flags |= F_IPV6; } + else if (qtype == T_SRV) + flags |= F_SRV; else + insert = 0; /* NOTE: do not cache data from CNAME queries. */ + + cname_loop1: + if (!(p1 = skip_questions(header, qlen))) + return 0; + + for (j = 0; j < ntohs(header->ancount); j++) { - /* everything other than PTR */ - struct crec *newc; - int addrlen = 0; - - if (qtype == T_A) + int secflag = 0; + + if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) + return 0; /* bad packet */ + + GETSHORT(aqtype, p1); + GETSHORT(aqclass, p1); + GETLONG(attl, p1); + if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) { - addrlen = INADDRSZ; - flags |= F_IPV4; + (p1) -= 4; + PUTLONG(daemon->max_ttl, p1); } - else if (qtype == T_AAAA) + GETSHORT(ardlen, p1); + endrr = p1+ardlen; + + /* Not what we're looking for? */ + if (aqclass != C_IN || res == 2) { - addrlen = IN6ADDRSZ; - flags |= F_IPV6; + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ + continue; } - else if (qtype == T_SRV) - flags |= F_SRV; - else - continue; - - cname_loop1: - if (!(p1 = skip_questions(header, qlen))) - return 0; - for (j = 0; j < ntohs(header->ancount); j++) +#ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) { - int secflag = 0; + secflag = F_DNSSECOK; - if (!(res = extract_name(header, qlen, &p1, name, 0, 10))) - return 0; /* bad packet */ + /* limit TTl based on sig. */ + if (daemon->rr_status[j] < attl) + attl = daemon->rr_status[j]; + } +#endif + + if (aqtype == T_CNAME) + { + if (!cname_count--) + return 0; /* looped CNAMES */ - GETSHORT(aqtype, p1); - GETSHORT(aqclass, p1); - GETLONG(attl, p1); - if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign) - { - (p1) -= 4; - PUTLONG(daemon->max_ttl, p1); - } - GETSHORT(ardlen, p1); - endrr = p1+ardlen; + log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL); - if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype)) + if (insert) { -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0) + if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag))) { - secflag = F_DNSSECOK; - - /* limit TTl based on sig. */ - if (daemon->rr_status[j] < attl) - attl = daemon->rr_status[j]; - } -#endif - if (aqtype == T_CNAME) - { - if (!cname_count--) - return 0; /* looped CNAMES */ - - if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag))) + newc->addr.cname.target.cache = NULL; + newc->addr.cname.is_name_ptr = 0; + if (cpp) { - newc->addr.cname.target.cache = NULL; - newc->addr.cname.is_name_ptr = 0; - if (cpp) - { - next_uid(newc); - cpp->addr.cname.target.cache = newc; - cpp->addr.cname.uid = newc->uid; - } + next_uid(newc); + cpp->addr.cname.target.cache = newc; + cpp->addr.cname.uid = newc->uid; } - - cpp = newc; - if (attl < cttl) - cttl = attl; - - namep = p1; - if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 0; - - goto cname_loop1; } - else if (!(flags & F_NXDOMAIN)) + + cpp = newc; + if (attl < cttl) + cttl = attl; + } + + namep = p1; + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + + goto cname_loop1; + } + else if (aqtype != qtype) + { +#ifdef HAVE_DNSSEC + if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG) +#endif + log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype)); + } + else if (!(flags & F_NXDOMAIN)) + { + found = 1; + + if (flags & F_SRV) + { + unsigned char *tmp = namep; + + if (!CHECK_LEN(header, p1, qlen, 6)) + return 0; /* bad packet */ + GETSHORT(addr.srv.priority, p1); + GETSHORT(addr.srv.weight, p1); + GETSHORT(addr.srv.srvport, p1); + if (!extract_name(header, qlen, &p1, name, 1, 0)) + return 0; + addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */ + if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen))) + return 0; + + /* we overwrote the original name, so get it back here. */ + if (!extract_name(header, qlen, &tmp, name, 1, 0)) + return 0; + } + else if (flags & (F_IPV4 | F_IPV6)) + { + /* copy address into aligned storage */ + if (!CHECK_LEN(header, p1, qlen, addrlen)) + return 0; /* bad packet */ + memcpy(&addr, p1, addrlen); + + /* check for returned address in private space */ + if (check_rebind) { - found = 1; + if ((flags & F_IPV4) && + private_net(addr.addr4, !option_bool(OPT_LOCAL_REBIND))) + return 1; - if (flags & F_SRV) - { - unsigned char *tmp = namep; - - if (!CHECK_LEN(header, p1, qlen, 6)) - return 0; /* bad packet */ - GETSHORT(addr.srv.priority, p1); - GETSHORT(addr.srv.weight, p1); - GETSHORT(addr.srv.srvport, p1); - if (!extract_name(header, qlen, &p1, name, 1, 0)) - return 0; - addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */ - if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen))) - return 0; - - /* we overwrote the original name, so get it back here. */ - if (!extract_name(header, qlen, &tmp, name, 1, 0)) - return 0; - } - else - { - /* copy address into aligned storage */ - if (!CHECK_LEN(header, p1, qlen, addrlen)) - return 0; /* bad packet */ - memcpy(&addr, p1, addrlen); - - /* check for returned address in private space */ - if (check_rebind) - { - if ((flags & F_IPV4) && - private_net(addr.addr4, !option_bool(OPT_LOCAL_REBIND))) - return 1; - - /* Block IPv4-mapped IPv6 addresses in private IPv4 address space */ - if (flags & F_IPV6) - { - if (IN6_IS_ADDR_V4MAPPED(&addr.addr6)) - { - struct in_addr v4; - v4.s_addr = ((const uint32_t *) (&addr.addr6))[3]; - if (private_net(v4, !option_bool(OPT_LOCAL_REBIND))) - return 1; - } - - /* Check for link-local (LL) and site-local (ULA) IPv6 addresses */ - if (IN6_IS_ADDR_LINKLOCAL(&addr.addr6) || - IN6_IS_ADDR_SITELOCAL(&addr.addr6)) - return 1; - - /* Check for the IPv6 loopback address (::1) when - option rebind-localhost-ok is NOT set */ - if (!option_bool(OPT_LOCAL_REBIND) && - IN6_IS_ADDR_LOOPBACK(&addr.addr6)) - return 1; - } - } - + if ((flags & F_IPV6) && + private_net6(&addr.addr6, !option_bool(OPT_LOCAL_REBIND))) + return 1; + } + #ifdef HAVE_IPSET - if (ipsets && (flags & (F_IPV4 | F_IPV6))) - { - ipsets_cur = ipsets; - while (*ipsets_cur) - { - log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); - add_to_ipset(*ipsets_cur++, &addr, flags, 0); - } - } -#endif - } - - newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag); - if (newc && cpp) + if (ipsets && (flags & (F_IPV4 | F_IPV6))) + { + ipsets_cur = ipsets; + while (*ipsets_cur) { - next_uid(newc); - cpp->addr.cname.target.cache = newc; - cpp->addr.cname.uid = newc->uid; + log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur); + add_to_ipset(*ipsets_cur++, &addr, flags, 0); } - cpp = NULL; } +#endif } - p1 = endrr; - if (!CHECK_LEN(header, p1, qlen, 0)) - return 0; /* bad packet */ + if (insert) + { + newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag); + if (newc && cpp) + { + next_uid(newc); + cpp->addr.cname.target.cache = newc; + cpp->addr.cname.uid = newc->uid; + } + cpp = NULL; + } + + if (aqtype == T_TXT) + { + if (!print_txt(header, qlen, name, p1, ardlen, secflag)) + return 0; + } + else + log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, querystr(NULL, aqtype)); } - if (!found && !option_bool(OPT_NO_NEG)) + p1 = endrr; + if (!CHECK_LEN(header, p1, qlen, 0)) + return 0; /* bad packet */ + } + + if (!found && !option_bool(OPT_NO_NEG)) + { + if (!searched_soa) { - if (!searched_soa) - { - searched_soa = 1; - ttl = find_soa(header, qlen, NULL, doctored); - } - /* If there's no SOA to get the TTL from, but there is a CNAME - pointing at this, inherit its TTL */ - if (ttl || cpp) + searched_soa = 1; + ttl = find_soa(header, qlen, doctored); + } + + /* If there's no SOA to get the TTL from, but there is a CNAME + pointing at this, inherit its TTL */ + if (ttl || cpp) + { + if (ttl == 0) + ttl = cttl; + + log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL); + + if (insert) { - newc = cache_insert(name, NULL, C_IN, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); + newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); if (newc && cpp) { next_uid(newc); @@ -884,6 +921,92 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t return 0; } +#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) +/* Don't pass control chars and weird escapes to UBus. */ +static int safe_name(char *name) +{ + unsigned char *r; + + for (r = (unsigned char *)name; *r; r++) + if (!isprint((int)*r)) + return 0; + + return 1; +} + +void report_addresses(struct dns_header *header, size_t len, u32 mark) +{ + unsigned char *p, *endrr; + int i; + unsigned long attl; + struct allowlist *allowlists; + char **pattern_pos; + + if (RCODE(header) != NOERROR) + return; + + for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next) + if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask)) + for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++) + if (!strcmp(*pattern_pos, "*")) + return; + + if (!(p = skip_questions(header, len))) + return; + for (i = ntohs(header->ancount); i != 0; i--) + { + int aqtype, aqclass, ardlen; + + if (!extract_name(header, len, &p, daemon->namebuff, 1, 10)) + return; + + if (!CHECK_LEN(header, p, len, 10)) + return; + GETSHORT(aqtype, p); + GETSHORT(aqclass, p); + GETLONG(attl, p); + GETSHORT(ardlen, p); + + if (!CHECK_LEN(header, p, len, ardlen)) + return; + endrr = p+ardlen; + + if (aqclass == C_IN) + { + if (aqtype == T_CNAME) + { + if (!extract_name(header, len, &p, daemon->workspacename, 1, 0)) + return; + if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename)) + ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, attl); + } + if (aqtype == T_A) + { + struct in_addr addr; + char ip[INET_ADDRSTRLEN]; + if (ardlen != INADDRSZ) + return; + memcpy(&addr, p, ardlen); + if (inet_ntop(AF_INET, &addr, ip, sizeof ip) && safe_name(daemon->namebuff)) + ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl); + } + else if (aqtype == T_AAAA) + { + struct in6_addr addr; + char ip[INET6_ADDRSTRLEN]; + if (ardlen != IN6ADDRSZ) + return; + memcpy(&addr, p, ardlen); + if (inet_ntop(AF_INET6, &addr, ip, sizeof ip) && safe_name(daemon->namebuff)) + ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl); + } + } + + p = endrr; + } +} +#endif + /* If the packet holds exactly one query return F_IPV4 or F_IPV6 and leave the name from the query in name */ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep) @@ -894,9 +1017,14 @@ 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. */ + if (!(header->hb3 & HB3_QR) && (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0)) + return 0; /* non-standard query. */ + if (!extract_name(header, qlen, &p, name, 1, 4)) return 0; /* bad packet */ @@ -916,24 +1044,20 @@ unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, return F_IPV4 | F_IPV6; } +#ifdef HAVE_DNSSEC /* F_DNSSECOK as agument to search_servers() inhibits forwarding to servers for domains without a trust anchor. This make the behaviour for DS and DNSKEY queries we forward the same as for DS and DNSKEY queries we originate. */ - if (qtype == T_DS || qtype == T_DNSKEY) + if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY)) return F_DNSSECOK; +#endif 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, int ede) { - 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,40 +1070,19 @@ 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 */ { union all_addr a; a.log.rcode = REFUSED; + a.log.ede = ede; 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. */ @@ -1017,47 +1120,61 @@ int check_for_local_domain(char *name, time_t now) return 0; } -/* Is the packet a reply with the answer address equal to addr? - If so mung is into an NXDOMAIN reply and also put that information - in the cache. */ -int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, - struct bogus_addr *baddr, time_t now) +static int check_bad_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr, char *name, unsigned long *ttlp) { unsigned char *p; int i, qtype, qclass, rdlen; unsigned long ttl; struct bogus_addr *baddrp; - + /* skip over questions */ if (!(p = skip_questions(header, qlen))) return 0; /* bad packet */ for (i = ntohs(header->ancount); i != 0; i--) { - if (!extract_name(header, qlen, &p, name, 1, 10)) + if (name && !extract_name(header, qlen, &p, name, 1, 10)) return 0; /* bad packet */ - + + if (!name && !(p = skip_name(p, header, qlen, 10))) + return 0; + GETSHORT(qtype, p); GETSHORT(qclass, p); GETLONG(ttl, p); GETSHORT(rdlen, p); + + if (ttlp) + *ttlp = ttl; - if (qclass == C_IN && qtype == T_A) + if (qclass == C_IN) { - if (!CHECK_LEN(header, p, qlen, INADDRSZ)) - return 0; - - for (baddrp = baddr; baddrp; baddrp = baddrp->next) - if (memcmp(&baddrp->addr, p, INADDRSZ) == 0) - { - /* Found a bogus address. Insert that info here, since there no SOA record - to get the ttl from in the normal processing */ - cache_start_insert(); - cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); - cache_end_insert(); - - return 1; - } + if (qtype == T_A) + { + struct in_addr addr; + + if (!CHECK_LEN(header, p, qlen, INADDRSZ)) + return 0; + + memcpy(&addr, p, INADDRSZ); + + for (baddrp = baddr; baddrp; baddrp = baddrp->next) + if (!baddrp->is6 && is_same_net_prefix(addr, baddrp->addr.addr4, baddrp->prefix)) + return 1; + } + else if (qtype == T_AAAA) + { + struct in6_addr addr; + + if (!CHECK_LEN(header, p, qlen, IN6ADDRSZ)) + return 0; + + memcpy(&addr, p, IN6ADDRSZ); + + for (baddrp = baddr; baddrp; baddrp = baddrp->next) + if (baddrp->is6 && is_same_net6(&addr, &baddrp->addr.addr6, baddrp->prefix)) + return 1; + } } if (!ADD_RDLEN(header, p, qlen, rdlen)) @@ -1067,43 +1184,31 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, return 0; } -int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr) +/* Is the packet a reply with the answer address equal to addr? + If so mung is into an NXDOMAIN reply and also put that information + in the cache. */ +int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, time_t now) { - unsigned char *p; - int i, qtype, qclass, rdlen; - struct bogus_addr *baddrp; - - /* skip over questions */ - if (!(p = skip_questions(header, qlen))) - return 0; /* bad packet */ + unsigned long ttl; - for (i = ntohs(header->ancount); i != 0; i--) + if (check_bad_address(header, qlen, daemon->bogus_addr, name, &ttl)) { - if (!(p = skip_name(p, header, qlen, 10))) - return 0; /* bad packet */ - - GETSHORT(qtype, p); - GETSHORT(qclass, p); - p += 4; /* TTL */ - GETSHORT(rdlen, p); - - if (qclass == C_IN && qtype == T_A) - { - if (!CHECK_LEN(header, p, qlen, INADDRSZ)) - return 0; - - for (baddrp = baddr; baddrp; baddrp = baddrp->next) - if (memcmp(&baddrp->addr, p, INADDRSZ) == 0) - return 1; - } - - if (!ADD_RDLEN(header, p, qlen, rdlen)) - return 0; + /* Found a bogus address. Insert that info here, since there no SOA record + to get the ttl from in the normal processing */ + cache_start_insert(); + cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); + cache_end_insert(); + + return 1; } - + return 0; } +int check_for_ignored_address(struct dns_header *header, size_t qlen) +{ + return check_bad_address(header, qlen, daemon->ignore_addr, NULL, NULL); +} int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...) @@ -1566,43 +1671,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, 1)) || (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); } } diff --git a/src/rfc2131.c b/src/rfc2131.c index d678068..c902eb7 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -372,9 +372,22 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (!context) { - my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"), - subnet_addr.s_addr ? _("with subnet selector") : _("via"), - subnet_addr.s_addr ? inet_ntoa(subnet_addr) : (mess->giaddr.s_addr ? inet_ntoa(mess->giaddr) : iface_name)); + const char *via; + if (subnet_addr.s_addr) + { + via = _("with subnet selector"); + inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN); + } + else + { + via = _("via"); + if (mess->giaddr.s_addr) + inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN); + else + safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN); + } + my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"), + via, daemon->addrbuff); return 0; } @@ -383,13 +396,19 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, struct dhcp_context *context_tmp; for (context_tmp = context; context_tmp; context_tmp = context_tmp->current) { - strcpy(daemon->namebuff, inet_ntoa(context_tmp->start)); + inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME); if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) - my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"), - ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->netmask)); + { + inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"), + ntohl(mess->xid), daemon->namebuff, daemon->addrbuff); + } else - my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), - ntohl(mess->xid), daemon->namebuff, inet_ntoa(context_tmp->end)); + { + inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), + ntohl(mess->xid), daemon->namebuff, daemon->addrbuff); + } } } @@ -1031,8 +1050,9 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, config->addr.s_addr == option_addr(opt).s_addr) { prettyprint_time(daemon->dhcp_buff, DECLINE_BACKOFF); + inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), - inet_ntoa(config->addr), daemon->dhcp_buff); + daemon->addrbuff, daemon->dhcp_buff); config->flags |= CONFIG_DECLINED; config->decline_time = now; } @@ -1078,7 +1098,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (have_config(config, CONFIG_ADDR)) { - char *addrs = inet_ntoa(config->addr); + inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN); if ((ltmp = lease_find_by_addr(config->addr)) && ltmp != lease && @@ -1088,7 +1108,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, unsigned char *mac = extended_hwaddr(ltmp->hwaddr_type, ltmp->hwaddr_len, ltmp->hwaddr, ltmp->clid_len, ltmp->clid, &len); my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s"), - addrs, print_mac(daemon->namebuff, mac, len)); + daemon->addrbuff, print_mac(daemon->namebuff, mac, len)); } else { @@ -1097,10 +1117,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (context->router.s_addr == config->addr.s_addr) break; if (tmp) - my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is in use by the server or relay"), addrs); + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is in use by the server or relay"), daemon->addrbuff); else if (have_config(config, CONFIG_DECLINED) && difftime(now, config->decline_time) < (float)DECLINE_BACKOFF) - my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), addrs); + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), daemon->addrbuff); else conf = config->addr; } @@ -1303,9 +1323,10 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, a lease from one of it's MACs to give the address to another. */ if (config && config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type)) { + inet_ntop(AF_INET, <mp->addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"), print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len), - inet_ntoa(ltmp->addr)); + daemon->addrbuff); lease = ltmp; } else @@ -1674,42 +1695,40 @@ static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt) static void log_packet(char *type, void *addr, unsigned char *ext_mac, int mac_len, char *interface, char *string, char *err, u32 xid) { - struct in_addr a; - if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP)) return; - /* addr may be misaligned */ + daemon->addrbuff[0] = 0; if (addr) - memcpy(&a, addr, sizeof(a)); + inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN); print_mac(daemon->namebuff, ext_mac, mac_len); - if(option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s", - ntohl(xid), - type, - interface, - addr ? inet_ntoa(a) : "", - addr ? " " : "", - daemon->namebuff, - string ? string : "", - err ? err : ""); + if (option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s", + ntohl(xid), + type, + interface, + daemon->addrbuff, + addr ? " " : "", + daemon->namebuff, + string ? string : "", + err ? err : ""); else my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s", type, interface, - addr ? inet_ntoa(a) : "", + daemon->addrbuff, addr ? " " : "", daemon->namebuff, string ? string : "", err ? err : ""); - + #ifdef HAVE_UBUS - if (!strcmp(type, "DHCPACK")) - ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); - else if (!strcmp(type, "DHCPRELEASE")) - ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); + if (!strcmp(type, "DHCPACK")) + ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface); + else if (!strcmp(type, "DHCPRELEASE")) + ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface); #endif } @@ -1861,7 +1880,10 @@ static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id if (option_bool(OPT_LOG_OPTS)) { if (mess->siaddr.s_addr != 0) - my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), inet_ntoa(mess->siaddr)); + { + inet_ntop(AF_INET, &mess->siaddr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), daemon->addrbuff); + } if ((mess->flags & htons(0x8000)) && mess->ciaddr.s_addr == 0) my_syslog(MS_DHCP | LOG_INFO, _("%u broadcast response"), ntohl(mess->xid)); @@ -2785,11 +2807,4 @@ static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid) } } -#endif - - - - - - - +#endif /* HAVE_DHCP */ diff --git a/src/rfc3315.c b/src/rfc3315.c index b3f0a0a..5c2ff97 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -919,11 +919,14 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ case DHCP6RENEW: + case DHCP6REBIND: { + int address_assigned = 0; + /* set reply message type */ *outmsgtypep = DHCP6REPLY; - log6_quiet(state, "DHCPRENEW", NULL, NULL); + log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL); for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end)) { @@ -952,24 +955,35 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state->iaid, &req_addr))) { - /* If the server cannot find a client entry for the IA the server - returns the IA containing no addresses with a Status Code option set - to NoBinding in the Reply message. */ - save_counter(iacntr); - t1cntr = 0; - - log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found")); - - o1 = new_opt6(OPTION6_STATUS_CODE); - put_opt6_short(DHCP6NOBINDING); - put_opt6_string(_("no binding found")); - end_opt6(o1); - - preferred_time = valid_time = 0; - break; + if (msg_type == DHCP6REBIND) + { + /* When rebinding, we can create a lease if it doesn't exist. */ + lease = lease6_allocate(&req_addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA); + if (lease) + lease_set_iaid(lease, state->iaid); + else + break; + } + else + { + /* If the server cannot find a client entry for the IA the server + returns the IA containing no addresses with a Status Code option set + to NoBinding in the Reply message. */ + save_counter(iacntr); + t1cntr = 0; + + log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found")); + + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOBINDING); + put_opt6_string(_("no binding found")); + end_opt6(o1); + + preferred_time = valid_time = 0; + break; + } } - if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) || (this_context = address6_valid(state->context, &req_addr, tagif, 1))) { @@ -1000,6 +1014,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ if (preferred_time == 0) message = _("deprecated"); + + address_assigned = 1; } else { @@ -1022,10 +1038,18 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_ end_ia(t1cntr, min_time, 1); end_opt6(o); } + + if (!address_assigned && msg_type == DHCP6REBIND) + { + /* can't create lease for any address, return error */ + o1 = new_opt6(OPTION6_STATUS_CODE); + put_opt6_short(DHCP6NOADDRS); + put_opt6_string(_("no addresses available")); + end_opt6(o1); + } tagif = add_options(state, 0); break; - } case DHCP6CONFIRM: diff --git a/src/rrfilter.c b/src/rrfilter.c index 16e6b55..58c6d8f 100644 --- a/src/rrfilter.c +++ b/src/rrfilter.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/slaac.c b/src/slaac.c index 4a3e01d..9b10063 100644 --- a/src/slaac.c +++ b/src/slaac.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 diff --git a/src/tables.c b/src/tables.c index b34ea77..ccfe9ed 100644 --- a/src/tables.c +++ b/src/tables.c @@ -98,7 +98,7 @@ int add_to_ipset(const char *setname, const union all_addr *ipaddr, io.pfrio_size = 1; if (ioctl(dev, DIOCRADDTABLES, &io)) { - my_syslog(LOG_WARNING, _("IPset: error:%s"), pfr_strerror(errno)); + my_syslog(LOG_WARNING, _("IPset: error: %s"), pfr_strerror(errno)); return -1; } @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -22,7 +22,7 @@ static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len) static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix); static void free_transfer(struct tftp_transfer *transfer); static ssize_t tftp_err(int err, char *packet, char *message, char *file); -static ssize_t tftp_err_oops(char *packet, char *file); +static ssize_t tftp_err_oops(char *packet, const char *file); static ssize_t get_block(char *packet, struct tftp_transfer *transfer); static char *next(char **p, char *end); static void sanitise(char *buf); @@ -39,6 +39,7 @@ static void sanitise(char *buf); #define ERR_PERM 2 #define ERR_FULL 3 #define ERR_ILL 4 +#define ERR_TID 5 void tftp_request(struct listener *listen, time_t now) { @@ -94,7 +95,7 @@ void tftp_request(struct listener *listen, time_t now) if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) return; - + /* Can always get recvd interface for IPv6 */ if (!check_dest) { @@ -583,11 +584,27 @@ void check_tftp_listeners(time_t now) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) if (poll_check(transfer->sockfd, POLLIN)) { + union mysockaddr peer; + socklen_t addr_len = sizeof(union mysockaddr); + ssize_t len; + /* we overwrote the buffer... */ daemon->srv_save = NULL; - handle_tftp(now, transfer, recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)); - } + if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0) + { + if (sockaddr_isequal(&peer, &transfer->peer)) + handle_tftp(now, transfer, len); + else + { + /* Wrong source address. See rfc1350 para 4. */ + prettyprint_addr(&peer, daemon->addrbuff); + len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff); + sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer)); + } + } + } + for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) { tmp = transfer->next; @@ -602,7 +619,7 @@ void check_tftp_listeners(time_t now) /* we overwrote the buffer... */ daemon->srv_save = NULL; - + if ((len = get_block(daemon->packet, transfer)) == -1) { len = tftp_err_oops(daemon->packet, transfer->file->filename); @@ -736,22 +753,25 @@ static ssize_t tftp_err(int err, char *packet, char *message, char *file) char *errstr = strerror(errno); memset(packet, 0, daemon->packet_buff_sz); - sanitise(file); + if (file) + sanitise(file); mess->op = htons(OP_ERR); mess->err = htons(err); len = snprintf(mess->message, MAXMESSAGE, message, file, errstr); ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */ - my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); + if (err != ERR_FNF || !option_bool(OPT_QUIET_TFTP)) + my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); return ret; } -static ssize_t tftp_err_oops(char *packet, char *file) +static ssize_t tftp_err_oops(char *packet, const char *file) { /* May have >1 refs to file, so potentially mangle a copy of the name */ - strcpy(daemon->namebuff, file); + if (file != daemon->namebuff) + strcpy(daemon->namebuff, file); return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff); } @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -21,17 +21,44 @@ #include <libubus.h> static struct blob_buf b; -static int notify; static int error_logged = 0; static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg); +#ifdef HAVE_CONNTRACK +enum { + SET_CONNMARK_ALLOWLIST_MARK, + SET_CONNMARK_ALLOWLIST_MASK, + SET_CONNMARK_ALLOWLIST_PATTERNS +}; +static const struct blobmsg_policy set_connmark_allowlist_policy[] = { + [SET_CONNMARK_ALLOWLIST_MARK] = { + .name = "mark", + .type = BLOBMSG_TYPE_INT32 + }, + [SET_CONNMARK_ALLOWLIST_MASK] = { + .name = "mask", + .type = BLOBMSG_TYPE_INT32 + }, + [SET_CONNMARK_ALLOWLIST_PATTERNS] = { + .name = "patterns", + .type = BLOBMSG_TYPE_ARRAY + } +}; +static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg); +#endif + static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj); static const struct ubus_method ubus_object_methods[] = { UBUS_METHOD_NOARG("metrics", ubus_handle_metrics), +#ifdef HAVE_CONNTRACK + UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy), +#endif }; static struct ubus_object_type ubus_object_type = @@ -50,17 +77,16 @@ static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj) (void)ctx; my_syslog(LOG_DEBUG, _("UBus subscription callback: %s subscriber(s)"), obj->has_subscribers ? "1" : "0"); - notify = obj->has_subscribers; } static void ubus_destroy(struct ubus_context *ubus) { - // Forces re-initialization when we're reusing the same definitions later on. - ubus_object.id = 0; - ubus_object_type.id = 0; - ubus_free(ubus); daemon->ubus = NULL; + + /* Forces re-initialization when we're reusing the same definitions later on. */ + ubus_object.id = 0; + ubus_object_type.id = 0; } static void ubus_disconnect_cb(struct ubus_context *ubus) @@ -76,42 +102,27 @@ static void ubus_disconnect_cb(struct ubus_context *ubus) } } -void ubus_init() +char *ubus_init() { struct ubus_context *ubus = NULL; int ret = 0; - ubus = ubus_connect(NULL); - if (!ubus) - { - if (!error_logged) - { - my_syslog(LOG_ERR, _("Cannot initialize UBus: connection failed")); - error_logged = 1; - } - - ubus_destroy(ubus); - return; - } - + if (!(ubus = ubus_connect(NULL))) + return NULL; + ubus_object.name = daemon->ubus_name; ret = ubus_add_object(ubus, &ubus_object); if (ret) { - if (!error_logged) - { - my_syslog(LOG_ERR, _("Cannot add object to UBus: %s"), ubus_strerror(ret)); - error_logged = 1; - } ubus_destroy(ubus); - return; - } - + return (char *)ubus_strerror(ret); + } + ubus->connection_lost = ubus_disconnect_cb; daemon->ubus = ubus; error_logged = 0; - my_syslog(LOG_INFO, _("Connected to system UBus")); + return NULL; } void set_ubus_listeners() @@ -160,6 +171,16 @@ void check_ubus_listeners() } } +#define CHECK(stmt) \ + do { \ + int e = (stmt); \ + if (e) \ + { \ + my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \ + return (UBUS_STATUS_UNKNOWN_ERROR); \ + } \ + } while (0) + static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) @@ -170,36 +191,196 @@ static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj (void)method; (void)msg; - blob_buf_init(&b, BLOBMSG_TYPE_TABLE); + CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE)); for (i=0; i < __METRIC_MAX; i++) - blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i]); + CHECK(blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i])); - return ubus_send_reply(ctx, req, b.head); + CHECK(ubus_send_reply(ctx, req, b.head)); + return UBUS_STATUS_OK; } +#ifdef HAVE_CONNTRACK +static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + const struct blobmsg_policy *policy = set_connmark_allowlist_policy; + size_t policy_len = countof(set_connmark_allowlist_policy); + struct allowlist *allowlists = NULL, **allowlists_pos; + char **patterns = NULL, **patterns_pos; + u32 mark, mask = UINT32_MAX; + size_t num_patterns = 0; + struct blob_attr *tb[policy_len]; + struct blob_attr *attr; + + if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg))) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (!tb[SET_CONNMARK_ALLOWLIST_MARK]) + return UBUS_STATUS_INVALID_ARGUMENT; + mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]); + if (!mark) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (tb[SET_CONNMARK_ALLOWLIST_MASK]) + { + mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]); + if (!mask || (mark & ~mask)) + return UBUS_STATUS_INVALID_ARGUMENT; + } + + if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) + { + struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); + size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); + __blob_for_each_attr(attr, head, len) + { + char *pattern; + if (blob_id(attr) != BLOBMSG_TYPE_STRING) + return UBUS_STATUS_INVALID_ARGUMENT; + if (!(pattern = blobmsg_get_string(attr))) + return UBUS_STATUS_INVALID_ARGUMENT; + if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern)) + return UBUS_STATUS_INVALID_ARGUMENT; + num_patterns++; + } + } + + for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next) + if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask) + { + struct allowlist *allowlists_next = (*allowlists_pos)->next; + for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++) + { + free(*patterns_pos); + *patterns_pos = NULL; + } + free((*allowlists_pos)->patterns); + (*allowlists_pos)->patterns = NULL; + free(*allowlists_pos); + *allowlists_pos = allowlists_next; + break; + } + + if (!num_patterns) + return UBUS_STATUS_OK; + + patterns = whine_malloc((num_patterns + 1) * sizeof(char *)); + if (!patterns) + goto fail; + patterns_pos = patterns; + if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS]) + { + struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); + size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]); + __blob_for_each_attr(attr, head, len) + { + char *pattern; + if (!(pattern = blobmsg_get_string(attr))) + goto fail; + if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1))) + goto fail; + strcpy(*patterns_pos++, pattern); + } + } + + allowlists = whine_malloc(sizeof(struct allowlist)); + if (!allowlists) + goto fail; + memset(allowlists, 0, sizeof(struct allowlist)); + allowlists->mark = mark; + allowlists->mask = mask; + allowlists->patterns = patterns; + allowlists->next = daemon->allowlists; + daemon->allowlists = allowlists; + return UBUS_STATUS_OK; + +fail: + if (patterns) + { + for (patterns_pos = patterns; *patterns_pos; patterns_pos++) + { + free(*patterns_pos); + *patterns_pos = NULL; + } + free(patterns); + patterns = NULL; + } + if (allowlists) + { + free(allowlists); + allowlists = NULL; + } + return UBUS_STATUS_UNKNOWN_ERROR; +} +#endif + +#undef CHECK + +#define CHECK(stmt) \ + do { \ + int e = (stmt); \ + if (e) \ + { \ + my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \ + return; \ + } \ + } while (0) + void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface) { struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; - int ret; - if (!ubus || !notify) + if (!ubus || !ubus_object.has_subscribers) return; - blob_buf_init(&b, BLOBMSG_TYPE_TABLE); + CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE)); if (mac) - blobmsg_add_string(&b, "mac", mac); + CHECK(blobmsg_add_string(&b, "mac", mac)); if (ip) - blobmsg_add_string(&b, "ip", ip); + CHECK(blobmsg_add_string(&b, "ip", ip)); if (name) - blobmsg_add_string(&b, "name", name); + CHECK(blobmsg_add_string(&b, "name", name)); if (interface) - blobmsg_add_string(&b, "interface", interface); + CHECK(blobmsg_add_string(&b, "interface", interface)); + + CHECK(ubus_notify(ubus, &ubus_object, type, b.head, -1)); +} + +#ifdef HAVE_CONNTRACK +void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name) +{ + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; + + if (!ubus || !ubus_object.has_subscribers) + return; + + CHECK(blob_buf_init(&b, 0)); + CHECK(blobmsg_add_u32(&b, "mark", mark)); + CHECK(blobmsg_add_string(&b, "name", name)); + + CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1)); +} + +void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *name, const char *value, u32 ttl) +{ + struct ubus_context *ubus = (struct ubus_context *)daemon->ubus; + + if (!ubus || !ubus_object.has_subscribers) + return; + + CHECK(blob_buf_init(&b, 0)); + CHECK(blobmsg_add_u32(&b, "mark", mark)); + CHECK(blobmsg_add_string(&b, "name", name)); + CHECK(blobmsg_add_string(&b, "value", value)); + CHECK(blobmsg_add_u32(&b, "ttl", ttl)); - ret = ubus_notify(ubus, &ubus_object, type, b.head, -1); - if (!ret) - my_syslog(LOG_ERR, _("Failed to send UBus event: %s"), ubus_strerror(ret)); + /* Set timeout to allow UBus subscriber to configure firewall rules before returning. */ + CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000)); } +#endif +#undef CHECK #endif /* HAVE_UBUS */ @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2020 Simon Kelley +/* 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 @@ -316,7 +316,7 @@ void *whine_malloc(size_t size) return ret; } -int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) +int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) { if (s1->sa.sa_family == s2->sa.sa_family) { @@ -436,7 +436,17 @@ int netmask_length(struct in_addr mask) int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask) { return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr); -} +} + +int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix) +{ + struct in_addr mask; + + mask.s_addr = htonl(~((1 << (32 - prefix)) - 1)); + + return is_same_net(a, b, mask); +} + int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen) { |