diff --git a/include/czmq_prelude.h b/include/czmq_prelude.h index f3eed8bf1..85db5b5e6 100644 --- a/include/czmq_prelude.h +++ b/include/czmq_prelude.h @@ -580,6 +580,7 @@ typedef int SOCKET; # include // This would normally come from net/if.h unsigned int if_nametoindex (const char *ifname); +char *if_indextoname (unsigned int ifindex, char *ifname); #else # if defined (HAVE_NET_IF_H) # include diff --git a/include/zsys.h b/include/zsys.h index 739cde86d..8436f87fc 100644 --- a/include/zsys.h +++ b/include/zsys.h @@ -167,7 +167,8 @@ CZMQ_EXPORT int zsys_udp_send (SOCKET udpsock, zframe_t *frame, inaddr_t *address, int addrlen); // Receive zframe from UDP socket, and set address of peer that sent it -// The peername must be a char [INET_ADDRSTRLEN] array. +// The peername must be a char [INET_ADDRSTRLEN] array if IPv6 is disabled or +// NI_MAXHOST if it's enabled. Returns NULL when failing to get peer address. // *** This is for CZMQ internal use only and may change arbitrarily *** CZMQ_EXPORT zframe_t * zsys_udp_recv (SOCKET udpsock, char *peername, int peerlen); diff --git a/src/zbeacon.c b/src/zbeacon.c index 01826f540..12fd80ff6 100644 --- a/src/zbeacon.c +++ b/src/zbeacon.c @@ -36,12 +36,13 @@ typedef struct { zsock_t *pipe; // Actor command pipe SOCKET udpsock; // UDP socket for send/recv - int port_nbr; // UDP port number we work on + SOCKET udpsock_send; // UDP socket for IPv6 send + char port_nbr [7]; // UDP port number we work on int interval; // Beacon broadcast interval int64_t ping_at; // Next broadcast time zframe_t *transmit; // Beacon transmit data zframe_t *filter; // Beacon filter data - inaddr_t broadcast; // Our broadcast address + inaddr_storage_t broadcast; // Our broadcast address bool terminated; // Did caller ask us to quit? bool verbose; // Verbose logging enabled? char hostname [NI_MAXHOST]; // Saved host name @@ -81,24 +82,69 @@ s_self_prepare_udp (self_t *self) // Create our UDP socket if (self->udpsock) zsys_udp_close (self->udpsock); + if (self->udpsock_send) + zsys_udp_close (self->udpsock_send); self->hostname [0] = 0; + // For IPv6 we need two sockets. At least on Linux, IPv6 multicast packets + // are NOT received despite joining the group and setting the interface + // option UNLESS the socket is bound to in6_addrany, which means the kernel + // will select an arbitrary IP address as the source when sending beacons + // out. This breaks zbeacon as the protocol uses the source address of a + // beacon to find the endpoint of a peer, which is then random and + // useless (could even be associated with a different interface, eg: a + // virtual bridge). + // As a workaround, use a different socket to send packets. So the socket + // that receives can be bound to in6_addrany, and the socket that sends + // can be bound to the actual intended host address. self->udpsock = zsys_udp_new (false); - if (self->udpsock == INVALID_SOCKET) + if (self->udpsock == INVALID_SOCKET) { + self->udpsock_send = INVALID_SOCKET; return; + } + self->udpsock_send = zsys_udp_new (false); + if (self->udpsock_send == INVALID_SOCKET) { + zsys_udp_close (self->udpsock); + self->udpsock = INVALID_SOCKET; + return; + } // Get the network interface fro ZSYS_INTERFACE or else use first // broadcast interface defined on system. ZSYS_INTERFACE=* means // use INADDR_ANY + INADDR_BROADCAST. const char *iface = zsys_interface (); - in_addr_t bind_to = 0; - in_addr_t send_to = 0; + struct addrinfo *bind_to = NULL; + struct addrinfo *send_to = NULL; + struct addrinfo hint; + memset (&hint, 0, sizeof(struct addrinfo)); + hint.ai_flags = AI_NUMERICHOST | AI_V4MAPPED; + hint.ai_socktype = SOCK_DGRAM; + hint.ai_protocol = IPPROTO_UDP; + hint.ai_family = zsys_ipv6 () ? AF_INET6 : AF_INET; + int rc; int found_iface = 0; + unsigned int if_index = 0; if (streq (iface, "*")) { // Wildcard means bind to INADDR_ANY and send to INADDR_BROADCAST - bind_to = INADDR_ANY; - send_to = INADDR_BROADCAST; + // IE - getaddrinfo with NULL as first parameter or 255.255.255.255 + // (or IPv6 multicast link-local all-node group ff02::1 + hint.ai_flags = hint.ai_flags | AI_PASSIVE; + rc = getaddrinfo (NULL, self->port_nbr, &hint, &bind_to); + assert (rc == 0); + + if (zsys_ipv6()) { + // Default is link-local all-node multicast group + rc = getaddrinfo (zsys_ipv6_mcast_address (), self->port_nbr, &hint, + &send_to); + assert (rc == 0); + } + else { + rc = getaddrinfo ("255.255.255.255", self->port_nbr, &hint, + &send_to); + assert (rc == 0); + } + found_iface = 1; } // if ZSYS_INTERFACE is a single digit, use the corresponding interface in @@ -106,17 +152,25 @@ s_self_prepare_udp (self_t *self) else if (strlen (iface) == 1 && iface[0] >= '0' && iface[0] <= '9') { int if_number = atoi (iface); - ziflist_t *iflist = ziflist_new (); + ziflist_t *iflist = ziflist_new_ipv6 (); assert (iflist); const char *name = ziflist_first (iflist); int idx = -1; while (name) { idx++; - if (idx == if_number) { + if (idx == if_number && + ((ziflist_is_ipv6 (iflist) && zsys_ipv6 ()) || + (!ziflist_is_ipv6 (iflist) && !zsys_ipv6 ()))) { // Using inet_addr instead of inet_aton or inet_atop // because these are not supported in Win XP - send_to = inet_addr (ziflist_broadcast (iflist)); - bind_to = inet_addr (ziflist_address (iflist)); + rc = getaddrinfo (ziflist_address (iflist), self->port_nbr, + &hint, &bind_to); + assert (rc == 0); + rc = getaddrinfo (ziflist_broadcast (iflist), self->port_nbr, + &hint, &send_to); + assert (rc == 0); + if_index = if_nametoindex (name); + if (self->verbose) zsys_info ("zbeacon: interface=%s address=%s broadcast=%s", name, ziflist_address (iflist), ziflist_broadcast (iflist)); @@ -127,17 +181,38 @@ s_self_prepare_udp (self_t *self) } ziflist_destroy (&iflist); } + else if (zsys_ipv6 () && strneq("", zsys_ipv6_address ()) && strneq (iface, "")) { + rc = getaddrinfo (zsys_ipv6_address (), self->port_nbr, + &hint, &bind_to); + assert (rc == 0); + rc = getaddrinfo (zsys_ipv6_mcast_address (), self->port_nbr, + &hint, &send_to); + assert (rc == 0); + if_index = if_nametoindex (iface); + + if (self->verbose) + zsys_info ("zbeacon: interface=%s address=%s broadcast=%s", + iface, zsys_ipv6_address (), zsys_ipv6_mcast_address ()); + found_iface = 1; + } else { // Look for matching interface, or first ziflist item - ziflist_t *iflist = ziflist_new (); + ziflist_t *iflist = ziflist_new_ipv6 (); assert (iflist); const char *name = ziflist_first (iflist); while (name) { - if (streq (iface, name) || streq (iface, "")) { - // Using inet_addr instead of inet_aton or inet_atop - // because these are not supported in Win XP - send_to = inet_addr (ziflist_broadcast (iflist)); - bind_to = inet_addr (ziflist_address (iflist)); + // If IPv6 is not enabled ignore IPv6 interfaces. + if ((streq (iface, name) || streq (iface, "")) && + ((ziflist_is_ipv6 (iflist) && zsys_ipv6 ()) || + (!ziflist_is_ipv6 (iflist) && !zsys_ipv6 ()))) { + rc = getaddrinfo (ziflist_address (iflist), self->port_nbr, + &hint, &bind_to); + assert (rc == 0); + rc = getaddrinfo (ziflist_broadcast (iflist), self->port_nbr, + &hint, &send_to); + assert (rc == 0); + if_index = if_nametoindex (name); + if (self->verbose) zsys_info ("zbeacon: interface=%s address=%s broadcast=%s", name, ziflist_address (iflist), ziflist_broadcast (iflist)); @@ -149,37 +224,63 @@ s_self_prepare_udp (self_t *self) ziflist_destroy (&iflist); } if (found_iface) { - self->broadcast.sin_family = AF_INET; - self->broadcast.sin_port = htons (self->port_nbr); - self->broadcast.sin_addr.s_addr = send_to; - inaddr_t address = self->broadcast; - address.sin_addr.s_addr = bind_to; - // Bind to the port on all interfaces + inaddr_storage_t bind_address; + + // On Windows we bind to the host address + // On *NIX we bind to INADDR_ANY or in6addr_any, otherwise multicast + // packets will be filtered out despite joining the group #if (defined (__WINDOWS__)) - inaddr_t sockaddr = address; -#elif (defined (__APPLE__)) - inaddr_t sockaddr = self->broadcast; - sockaddr.sin_addr.s_addr = htons (INADDR_ANY); + memcpy (&bind_address, bind_to->ai_addr, bind_to->ai_addrlen); #else - inaddr_t sockaddr = self->broadcast; + memcpy (&bind_address, send_to->ai_addr, send_to->ai_addrlen); + if (zsys_ipv6 ()) + bind_address.__inaddr_u.__addr6.sin6_addr = in6addr_any; + else + bind_address.__inaddr_u.__addr.sin_addr.s_addr = htonl (INADDR_ANY); #endif + memcpy (&self->broadcast, send_to->ai_addr, send_to->ai_addrlen); + + if (zsys_ipv6()) { + struct ipv6_mreq mreq; + mreq.ipv6mr_interface = if_index; + memcpy (&mreq.ipv6mr_multiaddr, + &(((in6addr_t *)(send_to->ai_addr))->sin6_addr), + sizeof (struct in6_addr)); + + if (setsockopt (self->udpsock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + (char *)&mreq, sizeof (mreq))) + zsys_socket_error ("zbeacon: setsockopt IPV6_JOIN_GROUP failed"); + + if (setsockopt (self->udpsock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + (char *)&if_index, sizeof (if_index))) + zsys_socket_error ("zbeacon: setsockopt IPV6_MULTICAST_IF failed"); + + if (setsockopt (self->udpsock_send, IPPROTO_IPV6, IPV6_JOIN_GROUP, + (char *)&mreq, sizeof (mreq))) + zsys_socket_error ("zbeacon: setsockopt IPV6_JOIN_GROUP failed"); + + if (setsockopt (self->udpsock_send, IPPROTO_IPV6, IPV6_MULTICAST_IF, + (char *)&if_index, sizeof (if_index))) + zsys_socket_error ("zbeacon: setsockopt IPV6_MULTICAST_IF failed"); + } + // If bind fails, we close the socket for opening again later (next poll interval) - if (bind (self->udpsock, (struct sockaddr *) &sockaddr, sizeof (inaddr_t))) - { + if (bind (self->udpsock_send, bind_to->ai_addr, bind_to->ai_addrlen) || + bind (self->udpsock, (struct sockaddr *)&bind_address, + zsys_ipv6 () ? sizeof (in6addr_t) : sizeof (inaddr_t))) { zsys_debug ("zbeacon: Unable to bind to broadcast address, reason=%s", strerror (errno)); zsys_udp_close (self->udpsock); self->udpsock = INVALID_SOCKET; - return; + zsys_udp_close (self->udpsock_send); + self->udpsock_send = INVALID_SOCKET; } - - // Get our hostname so we can send it back to the API - if (address.sin_addr.s_addr == INADDR_ANY) { + else if (streq (iface, "*")) { strcpy(self->hostname, "*"); if (self->verbose) zsys_info ("zbeacon: configured, hostname=%s", self->hostname); } - else if (getnameinfo ((struct sockaddr *) &address, sizeof (inaddr_t), - self->hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) { + else if (getnameinfo (bind_to->ai_addr, bind_to->ai_addrlen, + self->hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == 0) { if (self->verbose) zsys_info ("zbeacon: configured, hostname=%s", self->hostname); } @@ -189,7 +290,12 @@ s_self_prepare_udp (self_t *self) // No valid interface. Close the socket so that we can try again later zsys_udp_close(self->udpsock); self->udpsock = INVALID_SOCKET; + zsys_udp_close (self->udpsock_send); + self->udpsock_send = INVALID_SOCKET; } + + freeaddrinfo (bind_to); + freeaddrinfo (send_to); } @@ -201,7 +307,7 @@ static void s_self_configure (self_t *self, int port_nbr) { assert (port_nbr); - self->port_nbr = port_nbr; + snprintf (self->port_nbr, 7, "%d", port_nbr); s_self_prepare_udp (self); zstr_send (self->pipe, self->hostname); if (streq (self->hostname, "")) @@ -274,8 +380,10 @@ s_self_handle_udp (self_t *self) { assert (self); - char peername [INET_ADDRSTRLEN]; - zframe_t *frame = zsys_udp_recv (self->udpsock, peername, INET_ADDRSTRLEN); + char peername [NI_MAXHOST]; + zframe_t *frame = zsys_udp_recv (self->udpsock, peername, NI_MAXHOST); + if (!frame) + return; // If filter is set, check that beacon matches it bool is_valid = false; @@ -344,7 +452,9 @@ zbeacon (zsock_t *pipe, void *args) && zclock_mono () >= self->ping_at) { // Send beacon to any listening peers if (!self->udpsock || self->udpsock == INVALID_SOCKET || - zsys_udp_send (self->udpsock, self->transmit, &self->broadcast, sizeof(inaddr_t))) + zsys_udp_send (self->udpsock_send, self->transmit, + (inaddr_t *)&self->broadcast, + zsys_ipv6 () ? sizeof (in6addr_t) : sizeof (inaddr_t))) { const char *reason = (!self->udpsock || self->udpsock == INVALID_SOCKET) ? "invalid socket" : strerror (errno); zsys_debug ("zbeacon: failed to transmit, attempting reconnection. reason=%s", reason); diff --git a/src/zsys.c b/src/zsys.c index ba53d0a65..13caf6673 100644 --- a/src/zsys.c +++ b/src/zsys.c @@ -931,16 +931,14 @@ zsys_udp_send (SOCKET udpsock, zframe_t *frame, inaddr_t *address, int addrlen) // -------------------------------------------------------------------------- // Receive zframe from UDP socket, and set address of peer that sent it -// The peername must be a char [INET_ADDRSTRLEN] array. +// The peername must be a char [INET_ADDRSTRLEN] array if IPv6 is disabled or +// NI_MAXHOST if it's enabled. Returns NULL when failing to get peer address. zframe_t * zsys_udp_recv (SOCKET udpsock, char *peername, int peerlen) { char buffer [UDP_FRAME_MAX]; in6addr_t address6; -#if (!defined (__WINDOWS__)) - inaddr_t *address = (inaddr_t *) &address6; -#endif socklen_t address_len = sizeof (in6addr_t); ssize_t size = recvfrom ( udpsock, @@ -952,15 +950,15 @@ zsys_udp_recv (SOCKET udpsock, char *peername, int peerlen) zsys_socket_error ("recvfrom"); // Get sender address as printable string -#if (defined (__WINDOWS__)) - getnameinfo ((struct sockaddr *) &address6, address_len, + int rc = getnameinfo ((struct sockaddr *) &address6, address_len, peername, peerlen, NULL, 0, NI_NUMERICHOST); -#else - if (address6.sin6_family == AF_INET6) - inet_ntop (AF_INET6, &address6.sin6_addr, peername, address_len); - else - inet_ntop (AF_INET, &address->sin_addr, peername, address_len); -#endif + + if (rc) { + zsys_warning ("zsys_udp_recv: getnameinfo failed, reason=%s", + gai_strerror (rc)); + return NULL; + } + return zframe_new (buffer, size); }