From 2e85cf18c19ce86363de636ff30827fe232aa80b Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 17 Jul 2013 14:39:34 -0400 Subject: Cleanup and build fixes. --- node/Node.hpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'node/Node.hpp') diff --git a/node/Node.hpp b/node/Node.hpp index df6b946f..f4e2d423 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -58,11 +58,8 @@ public: * The node is not executed until run() is called. * * @param hp Home directory path - * @param url URL prefix for autoconfiguration (http and file permitted) - * @param configAuthorityIdentity Public identity used to encrypt/authenticate configuration from this URL (ASCII string format) - * @throws std::invalid_argument Invalid argument supplied to constructor */ - Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) + Node(const char *hp) throw(); ~Node(); @@ -98,12 +95,6 @@ public: void terminate() throw(); - /** - * Update the status file in the home directory on next service loop - */ - void updateStatusNow() - throw(); - /** * Get the ZeroTier version in major.minor.revision string format * -- cgit v1.2.3 From a677597b44ff94bf1f642f7ef81f926e09439ffd Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 18 Jul 2013 11:43:46 -0400 Subject: Better encode/decode code for control bus. --- node/Node.cpp | 65 ++++++++++++++++++++++++++ node/Node.hpp | 43 ++++++++++++++++++ node/NodeConfig.cpp | 129 +++++++++++++++++++++++++++++----------------------- node/NodeConfig.hpp | 43 +++++++++++++++--- 4 files changed, 217 insertions(+), 63 deletions(-) (limited to 'node/Node.hpp') diff --git a/node/Node.cpp b/node/Node.cpp index 5dbc5bb6..57bb8abc 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -57,6 +57,8 @@ #include "Constants.hpp" #include "InetAddress.hpp" #include "Pack.hpp" +#include "Salsa20.hpp" +#include "HMAC.hpp" #include "RuntimeEnvironment.hpp" #include "NodeConfig.hpp" #include "Defaults.hpp" @@ -71,6 +73,69 @@ namespace ZeroTier { +struct _LocalClientImpl +{ + unsigned char key[32]; + UdpSocket *sock; + void (*resultHandler)(void *,unsigned long,const char *); + void *arg; + Mutex inUseLock; +}; + +static void _CBlocalClientHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) +{ + _LocalClientImpl *impl = (_LocalClientImpl *)arg; + Mutex::Lock _l(impl->inUseLock); +} + +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 = 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() +{ + uint32_t convId = (uint32_t)rand(); + + return convId; +} + struct _NodeImpl { RuntimeEnvironment renv; diff --git a/node/Node.hpp b/node/Node.hpp index f4e2d423..bddced58 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -40,6 +40,45 @@ namespace ZeroTier { class Node { public: + /** + * Client for controlling a local ZeroTier One node + */ + class LocalClient + { + public: + /** + * Create a new node config client + * + * The result handler will be called from a different thread. Its + * arguments are the request ID generated by send() and each line + * of output. It may be called more than once per request result + * if the command generates more than one line of output. + * + * @param authToken Authentication token + * @param resultHandler Function to call when commands provide results + */ + LocalClient(const char *authToken,void (*resultHandler)(void *,unsigned long,const char *),void *arg) + throw(); + + ~LocalClient(); + + /** + * Send a command to the local node + * + * @param command + * @return Request ID that will be provided to result handler when/if results are sent back + */ + unsigned long send(const char *command) + throw(); + + private: + // LocalClient is not copyable + LocalClient(const LocalClient&); + const LocalClient& operator=(const LocalClient&); + + void *_impl; + }; + /** * Returned by node main if/when it terminates */ @@ -108,6 +147,10 @@ public: static unsigned int versionRevision() throw(); private: + // Nodes are not copyable + Node(const Node&); + const Node& operator=(const Node&); + void *const _impl; // private implementation }; diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp index 0daa9ebe..fca53942 100644 --- a/node/NodeConfig.cpp +++ b/node/NodeConfig.cpp @@ -52,19 +52,12 @@ namespace ZeroTier { NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken) throw(std::runtime_error) : _r(renv), - _authToken(authToken), _controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this) { SHA256_CTX sha; - - SHA256_Init(&sha); - SHA256_Update(&sha,_authToken.data(),_authToken.length()); - SHA256_Final(_keys,&sha); // first 32 bytes of keys[]: Salsa20 key - SHA256_Init(&sha); - SHA256_Update(&sha,_keys,32); - SHA256_Update(&sha,_authToken.data(),_authToken.length()); - SHA256_Final(_keys + 32,&sha); // second 32 bytes of keys[]: HMAC key + SHA256_Update(&sha,authToken,strlen(authToken)); + SHA256_Final(_controlSocketKey,&sha); } NodeConfig::~NodeConfig() @@ -146,64 +139,86 @@ std::vector NodeConfig::execute(const char *command) return r; } -void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) +std::vector< Buffer > NodeConfig::encodeControlMessage(const void *key,unsigned long conversationId,const std::vector &payload) + throw(std::out_of_range) { - char hmacKey[32]; char hmac[32]; - char buf[131072]; - NodeConfig *nc = (NodeConfig *)arg; - const RuntimeEnvironment *_r = nc->_r; + char keytmp[32]; + std::vector< Buffer > packets; + Buffer packet; - try { - // Minimum length - if (len < 28) - return; - if (len >= sizeof(buf)) // only up to len - 28 bytes are used on receive/decrypt - return; - - // Compare first 16 bytes of HMAC, which is after IV in packet - memcpy(hmacKey,nc->_keys + 32,32); - *((uint64_t *)hmacKey) ^= *((const uint64_t *)data); // include IV in HMAC - HMAC::sha256(hmacKey,32,((const unsigned char *)data) + 28,len - 28,hmac); - if (memcmp(hmac,((const unsigned char *)data) + 8,16)) - return; - - // Decrypt payload if we passed HMAC - Salsa20 s20(nc->_keys,256,data); // first 64 bits of data are IV - s20.decrypt(((const unsigned char *)data) + 28,buf,len - 28); - - // Null-terminate string for execute() - buf[len - 28] = (char)0; - - // Execute command - std::vector r(nc->execute(buf)); - - // Result packet contains a series of null-terminated results - unsigned int resultLen = 28; - for(std::vector::iterator i(r.begin());i!=r.end();++i) { - if ((resultLen + i->length() + 1) >= sizeof(buf)) - return; // result too long - memcpy(buf + resultLen,i->c_str(),i->length() + 1); - resultLen += i->length() + 1; + packet.setSize(16); // HMAC and IV + packet.append((uint32_t)(conversationId & 0xffffffff)); + for(unsigned int i=0;i= payload.size())||((packet.size() + payload[i + 1].length() + 1) >= packet.capacity())) { + Utils::getSecureRandom(packet.field(8,8),8); + + memcpy(keytmp,key,32); + for(unsigned int i=0;i<32;++i) + keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20 + HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac); + memcpy(packet.field(0,8),hmac,8); + + Salsa20 s20(key,256,packet.field(8,8)); + s20.encrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16); + + packets.push_back(packet); + + packet.setSize(16); // HMAC and IV + packet.append((uint32_t)(conversationId & 0xffffffff)); } + } - // Generate result packet IV - Utils::getSecureRandom(buf,8); + return packets; +} - // Generate result packet HMAC - memcpy(hmacKey,nc->_keys + 32,32); - *((uint64_t *)hmacKey) ^= *((const uint64_t *)buf); // include IV in HMAC - HMAC::sha256(hmacKey,32,((const unsigned char *)buf) + 28,resultLen - 28,hmac); - memcpy(buf + 8,hmac,16); +bool NodeConfig::decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector &payload) +{ + char hmac[32]; + char keytmp[32]; - // Copy arbitrary tag from original packet - memcpy(buf + 24,((const unsigned char *)data) + 24,4); + try { + if (len < 20) + return false; + + Buffer packet(data,len); + + memcpy(keytmp,key,32); + for(unsigned int i=0;i<32;++i) + keytmp[i] ^= 0x77; // use a different permutation of key for HMAC than for Salsa20 + HMAC::sha256(keytmp,32,packet.field(16,packet.size() - 16),packet.size() - 16,hmac); + if (memcmp(packet.field(0,8),hmac,8)) + return false; + + Salsa20 s20(key,256,packet.field(8,8)); + s20.decrypt(packet.field(16,packet.size() - 16),packet.field(16,packet.size() - 16),packet.size() - 16); + + conversationId = packet.at(16); + + const char *pl = ((const char *)packet.data()) + 20; + unsigned int pll = packet.size() - 20; + payload.clear(); + for(unsigned int i=0;i i) { + payload.push_back(std::string(pl + i,eos - i)); + i = eos + 1; + } else break; + } - // Send encrypted result back to requester - sock->send(remoteAddr,buf,resultLen,-1); + return true; } catch ( ... ) { - TRACE("unexpected exception parsing control packet or generating response"); + return false; } } +void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) +{ +} + } // namespace ZeroTier diff --git a/node/NodeConfig.hpp b/node/NodeConfig.hpp index 66c99448..d284062d 100644 --- a/node/NodeConfig.hpp +++ b/node/NodeConfig.hpp @@ -40,17 +40,25 @@ #include "Utils.hpp" #include "Http.hpp" #include "UdpSocket.hpp" +#include "Buffer.hpp" namespace ZeroTier { class RuntimeEnvironment; +/** + * Maximum size of a packet for node configuration + */ +#define ZT_NODECONFIG_MAX_PACKET_SIZE 4096 + /** * Node configuration endpoint * * Packet format for local UDP configuration packets: - * [8] random initialization vector * [16] first 16 bytes of HMAC-SHA-256 of payload + * [ -- begin HMAC'ed envelope -- ] + * [8] random initialization vector + * [ -- begin cryptographic envelope -- ] * [4] arbitrary tag, echoed in response * [...] payload * @@ -58,8 +66,6 @@ class RuntimeEnvironment; * responses, the payload consists of one or more response lines delimited * by NULL (0) characters. The tag field is replicated in the result * packet. - * - * TODO: further document use of keys, encryption... */ class NodeConfig { @@ -132,14 +138,39 @@ public: */ std::vector execute(const char *command); + /** + * Armor payload for control bus + * + * Note that no single element of payload can be longer than the max packet + * size. If this occurs out_of_range is thrown. + * + * @param key 32 byte key + * @param conversationId 32-bit conversation ID (bits beyond 32 are ignored) + * @param payload One or more strings to encode in packet + * @return One or more transport armored packets (if payload too big) + * @throws std::out_of_range An element of payload is too big + */ + static std::vector< Buffer > encodeControlMessage(const void *key,unsigned long conversationId,const std::vector &payload) + throw(std::out_of_range); + + /** + * Decode a packet from the control bus + * + * @param key 32 byte key + * @param data Packet data + * @param len Packet length + * @param conversationId Result parameter filled with conversation ID on success + * @param payload Result parameter filled with payload on success + * @return True on success, false on invalid packet or packet that failed authentication + */ + static bool decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector &payload); + private: static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len); const RuntimeEnvironment *_r; - const std::string _authToken; - unsigned char _keys[64]; // Salsa20 key, HMAC key - + unsigned char _controlSocketKey[32]; UdpSocket _controlSocket; std::map< uint64_t,SharedPtr > _networks; Mutex _networks_m; -- cgit v1.2.3 From 5f4eb1ebc60abdd762bc77ef5b1120fe528ccc8f Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 18 Jul 2013 16:35:52 -0400 Subject: Command line interface. --- Makefile.mac | 6 ++- cli.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++ node/Node.cpp | 38 +++++++++++++++- node/Node.hpp | 11 +++-- node/NodeConfig.cpp | 14 ++++++ 5 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 cli.cpp (limited to 'node/Node.hpp') diff --git a/Makefile.mac b/Makefile.mac index 89203243..bace24b2 100644 --- a/Makefile.mac +++ b/Makefile.mac @@ -20,12 +20,16 @@ LIBS=ext/bin/libcrypto/mac-x86_combined/libcrypto.a include objects.mk -all: one launcher mac-tap +all: one cli launcher mac-tap one: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-one main.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-one +cli: $(OBJS) + $(CXX) $(CXXFLAGS) -o zerotier-cli cli.cpp $(OBJS) $(LIBS) + $(STRIP) zerotier-cli + selftest: $(OBJS) $(CXX) $(CXXFLAGS) -o zerotier-selftest selftest.cpp $(OBJS) $(LIBS) $(STRIP) zerotier-selftest diff --git a/cli.cpp b/cli.cpp new file mode 100644 index 00000000..a72a0890 --- /dev/null +++ b/cli.cpp @@ -0,0 +1,124 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include +#include +#include + +#ifndef __WINDOWS__ +#include +#endif + +#include "node/Node.hpp" +#include "node/Constants.hpp" +#include "node/Utils.hpp" +#include "node/Thread.hpp" + +using namespace ZeroTier; + +static void printHelp(FILE *out,const char *exename) +{ + fprintf(out,"Usage: %s [-switches] "ZT_EOL_S,exename); + fprintf(out,ZT_EOL_S); + fprintf(out,"Switches:"ZT_EOL_S); + fprintf(out," -t - Specify token on command line"ZT_EOL_S); + fprintf(out," -T - Read token from file"ZT_EOL_S); + fprintf(out,ZT_EOL_S); + fprintf(out,"Use the 'help' command to get help from ZeroTier One itself."ZT_EOL_S); +} + +static volatile uint64_t lastResultTime = 0ULL; +static volatile unsigned int numResults = 0; + +static void resultHandler(void *arg,unsigned long id,const char *line) +{ + lastResultTime = Utils::now(); + ++numResults; + fprintf(stdout,"%s"ZT_EOL_S,line); +} + +int main(int argc,char **argv) +{ + if (argc <= 1) { + printHelp(stdout,argv[0]); + return -1; + } + + std::string authToken; + + for(int i=1;iresultHandler) + return; // sanity check Mutex::Lock _l(impl->inUseLock); + + try { + unsigned long convId = 0; + std::vector results; + if (!NodeConfig::decodeControlMessagePacket(impl->key,data,len,convId,results)) + return; + for(std::vector::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) @@ -114,6 +128,8 @@ Node::LocalClient::LocalClient(const char *authToken,void (*resultHandler)(void 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; } @@ -131,9 +147,27 @@ Node::LocalClient::~LocalClient() unsigned long Node::LocalClient::send(const char *command) throw() { - uint32_t convId = (uint32_t)rand(); + if (!_impl) + return 0; + _LocalClientImpl *impl = (_LocalClientImpl *)_impl; + Mutex::Lock _l(impl->inUseLock); + + try { + uint32_t convId = (uint32_t)rand(); + if (!convId) + convId = 1; - return convId; + std::vector tmp; + tmp.push_back(std::string(command)); + std::vector< Buffer > packets(NodeConfig::encodeControlMessage(impl->key,convId,tmp)); + + for(std::vector< Buffer >::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 diff --git a/node/Node.hpp b/node/Node.hpp index bddced58..b716b556 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -49,11 +49,6 @@ public: /** * Create a new node config client * - * The result handler will be called from a different thread. Its - * arguments are the request ID generated by send() and each line - * of output. It may be called more than once per request result - * if the command generates more than one line of output. - * * @param authToken Authentication token * @param resultHandler Function to call when commands provide results */ @@ -65,8 +60,12 @@ public: /** * Send a command to the local node * + * Note that the returned conversation ID will never be 0. A return value + * of 0 indicates a fatal error such as failure to bind to any local UDP + * port. + * * @param command - * @return Request ID that will be provided to result handler when/if results are sent back + * @return Conversation ID that will be provided to result handler when/if results are sent back */ unsigned long send(const char *command) throw(); diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp index 381bbd62..21ed5188 100644 --- a/node/NodeConfig.cpp +++ b/node/NodeConfig.cpp @@ -218,6 +218,20 @@ bool NodeConfig::decodeControlMessagePacket(const void *key,const void *data,uns void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len) { + NodeConfig *nc = (NodeConfig *)arg; + try { + unsigned long convId = 0; + std::vector commands; + + if (!decodeControlMessagePacket(nc->_controlSocketKey,data,len,convId,commands)) + return; + + for(std::vector::iterator c(commands.begin());c!=commands.end();++c) { + std::vector< Buffer > resultPackets(encodeControlMessage(nc->_controlSocketKey,convId,nc->execute(c->c_str()))); + for(std::vector< Buffer >::iterator p(resultPackets.begin());p!=resultPackets.end();++p) + sock->send(remoteAddr,p->data(),p->size(),-1); + } + } catch ( ... ) {} } } // namespace ZeroTier -- cgit v1.2.3