You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
From 4b8251a0c06b7d8706a28904fdef2414f045cc2c Mon Sep 17 00:00:00 2001 |
|
From: Shawn Routhier <sar@isc.org> |
|
Date: Mon, 21 Oct 2013 14:59:41 -0700 |
|
Subject: [PATCH] -n [master] Fix the socket handling for DHCPv6 clients to |
|
allow multiple instances of a clinet on a single machine to work properly. |
|
[ISC-Bugs #34784] |
|
|
|
--- |
|
common/discover.c | 19 ++++----- |
|
common/socket.c | 114 +++++++++++++++++++++++++++++++++++++++++++++--------- |
|
includes/dhcpd.h | 6 +-- |
|
4 files changed, 112 insertions(+), 33 deletions(-) |
|
|
|
diff --git a/common/discover.c b/common/discover.c |
|
index 1d55317..30da566 100644 |
|
--- a/common/discover.c |
|
+++ b/common/discover.c |
|
@@ -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; ... */ |
|
diff --git a/common/socket.c b/common/socket.c |
|
index 8a9ebea..2bedd3a 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,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) { |
|
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); |
|
@@ -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 |
|
@@ -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, |
|
diff --git a/includes/dhcpd.h b/includes/dhcpd.h |
|
index 73c632f..9e18818 100644 |
|
--- a/includes/dhcpd.h |
|
+++ b/includes/dhcpd.h |
|
@@ -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; |
|
-- |
|
2.1.0 |
|
|
|
|