Blame SOURCES/dnsmasq-2.85-CVE-2021-3448.patch

7bb5d0
From d88dc5e696f1b8b95e416890ac831eb0c26250ff Mon Sep 17 00:00:00 2001
7bb5d0
From: Simon Kelley <simon@thekelleys.org.uk>
7bb5d0
Date: Mon, 15 Mar 2021 21:59:51 +0000
7bb5d0
Subject: [PATCH] Use random source ports where possible if source
7bb5d0
 addresses/interfaces in use.
7bb5d0
7bb5d0
CVE-2021-3448 applies.
7bb5d0
7bb5d0
It's possible to specify the source address or interface to be
7bb5d0
used when contacting upstream nameservers: server=8.8.8.8@1.2.3.4
7bb5d0
or server=8.8.8.8@1.2.3.4#66 or server=8.8.8.8@eth0, and all of
7bb5d0
these have, until now, used a single socket, bound to a fixed
7bb5d0
port. This was originally done to allow an error (non-existent
7bb5d0
interface, or non-local address) to be detected at start-up. This
7bb5d0
means that any upstream servers specified in such a way don't use
7bb5d0
random source ports, and are more susceptible to cache-poisoning
7bb5d0
attacks.
7bb5d0
7bb5d0
We now use random ports where possible, even when the
7bb5d0
source is specified, so server=8.8.8.8@1.2.3.4 or
7bb5d0
server=8.8.8.8@eth0 will use random source
7bb5d0
ports. server=8.8.8.8@1.2.3.4#66 or any use of --query-port will
7bb5d0
use the explicitly configured port, and should only be done with
7bb5d0
understanding of the security implications.
7bb5d0
Note that this change changes non-existing interface, or non-local
7bb5d0
source address errors from fatal to run-time. The error will be
7bb5d0
logged and communiction with the server not possible.
7bb5d0
---
7bb5d0
 man/dnsmasq.8 |   4 +-
7bb5d0
 src/dnsmasq.c |  31 +++--
7bb5d0
 src/dnsmasq.h |  28 ++--
7bb5d0
 src/forward.c | 373 +++++++++++++++++++++++++++++++-------------------
7bb5d0
 src/loop.c    |  20 +--
7bb5d0
 src/network.c | 100 ++++----------
7bb5d0
 src/option.c  |   3 +-
7bb5d0
 src/tftp.c    |   6 +-
7bb5d0
 src/util.c    |   2 +-
7bb5d0
 9 files changed, 310 insertions(+), 257 deletions(-)
7bb5d0
7bb5d0
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
7bb5d0
index 45d2273..7f4c62e 100644
7bb5d0
--- a/man/dnsmasq.8
7bb5d0
+++ b/man/dnsmasq.8
7bb5d0
@@ -419,7 +419,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots
7bb5d0
 or domain parts, to upstream nameservers. If the name is not known
7bb5d0
 from /etc/hosts or DHCP then a "not found" answer is returned.
7bb5d0
 .TP
