diff options
Diffstat (limited to 'node')
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 |