#include "precompiled.hpp" #include #include #include "macros.hpp" #include "stdint.hpp" #include "err.hpp" #include "ip.hpp" #ifndef ZMQ_HAVE_WINDOWS #include #include #include #include #include #include #include #include #endif #include "ip_resolver.hpp" int zmq::ip_addr_t::family () const { return generic.sa_family; } bool zmq::ip_addr_t::is_multicast () const { if (family () == AF_INET) { // IPv4 Multicast: address MSBs are 1110 // Range: 224.0.0.0 - 239.255.255.255 return IN_MULTICAST (ntohl (ipv4.sin_addr.s_addr)); } // IPv6 Multicast: ff00::/8 return IN6_IS_ADDR_MULTICAST (&ipv6.sin6_addr) != 0; } uint16_t zmq::ip_addr_t::port () const { if (family () == AF_INET6) { return ntohs (ipv6.sin6_port); } return ntohs (ipv4.sin_port); } const struct sockaddr *zmq::ip_addr_t::as_sockaddr () const { return &generic; } zmq::zmq_socklen_t zmq::ip_addr_t::sockaddr_len () const { return static_cast (family () == AF_INET6 ? sizeof (ipv6) : sizeof (ipv4)); } void zmq::ip_addr_t::set_port (uint16_t port_) { if (family () == AF_INET6) { ipv6.sin6_port = htons (port_); } else { ipv4.sin_port = htons (port_); } } // Construct an "ANY" address for the given family zmq::ip_addr_t zmq::ip_addr_t::any (int family_) { ip_addr_t addr; if (family_ == AF_INET) { sockaddr_in *ip4_addr = &addr.ipv4; memset (ip4_addr, 0, sizeof (*ip4_addr)); ip4_addr->sin_family = AF_INET; ip4_addr->sin_addr.s_addr = htonl (INADDR_ANY); } else if (family_ == AF_INET6) { sockaddr_in6 *ip6_addr = &addr.ipv6; memset (ip6_addr, 0, sizeof (*ip6_addr)); ip6_addr->sin6_family = AF_INET6; #ifdef ZMQ_HAVE_VXWORKS struct in6_addr newaddr = IN6ADDR_ANY_INIT; memcpy (&ip6_addr->sin6_addr, &newaddr, sizeof (in6_addr)); #else memcpy (&ip6_addr->sin6_addr, &in6addr_any, sizeof (in6addr_any)); #endif } else { assert (0 == "unsupported address family"); } return addr; } zmq::ip_resolver_options_t::ip_resolver_options_t () : _bindable_wanted (false), _nic_name_allowed (false), _ipv6_wanted (false), _port_expected (false), _dns_allowed (false), _path_allowed (false) { } zmq::ip_resolver_options_t & zmq::ip_resolver_options_t::bindable (bool bindable_) { _bindable_wanted = bindable_; return *this; } zmq::ip_resolver_options_t & zmq::ip_resolver_options_t::allow_nic_name (bool allow_) { _nic_name_allowed = allow_; return *this; } zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::ipv6 (bool ipv6_) { _ipv6_wanted = ipv6_; return *this; } // If true we expect that the host will be followed by a colon and a port // number or service name zmq::ip_resolver_options_t & zmq::ip_resolver_options_t::expect_port (bool expect_) { _port_expected = expect_; return *this; } zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::allow_dns (bool allow_) { _dns_allowed = allow_; return *this; } zmq::ip_resolver_options_t &zmq::ip_resolver_options_t::allow_path (bool allow_) { _path_allowed = allow_; return *this; } bool zmq::ip_resolver_options_t::bindable () { return _bindable_wanted; } bool zmq::ip_resolver_options_t::allow_nic_name () { return _nic_name_allowed; } bool zmq::ip_resolver_options_t::ipv6 () { return _ipv6_wanted; } bool zmq::ip_resolver_options_t::expect_port () { return _port_expected; } bool zmq::ip_resolver_options_t::allow_dns () { return _dns_allowed; } bool zmq::ip_resolver_options_t::allow_path () { return _path_allowed; } zmq::ip_resolver_t::ip_resolver_t (ip_resolver_options_t opts_) : _options (opts_) { } int zmq::ip_resolver_t::resolve (ip_addr_t *ip_addr_, const char *name_) { std::string addr; uint16_t port; if (_options.expect_port ()) { // We expect 'addr:port'. It's important to use str*r*chr to only get // the latest colon since IPv6 addresses use colons as delemiters. const char *delim = strrchr (name_, ':'); if (delim == NULL) { errno = EINVAL; return -1; } addr = std::string (name_, delim - name_); const std::string port_str = std::string (delim + 1); if (port_str == "*") { if (_options.bindable ()) { // Resolve wildcard to 0 to allow autoselection of port port = 0; } else { errno = EINVAL; return -1; } } else if (port_str == "0") { // Using "0" for a bind address is equivalent to using "*". For a // connectable address it could be used to connect to port 0. port = 0; } else { // Parse the port number (0 is not a valid port). port = static_cast (atoi (port_str.c_str ())); if (port == 0) { errno = EINVAL; return -1; } } } else { addr = std::string (name_); port = 0; } // Check if path is allowed in ip address, if allowed it must be truncated if (_options.allow_path ()) { const size_t pos = addr.find ('/'); if (pos != std::string::npos) addr = addr.substr (0, pos); } // Trim any square brackets surrounding the address. Used for // IPv6 addresses to remove the confusion with the port // delimiter. // TODO Should we validate that the brackets are present if // 'addr' contains ':' ? const size_t brackets_length = 2; if (addr.size () >= brackets_length && addr[0] == '[' && addr[addr.size () - 1] == ']') { addr = addr.substr (1, addr.size () - brackets_length); } // Look for an interface name / zone_id in the address // Reference: https://tools.ietf.org/html/rfc4007 const std::size_t pos = addr.rfind ('%'); uint32_t zone_id = 0; if (pos != std::string::npos) { std::string if_str = addr.substr (pos + 1); if (if_str.empty ()) { errno = EINVAL; return -1; } addr = addr.substr (0, pos); if (isalpha (if_str.at (0))) { zone_id = do_if_nametoindex (if_str.c_str ()); } else { zone_id = static_cast (atoi (if_str.c_str ())); } if (zone_id == 0) { errno = EINVAL; return -1; } } bool resolved = false; const char *addr_str = addr.c_str (); if (_options.bindable () && addr == "*") { // Return an ANY address *ip_addr_ = ip_addr_t::any (_options.ipv6 () ? AF_INET6 : AF_INET); resolved = true; } if (!resolved && _options.allow_nic_name ()) { // Try to resolve the string as a NIC name. const int rc = resolve_nic_name (ip_addr_, addr_str); if (rc == 0) { resolved = true; } else if (errno != ENODEV) { return rc; } } if (!resolved) { const int rc = resolve_getaddrinfo (ip_addr_, addr_str); if (rc != 0) { return rc; } resolved = true; } // Store the port into the structure. We could get 'getaddrinfo' to do it // for us but since we don't resolve service names it's a bit overkill and // we'd still have to do it manually when the address is resolved by // 'resolve_nic_name' ip_addr_->set_port (port); if (ip_addr_->family () == AF_INET6) { ip_addr_->ipv6.sin6_scope_id = zone_id; } assert (resolved == true); return 0; } int zmq::ip_resolver_t::resolve_getaddrinfo (ip_addr_t *ip_addr_, const char *addr_) { #if defined ZMQ_HAVE_OPENVMS && defined __ia64 __addrinfo64 *res = NULL; __addrinfo64 req; #else addrinfo *res = NULL; addrinfo req; #endif memset (&req, 0, sizeof (req)); // Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for // IPv4-in-IPv6 addresses. req.ai_family = _options.ipv6 () ? AF_INET6 : AF_INET; // Arbitrary, not used in the output, but avoids duplicate results. req.ai_socktype = SOCK_STREAM; req.ai_flags = 0; if (_options.bindable ()) { req.ai_flags |= AI_PASSIVE; } if (!_options.allow_dns ()) { req.ai_flags |= AI_NUMERICHOST; } #if defined AI_V4MAPPED // In this API we only require IPv4-mapped addresses when // no native IPv6 interfaces are available (~AI_ALL). // This saves an additional DNS roundtrip for IPv4 addresses. if (req.ai_family == AF_INET6) { req.ai_flags |= AI_V4MAPPED; } #endif // Resolve the literal address. Some of the error info is lost in case // of error, however, there's no way to report EAI errors via errno. int rc = do_getaddrinfo (addr_, NULL, &req, &res); #if defined AI_V4MAPPED // Some OS do have AI_V4MAPPED defined but it is not supported in getaddrinfo() // returning EAI_BADFLAGS. Detect this and retry if (rc == EAI_BADFLAGS && (req.ai_flags & AI_V4MAPPED)) { req.ai_flags &= ~AI_V4MAPPED; rc = do_getaddrinfo (addr_, NULL, &req, &res); } #endif #if defined ZMQ_HAVE_WINDOWS // Resolve specific case on Windows platform when using IPv4 address // with ZMQ_IPv6 socket option. if ((req.ai_family == AF_INET6) && (rc == WSAHOST_NOT_FOUND)) { req.ai_family = AF_INET; rc = do_getaddrinfo (addr_, NULL, &req, &res); } #endif if (rc) { switch (rc) { case EAI_MEMORY: errno = ENOMEM; break; default: if (_options.bindable ()) { errno = ENODEV; } else { errno = EINVAL; } break; } return -1; } // Use the first result. zmq_assert (res != NULL); zmq_assert (static_cast (res->ai_addrlen) <= sizeof (*ip_addr_)); memcpy (ip_addr_, res->ai_addr, res->ai_addrlen); // Cleanup getaddrinfo after copying the possibly referenced result. do_freeaddrinfo (res); return 0; } #ifdef ZMQ_HAVE_SOLARIS #include // On Solaris platform, network interface name can be queried by ioctl. int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_) { // Create a socket. const int fd = open_socket (AF_INET, SOCK_DGRAM, 0); errno_assert (fd != -1); // Retrieve number of interfaces. lifnum ifn; ifn.lifn_family = AF_INET; ifn.lifn_flags = 0; int rc = ioctl (fd, SIOCGLIFNUM, (char *) &ifn); errno_assert (rc != -1); // Allocate memory to get interface names. const size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count; char *ifr = (char *) malloc (ifr_size); alloc_assert (ifr); // Retrieve interface names. lifconf ifc; ifc.lifc_family = AF_INET; ifc.lifc_flags = 0; ifc.lifc_len = ifr_size; ifc.lifc_buf = ifr; rc = ioctl (fd, SIOCGLIFCONF, (char *) &ifc); errno_assert (rc != -1); // Find the interface with the specified name and AF_INET family. bool found = false; lifreq *ifrp = ifc.lifc_req; for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq)); n++, ifrp++) { if (!strcmp (nic_, ifrp->lifr_name)) { rc = ioctl (fd, SIOCGLIFADDR, (char *) ifrp); errno_assert (rc != -1); if (ifrp->lifr_addr.ss_family == AF_INET) { ip_addr_->ipv4 = *(sockaddr_in *) &ifrp->lifr_addr; found = true; break; } } } // Clean-up. free (ifr); close (fd); if (!found) { errno = ENODEV; return -1; } return 0; } #elif defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX \ || defined ZMQ_HAVE_ANDROID || defined ZMQ_HAVE_VXWORKS #include #ifdef ZMQ_HAVE_VXWORKS #include #endif int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_) { #if defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX // IPv6 support not implemented for AIX or HP/UX. if (_options.ipv6 ()) { errno = ENODEV; return -1; } #endif // Create a socket. const int sd = open_socket (_options.ipv6 () ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); errno_assert (sd != -1); struct ifreq ifr; // Copy interface name for ioctl get. strncpy (ifr.ifr_name, nic_, sizeof (ifr.ifr_name)); // Fetch interface address. const int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (ifr)); // Clean up. close (sd); if (rc == -1) { errno = ENODEV; return -1; } const int family = ifr.ifr_addr.sa_family; if (family == (_options.ipv6 () ? AF_INET6 : AF_INET) && !strcmp (nic_, ifr.ifr_name)) { memcpy (ip_addr_, &ifr.ifr_addr, (family == AF_INET) ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); } else { errno = ENODEV; return -1; } return 0; } #elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD \ || defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD \ || defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD \ || defined ZMQ_HAVE_DRAGONFLY || defined ZMQ_HAVE_GNU) \ && defined ZMQ_HAVE_IFADDRS) #include // On these platforms, network interface name can be queried // using getifaddrs function. int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_) { // Get the addresses. ifaddrs *ifa = NULL; int rc = 0; const int max_attempts = 10; const int backoff_msec = 1; for (int i = 0; i < max_attempts; i++) { rc = getifaddrs (&ifa); if (rc == 0 || (rc < 0 && errno != ECONNREFUSED)) break; usleep ((backoff_msec << i) * 1000); } if (rc != 0 && ((errno == EINVAL) || (errno == EOPNOTSUPP))) { // Windows Subsystem for Linux compatibility errno = ENODEV; return -1; } errno_assert (rc == 0); zmq_assert (ifa != NULL); // Find the corresponding network interface. bool found = false; for (const ifaddrs *ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) { if (ifp->ifa_addr == NULL) continue; const int family = ifp->ifa_addr->sa_family; if (family == (_options.ipv6 () ? AF_INET6 : AF_INET) && !strcmp (nic_, ifp->ifa_name)) { memcpy (ip_addr_, ifp->ifa_addr, (family == AF_INET) ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); found = true; break; } } // Clean-up; freeifaddrs (ifa); if (!found) { errno = ENODEV; return -1; } return 0; } #elif (defined ZMQ_HAVE_WINDOWS) #include int zmq::ip_resolver_t::get_interface_name (unsigned long index_, char **dest_) const { #ifdef ZMQ_HAVE_WINDOWS_UWP char *buffer = (char *) malloc (1024); #else char *buffer = static_cast (malloc (IF_MAX_STRING_SIZE)); #endif alloc_assert (buffer); char *if_name_result = NULL; #if _WIN32_WINNT > _WIN32_WINNT_WINXP && !defined ZMQ_HAVE_WINDOWS_UWP if_name_result = if_indextoname (index_, buffer); #endif if (if_name_result == NULL) { free (buffer); return -1; } *dest_ = buffer; return 0; } int zmq::ip_resolver_t::wchar_to_utf8 (const WCHAR *src_, char **dest_) const { int rc; const int buffer_len = WideCharToMultiByte (CP_UTF8, 0, src_, -1, NULL, 0, NULL, 0); char *buffer = static_cast (malloc (buffer_len)); alloc_assert (buffer); rc = WideCharToMultiByte (CP_UTF8, 0, src_, -1, buffer, buffer_len, NULL, 0); if (rc == 0) { free (buffer); return -1; } *dest_ = buffer; return 0; } int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_) { int rc; bool found = false; const int max_attempts = 10; int iterations = 0; IP_ADAPTER_ADDRESSES *addresses; unsigned long out_buf_len = sizeof (IP_ADAPTER_ADDRESSES); do { addresses = static_cast (malloc (out_buf_len)); alloc_assert (addresses); rc = GetAdaptersAddresses (AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, addresses, &out_buf_len); if (rc == ERROR_BUFFER_OVERFLOW) { free (addresses); addresses = NULL; } else { break; } iterations++; } while ((rc == ERROR_BUFFER_OVERFLOW) && (iterations < max_attempts)); if (rc == 0) { for (const IP_ADAPTER_ADDRESSES *current_addresses = addresses; current_addresses; current_addresses = current_addresses->Next) { char *if_name = NULL; char *if_friendly_name = NULL; const int str_rc1 = get_interface_name (current_addresses->IfIndex, &if_name); const int str_rc2 = wchar_to_utf8 (current_addresses->FriendlyName, &if_friendly_name); // Find a network adapter by its "name" or "friendly name" if (((str_rc1 == 0) && (!strcmp (nic_, if_name))) || ((str_rc2 == 0) && (!strcmp (nic_, if_friendly_name)))) { // Iterate over all unicast addresses bound to the current network interface for (const IP_ADAPTER_UNICAST_ADDRESS *current_unicast_address = current_addresses->FirstUnicastAddress; current_unicast_address; current_unicast_address = current_unicast_address->Next) { const ADDRESS_FAMILY family = current_unicast_address->Address.lpSockaddr->sa_family; if (family == (_options.ipv6 () ? AF_INET6 : AF_INET)) { memcpy ( ip_addr_, current_unicast_address->Address.lpSockaddr, (family == AF_INET) ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); found = true; break; } } if (found) break; } if (str_rc1 == 0) free (if_name); if (str_rc2 == 0) free (if_friendly_name); } free (addresses); } if (!found) { errno = ENODEV; return -1; } return 0; } #else // On other platforms we assume there are no sane interface names. int zmq::ip_resolver_t::resolve_nic_name (ip_addr_t *ip_addr_, const char *nic_) { LIBZMQ_UNUSED (ip_addr_); LIBZMQ_UNUSED (nic_); errno = ENODEV; return -1; } #endif int zmq::ip_resolver_t::do_getaddrinfo (const char *node_, const char *service_, const struct addrinfo *hints_, struct addrinfo **res_) { return getaddrinfo (node_, service_, hints_, res_); } void zmq::ip_resolver_t::do_freeaddrinfo (struct addrinfo *res_) { freeaddrinfo (res_); } unsigned int zmq::ip_resolver_t::do_if_nametoindex (const char *ifname_) { #ifdef HAVE_IF_NAMETOINDEX return if_nametoindex (ifname_); #else LIBZMQ_UNUSED (ifname_); // The function 'if_nametoindex' is not supported on Windows XP. // If we are targeting XP using a vxxx_xp toolset then fail. // This is brutal as this code could be run on later windows clients // meaning the IPv6 zone_id cannot have an interface name. // This could be fixed with a runtime check. return 0; #endif }