7bb5d0
-.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>][@<source-ip>|<interface>[#<port>]]
7bb5d0
+.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>]][@<interface>][@<source-ip>[#<port>]]
7bb5d0
 Specify IP address of upstream servers directly. Setting this flag does
7bb5d0
 not suppress reading of /etc/resolv.conf, use -R to do that. If one or
7bb5d0
 more 
7bb5d0
@@ -481,7 +481,7 @@ source address specified but the port may be specified directly as
7bb5d0
 part of the source address. Forcing queries to an interface is not
7bb5d0
 implemented on all platforms supported by dnsmasq.
7bb5d0
 .TP
7bb5d0
-.B --rev-server=<ip-address>/<prefix-len>,<ipaddr>[#<port>][@<source-ip>|<interface>[#<port>]]
7bb5d0
+.B --rev-server=<ip-address>/<prefix-len>,<ipaddr>[#<port>][@<source-ip>|@<interface>[#<port>]]
7bb5d0
 This is functionally the same as 
7bb5d0
 .B --server, 
7bb5d0
 but provides some syntactic sugar to make specifying address-to-name queries easier. For example
7bb5d0
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
7bb5d0
index b7f0a29..3a1f65e 100644
7bb5d0
--- a/src/dnsmasq.c
7bb5d0
+++ b/src/dnsmasq.c
7bb5d0
@@ -1538,6 +1538,7 @@ static int set_dns_listeners(time_t now)
7bb5d0
 {
7bb5d0
   struct serverfd *serverfdp;
7bb5d0
   struct listener *listener;
7bb5d0
+  struct randfd_list *rfl;
7bb5d0
   int wait = 0, i;
7bb5d0
   
7bb5d0
 #ifdef HAVE_TFTP
7bb5d0
@@ -1557,11 +1558,14 @@ static int set_dns_listeners(time_t now)
7bb5d0
   for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
7bb5d0
     poll_listen(serverfdp->fd, POLLIN);
7bb5d0
     
7bb5d0
-  if (daemon->port != 0 && !daemon->osport)
7bb5d0
-    for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
-      if (daemon->randomsocks[i].refcount != 0)
7bb5d0
-	poll_listen(daemon->randomsocks[i].fd, POLLIN);
7bb5d0
-	  
7bb5d0
+  for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
+    if (daemon->randomsocks[i].refcount != 0)
7bb5d0
+      poll_listen(daemon->randomsocks[i].fd, POLLIN);
7bb5d0
+
7bb5d0
+  /* Check overflow random sockets too. */
7bb5d0
+  for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
7bb5d0
+    poll_listen(rfl->rfd->fd, POLLIN);
7bb5d0
+  
7bb5d0
   for (listener = daemon->listeners; listener; listener = listener->next)
7bb5d0
     {
7bb5d0
       /* only listen for queries if we have resources */
7bb5d0
@@ -1592,17 +1596,22 @@ static void check_dns_listeners(time_t now)
7bb5d0
 {
7bb5d0
   struct serverfd *serverfdp;
7bb5d0
   struct listener *listener;
7bb5d0
+  struct randfd_list *rfl;
7bb5d0
   int i;
7bb5d0
 
7bb5d0
   for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
7bb5d0
     if (poll_check(serverfdp->fd, POLLIN))
7bb5d0
-      reply_query(serverfdp->fd, serverfdp->source_addr.sa.sa_family, now);
7bb5d0
+      reply_query(serverfdp->fd, now);
7bb5d0
   
7bb5d0
-  if (daemon->port != 0 && !daemon->osport)
7bb5d0
-    for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
-      if (daemon->randomsocks[i].refcount != 0 && 
7bb5d0
-	  poll_check(daemon->randomsocks[i].fd, POLLIN))
7bb5d0
-	reply_query(daemon->randomsocks[i].fd, daemon->randomsocks[i].family, now);
7bb5d0
+  for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
+    if (daemon->randomsocks[i].refcount != 0 && 
7bb5d0
+	poll_check(daemon->randomsocks[i].fd, POLLIN))
7bb5d0
+      reply_query(daemon->randomsocks[i].fd, now);
7bb5d0
+
7bb5d0
+  /* Check overflow random sockets too. */
7bb5d0
+  for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
7bb5d0
+    if (poll_check(rfl->rfd->fd, POLLIN))
7bb5d0
+      reply_query(rfl->rfd->fd, now);
7bb5d0
   
7bb5d0
   for (listener = daemon->listeners; listener; listener = listener->next)
7bb5d0
     {
7bb5d0
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
7bb5d0
index 221f788..4beef35 100644
7bb5d0
--- a/src/dnsmasq.h
7bb5d0
+++ b/src/dnsmasq.h
7bb5d0
@@ -521,13 +521,20 @@ struct serverfd {
7bb5d0
 };
7bb5d0
 
7bb5d0
 struct randfd {
7bb5d0
+  struct server *serv;
7bb5d0
   int fd;
7bb5d0
-  unsigned short refcount, family;
7bb5d0
+  unsigned short refcount; /* refcount == 0xffff means overflow record. */
7bb5d0
 };
7bb5d0
-  
7bb5d0
+
7bb5d0
+struct randfd_list {
7bb5d0
+  struct randfd *rfd;
7bb5d0
+  struct randfd_list *next;
7bb5d0
+};
7bb5d0
+
7bb5d0
 struct server {
7bb5d0
   union mysockaddr addr, source_addr;
7bb5d0
   char interface[IF_NAMESIZE+1];
7bb5d0
+  unsigned int ifindex; /* corresponding to interface, above */
7bb5d0
   struct serverfd *sfd; 
7bb5d0
   char *domain; /* set if this server only handles a domain. */ 
7bb5d0
   int flags, tcpfd, edns_pktsz;
7bb5d0
@@ -640,10 +647,7 @@ struct frec {
7bb5d0
     struct frec_src *next;
7bb5d0
   } frec_src;
7bb5d0
   struct server *sentto; /* NULL means free */
7bb5d0
-  struct randfd *rfd4;
7bb5d0
-#ifdef HAVE_IPV6
7bb5d0
-  struct randfd *rfd6;
7bb5d0
-#endif
7bb5d0
+  struct randfd_list *rfds;
7bb5d0
   unsigned short new_id;
7bb5d0
   int forwardall, flags;
7bb5d0
   time_t time;
7bb5d0
@@ -1062,9 +1066,10 @@ extern struct daemon {
7bb5d0
   int forwardcount;
7bb5d0
   struct server *srv_save; /* Used for resend on DoD */
7bb5d0
   size_t packet_len;       /*      "        "        */
7bb5d0
-  struct randfd *rfd_save; /*      "        "        */
7bb5d0
+  int    fd_save;          /*      "        "        */
7bb5d0
   pid_t tcp_pids[MAX_PROCS];
7bb5d0
   struct randfd randomsocks[RANDOM_SOCKS];
7bb5d0
+  struct randfd_list *rfl_spare, *rfl_poll;
7bb5d0
   int v6pktinfo; 
7bb5d0
   struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
7bb5d0
   int log_id, log_display_id; /* ids of transactions for logging */
7bb5d0
@@ -1227,7 +1232,7 @@ void safe_strncpy(char *dest, const char *src, size_t size);
7bb5d0
 void safe_pipe(int *fd, int read_noblock);
7bb5d0
 void *whine_malloc(size_t size);
7bb5d0
 int sa_len(union mysockaddr *addr);
7bb5d0
-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2);
7bb5d0
+int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
7bb5d0
 int hostname_isequal(const char *a, const char *b);
7bb5d0
 time_t dnsmasq_time(void);
7bb5d0
 int netmask_length(struct in_addr mask);
7bb5d0
@@ -1276,7 +1281,7 @@ char *parse_server(char *arg, union mysockaddr *addr,
7bb5d0
 int option_read_dynfile(char *file, int flags);
7bb5d0
 
7bb5d0
 /* forward.c */
7bb5d0
-void reply_query(int fd, int family, time_t now);
7bb5d0
+void reply_query(int fd, time_t now);
7bb5d0
 void receive_query(struct listener *listen, time_t now);
7bb5d0
 unsigned char *tcp_request(int confd, time_t now,
7bb5d0
 			   union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
7bb5d0
@@ -1286,13 +1291,12 @@ int send_from(int fd, int nowild, char *packet, size_t len,
7bb5d0
 	       union mysockaddr *to, struct all_addr *source,
7bb5d0
 	       unsigned int iface);
7bb5d0
 void resend_query(void);
7bb5d0
-struct randfd *allocate_rfd(int family);
7bb5d0
-void free_rfd(struct randfd *rfd);
7bb5d0
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv);
7bb5d0
+void free_rfds(struct randfd_list **fdlp);
7bb5d0
 
7bb5d0
 /* network.c */
7bb5d0
 int indextoname(int fd, int index, char *name);
7bb5d0
 int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
7bb5d0
-int random_sock(int family);
7bb5d0
 void pre_allocate_sfds(void);
7bb5d0
 int reload_servers(char *fname);
7bb5d0
 void mark_servers(int flag);
7bb5d0
diff --git a/src/forward.c b/src/forward.c
7bb5d0
index 82dd850..11e0310 100644
7bb5d0
--- a/src/forward.c
7bb5d0
+++ b/src/forward.c
7bb5d0
@@ -16,7 +16,7 @@
7bb5d0
 
7bb5d0
 #include "dnsmasq.h"
7bb5d0
 
7bb5d0
-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash);
7bb5d0
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash);
7bb5d0
 static struct frec *lookup_frec_by_sender(unsigned short id,
7bb5d0
 					  union mysockaddr *addr,
7bb5d0
 					  void *hash);
7bb5d0
@@ -291,29 +291,19 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
7bb5d0
 	  if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
7bb5d0
 	    PUTSHORT(SAFE_PKTSZ, pheader);
7bb5d0
 	  
7bb5d0
-	  if (forward->sentto->addr.sa.sa_family == AF_INET) 
7bb5d0
-	    log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
7bb5d0
-#ifdef HAVE_IPV6
7bb5d0
-	  else
7bb5d0
-	    log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
7bb5d0
-#endif
7bb5d0
-  
7bb5d0
-	  if (forward->sentto->sfd)
7bb5d0
-	    fd = forward->sentto->sfd->fd;
7bb5d0
-	  else
7bb5d0
+	  if ((fd = allocate_rfd(&forward->rfds, forward->sentto)) != -1)
7bb5d0
 	    {
7bb5d0
+	      if (forward->sentto->addr.sa.sa_family == AF_INET) 
7bb5d0
+		log_query(F_NOEXTRA | F_DNSSEC | F_IPV4, "retry", (struct all_addr *)&forward->sentto->addr.in.sin_addr, "dnssec");
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
-	      if (forward->sentto->addr.sa.sa_family == AF_INET6)
7bb5d0
-		fd = forward->rfd6->fd;
7bb5d0
 	      else
7bb5d0
+		log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, "retry", (struct all_addr *)&forward->sentto->addr.in6.sin6_addr, "dnssec");
7bb5d0
 #endif
7bb5d0
-		fd = forward->rfd4->fd;
7bb5d0
+	      while (retry_send(sendto(fd, (char *)header, plen, 0,
7bb5d0
+				       &forward->sentto->addr.sa,
7bb5d0
+				       sa_len(&forward->sentto->addr))));
7bb5d0
 	    }
7bb5d0
-	  
7bb5d0
-	  while (retry_send( sendto(fd, (char *)header, plen, 0,
7bb5d0
-				    &forward->sentto->addr.sa,
7bb5d0
-				    sa_len(&forward->sentto->addr))));
7bb5d0
-	  
7bb5d0
+
7bb5d0
 	  return 1;
7bb5d0
 	}
7bb5d0
 #endif
7bb5d0
@@ -490,50 +480,26 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
7bb5d0
       
7bb5d0
       while (1)
7bb5d0
 	{ 
7bb5d0
+	  int fd;
7bb5d0
+
7bb5d0
 	  /* only send to servers dealing with our domain.
7bb5d0
 	     domain may be NULL, in which case server->domain 
7bb5d0
 	     must be NULL also. */
7bb5d0
 	  
7bb5d0
 	  if (type == (start->flags & SERV_TYPE) &&
7bb5d0
 	      (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) &&
7bb5d0
-	      !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)))
7bb5d0
+	      !(start->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
7bb5d0
+	      ((fd = allocate_rfd(&forward->rfds, start)) != -1))
7bb5d0
 	    {
7bb5d0
-	      int fd;
7bb5d0
-
7bb5d0
-	      /* find server socket to use, may need to get random one. */
7bb5d0
-	      if (start->sfd)
7bb5d0
-		fd = start->sfd->fd;
7bb5d0
-	      else 
7bb5d0
-		{
7bb5d0
-#ifdef HAVE_IPV6
7bb5d0
-		  if (start->addr.sa.sa_family == AF_INET6)
7bb5d0
-		    {
7bb5d0
-		      if (!forward->rfd6 &&
7bb5d0
-			  !(forward->rfd6 = allocate_rfd(AF_INET6)))
7bb5d0
-			break;
7bb5d0
-		      daemon->rfd_save = forward->rfd6;
7bb5d0
-		      fd = forward->rfd6->fd;
7bb5d0
-		    }
7bb5d0
-		  else
7bb5d0
-#endif
7bb5d0
-		    {
7bb5d0
-		      if (!forward->rfd4 &&
7bb5d0
-			  !(forward->rfd4 = allocate_rfd(AF_INET)))
7bb5d0
-			break;
7bb5d0
-		      daemon->rfd_save = forward->rfd4;
7bb5d0
-		      fd = forward->rfd4->fd;
7bb5d0
-		    }
7bb5d0
-
7bb5d0
 #ifdef HAVE_CONNTRACK
7bb5d0
-		  /* Copy connection mark of incoming query to outgoing connection. */
7bb5d0
-		  if (option_bool(OPT_CONNTRACK))
7bb5d0
-		    {
7bb5d0
-		      unsigned int mark;
7bb5d0
-		      if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
7bb5d0
-			setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
7bb5d0
-		    }
7bb5d0
-#endif
7bb5d0
+	      /* Copy connection mark of incoming query to outgoing connection. */
7bb5d0
+	      if (option_bool(OPT_CONNTRACK))
7bb5d0
+		{
7bb5d0
+		  unsigned int mark;
7bb5d0
+		  if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
7bb5d0
+		    setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
7bb5d0
 		}
7bb5d0
+#endif
7bb5d0
 	      
7bb5d0
 #ifdef HAVE_DNSSEC
7bb5d0
 	      if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER))
7bb5d0
@@ -561,6 +527,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
7bb5d0
 		  /* Keep info in case we want to re-send this packet */
7bb5d0
 		  daemon->srv_save = start;
7bb5d0
 		  daemon->packet_len = plen;
7bb5d0
+		  daemon->fd_save = fd;
7bb5d0
 		  
7bb5d0
 		  if (!gotname)
7bb5d0
 		    strcpy(daemon->namebuff, "query");
7bb5d0
@@ -579,7 +546,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
7bb5d0
 		    break;
7bb5d0
 		  forward->forwardall++;
7bb5d0
 		}
7bb5d0
-	    } 
7bb5d0
+	    }
7bb5d0
 	  
7bb5d0
 	  if (!(start = start->next))
7bb5d0
  	    start = daemon->servers;
7bb5d0
@@ -779,7 +746,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
7bb5d0
 }
7bb5d0
 
7bb5d0
 /* sets new last_server */
7bb5d0
-void reply_query(int fd, int family, time_t now)
7bb5d0
+void reply_query(int fd, time_t now)
7bb5d0
 {
7bb5d0
   /* packet from peer server, extract data for cache, and send to
7bb5d0
      original requester */
7bb5d0
@@ -794,9 +761,8 @@ void reply_query(int fd, int family, time_t now)
7bb5d0
 
7bb5d0
   /* packet buffer overwritten */
7bb5d0
   daemon->srv_save = NULL;
7bb5d0
-  
7bb5d0
+
7bb5d0
   /* Determine the address of the server replying  so that we can mark that as good */
7bb5d0
-  serveraddr.sa.sa_family = family;
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
   if (serveraddr.sa.sa_family == AF_INET6)
7bb5d0
     serveraddr.in6.sin6_flowinfo = 0;
7bb5d0
@@ -822,7 +788,7 @@ void reply_query(int fd, int family, time_t now)
7bb5d0
 
7bb5d0
   hash = hash_questions(header, n, daemon->namebuff);
7bb5d0
   
7bb5d0
-  if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
7bb5d0
+  if (!(forward = lookup_frec(ntohs(header->id), fd, hash)))
7bb5d0
     return;
7bb5d0
   
7bb5d0
   /* log_query gets called indirectly all over the place, so 
7bb5d0
@@ -1027,9 +993,8 @@ void reply_query(int fd, int family, time_t now)
7bb5d0
 			}
7bb5d0
 		      
7bb5d0
 		      new->sentto = server;
7bb5d0
-		      new->rfd4 = NULL;
7bb5d0
+		      new->rfds = NULL;
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
-		      new->rfd6 = NULL;
7bb5d0
 #endif
7bb5d0
 		      new->frec_src.next = NULL;
7bb5d0
 		      new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
7bb5d0
@@ -1059,26 +1024,7 @@ void reply_query(int fd, int family, time_t now)
7bb5d0
 		      /* Don't resend this. */
7bb5d0
 		      daemon->srv_save = NULL;
7bb5d0
 		      
7bb5d0
-		      if (server->sfd)
7bb5d0
-			fd = server->sfd->fd;
7bb5d0
-		      else
7bb5d0
-			{
7bb5d0
-			  fd = -1;
7bb5d0
-#ifdef HAVE_IPV6
7bb5d0
-			  if (server->addr.sa.sa_family == AF_INET6)
7bb5d0
-			    {
7bb5d0
-			      if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
7bb5d0
-				fd = new->rfd6->fd;
7bb5d0
-			    }
7bb5d0
-			  else
7bb5d0
-#endif
7bb5d0
-			    {
7bb5d0
-			      if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
7bb5d0
-				fd = new->rfd4->fd;
7bb5d0
-			    }
7bb5d0
-			}
7bb5d0
-		      
7bb5d0
-		      if (fd != -1)
7bb5d0
+		      if ((fd = allocate_rfd(&new->rfds, server)) != -1)
7bb5d0
 			{
7bb5d0
 #ifdef HAVE_CONNTRACK
7bb5d0
 			  /* Copy connection mark of incoming query to outgoing connection. */
7bb5d0
@@ -1234,7 +1180,7 @@ void receive_query(struct listener *listen, time_t now)
7bb5d0
 
7bb5d0
   /* packet buffer overwritten */
7bb5d0
   daemon->srv_save = NULL;
7bb5d0
-  
7bb5d0
+
7bb5d0
   dst_addr_4.s_addr = dst_addr.addr.addr4.s_addr = 0;
7bb5d0
   netmask.s_addr = 0;
7bb5d0
   
7bb5d0
@@ -2066,10 +2012,9 @@ static struct frec *allocate_frec(time_t now)
7bb5d0
       f->next = daemon->frec_list;
7bb5d0
       f->time = now;
7bb5d0
       f->sentto = NULL;
7bb5d0
-      f->rfd4 = NULL;
7bb5d0
+      f->rfds = NULL;
7bb5d0
       f->flags = 0;
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
-      f->rfd6 = NULL;
7bb5d0
 #endif
7bb5d0
 #ifdef HAVE_DNSSEC
7bb5d0
       f->dependent = NULL;
7bb5d0
@@ -2082,46 +2027,192 @@ static struct frec *allocate_frec(time_t now)
7bb5d0
   return f;
7bb5d0
 }
7bb5d0
 
7bb5d0
-struct randfd *allocate_rfd(int family)
7bb5d0
+/* return a UDP socket bound to a random port, have to cope with straying into
7bb5d0
+   occupied port nos and reserved ones. */
7bb5d0
+static int random_sock(struct server *s)
7bb5d0
 {
7bb5d0
-  static int finger = 0;
7bb5d0
-  int i;
7bb5d0
+  int fd;
7bb5d0
+
7bb5d0
+  if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
7bb5d0
+    {
7bb5d0
+      if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
7bb5d0
+	return fd;
7bb5d0
 
7bb5d0
+      if (s->interface[0] == 0)
7bb5d0
+	(void)prettyprint_addr(&s->source_addr, daemon->namebuff);
7bb5d0
+      else
7bb5d0
+	strcpy(daemon->namebuff, s->interface);
7bb5d0
+
7bb5d0
+      my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"),
7bb5d0
+		daemon->namebuff, strerror(errno));
7bb5d0
+      close(fd);
7bb5d0
+    }
7bb5d0
+  
7bb5d0
+  return -1;
7bb5d0
+}
7bb5d0
+
7bb5d0
+/* compare source addresses and interface, serv2 can be null. */
7bb5d0
+static int server_isequal(const struct server *serv1,
7bb5d0
+			 const struct server *serv2)
7bb5d0
+{
7bb5d0
+  return (serv2 &&
7bb5d0
+    serv2->ifindex == serv1->ifindex &&
7bb5d0
+    sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) &&
7bb5d0
+    strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0);
7bb5d0
+}
7bb5d0
+
7bb5d0
+/* fdlp points to chain of randomfds already in use by transaction.
7bb5d0
+   If there's already a suitable one, return it, else allocate a 
7bb5d0
+   new one and add it to the list. 
7bb5d0
+
7bb5d0
+   Not leaking any resources in the face of allocation failures
7bb5d0
+   is rather convoluted here.
7bb5d0
+   
7bb5d0
+   Note that rfd->serv may be NULL, when a server goes away.
7bb5d0
+*/
7bb5d0
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
7bb5d0
+{
7bb5d0
+  static int finger = 0;
7bb5d0
+  int i, j = 0;
7bb5d0
+  struct randfd_list *rfl;
7bb5d0
+  struct randfd *rfd = NULL;
7bb5d0
+  int fd = 0;
7bb5d0
+  
7bb5d0
+  /* If server has a pre-allocated fd, use that. */
7bb5d0
+  if (serv->sfd)
7bb5d0
+    return serv->sfd->fd;
7bb5d0
+  
7bb5d0
+  /* existing suitable random port socket linked to this transaction? */
7bb5d0
+  for (rfl = *fdlp; rfl; rfl = rfl->next)
7bb5d0
+    if (server_isequal(serv, rfl->rfd->serv))
7bb5d0
+      return rfl->rfd->fd;
7bb5d0
+
7bb5d0
+  /* No. need new link. */
7bb5d0
+  if ((rfl = daemon->rfl_spare))
7bb5d0
+    daemon->rfl_spare = rfl->next;
7bb5d0
+  else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
7bb5d0
+    return -1;
7bb5d0
+   
7bb5d0
   /* limit the number of sockets we have open to avoid starvation of 
7bb5d0
      (eg) TFTP. Once we have a reasonable number, randomness should be OK */
7bb5d0
-
7bb5d0
   for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
     if (daemon->randomsocks[i].refcount == 0)
7bb5d0
       {
7bb5d0
-	if ((daemon->randomsocks[i].fd = random_sock(family)) == -1)
7bb5d0
-	  break;
7bb5d0
-      
7bb5d0
-	daemon->randomsocks[i].refcount = 1;
7bb5d0
-	daemon->randomsocks[i].family = family;
7bb5d0
-	return &daemon->randomsocks[i];
7bb5d0
+	if ((fd = random_sock(serv)) != -1)
7bb5d0
+    	  {
7bb5d0
+	    rfd = &daemon->randomsocks[i];
7bb5d0
+	    rfd->serv = serv;
7bb5d0
+	    rfd->fd = fd;
7bb5d0
+	    rfd->refcount = 1;
7bb5d0
+	  }
7bb5d0
+	break;
7bb5d0
       }
7bb5d0
-
7bb5d0
+  
7bb5d0
   /* No free ones or cannot get new socket, grab an existing one */
7bb5d0
-  for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
+  if (!rfd)
7bb5d0
+    for (j = 0; j < RANDOM_SOCKS; j++)
7bb5d0
+      {
7bb5d0
+	i = (j + finger) % RANDOM_SOCKS;
7bb5d0
+	if (daemon->randomsocks[i].refcount != 0 &&
7bb5d0
+	    server_isequal(serv, daemon->randomsocks[i].serv) &&
7bb5d0
+	    daemon->randomsocks[i].refcount != 0xfffe)
7bb5d0
+	  {
7bb5d0
+	    finger = i + 1;
7bb5d0
+	    rfd = &daemon->randomsocks[i];
7bb5d0
+	    rfd->refcount++;
7bb5d0
+	    break;
7bb5d0
+	  }
7bb5d0
+      }
7bb5d0
+
7bb5d0
+  if (j == RANDOM_SOCKS)
7bb5d0
     {
7bb5d0
-      int j = (i+finger) % RANDOM_SOCKS;
7bb5d0
-      if (daemon->randomsocks[j].refcount != 0 &&
7bb5d0
-	  daemon->randomsocks[j].family == family && 
7bb5d0
-	  daemon->randomsocks[j].refcount != 0xffff)
7bb5d0
+      struct randfd_list *rfl_poll;
7bb5d0
+
7bb5d0
+      /* there are no free slots, and non with the same parameters we can piggy-back on. 
7bb5d0
+	 We're going to have to allocate a new temporary record, distinguished by
7bb5d0
+	 refcount == 0xffff. This will exist in the frec randfd list, never be shared,
7bb5d0
+	 and be freed when no longer in use. It will also be held on 
7bb5d0
+	 the daemon->rfl_poll list so the poll system can find it. */
7bb5d0
+
7bb5d0
+      if ((rfl_poll = daemon->rfl_spare))
7bb5d0
+	daemon->rfl_spare = rfl_poll->next;
7bb5d0
+      else
7bb5d0
+	rfl_poll = whine_malloc(sizeof(struct randfd_list));
7bb5d0
+      
7bb5d0
+      if (!rfl_poll ||
7bb5d0
+	  !(rfd = whine_malloc(sizeof(struct randfd))) ||
7bb5d0
+	  (fd = random_sock(serv)) == -1)
7bb5d0
 	{
7bb5d0
-	  finger = j;
7bb5d0
-	  daemon->randomsocks[j].refcount++;
7bb5d0
-	  return &daemon->randomsocks[j];
7bb5d0
+	  
7bb5d0
+	  /* Don't leak anything we may already have */
7bb5d0
+	  rfl->next = daemon->rfl_spare;
7bb5d0
+	  daemon->rfl_spare = rfl;
7bb5d0
+
7bb5d0
+	  if (rfl_poll)
7bb5d0
+	    {
7bb5d0
+	      rfl_poll->next = daemon->rfl_spare;
7bb5d0
+	      daemon->rfl_spare = rfl_poll;
7bb5d0
+	    }
7bb5d0
+	  
7bb5d0
+	  if (rfd)
7bb5d0
+	    free(rfd);
7bb5d0
+	  
7bb5d0
+	  return -1; /* doom */
7bb5d0
 	}
7bb5d0
-    }
7bb5d0
 
7bb5d0
-  return NULL; /* doom */
7bb5d0
+      /* Note rfd->serv not set here, since it's not reused */
7bb5d0
+      rfd->fd = fd;
7bb5d0
+      rfd->refcount = 0xffff; /* marker for temp record */
7bb5d0
+
7bb5d0
+      rfl_poll->rfd = rfd;
7bb5d0
+      rfl_poll->next = daemon->rfl_poll;
7bb5d0
+      daemon->rfl_poll = rfl_poll;
7bb5d0
+    }
7bb5d0
+  
7bb5d0
+  rfl->rfd = rfd;
7bb5d0
+  rfl->next = *fdlp;
7bb5d0
+  *fdlp = rfl;
7bb5d0
+  
7bb5d0
+  return rfl->rfd->fd;
7bb5d0
 }
