diff options
Diffstat (limited to 'src/forward.c')
-rw-r--r-- | src/forward.c | 271 |
1 files changed, 189 insertions, 82 deletions
diff --git a/src/forward.c b/src/forward.c index 9c2b2c6..7a95ddf 100644 --- a/src/forward.c +++ b/src/forward.c @@ -16,10 +16,12 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, void *hash); +static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, void *hash); +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); + static unsigned short get_id(void); static void free_frec(struct frec *f); @@ -255,20 +257,29 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, int type = SERV_DO_DNSSEC, norebind = 0; union all_addr *addrp = NULL; unsigned int flags = 0; + unsigned int fwd_flags = 0; struct server *start = NULL; -#ifdef HAVE_DNSSEC void *hash = hash_questions(header, plen, daemon->namebuff); +#ifdef HAVE_DNSSEC int do_dnssec = 0; -#else - unsigned int crc = questions_crc(header, plen, daemon->namebuff); - void *hash = &crc; #endif unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); (void)do_bit; - + + if (header->hb4 & HB4_CD) + fwd_flags |= FREC_CHECKING_DISABLED; + if (ad_reqd) + fwd_flags |= FREC_AD_QUESTION; + if (oph) + fwd_flags |= FREC_HAS_PHEADER; +#ifdef HAVE_DNSSEC + if (do_bit) + fwd_flags |= FREC_DO_QUESTION; +#endif + /* may be no servers available. */ - if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) + if (forward || (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. @@ -339,6 +350,39 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, } else { + /* Query from new source, but the same query may be in progress + from another source. If so, just add this client to the + list that will get the reply.*/ + + if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && + (forward = lookup_frec_by_query(hash, fwd_flags))) + { + /* Note whine_malloc() zeros memory. */ + if (!daemon->free_frec_src && + daemon->frec_src_count < daemon->ftabsize && + (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) + { + daemon->frec_src_count++; + daemon->free_frec_src->next = NULL; + } + + /* If we've been spammed with many duplicates, just drop the query. */ + if (daemon->free_frec_src) + { + struct frec_src *new = daemon->free_frec_src; + daemon->free_frec_src = new->next; + new->next = forward->frec_src.next; + forward->frec_src.next = new; + new->orig_id = ntohs(header->id); + new->source = *udpaddr; + new->dest = *dst_addr; + new->log_id = daemon->log_id; + new->iface = dst_iface; + } + + return 1; + } + if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); @@ -346,22 +390,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, do_dnssec = type & SERV_DO_DNSSEC; #endif type &= ~SERV_DO_DNSSEC; - + if (daemon->servers && !flags) forward = get_new_frec(now, NULL, NULL); /* table full - flags == 0, return REFUSED */ if (forward) { - forward->source = *udpaddr; - forward->dest = *dst_addr; - forward->iface = dst_iface; - forward->orig_id = ntohs(header->id); + forward->frec_src.source = *udpaddr; + forward->frec_src.orig_id = ntohs(header->id); + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; + forward->frec_src.next = NULL; forward->new_id = get_id(); forward->fd = udpfd; memcpy(forward->hash, hash, HASH_SIZE); forward->forwardall = 0; - forward->flags = 0; + forward->flags = fwd_flags; if (norebind) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) @@ -411,18 +456,21 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (!flags && forward) { struct server *firstsentto = start; - int subnet, forwarded = 0; + int subnet, cacheable, forwarded = 0; size_t edns0_len; unsigned char *pheader; /* If a query is retried, use the log_id for the retry when logging the answer. */ - forward->log_id = daemon->log_id; + forward->frec_src.log_id = daemon->log_id; - plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet); + plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable); if (subnet) forward->flags |= FREC_HAS_SUBNET; - + + if (!cacheable) + forward->flags |= FREC_NO_CACHE; + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) { @@ -490,7 +538,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, if (option_bool(OPT_CONNTRACK)) { unsigned int mark; - if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) + if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); } #endif @@ -555,7 +603,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, return 1; /* could not send on, prepare to return */ - header->id = htons(forward->orig_id); + header->id = htons(forward->frec_src.orig_id); free_frec(forward); /* cancel */ } @@ -606,7 +654,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server } } #endif - + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) { /* Get extended RCODE. */ @@ -769,9 +817,6 @@ void reply_query(int fd, int family, time_t now) size_t nn; struct server *server; void *hash; -#ifndef HAVE_DNSSEC - unsigned int crc; -#endif /* packet buffer overwritten */ daemon->srv_save = NULL; @@ -798,14 +843,9 @@ void reply_query(int fd, int family, time_t now) if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) server->edns_pktsz = daemon->edns_pktsz; -#ifdef HAVE_DNSSEC hash = hash_questions(header, n, daemon->namebuff); -#else - hash = &crc; - crc = questions_crc(header, n, daemon->namebuff); -#endif - if (!(forward = lookup_frec(ntohs(header->id), hash))) + if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) return; #ifdef HAVE_DUMPFILE @@ -815,8 +855,8 @@ void reply_query(int fd, int family, time_t now) /* log_query gets called indirectly all over the place, so pass these in global variables - sorry. */ - daemon->log_display_id = forward->log_id; - daemon->log_source_addr = &forward->source; + daemon->log_display_id = forward->frec_src.log_id; + daemon->log_source_addr = &forward->frec_src.source; if (daemon->ignore_addr && RCODE(header) == NOERROR && check_for_ignored_address(header, n, daemon->ignore_addr)) @@ -834,7 +874,6 @@ void reply_query(int fd, int family, time_t now) int is_sign; #ifdef HAVE_DNSSEC - /* For DNSSEC originated queries, just retry the query to the same server. */ if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) { struct server *start; @@ -860,6 +899,8 @@ void reply_query(int fd, int family, time_t now) } + fd = -1; + if (start->sfd) fd = start->sfd->fd; else @@ -867,19 +908,21 @@ void reply_query(int fd, int family, time_t now) if (start->addr.sa.sa_family == AF_INET6) { /* may have changed family */ - if (!forward->rfd6) - forward->rfd6 = allocate_rfd(AF_INET6); - fd = forward->rfd6->fd; + if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6))) + fd = forward->rfd6->fd; } else { /* may have changed family */ - if (!forward->rfd4) - forward->rfd4 = allocate_rfd(AF_INET); - fd = forward->rfd4->fd; + if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET))) + fd = forward->rfd4->fd; } } + /* Can't get socket. */ + if (fd == -1) + return; + #ifdef HAVE_DUMPFILE dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr); #endif @@ -1088,6 +1131,7 @@ void reply_query(int fd, int family, time_t now) new->sentto = server; new->rfd4 = NULL; new->rfd6 = NULL; + new->frec_src.next = NULL; new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); new->forwardall = 0; @@ -1115,8 +1159,7 @@ void reply_query(int fd, int family, time_t now) log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr), querystr("dnssec-query", querytype)); - if ((hash = hash_questions(header, nn, daemon->namebuff))) - memcpy(new->hash, hash, HASH_SIZE); + memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); new->new_id = get_id(); header->id = htons(new->new_id); /* Save query for retransmission */ @@ -1150,7 +1193,7 @@ void reply_query(int fd, int family, time_t now) if (option_bool(OPT_CONNTRACK)) { unsigned int mark; - if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark)) + if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark)) setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); } #endif @@ -1221,12 +1264,19 @@ void reply_query(int fd, int family, time_t now) header->hb4 |= HB4_CD; else header->hb4 &= ~HB4_CD; + + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (forward->flags & FREC_NO_CACHE) + no_cache_dnssec = 1; if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, - forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) { - header->id = htons(forward->orig_id); + struct frec_src *src; + + header->id = htons(forward->frec_src.orig_id); header->hb4 |= HB4_RA; /* recursion if available */ #ifdef HAVE_DNSSEC /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size @@ -1242,13 +1292,26 @@ void reply_query(int fd, int family, time_t now) } #endif + for (src = &forward->frec_src; src; src = src->next) + { + header->id = htons(src->orig_id); + #ifdef HAVE_DUMPFILE - dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source); + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); #endif - - send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, - &forward->source, &forward->dest, forward->iface); + + send_from(forward->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"); + } + } } + free_frec(forward); /* cancel */ } } @@ -1767,7 +1830,7 @@ unsigned char *tcp_request(int confd, time_t now, int local_auth = 0; #endif int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; - int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; size_t m; unsigned short qtype; unsigned int gotname; @@ -1938,7 +2001,7 @@ unsigned char *tcp_request(int confd, time_t now, char *domain = NULL; unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); - size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); + size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); @@ -1970,15 +2033,9 @@ unsigned char *tcp_request(int confd, time_t now, if (!flags && last_server) { struct server *firstsendto = NULL; -#ifdef HAVE_DNSSEC - unsigned char *newhash, hash[HASH_SIZE]; - if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) - memcpy(hash, newhash, HASH_SIZE); - else - memset(hash, 0, HASH_SIZE); -#else - unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); -#endif + unsigned char hash[HASH_SIZE]; + memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE); + /* Loop round available servers until we succeed in connecting to one. Note that this code subtly ensures that consecutive queries on this connection which can go to the same server, do so. */ @@ -2117,21 +2174,17 @@ unsigned char *tcp_request(int confd, time_t now, /* If the crc of the question section doesn't match the crc we sent, then someone might be attempting to insert bogus values into the cache by sending replies containing questions and bogus answers. */ -#ifdef HAVE_DNSSEC - newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); - if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) + if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0) { m = 0; break; } -#else - if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) - { - m = 0; - break; - } -#endif + /* Never cache answers which are contingent on the source or MAC address EDSN0 option, + since the cache is ignorant of such things. */ + if (!cacheable) + no_cache_dnssec = 1; + m = process_reply(header, now, last_server, (unsigned int)m, option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); @@ -2226,6 +2279,17 @@ void free_rfd(struct randfd *rfd) static void free_frec(struct frec *f) { + struct frec_src *last; + + /* add back to freelist if not the record builtin to every frec. */ + for (last = f->frec_src.next; last && last->next; last = last->next) ; + if (last) + { + last->next = daemon->free_frec_src; + daemon->free_frec_src = f->frec_src.next; + } + + f->frec_src.next = NULL; free_rfd(f->rfd4); f->rfd4 = NULL; f->sentto = NULL; @@ -2338,15 +2402,25 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force) return f; /* OK if malloc fails and this is NULL */ } -/* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, void *hash) +static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) { struct frec *f; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->new_id == id && - (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) - return f; + (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ + if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) + return f; + + if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) + return f; + + /* sent to upstream from bound socket. */ + if (f->sentto->sfd && f->sentto->sfd->fd == fd) + return f; + } return NULL; } @@ -2356,17 +2430,42 @@ static struct frec *lookup_frec_by_sender(unsigned short id, void *hash) { struct frec *f; + struct frec_src *src; + + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && + !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && + memcmp(hash, f->hash, HASH_SIZE) == 0) + for (src = &f->frec_src; src; src = src->next) + if (src->orig_id == id && + sockaddr_isequal(&src->source, addr)) + return f; + + return NULL; +} + +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) +{ + struct frec *f; + + /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below + ensures that no frec created for internal DNSSEC query can be returned here. + + Similarly FREC_NO_CACHE is never set in flags, so a query which is + contigent on a particular source address EDNS0 option will never be matched. */ + +#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ + | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) for(f = daemon->frec_list; f; f = f->next) if (f->sentto && - f->orig_id == id && - memcmp(hash, f->hash, HASH_SIZE) == 0 && - sockaddr_isequal(&f->source, addr)) + (f->flags & FLAGMASK) == flags && + memcmp(hash, f->hash, HASH_SIZE) == 0) return f; - + return NULL; } - + /* Send query packet again, if we can. */ void resend_query() { @@ -2407,12 +2506,20 @@ void server_gone(struct server *server) static unsigned short get_id(void) { unsigned short ret = 0; + struct frec *f; - do - ret = rand16(); - while (lookup_frec(ret, NULL)); - - return ret; + while (1) + { + ret = rand16(); + + /* ensure id is unique. */ + for (f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == ret) + break; + + if (!f) + return ret; + } } |