diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab1cc455..15a56bca7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,6 @@ cmake_minimum_required(VERSION 2.8.12) project(PIHOLE_FTL C) -set(DNSMASQ_VERSION pi-hole-v2.87) +set(DNSMASQ_VERSION pi-hole-v2.88test3) add_subdirectory(src) diff --git a/src/database/query-table.c b/src/database/query-table.c index ed5320660..bf2bdaafa 100644 --- a/src/database/query-table.c +++ b/src/database/query-table.c @@ -988,6 +988,7 @@ void DB_read_queries(void) break; case QUERY_CACHE: // Cached or local config + case QUERY_CACHE_STALE: // Nothing to be done here break; diff --git a/src/datastructure.c b/src/datastructure.c index 9a69b35c8..a2bf2a2f5 100644 --- a/src/datastructure.c +++ b/src/datastructure.c @@ -530,6 +530,7 @@ bool __attribute__ ((const)) is_blocked(const enum query_status status) case QUERY_RETRIED: case QUERY_RETRIED_DNSSEC: case QUERY_IN_PROGRESS: + case QUERY_CACHE_STALE: case QUERY_STATUS_MAX: default: return false; @@ -549,32 +550,58 @@ bool __attribute__ ((const)) is_blocked(const enum query_status status) } } -static const char *query_status_str[QUERY_STATUS_MAX] = { - "UNKNOWN", - "GRAVITY", - "FORWARDED", - "CACHE", - "REGEX", - "BLACKLIST", - "EXTERNAL_BLOCKED_IP", - "EXTERNAL_BLOCKED_NULL", - "EXTERNAL_BLOCKED_NXRA", - "GRAVITY_CNAME", - "REGEX_CNAME", - "BLACKLIST_CNAME", - "RETRIED", - "RETRIED_DNSSEC", - "IN_PROGRESS", - "DBBUSY", - "SPECIAL_DOMAIN" -}; +static const char* __attribute__ ((const)) query_status_str(const enum query_status status) +{ + switch (status) + { + case QUERY_UNKNOWN: + return "UNKNOWN"; + case QUERY_GRAVITY: + return "GRAVITY"; + case QUERY_FORWARDED: + return "FORWARDED"; + case QUERY_CACHE: + return "CACHE"; + case QUERY_REGEX: + return "REGEX"; + case QUERY_BLACKLIST: + return "BLACKLIST"; + case QUERY_EXTERNAL_BLOCKED_IP: + return "EXTERNAL_BLOCKED_IP"; + case QUERY_EXTERNAL_BLOCKED_NULL: + return "EXTERNAL_BLOCKED_NULL"; + case QUERY_EXTERNAL_BLOCKED_NXRA: + return "EXTERNAL_BLOCKED_NXRA"; + case QUERY_GRAVITY_CNAME: + return "GRAVITY_CNAME"; + case QUERY_REGEX_CNAME: + return "REGEX_CNAME"; + case QUERY_BLACKLIST_CNAME: + return "BLACKLIST_CNAME"; + case QUERY_RETRIED: + return "RETRIED"; + case QUERY_RETRIED_DNSSEC: + return "RETRIED_DNSSEC"; + case QUERY_IN_PROGRESS: + return "IN_PROGRESS"; + case QUERY_DBBUSY: + return "DBBUSY"; + case QUERY_SPECIAL_DOMAIN: + return "SPECIAL_DOMAIN"; + case QUERY_CACHE_STALE: + return "CACHE_STALE"; + case QUERY_STATUS_MAX: + return NULL; + } + return NULL; +} void _query_set_status(queriesData *query, const enum query_status new_status, const char *func, const int line, const char *file) { // Debug logging if(config.debug & DEBUG_STATUS) { - const char *oldstr = query->status < QUERY_STATUS_MAX ? query_status_str[query->status] : "INVALID"; + const char *oldstr = query->status < QUERY_STATUS_MAX ? query_status_str(query->status) : "INVALID"; if(query->status == new_status) { logg("Query %i: status unchanged: %s (%d) in %s() (%s:%i)", @@ -582,7 +609,7 @@ void _query_set_status(queriesData *query, const enum query_status new_status, c } else { - const char *newstr = new_status < QUERY_STATUS_MAX ? query_status_str[new_status] : "INVALID"; + const char *newstr = new_status < QUERY_STATUS_MAX ? query_status_str(new_status) : "INVALID"; logg("Query %i: status changed: %s (%d) -> %s (%d) in %s() (%s:%i)", query->id, oldstr, query->status, newstr, new_status, func, short_path(file), line); } diff --git a/src/dnsmasq/cache.c b/src/dnsmasq/cache.c index 0d1108618..5b3ceb438 100644 --- a/src/dnsmasq/cache.c +++ b/src/dnsmasq/cache.c @@ -190,7 +190,7 @@ void rehash(int size) else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec *)))) return; - for(i = 0; i < new_size; i++) + for (i = 0; i < new_size; i++) new[i] = NULL; old = hash_table; @@ -234,7 +234,8 @@ static void cache_hash(struct crec *crecp) immortal entries are at the end of the hash-chain. This allows reverse searches and garbage collection to be optimised */ - struct crec **up = hash_bucket(cache_get_name(crecp)); + char *name = cache_get_name(crecp); + struct crec **up = hash_bucket(name); if (!(crecp->flags & F_REVERSE)) { @@ -245,6 +246,11 @@ static void cache_hash(struct crec *crecp) while (*up && !((*up)->flags & F_IMMORTAL)) up = &((*up)->hash_next); } + + /* Preserve order when inserting the same name multiple times. */ + while (*up && hostname_isequal(cache_get_name(*up), name)) + up = &((*up)->hash_next); + crecp->hash_next = *up; *up = crecp; } @@ -375,6 +381,11 @@ static int is_outdated_cname_pointer(struct crec *crecp) static int is_expired(time_t now, struct crec *crecp) { + /* Don't dump expired entries if we're using them, cache becomes strictly LRU in that case. + Never use expired DS or DNSKEY entries. */ + if (option_bool(OPT_STALE_CACHE) && !(crecp->flags & (F_DS | F_DNSKEY))) + return 0; + if (crecp->flags & F_IMMORTAL) return 0; @@ -384,6 +395,27 @@ static int is_expired(time_t now, struct crec *crecp) return 1; } +/* Remove entries with a given UID from the cache */ +unsigned int cache_remove_uid(const unsigned int uid) +{ + int i; + unsigned int removed = 0; + struct crec *crecp, **up; + + for (i = 0; i < hash_size; i++) + for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = crecp->hash_next) + if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid) + { + *up = crecp->hash_next; + free(crecp); + removed++; + } + else + up = &crecp->hash_next; + + return removed; +} + static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now, unsigned int flags, struct crec **target_crec, unsigned int *target_uid) { @@ -554,7 +586,7 @@ static struct crec *really_insert(char *name, union all_addr *addr, unsigned sho { struct crec *new, *target_crec = NULL; union bigname *big_name = NULL; - int freed_all = flags & F_REVERSE; + int freed_all = (flags & F_REVERSE); int free_avail = 0; unsigned int target_uid; @@ -629,8 +661,12 @@ static struct crec *really_insert(char *name, union all_addr *addr, unsigned sho { /* For DNSSEC records, uid holds class. */ free_avail = 1; /* Must be free space now. */ + + /* condition valid when stale-caching */ + if (difftime(now, new->ttd) < 0) + daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++; + cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL); - daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++; } else { @@ -693,7 +729,7 @@ static struct crec *really_insert(char *name, union all_addr *addr, unsigned sho new->ttd = now + (time_t)ttl; new->next = new_chain; new_chain = new; - + return new; } @@ -871,7 +907,7 @@ int cache_find_non_terminal(char *name, time_t now) struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot) { struct crec *ans; - int no_rr = prot & F_NO_RR; + int no_rr = (prot & F_NO_RR) || option_bool(OPT_NORR); prot &= ~F_NO_RR; @@ -1019,16 +1055,17 @@ struct crec *cache_find_by_addr(struct crec *crecp, union all_addr *addr, void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen, unsigned int index, struct crec **rhash, int hashsz) { - struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)); int i; unsigned int j; + struct crec *lookup = NULL; /* Remove duplicates in hosts files. */ - if (lookup && (lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0) - { - free(cache); - return; - } + while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6)))) + if ((lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0) + { + free(cache); + return; + } /* Ensure there is only one address -> name mapping (first one trumps) We do this by steam here, The entries are kept in hash chains, linked @@ -1133,7 +1170,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr { FILE *f = fopen(filename, "r"); char *token = daemon->namebuff, *domain_suffix = NULL; - int addr_count = 0, name_count = cache_size, lineno = 1; + int names_done = 0, name_count = cache_size, lineno = 1; unsigned int flags = 0; union all_addr addr; int atnl, addrlen = 0; @@ -1169,8 +1206,6 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr continue; } - addr_count++; - /* rehash every 1000 names. */ if (rhash && ((name_count - cache_size) > 1000)) { @@ -1202,6 +1237,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr cache->ttd = daemon->local_ttl; add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; + names_done++; } if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1))) { @@ -1210,6 +1246,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr cache->ttd = daemon->local_ttl; add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz); name_count++; + names_done++; } free(canon); @@ -1226,7 +1263,7 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size, struct cr if (rhash) rehash(name_count); - my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count); + my_syslog(LOG_INFO, _("read %s - %d names"), filename, names_done); return name_count; } @@ -1765,6 +1802,8 @@ void dump_cache(time_t now) daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]); my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]); + if (option_bool(OPT_STALE_CACHE)) + my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]); #ifdef HAVE_AUTH my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); #endif @@ -1779,16 +1818,23 @@ void dump_cache(time_t now) if (!(serv->flags & SERV_MARK)) { int port; - unsigned int queries = 0, failed_queries = 0; + unsigned int queries = 0, failed_queries = 0, nxdomain_replies = 0, retrys = 0; + unsigned int sigma_latency = 0, count_latency = 0; + for (serv1 = serv; serv1; serv1 = serv1->next) if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr)) { serv1->flags |= SERV_MARK; queries += serv1->queries; failed_queries += serv1->failed_queries; + nxdomain_replies += serv1->nxdomain_replies; + retrys += serv1->retrys; + sigma_latency += serv1->query_latency; + count_latency++; } 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); + my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried %u, failed %u, nxdomain replies %u, avg. latency %ums"), + daemon->addrbuff, port, queries, retrys, failed_queries, nxdomain_replies, sigma_latency/count_latency); } if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG)) @@ -1883,6 +1929,7 @@ void dump_cache(time_t now) char *record_source(unsigned int index) { struct hostsfile *ah; + struct dyndir *dd; if (index == SRC_CONFIG) return "config"; @@ -1894,9 +1941,11 @@ char *record_source(unsigned int index) return ah->fname; #ifdef HAVE_INOTIFY - for (ah = daemon->dynamic_dirs; ah; ah = ah->next) - if (ah->index == index) - return ah->fname; + /* Dynamic directories contain multiple files */ + for (dd = daemon->dynamic_dirs; dd; dd = dd->next) + for (ah = dd->files; ah; ah = ah->next) + if (ah->index == index) + return ah->fname; #endif return ""; @@ -2124,6 +2173,8 @@ void _log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, name = arg; verb = daemon->addrbuff; } + else if (flags & F_STALE) + source = "cached-stale"; else source = "cached"; diff --git a/src/dnsmasq/config.h b/src/dnsmasq/config.h index 18429bf39..3f6dd6baa 100644 --- a/src/dnsmasq/config.h +++ b/src/dnsmasq/config.h @@ -24,6 +24,7 @@ #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 SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */ #define FORWARD_TEST 1000 /* try all servers every 1000 queries */ #define FORWARD_TIME 600 /* or 10 minutes */ #define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */ @@ -60,6 +61,7 @@ #define SOA_EXPIRY 1209600 /* SOA expiry default */ #define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */ #define LOOP_TEST_TYPE T_TXT +#define DEFAULT_FAST_RETRY 1000 /* ms, default delay before fast retry */ /* compile-time options: uncomment below to enable or do eg. make COPTS=-DHAVE_BROKEN_RTC diff --git a/src/dnsmasq/dbus.c b/src/dnsmasq/dbus.c index bf6b6613c..ef807111b 100644 --- a/src/dnsmasq/dbus.c +++ b/src/dnsmasq/dbus.c @@ -91,6 +91,11 @@ const char* introspection_xml_template = " \n" " \n" " \n" +" \n" +" \n" +" \n" +" \n" +" \n" " \n" "\n"; @@ -287,6 +292,11 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) u16 flags = 0; char interface[IF_NAMESIZE]; char *str_addr, *str_domain = NULL; + struct server_details sdetails = { 0 }; + sdetails.addr = &addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; if (strings) { @@ -369,20 +379,6 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) strcpy(str_addr, str); } - /* parse the IP address */ - if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags))) - { - error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, - "Invalid IP address '%s': %s", - str, addr_err); - break; - } - - /* 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_LITERAL_ADDRESS; - if (strings) { char *p; @@ -396,7 +392,31 @@ 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, NULL); + if (strings && strlen(str_addr) == 0) + add_update_server(SERV_LITERAL_ADDRESS | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL); + else + { + if ((addr_err = parse_server(str_addr, &sdetails))) + { + error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s': %s", + str, addr_err); + break; + } + + while (parse_server_next(&sdetails)) + { + if ((addr_err = parse_server_addr(&sdetails))) + { + error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s': %s", + str, addr_err); + break; + } + + add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL); + } + } } while ((str_domain = p)); } else @@ -410,11 +430,40 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings) if (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING) dbus_message_iter_get_basic(&string_iter, &str); dbus_message_iter_next (&string_iter); + + if ((addr_err = parse_server(str_addr, &sdetails))) + { + error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s': %s", + str, addr_err); + break; + } - add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str, NULL); + while (parse_server_next(&sdetails)) + { + if ((addr_err = parse_server_addr(&sdetails))) + { + error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS, + "Invalid IP address '%s': %s", + str, addr_err); + break; + } + + /* 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_LITERAL_ADDRESS; + else + flags &= ~SERV_LITERAL_ADDRESS; + + 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); } - + + if (sdetails.resolved) + freeaddrinfo(sdetails.hostinfo); + /* jump to next element in outer array */ dbus_message_iter_next(&array_iter); } @@ -644,6 +693,77 @@ static DBusMessage *dbus_get_metrics(DBusMessage* message) return reply; } +static void add_dict_entry(DBusMessageIter *container, const char *key, const char *val) +{ + DBusMessageIter dict; + + dbus_message_iter_open_container(container, DBUS_TYPE_DICT_ENTRY, NULL, &dict); + dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &key); + dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &val); + dbus_message_iter_close_container(container, &dict); +} + +static void add_dict_int(DBusMessageIter *container, const char *key, const unsigned int val) +{ + snprintf(daemon->namebuff, MAXDNAME, "%u", val); + + add_dict_entry(container, key, daemon->namebuff); +} + +static DBusMessage *dbus_get_server_metrics(DBusMessage* message) +{ + DBusMessage *reply = dbus_message_new_method_return(message); + DBusMessageIter server_array, dict_array, server_iter; + struct server *serv; + + dbus_message_iter_init_append(reply, &server_iter); + dbus_message_iter_open_container(&server_iter, DBUS_TYPE_ARRAY, "a{ss}", &server_array); + + /* sum counts from different records for same server */ + for (serv = daemon->servers; serv; serv = serv->next) + serv->flags &= ~SERV_MARK; + + for (serv = daemon->servers; serv; serv = serv->next) + if (!(serv->flags & SERV_MARK)) + { + unsigned int port; + unsigned int queries = 0, failed_queries = 0, nxdomain_replies = 0, retrys = 0; + unsigned int sigma_latency = 0, count_latency = 0; + + struct server *serv1; + + for (serv1 = serv; serv1; serv1 = serv1->next) + if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr)) + { + serv1->flags |= SERV_MARK; + queries += serv1->queries; + failed_queries += serv1->failed_queries; + nxdomain_replies += serv1->nxdomain_replies; + retrys += serv1->retrys; + sigma_latency += serv1->query_latency; + count_latency++; + } + + dbus_message_iter_open_container(&server_array, DBUS_TYPE_ARRAY, "{ss}", &dict_array); + + port = prettyprint_addr(&serv->addr, daemon->namebuff); + add_dict_entry(&dict_array, "address", daemon->namebuff); + + add_dict_int(&dict_array, "port", port); + add_dict_int(&dict_array, "queries", serv->queries); + add_dict_int(&dict_array, "failed_queries", serv->failed_queries); + add_dict_int(&dict_array, "nxdomain", serv->nxdomain_replies); + add_dict_int(&dict_array, "retries", serv->retrys); + add_dict_int(&dict_array, "latency", sigma_latency/count_latency); + + dbus_message_iter_close_container(&server_array, &dict_array); + } + + dbus_message_iter_close_container(&server_iter, &server_array); + + return reply; +} + DBusHandlerResult message_handler(DBusConnection *connection, DBusMessage *message, void *user_data) @@ -719,6 +839,14 @@ DBusHandlerResult message_handler(DBusConnection *connection, { reply = dbus_get_metrics(message); } + else if (strcmp(method, "GetServerMetrics") == 0) + { + reply = dbus_get_server_metrics(message); + } + else if (strcmp(method, "ClearMetrics") == 0) + { + clear_metrics(); + } else if (strcmp(method, "ClearCache") == 0) clear_cache = 1; else diff --git a/src/dnsmasq/dnsmasq.c b/src/dnsmasq/dnsmasq.c index 02d0a4a62..1d02462c1 100644 --- a/src/dnsmasq/dnsmasq.c +++ b/src/dnsmasq/dnsmasq.c @@ -272,6 +272,10 @@ int main_dnsmasq (int argc, char **argv) if (daemon->max_port < daemon->min_port) die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF); + + if (daemon->max_port != 0 && + daemon->max_port - daemon->min_port + 1 < daemon->randport_limit) + die(_("port_limit must not be larger than available port range"), NULL, EC_BADCONF); now = dnsmasq_time(); @@ -1068,19 +1072,20 @@ int main_dnsmasq (int argc, char **argv) while (!terminate) { - int timeout = -1; + int timeout = fast_retry(now); poll_reset(); /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */ - if (daemon->tftp_trans || - (option_bool(OPT_DBUS) && !daemon->dbus)) + if ((daemon->tftp_trans || (option_bool(OPT_DBUS) && !daemon->dbus)) && + (timeout == -1 || timeout > 250)) timeout = 250; - + /* Wake every second whilst waiting for DAD to complete */ - else if (is_dad_listeners()) + else if (is_dad_listeners() && + (timeout == -1 || timeout > 1000)) timeout = 1000; - + set_dns_listeners(); #ifdef HAVE_DBUS @@ -2051,9 +2056,6 @@ static void check_dns_listeners(time_t now) FTL_TCP_worker_terminating(true); /**********************************************/ - shutdown(confd, SHUT_RDWR); - close(confd); - if (buff) free(buff); diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index 5cdb11c06..2121c85e0 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -140,6 +140,7 @@ typedef unsigned long long u64; #include #include #include +#include #ifndef HAVE_LINUX_NETWORK # include #endif @@ -286,7 +287,9 @@ struct event_desc { #define OPT_FILTER_AAAA 68 #define OPT_STRIP_ECS 69 #define OPT_STRIP_MAC 70 -#define OPT_LAST 71 +#define OPT_STALE_CACHE 71 +#define OPT_NORR 72 +#define OPT_LAST 73 #define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) @@ -520,6 +523,7 @@ struct crec { #define F_DOMAINSRV (1u<<28) #define F_RCODE (1u<<29) #define F_SRV (1u<<30) +#define F_STALE (1u<<31) #define UID_NONE 0 /* Values of uid in crecs with F_CONFIG bit set. */ @@ -593,7 +597,8 @@ struct server { struct serverfd *sfd; int tcpfd, edns_pktsz; time_t pktsz_reduced; - unsigned int queries, failed_queries; + unsigned int queries, failed_queries, nxdomain_replies, retrys; + unsigned int query_latency, mma_latency; time_t forwardtime; int forwardcount; #ifdef HAVE_LOOP @@ -696,10 +701,17 @@ struct hostsfile { struct hostsfile *next; int flags; char *fname; + unsigned int index; /* matches to cache entries for logging */ +}; + +struct dyndir { + struct dyndir *next; + struct hostsfile *files; + int flags; + char *dname; #ifdef HAVE_INOTIFY int wd; /* inotify watch descriptor */ #endif - unsigned int index; /* matches to cache entries for logging */ }; /* packet-dump flags */ @@ -767,11 +779,13 @@ struct frec { unsigned short new_id; int forwardall, flags; time_t time; + u32 forward_timestamp; + int forward_delay; unsigned char *hash[HASH_SIZE]; -#ifdef HAVE_DNSSEC - int class, work_counter; struct blockdata *stash; /* Saved reply, whilst we validate */ size_t stash_len; +#ifdef HAVE_DNSSEC + int class, work_counter; 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. */ @@ -1151,6 +1165,7 @@ extern struct daemon { int log_fac; /* log facility */ char *log_file; /* optional log file */ int max_logs; /* queue limit */ + int randport_limit; /* Maximum number of source ports for query. */ int cachesize, ftabsize; 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; @@ -1158,6 +1173,7 @@ extern struct daemon { u32 umbrella_org; u32 umbrella_asset; u8 umbrella_device[8]; + int host_index; struct hostsfile *addn_hosts; struct dhcp_context *dhcp, *dhcp6; struct ra_interface *ra_interfaces; @@ -1178,7 +1194,8 @@ extern struct daemon { int doing_ra, doing_dhcp6; struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names; struct dhcp_netid_list *force_broadcast, *bootp_dynamic; - struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs; + struct hostsfile *dhcp_hosts_file, *dhcp_opts_file; + struct dyndir *dynamic_dirs; int dhcp_max, tftp_max, tftp_mtu; int dhcp_server_port, dhcp_client_port; int start_tftp_port, end_tftp_port; @@ -1195,6 +1212,7 @@ extern struct daemon { int dump_mask; unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry; u32 metrics[__METRIC_MAX]; + int fast_retry_time, fast_retry_timeout; #ifdef HAVE_DNSSEC struct ds_config *ds; char *timestamp_file; @@ -1287,6 +1305,14 @@ extern struct daemon { #endif } *daemon; +struct server_details { + union mysockaddr *addr, *source_addr; + struct addrinfo *hostinfo; + char *interface, *source, *scope_id, *interface_opt; + int serv_port, source_port, addr_type, scope_index, valid, resolved; + u16 *flags; +}; + /* cache.c */ void cache_init(void); void next_uid(struct crec *crecp); @@ -1317,6 +1343,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot); void cache_end_insert(void); void cache_start_insert(void); +unsigned int cache_remove_uid(const unsigned int uid); int cache_recv_insert(time_t now, int fd); struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class, time_t now, unsigned long ttl, unsigned int flags); @@ -1366,7 +1393,8 @@ 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); + time_t now, int ad_reqd, int do_bit, int have_pseudoheader, + int *stale); int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, time_t now); int check_for_ignored_address(struct dns_header *header, size_t qlen); @@ -1428,10 +1456,12 @@ void *whine_malloc(size_t size); void *whine_realloc(void *ptr, size_t size); int sa_len(union mysockaddr *addr); int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); +int sockaddr_isnull(const union mysockaddr *s); int hostname_order(const char *a, const char *b); int hostname_isequal(const char *a, const char *b); int hostname_issubdomain(char *a, char *b); time_t dnsmasq_time(void); +u32 dnsmasq_milliseconds(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); @@ -1475,8 +1505,9 @@ void read_servers_file(void); 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, u16 *flags); +char *parse_server(char *arg, struct server_details *sdetails); +char *parse_server_addr(struct server_details *sdetails); +int parse_server_next(struct server_details *sdetails); int option_read_dynfile(char *file, int flags); /* forward.c */ @@ -1491,6 +1522,7 @@ int send_from(int fd, int nowild, char *packet, size_t len, void resend_query(void); int allocate_rfd(struct randfd_list **fdlp, struct server *serv); void free_rfds(struct randfd_list **fdlp); +int fast_retry(time_t now); /* network.c */ int indextoname(int fd, int index, char *name); diff --git a/src/dnsmasq/domain-match.c b/src/dnsmasq/domain-match.c index f7db0fe88..76a1109e4 100644 --- a/src/dnsmasq/domain-match.c +++ b/src/dnsmasq/domain-match.c @@ -683,7 +683,9 @@ int add_update_server(int flags, serv->next = NULL; } break; - } + } + else + up = &serv->next; } if (serv) diff --git a/src/dnsmasq/forward.c b/src/dnsmasq/forward.c index f8235c8ca..986bdc581 100644 --- a/src/dnsmasq/forward.c +++ b/src/dnsmasq/forward.c @@ -173,7 +173,7 @@ static int domain_no_rebind(char *domain) 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, char *limit, time_t now, - struct frec *forward, int ad_reqd, int do_bit) + struct frec *forward, int ad_reqd, int do_bit, int fast_retry) { unsigned int flags = 0; unsigned int fwd_flags = 0; @@ -319,6 +319,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, goto reply; /* table full - flags == 0, return REFUSED */ + /* Keep copy of query if we're doing fast retry. */ + if (daemon->fast_retry_time != 0) + { + forward->stash = blockdata_alloc((char *)header, plen); + forward->stash_len = plen; + } + forward->frec_src.log_id = daemon->log_id; forward->frec_src.source = *udpaddr; forward->frec_src.orig_id = ntohs(header->id); @@ -366,14 +373,14 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, #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) + while (forward->blocking_query) + forward = forward->blocking_query; + + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) { int is_sign; unsigned char *pheader; - while (forward->blocking_query) - forward = forward->blocking_query; - /* log_id should match previous DNSSEC query. */ daemon->log_display_id = forward->frec_src.log_id; @@ -398,7 +405,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, #endif { /* retry on existing query, from original source. Send to all available servers */ - forward->sentto->failed_queries++; + if (udpfd == -1 && !fast_retry) + forward->sentto->failed_queries++; + else + forward->sentto->retrys++; FTL_forwarding_retried(forward->sentto, forward->frec_src.log_id, daemon->log_display_id, false); @@ -408,13 +418,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, master = daemon->serverarray[first]; /* Forward to all available servers on retry of query from same host. */ - if (!option_bool(OPT_ORDER) && old_src) + if (!option_bool(OPT_ORDER) && old_src && !fast_retry) forward->forwardall = 1; else { start = forward->sentto->arrayposn; - if (option_bool(OPT_ORDER)) + if (option_bool(OPT_ORDER) && !fast_retry) { /* In strict order mode, there must be a server later in the list left to send to, otherwise without the forwardall mechanism, @@ -563,7 +573,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } if (forwarded || is_dnssec) - return 1; + { + forward->forward_timestamp = dnsmasq_milliseconds(); + return 1; + } /* could not send on, prepare to return */ header->id = htons(forward->frec_src.orig_id); @@ -602,6 +615,61 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, return 0; } +/* Check if any frecs need to do a retry, and action that if so. + Return time in milliseconds until he next retry will be required, + or -1 if none. */ +int fast_retry(time_t now) +{ + struct frec *f; + int ret = -1; + + if (daemon->fast_retry_time != 0) + { + u32 millis = dnsmasq_milliseconds(); + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->stash && difftime(now, f->time) < daemon->fast_retry_timeout) + { +#ifdef HAVE_DNSSEC + if (f->blocking_query) + continue; +#endif + /* t is milliseconds since last query sent. */ + int to_run, t = (int)(millis - f->forward_timestamp); + + if (t < f->forward_delay) + to_run = f->forward_delay - t; + else + { + unsigned char *udpsz; + unsigned short udp_size = PACKETSZ; /* default if no EDNS0 */ + struct dns_header *header = (struct dns_header *)daemon->packet; + + /* packet buffer overwritten */ + daemon->srv_save = NULL; + + blockdata_retrieve(f->stash, f->stash_len, (void *)header); + + /* UDP size already set in saved query. */ + if (find_pseudoheader(header, f->stash_len, NULL, &udpsz, NULL, NULL)) + GETSHORT(udp_size, udpsz); + + daemon->log_display_id = f->frec_src.log_id; + + forward_query(-1, NULL, NULL, 0, header, f->stash_len, ((char *) header) + udp_size, now, f, + f->flags & FREC_AD_QUESTION, f->flags & FREC_DO_QUESTION, 1); + + to_run = f->forward_delay = 2 * f->forward_delay; + } + + if (ret == -1 || ret > to_run) + ret = to_run; + } + + } + return ret; +} + static struct ipsets *domain_find_sets(struct ipsets *setlist, const char *domain) { /* Similar algorithm to search_servers. */ struct ipsets *ipset_pos, *ret = NULL; @@ -848,6 +916,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1); } + if (RCODE(header) == NXDOMAIN) + server->nxdomain_replies++; + return n; } @@ -990,6 +1061,8 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, /* Save query for retransmission and de-dup */ new->stash = blockdata_alloc((char *)header, nn); new->stash_len = nn; + if (daemon->fast_retry_time != 0) + new->forward_timestamp = dnsmasq_milliseconds(); /* Don't resend this. */ daemon->srv_save = NULL; @@ -1107,7 +1180,7 @@ void reply_query(int fd, time_t now) if (daemon->ignore_addr && RCODE(header) == NOERROR && check_for_ignored_address(header, n)) return; - + /* Note: if we send extra options in the EDNS0 header, we can't recreate the query from the reply. */ if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && @@ -1138,38 +1211,50 @@ void reply_query(int fd, time_t now) else #endif { - /* recreate query from reply */ - if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL))) - GETSHORT(udp_size, udpsz); - - /* If the client provides an EDNS0 UDP size, use that to limit our reply. - (bounded by the maximum configured). If no EDNS0, then it - defaults to 512 */ - if (udp_size > daemon->edns_pktsz) - udp_size = daemon->edns_pktsz; - else if (udp_size < PACKETSZ) - udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ - - if (!is_sign && - (nn = resize_packet(header, (size_t)n, pheader, plen)) && - (forward->flags & FREC_DO_QUESTION)) - add_do_bit(header, nn, (unsigned char *)pheader + plen); - - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - 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; + /* in fast retry mode, we have a copy of the query. */ + if (daemon->fast_retry_time != 0 && forward->stash) + { + blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); + nn = forward->stash_len; + /* UDP size already set in saved query. */ + if (find_pseudoheader(header, (size_t)n, NULL, &udpsz, NULL, NULL)) + GETSHORT(udp_size, udpsz); + } + else + { + /* recreate query from reply */ + if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL))) + GETSHORT(udp_size, udpsz); + + /* If the client provides an EDNS0 UDP size, use that to limit our reply. + (bounded by the maximum configured). If no EDNS0, then it + defaults to 512 */ + if (udp_size > daemon->edns_pktsz) + udp_size = daemon->edns_pktsz; + else if (udp_size < PACKETSZ) + udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */ + + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + 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 (!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); + } } - + if (nn) { forward_query(-1, NULL, NULL, 0, header, nn, ((char *) header) + udp_size, now, forward, - forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION); + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 0); return; } } @@ -1203,6 +1288,15 @@ void reply_query(int fd, time_t now) answers, to conserve file descriptors, and to save work reading and discarding answers for other upstreams. */ free_rfds(&forward->rfds); + + /* calculate modified moving average of server latency */ + if (server->query_latency == 0) + server->mma_latency = (dnsmasq_milliseconds() - forward->forward_timestamp) * 128; /* init */ + else + server->mma_latency += dnsmasq_milliseconds() - forward->forward_timestamp - server->query_latency; + /* denominator controls how many queries we average over. */ + server->query_latency = server->mma_latency/128; + #ifdef HAVE_DNSSEC if ((forward->sentto->flags & SERV_DO_DNSSEC) && @@ -1309,10 +1403,6 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he { header->id = htons(src->orig_id); -#ifdef HAVE_DUMPFILE - dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source, src->fd); -#endif - #if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) if (option_bool(OPT_CMARK_ALST_EN)) { @@ -1326,14 +1416,20 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he /* Pi-hole modification */ int first_ID = -1; - 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) + if (src->fd != -1) { - daemon->log_display_id = src->log_id; - daemon->log_source_addr = &src->source; - log_query(F_UPSTREAM, "query", NULL, "duplicate", 0); +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source, src->fd); +#endif + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); + + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) + { + daemon->log_display_id = src->log_id; + daemon->log_source_addr = &src->source; + log_query(F_UPSTREAM, "query", NULL, "duplicate", 0); + } } /* Pi-hole modification */ FTL_multiple_replies(src->log_id, &first_ID); @@ -1429,7 +1525,7 @@ void receive_query(struct listener *listen, time_t now) /************ Pi-hole modification ************/ bool piholeblocked = false; /**********************************************/ - + /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -1763,7 +1859,11 @@ void receive_query(struct listener *listen, time_t now) #endif else { + int stale; int ad_reqd = do_bit; + u16 hb3 = header->hb3, hb4 = header->hb4; + int fd = listen->fd; + /* RFC 6840 5.7 */ if (header->hb4 & HB4_AD) ad_reqd = 1; @@ -1799,10 +1899,17 @@ void receive_query(struct listener *listen, time_t now) /**********************************************/ m = answer_request(header, ((char *) header) + udp_size, (size_t)n, - dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader); + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale); if (m >= 1) { + if (stale && have_pseudoheader) + { + u16 swap = htons(EDE_STALE); + + m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + } #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_REPLY, daemon->packet, m, NULL, &source_addr, listen->fd); #endif @@ -1813,12 +1920,39 @@ void receive_query(struct listener *listen, time_t now) 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]++; + if (stale) + daemon->metrics[METRIC_DNS_STALE_ANSWERED]++; + } + + if (m == 0 || stale) + { + if (m != 0) + { + size_t plen; + + /* We answered with stale cache data, so forward the query anyway to + refresh that. Restore the query from the answer packet. */ + pheader = find_pseudoheader(header, (size_t)m, &plen, NULL, NULL, NULL); + + header->hb3 = hb3; + header->hb4 = hb4; + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + m = resize_packet(header, m, pheader, plen); + + /* We've already answered the client, so don't send it the answer + when it comes back. */ + fd = -1; + } + + if (forward_query(fd, &source_addr, &dst_addr, if_index, + header, (size_t)n, ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit, 0)) + daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++; + else + daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; } - else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index, - 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]++; } } @@ -2045,8 +2179,9 @@ unsigned char *tcp_request(int confd, time_t now, unsigned char *pheader; unsigned int mark = 0; int have_mark = 0; - int first, last; + int first, last, stale, do_stale = 0; unsigned int flags = 0; + u16 hb3, hb4; /************ Pi-hole modification ************/ bool piholeblocked = false; @@ -2105,13 +2240,37 @@ unsigned char *tcp_request(int confd, time_t now, { int ede = EDE_UNSET; - if (query_count == TCP_MAX_QUERIES || - !packet || - !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || - !(size = c1 << 8 | c2) || - !read_write(confd, payload, size, 1)) - return packet; - + if (query_count == TCP_MAX_QUERIES) + return packet; + + if (do_stale) + { + size_t plen; + + /* We answered the last query with stale data. Now try and get fresh data. + Restore query from answer. */ + pheader = find_pseudoheader(header, m, &plen, NULL, NULL, NULL); + + header->hb3 = hb3; + header->hb4 = hb4; + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + + size = resize_packet(header, m, pheader, plen); + } + else + { + if (!read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || + !(size = c1 << 8 | c2) || + !read_write(confd, payload, size, 1)) + return packet; + + /* for stale-answer processing. */ + hb3 = header->hb3; + hb4 = header->hb4; + } + if (size < (int)sizeof(struct dns_header)) continue; @@ -2142,28 +2301,31 @@ unsigned char *tcp_request(int confd, time_t now, struct auth_zone *zone; #endif - log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff, - &peer_addr, auth_dns ? "auth" : "query", qtype); - - piholeblocked = FTL_new_query(F_QUERY | F_FORWARD, daemon->namebuff, - &peer_addr, auth_dns ? "auth" : "query", qtype, daemon->log_display_id, &edns, TCP); - #ifdef HAVE_CONNTRACK is_single_query = 1; #endif - + + if (!do_stale) + { + log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff, + &peer_addr, auth_dns ? "auth" : "query", qtype); + + piholeblocked = FTL_new_query(F_QUERY | F_FORWARD, daemon->namebuff, + &peer_addr, auth_dns ? "auth" : "query", qtype, daemon->log_display_id, &edns, TCP); + #ifdef HAVE_AUTH - /* find queries for zones we're authoritative for, and answer them directly */ - if (!auth_dns && !option_bool(OPT_LOCALISE)) - for (zone = daemon->auth_zones; zone; zone = zone->next) - if (in_zone(zone, daemon->namebuff, NULL)) - { - auth_dns = 1; - local_auth = 1; - break; - } + /* find queries for zones we're authoritative for, and answer them directly */ + if (!auth_dns && !option_bool(OPT_LOCALISE)) + for (zone = daemon->auth_zones; zone; zone = zone->next) + if (in_zone(zone, daemon->namebuff, NULL)) + { + auth_dns = 1; + local_auth = 1; + break; + } #endif + } } norebind = domain_no_rebind(daemon->namebuff); @@ -2225,6 +2387,7 @@ unsigned char *tcp_request(int confd, time_t now, if(piholeblocked) { int ede = EDE_UNSET; + stale = 0; // Generate DNS packet for reply m = FTL_make_answer(header, ((char *) header) + 65536, size, &ede); // The pseudoheader may contain important information such as EDNS0 version important for @@ -2243,11 +2406,14 @@ unsigned char *tcp_request(int confd, time_t now, else { /**********************************************/ + + if (do_stale) + m = 0; + else + /* m > 0 if answered from cache */ + m = answer_request(header, ((char *) header) + 65536, (size_t)size, + dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader, &stale); - /* m > 0 if answered from cache */ - m = answer_request(header, ((char *) header) + 65536, (size_t)size, - dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader); - /* Do this by steam now we're not in the select() loop */ check_log_writer(1); @@ -2368,6 +2534,9 @@ unsigned char *tcp_request(int confd, time_t now, /**********************************************/ } + if (do_stale) + break; + /* In case of local answer or no connections made. */ if (m == 0 && !piholeblocked) // Pi-hole modified to ensure we don't provide local answers when dropping the reply { @@ -2378,13 +2547,19 @@ unsigned char *tcp_request(int confd, time_t now, 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); + + 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); } } + else if (stale) + { + u16 swap = htons((u16)EDE_STALE); + + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + } check_log_writer(1); @@ -2399,8 +2574,26 @@ unsigned char *tcp_request(int confd, time_t now, #endif if (!read_write(confd, packet, m + sizeof(u16), 0)) break; + + /* If we answered with stale data, this process will now try and get fresh data into + the cache then and cannot therefore accept new queries. Close the incoming + connection to signal that to the client. Then set do_stale and loop round + once more to try and get fresh data, after which we exit. */ + if (stale) + { + shutdown(confd, SHUT_RDWR); + close(confd); + do_stale = 1; + } } - + + /* If we ran once to get fresh data, confd is already closed. */ + if (!do_stale) + { + shutdown(confd, SHUT_RDWR); + close(confd); + } + return packet; } @@ -2412,16 +2605,36 @@ static int random_sock(struct server *s) if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) { + /* We need to set IPV6ONLY so we can use the same ports + for IPv4 and IPV6, otherwise, in restriced port situations, + we can end up with all our available ports in use for + one address family, and the other address family cannot be used. */ + if (s->source_addr.sa.sa_family == AF_INET6) + { + int opt = 1; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) + { + close(fd); + return -1; + } + } + if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) return fd; - if (s->interface[0] == 0) - (void)prettyprint_addr(&s->source_addr, daemon->namebuff); - else - strcpy(daemon->namebuff, s->interface); - - my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), - daemon->namebuff, strerror(errno)); + /* don't log errors due to running out of available ports, we handle those. */ + if (!sockaddr_isnull(&s->source_addr) || errno != EADDRINUSE) + { + if (s->interface[0] == 0) + (void)prettyprint_addr(&s->source_addr, daemon->addrbuff); + else + safe_strncpy(daemon->addrbuff, s->interface, ADDRSTRLEN); + + my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), + daemon->addrbuff, strerror(errno)); + } + close(fd); } @@ -2451,39 +2664,93 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv) { static int finger = 0; int i, j = 0; - struct randfd_list *rfl; + int ports_full = 0; + struct randfd_list **up, *rfl, *found, **found_link; struct randfd *rfd = NULL; int fd = 0; + int ports_avail = 0; + + /* We can't have more randomsocks for this AF available than ports in our port range, + so check that here, to avoid trying and failing to bind every port + in local_bind(), called from random_sock(). The actual check is below when + ports_avail != 0 */ + if (daemon->max_port != 0) + { + ports_avail = daemon->max_port - daemon->min_port + 1; + if (ports_avail >= SMALL_PORT_RANGE) + ports_avail = 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) + /* existing suitable random port socket linked to this transaction? + Find the last one in the list and count how many there are. */ + for (found = NULL, found_link = NULL, i = 0, up = fdlp, rfl = *fdlp; rfl; up = &rfl->next, rfl = rfl->next) if (server_isequal(serv, rfl->rfd->serv)) - return rfl->rfd->fd; + { + i++; + found = rfl; + found_link = up; + } + + /* We have the maximum number for this query already. Promote + the last one on the list to the head, to circulate them, + and return it. */ + if (found && i >= daemon->randport_limit) + { + *found_link = found->next; + found->next = *fdlp; + *fdlp = found; + return found->rfd->fd; + } + + /* check for all available ports in use. */ + if (ports_avail != 0) + { + int ports_inuse; - /* No. need new link. */ + for (ports_inuse = 0, i = 0; i < daemon->numrrand; i++) + if (daemon->randomsocks[i].refcount != 0 && + daemon->randomsocks[i].serv->source_addr.sa.sa_family == serv->source_addr.sa.sa_family && + ++ports_inuse >= ports_avail) + { + ports_full = 1; + break; + } + } + + /* limit the number of sockets we have open to avoid starvation of + (eg) TFTP. Once we have a reasonable number, randomness should be OK */ + if (!ports_full) + for (i = 0; i < daemon->numrrand; i++) + if (daemon->randomsocks[i].refcount == 0) + { + if ((fd = random_sock(serv)) != -1) + { + rfd = &daemon->randomsocks[i]; + rfd->serv = serv; + rfd->fd = fd; + rfd->refcount = 1; + } + break; + } + + /* No good existing. 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 < daemon->numrrand; i++) - if (daemon->randomsocks[i].refcount == 0) - { - if ((fd = random_sock(serv)) != -1) - { - rfd = &daemon->randomsocks[i]; - rfd->serv = serv; - rfd->fd = fd; - rfd->refcount = 1; - } - break; - } + { + /* malloc failed, don't leak allocated sock */ + if (rfd) + { + close(rfd->fd); + rfd->refcount = 0; + } + + return -1; + } /* No free ones or cannot get new socket, grab an existing one */ if (!rfd) @@ -2494,10 +2761,19 @@ int allocate_rfd(struct randfd_list **fdlp, struct server *serv) server_isequal(serv, daemon->randomsocks[i].serv) && daemon->randomsocks[i].refcount != 0xfffe) { - finger = i + 1; - rfd = &daemon->randomsocks[i]; - rfd->refcount++; - break; + struct randfd_list *rl; + /* Don't pick one we already have. */ + for (rl = *fdlp; rl; rl = rl->next) + if (rl->rfd == &daemon->randomsocks[i]) + break; + + if (!rl) + { + finger = i + 1; + rfd = &daemon->randomsocks[i]; + rfd->refcount++; + break; + } } } @@ -2609,13 +2885,13 @@ static void free_frec(struct frec *f) f->sentto = NULL; f->flags = 0; -#ifdef HAVE_DNSSEC if (f->stash) { blockdata_free(f->stash); f->stash = NULL; } - + +#ifdef HAVE_DNSSEC /* Anything we're waiting on is pointless now, too */ if (f->blocking_query) { @@ -2671,6 +2947,7 @@ static struct frec *get_new_frec(time_t now, struct server *master, int force) { if (difftime(now, f->time) >= 4*TIMEOUT) { + daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++; free_frec(f); target = f; } @@ -2692,6 +2969,7 @@ static struct frec *get_new_frec(time_t now, struct server *master, int force) if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT) { /* can't find empty one, use oldest if there is one and it's older than timeout */ + daemon->metrics[METRIC_DNS_UNANSWERED_QUERY]++; free_frec(oldest); target = oldest; } @@ -2703,8 +2981,11 @@ static struct frec *get_new_frec(time_t now, struct server *master, int force) } if (target) - target->time = now; - + { + target->time = now; + target->forward_delay = daemon->fast_retry_time; + } + return target; } diff --git a/src/dnsmasq/inotify.c b/src/dnsmasq/inotify.c index 5687e37cb..d3c8277b6 100644 --- a/src/dnsmasq/inotify.c +++ b/src/dnsmasq/inotify.c @@ -133,81 +133,112 @@ void inotify_dnsmasq_init() } } +static struct hostsfile *dyndir_addhosts(struct dyndir *dd, char *path) +{ + /* Check if this file is already known in dd->files */ + struct hostsfile *ah = NULL; + for(ah = dd->files; ah; ah = ah->next) + if(ah && ah->fname && strcmp(path, ah->fname) == 0) + return ah; + + /* Not known, create new hostsfile record for this dyndir */ + struct hostsfile *newah = NULL; + if(!(newah = whine_malloc(sizeof(struct hostsfile)))) + return NULL; + + /* Add this file to the tip of the linked list */ + newah->next = dd->files; + dd->files = newah; + + /* Copy flags, set index and the full file path */ + newah->flags = dd->flags; + newah->index = daemon->host_index++; + newah->fname = path; + + return newah; +} + /* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */ void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz) { - struct hostsfile *ah; - - for (ah = daemon->dynamic_dirs; ah; ah = ah->next) + struct dyndir *dd; + + for (dd = daemon->dynamic_dirs; dd; dd = dd->next) { DIR *dir_stream = NULL; struct dirent *ent; struct stat buf; - - if (!(ah->flags & flag)) + + if (!(dd->flags & flag)) continue; - - if (stat(ah->fname, &buf) == -1) + + if (stat(dd->dname, &buf) == -1) { my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), - ah->fname, strerror(errno)); + dd->dname, strerror(errno)); continue; } if (!(S_ISDIR(buf.st_mode))) { my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), - ah->fname, _("not a directory")); + dd->dname, _("not a directory")); continue; } - - if (!(ah->flags & AH_WD_DONE)) + + if (!(dd->flags & AH_WD_DONE)) { - ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO); - ah->flags |= AH_WD_DONE; + dd->wd = inotify_add_watch(daemon->inotifyfd, dd->dname, IN_CLOSE_WRITE | IN_MOVED_TO | IN_DELETE); + dd->flags |= AH_WD_DONE; } /* Read contents of dir _after_ calling add_watch, in the hope of avoiding a race which misses files being added as we start */ - if (ah->wd == -1 || !(dir_stream = opendir(ah->fname))) + if (dd->wd == -1 || !(dir_stream = opendir(dd->dname))) { my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"), - ah->fname, strerror(errno)); + dd->dname, strerror(errno)); continue; } while ((ent = readdir(dir_stream))) { - size_t lendir = strlen(ah->fname); + size_t lendir = strlen(dd->dname); size_t lenfile = strlen(ent->d_name); 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; - + if ((path = whine_malloc(lendir + lenfile + 2))) { - strcpy(path, ah->fname); + struct hostsfile *ah; + + strcpy(path, dd->dname); strcat(path, "/"); strcat(path, ent->d_name); + + if (!(ah = dyndir_addhosts(dd, path))) + { + free(path); + continue; + } /* ignore non-regular files */ if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode)) { - if (ah->flags & AH_HOSTS) + if (dd->flags & AH_HOSTS) total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz); #ifdef HAVE_DHCP - else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT)) - option_read_dynfile(path, ah->flags); + else if (dd->flags & (AH_DHCP_HST | AH_DHCP_OPT)) + option_read_dynfile(path, dd->flags); #endif } - - free(path); } } @@ -218,7 +249,7 @@ void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revh int inotify_check(time_t now) { int hit = 0; - struct hostsfile *ah; + struct dyndir *dd; while (1) { @@ -249,36 +280,51 @@ int inotify_check(time_t now) if (res->wd == in->wd && strcmp(res->file, in->name) == 0) hit = 1; - for (ah = daemon->dynamic_dirs; ah; ah = ah->next) - if (ah->wd == in->wd) + for (dd = daemon->dynamic_dirs; dd; dd = dd->next) + if (dd->wd == in->wd) { - size_t lendir = strlen(ah->fname); + size_t lendir = strlen(dd->dname); char *path; - + if ((path = whine_malloc(lendir + in->len + 2))) { - strcpy(path, ah->fname); + struct hostsfile *ah = NULL; + + strcpy(path, dd->dname); strcat(path, "/"); strcat(path, in->name); - - my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path); - if (ah->flags & AH_HOSTS) + /* Is this is a deletion event? */ + if (in->mask & IN_DELETE) + my_syslog(LOG_INFO, _("inotify: %s removed"), path); + else + my_syslog(LOG_INFO, _("inotify: %s new or modified"), path); + + if (dd->flags & AH_HOSTS) { - read_hostsfile(path, ah->index, 0, NULL, 0); -#ifdef HAVE_DHCP - if (daemon->dhcp || daemon->doing_dhcp6) + if ((ah = dyndir_addhosts(dd, path))) { - /* Propagate the consequences of loading a new dhcp-host */ - dhcp_update_configs(daemon->dhcp_conf); - lease_update_from_configs(); - lease_update_file(now); - lease_update_dns(1); - } + const unsigned int removed = cache_remove_uid(ah->index); + if (removed > 0) + my_syslog(LOG_INFO, _("inotify: flushed %u names read from %s"), removed, path); + + /* (Re-)load hostsfile only if this event isn't triggered by deletion */ + if (!(in->mask & IN_DELETE)) + read_hostsfile(path, ah->index, 0, NULL, 0); +#ifdef HAVE_DHCP + if (daemon->dhcp || daemon->doing_dhcp6) + { + /* Propagate the consequences of loading a new dhcp-host */ + dhcp_update_configs(daemon->dhcp_conf); + lease_update_from_configs(); + lease_update_file(now); + lease_update_dns(1); + } #endif + } } #ifdef HAVE_DHCP - else if (ah->flags & AH_DHCP_HST) + else if (dd->flags & AH_DHCP_HST) { if (option_read_dynfile(path, AH_DHCP_HST)) { @@ -289,11 +335,12 @@ int inotify_check(time_t now) lease_update_dns(1); } } - else if (ah->flags & AH_DHCP_OPT) + else if (dd->flags & AH_DHCP_OPT) option_read_dynfile(path, AH_DHCP_OPT); #endif - free(path); + if (!ah) + free(path); } } } diff --git a/src/dnsmasq/metrics.c b/src/dnsmasq/metrics.c index 687352931..f3e6728af 100644 --- a/src/dnsmasq/metrics.c +++ b/src/dnsmasq/metrics.c @@ -22,6 +22,8 @@ const char * metric_names[] = { "dns_queries_forwarded", "dns_auth_answered", "dns_local_answered", + "dns_stale_answered", + "dns_unanswered", "bootp", "pxe", "dhcp_ack", @@ -42,3 +44,23 @@ const char * metric_names[] = { const char* get_metric_name(int i) { return metric_names[i]; } + +void clear_metrics(void) +{ + int i; + struct server *serv; + + for (i = 0; i < __METRIC_MAX; i++) + daemon->metrics[i] = 0; + + for (serv = daemon->servers; serv; serv = serv->next) + { + serv->queries = 0; + serv->failed_queries = 0; + serv->failed_queries = 0; + serv->retrys = 0; + serv->nxdomain_replies = 0; + serv->query_latency = 0; + } +} + diff --git a/src/dnsmasq/metrics.h b/src/dnsmasq/metrics.h index df72ec689..6f62a4065 100644 --- a/src/dnsmasq/metrics.h +++ b/src/dnsmasq/metrics.h @@ -21,6 +21,8 @@ enum { METRIC_DNS_QUERIES_FORWARDED, METRIC_DNS_AUTH_ANSWERED, METRIC_DNS_LOCAL_ANSWERED, + METRIC_DNS_STALE_ANSWERED, + METRIC_DNS_UNANSWERED_QUERY, METRIC_BOOTP, METRIC_PXE, METRIC_DHCPACK, @@ -41,3 +43,4 @@ enum { }; const char* get_metric_name(int); +void clear_metrics(void); diff --git a/src/dnsmasq/network.c b/src/dnsmasq/network.c index 239c35697..f5900a7b0 100644 --- a/src/dnsmasq/network.c +++ b/src/dnsmasq/network.c @@ -362,7 +362,7 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, if (int_name->flags & INP4) { - if (netmask.s_addr == 0xffff) + if (netmask.s_addr == 0xffffffff) continue; newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) | @@ -1391,7 +1391,7 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind 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; + tries = (ports_avail < SMALL_PORT_RANGE) ? ports_avail : 100; port = htons(daemon->min_port + (rand16() % ports_avail)); } @@ -1420,7 +1420,16 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind if (--tries == 0) return 0; - port = htons(daemon->min_port + (rand16() % ports_avail)); + /* For small ranges, do a systematic search, not a random one. */ + if (ports_avail < SMALL_PORT_RANGE) + { + unsigned short hport = ntohs(port); + if (hport++ == daemon->max_port) + hport = daemon->min_port; + port = htons(hport); + } + else + port = htons(daemon->min_port + (rand16() % ports_avail)); } if (!is_tcp && ifindex > 0) diff --git a/src/dnsmasq/option.c b/src/dnsmasq/option.c index a36dcfde3..b511ef34d 100644 --- a/src/dnsmasq/option.c +++ b/src/dnsmasq/option.c @@ -185,6 +185,10 @@ struct myoption { #define LOPT_STRIP_MAC 372 #define LOPT_CONF_OPT 373 #define LOPT_CONF_SCRIPT 374 +#define LOPT_RANDPORT_LIM 375 +#define LOPT_FAST_RETRY 376 +#define LOPT_STALE_CACHE 377 +#define LOPT_NORR 378 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -237,6 +241,7 @@ static const struct myoption opts[] = { "localmx", 0, 0, 'L' }, { "local-ttl", 1, 0, 'T' }, { "no-negcache", 0, 0, 'N' }, + { "no-round-robin", 0, 0, LOPT_NORR }, { "addn-hosts", 1, 0, 'H' }, { "hostsdir", 1, 0, LOPT_HOST_INOTIFY }, { "query-port", 1, 0, 'Q' }, @@ -370,6 +375,9 @@ static const struct myoption opts[] = { "log-debug", 0, 0, LOPT_LOG_DEBUG }, { "umbrella", 2, 0, LOPT_UMBRELLA }, { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP }, + { "port-limit", 1, 0, LOPT_RANDPORT_LIM }, + { "fast-dns-retry", 2, 0, LOPT_FAST_RETRY }, + { "use-stale-cache", 0, 0 , LOPT_STALE_CACHE }, { NULL, 0, 0, 0 } }; @@ -427,6 +435,7 @@ static struct { { 'M', ARG_DUP, "", gettext_noop("Specify BOOTP options to DHCP server."), NULL }, { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL }, + { LOPT_STALE_CACHE, OPT_STALE_CACHE, NULL, gettext_noop("Use expired cache data for faster reply."), NULL }, { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE }, { 'O', ARG_DUP, "", gettext_noop("Specify options to be sent to DHCP clients."), NULL }, { LOPT_FORCE, ARG_DUP, "", gettext_noop("DHCP option sent even if the client does not request it."), NULL}, @@ -434,6 +443,7 @@ static struct { { 'P', ARG_ONE, "", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" }, { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL }, { 'Q', ARG_ONE, "", gettext_noop("Force the originating port for upstream DNS queries."), NULL }, + { LOPT_RANDPORT_LIM, ARG_ONE, "#ports", gettext_noop("Set maximum number of random originating ports for a query."), NULL }, { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL }, { 'r', ARG_DUP, "", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, { LOPT_SERVERS_FILE, ARG_ONE, "", gettext_noop("Specify path to file with server= options"), NULL }, @@ -447,6 +457,7 @@ static struct { { LOPT_MAXTTL, ARG_ONE, "", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL }, { LOPT_MAXCTTL, ARG_ONE, "", gettext_noop("Specify time-to-live ceiling for cache."), NULL }, { LOPT_MINCTTL, ARG_ONE, "", gettext_noop("Specify time-to-live floor for cache."), NULL }, + { LOPT_FAST_RETRY, ARG_ONE, "", gettext_noop("Retry DNS queries after this many milliseconds."), NULL}, { 'u', ARG_ONE, "", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, { 'U', ARG_DUP, "set:,", gettext_noop("Map DHCP vendor class to tag."), NULL }, { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL }, @@ -562,6 +573,7 @@ static struct { { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL }, { LOPT_UMBRELLA, ARG_ONE, "[=]", 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 }, + { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL }, { 0, 0, NULL, NULL, NULL } }; @@ -847,117 +859,239 @@ 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, u16 *flags) +char *parse_server(char *arg, struct server_details *sdetails) { - int source_port = 0, serv_port = NAMESERVER_PORT; - char *portno, *source; - char *interface_opt = NULL; - int scope_index = 0; - char *scope_id; - - *interface = 0; + sdetails->serv_port = NAMESERVER_PORT; + char *portno; + int ecode = 0; + struct addrinfo hints = { 0 }; + *sdetails->interface = 0; + sdetails->addr_type = AF_UNSPEC; + if (strcmp(arg, "#") == 0) { - if (flags) - *flags |= SERV_USE_RESOLV; + if (sdetails->flags) + *sdetails->flags |= SERV_USE_RESOLV; + sdetails->addr_type = AF_LOCAL; + sdetails->valid = 1; return NULL; } - if ((source = split_chr(arg, '@')) && /* is there a source. */ - (portno = split_chr(source, '#')) && - !atoi_check16(portno, &source_port)) + if ((sdetails->source = split_chr(arg, '@')) && /* is there a source. */ + (portno = split_chr(sdetails->source, '#')) && + !atoi_check16(portno, &sdetails->source_port)) return _("bad port"); if ((portno = split_chr(arg, '#')) && /* is there a port no. */ - !atoi_check16(portno, &serv_port)) + !atoi_check16(portno, &sdetails->serv_port)) return _("bad port"); - scope_id = split_chr(arg, '%'); + sdetails->scope_id = split_chr(arg, '%'); - if (source) { - interface_opt = split_chr(source, '@'); + if (sdetails->source) { + sdetails->interface_opt = split_chr(sdetails->source, '@'); - if (interface_opt) + if (sdetails->interface_opt) { #if defined(SO_BINDTODEVICE) - safe_strncpy(interface, source, IF_NAMESIZE); - source = interface_opt; + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); + sdetails->source = sdetails->interface_opt; #else return _("interface binding not supported"); #endif } } - if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0) + if (inet_pton(AF_INET, arg, &sdetails->addr->in.sin_addr) > 0) + sdetails->addr_type = AF_INET; + else if (inet_pton(AF_INET6, arg, &sdetails->addr->in6.sin6_addr) > 0) + sdetails->addr_type = AF_INET6; + else + { + /* if the argument is neither an IPv4 not an IPv6 address, it might be a + hostname and we should try to resolve it to a suitable address. */ + memset(&hints, 0, sizeof(hints)); + /* The AI_ADDRCONFIG flag ensures that then IPv4 addresses are returned in + the result only if the local system has at least one IPv4 address + configured, and IPv6 addresses are returned only if the local system + has at least one IPv6 address configured. The loopback address is not + considered for this case as valid as a configured address. This flag is + useful on, for example, IPv4-only systems, to ensure that getaddrinfo() + does not return IPv6 socket addresses that would always fail in + subsequent connect() or bind() attempts. */ + hints.ai_flags = AI_ADDRCONFIG; +#if defined(HAVE_IDN) && defined(AI_IDN) + /* If the AI_IDN flag is specified and we have glibc 2.3.4 or newer, then + the node name given in node is converted to IDN format if necessary. + The source encoding is that of the current locale. */ + hints.ai_flags |= AI_IDN; +#endif + /* The value AF_UNSPEC indicates that getaddrinfo() should return socket + addresses for any address family (either IPv4 or IPv6, for example) + that can be used with node and service "domain". */ + hints.ai_family = AF_UNSPEC; + + /* Get addresses suitable for sending datagrams. We assume that we can use the + same addresses for TCP connections. Settting this to zero gets each address + threes times, for SOCK_STREAM, SOCK_RAW and SOCK_DGRAM, which is not useful. */ + hints.ai_socktype = SOCK_DGRAM; + + /* Get address associated with this hostname */ + ecode = getaddrinfo(arg, NULL, &hints, &sdetails->hostinfo); + if (ecode == 0) + { + /* The getaddrinfo() function allocated and initialized a linked list of + addrinfo structures, one for each network address that matches node + and service, subject to the restrictions imposed by our + above, and returns a pointer to the start of the list in . + The items in the linked list are linked by the field. */ + sdetails->valid = 1; + sdetails->resolved = 1; + return NULL; + } + else + { + /* Lookup failed, return human readable error string */ + if (ecode == EAI_AGAIN) + return _("Cannot resolve server name"); + else + return _((char*)gai_strerror(ecode)); + } + } + + sdetails->valid = 1; + return NULL; +} + +char *parse_server_addr(struct server_details *sdetails) +{ + if (sdetails->addr_type == AF_INET) { - addr->in.sin_port = htons(serv_port); - addr->sa.sa_family = source_addr->sa.sa_family = AF_INET; + sdetails->addr->in.sin_port = htons(sdetails->serv_port); + sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET; #ifdef HAVE_SOCKADDR_SA_LEN source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in); #endif - source_addr->in.sin_addr.s_addr = INADDR_ANY; - source_addr->in.sin_port = htons(daemon->query_port); + sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY; + sdetails->source_addr->in.sin_port = htons(daemon->query_port); - if (source) + if (sdetails->source) { - if (flags) - *flags |= SERV_HAS_SOURCE; - source_addr->in.sin_port = htons(source_port); - if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0)) + if (sdetails->flags) + *sdetails->flags |= SERV_HAS_SOURCE; + sdetails->source_addr->in.sin_port = htons(sdetails->source_port); + if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 0) { + if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 1) + { + sdetails->source_addr->sa.sa_family = AF_INET6; + /* When resolving a server IP by hostname, we can simply skip mismatching + server / source IP pairs. Otherwise, when an IP address is given directly, + this is a fatal error. */ + if (!sdetails->resolved) + return _("cannot use IPv4 server address with IPv6 source address"); + } + else + { #if defined(SO_BINDTODEVICE) - if (interface_opt) - return _("interface can only be specified once"); - - source_addr->in.sin_addr.s_addr = INADDR_ANY; - safe_strncpy(interface, source, IF_NAMESIZE); + if (sdetails->interface_opt) + return _("interface can only be specified once"); + + sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY; + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); #else - return _("interface binding not supported"); + return _("interface binding not supported"); #endif + } } } } - else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0) + else if (sdetails->addr_type == AF_INET6) { - if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0) + if (sdetails->scope_id && (sdetails->scope_index = if_nametoindex(sdetails->scope_id)) == 0) return _("bad interface name"); - - addr->in6.sin6_port = htons(serv_port); - addr->in6.sin6_scope_id = scope_index; - source_addr->in6.sin6_addr = in6addr_any; - source_addr->in6.sin6_port = htons(daemon->query_port); - source_addr->in6.sin6_scope_id = 0; - addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6; - addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0; + + sdetails->addr->in6.sin6_port = htons(sdetails->serv_port); + sdetails->addr->in6.sin6_scope_id = sdetails->scope_index; + sdetails->source_addr->in6.sin6_addr = in6addr_any; + sdetails->source_addr->in6.sin6_port = htons(daemon->query_port); + sdetails->source_addr->in6.sin6_scope_id = 0; + sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET6; + sdetails->addr->in6.sin6_flowinfo = sdetails->source_addr->in6.sin6_flowinfo = 0; #ifdef HAVE_SOCKADDR_SA_LEN - addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6); + sdetails->addr->in6.sin6_len = sdetails->source_addr->in6.sin6_len = sizeof(addr->in6); #endif - if (source) + if (sdetails->source) { - if (flags) - *flags |= SERV_HAS_SOURCE; - source_addr->in6.sin6_port = htons(source_port); - if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0) + if (sdetails->flags) + *sdetails->flags |= SERV_HAS_SOURCE; + sdetails->source_addr->in6.sin6_port = htons(sdetails->source_port); + if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 0) { + if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 1) + { + sdetails->source_addr->sa.sa_family = AF_INET; + /* When resolving a server IP by hostname, we can simply skip mismatching + server / source IP pairs. Otherwise, when an IP address is given directly, + this is a fatal error. */ + if(!sdetails->resolved) + return _("cannot use IPv6 server address with IPv4 source address"); + } + else + { #if defined(SO_BINDTODEVICE) - if (interface_opt) - return _("interface can only be specified once"); - - source_addr->in6.sin6_addr = in6addr_any; - safe_strncpy(interface, source, IF_NAMESIZE); + if (sdetails->interface_opt) + return _("interface can only be specified once"); + + sdetails->source_addr->in6.sin6_addr = in6addr_any; + safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE); #else - return _("interface binding not supported"); + return _("interface binding not supported"); #endif + } } } } - else + else if (sdetails->addr_type != AF_LOCAL) return _("bad address"); - + return NULL; } +int parse_server_next(struct server_details *sdetails) +{ + /* Looping over resolved addresses? */ + if (sdetails->hostinfo) + { + /* Get address type */ + sdetails->addr_type = sdetails->hostinfo->ai_family; + + /* Get address */ + if (sdetails->addr_type == AF_INET) + memcpy(&sdetails->addr->in.sin_addr, + &((struct sockaddr_in *) sdetails->hostinfo->ai_addr)->sin_addr, + sizeof(sdetails->addr->in.sin_addr)); + else if (sdetails->addr_type == AF_INET6) + memcpy(&sdetails->addr->in6.sin6_addr, + &((struct sockaddr_in6 *) sdetails->hostinfo->ai_addr)->sin6_addr, + sizeof(sdetails->addr->in6.sin6_addr)); + + /* Iterate to the next available address */ + sdetails->valid = sdetails->hostinfo->ai_next != NULL; + sdetails->hostinfo = sdetails->hostinfo->ai_next; + return 1; + } + else if (sdetails->valid) + { + /* When using an IP address, we return the address only once */ + sdetails->valid = 0; + return 1; + } + /* Stop iterating here, we used all available addresses */ + return 0; +} + static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int size) { int i, j; @@ -968,22 +1102,27 @@ static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int union mysockaddr serv_addr, source_addr; char interface[IF_NAMESIZE+1]; int count = 1, rem, addrbytes, addrbits; - + struct server_details sdetails = { 0 }; + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; + if (!server) flags = SERV_LITERAL_ADDRESS; - else if ((string = parse_server(server, &serv_addr, &source_addr, interface, &flags))) + else if ((string = parse_server(server, &sdetails))) return string; - + if (from_file) flags |= SERV_FROM_FILE; - + rem = size & 0x7; addrbytes = (32 - size) >> 3; addrbits = (32 - size) & 7; if (size > 32 || size < 1) return _("bad IPv4 prefix length"); - + /* Zero out last address bits according to CIDR mask */ ((u8 *)addr4)[3-addrbytes] &= ~((1 << addrbits)-1); @@ -997,23 +1136,40 @@ static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int *domain = 0; string = domain; msize = size/8; - + for (j = (rem == 0) ? msize-1 : msize; j >= 0; j--) { int dig = ((unsigned char *)addr4)[j]; - + if (j == msize) dig += i; - + string += sprintf(string, "%d.", dig); } - + sprintf(string, "in-addr.arpa"); - if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) - return _("error"); - } + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + while (parse_server_next(&sdetails)) + { + if ((string = parse_server_addr(&sdetails))) + return string; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + if (sdetails.resolved) + freeaddrinfo(sdetails.hostinfo); + } + } + return NULL; } @@ -1027,10 +1183,15 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in union mysockaddr serv_addr, source_addr; char interface[IF_NAMESIZE+1]; int count = 1, rem, addrbytes, addrbits; + struct server_details sdetails = { 0 }; + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; if (!server) flags = SERV_LITERAL_ADDRESS; - else if ((string = parse_server(server, &serv_addr, &source_addr, interface, &flags))) + else if ((string = parse_server(server, &sdetails))) return string; if (from_file) @@ -1071,10 +1232,27 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in sprintf(string, "ip6.arpa"); - if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) - return _("error"); - } + if (flags & SERV_LITERAL_ADDRESS) + { + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + else + { + while (parse_server_next(&sdetails)) + { + if ((string = parse_server_addr(&sdetails))) + return string; + + if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL)) + return _("error"); + } + if (sdetails.resolved) + freeaddrinfo(sdetails.hostinfo); + } + } + return NULL; } @@ -2157,15 +2335,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_DHCP_HOST: /* --dhcp-hostsfile */ case LOPT_DHCP_OPTS: /* --dhcp-optsfile */ - case LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */ - case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */ - case LOPT_HOST_INOTIFY: /* --hostsdir */ case 'H': /* --addn-hosts */ { struct hostsfile *new = opt_malloc(sizeof(struct hostsfile)); - static unsigned int hosts_index = SRC_AH; new->fname = opt_string_alloc(arg); - new->index = hosts_index++; + new->index = daemon->host_index++; new->flags = 0; if (option == 'H') { @@ -2181,21 +2355,29 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma { new->next = daemon->dhcp_opts_file; daemon->dhcp_opts_file = new; - } - else - { - new->next = daemon->dynamic_dirs; - daemon->dynamic_dirs = new; - if (option == LOPT_DHCP_INOTIFY) - new->flags |= AH_DHCP_HST; - else if (option == LOPT_DHOPT_INOTIFY) - new->flags |= AH_DHCP_OPT; - else if (option == LOPT_HOST_INOTIFY) - new->flags |= AH_HOSTS; } break; } + + case LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */ + case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */ + case LOPT_HOST_INOTIFY: /* --hostsdir */ + { + struct dyndir *new = opt_malloc(sizeof(struct dyndir)); + new->dname = opt_string_alloc(arg); + new->flags = 0; + new->next = daemon->dynamic_dirs; + daemon->dynamic_dirs = new; + if (option == LOPT_DHCP_INOTIFY) + new->flags |= AH_DHCP_HST; + else if (option == LOPT_DHOPT_INOTIFY) + new->flags |= AH_DHCP_OPT; + else if (option == LOPT_HOST_INOTIFY) + new->flags |= AH_HOSTS; + + break; + } case LOPT_AUTHSERV: /* --auth-server */ comma = split(arg); @@ -2781,13 +2963,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_LOCAL: /* --local */ case 'A': /* --address */ { - char *lastdomain = NULL, *domain = ""; + char *lastdomain = NULL, *domain = "", *cur_domain; u16 flags = 0; char *err; union all_addr addr; union mysockaddr serv_addr, source_addr; char interface[IF_NAMESIZE+1]; + struct server_details sdetails = { 0 }; + sdetails.addr = &serv_addr; + sdetails.source_addr = &source_addr; + sdetails.interface = interface; + sdetails.flags = &flags; + unhide_metas(arg); /* split the domain args, if any and skip to the end of them. */ @@ -2820,36 +3008,55 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } else { - if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags))) + if ((err = parse_server(arg, &sdetails))) ret_err(err); } if (servers_only && option == 'S') flags |= SERV_FROM_FILE; - - while (1) + + cur_domain = domain; + while ((flags & SERV_LITERAL_ADDRESS) || parse_server_next(&sdetails)) { - /* server=//1.2.3.4 is special. */ - if (lastdomain) + cur_domain = domain; + + if (!(flags & SERV_LITERAL_ADDRESS) && (err = parse_server_addr(&sdetails))) + ret_err(err); + + /* When source is set only use DNS records of the same type and skip all others */ + if (flags & SERV_HAS_SOURCE && sdetails.addr_type != sdetails.source_addr->sa.sa_family) + continue; + + while (1) { - if (strlen(domain) == 0) - flags |= SERV_FOR_NODOTS; - else - flags &= ~SERV_FOR_NODOTS; + /* server=//1.2.3.4 is special. */ + if (lastdomain) + { + if (strlen(cur_domain) == 0) + flags |= SERV_FOR_NODOTS; + else + flags &= ~SERV_FOR_NODOTS; + + /* address=/#/ matches the same as without domain */ + if (option == 'A' && cur_domain[0] == '#' && cur_domain[1] == 0) + cur_domain[0] = 0; + } + + if (!add_update_server(flags, sdetails.addr, sdetails.source_addr, sdetails.interface, cur_domain, &addr)) + ret_err(gen_err); + + if (!lastdomain || cur_domain == lastdomain) + break; - /* address=/#/ matches the same as without domain */ - if (option == 'A' && domain[0] == '#' && domain[1] == 0) - domain[0] = 0; + cur_domain += strlen(cur_domain) + 1; } - - if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr)) - ret_err(gen_err); - - if (!lastdomain || domain == lastdomain) + + if (flags & SERV_LITERAL_ADDRESS) break; - - domain += strlen(domain) + 1; } + + if (sdetails.resolved) + freeaddrinfo(sdetails.hostinfo); break; } @@ -3183,6 +3390,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma if (daemon->query_port == 0) daemon->osport = 1; break; + + case LOPT_RANDPORT_LIM: /* --port-limit */ + if (!atoi_check(arg, &daemon->randport_limit) || (daemon->randport_limit < 1)) + ret_err(gen_err); + break; case 'T': /* --local-ttl */ case LOPT_NEGTTL: /* --neg-ttl */ @@ -3218,7 +3430,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma daemon->local_ttl = (unsigned long)ttl; break; } + + case LOPT_FAST_RETRY: + daemon->fast_retry_timeout = TIMEOUT; + if (!arg) + daemon->fast_retry_time = DEFAULT_FAST_RETRY; + else + { + int retry; + + comma = split(arg); + if (!atoi_check(arg, &retry) || retry < 50) + ret_err(gen_err); + daemon->fast_retry_time = retry; + + if (comma) + { + if (!atoi_check(comma, &retry)) + ret_err(gen_err); + daemon->fast_retry_timeout = retry/1000; + } + } + break; + #ifdef HAVE_DHCP case 'X': /* --dhcp-lease-max */ if (!atoi_check(arg, &daemon->dhcp_max)) @@ -5498,6 +5733,8 @@ 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->randport_limit = 1; + daemon->host_index = SRC_AH; #ifndef NO_ID add_txt("version.bind", "dnsmasq-" VERSION, 0 ); diff --git a/src/dnsmasq/rfc1035.c b/src/dnsmasq/rfc1035.c index e9aeceff2..3a0c03af8 100644 --- a/src/dnsmasq/rfc1035.c +++ b/src/dnsmasq/rfc1035.c @@ -1373,8 +1373,15 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int #undef CHECK_LIMIT } +static int crec_isstale(struct crec *crecp, time_t now) +{ + return (!(crecp->flags & F_IMMORTAL)) && difftime(crecp->ttd, now) < 0; +} + static unsigned long crec_ttl(struct crec *crecp, time_t now) { + signed long ttl = difftime(crecp->ttd, now); + /* Return 0 ttl for DHCP entries, which might change before the lease expires, unless configured otherwise. */ @@ -1383,8 +1390,8 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now) int conf_ttl = daemon->use_dhcp_ttl ? daemon->dhcp_ttl : daemon->local_ttl; /* Apply ceiling of actual lease length to configured TTL. */ - if (!(crecp->flags & F_IMMORTAL) && (crecp->ttd - now) < conf_ttl) - return crecp->ttd - now; + if (!(crecp->flags & F_IMMORTAL) && ttl < conf_ttl) + return ttl; return conf_ttl; } @@ -1393,9 +1400,13 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now) if (crecp->flags & F_IMMORTAL) return crecp->ttd; + /* Stale cache entries. */ + if (ttl < 0) + return 0; + /* Return the Max TTL value if it is lower than the actual TTL */ - if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl)) - return crecp->ttd - now; + if (daemon->max_ttl == 0 || ((unsigned)ttl < daemon->max_ttl)) + return ttl; else return daemon->max_ttl; } @@ -1408,7 +1419,8 @@ static int cache_validated(const struct crec *crecp) /* return zero if we can't answer from cache, or packet size if we can */ 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) + time_t now, int ad_reqd, int do_bit, int have_pseudoheader, + int *stale) { char *name = daemon->namebuff; unsigned char *p, *ansp; @@ -1424,6 +1436,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, size_t len; int rd_bit = (header->hb3 & HB3_RD); + if (stale) + *stale = 0; + /* never answer queries with RD unset, to avoid cache snooping. */ if (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0 || @@ -1472,13 +1487,22 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME | F_NXDOMAIN))) { char *cname_target; - + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + stale_flag = F_STALE; + } + if (crecp->flags & F_NXDOMAIN) { if (qtype == T_CNAME) { if (!dryrun) - log_query(crecp->flags, name, NULL, record_source(crecp->uid), 0); + log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); auth = 0; nxdomain = 1; ans = 1; @@ -1500,7 +1524,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) { - log_query(crecp->flags, name, NULL, record_source(crecp->uid), 0); + log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, crec_ttl(crecp, now), &nameoffset, T_CNAME, C_IN, "d", cname_target)) @@ -1669,22 +1693,33 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, { do { + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + stale_flag = F_STALE; + } + /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */ if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP))) continue; + if (!(crecp->flags & F_DNSSECOK)) sec_data = 0; - + ans = 1; - + if (crecp->flags & F_NEG) { auth = 0; if (crecp->flags & F_NXDOMAIN) nxdomain = 1; if (!dryrun) - log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL, 0); + log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0); } else { @@ -1692,9 +1727,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, auth = 0; if (!dryrun) { - log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, + log_query(stale_flag | (crecp->flags & ~F_FORWARD), cache_get_name(crecp), &addr, record_source(crecp->uid), 0); - + if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, crec_ttl(crecp, now), NULL, T_PTR, C_IN, "d", cache_get_name(crecp))) @@ -1801,7 +1836,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if ((crecp = cache_find_by_name(NULL, name, now, flag | F_NXDOMAIN | (dryrun ? F_NO_RR : 0)))) { int localise = 0; - + /* See if a putative address is on the network from which we received the query, is so we'll filter other answers. */ if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4) @@ -1823,6 +1858,16 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, (rd_bit && (!do_bit || cache_validated(crecp)) )) do { + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + stale_flag = F_STALE; + } + /* don't answer wildcard queries with data not from /etc/hosts or DHCP leases */ if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))) @@ -1840,7 +1885,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!dryrun) // Pi-hole modification: Added record_source(crecp->uid) such that the subroutines know // where the reply came from (e.g. gravity.list) - log_query(crecp->flags, name, NULL, record_source(crecp->uid), 0); + log_query(stale_flag | crecp->flags, name, NULL, record_source(crecp->uid), 0); } else { @@ -1857,7 +1902,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ans = 1; if (!dryrun) { - log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr, + log_query(stale_flag | (crecp->flags & ~F_REVERSE), name, &crecp->addr, record_source(crecp->uid), 0); // ****************************** Pi-hole modification ****************************** const char *src = crecp != NULL ? crecp->flags & F_BIGNAME ? crecp->name.bname->name : crecp->name.sname : NULL; @@ -1981,6 +2026,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))) do { + int stale_flag = 0; + + if (crec_isstale(crecp, now)) + { + if (stale) + *stale = 1; + + stale_flag = F_STALE; + } /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases, except for NXDOMAIN */ if (qtype == T_ANY && !(crecp->flags & (F_NXDOMAIN))) break; @@ -1996,12 +2050,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (crecp->flags & F_NXDOMAIN) nxdomain = 1; if (!dryrun) - log_query(crecp->flags, name, NULL, NULL, 0); + log_query(stale_flag | crecp->flags, name, NULL, NULL, 0); } else if (!dryrun) { char *target = blockdata_retrieve(crecp->addr.srv.target, crecp->addr.srv.targetlen, NULL); - log_query(crecp->flags, name, NULL, NULL, 0); + log_query(stale_flag | crecp->flags, name, NULL, NULL, 0); if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, crec_ttl(crecp, now), NULL, T_SRV, C_IN, "sssd", diff --git a/src/dnsmasq/rfc2131.c b/src/dnsmasq/rfc2131.c index 2056cbad1..17e97b52a 100644 --- a/src/dnsmasq/rfc2131.c +++ b/src/dnsmasq/rfc2131.c @@ -1153,15 +1153,22 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, tagif_netid = run_tag_if(&context->netid); } - log_tags(tagif_netid, ntohl(mess->xid)); apply_delay(mess->xid, recvtime, tagif_netid); if (option_bool(OPT_RAPID_COMMIT) && option_find(mess, sz, OPTION_RAPID_COMMIT, 0)) { rapid_commit = 1; + /* If a lease exists for this host and another address, squash it. */ + if (lease && lease->addr.s_addr != mess->yiaddr.s_addr) + { + lease_prune(lease, now); + lease = NULL; + } goto rapid_commit; } + log_tags(tagif_netid, ntohl(mess->xid)); + daemon->metrics[METRIC_DHCPOFFER]++; log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); diff --git a/src/dnsmasq/util.c b/src/dnsmasq/util.c index 140a3543c..e0ce67d3a 100644 --- a/src/dnsmasq/util.c +++ b/src/dnsmasq/util.c @@ -364,6 +364,19 @@ int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) return 0; } +int sockaddr_isnull(const union mysockaddr *s) +{ + if (s->sa.sa_family == AF_INET && + s->in.sin_addr.s_addr == 0) + return 1; + + if (s->sa.sa_family == AF_INET6 && + IN6_IS_ADDR_UNSPECIFIED(&s->in6.sin6_addr)) + return 1; + + return 0; +} + int sa_len(union mysockaddr *addr) { #ifdef HAVE_SOCKADDR_SA_LEN @@ -457,6 +470,15 @@ time_t dnsmasq_time(void) #endif } +u32 dnsmasq_milliseconds(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (tv.tv_sec) * 1000 + (tv.tv_usec / 1000); +} + int netmask_length(struct in_addr mask) { int zero_count = 0; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 7506d1680..49c8bc872 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -95,7 +95,7 @@ static struct { static union mysockaddr last_server = {{ 0 }}; unsigned char* pihole_privacylevel = &config.privacylevel; -const char *flagnames[] = {"F_IMMORTAL ", "F_NAMEP ", "F_REVERSE ", "F_FORWARD ", "F_DHCP ", "F_NEG ", "F_HOSTS ", "F_IPV4 ", "F_IPV6 ", "F_BIGNAME ", "F_NXDOMAIN ", "F_CNAME ", "F_DNSKEY ", "F_CONFIG ", "F_DS ", "F_DNSSECOK ", "F_UPSTREAM ", "F_RRNAME ", "F_SERVER ", "F_QUERY ", "F_NOERR ", "F_AUTH ", "F_DNSSEC ", "F_KEYTAG ", "F_SECSTAT ", "F_NO_RR ", "F_IPSET ", "F_NOEXTRA ", "F_SERVFAIL", "F_RCODE"}; +const char *flagnames[] = {"F_IMMORTAL ", "F_NAMEP ", "F_REVERSE ", "F_FORWARD ", "F_DHCP ", "F_NEG ", "F_HOSTS ", "F_IPV4 ", "F_IPV6 ", "F_BIGNAME ", "F_NXDOMAIN ", "F_CNAME ", "F_DNSKEY ", "F_CONFIG ", "F_DS ", "F_DNSSECOK ", "F_UPSTREAM ", "F_RRNAME ", "F_SERVER ", "F_QUERY ", "F_NOERR ", "F_AUTH ", "F_DNSSEC ", "F_KEYTAG ", "F_SECSTAT ", "F_NO_RR ", "F_IPSET ", "F_NOEXTRA ", "F_SERVFAIL", "F_RCODE", "F_SRV", "F_STALE" }; void FTL_hook(unsigned int flags, const char *name, union all_addr *addr, char *arg, int id, unsigned short type, const char* file, const int line) { @@ -173,6 +173,8 @@ void FTL_hook(unsigned int flags, const char *name, union all_addr *addr, char * // This is inspired by make_local_answer() size_t _FTL_make_answer(struct dns_header *header, char *limit, const size_t len, int *ede, const char *file, const int line) { + if(config.debug & DEBUG_FLAGS) + logg("FTL_make_answer() called from %s:%d", short_path(file), line); // Exit early if there are no questions in this query if(ntohs(header->qdcount) == 0) return 0; @@ -1930,6 +1932,9 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al logg("***** Unknown cache query"); } + // Is this a stale reply? + const bool stale = flags & F_STALE; + // Possible debugging output if(config.debug & DEBUG_QUERIES) { @@ -1981,16 +1986,18 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al if(cached || last_server.sa.sa_family == 0) // Log cache or upstream reply from unknown source - logg("**** got %s reply: %s is %s (ID %i, %s:%i)", - cached ? "cache" : "upstream", dispname, answer, id, file, line); + logg("**** got %s%s reply: %s is %s (ID %i, %s:%i)", + stale ? "stale ": "", cached ? "cache" : "upstream", + dispname, answer, id, file, line); else { char ip[ADDRSTRLEN+1] = { 0 }; in_port_t port = 0; mysockaddr_extract_ip_port(&last_server, ip, &port); // Log server which replied to our request - logg("**** got %s reply from %s#%d: %s is %s (ID %i, %s:%i)", - cached ? "cache" : "upstream", ip, port, dispname, answer, id, file, line); + logg("**** got %s%s reply from %s#%d: %s is %s (ID %i, %s:%i)", + stale ? "stale ": "", cached ? "cache" : "upstream", + ip, port, dispname, answer, id, file, line); } } @@ -2040,13 +2047,16 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al return; } + // Determine query status (live or stale data?) + const enum query_status qs = stale ? QUERY_CACHE_STALE : QUERY_CACHE; + // This is either a reply served from cache or a blocked query (which appear // to be from cache because of flags containing F_HOSTS) if(cached) { // Set status of this query only if this is not a blocked query if(!is_blocked(query->status)) - query_set_status(query, QUERY_CACHE); + query_set_status(query, qs); // Detect if returned IP indicates that this query was blocked const enum query_status new_status = detect_blocked_IP(flags, addr, query, domain); @@ -2087,7 +2097,7 @@ static void FTL_reply(const unsigned int flags, const char *name, const union al // Answered from a custom (user provided) cache file or because // we're the authoritative DNS server (e.g. DHCP server and this // is our own domain) - query_set_status(query, QUERY_CACHE); + query_set_status(query, qs); // Save reply type and update individual reply counters query_set_reply(flags, 0, addr, query, response); @@ -3340,7 +3350,7 @@ int check_struct_sizes(void) result += check_one_struct("regexData", sizeof(regexData), 56, 44); result += check_one_struct("SharedMemory", sizeof(SharedMemory), 24, 12); result += check_one_struct("ShmSettings", sizeof(ShmSettings), 16, 16); - result += check_one_struct("countersStruct", sizeof(countersStruct), 244, 244); + result += check_one_struct("countersStruct", sizeof(countersStruct), 248, 248); result += check_one_struct("sqlite3_stmt_vec", sizeof(sqlite3_stmt_vec), 32, 16); if(result == 0) diff --git a/src/enums.h b/src/enums.h index cd4dd3fde..9ec7dc9d4 100644 --- a/src/enums.h +++ b/src/enums.h @@ -46,6 +46,7 @@ enum query_status { QUERY_IN_PROGRESS, QUERY_DBBUSY, QUERY_SPECIAL_DOMAIN, + QUERY_CACHE_STALE, QUERY_STATUS_MAX } __attribute__ ((packed)); diff --git a/src/gc.c b/src/gc.c index c10c5d60c..47734c110 100644 --- a/src/gc.c +++ b/src/gc.c @@ -215,6 +215,7 @@ void *GC_thread(void *val) // Adjusting counters is done below in moveOverTimeMemory() break; case QUERY_CACHE: + case QUERY_CACHE_STALE: // Answered from local cache _or_ local config break; case QUERY_GRAVITY: // Blocked by Pi-hole's blocking lists (fall through)