7bb5d0
 
7bb5d0
-void free_rfd(struct randfd *rfd)
7bb5d0
+void free_rfds(struct randfd_list **fdlp)
7bb5d0
 {
7bb5d0
-  if (rfd && --(rfd->refcount) == 0)
7bb5d0
-    close(rfd->fd);
7bb5d0
+  struct randfd_list *tmp, *rfl, *poll, *next, **up;
7bb5d0
+  
7bb5d0
+  for (rfl = *fdlp; rfl; rfl = tmp)
7bb5d0
+    {
7bb5d0
+      if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0)
7bb5d0
+	close(rfl->rfd->fd);
7bb5d0
+
7bb5d0
+      /* temporary overflow record */
7bb5d0
+      if (rfl->rfd->refcount == 0xffff)
7bb5d0
+	{
7bb5d0
+	  free(rfl->rfd);
7bb5d0
+	  
7bb5d0
+	  /* go through the link of all these by steam to delete.
7bb5d0
+	     This list is expected to be almost always empty. */
7bb5d0
+	  for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next)
7bb5d0
+	    {
7bb5d0
+	      next = poll->next;
7bb5d0
+	      
7bb5d0
+	      if (poll->rfd == rfl->rfd)
7bb5d0
+		{
7bb5d0
+		  *up = poll->next;
7bb5d0
+		  poll->next = daemon->rfl_spare;
7bb5d0
+		  daemon->rfl_spare = poll;
7bb5d0
+		}
7bb5d0
+	      else
7bb5d0
+		up = &poll->next;
7bb5d0
+	    }
7bb5d0
+	}
7bb5d0
+
7bb5d0
+      tmp = rfl->next;
7bb5d0
+      rfl->next = daemon->rfl_spare;
7bb5d0
+      daemon->rfl_spare = rfl;
7bb5d0
+    }
7bb5d0
+
7bb5d0
+  *fdlp = NULL;
7bb5d0
 }
