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;