From 4b8251a0c06b7d8706a28904fdef2414f045cc2c Mon Sep 17 00:00:00 2001 From: Shawn Routhier 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