7bb5d0
 
7bb5d0
 static void free_frec(struct frec *f)
7bb5d0
@@ -2137,14 +2228,11 @@ static void free_frec(struct frec *f)
7bb5d0
     }
7bb5d0
     
7bb5d0
   f->frec_src.next = NULL;    
7bb5d0
-  free_rfd(f->rfd4);
7bb5d0
-  f->rfd4 = NULL;
7bb5d0
+  free_rfds(&f->rfds);
7bb5d0
   f->sentto = NULL;
7bb5d0
   f->flags = 0;
7bb5d0
   
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
-  free_rfd(f->rfd6);
7bb5d0
-  f->rfd6 = NULL;
7bb5d0
 #endif
7bb5d0
 
7bb5d0
 #ifdef HAVE_DNSSEC
7bb5d0
@@ -2252,26 +2340,39 @@ struct frec *get_new_frec(time_t now, int *wait, int force)
7bb5d0
 }
7bb5d0
 
7bb5d0
 /* crc is all-ones if not known. */
7bb5d0
-static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
7bb5d0
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash)
7bb5d0
 {
7bb5d0
   struct frec *f;
7bb5d0
-
7bb5d0
+  struct server *s;
7bb5d0
+  int type;
7bb5d0
+  struct randfd_list *fdl;
7bb5d0
+  
7bb5d0
   for(f = daemon->frec_list; f; f = f->next)
7bb5d0
     if (f->sentto && f->new_id == id && 
7bb5d0
 	(memcmp(hash, f->hash, HASH_SIZE) == 0))
7bb5d0
       {
7bb5d0
 	/* sent from random port */
7bb5d0
-	if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
7bb5d0
-	  return f;
7bb5d0
-
7bb5d0
-	if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd)
7bb5d0
-	  return f;
7bb5d0
-
7bb5d0
-	/* sent to upstream from bound socket. */
7bb5d0
-	if (f->sentto->sfd && f->sentto->sfd->fd == fd)
7bb5d0
+	for (fdl = f->rfds; fdl; fdl = fdl->next)
7bb5d0
+	  if (fdl->rfd->fd == fd)
7bb5d0
 	  return f;
7bb5d0
+	
7bb5d0
+	/* Sent to upstream from socket associated with a server. 
7bb5d0
+	   Note we have to iterate over all the possible servers, since they may
7bb5d0
+	   have different bound sockets. */
7bb5d0
+	type = f->sentto->flags & SERV_TYPE;
7bb5d0
+	s = f->sentto;
7bb5d0
+	do {
7bb5d0
+	  if ((type == (s->flags & SERV_TYPE)) &&
7bb5d0
+	      (type != SERV_HAS_DOMAIN ||
7bb5d0
+	       (s->domain && hostname_isequal(f->sentto->domain, s->domain))) &&
7bb5d0
+	      !(s->flags & (SERV_LITERAL_ADDRESS | SERV_LOOP)) &&
7bb5d0
+	      s->sfd && s->sfd->fd == fd)
7bb5d0
+	    return f;
7bb5d0
+	  
7bb5d0
+	  s = s->next ? s->next : daemon->servers;
7bb5d0
+	} while (s != f->sentto);
7bb5d0
       }
