Martin missed an updated patch from before, here it is again with some extras.
* Fix NIC name binding always returning inaddr-any/in6addr-any. * Binding upon a NIC name in IPv6 may resolve to multiple interfaces, use the IPv6 scope identifier to force the bind. * Use SO_BINDTODEVICE on Linux to workaround lack of in6addr_any:scope_id support. * Add support for IPv6 literal addresses. *NIC NAME BINDING* Basically NIC binding isn't too hot, so what we have here is when you bind like this: *./perf/local_thr tcp://eth0:5555 1 100000* You are actually getting the same as this: *./perf/local_thr tcp://*:5555 1 100000* On Linux I have added a workaround that you can use system capabilities to force the bind: *sudo execcap 'cap_net_raw=ep' ./perf/local_thr tcp://eth0:5555 1 100000* * * On modern distro's this utility has disappeared, the file system tools don't appear to work out-of-the-box, but capsh does: *sudo capsh --caps="cap_net_raw=ep" -- -c "./perf/local_thr tcp://eth0:5555 1 100000"* * * That is the same capability as supporting ICMP, raw sockets, and IP/PGM. To test simply try using another adapter such as loopback and it should wait on reconnecting. *./perf/remote_thr tcp://127.0.0.1:5555 1 100000* *IPv6 LITERAL ADDRESSES* Nothing particularly amazing, but to avoid confusion with port specifiers the RFC recommend that all IPv6 literal addresses be enclosed within square brackets. Note in dual-stack mode IPv4 addresses are also valid. Example: *./perf/remote_thr tcp://[fe80::230:1bff:feb7:a209%eth0]:5555 1 100000* *IPv6 LINK-LOCAL ADDRESSES* The TCP stack isn't as forgiving as the UDP stack, when using link-local addresses, even if you only have one available, the scope identifier must be specified. Valid: *./perf/remote_thr tcp://[fe80::230:1bff:feb7:a209%eth0]:5555 1 100000* * * Invalid: *./perf/remote_thr tcp://[fe80::230:1bff:feb7:a209]:5555 1 100000* Assertion failed: s == retired_fd (tcp_connecter.cpp:184) Aborted -- Steve-o
From a4ad727c6549eedcddee72d40a0f850db3c5f298 Mon Sep 17 00:00:00 2001 From: Steven McCoy <[email protected]> Date: Mon, 22 Aug 2011 14:31:34 +0800 Subject: [PATCH] Binding upon a NIC name in IPv6 may resolve to multiple interfaces, use the IPv6 scope identifier to force the bind. Use SO_BINDTODEVICE on Linux to workaround lack of in6addr_any:scope_id support. Add support for IPv6 literal addresses. Signed-off-by: Steven McCoy <[email protected]> --- src/tcp_address.cpp | 46 ++++++++++++++++++++++++++++++++++++++-------- src/tcp_listener.cpp | 20 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/tcp_address.cpp b/src/tcp_address.cpp index 999c015..033449d 100644 --- a/src/tcp_address.cpp +++ b/src/tcp_address.cpp @@ -164,7 +164,8 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv4only_) zmq_assert (ifa != NULL); // Find the corresponding network interface. - bool found = false; + int found = 0; + uint32_t sin6_scope_id = 0; for (ifaddrs *ifp = ifa; ifp != NULL ;ifp = ifp->ifa_next) { if (ifp->ifa_addr == NULL) @@ -176,11 +177,19 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv4only_) || (!ipv4only_ && family == AF_INET6)) && !strcmp (nic_, ifp->ifa_name)) { + found++; memcpy (&address, ifp->ifa_addr, (family == AF_INET) ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); - found = true; - break; + // Duplicate interfaces only occur on IPv6 enabled adapters. + if (ipv4only_) + break; + // Save scope identifier for this adapter. + if (family == AF_INET6) { + sockaddr_in6 sin6; + memcpy (&sin6, ifp->ifa_addr, sizeof (sin6)); + sin6_scope_id = sin6.sin6_scope_id; + } } } @@ -192,6 +201,16 @@ int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv4only_) return -1; } + // Multiple matching interfaces, therefore return entire adapter. + if (found > 1) { + sockaddr_in6 sin6; + memset (&sin6, 0, sizeof (sin6)); + sin6.sin6_family = AF_INET6; + memcpy (&sin6.sin6_addr, &in6addr_any, sizeof (in6addr_any)); + sin6.sin6_scope_id = sin6_scope_id; + memcpy (&address, &sin6, sizeof (sin6)); + } + return 0; } @@ -248,11 +267,8 @@ int zmq::tcp_address_t::resolve_interface (char const *interface_, int rc = resolve_nic_name (interface_, ipv4only_); if (rc != 0 && errno != ENODEV) return rc; - if (rc == 0) { - zmq_assert (out_addrlen <= (socklen_t) sizeof (address)); - memcpy (&address, out_addr, out_addrlen); + if (rc == 0) return 0; - } // There's no such interface name. Assume literal address. #if defined ZMQ_HAVE_OPENVMS && defined __ia64 @@ -306,6 +322,8 @@ int zmq::tcp_address_t::resolve_interface (char const *interface_, int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv4only_) { // Set up the query. + char literal_addr[INET6_ADDRSTRLEN]; + const char *node = hostname_; addrinfo req; memset (&req, 0, sizeof (req)); @@ -325,10 +343,22 @@ int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv4only_) req.ai_flags |= AI_V4MAPPED; #endif + // IPv6 literal addresses must be surrounded by square brackets. + if (!ipv4only_ && hostname_[0] == '[') { + size_t hostnamelen = strlen (hostname_); + if (']' == hostname_[ hostnamelen - 1 ] && hostnamelen < INET6_ADDRSTRLEN) { + req.ai_family = AF_INET6; + req.ai_flags |= AI_NUMERICHOST; + memcpy (literal_addr, hostname_ + 1, hostnamelen - 2); + literal_addr[ hostnamelen - 2 ] = 0; + node = &literal_addr[0]; + } + } + // Resolve host name. Some of the error info is lost in case of error, // however, there's no way to report EAI errors via errno. addrinfo *res; - int rc = getaddrinfo (hostname_, NULL, &req, &res); + int rc = getaddrinfo (node, NULL, &req, &res); if (rc) { switch (rc) { case EAI_MEMORY: diff --git a/src/tcp_listener.cpp b/src/tcp_listener.cpp index e78a4a3..716176a 100644 --- a/src/tcp_listener.cpp +++ b/src/tcp_listener.cpp @@ -43,6 +43,10 @@ #include <fcntl.h> #endif +#ifdef ZMQ_HAVE_LINUX +#include <net/if.h> +#endif + #ifdef ZMQ_HAVE_OPENVMS #include <ioctl.h> #endif @@ -156,6 +160,22 @@ int zmq::tcp_listener_t::set_address (const char *addr_) if (address.family () == AF_INET6) enable_ipv4_mapping (s); +#ifdef ZMQ_HAVE_LINUX + // Force the IPv6 scope identifier by binding the socket to the adapter + // for NIC wildcard bindings. + if (address.family () == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)address.addr (); + if (sin6->sin6_scope_id != 0 && IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + char ifname[IF_NAMESIZE], + *rptr = if_indextoname (sin6->sin6_scope_id, ifname); + errno_assert (rptr != NULL); + // SO_BINDTODEVICE since Linux 2.2.11 requires CAP_NET_RAW permissions. + rc = setsockopt (s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen (ifname)); + errno_assert (rc == 0 || (rc == -1 && errno == EPERM)); + } + } +#endif + // Allow reusing of the address. int flag = 1; #ifdef ZMQ_HAVE_WINDOWS -- 1.7.4.1
_______________________________________________ zeromq-dev mailing list [email protected] http://lists.zeromq.org/mailman/listinfo/zeromq-dev
