summaryrefslogtreecommitdiff
path: root/node
diff options
context:
space:
mode:
Diffstat (limited to 'node')
-rw-r--r--node/Address.hpp186
-rw-r--r--node/BlobArray.hpp94
-rw-r--r--node/Constants.hpp47
-rw-r--r--node/Defaults.cpp4
-rw-r--r--node/Defaults.hpp10
-rw-r--r--node/Demarc.cpp4
-rw-r--r--node/Demarc.hpp3
-rw-r--r--node/Dictionary.hpp207
-rw-r--r--node/EllipticCurveKey.hpp12
-rw-r--r--node/EllipticCurveKeyPair.cpp20
-rw-r--r--node/EllipticCurveKeyPair.hpp2
-rw-r--r--node/EthernetTap.cpp14
-rw-r--r--node/Filter.cpp301
-rw-r--r--node/Filter.hpp71
-rw-r--r--node/Http.cpp323
-rw-r--r--node/Http.hpp129
-rw-r--r--node/Identity.cpp12
-rw-r--r--node/Identity.hpp8
-rw-r--r--node/Multicaster.hpp16
-rw-r--r--node/Network.cpp123
-rw-r--r--node/Network.hpp348
-rw-r--r--node/Node.cpp278
-rw-r--r--node/Node.hpp53
-rw-r--r--node/NodeConfig.cpp313
-rw-r--r--node/NodeConfig.hpp90
-rw-r--r--node/Pack.cpp159
-rw-r--r--node/Pack.hpp141
-rw-r--r--node/Packet.cpp4
-rw-r--r--node/Packet.hpp78
-rw-r--r--node/PacketDecoder.cpp97
-rw-r--r--node/PacketDecoder.hpp3
-rw-r--r--node/RuntimeEnvironment.hpp21
-rw-r--r--node/Service.cpp241
-rw-r--r--node/Service.hpp129
-rw-r--r--node/Switch.cpp10
-rw-r--r--node/Thread.cpp12
-rw-r--r--node/Thread.hpp5
-rw-r--r--node/Topology.cpp20
-rw-r--r--node/Topology.hpp37
-rw-r--r--node/UdpSocket.cpp9
-rw-r--r--node/UdpSocket.hpp2
-rw-r--r--node/Utils.cpp46
-rw-r--r--node/Utils.hpp70
43 files changed, 2275 insertions, 1477 deletions
diff --git a/node/Address.hpp b/node/Address.hpp
index 4c912540..fce4f20c 100644
--- a/node/Address.hpp
+++ b/node/Address.hpp
@@ -28,72 +28,156 @@
#ifndef _ZT_ADDRESS_HPP
#define _ZT_ADDRESS_HPP
+#include <stdio.h>
+#include <stdlib.h>
#include <stdint.h>
+#include <string.h>
#include <string>
#include "Utils.hpp"
#include "MAC.hpp"
#include "Constants.hpp"
+#include "Buffer.hpp"
namespace ZeroTier {
/**
- * ZeroTier address, which doubles as the last 5 octets of the MAC on taps
- *
- * Natural sort order will differ on big vs. little endian machines, but that
- * won't matter when it's used as a local map/set key.
+ * A ZeroTier address
*/
class Address
{
-private:
- union {
- unsigned char o[ZT_ADDRESS_LENGTH];
- uint64_t v;
- } _a;
-
public:
Address()
- throw()
+ throw() :
+ _a(0)
{
- _a.v = 0;
}
Address(const Address &a)
+ throw() :
+ _a(a._a)
+ {
+ }
+
+ Address(uint64_t a)
+ throw() :
+ _a(a & 0xffffffffffULL)
+ {
+ }
+
+ Address(const char *s)
throw()
{
- _a.v = a._a.v;
+ unsigned char foo[ZT_ADDRESS_LENGTH];
+ setTo(foo,Utils::unhex(s,foo,ZT_ADDRESS_LENGTH));
}
- /**
- * Create from a ZeroTier MAC
- *
- * @param m MAC (assumed to be a ZeroTier MAC)
- */
- Address(const MAC &m)
+ Address(const std::string &s)
throw()
{
- _a.v = 0;
- for(int i=0;i<ZT_ADDRESS_LENGTH;++i)
- _a.o[i] = m.data[i + 1];
+ unsigned char foo[ZT_ADDRESS_LENGTH];
+ setTo(foo,Utils::unhex(s.c_str(),foo,ZT_ADDRESS_LENGTH));
}
/**
- * @param bits Raw address -- 5 bytes in length
+ * @param bits Raw address -- 5 bytes, big-endian byte order
+ * @param len Length of array
*/
- Address(const void *bits)
+ Address(const void *bits,unsigned int len)
throw()
{
- _a.v = 0;
- for(int i=0;i<ZT_ADDRESS_LENGTH;++i)
- _a.o[i] = ((const unsigned char *)bits)[i];
+ setTo(bits,len);
}
inline Address &operator=(const Address &a)
throw()
{
- _a.v = a._a.v;
+ _a = a._a;
return *this;
}
+ inline Address &operator=(const uint64_t a)
+ throw()
+ {
+ _a = (a & 0xffffffffffULL);
+ return *this;
+ }
+
+ /**
+ * @param bits Raw address -- 5 bytes, big-endian byte order
+ * @param len Length of array
+ */
+ inline void setTo(const void *bits,unsigned int len)
+ throw()
+ {
+ if (len < ZT_ADDRESS_LENGTH) {
+ _a = 0;
+ return;
+ }
+ const unsigned char *b = (const unsigned char *)bits;
+ uint64_t a = ((uint64_t)*b++) << 32;
+ a |= ((uint64_t)*b++) << 24;
+ a |= ((uint64_t)*b++) << 16;
+ a |= ((uint64_t)*b++) << 8;
+ a |= ((uint64_t)*b);
+ _a = a;
+ }
+
+ /**
+ * @param bits Buffer to hold 5-byte address in big-endian byte order
+ * @param len Length of array
+ */
+ inline void copyTo(void *bits,unsigned int len) const
+ throw()
+ {
+ if (len < ZT_ADDRESS_LENGTH)
+ return;
+ unsigned char *b = (unsigned char *)bits;
+ *(b++) = (unsigned char)((_a >> 32) & 0xff);
+ *(b++) = (unsigned char)((_a >> 24) & 0xff);
+ *(b++) = (unsigned char)((_a >> 16) & 0xff);
+ *(b++) = (unsigned char)((_a >> 8) & 0xff);
+ *b = (unsigned char)(_a & 0xff);
+ }
+
+ /**
+ * Append to a buffer in big-endian byte order
+ *
+ * @param b Buffer to append to
+ */
+ template<unsigned int C>
+ inline void appendTo(Buffer<C> &b) const
+ throw(std::out_of_range)
+ {
+ b.append((unsigned char)((_a >> 32) & 0xff));
+ b.append((unsigned char)((_a >> 24) & 0xff));
+ b.append((unsigned char)((_a >> 16) & 0xff));
+ b.append((unsigned char)((_a >> 8) & 0xff));
+ b.append((unsigned char)(_a & 0xff));
+ }
+
+ /**
+ * @return String containing address as 5 binary bytes
+ */
+ inline std::string toBinaryString() const
+ {
+ std::string b;
+ b.push_back((char)((_a >> 32) & 0xff));
+ b.push_back((char)((_a >> 24) & 0xff));
+ b.push_back((char)((_a >> 16) & 0xff));
+ b.push_back((char)((_a >> 8) & 0xff));
+ b.push_back((char)(_a & 0xff));
+ return b;
+ }
+
+ /**
+ * @return Integer containing address (0 to 2^40)
+ */
+ inline uint64_t toInt() const
+ throw()
+ {
+ return _a;
+ }
+
/**
* Derive a MAC whose first octet is the ZeroTier LAN standard
*
@@ -104,8 +188,7 @@ public:
{
MAC m;
m.data[0] = ZT_MAC_FIRST_OCTET;
- for(int i=1;i<6;++i)
- m.data[i] = _a.o[i - 1];
+ copyTo(m.data + 1,ZT_ADDRESS_LENGTH);
return m;
}
@@ -114,18 +197,15 @@ public:
*/
inline std::string toString() const
{
- return Utils::hex(_a.o,ZT_ADDRESS_LENGTH);
+ char buf[16];
+ sprintf(buf,"%.10llx",(unsigned long long)_a);
+ return std::string(buf);
};
/**
- * Set address to zero
- */
- inline void zero() throw() { _a.v = 0; }
-
- /**
* @return True if this address is not zero
*/
- inline operator bool() const throw() { return (_a.v); }
+ inline operator bool() const throw() { return (_a); }
/**
* @return Sum of all bytes in address
@@ -133,10 +213,7 @@ public:
inline unsigned int sum() const
throw()
{
- unsigned int s = 0;
- for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i)
- s += _a.o[i];
- return s;
+ return (unsigned int)(((_a >> 32) & 0xff) + ((_a >> 24) & 0xff) + ((_a >> 16) & 0xff) + ((_a >> 8) & 0xff) + (_a & 0xff));
}
/**
@@ -151,23 +228,24 @@ public:
inline bool isReserved() const
throw()
{
- return ((!_a.v)||(_a.o[0] == ZT_ADDRESS_RESERVED_PREFIX));
+ return ((!_a)||((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX));
}
- inline unsigned char *data() throw() { return _a.o; }
- inline const unsigned char *data() const throw() { return _a.o; }
-
- inline unsigned int size() const throw() { return ZT_ADDRESS_LENGTH; }
+ /**
+ * @param i Value from 0 to 4 (inclusive)
+ * @return Byte at said position (address interpreted in big-endian order)
+ */
+ inline unsigned char operator[](unsigned int i) const throw() { return (unsigned char)((_a >> (32 - (i * 8))) & 0xff); }
- inline unsigned char &operator[](unsigned int i) throw() { return _a.o[i]; }
- inline unsigned char operator[](unsigned int i) const throw() { return _a.o[i]; }
+ inline bool operator==(const Address &a) const throw() { return (_a == a._a); }
+ inline bool operator!=(const Address &a) const throw() { return (_a != a._a); }
+ inline bool operator>(const Address &a) const throw() { return (_a > a._a); }
+ inline bool operator<(const Address &a) const throw() { return (_a < a._a); }
+ inline bool operator>=(const Address &a) const throw() { return (_a >= a._a); }
+ inline bool operator<=(const Address &a) const throw() { return (_a <= a._a); }
- inline bool operator==(const Address &a) const throw() { return (_a.v == a._a.v); }
- inline bool operator!=(const Address &a) const throw() { return (_a.v != a._a.v); }
- inline bool operator<(const Address &a) const throw() { return (_a.v < a._a.v); }
- inline bool operator>(const Address &a) const throw() { return (_a.v > a._a.v); }
- inline bool operator<=(const Address &a) const throw() { return (_a.v <= a._a.v); }
- inline bool operator>=(const Address &a) const throw() { return (_a.v >= a._a.v); }
+private:
+ uint64_t _a;
};
} // namespace ZeroTier
diff --git a/node/BlobArray.hpp b/node/BlobArray.hpp
deleted file mode 100644
index d78bcffa..00000000
--- a/node/BlobArray.hpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * 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/
- */
-
-#ifndef _ZT_BLOBARRAY_HPP
-#define _ZT_BLOBARRAY_HPP
-
-#include <vector>
-#include <string>
-#include <algorithm>
-
-namespace ZeroTier {
-
-/**
- * A vector of binary strings serializable in a packed format
- *
- * The format uses variable-length integers to indicate the length of each
- * field. Each byte of the length has another byte with seven more significant
- * bits if its 8th bit is set. Fields can be up to 2^28 in length.
- */
-class BlobArray : public std::vector<std::string>
-{
-public:
- inline std::string serialize() const
- {
- std::string r;
- for(BlobArray::const_iterator i=begin();i!=end();++i) {
- unsigned int flen = (unsigned int)i->length();
- do {
- unsigned char flenb = (unsigned char)(flen & 0x7f);
- flen >>= 7;
- flenb |= (flen) ? 0x80 : 0;
- r.push_back((char)flenb);
- } while (flen);
- r.append(*i);
- }
- return r;
- }
-
- /**
- * Deserialize, replacing the current contents of this array
- *
- * @param data Serialized binary data
- * @param len Length of serialized data
- */
- inline void deserialize(const void *data,unsigned int len)
- {
- clear();
- for(unsigned int i=0;i<len;) {
- unsigned int flen = 0;
- unsigned int chunk = 0;
- while (i < len) {
- flen |= ((unsigned int)(((const unsigned char *)data)[i] & 0x7f)) << (7 * chunk++);
- if (!(((const unsigned char *)data)[i++] & 0x80))
- break;
- }
- flen = std::min(flen,len - i);
- push_back(std::string(((const char *)data) + i,flen));
- i += flen;
- }
- }
- inline void deserialize(const std::string &data)
- {
- deserialize(data.data(),(unsigned int)data.length());
- }
-};
-
-} // namespace ZeroTier
-
-#endif
-
diff --git a/node/Constants.hpp b/node/Constants.hpp
index fbb309e6..36973b38 100644
--- a/node/Constants.hpp
+++ b/node/Constants.hpp
@@ -118,6 +118,11 @@ error_no_ZT_ARCH_defined;
#define ZT_DEFAULT_UDP_PORT 8993
/**
+ * Local control port, also used for multiple invocation check
+ */
+#define ZT_CONTROL_UDP_PORT 39393
+
+/**
* Default payload MTU for UDP packets
*
* In the future we might support UDP path MTU discovery, but for now we
@@ -152,13 +157,6 @@ error_no_ZT_ARCH_defined;
#define ZT_IF_MTU 2800
/**
- * Maximum number of networks we can be a member of
- *
- * This is a safe value that's within the tap device limit on all known OSes.
- */
-#define ZT_MAX_NETWORK_MEMBERSHIPS 16
-
-/**
* Maximum number of packet fragments we'll support
*
* The actual spec allows 16, but this is the most we'll support right
@@ -185,9 +183,9 @@ error_no_ZT_ARCH_defined;
#define ZT_MAC_FIRST_OCTET 0x32
/**
- * How often Topology::clean() is called in ms
+ * How often Topology::clean() and Network::clean() are called in ms
*/
-#define ZT_TOPOLOGY_CLEAN_PERIOD 300000
+#define ZT_DB_CLEAN_PERIOD 300000
/**
* Delay between WHOIS retries in ms
@@ -233,21 +231,16 @@ error_no_ZT_ARCH_defined;
#define ZT_MULTICAST_PROPAGATION_DEPTH 7
/**
- * Length of circular ring buffer history of multicast packets
+ * Length of ring buffer history of recent multicast packets
*/
#define ZT_MULTICAST_DEDUP_HISTORY_LENGTH 1024
/**
- * Expiration time in ms for multicast history items
+ * Expiration time in ms for multicast deduplication history items
*/
#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 4000
/**
- * Number of bits to randomly "decay" in bloom filter per hop
- */
-#define ZT_MULTICAST_BLOOM_FILTER_DECAY_RATE 2
-
-/**
* Period between announcements of all multicast 'likes' in ms
*
* Announcement occurs when a multicast group is locally joined, but all
@@ -282,23 +275,6 @@ error_no_ZT_ARCH_defined;
#define ZT_PEER_DIRECT_PING_DELAY 120000
/**
- * Period between rechecks of autoconfigure URL
- *
- * This is in the absence of an external message ordering a recheck.
- */
-#define ZT_AUTOCONFIGURE_INTERVAL 3600000
-
-/**
- * Period between autoconfigure attempts if no successful autoconfig
- */
-#define ZT_AUTOCONFIGURE_CHECK_DELAY 15000
-
-/**
- * Delay between updates of status file in home directory
- */
-#define ZT_STATUS_OUTPUT_PERIOD 120000
-
-/**
* Minimum delay in Node service loop
*
* This is the shortest of the check delays/periods.
@@ -348,9 +324,4 @@ error_no_ZT_ARCH_defined;
*/
#define ZT_RENDEZVOUS_NAT_T_DELAY 500
-/**
- * Generate a new ownership verify secret on launch if older than this
- */
-#define ZT_OVS_GENERATE_NEW_IF_OLDER_THAN 86400000
-
#endif
diff --git a/node/Defaults.cpp b/node/Defaults.cpp
index f1454796..3e936b2d 100644
--- a/node/Defaults.cpp
+++ b/node/Defaults.cpp
@@ -68,9 +68,7 @@ static inline std::map< Identity,std::vector<InetAddress> > _mkSupernodeMap()
Defaults::Defaults()
throw(std::runtime_error) :
- supernodes(_mkSupernodeMap()),
- configUrlPrefix("http://api.zerotier.com/one/nc/"),
- configAuthority("f9f34184ac:1:AwGgrWjb8dARXzruqxiy1+Qf+gz4iM5IMfQTCWrJXkwERdvbvxTPZvtIyitw4gS90TGIxW+e7uJxweg9Vyq5lZJBrg==:QeEQLm9ymLC3EcnIw2OUqufUwb2wgHSAg6wQOXKyhT779p/8Hz5485PZLJCbr/aVHjwzop8APJk9B45Zm0Mb/LEhQTBMH2jvc7qqoYnMCNCO9jpADeMJwMW5e1VFgIObWl9uNjhRbf5/m8dZcn0pKKGwjSoP1QTeVWOC8GkZhE25bUWj")
+ supernodes(_mkSupernodeMap())
{
}
diff --git a/node/Defaults.hpp b/node/Defaults.hpp
index b9c8ecf5..3a350865 100644
--- a/node/Defaults.hpp
+++ b/node/Defaults.hpp
@@ -55,16 +55,6 @@ public:
* Supernodes on the ZeroTier network
*/
const std::map< Identity,std::vector<InetAddress> > supernodes;
-
- /**
- * URL prefix for autoconfiguration
- */
- const std::string configUrlPrefix;
-
- /**
- * Identity used to encrypt and authenticate configuration from URL
- */
- const std::string configAuthority;
};
extern const Defaults ZT_DEFAULTS;
diff --git a/node/Demarc.cpp b/node/Demarc.cpp
index 5efe55bc..b935ce53 100644
--- a/node/Demarc.cpp
+++ b/node/Demarc.cpp
@@ -100,7 +100,7 @@ bool Demarc::bindLocalUdp(unsigned int localPort)
DemarcPortObj *v4r = &(_ports[(Port)v4p]);
v4r->port = (Port)v4p;
v4r->parent = this;
- v4r->obj = v4 = new UdpSocket(localPort,false,&Demarc::_CBudpSocketPacketHandler,v4r);
+ v4r->obj = v4 = new UdpSocket(false,localPort,false,&Demarc::_CBudpSocketPacketHandler,v4r);
v4r->type = PORT_TYPE_UDP_SOCKET_V4;
} catch ( ... ) {
_ports.erase((Port)v4p);
@@ -112,7 +112,7 @@ bool Demarc::bindLocalUdp(unsigned int localPort)
DemarcPortObj *v6r = &(_ports[(Port)v6p]);
v6r->port = (Port)v6p;
v6r->parent = this;
- v6r->obj = v6 = new UdpSocket(localPort,true,&Demarc::_CBudpSocketPacketHandler,v6r);
+ v6r->obj = v6 = new UdpSocket(false,localPort,true,&Demarc::_CBudpSocketPacketHandler,v6r);
v6r->type = PORT_TYPE_UDP_SOCKET_V6;
} catch ( ... ) {
_ports.erase((Port)v6p);
diff --git a/node/Demarc.hpp b/node/Demarc.hpp
index e13ed0cb..721dc0c8 100644
--- a/node/Demarc.hpp
+++ b/node/Demarc.hpp
@@ -49,6 +49,9 @@ class UdpSocket;
* about what they actually are.
*
* All ports are closed when this class is destroyed.
+ *
+ * Its name "demarcation point" comes from the telco/cable terminology for
+ * the box where wires terminate at a customer's property.
*/
class Demarc
{
diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp
new file mode 100644
index 00000000..f706168f
--- /dev/null
+++ b/node/Dictionary.hpp
@@ -0,0 +1,207 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * 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/
+ */
+
+#ifndef _ZT_DICTIONARY_HPP
+#define _ZT_DICTIONARY_HPP
+
+#include <string>
+#include <map>
+#include <stdexcept>
+#include "Constants.hpp"
+
+namespace ZeroTier {
+
+/**
+ * Simple key/value dictionary with string serialization
+ *
+ * The serialization format is a flat key=value with backslash escape.
+ * It does not support comments or other syntactic complexities. It is
+ * human-readable if the keys and values in the dictionary are also
+ * human-readable. Otherwise it might contain unprintable characters.
+ */
+class Dictionary : public std::map<std::string,std::string>
+{
+public:
+ Dictionary()
+ {
+ }
+
+ /**
+ * @param s String-serialized dictionary
+ */
+ Dictionary(const char *s)
+ {
+ fromString(s);
+ }
+
+ /**
+ * @param s String-serialized dictionary
+ */
+ Dictionary(const std::string &s)
+ {
+ fromString(s.c_str());
+ }
+
+ /**
+ * Get a key, throwing an exception if it is not present
+ *
+ * @param key Key to look up
+ * @return Reference to value
+ * @throws std::invalid_argument Key not found
+ */
+ inline const std::string &get(const std::string &key) const
+ throw(std::invalid_argument)
+ {
+ const_iterator e(find(key));
+ if (e == end())
+ throw std::invalid_argument(std::string("missing required field: ")+key);
+ return e->second;
+ }
+
+ /**
+ * Get a key, returning a default if not present
+ *
+ * @param key Key to look up
+ * @param dfl Default if not present
+ * @return Value or default
+ */
+ inline const std::string &get(const std::string &key,const std::string &dfl) const
+ {
+ const_iterator e(find(key));
+ if (e == end())
+ return dfl;
+ return e->second;
+ }
+
+ /**
+ * @param key Key to check
+ * @return True if dictionary contains key
+ */
+ inline bool contains(const std::string &key) const
+ {
+ return (find(key) != end());
+ }
+
+ /**
+ * @return String-serialized dictionary
+ */
+ inline std::string toString() const
+ {
+ std::string s;
+
+ for(const_iterator kv(begin());kv!=end();++kv) {
+ _appendEsc(kv->first.data(),kv->first.length(),s);
+ s.push_back('=');
+ _appendEsc(kv->second.data(),kv->second.length(),s);
+ s.append(ZT_EOL_S);
+ }
+
+ return s;
+ }
+
+ /**
+ * Clear and initialize from a string
+ *
+ * @param s String-serialized dictionary
+ */
+ inline void fromString(const char *s)
+ {
+ clear();
+ bool escapeState = false;
+ std::string keyBuf;
+ std::string *element = &keyBuf;
+ while (*s) {
+ if (escapeState) {
+ escapeState = false;
+ switch(*s) {
+ case '0':
+ element->push_back((char)0);
+ break;
+ case 'r':
+ element->push_back('\r');
+ break;
+ case 'n':
+ element->push_back('\n');
+ break;
+ default:
+ element->push_back(*s);
+ break;
+ }
+ } else {
+ if (*s == '\\') {
+ escapeState = true;
+ } else if (*s == '=') {
+ if (element == &keyBuf)
+ element = &((*this)[keyBuf]);
+ } else if ((*s == '\r')||(*s == '\n')) {
+ if ((element == &keyBuf)&&(keyBuf.length() > 0))
+ (*this)[keyBuf];
+ keyBuf = "";
+ element = &keyBuf;
+ } else element->push_back(*s);
+ }
+ ++s;
+ }
+ if ((element == &keyBuf)&&(keyBuf.length() > 0))
+ (*this)[keyBuf];
+ }
+ inline void fromString(const std::string &s)
+ {
+ fromString(s.c_str());
+ }
+
+private:
+ static inline void _appendEsc(const char *data,unsigned int len,std::string &to)
+ {
+ for(unsigned int i=0;i<len;++i) {
+ switch(data[i]) {
+ case 0:
+ to.append("\\0");
+ break;
+ case '\r':
+ to.append("\\r");
+ break;
+ case '\n':
+ to.append("\\n");
+ break;
+ case '\\':
+ to.append("\\\\");
+ break;
+ case '=':
+ to.append("\\=");
+ break;
+ default:
+ to.push_back(data[i]);
+ break;
+ }
+ }
+ }
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/node/EllipticCurveKey.hpp b/node/EllipticCurveKey.hpp
index 5a7b895f..b32f702d 100644
--- a/node/EllipticCurveKey.hpp
+++ b/node/EllipticCurveKey.hpp
@@ -65,15 +65,19 @@ public:
throw() :
_bytes(0)
{
+ memset(_key,0,sizeof(_key));
}
EllipticCurveKey(const void *data,unsigned int len)
throw()
{
- if (len <= ZT_EC_MAX_BYTES) {
- _bytes = len;
- memcpy(_key,data,len);
- } else _bytes = 0;
+ set(data,len);
+ }
+
+ EllipticCurveKey(const std::string &data)
+ throw()
+ {
+ set(data.data(),data.length());
}
EllipticCurveKey(const EllipticCurveKey &k)
diff --git a/node/EllipticCurveKeyPair.cpp b/node/EllipticCurveKeyPair.cpp
index bed0725e..66acf320 100644
--- a/node/EllipticCurveKeyPair.cpp
+++ b/node/EllipticCurveKeyPair.cpp
@@ -55,7 +55,20 @@ public:
};
static _EC_Group ZT_EC_GROUP;
-/* Key derivation function */
+/**
+ * Key derivation function
+ *
+ * TODO:
+ * If/when we document the protocol, this will have to be documented as
+ * well. It's a fairly standard KDF that uses SHA-256 to transform the
+ * raw EC key. It's generally considered good crypto practice to do this
+ * to eliminate the possibility of leaking information from EC exchange to
+ * downstream algorithms.
+ *
+ * In our code it is used to produce a two 32-bit keys. One key is used
+ * for Salsa20 and the other for HMAC-SHA-256. They are generated together
+ * as a single 64-bit key.
+ */
static void *_zt_EC_KDF(const void *in,size_t inlen,void *out,size_t *outlen)
{
SHA256_CTX sha;
@@ -130,9 +143,8 @@ bool EllipticCurveKeyPair::generate()
fread(tmp,sizeof(tmp),1,rf);
fclose(rf);
} else {
- fprintf(stderr,"WARNING: cannot open /dev/urandom\n");
- for(unsigned int i=0;i<sizeof(tmp);++i)
- tmp[i] = (unsigned char)(rand() >> 3);
+ fprintf(stderr,"FATAL: could not open /dev/urandom\n");
+ exit(-1);
}
RAND_seed(tmp,sizeof(tmp));
#else
diff --git a/node/EllipticCurveKeyPair.hpp b/node/EllipticCurveKeyPair.hpp
index 2649f4c4..dbe08cc9 100644
--- a/node/EllipticCurveKeyPair.hpp
+++ b/node/EllipticCurveKeyPair.hpp
@@ -35,6 +35,8 @@ namespace ZeroTier {
/**
* An elliptic curve key pair supporting generation and key agreement
+ *
+ * This is basically OpenSSL libcrypto glue.
*/
class EllipticCurveKeyPair
{
diff --git a/node/EthernetTap.cpp b/node/EthernetTap.cpp
index 8fb5e788..c45d50c2 100644
--- a/node/EthernetTap.cpp
+++ b/node/EthernetTap.cpp
@@ -218,7 +218,7 @@ EthernetTap::EthernetTap(
int kextpid;
char tmp[4096];
strcpy(tmp,_r->homePath.c_str());
- if ((kextpid = (int)fork()) == 0) {
+ if ((kextpid = (int)vfork()) == 0) {
chdir(tmp);
execl(ZT_MAC_KEXTLOAD,ZT_MAC_KEXTLOAD,"-q","-repository",tmp,"tap.kext",(const char *)0);
exit(-1);
@@ -255,7 +255,7 @@ EthernetTap::EthernetTap(
// Configure MAC address and MTU, bring interface up
long cpid;
- if ((cpid = (long)fork()) == 0) {
+ if ((cpid = (long)vfork()) == 0) {
execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0);
exit(-1);
} else {
@@ -285,7 +285,7 @@ EthernetTap::~EthernetTap()
#ifdef __APPLE__
void EthernetTap::whack()
{
- long cpid = (long)fork();
+ long cpid = (long)vfork();
if (cpid == 0) {
execl(ZT_MAC_IPCONFIG,ZT_MAC_IPCONFIG,"set",_dev,"AUTOMATIC-V6",(const char *)0);
exit(-1);
@@ -304,7 +304,7 @@ void EthernetTap::whack() {}
#ifdef __LINUX__
static bool ___removeIp(const char *_dev,const InetAddress &ip)
{
- long cpid = (long)fork();
+ long cpid = (long)vfork();
if (cpid == 0) {
execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","del",ip.toString().c_str(),"dev",_dev,(const char *)0);
exit(1); /* not reached unless exec fails */
@@ -337,7 +337,7 @@ bool EthernetTap::addIP(const InetAddress &ip)
}
long cpid;
- if ((cpid = (long)fork()) == 0) {
+ if ((cpid = (long)vfork()) == 0) {
execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","add",ip.toString().c_str(),"dev",_dev,(const char *)0);
exit(-1);
} else {
@@ -357,7 +357,7 @@ bool EthernetTap::addIP(const InetAddress &ip)
static bool ___removeIp(const char *_dev,const InetAddress &ip)
{
int cpid;
- if ((cpid = (int)fork()) == 0) {
+ if ((cpid = (int)vfork()) == 0) {
execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"inet",ip.toIpString().c_str(),"-alias",(const char *)0);
exit(-1);
} else {
@@ -390,7 +390,7 @@ bool EthernetTap::addIP(const InetAddress &ip)
}
int cpid;
- if ((cpid = (int)fork()) == 0) {
+ if ((cpid = (int)vfork()) == 0) {
execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0);
exit(-1);
} else {
diff --git a/node/Filter.cpp b/node/Filter.cpp
index 5cd295c3..a0412173 100644
--- a/node/Filter.cpp
+++ b/node/Filter.cpp
@@ -25,6 +25,9 @@
* LLC. Start here: http://www.zerotier.com/
*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <stdint.h>
#include "RuntimeEnvironment.hpp"
@@ -34,21 +37,20 @@
namespace ZeroTier {
+const char *const Filter::UNKNOWN_NAME = "(unknown)";
+const Range<unsigned int> Filter::ANY;
+
bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int len) const
+ throw(std::invalid_argument)
{
if ((!_etherType)||(_etherType(etype))) { // ethertype is ANY, or matches
// Ethertype determines meaning of protocol and port
switch(etype) {
- default:
- if ((!_protocol)&&(!_port))
- return true; // match other ethertypes if protocol and port are ANY, since we don't know what to do with them
- break;
-
case ZT_ETHERTYPE_IPV4:
if (len > 20) {
- if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // IP protocol
- if (!_port)
- return true; // protocol matches or is ANY, port is ANY
+ if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // protocol is ANY or match
+ if (!_port) // port is ANY
+ return true;
// Don't match on fragments beyond fragment 0. If we've blocked
// fragment 0, further fragments will fall on deaf ears anyway.
@@ -60,22 +62,27 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l
switch(((const uint8_t *)data)[9]) { // port's meaning depends on IP protocol
case ZT_IPPROTO_ICMP:
- return _port(((const uint8_t *)data)[ihl]); // port = ICMP type
+ // For ICMP, port is ICMP type
+ return _port(((const uint8_t *)data)[ihl]);
case ZT_IPPROTO_TCP:
case ZT_IPPROTO_UDP:
case ZT_IPPROTO_SCTP:
case ZT_IPPROTO_UDPLITE:
- return _port(((const uint16_t *)data)[(ihl / 2) + 1]); // destination port
+ // For these, port is destination port. Protocol designers were
+ // nice enough to put the field in the same place.
+ return _port(((const uint16_t *)data)[(ihl / 2) + 1]);
+ default:
+ // port has no meaning for other IP types, so ignore it
+ return true;
}
return false; // no match on port
}
- }
+ } else throw std::invalid_argument("undersized IPv4 packet");
break;
case ZT_ETHERTYPE_IPV6:
if (len > 40) {
- // see: http://stackoverflow.com/questions/17518951/is-the-ipv6-header-really-this-nutty
int nextHeader = ((const uint8_t *)data)[6];
unsigned int pos = 40;
while ((pos < len)&&(nextHeader >= 0)&&(nextHeader != 59)) { // 59 == no next header
@@ -102,9 +109,11 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l
case ZT_IPPROTO_ESP: // ESP
return _protocol(ZT_IPPROTO_ESP); // true if ESP is matched protocol, otherwise false since packet will be IPsec
case ZT_IPPROTO_ICMPV6:
- if (_protocol(ZT_IPPROTO_ICMPV6)) { // only match ICMPv6 if specified
+ // Only match ICMPv6 if we've selected it specifically
+ if (_protocol(ZT_IPPROTO_ICMPV6)) {
+ // Port is interpreted as ICMPv6 type
if ((!_port)||(_port(((const uint8_t *)data)[pos])))
- return true; // protocol matches, port is ANY or matches ICMP type
+ return true;
}
break;
case ZT_IPPROTO_TCP:
@@ -118,25 +127,75 @@ bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int l
return true; // protocol matches or is ANY, port is ANY or matches
}
break;
+ default: {
+ char foo[128];
+ sprintf(foo,"unrecognized IPv6 header type %d",(int)nextHeader);
+ throw std::invalid_argument(foo);
+ }
}
fprintf(stderr,"[rule] V6: end header parse, next header %.2x, new pos %d\n",nextHeader,pos);
}
- }
+ } else throw std::invalid_argument("undersized IPv6 packet");
break;
+
+ default:
+ // For other ethertypes, protocol and port are ignored. What would they mean?
+ return true;
}
}
return false;
}
-Filter::Filter(const RuntimeEnvironment *renv) :
- _r(renv)
+std::string Filter::Rule::toString() const
{
-}
+ char buf[128];
+ std::string s;
-Filter::~Filter()
-{
+ switch(_etherType.magnitude()) {
+ case 0:
+ s.push_back('*');
+ break;
+ case 1:
+ sprintf(buf,"%u",_etherType.start);
+ s.append(buf);
+ break;
+ default:
+ sprintf(buf,"%u-%u",_etherType.start,_etherType.end);
+ s.append(buf);
+ break;
+ }
+ s.push_back('/');
+ switch(_protocol.magnitude()) {
+ case 0:
+ s.push_back('*');
+ break;
+ case 1:
+ sprintf(buf,"%u",_protocol.start);
+ s.append(buf);
+ break;
+ default:
+ sprintf(buf,"%u-%u",_protocol.start,_protocol.end);
+ s.append(buf);
+ break;
+ }
+ s.push_back('/');
+ switch(_port.magnitude()) {
+ case 0:
+ s.push_back('*');
+ break;
+ case 1:
+ sprintf(buf,"%u",_port.start);
+ s.append(buf);
+ break;
+ default:
+ sprintf(buf,"%u-%u",_port.start,_port.end);
+ s.append(buf);
+ break;
+ }
+
+ return s;
}
void Filter::add(const Rule &r,const Action &a)
@@ -153,89 +212,161 @@ void Filter::add(const Rule &r,const Action &a)
std::string Filter::toString(const char *sep) const
{
- char buf[256];
-
if (!sep)
sep = ",";
std::string s;
+ bool first = true;
Mutex::Lock _l(_chain_m);
for(std::vector<Entry>::const_iterator i(_chain.begin());i!=_chain.end();++i) {
- bool first = (i == _chain.begin());
+ s.append(i->rule.toString());
+ if (first)
+ first = false;
+ else s.append(sep);
+ }
- s.push_back('[');
+ return s;
+}
- if (i->rule.etherType()) {
- if (i->rule.etherType().magnitude() > 1)
- sprintf(buf,"%u-%u",i->rule.etherType().start,i->rule.etherType().end);
- else sprintf(buf,"%u",i->rule.etherType().start);
- s.append(buf);
- } else s.push_back('*');
+const char *Filter::etherTypeName(const unsigned int etherType)
+ throw()
+{
+ switch(etherType) {
+ case ZT_ETHERTYPE_IPV4: return "ETHERTYPE_IPV4";
+ case ZT_ETHERTYPE_ARP: return "ETHERTYPE_ARP";
+ case ZT_ETHERTYPE_RARP: return "ETHERTYPE_RARP";
+ case ZT_ETHERTYPE_ATALK: return "ETHERTYPE_ATALK";
+ case ZT_ETHERTYPE_AARP: return "ETHERTYPE_AARP";
+ case ZT_ETHERTYPE_IPX_A: return "ETHERTYPE_IPX_A";
+ case ZT_ETHERTYPE_IPX_B: return "ETHERTYPE_IPX_B";
+ case ZT_ETHERTYPE_IPV6: return "ETHERTYPE_IPV6";
+ }
+ return UNKNOWN_NAME;
+}
+
+const char *Filter::ipProtocolName(const unsigned int ipp)
+ throw()
+{
+ switch(ipp) {
+ case ZT_IPPROTO_ICMP: return "IPPROTO_ICMP";
+ case ZT_IPPROTO_IGMP: return "IPPROTO_IGMP";
+ case ZT_IPPROTO_TCP: return "IPPROTO_TCP";
+ case ZT_IPPROTO_UDP: return "IPPROTO_UDP";
+ case ZT_IPPROTO_GRE: return "IPPROTO_GRE";
+ case ZT_IPPROTO_ESP: return "IPPROTO_ESP";
+ case ZT_IPPROTO_AH: return "IPPROTO_AH";
+ case ZT_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6";
+ case ZT_IPPROTO_OSPF: return "IPPROTO_OSPF";
+ case ZT_IPPROTO_IPIP: return "IPPROTO_IPIP";
+ case ZT_IPPROTO_IPCOMP: return "IPPROTO_IPCOMP";
+ case ZT_IPPROTO_L2TP: return "IPPROTO_L2TP";
+ case ZT_IPPROTO_SCTP: return "IPPROTO_SCTP";
+ case ZT_IPPROTO_FC: return "IPPROTO_FC";
+ case ZT_IPPROTO_UDPLITE: return "IPPROTO_UDPLITE";
+ case ZT_IPPROTO_HIP: return "IPPROTO_HIP";
+ }
+ return UNKNOWN_NAME;
+}
- s.push_back(';');
+const char *Filter::icmpTypeName(const unsigned int icmpType)
+ throw()
+{
+ switch(icmpType) {
+ case ZT_ICMP_ECHO_REPLY: return "ICMP_ECHO_REPLY";
+ case ZT_ICMP_DESTINATION_UNREACHABLE: return "ICMP_DESTINATION_UNREACHABLE";
+ case ZT_ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH";
+ case ZT_ICMP_REDIRECT: return "ICMP_REDIRECT";
+ case ZT_ICMP_ALTERNATE_HOST_ADDRESS: return "ICMP_ALTERNATE_HOST_ADDRESS";
+ case ZT_ICMP_ECHO_REQUEST: return "ICMP_ECHO_REQUEST";
+ case ZT_ICMP_ROUTER_ADVERTISEMENT: return "ICMP_ROUTER_ADVERTISEMENT";
+ case ZT_ICMP_ROUTER_SOLICITATION: return "ICMP_ROUTER_SOLICITATION";
+ case ZT_ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED";
+ case ZT_ICMP_BAD_IP_HEADER: return "ICMP_BAD_IP_HEADER";
+ case ZT_ICMP_TIMESTAMP: return "ICMP_TIMESTAMP";
+ case ZT_ICMP_TIMESTAMP_REPLY: return "ICMP_TIMESTAMP_REPLY";
+ case ZT_ICMP_INFORMATION_REQUEST: return "ICMP_INFORMATION_REQUEST";
+ case ZT_ICMP_INFORMATION_REPLY: return "ICMP_INFORMATION_REPLY";
+ case ZT_ICMP_ADDRESS_MASK_REQUEST: return "ICMP_ADDRESS_MASK_REQUEST";
+ case ZT_ICMP_ADDRESS_MASK_REPLY: return "ICMP_ADDRESS_MASK_REPLY";
+ case ZT_ICMP_TRACEROUTE: return "ICMP_TRACEROUTE";
+ case ZT_ICMP_MOBILE_HOST_REDIRECT: return "ICMP_MOBILE_HOST_REDIRECT";
+ case ZT_ICMP_MOBILE_REGISTRATION_REQUEST: return "ICMP_MOBILE_REGISTRATION_REQUEST";
+ case ZT_ICMP_MOBILE_REGISTRATION_REPLY: return "ICMP_MOBILE_REGISTRATION_REPLY";
+ }
+ return UNKNOWN_NAME;
+}
- if (i->rule.protocol()) {
- if (i->rule.protocol().magnitude() > 1)
- sprintf(buf,"%u-%u",i->rule.protocol().start,i->rule.protocol().end);
- else sprintf(buf,"%u",i->rule.protocol().start);
- s.append(buf);
- } else s.push_back('*');
+const char *Filter::icmp6TypeName(const unsigned int icmp6Type)
+ throw()
+{
+ switch(icmp6Type) {
+ case ZT_ICMP6_DESTINATION_UNREACHABLE: return "ICMP6_DESTINATION_UNREACHABLE";
+ case ZT_ICMP6_PACKET_TOO_BIG: return "ICMP6_PACKET_TOO_BIG";
+ case ZT_ICMP6_TIME_EXCEEDED: return "ICMP6_TIME_EXCEEDED";
+ case ZT_ICMP6_PARAMETER_PROBLEM: return "ICMP6_PARAMETER_PROBLEM";
+ case ZT_ICMP6_ECHO_REQUEST: return "ICMP6_ECHO_REQUEST";
+ case ZT_ICMP6_ECHO_REPLY: return "ICMP6_ECHO_REPLY";
+ case ZT_ICMP6_MULTICAST_LISTENER_QUERY: return "ICMP6_MULTICAST_LISTENER_QUERY";
+ case ZT_ICMP6_MULTICAST_LISTENER_REPORT: return "ICMP6_MULTICAST_LISTENER_REPORT";
+ case ZT_ICMP6_MULTICAST_LISTENER_DONE: return "ICMP6_MULTICAST_LISTENER_DONE";
+ case ZT_ICMP6_ROUTER_SOLICITATION: return "ICMP6_ROUTER_SOLICITATION";
+ case ZT_ICMP6_ROUTER_ADVERTISEMENT: return "ICMP6_ROUTER_ADVERTISEMENT";
+ case ZT_ICMP6_NEIGHBOR_SOLICITATION: return "ICMP6_NEIGHBOR_SOLICITATION";
+ case ZT_ICMP6_NEIGHBOR_ADVERTISEMENT: return "ICMP6_NEIGHBOR_ADVERTISEMENT";
+ case ZT_ICMP6_REDIRECT_MESSAGE: return "ICMP6_REDIRECT_MESSAGE";
+ case ZT_ICMP6_ROUTER_RENUMBERING: return "ICMP6_ROUTER_RENUMBERING";
+ case ZT_ICMP6_NODE_INFORMATION_QUERY: return "ICMP6_NODE_INFORMATION_QUERY";
+ case ZT_ICMP6_NODE_INFORMATION_RESPONSE: return "ICMP6_NODE_INFORMATION_RESPONSE";
+ case ZT_ICMP6_INV_NEIGHBOR_SOLICITATION: return "ICMP6_INV_NEIGHBOR_SOLICITATION";
+ case ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT: return "ICMP6_INV_NEIGHBOR_ADVERTISEMENT";
+ case ZT_ICMP6_MLDV2: return "ICMP6_MLDV2";
+ case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST";
+ case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY";
+ case ZT_ICMP6_MOBILE_PREFIX_SOLICITATION: return "ICMP6_MOBILE_PREFIX_SOLICITATION";
+ case ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT: return "ICMP6_MOBILE_PREFIX_ADVERTISEMENT";
+ case ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION: return "ICMP6_CERTIFICATION_PATH_SOLICITATION";
+ case ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT: return "ICMP6_CERTIFICATION_PATH_ADVERTISEMENT";
+ case ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT: return "ICMP6_MULTICAST_ROUTER_ADVERTISEMENT";
+ case ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION: return "ICMP6_MULTICAST_ROUTER_SOLICITATION";
+ case ZT_ICMP6_MULTICAST_ROUTER_TERMINATION: return "ICMP6_MULTICAST_ROUTER_TERMINATION";
+ case ZT_ICMP6_RPL_CONTROL_MESSAGE: return "ICMP6_RPL_CONTROL_MESSAGE";
+ }
+ return UNKNOWN_NAME;
+}
- s.push_back(';');
+Filter::Action Filter::operator()(const RuntimeEnvironment *_r,unsigned int etherType,const void *frame,unsigned int len) const
+{
+ Mutex::Lock _l(_chain_m);
- if (i->rule.port()) {
- if (i->rule.port().magnitude() > 1)
- sprintf(buf,"%u-%u",i->rule.port().start,i->rule.port().end);
- else sprintf(buf,"%u",i->rule.port().start);
- s.append(buf);
- } else s.push_back('*');
+ TRACE("starting match against %d rules",(int)_chain.size());
- s.append("]:");
+ int ruleNo = 0;
+ for(std::vector<Entry>::const_iterator r(_chain.begin());r!=_chain.end();++r,++ruleNo) {
+ try {
+ if (r->rule(etherType,frame,len)) {
+ TRACE("match: %s",r->rule.toString().c_str());
- switch(i->action) {
- case ACTION_DENY:
- s.append("DENY");
- break;
- case ACTION_ALLOW:
- s.append("ALLOW");
- break;
- case ACTION_LOG:
- s.append("LOG");
- break;
+ switch(r->action) {
+ case ACTION_ALLOW:
+ case ACTION_DENY:
+ return r->action;
+ default:
+ break;
+ }
+ } else {
+ TRACE("no match: %s",r->rule.toString().c_str());
+ }
+ } catch (std::invalid_argument &exc) {
+ LOG("filter: unable to parse packet on rule %s (%d): %s",r->rule.toString().c_str(),ruleNo,exc.what());
+ return ACTION_UNPARSEABLE;
+ } catch ( ... ) {
+ LOG("filter: unable to parse packet on rule %s (%d): unknown exception",r->rule.toString().c_str(),ruleNo);
+ return ACTION_UNPARSEABLE;
}
-
- if (!first)
- s.append(sep);
}
- return s;
-}
-
-const char *Filter::etherTypeName(const unsigned int etherType)
- throw()
-{
- static char tmp[6];
- switch(etherType) {
- case ZT_ETHERTYPE_IPV4:
- return "IPV4";
- case ZT_ETHERTYPE_ARP:
- return "ARP";
- case ZT_ETHERTYPE_RARP:
- return "RARP";
- case ZT_ETHERTYPE_ATALK:
- return "ATALK";
- case ZT_ETHERTYPE_AARP:
- return "AARP";
- case ZT_ETHERTYPE_IPX_A:
- return "IPX_A";
- case ZT_ETHERTYPE_IPX_B:
- return "IPX_B";
- case ZT_ETHERTYPE_IPV6:
- return "IPV6";
- }
- sprintf(tmp,"%.4x",etherType);
- return tmp; // technically not thread safe, but we're only going to see this in debugging if ever
+ return ACTION_ALLOW;
}
} // namespace ZeroTier
diff --git a/node/Filter.hpp b/node/Filter.hpp
index 861603f0..8b86b48f 100644
--- a/node/Filter.hpp
+++ b/node/Filter.hpp
@@ -33,6 +33,7 @@
#include <string>
#include <vector>
#include <utility>
+#include <stdexcept>
#include "Mutex.hpp"
#include "Range.hpp"
@@ -130,6 +131,19 @@ class Filter
{
public:
/**
+ * Value returned by etherTypeName, etc. on unknown
+ *
+ * These static methods return precisely this, so a pointer equality
+ * check will work.
+ */
+ static const char *const UNKNOWN_NAME;
+
+ /**
+ * An empty range as a more idiomatic way of specifying a wildcard match
+ */
+ static const Range<unsigned int> ANY;
+
+ /**
* A filter rule
*
* This behaves as an immutable value object.
@@ -171,8 +185,15 @@ public:
* @param data Ethernet frame data
* @param len Length of ethernet frame
* @return True if rule matches
+ * @throws std::invalid_argument Frame invalid or not parseable
*/
- bool operator()(unsigned int etype,const void *data,unsigned int len) const;
+ bool operator()(unsigned int etype,const void *data,unsigned int len) const
+ throw(std::invalid_argument);
+
+ /**
+ * @return Human readable representation of rule
+ */
+ std::string toString() const;
inline bool operator==(const Rule &r) const throw() { return ((_etherType == r._etherType)&&(_protocol == r._protocol)&&(_port == r._port)); }
inline bool operator!=(const Rule &r) const throw() { return !(*this == r); }
@@ -208,7 +229,7 @@ public:
{
ACTION_DENY = 0,
ACTION_ALLOW = 1,
- ACTION_LOG = 2
+ ACTION_UNPARSEABLE = 2
};
/**
@@ -227,8 +248,27 @@ public:
Action action;
};
- Filter(const RuntimeEnvironment *renv);
- ~Filter();
+ Filter() :
+ _chain(),
+ _chain_m()
+ {
+ }
+
+ Filter(const Filter &f) :
+ _chain(),
+ _chain_m()
+ {
+ Mutex::Lock _l(f._chain_m);
+ _chain = f._chain;
+ }
+
+ inline Filter &operator=(const Filter &f)
+ {
+ Mutex::Lock _l1(_chain_m);
+ Mutex::Lock _l2(f._chain_m);
+ _chain = f._chain;
+ return *this;
+ }
/**
* Remove all filter entries
@@ -281,16 +321,27 @@ public:
*/
std::string toString(const char *sep = (const char *)0) const;
- /**
- * @param etherType Ethernet type ID
- * @return Name of Ethernet protocol (e.g. ARP, IPV4)
- */
static const char *etherTypeName(const unsigned int etherType)
throw();
+ static const char *ipProtocolName(const unsigned int ipp)
+ throw();
+ static const char *icmpTypeName(const unsigned int icmpType)
+ throw();
+ static const char *icmp6TypeName(const unsigned int icmp6Type)
+ throw();
-private:
- const RuntimeEnvironment *_r;
+ /**
+ * Match against an Ethernet frame
+ *
+ * @param _r Runtime environment
+ * @param etherType Ethernet frame type
+ * @param frame Ethernet frame data
+ * @param len Length of frame in bytes
+ * @return Action if matched or ACTION_ALLOW if not matched
+ */
+ Action operator()(const RuntimeEnvironment *_r,unsigned int etherType,const void *frame,unsigned int len) const;
+private:
std::vector<Entry> _chain;
Mutex _chain_m;
};
diff --git a/node/Http.cpp b/node/Http.cpp
deleted file mode 100644
index 86ae2d25..00000000
--- a/node/Http.cpp
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netdb.h>
-#include <vector>
-#include <set>
-#include <list>
-
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
-#include "Http.hpp"
-#include "Utils.hpp"
-#include "InetAddress.hpp"
-
-static http_parser_settings _http_parser_settings;
-
-namespace ZeroTier {
-
-static bool _sendAll(int fd,const void *buf,unsigned int len)
-{
- for(;;) {
- int n = (int)::send(fd,buf,len,0);
- if ((n < 0)&&(errno == EINTR))
- continue;
- return (n == (int)len);
- }
-}
-
-const std::map<std::string,std::string> Http::EMPTY_HEADERS;
-
-Http::Request::Request(
- Http::Method m,
- const std::string &url,
- const std::map<std::string,std::string> &rh,
- const std::string &rb,
- bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &),
- void *arg) :
- _url(url),
- _requestHeaders(rh),
- _handler(handler),
- _arg(arg),
- _method(m),
- _fd(0)
-{
- _http_parser_settings.on_message_begin = &Http::Request::_http_on_message_begin;
- _http_parser_settings.on_url = &Http::Request::_http_on_url;
- _http_parser_settings.on_status_complete = &Http::Request::_http_on_status_complete;
- _http_parser_settings.on_header_field = &Http::Request::_http_on_header_field;
- _http_parser_settings.on_header_value = &Http::Request::_http_on_header_value;
- _http_parser_settings.on_headers_complete = &Http::Request::_http_on_headers_complete;
- _http_parser_settings.on_body = &Http::Request::_http_on_body;
- _http_parser_settings.on_message_complete = &Http::Request::_http_on_message_complete;
-
- start();
-}
-
-Http::Request::~Request()
-{
- if (_fd > 0)
- ::close(_fd);
- join();
-}
-
-void Http::Request::main()
- throw()
-{
- char buf[131072];
-
- try {
- http_parser_init(&_parser,HTTP_RESPONSE);
- _parser.data = this;
-
- http_parser_url urlParsed;
- if (http_parser_parse_url(_url.c_str(),_url.length(),0,&urlParsed)) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL parse error");
- return;
- }
- if (!(urlParsed.field_set & (1 << UF_SCHEMA))) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL specifies no schema");
- return;
- }
-
- std::string schema(_url.substr(urlParsed.field_data[UF_SCHEMA].off,urlParsed.field_data[UF_SCHEMA].len));
-
- if (schema == "file") {
- const std::string filePath(_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len));
-
- uint64_t lm = Utils::getLastModified(filePath.c_str());
- if (lm) {
- const std::map<std::string,std::string>::const_iterator ifModSince(_requestHeaders.find("If-Modified-Since"));
- if ((ifModSince != _requestHeaders.end())&&(ifModSince->second.length())) {
- uint64_t t64 = Utils::fromRfc1123(ifModSince->second);
- if ((t64)&&(lm > t64)) {
- suicidalThread = !_handler(this,_arg,_url,304,_responseHeaders,"");
- return;
- }
- }
-
- if (Utils::readFile(filePath.c_str(),_responseBody)) {
- _responseHeaders["Last-Modified"] = Utils::toRfc1123(lm);
- suicidalThread = !_handler(this,_arg,_url,200,_responseHeaders,_responseBody);
- return;
- }
- }
-
- suicidalThread = !_handler(this,_arg,_url,404,_responseHeaders,"file not found or not readable");
- return;
- } else if (schema == "http") {
- if (!(urlParsed.field_set & (1 << UF_HOST))) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL contains no host");
- return;
- }
- std::string host(_url.substr(urlParsed.field_data[UF_HOST].off,urlParsed.field_data[UF_HOST].len));
-
- std::list<InetAddress> v4,v6;
- {
- struct addrinfo *res = (struct addrinfo *)0;
- if (!getaddrinfo(host.c_str(),(const char *)0,(const struct addrinfo *)0,&res)) {
- struct addrinfo *p = res;
- do {
- if (p->ai_family == AF_INET)
- v4.push_back(InetAddress(p->ai_addr));
- else if (p->ai_family == AF_INET6)
- v6.push_back(InetAddress(p->ai_addr));
- } while ((p = p->ai_next));
- freeaddrinfo(res);
- }
- }
-
- std::list<InetAddress> *addrList;
- if (v4.empty()&&v6.empty()) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not find address for host in URL");
- return;
- } else if (v4.empty()) {
- addrList = &v6;
- } else {
- addrList = &v4;
- }
- InetAddress *addr;
- {
- addrList->sort();
- addrList->unique();
- unsigned int i = 0,k = 0;
- k = rand() % addrList->size();
- std::list<InetAddress>::iterator a(addrList->begin());
- while (i++ != k) ++a;
- addr = &(*a);
- }
-
- int remotePort = ((urlParsed.field_set & (1 << UF_PORT))&&(urlParsed.port)) ? (int)urlParsed.port : (int)80;
- if ((remotePort <= 0)||(remotePort > 0xffff)) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL port out of range");
- return;
- }
- addr->setPort(remotePort);
-
- _fd = socket(addr->isV6() ? AF_INET6 : AF_INET,SOCK_STREAM,0);
- if (_fd <= 0) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not open socket");
- return;
- }
-
- for(;;) {
- if (connect(_fd,addr->saddr(),addr->saddrLen())) {
- if (errno == EINTR)
- continue;
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"connection failed to remote host");
- return;
- } else break;
- }
-
- const char *mstr = "GET";
- switch(_method) {
- case HTTP_METHOD_HEAD: mstr = "HEAD"; break;
- default: break;
- }
- int mlen = (int)snprintf(buf,sizeof(buf),"%s %s HTTP/1.1\r\nAccept-Encoding: \r\nHost: %s\r\n",mstr,_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len).c_str(),host.c_str());
- if (mlen >= (int)sizeof(buf)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL too long");
- return;
- }
- if (!_sendAll(_fd,buf,mlen)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error");
- return;
- }
-
- for(std::map<std::string,std::string>::const_iterator rh(_requestHeaders.begin());rh!=_requestHeaders.end();++rh) {
- mlen = (int)snprintf(buf,sizeof(buf),"%s: %s\r\n",rh->first.c_str(),rh->second.c_str());
- if (mlen >= (int)sizeof(buf)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"header too long");
- return;
- }
- if (!_sendAll(_fd,buf,mlen)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error");
- return;
- }
- }
-
- if (!_sendAll(_fd,"\r\n",2)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error");
- return;
- }
-
- _responseStatusCode = 0;
- _messageComplete = false;
- for(;;) {
- mlen = (int)::recv(_fd,buf,sizeof(buf),0);
- if (mlen < 0) {
- if (errno != EINTR)
- break;
- else continue;
- }
- if (((int)http_parser_execute(&_parser,&_http_parser_settings,buf,mlen) != mlen)||(_parser.upgrade)) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"invalid HTTP response from server");
- return;
- }
- if (_messageComplete) {
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,_responseStatusCode,_responseHeaders,_responseBody);
- return;
- }
- }
-
- ::close(_fd); _fd = 0;
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"empty HTTP response from server");
- return;
- } else {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"only 'file' and 'http' methods are supported");
- return;
- }
- } catch ( ... ) {
- suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"unexpected exception retrieving URL");
- return;
- }
-}
-
-int Http::Request::_http_on_message_begin(http_parser *parser)
-{
- return 0;
-}
-int Http::Request::_http_on_url(http_parser *parser,const char *data,size_t length)
-{
- return 0;
-}
-int Http::Request::_http_on_status_complete(http_parser *parser)
-{
- Http::Request *r = (Http::Request *)parser->data;
- r->_responseStatusCode = parser->status_code;
- return 0;
-}
-int Http::Request::_http_on_header_field(http_parser *parser,const char *data,size_t length)
-{
- Http::Request *r = (Http::Request *)parser->data;
- if ((r->_currentHeaderField.length())&&(r->_responseHeaders.find(r->_currentHeaderField) != r->_responseHeaders.end()))
- r->_currentHeaderField.assign("");
- r->_currentHeaderField.append(data,length);
- return 0;
-}
-int Http::Request::_http_on_header_value(http_parser *parser,const char *data,size_t length)
-{
- Http::Request *r = (Http::Request *)parser->data;
- if (r->_currentHeaderField.length())
- r->_responseHeaders[r->_currentHeaderField].append(data,length);
- return 0;
-}
-int Http::Request::_http_on_headers_complete(http_parser *parser)
-{
- Http::Request *r = (Http::Request *)parser->data;
- return ((r->_method == Http::HTTP_METHOD_HEAD) ? 1 : 0);
-}
-int Http::Request::_http_on_body(http_parser *parser,const char *data,size_t length)
-{
- Http::Request *r = (Http::Request *)parser->data;
- r->_responseBody.append(data,length);
- return 0;
-}
-int Http::Request::_http_on_message_complete(http_parser *parser)
-{
- Http::Request *r = (Http::Request *)parser->data;
- r->_messageComplete = true;
- return 0;
-}
-
-} // namespace ZeroTier
diff --git a/node/Http.hpp b/node/Http.hpp
deleted file mode 100644
index a099b15d..00000000
--- a/node/Http.hpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * 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/
- */
-
-#ifndef _ZT_HTTP_HPP
-#define _ZT_HTTP_HPP
-
-#include <map>
-#include <string>
-#include <stdexcept>
-#include "Thread.hpp"
-
-#include "../ext/http-parser/http_parser.h"
-
-namespace ZeroTier {
-
-class Http
-{
-public:
- /**
- * HTTP request methods
- */
- enum Method
- {
- HTTP_METHOD_GET,
- HTTP_METHOD_HEAD
- };
-
- /**
- * An empty headers map for convenience
- */
- static const std::map<std::string,std::string> EMPTY_HEADERS;
-
- /**
- * HTTP request
- */
- class Request : protected Thread
- {
- public:
- /**
- * Create and issue an HTTP request
- *
- * The supplied handler is called when the request is
- * complete or if an error occurs. A code of zero indicates
- * that the server could not be reached, and a description
- * of the error will be in 'body'. If the handler returns
- * false the Request object deletes itself. Otherwise the
- * object must be deleted by other code.
- *
- * @param m Request method
- * @param url Destination URL
- * @param rh Request headers
- * @param rb Request body or empty string for none (currently unused)
- * @param handler Request handler function
- * @param arg First argument to request handler
- */
- Request(
- Http::Method m,
- const std::string &url,
- const std::map<std::string,std::string> &rh,
- const std::string &rb,
- bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &),
- void *arg);
-
- /**
- * Destruction cancels any in-progress request
- */
- virtual ~Request();
-
- protected:
- virtual void main()
- throw();
-
- private:
- // HTTP parser handlers
- static int _http_on_message_begin(http_parser *parser);
- static int _http_on_url(http_parser *parser,const char *data,size_t length);
- static int _http_on_status_complete(http_parser *parser);
- static int _http_on_header_field(http_parser *parser,const char *data,size_t length);
- static int _http_on_header_value(http_parser *parser,const char *data,size_t length);
- static int _http_on_headers_complete(http_parser *parser);
- static int _http_on_body(http_parser *parser,const char *data,size_t length);
- static int _http_on_message_complete(http_parser *parser);
-
- http_parser _parser;
- std::string _url;
-
- std::map<std::string,std::string> _requestHeaders;
- std::map<std::string,std::string> _responseHeaders;
-
- std::string _currentHeaderField;
- std::string _responseBody;
-
- bool (*_handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &);
- void *_arg;
-
- Http::Method _method;
- int _responseStatusCode;
- bool _messageComplete;
- volatile int _fd;
- };
-};
-
-} // namespace ZeroTier
-
-#endif
diff --git a/node/Identity.cpp b/node/Identity.cpp
index f16947a0..e9cbef3d 100644
--- a/node/Identity.cpp
+++ b/node/Identity.cpp
@@ -57,11 +57,13 @@ void Identity::generate()
// the address of an identity will be detected as its signature will be
// invalid. Of course, deep verification of address/key relationship is
// required to cover the more elaborate address claim jump attempt case.
+ unsigned char atmp[ZT_ADDRESS_LENGTH];
+ _address.copyTo(atmp,ZT_ADDRESS_LENGTH);
SHA256_CTX sha;
unsigned char dig[32];
unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0;
SHA256_Init(&sha);
- SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH);
+ SHA256_Update(&sha,atmp,ZT_ADDRESS_LENGTH);
SHA256_Update(&sha,&zero,1);
SHA256_Update(&sha,&idtype,1);
SHA256_Update(&sha,&zero,1);
@@ -73,11 +75,13 @@ void Identity::generate()
bool Identity::locallyValidate(bool doAddressDerivationCheck) const
{
+ unsigned char atmp[ZT_ADDRESS_LENGTH];
+ _address.copyTo(atmp,ZT_ADDRESS_LENGTH);
SHA256_CTX sha;
unsigned char dig[32];
unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0;
SHA256_Init(&sha);
- SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH);
+ SHA256_Update(&sha,atmp,ZT_ADDRESS_LENGTH);
SHA256_Update(&sha,&zero,1);
SHA256_Update(&sha,&idtype,1);
SHA256_Update(&sha,&zero,1);
@@ -119,7 +123,7 @@ bool Identity::fromString(const char *str)
std::string b(Utils::unhex(fields[0]));
if (b.length() != ZT_ADDRESS_LENGTH)
return false;
- _address = b.data();
+ _address.setTo(b.data(),ZT_ADDRESS_LENGTH);
b = Utils::base64Decode(fields[2]);
if ((!b.length())||(b.length() > ZT_EC_MAX_BYTES))
@@ -218,7 +222,7 @@ Address Identity::deriveAddress(const void *keyBytes,unsigned int keyLen)
delete [] ram;
- return Address(dig); // first 5 bytes of dig[]
+ return Address(dig,ZT_ADDRESS_LENGTH); // first 5 bytes of dig[]
}
std::string Identity::encrypt(const Identity &to,const void *data,unsigned int len) const
diff --git a/node/Identity.hpp b/node/Identity.hpp
index 5cdfe9f8..1cce4fb0 100644
--- a/node/Identity.hpp
+++ b/node/Identity.hpp
@@ -104,7 +104,7 @@ public:
_keyPair((EllipticCurveKeyPair *)0)
{
if (!fromString(str))
- throw std::invalid_argument("invalid string-serialized identity");
+ throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str);
}
Identity(const std::string &str)
@@ -112,7 +112,7 @@ public:
_keyPair((EllipticCurveKeyPair *)0)
{
if (!fromString(str))
- throw std::invalid_argument("invalid string-serialized identity");
+ throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str);
}
template<unsigned int C>
@@ -307,7 +307,7 @@ public:
inline void serialize(Buffer<C> &b,bool includePrivate = false) const
throw(std::out_of_range)
{
- b.append(_address.data(),ZT_ADDRESS_LENGTH);
+ _address.appendTo(b);
b.append((unsigned char)IDENTITY_TYPE_NIST_P_521);
b.append((unsigned char)(_publicKey.size() & 0xff));
b.append(_publicKey.data(),_publicKey.size());
@@ -340,7 +340,7 @@ public:
unsigned int p = startAt;
- _address = b.field(p,ZT_ADDRESS_LENGTH);
+ _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
p += ZT_ADDRESS_LENGTH;
if (b[p++] != IDENTITY_TYPE_NIST_P_521)
diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp
index 1addf42d..ac89f84b 100644
--- a/node/Multicaster.hpp
+++ b/node/Multicaster.hpp
@@ -261,9 +261,21 @@ public:
bf.set((peers[chosen++] = *i)->address().sum());
// Add a supernode if there are fewer than the desired
- // number of recipients.
+ // number of recipients. Note that we do not use the bloom
+ // filter to track visits to supernodes, intentionally
+ // allowing multicasts to ping pong between supernodes.
+ // Supernodes propagate even messages they've already seen,
+ // while regular nodes do not. Thus this ping-ponging will
+ // cause the supernodes to pick new starting points for
+ // peer to peer graph traversal multiple times. It's a
+ // simple, stateless way to increase supernode-driven
+ // propagation of a multicast in the event that peer to
+ // peer connectivity for its group is sparse.
if (chosen < max) {
- P peer = topology.getBestSupernode(&originalSubmitter,1,true);
+ Address avoid[2];
+ avoid[0] = originalSubmitter;
+ avoid[1] = upstream;
+ P peer = topology.getBestSupernode(avoid,2,true);
if (peer)
peers[chosen++] = peer;
}
diff --git a/node/Network.cpp b/node/Network.cpp
index f34e07e0..a50d56dc 100644
--- a/node/Network.cpp
+++ b/node/Network.cpp
@@ -25,19 +25,87 @@
* LLC. Start here: http://www.zerotier.com/
*/
+#include <stdlib.h>
+#include <math.h>
+
+#include <openssl/sha.h>
+
+#include "RuntimeEnvironment.hpp"
+#include "NodeConfig.hpp"
#include "Network.hpp"
#include "Switch.hpp"
+#include "Packet.hpp"
namespace ZeroTier {
+void Network::Certificate::_shaForSignature(unsigned char *dig) const
+{
+ SHA256_CTX sha;
+ SHA256_Init(&sha);
+ unsigned char zero = 0;
+ for(const_iterator i(begin());i!=end();++i) {
+ SHA256_Update(&sha,&zero,1);
+ SHA256_Update(&sha,(const unsigned char *)i->first.data(),i->first.length());
+ SHA256_Update(&sha,&zero,1);
+ SHA256_Update(&sha,(const unsigned char *)i->second.data(),i->second.length());
+ SHA256_Update(&sha,&zero,1);
+ }
+ SHA256_Final(dig,&sha);
+}
+
+static const std::string _DELTA_PREFIX("~");
+bool Network::Certificate::qualifyMembership(const Network::Certificate &mc) const
+{
+ // Note: optimization probably needed here, probably via some kind of
+ // memoization / dynamic programming.
+
+ for(const_iterator myField(begin());myField!=end();++myField) {
+ if (!((myField->first.length() > 1)&&(myField->first[0] == '~'))) { // ~fields are max delta range specs
+ // If they lack the same field, comparison fails.
+ const_iterator theirField(mc.find(myField->first));
+ if (theirField == mc.end())
+ return false;
+
+ const_iterator deltaField(find(_DELTA_PREFIX + myField->first));
+ if (deltaField == end()) {
+ // If there is no delta, compare on simple equality
+ if (myField->second != theirField->second)
+ return false;
+ } else {
+ // Otherwise compare range with max delta. Presence of a dot in delta
+ // indicates a floating point comparison. Otherwise an integer
+ // comparison occurs.
+ if (deltaField->second.find('.') != std::string::npos) {
+ double my = strtod(myField->second.c_str(),(char **)0);
+ double their = strtod(theirField->second.c_str(),(char **)0);
+ double delta = strtod(deltaField->second.c_str(),(char **)0);
+ if (fabs(my - their) > delta)
+ return false;
+ } else {
+ int64_t my = strtoll(myField->second.c_str(),(char **)0,10);
+ int64_t their = strtoll(theirField->second.c_str(),(char **)0,10);
+ int64_t delta = strtoll(deltaField->second.c_str(),(char **)0,10);
+ if (my > their) {
+ if ((my - their) > delta)
+ return false;
+ } else {
+ if ((their - my) > delta)
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
Network::Network(const RuntimeEnvironment *renv,uint64_t id)
throw(std::runtime_error) :
_r(renv),
- _id(id),
_tap(renv,renv->identity.address().toMAC(),ZT_IF_MTU,&_CBhandleTapData,this),
- _members(),
- _open(false),
- _lock()
+ _lastConfigUpdate(0),
+ _id(id)
{
}
@@ -45,6 +113,53 @@ Network::~Network()
{
}
+void Network::setConfiguration(const Network::Config &conf)
+{
+ Mutex::Lock _l(_lock);
+ if ((conf.networkId() == _id)&&(conf.peerAddress() == _r->identity.address())) { // sanity check
+ _configuration = conf;
+ _myCertificate = conf.certificateOfMembership();
+ _lastConfigUpdate = Utils::now();
+ }
+}
+
+void Network::requestConfiguration()
+{
+ Packet outp(controller(),_r->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST);
+ outp.append((uint64_t)_id);
+ _r->sw->send(outp,true);
+}
+
+bool Network::isAllowed(const Address &peer) const
+{
+ // Exceptions can occur if we do not yet have *our* configuration.
+ try {
+ Mutex::Lock _l(_lock);
+ if (_configuration.isOpen())
+ return true;
+ std::map<Address,Certificate>::const_iterator pc(_membershipCertificates.find(peer));
+ if (pc == _membershipCertificates.end())
+ return false;
+ return _myCertificate.qualifyMembership(pc->second);
+ } catch (std::exception &exc) {
+ TRACE("isAllowed() check failed for peer %s: unexpected exception: %s",peer.toString().c_str(),exc.what());
+ return false;
+ } catch ( ... ) {
+ TRACE("isAllowed() check failed for peer %s: unexpected exception: unknown exception",peer.toString().c_str());
+ return false;
+ }
+}
+
+void Network::clean()
+{
+ Mutex::Lock _l(_lock);
+ for(std::map<Address,Certificate>::iterator i=(_membershipCertificates.begin());i!=_membershipCertificates.end();) {
+ if (_myCertificate.qualifyMembership(i->second))
+ ++i;
+ else _membershipCertificates.erase(i++);
+ }
+}
+
void Network::_CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data)
{
const RuntimeEnvironment *_r = ((Network *)arg)->_r;
diff --git a/node/Network.hpp b/node/Network.hpp
index 417688f0..637c6664 100644
--- a/node/Network.hpp
+++ b/node/Network.hpp
@@ -30,33 +30,277 @@
#include <string>
#include <set>
+#include <map>
#include <vector>
#include <stdexcept>
+
+#include "Constants.hpp"
+#include "Utils.hpp"
#include "EthernetTap.hpp"
#include "Address.hpp"
#include "Mutex.hpp"
-#include "InetAddress.hpp"
-#include "Constants.hpp"
#include "SharedPtr.hpp"
#include "AtomicCounter.hpp"
-#include "RuntimeEnvironment.hpp"
#include "MulticastGroup.hpp"
#include "NonCopyable.hpp"
#include "MAC.hpp"
+#include "Dictionary.hpp"
+#include "Identity.hpp"
+#include "InetAddress.hpp"
namespace ZeroTier {
+class RuntimeEnvironment;
class NodeConfig;
/**
- * Local network endpoint
+ * A virtual LAN
+ *
+ * Networks can be open or closed. Each network has an ID whose most
+ * significant 40 bits are the ZeroTier address of the node that should
+ * be contacted for network configuration. The least significant 24
+ * bits are arbitrary, allowing up to 2^24 networks per managing
+ * node.
+ *
+ * Open networks do not track membership. Anyone is allowed to communicate
+ * over them.
+ *
+ * Closed networks track membership by way of timestamped signatures. When
+ * the network requests its configuration, one of the fields returned is
+ * a signature for the identity of the peer on the network. This signature
+ * includes a timestamp. When a peer communicates with other peers on a
+ * closed network, it periodically (and pre-emptively) propagates this
+ * signature to the peers with which it is communicating. Peers reject
+ * packets with an error if no recent signature is on file.
*/
class Network : NonCopyable
{
friend class SharedPtr<Network>;
friend class NodeConfig;
+public:
+ /**
+ * A certificate of network membership
+ */
+ class Certificate : private Dictionary
+ {
+ public:
+ Certificate()
+ {
+ }
+
+ Certificate(const char *s) :
+ Dictionary(s)
+ {
+ }
+
+ Certificate(const std::string &s) :
+ Dictionary(s)
+ {
+ }
+
+ /**
+ * @return Read-only underlying dictionary
+ */
+ inline const Dictionary &dictionary() const { return *this; }
+
+ inline void setNetworkId(uint64_t id)
+ {
+ char buf[32];
+ sprintf(buf,"%.16llx",(unsigned long long)id);
+ (*this)["nwid"] = buf;
+ }
+
+ inline uint64_t networkId() const
+ throw(std::invalid_argument)
+ {
+ return strtoull(get("nwid").c_str(),(char **)0,16);
+ }
+
+ inline void setPeerAddress(Address &a)
+ {
+ (*this)["peer"] = a.toString();
+ }
+
+ inline Address peerAddress() const
+ throw(std::invalid_argument)
+ {
+ return Address(get("peer"));
+ }
+
+ /**
+ * Set the timestamp and timestamp max-delta
+ *
+ * @param ts Timestamp in ms since epoch
+ * @param maxDelta Maximum difference between two peers on the same network
+ */
+ inline void setTimestamp(uint64_t ts,uint64_t maxDelta)
+ {
+ char foo[32];
+ sprintf(foo,"%llu",(unsigned long long)ts);
+ (*this)["ts"] = foo;
+ sprintf(foo,"%llu",(unsigned long long)maxDelta);
+ (*this)["~ts"] = foo;
+ }
+
+ /**
+ * Sign this certificate
+ *
+ * @param with Signing identity -- the identity of this network's controller
+ * @return Signature or empty string on failure
+ */
+ inline std::string sign(const Identity &with) const
+ {
+ unsigned char dig[32];
+ _shaForSignature(dig);
+ return with.sign(dig);
+ }
+
+ /**
+ * Verify this certificate's signature
+ *
+ * @param with Signing identity -- the identity of this network's controller
+ * @param sig Signature
+ * @param siglen Length of signature in bytes
+ */
+ inline bool verify(const Identity &with,const void *sig,unsigned int siglen) const
+ {
+ unsigned char dig[32];
+ _shaForSignature(dig);
+ return with.verifySignature(dig,sig,siglen);
+ }
+
+ /**
+ * Check if another peer is indeed a current member of this network
+ *
+ * Fields with companion ~fields are compared with the defined maximum
+ * delta in this certificate. Fields without ~fields are compared for
+ * equality.
+ *
+ * This does not verify the certificate's signature!
+ *
+ * @param mc Peer membership certificate
+ * @return True if mc's membership in this network is current
+ */
+ bool qualifyMembership(const Certificate &mc) const;
+
+ private:
+ void _shaForSignature(unsigned char *dig) const;
+ };
+
+ /**
+ * A network configuration for a given node
+ */
+ class Config : private Dictionary
+ {
+ public:
+ Config()
+ {
+ }
+
+ Config(const char *s) :
+ Dictionary(s)
+ {
+ }
+
+ Config(const std::string &s) :
+ Dictionary(s)
+ {
+ }
+
+ inline void setNetworkId(uint64_t id)
+ {
+ char buf[32];
+ sprintf(buf,"%.16llx",(unsigned long long)id);
+ (*this)["nwid"] = buf;
+ }
+
+ inline uint64_t networkId() const
+ throw(std::invalid_argument)
+ {
+ return strtoull(get("nwid").c_str(),(char **)0,16);
+ }
+
+ inline void setPeerAddress(Address &a)
+ {
+ (*this)["peer"] = a.toString();
+ }
+
+ inline Address peerAddress() const
+ throw(std::invalid_argument)
+ {
+ return Address(get("peer"));
+ }
+
+ /**
+ * @return Certificate of membership for this network, or empty cert if none
+ */
+ inline Certificate certificateOfMembership() const
+ {
+ return Certificate(get("com",""));
+ }
+
+ /**
+ * @return True if this is an open non-access-controlled network
+ */
+ inline bool isOpen() const
+ {
+ return (get("isOpen") == "1");
+ }
+
+ /**
+ * @return All static addresses / netmasks, IPv4 or IPv6
+ */
+ inline std::set<InetAddress> staticAddresses() const
+ {
+ std::set<InetAddress> sa;
+ std::vector<std::string> ips(Utils::split(get("ipv4Static","").c_str(),",","",""));
+ for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i)
+ sa.insert(InetAddress(*i));
+ ips = Utils::split(get("ipv6Static","").c_str(),",","","");
+ for(std::vector<std::string>::const_iterator i(ips.begin());i!=ips.end();++i)
+ sa.insert(InetAddress(*i));
+ return sa;
+ }
+
+ /**
+ * Set static IPv4 and IPv6 addresses
+ *
+ * This sets the ipv4Static and ipv6Static fields to comma-delimited
+ * lists of assignments. The port field in InetAddress must be the
+ * number of bits in the netmask.
+ *
+ * @param begin Start of container or array of addresses (InetAddress)
+ * @param end End of container or array of addresses (InetAddress)
+ * @tparam I Type of container or array
+ */
+ template<typename I>
+ inline void setStaticInetAddresses(const I &begin,const I &end)
+ {
+ std::string v4;
+ std::string v6;
+ for(I i(begin);i!=end;++i) {
+ if (i->isV4()) {
+ if (v4.length())
+ v4.push_back(',');
+ v4.append(i->toString());
+ } else if (i->isV6()) {
+ if (v6.length())
+ v6.push_back(',');
+ v6.append(i->toString());
+ }
+ }
+ if (v4.length())
+ (*this)["ipv4Static"] = v4;
+ else erase("ipv4Static");
+ if (v6.length())
+ (*this)["ipv6Static"] = v6;
+ else erase("ipv6Static");
+ }
+ };
+
private:
+ // Only NodeConfig can create, only SharedPtr can delete
Network(const RuntimeEnvironment *renv,uint64_t id)
throw(std::runtime_error);
@@ -74,83 +318,101 @@ public:
inline EthernetTap &tap() throw() { return _tap; }
/**
- * Get this network's members
- *
- * If this is an open network, membership isn't relevant and this doesn't
- * mean much. If it's a closed network, frames will only be exchanged to/from
- * members.
- *
- * @return Members of this network
+ * @return Address of network's controlling node
*/
- inline std::set<Address> members() const
- {
- Mutex::Lock _l(_lock);
- return _members;
- }
+ inline Address controller() throw() { return Address(_id >> 24); }
/**
- * @param addr Address to check
- * @return True if address is a member
+ * @return True if network is open (no membership required)
*/
- inline bool isMember(const Address &addr) const
+ inline bool isOpen() const
throw()
{
- Mutex::Lock _l(_lock);
- return (_members.count(addr) > 0);
+ try {
+ Mutex::Lock _l(_lock);
+ return _configuration.isOpen();
+ } catch ( ... ) {
+ return false;
+ }
}
/**
- * Shortcut to check open() and then isMember()
- *
- * @param addr Address to check
- * @return True if network is open or if address is a member
+ * Update multicast groups for this network's tap
+ *
+ * @return True if internal multicast group set has changed
*/
- inline bool isAllowed(const Address &addr) const
- throw()
+ inline bool updateMulticastGroups()
{
Mutex::Lock _l(_lock);
- return ((_open)||(_members.count(addr) > 0));
+ return _tap.updateMulticastGroups(_multicastGroups);
}
/**
- * @return True if network is open (no membership required)
+ * @return Latest set of multicast groups for this network's tap
*/
- inline bool open() const
- throw()
+ inline std::set<MulticastGroup> multicastGroups() const
{
Mutex::Lock _l(_lock);
- return _open;
+ return _multicastGroups;
}
/**
- * Update internal multicast group set and return true if changed
+ * Set or update this network's configuration
*
- * @return True if internal multicast group set has changed
+ * This is called by PacketDecoder when an update comes over the wire, or
+ * internally when an old config is reloaded from disk.
+ *
+ * @param conf Configuration in key/value dictionary form
*/
- inline bool updateMulticastGroups()
+ void setConfiguration(const Config &conf);
+
+ /**
+ * Causes this network to request an updated configuration from its master node now
+ */
+ void requestConfiguration();
+
+ /**
+ * Add or update a peer's membership certificate
+ *
+ * The certificate must already have been validated via signature checking.
+ *
+ * @param peer Peer that owns certificate
+ * @param cert Certificate itself
+ */
+ inline void addMembershipCertificate(const Address &peer,const Certificate &cert)
{
Mutex::Lock _l(_lock);
- return _tap.updateMulticastGroups(_multicastGroups);
+ _membershipCertificates[peer] = cert;
}
+ bool isAllowed(const Address &peer) const;
+
/**
- * @return Latest set of multicast groups
+ * Perform periodic database cleaning such as removing expired membership certificates
*/
- inline std::set<MulticastGroup> multicastGroups() const
+ void clean();
+
+ /**
+ * @return Time of last updated configuration or 0 if none
+ */
+ inline uint64_t lastConfigUpdate() const
+ throw()
{
- Mutex::Lock _l(_lock);
- return _multicastGroups;
+ return _lastConfigUpdate;
}
private:
static void _CBhandleTapData(void *arg,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data);
const RuntimeEnvironment *_r;
- uint64_t _id;
+
EthernetTap _tap;
- std::set<Address> _members;
std::set<MulticastGroup> _multicastGroups;
- bool _open;
+ std::map<Address,Certificate> _membershipCertificates;
+ Config _configuration;
+ Certificate _myCertificate;
+ uint64_t _lastConfigUpdate;
+ uint64_t _id;
Mutex _lock;
AtomicCounter __refCount;
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:
diff --git a/node/Node.hpp b/node/Node.hpp
index df6b946f..b716b556 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -41,6 +41,44 @@ class Node
{
public:
/**
+ * Client for controlling a local ZeroTier One node
+ */
+ class LocalClient
+ {
+ public:
+ /**
+ * Create a new node config client
+ *
+ * @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
+ *
+ * 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 Conversation 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
@@ -58,11 +96,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();
@@ -99,12 +134,6 @@ public:
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
*
* @return Version in string form
@@ -117,6 +146,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 fcbbc6bd..4a174535 100644
--- a/node/NodeConfig.cpp
+++ b/node/NodeConfig.cpp
@@ -27,180 +27,225 @@
#include <stdio.h>
#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
#include <memory>
#include <string>
-#include <json/json.h>
+#include <openssl/sha.h>
#include "NodeConfig.hpp"
#include "RuntimeEnvironment.hpp"
#include "Defaults.hpp"
#include "Utils.hpp"
#include "Logger.hpp"
+#include "Topology.hpp"
+#include "Demarc.hpp"
+#include "InetAddress.hpp"
+#include "Peer.hpp"
+#include "Salsa20.hpp"
+#include "HMAC.hpp"
namespace ZeroTier {
-NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const std::string &url) :
+NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const char *authToken)
+ throw(std::runtime_error) :
_r(renv),
- _lastAutoconfigure(0),
- _lastAutoconfigureLastModified(),
- _url(url),
- _autoconfigureLock(),
- _networks(),
- _networks_m()
+ _controlSocket(true,ZT_CONTROL_UDP_PORT,false,&_CBcontrolPacketHandler,this)
{
+ SHA256_CTX sha;
+ SHA256_Init(&sha);
+ SHA256_Update(&sha,authToken,strlen(authToken));
+ SHA256_Final(_controlSocketKey,&sha);
}
NodeConfig::~NodeConfig()
{
- _autoconfigureLock.lock(); // wait for any autoconfs to finish
- _autoconfigureLock.unlock();
}
-void NodeConfig::refreshConfiguration()
+void NodeConfig::whackAllTaps()
+{
+ std::vector< SharedPtr<Network> > nwlist;
+ Mutex::Lock _l(_networks_m);
+ for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n)
+ n->second->tap().whack();
+}
+
+void NodeConfig::cleanAllNetworks()
+{
+ Mutex::Lock _l(_networks_m);
+ for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n)
+ n->second->clean();
+}
+
+// Macro used in execute()
+#undef _P
+#define _P(f,...) { r.push_back(std::string()); Utils::stdsprintf(r.back(),(f),##__VA_ARGS__); }
+
+// Used with Topology::eachPeer to dump peer stats
+class _DumpPeerStatistics
{
- _autoconfigureLock.lock(); // unlocked when handler gets called
+public:
+ _DumpPeerStatistics(std::vector<std::string> &out) :
+ r(out),
+ _now(Utils::now())
+ {
+ }
+
+ inline void operator()(Topology &t,const SharedPtr<Peer> &p)
+ {
+ InetAddress v4(p->ipv4ActivePath(_now));
+ InetAddress v6(p->ipv6ActivePath(_now));
+ _P("200 listpeers %s %s %s %u",
+ p->address().toString().c_str(),
+ ((v4) ? v4.toString().c_str() : "(none)"),
+ ((v6) ? v6.toString().c_str() : "(none)"),
+ (((v4)||(v6)) ? p->latency() : 0));
+ }
- TRACE("refreshing autoconfigure URL %s (if modified since: '%s')",_url.c_str(),_lastAutoconfigureLastModified.c_str());
+private:
+ std::vector<std::string> &r;
+ uint64_t _now;
+};
- std::map<std::string,std::string> reqHeaders;
- reqHeaders["X-ZT-ID"] = _r->identity.toString(false);
- reqHeaders["X-ZT-OVSH"] = _r->ownershipVerificationSecretHash;
- if (_lastAutoconfigureLastModified.length())
- reqHeaders["If-Modified-Since"] = _lastAutoconfigureLastModified;
+std::vector<std::string> NodeConfig::execute(const char *command)
+{
+ std::vector<std::string> r;
+ std::vector<std::string> cmd(Utils::split(command,"\r\n \t","\\","'"));
+
+ //
+ // Not coincidentally, response type codes correspond with HTTP
+ // status codes.
+ //
+
+ if ((cmd.empty())||(cmd[0] == "help")) {
+ _P("200 help help");
+ _P("200 help listpeers");
+ _P("200 help listnetworks");
+ _P("200 help join <network ID> [<network invitation code>]");
+ _P("200 help leave <network ID>");
+ } else if (cmd[0] == "listpeers") {
+ _r->topology->eachPeer(_DumpPeerStatistics(r));
+ } else if (cmd[0] == "listnetworks") {
+ Mutex::Lock _l(_networks_m);
+ for(std::map< uint64_t,SharedPtr<Network> >::const_iterator nw(_networks.begin());nw!=_networks.end();++nw) {
+ _P("200 listnetworks %llu %s %s",
+ nw->first,
+ nw->second->tap().deviceName().c_str(),
+ (nw->second->isOpen() ? "public" : "private"));
+ }
+ } else if (cmd[0] == "join") {
+ _P("404 join Not implemented yet.");
+ } else if (cmd[0] == "leave") {
+ _P("404 leave Not implemented yet.");
+ } else {
+ _P("404 %s No such command. Use 'help' for help.",cmd[0].c_str());
+ }
- new Http::Request(Http::HTTP_METHOD_GET,_url,reqHeaders,std::string(),&NodeConfig::_CBautoconfHandler,this);
+ return r;
}
-void NodeConfig::__CBautoconfHandler(const std::string &lastModified,const std::string &body)
+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)
{
- try {
- Json::Value root;
- Json::Reader reader;
+ char hmac[32];
+ char keytmp[32];
+ std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > packets;
+ Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> packet;
- std::string dec(_r->identity.decrypt(_r->configAuthority,body.data(),body.length()));
- if (!dec.length()) {
- LOG("autoconfigure from %s failed: data did not decrypt as from config authority %s",_url.c_str(),_r->configAuthority.address().toString().c_str());
- return;
- }
- TRACE("decrypted autoconf: %s",dec.c_str());
+ 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 (!reader.parse(dec,root,false)) {
- LOG("autoconfigure from %s failed: JSON parse error: %s",_url.c_str(),reader.getFormattedErrorMessages().c_str());
- return;
- }
+ if (((i + 1) >= payload.size())||((packet.size() + payload[i + 1].length() + 1) >= packet.capacity())) {
+ Utils::getSecureRandom(packet.field(8,8),8);
- if (!root.isObject()) {
- LOG("autoconfigure from %s failed: not a JSON object",_url.c_str());
- return;
+ 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);
+
+ 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);
+
+ packets.push_back(packet);
+
+ packet.setSize(16); // HMAC and IV
+ packet.append((uint32_t)(conversationId & 0xffffffff));
}
+ }
- // Configure networks
- const Json::Value &networks = root["_networks"];
- if (networks.isArray()) {
- Mutex::Lock _l(_networks_m);
- for(unsigned int ni=0;ni<networks.size();++ni) {
- if (networks[ni].isObject()) {
- const Json::Value &nwid_ = networks[ni]["id"];
- uint64_t nwid = nwid_.isNumeric() ? (uint64_t)nwid_.asUInt64() : (uint64_t)strtoull(networks[ni]["id"].asString().c_str(),(char **)0,10);
-
- if (nwid) {
- SharedPtr<Network> nw;
- std::map< uint64_t,SharedPtr<Network> >::iterator nwent(_networks.find(nwid));
- if (nwent != _networks.end())
- nw = nwent->second;
- else {
- try {
- nw = SharedPtr<Network>(new Network(_r,nwid));
- _networks[nwid] = nw;
- } catch (std::exception &exc) {
- LOG("unable to create network %llu: %s",nwid,exc.what());
- } catch ( ... ) {
- LOG("unable to create network %llu: unknown exception",nwid);
- }
- }
-
- if (nw) {
- Mutex::Lock _l2(nw->_lock);
- nw->_open = networks[ni]["isOpen"].asBool();
-
- // Ensure that TAP device has all the right IP addresses
- // TODO: IPv6 might work a tad differently
- std::set<InetAddress> allIps;
- const Json::Value &addresses = networks[ni]["_addresses"];
- if (addresses.isArray()) {
- for(unsigned int ai=0;ai<addresses.size();++ai) {
- if (addresses[ai].isString()) {
- InetAddress addr(addresses[ai].asString());
- if (addr) {
- TRACE("network %llu IP/netmask: %s",nwid,addr.toString().c_str());
- allIps.insert(addr);
- }
- }
- }
- }
- nw->_tap.setIps(allIps);
-
- // NOTE: the _members field is optional for open networks,
- // since members of open nets do not need to check membership
- // of packet senders and mutlicasters.
- const Json::Value &members = networks[ni]["_members"];
- nw->_members.clear();
- if (members.isArray()) {
- for(unsigned int mi=0;mi<members.size();++mi) {
- std::string rawAddr(Utils::unhex(members[mi].asString()));
- if (rawAddr.length() == ZT_ADDRESS_LENGTH) {
- Address addr(rawAddr.data());
- if ((addr)&&(!addr.isReserved())) {
- //TRACE("network %llu member: %s",nwid,addr.toString().c_str());
- nw->_members.insert(addr);
- }
- }
- }
- }
- }
- } else {
- TRACE("ignored networks[%u], 'id' field missing");
- }
- } else {
- TRACE("ignored networks[%u], not a JSON object",ni);
- }
- }
+ return packets;
+}
+
+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];
+
+ 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;
+ 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;
}
- _lastAutoconfigure = Utils::now();
- _lastAutoconfigureLastModified = lastModified;
- } catch (std::exception &exc) {
- TRACE("exception parsing autoconf URL response: %s",exc.what());
+ return true;
} catch ( ... ) {
- TRACE("unexpected exception parsing autoconf URL response");
+ return false;
}
}
-bool NodeConfig::_CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body)
+void NodeConfig::_CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len)
{
-#ifdef ZT_TRACE
- const RuntimeEnvironment *_r = ((NodeConfig *)arg)->_r;
-#endif
-
- if (code == 200) {
- TRACE("200 got autoconfigure response from %s: %u bytes",url.c_str(),(unsigned int)body.length());
-
- std::map<std::string,std::string>::const_iterator lm(headers.find("Last-Modified"));
- if (lm != headers.end())
- ((NodeConfig *)arg)->__CBautoconfHandler(lm->second,body);
- else ((NodeConfig *)arg)->__CBautoconfHandler(std::string(),body);
- } else if (code == 304) {
- TRACE("304 autoconfigure deferred, remote URL %s not modified",url.c_str());
- ((NodeConfig *)arg)->_lastAutoconfigure = Utils::now(); // still considered a success
- } else if (code == 409) { // conflict, ID address in use by another ID
- TRACE("%d autoconfigure failed from %s",code,url.c_str());
- } else {
- TRACE("%d autoconfigure failed from %s",code,url.c_str());
- }
+ NodeConfig *nc = (NodeConfig *)arg;
+ const RuntimeEnvironment *_r = nc->_r;
+
+ try {
+ unsigned long convId = 0;
+ std::vector<std::string> commands;
+
+ if (!decodeControlMessagePacket(nc->_controlSocketKey,data,len,convId,commands)) {
+ TRACE("control bus packet from %s failed decode, discarded",remoteAddr.toString().c_str());
+ return;
+ }
+ TRACE("control bus packet from %s, contains %d commands",remoteAddr.toString().c_str(),(int)commands.size());
- ((NodeConfig *)arg)->_autoconfigureLock.unlock();
- return false; // causes Request to delete itself
+ for(std::vector<std::string>::iterator c(commands.begin());c!=commands.end();++c) {
+ std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> > resultPackets(encodeControlMessage(nc->_controlSocketKey,convId,nc->execute(c->c_str())));
+ for(std::vector< Buffer<ZT_NODECONFIG_MAX_PACKET_SIZE> >::iterator p(resultPackets.begin());p!=resultPackets.end();++p)
+ sock->send(remoteAddr,p->data(),p->size(),-1);
+ }
+ } catch ( ... ) {
+ TRACE("exception handling control bus packet from %s",remoteAddr.toString().c_str());
+ }
}
} // namespace ZeroTier
diff --git a/node/NodeConfig.hpp b/node/NodeConfig.hpp
index 5caf18ab..62b23609 100644
--- a/node/NodeConfig.hpp
+++ b/node/NodeConfig.hpp
@@ -31,27 +31,51 @@
#include <map>
#include <set>
#include <string>
+#include <vector>
+#include <stdexcept>
+
#include <stdint.h>
#include "SharedPtr.hpp"
#include "Network.hpp"
#include "Utils.hpp"
-#include "Http.hpp"
+#include "UdpSocket.hpp"
+#include "Buffer.hpp"
namespace ZeroTier {
class RuntimeEnvironment;
/**
- * Node configuration holder and fetcher
+ * 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:
+ * [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
+ *
+ * For requests, the payload consists of a single ASCII command. For
+ * responses, the payload consists of one or more response lines delimited
+ * by NULL (0) characters. The tag field is replicated in the result
+ * packet.
*/
class NodeConfig
{
public:
/**
* @param renv Runtime environment
- * @param url Autoconfiguration URL (http:// or file://)
+ * @param authToken Configuration authentication token
+ * @throws std::runtime_error Unable to bind to local control port
*/
- NodeConfig(const RuntimeEnvironment *renv,const std::string &url);
+ NodeConfig(const RuntimeEnvironment *renv,const char *authToken)
+ throw(std::runtime_error);
~NodeConfig();
@@ -81,13 +105,12 @@ public:
/**
* Call whack() on all networks' tap devices
*/
- inline void whackAllTaps()
- {
- std::vector< SharedPtr<Network> > nwlist;
- Mutex::Lock _l(_networks_m);
- for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n)
- n->second->tap().whack();
- }
+ void whackAllTaps();
+
+ /**
+ * Call clean() on all networks
+ */
+ void cleanAllNetworks();
/**
* @param nwid Network ID
@@ -112,32 +135,49 @@ public:
}
/**
- * @return Time of last successful autoconfigure or refresh
+ * Execute a command
+ *
+ * @param command Command and arguments separated by whitespace (must already be trimmed of CR+LF, etc.)
+ * @return One or more command results (lines of output)
*/
- inline uint64_t lastAutoconfigure() const { return _lastAutoconfigure; }
+ std::vector<std::string> execute(const char *command);
/**
- * @return Autoconfiguration URL
+ * 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
*/
- inline const std::string &url() const { return _url; }
+ 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);
/**
- * Refresh configuration from autoconf URL
+ * Decode a packet from the control bus
+ *
+ * Note that 'payload' is appended to. Existing data is not cleared.
+ *
+ * @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 to which results are appended
+ * @return True on success, false on invalid packet or packet that failed authentication
*/
- void refreshConfiguration();
+ static bool decodeControlMessagePacket(const void *key,const void *data,unsigned int len,unsigned long &conversationId,std::vector<std::string> &payload);
private:
- void __CBautoconfHandler(const std::string &lastModified,const std::string &body);
- static bool _CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body);
+ static void _CBcontrolPacketHandler(UdpSocket *sock,void *arg,const InetAddress &remoteAddr,const void *data,unsigned int len);
const RuntimeEnvironment *_r;
- volatile uint64_t _lastAutoconfigure;
-
- std::string _lastAutoconfigureLastModified;
- std::string _url;
- Mutex _autoconfigureLock;
-
+ unsigned char _controlSocketKey[32];
+ UdpSocket _controlSocket;
std::map< uint64_t,SharedPtr<Network> > _networks;
Mutex _networks_m;
};
diff --git a/node/Pack.cpp b/node/Pack.cpp
deleted file mode 100644
index 8bac1300..00000000
--- a/node/Pack.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * 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 <iostream>
-#include <string.h>
-#include <stdlib.h>
-#include "Pack.hpp"
-#include "BlobArray.hpp"
-#include "Utils.hpp"
-
-#include <openssl/sha.h>
-
-namespace ZeroTier {
-
-std::vector<const Pack::Entry *> Pack::getAll() const
-{
- std::vector<const Entry *> v;
- for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e)
- v.push_back(&(e->second));
- return v;
-}
-
-const Pack::Entry *Pack::get(const std::string &name) const
-{
- std::map<std::string,Entry>::const_iterator e(_entries.find(name));
- return ((e == _entries.end()) ? (const Entry *)0 : &(e->second));
-}
-
-const Pack::Entry *Pack::put(const std::string &name,const std::string &content)
-{
- SHA256_CTX sha;
-
- Pack::Entry &e = _entries[name];
- e.name = name;
- e.content = content;
-
- SHA256_Init(&sha);
- SHA256_Update(&sha,content.data(),content.length());
- SHA256_Final(e.sha256,&sha);
-
- e.signedBy.zero();
- e.signature.assign((const char *)0,0);
-
- return &e;
-}
-
-void Pack::clear()
-{
- _entries.clear();
-}
-
-std::string Pack::serialize() const
-{
- BlobArray archive;
- for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) {
- BlobArray entry;
- entry.push_back(e->second.name);
- entry.push_back(e->second.content);
- entry.push_back(std::string((const char *)e->second.sha256,sizeof(e->second.sha256)));
- entry.push_back(std::string((const char *)e->second.signedBy.data(),e->second.signedBy.size()));
- entry.push_back(e->second.signature);
- archive.push_back(entry.serialize());
- }
-
- std::string ser(archive.serialize());
- std::string comp;
- Utils::compress(ser.begin(),ser.end(),Utils::StringAppendOutput(comp));
- return comp;
-}
-
-bool Pack::deserialize(const void *sd,unsigned int sdlen)
-{
- unsigned char dig[32];
- SHA256_CTX sha;
-
- std::string decomp;
- if (!Utils::decompress(((const char *)sd),((const char *)sd) + sdlen,Utils::StringAppendOutput(decomp)))
- return false;
-
- BlobArray archive;
- archive.deserialize(decomp.data(),decomp.length());
- clear();
- for(BlobArray::const_iterator i=archive.begin();i!=archive.end();++i) {
- BlobArray entry;
- entry.deserialize(i->data(),i->length());
-
- if (entry.size() != 5) return false;
- if (entry[2].length() != 32) return false; // SHA-256
- if (entry[3].length() != ZT_ADDRESS_LENGTH) return false; // Address
-
- Pack::Entry &e = _entries[entry[0]];
- e.name = entry[0];
- e.content = entry[1];
-
- SHA256_Init(&sha);
- SHA256_Update(&sha,e.content.data(),e.content.length());
- SHA256_Final(dig,&sha);
- if (memcmp(dig,entry[2].data(),32)) return false; // integrity check failed
- memcpy(e.sha256,dig,32);
-
- if (entry[3].length() == ZT_ADDRESS_LENGTH)
- e.signedBy = entry[3].data();
- else e.signedBy.zero();
- e.signature = entry[4];
- }
- return true;
-}
-
-bool Pack::signAll(const Identity &id)
-{
- for(std::map<std::string,Entry>::iterator e=_entries.begin();e!=_entries.end();++e) {
- e->second.signedBy = id.address();
- e->second.signature = id.sign(e->second.sha256);
- if (!e->second.signature.length())
- return false;
- }
- return true;
-}
-
-std::vector<const Pack::Entry *> Pack::verifyAll(const Identity &id,bool mandatory) const
-{
- std::vector<const Entry *> bad;
- for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) {
- if ((e->second.signedBy)&&(e->second.signature.length())) {
- if (id.address() != e->second.signedBy)
- bad.push_back(&(e->second));
- else if (!id.verifySignature(e->second.sha256,e->second.signature.data(),e->second.signature.length()))
- bad.push_back(&(e->second));
- } else if (mandatory)
- bad.push_back(&(e->second));
- }
- return bad;
-}
-
-} // namespace ZeroTier
diff --git a/node/Pack.hpp b/node/Pack.hpp
deleted file mode 100644
index a0aecd6e..00000000
--- a/node/Pack.hpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * 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/
- */
-
-#ifndef _ZT_PACK_HPP
-#define _ZT_PACK_HPP
-
-#include <string>
-#include <map>
-#include <list>
-#include <stdexcept>
-#include "Address.hpp"
-#include "Identity.hpp"
-
-namespace ZeroTier {
-
-/**
- * A very simple archive format for distributing packs of files or resources
- *
- * This is used for things like the auto-updater. It's not suitable for huge
- * files, since at present it must work in memory. Packs support signing with
- * identities and signature verification.
- */
-class Pack
-{
-public:
- /**
- * Pack entry structure for looking up deserialized entries
- */
- struct Entry
- {
- std::string name;
- std::string content;
- unsigned char sha256[32];
- Address signedBy;
- std::string signature;
- };
-
- Pack() {}
- ~Pack() {}
-
- /**
- * @return Vector of all entries
- */
- std::vector<const Entry *> getAll() const;
-
- /**
- * Look up an entry
- *
- * @param name Name to look up
- * @return Pointer to entry if it exists or NULL if not found
- */
- const Entry *get(const std::string &name) const;
-
- /**
- * Add an entry to this pack
- *
- * @param name Entry to add
- * @param content Entry's contents
- * @return The new entry
- */
- const Entry *put(const std::string &name,const std::string &content);
-
- /**
- * Remove all entries
- */
- void clear();
-
- /**
- * @return Number of entries in pack
- */
- inline unsigned int numEntries() const { return (unsigned int)_entries.size(); }
-
- /**
- * Serialize this pack
- *
- * @return Serialized form (compressed with LZ4)
- */
- std::string serialize() const;
-
- /**
- * Deserialize this pack
- *
- * Any current contents are lost. This does not verify signatures,
- * but does check SHA256 hashes for entry integrity. If the return
- * value is false, the pack's contents are undefined.
- *
- * @param sd Serialized data
- * @param sdlen Length of serialized data
- * @return True on success, false on deserialization error
- */
- bool deserialize(const void *sd,unsigned int sdlen);
- inline bool deserialize(const std::string &sd) { return deserialize(sd.data(),sd.length()); }
-
- /**
- * Sign all entries in this pack with a given identity
- *
- * @param id Identity to sign with
- * @return True on signature success, false if error
- */
- bool signAll(const Identity &id);
-
- /**
- * Verify all signed entries
- *
- * @param id Identity to verify against
- * @param mandatory If true, require that all entries be signed and fail if no signature
- * @return Vector of entries that failed verification or empty vector if all passed
- */
- std::vector<const Entry *> verifyAll(const Identity &id,bool mandatory) const;
-
-private:
- std::map<std::string,Entry> _entries;
-};
-
-} // namespace ZeroTier
-
-#endif
diff --git a/node/Packet.cpp b/node/Packet.cpp
index d12f396d..94d9164b 100644
--- a/node/Packet.cpp
+++ b/node/Packet.cpp
@@ -42,6 +42,9 @@ const char *Packet::verbString(Verb v)
case VERB_FRAME: return "FRAME";
case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME";
case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE";
+ case VERB_NETWORK_MEMBERSHIP_CERTIFICATE: return "NETWORK_MEMBERSHIP_CERTIFICATE";
+ case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST";
+ case VERB_NETWORK_CONFIG_REFRESH: return "NETWORK_CONFIG_REFRESH";
}
return "(unknown)";
}
@@ -57,6 +60,7 @@ const char *Packet::errorString(ErrorCode e)
case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION";
case ERROR_IDENTITY_INVALID: return "IDENTITY_INVALID";
case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION";
+ case ERROR_NO_MEMBER_CERTIFICATE: return "NO_MEMBER_CERTIFICATE";
}
return "(unknown)";
}
diff --git a/node/Packet.hpp b/node/Packet.hpp
index a5c450fb..0e7ccea3 100644
--- a/node/Packet.hpp
+++ b/node/Packet.hpp
@@ -132,27 +132,34 @@
#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE_BYTES 64
// Field incides for parsing verbs
+
#define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1)
#define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1)
#define ZT_PROTO_VERB_HELLO_IDX_REVISION (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1)
#define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2)
#define ZT_PROTO_VERB_HELLO_IDX_IDENTITY (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8)
+
#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB + 1)
#define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE (ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8)
#define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD (ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1)
+
#define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_OK_IDX_IN_RE_VERB + 1)
#define ZT_PROTO_VERB_OK_IDX_PAYLOAD (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8)
+
#define ZT_PROTO_VERB_WHOIS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD)
+
#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5)
#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN (ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT + 2)
#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN + 1)
+
#define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8)
#define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2)
+
#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS (ZT_PACKET_IDX_PAYLOAD)
#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1)
#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8)
@@ -166,6 +173,12 @@
#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD_LENGTH + 2)
#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SIGNATURE_LENGTH + 2)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8)
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2)
+
+#define ZT_PROTO_VERB_NETWORK_CONFIG_REFRESH_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD)
+
// Field indices for parsing OK and ERROR payloads of replies
#define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
#define ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY (ZT_PROTO_VERB_OK_IDX_PAYLOAD)
@@ -287,7 +300,7 @@ public:
*
* @return Destination ZT address
*/
- inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH)); }
+ inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
/**
* @return True if fragment is of a valid length
@@ -449,7 +462,7 @@ public:
* <[2] 16-bit length of payload>
* <[2] 16-bit length of signature>
* <[...] ethernet payload>
- * <[...] ECDSA signature>
+ * <[...] ECDSA signature of SHA-256 hash (see below)>
*
* The signature is made using the key of the original submitter, and
* can be used to authenticate the submitter for security and rate
@@ -463,7 +476,57 @@ public:
*
* No OK or ERROR is generated.
*/
- VERB_MULTICAST_FRAME = 9
+ VERB_MULTICAST_FRAME = 9,
+
+ /* Network member certificate for sending peer:
+ * <[8] 64-bit network ID>
+ * <[2] 16-bit length of certificate>
+ * <[2] 16-bit length of signature>
+ * <[...] string-serialized certificate dictionary>
+ * <[...] ECDSA signature of certificate>
+ *
+ * OK is generated on acceptance. ERROR is returned on failure. In both
+ * cases the payload is the network ID.
+ */
+ VERB_NETWORK_MEMBERSHIP_CERTIFICATE = 10,
+
+ /* Network configuration request:
+ * <[8] 64-bit network ID>
+ * <[2] 16-bit length of request meta-data dictionary>
+ * <[...] string-serialized request meta-data>
+ *
+ * This message requests network configuration from a node capable of
+ * providing it. Such nodes run the netconf service, which must be
+ * installed into the ZeroTier home directory.
+ *
+ * OK response payload:
+ * <[8] 64-bit network ID>
+ * <[2] 16-bit length of network configuration dictionary>
+ * <[...] network configuration dictionary>
+ *
+ * OK returns a Dictionary (string serialized) containing the network's
+ * configuration and IP address assignment information for the querying
+ * node. It also contains a membership certificate that the querying
+ * node can push to other peers to demonstrate its right to speak on
+ * a given network.
+ *
+ * ERROR may be NOT_FOUND if no such network is known, or
+ * UNSUPPORTED_OPERATION if the netconf service isn't available. The
+ * payload will be the network ID.
+ */
+ VERB_NETWORK_CONFIG_REQUEST = 11,
+
+ /* Network configuration refresh request:
+ * <[8] 64-bit network ID>
+ *
+ * This message can be sent by the network configuration master node
+ * to request that nodes refresh their network configuration. It can
+ * thus be used to "push" updates.
+ *
+ * It does not generate an OK or ERROR message, and is treated only as
+ * a hint to refresh now.
+ */
+ VERB_NETWORK_CONFIG_REFRESH = 12
};
/**
@@ -490,7 +553,10 @@ public:
ERROR_IDENTITY_INVALID = 5,
/* Verb or use case not supported/enabled by this node */
- ERROR_UNSUPPORTED_OPERATION = 6
+ ERROR_UNSUPPORTED_OPERATION = 6,
+
+ /* Message to private network rejected -- no unexpired certificate on file */
+ ERROR_NO_MEMBER_CERTIFICATE = 7
};
/**
@@ -603,14 +669,14 @@ public:
*
* @return Destination ZT address
*/
- inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH)); }
+ inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
/**
* Get this packet's source
*
* @return Source ZT address
*/
- inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH)); }
+ inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); }
/**
* @return True if packet is of valid length
diff --git a/node/PacketDecoder.cpp b/node/PacketDecoder.cpp
index ba1a9001..518ed9e7 100644
--- a/node/PacketDecoder.cpp
+++ b/node/PacketDecoder.cpp
@@ -25,6 +25,7 @@
* LLC. Start here: http://www.zerotier.com/
*/
+#include "Constants.hpp"
#include "RuntimeEnvironment.hpp"
#include "Topology.hpp"
#include "PacketDecoder.hpp"
@@ -32,6 +33,7 @@
#include "Peer.hpp"
#include "NodeConfig.hpp"
#include "Filter.hpp"
+#include "Service.hpp"
namespace ZeroTier {
@@ -102,6 +104,12 @@ bool PacketDecoder::tryDecode(const RuntimeEnvironment *_r)
return _doMULTICAST_LIKE(_r,peer);
case Packet::VERB_MULTICAST_FRAME:
return _doMULTICAST_FRAME(_r,peer);
+ case Packet::VERB_NETWORK_MEMBERSHIP_CERTIFICATE:
+ return _doNETWORK_MEMBERSHIP_CERTIFICATE(_r,peer);
+ case Packet::VERB_NETWORK_CONFIG_REQUEST:
+ return _doNETWORK_CONFIG_REQUEST(_r,peer);
+ case Packet::VERB_NETWORK_CONFIG_REFRESH:
+ return _doNETWORK_CONFIG_REFRESH(_r,peer);
default:
// This might be something from a new or old version of the protocol.
// Technically it passed HMAC so the packet is still valid, but we
@@ -305,7 +313,7 @@ bool PacketDecoder::_doOK(const RuntimeEnvironment *_r,const SharedPtr<Peer> &pe
bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
{
if (payloadLength() == ZT_ADDRESS_LENGTH) {
- SharedPtr<Peer> p(_r->topology->getPeer(Address(payload())));
+ SharedPtr<Peer> p(_r->topology->getPeer(Address(payload(),ZT_ADDRESS_LENGTH)));
if (p) {
Packet outp(source(),_r->identity.address(),Packet::VERB_OK);
outp.append((unsigned char)Packet::VERB_WHOIS);
@@ -314,7 +322,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer>
outp.encrypt(peer->cryptKey());
outp.hmacSet(peer->macKey());
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
- TRACE("sent WHOIS response to %s for %s",source().toString().c_str(),Address(payload()).toString().c_str());
+ TRACE("sent WHOIS response to %s for %s",source().toString().c_str(),Address(payload(),ZT_ADDRESS_LENGTH).toString().c_str());
} else {
Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
outp.append((unsigned char)Packet::VERB_WHOIS);
@@ -324,7 +332,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer>
outp.encrypt(peer->cryptKey());
outp.hmacSet(peer->macKey());
_r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
- TRACE("sent WHOIS ERROR to %s for %s (not found)",source().toString().c_str(),Address(payload()).toString().c_str());
+ TRACE("sent WHOIS ERROR to %s for %s (not found)",source().toString().c_str(),Address(payload(),ZT_ADDRESS_LENGTH).toString().c_str());
}
} else {
TRACE("dropped WHOIS from %s(%s): missing or invalid address",source().toString().c_str(),_remoteAddress.toString().c_str());
@@ -335,7 +343,7 @@ bool PacketDecoder::_doWHOIS(const RuntimeEnvironment *_r,const SharedPtr<Peer>
bool PacketDecoder::_doRENDEZVOUS(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
{
try {
- Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH));
+ Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
SharedPtr<Peer> withPeer(_r->topology->getPeer(with));
if (withPeer) {
unsigned int port = at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT);
@@ -433,7 +441,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
if (network->isAllowed(source())) {
if (size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) {
- Address originalSubmitterAddress(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH));
+ Address originalSubmitterAddress(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SUBMITTER_ADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH);
MAC fromMac(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC,6));
MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DESTINATION_MAC,6)),at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI));
unsigned int hops = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOP_COUNT];
@@ -450,19 +458,28 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
// Technically should not happen, since the original submitter is
// excluded from consideration as a propagation recipient.
TRACE("dropped boomerang MULTICAST_FRAME received from %s(%s)",source().toString().c_str(),_remoteAddress.toString().c_str());
- } else if ((!isDuplicate)||(_r->topology->isSupernode(_r->identity.address()))) {
+ } else if ((!isDuplicate)||(_r->topology->amSupernode())) {
+ //
// If I am a supernode, I will repeatedly propagate duplicates. That's
// because supernodes are used to bridge sparse multicast groups. Non-
// supernodes will ignore duplicates completely.
+ //
+ // TODO: supernodes should keep a local bloom filter too and OR it with
+ // the bloom from the packet in order to pick different recipients each
+ // time a multicast returns to them for repropagation.
+ //
+
SharedPtr<Peer> originalSubmitter(_r->topology->getPeer(originalSubmitterAddress));
if (!originalSubmitter) {
TRACE("requesting WHOIS on original multicast frame submitter %s",originalSubmitterAddress.toString().c_str());
_r->sw->requestWhois(originalSubmitterAddress);
_step = DECODE_STEP_WAITING_FOR_ORIGINAL_SUBMITTER_LOOKUP;
- return false;
+ return false; // try again if/when we get OK(WHOIS)
} else if (Multicaster::verifyMulticastPacket(originalSubmitter->identity(),network->id(),fromMac,mg,etherType,dataAndSignature,datalen,dataAndSignature + datalen,signaturelen)) {
_r->multicaster->addToDedupHistory(mccrc,now);
+ // Even if we are a supernode, we still don't repeatedly inject
+ // duplicates into our own tap.
if (!isDuplicate)
network->tap().put(fromMac,mg.mac(),etherType,dataAndSignature,datalen);
@@ -494,7 +511,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
compress();
for(unsigned int i=0;i<np;++i) {
- TRACE("propagating multicast from original node %s: %s -> %s",originalSubmitterAddress.toString().c_str(),upstream.toString().c_str(),propPeers[i]->address().toString().c_str());
+ //TRACE("propagating multicast from original node %s: %s -> %s",originalSubmitterAddress.toString().c_str(),upstream.toString().c_str(),propPeers[i]->address().toString().c_str());
// Re-use this packet to re-send multicast frame to everyone
// downstream from us.
newInitializationVector();
@@ -504,7 +521,7 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
return true;
} else {
- TRACE("terminating MULTICAST_FRAME propagation from %s(%s): max depth reached",source().toString().c_str(),_remoteAddress.toString().c_str());
+ //TRACE("terminating MULTICAST_FRAME propagation from %s(%s): max depth reached",source().toString().c_str(),_remoteAddress.toString().c_str());
}
} else {
LOG("rejected MULTICAST_FRAME from %s(%s) due to failed signature check (claims original sender %s)",source().toString().c_str(),_remoteAddress.toString().c_str(),originalSubmitterAddress.toString().c_str());
@@ -529,4 +546,66 @@ bool PacketDecoder::_doMULTICAST_FRAME(const RuntimeEnvironment *_r,const Shared
return true;
}
+bool PacketDecoder::_doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+ // TODO: not implemented yet, will be needed for private networks.
+
+ return true;
+}
+
+bool PacketDecoder::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+ char tmp[128];
+ try {
+ uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID);
+#ifndef __WINDOWS__
+ if (_r->netconfService) {
+ unsigned int dictLen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN);
+ std::string dict((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,dictLen),dictLen);
+
+ Dictionary request;
+ request["type"] = "netconf-request";
+ request["peerId"] = peer->identity().toString(false);
+ sprintf(tmp,"%llx",(unsigned long long)nwid);
+ request["nwid"] = tmp;
+ sprintf(tmp,"%llx",(unsigned long long)packetId());
+ request["requestId"] = tmp;
+ _r->netconfService->send(request);
+ } else {
+#endif // !__WINDOWS__
+ Packet outp(source(),_r->identity.address(),Packet::VERB_ERROR);
+ outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST);
+ outp.append(packetId());
+ outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION);
+ outp.append(nwid);
+ outp.encrypt(peer->cryptKey());
+ outp.hmacSet(peer->macKey());
+ _r->demarc->send(_localPort,_remoteAddress,outp.data(),outp.size(),-1);
+ TRACE("sent ERROR(NETWORK_CONFIG_REQUEST,UNSUPPORTED_OPERATION) to %s(%s)",peer->address().toString().c_str(),_remoteAddress.toString().c_str());
+#ifndef __WINDOWS__
+ }
+#endif // !__WINDOWS__
+ } catch (std::exception &exc) {
+ TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
+ } catch ( ... ) {
+ TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ }
+ return true;
+}
+
+bool PacketDecoder::_doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer)
+{
+ try {
+ uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REFRESH_IDX_NETWORK_ID);
+ SharedPtr<Network> nw(_r->nc->network(nwid));
+ if ((nw)&&(source() == nw->controller())) // only respond to requests from controller
+ nw->requestConfiguration();
+ } catch (std::exception &exc) {
+ TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: %s",source().toString().c_str(),_remoteAddress.toString().c_str(),exc.what());
+ } catch ( ... ) {
+ TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception: (unknown)",source().toString().c_str(),_remoteAddress.toString().c_str());
+ }
+ return true;
+}
+
} // namespace ZeroTier
diff --git a/node/PacketDecoder.hpp b/node/PacketDecoder.hpp
index e595d326..51408ba5 100644
--- a/node/PacketDecoder.hpp
+++ b/node/PacketDecoder.hpp
@@ -122,6 +122,9 @@ private:
bool _doFRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
bool _doMULTICAST_LIKE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
bool _doMULTICAST_FRAME(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+ bool _doNETWORK_MEMBERSHIP_CERTIFICATE(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+ bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
+ bool _doNETWORK_CONFIG_REFRESH(const RuntimeEnvironment *_r,const SharedPtr<Peer> &peer);
uint64_t _receiveTime;
Demarc::Port _localPort;
diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp
index 5f76c8ac..00d393af 100644
--- a/node/RuntimeEnvironment.hpp
+++ b/node/RuntimeEnvironment.hpp
@@ -29,6 +29,7 @@
#define _ZT_RUNTIMEENVIRONMENT_HPP
#include <string>
+#include "Constants.hpp"
#include "Identity.hpp"
#include "Condition.hpp"
@@ -42,6 +43,7 @@ class Topology;
class SysEnv;
class Multicaster;
class CMWC4096;
+class Service;
/**
* Holds global state for an instance of ZeroTier::Node
@@ -59,29 +61,30 @@ class RuntimeEnvironment
{
public:
RuntimeEnvironment() :
- identity(),
log((Logger *)0),
prng((CMWC4096 *)0),
nc((NodeConfig *)0),
demarc((Demarc *)0),
multicaster((Multicaster *)0),
sw((Switch *)0),
- topology((Topology *)0)
+ topology((Topology *)0),
+ sysEnv((SysEnv *)0)
+#ifndef __WINDOWS__
+ ,netconfService((Service *)0)
+#endif
{
}
std::string homePath;
- std::string autoconfUrlPrefix;
- std::string configAuthorityIdentityStr;
- std::string ownershipVerificationSecret;
- std::string ownershipVerificationSecretHash; // base64 of SHA-256 X16 rounds
// signal() to prematurely interrupt main loop wait
Condition mainLoopWaitCondition;
- Identity configAuthority;
Identity identity;
+ // Order matters a bit here. These are constructed in this order
+ // and then deleted in the opposite order on Node exit.
+
Logger *log; // may be null
CMWC4096 *prng;
NodeConfig *nc;
@@ -90,6 +93,10 @@ public:
Switch *sw;
Topology *topology;
SysEnv *sysEnv;
+
+#ifndef __WINDOWS__
+ Service *netconfService; // may be null
+#endif
};
} // namespace ZeroTier
diff --git a/node/Service.cpp b/node/Service.cpp
new file mode 100644
index 00000000..c614e4e4
--- /dev/null
+++ b/node/Service.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * 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 "Constants.hpp"
+
+#ifndef __WINDOWS__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+
+#include "Service.hpp"
+#include "RuntimeEnvironment.hpp"
+#include "Utils.hpp"
+#include "Logger.hpp"
+
+namespace ZeroTier {
+
+Service::Service(const RuntimeEnvironment *renv,const char *name,const char *path,void (*handler)(void *,Service &,const Dictionary &),void *arg) :
+ _r(renv),
+ _path(path),
+ _name(name),
+ _arg(arg),
+ _handler(handler),
+ _pid(-1),
+ _childStdin(0),
+ _childStdout(0),
+ _childStderr(0),
+ _run(true)
+{
+ start();
+}
+
+Service::~Service()
+{
+ _run = false;
+ long pid = _pid;
+ if (pid > 0) {
+ int st = 0;
+ ::kill(pid,SIGTERM);
+ for(int i=0;i<20;++i) {
+ if (waitpid(pid,&st,WNOHANG) == pid) {
+ pid = 0;
+ break;
+ }
+ Thread::sleep(100);
+ }
+ if (pid > 0) {
+ ::kill(pid,SIGKILL);
+ waitpid(pid,&st,0);
+ }
+ }
+ join();
+}
+
+bool Service::send(const Dictionary &msg)
+{
+ if (_childStdin <= 0)
+ return false;
+
+ std::string mser = msg.toString();
+ if (mser.length() > ZT_SERVICE_MAX_MESSAGE_SIZE)
+ return false;
+
+ // This can technically block. We'll fix this if it ends up being a
+ // problem.
+ uint32_t len = Utils::hton((uint32_t)mser.length());
+ if (write(_childStdin,&len,4) != 4)
+ return false;
+ if ((int)write(_childStdin,mser.data(),mser.length()) != (int)mser.length())
+ return false;
+
+ return true;
+}
+
+void Service::main()
+ throw()
+{
+ char buf[131072];
+ fd_set readfds,writefds,exceptfds;
+ struct timeval tv;
+
+ std::string stderrBuf;
+ std::string stdoutBuf;
+ unsigned int stdoutExpecting = 0;
+
+ while (_run) {
+ if (_pid <= 0) {
+ LOG("launching service %s...",_name.c_str());
+
+ int in[2],out[2],err[2];
+ pipe(in);
+ pipe(out);
+ pipe(err);
+
+ long pid = vfork();
+ if (pid < 0) {
+ LOG("service %s terminating: could not fork!",_name.c_str());
+ return;
+ } else if (pid) {
+ // Parent
+ close(in[0]);
+ close(out[1]);
+ close(err[1]);
+ Thread::sleep(500); // give child time to start
+ _childStdin = in[1];
+ _childStdout = out[0];
+ _childStderr = err[0];
+ fcntl(_childStdout,F_SETFL,O_NONBLOCK);
+ fcntl(_childStderr,F_SETFL,O_NONBLOCK);
+ _pid = pid;
+ } else {
+ // Child
+ close(in[1]);
+ close(out[0]);
+ close(err[0]);
+ dup2(in[0],STDIN_FILENO);
+ dup2(out[1],STDOUT_FILENO);
+ dup2(err[1],STDERR_FILENO);
+ execl(_path.c_str(),_path.c_str(),_r->homePath.c_str(),(const char *)0);
+ exit(-1);
+ }
+ } else {
+ int st = 0;
+ if (waitpid(_pid,&st,WNOHANG) == _pid) {
+ if (_childStdin > 0) close(_childStdin);
+ _childStdin = 0;
+ if (_childStdout > 0) close(_childStdout);
+ if (_childStderr > 0) close(_childStderr);
+ _pid = 0;
+
+ if (!_run)
+ return;
+
+ LOG("service %s exited with exit code: %d, delaying 1s to attempt relaunch",_name.c_str(),st);
+
+ Thread::sleep(1000); // wait to relaunch
+ continue;
+ }
+ }
+
+ // If we've made it here, _pid is running last we checked.
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+
+ FD_SET(_childStdout,&readfds);
+ FD_SET(_childStderr,&readfds);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ select(std::max(_childStdout,_childStderr)+1,&readfds,&writefds,&exceptfds,&tv);
+
+ if (!_run) {
+ if (_childStdin > 0) close(_childStdin);
+ _childStdin = 0;
+ if (_childStdout > 0) close(_childStdout);
+ if (_childStderr > 0) close(_childStderr);
+ return;
+ }
+
+ if ((_childStderr > 0)&&(FD_ISSET(_childStderr,&readfds))) {
+ int n = (int)read(_childStderr,buf,sizeof(buf));
+ for(int i=0;i<n;++i) {
+ if ((buf[i] == '\r')||(buf[i] == '\n')) {
+ stderrBuf = Utils::trim(stderrBuf);
+ if (stderrBuf.length())
+ LOG("service %s: %s",_name.c_str(),stderrBuf.c_str());
+ stderrBuf = "";
+ } else stderrBuf.push_back(buf[i]);
+ }
+ }
+
+ if ((_childStdout > 0)&&(FD_ISSET(_childStdout,&readfds))) {
+ int n = (int)read(_childStdout,buf,sizeof(buf));
+ for(int i=0;i<n;++i) {
+ stdoutBuf.push_back(buf[i]);
+ if (stdoutExpecting) {
+ if (stdoutBuf.length() == stdoutExpecting) {
+ try {
+ _handler(_arg,*this,Dictionary(stdoutBuf));
+ } catch ( ... ) {
+ LOG("unexpected exception handling message from service %s",_name.c_str());
+ }
+ stdoutBuf = "";
+ stdoutExpecting = 0;
+ }
+ } else if (stdoutBuf.length() == 4) {
+ stdoutExpecting = Utils::ntoh(*((const uint32_t *)stdoutBuf.data()));
+ stdoutBuf = "";
+ if (stdoutExpecting > ZT_SERVICE_MAX_MESSAGE_SIZE) {
+ LOG("message size overrun from service %s: %u bytes -- restarting service",_name.c_str(),stdoutExpecting);
+ stdoutExpecting = 0;
+ kill(_pid,SIGKILL);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace ZeroTier
+
+#endif // __WINDOWS__
+
diff --git a/node/Service.hpp b/node/Service.hpp
new file mode 100644
index 00000000..c8b729c7
--- /dev/null
+++ b/node/Service.hpp
@@ -0,0 +1,129 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * 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/
+ */
+
+#ifndef _ZT_SERVICE_HPP
+#define _ZT_SERVICE_HPP
+
+#include <string>
+#include <stdexcept>
+
+#include "Constants.hpp"
+#include "Dictionary.hpp"
+#include "Thread.hpp"
+#include "Mutex.hpp"
+
+/**
+ * Maximum size of a service message in bytes (sanity limit)
+ */
+#define ZT_SERVICE_MAX_MESSAGE_SIZE 131072
+
+namespace ZeroTier {
+
+class RuntimeEnvironment;
+
+#ifndef __WINDOWS__
+/**
+ * A subprocess that communicates with the host via a simple protocol
+ *
+ * This is currently only supported on *nix systems, and is used to implement
+ * special plugins that are used by supernodes and network configuration
+ * master nodes. Users will probably have no use for it.
+ *
+ * The simple binary protocol consists of a bidirectional stream of string-
+ * serialized Dictionaries prefixed by a 32-bit message length. Input
+ * messages are sent to the subprocess via its stdin, and output is read
+ * from its stdout. Messages printed by the subprocess on its stderr are
+ * logged via the standard Logger instance. If the subprocess dies, an
+ * attempt is made to restart it every second.
+ */
+class Service : protected Thread
+{
+public:
+ /**
+ * Create and launch a new service
+ *
+ * @param renv Runtime environment
+ * @param name Name of service
+ * @param path Path to service binary
+ * @param handler Handler function to call when service generates output
+ * @param arg First argument to service
+ */
+ Service(const RuntimeEnvironment *renv,
+ const char *name,
+ const char *path,
+ void (*handler)(void *,Service &,const Dictionary &),
+ void *arg);
+
+ virtual ~Service();
+
+ /**
+ * Send a message to service subprocess
+ *
+ * @param msg Message in key/value dictionary form
+ * @return True if message was sent
+ */
+ bool send(const Dictionary &msg);
+
+ /**
+ * @return Name of service
+ */
+ inline const char *name() const
+ throw()
+ {
+ return _name.c_str();
+ }
+
+ /**
+ * @return True if subprocess is running
+ */
+ inline bool running() const
+ throw()
+ {
+ return (_pid > 0);
+ }
+
+protected:
+ virtual void main()
+ throw();
+
+private:
+ const RuntimeEnvironment *_r;
+ std::string _path;
+ std::string _name;
+ void *_arg;
+ void (*_handler)(void *,Service &,const Dictionary &);
+ long _pid;
+ int _childStdin;
+ int _childStdout;
+ int _childStderr;
+ volatile bool _run;
+};
+#endif // __WINDOWS__
+
+} // namespace ZeroTier
+
+#endif
diff --git a/node/Switch.cpp b/node/Switch.cpp
index 5275f5ad..bb10b412 100644
--- a/node/Switch.cpp
+++ b/node/Switch.cpp
@@ -124,7 +124,7 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c
Packet outpTmpl(propPeers[0]->address(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME);
outpTmpl.append((uint8_t)0);
outpTmpl.append((uint64_t)network->id());
- outpTmpl.append(_r->identity.address().data(),ZT_ADDRESS_LENGTH);
+ _r->identity.address().appendTo(outpTmpl);
outpTmpl.append(from.data,6);
outpTmpl.append(mg.mac().data,6);
outpTmpl.append((uint32_t)mg.adi());
@@ -246,7 +246,7 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force)
{ // tell p1 where to find p2
Packet outp(p1,_r->identity.address(),Packet::VERB_RENDEZVOUS);
- outp.append(p2.data(),ZT_ADDRESS_LENGTH);
+ p2.appendTo(outp);
outp.append((uint16_t)cg.first.port());
if (cg.first.isV6()) {
outp.append((unsigned char)16);
@@ -262,7 +262,7 @@ bool Switch::unite(const Address &p1,const Address &p2,bool force)
}
{ // tell p2 where to find p1
Packet outp(p2,_r->identity.address(),Packet::VERB_RENDEZVOUS);
- outp.append(p1.data(),ZT_ADDRESS_LENGTH);
+ p1.appendTo(outp);
outp.append((uint16_t)cg.second.port());
if (cg.second.isV6()) {
outp.append((unsigned char)16);
@@ -386,7 +386,7 @@ void Switch::announceMulticastGroups(const std::map< SharedPtr<Network>,std::set
Packet outp((*p)->address(),_r->identity.address(),Packet::VERB_MULTICAST_LIKE);
for(std::map< SharedPtr<Network>,std::set<MulticastGroup> >::const_iterator nwmgs(allMemberships.begin());nwmgs!=allMemberships.end();++nwmgs) {
- if ((nwmgs->first->open())||(_r->topology->isSupernode((*p)->address()))||(nwmgs->first->isMember((*p)->address()))) {
+ if ((_r->topology->isSupernode((*p)->address()))||(nwmgs->first->isAllowed((*p)->address()))) {
for(std::set<MulticastGroup>::iterator mg(nwmgs->second.begin());mg!=nwmgs->second.end();++mg) {
if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) {
send(outp,true);
@@ -592,7 +592,7 @@ Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlread
SharedPtr<Peer> supernode(_r->topology->getBestSupernode(peersAlreadyConsulted,numPeersAlreadyConsulted,false));
if (supernode) {
Packet outp(supernode->address(),_r->identity.address(),Packet::VERB_WHOIS);
- outp.append(addr.data(),ZT_ADDRESS_LENGTH);
+ addr.appendTo(outp);
outp.encrypt(supernode->cryptKey());
outp.hmacSet(supernode->macKey());
diff --git a/node/Thread.cpp b/node/Thread.cpp
index 37a1a5a5..f650f6fc 100644
--- a/node/Thread.cpp
+++ b/node/Thread.cpp
@@ -47,7 +47,6 @@ static void *__m_thread_main(void *ptr)
namespace ZeroTier {
Thread::Thread() :
- suicidalThread(false),
_impl(malloc(sizeof(pthread_t))),
_running()
{
@@ -76,7 +75,7 @@ void Thread::join()
void Thread::sleep(unsigned long ms)
{
- usleep(ms);
+ usleep(ms * 1000);
}
void Thread::__intl_run()
@@ -84,10 +83,6 @@ void Thread::__intl_run()
for(;;) {
_notInit = false;
this->main();
- if (suicidalThread) {
- delete this;
- return;
- }
if (_notInit) // UGLY ASS HACK: see main()
usleep(50);
else break;
@@ -127,7 +122,6 @@ struct __m_thread_info
namespace ZeroTier {
Thread::Thread() :
- suicidalThread(false),
_impl(malloc(sizeof(__m_thread_info))),
_running()
{
@@ -162,10 +156,6 @@ void Thread::__intl_run()
for(;;) {
_notInit = false;
this->main();
- if (suicidalThread) {
- delete this;
- return;
- }
if (_notInit)
Thread::sleep(50);
else break;
diff --git a/node/Thread.hpp b/node/Thread.hpp
index b023fbae..9b399a00 100644
--- a/node/Thread.hpp
+++ b/node/Thread.hpp
@@ -78,11 +78,6 @@ protected:
virtual void main()
throw();
- /**
- * Subclasses can set to true to cause Thread to delete itself on exit
- */
- volatile bool suicidalThread;
-
private:
void *_impl;
AtomicCounter _running;
diff --git a/node/Topology.cpp b/node/Topology.cpp
index e627e767..8487684e 100644
--- a/node/Topology.cpp
+++ b/node/Topology.cpp
@@ -39,7 +39,8 @@ namespace ZeroTier {
Topology::Topology(const RuntimeEnvironment *renv,const char *dbpath)
throw(std::runtime_error) :
Thread(),
- _r(renv)
+ _r(renv),
+ _amSupernode(false)
{
if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWCREAT,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) {
if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWREPLACE,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE))
@@ -77,9 +78,11 @@ Topology::~Topology()
void Topology::setSupernodes(const std::map< Identity,std::vector<InetAddress> > &sn)
{
Mutex::Lock _l(_supernodes_m);
+
_supernodes = sn;
_supernodeAddresses.clear();
_supernodePeers.clear();
+
for(std::map< Identity,std::vector<InetAddress> >::const_iterator i(sn.begin());i!=sn.end();++i) {
if (i->first != _r->identity) {
SharedPtr<Peer> p(getPeer(i->first.address()));
@@ -93,6 +96,8 @@ void Topology::setSupernodes(const std::map< Identity,std::vector<InetAddress> >
}
_supernodeAddresses.insert(i->first.address());
}
+
+ _amSupernode = (_supernodes.find(_r->identity) != _supernodes.end());
}
void Topology::addPeer(const SharedPtr<Peer> &candidate,void (*callback)(void *,const SharedPtr<Peer> &,Topology::PeerVerifyResult),void *arg)
@@ -127,9 +132,12 @@ SharedPtr<Peer> Topology::getPeer(const Address &zta)
return ap->second;
}
+ unsigned char ztatmp[ZT_ADDRESS_LENGTH];
+ zta.copyTo(ztatmp,ZT_ADDRESS_LENGTH);
+
Buffer<ZT_KISSDB_VALUE_SIZE> b(ZT_KISSDB_VALUE_SIZE);
_dbm_m.lock();
- if (!KISSDB_get(&_dbm,zta.data(),b.data())) {
+ if (!KISSDB_get(&_dbm,ztatmp,b.data())) {
_dbm_m.unlock();
SharedPtr<Peer> p(new Peer());
@@ -300,11 +308,13 @@ void Topology::main()
for(std::map< Address,SharedPtr<Peer> >::iterator p(_activePeers.begin());p!=_activePeers.end();++p) {
if (p->second->getAndResetDirty()) {
try {
+ uint64_t atmp[ZT_ADDRESS_LENGTH];
+ p->second->identity().address().copyTo(atmp,ZT_ADDRESS_LENGTH);
Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b;
p->second->serialize(b);
b.zeroUnused();
_dbm_m.lock();
- if (KISSDB_put(&_dbm,p->second->identity().address().data(),b.data())) {
+ if (KISSDB_put(&_dbm,atmp,b.data())) {
TRACE("error writing %s to peer.db",p->second->identity().address().toString().c_str());
}
_dbm_m.unlock();
@@ -329,11 +339,13 @@ void Topology::_reallyAddPeer(const SharedPtr<Peer> &p)
_activePeers[p->identity().address()] = p;
}
try {
+ uint64_t atmp[ZT_ADDRESS_LENGTH];
+ p->address().copyTo(atmp,ZT_ADDRESS_LENGTH);
Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b;
p->serialize(b);
b.zeroUnused();
_dbm_m.lock();
- if (KISSDB_put(&_dbm,p->identity().address().data(),b.data())) {
+ if (KISSDB_put(&_dbm,atmp,b.data())) {
TRACE("error writing %s to peerdb",p->address().toString().c_str());
} else p->getAndResetDirty();
_dbm_m.unlock();
diff --git a/node/Topology.hpp b/node/Topology.hpp
index ae1a15b4..104ad344 100644
--- a/node/Topology.hpp
+++ b/node/Topology.hpp
@@ -163,6 +163,11 @@ public:
}
/**
+ * @return True if this node's identity is in the supernode set
+ */
+ inline bool amSupernode() const { return _amSupernode; }
+
+ /**
* Clean and flush database now (runs in the background)
*/
void clean();
@@ -271,35 +276,6 @@ public:
std::vector< SharedPtr<Peer> > &_v;
};
- /**
- * Dump peer I/O statistics to an open FILE (for status reporting and debug)
- */
- class DumpPeerStatistics
- {
- public:
- DumpPeerStatistics(FILE *out) :
- _out(out),
- _now(Utils::now())
- {
- fprintf(_out,"Peer Direct IPv4 Direct IPv6 Latency(ms)"ZT_EOL_S);
- }
-
- inline void operator()(Topology &t,const SharedPtr<Peer> &p)
- {
- InetAddress v4(p->ipv4ActivePath(_now));
- InetAddress v6(p->ipv6ActivePath(_now));
- fprintf(_out,"%-10s %-21s %-51s %u"ZT_EOL_S,
- p->address().toString().c_str(),
- ((v4) ? v4.toString().c_str() : "(none)"),
- ((v6) ? v6.toString().c_str() : "(none)"),
- p->latency());
- }
-
- private:
- FILE *_out;
- uint64_t _now;
- };
-
protected:
virtual void main()
throw();
@@ -334,6 +310,9 @@ private:
std::vector< SharedPtr<Peer> > _supernodePeers;
Mutex _supernodes_m;
+ // Set to true if my identity is in _supernodes
+ volatile bool _amSupernode;
+
KISSDB _dbm;
Mutex _dbm_m;
};
diff --git a/node/UdpSocket.cpp b/node/UdpSocket.cpp
index 95156fcc..6178d16e 100644
--- a/node/UdpSocket.cpp
+++ b/node/UdpSocket.cpp
@@ -49,6 +49,7 @@
namespace ZeroTier {
UdpSocket::UdpSocket(
+ bool localOnly,
int localPort,
bool ipv6,
void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int),
@@ -87,7 +88,9 @@ UdpSocket::UdpSocket(
memset(&sin6,0,sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(localPort);
- memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr));
+ if (localOnly)
+ memcpy(&(sin6.sin6_addr.s6_addr),InetAddress::LO6.rawIpData(),16);
+ else memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr));
if (::bind(_sock,(const struct sockaddr *)&sin6,sizeof(sin6))) {
::close(_sock);
throw std::runtime_error("unable to bind to port");
@@ -109,7 +112,9 @@ UdpSocket::UdpSocket(
memset(&sin,0,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(localPort);
- sin.sin_addr.s_addr = INADDR_ANY;
+ if (localOnly)
+ memcpy(&(sin.sin_addr.s_addr),InetAddress::LO4.rawIpData(),4);
+ else sin.sin_addr.s_addr = INADDR_ANY;
if (::bind(_sock,(const struct sockaddr *)&sin,sizeof(sin))) {
::close(_sock);
throw std::runtime_error("unable to bind to port");
diff --git a/node/UdpSocket.hpp b/node/UdpSocket.hpp
index be407d56..a3223f1f 100644
--- a/node/UdpSocket.hpp
+++ b/node/UdpSocket.hpp
@@ -46,6 +46,7 @@ public:
/**
* Create and bind a local UDP socket
*
+ * @param localOnly If true, bind to loopback address only
* @param localPort Local port to listen to
* @param ipv6 If true, bind this as an IPv6 socket, otherwise IPv4
* @param packetHandler Function to call when packets are read
@@ -53,6 +54,7 @@ public:
* @throws std::runtime_error Unable to bind
*/
UdpSocket(
+ bool localOnly,
int localPort,
bool ipv6,
void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int),
diff --git a/node/Utils.cpp b/node/Utils.cpp
index b9db07b9..7a4d51ba 100644
--- a/node/Utils.cpp
+++ b/node/Utils.cpp
@@ -28,14 +28,14 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
-#include "Utils.hpp"
-#include "Mutex.hpp"
+#include <stdarg.h>
#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <dirent.h>
#endif
#ifdef _WIN32
@@ -45,6 +45,9 @@
#include <sys/stat.h>
#include <openssl/rand.h>
+#include "Utils.hpp"
+#include "Mutex.hpp"
+
namespace ZeroTier {
const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
@@ -213,6 +216,29 @@ const char Utils::base64DecMap[128] = {
static const char *DAY_NAMES[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
static const char *MONTH_NAMES[12] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" };
+std::map<std::string,bool> Utils::listDirectory(const char *path)
+{
+ struct dirent de;
+ struct dirent *dptr;
+ std::map<std::string,bool> r;
+
+ DIR *d = opendir(path);
+ if (!d)
+ return r;
+
+ dptr = (struct dirent *)0;
+ for(;;) {
+ if (readdir_r(d,&de,&dptr))
+ break;
+ if (dptr) {
+ if ((!strcmp(dptr->d_name,"."))&&(!strcmp(dptr->d_name,"..")))
+ r[std::string(dptr->d_name)] = (dptr->d_type == DT_DIR);
+ } else break;
+ }
+
+ return r;
+}
+
std::string Utils::base64Encode(const void *data,unsigned int len)
{
if (!len)
@@ -530,4 +556,20 @@ std::string Utils::trim(const std::string &s)
return s.substr(start,end - start);
}
+void Utils::stdsprintf(std::string &s,const char *fmt,...)
+ throw(std::bad_alloc,std::length_error)
+{
+ char buf[65536];
+ va_list ap;
+
+ va_start(ap,fmt);
+ int n = vsnprintf(buf,sizeof(buf),fmt,ap);
+ va_end(ap);
+
+ if ((n >= (int)sizeof(buf))||(n < 0))
+ throw std::length_error("printf result too large");
+
+ s.append(buf);
+}
+
} // namespace ZeroTier
diff --git a/node/Utils.hpp b/node/Utils.hpp
index e6bef5e5..a222ca4f 100644
--- a/node/Utils.hpp
+++ b/node/Utils.hpp
@@ -34,13 +34,14 @@
#include <time.h>
#include <sys/time.h>
#include <arpa/inet.h>
+
#include <string>
#include <stdexcept>
#include <vector>
+#include <map>
#include "../ext/lz4/lz4.h"
#include "../ext/lz4/lz4hc.h"
-#include "../ext/huffandpuff/huffman.h"
#include "Constants.hpp"
@@ -58,6 +59,16 @@ class Utils
{
public:
/**
+ * List a directory's contents
+ *
+ * @param path Path to list
+ * @param files Set to fill with files
+ * @param directories Set to fill with directories
+ * @return Map of entries and whether or not they are also directories (empty on failure)
+ */
+ static std::map<std::string,bool> listDirectory(const char *path);
+
+ /**
* @param data Data to convert to hex
* @param len Length of data
* @return Hexadecimal string
@@ -109,6 +120,15 @@ public:
static uint64_t getLastModified(const char *path);
/**
+ * @param path Path to check
+ * @return True if file or directory exists at path location
+ */
+ static inline bool fileExists(const char *path)
+ {
+ return (getLastModified(path) != 0);
+ }
+
+ /**
* @param t64 Time in ms since epoch
* @return RFC1123 date string
*/
@@ -164,7 +184,6 @@ public:
template<typename I,typename O>
static inline void compress(I begin,I end,O out)
{
- char huffheap[HUFFHEAP_SIZE];
unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE);
char *buf = new char[bufLen * 2];
char *buf2 = buf + bufLen;
@@ -198,16 +217,9 @@ public:
continue;
}
- unsigned long huffCompressedLen = huffman_compress((const unsigned char *)buf2,lz4CompressedLen,(unsigned char *)buf,bufLen,huffheap);
- if ((!huffCompressedLen)||((int)huffCompressedLen >= lz4CompressedLen)) {
- l = hton((uint32_t)lz4CompressedLen); // lz4 only
- out((const void *)&l,4);
- out((const void *)buf2,(unsigned int)lz4CompressedLen);
- } else {
- l = hton((uint32_t)0x80000000 | (uint32_t)huffCompressedLen); // lz4 with huffman
- out((const void *)&l,4);
- out((const void *)buf,(unsigned int)huffCompressedLen);
- }
+ l = hton((uint32_t)lz4CompressedLen); // lz4 only
+ out((const void *)&l,4);
+ out((const void *)buf2,(unsigned int)lz4CompressedLen);
}
delete [] buf;
@@ -230,7 +242,6 @@ public:
template<typename I,typename O>
static inline bool decompress(I begin,I end,O out)
{
- char huffheap[HUFFHEAP_SIZE];
volatile char i32c[4];
void *const i32cp = (void *)i32c;
unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE);
@@ -267,23 +278,10 @@ public:
return false;
}
- if ((_compressedSize & 0x80000000)) { // lz4 and huffman
- unsigned long lz4CompressedSize = huffman_decompress((const unsigned char *)buf,compressedSize,(unsigned char *)buf2,bufLen,huffheap);
- if (lz4CompressedSize) {
- if (LZ4_uncompress_unknownOutputSize(buf2,buf,lz4CompressedSize,bufLen) != (int)originalSize) {
- delete [] buf;
- return false;
- } else out((const void *)buf,(unsigned int)originalSize);
- } else {
- delete [] buf;
- return false;
- }
- } else { // lz4 only
- if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) {
- delete [] buf;
- return false;
- } else out((const void *)buf2,(unsigned int)originalSize);
- }
+ if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) {
+ delete [] buf;
+ return false;
+ } else out((const void *)buf2,(unsigned int)originalSize);
} else { // stored
if (originalSize > bufLen) {
delete [] buf;
@@ -392,6 +390,18 @@ public:
static std::string trim(const std::string &s);
/**
+ * Like sprintf, but appends to std::string
+ *
+ * @param s String to append to
+ * @param fmt Printf format string
+ * @param ... Format arguments
+ * @throws std::bad_alloc Memory allocation failure
+ * @throws std::length_error Format + args exceeds internal buffer maximum
+ */
+ static void stdsprintf(std::string &s,const char *fmt,...)
+ throw(std::bad_alloc,std::length_error);
+
+ /**
* Count the number of bits set in an integer
*
* @param v 32-bit integer