7bb5d0
-      
7bb5d0
+
7bb5d0
   return NULL;
7bb5d0
 }
7bb5d0
 
7bb5d0
@@ -2317,31 +2418,27 @@ static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
7bb5d0
 void resend_query()
7bb5d0
 {
7bb5d0
   if (daemon->srv_save)
7bb5d0
-    {
7bb5d0
-      int fd;
7bb5d0
-      
7bb5d0
-      if (daemon->srv_save->sfd)
7bb5d0
-	fd = daemon->srv_save->sfd->fd;
7bb5d0
-      else if (daemon->rfd_save && daemon->rfd_save->refcount != 0)
7bb5d0
-	fd = daemon->rfd_save->fd;
7bb5d0
-      else
7bb5d0
-	return;
7bb5d0
-      
7bb5d0
-      while(retry_send(sendto(fd, daemon->packet, daemon->packet_len, 0,
7bb5d0
-			      &daemon->srv_save->addr.sa, 
7bb5d0
-			      sa_len(&daemon->srv_save->addr)))); 
7bb5d0
-    }
7bb5d0
+    while(retry_send(sendto(daemon->fd_save, daemon->packet, daemon->packet_len, 0,
7bb5d0
+			    &daemon->srv_save->addr.sa, 
7bb5d0
+			    sa_len(&daemon->srv_save->addr)))); 
7bb5d0
 }
