Compare commits

...

6 Commits

Author SHA1 Message Date
Simon Kelley
1c9f136b57 Man page update, lease times can be given in days or weeks. 2021-06-15 22:07:59 +01:00
Simon Kelley
5ab7e4a475 Improve efficiency of DNSSEC.
The sharing point for DNSSEC RR data used to be when it entered the
cache, having been validated. After that queries requiring the KEY or
DS records would share the cached values. There is a common case in
dual-stack hosts that queries for A and AAAA records for the same
domain are made simultaneously.  If required keys were not in the
cache, this would result in two requests being sent upstream for the
same key data (and all the subsequent chain-of-trust queries.) Now we
combine these requests and elide the duplicates, resulting in fewer
queries upstream and better performance. To keep a better handle on
what's going on, the "extra" logging mode has been modified to
associate queries and answers for DNSSEC queries in the same way as
ordinary queries. The requesting address and port have been removed
from DNSSEC logging lines, since this is no longer strictly defined.
2021-06-15 15:27:29 +01:00
Simon Kelley
3236f358f8 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.
2021-06-13 21:29:22 +01:00
Simon Kelley
4a6550d69a Move make_local_answer() to domain-match.c 2021-06-10 21:40:52 +01:00
Simon Kelley
ff523d0c67 Fix TCP replies with --domain-needed. 2021-06-10 21:31:38 +01:00
Simon Kelley
3c93e8eb41 Re-order UBus initialisation to avoid logging before logs set up. 2021-06-08 23:13:48 +01:00
9 changed files with 368 additions and 345 deletions

View File

@@ -38,7 +38,33 @@ version 2.86
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.
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.
Improve efficiency of DNSSEC. The sharing point for DNSSEC RR data
used to be when it entered the cache, having been validated. After
that queries requiring the KEY or DS records would share the cached
values. There is a common case in dual-stack hosts that queries for
A and AAAA records for the same domain are made simultaneously.
If required keys were not in the cache, this would result in two
requests being sent upstream for the same key data (and all the
subsequent chain-of-trust queries.) Now we combine these requests
and elide the duplicates, resulting in fewer queries upstream
and better performance. To keep a better handle on what's
going on, the "extra" logging mode has been modified to associate
queries and answers for DNSSEC queries in the same way as ordinary
queries. The requesting address and port have been removed from
DNSSEC logging lines, since this is no longer strictly defined.
version 2.85
Fix problem with DNS retries in 2.83/2.84.
The new logic in 2.83/2.84 which merges distinct requests

View File

@@ -731,7 +731,8 @@ identical queries without forwarding them again.
Set the maximum number of concurrent DNS queries. The default value is
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
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
.B --dnssec
Validate DNS replies and cache DNSSEC data. When forwarding DNS queries, dnsmasq requests the
@@ -875,7 +876,7 @@ in
.B --dhcp-host
options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or "infinite". If not given,
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP

View File

@@ -1965,11 +1965,13 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
if (option_bool(OPT_EXTRALOG))
{
int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
if (flags & F_NOEXTRA)
my_syslog(LOG_INFO, "* %s/%u %s %s %s %s", daemon->addrbuff2, port, source, name, verb, dest);
my_syslog(LOG_INFO, "%u %s %s %s %s", daemon->log_display_id, source, name, verb, dest);
else
my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest);
{
int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest);
}
}
else
my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);

View File

@@ -23,7 +23,7 @@
#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define FORWARD_TEST 50 /* try all servers every 50 queries */
#define FORWARD_TIME 20 /* or 20 seconds */
#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */

View File

