]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
-n [master]
authorShawn Routhier <sar@isc.org>
Mon, 21 Oct 2013 21:59:41 +0000 (14:59 -0700)
committerShawn Routhier <sar@isc.org>
Mon, 21 Oct 2013 21:59:41 +0000 (14:59 -0700)
Fix the socket handling for DHCPv6 clients to allow multiple instances
of a clinet on a single machine to work properly.
[ISC-Bugs #34784]

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

index 81919f42e88ee0c0bb88b01773ca247e29d4b1c9..3e7350d5d2ab388ba2dc0b07fe15bb8e1b405c59 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -123,6 +123,12 @@ work on other platforms. Please report any problems and suggested fixes to
 - Update client script for use with openwrt.
   [ISC-Bugs #29843]
 
+- Fix the socket handling for DHCPv6 clients to allow multiple instances
+  of a client on a single machine to work properly.  Previously only
+  one client would receive the packets.  Thanks to Jiri Popelka at Red Hat
+  for the bug report and a potential patch.
+  [ISC-Bugs #34784]
+
                        Changes since 4.2.4
 
 - Correct code to calculate timing values in client to compare
index 1d55317b24246130fbbab3ffb7df7bc2f9c6d2c0..30da56649f632645493444a08643d92e927b27a2 100644 (file)
@@ -58,10 +58,6 @@ struct in_addr limited_broadcast;
 int local_family = AF_INET;
 struct in_addr local_address;
 
-#ifdef DHCPv6
-struct in6_addr local_address6;
-#endif /* DHCPv6 */
-
 void (*bootp_packet_handler) (struct interface_info *,
                              struct dhcp_packet *, unsigned,
                              unsigned int,
@@ -1242,7 +1238,7 @@ discover_interfaces(int state) {
                            (state == DISCOVER_RELAY)) {
                                if_register6(tmp, 1);
                        } else {
-                               if_register6(tmp, 0);
+                               if_register_linklocal6(tmp);
                        }
 #endif /* DHCPv6 */
                }
@@ -1298,13 +1294,14 @@ discover_interfaces(int state) {
                                   tmp -> name, isc_result_totext (status));
 
 #if defined(DHCPv6)
-               /* Only register the first interface for V6, since they all
-                * use the same socket.  XXX: This has some messy side
-                * effects if we start dynamically adding and removing
-                * interfaces, but we're well beyond that point in terms of
-                * mess.
+               /* Only register the first interface for V6, since
+                * servers and relays all use the same socket.
+                * XXX: This has some messy side effects if we start
+                * dynamically adding and removing interfaces, but
+                * we're well beyond that point in terms of mess.
                 */
-               if (local_family == AF_INET6)
+               if (((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) &&
+                   (local_family == AF_INET6))
                        break;
 #endif
        } /* for (tmp = interfaces; ... */
index 8a9ebea5f59a3fd4273b41a36b9e91faa08bc594..2bedd3a9f6bbd0d31ab75ad3e6237460fb8788e7 100644 (file)
@@ -67,6 +67,7 @@
  * XXX: this is gross.  we need to go back and overhaul the API for socket
  * handling.
  */
+static int no_global_v6_socket = 0;
 static unsigned int global_v6_socket_references = 0;
 static int global_v6_socket = -1;
 
@@ -127,7 +128,7 @@ void if_reinitialize_receive (info)
 /* Generic interface registration routine... */
 int
 if_register_socket(struct interface_info *info, int family,
-                  int *do_multicast)
+                  int *do_multicast, struct in6_addr *linklocal6)
 {
        struct sockaddr_storage name;
        int name_len;
@@ -161,10 +162,12 @@ if_register_socket(struct interface_info *info, int family,
                addr6 = (struct sockaddr_in6 *)&name; 
                addr6->sin6_family = AF_INET6;
                addr6->sin6_port = local_port;
-               /* XXX: What will happen to multicasts if this is nonzero? */
-               memcpy(&addr6->sin6_addr,
-                      &local_address6, 
-                      sizeof(addr6->sin6_addr));
+               if (linklocal6) {
+                       memcpy(&addr6->sin6_addr,
+                              linklocal6,
+                              sizeof(addr6->sin6_addr));
+                       addr6->sin6_scope_id = if_nametoindex(info->name);
+               }
 #ifdef HAVE_SA_LEN
                addr6->sin6_len = sizeof(*addr6);
 #endif
@@ -221,7 +224,7 @@ if_register_socket(struct interface_info *info, int family,
         * 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.
+        * less multiple sockets. Make sense only with multicast.
         */
        if ((local_family == AF_INET6) && *do_multicast) {
                flag = 1;
@@ -322,7 +325,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, 0, NULL);
        /* If this is a normal IPv4 address, get the hardware address. */
        if (strcmp(info->name, "fallback") != 0)
                get_hw_addr(info->name, &info->hw_address);
@@ -368,7 +371,7 @@ void if_register_receive (info)
 
 #if defined(IP_PKTINFO) && defined(IP_RECVPKTINFO) && defined(USE_V4_PKTINFO)
        if (global_v4_socket_references == 0) {
-               global_v4_socket = if_register_socket(info, AF_INET, 0);
+               global_v4_socket = if_register_socket(info, AF_INET, 0, NULL);
                if (global_v4_socket < 0) {
                        /*
                         * if_register_socket() fatally logs if it fails to
@@ -384,7 +387,7 @@ void if_register_receive (info)
 #else
        /* If we're using the socket API for sending and receiving,
           we don't need to register this interface twice. */
-       info->rfdesc = if_register_socket(info, AF_INET, 0);
+       info->rfdesc = if_register_socket(info, AF_INET, 0, NULL);
 #endif /* IP_PKTINFO... */
        /* If this is a normal IPv4 address, get the hardware address. */
        if (strcmp(info->name, "fallback") != 0)
@@ -477,9 +480,13 @@ if_register6(struct interface_info *info, int do_multicast) {
        /* Bounce do_multicast to a stack variable because we may change it. */
        int req_multi = do_multicast;
 
+       if (no_global_v6_socket) {
+               log_fatal("Impossible condition at %s:%d", MDL);
+       }
+
        if (global_v6_socket_references == 0) {
                global_v6_socket = if_register_socket(info, AF_INET6,
-                                                     &req_multi);
+                                                     &req_multi, NULL);
                if (global_v6_socket < 0) {
                        /*
                         * if_register_socket() fatally logs if it fails to
@@ -515,12 +522,73 @@ if_register6(struct interface_info *info, int do_multicast) {
        }
 }
 
+/*
+ * Register an IPv6 socket bound to the link-local address of
+ * the argument interface (used by clients on a multiple interface box,
+ * vs. a server or a relay using the global IPv6 socket and running
+ * *only* in a single instance).
+ */
+void
+if_register_linklocal6(struct interface_info *info) {
+       int sock;
+       int count;
+       struct in6_addr *addr6 = NULL;
+       int req_multi = 0;
+
+       if (global_v6_socket >= 0) {
+               log_fatal("Impossible condition at %s:%d", MDL);
+       }
+               
+       no_global_v6_socket = 1;
+
+       /* get the (?) link-local address */
+       for (count = 0; count < info->v6address_count; count++) {
+               addr6 = &info->v6addresses[count];
+               if (IN6_IS_ADDR_LINKLOCAL(addr6))
+                       break;
+       }
+
+       if (!addr6) {
+               log_fatal("no link-local IPv6 address for %s", info->name);
+       }
+
+       sock = if_register_socket(info, AF_INET6, &req_multi, addr6);
+
+       if (sock < 0) {
+               log_fatal("if_register_socket for %s fails", info->name);
+       }
+
+       info->rfdesc = sock;
+       info->wfdesc = sock;
+
+       get_hw_addr(info->name, &info->hw_address);
+
+       if (!quiet_interface_discovery) {
+               if (info->shared_network != NULL) {
+                       log_info("Listening on Socket/%d/%s/%s",
+                                global_v6_socket, info->name, 
+                                info->shared_network->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);
+                       log_info("Sending on   Socket/%s", info->name);
+               }
+       }
+}
+
 void 
 if_deregister6(struct interface_info *info) {
-       /* Dereference the global v6 socket. */
-       if ((info->rfdesc == global_v6_socket) &&
-           (info->wfdesc == global_v6_socket) &&
-           (global_v6_socket_references > 0)) {
+       /* client case */
+       if (no_global_v6_socket) {
+               close(info->rfdesc);
+               info->rfdesc = -1;
+               info->wfdesc = -1;
+       } else if ((info->rfdesc == global_v6_socket) &&
+                  (info->wfdesc == global_v6_socket) &&
+                  (global_v6_socket_references > 0)) {
+               /* Dereference the global v6 socket. */
                global_v6_socket_references--;
                info->rfdesc = -1;
                info->wfdesc = -1;
@@ -540,7 +608,8 @@ if_deregister6(struct interface_info *info) {
                }
        }
 
-       if (global_v6_socket_references == 0) {
+       if (!no_global_v6_socket &&
+           (global_v6_socket_references == 0)) {
                close(global_v6_socket);
                global_v6_socket = -1;
 
@@ -692,9 +761,11 @@ ssize_t send_packet6(struct interface_info *interface,
                     struct sockaddr_in6 *to) {
        struct msghdr m;
        struct iovec v;
+       struct sockaddr_in6 dst;
        int result;
        struct in6_pktinfo *pktinfo;
        struct cmsghdr *cmsg;
+       unsigned int ifindex;
 
        /*
         * If necessary allocate space for the control message header.
@@ -717,9 +788,14 @@ ssize_t send_packet6(struct interface_info *interface,
 
        /*
         * Set the target address we're sending to.
+        * Enforce the scope ID for bogus BSDs.
         */
-       m.msg_name = to;
-       m.msg_namelen = sizeof(*to);
+       memcpy(&dst, to, sizeof(dst));
+       m.msg_name = &dst;
+       m.msg_namelen = sizeof(dst);
+       ifindex = if_nametoindex(interface->name);
+       if (no_global_v6_socket)
+               dst.sin6_scope_id = ifindex;
 
        /*
         * Set the data buffer we're sending. (Using this wacky 
@@ -748,7 +824,7 @@ ssize_t send_packet6(struct interface_info *interface,
        cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
        pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
        memset(pktinfo, 0, sizeof(*pktinfo));
-       pktinfo->ipi6_ifindex = if_nametoindex(interface->name);
+       pktinfo->ipi6_ifindex = ifindex;
        m.msg_controllen = cmsg->cmsg_len;
 
        result = sendmsg(interface->wfdesc, &m, 0);
@@ -1047,7 +1123,7 @@ void maybe_setup_fallback ()
        isc_result_t status;
        struct interface_info *fbi = (struct interface_info *)0;
        if (setup_fallback (&fbi, MDL)) {
-               fbi -> wfdesc = if_register_socket (fbi, AF_INET, 0);
+               fbi -> wfdesc = if_register_socket (fbi, AF_INET, 0, NULL);
                fbi -> rfdesc = fbi -> wfdesc;
                log_info ("Sending on   Socket/%s%s%s",
                      fbi -> name,
index 73c632f40ff21e7988e57b6124aaf7153230acad..9e18818b8604535ef10eea46457420b56c27ecd0 100644 (file)
@@ -2414,7 +2414,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, int *, struct in6_addr *);
 #endif
 
 #if defined (USE_SOCKET_FALLBACK) && !defined (USE_SOCKET_SEND)
@@ -2425,7 +2425,7 @@ ssize_t send_fallback (struct interface_info *,
                       struct in_addr,
                       struct sockaddr_in *, struct hardware *);
 ssize_t send_fallback6(struct interface_info *, struct packet *,
-                      struct dhcp_packet *, size_t, struct in6_addr,
+                      struct dhcp_packet *, size_t, struct in6_addr *,
                       struct sockaddr_in6 *, struct hardware *);
 #endif
 
@@ -2461,6 +2461,7 @@ void maybe_setup_fallback (void);
 #endif
 
 void if_register6(struct interface_info *info, int do_multicast);
+void if_register_linklocal6(struct interface_info *info);
 ssize_t receive_packet6(struct interface_info *interface,
                        unsigned char *buf, size_t len,
                        struct sockaddr_in6 *from, struct in6_addr *to_addr,
@@ -2606,7 +2607,6 @@ void interface_trace_setup (void);
 extern struct in_addr limited_broadcast;
 extern int local_family;
 extern struct in_addr local_address;
-extern struct in6_addr local_address6;
 
 extern u_int16_t local_port;
 extern u_int16_t remote_port;