summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2022-10-13 15:37:52 +0100
committerSimon Kelley <simon@thekelleys.org.uk>2022-10-13 15:37:52 +0100
commitfdd9a96a8cb71dc240e319575575d7a4cfda5e41 (patch)
tree9fcb0671fc3658fd3b585aacc37c0ad74500b081
parentb87d7aa0411f267a7e0fb1184643a14d4b54a59b (diff)
parenta5cbe6d1127927e762a38bfde0c5822b11eda87a (diff)
downloaddnsmasq-fdd9a96a8cb71dc240e319575575d7a4cfda5e41.tar.gz
Merge branch 'aws'
-rw-r--r--CHANGELOG16
-rw-r--r--dbus/DBus-interface9
-rw-r--r--man/dnsmasq.826
-rw-r--r--src/cache.c28
-rw-r--r--src/config.h2
-rw-r--r--src/dbus.c84
-rw-r--r--src/dnsmasq.c20
-rw-r--r--src/dnsmasq.h21
-rw-r--r--src/forward.c526
-rw-r--r--src/metrics.c22
-rw-r--r--src/metrics.h3
-rw-r--r--src/network.c13
-rw-r--r--src/option.c40
-rw-r--r--src/rfc1035.c88
-rw-r--r--src/util.c22
15 files changed, 758 insertions, 162 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 891caff..db13583 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -89,6 +89,22 @@ version 2.87
client facing network. Thanks to Luis Thomas for spotting this
and initial patch.
+ Add --fast-dns-retry option. This gives dnsmasq the ability
+ to originate retries for upstream DNS queries itself, rather
+ than relying on the downstream client. This is most useful
+ when doing DNSSEC over unreliable upstream network. It comes
+ with some cost in memory usage and network bandwidth.
+
+ Add -use-stale-cache option. When set, if a DNS name exists
+ in the cache, but its time-to-live has expired, dnsmasq will
+ return the data anyway. (It attempts to refresh the
+ data with an upstream query after returning the stale data.)
+ This can improve speed and reliability. It comes
+ at the expense of sometimes returning out-of-date data and
+ less efficient cache utilisation, since old data cannot be
+ flushed when its TTL expires, so the cache becomes
+ strictly least-recently-used.
+
version 2.86
Handle DHCPREBIND requests in the DHCPv6 server code.
diff --git a/dbus/DBus-interface b/dbus/DBus-interface
index df41d79..59b41b1 100644
--- a/dbus/DBus-interface
+++ b/dbus/DBus-interface
@@ -252,6 +252,15 @@ GetMetrics
Returns an array with various metrics for DNS and DHCP.
+GetServerMetrics
+----------------
+
+Returns per-DNS-server metrics.
+
+ClearMetrics
+------------
+
+Clear call metric counters, global and per-server.
2. SIGNALS
----------
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index adb10a4..3ae3000 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -105,6 +105,16 @@ Dnsmasq limits the value of this option to one hour, unless recompiled.
.B --auth-ttl=<time>
Set the TTL value returned in answers from the authoritative server.
.TP
+.B --fast-dns-retry=[<initial retry delay in ms>[,<time to continue retries in ms>]]
+Under normal circumstances, dnsmasq relies on DNS clients to do retries; it
+does not generate timeouts itself. Setting this option
+instructs dnsmasq to generate its own retries starting after a delay
+which defaults to 1000ms. If the second parameter is given this controls
+how long the retries will continue for
+otherwise this defaults to 10000ms. Retries are repeated with exponential
+backoff. Using this option increases memory usage and
+network bandwidth.
+.TP
.B \-k, --keep-in-foreground
Do not go into the background at startup but otherwise run as
normal. This is intended for use when dnsmasq is run under daemontools
@@ -180,7 +190,15 @@ specific UDP port <query_port> instead of using random ports. NOTE
that using this option will make dnsmasq less secure against DNS
spoofing attacks but it may be faster and use less resources. Setting this option
to zero makes dnsmasq use a single port allocated to it by the
-OS: this was the default behaviour in versions prior to 2.43.
+OS: this was the default behaviour in versions prior to 2.43.
+.TP
+.B --port-limit=<#ports>
+By default, when sending a query via random ports to multiple upstream servers or
+retrying a query dnsmasq will use a single random port for all the tries/retries.
+This option allows a larger number of ports to be used, which can increase robustness
+in certain network configurations. Note that increasing this to more than
+two or three can have security and resource implications and should only
+be done with understanding of those.
.TP
.B --min-port=<port>
Do not use ports less than that given as source for outbound DNS
@@ -797,6 +815,12 @@ Disable negative caching. Negative caching allows dnsmasq to remember
"no such domain" answers from upstream nameservers and answer
identical queries without forwarding them again.
.TP
+.B --use-stale-cache
+When set, if a DNS name exists in the cache, but its time-to-live has expired, dnsmasq will return the data anyway. (It attempts to refresh the
+data with an upstream query after returning the stale data.) This can improve speed and reliability. It comes at the expense
+of sometimes returning out-of-date data and less efficient cache utilisation, since old data cannot be flushed when its TTL expires, so the cache becomes
+strictly least-recently-used.
+.TP
.B \-0, --dns-forward-max=<queries>
Set the maximum number of concurrent DNS queries. The default value is
150, which should be fine for most setups. The only known situation
diff --git a/src/cache.c b/src/cache.c
index 8ed4740..4ca04fd 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -374,6 +374,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;
@@ -553,7 +558,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;
@@ -628,8 +633,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
{
@@ -1725,6 +1734,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
@@ -1739,16 +1750,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))
@@ -2079,6 +2097,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/config.h b/src/config.h
index cd37900..df1d985 100644
--- a/src/config.h
+++ b/src/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 50 /* try all servers every 50 queries */
#define FORWARD_TIME 20 /* or 20 seconds */
#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */
@@ -58,6 +59,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/dbus.c b/src/dbus.c
index bf6b661..4fdad46 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -91,6 +91,11 @@ const char* introspection_xml_template =
" <method name=\"GetMetrics\">\n"
" <arg name=\"metrics\" direction=\"out\" type=\"a{su}\"/>\n"
" </method>\n"
+" <method name=\"GetServerMetrics\">\n"
+" <arg name=\"metrics\" direction=\"out\" type=\"a{ss}\"/>\n"
+" </method>\n"
+" <method name=\"ClearMetrics\">\n"
+" </method>\n"
" </interface>\n"
"</node>\n";
@@ -644,6 +649,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 +795,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.c b/src/dnsmasq.c
index 858c731..bd3dcf5 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -265,6 +265,10 @@ int main (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();
@@ -1055,19 +1059,20 @@ int main (int argc, char **argv)
while (1)
{
- 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
@@ -2014,9 +2019,6 @@ static void check_dns_listeners(time_t now)
buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
- shutdown(confd, SHUT_RDWR);
- close(confd);
-
if (buff)
free(buff);
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 36d17fe..1835a11 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -279,7 +279,8 @@ 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_LAST 72
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -510,6 +511,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. */
@@ -584,7 +586,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
@@ -755,11 +758,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. */
@@ -1139,6 +1144,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;
@@ -1183,6 +1189,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;
@@ -1337,7 +1344,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);
@@ -1399,10 +1407,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);
@@ -1462,6 +1472,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/forward.c b/src/forward.c
index fa80251..b4e3c5a 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -170,7 +170,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;
@@ -313,6 +313,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);
@@ -360,14 +367,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;
@@ -390,7 +397,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++;
if (!filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last))
goto reply;
@@ -398,13 +408,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,
@@ -553,7 +563,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);
@@ -592,6 +605,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;
@@ -807,6 +875,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;
}
@@ -949,6 +1020,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;
@@ -1066,7 +1139,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) &&
@@ -1097,38 +1170,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;
}
}
@@ -1162,6 +1247,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) &&
@@ -1268,10 +1362,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))
{
@@ -1282,14 +1372,20 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
}
#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)
+ 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);
+ }
}
}
}
@@ -1379,7 +1475,7 @@ void receive_query(struct listener *listen, time_t now)
int family = listen->addr.sa.sa_family;
/* Can always get recvd interface for IPv6 */
int check_dst = !option_bool(OPT_NOWILD) || family == AF_INET6;
-
+
/* packet buffer overwritten */
daemon->srv_save = NULL;
@@ -1702,16 +1798,27 @@ 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;
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
@@ -1722,12 +1829,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]++;
}
}
@@ -1954,8 +2088,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;
if (!packet || getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet;
@@ -2010,13 +2145,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;
@@ -2041,24 +2200,27 @@ 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);
-
#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);
+
#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);
@@ -2114,11 +2276,14 @@ unsigned char *tcp_request(int confd, time_t now,
/* RFC 6840 5.7 */
if (header->hb4 & HB4_AD)
ad_reqd = 1;
+
+ 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);
@@ -2236,6 +2401,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)
{
@@ -2246,13 +2414,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);
@@ -2267,8 +2441,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;
}
@@ -2280,16 +2472,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);
}
@@ -2319,39 +2531,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)
@@ -2362,10 +2628,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;
+ }
}
}
@@ -2477,13 +2752,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)
{
@@ -2539,6 +2814,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;
}
@@ -2560,6 +2836,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;
}
@@ -2571,8 +2848,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/metrics.c b/src/metrics.c
index 6873529..f3e6728 100644
--- a/src/metrics.c
+++ b/src/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/metrics.h b/src/metrics.h
index df72ec6..6f62a40 100644
--- a/src/metrics.h
+++ b/src/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/network.c b/src/network.c
index b8dcc75..1e41a78 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1371,7 +1371,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));
}
@@ -1400,7 +1400,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/option.c b/src/option.c
index c5e8cb4..f2110cf 100644
--- a/src/option.c
+++ b/src/option.c
@@ -181,6 +181,9 @@ 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
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -366,6 +369,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 }
};
@@ -423,6 +429,7 @@ static struct {
{ 'M', ARG_DUP, "<bootp opts>", 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, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL },
{ LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL},
@@ -430,6 +437,7 @@ static struct {
{ 'P', ARG_ONE, "<integer>", 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, "<integer>", 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, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE },
{ LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
@@ -443,6 +451,7 @@ static struct {
{ LOPT_MAXTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL },
{ LOPT_MAXCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live ceiling for cache."), NULL },
{ LOPT_MINCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live floor for cache."), NULL },
+ { LOPT_FAST_RETRY, ARG_ONE, "<milliseconds>", gettext_noop("Retry DNS queries after this many milliseconds."), NULL},
{ 'u', ARG_ONE, "<username>", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER },
{ 'U', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP vendor class to tag."), NULL },
{ 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL },
@@ -3179,6 +3188,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 */
@@ -3214,7 +3228,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))
@@ -5494,7 +5531,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;
+
#ifndef NO_ID
add_txt("version.bind", "dnsmasq-" VERSION, 0 );
add_txt("authors.bind", "Simon Kelley", 0);
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 60ef272..d6d3b49 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1360,8 +1360,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. */
@@ -1370,8 +1377,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;
}
@@ -1380,9 +1387,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;
}
@@ -1395,7 +1406,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;
@@ -1411,6 +1423,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 ||
@@ -1459,13 +1474,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;
@@ -1487,7 +1511,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))
@@ -1656,22 +1680,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
{
@@ -1679,7 +1714,7 @@ 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,
@@ -1788,7 +1823,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)
@@ -1810,6 +1845,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)))
@@ -1825,7 +1870,7 @@ 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
{
@@ -1842,7 +1887,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);
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
@@ -1953,6 +1998,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;
@@ -1968,12 +2022,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/util.c b/src/util.c
index 140a354..e0ce67d 100644
--- a/src/util.c
+++ b/src/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;