]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- The BSD socket code will now permit multiple DHCPv6 server instances to
authorDavid Hankins <dhankins@isc.org>
Fri, 8 Aug 2008 20:26:57 +0000 (20:26 +0000)
committerDavid Hankins <dhankins@isc.org>
Fri, 8 Aug 2008 20:26:57 +0000 (20:26 +0000)
  operate on different interfaces. [ISC-Bugs #17610]

RELNOTES
common/discover.c
common/socket.c
includes/dhcpd.h

index 049287828d5ba10b6f9909fab8555739db95e1da..bc977bcf0c671d521cc764f87b6651a210681484 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -48,7 +48,7 @@ work on other platforms. Please report any problems and suggested fixes to
 <dhcp-users@isc.org>.
 
 
-                       Changes since 4.1.0a2
+                       Changes since 4.1.0a1
 
 - Corrected list of failover state values in dhcpd man page.
 
@@ -60,8 +60,6 @@ work on other platforms. Please report any problems and suggested fixes to
 
 - The server wasn't always sending the FQDN option when it should.
 
-                       Changes since 4.1.0a1
-
 - Fixed a coredump when adding a class via OMAPI.
 
 - Check whether files are zero length before trying to parse them.
@@ -111,6 +109,9 @@ work on other platforms. Please report any problems and suggested fixes to
 - Fix handling of -A and -a flags in dhcrelay; it was failing to expand
   packet size as needed to add relay agent options.
 
+- The BSD socket code will now permit multiple DHCPv6 server instances to
+  operate on different interfaces.
+
                        Changes since 4.0.0 (new features)
 
 - Added DHCPv6 rapid commit support.
index 7022b091ab50a0b5c981412749a6e6260b76ec21..20aaf6612f15916328287c778b2ec3ff6b1909d9 100644 (file)
@@ -1265,6 +1265,10 @@ discover_interfaces(int state) {
                if (status != ISC_R_SUCCESS)
                        log_fatal ("Can't register I/O handle for %s: %s",
                                   tmp -> name, isc_result_totext (status));
+#if defined(DHCPv6)
+               /* Only register the first interface with OMAPI for select. */
+               break;
+#endif
        }
 
        if (state == DISCOVER_SERVER && wifcount == 0) {
@@ -1395,6 +1399,7 @@ got_one_v6(omapi_object_t *h) {
        char buf[65536];        /* maximum size for a UDP packet is 65536 */
        struct interface_info *ip;
        int is_unicast;
+       unsigned int if_idx = 0;
 
        if (h->type != dhcp_type_interface) {
                return ISC_R_INVALIDARG;
@@ -1402,12 +1407,16 @@ got_one_v6(omapi_object_t *h) {
        ip = (struct interface_info *)h;
 
        result = receive_packet6(ip, (unsigned char *)buf, sizeof(buf),
-                                &from, &to);
+                                &from, &to, &if_idx);
        if (result < 0) {
                log_error("receive_packet6() failed on %s: %m", ip->name);
                return ISC_R_UNEXPECTED;
        }
 
+       /* 0 is 'any' interface. */
+       if (if_idx == 0)
+               return ISC_R_NOTFOUND;
+
        if (dhcpv6_packet_handler != NULL) {
                /*
                 * If a packet is not multicast, we assume it is unicast.
@@ -1421,6 +1430,13 @@ got_one_v6(omapi_object_t *h) {
                ifrom.len = 16;
                memcpy(ifrom.iabuf, &from.sin6_addr, ifrom.len);
 
+               /* Seek forward to find the matching source interface. */
+               while ((ip != NULL) && (if_nametoindex(ip->name) != if_idx))
+                       ip = ip->next;
+
+               if (ip == NULL)
+                       return ISC_R_NOTFOUND;
+
                (*dhcpv6_packet_handler)(ip, buf, 
                                         result, from.sin6_port, 
                                         &ifrom, is_unicast);
index c748875ffdc5381fcba2a06f3f817c87210fb5e9..e5eb982298f216a3c33d2fb66159143306af76e4 100644 (file)
 # endif
 #endif
 
+#if defined(DHCPv6)
+/*
+ * XXX: this is gross.  we need to go back and overhaul the API for socket
+ * handling and make an additional layer for v6 sockets.
+ */
+static unsigned int global_v6_socket_references = 0;
+static int global_v6_socket = -1;
+
+static void if_register_multicast(struct interface_info *info);
+#endif
+
 /*
  * If we can't bind() to a specific interface, then we can only have
  * a single socket. This variable insures that we don't try to listen
@@ -131,7 +142,7 @@ get_ifaddr6(struct interface_info *info, struct in6_addr *ifaddr6) {
 
 /* Generic interface registration routine... */
 int
-if_register_socket(struct interface_info *info, int family, int do_multicast) {
+if_register_socket(struct interface_info *info, int family) {
        struct sockaddr_storage name;
        int name_len;
        int sock;
@@ -159,6 +170,7 @@ if_register_socket(struct interface_info *info, int family, int do_multicast) {
                struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&name; 
                addr->sin6_family = AF_INET6;
                addr->sin6_port = local_port;
+               /* XXX: what will happen to multicasts if this is nonzero? */
                memcpy(&addr->sin6_addr,
                       &local_address6, 
                       sizeof(addr->sin6_addr));
@@ -217,6 +229,24 @@ if_register_socket(struct interface_info *info, int family, int do_multicast) {
                log_fatal("Can't set SO_BROADCAST option on dhcp socket: %m");
        }
 
+#if defined(DHCPv6) && defined(SO_REUSEPORT)
+       /*
+        * We only set SO_REUSEPORT on AF_INET6 sockets, so that multiple
+        * daemons can bind to their own sockets and get data for their
+        * respective interfaces.  This does not (and should not) affect
+        * DHCPv4 sockets; we can't yet support BSD sockets well, much
+        * less multiple sockets.
+        */
+       if (local_family == AF_INET6) {
+               flag = 1;
+               if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+                              (char *)&flag, sizeof(flag)) < 0) {
+                       log_fatal("Can't set SO_REUSEPORT option on dhcp "
+                                 "socket: %m");
+               }
+       }
+#endif
+
        /* Bind the socket to this interface's IP address. */
        if (bind(sock, (struct sockaddr *)&name, name_len) < 0) {
                log_error("Can't bind to dhcp address: %m");
@@ -229,7 +259,7 @@ if_register_socket(struct interface_info *info, int family, int do_multicast) {
 
 #if defined(SO_BINDTODEVICE)
        /* Bind this socket to this interface. */
-       if (info->ifp &&
+       if ((local_family != AF_INET6) && (info->ifp != NULL) &&
            setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE,
                        (char *)(info -> ifp), sizeof(*(info -> ifp))) < 0) {
                log_fatal("setsockopt: SO_BINDTODEVICE: %m");
@@ -272,36 +302,6 @@ if_register_socket(struct interface_info *info, int family, int do_multicast) {
                }
 #endif
        }
-       
-       if ((family == AF_INET6) && do_multicast) {
-               struct ipv6_mreq mreq;
-
-               /*
-                * Join the DHCPv6 multicast groups so we will receive
-                * multicast messages.
-                */
-               if (inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers,
-                             &mreq.ipv6mr_multiaddr) <= 0) {
-                       log_fatal("inet_pton: unable to convert '%s'", 
-                                 All_DHCP_Relay_Agents_and_Servers);
-               }
-               mreq.ipv6mr_interface = if_nametoindex(info->name);
-               if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, 
-                              &mreq, sizeof(mreq)) < 0) {
-                       log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
-               }
-               if (inet_pton(AF_INET6, All_DHCP_Servers,
-                             &mreq.ipv6mr_multiaddr) <= 0) {
-                       log_fatal("inet_pton: unable to convert '%s'", 
-                                 All_DHCP_Servers);
-               }
-               mreq.ipv6mr_interface = if_nametoindex(info->name);
-               if (((info->flags & INTERFACE_DOWNSTREAM) == 0) &&
-                   (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
-                              &mreq, sizeof(mreq)) < 0)) {
-                       log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
-               }
-       }
 
        if ((family == AF_INET6) &&
            ((info->flags & INTERFACE_UPSTREAM) != 0)) {
@@ -313,7 +313,8 @@ if_register_socket(struct interface_info *info, int family, int do_multicast) {
        }
 #endif /* DHCPv6 */
 
-       if (strcmp(info->name, "fallback") != 0)
+       /* If this is a normal IPv4 address, get the hardware address. */
+       if ((local_family == AF_INET) && (strcmp(info->name, "fallback") != 0))
                get_hw_addr(info->name, &info->hw_address);
 
        return sock;
@@ -325,7 +326,7 @@ void if_register_send (info)
        struct interface_info *info;
 {
 #ifndef USE_SOCKET_RECEIVE
-       info -> wfdesc = if_register_socket (info, AF_INET, 0);
+       info -> wfdesc = if_register_socket (info, AF_INET);
 #if defined (USE_SOCKET_FALLBACK)
        /* Fallback only registers for send, but may need to receive as
           well. */
@@ -393,15 +394,70 @@ void if_deregister_receive (info)
 
 
 #ifdef DHCPv6 
+/*
+ * This function joines the interface to DHCPv6 multicast groups so we will
+ * receive multicast messages.
+ */
+static void
+if_register_multicast(struct interface_info *info) {
+       int sock = info->rfdesc;
+       struct ipv6_mreq mreq;
+
+       if (inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers,
+                     &mreq.ipv6mr_multiaddr) <= 0) {
+               log_fatal("inet_pton: unable to convert '%s'", 
+                         All_DHCP_Relay_Agents_and_Servers);
+       }
+       mreq.ipv6mr_interface = if_nametoindex(info->name);
+       if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, 
+                      &mreq, sizeof(mreq)) < 0) {
+               log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
+       }
+       if (inet_pton(AF_INET6, All_DHCP_Servers,
+                     &mreq.ipv6mr_multiaddr) <= 0) {
+               log_fatal("inet_pton: unable to convert '%s'", 
+                         All_DHCP_Servers);
+       }
+       mreq.ipv6mr_interface = if_nametoindex(info->name);
+       if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, 
+                      &mreq, sizeof(mreq)) < 0) {
+               log_fatal("setsockopt: IPV6_JOIN_GROUP: %m");
+       }
+}
+
 void
 if_register6(struct interface_info *info, int do_multicast) {
-       info->rfdesc = if_register_socket(info, AF_INET6, do_multicast);
-       info->wfdesc = info->rfdesc;
+       if (global_v6_socket_references == 0) {
+               global_v6_socket = if_register_socket(info, AF_INET6);
+               if (global_v6_socket < 0) {
+                       /*
+                        * if_register_socket() fatally logs if it fails
+                        * to create a socket, so this is just a sanity
+                        * check.
+                        */
+                       log_fatal("Impossible condition at %s:%d", MDL);
+               } else {
+                       log_info("Bound to *:%d", ntohs(local_port));
+               }
+       }
+
+       /* Reference the global v6 socket. */
+       info->rfdesc = global_v6_socket;
+       info->wfdesc = global_v6_socket;
+       global_v6_socket_references++;
+
+       if (do_multicast)
+               if_register_multicast(info);
+
+        get_hw_addr(info->name, &info->hw_address);
+
        if (!quiet_interface_discovery) {
                if (info->shared_network != NULL) {
-                       log_info("Listening on Socket/%s/%s", info->name, 
+                       log_info("Listening on Socket/%d/%s/%s",
+                                global_v6_socket, info->name, 
                                 info->shared_network->name);
-                       log_info("Sending on   Socket/%s/%s", info->name,
+                       log_info("Sending on   Socket/%d/%s/%s",
+                                global_v6_socket, info->name,
                                 info->shared_network->name);
                } else {
                        log_info("Listening on Socket/%s", info->name);
@@ -412,14 +468,16 @@ if_register6(struct interface_info *info, int do_multicast) {
 
 void 
 if_deregister6(struct interface_info *info) {
-       /* 
-        * XXX: it would be nice to check for >= 0, but we need to change 
-        *      interface_allocate() to set the file descriptors for that.
-        */
-       close(info->rfdesc);    
-       info->rfdesc = -1;
-       close(info->wfdesc);    
-       info->wfdesc = -1;
+       /* Dereference the global v6 socket. */
+       if ((info->rfdesc == global_v6_socket) &&
+           (info->wfdesc == global_v6_socket) &&
+           (global_v6_socket_references > 0)) {
+               global_v6_socket_references--;
+               info->rfdesc = -1;
+               info->wfdesc = -1;
+       } else {
+               log_fatal("Impossible condition at %s:%d", MDL);
+       }
 
        if (!quiet_interface_discovery) {
                if (info->shared_network != NULL) {
@@ -432,6 +490,13 @@ if_deregister6(struct interface_info *info) {
                        log_info("Disabling output on Socket/%s", info->name);
                }
        }
+
+       if (global_v6_socket_references == 0) {
+               close(global_v6_socket);
+               global_v6_socket = -1;
+
+               log_info("Unbound from *:%d", ntohs(local_port));
+       }
 }
 #endif /* DHCPv6 */
 
@@ -640,13 +705,14 @@ ssize_t receive_packet (interface, buf, len, from, hfrom)
 ssize_t 
 receive_packet6(struct interface_info *interface, 
                unsigned char *buf, size_t len, 
-               struct sockaddr_in6 *from, struct in6_addr *to_addr) {
+               struct sockaddr_in6 *from, struct in6_addr *to_addr,
+               unsigned int *if_idx) {
        struct msghdr m;
        struct iovec v;
        int result;
        struct cmsghdr *cmsg;
        struct in6_pktinfo *pktinfo;
-       int found_to_addr;
+       int found_pktinfo;
        union {
                struct cmsghdr cmsg_sizer;
                u_int8_t pktinfo_sizer[CMSG_SPACE(sizeof(struct in6_pktinfo))];
@@ -695,18 +761,19 @@ receive_packet6(struct interface_info *interface,
                 * We also keep a flag to see if we found it. If we 
                 * didn't, then we consider this to be an error.
                 */
-               found_to_addr = 0;
+               found_pktinfo = 0;
                cmsg = CMSG_FIRSTHDR(&m);
                while (cmsg != NULL) {
                        if ((cmsg->cmsg_level == IPPROTO_IPV6) && 
                            (cmsg->cmsg_type == IPV6_PKTINFO)) {
                                pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
                                *to_addr = pktinfo->ipi6_addr;
-                               found_to_addr = 1;
+                               *if_idx = pktinfo->ipi6_ifindex;
+                               found_pktinfo = 1;
                        }
                        cmsg = CMSG_NXTHDR(&m, cmsg);
                }
-               if (!found_to_addr) {
+               if (!found_pktinfo) {
                        result = -1;
                        errno = EIO;
                }
index 98ee91ef5556155c4b8eac6a76793219c7ce4970..ac5c8dd30d4700bc482b388620378c329c8ff8ec 100644 (file)
@@ -1124,7 +1124,7 @@ struct interface_info {
        unsigned remote_id_len;         /* Length of Remote ID. */
 
        char name [IFNAMSIZ];           /* Its name... */
-       int index;                      /* Its index. */
+       int index;                      /* Its index in the registry. */
        int rfdesc;                     /* Its read file descriptor. */
        int wfdesc;                     /* Its write file descriptor, if
                                           different. */
@@ -2135,7 +2135,7 @@ void get_hw_addr(const char *name, struct hardware *hw);
 /* socket.c */
 #if defined (USE_SOCKET_SEND) || defined (USE_SOCKET_RECEIVE) \
        || defined (USE_SOCKET_FALLBACK)
-int if_register_socket(struct interface_info *, int, int);
+int if_register_socket(struct interface_info *, int);
 #endif
 
 #if defined (USE_SOCKET_FALLBACK) && !defined (USE_SOCKET_SEND)
@@ -2184,7 +2184,8 @@ void maybe_setup_fallback PROTO ((void));
 void if_register6(struct interface_info *info, int do_multicast);
 ssize_t receive_packet6(struct interface_info *interface,
                        unsigned char *buf, size_t len,
-                       struct sockaddr_in6 *from, struct in6_addr *to_addr);
+                       struct sockaddr_in6 *from, struct in6_addr *to_addr,
+                       unsigned int *if_index);
 void if_deregister6(struct interface_info *info);