@@ -24,7 +24,7 @@ struct daemon *daemon;
static volatile pid_t pid = 0;
static volatile int pipewrite;
static int set_dns_listeners(time_t now);
static void set_dns_listeners(void);
static void check_dns_listeners(time_t now);
static void sig_handler(int sig);
static void async_event(int pipe, time_t now);
@@ -442,8 +442,10 @@ int main (int argc, char **argv)
if (option_bool(OPT_UBUS))
#ifdef HAVE_UBUS
{
char *err;
daemon->ubus = NULL;
ubus_init();
if ((err = ubus_init()))
die(_("UBus error: %s"), err, EC_MISC);
}
#else
die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
@@ -1040,16 +1042,10 @@ int main (int argc, char **argv)
while (1)
{
int t, timeout = -1;
int timeout = -1;
poll_reset();
/* if we are out of resources, find how long we have to wait
for some to come free, we'll loop around then and restart
listening for queries */
if ((t = set_dns_listeners(now)) != 0)
timeout = t * 1000;
/* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
if (daemon->tftp_trans ||
(option_bool(OPT_DBUS) && !daemon->dbus))
@@ -1059,6 +1055,8 @@ int main (int argc, char **argv)
else if (is_dad_listeners())
timeout = 1000;
set_dns_listeners();
#ifdef HAVE_DBUS
set_dbus_listeners();
#endif
@@ -1195,20 +1193,20 @@ int main (int argc, char **argv)
if (daemon->dbus)
my_syslog(LOG_INFO, _("connected to system DBus"));
}
check_dbus_listeners();
check_dbus_listeners();
#endif
#ifdef HAVE_UBUS
if (option_bool(OPT_UBUS))
/* if we didn't create a UBus connection, retry now. */
if (option_bool(OPT_UBUS) && !daemon->ubus)
{
/* if we didn't create a UBus connection, retry now. */
if (!daemon->ubus)
{
ubus_init();
}
check_ubus_listeners();
}
char *err;
if ((err = ubus_init()))
my_syslog(LOG_WARNING, _("UBus error: %s"), err);
if (daemon->ubus)
my_syslog(LOG_INFO, _("connected to system UBus"));
}
check_ubus_listeners();
#endif
check_dns_listeners(now);
@@ -1683,12 +1681,12 @@ void clear_cache_and_reload(time_t now)
#endif
}
static int set_dns_listeners(time_t now)
static void set_dns_listeners(void)
{
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int wait = 0, i;
int i;
#ifdef HAVE_TFTP
int tftp = 0;
@@ -1701,10 +1699,6 @@ static int set_dns_listeners(time_t now)
}
#endif
/* will we be able to get memory? */
if (daemon->port != 0)
get_new_frec(now, &wait, NULL);
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN);
@@ -1723,10 +1717,9 @@ static int set_dns_listeners(time_t now)
for (listener = daemon->listeners; listener; listener = listener->next)
{
/* only listen for queries if we have resources */
if (listener->fd != -1 && wait == 0)
if (listener->fd != -1)
poll_listen(listener->fd, POLLIN);
/* Only listen for TCP connections when a process slot
is available. Death of a child goes through the select loop, so
we don't need to explicitly arrange to wake up here,
@@ -1739,15 +1732,12 @@ static int set_dns_listeners(time_t now)
if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
poll_listen(listener->tftpfd, POLLIN);
#endif
}
if (!option_bool(OPT_DEBUG))
for (i = 0; i < MAX_PROCS; i++)
if (daemon->tcp_pipes[i] != -1)
poll_listen(daemon->tcp_pipes[i], POLLIN);
return wait;
}
static void check_dns_listeners(time_t now)
@@ -2098,7 +2088,7 @@ int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
poll_reset();
if (fd != -1)
poll_listen(fd, POLLIN);
set_dns_listeners(now);
set_dns_listeners();
set_log_writer();
#ifdef HAVE_DHCP6

View File

@@ -690,7 +690,6 @@ struct hostsfile {
#define STAT_SECURE_WILDCARD 7
#define STAT_OK 8
#define STAT_ABANDONED 9
#define STAT_INPROGRESS 10
#define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2
@@ -727,6 +726,7 @@ struct frec {
struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *next_dependent; /* list of above. */
struct frec *blocking_query; /* Query which is blocking us. */
#endif
struct frec *next;
@@ -1390,7 +1390,6 @@ void receive_query(struct listener *listen, time_t now);
unsigned char *tcp_request(int confd, time_t now,
union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
void server_gone(struct server *server);
struct frec *get_new_frec(time_t now, int *wait, struct frec *force);
int send_from(int fd, int nowild, char *packet, size_t len,
union mysockaddr *to, union all_addr *source,
unsigned int iface);
@@ -1543,7 +1542,7 @@ void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname);
/* ubus.c */
#ifdef HAVE_UBUS
void ubus_init(void);
char *ubus_init(void);
void set_ubus_listeners(void);
void check_ubus_listeners(void);
void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
@@ -1726,6 +1725,9 @@ void build_server_array(void);
int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
int filter_servers(int seed, int flags, int *lowout, int *highout);
int is_local_answer(time_t now, int first, char *name);
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
char *name, int first, int last);
int server_samegroup(struct server *a, struct server *b);
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
#endif

