diff options
-rwxr-xr-x | .gitignore | 3 | ||||
-rw-r--r-- | controller/SqliteNetworkController.cpp | 62 | ||||
-rw-r--r-- | controller/SqliteNetworkController.hpp | 7 | ||||
-rw-r--r-- | make-linux.mk | 3 | ||||
-rw-r--r-- | node/InetAddress.hpp | 2 | ||||
-rw-r--r-- | node/Peer.cpp | 9 | ||||
-rw-r--r-- | node/Switch.cpp | 85 | ||||
-rw-r--r-- | osdep/OSUtils.hpp | 1 | ||||
-rw-r--r-- | selftest.cpp | 1 | ||||
-rw-r--r-- | tests/http/agent.js | 8 | ||||
-rw-r--r-- | tests/http/big-test-hosts | 2 | ||||
-rwxr-xr-x | tests/http/big-test-kill.sh | 13 | ||||
-rwxr-xr-x | tests/http/big-test-ready.sh | 30 | ||||
-rwxr-xr-x | tests/http/big-test-start.sh | 26 | ||||
-rwxr-xr-x | tests/http/docker-main.sh | 4 |
15 files changed, 161 insertions, 95 deletions
@@ -53,8 +53,7 @@ node_modules cluster-geo/cluster-geo/config.js cluster-geo/cluster-geo/cache.* tests/http/zerotier-one -tests/http/result_* -tests/http/big-test-out +tests/http/big-test-hosts # MacGap wrapper build files /ext/mac-ui-macgap1-wrapper/src/MacGap.xcodeproj/project.xcworkspace/xcuserdata/* diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index 52b47665..049db04e 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -71,6 +71,9 @@ // than this (ms). #define ZT_NETCONF_MIN_REQUEST_PERIOD 1000 +// Delay between backups in milliseconds +#define ZT_NETCONF_BACKUP_PERIOD 60000 + namespace ZeroTier { namespace { @@ -122,6 +125,7 @@ struct NetworkRecord { SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,const char *circuitTestPath) : _node(node), + _backupThreadRun(true), _dbPath(dbPath), _circuitTestPath(circuitTestPath), _db((sqlite3 *)0) @@ -247,10 +251,15 @@ SqliteNetworkController::SqliteNetworkController(Node *node,const char *dbPath,c throw std::runtime_error("SqliteNetworkController unable to read instanceId (it's NULL)"); _instanceId = iid; } + + _backupThread = Thread::start(this); } SqliteNetworkController::~SqliteNetworkController() { + _backupThreadRun = false; + Thread::join(_backupThread); + Mutex::Lock _l(_lock); if (_db) { sqlite3_finalize(_sGetNetworkById); @@ -991,6 +1000,59 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpDELETE( return 404; } +void SqliteNetworkController::threadMain() + throw() +{ + uint64_t lastBackupTime = 0; + while (_backupThreadRun) { + if ((OSUtils::now() - lastBackupTime) >= ZT_NETCONF_BACKUP_PERIOD) { + lastBackupTime = OSUtils::now(); + + char backupPath[4096],backupPath2[4096]; + Utils::snprintf(backupPath,sizeof(backupPath),"%s.backupInProgress",_dbPath.c_str()); + Utils::snprintf(backupPath2,sizeof(backupPath),"%s.backup",_dbPath.c_str()); + OSUtils::rm(backupPath); // delete any unfinished backups + + sqlite3 *bakdb = (sqlite3 *)0; + sqlite3_backup *bak = (sqlite3_backup *)0; + if (sqlite3_open_v2(backupPath,&bakdb,SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE,(const char *)0) != SQLITE_OK) { + fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_open_v2()"ZT_EOL_S); + continue; + } + bak = sqlite3_backup_init(bakdb,"main",_db,"main"); + if (!bak) { + sqlite3_close(bakdb); + OSUtils::rm(backupPath); // delete any unfinished backups + fprintf(stderr,"SqliteNetworkController: CRITICAL: backup failed on sqlite3_backup_init()"ZT_EOL_S); + continue; + } + + int rc = SQLITE_OK; + for(;;) { + if (!_backupThreadRun) { + sqlite3_backup_finish(bak); + sqlite3_close(bakdb); + OSUtils::rm(backupPath); + return; + } + _lock.lock(); + rc = sqlite3_backup_step(bak,64); + _lock.unlock(); + if ((rc == SQLITE_OK)||(rc == SQLITE_LOCKED)||(rc == SQLITE_BUSY)) + Thread::sleep(50); + else break; + } + + sqlite3_backup_finish(bak); + sqlite3_close(bakdb); + + OSUtils::rm(backupPath2); + ::rename(backupPath,backupPath2); + } + Thread::sleep(250); + } +} + unsigned int SqliteNetworkController::_doCPGet( const std::vector<std::string> &path, const std::map<std::string,std::string> &urlArgs, diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index a3d5dfc7..0e2bb63e 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -39,6 +39,7 @@ #include "../node/Constants.hpp" #include "../node/NetworkController.hpp" #include "../node/Mutex.hpp" +#include "../osdep/Thread.hpp" // Number of in-memory last log entries to maintain per user #define ZT_SQLITENETWORKCONTROLLER_IN_MEMORY_LOG_SIZE 32 @@ -86,6 +87,10 @@ public: std::string &responseBody, std::string &responseContentType); + // threadMain() for backup thread -- do not call directly + void threadMain() + throw(); + private: enum IpAssignmentType { // IP assignment is a static IP address @@ -112,6 +117,8 @@ private: static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report); Node *_node; + Thread _backupThread; + volatile bool _backupThreadRun; std::string _dbPath; std::string _circuitTestPath; std::string _instanceId; diff --git a/make-linux.mk b/make-linux.mk index 5e0a2072..eddef7fc 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -88,6 +88,9 @@ else LDFLAGS=-pie -Wl,-z,relro,-z,now STRIP=strip --strip-all endif +ifeq ($(ZT_TRACE),1) + DEFS+=-DZT_TRACE +endif # Uncomment for gprof profile build #CFLAGS=-Wall -g -pg -pthread $(INCLUDES) $(DEFS) diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp index c4d5cfda..74efc943 100644 --- a/node/InetAddress.hpp +++ b/node/InetAddress.hpp @@ -411,7 +411,7 @@ struct InetAddress : public sockaddr_storage // TODO: Ethernet address (but accept for forward compatibility) return 7; case 0x02: - // TODO: Bluetooth address (but accept for forward compatibility) + // TODO: Bluetooth address (but accept for forward compatibility) return 7; case 0x03: // TODO: Other address types (but accept for forward compatibility) diff --git a/node/Peer.cpp b/node/Peer.cpp index 9d0d78e5..0b981c8e 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -83,10 +83,10 @@ void Peer::received( Packet::Verb inReVerb) { #ifdef ZT_ENABLE_CLUSTER - InetAddress redirectTo; if ((RR->cluster)&&(hops == 0)) { // Note: findBetterEndpoint() is first since we still want to check // for a better endpoint even if we don't actually send a redirect. + InetAddress redirectTo; if ( (RR->cluster->findBetterEndpoint(redirectTo,_id.address(),remoteAddr,false)) && (verb != Packet::VERB_OK)&&(verb != Packet::VERB_ERROR)&&(verb != Packet::VERB_RENDEZVOUS)&&(verb != Packet::VERB_PUSH_DIRECT_PATHS) ) { if (_vProto >= 5) { // For newer peers we can send a more idiomatic verb: PUSH_DIRECT_PATHS. @@ -141,13 +141,6 @@ void Peer::received( else if (verb == Packet::VERB_MULTICAST_FRAME) _lastMulticastFrame = now; -#ifdef ZT_ENABLE_CLUSTER - // If we think this peer belongs elsewhere, don't learn this path or - // do other connection init stuff. - if (redirectTo) - return; -#endif - if ((now - _lastAnnouncedTo) >= ((ZT_MULTICAST_LIKE_EXPIRE / 2) - 1000)) { _lastAnnouncedTo = now; needMulticastGroupAnnounce = true; diff --git a/node/Switch.cpp b/node/Switch.cpp index b7a9c522..97befbc6 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -154,25 +154,84 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c MulticastGroup mg(to,0); if (to.isBroadcast()) { - if ( - (etherType == ZT_ETHERTYPE_ARP)&& - (len >= 28)&& - ( - (((const unsigned char *)data)[2] == 0x08)&& - (((const unsigned char *)data)[3] == 0x00)&& - (((const unsigned char *)data)[4] == 6)&& - (((const unsigned char *)data)[5] == 4)&& - (((const unsigned char *)data)[7] == 0x01) - ) - ) { - // Cram IPv4 IP into ADI field to make IPv4 ARP broadcast channel specific and scalable - // Also: enableBroadcast() does not apply to ARP since it's required for IPv4 + if ( (etherType == ZT_ETHERTYPE_ARP) && (len >= 28) && ((((const uint8_t *)data)[2] == 0x08)&&(((const uint8_t *)data)[3] == 0x00)&&(((const uint8_t *)data)[4] == 6)&&(((const uint8_t *)data)[5] == 4)&&(((const uint8_t *)data)[7] == 0x01)) ) { + /* IPv4 ARP is one of the few special cases that we impose upon what is + * otherwise a straightforward Ethernet switch emulation. Vanilla ARP + * is dumb old broadcast and simply doesn't scale. ZeroTier multicast + * groups have an additional field called ADI (additional distinguishing + * information) which was added specifically for ARP though it could + * be used for other things too. We then take ARP broadcasts and turn + * them into multicasts by stuffing the IP address being queried into + * the 32-bit ADI field. In practice this uses our multicast pub/sub + * system to implement a kind of extended/distributed ARP table. */ mg = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char *)data) + 24,4,0)); } else if (!nconf->enableBroadcast()) { // Don't transmit broadcasts if this network doesn't want them TRACE("%.16llx: dropped broadcast since ff:ff:ff:ff:ff:ff is not enabled",network->id()); return; } + } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(len >= (40 + 8 + 16))) { + /* IPv6 NDP emulation on ZeroTier-RFC4193 addressed networks! This allows + * for multicast-free operation in IPv6 networks, which both improves + * performance and is friendlier to mobile and (especially) IoT devices. + * In the future there may be a no-multicast build option for embedded + * and IoT use and this will be the preferred addressing mode. Note that + * it plays nice with our L2 emulation philosophy and even with bridging. + * While "real" devices behind the bridge can't have ZT-RFC4193 addresses + * themselves, they can look these addresses up with NDP and it will + * work just fine. */ + if ((reinterpret_cast<const uint8_t *>(data)[6] == 0x3a)&&(reinterpret_cast<const uint8_t *>(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + for(std::vector<InetAddress>::const_iterator sip(nconf->staticIps().begin()),sipend(nconf->staticIps().end());sip!=sipend;++sip) { + if ((sip->ss_family == AF_INET6)&&(Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_port) == 88)) { + const uint8_t *my6 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_addr.s6_addr); + if ((my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 == fd__:____:____:____:__99:93__:____:____ / 88 + const uint8_t *pkt6 = reinterpret_cast<const uint8_t *>(data) + 40 + 8; + unsigned int ptr = 0; + while (ptr != 11) { + if (pkt6[ptr] != my6[ptr]) + break; + ++ptr; + } + if (ptr == 11) { // /88 matches an assigned address on this network + const Address atPeer(pkt6 + ptr,5); + if (atPeer != RR->identity.address()) { + const MAC atPeerMac(atPeer,network->id()); + TRACE("ZT-RFC4193 NDP emulation: %.16llx: forging response for %s/%s",network->id(),atPeer.toString().c_str(),atPeerMac.toString().c_str()); + + uint8_t adv[72]; + adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; + adv[4] = 0x00; adv[5] = 0x20; + adv[6] = 0x3a; adv[7] = 0xff; + for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; + for(int i=0;i<16;++i) adv[24 + i] = my6[i]; + adv[40] = 0x88; adv[41] = 0x00; + adv[42] = 0x00; adv[43] = 0x00; // future home of checksum + adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; + for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; + adv[64] = 0x02; adv[65] = 0x01; + adv[66] = atPeerMac[0]; adv[67] = atPeerMac[1]; adv[68] = atPeerMac[2]; adv[69] = atPeerMac[3]; adv[70] = atPeerMac[4]; adv[71] = atPeerMac[5]; + + uint16_t pseudo_[36]; + uint8_t *const pseudo = reinterpret_cast<uint8_t *>(pseudo_); + for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; + pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; + pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; + for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; + uint32_t checksum = 0; + for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); + while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); + checksum = ~checksum; + adv[42] = (checksum >> 8) & 0xff; + adv[43] = checksum & 0xff; + + RR->node->putFrame(network->id(),atPeerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72); + return; // stop processing: we have handled this frame with a spoofed local reply so no need to send it anywhere + } + } + } + } + } + } } /* Learn multicast groups for bridged-in hosts. diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 5de35eba..43fd2813 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -95,7 +95,6 @@ public: static inline bool rm(const std::string &path) throw() { return rm(path.c_str()); } static inline bool mkdir(const char *path) - throw() { #ifdef __WINDOWS__ if (::PathIsDirectoryA(path)) diff --git a/selftest.cpp b/selftest.cpp index 0787925f..fa8df48b 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -41,6 +41,7 @@ #include "node/InetAddress.hpp" #include "node/Utils.hpp" #include "node/Identity.hpp" +#include "node/Buffer.hpp" #include "node/Packet.hpp" #include "node/Salsa20.hpp" #include "node/MAC.hpp" diff --git a/tests/http/agent.js b/tests/http/agent.js index bc7c475e..e90ee482 100644 --- a/tests/http/agent.js +++ b/tests/http/agent.js @@ -4,20 +4,20 @@ // Customizable parameters: // Maximum interval between test attempts -var TEST_INTERVAL_MAX = 60000; +var TEST_INTERVAL_MAX = (60000 * 5); // Test timeout in ms -var TEST_TIMEOUT = 30000; +var TEST_TIMEOUT = 60000; // Where should I contact to register and query a list of other test agents? -var SERVER_HOST = '104.238.141.145'; +var SERVER_HOST = '174.136.102.178'; var SERVER_PORT = 18080; // Which port should agents use for their HTTP? var AGENT_PORT = 18888; // Payload size in bytes -var PAYLOAD_SIZE = 10000; +var PAYLOAD_SIZE = 5000; // --------------------------------------------------------------------------- diff --git a/tests/http/big-test-hosts b/tests/http/big-test-hosts deleted file mode 100644 index 93b6f23f..00000000 --- a/tests/http/big-test-hosts +++ /dev/null @@ -1,2 +0,0 @@ -root@104.156.246.48 -root@104.156.252.136 diff --git a/tests/http/big-test-kill.sh b/tests/http/big-test-kill.sh index 59f36788..29dbd638 100755 --- a/tests/http/big-test-kill.sh +++ b/tests/http/big-test-kill.sh @@ -1,18 +1,9 @@ #!/bin/bash -# Edit as needed -- note that >1000 per host is likely problematic due to Linux kernel limits -NUM_CONTAINERS=100 -CONTAINER_IMAGE=zerotier/http-test - -# -# This script is designed to be run on Docker hosts to run NUM_CONTAINERS -# -# It can then be run on each Docker host via pssh or similar to run very -# large scale tests. -# +# Kills all running Docker containers on all big-test-hosts export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin -pssh -h big-test-hosts -i -t 0 -p 256 "docker ps -aq | xargs -r docker rm -f" +pssh -h big-test-hosts -i -t 0 -p 256 "sudo docker ps -aq | xargs -r sudo docker rm -f" exit 0 diff --git a/tests/http/big-test-ready.sh b/tests/http/big-test-ready.sh deleted file mode 100755 index aa540bba..00000000 --- a/tests/http/big-test-ready.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Edit as needed -- note that >1000 per host is likely problematic due to Linux kernel limits -NUM_CONTAINERS=100 -CONTAINER_IMAGE=zerotier/http-test - -# -# This script is designed to be run on Docker hosts to run NUM_CONTAINERS -# -# It can then be run on each Docker host via pssh or similar to run very -# large scale tests. -# - -export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin - -# Kill and clean up old test containers if any -- note that this kills all containers on the system! -#docker ps -q | xargs -n 1 docker kill -#docker ps -aq | xargs -n 1 docker rm - -# Pull latest if needed -- change this to your image name and/or where to pull it from -#docker pull $CONTAINER_IMAGE - -# Run NUM_CONTAINERS -#for ((n=0;n<$NUM_CONTAINERS;n++)); do -# docker run --device=/dev/net/tun --privileged -d $CONTAINER_IMAGE -#done - -pssh -h big-test-hosts -i -t 0 -p 256 "docker pull $CONTAINER_IMAGE" - -exit 0 diff --git a/tests/http/big-test-start.sh b/tests/http/big-test-start.sh index f300ac61..3ef4a316 100755 --- a/tests/http/big-test-start.sh +++ b/tests/http/big-test-start.sh @@ -1,30 +1,12 @@ #!/bin/bash -# Edit as needed -- note that >1000 per host is likely problematic due to Linux kernel limits -NUM_CONTAINERS=50 +# More than 500 container seems to result in a lot of sporadic failures, probably due to Linux kernel scaling issues with virtual network ports +# 250 with a 16GB RAM VM like Amazon m4.xlarge seems good +NUM_CONTAINERS=250 CONTAINER_IMAGE=zerotier/http-test -# -# This script is designed to be run on Docker hosts to run NUM_CONTAINERS -# -# It can then be run on each Docker host via pssh or similar to run very -# large scale tests. -# - export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin -# Kill and clean up old test containers if any -- note that this kills all containers on the system! -#docker ps -q | xargs -n 1 docker kill -#docker ps -aq | xargs -n 1 docker rm - -# Pull latest if needed -- change this to your image name and/or where to pull it from -#docker pull $CONTAINER_IMAGE - -# Run NUM_CONTAINERS -#for ((n=0;n<$NUM_CONTAINERS;n++)); do -# docker run --device=/dev/net/tun --privileged -d $CONTAINER_IMAGE -#done - -pssh -h big-test-hosts -o big-test-out -t 0 -p 256 "for ((n=0;n<$NUM_CONTAINERS;n++)); do docker run --device=/dev/net/tun --privileged -d $CONTAINER_IMAGE; sleep 0.25; done" +pssh -h big-test-hosts -o big-test-out -t 0 -p 256 "for ((n=0;n<$NUM_CONTAINERS;n++)); do sudo docker run --device=/dev/net/tun --privileged -d $CONTAINER_IMAGE; sleep 0.1; done" exit 0 diff --git a/tests/http/docker-main.sh b/tests/http/docker-main.sh index f9e11de5..29cdced9 100755 --- a/tests/http/docker-main.sh +++ b/tests/http/docker-main.sh @@ -4,11 +4,13 @@ export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin /zerotier-one -d >>zerotier-one.out 2>&1 +# Wait for ZeroTier to start and join the network while [ ! -d "/proc/sys/net/ipv6/conf/zt0" ]; do sleep 0.25 done -sleep 2 +# Wait just a bit longer for stuff to settle +sleep 5 exec node --harmony /agent.js >>agent.out 2>&1 #exec node --harmony /agent.js |