7bb5d0
 
7bb5d0
 /* A server record is going away, remove references to it */
7bb5d0
 void server_gone(struct server *server)
7bb5d0
 {
7bb5d0
   struct frec *f;
7bb5d0
+  int i;
7bb5d0
   
7bb5d0
   for (f = daemon->frec_list; f; f = f->next)
7bb5d0
     if (f->sentto && f->sentto == server)
7bb5d0
       free_frec(f);
7bb5d0
-  
7bb5d0
+
7bb5d0
+  /* If any random socket refers to this server, NULL the reference.
7bb5d0
+     No more references to the socket will be created in the future. */
7bb5d0
+  for (i = 0; i < RANDOM_SOCKS; i++)
7bb5d0
+    if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server)
7bb5d0
+      daemon->randomsocks[i].serv = NULL;
7bb5d0
+
7bb5d0
   if (daemon->last_server == server)
7bb5d0
     daemon->last_server = NULL;
7bb5d0
 
7bb5d0
diff --git a/src/loop.c b/src/loop.c
7bb5d0
index 0b47a2f..98d0b9e 100644
7bb5d0
--- a/src/loop.c
7bb5d0
+++ b/src/loop.c
7bb5d0
@@ -22,6 +22,7 @@ static ssize_t loop_make_probe(u32 uid);
7bb5d0
 void loop_send_probes()
7bb5d0
 {
7bb5d0
    struct server *serv;
7bb5d0
+   struct randfd_list *rfds = NULL;
7bb5d0
    
7bb5d0
    if (!option_bool(OPT_LOOP_DETECT))
7bb5d0
      return;
7bb5d0
@@ -34,29 +35,22 @@ void loop_send_probes()
7bb5d0
        {
7bb5d0
 	 ssize_t len = loop_make_probe(serv->uid);
7bb5d0
 	 int fd;
7bb5d0
-	 struct randfd *rfd = NULL;
7bb5d0
 	 
7bb5d0
-	 if (serv->sfd)
7bb5d0
-	   fd = serv->sfd->fd;
7bb5d0
-	 else 
7bb5d0
-	   {
7bb5d0
-	     if (!(rfd = allocate_rfd(serv->addr.sa.sa_family)))
7bb5d0
-	       continue;
7bb5d0
-	     fd = rfd->fd;
7bb5d0
-	   }
7bb5d0
-
7bb5d0
+	 if ((fd = allocate_rfd(&rfds, serv)) == -1)
7bb5d0
+	   continue;
7bb5d0
+	 
7bb5d0
 	 while (retry_send(sendto(fd, daemon->packet, len, 0, 
7bb5d0
 				  &serv->addr.sa, sa_len(&serv->addr))));
7bb5d0
-	 
7bb5d0
-	 free_rfd(rfd);
7bb5d0
        }
7bb5d0
+
7bb5d0
+   free_rfds(&rfds);
7bb5d0
 }
