Set SO_BINDTODEVICE on DHCP sockets when doing DHCP on one interface
only. Fixes OpenSTack use-case.
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index f19a33e..9d1290c 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -451,6 +451,41 @@
 }
 #endif
 
+#ifdef HAVE_LINUX_NETWORK 
+void bindtodevice(int fd)
+{
+  /* If we are doing DHCP on exactly one interface, and running linux, do SO_BINDTODEVICE
+     to that device. This is for the use case of  (eg) OpenStack, which runs a new
+     dnsmasq instance for each VLAN interface it creates. Without the BINDTODEVICE, 
+     individual processes don't always see the packets they should.
+     SO_BINDTODEVICE is only available Linux. */
+  
+  struct irec *iface, *found;
+
+  for (found = NULL, iface = daemon->interfaces; iface; iface = iface->next)
+    if (iface->dhcp_ok)
+      {
+	if (!found)
+	  found = iface;
+	else if (strcmp(found->name, iface->name) != 0) 
+	  {
+	    /* more than one. */
+	    found = NULL;
+	    break;
+	  }
+      }
+  
+  if (found)
+	{
+	  struct ifreq ifr;
+	  strcpy(ifr.ifr_name, found->name);
+	  /* only allowed by root. */
+	  if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) == -1 &&
+	      errno != EPERM)
+	    die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET);
+	}
+}
+#endif
 
 static const struct opttab_t {
   char *name;
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 983fc08..34c979b 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -209,6 +209,21 @@
       for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
 	if (if_tmp->name && !if_tmp->used)
 	  die(_("unknown interface %s"), if_tmp->name, EC_BADNET);
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
+      /* after enumerate_interfaces()  */
+      if (daemon->dhcp)
+	{
+	  bindtodevice(daemon->dhcpfd);
+	  if (daemon->enable_pxe)
+	    bindtodevice(daemon->pxefd);
+	}
+#endif
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
+      if (daemon->dhcp6)
+	bindtodevice(daemon->dhcp6fd);
+#endif
     }
   else 
     create_wildcard_listeners();
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index fa8fe05..082c923 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -388,7 +388,7 @@
 struct irec {
   union mysockaddr addr;
   struct in_addr netmask; /* only valid for IPv4 */
-  int tftp_ok, mtu, done, dad;
+  int tftp_ok, dhcp_ok, mtu, done, dad;
   char *name;
   struct irec *next;
 };
@@ -1108,6 +1108,9 @@
 u16 lookup_dhcp_len(int prot, u16 val);
 char *option_string(int prot, unsigned int opt, unsigned char *val, 
 		    int opt_len, char *buf, int buf_len);
+#ifdef HAVE_LINUX_NETWORK
+void bindtodevice(int fd);
+#endif
 #  ifdef HAVE_DHCP6
 void display_opts6(void);
 void join_multicast(void);
diff --git a/src/network.c b/src/network.c
index f5dcf97..b997b48 100644
--- a/src/network.c
+++ b/src/network.c
@@ -162,6 +162,7 @@
   int fd, mtu = 0, loopback;
   struct ifreq ifr;
   int tftp_ok = daemon->tftp_unlimited;
+  int dhcp_ok = 1;
 #ifdef HAVE_DHCP
   struct iname *tmp;
 #endif
@@ -190,6 +191,9 @@
     }
    
   loopback = ifr.ifr_flags & IFF_LOOPBACK;
+  
+  if (loopback)
+     dhcp_ok = 0;
 
   if (ioctl(fd, SIOCGIFMTU, &ifr) != -1)
     mtu = ifr.ifr_mtu;
@@ -238,7 +242,10 @@
 #ifdef HAVE_DHCP
       for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
 	if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
-	  tftp_ok = 0;
+	  {
+	    tftp_ok = 0;
+	    dhcp_ok = 0;
+	  }
 #endif
       
 #ifdef HAVE_IPV6
@@ -254,6 +261,7 @@
       iface->addr = *addr;
       iface->netmask = netmask;
       iface->tftp_ok = tftp_ok;
+      iface->dhcp_ok = dhcp_ok;
       iface->mtu = mtu;
       iface->dad = dad;
       iface->done = 0;