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.
This commit is contained in:
Simon Kelley
2022-08-21 18:07:17 +01:00
parent 24c3b5b3d4
commit d21438a7df
7 changed files with 166 additions and 44 deletions

View File

@@ -305,6 +305,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);
@@ -352,14 +359,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;
@@ -545,7 +552,11 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
if (forwarded || is_dnssec)
return 1;
{
if (daemon->fast_retry_time != 0)
forward->forward_timestamp = dnsmasq_milliseconds();
return 1;
}
/* could not send on, prepare to return */
header->id = htons(forward->frec_src.orig_id);
@@ -584,6 +595,64 @@ 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 retyr 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) < 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 < daemon->fast_retry_time)
to_run = daemon->fast_retry_time - 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;
if (option_bool(OPT_LOG))
my_syslog(LOG_INFO, _("fast retry"), NULL);
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);
to_run = daemon->fast_retry_time;
}
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;
@@ -941,6 +1010,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;
@@ -1058,7 +1129,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) &&
@@ -1089,34 +1160,46 @@ 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 */
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;
}
}
if (nn)
{
forward_query(-1, NULL, NULL, 0, header, nn, ((char *) header) + udp_size, now, forward,
@@ -2504,13 +2587,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)
{