Add --use-stale-cache option.

This commit is contained in:
Simon Kelley
2022-08-29 21:44:05 +01:00
parent d21438a7df
commit d334e7c34f
8 changed files with 238 additions and 76 deletions

View File

@@ -374,6 +374,10 @@ 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. */
if (option_bool(OPT_STALE_CACHE))
return 0;
if (crecp->flags & F_IMMORTAL)
return 0;
@@ -2079,6 +2083,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";

View File

@@ -2015,9 +2015,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);

View File

@@ -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. */
@@ -1340,7 +1342,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);

View File

@@ -1183,11 +1183,6 @@ void reply_query(int fd, time_t now)
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);
@@ -1197,6 +1192,11 @@ void reply_query(int fd, time_t now)
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);
}
}
@@ -1343,10 +1343,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))
{
@@ -1357,14 +1353,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);
}
}
}
}
@@ -1454,7 +1456,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;
@@ -1777,13 +1779,17 @@ 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)
{
@@ -1798,11 +1804,36 @@ void receive_query(struct listener *listen, time_t now)
(char *)header, m, &source_addr, &dst_addr, if_index);
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
}
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
header, (size_t)n, ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit))
daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
else
daemon->metrics[METRIC_DNS_LOCAL_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))
daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
else
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
}
}
}
@@ -2029,8 +2060,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;
@@ -2085,13 +2117,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;
@@ -2116,24 +2172,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);
@@ -2189,11 +2248,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);
@@ -2311,6 +2373,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)
{
@@ -2321,11 +2386,11 @@ 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);
}
}
@@ -2342,8 +2407,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;
}

View File

@@ -183,6 +183,7 @@ struct myoption {
#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[] =
@@ -370,6 +371,7 @@ static const struct myoption opts[] =
{ "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
{ "port-limit", 1, 0, LOPT_RANDPORT_LIM },
{ "fast-dns-retry", 1, 0, LOPT_FAST_RETRY },
{ "use-stale-cache", 0, 0 , LOPT_STALE_CACHE },
{ NULL, 0, 0, 0 }
};
@@ -427,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},

View File

@@ -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",