From f5d0bf08873e6570d119aebac64c4c88091f012c Mon Sep 17 00:00:00 2001
From: Jiri Popelka <jpopelka@redhat.com>
Date: Thu, 12 Sep 2013 13:25:17 +0200
Subject: [PATCH] Bind v6 client to link local address.

When there are more 'dhclient -6' instances running
(one instance per network interface),
always only one of them gets all the packets
(even those destined for the other instances).
The exact behaviour depends on linux kernel version, either only the
first or only the last started instance gets all the packets,
but the problem remains.

I think the problem is that DHCPv6 part of if_register_socket() binds to
address from local_address6 variable but this variable has never been
initialized so it's always 0.

The solution could be to bind to link local address of interface.

We also need to have separate socket for each interface, not one
single for all - this was changed in 4.1.0a2.
---
 common/socket.c | 78 +++++++++++++++++++++++++--------------------------------
 1 file changed, 34 insertions(+), 44 deletions(-)

diff --git a/common/socket.c b/common/socket.c
index f95665c..3e6a80e 100644
--- a/common/socket.c
+++ b/common/socket.c
@@ -63,13 +63,6 @@
 #endif
 
 #if defined(DHCPv6)
-/*
- * XXX: this is gross.  we need to go back and overhaul the API for socket
- * handling.
- */
-static unsigned int global_v6_socket_references = 0;
-static int global_v6_socket = -1;
-
 static void if_register_multicast(struct interface_info *info);
 #endif
 
@@ -134,6 +127,7 @@ if_register_socket(struct interface_info *info, int family,
 	int sock;
 	int flag;
 	int domain;
+	int i;
 #ifdef DHCPv6
 	struct sockaddr_in6 *addr6;
 #endif
@@ -165,6 +159,18 @@ if_register_socket(struct interface_info *info, int family,
 		memcpy(&addr6->sin6_addr,
 		       &local_address6, 
 		       sizeof(addr6->sin6_addr));
+		/* bind client to link-local address */
+		if (*do_multicast == 0) {
+			for (i=0; i < info->v6address_count; i++) {
+				if (IN6_IS_ADDR_LINKLOCAL(&info->v6addresses[i])) {
+					memcpy(&addr6->sin6_addr,
+					       &info->v6addresses[i],
+					       sizeof(addr6->sin6_addr));
+					addr6->sin6_scope_id = if_nametoindex(info->name);
+					break;
+				}
+			}
+		}
 #ifdef HAVE_SA_LEN
 		addr6->sin6_len = sizeof(*addr6);
 #endif
@@ -477,23 +483,18 @@ 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 (global_v6_socket_references == 0) {
-		global_v6_socket = if_register_socket(info, AF_INET6,
-						      &req_multi);
-		if (global_v6_socket < 0) {
-			/*
-			 * if_register_socket() fatally logs if it fails to
-			 * create a socket, this is just a sanity check.
-			 */
-			log_fatal("Impossible condition at %s:%d", MDL);
-		} else {
-			log_info("Bound to *:%d", ntohs(local_port));
-		}
+	info->rfdesc = if_register_socket(info, AF_INET6,
+						&req_multi);
+	if (info->rfdesc < 0) {
+		/*
+		 * if_register_socket() fatally logs if it fails to
+		 * create a socket, this is just a sanity check.
+		 */
+		log_fatal("Impossible condition at %s:%d", MDL);
+	} else {
+		log_info("Bound to *:%d", ntohs(local_port));
 	}
-		
-	info->rfdesc = global_v6_socket;
-	info->wfdesc = global_v6_socket;
-	global_v6_socket_references++;
+	info->wfdesc = info->rfdesc;
 
 	if (req_multi)
 		if_register_multicast(info);
@@ -503,30 +504,26 @@ if_register6(struct interface_info *info, int do_multicast) {
 	if (!quiet_interface_discovery) {
 		if (info->shared_network != NULL) {
 			log_info("Listening on Socket/%d/%s/%s",
-				 global_v6_socket, info->name, 
+				 info->rfdesc, info->name,
 				 info->shared_network->name);
 			log_info("Sending on   Socket/%d/%s/%s",
-				 global_v6_socket, info->name,
+				 info->wfdesc, info->name,
 				 info->shared_network->name);
 		} else {
-			log_info("Listening on Socket/%s", info->name);
-			log_info("Sending on   Socket/%s", info->name);
+			log_info("Listening on Socket/%d/%s", 
+					info->rfdesc, info->name);
+			log_info("Sending on   Socket/%d/%s",
+					info->wfdesc, 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)) {
-		global_v6_socket_references--;
-		info->rfdesc = -1;
-		info->wfdesc = -1;
-	} else {
-		log_fatal("Impossible condition at %s:%d", MDL);
-	}
+	close(info->rfdesc);
+	info->rfdesc = -1;
+	close(info->wfdesc);
+	info->wfdesc = -1;
 
 	if (!quiet_interface_discovery) {
 		if (info->shared_network != NULL) {
@@ -539,13 +536,6 @@ 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 */
 
-- 
1.8.3.1

