From e5e8c14d87bfe5016789739b7f4d897ad4e5867a Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Sat, 23 Nov 2024 22:38:41 +0000 Subject: [PATCH] Large refactor of EDNS0 UDP packet size handling. This was kinda strange before, with a lot of cargo-cult copied code, and no clear strategy. Now it works like this: When talking upstream we always add a pseudoheader, and set the UDP packet size to --edns-packet-max unless we've had problems talking to a server, when it's reduced to 1280 if that fixes things. Answering queries from downstream, we get the answer (either from upstream or local data) If local data won't fit the advertised size (or 512 if there's not pseudoheader) return truncated. If upstream returns truncated, do likewise. If upstream is OK, but the answer is too big for downstream, truncate the answer. --- src/dns-protocol.h | 16 +-- src/dnsmasq.h | 7 +- src/dnssec.c | 12 +-- src/edns0.c | 37 ++++--- src/forward.c | 260 ++++++++++++++++++++------------------------- src/rfc1035.c | 9 +- 6 files changed, 157 insertions(+), 184 deletions(-) diff --git a/src/dns-protocol.h b/src/dns-protocol.h index 2777be9..565ac90 100644 --- a/src/dns-protocol.h +++ b/src/dns-protocol.h @@ -142,15 +142,15 @@ struct dns_header { #define RCODE(x) ((x)->hb4 & HB4_RCODE) #define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code -#define GETSHORT(s, cp) { \ +#define GETSHORT(s, cp) do { \ unsigned char *t_cp = (unsigned char *)(cp); \ (s) = ((u16)t_cp[0] << 8) \ | ((u16)t_cp[1]) \ ; \ (cp) += 2; \ -} + } while(0) -#define GETLONG(l, cp) { \ +#define GETLONG(l, cp) do { \ unsigned char *t_cp = (unsigned char *)(cp); \ (l) = ((u32)t_cp[0] << 24) \ | ((u32)t_cp[1] << 16) \ @@ -158,17 +158,17 @@ struct dns_header { | ((u32)t_cp[3]) \ ; \ (cp) += 4; \ -} + } while (0) -#define PUTSHORT(s, cp) { \ +#define PUTSHORT(s, cp) do { \ u16 t_s = (u16)(s); \ unsigned char *t_cp = (unsigned char *)(cp); \ *t_cp++ = t_s >> 8; \ *t_cp = t_s; \ (cp) += 2; \ -} + } while(0) -#define PUTLONG(l, cp) { \ +#define PUTLONG(l, cp) do { \ u32 t_l = (u32)(l); \ unsigned char *t_cp = (unsigned char *)(cp); \ *t_cp++ = t_l >> 24; \ @@ -176,7 +176,7 @@ struct dns_header { *t_cp++ = t_l >> 8; \ *t_cp = t_l; \ (cp) += 4; \ -} + } while (0) #define CHECK_LEN(header, pp, plen, len) \ ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 379e8a4..f79222e 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -779,7 +779,7 @@ struct frec { union all_addr dest; unsigned int iface, log_id; int fd; - unsigned short orig_id; + unsigned short orig_id, udp_pkt_size; struct frec_src *next; } frec_src; struct server *sentto; /* NULL means free */ @@ -1395,8 +1395,7 @@ 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, - int *stale, int *filtered); + time_t now, int ad_reqd, int do_bit, int *stale, int *filtered); 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); @@ -1419,7 +1418,7 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); /* dnssec.c */ #ifdef HAVE_DNSSEC -size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int id, int type, int edns_pktsz); +size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int id, int type); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class, int *validate_count); int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, diff --git a/src/dnssec.c b/src/dnssec.c index c791480..027bb37 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -2204,11 +2204,10 @@ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen) } size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, - int class, int id, int type, int edns_pktsz) + int class, int id, int type) { unsigned char *p; - size_t ret; - + header->qdcount = htons(1); header->ancount = htons(0); header->nscount = htons(0); @@ -2228,12 +2227,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char PUTSHORT(type, p); PUTSHORT(class, p); - ret = add_do_bit(header, p - (unsigned char *)header, end); - - if (find_pseudoheader(header, ret, NULL, &p, NULL, NULL)) - PUTSHORT(edns_pktsz, p); - - return ret; + return add_do_bit(header, p - (unsigned char *)header, end); } int errflags_to_ede(int status) diff --git a/src/edns0.c b/src/edns0.c index 598478f..89ae2a7 100644 --- a/src/edns0.c +++ b/src/edns0.c @@ -96,7 +96,12 @@ unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t } -/* replace == 2 ->delete existing option only. */ +/* replace == 0 ->don't replace existing option + replace == 1 ->replace existing or add option + replace == 2 ->relpace existing option only. + + udp_sz == 0 -> leave unchanged in existing EDNS0 or set to deamon->edns_pksz in a new one. +*/ 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, int replace) { @@ -114,9 +119,14 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l /* Existing header */ int i; unsigned short code, len; - + p = udp_len; - GETSHORT(udp_sz, p); + + if (udp_sz == 0) + GETSHORT(udp_sz, p); + else + PUTSHORT(udp_sz, p); + GETSHORT(rcode, p); GETSHORT(flags, p); @@ -197,12 +207,15 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l free(buff); return plen; /* bad packet */ } - + + if (udp_sz == 0) + udp_sz = daemon->edns_pktsz; + *p++ = 0; /* empty name */ PUTSHORT(T_OPT, p); PUTSHORT(udp_sz, p); /* max packet length, 512 if not given in EDNS0 header */ - PUTSHORT(rcode, p); /* extended RCODE and version */ - PUTSHORT(flags, p); /* DO flag */ + PUTSHORT(rcode, p); /* extended RCODE and version */ + PUTSHORT(flags, p); /* DO flag */ lenp = p; PUTSHORT(rdlen, p); /* RDLEN */ datap = p; @@ -245,7 +258,7 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit) { - return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1, 0); + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, 0, NULL, 0, 1, 0); } static unsigned char char64(unsigned char c) @@ -290,7 +303,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch replace = 2; if (replace != 0 || maclen == 6) - plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace); + plen = add_pseudoheader(header, plen, limit, 0, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace); return plen; } @@ -315,7 +328,7 @@ static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *lim replace = 2; if (replace != 0 || maclen != 0) - plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, replace); + plen = add_pseudoheader(header, plen, limit, 0, EDNS0_OPTION_MAC, mac, maclen, 0, replace); return plen; } @@ -415,7 +428,7 @@ static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned c else return plen; - return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace); + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, replace); } int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer) @@ -515,7 +528,7 @@ static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned PUTLONG(daemon->umbrella_asset, u); } - return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1); + return add_pseudoheader(header, plen, (unsigned char *)limit, 0, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, u - (u8 *)&opt, 0, 1); } /* Set *check_subnet if we add a client subnet option, which needs to checked @@ -530,7 +543,7 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l plen = add_dns_client(header, plen, limit, source, now, cacheable); if (daemon->dns_client_id) - plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, + plen = add_pseudoheader(header, plen, limit, 0, EDNS0_OPTION_NOMCPEID, (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); if (option_bool(OPT_UMBRELLA)) diff --git a/src/forward.c b/src/forward.c index 4536555..d7f4cfa 100644 --- a/src/forward.c +++ b/src/forward.c @@ -165,7 +165,7 @@ static int domain_no_rebind(char *domain) 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, + struct dns_header *header, size_t plen, size_t replylimit, time_t now, struct frec *forward, int ad_reqd, int do_bit, int fast_retry) { unsigned int flags = 0; @@ -176,8 +176,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, int old_src = 0, old_reply = 0; int first, last, start = 0; int cacheable, forwarded = 0; - size_t edns0_len; - unsigned char *pheader, *oph; + unsigned char *oph; int ede = EDE_UNSET; (void)do_bit; unsigned short rrtype; @@ -263,6 +262,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, src->log_id = daemon->log_id; src->iface = dst_iface; src->fd = udpfd; + src->udp_pkt_size = (unsigned short)replylimit; /* closely spaced identical queries cannot be a try and a retry, so it's safe to wait for the reply from the first without @@ -313,8 +313,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (!(forward = get_new_frec(now, master, 0))) goto reply; /* table full - flags == 0, return REFUSED */ - - plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &cacheable); + + forward->flags = fwd_flags; + + plen = add_edns0_config(header, plen, ((unsigned char *)header) + daemon->edns_pktsz, &forward->frec_src.source, now, &cacheable); if (!cacheable) forward->flags |= FREC_NO_CACHE; @@ -322,7 +324,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC)) { - plen = add_do_bit(header, plen, ((unsigned char *) header) + PACKETSZ); + plen = add_do_bit(header, plen, ((unsigned char *) header) + daemon->edns_pktsz); /* For debugging, set Checking Disabled, otherwise, have the upstream check too, this allows it to select auth servers when one is returning bad data. */ @@ -331,17 +333,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } #endif - if (find_pseudoheader(header, plen, &edns0_len, &pheader, NULL, NULL)) - { - /* If there wasn't a PH before, and there is now, we added it. */ - if (!oph) - forward->flags |= FREC_ADDED_PHEADER; - - /* Reduce udp size on retransmits. */ - if (forward->flags & FREC_TEST_PKTSZ) - PUTSHORT(SAFE_PKTSZ, pheader); - } - + /* If there wasn't a PH before, and there is now, we added it. */ + if (!oph && find_pseudoheader(header, plen, NULL, NULL, NULL, NULL)) + forward->flags |= FREC_ADDED_PHEADER; + /* Do these before saving query. */ forward->frec_src.orig_id = ntohs(header->id); forward->new_id = get_id(); @@ -361,8 +356,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, forward->frec_src.iface = dst_iface; forward->frec_src.next = NULL; forward->frec_src.fd = udpfd; + forward->frec_src.udp_pkt_size = (unsigned short)replylimit; forward->forwardall = 0; - forward->flags = fwd_flags; if (domain_no_rebind(daemon->namebuff)) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) @@ -405,20 +400,13 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) { - int is_sign; - unsigned char *pheader; - /* log_id should match previous DNSSEC query. */ daemon->log_display_id = forward->frec_src.log_id; blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); plen = forward->stash_len; /* get query for logging. */ - extract_request(header, plen, daemon->namebuff, NULL); - - /* Use go-anywhere size limit, as it's a retry. */ - if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) - PUTSHORT(SAFE_PKTSZ, pheader); + gotname = extract_request(header, plen, daemon->namebuff, NULL); /* Find suitable servers: should never fail. */ if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last)) @@ -498,22 +486,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (option_bool(OPT_CONNTRACK)) set_outgoing_mark(forward, fd); #endif - -#ifdef HAVE_DNSSEC - if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) - { - /* 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. We check returned size after stripping and set - the truncated bit if it's still too big. */ - unsigned char *pheader; - int is_sign; - if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) - PUTSHORT(srv->edns_pktsz, pheader); - } -#endif + + /* send EDNS0 packet size to the maximum size we believe the link allows at this time. */ + plen = add_pseudoheader(header, plen, (unsigned char *)(header + daemon->edns_pktsz), srv->edns_pktsz, 0, NULL, 0, 0, 0); if (retry_send(sendto(fd, (char *)header, plen, 0, &srv->addr.sa, @@ -531,17 +506,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, daemon->packet_len = plen; daemon->fd_save = fd; - if (!(forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))) - { - if (!gotname) - strcpy(daemon->namebuff, "query"); - log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, - &srv->addr, NULL, 0); - } + if (!gotname) + strcpy(daemon->namebuff, "query"); + + if (!(forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))) + log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, + &srv->addr, NULL, 0); #ifdef HAVE_DNSSEC - else - log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, daemon->namebuff, &srv->addr, - (forward->flags & FREC_DNSKEY_QUERY) ? "dnssec-retry[DNSKEY]" : "dnssec-retry[DS]", 0); + else + log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, daemon->namebuff, &srv->addr, + (forward->flags & FREC_DNSKEY_QUERY) ? "dnssec-retry[DNSKEY]" : "dnssec-retry[DS]", 0); #endif srv->queries++; @@ -571,7 +545,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, reply: if (udpfd != -1) { - if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last, ede))) + if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, (char *)(header + replylimit), first, last, ede))) return 0; if (oph) @@ -579,9 +553,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, u16 swap = htons((u16)ede); if (ede != EDE_UNSET) - plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + plen = add_pseudoheader(header, plen, (unsigned char *)(header + replylimit), 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); else - plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); + plen = add_pseudoheader(header, plen, (unsigned char *)(header + replylimit), 0, 0, NULL, 0, do_bit, 0); } #if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) @@ -626,8 +600,6 @@ int fast_retry(time_t now) to_run = f->forward_delay - 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 */ @@ -635,16 +607,12 @@ int fast_retry(time_t now) 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; daemon->log_source_addr = NULL; - forward_query(-1, NULL, NULL, 0, header, f->stash_len, ((char *) header) + udp_size, now, f, + forward_query(-1, NULL, NULL, 0, header, f->stash_len, 0, now, f, f->flags & FREC_AD_QUESTION, f->flags & FREC_DO_QUESTION, 1); - + to_run = f->forward_delay = 2 * f->forward_delay; } @@ -703,7 +671,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server { /* Get extended RCODE. */ rcode |= sizep[2] << 4; - + if (option_bool(OPT_CLIENT_SUBNET) && !check_source(header, plen, pheader, query_source)) { my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch")); @@ -720,17 +688,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } 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; - GETSHORT(udpsz, sizep); - if (udpsz > daemon->edns_pktsz) - { - sizep -= 2; - PUTSHORT(daemon->edns_pktsz, sizep); - } - + /* Advertise our max UDP packet to the client. */ + PUTSHORT(daemon->edns_pktsz, sizep); + #ifdef HAVE_DNSSEC /* If the client didn't set the do bit, but we did, reset it. */ if (option_bool(OPT_DNSSEC_VALID) && !do_bit) @@ -874,7 +834,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server if (pheader && ede != EDE_UNSET) { u16 swap = htons((u16)ede); - n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1); + n = add_pseudoheader(header, n, limit, 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1); } if (RCODE(header) == NXDOMAIN) @@ -1017,6 +977,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, struct randfd_list *rfds = NULL; struct frec *new = NULL; struct blockdata *newstash = NULL; + unsigned char *p; /* Make sure we don't expire and free the orig frec during the allocation of a new one: third arg of get_new_frec() does that. */ @@ -1024,7 +985,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, (server = daemon->serverarray[serverind]) && (nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz, daemon->keyname, forward->class, get_id(), - STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) && + STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS)) && (fd = allocate_rfd(&rfds, server)) != -1 && (newstash = blockdata_alloc((char *)header, nn)) && (new = get_new_frec(now, server, 1))) @@ -1067,6 +1028,10 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, if (option_bool(OPT_CONNTRACK)) set_outgoing_mark(orig, fd); #endif + + /* Maximum packet size depends on the server */ + if (find_pseudoheader(header, nn, NULL, &p, NULL, NULL)) + PUTSHORT(server->edns_pktsz, p); server_send(server, fd, header, nn, 0); server->queries++; @@ -1230,9 +1195,6 @@ void reply_query(int fd, time_t now) if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) && forward->forwardall == 0) /* for broken servers, attempt to send to another one. */ { - unsigned char *udpsz; - unsigned short udp_size = PACKETSZ; /* default if no EDNS0 */ - #ifdef HAVE_DNSSEC /* The query MAY have got a good answer, and be awaiting the results of further queries, in which case @@ -1244,11 +1206,7 @@ void reply_query(int fd, time_t now) /* Get the saved query back. */ blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); - /* UDP size already set in saved query. */ - if (find_pseudoheader(header, (size_t)forward->stash_len, NULL, &udpsz, NULL, NULL)) - GETSHORT(udp_size, udpsz); - - forward_query(-1, NULL, NULL, 0, header, forward->stash_len, ((char *) header) + udp_size, now, forward, + forward_query(-1, NULL, NULL, 0, header, forward->stash_len, 0, now, forward, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 0); return; } @@ -1264,9 +1222,9 @@ void reply_query(int fd, time_t now) /* We tried resending to this server with a smaller maximum size and got an answer. Make that permanent. To avoid reduxing the packet size for a single dropped packet, - only do this when we get a truncated answer, or one larger than the safe size. */ + only do this when we get a truncated answer, or one that fits the safe size. */ if (server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && - ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ)) + ((header->hb3 & HB3_TC) || n <= SAFE_PKTSZ)) { server->edns_pktsz = SAFE_PKTSZ; server->pktsz_reduced = now; @@ -1335,7 +1293,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s union all_addr a; a.log.ede = ede = errflags_to_ede(status); - + if (STAT_ISEQUAL(status, STAT_ABANDONED)) { result = "ABANDONED"; @@ -1358,10 +1316,10 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s log_query(F_SECSTAT, domain, &a, result, 0); } } - + if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM]) daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter; - + if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM]) daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter; #endif @@ -1386,23 +1344,9 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s ((unsigned char *)header) + daemon->edns_pktsz, ede))) { struct frec_src *src; + int do_trunc; - header->id = htons(forward->frec_src.orig_id); -#ifdef HAVE_DNSSEC - /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size - greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0 - header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */ - if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ)) - { - header->ancount = htons(0); - header->nscount = htons(0); - header->arcount = htons(0); - header->hb3 |= HB3_TC; - nn = resize_packet(header, nn, NULL, 0); - } -#endif - - for (src = &forward->frec_src; src; src = src->next) + for (do_trunc = 0, src = &forward->frec_src; src; src = src->next) { header->id = htons(src->orig_id); @@ -1418,8 +1362,18 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s if (src->fd != -1) { - send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, - &src->source, &src->dest, src->iface); + /* Only send packets that fit what the requestor allows. + We'll send a truncated packet to others below. */ + if (nn <= src->udp_pkt_size) + { + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, + &src->source, &src->dest, src->iface); +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source, src->fd); +#endif + } + else + do_trunc = 1; if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) { @@ -1427,17 +1381,42 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s 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 } } - } + + /* The packet is too big for one or more requestors, send them a truncated answer. */ + if (do_trunc) + { + size_t hlen, new; + unsigned char *pheader = find_pseudoheader(header, nn, &hlen, NULL, NULL, NULL); + + header->ancount = htons(0); + header->nscount = htons(0); + header->arcount = htons(0); + header->hb3 |= HB3_TC; + new = resize_packet(header, nn, pheader, hlen); + daemon->log_display_id = forward->frec_src.log_id; + daemon->log_source_addr = &forward->frec_src.source; + log_query(F_UPSTREAM, "query", NULL, "truncated", 0); + + for (src = &forward->frec_src; src; src = src->next) + if (src->fd != -1 && nn > src->udp_pkt_size) + { + header->id = htons(src->orig_id); + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, new, + &src->source, &src->dest, src->iface); + +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source, src->fd); +#endif + } + } + } + free_frec(forward); /* cancel */ } - + #ifdef HAVE_CONNTRACK static int is_query_allowed_for_mark(u32 mark, const char *name) @@ -1481,7 +1460,7 @@ static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark return p - (unsigned char *)header; } #endif - + void receive_query(struct listener *listen, time_t now) { struct dns_header *header = (struct dns_header *)daemon->packet; @@ -1784,9 +1763,6 @@ void receive_query(struct listener *listen, time_t now) 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 */ - - pheader -= 6; /* ext_class */ - PUTSHORT(udp_size, pheader); /* Bounding forwarded queries to maximum configured */ } #ifdef HAVE_CONNTRACK @@ -1806,7 +1782,7 @@ void receive_query(struct listener *listen, time_t now) m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL); if (have_pseudoheader && m != 0) - m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + m = add_pseudoheader(header, m, ((unsigned char *) header) + deamon->edns_pktsz, 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); if (m >= 1) @@ -1853,7 +1829,7 @@ void receive_query(struct listener *listen, time_t now) 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, &stale, &filtered); + dst_addr_4, netmask, now, ad_reqd, do_bit, &stale, &filtered); if (m >= 1) { @@ -1870,9 +1846,12 @@ void receive_query(struct listener *listen, time_t now) { u16 swap = htons(ede); - m = add_pseudoheader(header, m, ((unsigned char *) header) + udp_size, daemon->edns_pktsz, + m = add_pseudoheader(header, m, ((unsigned char *) header) + daemon->edns_pktsz, 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); } + else + m = add_pseudoheader(header, m, ((unsigned char *) header) + daemon->edns_pktsz, 0, + 0, NULL, 0, do_bit, 0); } #ifdef HAVE_DUMPFILE @@ -1911,7 +1890,7 @@ void receive_query(struct listener *listen, time_t now) blockdata_retrieve(saved_question, (size_t)n, (void *)header); blockdata_free(saved_question); if (forward_query(fd, &source_addr, &dst_addr, if_index, header, (size_t)n, - ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit, 0)) + udp_size, now, NULL, ad_reqd, do_bit, 0)) daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++; else daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++; @@ -1921,7 +1900,7 @@ void receive_query(struct listener *listen, time_t now) } } } - + /* Send query in packet, qsize to a server determined by first,last,start and get the reply. return reply size. */ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, size_t qsize, @@ -2200,7 +2179,7 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si } m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, 0, - STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz); + STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS); if ((start = dnssec_server(server, keyname, &first, &last)) == -1) { @@ -2427,7 +2406,7 @@ unsigned char *tcp_request(int confd, time_t now, m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL); if (have_pseudoheader && m != 0) - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); } #endif @@ -2455,7 +2434,7 @@ unsigned char *tcp_request(int confd, time_t now, /* 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, &filtered); + dst_addr_4, netmask, now, ad_reqd, do_bit, &stale, &filtered); } /* Do this by steam now we're not in the select() loop */ check_log_writer(1); @@ -2596,34 +2575,27 @@ unsigned char *tcp_request(int confd, time_t now, if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff, ((char *) header) + 65536, first, last, ede))) break; - - 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); - } } - else if (have_pseudoheader) + else { ede = EDE_UNSET; - + if (filtered) ede = EDE_FILTERED; else if (stale) ede = EDE_STALE; + } + + if (have_pseudoheader) + { + u16 swap = htons((u16)ede); if (ede != EDE_UNSET) - { - u16 swap = htons((u16)ede); - - m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); - } + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, 0, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0); + else + m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, 0, 0, NULL, 0, do_bit, 0); } - + check_log_writer(1); *length = htons(m); diff --git a/src/rfc1035.c b/src/rfc1035.c index 387d894..478e6d0 100644 --- a/src/rfc1035.c +++ b/src/rfc1035.c @@ -1316,7 +1316,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu GETSHORT(qtype, p); GETSHORT(qclass, p); GETLONG(ttl, p); - GETSHORT(rdlen, p) + GETSHORT(rdlen, p); if (ttlp) *ttlp = ttl; @@ -1565,8 +1565,7 @@ 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, - int *stale, int *filtered) + time_t now, int ad_reqd, int do_bit, int *stale, int *filtered) { char *name = daemon->namebuff; unsigned char *p, *ansp; @@ -2342,10 +2341,6 @@ 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, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0); - if (ad_reqd && sec_data) header->hb4 |= HB4_AD; else