summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--node/Node.cpp65
-rw-r--r--node/Node.hpp43
-rw-r--r--node/NodeConfig.cpp129
-rw-r--r--node/NodeConfig.hpp43
4 files changed, 217 insertions, 63 deletions
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
@@ -41,6 +41,45 @@ 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
*/
enum ReasonForTermination
@@ -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<std::string> 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<ZT_NODECONFIG_MAX_PACKET_SIZE> > NodeConfig::encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &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<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets;
+ Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> 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<std::string> r(nc->execute(buf));
-
- // Result packet contains a series of null-terminated results
- unsigned int resultLen = 28;
- for(std::vector<std::string>::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();++i) {
+ packet.append(payload[i]); // will throw if too big
+ packet.append((unsigned char)0);
+
+ if (((i + 1) >= 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<std::string> &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<ZT_NODECONFIG_MAX_PACKET_SIZE> 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<uint32_t>(16);
+
+ const char *pl = ((const char *)packet.data()) + 20;
+ unsigned int pll = packet.size() - 20;
+ payload.clear();
+ for(unsigned int i=0;i<pll;) {
+ unsigned int eos = i;
+ while ((eos < pll)&&(pl[eos]))
+ ++eos;
+ if (eos > 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<std::string> 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<ZT_NODECONFIG_MAX_PACKET_SIZE> > encodeControlMessage(const void *key,unsigned long conversationId,const std::vector<std::string> &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<std::string> &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<Network> > _networks;
Mutex _networks_m;