View File

@@ -195,6 +195,11 @@ int lookup_domain(char *qdomain, int flags, int *lowout, int *highout)
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)
{
@@ -284,6 +289,57 @@ int is_local_answer(time_t now, int first, char *name)
return rc;
}
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, int first, int last)
{
int trunc = 0;
unsigned char *p;
int start;
union all_addr addr;
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
setup_reply(header, flags);
if (!(p = skip_questions(header, size)))
return 0;
if (flags & gotname & F_IPV4)
for (start = first; start != last; start++)
{
struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr4 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
}
if (flags & gotname & F_IPV6)
for (start = first; start != last; start++)
{
struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr6 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
}
if (trunc)
header->hb3 |= HB3_TC;
return p - (unsigned char *)header;
}
#ifdef HAVE_DNSSEC
int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
{

View File

@@ -16,12 +16,15 @@
#include "dnsmasq.h"
static struct frec *get_new_frec(time_t now, struct server *serv, int force);
static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp);
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask);
static unsigned short get_id(void);
static void free_frec(struct frec *f);
static void query_full(time_t now);
static void query_full(time_t now, char *domain);
static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status);
/* Send a UDP packet with its source address set as "source"
unless nowild is true, when we just send it with the kernel default */
@@ -158,57 +161,6 @@ static int domain_no_rebind(char *domain)
return 0;
}
size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, int first, int last)
{
int trunc = 0;
unsigned char *p;
int start;
union all_addr addr;
if (flags & (F_NXDOMAIN | F_NOERR))
log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
setup_reply(header, flags);
if (!(p = skip_questions(header, size)))
return 0;
if (flags & gotname & F_IPV4)
for (start = first; start != last; start++)
{
struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr4 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
}
if (flags & gotname & F_IPV6)
for (start = first; start != last; start++)
{
struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
if (srv->flags & SERV_ALL_ZEROS)
memset(&addr, 0, sizeof(addr));
else
addr.addr6 = srv->addr;
header->ancount = htons(ntohs(header->ancount) + 1);
add_resource_record(header, ((char *)header) + 65536, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
}
if (trunc)
header->hb3 |= HB3_TC;
return p - (unsigned char *)header;
}
static int forward_query(int udpfd, union mysockaddr *udpaddr,
union all_addr *dst_addr, unsigned int dst_iface,
struct dns_header *header, size_t plen, char *limit, time_t now,
@@ -239,10 +191,17 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
fwd_flags |= FREC_DO_QUESTION;
#endif
/* Check for retry on existing query */
/* Check for retry on existing query.
FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
ensures that no frec created for internal DNSSEC query can be returned here.
Similarly FREC_NO_CACHE is never set in flags, so a query which is
contigent on a particular source address EDNS0 option will never be matched. */
if (forward)
old_src = 1;
else if ((forward = lookup_frec_by_query(hash, fwd_flags)))
else if ((forward = lookup_frec_by_query(hash, fwd_flags,
FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
{
struct frec_src *src;
@@ -270,7 +229,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
/* If we've been spammed with many duplicates, return REFUSED. */
if (!daemon->free_frec_src)
{
query_full(now);
query_full(now, NULL);
goto reply;
}
@@ -293,7 +252,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
}
/* retry existing query */
/* new query */
if (!forward)
{
/* new query */
@@ -320,7 +279,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
master = daemon->serverarray[first];
if (!(forward = get_new_frec(now, NULL, NULL)))
if (!(forward = get_new_frec(now, master, 0)))
goto reply;
/* table full - flags == 0, return REFUSED */
@@ -428,13 +387,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
for this server */
forward->flags |= FREC_TEST_PKTSZ;
}
/* If a query is retried, use the log_id for the retry when logging the answer. */
forward->frec_src.log_id = daemon->log_id;
/* We may be resending a DNSSEC query here, for which the below processing is not necessary. */
if (!is_dnssec)
{
/* If a query is retried, use the log_id for the retry when logging the answer. */
forward->frec_src.log_id = daemon->log_id;
header->id = htons(forward->new_id);
plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
@@ -765,15 +724,14 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
#ifdef HAVE_DNSSEC
static int dnssec_validate(struct frec **forwardp, struct dns_header *header,
ssize_t plen, struct server *server, time_t now)
static void dnssec_validate(struct frec *forward, struct dns_header *header,
ssize_t plen, int status, time_t now)
{
int status = 0;
struct frec *forward = *forwardp;
daemon->log_display_id = forward->frec_src.log_id;
/* We've had a reply already, which we're validating. Ignore this duplicate */
if (forward->blocking_query)
return STAT_INPROGRESS;
return;
/* Truncated answer can't be validated.
If this is an answer to a DNSSEC-generated query, we still
@@ -787,102 +745,111 @@ static int dnssec_validate(struct frec **forwardp, struct dns_header *header,
if (RCODE(header) == REFUSED)
status = STAT_ABANDONED;
while (1)
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
{
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
{
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL);
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL);
#ifdef HAVE_DUMPFILE
if (status == STAT_BOGUS)
dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &server->addr, NULL);
if (status == STAT_BOGUS)
dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL);
#endif
}
}
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
struct frec *new = NULL;
int serverind;
struct blockdata *stash;
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
/* Now save reply pending receipt of key data */
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
(stash = blockdata_alloc((char *)header, plen)))
{
struct frec *new = NULL, *orig;
int serverind;
/* Free any saved query */
if (forward->stash)
blockdata_free(forward->stash);
/* Now save reply pending receipt of key data */
if (!(forward->stash = blockdata_alloc((char *)header, plen)))
return STAT_ABANDONED;
forward->stash_len = plen;
struct server *server = daemon->serverarray[serverind];
struct frec *orig;
unsigned int flags;
void *hash;
size_t nn;
/* validate routines leave name of required record in daemon->keyname */
nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz,
daemon->keyname, forward->class,
status == STAT_NEED_KEY ? T_DNSKEY : T_DS, server->edns_pktsz);
flags = (status == STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
hash = hash_questions(header, nn, daemon->namebuff);
if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
{
forward->next_dependent = new->dependent;
new->dependent = forward;
/* Make consistent, only replace query copy with unvalidated answer
when we set ->blocking_query. */
if (forward->stash)
blockdata_free(forward->stash);
forward->blocking_query = new;
forward->stash_len = plen;
forward->stash = stash;
return;
}
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
/* Make sure we don't expire and free the orig frec during the
allocation of a new one. */
if (--orig->work_counter == 0 ||
!(new = get_new_frec(now, NULL, orig)) ||
(serverind = dnssec_server(server, daemon->keyname, NULL, NULL)) == -1)
{
status = STAT_ABANDONED;
if (new)
free_frec(new);
}
allocation of a new one: third arg of get_new_frec() does that. */
if (--orig->work_counter == 0 || !(new = get_new_frec(now, server, 1)))
blockdata_free(stash); /* don't leak this on failure. */
else
{
int querytype, fd;
int fd;
struct frec *next = new->next;
size_t nn;
server = daemon->serverarray[serverind];
*new = *forward; /* copy everything, then overwrite */
new->next = next;
new->blocking_query = NULL;
new->frec_src.log_id = daemon->log_display_id = ++daemon->log_id;
new->sentto = server;
new->rfds = NULL;
new->frec_src.next = NULL;
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
new->flags |= flags;
new->forwardall = 0;
forward->next_dependent = NULL;
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
/* validate routines leave name of required record in daemon->keyname */
if (status == STAT_NEED_KEY)
{
new->flags |= FREC_DNSKEY_QUERY;
querytype = T_DNSKEY;
}
else
{
new->flags |= FREC_DS_QUERY;
querytype = T_DS;
}
/* Make consistent, only replace query copy with unvalidated answer
when we set ->blocking_query. */
forward->blocking_query = new;
if (forward->stash)
blockdata_free(forward->stash);
forward->stash_len = plen;
forward->stash = stash;
nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz,
daemon->keyname, forward->class, querytype, server->edns_pktsz);
memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE);
memcpy(new->hash, hash, HASH_SIZE);
new->new_id = get_id();
header->id = htons(new->new_id);
/* Save query for retransmission */
new->stash = blockdata_alloc((char *)header, nn);
new->stash_len = nn;
/* Don't resend this. */
daemon->srv_save = NULL;
if ((fd = allocate_rfd(&new->rfds, server)) != -1)
{
#ifdef HAVE_CONNTRACK
@@ -891,28 +858,38 @@ static int dnssec_validate(struct frec **forwardp, struct dns_header *header,
#endif
server_send_log(server, fd, header, nn, DUMP_SEC_QUERY,
F_NOEXTRA | F_DNSSEC, daemon->keyname,
querystr("dnssec-query", querytype));
querystr("dnssec-query", status == STAT_NEED_KEY ? T_DNSKEY : T_DS));
server->queries++;
}
}
return STAT_INPROGRESS;
return;
}
}
/* Validated original answer, all done. */
if (!forward->dependent)
break;
/* validated subsidiary query, (and cached result)
pop that and return to the previous query we were working on. */
struct frec *prev = forward->dependent;
free_frec(forward);
*forwardp = forward = prev;
forward->blocking_query = NULL; /* already gone */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
/* sending DNSSEC query failed. */
status = STAT_ABANDONED;
}
return status;
/* Validated original answer, all done. */
if (!forward->dependent)
return_reply(now, forward, header, plen, status);
else
{
/* validated subsidiary query/queries, (and cached result)
pop that and return to the previous query/queries we were working on. */
struct frec *prev, *nxt = forward->dependent;
free_frec(forward);
while ((prev = nxt))
{
/* ->next_dependent will have changed after return from recursive call below. */
nxt = prev->next_dependent;
prev->blocking_query = NULL; /* already gone */
blockdata_retrieve(prev->stash, prev->stash_len, (void *)header);
dnssec_validate(prev, header, prev->stash_len, status, now);
}
}
}
#endif
@@ -926,12 +903,10 @@ void reply_query(int fd, time_t now)
struct frec *forward;
socklen_t addrlen = sizeof(serveraddr);
ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen);
size_t nn;
struct server *server;
void *hash;
int first, last, c;
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
/* packet buffer overwritten */
daemon->srv_save = NULL;
@@ -995,10 +970,14 @@ void reply_query(int fd, time_t now)
unsigned short udp_size = PACKETSZ; /* default if no EDNS0 */
size_t plen;
int is_sign;
size_t nn = 0;
#ifdef HAVE_DNSSEC
/* DNSSEC queries have a copy of the original query stashed. */
if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
/* DNSSEC queries have a copy of the original query stashed.
The query MAY have got a good answer, and be awaiting
the results of further queries, in which case
The Stash contains something else and we don't need to retry anyway. */
if ((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && !forward->blocking_query)
{
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
nn = forward->stash_len;
@@ -1007,8 +986,6 @@ void reply_query(int fd, time_t now)
else
#endif
{
nn = 0;
/* recreate query from reply */
if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL)))
GETSHORT(udp_size, udpsz);
@@ -1066,25 +1043,36 @@ void reply_query(int fd, time_t now)
my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ);
}
/* Don't cache replies where DNSSEC validation was turned off, either
the upstream server told us so, or the original query specified it. */
if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
no_cache_dnssec = 1;
forward->sentto = server;
#ifdef HAVE_DNSSEC
if ((forward->sentto->flags & SERV_DO_DNSSEC) &&
option_bool(OPT_DNSSEC_VALID) &&
!(forward->flags & FREC_CHECKING_DISABLED))
{
/* Note that the value of forward may change here:
we can start with a DNSSEC query reply and
return with a query that was suspended pending
that DNSSEC query. */
int status = dnssec_validate(&forward, header, n, server, now);
dnssec_validate(forward, header, n, STAT_OK, now);
else
#endif
return_reply(now, forward, header, n, STAT_OK);
}
if (status == STAT_INPROGRESS)
return;
static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status)
{
int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
size_t nn;
(void)status;
daemon->log_display_id = forward->frec_src.log_id;
daemon->log_source_addr = &forward->frec_src.source;
/* Don't cache replies where DNSSEC validation was turned off, either
the upstream server told us so, or the original query specified it. */
if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
no_cache_dnssec = 1;
#ifdef HAVE_DNSSEC
if (status != STAT_OK)
{
no_cache_dnssec = 0;
if (status == STAT_TRUNCATED)
@@ -1131,7 +1119,7 @@ void reply_query(int fd, time_t now)
if (forward->flags & FREC_NO_CACHE)
no_cache_dnssec = 1;
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
{
@@ -1520,6 +1508,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
(void)have_mark;
memcpy(hash, hash_questions(header, (unsigned int)qsize, daemon->namebuff), HASH_SIZE);
while (1)
{
int data_sent = 0;
@@ -1623,7 +1612,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
while (1)
{
size_t m;
size_t m;
int log_save;
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
if (--(*keycount) == 0)
@@ -1663,11 +1653,16 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
new_status = STAT_ABANDONED;
break;
}
log_save = daemon->log_display_id;
daemon->log_display_id = ++daemon->log_id;
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr,
querystr("dnssec-query", new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS));
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
daemon->log_display_id = log_save;
if (new_status != STAT_OK)
break;
@@ -1864,13 +1859,8 @@ unsigned char *tcp_request(int confd, time_t now,
(gotname & (F_IPV4 | F_IPV6)) &&
!strchr(daemon->namebuff, '.') &&
strlen(daemon->namebuff) != 0)
{
flags = F_NOERR;
break;;
}
/* Configured answer or no available server. */
if (lookup_domain(daemon->namebuff, gotname, &first, &last) && !(flags = is_local_answer(now, first, daemon->namebuff)))
flags = F_NOERR;
else if (lookup_domain(daemon->namebuff, gotname, &first, &last) && !(flags = is_local_answer(now, first, daemon->namebuff)))
{
master = daemon->serverarray[first];
@@ -1978,29 +1968,6 @@ unsigned char *tcp_request(int confd, time_t now,
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
occupied port nos and reserved ones. */
static int random_sock(struct server *s)
@@ -2215,104 +2182,107 @@ static void free_frec(struct frec *f)
/* Anything we're waiting on is pointless now, too */
if (f->blocking_query)
free_frec(f->blocking_query);
{
struct frec *n, **up;
/* unlink outselves from the blocking query's dependents list. */
for (n = f->blocking_query->dependent, up = &f->blocking_query->dependent; n; n = n->next_dependent)
if (n == f)
{
*up = n->next_dependent;
break;
}
else
up = &n->next_dependent;
/* If we were the only/last dependent, free the blocking query too. */
if (!f->blocking_query->dependent)
free_frec(f->blocking_query);
}
f->blocking_query = NULL;
f->dependent = NULL;
f->next_dependent = NULL;
#endif
}
/* if wait==NULL return a free or older than TIMEOUT record.
else return *wait zero if one available, or *wait is delay to
when the oldest in-use record will expire. Impose an absolute
/* Impose an absolute
limit of 4*TIMEOUT before we wipe things (for random sockets).
If force is non-NULL, always return a result, even if we have
to allocate above the limit, and never free the record pointed
to by the force argument. */
struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
If force is set, always return a result, even if we have
to allocate above the limit, and don'y free any records.
This is set when allocating for DNSSEC to avoid cutting off
the branch we are sitting on. */
static struct frec *get_new_frec(time_t now, struct server *master, int force)
{
struct frec *f, *oldest, *target;
int count;
if (wait)
*wait = 0;
for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next, count++)
if (!f->sentto)
target = f;
else
{
#ifdef HAVE_DNSSEC
/* Don't free DNSSEC sub-queries here, as we may end up with
dangling references to them. They'll go when their "real" query
is freed. */
if (!f->dependent && f != force)
#endif
{
if (difftime(now, f->time) >= 4*TIMEOUT)
{
free_frec(f);
target = f;
}
if (!oldest || difftime(f->time, oldest->time) <= 0)
oldest = f;
}
}
if (target)
/* look for free records, garbage collect old records and count number in use by our server-group. */
for (f = daemon->frec_list, oldest = NULL, target = NULL, count = 0; f; f = f->next)
{
target->time = now;
return target;
}
/* can't find empty one, use oldest if there is one
and it's older than timeout */
if (!force && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
{
/* keep stuff for twice timeout if we can by allocating a new
record instead */
if (difftime(now, oldest->time) < 2*TIMEOUT &&
count <= daemon->ftabsize &&
(f = allocate_frec(now)))
return f;
if (!wait)
if (!f->sentto)
target = f;
else
{
free_frec(oldest);
oldest->time = now;
#ifdef HAVE_DNSSEC
/* Don't free DNSSEC sub-queries here, as we may end up with
dangling references to them. They'll go when their "real" query
is freed. */
if (!f->dependent && !force)
#endif
{
if (difftime(now, f->time) >= 4*TIMEOUT)
{
free_frec(f);
target = f;
}
else if (!oldest || difftime(f->time, oldest->time) <= 0)
oldest = f;
}
}
return oldest;
if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master))
count++;
}
/* none available, calculate time 'till oldest record expires */
if (!force && count > daemon->ftabsize)
if (!force && count >= daemon->ftabsize)
{
if (oldest && wait)
*wait = oldest->time + (time_t)TIMEOUT - now;
query_full(now);
query_full(now, master->domain);
return NULL;
}
if (!(f = allocate_frec(now)) && wait)
/* wait one second on malloc failure */
*wait = 1;
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 */
free_frec(oldest);
target = oldest;
}
if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec))))
{
target->next = daemon->frec_list;
daemon->frec_list = target;
}
return f; /* OK if malloc fails and this is NULL */
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;
if ((int)difftime(now, last_log) > 5)
{
last_log = now;
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
if (!domain || strlen(domain) == 0)
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
else
my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize);
}
}
@@ -2349,22 +2319,13 @@ static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firs
return NULL;
}
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask)
{
struct frec *f;
/* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
ensures that no frec created for internal DNSSEC query can be returned here.
Similarly FREC_NO_CACHE is never set in flags, so a query which is
contigent on a particular source address EDNS0 option will never be matched. */
#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
| FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)
for(f = daemon->frec_list; f; f = f->next)
if (f->sentto &&
(f->flags & FLAGMASK) == flags &&
(f->flags & flagmask) == flags &&
memcmp(hash, f->hash, HASH_SIZE) == 0)
return f;

View File

@@ -76,42 +76,27 @@ static void ubus_disconnect_cb(struct ubus_context *ubus)
}
}
void ubus_init()
char *ubus_init()
{
struct ubus_context *ubus = NULL;
int ret = 0;
ubus = ubus_connect(NULL);
if (!ubus)
{
if (!error_logged)
{
my_syslog(LOG_ERR, _("Cannot initialize UBus: connection failed"));
error_logged = 1;
}
ubus_destroy(ubus);
return;
}
if (!(ubus = ubus_connect(NULL)))
return NULL;
ubus_object.name = daemon->ubus_name;
ret = ubus_add_object(ubus, &ubus_object);
if (ret)
{
if (!error_logged)
{
my_syslog(LOG_ERR, _("Cannot add object to UBus: %s"), ubus_strerror(ret));
error_logged = 1;
}
ubus_destroy(ubus);
return;
}
return ubus_strerror(ret);
}
ubus->connection_lost = ubus_disconnect_cb;
daemon->ubus = ubus;
error_logged = 0;
my_syslog(LOG_INFO, _("Connected to system UBus"));
return NULL;
}
void set_ubus_listeners()