by quiet-dhcp6. Thanks to J. Pablo Abonia for
spotting the problem.
+ Try and handle net connections with broken fragmentation
+ that lose large UDP packets. If a server times out,
+ reduce the maximum UDP packet size field in the EDNS0
+ header to 1280 bytes. If it then answers, make that
+ change permanent.
+
version 2.72
Add ra-advrouter mode, for RFC-3775 mobile IPv6 support.
#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
#define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
+#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
#define KEYBLOCK_LEN 40 /* choose to mininise 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 */
char interface[IF_NAMESIZE+1];
struct serverfd *sfd;
char *domain; /* set if this server only handles a domain. */
- int flags, tcpfd;
+ int flags, tcpfd, edns_pktsz;
unsigned int queries, failed_queries;
#ifdef HAVE_LOOP
u32 uid;
#define FREC_DO_QUESTION 64
#define FREC_ADDED_PHEADER 128
#define FREC_CHECK_NOSIGN 256
+#define FREC_TEST_PKTSZ 512
#ifdef HAVE_DNSSEC
#define HASH_SIZE 20 /* SHA-1 digest size */
#endif
/* dnssec.c */
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer, int *nons);
}
}
-size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr)
+size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class,
+ int type, union mysockaddr *addr, int edns_pktsz)
{
unsigned char *p;
char *types = querystr("dnssec-query", type);
+ size_t ret;
if (addr->sa.sa_family == AF_INET)
log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
PUTSHORT(type, p);
PUTSHORT(class, p);
- return add_do_bit(header, p - (unsigned char *)header, end);
+ ret = add_do_bit(header, p - (unsigned char *)header, end);
+
+ if (find_pseudoheader(header, ret, NULL, &p, NULL))
+ PUTSHORT(edns_pktsz, p);
+
+ return ret;
}
/* Go through a domain name, find "pointers" and fix them up based on how many bytes
void *hash = &crc;
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+ unsigned char *pheader;
(void)do_bit;
forward = NULL;
else if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
{
+ /* If we didn't get an answer advertising a maximal packet in EDNS,
+ fall back to 1280, which should work everywhere on IPv6.
+ If that generates an answer, it will become the new default
+ for this server */
+ forward->flags |= FREC_TEST_PKTSZ;
+
#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)
{
int fd;
-
+
+ forward->flags &= ~FREC_TEST_PKTSZ;
+
while (forward->blocking_query)
forward = forward->blocking_query;
+
+ forward->flags |= FREC_TEST_PKTSZ;
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
+ if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+ PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : forward->sentto->edns_pktsz, pheader);
+
if (forward->sentto->addr.sa.sa_family == AF_INET)
log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
#ifdef HAVE_IPV6
plen = new_plen;
}
#endif
-
+
while (1)
{
/* only send to servers dealing with our domain.
}
#endif
}
+
+ if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
+ PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader);
if (retry_send(sendto(fd, (char *)header, plen, 0,
&start->addr.sa,
}
server = forward->sentto;
-
if ((forward->sentto->flags & SERV_TYPE) == 0)
{
if (RCODE(header) == REFUSED)
if (!option_bool(OPT_ALL_SERVERS))
daemon->last_server = server;
}
-
+
+ /* We tried resending to this server with a smaller maximum size and got an answer.
+ Make that permanent. */
+ if (server && (forward->flags & FREC_TEST_PKTSZ))
+ server->edns_pktsz = SAFE_PKTSZ;
+
/* If the answer is an error, keep the forward record in place in case
we get a good reply from another server. Kill it when we've
had replies from all to avoid filling the forwarding table when
{
new->flags |= FREC_DNSKEY_QUERY;
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
- daemon->keyname, forward->class, T_DNSKEY, &server->addr);
+ daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
}
else
{
else
new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
- daemon->keyname, forward->class, T_DS, &server->addr);
+ daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
}
if ((hash = hash_questions(header, nn, daemon->namebuff)))
memcpy(new->hash, hash, HASH_SIZE);
/* Can't find it in the cache, have to send a query */
- m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+ m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
*length = htons(m);
another_tcp_key:
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
- new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr);
+ new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
*length = htons(m);
serv->domain = domain_str;
serv->next = next;
serv->queries = serv->failed_queries = 0;
+ serv->edns_pktsz = daemon->edns_pktsz;
#ifdef HAVE_LOOP
serv->uid = rand32();
#endif
{
struct server *tmp;
for (tmp = daemon->servers; tmp; tmp = tmp->next)
- if (!(tmp->flags & SERV_HAS_SOURCE))
- {
- if (tmp->source_addr.sa.sa_family == AF_INET)
- tmp->source_addr.in.sin_port = htons(daemon->query_port);
+ {
+ tmp->edns_pktsz = daemon->edns_pktsz;
+
+ if (!(tmp->flags & SERV_HAS_SOURCE))
+ {
+ if (tmp->source_addr.sa.sa_family == AF_INET)
+ tmp->source_addr.in.sin_port = htons(daemon->query_port);
#ifdef HAVE_IPV6
- else if (tmp->source_addr.sa.sa_family == AF_INET6)
- tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
+ else if (tmp->source_addr.sa.sa_family == AF_INET6)
+ tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
#endif
- }
+ }
+ }
}
if (daemon->if_addrs)
return plen;
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
- PUTSHORT(daemon->edns_pktsz, p); /* max packet length */
+ PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
PUTSHORT(0, p); /* extended RCODE and version */
PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
lenp = p;
unsigned short flag;
int q, ans, anscount = 0, addncount = 0;
int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
- int is_sign;
struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
forward rather than answering from the cache, which doesn't include
security information, unless we're in DNSSEC validation mode. */
- if (find_pseudoheader(header, qlen, NULL, &pheader, &is_sign))
+ if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
{
- unsigned short udpsz, flags;
- unsigned char *psave = pheader;
-
+ unsigned short flags;
+
have_pseudoheader = 1;
- GETSHORT(udpsz, pheader);
- pheader += 2; /* ext_rcode */
+ pheader += 4; /* udp size, ext_rcode */
GETSHORT(flags, pheader);
if ((sec_reqd = flags & 0x8000))
*do_bit = 1;/* do bit */
- *ad_reqd = 1;
-
- /* If our client is advertising a larger UDP packet size
- than we allow, trim it so that we don't get an overlarge
- response from upstream */
-
- if (!is_sign && (udpsz > daemon->edns_pktsz))
- PUTSHORT(daemon->edns_pktsz, psave);
+ *ad_reqd = 1;
dryrun = 1;
}