mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-20 02:38:32 +00:00
Revise resource handling for number of concurrent DNS queries.
This used to have a global limit, but that has a problem when using different servers for different upstream domains. Queries which are routed by domain to an upstream server which is not responding will build up and trigger the limit, which breaks DNS service for all other domains which could be handled by other servers. The change is to make the limit per server-group, where a server group is the set of servers configured for a particular domain. In the common case, where only default servers are declared, there is no effective change.
This commit is contained in:
11
CHANGELOG
11
CHANGELOG
@@ -38,6 +38,17 @@ version 2.86
|
|||||||
Finally, some of the oldest and gnarliest code in dnsmasq has had
|
Finally, some of the oldest and gnarliest code in dnsmasq has had
|
||||||
a significant clean-up. It's far from perfect, but it _is_ better.
|
a significant clean-up. It's far from perfect, but it _is_ better.
|
||||||
|
|
||||||
|
Revise resource handling for number of concurrent DNS queries. This
|
||||||
|
used to have a global limit, but that has a problem when using
|
||||||
|
different servers for different upstream domains. Queries which are
|
||||||
|
routed by domain to an upstream server which is not responding will
|
||||||
|
build up and trigger the limit, which breaks DNS service for
|
||||||
|
all other domains which could be handled by other servers. The
|
||||||
|
change is to make the limit per server-group, where a server group
|
||||||
|
is the set of servers configured for a particular domain. In the
|
||||||
|
common case, where only default servers are declared, there is
|
||||||
|
no effective change.
|
||||||
|
|
||||||
|
|
||||||
version 2.85
|
version 2.85
|
||||||
Fix problem with DNS retries in 2.83/2.84.
|
Fix problem with DNS retries in 2.83/2.84.
|
||||||
|
|||||||
@@ -731,7 +731,8 @@ identical queries without forwarding them again.
|
|||||||
Set the maximum number of concurrent DNS queries. The default value is
|
Set the maximum number of concurrent DNS queries. The default value is
|
||||||
150, which should be fine for most setups. The only known situation
|
150, which should be fine for most setups. The only known situation
|
||||||
where this needs to be increased is when using web-server log file
|
where this needs to be increased is when using web-server log file
|
||||||
resolvers, which can generate large numbers of concurrent queries.
|
resolvers, which can generate large numbers of concurrent queries. This
|
||||||
|
parameter actually controls the number of concurrent queries per server group, where a server group is the set of server(s) associated with a single domain. So if a domain has it's own server via --server=/example.com/1.2.3.4 and 1.2.3.4 is not responding, but queries for *.example.com cannot go elsewhere, then other queries will not be affected. On configurations with many such server groups and tight resources, this value may need to be reduced.
|
||||||
.TP
|
.TP
|
||||||
.B --dnssec
|
.B --dnssec
|
||||||
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
|
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ struct daemon *daemon;
|
|||||||
static volatile pid_t pid = 0;
|
static volatile pid_t pid = 0;
|
||||||
static volatile int pipewrite;
|
static volatile int pipewrite;
|
||||||
|
|
||||||
static int set_dns_listeners(time_t now);
|
static void set_dns_listeners(void);
|
||||||
static void check_dns_listeners(time_t now);
|
static void check_dns_listeners(time_t now);
|
||||||
static void sig_handler(int sig);
|
static void sig_handler(int sig);
|
||||||
static void async_event(int pipe, time_t now);
|
static void async_event(int pipe, time_t now);
|
||||||
@@ -1042,16 +1042,10 @@ int main (int argc, char **argv)
|
|||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
int t, timeout = -1;
|
int timeout = -1;
|
||||||
|
|
||||||
poll_reset();
|
poll_reset();
|
||||||
|
|
||||||
/* if we are out of resources, find how long we have to wait
|
|
||||||
for some to come free, we'll loop around then and restart
|
|
||||||
listening for queries */
|
|
||||||
if ((t = set_dns_listeners(now)) != 0)
|
|
||||||
timeout = t * 1000;
|
|
||||||
|
|
||||||
/* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
|
/* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
|
||||||
if (daemon->tftp_trans ||
|
if (daemon->tftp_trans ||
|
||||||
(option_bool(OPT_DBUS) && !daemon->dbus))
|
(option_bool(OPT_DBUS) && !daemon->dbus))
|
||||||
@@ -1061,6 +1055,8 @@ int main (int argc, char **argv)
|
|||||||
else if (is_dad_listeners())
|
else if (is_dad_listeners())
|
||||||
timeout = 1000;
|
timeout = 1000;
|
||||||
|
|
||||||
|
set_dns_listeners();
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
set_dbus_listeners();
|
set_dbus_listeners();
|
||||||
#endif
|
#endif
|
||||||
@@ -1685,12 +1681,12 @@ void clear_cache_and_reload(time_t now)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int set_dns_listeners(time_t now)
|
static void set_dns_listeners(void)
|
||||||
{
|
{
|
||||||
struct serverfd *serverfdp;
|
struct serverfd *serverfdp;
|
||||||
struct listener *listener;
|
struct listener *listener;
|
||||||
struct randfd_list *rfl;
|
struct randfd_list *rfl;
|
||||||
int wait = 0, i;
|
int i;
|
||||||
|
|
||||||
#ifdef HAVE_TFTP
|
#ifdef HAVE_TFTP
|
||||||
int tftp = 0;
|
int tftp = 0;
|
||||||
@@ -1703,10 +1699,6 @@ static int set_dns_listeners(time_t now)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* will we be able to get memory? */
|
|
||||||
if (daemon->port != 0)
|
|
||||||
get_new_frec(now, &wait, NULL);
|
|
||||||
|
|
||||||
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
|
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
|
||||||
poll_listen(serverfdp->fd, POLLIN);
|
poll_listen(serverfdp->fd, POLLIN);
|
||||||
|
|
||||||
@@ -1725,8 +1717,7 @@ static int set_dns_listeners(time_t now)
|
|||||||
|
|
||||||
for (listener = daemon->listeners; listener; listener = listener->next)
|
for (listener = daemon->listeners; listener; listener = listener->next)
|
||||||
{
|
{
|
||||||
/* only listen for queries if we have resources */
|
if (listener->fd != -1)
|
||||||
if (listener->fd != -1 && wait == 0)
|
|
||||||
poll_listen(listener->fd, POLLIN);
|
poll_listen(listener->fd, POLLIN);
|
||||||
|
|
||||||
/* Only listen for TCP connections when a process slot
|
/* Only listen for TCP connections when a process slot
|
||||||
@@ -1741,15 +1732,12 @@ static int set_dns_listeners(time_t now)
|
|||||||
if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
|
if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
|
||||||
poll_listen(listener->tftpfd, POLLIN);
|
poll_listen(listener->tftpfd, POLLIN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!option_bool(OPT_DEBUG))
|
if (!option_bool(OPT_DEBUG))
|
||||||
for (i = 0; i < MAX_PROCS; i++)
|
for (i = 0; i < MAX_PROCS; i++)
|
||||||
if (daemon->tcp_pipes[i] != -1)
|
if (daemon->tcp_pipes[i] != -1)
|
||||||
poll_listen(daemon->tcp_pipes[i], POLLIN);
|
poll_listen(daemon->tcp_pipes[i], POLLIN);
|
||||||
|
|
||||||
return wait;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_dns_listeners(time_t now)
|
static void check_dns_listeners(time_t now)
|
||||||
@@ -2100,7 +2088,7 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
|
|||||||
poll_reset();
|
poll_reset();
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
poll_listen(fd, POLLIN);
|
poll_listen(fd, POLLIN);
|
||||||
set_dns_listeners(now);
|
set_dns_listeners();
|
||||||
set_log_writer();
|
set_log_writer();
|
||||||
|
|
||||||
#ifdef HAVE_DHCP6
|
#ifdef HAVE_DHCP6
|
||||||
|
|||||||
@@ -1390,7 +1390,6 @@ void receive_query(struct listener *listen, time_t now);
|
|||||||
unsigned char *tcp_request(int confd, time_t now,
|
unsigned char *tcp_request(int confd, time_t now,
|
||||||
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
|
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
|
||||||
void server_gone(struct server *server);
|
void server_gone(struct server *server);
|
||||||
struct frec *get_new_frec(time_t now, int *wait, struct frec *force);
|
|
||||||
int send_from(int fd, int nowild, char *packet, size_t len,
|
int send_from(int fd, int nowild, char *packet, size_t len,
|
||||||
union mysockaddr *to, union all_addr *source,
|
union mysockaddr *to, union all_addr *source,
|
||||||
unsigned int iface);
|
unsigned int iface);
|
||||||
@@ -1728,6 +1727,7 @@ int filter_servers(int seed, int flags, int *lowout, int *highout);
|
|||||||
int is_local_answer(time_t now, int first, char *name);
|
int is_local_answer(time_t now, int first, char *name);
|
||||||
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
|
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
|
||||||
char *name, int first, int last);
|
char *name, int first, int last);
|
||||||
|
int server_samegroup(struct server *a, struct server *b);
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
|
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -195,6 +195,11 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return first server in group of equivalent servers; this is the "master" record. */
|
||||||
|
int server_samegroup(struct server *a, struct server *b)
|
||||||
|
{
|
||||||
|
return order_servers(a, b) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
int filter_servers(int seed, int flags, int *lowout, int *highout)
|
int filter_servers(int seed, int flags, int *lowout, int *highout)
|
||||||
{
|
{
|
||||||
|
|||||||
112
src/forward.c
112
src/forward.c
@@ -16,12 +16,13 @@
|
|||||||
|
|
||||||
#include "dnsmasq.h"
|
#include "dnsmasq.h"
|
||||||
|
|
||||||
|
static struct frec *get_new_frec(time_t now, struct server *serv, struct frec *force);
|
||||||
static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp);
|
static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp);
|
||||||
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
|
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
|
||||||
|
|
||||||
static unsigned short get_id(void);
|
static unsigned short get_id(void);
|
||||||
static void free_frec(struct frec *f);
|
static void free_frec(struct frec *f);
|
||||||
static void query_full(time_t now);
|
static void query_full(time_t now, char *domain);
|
||||||
|
|
||||||
/* Send a UDP packet with its source address set as "source"
|
/* Send a UDP packet with its source address set as "source"
|
||||||
unless nowild is true, when we just send it with the kernel default */
|
unless nowild is true, when we just send it with the kernel default */
|
||||||
@@ -219,7 +220,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
/* If we've been spammed with many duplicates, return REFUSED. */
|
/* If we've been spammed with many duplicates, return REFUSED. */
|
||||||
if (!daemon->free_frec_src)
|
if (!daemon->free_frec_src)
|
||||||
{
|
{
|
||||||
query_full(now);
|
query_full(now, NULL);
|
||||||
goto reply;
|
goto reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* retry existing query */
|
/* new query */
|
||||||
if (!forward)
|
if (!forward)
|
||||||
{
|
{
|
||||||
/* new query */
|
/* new query */
|
||||||
@@ -269,7 +270,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|||||||
|
|
||||||
master = daemon->serverarray[first];
|
master = daemon->serverarray[first];
|
||||||
|
|
||||||
if (!(forward = get_new_frec(now, NULL, NULL)))
|
if (!(forward = get_new_frec(now, master, NULL)))
|
||||||
goto reply;
|
goto reply;
|
||||||
/* table full - flags == 0, return REFUSED */
|
/* table full - flags == 0, return REFUSED */
|
||||||
|
|
||||||
@@ -780,8 +781,9 @@ static int dnssec_validate(struct frec **forwardp, struct dns_header *header,
|
|||||||
/* Make sure we don't expire and free the orig frec during the
|
/* Make sure we don't expire and free the orig frec during the
|
||||||
allocation of a new one. */
|
allocation of a new one. */
|
||||||
if (--orig->work_counter == 0 ||
|
if (--orig->work_counter == 0 ||
|
||||||
!(new = get_new_frec(now, NULL, orig)) ||
|
(serverind = dnssec_server(server, daemon->keyname, NULL, NULL)) == -1 ||
|
||||||
(serverind = dnssec_server(server, daemon->keyname, NULL, NULL)) == -1)
|
!(server = daemon->serverarray[serverind]) ||
|
||||||
|
!(new = get_new_frec(now, server, orig)))
|
||||||
{
|
{
|
||||||
status = STAT_ABANDONED;
|
status = STAT_ABANDONED;
|
||||||
if (new)
|
if (new)
|
||||||
@@ -793,8 +795,6 @@ static int dnssec_validate(struct frec **forwardp, struct dns_header *header,
|
|||||||
struct frec *next = new->next;
|
struct frec *next = new->next;
|
||||||
size_t nn;
|
size_t nn;
|
||||||
|
|
||||||
server = daemon->serverarray[serverind];
|
|
||||||
|
|
||||||
*new = *forward; /* copy everything, then overwrite */
|
*new = *forward; /* copy everything, then overwrite */
|
||||||
new->next = next;
|
new->next = next;
|
||||||
new->blocking_query = NULL;
|
new->blocking_query = NULL;
|
||||||
@@ -1922,29 +1922,6 @@ unsigned char *tcp_request(int confd, time_t now,
|
|||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static struct frec *allocate_frec(time_t now)
|
|
||||||
{
|
|
||||||
struct frec *f;
|
|
||||||
|
|
||||||
if ((f = (struct frec *)whine_malloc(sizeof(struct frec))))
|
|
||||||
{
|
|
||||||
f->next = daemon->frec_list;
|
|
||||||
f->time = now;
|
|
||||||
f->sentto = NULL;
|
|
||||||
f->rfds = NULL;
|
|
||||||
f->flags = 0;
|
|
||||||
#ifdef HAVE_DNSSEC
|
|
||||||
f->dependent = NULL;
|
|
||||||
f->blocking_query = NULL;
|
|
||||||
f->stash = NULL;
|
|
||||||
#endif
|
|
||||||
daemon->frec_list = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* return a UDP socket bound to a random port, have to cope with straying into
|
/* return a UDP socket bound to a random port, have to cope with straying into
|
||||||
occupied port nos and reserved ones. */
|
occupied port nos and reserved ones. */
|
||||||
static int random_sock(struct server *s)
|
static int random_sock(struct server *s)
|
||||||
@@ -2167,22 +2144,19 @@ static void free_frec(struct frec *f)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* if wait==NULL return a free or older than TIMEOUT record.
|
/* Impose an absolute
|
||||||
else return *wait zero if one available, or *wait is delay to
|
|
||||||
when the oldest in-use record will expire. Impose an absolute
|
|
||||||
limit of 4*TIMEOUT before we wipe things (for random sockets).
|
limit of 4*TIMEOUT before we wipe things (for random sockets).
|
||||||
If force is non-NULL, always return a result, even if we have
|
If force is non-NULL, always return a result, even if we have
|
||||||
to allocate above the limit, and never free the record pointed
|
to allocate above the limit, and never free the record pointed
|
||||||
to by the force argument. */
|
to by the force argument. */
|
||||||
struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
|
static struct frec *get_new_frec(time_t now, struct server *master, struct frec *force)
|
||||||
{
|
{
|
||||||
struct frec *f, *oldest, *target;
|
struct frec *f, *oldest, *target;
|
||||||
int count;
|
int count;
|
||||||
|
|
||||||
if (wait)
|
/* look for free records, garbage collect old records and count number in use by our server-group. */
|
||||||
*wait = 0;
|
for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next)
|
||||||
|
{
|
||||||
for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next, count++)
|
|
||||||
if (!f->sentto)
|
if (!f->sentto)
|
||||||
target = f;
|
target = f;
|
||||||
else
|
else
|
||||||
@@ -2200,63 +2174,51 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
|
|||||||
target = f;
|
target = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!oldest || difftime(f->time, oldest->time) <= 0)
|
if (!oldest || difftime(f->time, oldest->time) <= 0)
|
||||||
oldest = f;
|
oldest = f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target)
|
if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master))
|
||||||
{
|
count++;
|
||||||
target->time = now;
|
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* can't find empty one, use oldest if there is one
|
if (!force && count >= daemon->ftabsize)
|
||||||
and it's older than timeout */
|
|
||||||
if (!force && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
|
|
||||||
{
|
{
|
||||||
/* keep stuff for twice timeout if we can by allocating a new
|
query_full(now, master->domain);
|
||||||
record instead */
|
|
||||||
if (difftime(now, oldest->time) < 2*TIMEOUT &&
|
|
||||||
count <= daemon->ftabsize &&
|
|
||||||
(f = allocate_frec(now)))
|
|
||||||
return f;
|
|
||||||
|
|
||||||
if (!wait)
|
|
||||||
{
|
|
||||||
free_frec(oldest);
|
|
||||||
oldest->time = now;
|
|
||||||
}
|
|
||||||
return oldest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* none available, calculate time 'till oldest record expires */
|
|
||||||
if (!force && count > daemon->ftabsize)
|
|
||||||
{
|
|
||||||
if (oldest && wait)
|
|
||||||
*wait = oldest->time + (time_t)TIMEOUT - now;
|
|
||||||
|
|
||||||
query_full(now);
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(f = allocate_frec(now)) && wait)
|
if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
|
||||||
/* wait one second on malloc failure */
|
{
|
||||||
*wait = 1;
|
/* can't find empty one, use oldest if there is one and it's older than timeout */
|
||||||
|
free_frec(oldest);
|
||||||
|
target = oldest;
|
||||||
|
}
|
||||||
|
|
||||||
return f; /* OK if malloc fails and this is NULL */
|
if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec))))
|
||||||
|
{
|
||||||
|
target->next = daemon->frec_list;
|
||||||
|
daemon->frec_list = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target)
|
||||||
|
target->time = now;
|
||||||
|
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void query_full(time_t now)
|
static void query_full(time_t now, char *domain)
|
||||||
{
|
{
|
||||||
static time_t last_log = 0;
|
static time_t last_log = 0;
|
||||||
|
|
||||||
if ((int)difftime(now, last_log) > 5)
|
if ((int)difftime(now, last_log) > 5)
|
||||||
{
|
{
|
||||||
last_log = now;
|
last_log = now;
|
||||||
|
if (!domain || strlen(domain) == 0)
|
||||||
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
|
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
|
||||||
|
else
|
||||||
|
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user