diff options
Diffstat (limited to 'node/Node.cpp')
-rw-r--r-- | node/Node.cpp | 278 |
1 files changed, 200 insertions, 78 deletions
diff --git a/node/Node.cpp b/node/Node.cpp index 3b18d7ba..8079e801 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -37,26 +37,28 @@ #include <vector> #include <string> -#ifndef _WIN32 +#ifdef _WIN32 +#include <Windows.h> +#else #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <sys/file.h> #endif -#include <openssl/sha.h> - #include "Condition.hpp" #include "Node.hpp" #include "Topology.hpp" #include "Demarc.hpp" +#include "Packet.hpp" #include "Switch.hpp" #include "Utils.hpp" #include "EthernetTap.hpp" #include "Logger.hpp" #include "Constants.hpp" #include "InetAddress.hpp" -#include "Pack.hpp" +#include "Salsa20.hpp" +#include "HMAC.hpp" #include "RuntimeEnvironment.hpp" #include "NodeConfig.hpp" #include "Defaults.hpp" @@ -66,11 +68,109 @@ #include "Mutex.hpp" #include "Multicaster.hpp" #include "CMWC4096.hpp" +#include "Service.hpp" #include "../version.h" namespace ZeroTier { +struct _LocalClientImpl +{ + unsigned char key[32]; + UdpSocket *sock; + void (*resultHandler)(void *,unsigned long,const char *); + void *arg; + InetAddress localDestAddr; + Mutex inUseLock; +}; + +static void _CBlocalClientHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) +{ + _LocalClientImpl *impl = (_LocalClientImpl *)arg; + if (!impl) + return; + if (!impl->resultHandler) + return; // sanity check + Mutex::Lock _l(impl->inUseLock); + + try { + unsigned long convId = 0; + std::vector<std::string> results; + if (!NodeConfig::decodeControlMessagePacket(impl->key,data,len,convId,results)) + return; + for(std::vector<std::string>::iterator r(results.begin());r!=results.end();++r) + impl->resultHandler(impl->arg,convId,r->c_str()); + } catch ( ... ) {} +} + +Node::LocalClient::LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg) + throw() : + _impl((void *)0) +{ + _LocalClientImpl *impl = new _LocalClientImpl; + + UdpSocket *sock = (UdpSocket *)0; + for(unsigned int i=0;i<5000;++i) { + try { + sock = new UdpSocket(true,32768 + (rand() % 20000),false,&_CBlocalClientHandler,impl); + break; + } catch ( ... ) { + sock = (UdpSocket *)0; + } + } + + // If socket fails to bind, there's a big problem like missing IPv4 stack + if (sock) { + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha,authToken,strlen(authToken)); + SHA256_Final(impl->key,&sha); + + impl->sock = sock; + impl->resultHandler = resultHandler; + impl->arg = arg; + impl->localDestAddr = InetAddress::LO4; + impl->localDestAddr.setPort(ZT_CONTROL_UDP_PORT); + _impl = impl; + } else delete impl; +} + +Node::LocalClient::~LocalClient() +{ + if (_impl) { + ((_LocalClientImpl *)_impl)->inUseLock.lock(); + delete ((_LocalClientImpl *)_impl)->sock; + ((_LocalClientImpl *)_impl)->inUseLock.unlock(); + delete ((_LocalClientImpl *)_impl); + } +} + +unsigned long Node::LocalClient::send(const char *command) + throw() +{ + if (!_impl) + return 0; + _LocalClientImpl *impl = (_LocalClientImpl *)_impl; + Mutex::Lock _l(impl->inUseLock); + + try { + uint32_t convId = (uint32_t)rand(); + if (!convId) + convId = 1; + + std::vector<std::string> tmp; + tmp.push_back(std::string(command)); + std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets(NodeConfig::encodeControlMessage(impl->key,convId,tmp)); + + for(std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> >::iterator p(packets.begin());p!=packets.end();++p) + impl->sock->send(impl->localDestAddr,p->data(),p->size(),-1); + + return convId; + } catch ( ... ) { + return 0; + } +} + struct _NodeImpl { RuntimeEnvironment renv; @@ -78,7 +178,6 @@ struct _NodeImpl Node::ReasonForTermination reasonForTermination; volatile bool started; volatile bool running; - volatile bool updateStatusNow; volatile bool terminateNow; // Helper used to rapidly terminate from run() @@ -94,20 +193,66 @@ struct _NodeImpl } }; -Node::Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) +#ifndef __WINDOWS__ +static void _netconfServiceMessageHandler(void *renv,Service &svc,const Dictionary &msg) +{ + if (!renv) + return; // sanity check + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)renv; + + try { + const std::string &type = msg.get("type"); + if (type == "netconf-response") { + uint64_t inRePacketId = strtoull(msg.get("requestId").c_str(),(char **)0,16); + SharedPtr<Network> network = _r->nc->network(strtoull(msg.get("nwid").c_str(),(char **)0,16)); + Address peerAddress(msg.get("peer").c_str()); + + if ((network)&&(peerAddress)) { + if (msg.contains("error")) { + Packet::ErrorCode errCode = Packet::ERROR_INVALID_REQUEST; + const std::string &err = msg.get("error"); + if (err == "NOT_FOUND") + errCode = Packet::ERROR_NOT_FOUND; + + Packet outp(peerAddress,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(inRePacketId); + outp.append((unsigned char)errCode); + outp.append(network->id()); + _r->sw->send(outp,true); + } else if (msg.contains("netconf")) { + const std::string &netconf = msg.get("netconf"); + if (netconf.length() < 2048) { // sanity check + Packet outp(peerAddress,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(inRePacketId); + outp.append(network->id()); + outp.append((uint16_t)netconf.length()); + outp.append(netconf.data(),netconf.length()); + _r->sw->send(outp,true); + } + } + } + } + } catch (std::exception &exc) { + LOG("unexpected exception parsing response from netconf service: %s",exc.what()); + } catch ( ... ) { + LOG("unexpected exception parsing response from netconf service: unknown exception"); + } +} +#endif // !__WINDOWS__ + +Node::Node(const char *hp) throw() : _impl(new _NodeImpl) { _NodeImpl *impl = (_NodeImpl *)_impl; impl->renv.homePath = hp; - impl->renv.autoconfUrlPrefix = urlPrefix; - impl->renv.configAuthorityIdentityStr = configAuthorityIdentity; impl->reasonForTermination = Node::NODE_RUNNING; impl->started = false; impl->running = false; - impl->updateStatusNow = false; impl->terminateNow = false; } @@ -115,6 +260,10 @@ Node::~Node() { _NodeImpl *impl = (_NodeImpl *)_impl; +#ifndef __WINDOWS__ + delete impl->renv.netconfService; +#endif + delete impl->renv.sysEnv; delete impl->renv.topology; delete impl->renv.sw; @@ -155,11 +304,9 @@ Node::ReasonForTermination Node::run() TRACE("initializing..."); + // Create non-crypto PRNG right away in case other code in init wants to use it _r->prng = new CMWC4096(); - if (!_r->configAuthority.fromString(_r->configAuthorityIdentityStr)) - return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"configuration authority identity is not valid"); - bool gotId = false; std::string identitySecretPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.secret"); std::string identityPublicPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.public"); @@ -188,37 +335,35 @@ Node::ReasonForTermination Node::run() } Utils::lockDownFile(identitySecretPath.c_str(),false); - // Generate ownership verification secret, which can be presented to - // a controlling web site (like ours) to prove ownership of a node and - // permit its configuration to be centrally modified. When ZeroTier One - // requests its config it sends a hash of this secret, and so the - // config server can verify this hash to determine if the secret the - // user presents is correct. - std::string ovsPath(_r->homePath + ZT_PATH_SEPARATOR_S + "thisdeviceismine"); - if (((Utils::now() - Utils::getLastModified(ovsPath.c_str())) >= ZT_OVS_GENERATE_NEW_IF_OLDER_THAN)||(!Utils::readFile(ovsPath.c_str(),_r->ownershipVerificationSecret))) { - _r->ownershipVerificationSecret = ""; - unsigned int securern = 0; + // Clean up some obsolete files if present -- this will be removed later + unlink((_r->homePath + ZT_PATH_SEPARATOR_S + "status").c_str()); + unlink((_r->homePath + ZT_PATH_SEPARATOR_S + "thisdeviceismine").c_str()); + + // Load or generate config authentication secret + std::string configAuthTokenPath(_r->homePath + ZT_PATH_SEPARATOR_S + "authtoken.secret"); + std::string configAuthToken; + if (!Utils::readFile(configAuthTokenPath.c_str(),configAuthToken)) { + configAuthToken = ""; + unsigned int sr = 0; for(unsigned int i=0;i<24;++i) { - Utils::getSecureRandom(&securern,sizeof(securern)); - _r->ownershipVerificationSecret.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[securern % 62]); + Utils::getSecureRandom(&sr,sizeof(sr)); + configAuthToken.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[sr % 62]); } - _r->ownershipVerificationSecret.append(ZT_EOL_S); - if (!Utils::writeFile(ovsPath.c_str(),_r->ownershipVerificationSecret)) - return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write 'thisdeviceismine' (home path not writable?)"); + if (!Utils::writeFile(configAuthTokenPath.c_str(),configAuthToken)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write authtoken.secret (home path not writable?)"); } - Utils::lockDownFile(ovsPath.c_str(),false); - _r->ownershipVerificationSecret = Utils::trim(_r->ownershipVerificationSecret); // trim off CR file is saved with - unsigned char ovsDig[32]; - SHA256_CTX sha; - SHA256_Init(&sha); - SHA256_Update(&sha,_r->ownershipVerificationSecret.data(),_r->ownershipVerificationSecret.length()); - SHA256_Final(ovsDig,&sha); - _r->ownershipVerificationSecretHash = Utils::base64Encode(ovsDig,32); + Utils::lockDownFile(configAuthTokenPath.c_str(),false); // Create the core objects in RuntimeEnvironment: node config, demarcation // point, switch, network topology database, and system environment // watcher. - _r->nc = new NodeConfig(_r,_r->autoconfUrlPrefix + _r->identity.address().toString()); + try { + _r->nc = new NodeConfig(_r,configAuthToken.c_str()); + } catch ( ... ) { + // An exception here currently means that another instance of ZeroTier + // One is running. + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"another instance of ZeroTier One appears to be running, or local control UDP port cannot be bound"); + } _r->demarc = new Demarc(_r); _r->multicaster = new Multicaster(); _r->sw = new Switch(_r); @@ -247,17 +392,26 @@ Node::ReasonForTermination Node::run() return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"unknown exception during initialization"); } +#ifndef __WINDOWS__ try { - std::string statusPath(_r->homePath + ZT_PATH_SEPARATOR_S + "status"); + std::string netconfServicePath(_r->homePath + ZT_PATH_SEPARATOR_S + "services.d" + ZT_PATH_SEPARATOR_S + "netconf.service"); + if (Utils::fileExists(netconfServicePath.c_str())) { + LOG("netconf.d/netconfi.service appears to exist, starting..."); + _r->netconfService = new Service(_r,"netconf",netconfServicePath.c_str(),&_netconfServiceMessageHandler,_r); + } + } catch ( ... ) { + LOG("unexpected exception attempting to start services"); + } +#endif + try { uint64_t lastPingCheck = 0; - uint64_t lastTopologyClean = Utils::now(); // don't need to do this immediately + uint64_t lastClean = Utils::now(); // don't need to do this immediately uint64_t lastNetworkFingerprintCheck = 0; uint64_t lastAutoconfigureCheck = 0; uint64_t networkConfigurationFingerprint = _r->sysEnv->getNetworkConfigurationFingerprint(); uint64_t lastMulticastCheck = 0; uint64_t lastMulticastAnnounceAll = 0; - uint64_t lastStatusUpdate = 0; long lastDelayDelta = 0; LOG("%s starting version %s",_r->identity.address().toString().c_str(),versionString()); @@ -292,16 +446,6 @@ Node::ReasonForTermination Node::run() } } - if ((now - lastAutoconfigureCheck) >= ZT_AUTOCONFIGURE_CHECK_DELAY) { - // It seems odd to only do this simple check every so often, but the purpose is to - // delay between calls to refreshConfiguration() enough that the previous attempt - // has time to either succeed or fail. Otherwise we'll block the whole loop, since - // config update is guarded by a Mutex. - lastAutoconfigureCheck = now; - if ((now - _r->nc->lastAutoconfigure()) >= ZT_AUTOCONFIGURE_INTERVAL) - _r->nc->refreshConfiguration(); // happens in background - } - // Periodically check for changes in our local multicast subscriptions and broadcast // those changes to peers. if ((now - lastMulticastCheck) >= ZT_MULTICAST_LOCAL_POLL_PERIOD) { @@ -337,11 +481,9 @@ Node::ReasonForTermination Node::run() if ((now - lastPingCheck) >= ZT_PING_CHECK_DELAY) { lastPingCheck = now; try { - if (_r->topology->isSupernode(_r->identity.address())) { - // The only difference in how supernodes behave is here: they only - // actively ping each other and only passively listen for pings - // from anyone else. They also don't send firewall openers, since - // they're never firewalled. + if (_r->topology->amSupernode()) { + // Supernodes do not ping anyone but each other. They also don't + // send firewall openers, since they aren't ever firewalled. std::vector< SharedPtr<Peer> > sns(_r->topology->supernodePeers()); for(std::vector< SharedPtr<Peer> >::const_iterator p(sns.begin());p!=sns.end();++p) { if ((now - (*p)->lastDirectSend()) > ZT_PEER_DIRECT_PING_DELAY) @@ -384,23 +526,10 @@ Node::ReasonForTermination Node::run() } } - if ((now - lastTopologyClean) >= ZT_TOPOLOGY_CLEAN_PERIOD) { - lastTopologyClean = now; - _r->topology->clean(); // happens in background - } - - if (((now - lastStatusUpdate) >= ZT_STATUS_OUTPUT_PERIOD)||(impl->updateStatusNow)) { - lastStatusUpdate = now; - impl->updateStatusNow = false; - FILE *statusf = ::fopen(statusPath.c_str(),"w"); - if (statusf) { - try { - _r->topology->eachPeer(Topology::DumpPeerStatistics(statusf)); - } catch ( ... ) { - TRACE("unexpected exception updating status dump"); - } - ::fclose(statusf); - } + if ((now - lastClean) >= ZT_DB_CLEAN_PERIOD) { + lastClean = now; + _r->topology->clean(); + _r->nc->cleanAllNetworks(); } try { @@ -436,13 +565,6 @@ void Node::terminate() ((_NodeImpl *)_impl)->renv.mainLoopWaitCondition.signal(); } -void Node::updateStatusNow() - throw() -{ - ((_NodeImpl *)_impl)->updateStatusNow = true; - ((_NodeImpl *)_impl)->renv.mainLoopWaitCondition.signal(); -} - class _VersionStringMaker { public: |