7bb5d0
   
7bb5d0
 static ssize_t loop_make_probe(u32 uid)
7bb5d0
 {
7bb5d0
   struct dns_header *header = (struct dns_header *)daemon->packet;
7bb5d0
   unsigned char *p = (unsigned char *)(header+1);
7bb5d0
-
7bb5d0
+  
7bb5d0
   /* packet buffer overwritten */
7bb5d0
   daemon->srv_save = NULL;
7bb5d0
   
7bb5d0
diff --git a/src/network.c b/src/network.c
7bb5d0
index 47caf38..4eda1fd 100644
7bb5d0
--- a/src/network.c
7bb5d0
+++ b/src/network.c
7bb5d0
@@ -639,7 +639,8 @@ int enumerate_interfaces(int reset)
7bb5d0
 #ifdef HAVE_AUTH
7bb5d0
   struct auth_zone *zone;
7bb5d0
 #endif
7bb5d0
-
7bb5d0
+  struct server *serv;
7bb5d0
+  
7bb5d0
   /* Do this max once per select cycle  - also inhibits netlink socket use
7bb5d0
    in TCP child processes. */
7bb5d0
 
7bb5d0
@@ -657,6 +658,13 @@ int enumerate_interfaces(int reset)
7bb5d0
   if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
7bb5d0
     return 0;
7bb5d0
 
7bb5d0
+  /* iface indexes can change when interfaces are created/destroyed.
7bb5d0
+     We use them in the main forwarding control path, when the path
7bb5d0
+     to a server is specified by an interface, so cache them.
7bb5d0
+     Update the cache here. */
7bb5d0
+  for (serv = daemon->servers; serv; serv = serv->next)
7bb5d0
+    serv->ifindex = if_nametoindex(serv->interface);
7bb5d0
+
7bb5d0
 again:
7bb5d0
   /* Mark interfaces for garbage collection */
7bb5d0
   for (iface = daemon->interfaces; iface; iface = iface->next) 
7bb5d0
@@ -754,7 +762,7 @@ again:
7bb5d0
 
7bb5d0
   errno = errsave;
7bb5d0
   spare = param.spare;
7bb5d0
-    
7bb5d0
+  
7bb5d0
   return ret;
7bb5d0
 }
7bb5d0
 
7bb5d0
@@ -893,10 +901,10 @@ int tcp_interface(int fd, int af)
7bb5d0
   /* use mshdr so that the CMSDG_* macros are available */
7bb5d0
   msg.msg_control = daemon->packet;
7bb5d0
   msg.msg_controllen = len = daemon->packet_buff_sz;
7bb5d0
-  
7bb5d0
+
7bb5d0
   /* we overwrote the buffer... */
7bb5d0
-  daemon->srv_save = NULL;
7bb5d0
-  
7bb5d0
+  daemon->srv_save = NULL; 
7bb5d0
+
7bb5d0
   if (af == AF_INET)
7bb5d0
     {
7bb5d0
       if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 &&
7bb5d0
@@ -1228,61 +1236,6 @@ void join_multicast(int dienow)
7bb5d0
 }
7bb5d0
 #endif
7bb5d0
 
7bb5d0
-/* return a UDP socket bound to a random port, have to cope with straying into
7bb5d0
-   occupied port nos and reserved ones. */
7bb5d0
-int random_sock(int family)
7bb5d0
-{
7bb5d0
-  int fd;
7bb5d0
-
7bb5d0
-  if ((fd = socket(family, SOCK_DGRAM, 0)) != -1)
7bb5d0
-    {
7bb5d0
-      union mysockaddr addr;
7bb5d0
-      unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1;
7bb5d0
-      int tries = ports_avail < 30 ? 3 * ports_avail : 100;
7bb5d0
-
7bb5d0
-      memset(&addr, 0, sizeof(addr));
7bb5d0
-      addr.sa.sa_family = family;
7bb5d0
-
7bb5d0
-      /* don't loop forever if all ports in use. */
7bb5d0
-
7bb5d0
-      if (fix_fd(fd))
7bb5d0
-	while(tries--)
7bb5d0
-	  {
7bb5d0
-	    unsigned short port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
7bb5d0
-	    
7bb5d0
-	    if (family == AF_INET) 
7bb5d0
-	      {
7bb5d0
-		addr.in.sin_addr.s_addr = INADDR_ANY;
7bb5d0
-		addr.in.sin_port = port;
7bb5d0
-#ifdef HAVE_SOCKADDR_SA_LEN
7bb5d0
-		addr.in.sin_len = sizeof(struct sockaddr_in);
7bb5d0
-#endif
7bb5d0
-	      }
7bb5d0
-#ifdef HAVE_IPV6
7bb5d0
-	    else
7bb5d0
-	      {
7bb5d0
-		addr.in6.sin6_addr = in6addr_any; 
7bb5d0
-		addr.in6.sin6_port = port;
7bb5d0
-#ifdef HAVE_SOCKADDR_SA_LEN
7bb5d0
-		addr.in6.sin6_len = sizeof(struct sockaddr_in6);
7bb5d0
-#endif
7bb5d0
-	      }
7bb5d0
-#endif
7bb5d0
-	    
7bb5d0
-	    if (bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == 0)
7bb5d0
-	      return fd;
7bb5d0
-	    
7bb5d0
-	    if (errno != EADDRINUSE && errno != EACCES)
7bb5d0
-	      break;
7bb5d0
-	  }
7bb5d0
-
7bb5d0
-      close(fd);
7bb5d0
-    }
7bb5d0
-
7bb5d0
-  return -1; 
7bb5d0
-}
7bb5d0
-  
7bb5d0
-
7bb5d0
 int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
7bb5d0
 {
7bb5d0
   union mysockaddr addr_copy = *addr;
7bb5d0
@@ -1328,39 +1281,34 @@ int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifind
7bb5d0
   return 1;
7bb5d0
 }
7bb5d0
 
