/* * Copyright (c)2019 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: 2023-01-01 * * On the date above, in accordance with the Business Source License, use * of this software will be governed by vergnn 2.0 of the Apache License. */ /****/ #include "GoGlue.h" #include "../../node/Constants.hpp" #include "../../node/InetAddress.hpp" #include "../../node/Node.hpp" #include "../../node/Utils.hpp" #include "../../node/MAC.hpp" #include "../../node/Address.hpp" #include "../../osdep/OSUtils.hpp" #include "../../osdep/BlockingQueue.hpp" #include "../../osdep/EthernetTap.hpp" #include #include #include #ifndef __WINDOWS__ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __BSD__ #include #endif #ifdef __LINUX__ #ifndef IPV6_DONTFRAG #define IPV6_DONTFRAG 62 #endif #endif #endif // !__WINDOWS__ #include #include #include #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 { std::string ip; int port; int af; std::atomic_bool run; std::thread thr; }; struct ZT_GoNode_Impl { Node *node; volatile int64_t nextBackgroundTaskDeadline; int (*goPathCheckFunc)(ZT_GoNode *,ZT_Node *,uint64_t ztAddress,const void *); int (*goPathLookupFunc)(ZT_GoNode *,ZT_Node *,int desiredAddressFamily,void *); int (*goStateObjectGetFunc)(ZT_GoNode *,ZT_Node *,int objType,const uint64_t id[2],void *buf,unsigned int bufSize); std::string path; std::atomic_bool run; std::map< ZT_SOCKET,ZT_GoNodeThread > threads; std::mutex threads_l; std::map< uint64_t,std::shared_ptr > taps; std::mutex taps_l; BlockingQueue eq; std::thread backgroundTaskThread; }; ////////////////////////////////////////////////////////////////////////////// static int ZT_GoNode_VirtualNetworkConfigFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t nwid, void **nptr, enum ZT_VirtualNetworkConfigOperation op, const ZT_VirtualNetworkConfig *cfg) { ZT_GoNodeEvent ev; ev.type = ZT_GONODE_EVENT_NETWORK_CONFIG_UPDATE; ev.data.nconf.op = op; if (cfg) ev.data.nconf.conf = *cfg; reinterpret_cast(uptr)->eq.post(ev); } 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) { ZT_GoNodeEvent ev; ev.type = ZT_GONODE_EVENT_ZTEVENT; ev.data.zt.type = et; reinterpret_cast(uptr)->eq.post(ev); } 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) { if (len < ZT_MAX_STATE_OBJECT_SIZE) { // sanity check ZT_GoNodeEvent ev; ev.type = (len >= 0) ? ZT_GONODE_EVENT_STATE_PUT : ZT_GONODE_EVENT_STATE_DELETE; if (len > 0) { memcpy(ev.data.sobj.data,data,len); ev.data.sobj.len = (unsigned int)len; } ev.data.sobj.objType = objType; ev.data.sobj.id[0] = id[0]; ev.data.sobj.id[1] = id[1]; reinterpret_cast(uptr)->eq.post(ev); } } static int ZT_GoNode_StateGetFunction( ZT_Node *node, void *uptr, void *tptr, enum ZT_StateObjectType objType, const uint64_t id[2], void *buf, unsigned int buflen) { return reinterpret_cast(uptr)->goStateObjectGetFunc(reinterpret_cast(uptr),reinterpret_cast(uptr)->node,(int)objType,id,buf,buflen); } static ZT_ALWAYS_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 ((ipTTL > 0)&&(ipTTL < 255)) { #ifdef __WINDOWS__ DWORD tmp = (DWORD)ipTTL; #else int tmp = (int)ipTTL; #endif setsockopt(sock,IPPROTO_IP,IP_TTL,&tmp,sizeof(tmp)); sendto(sock,data,len,MSG_DONTWAIT,(const sockaddr *)addr,sizeof(struct sockaddr_in)); tmp = 255; setsockopt(sock,IPPROTO_IP,IP_TTL,&tmp,sizeof(tmp)); } else { sendto(sock,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,data,len,MSG_DONTWAIT,(const sockaddr *)addr,sizeof(struct sockaddr_in6)); break; } } static void 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 ((localSocket != -1)&&(localSocket != ZT_INVALID_SOCKET)) { doUdpSend((ZT_SOCKET)localSocket,addr,data,len,ipTTL); } else { ZT_GoNode *const gn = reinterpret_cast(uptr); std::set ipsSentFrom; std::lock_guard l(gn->threads_l); for(auto t=gn->threads.begin();t!=gn->threads.end();++t) { if (t->second.af == addr->ss_family) { if (ipsSentFrom.insert(t->second.ip).second) { doUdpSend(t->first,addr,data,len,ipTTL); } } } } } static int ZT_GoNode_PathCheckFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t ztAddress, int64_t localSocket, const struct sockaddr_storage *sa) { return reinterpret_cast(uptr)->goPathCheckFunc(reinterpret_cast(uptr),reinterpret_cast(uptr)->node,ztAddress,sa); } static int ZT_GoNode_PathLookupFunction( ZT_Node *node, void *uptr, void *tptr, uint64_t ztAddress, int desiredAddressFamily, struct sockaddr_storage *sa) { return reinterpret_cast(uptr)->goPathLookupFunc(reinterpret_cast(uptr),reinterpret_cast(uptr)->node,desiredAddressFamily,sa); } static void ZT_GoNode_DNSResolver( ZT_Node *node, void *uptr, void *tptr, const enum ZT_DNSRecordType *types, unsigned int numTypes, const char *name, uintptr_t requestId) { ZT_GoNodeEvent ev; ev.type = ZT_GONODE_EVENT_DNS_GET_TXT; Utils::scopy(ev.data.dns.dnsName,sizeof(ev.data.dns.dnsName),name); reinterpret_cast(uptr)->eq.post(ev); } ////////////////////////////////////////////////////////////////////////////// extern "C" ZT_GoNode *ZT_GoNode_new( const char *workingPath, int (*goPathCheckFunc)(ZT_GoNode *,ZT_Node *,uint64_t ztAddress,const void *), int (*goPathLookupFunc)(ZT_GoNode *,ZT_Node *,int desiredAddressFamily,void *), int (*goStateObjectGetFunc)(ZT_GoNode *,ZT_Node *,int objType,const uint64_t id[2],void *buf,unsigned int bufSize)) { try { struct ZT_Node_Callbacks cb; cb.virtualNetworkConfigFunction = &ZT_GoNode_VirtualNetworkConfigFunction; cb.virtualNetworkFrameFunction = &ZT_GoNode_VirtualNetworkFrameFunction; cb.eventCallback = &ZT_GoNode_EventCallback; cb.statePutFunction = &ZT_GoNode_StatePutFunction; cb.stateGetFunction = &ZT_GoNode_StateGetFunction; cb.pathCheckFunction = &ZT_GoNode_PathCheckFunction; cb.pathLookupFunction = &ZT_GoNode_PathLookupFunction; cb.dnsResolver = &ZT_GoNode_DNSResolver; ZT_GoNode_Impl *gn = new ZT_GoNode_Impl; const int64_t now = OSUtils::now(); gn->node = new Node(reinterpret_cast(gn),nullptr,&cb,now); gn->nextBackgroundTaskDeadline = now; gn->goPathCheckFunc = goPathCheckFunc; gn->goPathLookupFunc = goPathLookupFunc; gn->goStateObjectGetFunc = goStateObjectGetFunc; gn->path = workingPath; gn->run = true; gn->backgroundTaskThread = std::thread([gn] { while (gn->run) { std::this_thread::sleep_for(std::chrono::milliseconds(250)); const int64_t now = OSUtils::now(); if (now >= gn->nextBackgroundTaskDeadline) gn->node->processBackgroundTasks(nullptr,now,&(gn->nextBackgroundTaskDeadline)); } }); 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; ZT_GoNodeEvent sd; sd.type = ZT_GONODE_EVENT_SHUTDOWN; gn->eq.post(sd); std::vector th; gn->threads_l.lock(); for(auto t=gn->threads.begin();t!=gn->threads.end();++t) { t->second.run = false; shutdown(t->first,SHUT_RDWR); close(t->first); th.emplace_back(t->second.thr); } gn->threads_l.unlock(); for(auto t=th.begin();t!=th.end();++t) t->join(); 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(); delete gn->node; delete gn; } extern "C" ZT_Node *ZT_GoNode_getNode(ZT_GoNode *gn) { return gn->node; } // Sets flags and socket options common to both IPv4 and IPv6 UDP sockets 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,(void *)&fl,sizeof(fl)); #endif #ifndef __LINUX__ // linux wants just SO_REUSEPORT fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock,SOL_SOCKET,SO_REUSEADDR,(void *)&fl,sizeof(fl)); #endif fl = SETSOCKOPT_FLAG_TRUE; setsockopt(udpSock,SOL_SOCKET,SO_BROADCAST,(void *)&fl,sizeof(fl)); #ifdef IP_DONTFRAG fl = SETSOCKOPT_FLAG_FALSE; setsockopt(udpSock,IPPROTO_IP,IP_DONTFRAG,(void *)&fl,sizeof(fl)); #endif #ifdef IP_MTU_DISCOVER fl = SETSOCKOPT_FLAG_FALSE; setsockopt(udpSock,IPPROTO_IP,IP_MTU_DISCOVER,(void *)&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) { 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,&fl,sizeof(fl)); #endif if (bind(udpSock,reinterpret_cast(&in6),sizeof(in6)) != 0) return errno; { std::lock_guard l(gn->threads_l); ZT_GoNodeThread &gnt = gn->threads[udpSock]; gnt.ip = ip; gnt.port = port; gnt.af = AF_INET6; gnt.run = true; gnt.thr = std::thread([udpSock,gn,&gnt] { struct sockaddr_in6 in6; socklen_t salen; char buf[16384]; while (gnt.run) { salen = sizeof(in6); int s = (int)recvfrom(udpSock,buf,sizeof(buf),0,reinterpret_cast(&in6),&salen); if (s > 0) { gn->node->processWirePacket(&gnt,OSUtils::now(),(int64_t)udpSock,reinterpret_cast(&in6),buf,(unsigned int)s,&(gn->nextBackgroundTaskDeadline)); } } }); } } 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 l(gn->threads_l); ZT_GoNodeThread &gnt = gn->threads[udpSock]; gnt.ip = ip; gnt.port = port; gnt.af = AF_INET6; gnt.run = true; gnt.thr = std::thread([udpSock,gn,&gnt] { struct sockaddr_in in4; socklen_t salen; char buf[16384]; while (gnt.run) { salen = sizeof(in4); int s = (int)recvfrom(udpSock,buf,sizeof(buf),0,reinterpret_cast(&in4),&salen); if (s > 0) { gn->node->processWirePacket(&gnt,OSUtils::now(),(int64_t)udpSock,reinterpret_cast(&in4),buf,(unsigned int)s,&(gn->nextBackgroundTaskDeadline)); } } }); } } return 0; } extern "C" int ZT_GoNode_phyStopListen(ZT_GoNode *gn,const char *dev,const char *ip,const int port) { { std::lock_guard 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; shutdown(t->first,SHUT_RDWR); close(t->first); t->second.thr.join(); gn->threads.erase(t++); } else ++t; } } } extern "C" int ZT_GoNode_waitForEvent(ZT_GoNode *gn,ZT_GoNodeEvent *ev) { gn->eq.get(*ev); } 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_GoNode *const gn = reinterpret_cast(uptr); gn->node->processVirtualNetworkFrame(tptr,OSUtils::now(),nwid,from.toInt(),to.toInt(),etherType,vlanId,data,len,&(gn->nextBackgroundTaskDeadline)); } extern "C" ZT_GoTap *ZT_GoNode_join(ZT_GoNode *gn,uint64_t nwid) { try { std::lock_guard 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 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,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 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); } }