From b8b65da51c196d87196776d724418bb86c921cb8 Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Wed, 26 May 2021 11:01:44 -0700 Subject: [PATCH] Don't bind temporary IPv6 addresses (macOS) --- osdep/Binder.hpp | 326 +++++++++++++++++++++++++++-------------------- 1 file changed, 188 insertions(+), 138 deletions(-) diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp index 00b7dd346..35b001012 100644 --- a/osdep/Binder.hpp +++ b/osdep/Binder.hpp @@ -22,37 +22,42 @@ #include #ifdef __WINDOWS__ +#include #include #include -#include -#include #include +#include #else -#include +#include #include +#include #include #include -#include #ifdef __LINUX__ -#include #include +#include #endif #endif -#include -#include -#include -#include -#include -#include -#include +#if defined(__APPLE__) && defined(TARGET_OS_MAC) +#include +#include +#include +#endif #include "../node/InetAddress.hpp" #include "../node/Mutex.hpp" #include "../node/Utils.hpp" - -#include "Phy.hpp" #include "OSUtils.hpp" +#include "Phy.hpp" + +#include +#include +#include +#include +#include +#include +#include // Period between refreshes of bindings #define ZT_BINDER_REFRESH_PERIOD 30000 @@ -73,32 +78,33 @@ namespace ZeroTier { * On OSes that do not support local port enumeration or where this is not * meaningful, this degrades to binding to wildcard. */ -class Binder -{ -private: - struct _Binding - { - _Binding() : udpSock((PhySocket *)0),tcpListenSock((PhySocket *)0) {} - PhySocket *udpSock; - PhySocket *tcpListenSock; +class Binder { + private: + struct _Binding { + _Binding() : udpSock((PhySocket*)0), tcpListenSock((PhySocket*)0) + { + } + PhySocket* udpSock; + PhySocket* tcpListenSock; InetAddress address; }; -public: - Binder() : _bindingCount(0) {} + public: + Binder() : _bindingCount(0) + { + } /** * Close all bound ports, should be called on shutdown * * @param phy Physical interface */ - template - void closeAll(Phy &phy) + template void closeAll(Phy& phy) { Mutex::Lock _l(_lock); - for(unsigned int b=0,c=_bindingCount;b template * @tparam INTERFACE_CHECKER Type for class containing shouldBindInterface() method */ - template - void refresh(Phy &phy,unsigned int *ports,unsigned int portCount,const std::vector explicitBind,INTERFACE_CHECKER &ifChecker) + template void refresh(Phy& phy, unsigned int* ports, unsigned int portCount, const std::vector explicitBind, INTERFACE_CHECKER& ifChecker) { - std::map localIfAddrs; - PhySocket *udps,*tcps; + std::map localIfAddrs; + PhySocket *udps, *tcps; Mutex::Lock _l(_lock); bool interfacesEnumerated = true; @@ -130,7 +135,7 @@ public: char aabuf[32768]; ULONG aalen = sizeof(aabuf); - if (GetAdaptersAddresses(AF_UNSPEC,GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER,(void *)0,reinterpret_cast(aabuf),&aalen) == NO_ERROR) { + if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, (void*)0, reinterpret_cast(aabuf), &aalen) == NO_ERROR) { PIP_ADAPTER_ADDRESSES a = reinterpret_cast(aabuf); while (a) { PIP_ADAPTER_UNICAST_ADDRESS ua = a->FirstUnicastAddress; @@ -165,7 +170,7 @@ public: interfacesEnumerated = false; } -#else // not __WINDOWS__ +#else // not __WINDOWS__ /* On Linux we use an alternative method if available since getifaddrs() * gets very slow when there are lots of network namespaces. This won't @@ -173,21 +178,21 @@ public: * embedded systems, so revert to getifaddrs() there. */ #ifdef __LINUX__ - char fn[256],tmp[256]; + char fn[256], tmp[256]; std::set ifnames; const unsigned long pid = (unsigned long)getpid(); // Get all device names - OSUtils::ztsnprintf(fn,sizeof(fn),"/proc/%lu/net/dev",pid); - FILE *procf = fopen(fn,"r"); + OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/dev", pid); + FILE* procf = fopen(fn, "r"); if (procf) { - while (fgets(tmp,sizeof(tmp),procf)) { + while (fgets(tmp, sizeof(tmp), procf)) { tmp[255] = 0; - char *saveptr = (char *)0; - for(char *f=Utils::stok(tmp," \t\r\n:|",&saveptr);(f);f=Utils::stok((char *)0," \t\r\n:|",&saveptr)) { - if ((strcmp(f,"Inter-") != 0)&&(strcmp(f,"face") != 0)&&(f[0] != 0)) + char* saveptr = (char*)0; + for (char* f = Utils::stok(tmp, " \t\r\n:|", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n:|", &saveptr)) { + if ((strcmp(f, "Inter-") != 0) && (strcmp(f, "face") != 0) && (f[0] != 0)) ifnames.insert(f); - break; // we only want the first field + break; // we only want the first field } } fclose(procf); @@ -197,39 +202,40 @@ public: } // Get IPv6 addresses (and any device names we don't already know) - OSUtils::ztsnprintf(fn,sizeof(fn),"/proc/%lu/net/if_inet6",pid); - procf = fopen(fn,"r"); + OSUtils::ztsnprintf(fn, sizeof(fn), "/proc/%lu/net/if_inet6", pid); + procf = fopen(fn, "r"); if (procf) { - while (fgets(tmp,sizeof(tmp),procf)) { + while (fgets(tmp, sizeof(tmp), procf)) { tmp[255] = 0; - char *saveptr = (char *)0; + char* saveptr = (char*)0; unsigned char ipbits[16]; - memset(ipbits,0,sizeof(ipbits)); - char *devname = (char *)0; + memset(ipbits, 0, sizeof(ipbits)); + char* devname = (char*)0; int n = 0; - for(char *f=Utils::stok(tmp," \t\r\n",&saveptr);(f);f=Utils::stok((char *)0," \t\r\n",&saveptr)) { - switch(n++) { - case 0: // IP in hex - Utils::unhex(f,32,ipbits,16); + for (char* f = Utils::stok(tmp, " \t\r\n", &saveptr); (f); f = Utils::stok((char*)0, " \t\r\n", &saveptr)) { + switch (n++) { + case 0: // IP in hex + Utils::unhex(f, 32, ipbits, 16); break; - case 5: // device name + case 5: // device name devname = f; break; } } if (devname) { ifnames.insert(devname); - InetAddress ip(ipbits,16,0); - if (ifChecker.shouldBindInterface(devname,ip)) { - switch(ip.ipScope()) { - default: break; + InetAddress ip(ipbits, 16, 0); + if (ifChecker.shouldBindInterface(devname, ip)) { + switch (ip.ipScope()) { + default: + break; case InetAddress::IP_SCOPE_PSEUDOPRIVATE: case InetAddress::IP_SCOPE_GLOBAL: case InetAddress::IP_SCOPE_SHARED: case InetAddress::IP_SCOPE_PRIVATE: - for(int x=0;x<(int)portCount;++x) { + for (int x = 0; x < (int)portCount; ++x) { ip.setPort(ports[x]); - localIfAddrs.insert(std::pair(ip,std::string(devname))); + localIfAddrs.insert(std::pair(ip, std::string(devname))); } break; } @@ -240,71 +246,108 @@ public: } // Get IPv4 addresses for each device - if (!ifnames.empty()) { - const int controlfd = (int)socket(AF_INET,SOCK_DGRAM,0); + if (! ifnames.empty()) { + const int controlfd = (int)socket(AF_INET, SOCK_DGRAM, 0); struct ifconf configuration; configuration.ifc_len = 0; configuration.ifc_buf = nullptr; - if (controlfd < 0) goto ip4_address_error; - if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) goto ip4_address_error; + if (controlfd < 0) + goto ip4_address_error; + if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) + goto ip4_address_error; configuration.ifc_buf = (char*)malloc(configuration.ifc_len); - if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) goto ip4_address_error; + if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) + goto ip4_address_error; - for (int i=0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i ++) { + for (int i = 0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i++) { struct ifreq& request = configuration.ifc_req[i]; struct sockaddr* addr = &request.ifr_ifru.ifru_addr; - if (addr->sa_family != AF_INET) continue; + if (addr->sa_family != AF_INET) + continue; std::string ifname = request.ifr_ifrn.ifrn_name; // name can either be just interface name or interface name followed by ':' and arbitrary label if (ifname.find(':') != std::string::npos) ifname = ifname.substr(0, ifname.find(':')); - InetAddress ip(&(((struct sockaddr_in *)addr)->sin_addr),4,0); + InetAddress ip(&(((struct sockaddr_in*)addr)->sin_addr), 4, 0); if (ifChecker.shouldBindInterface(ifname.c_str(), ip)) { - switch(ip.ipScope()) { - default: break; - case InetAddress::IP_SCOPE_PSEUDOPRIVATE: - case InetAddress::IP_SCOPE_GLOBAL: - case InetAddress::IP_SCOPE_SHARED: - case InetAddress::IP_SCOPE_PRIVATE: - for(int x=0;x<(int)portCount;++x) { - ip.setPort(ports[x]); - localIfAddrs.insert(std::pair(ip,ifname)); - } - break; + switch (ip.ipScope()) { + default: + break; + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_GLOBAL: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_PRIVATE: + for (int x = 0; x < (int)portCount; ++x) { + ip.setPort(ports[x]); + localIfAddrs.insert(std::pair(ip, ifname)); + } + break; } } } ip4_address_error: free(configuration.ifc_buf); - if (controlfd > 0) close(controlfd); + if (controlfd > 0) + close(controlfd); } - const bool gotViaProc = (!localIfAddrs.empty()); + const bool gotViaProc = (! localIfAddrs.empty()); #else const bool gotViaProc = false; #endif -#if !defined(ZT_SDK) || !defined(__ANDROID__) // getifaddrs() freeifaddrs() not available on Android - if (!gotViaProc) { - struct ifaddrs *ifatbl = (struct ifaddrs *)0; - struct ifaddrs *ifa; - if ((getifaddrs(&ifatbl) == 0)&&(ifatbl)) { +#if ! defined(ZT_SDK) || ! defined(__ANDROID__) // getifaddrs() freeifaddrs() not available on Android + if (! gotViaProc) { + struct ifaddrs* ifatbl = (struct ifaddrs*)0; + struct ifaddrs* ifa; + +#if defined(__APPLE__) + // set up an IPv6 socket so we can check the state of interfaces via SIOCGIFAFLAG_IN6 + int infoSock = socket(AF_INET6, SOCK_DGRAM, 0); +#endif + if ((getifaddrs(&ifatbl) == 0) && (ifatbl)) { ifa = ifatbl; while (ifa) { - if ((ifa->ifa_name)&&(ifa->ifa_addr)) { + if ((ifa->ifa_name) && (ifa->ifa_addr)) { InetAddress ip = *(ifa->ifa_addr); - if (ifChecker.shouldBindInterface(ifa->ifa_name,ip)) { - switch(ip.ipScope()) { - default: break; +#if defined(__APPLE__) && defined(TARGET_OS_MAC) + // Check if the address is an IPv6 Temporary Address, macOS version + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6* sa6 = (struct sockaddr_in6*)ifa->ifa_addr; + struct in6_ifreq ifr6; + memset(&ifr6, 0, sizeof(ifr6)); + strcpy(ifr6.ifr_name, ifa->ifa_name); + ifr6.ifr_ifru.ifru_addr = *sa6; + + int flags = 0; + if (ioctl(infoSock, SIOCGIFAFLAG_IN6, (unsigned long long)&ifr6) != -1) { + flags = ifr6.ifr_ifru.ifru_flags6; + } + + // if this is a temporary IPv6 address, skip to the next address + if (flags & IN6_IFF_TEMPORARY) { + char buf[64]; +#ifdef ZT_TRACE + fprintf(stderr, "skip binding to temporary IPv6 address: %s\n", ip.toIpString(buf)); +#endif + ifa = ifa->ifa_next; + continue; + } + } +#endif + if (ifChecker.shouldBindInterface(ifa->ifa_name, ip)) { + switch (ip.ipScope()) { + default: + break; case InetAddress::IP_SCOPE_PSEUDOPRIVATE: case InetAddress::IP_SCOPE_GLOBAL: case InetAddress::IP_SCOPE_SHARED: case InetAddress::IP_SCOPE_PRIVATE: - for(int x=0;x<(int)portCount;++x) { + for (int x = 0; x < (int)portCount; ++x) { ip.setPort(ports[x]); - localIfAddrs.insert(std::pair(ip,std::string(ifa->ifa_name))); + localIfAddrs.insert(std::pair(ip, std::string(ifa->ifa_name))); } break; } @@ -317,20 +360,24 @@ public: else { interfacesEnumerated = false; } +#if defined(__APPLE__) + close(infoSock); +#endif } #endif #endif - } else { - for(std::vector::const_iterator i(explicitBind.begin());i!=explicitBind.end();++i) - localIfAddrs.insert(std::pair(*i,std::string())); + } + else { + for (std::vector::const_iterator i(explicitBind.begin()); i != explicitBind.end(); ++i) + localIfAddrs.insert(std::pair(*i, std::string())); } // Default to binding to wildcard if we can't enumerate addresses - if (!interfacesEnumerated && localIfAddrs.empty()) { - for(int x=0;x<(int)portCount;++x) { - localIfAddrs.insert(std::pair(InetAddress((uint32_t)0,ports[x]),std::string())); - localIfAddrs.insert(std::pair(InetAddress((const void *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",16,ports[x]),std::string())); + if (! interfacesEnumerated && localIfAddrs.empty()) { + for (int x = 0; x < (int)portCount; ++x) { + localIfAddrs.insert(std::pair(InetAddress((uint32_t)0, ports[x]), std::string())); + localIfAddrs.insert(std::pair(InetAddress((const void*)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16, ports[x]), std::string())); } } @@ -338,35 +385,36 @@ public: _bindingCount = 0; // Save bindings that are still valid, close those that are not - for(unsigned int b=0;b::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + for (std::map::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) { linkIfNames.insert(ii->second); } - for (std::set::iterator si(linkIfNames.begin());si!=linkIfNames.end();) { + for (std::set::iterator si(linkIfNames.begin()); si != linkIfNames.end();) { bool bFoundMatch = false; - for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + for (std::map::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) { if (ii->second == *si) { bFoundMatch = true; break; } } - if (!bFoundMatch) { + if (! bFoundMatch) { linkIfNames.erase(si++); } else { @@ -375,7 +423,7 @@ public: } // Create new bindings for those not already bound - for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + for (std::map::const_iterator ii(localIfAddrs.begin()); ii != localIfAddrs.end(); ++ii) { unsigned int bi = 0; while (bi != _bindingCount) { if (_bindings[bi].address == ii->first) @@ -383,32 +431,33 @@ public: ++bi; } if (bi == _bindingCount) { - udps = phy.udpBind(reinterpret_cast(&(ii->first)),(void *)0,ZT_UDP_DESIRED_BUF_SIZE); - tcps = phy.tcpListen(reinterpret_cast(&(ii->first)),(void *)0); - if ((udps)&&(tcps)) { + udps = phy.udpBind(reinterpret_cast(&(ii->first)), (void*)0, ZT_UDP_DESIRED_BUF_SIZE); + tcps = phy.tcpListen(reinterpret_cast(&(ii->first)), (void*)0); + if ((udps) && (tcps)) { #ifdef __LINUX__ // Bind Linux sockets to their device so routes that we manage do not override physical routes (wish all platforms had this!) if (ii->second.length() > 0) { char tmp[256]; - Utils::scopy(tmp,sizeof(tmp),ii->second.c_str()); + Utils::scopy(tmp, sizeof(tmp), ii->second.c_str()); int fd = (int)Phy::getDescriptor(udps); if (fd >= 0) - setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,tmp,strlen(tmp)); + setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp)); fd = (int)Phy::getDescriptor(tcps); if (fd >= 0) - setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,tmp,strlen(tmp)); + setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, strlen(tmp)); } -#endif // __LINUX__ +#endif // __LINUX__ if (_bindingCount < ZT_BINDER_MAX_BINDINGS) { _bindings[_bindingCount].udpSock = udps; _bindings[_bindingCount].tcpListenSock = tcps; _bindings[_bindingCount].address = ii->first; - phy.setIfName(udps,(char*)ii->second.c_str(),(int)ii->second.length()); + phy.setIfName(udps, (char*)ii->second.c_str(), (int)ii->second.length()); ++_bindingCount; } - } else { - phy.close(udps,false); - phy.close(tcps,false); + } + else { + phy.close(udps, false); + phy.close(tcps, false); } } } @@ -421,7 +470,7 @@ public: { std::vector aa; Mutex::Lock _l(_lock); - for(unsigned int b=0,c=_bindingCount;b - inline bool udpSendAll(Phy &phy,const struct sockaddr_storage *addr,const void *data,unsigned int len,unsigned int ttl) + template inline bool udpSendAll(Phy& phy, const struct sockaddr_storage* addr, const void* data, unsigned int len, unsigned int ttl) { bool r = false; Mutex::Lock _l(_lock); - for(unsigned int b=0,c=_bindingCount;b linkIfNames; _Binding _bindings[ZT_BINDER_MAX_BINDINGS]; std::atomic _bindingCount; Mutex _lock; }; -} // namespace ZeroTier +} // namespace ZeroTier #endif