From 74d4fcd756a85bc1823232ea74334f7ccfb9d5d2 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 15 Mar 2021 21:59:51 +0000 Subject: [PATCH] Use random source ports where possible if source addresses/interfaces in use. CVE-2021-3448 applies. It's possible to specify the source address or interface to be used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4 or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of these have, until now, used a single socket, bound to a fixed port. This was originally done to allow an error (non-existent interface, or non-local address) to be detected at start-up. This means that any upstream servers specified in such a way don't use random source ports, and are more susceptible to cache-poisoning attacks. We now use random ports where possible, even when the source is specified, so server=8.8.8.8@1.2.3.4 or server=8.8.8.8@eth0 will use random source ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will use the explicitly configured port, and should only be done with understanding of the security implications. Note that this change changes non-existing interface, or non-local source address errors from fatal to run-time. The error will be logged and communiction with the server not possible. Upstream-Status: Backport CVE: CVE-2021-3448 Signed-off-by: Armin Kuster --- CHANGELOG | 22 +++ man/dnsmasq.8 | 4 +- src/dnsmasq.c | 31 ++-- src/dnsmasq.h | 26 ++-- src/forward.c | 392 ++++++++++++++++++++++++++++++-------------------- src/loop.c | 20 +-- src/network.c | 110 +++++--------- src/option.c | 3 +- src/tftp.c | 6 +- src/util.c | 2 +- 10 files changed, 344 insertions(+), 272 deletions(-) Index: dnsmasq-2.81/man/dnsmasq.8 =================================================================== --- dnsmasq-2.81.orig/man/dnsmasq.8 +++ dnsmasq-2.81/man/dnsmasq.8 @@ -489,7 +489,7 @@ source address specified but the port ma part of the source address. Forcing queries to an interface is not implemented on all platforms supported by dnsmasq. .TP -.B --rev-server=/[,][#][@|[#]] +.B --rev-server=/[,][#][@][@[#]] This is functionally the same as .B --server, but provides some syntactic sugar to make specifying address-to-name queries easier. For example Index: dnsmasq-2.81/src/dnsmasq.c =================================================================== --- dnsmasq-2.81.orig/src/dnsmasq.c +++ dnsmasq-2.81/src/dnsmasq.c @@ -1668,6 +1668,7 @@ static int set_dns_listeners(time_t now) { struct serverfd *serverfdp; struct listener *listener; + struct randfd_list *rfl; int wait = 0, i; #ifdef HAVE_TFTP @@ -1688,11 +1689,14 @@ static int set_dns_listeners(time_t now) for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) poll_listen(serverfdp->fd, POLLIN); - if (daemon->port != 0 && !daemon->osport) - for (i = 0; i < RANDOM_SOCKS; i++) - if (daemon->randomsocks[i].refcount != 0) - poll_listen(daemon->randomsocks[i].fd, POLLIN); - + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount != 0) + poll_listen(daemon->randomsocks[i].fd, POLLIN); + + /* Check overflow random sockets too. */ + for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) + poll_listen(rfl->rfd->fd, POLLIN); + for (listener = daemon->listeners; listener; listener = listener->next) { /* only listen for queries if we have resources */ @@ -1729,18 +1733,23 @@ static void check_dns_listeners(time_t n { struct serverfd *serverfdp; struct listener *listener; + struct randfd_list *rfl; int i; int pipefd[2]; for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next) if (poll_check(serverfdp->fd, POLLIN)) - reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now); + reply_query(serverfdp->fd, now); - if (daemon->port != 0 && !daemon->osport) - for (i = 0; i < RANDOM_SOCKS; i++) - if (daemon->randomsocks[i].refcount != 0 && - poll_check(daemon->randomsocks[i].fd, POLLIN)) - reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now); + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount != 0 && + poll_check(daemon->randomsocks[i].fd, POLLIN)) + reply_query(daemon->randomsocks[i].fd, now); + + /* Check overflow random sockets too. */ + for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next) + if (poll_check(rfl->rfd->fd, POLLIN)) + reply_query(rfl->rfd->fd, now); /* Races. The child process can die before we read all of the data from the pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the Index: dnsmasq-2.81/src/dnsmasq.h =================================================================== --- dnsmasq-2.81.orig/src/dnsmasq.h +++ dnsmasq-2.81/src/dnsmasq.h @@ -542,13 +542,20 @@ struct serverfd { }; struct randfd { + struct server *serv; int fd; - unsigned short refcount, family; + unsigned short refcount; /* refcount == 0xffff means overflow record. */ }; - + +struct randfd_list { + struct randfd *rfd; + struct randfd_list *next; +}; + struct server { union mysockaddr addr, source_addr; char interface[IF_NAMESIZE+1]; + unsigned int ifindex; /* corresponding to interface, above */ struct serverfd *sfd; char *domain; /* set if this server only handles a domain. */ int flags, tcpfd, edns_pktsz; @@ -669,8 +676,7 @@ struct frec { struct frec_src *next; } frec_src; struct server *sentto; /* NULL means free */ - struct randfd *rfd4; - struct randfd *rfd6; + struct randfd_list *rfds; unsigned short new_id; int fd, forwardall, flags; time_t time; @@ -1100,11 +1106,12 @@ extern struct daemon { int forwardcount; struct server *srv_save; /* Used for resend on DoD */ size_t packet_len; /* " " */ - struct randfd *rfd_save; /* " " */ + int fd_save; /* " " */ pid_t tcp_pids[MAX_PROCS]; int tcp_pipes[MAX_PROCS]; int pipe_to_parent; struct randfd randomsocks[RANDOM_SOCKS]; + struct randfd_list *rfl_spare, *rfl_poll; int v6pktinfo; struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */ int log_id, log_display_id; /* ids of transactions for logging */ @@ -1275,7 +1282,7 @@ void safe_strncpy(char *dest, const char void safe_pipe(int *fd, int read_noblock); void *whine_malloc(size_t size); int sa_len(union mysockaddr *addr); -int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2); +int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2); int hostname_isequal(const char *a, const char *b); int hostname_issubdomain(char *a, char *b); time_t dnsmasq_time(void); @@ -1326,7 +1333,7 @@ char *parse_server(char *arg, union myso int option_read_dynfile(char *file, int flags); /* forward.c */ -void reply_query(int fd, int family, time_t now); +void reply_query(int fd, time_t now); void receive_query(struct listener *listen, time_t now); unsigned char *tcp_request(int confd, time_t now, union mysockaddr *local_addr, struct in_addr netmask, int auth_dns); @@ -1336,13 +1343,12 @@ int send_from(int fd, int nowild, char * union mysockaddr *to, union all_addr *source, unsigned int iface); void resend_query(void); -struct randfd *allocate_rfd(int family); -void free_rfd(struct randfd *rfd); +int allocate_rfd(struct randfd_list **fdlp, struct server *serv); +void free_rfds(struct randfd_list **fdlp); /* network.c */ int indextoname(int fd, int index, char *name); int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp); -int random_sock(int family); void pre_allocate_sfds(void); int reload_servers(char *fname); void mark_servers(int flag); Index: dnsmasq-2.81/src/forward.c =================================================================== --- dnsmasq-2.81.orig/src/forward.c +++ dnsmasq-2.81/src/forward.c @@ -16,7 +16,7 @@ #include "dnsmasq.h" -static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); +static struct frec *lookup_frec(unsigned short id, int fd, void *hash); static struct frec *lookup_frec_by_sender(unsigned short id, union mysockaddr *addr, void *hash); @@ -307,26 +307,18 @@ static int forward_query(int udpfd, unio if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign) PUTSHORT(SAFE_PKTSZ, pheader); - if (forward->sentto->addr.sa.sa_family == AF_INET) - log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); - else - log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); - - - if (forward->sentto->sfd) - fd = forward->sentto->sfd->fd; - else + if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1) { - if (forward->sentto->addr.sa.sa_family == AF_INET6) - fd = forward->rfd6->fd; + if (forward->sentto->addr.sa.sa_family == AF_INET) + log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (union all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec"); else - fd = forward->rfd4->fd; + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (union all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec"); + + while (retry_send(sendto(fd, (char *)header, plen, 0, + &forward->sentto->addr.sa, + sa_len(&forward->sentto->addr)))); } - while (retry_send(sendto(fd, (char *)header, plen, 0, - &forward->sentto->addr.sa, - sa_len(&forward->sentto->addr)))); - return 1; } #endif @@ -501,49 +493,28 @@ static int forward_query(int udpfd, unio while (1) { + int fd; + /* only send to servers dealing with our domain. domain may be NULL, in which case server->domain must be NULL also. */ if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && - !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP))) + !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && + ((fd = allocate_rfd(&forward->rfds, start)) != -1)) { - int fd; - - /* find server socket to use, may need to get random one. */ - if (start->sfd) - fd = start->sfd->fd; - else - { - if (start->addr.sa.sa_family == AF_INET6) - { - if (!forward->rfd6 && - !(forward->rfd6 = allocate_rfd(AF_INET6))) - break; - daemon->rfd_save = forward->rfd6; - fd = forward->rfd6->fd; - } - else - { - if (!forward->rfd4 && - !(forward->rfd4 = allocate_rfd(AF_INET))) - break; - daemon->rfd_save = forward->rfd4; - fd = forward->rfd4->fd; - } #ifdef HAVE_CONNTRACK - /* Copy connection mark of incoming query to outgoing connection. */ - if (option_bool(OPT_CONNTRACK)) - { - unsigned int mark; - if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) - setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); - } -#endif + /* Copy connection mark of incoming query to outgoing connection. */ + if (option_bool(OPT_CONNTRACK)) + { + unsigned int 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 + #ifdef HAVE_DNSSEC if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER)) { @@ -574,6 +545,7 @@ static int forward_query(int udpfd, unio /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; daemon->packet_len = plen; + daemon->fd_save = fd; if (!gotname) strcpy(daemon->namebuff, "query"); @@ -590,7 +562,7 @@ static int forward_query(int udpfd, unio break; forward->forwardall++; } - } + } if (!(start = start->next)) start = daemon->servers; @@ -805,7 +777,7 @@ static size_t process_reply(struct dns_h } /* sets new last_server */ -void reply_query(int fd, int family, time_t now) +void reply_query(int fd, time_t now) { /* packet from peer server, extract data for cache, and send to original requester */ @@ -820,9 +792,9 @@ void reply_query(int fd, int family, tim /* packet buffer overwritten */ daemon->srv_save = NULL; - + /* Determine the address of the server replying so that we can mark that as good */ - if ((serveraddr.sa.sa_family = family) == AF_INET6) + if (serveraddr.sa.sa_family == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; header = (struct dns_header *)daemon->packet; @@ -845,7 +817,7 @@ void reply_query(int fd, int family, tim hash = hash_questions(header, n, daemon->namebuff); - if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) + if (!(forward = lookup_frec(ntohs(header->id), fd, hash))) return; #ifdef HAVE_DUMPFILE @@ -900,25 +872,8 @@ void reply_query(int fd, int family, tim } - if (start->sfd) - fd = start->sfd->fd; - else - { - 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; - } - else - { - /* may have changed family */ - if (!forward->rfd4) - forward->rfd4 = allocate_rfd(AF_INET); - fd = forward->rfd4->fd; - } - } + if ((fd = allocate_rfd(&forward->rfds, start)) == -1) + return; #ifdef HAVE_DUMPFILE dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr); @@ -1126,8 +1081,7 @@ void reply_query(int fd, int family, tim } new->sentto = server; - new->rfd4 = NULL; - new->rfd6 = NULL; + new->rfds = NULL; new->frec_src.next = NULL; new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); new->forwardall = 0; @@ -1166,24 +1120,7 @@ void reply_query(int fd, int family, tim /* Don't resend this. */ daemon->srv_save = NULL; - if (server->sfd) - fd = server->sfd->fd; - else - { - fd = -1; - if (server->addr.sa.sa_family == AF_INET6) - { - if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) - fd = new->rfd6->fd; - } - else - { - if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) - fd = new->rfd4->fd; - } - } - - if (fd != -1) + if ((fd = allocate_rfd(&new->rfds, server)) != -1) { #ifdef HAVE_CONNTRACK /* Copy connection mark of incoming query to outgoing connection. */ @@ -1344,7 +1281,7 @@ void receive_query(struct listener *list /* packet buffer overwritten */ daemon->srv_save = NULL; - + dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0; netmask.s_addr = 0; @@ -2207,9 +2144,8 @@ static struct frec *allocate_frec(time_t f->next = daemon->frec_list; f->time = now; f->sentto = NULL; - f->rfd4 = NULL; + f->rfds = NULL; f->flags = 0; - f->rfd6 = NULL; #ifdef HAVE_DNSSEC f->dependent = NULL; f->blocking_query = NULL; @@ -2221,46 +2157,192 @@ static struct frec *allocate_frec(time_t return f; } -struct randfd *allocate_rfd(int family) +/* return a UDP socket bound to a random port, have to cope with straying into + occupied port nos and reserved ones. */ +static int random_sock(struct server *s) +{ + int fd; + + if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1) + { + if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0)) + return fd; + + if (s->interface[0] == 0) + (void)prettyprint_addr(&s->source_addr, daemon->namebuff); + else + strcpy(daemon->namebuff, s->interface); + + my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"), + daemon->namebuff, strerror(errno)); + close(fd); + } + + return -1; +} + +/* compare source addresses and interface, serv2 can be null. */ +static int server_isequal(const struct server *serv1, + const struct server *serv2) +{ + return (serv2 && + serv2->ifindex == serv1->ifindex && + sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) && + strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0); +} + +/* fdlp points to chain of randomfds already in use by transaction. + If there's already a suitable one, return it, else allocate a + new one and add it to the list. + + Not leaking any resources in the face of allocation failures + is rather convoluted here. + + Note that rfd->serv may be NULL, when a server goes away. +*/ +int allocate_rfd(struct randfd_list **fdlp, struct server *serv) { static int finger = 0; - int i; + int i, j = 0; + struct randfd_list *rfl; + struct randfd *rfd = NULL; + int fd = 0; + + /* If server has a pre-allocated fd, use that. */ + if (serv->sfd) + return serv->sfd->fd; + + /* existing suitable random port socket linked to this transaction? */ + for (rfl = *fdlp; rfl; rfl = rfl->next) + if (server_isequal(serv, rfl->rfd->serv)) + return rfl->rfd->fd; + + /* No. need new link. */ + if ((rfl = daemon->rfl_spare)) + daemon->rfl_spare = rfl->next; + else if (!(rfl = whine_malloc(sizeof(struct randfd_list)))) + return -1; /* limit the number of sockets we have open to avoid starvation of (eg) TFTP. Once we have a reasonable number, randomness should be OK */ - for (i = 0; i < RANDOM_SOCKS; i++) if (daemon->randomsocks[i].refcount == 0) { - if ((daemon->randomsocks[i].fd = random_sock(family)) == -1) - break; - - daemon->randomsocks[i].refcount = 1; - daemon->randomsocks[i].family = family; - return &daemon->randomsocks[i]; + if ((fd = random_sock(serv)) != -1) + { + rfd = &daemon->randomsocks[i]; + rfd->serv = serv; + rfd->fd = fd; + rfd->refcount = 1; + } + break; } /* No free ones or cannot get new socket, grab an existing one */ - for (i = 0; i < RANDOM_SOCKS; i++) + if (!rfd) + for (j = 0; j < RANDOM_SOCKS; j++) + { + i = (j + finger) % RANDOM_SOCKS; + if (daemon->randomsocks[i].refcount != 0 && + server_isequal(serv, daemon->randomsocks[i].serv) && + daemon->randomsocks[i].refcount != 0xfffe) + { + finger = i + 1; + rfd = &daemon->randomsocks[i]; + rfd->refcount++; + break; + } + } + + if (j == RANDOM_SOCKS) { - int j = (i+finger) % RANDOM_SOCKS; - if (daemon->randomsocks[j].refcount != 0 && - daemon->randomsocks[j].family == family && - daemon->randomsocks[j].refcount != 0xffff) + struct randfd_list *rfl_poll; + + /* there are no free slots, and non with the same parameters we can piggy-back on. + We're going to have to allocate a new temporary record, distinguished by + refcount == 0xffff. This will exist in the frec randfd list, never be shared, + and be freed when no longer in use. It will also be held on + the daemon->rfl_poll list so the poll system can find it. */ + + if ((rfl_poll = daemon->rfl_spare)) + daemon->rfl_spare = rfl_poll->next; + else + rfl_poll = whine_malloc(sizeof(struct randfd_list)); + + if (!rfl_poll || + !(rfd = whine_malloc(sizeof(struct randfd))) || + (fd = random_sock(serv)) == -1) { - finger = j; - daemon->randomsocks[j].refcount++; - return &daemon->randomsocks[j]; + + /* Don't leak anything we may already have */ + rfl->next = daemon->rfl_spare; + daemon->rfl_spare = rfl; + + if (rfl_poll) + { + rfl_poll->next = daemon->rfl_spare; + daemon->rfl_spare = rfl_poll; + } + + if (rfd) + free(rfd); + + return -1; /* doom */ } + + /* Note rfd->serv not set here, since it's not reused */ + rfd->fd = fd; + rfd->refcount = 0xffff; /* marker for temp record */ + + rfl_poll->rfd = rfd; + rfl_poll->next = daemon->rfl_poll; + daemon->rfl_poll = rfl_poll; } - return NULL; /* doom */ + rfl->rfd = rfd; + rfl->next = *fdlp; + *fdlp = rfl; + + return rfl->rfd->fd; } -void free_rfd(struct randfd *rfd) +void free_rfds(struct randfd_list **fdlp) { - if (rfd && --(rfd->refcount) == 0) - close(rfd->fd); + struct randfd_list *tmp, *rfl, *poll, *next, **up; + + for (rfl = *fdlp; rfl; rfl = tmp) + { + if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0) + close(rfl->rfd->fd); + + /* temporary overflow record */ + if (rfl->rfd->refcount == 0xffff) + { + free(rfl->rfd); + + /* go through the link of all these by steam to delete. + This list is expected to be almost always empty. */ + for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next) + { + next = poll->next; + + if (poll->rfd == rfl->rfd) + { + *up = poll->next; + poll->next = daemon->rfl_spare; + daemon->rfl_spare = poll; + } + else + up = &poll->next; + } + } + + tmp = rfl->next; + rfl->next = daemon->rfl_spare; + daemon->rfl_spare = rfl; + } + + *fdlp = NULL; } static void free_frec(struct frec *f) @@ -2276,12 +2358,9 @@ static void free_frec(struct frec *f) } f->frec_src.next = NULL; - free_rfd(f->rfd4); - f->rfd4 = NULL; + free_rfds(&f->rfds); f->sentto = NULL; f->flags = 0; - free_rfd(f->rfd6); - f->rfd6 = NULL; #ifdef HAVE_DNSSEC if (f->stash) @@ -2389,26 +2468,39 @@ struct frec *get_new_frec(time_t now, in } /* crc is all-ones if not known. */ -static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) +static struct frec *lookup_frec(unsigned short id, int fd, void *hash) { struct frec *f; + struct server *s; + int type; + struct randfd_list *fdl; for(f = daemon->frec_list; f; f = f->next) if (f->sentto && f->new_id == id && (memcmp(hash, f->hash, HASH_SIZE) == 0)) { /* sent from random port */ - if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) + for (fdl = f->rfds; fdl; fdl = fdl->next) + if (fdl->rfd->fd == fd) return f; + } - if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) - return f; + /* Sent to upstream from socket associated with a server. + Note we have to iterate over all the possible servers, since they may + have different bound sockets. */ + type = f->sentto->flags & SERV_TYPE; + s = f->sentto; + do { + if ((type == (s->flags & SERV_TYPE)) && + (type != SERV_HAS_DOMAIN || + (s->domain && hostname_isequal(f->sentto->domain, s->domain))) && + !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) && + s->sfd && s->sfd->fd == fd) + return f; + + s = s->next ? s->next : daemon->servers; + } while (s != f->sentto); - /* sent to upstream from bound socket. */ - if (f->sentto->sfd && f->sentto->sfd->fd == fd) - return f; - } - return NULL; } @@ -2454,30 +2546,26 @@ static struct frec *lookup_frec_by_query void resend_query() { if (daemon->srv_save) - { - int fd; - - if (daemon->srv_save->sfd) - fd = daemon->srv_save->sfd->fd; - else if (daemon->rfd_save && daemon->rfd_save->refcount != 0) - fd = daemon->rfd_save->fd; - else - return; - - while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0, - &daemon->srv_save->addr.sa, - sa_len(&daemon->srv_save->addr)))); - } + while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0, + &daemon->srv_save->addr.sa, + sa_len(&daemon->srv_save->addr)))); } /* A server record is going away, remove references to it */ void server_gone(struct server *server) { struct frec *f; + int i; for (f = daemon->frec_list; f; f = f->next) if (f->sentto && f->sentto == server) free_frec(f); + + /* If any random socket refers to this server, NULL the reference. + No more references to the socket will be created in the future. */ + for (i = 0; i < RANDOM_SOCKS; i++) + if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server) + daemon->randomsocks[i].serv = NULL; if (daemon->last_server == server) daemon->last_server = NULL; Index: dnsmasq-2.81/src/loop.c =================================================================== --- dnsmasq-2.81.orig/src/loop.c +++ dnsmasq-2.81/src/loop.c @@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid); void loop_send_probes() { struct server *serv; + struct randfd_list *rfds = NULL; if (!option_bool(OPT_LOOP_DETECT)) return; @@ -34,22 +35,15 @@ void loop_send_probes() { ssize_t len = loop_make_probe(serv->uid); int fd; - struct randfd *rfd = NULL; - if (serv->sfd) - fd = serv->sfd->fd; - else - { - if (!(rfd = allocate_rfd(serv->addr.sa.sa_family))) - continue; - fd = rfd->fd; - } + if ((fd = allocate_rfd(&rfds, serv)) == -1) + continue; while (retry_send(sendto(fd, daemon->packet, len, 0, &serv->addr.sa, sa_len(&serv->addr)))); - - free_rfd(rfd); } + + free_rfds(&rfds); } static ssize_t loop_make_probe(u32 uid) Index: dnsmasq-2.81/src/network.c =================================================================== --- dnsmasq-2.81.orig/src/network.c +++ dnsmasq-2.81/src/network.c @@ -545,6 +545,7 @@ int enumerate_interfaces(int reset) #ifdef HAVE_AUTH struct auth_zone *zone; #endif + struct server *serv; /* Do this max once per select cycle - also inhibits netlink socket use in TCP child processes. */ @@ -562,7 +563,21 @@ int enumerate_interfaces(int reset) if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) return 0; - + + /* iface indexes can change when interfaces are created/destroyed. + We use them in the main forwarding control path, when the path + to a server is specified by an interface, so cache them. + Update the cache here. */ + for (serv = daemon->servers; serv; serv = serv->next) + if (strlen(serv->interface) != 0) + { + struct ifreq ifr; + + safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE); + if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1) + serv->ifindex = ifr.ifr_ifindex; + } + /* Mark interfaces for garbage collection */ for (iface = daemon->interfaces; iface; iface = iface->next) iface->found = 0; @@ -658,7 +673,7 @@ int enumerate_interfaces(int reset) errno = errsave; spare = param.spare; - + return ret; } @@ -798,10 +813,10 @@ int tcp_interface(int fd, int af) /* use mshdr so that the CMSDG_* macros are available */ msg.msg_control = daemon->packet; msg.msg_controllen = len = daemon->packet_buff_sz; - + /* we overwrote the buffer... */ daemon->srv_save = NULL; - + if (af == AF_INET) { if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 && @@ -1102,59 +1117,6 @@ void join_multicast(int dienow) } #endif -/* return a UDP socket bound to a random port, have to cope with straying into - occupied port nos and reserved ones. */ -int random_sock(int family) -{ - int fd; - - if ((fd = socket(family, SOCK_DGRAM, 0)) != -1) - { - union mysockaddr addr; - unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1; - int tries = ports_avail < 30 ? 3 * ports_avail : 100; - - memset(&addr, 0, sizeof(addr)); - addr.sa.sa_family = family; - - /* don't loop forever if all ports in use. */ - - if (fix_fd(fd)) - while(tries--) - { - unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail))); - - if (family == AF_INET) - { - addr.in.sin_addr.s_addr = INADDR_ANY; - addr.in.sin_port = port; -#ifdef HAVE_SOCKADDR_SA_LEN - addr.in.sin_len = sizeof(struct sockaddr_in); -#endif - } - else - { - addr.in6.sin6_addr = in6addr_any; - addr.in6.sin6_port = port; -#ifdef HAVE_SOCKADDR_SA_LEN - addr.in6.sin6_len = sizeof(struct sockaddr_in6); -#endif - } - - if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0) - return fd; - - if (errno != EADDRINUSE && errno != EACCES) - break; - } - - close(fd); - } - - return -1; -} - - int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp) { union mysockaddr addr_copy = *addr; @@ -1199,38 +1161,33 @@ int local_bind(int fd, union mysockaddr return 1; } -static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname) +static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex) { struct serverfd *sfd; - unsigned int ifindex = 0; int errsave; int opt = 1; /* when using random ports, servers which would otherwise use - the INADDR_ANY/port0 socket have sfd set to NULL */ - if (!daemon->osport && intname[0] == 0) + the INADDR_ANY/port0 socket have sfd set to NULL, this is + anything without an explictly set source port. */ + if (!daemon->osport) { errno = 0; if (addr->sa.sa_family == AF_INET && - addr->in.sin_addr.s_addr == INADDR_ANY && addr->in.sin_port == htons(0)) return NULL; if (addr->sa.sa_family == AF_INET6 && - memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && addr->in6.sin6_port == htons(0)) return NULL; } - if (intname && strlen(intname) != 0) - ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */ - /* may have a suitable one already */ for (sfd = daemon->sfds; sfd; sfd = sfd->next ) - if (sockaddr_isequal(&sfd->source_addr, addr) && - strcmp(intname, sfd->interface) == 0 && - ifindex == sfd->ifindex) + if (ifindex == sfd->ifindex && + sockaddr_isequal(&sfd->source_addr, addr) && + strcmp(intname, sfd->interface) == 0) return sfd; /* need to make a new one. */ @@ -1281,7 +1238,7 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(struct sockaddr_in); #endif - if ((sfd = allocate_sfd(&addr, ""))) + if ((sfd = allocate_sfd(&addr, "", 0))) sfd->preallocated = 1; memset(&addr, 0, sizeof(addr)); @@ -1291,13 +1248,13 @@ void pre_allocate_sfds(void) #ifdef HAVE_SOCKADDR_SA_LEN addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - if ((sfd = allocate_sfd(&addr, ""))) + if ((sfd = allocate_sfd(&addr, "", 0))) sfd->preallocated = 1; } for (srv = daemon->servers; srv; srv = srv->next) if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) && - !allocate_sfd(&srv->source_addr, srv->interface) && + !allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) && errno != 0 && option_bool(OPT_NOWILD)) { @@ -1506,7 +1463,7 @@ void check_servers(void) /* Do we need a socket set? */ if (!serv->sfd && - !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) && + !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) && errno != 0) { my_syslog(LOG_WARNING, Index: dnsmasq-2.81/src/option.c =================================================================== --- dnsmasq-2.81.orig/src/option.c +++ dnsmasq-2.81/src/option.c @@ -810,7 +810,8 @@ char *parse_server(char *arg, union myso if (interface_opt) { #if defined(SO_BINDTODEVICE) - safe_strncpy(interface, interface_opt, IF_NAMESIZE); + safe_strncpy(interface, source, IF_NAMESIZE); + source = interface_opt; #else return _("interface binding not supported"); #endif Index: dnsmasq-2.81/src/tftp.c =================================================================== --- dnsmasq-2.81.orig/src/tftp.c +++ dnsmasq-2.81/src/tftp.c @@ -601,7 +601,7 @@ void check_tftp_listeners(time_t now) /* we overwrote the buffer... */ daemon->srv_save = NULL; - + if ((len = get_block(daemon->packet, transfer)) == -1) { len = tftp_err_oops(daemon->packet, transfer->file->filename); Index: dnsmasq-2.81/src/util.c =================================================================== --- dnsmasq-2.81.orig/src/util.c +++ dnsmasq-2.81/src/util.c @@ -316,7 +316,7 @@ void *whine_malloc(size_t size) return ret; } -int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2) +int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2) { if (s1->sa.sa_family == s2->sa.sa_family) {