7bb5d0
-static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname)
7bb5d0
+static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex)
7bb5d0
 {
7bb5d0
   struct serverfd *sfd;
7bb5d0
-  unsigned int ifindex = 0;
7bb5d0
   int errsave;
7bb5d0
 
7bb5d0
   /* when using random ports, servers which would otherwise use
7bb5d0
-     the INADDR_ANY/port0 socket have sfd set to NULL */
7bb5d0
-  if (!daemon->osport && intname[0] == 0)
7bb5d0
+     the INADDR_ANY/port0 socket have sfd set to NULL, this is 
7bb5d0
+     anything without an explictly set source port. */
7bb5d0
+  if (!daemon->osport)
7bb5d0
     {
7bb5d0
       errno = 0;
7bb5d0
       
7bb5d0
       if (addr->sa.sa_family == AF_INET &&
7bb5d0
-	  addr->in.sin_addr.s_addr == INADDR_ANY &&
7bb5d0
 	  addr->in.sin_port == htons(0)) 
7bb5d0
 	return NULL;
7bb5d0
 
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
       if (addr->sa.sa_family == AF_INET6 &&
7bb5d0
-	  memcmp(&addr->in6.sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 &&
7bb5d0
 	  addr->in6.sin6_port == htons(0)) 
7bb5d0
 	return NULL;
7bb5d0
 #endif
7bb5d0
     }
7bb5d0
 
7bb5d0
-  if (intname && strlen(intname) != 0)
7bb5d0
-    ifindex = if_nametoindex(intname); /* index == 0 when not binding to an interface */
7bb5d0
-      
7bb5d0
   /* may have a suitable one already */
7bb5d0
   for (sfd = daemon->sfds; sfd; sfd = sfd->next )
7bb5d0
-    if (sockaddr_isequal(&sfd->source_addr, addr) &&
7bb5d0
-	strcmp(intname, sfd->interface) == 0 &&
7bb5d0
-	ifindex == sfd->ifindex) 
7bb5d0
+    if (ifindex == sfd->ifindex &&
7bb5d0
+	sockaddr_isequal(&sfd->source_addr, addr) &&
7bb5d0
+	strcmp(intname, sfd->interface) == 0)
7bb5d0
       return sfd;
7bb5d0
   
7bb5d0
   /* need to make a new one. */
7bb5d0
@@ -1408,7 +1356,7 @@ void pre_allocate_sfds(void)
7bb5d0
 #ifdef HAVE_SOCKADDR_SA_LEN
7bb5d0
       addr.in.sin_len = sizeof(struct sockaddr_in);
7bb5d0
 #endif
7bb5d0
-      allocate_sfd(&addr, "");
7bb5d0
+      allocate_sfd(&addr, "", 0);
7bb5d0
 #ifdef HAVE_IPV6
7bb5d0
       memset(&addr, 0, sizeof(addr));
7bb5d0
       addr.in6.sin6_family = AF_INET6;
7bb5d0
@@ -1417,13 +1365,13 @@ void pre_allocate_sfds(void)
7bb5d0
 #ifdef HAVE_SOCKADDR_SA_LEN
7bb5d0
       addr.in6.sin6_len = sizeof(struct sockaddr_in6);
7bb5d0
 #endif
7bb5d0
-      allocate_sfd(&addr, "");
7bb5d0
+      allocate_sfd(&addr, "", 0);
7bb5d0
 #endif
7bb5d0
     }
7bb5d0
   
7bb5d0
   for (srv = daemon->servers; srv; srv = srv->next)
7bb5d0
     if (!(srv->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
7bb5d0
-	!allocate_sfd(&srv->source_addr, srv->interface) &&
7bb5d0
+	!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
7bb5d0
 	errno != 0 &&
7bb5d0
 	option_bool(OPT_NOWILD))
7bb5d0
       {
7bb5d0
@@ -1631,7 +1579,7 @@ void check_servers(void)
7bb5d0
 	  
7bb5d0
 	  /* Do we need a socket set? */
7bb5d0
 	  if (!serv->sfd && 
7bb5d0
-	      !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface)) &&
7bb5d0
+	      !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
7bb5d0
 	      errno != 0)
7bb5d0
 	    {
7bb5d0
 	      my_syslog(LOG_WARNING, 
7bb5d0
diff --git a/src/option.c b/src/option.c
7bb5d0
index 79122df..abc5a48 100644
7bb5d0
--- a/src/option.c
7bb5d0
+++ b/src/option.c
7bb5d0
@@ -795,7 +795,8 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
7bb5d0
     if (interface_opt)
7bb5d0
       {
7bb5d0
 #if defined(SO_BINDTODEVICE)
7bb5d0
-	safe_strncpy(interface, interface_opt, IF_NAMESIZE);
7bb5d0
+	safe_strncpy(interface, source, IF_NAMESIZE);
7bb5d0
+	source = interface_opt;
7bb5d0
 #else
7bb5d0
 	return _("interface binding not supported");
7bb5d0
 #endif
7bb5d0
diff --git a/src/tftp.c b/src/tftp.c
7bb5d0
index f2eccbc..ba9833e 100644
7bb5d0
--- a/src/tftp.c
7bb5d0
+++ b/src/tftp.c
7bb5d0
@@ -96,7 +96,7 @@ void tftp_request(struct listener *listen, time_t now)
7bb5d0
 
7bb5d0
   if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
7bb5d0
     return;
7bb5d0
-
7bb5d0
+  
7bb5d0
   /* Can always get recvd interface for IPv6 */
7bb5d0
   if (!check_dest)
7bb5d0
     {
7bb5d0
@@ -566,7 +566,7 @@ void check_tftp_listeners(time_t now)
7bb5d0
 	{
7bb5d0
 	  /* we overwrote the buffer... */
7bb5d0
 	  daemon->srv_save = NULL;
7bb5d0
-	  
7bb5d0
+
7bb5d0
 	  if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
7bb5d0
 	    {
7bb5d0
 	      if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) 
7bb5d0
@@ -609,7 +609,7 @@ void check_tftp_listeners(time_t now)
7bb5d0
 	  	  
7bb5d0
 	  /* we overwrote the buffer... */
7bb5d0
 	  daemon->srv_save = NULL;
7bb5d0
-	 
7bb5d0
+
7bb5d0
 	  if ((len = get_block(daemon->packet, transfer)) == -1)
7bb5d0
 	    {
7bb5d0
 	      len = tftp_err_oops(daemon->packet, transfer->file->filename);
7bb5d0
diff --git a/src/util.c b/src/util.c
7bb5d0
index 6287529..d016db6 100644
7bb5d0
--- a/src/util.c
7bb5d0
+++ b/src/util.c
7bb5d0
@@ -311,7 +311,7 @@ void *whine_malloc(size_t size)
7bb5d0
   return ret;
7bb5d0
 }
7bb5d0
 
7bb5d0
-int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2)
7bb5d0
+int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
7bb5d0
 {
7bb5d0
   if (s1->sa.sa_family == s2->sa.sa_family)
7bb5d0
     { 
7bb5d0
-- 
7bb5d0
2.26.2
7bb5d0