Blob Blame History Raw
From 198a1fcc9f6f8c39ddf8c6e962b7e4925d43072c Mon Sep 17 00:00:00 2001
From: Jiri Popelka <jpopelka@redhat.com>
Date: Thu, 24 Oct 2013 10:03:52 +0200
Subject: [PATCH] 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]
---
 common/discover.c |  19 ++++-----
 common/socket.c   | 116 ++++++++++++++++++++++++++++++++++++++++++++----------
 includes/dhcpd.h  |   6 +--
 3 files changed, 107 insertions(+), 34 deletions(-)

diff --git a/common/discover.c b/common/discover.c
index a305d92..4027d1a 100644
--- a/common/discover.c
+++ b/common/discover.c
@@ -57,10 +57,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,
@@ -877,7 +873,7 @@ discover_interfaces(int state) {
 			    (state == DISCOVER_RELAY)) {
 				if_register6(tmp, 1);
 			} else {
-				if_register6(tmp, 0);
+				if_register_linklocal6(tmp);
 			}
 #endif /* DHCPv6 */
 		}
@@ -933,13 +929,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; ... */
diff --git a/common/socket.c b/common/socket.c
index 8fead01..f0c2c94 100644
--- a/common/socket.c
+++ b/common/socket.c
@@ -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,9 +224,9 @@ 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) {
+	if (local_family == AF_INET6 && *do_multicast) {
 		flag = 1;
 		if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
 			       (char *)&flag, sizeof(flag)) < 0) {
@@ -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);
@@ -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);
+
+	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 
@@ -747,7 +823,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);
@@ -1046,7 +1122,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,
diff --git a/includes/dhcpd.h b/includes/dhcpd.h
index b2fbc8b..56d4eab 100644
--- a/includes/dhcpd.h
+++ b/includes/dhcpd.h
@@ -2378,7 +2378,7 @@ void get_hw_addr(struct interface_info *info);
 /* 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)
@@ -2389,7 +2389,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
 
@@ -2425,6 +2425,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,
@@ -2570,7 +2571,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;
-- 
1.8.3.1