/* * Copyright (c)2013-2021 ZeroTier, Inc. * * Use of this software is governed by the Business Source License included * in the LICENSE.TXT file in the project's root directory. * * Change Date: 2026-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by version 2.0 of the Apache License. */ /****/ #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x06010000 #endif #include "GoGlue.h" #include "../core/Constants.hpp" #include "../core/InetAddress.hpp" #include "../core/Node.hpp" #include "../core/Utils.hpp" #include "../core/MAC.hpp" #include "../core/Address.hpp" #include "../core/Containers.hpp" #include "../core/Certificate.hpp" #include "../osdep/OSUtils.hpp" #include "../osdep/EthernetTap.hpp" #ifndef __WINDOWS__ #include #include #include #include #include #include #include #include #ifdef __BSD__ #include #endif #include #include #ifdef __LINUX__ #ifndef IPV6_DONTFRAG #define IPV6_DONTFRAG 62 #endif #endif #endif // !__WINDOWS__ #include #include #include #include #ifdef __WINDOWS__ #define SETSOCKOPT_FLAG_TYPE BOOL #define SETSOCKOPT_FLAG_TRUE TRUE #define SETSOCKOPT_FLAG_FALSE FALSE #else #define SETSOCKOPT_FLAG_TYPE int #define SETSOCKOPT_FLAG_TRUE 1 #define SETSOCKOPT_FLAG_FALSE 0 #endif #ifndef MSG_DONTWAIT #define MSG_DONTWAIT 0 #endif using namespace ZeroTier; struct ZT_GoNodeThread { String ip; int port; int af; bool primary; std::atomic< bool > run; std::thread thr; }; struct ZT_GoNode_Impl { void *goUserPtr; Node *node; volatile int64_t nextBackgroundTaskDeadline; String path; std::atomic< bool > run; Map< ZT_SOCKET, ZT_GoNodeThread > threads; Map< uint64_t, std::shared_ptr< EthernetTap > > taps; std::mutex threads_l; std::mutex taps_l; std::thread backgroundTaskThread; }; static const String defaultHomePath(OSUtils::platformDefaultHomePath()); const char *const ZT_PLATFORM_DEFAULT_HOMEPATH = defaultHomePath.c_str(); // These are implemented in Go code. extern "C" int goPathCheckFunc(void *, const ZT_Identity *, int, const void *, int); extern "C" int goPathLookupFunc(void *, uint64_t, int, const ZT_Identity *, int *, uint8_t [16], int *); extern "C" void goStateObjectPutFunc(void *, int, const uint64_t *, const void *, int); extern "C" int goStateObjectGetFunc(void *, int, const uint64_t *, void **); extern "C" void goVirtualNetworkConfigFunc(void *, ZT_GoTap *, uint64_t, int, const ZT_VirtualNetworkConfig *); extern "C" void goZtEvent(void *, int, const void *); extern "C" void goHandleTapAddedMulticastGroup(void *, ZT_GoTap *, uint64_t, uint64_t, uint32_t); extern "C" void goHandleTapRemovedMulticastGroup(void *, ZT_GoTap *, uint64_t, uint64_t, uint32_t); static void ZT_GoNode_VirtualNetworkConfigFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t nwid, void **nptr, enum ZT_VirtualNetworkConfigOperation op, const ZT_VirtualNetworkConfig *cfg) { goVirtualNetworkConfigFunc(reinterpret_cast(uptr)->goUserPtr, reinterpret_cast(*nptr), nwid, op, cfg); } static void ZT_GoNode_VirtualNetworkFrameFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t nwid, void **nptr, uint64_t srcMac, uint64_t destMac, unsigned int etherType, unsigned int vlanId, const void *data, unsigned int len) { if (*nptr) reinterpret_cast(*nptr)->put(MAC(srcMac), MAC(destMac), etherType, data, len); } static void ZT_GoNode_EventCallback( ZT_Node *node, void *uptr, void *tptr, enum ZT_Event et, const void *data) { goZtEvent(reinterpret_cast(uptr)->goUserPtr, et, data); } static void ZT_GoNode_StatePutFunction( ZT_Node *node, void *uptr, void *tptr, enum ZT_StateObjectType objType, const uint64_t id[2], const void *data, int len) { goStateObjectPutFunc( reinterpret_cast(uptr)->goUserPtr, objType, id, data, len); } static void _freeFunc(void *p) { if (p) free(p); } static int ZT_GoNode_StateGetFunction( ZT_Node *node, void *uptr, void *tptr, enum ZT_StateObjectType objType, const uint64_t id[2], void **data, void (**freeFunc)(void *)) { *freeFunc = &_freeFunc; return goStateObjectGetFunc( reinterpret_cast(uptr)->goUserPtr, (int)objType, id, data); } static ZT_INLINE void doUdpSend(ZT_SOCKET sock, const struct sockaddr_storage *addr, const void *data, const unsigned int len, const unsigned int ipTTL) { switch (addr->ss_family) { case AF_INET: if (unlikely((ipTTL > 0) && (ipTTL < 255))) { #ifdef __WINDOWS__ DWORD tmp = (DWORD)ipTTL; #else int tmp = (int)ipTTL; #endif setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast(&tmp), sizeof(tmp)); sendto(sock, reinterpret_cast(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in)); tmp = 255; setsockopt(sock, IPPROTO_IP, IP_TTL, reinterpret_cast(&tmp), sizeof(tmp)); } else { sendto(sock, reinterpret_cast(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in)); } break; case AF_INET6: // The ipTTL option isn't currently used with IPv6. It's only used // with IPv4 "firewall opener" / "NAT buster" preamble packets as part // of IPv4 NAT traversal. sendto(sock, reinterpret_cast(data), len, MSG_DONTWAIT, (const sockaddr *)addr, sizeof(struct sockaddr_in6)); break; } } static int ZT_GoNode_WirePacketSendFunction( ZT_Node *node, void *uptr, void *tptr, int64_t localSocket, const struct sockaddr_storage *addr, const void *data, unsigned int len, unsigned int ipTTL) { if (likely(localSocket > 0)) { doUdpSend((ZT_SOCKET)localSocket, addr, data, len, ipTTL); } else { ZT_GoNode *const gn = reinterpret_cast(uptr); std::lock_guard< std::mutex > l(gn->threads_l); for (auto t = gn->threads.begin(); t != gn->threads.end(); ++t) { if ((t->second.af == addr->ss_family) && (t->second.primary)) { doUdpSend(t->first, addr, data, len, ipTTL); break; } } } return 0; } static int ZT_GoNode_PathCheckFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t ztAddress, const ZT_Identity *id, int64_t localSocket, const struct sockaddr_storage *sa) { switch (sa->ss_family) { case AF_INET: return goPathCheckFunc( reinterpret_cast(uptr)->goUserPtr, id, AF_INET, &(reinterpret_cast(sa)->sin_addr.s_addr), Utils::ntoh((uint16_t)reinterpret_cast(sa)->sin_port)); case AF_INET6: return goPathCheckFunc( reinterpret_cast(uptr)->goUserPtr, id, AF_INET6, reinterpret_cast(sa)->sin6_addr.s6_addr, Utils::ntoh((uint16_t)reinterpret_cast(sa)->sin6_port)); } return 0; } static int ZT_GoNode_PathLookupFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t ztAddress, const ZT_Identity *id, int desiredAddressFamily, struct sockaddr_storage *sa) { int family = 0; uint8_t ip[16]; int port = 0; const int result = goPathLookupFunc( reinterpret_cast(uptr)->goUserPtr, ztAddress, desiredAddressFamily, id, &family, ip, &port ); if (result != 0) { switch (family) { case AF_INET: reinterpret_cast(sa)->sin_family = AF_INET; memcpy(&(reinterpret_cast(sa)->sin_addr.s_addr), ip, 4); reinterpret_cast(sa)->sin_port = Utils::hton((uint16_t)port); return 1; case AF_INET6: reinterpret_cast(sa)->sin6_family = AF_INET6; memcpy(reinterpret_cast(sa)->sin6_addr.s6_addr, ip, 16); reinterpret_cast(sa)->sin6_port = Utils::hton((uint16_t)port); return 1; } } return 0; } extern "C" ZT_GoNode *ZT_GoNode_new(const char *workingPath, uintptr_t userPtr) { try { struct ZT_Node_Callbacks cb; cb.statePutFunction = &ZT_GoNode_StatePutFunction; cb.stateGetFunction = &ZT_GoNode_StateGetFunction; cb.wirePacketSendFunction = &ZT_GoNode_WirePacketSendFunction; cb.virtualNetworkFrameFunction = &ZT_GoNode_VirtualNetworkFrameFunction; cb.virtualNetworkConfigFunction = &ZT_GoNode_VirtualNetworkConfigFunction; cb.eventCallback = &ZT_GoNode_EventCallback; cb.pathCheckFunction = &ZT_GoNode_PathCheckFunction; cb.pathLookupFunction = &ZT_GoNode_PathLookupFunction; ZT_GoNode_Impl *gn = new ZT_GoNode_Impl; const int64_t now = OSUtils::now(); gn->goUserPtr = reinterpret_cast(userPtr); gn->node = new Node(reinterpret_cast(gn), nullptr, &cb, now); gn->nextBackgroundTaskDeadline = now; gn->path = workingPath; gn->run = true; gn->backgroundTaskThread = std::thread([gn] { int64_t lastCheckedTaps = 0; while (gn->run) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); const int64_t now = OSUtils::now(); if (now >= gn->nextBackgroundTaskDeadline) gn->node->processBackgroundTasks(nullptr, now, &(gn->nextBackgroundTaskDeadline)); if ((now - lastCheckedTaps) > 10000) { lastCheckedTaps = now; std::vector< MulticastGroup > added, removed; std::lock_guard< std::mutex > tl(gn->taps_l); for (auto t = gn->taps.begin(); t != gn->taps.end(); ++t) { added.clear(); removed.clear(); t->second->scanMulticastGroups(added, removed); for (auto g = added.begin(); g != added.end(); ++g) goHandleTapAddedMulticastGroup(gn, (ZT_GoTap *)t->second.get(), t->first, g->mac().toInt(), g->adi()); for (auto g = removed.begin(); g != removed.end(); ++g) goHandleTapRemovedMulticastGroup(gn, (ZT_GoTap *)t->second.get(), t->first, g->mac().toInt(), g->adi()); } } } }); return gn; } catch (...) { fprintf(stderr, "FATAL: unable to create new instance of Node (out of memory?)" ZT_EOL_S); exit(1); } } extern "C" void ZT_GoNode_delete(ZT_GoNode *gn) { gn->run = false; gn->threads_l.lock(); for (auto t = gn->threads.begin(); t != gn->threads.end(); ++t) { t->second.run = false; #ifdef __WINDOWS__ shutdown(t->first, SD_BOTH); closesocket(t->first); #else shutdown(t->first, SHUT_RDWR); close(t->first); #endif t->second.thr.join(); } gn->threads_l.unlock(); gn->taps_l.lock(); for (auto t = gn->taps.begin(); t != gn->taps.end(); ++t) gn->node->leave(t->first, nullptr, nullptr); gn->taps.clear(); gn->taps_l.unlock(); gn->backgroundTaskThread.join(); gn->node->shutdown(nullptr); delete gn->node; delete gn; } extern "C" ZT_Node *ZT_GoNode_getNode(ZT_GoNode *gn) { return gn->node; } static void setCommonUdpSocketSettings(ZT_SOCKET udpSock, const char *dev) { int bufSize = 1048576; while (bufSize > 131072) { if (setsockopt(udpSock, SOL_SOCKET, SO_RCVBUF, (const char *)&bufSize, sizeof(bufSize)) == 0) break; bufSize -= 131072; } bufSize = 1048576; while (bufSize > 131072) { if (setsockopt(udpSock, SOL_SOCKET, SO_SNDBUF, (const char *)&bufSize, sizeof(bufSize)) == 0) break; bufSize -= 131072; } SETSOCKOPT_FLAG_TYPE fl; #ifdef SO_REUSEPORT fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock, SOL_SOCKET, SO_REUSEPORT, &fl, sizeof(fl)); #endif #ifndef __LINUX__ // linux wants just SO_REUSEPORT fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&fl), sizeof(fl)); #endif fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&fl), sizeof(fl)); #ifdef IP_DONTFRAG fl = SETSOCKOPT_FLAG_FALSE; setsockopt(udpSock,IPPROTO_IP,IP_DONTFRAG,&fl,sizeof(fl)); #endif #ifdef IP_MTU_DISCOVER fl = SETSOCKOPT_FLAG_FALSE; setsockopt(udpSock,IPPROTO_IP,IP_MTU_DISCOVER,&fl,sizeof(fl)); #endif #ifdef SO_BINDTODEVICE if ((dev)&&(strlen(dev))) setsockopt(udpSock,SOL_SOCKET,SO_BINDTODEVICE,dev,strlen(dev)); #endif #if defined(__BSD__) && defined(IP_BOUND_IF) if ((dev) && (strlen(dev))) { int idx = if_nametoindex(dev); if (idx != 0) setsockopt(udpSock, IPPROTO_IP, IP_BOUND_IF, (void *)&idx, sizeof(idx)); } #endif } extern "C" int ZT_GoNode_phyStartListen(ZT_GoNode *gn, const char *dev, const char *ip, const int port, const int primary) { if (strchr(ip, ':')) { struct sockaddr_in6 in6; memset(&in6, 0, sizeof(in6)); in6.sin6_family = AF_INET6; if (inet_pton(AF_INET6, ip, &(in6.sin6_addr)) <= 0) return errno; in6.sin6_port = htons((uint16_t)port); ZT_SOCKET udpSock = socket(AF_INET6, SOCK_DGRAM, 0); if (udpSock == ZT_INVALID_SOCKET) return errno; setCommonUdpSocketSettings(udpSock, dev); SETSOCKOPT_FLAG_TYPE fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&fl, sizeof(fl)); #ifdef IPV6_DONTFRAG fl = SETSOCKOPT_FLAG_FALSE; setsockopt(udpSock, IPPROTO_IPV6, IPV6_DONTFRAG, reinterpret_cast(&fl), sizeof(fl)); #endif if (bind(udpSock, reinterpret_cast(&in6), sizeof(in6)) != 0) return errno; { std::lock_guard< std::mutex > l(gn->threads_l); ZT_GoNodeThread &gnt = gn->threads[udpSock]; gnt.ip = ip; gnt.port = port; gnt.af = AF_INET6; gnt.primary = (primary != 0); gnt.run = true; gnt.thr = std::thread([udpSock, gn, &gnt] { struct sockaddr_in6 in6; socklen_t salen; while (gnt.run) { salen = sizeof(in6); void *buf = ZT_getBuffer(); if (buf) { int s = (int)recvfrom(udpSock, reinterpret_cast(buf), 16384, 0, reinterpret_cast(&in6), &salen); if (s > 0) { ZT_Node_processWirePacket( reinterpret_cast(gn->node), nullptr, OSUtils::now(), (int64_t)udpSock, reinterpret_cast(&in6), buf, (unsigned int)s, 1, &(gn->nextBackgroundTaskDeadline)); } else { ZT_freeBuffer(buf); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } } }); } } else { struct sockaddr_in in; memset(&in, 0, sizeof(in)); in.sin_family = AF_INET; if (inet_pton(AF_INET, ip, &(in.sin_addr)) <= 0) return errno; in.sin_port = htons((uint16_t)port); ZT_SOCKET udpSock = socket(AF_INET, SOCK_DGRAM, 0); if (udpSock == ZT_INVALID_SOCKET) return errno; setCommonUdpSocketSettings(udpSock, dev); #ifdef SO_NO_CHECK SETSOCKOPT_FLAG_TYPE fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock,SOL_SOCKET,SO_NO_CHECK,&fl,sizeof(fl)); #endif if (bind(udpSock, reinterpret_cast(&in), sizeof(in)) != 0) return errno; { std::lock_guard< std::mutex > l(gn->threads_l); ZT_GoNodeThread &gnt = gn->threads[udpSock]; gnt.ip = ip; gnt.port = port; gnt.af = AF_INET6; gnt.primary = (primary != 0); gnt.run = true; gnt.thr = std::thread([udpSock, gn, &gnt] { struct sockaddr_in in4; socklen_t salen; while (gnt.run) { salen = sizeof(in4); void *buf = ZT_getBuffer(); if (buf) { int s = (int)recvfrom(udpSock, reinterpret_cast(buf), sizeof(buf), 0, reinterpret_cast(&in4), &salen); if (s > 0) { ZT_Node_processWirePacket( reinterpret_cast(gn->node), nullptr, OSUtils::now(), (int64_t)udpSock, reinterpret_cast(&in4), buf, (unsigned int)s, 1, &(gn->nextBackgroundTaskDeadline)); } else { ZT_freeBuffer(buf); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } } }); } } return 0; } extern "C" int ZT_GoNode_phyStopListen(ZT_GoNode *gn, const char *dev, const char *ip, const int port) { { std::lock_guard< std::mutex > l(gn->threads_l); for (auto t = gn->threads.begin(); t != gn->threads.end();) { if ((t->second.ip == ip) && (t->second.port == port)) { t->second.run = false; #ifdef __WINDOWS__ shutdown(t->first, SD_BOTH); closesocket(t->first); #else shutdown(t->first, SHUT_RDWR); close(t->first); #endif t->second.thr.join(); gn->threads.erase(t++); } else ++t; } } return 0; } static void tapFrameHandler(void *uptr, void *tptr, uint64_t nwid, const MAC &from, const MAC &to, unsigned int etherType, unsigned int vlanId, const void *data, unsigned int len) { ZT_Node_processVirtualNetworkFrame( reinterpret_cast(reinterpret_cast(uptr)->node), tptr, OSUtils::now(), nwid, from.toInt(), to.toInt(), etherType, vlanId, data, len, 0, &(reinterpret_cast(uptr)->nextBackgroundTaskDeadline)); } extern "C" ZT_GoTap *ZT_GoNode_join(ZT_GoNode *gn, uint64_t nwid, const ZT_Fingerprint *const controllerFingerprint) { try { std::lock_guard< std::mutex > l(gn->taps_l); auto existingTap = gn->taps.find(nwid); if (existingTap != gn->taps.end()) return (ZT_GoTap *)existingTap->second.get(); char tmp[256]; OSUtils::ztsnprintf(tmp, sizeof(tmp), "ZeroTier Network %.16llx", (unsigned long long)nwid); std::shared_ptr< EthernetTap > tap(EthernetTap::newInstance(nullptr, gn->path.c_str(), MAC(Address(gn->node->address()), nwid), ZT_DEFAULT_MTU, 0, nwid, tmp, &tapFrameHandler, gn)); if (!tap) return nullptr; gn->taps[nwid] = tap; gn->node->join(nwid, controllerFingerprint, tap.get(), nullptr); return (ZT_GoTap *)tap.get(); } catch (...) { return nullptr; } } extern "C" void ZT_GoNode_leave(ZT_GoNode *gn, uint64_t nwid) { std::lock_guard< std::mutex > l(gn->taps_l); auto existingTap = gn->taps.find(nwid); if (existingTap != gn->taps.end()) { gn->node->leave(nwid, nullptr, nullptr); gn->taps.erase(existingTap); } } extern "C" void ZT_GoTap_setEnabled(ZT_GoTap *tap, int enabled) { reinterpret_cast(tap)->setEnabled(enabled != 0); } extern "C" int ZT_GoTap_addIp(ZT_GoTap *tap, int af, const void *ip, int netmaskBits) { switch (af) { case AF_INET: return (reinterpret_cast(tap)->addIp(InetAddress(ip, 4, (unsigned int)netmaskBits)) ? 1 : 0); case AF_INET6: return (reinterpret_cast(tap)->addIp(InetAddress(ip, 16, (unsigned int)netmaskBits)) ? 1 : 0); } return 0; } extern "C" int ZT_GoTap_removeIp(ZT_GoTap *tap, int af, const void *ip, int netmaskBits) { switch (af) { case AF_INET: return (reinterpret_cast(tap)->removeIp(InetAddress(ip, 4, (unsigned int)netmaskBits)) ? 1 : 0); case AF_INET6: return (reinterpret_cast(tap)->removeIp(InetAddress(ip, 16, (unsigned int)netmaskBits)) ? 1 : 0); } return 0; } extern "C" int ZT_GoTap_ips(ZT_GoTap *tap, void *buf, unsigned int bufSize) { auto ips = reinterpret_cast(tap)->ips(); unsigned int p = 0; uint8_t *const b = reinterpret_cast(buf); for (auto ip = ips.begin(); ip != ips.end(); ++ip) { if ((p + 6) > bufSize) break; const uint8_t *const ipd = reinterpret_cast(ip->rawIpData()); if (ip->isV4()) { b[p++] = AF_INET; b[p++] = ipd[0]; b[p++] = ipd[1]; b[p++] = ipd[2]; b[p++] = ipd[3]; b[p++] = (uint8_t)ip->netmaskBits(); } else if (ip->isV6()) { if ((p + 18) <= bufSize) { b[p++] = AF_INET6; for (int j = 0; j < 16; ++j) b[p++] = ipd[j]; b[p++] = (uint8_t)ip->netmaskBits(); } } } return (int)p; } extern "C" void ZT_GoTap_deviceName(ZT_GoTap *tap, char nbuf[256]) { Utils::scopy(nbuf, 256, reinterpret_cast(tap)->deviceName().c_str()); } extern "C" void ZT_GoTap_setFriendlyName(ZT_GoTap *tap, const char *friendlyName) { reinterpret_cast(tap)->setFriendlyName(friendlyName); } extern "C" void ZT_GoTap_setMtu(ZT_GoTap *tap, unsigned int mtu) { reinterpret_cast(tap)->setMtu(mtu); } #if defined(IFA_F_SECONDARY) && !defined(IFA_F_TEMPORARY) #define IFA_F_TEMPORARY IFA_F_SECONDARY #endif extern "C" int ZT_isTemporaryV6Address(const char *ifname, const struct sockaddr_storage *a) { #if defined(IN6_IFF_TEMPORARY) && defined(SIOCGIFAFLAG_IN6) static ZT_SOCKET s_tmpV6Socket = ZT_INVALID_SOCKET; static std::mutex s_lock; std::lock_guard< std::mutex > l(s_lock); if (s_tmpV6Socket == ZT_INVALID_SOCKET) { s_tmpV6Socket = socket(AF_INET6, SOCK_DGRAM, 0); if (s_tmpV6Socket <= 0) return 0; } struct in6_ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); memcpy(&(ifr.ifr_addr), a, sizeof(sockaddr_in6)); if (ioctl(s_tmpV6Socket, SIOCGIFAFLAG_IN6, &ifr) < 0) { return 0; } return ((ifr.ifr_ifru.ifru_flags6 & IN6_IFF_TEMPORARY) != 0) ? 1 : 0; #else return 0; #endif } extern "C" void *ZT_malloc(unsigned long s) { return (void *)malloc((size_t)s); }