Commit fa14bec8 authored by Simon Kelley's avatar Simon Kelley

Major tidy up of EDNS0 handling and computation/use of udp packet size.

parent 14a4ae88
......@@ -81,7 +81,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut)
}
size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr, int local_query)
size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr,
int local_query, int do_bit, int have_pseudoheader)
{
char *name = daemon->namebuff;
unsigned char *p, *ansp;
......@@ -820,6 +821,11 @@ size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t n
header->ancount = htons(anscount);
header->nscount = htons(authcount);
header->arcount = htons(0);
/* Advertise our packet size limit in our reply */
if (have_pseudoheader)
return add_pseudoheader(header, ansp - (unsigned char *)header, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit);
return ansp - (unsigned char *)header;
}
......
......@@ -1113,7 +1113,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
int no_cache, int secure, int *doctored);
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);
time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
struct bogus_addr *addr, time_t now);
int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr);
......@@ -1123,6 +1123,8 @@ int check_for_local_domain(char *name, time_t now);
unsigned int questions_crc(struct dns_header *header, size_t plen, char *buff);
size_t resize_packet(struct dns_header *header, size_t plen,
unsigned char *pheader, size_t hlen);
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do);
size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysockaddr *l3);
size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, union mysockaddr *source);
#ifdef HAVE_DNSSEC
......@@ -1141,7 +1143,8 @@ int private_net(struct in_addr addr, int ban_localhost);
/* auth.c */
#ifdef HAVE_AUTH
size_t answer_auth(struct dns_header *header, char *limit, size_t qlen,
time_t now, union mysockaddr *peer_addr, int local_query);
time_t now, union mysockaddr *peer_addr, int local_query,
int do_bit, int have_pseudoheader);
int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
......
......@@ -67,7 +67,6 @@ static char *algo_digest_name(int algo)
case 12: return "gosthash94";
case 13: return "sha256";
case 14: return "sha384";
default: return NULL;
}
}
......
......@@ -244,7 +244,6 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
void *hash = &crc;
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
unsigned char *pheader;
(void)do_bit;
......@@ -264,7 +263,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
there's no point retrying the query, retry the key query instead...... */
if (forward->blocking_query)
{
int fd;
int fd, is_sign;
unsigned char *pheader;
forward->flags &= ~FREC_TEST_PKTSZ;
......@@ -276,8 +276,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
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 (find_pseudoheader(header, plen, NULL, &pheader, &is_sign) && !is_sign)
PUTSHORT(SAFE_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");
......@@ -394,32 +394,40 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
forward->log_id = daemon->log_id;
if (option_bool(OPT_ADD_MAC))
plen = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source);
{
size_t new = add_mac(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source);
if (new != plen)
{
plen = new;
forward->flags |= FREC_ADDED_PHEADER;
}
}
if (option_bool(OPT_CLIENT_SUBNET))
{
size_t new = add_source_addr(header, plen, ((char *) header) + daemon->packet_buff_sz, &forward->source);
if (new != plen)
{
plen = new;
forward->flags |= FREC_HAS_SUBNET;
forward->flags |= FREC_HAS_SUBNET | FREC_ADDED_PHEADER;
}
}
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
size_t new = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
if (new != plen)
forward->flags |= FREC_ADDED_PHEADER;
plen = new;
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
this allows it to select auth servers when one is returning bad data. */
if (option_bool(OPT_DNSSEC_DEBUG))
header->hb4 |= HB4_CD;
if (new_plen != plen)
forward->flags |= FREC_ADDED_PHEADER;
plen = new_plen;
}
#endif
......@@ -469,10 +477,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
#endif
}
if (find_pseudoheader(header, plen, NULL, &pheader, NULL))
PUTSHORT((forward->flags & FREC_TEST_PKTSZ) ? SAFE_PKTSZ : start->edns_pktsz, pheader);
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !do_bit)
{
/* Difficult one here. If our client didn't send EDNS0, we will have set the UDP
packet size to 512. But that won't provide space for the RRSIGS in many cases.
The RRSIGS will be stripped out before the answer goes back, so the packet should
shrink again. So, if we added a do-bit, bump the udp packet size to the value
known to be OK for this server. Maybe check returned size after stripping and set
the truncated bit? */
unsigned char *pheader;
int is_sign;
if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign))
PUTSHORT(start->edns_pktsz, pheader);
}
#endif
if (retry_send(sendto(fd, (char *)header, plen, 0,
&start->addr.sa,
sa_len(&start->addr))))
......@@ -563,30 +584,34 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
#endif
/* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign)))
{
unsigned short udpsz;
unsigned char *psave = sizep;
GETSHORT(udpsz, sizep);
if (!is_sign && udpsz > daemon->edns_pktsz)
PUTSHORT(daemon->edns_pktsz, psave);
if (check_subnet && !check_source(header, plen, pheader, query_source))
{
my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
return 0;
}
if (added_pheader)
if (!is_sign)
{
pheader = 0;
header->arcount = htons(0);
if (added_pheader)
{
/* client didn't send EDNS0, we added one, strip it off before returning answer. */
n = rrfilter(header, n, 0);
pheader = NULL;
}
else
{
/* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
unsigned short udpsz;
unsigned char *psave = sizep;
GETSHORT(udpsz, sizep);
if (udpsz > daemon->edns_pktsz)
PUTSHORT(daemon->edns_pktsz, psave);
}
}
}
......@@ -655,14 +680,16 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
if (option_bool(OPT_DNSSEC_VALID))
header->hb4 &= ~HB4_AD;
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
header->hb4 |= HB4_AD;
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */
if (!do_bit)
n = rrfilter(header, n, 1);
{
header->hb4 &= ~HB4_AD;
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
header->hb4 |= HB4_AD;
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */
if (!do_bit)
n = rrfilter(header, n, 1);
}
#endif
/* do this after extract_addresses. Ensure NODATA reply and remove
......@@ -761,8 +788,14 @@ void reply_query(int fd, int family, time_t now)
if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
{
header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC);
header->hb4 &= ~(HB4_RA | HB4_RCODE);
forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0);
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 (forward->flags & FREC_DO_QUESTION)
add_do_bit(header, nn, (char *)pheader + plen);
forward_query(-1, NULL, NULL, 0, header, nn, now, forward, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION);
return;
}
}
......@@ -1007,12 +1040,13 @@ void receive_query(struct listener *listen, time_t now)
{
struct dns_header *header = (struct dns_header *)daemon->packet;
union mysockaddr source_addr;
unsigned short type;
unsigned char *pheader;
unsigned short type, udp_size = PACKETSZ; /* default if no EDNS0 */
struct all_addr dst_addr;
struct in_addr netmask, dst_addr_4;
size_t m;
ssize_t n;
int if_index = 0, auth_dns = 0;
int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0;
#ifdef HAVE_AUTH
int local_auth = 0;
#endif
......@@ -1279,10 +1313,30 @@ void receive_query(struct listener *listen, time_t now)
#endif
}
if (find_pseudoheader(header, (size_t)n, NULL, &pheader, NULL))
{
unsigned short flags;
have_pseudoheader = 1;
GETSHORT(udp_size, pheader);
pheader += 2; /* ext_rcode */
GETSHORT(flags, pheader);
if (flags & 0x8000)
do_bit = 1;/* do bit */
/* 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;
}
#ifdef HAVE_AUTH
if (auth_dns)
{
m = answer_auth(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, now, &source_addr, local_auth);
m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr,
local_auth, do_bit, have_pseudoheader);
if (m >= 1)
{
send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
......@@ -1293,9 +1347,13 @@ void receive_query(struct listener *listen, time_t now)
else
#endif
{
int ad_reqd, do_bit;
m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
dst_addr_4, netmask, now, &ad_reqd, &do_bit);
int ad_reqd = do_bit;
/* 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);
if (m >= 1)
{
......@@ -1397,7 +1455,7 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_AUTH
int local_auth = 0;
#endif
int checking_disabled, ad_question, do_bit, added_pheader = 0;
int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
size_t m;
unsigned short qtype;
......@@ -1414,6 +1472,7 @@ unsigned char *tcp_request(int confd, time_t now,
union mysockaddr peer_addr;
socklen_t peer_len = sizeof(union mysockaddr);
int query_count = 0;
unsigned char *pheader;
if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
return packet;
......@@ -1508,15 +1567,35 @@ unsigned char *tcp_request(int confd, time_t now,
else
dst_addr_4.s_addr = 0;
do_bit = 0;
if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL))
{
unsigned short flags;
have_pseudoheader = 1;
pheader += 4; /* udp_size, ext_rcode */
GETSHORT(flags, pheader);
if (flags & 0x8000)
do_bit = 1;/* do bit */
}
#ifdef HAVE_AUTH
if (auth_dns)
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, local_auth);
m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr,
local_auth, do_bit, have_pseudoheader);
else
#endif
{
/* m > 0 if answered from cache */
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
dst_addr_4, netmask, now, &ad_question, &do_bit);
int ad_reqd = do_bit;
/* RFC 6840 5.7 */
if (header->hb4 & HB4_AD)
ad_reqd = 1;
/* 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);
......@@ -1615,6 +1694,7 @@ unsigned char *tcp_request(int confd, time_t now,
}
#ifdef HAVE_DNSSEC
added_pheader = 0;
if (option_bool(OPT_DNSSEC_VALID))
{
size_t new_size = add_do_bit(header, size, ((char *) header) + 65536);
......@@ -1719,7 +1799,7 @@ unsigned char *tcp_request(int confd, time_t now,
m = process_reply(header, now, last_server, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
ad_question, do_bit, added_pheader, check_subnet, &peer_addr);
ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr);
break;
}
......
......@@ -288,7 +288,8 @@ int iface_enumerate(int family, void *parm, int (*callback)())
rta = RTA_NEXT(rta, len1);
}
if (inaddr && mac && callback_ok)
if (!(neigh->ndm_state & (NUD_NOARP | NUD_INCOMPLETE | NUD_FAILED)) &&
inaddr && mac && callback_ok)
if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm)))
callback_ok = 0;
}
......
......@@ -489,8 +489,8 @@ struct macparm {
union mysockaddr *l3;
};
static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
int optno, unsigned char *opt, size_t optlen, int set_do)
size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit,
unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do)
{
unsigned char *lenp, *datap, *p;
int rdlen, is_sign;
......@@ -508,7 +508,7 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return plen;
*p++ = 0; /* empty name */
PUTSHORT(T_OPT, p);
PUTSHORT(SAFE_PKTSZ, p); /* max packet length, this will be overwritten */
PUTSHORT(udp_sz, p); /* max packet length, 512 if not given in EDNS0 header */
PUTSHORT(0, p); /* extended RCODE and version */
PUTSHORT(set_do ? 0x8000 : 0, p); /* DO flag */
lenp = p;
......@@ -594,7 +594,7 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
if (!match)
return 1; /* continue */
parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0);
parm->plen = add_pseudoheader(parm->header, parm->plen, parm->limit, PACKETSZ, EDNS0_OPTION_MAC, (unsigned char *)mac, maclen, 0);
return 0; /* done */
}
......@@ -603,12 +603,6 @@ size_t add_mac(struct dns_header *header, size_t plen, char *limit, union mysock
{
struct macparm parm;
/* Must have an existing pseudoheader as the only ar-record,
or have no ar-records. Must also not be signed */
if (ntohs(header->arcount) > 1)
return plen;
parm.header = header;
parm.limit = (unsigned char *)limit;
parm.plen = plen;
......@@ -699,13 +693,13 @@ size_t add_source_addr(struct dns_header *header, size_t plen, char *limit, unio
struct subnet_opt opt;
len = calc_subnet_opt(&opt, source);
return add_pseudoheader(header, plen, (unsigned char *)limit, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0);
return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0);
}
#ifdef HAVE_DNSSEC
size_t add_do_bit(struct dns_header *header, size_t plen, char *limit)
{
return add_pseudoheader(header, plen, (unsigned char *)limit, 0, NULL, 0, 1);
return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1);
}
#endif
......@@ -1525,16 +1519,16 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
/* 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)
time_t now, int ad_reqd, int do_bit, int have_pseudoheader)
{
char *name = daemon->namebuff;
unsigned char *p, *ansp, *pheader;
unsigned char *p, *ansp;
unsigned int qtype, qclass;
struct all_addr addr;
int nameoffset;
unsigned short flag;
int q, ans, anscount = 0, addncount = 0;
int dryrun = 0, sec_reqd = 0, have_pseudoheader = 0;
int dryrun = 0;
struct crec *crecp;
int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec;
......@@ -1550,35 +1544,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (header->hb4 & HB4_CD)
sec_data = 0;
/* RFC 6840 5.7 */
*ad_reqd = header->hb4 & HB4_AD;
*do_bit = 0;
/* If there is an additional data section then it will be overwritten by
partial replies, so we have to do a dry run to see if we can answer
the query. */
if (ntohs(header->arcount) != 0)
{
dryrun = 1;
/* If there's an additional section, there might be an EDNS(0) pseudoheader */
if (find_pseudoheader(header, qlen, NULL, &pheader, NULL))
{
unsigned short flags;
have_pseudoheader = 1;
pheader += 4; /* udp size, ext_rcode */
GETSHORT(flags, pheader);
if ((sec_reqd = flags & 0x8000))
{
*do_bit = 1;/* do bit */
*ad_reqd = 1;
}
}
}
dryrun = 1;
for (rec = daemon->mxnames; rec; rec = rec->next)
rec->offset = 0;
......@@ -1603,11 +1573,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
GETSHORT(qtype, p);
GETSHORT(qclass, p);
/* Don't filter RRSIGS from answers to ANY queries, even if do-bit
not set. */
if (qtype == T_ANY)
*do_bit = 1;
ans = 0; /* have we answered this question */
if (qtype == T_TXT || qtype == T_ANY)
......@@ -1739,7 +1704,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
the zone is unsigned, which implies that we're doing
validation. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
!sec_reqd ||
!do_bit ||
(option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK)))
{
do
......@@ -1927,7 +1892,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
/* If the client asked for DNSSEC don't use cached data. */
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !sec_reqd || !(crecp->flags & F_DNSSECOK))
if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) || !do_bit || !(crecp->flags & F_DNSSECOK))
do
{
/* don't answer wildcard queries with data not from /etc/hosts
......@@ -1961,17 +1926,12 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (crecp->flags & F_NEG)
{
/* We don't cache NSEC records, so if a DNSSEC-validated negative answer
is cached and the client wants DNSSEC, forward rather than answering from the cache */
if (!sec_reqd || !(crecp->flags & F_DNSSECOK))
{
ans = 1;
auth = 0;
if (crecp->flags & F_NXDOMAIN)
nxdomain = 1;
if (!dryrun)
log_query(crecp->flags, name, NULL, NULL);
}
ans = 1;
auth = 0;
if (crecp->flags & F_NXDOMAIN)
nxdomain = 1;
if (!dryrun)
log_query(crecp->flags, name, NULL, NULL);
}
else
{
......@@ -2209,10 +2169,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
len = ansp - (unsigned char *)header;
/* Advertise our packet size limit in our reply */
if (have_pseudoheader)
len = add_pseudoheader(header, len, (unsigned char *)limit, 0, NULL, 0, sec_reqd);
len = add_pseudoheader(header, len, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit);
if (*ad_reqd && sec_data)
if (ad_reqd && sec_data)
header->hb4 |= HB4_AD;
else
header->hb4 &= ~HB4_AD;
......
......@@ -243,7 +243,7 @@ size_t rrfilter(struct dns_header *header, size_t plen, int mode)
for (p = rrs[0], i = 1; i < rr_found; i += 2)
{
unsigned char *start = rrs[i];
unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen;
memmove(p, start, end-start);
p += end-start;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment