From 9380ba70d67db6b69f817d8e318de5ba1e990b12 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 16 Apr 2012 14:41:56 +0100 Subject: [PATCH] Set SO_BINDTODEVICE on DHCP sockets when doing DHCP on one interface only. Fixes OpenSTack use-case. --- CHANGELOG | 7 +++++++ src/dhcp-common.c | 35 +++++++++++++++++++++++++++++++++++ src/dnsmasq.c | 15 +++++++++++++++ src/dnsmasq.h | 5 ++++- src/network.c | 10 +++++++++- 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c100d97..8b34533 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,13 @@ version 2.61 Don't cache data from non-recursive nameservers, since it may erroneously look like a valid CNAME to a non-exitant name. Thanks to Ben Winslow for finding this. + + Call SO_BINDTODEVICE on the DHCP socket(s) when doing DHCP + on exacly one interface and --bind-interfaces is set. This + makes the OpenStack use-case of one dnsmasq per virtual + interface work. This is only available on Linux; it's not + supported on other platforms. Thanks to Vishvananda Ishaya + and thr OpenStack team for the suggestion. version 2.60 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 @@ void join_multicast(void) } #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 @@ int main (int argc, char **argv) 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 server { 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_opt(int prot, char *name); 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 @@ static int iface_allowed(struct irec **irecp, int if_index, 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 @@ static int iface_allowed(struct irec **irecp, int if_index, } 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 @@ static int iface_allowed(struct irec **irecp, int if_index, #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 @@ static int iface_allowed(struct irec **irecp, int if_index, 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;