diff options
Diffstat (limited to 'zerotierone/node')
23 files changed, 1279 insertions, 1263 deletions
diff --git a/zerotierone/node/CertificateOfMembership.hpp b/zerotierone/node/CertificateOfMembership.hpp index 43c8c0a..0342bc3 100644 --- a/zerotierone/node/CertificateOfMembership.hpp +++ b/zerotierone/node/CertificateOfMembership.hpp @@ -295,7 +295,6 @@ public: * @param s String to deserialize */ void fromString(const char *s); - inline void fromString(const std::string &s) { fromString(s.c_str()); } #endif // ZT_SUPPORT_OLD_STYLE_NETCONF /** diff --git a/zerotierone/node/Dictionary.cpp b/zerotierone/node/Dictionary.cpp deleted file mode 100644 index b334066..0000000 --- a/zerotierone/node/Dictionary.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ - * - * 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/>. - */ - -#include "Dictionary.hpp" - -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - -#include "C25519.hpp" -#include "Identity.hpp" -#include "Utils.hpp" - -namespace ZeroTier { - -Dictionary::iterator Dictionary::find(const std::string &key) -{ - for(iterator i(begin());i!=end();++i) { - if (i->first == key) - return i; - } - return end(); -} -Dictionary::const_iterator Dictionary::find(const std::string &key) const -{ - for(const_iterator i(begin());i!=end();++i) { - if (i->first == key) - return i; - } - return end(); -} - -bool Dictionary::getBoolean(const std::string &key,bool dfl) const -{ - const_iterator e(find(key)); - if (e == end()) - return dfl; - if (e->second.length() < 1) - return dfl; - switch(e->second[0]) { - case '1': - case 't': - case 'T': - case 'y': - case 'Y': - return true; - } - return false; -} - -std::string &Dictionary::operator[](const std::string &key) -{ - for(iterator i(begin());i!=end();++i) { - if (i->first == key) - return i->second; - } - push_back(std::pair<std::string,std::string>(key,std::string())); - std::sort(begin(),end()); - for(iterator i(begin());i!=end();++i) { - if (i->first == key) - return i->second; - } - return front().second; // should be unreachable! -} - -std::string Dictionary::toString() const -{ - std::string s; - for(const_iterator kv(begin());kv!=end();++kv) { - _appendEsc(kv->first.data(),(unsigned int)kv->first.length(),s); - s.push_back('='); - _appendEsc(kv->second.data(),(unsigned int)kv->second.length(),s); - s.append(ZT_EOL_S); - } - return s; -} - -void Dictionary::updateFromString(const char *s,unsigned int maxlen) -{ - bool escapeState = false; - std::string keyBuf; - std::string *element = &keyBuf; - const char *end = s + maxlen; - while ((*s)&&(s < end)) { - 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]; -} - -void Dictionary::fromString(const char *s,unsigned int maxlen) -{ - clear(); - updateFromString(s,maxlen); -} - -void Dictionary::eraseKey(const std::string &key) -{ - for(iterator i(begin());i!=end();++i) { - if (i->first == key) { - this->erase(i); - return; - } - } -} - -bool Dictionary::sign(const Identity &id,uint64_t now) -{ - try { - // Sign identity and timestamp fields too. If there's an existing - // signature, _mkSigBuf() ignores it. - char nows[32]; - Utils::snprintf(nows,sizeof(nows),"%llx",(unsigned long long)now); - (*this)[ZT_DICTIONARY_SIGNATURE_IDENTITY] = id.toString(false); - (*this)[ZT_DICTIONARY_SIGNATURE_TIMESTAMP] = nows; - - // Create a blob to hash and sign from fields in sorted order - std::string buf; - _mkSigBuf(buf); - - // Add signature field - C25519::Signature sig(id.sign(buf.data(),(unsigned int)buf.length())); - (*this)[ZT_DICTIONARY_SIGNATURE] = Utils::hex(sig.data,(unsigned int)sig.size()); - - return true; - } catch ( ... ) { - // Probably means identity has no secret key field - removeSignature(); - return false; - } -} - -bool Dictionary::verify(const Identity &id) const -{ - try { - std::string buf; - _mkSigBuf(buf); - const_iterator sig(find(ZT_DICTIONARY_SIGNATURE)); - if (sig == end()) - return false; - std::string sigbin(Utils::unhex(sig->second)); - return id.verify(buf.data(),(unsigned int)buf.length(),sigbin.data(),(unsigned int)sigbin.length()); - } catch ( ... ) { - return false; - } -} - -uint64_t Dictionary::signatureTimestamp() const -{ - const_iterator ts(find(ZT_DICTIONARY_SIGNATURE_TIMESTAMP)); - if (ts == end()) - return 0; - return Utils::hexStrToU64(ts->second.c_str()); -} - -void Dictionary::_mkSigBuf(std::string &buf) const -{ - unsigned long pairs = 0; - for(const_iterator i(begin());i!=end();++i) { - if (i->first != ZT_DICTIONARY_SIGNATURE) { - buf.append(i->first); - buf.push_back('='); - buf.append(i->second); - buf.push_back('\0'); - ++pairs; - } - } - buf.push_back((char)0xff); - buf.push_back((char)((pairs >> 24) & 0xff)); // pad with number of key/value pairs at end - buf.push_back((char)((pairs >> 16) & 0xff)); - buf.push_back((char)((pairs >> 8) & 0xff)); - buf.push_back((char)(pairs & 0xff)); -} - -void Dictionary::_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 // ZT_SUPPORT_OLD_STYLE_NETCONF diff --git a/zerotierone/node/Dictionary.hpp b/zerotierone/node/Dictionary.hpp index d4cdd23..59fc4bb 100644 --- a/zerotierone/node/Dictionary.hpp +++ b/zerotierone/node/Dictionary.hpp @@ -20,261 +20,443 @@ #define ZT_DICTIONARY_HPP #include "Constants.hpp" - -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - -#include <stdint.h> - -#include <string> -#include <vector> -#include <stdexcept> -#include <algorithm> - #include "Utils.hpp" +#include "Buffer.hpp" +#include "Address.hpp" -// Three fields are added/updated by sign() -#define ZT_DICTIONARY_SIGNATURE "~!ed25519" -#define ZT_DICTIONARY_SIGNATURE_IDENTITY "~!sigid" -#define ZT_DICTIONARY_SIGNATURE_TIMESTAMP "~!sigts" +#include <stdint.h> namespace ZeroTier { -class Identity; - /** - * Simple key/value dictionary with string serialization + * A small (in code and data) packed key=value store + * + * This stores data in the form of a compact blob that is sort of human + * readable (depending on whether you put binary data in it) and is backward + * compatible with older versions. Binary data is escaped such that the + * serialized form of a Dictionary is always a valid null-terminated C string. + * + * Keys are restricted: no binary data, no CR/LF, and no equals (=). If a key + * contains these characters it may not be retrievable. This is not checked. + * + * Lookup is via linear search and will be slow with a lot of keys. It's + * designed for small things. * - * 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. + * There is code to test and fuzz this in selftest.cpp. Fuzzing a blob of + * pointer tricks like this is important after any modifications. * - * Keys beginning with "~!" are reserved for signature data fields. + * This is used for network configurations and for saving some things on disk + * in the ZeroTier One service code. * - * It's stored as a simple vector and can be linearly scanned or - * binary searched. Dictionaries are only used for very small things - * outside the core loop, so this is not a significant performance - * issue and it reduces memory use and code footprint. + * @tparam C Dictionary max capacity in bytes */ -class Dictionary : public std::vector< std::pair<std::string,std::string> > +template<unsigned int C> +class Dictionary { public: - Dictionary() {} + Dictionary() + { + _d[0] = (char)0; + } - /** - * @param s String-serialized dictionary - * @param maxlen Maximum length of buffer - */ - Dictionary(const char *s,unsigned int maxlen) { fromString(s,maxlen); } + Dictionary(const char *s) + { + Utils::scopy(_d,sizeof(_d),s); + } - /** - * @param s String-serialized dictionary - */ - Dictionary(const std::string &s) { fromString(s.c_str(),(unsigned int)s.length()); } + Dictionary(const char *s,unsigned int len) + { + if (len > (C-1)) + len = C-1; + memcpy(_d,s,len); + _d[len] = (char)0; + } - iterator find(const std::string &key); - const_iterator find(const std::string &key) const; + Dictionary(const Dictionary &d) + { + Utils::scopy(_d,sizeof(_d),d._d); + } - /** - * 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 + inline Dictionary &operator=(const Dictionary &d) { - const_iterator e(find(key)); - if (e == end()) - return dfl; - return e->second; + Utils::scopy(_d,sizeof(_d),d._d); + return *this; } /** - * @param key Key to get - * @param dfl Default boolean result if key not found or empty (default: false) - * @return Boolean value of key + * Load a dictionary from a C-string + * + * @param s Dictionary in string form + * @return False if 's' was longer than our capacity */ - bool getBoolean(const std::string &key,bool dfl = false) const; + inline bool load(const char *s) + { + return Utils::scopy(_d,sizeof(_d),s); + } /** - * @param key Key to get - * @param dfl Default value if not present (default: 0) - * @return Value converted to unsigned 64-bit int or 0 if not found + * Delete all entries */ - inline uint64_t getUInt(const std::string &key,uint64_t dfl = 0) const + inline void clear() { - const_iterator e(find(key)); - if (e == end()) - return dfl; - return Utils::strToU64(e->second.c_str()); + _d[0] = (char)0; } /** - * @param key Key to get - * @param dfl Default value if not present (default: 0) - * @return Value converted to unsigned 64-bit int or 0 if not found + * @return Size of dictionary in bytes not including terminating NULL */ - inline uint64_t getHexUInt(const std::string &key,uint64_t dfl = 0) const + inline unsigned int sizeBytes() const { - const_iterator e(find(key)); - if (e == end()) - return dfl; - return Utils::hexStrToU64(e->second.c_str()); + for(unsigned int i=0;i<C;++i) { + if (!_d[i]) + return i; + } + return C-1; } /** - * @param key Key to get - * @param dfl Default value if not present (default: 0) - * @return Value converted to signed 64-bit int or 0 if not found + * Get an entry + * + * Note that to get binary values, dest[] should be at least one more than + * the maximum size of the value being retrieved. That's because even if + * the data is binary a terminating 0 is still appended to dest[] after it. + * + * If the key is not found, dest[0] is set to 0 to make dest[] an empty + * C string in that case. The dest[] array will *never* be unterminated + * after this call. + * + * Security note: if 'key' is ever directly based on anything that is not + * a hard-code or internally-generated name, it must be checked to ensure + * that the buffer is NULL-terminated since key[] does not take a secondary + * size parameter. In NetworkConfig all keys are hard-coded strings so this + * isn't a problem in the core. + * + * @param key Key to look up + * @param dest Destination buffer + * @param destlen Size of destination buffer + * @return -1 if not found, or actual number of bytes stored in dest[] minus trailing 0 */ - inline int64_t getInt(const std::string &key,int64_t dfl = 0) const + inline int get(const char *key,char *dest,unsigned int destlen) const { - const_iterator e(find(key)); - if (e == end()) - return dfl; - return Utils::strTo64(e->second.c_str()); + const char *p = _d; + const char *const eof = p + C; + const char *k; + bool esc; + int j; + + if (!destlen) // sanity check + return -1; + + while (*p) { + k = key; + while ((*k)&&(*p)) { + if (*p != *k) + break; + ++k; + if (++p == eof) { + dest[0] = (char)0; + return -1; + } + } + + if ((!*k)&&(*p == '=')) { + j = 0; + esc = false; + ++p; + while ((*p != 0)&&(*p != '\r')&&(*p != '\n')) { + if (esc) { + esc = false; + switch(*p) { + case 'r': dest[j++] = '\r'; break; + case 'n': dest[j++] = '\n'; break; + case '0': dest[j++] = (char)0; break; + case 'e': dest[j++] = '='; break; + default: dest[j++] = *p; break; + } + if (j == (int)destlen) { + dest[j-1] = (char)0; + return j-1; + } + } else if (*p == '\\') { + esc = true; + } else { + dest[j++] = *p; + if (j == (int)destlen) { + dest[j-1] = (char)0; + return j-1; + } + } + if (++p == eof) { + dest[0] = (char)0; + return -1; + } + } + dest[j] = (char)0; + return j; + } else { + while ((*p)&&(*p != '\r')&&(*p != '\n')) { + if (++p == eof) { + dest[0] = (char)0; + return -1; + } + } + if (*p) { + if (++p == eof) { + dest[0] = (char)0; + return -1; + } + } + else break; + } + } + + dest[0] = (char)0; + return -1; } - std::string &operator[](const std::string &key); - /** - * @param key Key to set - * @param value String value + * Get the contents of a key into a buffer + * + * @param key Key to get + * @param dest Destination buffer + * @return True if key was found (if false, dest will be empty) + * @tparam BC Buffer capacity (usually inferred) */ - inline void set(const std::string &key,const char *value) + template<unsigned int BC> + inline bool get(const char *key,Buffer<BC> &dest) const { - (*this)[key] = value; + const int r = this->get(key,const_cast<char *>(reinterpret_cast<const char *>(dest.data())),BC); + if (r >= 0) { + dest.setSize((unsigned int)r); + return true; + } else { + dest.clear(); + return false; + } } /** - * @param key Key to set - * @param value String value + * Get a boolean value + * + * @param key Key to look up + * @param dfl Default value if not found in dictionary + * @return Boolean value of key or 'dfl' if not found */ - inline void set(const std::string &key,const std::string &value) + bool getB(const char *key,bool dfl = false) const { - (*this)[key] = value; + char tmp[4]; + if (this->get(key,tmp,sizeof(tmp)) >= 0) + return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T')); + return dfl; } /** - * @param key Key to set - * @param value Boolean value + * Get an unsigned int64 stored as hex in the dictionary + * + * @param key Key to look up + * @param dfl Default value or 0 if unspecified + * @return Decoded hex UInt value or 'dfl' if not found */ - inline void set(const std::string &key,bool value) + inline uint64_t getUI(const char *key,uint64_t dfl = 0) const { - (*this)[key] = ((value) ? "1" : "0"); + char tmp[128]; + if (this->get(key,tmp,sizeof(tmp)) >= 1) + return Utils::hexStrToU64(tmp); + return dfl; } /** - * @param key Key to set - * @param value Integer value + * Add a new key=value pair + * + * If the key is already present this will append another, but the first + * will always be returned by get(). This is not checked. If you want to + * ensure a key is not present use erase() first. + * + * Use the vlen parameter to add binary values. Nulls will be escaped. + * + * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters + * @param value Value to set + * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0 + * @return True if there was enough room to add this key=value pair */ - inline void set(const std::string &key,uint64_t value) + inline bool add(const char *key,const char *value,int vlen = -1) { - char tmp[24]; - Utils::snprintf(tmp,sizeof(tmp),"%llu",(unsigned long long)value); - (*this)[key] = tmp; + for(unsigned int i=0;i<C;++i) { + if (!_d[i]) { + unsigned int j = i; + + if (j > 0) { + _d[j++] = '\n'; + if (j == C) { + _d[i] = (char)0; + return false; + } + } + + const char *p = key; + while (*p) { + _d[j++] = *(p++); + if (j == C) { + _d[i] = (char)0; + return false; + } + } + + _d[j++] = '='; + if (j == C) { + _d[i] = (char)0; + return false; + } + + p = value; + int k = 0; + while ( ((vlen < 0)&&(*p)) || (k < vlen) ) { + switch(*p) { + case 0: + case '\r': + case '\n': + case '\\': + case '=': + _d[j++] = '\\'; + if (j == C) { + _d[i] = (char)0; + return false; + } + switch(*p) { + case 0: _d[j++] = '0'; break; + case '\r': _d[j++] = 'r'; break; + case '\n': _d[j++] = 'n'; break; + case '\\': _d[j++] = '\\'; break; + case '=': _d[j++] = 'e'; break; + } + if (j == C) { + _d[i] = (char)0; + return false; + } + break; + default: + _d[j++] = *p; + if (j == C) { + _d[i] = (char)0; + return false; + } + break; + } + ++p; + ++k; + } + + _d[j] = (char)0; + + return true; + } + } + return false; } /** - * @param key Key to set - * @param value Integer value + * Add a boolean as a '1' or a '0' */ - inline void set(const std::string &key,int64_t value) + inline bool add(const char *key,bool value) { - char tmp[24]; - Utils::snprintf(tmp,sizeof(tmp),"%lld",(long long)value); - (*this)[key] = tmp; + return this->add(key,(value) ? "1" : "0",1); } - /** - * @param key Key to set - * @param value Integer value + /** + * Add a 64-bit integer (unsigned) as a hex value */ - inline void setHex(const std::string &key,uint64_t value) + inline bool add(const char *key,uint64_t value) { - char tmp[24]; + char tmp[32]; Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)value); - (*this)[key] = tmp; + return this->add(key,tmp,-1); } - /** - * @param key Key to check - * @return True if dictionary contains key + /** + * Add a 64-bit integer (unsigned) as a hex value */ - inline bool contains(const std::string &key) const { return (find(key) != end()); } - - /** - * @return String-serialized dictionary - */ - std::string toString() const; + inline bool add(const char *key,const Address &a) + { + char tmp[32]; + Utils::snprintf(tmp,sizeof(tmp),"%.10llx",(unsigned long long)a.toInt()); + return this->add(key,tmp,-1); + } /** - * Clear and initialize from a string + * Add a binary buffer's contents as a value * - * @param s String-serialized dictionary - * @param maxlen Maximum length of string buffer + * @tparam BC Buffer capacity (usually inferred) */ - void fromString(const char *s,unsigned int maxlen); - inline void fromString(const std::string &s) { fromString(s.c_str(),(unsigned int)s.length()); } - void updateFromString(const char *s,unsigned int maxlen); - inline void update(const char *s,unsigned int maxlen) { updateFromString(s, maxlen); } - inline void update(const std::string &s) { updateFromString(s.c_str(),(unsigned int)s.length()); } - - /** - * @return True if this dictionary is cryptographically signed - */ - inline bool hasSignature() const { return (find(ZT_DICTIONARY_SIGNATURE) != end()); } - - /** - * @return Signing identity in string-serialized format or empty string if none - */ - inline std::string signingIdentity() const { return get(ZT_DICTIONARY_SIGNATURE_IDENTITY,std::string()); } + template<unsigned int BC> + inline bool add(const char *key,const Buffer<BC> &value) + { + return this->add(key,(const char *)value.data(),(int)value.size()); + } /** - * @return Signature timestamp in milliseconds since epoch or 0 if none + * @param key Key to check + * @return True if key is present */ - uint64_t signatureTimestamp() const; + inline bool contains(const char *key) const + { + char tmp[2]; + return (this->get(key,tmp,2) >= 0); + } /** + * Erase a key from this dictionary + * + * Use this before add() to ensure that a key is replaced if it might + * already be present. + * * @param key Key to erase + * @return True if key was found and erased */ - void eraseKey(const std::string &key); - - /** - * Remove any signature from this dictionary - */ - inline void removeSignature() + inline bool erase(const char *key) { - eraseKey(ZT_DICTIONARY_SIGNATURE); - eraseKey(ZT_DICTIONARY_SIGNATURE_IDENTITY); - eraseKey(ZT_DICTIONARY_SIGNATURE_TIMESTAMP); + char d2[C]; + char *saveptr = (char *)0; + unsigned int d2ptr = 0; + bool found = false; + for(char *f=Utils::stok(_d,"\r\n",&saveptr);(f);f=Utils::stok((char *)0,"\r\n",&saveptr)) { + if (*f) { + const char *p = f; + const char *k = key; + while ((*k)&&(*p)) { + if (*k != *p) + break; + ++k; + ++p; + } + if (*k) { + p = f; + while (*p) + d2[d2ptr++] = *(p++); + d2[d2ptr++] = '\n'; + } else { + found = true; + } + } + } + d2[d2ptr++] = (char)0; + memcpy(_d,d2,d2ptr); + return found; } /** - * Add or update signature fields with a signature of all other keys and values - * - * @param with Identity to sign with (must have secret key) - * @param now Current time - * @return True on success + * @return Dictionary data as a 0-terminated C-string */ - bool sign(const Identity &id,uint64_t now); + inline const char *data() const { return _d; } /** - * Verify signature against an identity - * - * @param id Identity to verify against - * @return True if signature verification OK + * @return Value of C template parameter */ - bool verify(const Identity &id) const; + inline unsigned int capacity() const { return C; } private: - void _mkSigBuf(std::string &buf) const; - static void _appendEsc(const char *data,unsigned int len,std::string &to); + char _d[C]; }; } // namespace ZeroTier -#endif // ZT_SUPPORT_OLD_STYLE_NETCONF - #endif diff --git a/zerotierone/node/IncomingPacket.cpp b/zerotierone/node/IncomingPacket.cpp index 9e0226b..37af842 100644 --- a/zerotierone/node/IncomingPacket.cpp +++ b/zerotierone/node/IncomingPacket.cpp @@ -43,8 +43,23 @@ namespace ZeroTier { bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,bool deferred) { const Address sourceAddress(source()); + try { - if ((cipher() == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) { + // Check for trusted paths or unencrypted HELLOs (HELLO is the only packet sent in the clear) + const unsigned int c = cipher(); + bool trusted = false; + if (c == ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH) { + // If this is marked as a packet via a trusted path, check source address and path ID. + // Obviously if no trusted paths are configured this always returns false and such + // packets are dropped on the floor. + if (RR->topology->shouldInboundPathBeTrusted(_remoteAddress,trustedPathId())) { + trusted = true; + TRACE("TRUSTED PATH packet approved from %s(%s), trusted path ID %llx",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str(),trustedPathId()); + } else { + TRACE("dropped packet from %s(%s), cipher set to trusted path mode but path %llx@%s is not trusted!",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str(),trustedPathId(),_remoteAddress.toString().c_str()); + return true; + } + } else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) { // Unencrypted HELLOs require some potentially expensive verification, so // do this in the background if background processing is enabled. if ((RR->dpEnabled > 0)&&(!deferred)) { @@ -61,12 +76,15 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,bool deferred) SharedPtr<Peer> peer(RR->topology->getPeer(sourceAddress)); if (peer) { - if (!dearmor(peer->key())) { - TRACE("dropped packet from %s(%s), MAC authentication failed (size: %u)",peer->address().toString().c_str(),_remoteAddress.toString().c_str(),size()); - return true; + if (!trusted) { + if (!dearmor(peer->key())) { + TRACE("dropped packet from %s(%s), MAC authentication failed (size: %u)",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str(),size()); + return true; + } } + if (!uncompress()) { - TRACE("dropped packet from %s(%s), compressed data invalid",peer->address().toString().c_str(),_remoteAddress.toString().c_str()); + TRACE("dropped packet from %s(%s), compressed data invalid",sourceAddress.toString().c_str(),_remoteAddress.toString().c_str()); return true; } @@ -403,8 +421,12 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &p if ((nw)&&(nw->controller() == peer->address())) { const unsigned int nclen = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN); if (nclen) { - nw->setConfiguration(field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,nclen),nclen,true); - TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str()); + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> dconf((const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT,nclen),nclen); + NetworkConfig nconf; + if (nconf.fromDictionary(dconf)) { + nw->setConfiguration(nconf,true); + TRACE("got network configuration for network %.16llx from %s",(unsigned long long)nw->id(),source().toString().c_str()); + } } } } break; @@ -679,27 +701,8 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons const uint64_t nwid = at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); const unsigned int metaDataLength = at<uint16_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN); - const uint8_t *metaDataBytes = (const uint8_t *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength); - - NetworkConfigRequestMetaData metaData; - bool haveNewStyleMetaData = false; - for(unsigned int i=0;i<metaDataLength;++i) { - if ((metaDataBytes[i] == 0)&&(i < (metaDataLength - 2))) { - haveNewStyleMetaData = true; - break; - } - } - if (haveNewStyleMetaData) { - Buffer<4096> md(metaDataBytes,metaDataLength); - metaData.deserialize(md,0); // the meta-data deserializer automatically skips old-style meta-data - } else { -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - const Dictionary oldStyleMetaData((const char *)metaDataBytes,metaDataLength); - metaData.majorVersion = (unsigned int)oldStyleMetaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,0); - metaData.minorVersion = (unsigned int)oldStyleMetaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,0); - metaData.revision = (unsigned int)oldStyleMetaData.getHexUInt(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,0); -#endif - } + const char *metaDataBytes = (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength); + const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> metaData(metaDataBytes,metaDataLength); //const uint64_t haveRevision = ((ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength + 8) <= size()) ? at<uint64_t>(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT + metaDataLength) : 0ULL; @@ -708,22 +711,21 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,cons peer->received(_localAddress,_remoteAddress,h,pid,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP); if (RR->localNetworkController) { - Buffer<8194> netconf; + NetworkConfig netconf; switch(RR->localNetworkController->doNetworkConfigRequest((h > 0) ? InetAddress() : _remoteAddress,RR->identity,peer->identity(),nwid,metaData,netconf)) { case NetworkController::NETCONF_QUERY_OK: { - Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); - outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); - outp.append(pid); - outp.append(nwid); - outp.append((uint16_t)netconf.size()); - outp.append(netconf.data(),(unsigned int)netconf.size()); - outp.compress(); - outp.armor(peer->key(),true); - if (outp.size() > ZT_PROTO_MAX_PACKET_LENGTH) { // sanity check - //TRACE("NETWORK_CONFIG_REQUEST failed: internal error: netconf size %u is too large",(unsigned int)netconfStr.length()); - } else { - RR->node->putPacket(_localAddress,_remoteAddress,outp.data(),outp.size()); + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> dconf; + if (netconf.toDictionary(dconf,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6)) { + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(pid); + outp.append(nwid); + const unsigned int dlen = dconf.sizeBytes(); + outp.append((uint16_t)dlen); + outp.append((const void *)dconf.data(),dlen); + outp.compress(); + RR->sw->send(outp,true,0); } } break; @@ -1208,8 +1210,20 @@ bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,const S bool IncomingPacket::_doREQUEST_PROOF_OF_WORK(const RuntimeEnvironment *RR,const SharedPtr<Peer> &peer) { try { - // Right now this is only allowed from root servers -- may be allowed from controllers and relays later. - if (RR->topology->isRoot(peer->identity())) { + // If this were allowed from anyone, it would itself be a DOS vector. Right + // now we only allow it from roots and controllers of networks you have joined. + bool allowed = RR->topology->isRoot(peer->identity()); + if (!allowed) { + std::vector< SharedPtr<Network> > allNetworks(RR->node->allNetworks()); + for(std::vector< SharedPtr<Network> >::const_iterator n(allNetworks.begin());n!=allNetworks.end();++n) { + if (peer->address() == (*n)->controller()) { + allowed = true; + break; + } + } + } + + if (allowed) { const uint64_t pid = packetId(); const unsigned int difficulty = (*this)[ZT_PACKET_IDX_PAYLOAD + 1]; const unsigned int challengeLength = at<uint16_t>(ZT_PACKET_IDX_PAYLOAD + 2); diff --git a/zerotierone/node/IncomingPacket.hpp b/zerotierone/node/IncomingPacket.hpp index 96e46c0..cd0b7dc 100644 --- a/zerotierone/node/IncomingPacket.hpp +++ b/zerotierone/node/IncomingPacket.hpp @@ -79,10 +79,10 @@ public: * @throws std::out_of_range Range error processing packet */ IncomingPacket(const void *data,unsigned int len,const InetAddress &localAddress,const InetAddress &remoteAddress,uint64_t now) : - Packet(data,len), - _receiveTime(now), - _localAddress(localAddress), - _remoteAddress(remoteAddress) + Packet(data,len), + _receiveTime(now), + _localAddress(localAddress), + _remoteAddress(remoteAddress) { } diff --git a/zerotierone/node/InetAddress.cpp b/zerotierone/node/InetAddress.cpp index dca772e..3f6b9be 100644 --- a/zerotierone/node/InetAddress.cpp +++ b/zerotierone/node/InetAddress.cpp @@ -279,6 +279,8 @@ bool InetAddress::containsAddress(const InetAddress &addr) const switch(ss_family) { case AF_INET: { const unsigned int bits = netmaskBits(); + if (bits == 0) + return true; return ( (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr) >> (32 - bits)) ); } case AF_INET6: { @@ -393,7 +395,6 @@ bool InetAddress::operator<(const InetAddress &a) const } InetAddress InetAddress::makeIpv6LinkLocal(const MAC &mac) - throw() { struct sockaddr_in6 sin6; sin6.sin6_family = AF_INET6; @@ -418,7 +419,6 @@ InetAddress InetAddress::makeIpv6LinkLocal(const MAC &mac) } InetAddress InetAddress::makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress) - throw() { InetAddress r; struct sockaddr_in6 *const sin6 = reinterpret_cast<struct sockaddr_in6 *>(&r); @@ -443,4 +443,25 @@ InetAddress InetAddress::makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress) return r; } +InetAddress InetAddress::makeIpv66plane(uint64_t nwid,uint64_t zeroTierAddress) +{ + nwid ^= (nwid >> 32); + InetAddress r; + struct sockaddr_in6 *const sin6 = reinterpret_cast<struct sockaddr_in6 *>(&r); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr.s6_addr[0] = 0xfc; + sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 24); + sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 16); + sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 8); + sin6->sin6_addr.s6_addr[4] = (uint8_t)nwid; + sin6->sin6_addr.s6_addr[5] = (uint8_t)(zeroTierAddress >> 32); + sin6->sin6_addr.s6_addr[6] = (uint8_t)(zeroTierAddress >> 24); + sin6->sin6_addr.s6_addr[7] = (uint8_t)(zeroTierAddress >> 16); + sin6->sin6_addr.s6_addr[8] = (uint8_t)(zeroTierAddress >> 8); + sin6->sin6_addr.s6_addr[9] = (uint8_t)zeroTierAddress; + sin6->sin6_addr.s6_addr[15] = 0x01; + sin6->sin6_port = Utils::hton((uint16_t)40); + return r; +} + } // namespace ZeroTier diff --git a/zerotierone/node/InetAddress.hpp b/zerotierone/node/InetAddress.hpp index b60a5a3..e03deb7 100644 --- a/zerotierone/node/InetAddress.hpp +++ b/zerotierone/node/InetAddress.hpp @@ -231,7 +231,6 @@ struct InetAddress : public sockaddr_storage * @param port Port, 0 to 65535 */ inline void setPort(unsigned int port) - throw() { switch(ss_family) { case AF_INET: @@ -244,6 +243,25 @@ struct InetAddress : public sockaddr_storage } /** + * @return True if this network/netmask route describes a default route (e.g. 0.0.0.0/0) + */ + inline bool isDefaultRoute() const + { + switch(ss_family) { + case AF_INET: + return ( (reinterpret_cast<const struct sockaddr_in *>(this)->sin_addr.s_addr == 0) && (reinterpret_cast<const struct sockaddr_in *>(this)->sin_port == 0) ); + case AF_INET6: + const uint8_t *ipb = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_addr.s6_addr); + for(int i=0;i<16;++i) { + if (ipb[i]) + return false; + } + return (reinterpret_cast<const struct sockaddr_in6 *>(this)->sin6_port == 0); + } + return false; + } + + /** * @return ASCII IP/port format representation */ std::string toString() const; @@ -453,8 +471,7 @@ struct InetAddress : public sockaddr_storage * @param mac MAC address seed * @return IPv6 link-local address */ - static InetAddress makeIpv6LinkLocal(const MAC &mac) - throw(); + static InetAddress makeIpv6LinkLocal(const MAC &mac); /** * Compute private IPv6 unicast address from network ID and ZeroTier address @@ -497,8 +514,12 @@ struct InetAddress : public sockaddr_storage * @param zeroTierAddress 40-bit device address (in least significant 40 bits, highest 24 bits ignored) * @return IPv6 private unicast address with /88 netmask */ - static InetAddress makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress) - throw(); + static InetAddress makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress); + + /** + * Compute a private IPv6 "6plane" unicast address from network ID and ZeroTier address + */ + static InetAddress makeIpv66plane(uint64_t nwid,uint64_t zeroTierAddress); }; } // namespace ZeroTier diff --git a/zerotierone/node/Multicaster.cpp b/zerotierone/node/Multicaster.cpp index 1dfa67d..e1d4567 100644 --- a/zerotierone/node/Multicaster.cpp +++ b/zerotierone/node/Multicaster.cpp @@ -228,7 +228,9 @@ void Multicaster::send( gs.lastExplicitGather = now; SharedPtr<Peer> explicitGatherPeers[2]; explicitGatherPeers[0] = RR->topology->getBestRoot(); - explicitGatherPeers[1] = RR->topology->getPeer(Network::controllerFor(nwid)); + const Address nwidc(Network::controllerFor(nwid)); + if (nwidc != RR->identity.address()) + explicitGatherPeers[1] = RR->topology->getPeer(nwidc); for(unsigned int k=0;k<2;++k) { const SharedPtr<Peer> &p = explicitGatherPeers[k]; if (!p) diff --git a/zerotierone/node/Network.cpp b/zerotierone/node/Network.cpp index c1c3414..2511664 100644 --- a/zerotierone/node/Network.cpp +++ b/zerotierone/node/Network.cpp @@ -41,7 +41,6 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : _uPtr(uptr), _id(nwid), _mac(renv->identity.address(),nwid), - _enabled(true), _portInitialized(false), _lastConfigUpdate(0), _destroyed(false), @@ -65,9 +64,13 @@ Network::Network(const RuntimeEnvironment *renv,uint64_t nwid,void *uptr) : try { std::string conf(RR->node->dataStoreGet(confn)); if (conf.length()) { - this->setConfiguration((const void *)conf.data(),(unsigned int)conf.length(),false); - _lastConfigUpdate = 0; // we still want to re-request a new config from the network - gotConf = true; + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> dconf(conf.c_str()); + NetworkConfig nconf; + if (nconf.fromDictionary(dconf)) { + this->setConfiguration(nconf,false); + _lastConfigUpdate = 0; // we still want to re-request a new config from the network + gotConf = true; + } } } catch ( ... ) {} // ignore invalids, we'll re-request @@ -178,49 +181,21 @@ bool Network::applyConfiguration(const NetworkConfig &conf) return false; } -int Network::setConfiguration(const void *confBytes,unsigned int confLen,bool saveToDisk) +int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk) { try { - if (confLen <= 1) - return 0; - - NetworkConfig newConfig; - - // Find the length of any string-serialized old-style Dictionary, - // including its terminating NULL (if any). If this is before - // the end of the config, that tells us there is a new-style - // binary config which is preferred. - unsigned int dictLen = 0; - while (dictLen < confLen) { - if (!(reinterpret_cast<const uint8_t *>(confBytes)[dictLen++])) - break; - } - - if (dictLen < (confLen - 2)) { - Buffer<8194> tmp(reinterpret_cast<const uint8_t *>(confBytes) + dictLen,confLen - dictLen); - newConfig.deserialize(tmp,0); - } else { -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - newConfig.fromDictionary(reinterpret_cast<const char *>(confBytes),confLen); // throws if invalid -#else - return 0; -#endif - } - - if (!newConfig) - return 0; - { Mutex::Lock _l(_lock); - if (_config == newConfig) + if (_config == nconf) return 1; // OK config, but duplicate of what we already have } - - if (applyConfiguration(newConfig)) { + if (applyConfiguration(nconf)) { if (saveToDisk) { - char n[128]; + char n[64]; Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); - RR->node->dataStorePut(n,confBytes,confLen,true); + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> d; + if (nconf.toDictionary(d,false)) + RR->node->dataStorePut(n,(const void *)d.data(),d.sizeBytes(),true); } return 2; // OK and configuration has changed } @@ -235,12 +210,19 @@ void Network::requestConfiguration() if (_id == ZT_TEST_NETWORK_ID) // pseudo-network-ID, uses locally generated static config return; + Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> rmd; + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,(uint64_t)ZEROTIER_ONE_VERSION_REVISION); + if (controller() == RR->identity.address()) { if (RR->localNetworkController) { - Buffer<8194> tmp; - switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,NetworkConfigRequestMetaData(),tmp)) { + NetworkConfig nconf; + switch(RR->localNetworkController->doNetworkConfigRequest(InetAddress(),RR->identity,RR->identity,_id,rmd,nconf)) { case NetworkController::NETCONF_QUERY_OK: - this->setConfiguration(tmp.data(),tmp.size(),true); + this->setConfiguration(nconf,true); return; case NetworkController::NETCONF_QUERY_OBJECT_NOT_FOUND: this->setNotFound(); @@ -259,16 +241,13 @@ void Network::requestConfiguration() TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,controller().toString().c_str()); - NetworkConfigRequestMetaData metaData; - metaData.initWithDefaults(); - Buffer<4096> mds; - metaData.serialize(mds); // this always includes legacy fields to support old controllers - Packet outp(controller(),RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST); outp.append((uint64_t)_id); - outp.append((uint16_t)mds.size()); - outp.append(mds.data(),mds.size()); + const unsigned int rmdSize = rmd.sizeBytes(); + outp.append((uint16_t)rmdSize); + outp.append((const void *)rmd.data(),rmdSize); outp.append((_config) ? (uint64_t)_config.revision : (uint64_t)0); + outp.compress(); RR->sw->send(outp,true,0); } @@ -337,21 +316,9 @@ void Network::learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now) _announceMulticastGroups(); } -void Network::setEnabled(bool enabled) -{ - Mutex::Lock _l(_lock); - if (_enabled != enabled) { - _enabled = enabled; - ZT_VirtualNetworkConfig ctmp; - _externalConfig(&ctmp); - _portError = RR->node->configureVirtualNetworkPort(_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE,&ctmp); - } -} - void Network::destroy() { Mutex::Lock _l(_lock); - _enabled = false; _destroyed = true; } @@ -388,15 +355,8 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const ec->bridge = ((_config.allowPassiveBridging())||(std::find(ab.begin(),ab.end(),RR->identity.address()) != ab.end())) ? 1 : 0; ec->broadcastEnabled = (_config) ? (_config.enableBroadcast() ? 1 : 0) : 0; ec->portError = _portError; - ec->enabled = (_enabled) ? 1 : 0; ec->netconfRevision = (_config) ? (unsigned long)_config.revision : 0; - ec->multicastSubscriptionCount = std::min((unsigned int)_myMulticastGroups.size(),(unsigned int)ZT_MAX_NETWORK_MULTICAST_SUBSCRIPTIONS); - for(unsigned int i=0;i<ec->multicastSubscriptionCount;++i) { - ec->multicastSubscriptions[i].mac = _myMulticastGroups[i].mac().toInt(); - ec->multicastSubscriptions[i].adi = _myMulticastGroups[i].adi(); - } - ec->assignedAddressCount = 0; for(unsigned int i=0;i<ZT_MAX_ZT_ASSIGNED_ADDRESSES;++i) { if (i < _config.staticIpCount) { @@ -406,6 +366,16 @@ void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const memset(&(ec->assignedAddresses[i]),0,sizeof(struct sockaddr_storage)); } } + + ec->routeCount = 0; + for(unsigned int i=0;i<ZT_MAX_NETWORK_ROUTES;++i) { + if (i < _config.routeCount) { + memcpy(&(ec->routes[i]),&(_config.routes[i]),sizeof(ZT_VirtualNetworkRoute)); + ++ec->routeCount; + } else { + memset(&(ec->routes[i]),0,sizeof(ZT_VirtualNetworkRoute)); + } + } } bool Network::_isAllowed(const SharedPtr<Peer> &peer) const diff --git a/zerotierone/node/Network.hpp b/zerotierone/node/Network.hpp index 9d280fb..17eed4b 100644 --- a/zerotierone/node/Network.hpp +++ b/zerotierone/node/Network.hpp @@ -151,12 +151,11 @@ public: /** * Set or update this network's configuration * - * @param confBytes Network configuration in old-style Dictionary or new-style serialized format - * @param confLen Length of network configuration in bytes + * @param nconf Network configuration * @param saveToDisk IF true (default), write config to disk * @return 0 -- rejected, 1 -- accepted but not new, 2 -- accepted new config */ - int setConfiguration(const void *confBytes,unsigned int confLen,bool saveToDisk); + int setConfiguration(const NetworkConfig &nconf,bool saveToDisk); /** * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this @@ -281,16 +280,6 @@ public: void learnBridgedMulticastGroup(const MulticastGroup &mg,uint64_t now); /** - * @return True if traffic on this network's tap is enabled - */ - inline bool enabled() const throw() { return _enabled; } - - /** - * @param enabled Should traffic be allowed on this network? - */ - void setEnabled(bool enabled); - - /** * Destroy this network * * This causes the network to disable itself, destroy its tap device, and on @@ -323,7 +312,6 @@ private: void *_uPtr; uint64_t _id; MAC _mac; // local MAC address - volatile bool _enabled; volatile bool _portInitialized; std::vector< MulticastGroup > _myMulticastGroups; // multicast groups that we belong to (according to tap) diff --git a/zerotierone/node/NetworkConfig.cpp b/zerotierone/node/NetworkConfig.cpp index 58a48fa..9d5c5f1 100644 --- a/zerotierone/node/NetworkConfig.cpp +++ b/zerotierone/node/NetworkConfig.cpp @@ -23,158 +23,479 @@ namespace ZeroTier { -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - -void NetworkConfig::fromDictionary(const char *ds,unsigned int dslen) +bool NetworkConfig::toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,bool includeLegacy) const { - static const std::string zero("0"); - static const std::string one("1"); - - Dictionary d(ds,dslen); + Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY> tmp; - memset(this,0,sizeof(NetworkConfig)); + d.clear(); - // NOTE: d.get(name) throws if not found, d.get(name,default) returns default + // Try to put the more human-readable fields first - networkId = Utils::hexStrToU64(d.get(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,"0").c_str()); - if (!networkId) - throw std::invalid_argument("configuration contains zero network ID"); + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) return false; - timestamp = Utils::hexStrToU64(d.get(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,"0").c_str()); - revision = Utils::hexStrToU64(d.get(ZT_NETWORKCONFIG_DICT_KEY_REVISION,"1").c_str()); // older controllers don't send this, so default to 1 - issuedTo = Address(d.get(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,"0")); +#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + if (includeLegacy) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD,this->allowPassiveBridging())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) return false; - multicastLimit = Utils::hexStrToUInt(d.get(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,zero).c_str()); - if (multicastLimit == 0) multicastLimit = ZT_MULTICAST_DEFAULT_LIMIT; + std::string v4s; + for(unsigned int i=0;i<staticIpCount;++i) { + if (this->staticIps[i].ss_family == AF_INET) { + if (v4s.length() > 0) + v4s.push_back(','); + v4s.append(this->staticIps[i].toString()); + } + } + if (v4s.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) return false; + } + std::string v6s; + for(unsigned int i=0;i<staticIpCount;++i) { + if (this->staticIps[i].ss_family == AF_INET6) { + if (v6s.length() > 0) + v6s.push_back(','); + v6s.append(this->staticIps[i].toString()); + } + } + if (v6s.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) return false; + } - flags |= ((Utils::hexStrToUInt(d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING,zero).c_str()) != 0) ? ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING : 0); - flags |= ((Utils::hexStrToUInt(d.get(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST,one).c_str()) != 0) ? ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST : 0); + std::string ets; + unsigned int et = 0; + ZT_VirtualNetworkRuleType lastrt = ZT_NETWORK_RULE_ACTION_ACCEPT; + for(unsigned int i=0;i<ruleCount;++i) { + ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f); + if (rt == ZT_NETWORK_RULE_MATCH_ETHERTYPE) { + et = rules[i].v.etherType; + } else if (rt == ZT_NETWORK_RULE_ACTION_ACCEPT) { + if (((int)lastrt < 32)||(lastrt == ZT_NETWORK_RULE_MATCH_ETHERTYPE)) { + if (ets.length() > 0) + ets.push_back(','); + char tmp[16]; + Utils::snprintf(tmp,sizeof(tmp),"%x",et); + ets.append(tmp); + } + et = 0; + } + lastrt = rt; + } + if (ets.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) return false; + } - this->type = (Utils::hexStrToUInt(d.get(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE,one).c_str()) != 0) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + if (this->com) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) return false; + } - std::string nametmp(d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME,"")); - for(unsigned long i=0;((i<ZT_MAX_NETWORK_SHORT_NAME_LENGTH)&&(i<nametmp.length()));++i) - name[i] = (char)nametmp[i]; - // we zeroed the entire structure above and _name is ZT_MAX_NETWORK_SHORT_NAME_LENGTH+1, so it will always null-terminate + std::string ab; + for(unsigned int i=0;i<this->specialistCount;++i) { + if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { + if (ab.length() > 0) + ab.push_back(','); + ab.append(Address(this->specialists[i]).toString().c_str()); + } + } + if (ab.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) return false; + } - std::vector<std::string> activeBridgesSplit(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES,"").c_str(),",","","")); - for(std::vector<std::string>::const_iterator a(activeBridgesSplit.begin());a!=activeBridgesSplit.end();++a) { - if (a->length() == ZT_ADDRESS_LENGTH_HEX) { // ignore empty or garbage fields - Address tmp(*a); - if (!tmp.isReserved()) { - uint64_t specialist = tmp.toInt(); - for(unsigned int i=0;i<specialistCount;++i) { - if ((specialists[i] & 0xffffffffffULL) == specialist) { - specialists[i] |= ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE; - specialist = 0; - break; - } - } - if ((specialist)&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) - specialists[specialistCount++] = specialist | ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE; + std::vector<Relay> rvec(this->relays()); + std::string rl; + for(std::vector<Relay>::const_iterator i(rvec.begin());i!=rvec.end();++i) { + if (rl.length() > 0) + rl.push_back(','); + rl.append(i->address.toString()); + if (i->phy4) { + rl.push_back(';'); + rl.append(i->phy4.toString()); + } else if (i->phy6) { + rl.push_back(';'); + rl.append(i->phy6.toString()); } } + if (rl.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD,rl.c_str())) return false; + } } +#endif // ZT_SUPPORT_OLD_STYLE_NETCONF - std::string ipAddrs(d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC,std::string())); - { - std::string v6s(d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC,std::string())); - if (v6s.length()) { - if (ipAddrs.length()) - ipAddrs.push_back(','); - ipAddrs.append(v6s); - } + // Then add binary blobs + + if (this->com) { + tmp.clear(); + this->com.serialize(tmp); + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,tmp)) return false; } - std::vector<std::string> ipAddrsSplit(Utils::split(ipAddrs.c_str(),",","","")); - for(std::vector<std::string>::const_iterator ipstr(ipAddrsSplit.begin());ipstr!=ipAddrsSplit.end();++ipstr) { - InetAddress addr(*ipstr); - switch(addr.ss_family) { - case AF_INET: - if ((!addr.netmaskBits())||(addr.netmaskBits() > 32)) - continue; - break; - case AF_INET6: - if ((!addr.netmaskBits())||(addr.netmaskBits() > 128)) - continue; - break; - default: // ignore unrecognized address types or junk/empty fields - continue; - } - if (!addr.isNetwork()) { - if ((staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)&&(std::find(&(staticIps[0]),&(staticIps[staticIpCount]),addr) == &(staticIps[staticIpCount]))) - staticIps[staticIpCount++] = addr; + + tmp.clear(); + for(unsigned int i=0;i<this->specialistCount;++i) { + tmp.append((uint64_t)this->specialists[i]); + } + if (tmp.size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,tmp)) return false; + } + + tmp.clear(); + for(unsigned int i=0;i<this->routeCount;++i) { + reinterpret_cast<const InetAddress *>(&(this->routes[i].target))->serialize(tmp); + reinterpret_cast<const InetAddress *>(&(this->routes[i].via))->serialize(tmp); + tmp.append((uint16_t)this->routes[i].flags); + tmp.append((uint16_t)this->routes[i].metric); + } + if (tmp.size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,tmp)) return false; + } + + tmp.clear(); + for(unsigned int i=0;i<this->staticIpCount;++i) { + this->staticIps[i].serialize(tmp); + } + if (tmp.size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,tmp)) return false; + } + + tmp.clear(); + for(unsigned int i=0;i<this->pinnedCount;++i) { + this->pinned[i].zt.appendTo(tmp); + this->pinned[i].phy.serialize(tmp); + } + if (tmp.size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PINNED,tmp)) return false; + } + + tmp.clear(); + for(unsigned int i=0;i<this->ruleCount;++i) { + tmp.append((uint8_t)rules[i].t); + switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f)) { + //case ZT_NETWORK_RULE_ACTION_DROP: + //case ZT_NETWORK_RULE_ACTION_ACCEPT: + default: + tmp.append((uint8_t)0); + break; + case ZT_NETWORK_RULE_ACTION_TEE: + case ZT_NETWORK_RULE_ACTION_REDIRECT: + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + tmp.append((uint8_t)5); + Address(rules[i].v.zt).appendTo(tmp); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + tmp.append((uint8_t)2); + tmp.append((uint16_t)rules[i].v.vlanId); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + tmp.append((uint8_t)1); + tmp.append((uint8_t)rules[i].v.vlanPcp); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + tmp.append((uint8_t)1); + tmp.append((uint8_t)rules[i].v.vlanDei); + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + tmp.append((uint8_t)2); + tmp.append((uint16_t)rules[i].v.etherType); + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + tmp.append((uint8_t)6); + tmp.append(rules[i].v.mac,6); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + tmp.append((uint8_t)5); + tmp.append(&(rules[i].v.ipv4.ip),4); + tmp.append((uint8_t)rules[i].v.ipv4.mask); + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + tmp.append((uint8_t)17); + tmp.append(rules[i].v.ipv6.ip,16); + tmp.append((uint8_t)rules[i].v.ipv6.mask); + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + tmp.append((uint8_t)1); + tmp.append((uint8_t)rules[i].v.ipTos); + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + tmp.append((uint8_t)1); + tmp.append((uint8_t)rules[i].v.ipProtocol); + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + tmp.append((uint8_t)4); + tmp.append((uint16_t)rules[i].v.port[0]); + tmp.append((uint16_t)rules[i].v.port[1]); + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + tmp.append((uint8_t)8); + tmp.append((uint64_t)rules[i].v.characteristics); + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + tmp.append((uint8_t)4); + tmp.append((uint16_t)rules[i].v.frameSize[0]); + tmp.append((uint16_t)rules[i].v.frameSize[1]); + break; + case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: + tmp.append((uint8_t)8); + tmp.append((uint32_t)rules[i].v.tcpseq[0]); + tmp.append((uint32_t)rules[i].v.tcpseq[1]); + break; + case ZT_NETWORK_RULE_MATCH_COM_FIELD_GE: + case ZT_NETWORK_RULE_MATCH_COM_FIELD_LE: + tmp.append((uint8_t)16); + tmp.append((uint64_t)rules[i].v.comIV[0]); + tmp.append((uint64_t)rules[i].v.comIV[1]); + break; } } - std::sort(&(staticIps[0]),&(staticIps[staticIpCount])); - - /* Old versions don't support gateways anyway, so ignore this in old netconfs - std::vector<std::string> gatewaysSplit(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS,"").c_str(),",","","")); - for(std::vector<std::string>::const_iterator gwstr(gatewaysSplit.begin());gwstr!=gatewaysSplit.end();++gwstr) { - InetAddress gw(*gwstr); - if ((gw)&&(_gatewayCount < ZT_MAX_NETWORK_GATEWAYS)&&(std::find(&(_gateways[0]),&(_gateways[_gatewayCount]),gw) == &(_gateways[_gatewayCount]))) - _gateways[_gatewayCount++] = gw; + if (tmp.size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,tmp)) return false; } - std::sort(&(_gateways[0]),&(_gateways[_gatewayCount])); - */ - - std::vector<std::string> relaysSplit(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_RELAYS,"").c_str(),",","","")); - for(std::vector<std::string>::const_iterator r(relaysSplit.begin());r!=relaysSplit.end();++r) { - if (r->length() >= ZT_ADDRESS_LENGTH_HEX) { - Address zt(r->substr(0,ZT_ADDRESS_LENGTH_HEX).c_str()); - InetAddress phy[2]; - unsigned int phyCount = 0; - const std::size_t semi(r->find(';')); - if ((semi > ZT_ADDRESS_LENGTH_HEX)&&(semi < (r->length() - 2))) { - std::vector<std::string> phySplit(Utils::split(r->substr(semi+1).c_str(),",","","")); - for(std::vector<std::string>::const_iterator p(phySplit.begin());((p!=phySplit.end())&&(phyCount < 2));++p) { - phy[phyCount] = InetAddress(*p); - if (phy[phyCount]) - ++phyCount; - else phy[phyCount].zero(); + + return true; +} + +bool NetworkConfig::fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d) +{ + try { + Buffer<ZT_NETWORKCONFIG_DICT_CAPACITY> tmp; + char tmp2[ZT_NETWORKCONFIG_DICT_CAPACITY]; + + memset(this,0,sizeof(NetworkConfig)); + + // Fields that are always present, new or old + this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,0); + if (!this->networkId) + return false; + this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,0); + this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION,0); + this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,0); + if (!this->issuedTo) + return false; + this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,0); + d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name,sizeof(this->name)); + + if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) { + #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + // Decode legacy fields if version is old + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD)) + this->flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) + this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; // always enable for old-style netconf + this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + InetAddress ip(f); + if (!ip.isNetwork()) + this->staticIps[this->staticIpCount++] = ip; + } + } + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + InetAddress ip(f); + if (!ip.isNetwork()) + this->staticIps[this->staticIpCount++] = ip; + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,tmp2,sizeof(tmp2)) > 0) { + this->com.fromString(tmp2); + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + unsigned int et = Utils::hexStrToUInt(f) & 0xffff; + if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) break; + if (et > 0) { + this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE; + this->rules[this->ruleCount].v.etherType = (uint16_t)et; + ++this->ruleCount; + } + this->rules[this->ruleCount++].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + } + } else { + this->rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; + this->ruleCount = 1; + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + this->addSpecialist(Address(f),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); } } - uint64_t specialist = zt.toInt(); - for(unsigned int i=0;i<specialistCount;++i) { - if ((specialists[i] & 0xffffffffffULL) == specialist) { - specialists[i] |= ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY; - specialist = 0; - break; + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + char tmp3[256]; + Utils::scopy(tmp3,sizeof(tmp3),f); + + InetAddress phy; + char *semi = tmp3; + while (*semi) { + if (*semi == ';') { + *semi = (char)0; + ++semi; + phy = InetAddress(semi); + } else ++semi; + } + Address zt(tmp3); + + this->addSpecialist(zt,ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY); + if ((phy)&&(this->pinnedCount < ZT_MAX_NETWORK_PINNED)) { + this->pinned[this->pinnedCount].zt = zt; + this->pinned[this->pinnedCount].phy = phy; + ++this->pinnedCount; + } } } + #else + return false; + #endif // ZT_SUPPORT_OLD_STYLE_NETCONF + } else { + // Otherwise we can use the new fields + this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0); + this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE); - if ((specialist)&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) - specialists[specialistCount++] = specialist | ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY; + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,tmp)) { + this->com.deserialize(tmp,0); + } - if ((phy[0])&&(pinnedCount < ZT_MAX_NETWORK_PINNED)) { - pinned[pinnedCount].zt = zt; - pinned[pinnedCount].phy = phy[0]; - ++pinnedCount; + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,tmp)) { + unsigned int p = 0; + while (((p + 8) <= tmp.size())&&(specialistCount < ZT_MAX_NETWORK_SPECIALISTS)) { + this->specialists[this->specialistCount++] = tmp.at<uint64_t>(p); + p += 8; + } } - if ((phy[1])&&(pinnedCount < ZT_MAX_NETWORK_PINNED)) { - pinned[pinnedCount].zt = zt; - pinned[pinnedCount].phy = phy[0]; - ++pinnedCount; + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,tmp)) { + unsigned int p = 0; + while ((p < tmp.size())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) { + p += reinterpret_cast<InetAddress *>(&(this->routes[this->routeCount].target))->deserialize(tmp,p); + p += reinterpret_cast<InetAddress *>(&(this->routes[this->routeCount].via))->deserialize(tmp,p); + this->routes[this->routeCount].flags = tmp.at<uint16_t>(p); p += 2; + this->routes[this->routeCount].metric = tmp.at<uint16_t>(p); p += 2; + ++this->routeCount; + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,tmp)) { + unsigned int p = 0; + while ((p < tmp.size())&&(staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + p += this->staticIps[this->staticIpCount++].deserialize(tmp,p); + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_PINNED,tmp)) { + unsigned int p = 0; + while ((p < tmp.size())&&(pinnedCount < ZT_MAX_NETWORK_PINNED)) { + this->pinned[this->pinnedCount].zt.setTo(tmp.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + p += this->pinned[this->pinnedCount].phy.deserialize(tmp,p); + ++this->pinnedCount; + } } - } - } - std::vector<std::string> ets(Utils::split(d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES,"").c_str(),",","","")); - for(std::vector<std::string>::const_iterator et(ets.begin());et!=ets.end();++et) { - unsigned int et2 = Utils::hexStrToUInt(et->c_str()) & 0xffff; - if ((ruleCount + 1) < ZT_MAX_NETWORK_RULES) { - if (et2) { - rules[ruleCount].t = ZT_NETWORK_RULE_MATCH_ETHERTYPE; - rules[ruleCount].v.etherType = (uint16_t)et2; - ++ruleCount; + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES,tmp)) { + unsigned int p = 0; + while ((p < tmp.size())&&(ruleCount < ZT_MAX_NETWORK_RULES)) { + rules[ruleCount].t = (uint8_t)tmp[p++]; + unsigned int fieldLen = (unsigned int)tmp[p++]; + switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x7f)) { + default: + break; + case ZT_NETWORK_RULE_ACTION_TEE: + case ZT_NETWORK_RULE_ACTION_REDIRECT: + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + rules[ruleCount].v.zt = Address(tmp.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + rules[ruleCount].v.vlanId = tmp.at<uint16_t>(p); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + rules[ruleCount].v.vlanPcp = (uint8_t)tmp[p]; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + rules[ruleCount].v.vlanDei = (uint8_t)tmp[p]; + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + rules[ruleCount].v.etherType = tmp.at<uint16_t>(p); + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + memcpy(rules[ruleCount].v.mac,tmp.field(p,6),6); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + memcpy(&(rules[ruleCount].v.ipv4.ip),tmp.field(p,4),4); + rules[ruleCount].v.ipv4.mask = (uint8_t)tmp[p + 4]; + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + memcpy(rules[ruleCount].v.ipv6.ip,tmp.field(p,16),16); + rules[ruleCount].v.ipv6.mask = (uint8_t)tmp[p + 16]; + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + rules[ruleCount].v.ipTos = (uint8_t)tmp[p]; + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + rules[ruleCount].v.ipProtocol = (uint8_t)tmp[p]; + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + rules[ruleCount].v.port[0] = tmp.at<uint16_t>(p); + rules[ruleCount].v.port[1] = tmp.at<uint16_t>(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + rules[ruleCount].v.characteristics = tmp.at<uint64_t>(p); + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + rules[ruleCount].v.frameSize[0] = tmp.at<uint16_t>(p); + rules[ruleCount].v.frameSize[0] = tmp.at<uint16_t>(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: + rules[ruleCount].v.tcpseq[0] = tmp.at<uint32_t>(p); + rules[ruleCount].v.tcpseq[1] = tmp.at<uint32_t>(p + 4); + break; + case ZT_NETWORK_RULE_MATCH_COM_FIELD_GE: + case ZT_NETWORK_RULE_MATCH_COM_FIELD_LE: + rules[ruleCount].v.comIV[0] = tmp.at<uint64_t>(p); + rules[ruleCount].v.comIV[1] = tmp.at<uint64_t>(p + 8); + break; + } + p += fieldLen; + ++ruleCount; + } } - rules[ruleCount++].t = ZT_NETWORK_RULE_ACTION_ACCEPT; } - } - this->com.fromString(d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP,std::string())); -} + //printf("~~~\n%s\n~~~\n",d.data()); + //dump(); + //printf("~~~\n"); -#endif // ZT_SUPPORT_OLD_STYLE_NETCONF + return true; + } catch ( ... ) { + return false; + } +} } // namespace ZeroTier diff --git a/zerotierone/node/NetworkConfig.hpp b/zerotierone/node/NetworkConfig.hpp index a7ed77b..5271c5a 100644 --- a/zerotierone/node/NetworkConfig.hpp +++ b/zerotierone/node/NetworkConfig.hpp @@ -35,21 +35,22 @@ #include "MulticastGroup.hpp" #include "Address.hpp" #include "CertificateOfMembership.hpp" - -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF #include "Dictionary.hpp" -#include <string> -#endif /** * Flag: allow passive bridging (experimental) */ -#define ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING 0x0001 +#define ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING 0x0000000000000001ULL /** * Flag: enable broadcast */ -#define ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST 0x0002 +#define ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST 0x0000000000000002ULL + +/** + * Flag: enable IPv6 NDP emulation for certain V6 address patterns + */ +#define ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION 0x0000000000000004ULL /** * Device is a network preferred relay @@ -68,18 +69,23 @@ namespace ZeroTier { -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF +// Maximum size of a network config dictionary (can be increased) +#define ZT_NETWORKCONFIG_DICT_CAPACITY 8194 + +// Network config version +#define ZT_NETWORKCONFIG_VERSION 6 // Fields for meta-data sent with network config requests +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION "v" +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION "pv" #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION "majv" #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION "minv" #define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION "revv" -// These dictionary keys are short so they don't take up much room in -// netconf response packets. +// These dictionary keys are short so they don't take up much room. -// integer(hex)[,integer(hex),...] -#define ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES "et" +// network config version +#define ZT_NETWORKCONFIG_DICT_KEY_VERSION "v" // network ID #define ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID "nwid" // integer(hex) @@ -88,34 +94,49 @@ namespace ZeroTier { #define ZT_NETWORKCONFIG_DICT_KEY_REVISION "r" // address of member #define ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO "id" +// flags(hex) +#define ZT_NETWORKCONFIG_DICT_KEY_FLAGS "f" // integer(hex) #define ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT "ml" -// 0/1 -#define ZT_NETWORKCONFIG_DICT_KEY_PRIVATE "p" +// network type (hex) +#define ZT_NETWORKCONFIG_DICT_KEY_TYPE "t" // text #define ZT_NETWORKCONFIG_DICT_KEY_NAME "n" -// text -#define ZT_NETWORKCONFIG_DICT_KEY_DESC "d" +// binary serialized certificate of membership +#define ZT_NETWORKCONFIG_DICT_KEY_COM "C" +// specialists (binary array of uint64_t) +#define ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS "S" +// routes (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_ROUTES "RT" +// static IPs (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS "I" +// pinned address physical route mappings (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_PINNED "P" +// rules (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_RULES "R" + +// Legacy fields -- these are obsoleted but are included when older clients query + +// boolean (now a flag) +#define ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD "pb" +// boolean (now a flag) +#define ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD "eb" // IP/bits[,IP/bits,...] // Note that IPs that end in all zeroes are routes with no assignment in them. -#define ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC "v4s" +#define ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD "v4s" // IP/bits[,IP/bits,...] // Note that IPs that end in all zeroes are routes with no assignment in them. -#define ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC "v6s" -// serialized CertificateOfMembership -#define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP "com" +#define ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD "v6s" // 0/1 -#define ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST "eb" -// 0/1 -#define ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING "pb" +#define ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD "p" +// integer(hex)[,integer(hex),...] +#define ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD "et" +// string-serialized CertificateOfMembership +#define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD "com" // node[,node,...] -#define ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES "ab" +#define ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD "ab" // node;IP/port[,node;IP/port] -#define ZT_NETWORKCONFIG_DICT_KEY_RELAYS "rl" -// IP/metric[,IP/metric,...] -#define ZT_NETWORKCONFIG_DICT_KEY_GATEWAYS "gw" - -#endif // ZT_SUPPORT_OLD_STYLE_NETCONF +#define ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD "rl" /** * Network configuration received from network controller nodes @@ -215,6 +236,23 @@ public: } /** + * Write this network config to a dictionary for transport + * + * @param d Dictionary + * @param includeLegacy If true, include legacy fields for old node versions + * @return True if dictionary was successfully created, false if e.g. overflow + */ + bool toDictionary(Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d,bool includeLegacy) const; + + /** + * Read this network config from a dictionary + * + * @param d Dictionary + * @return True if dictionary was valid and network config successfully initialized + */ + bool fromDictionary(const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &d); + + /** * @return True if passive bridging is allowed (experimental) */ inline bool allowPassiveBridging() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING) != 0); } @@ -225,6 +263,11 @@ public: inline bool enableBroadcast() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0); } /** + * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns + */ + inline bool ndpEmulation() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); } + + /** * @return Network type is public (no access control) */ inline bool isPublic() const throw() { return (this->type == ZT_NETWORK_TYPE_PUBLIC); } @@ -322,9 +365,10 @@ public: { while (ptr < specialistCount) { if ((specialists[ptr] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_NETWORK_PREFERRED_RELAY) != 0) { - return Address(specialists[ptr]); + return Address(specialists[ptr++]); + } else { + ++ptr; } - ++ptr; } return Address(); } @@ -350,265 +394,6 @@ public: inline bool operator==(const NetworkConfig &nc) const { return (memcmp(this,&nc,sizeof(NetworkConfig)) == 0); } inline bool operator!=(const NetworkConfig &nc) const { return (!(*this == nc)); } - template<unsigned int C> - inline void serialize(Buffer<C> &b) const - { - b.append((uint16_t)1); // version - - b.append((uint64_t)networkId); - b.append((uint64_t)timestamp); - b.append((uint64_t)revision); - issuedTo.appendTo(b); - b.append((uint32_t)multicastLimit); - b.append((uint32_t)flags); - b.append((uint8_t)type); - - unsigned int nl = (unsigned int)strlen(name); - if (nl > 255) nl = 255; // sanity check - b.append((uint8_t)nl); - b.append((const void *)name,nl); - - b.append((uint16_t)specialistCount); - for(unsigned int i=0;i<specialistCount;++i) - b.append((uint64_t)specialists[i]); - - b.append((uint16_t)routeCount); - for(unsigned int i=0;i<routeCount;++i) { - reinterpret_cast<const InetAddress *>(&(routes[i].target))->serialize(b); - reinterpret_cast<const InetAddress *>(&(routes[i].via))->serialize(b); - } - - b.append((uint16_t)staticIpCount); - for(unsigned int i=0;i<staticIpCount;++i) - staticIps[i].serialize(b); - - b.append((uint16_t)pinnedCount); - for(unsigned int i=0;i<pinnedCount;++i) { - pinned[i].zt.appendTo(b); - pinned[i].phy.serialize(b); - } - - b.append((uint16_t)ruleCount); - for(unsigned int i=0;i<ruleCount;++i) { - b.append((uint8_t)rules[i].t); - switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f)) { - //case ZT_NETWORK_RULE_ACTION_DROP: - //case ZT_NETWORK_RULE_ACTION_ACCEPT: - default: - b.append((uint8_t)0); - break; - case ZT_NETWORK_RULE_ACTION_TEE: - case ZT_NETWORK_RULE_ACTION_REDIRECT: - case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: - case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: - b.append((uint8_t)5); - Address(rules[i].v.zt).appendTo(b); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_ID: - b.append((uint8_t)2); - b.append((uint16_t)rules[i].v.vlanId); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_PCP: - b.append((uint8_t)1); - b.append((uint8_t)rules[i].v.vlanPcp); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_DEI: - b.append((uint8_t)1); - b.append((uint8_t)rules[i].v.vlanDei); - break; - case ZT_NETWORK_RULE_MATCH_ETHERTYPE: - b.append((uint8_t)2); - b.append((uint16_t)rules[i].v.etherType); - break; - case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: - case ZT_NETWORK_RULE_MATCH_MAC_DEST: - b.append((uint8_t)6); - b.append(rules[i].v.mac,6); - break; - case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV4_DEST: - b.append((uint8_t)5); - b.append(&(rules[i].v.ipv4.ip),4); - b.append((uint8_t)rules[i].v.ipv4.mask); - break; - case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV6_DEST: - b.append((uint8_t)17); - b.append(rules[i].v.ipv6.ip,16); - b.append((uint8_t)rules[i].v.ipv6.mask); - break; - case ZT_NETWORK_RULE_MATCH_IP_TOS: - b.append((uint8_t)1); - b.append((uint8_t)rules[i].v.ipTos); - break; - case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: - b.append((uint8_t)1); - b.append((uint8_t)rules[i].v.ipProtocol); - break; - case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: - case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: - b.append((uint8_t)4); - b.append((uint16_t)rules[i].v.port[0]); - b.append((uint16_t)rules[i].v.port[1]); - break; - case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: - b.append((uint8_t)8); - b.append((uint64_t)rules[i].v.characteristics); - break; - case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: - b.append((uint8_t)4); - b.append((uint16_t)rules[i].v.frameSize[0]); - b.append((uint16_t)rules[i].v.frameSize[1]); - break; - case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: - b.append((uint8_t)8); - b.append((uint32_t)rules[i].v.tcpseq[0]); - b.append((uint32_t)rules[i].v.tcpseq[1]); - break; - } - } - - this->com.serialize(b); - - b.append((uint16_t)0); // extended bytes, currently 0 since unused - } - - template<unsigned int C> - inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) - { - memset(this,0,sizeof(NetworkConfig)); - - unsigned int p = startAt; - - if (b.template at<uint16_t>(p) != 1) - throw std::invalid_argument("unrecognized version"); - p += 2; - - networkId = b.template at<uint64_t>(p); p += 8; - timestamp = b.template at<uint64_t>(p); p += 8; - revision = b.template at<uint64_t>(p); p += 8; - issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - multicastLimit = (unsigned int)b.template at<uint32_t>(p); p += 4; - flags = (unsigned int)b.template at<uint32_t>(p); p += 4; - type = (ZT_VirtualNetworkType)b[p++]; - - unsigned int nl = (unsigned int)b[p++]; - memcpy(this->name,b.field(p,nl),std::min(nl,(unsigned int)ZT_MAX_NETWORK_SHORT_NAME_LENGTH)); - p += nl; - // _name will always be null terminated since field size is ZT_MAX_NETWORK_SHORT_NAME_LENGTH + 1 - - specialistCount = (unsigned int)b.template at<uint16_t>(p); p += 2; - if (specialistCount > ZT_MAX_NETWORK_SPECIALISTS) - throw std::invalid_argument("overflow (specialists)"); - for(unsigned int i=0;i<specialistCount;++i) { - specialists[i] = b.template at<uint64_t>(p); p += 8; - } - - routeCount = (unsigned int)b.template at<uint16_t>(p); p += 2; - if (routeCount > ZT_MAX_NETWORK_ROUTES) - throw std::invalid_argument("overflow (routes)"); - for(unsigned int i=0;i<routeCount;++i) { - p += reinterpret_cast<InetAddress *>(&(routes[i].target))->deserialize(b,p); - p += reinterpret_cast<InetAddress *>(&(routes[i].via))->deserialize(b,p); - } - - staticIpCount = (unsigned int)b.template at<uint16_t>(p); p += 2; - if (staticIpCount > ZT_MAX_ZT_ASSIGNED_ADDRESSES) - throw std::invalid_argument("overflow (static IPs)"); - for(unsigned int i=0;i<staticIpCount;++i) { - p += staticIps[i].deserialize(b,p); - } - - pinnedCount = (unsigned int)b.template at<uint16_t>(p); p += 2; - if (pinnedCount > ZT_MAX_NETWORK_PINNED) - throw std::invalid_argument("overflow (static addresses)"); - for(unsigned int i=0;i<pinnedCount;++i) { - pinned[i].zt.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; - p += pinned[i].phy.deserialize(b,p); - } - - ruleCount = (unsigned int)b.template at<uint16_t>(p); p += 2; - if (ruleCount > ZT_MAX_NETWORK_RULES) - throw std::invalid_argument("overflow (rules)"); - for(unsigned int i=0;i<ruleCount;++i) { - rules[i].t = (uint8_t)b[p++]; - unsigned int rlen = (unsigned int)b[p++]; - switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x7f)) { - //case ZT_NETWORK_RULE_ACTION_DROP: - //case ZT_NETWORK_RULE_ACTION_ACCEPT: - default: - break; - case ZT_NETWORK_RULE_ACTION_TEE: - case ZT_NETWORK_RULE_ACTION_REDIRECT: - case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: - case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: { - Address tmp; - tmp.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); - rules[i].v.zt = tmp.toInt(); - } break; - case ZT_NETWORK_RULE_MATCH_VLAN_ID: - rules[i].v.vlanId = b.template at<uint16_t>(p); - break; - case ZT_NETWORK_RULE_MATCH_VLAN_PCP: - rules[i].v.vlanPcp = (uint8_t)b[p]; - break; - case ZT_NETWORK_RULE_MATCH_VLAN_DEI: - rules[i].v.vlanDei = (uint8_t)b[p]; - break; - case ZT_NETWORK_RULE_MATCH_ETHERTYPE: - rules[i].v.etherType = b.template at<uint16_t>(p); - break; - case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: - case ZT_NETWORK_RULE_MATCH_MAC_DEST: - memcpy(rules[i].v.mac,b.field(p,6),6); - break; - case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV4_DEST: - memcpy(&(rules[i].v.ipv4.ip),b.field(p,4),4); - rules[i].v.ipv4.mask = (uint8_t)b[p+4]; - break; - case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: - case ZT_NETWORK_RULE_MATCH_IPV6_DEST: - memcpy(rules[i].v.ipv6.ip,b.field(p,16),16); - rules[i].v.ipv6.mask = (uint8_t)b[p+16]; - break; - case ZT_NETWORK_RULE_MATCH_IP_TOS: - rules[i].v.ipTos = (uint8_t)b[p]; - break; - case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: - rules[i].v.ipProtocol = (uint8_t)b[p]; - break; - case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: - case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: - rules[i].v.port[0] = b.template at<uint16_t>(p); - rules[i].v.port[1] = b.template at<uint16_t>(p+2); - break; - case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: - rules[i].v.characteristics = b.template at<uint64_t>(p); - break; - case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: - rules[i].v.frameSize[0] = b.template at<uint16_t>(p); - rules[i].v.frameSize[1] = b.template at<uint16_t>(p+2); - break; - case ZT_NETWORK_RULE_MATCH_TCP_RELATIVE_SEQUENCE_NUMBER_RANGE: - rules[i].v.tcpseq[0] = b.template at<uint32_t>(p); - rules[i].v.tcpseq[1] = b.template at<uint32_t>(p + 4); - break; - } - p += rlen; - } - - p += this->com.deserialize(b,p); - - p += b.template at<uint16_t>(p) + 2; - - return (p - startAt); - } - -#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF - void fromDictionary(const char *ds,unsigned int dslen); -#endif - /* inline void dump() const { @@ -623,16 +408,18 @@ public: printf(" specialists[%u]==%.16llx\n",i,specialists[i]); printf("routeCount==%u\n",routeCount); for(unsigned int i=0;i<routeCount;++i) { - printf(" routes[i].target==%s\n",reinterpret_cast<const struct sockaddr_storage *>(&(routes[i].target))->toString().c_str()); - printf(" routes[i].via==%s\n",reinterpret_cast<const struct sockaddr_storage *>(&(routes[i].via))->toString().c_str()); + printf(" routes[i].target==%s\n",reinterpret_cast<const InetAddress *>(&(routes[i].target))->toString().c_str()); + printf(" routes[i].via==%s\n",reinterpret_cast<const InetAddress *>(&(routes[i].via))->toIpString().c_str()); + printf(" routes[i].flags==%.4x\n",(unsigned int)routes[i].flags); + printf(" routes[i].metric==%u\n",(unsigned int)routes[i].metric); } printf("staticIpCount==%u\n",staticIpCount); for(unsigned int i=0;i<staticIpCount;++i) printf(" staticIps[i]==%s\n",staticIps[i].toString().c_str()); printf("pinnedCount==%u\n",pinnedCount); for(unsigned int i=0;i<pinnedCount;++i) { - printf(" pinned[i].zt==%s\n",pinned[i].zt->toString().c_str()); - printf(" pinned[i].phy==%s\n",pinned[i].zt->toString().c_str()); + printf(" pinned[i].zt==%s\n",pinned[i].zt.toString().c_str()); + printf(" pinned[i].phy==%s\n",pinned[i].phy.toString().c_str()); } printf("ruleCount==%u\n",ruleCount); printf("name==%s\n",name); @@ -641,6 +428,32 @@ public: */ /** + * Add a specialist or mask flags if already present + * + * This masks the existing flags if the specialist is already here or adds + * it otherwise. + * + * @param a Address of specialist + * @param f Flags (OR of specialist role/type flags) + * @return True if successfully masked or added + */ + inline bool addSpecialist(const Address &a,const uint64_t f) + { + const uint64_t aint = a.toInt(); + for(unsigned int i=0;i<specialistCount;++i) { + if ((specialists[i] & 0xffffffffffULL) == aint) { + specialists[i] |= f; + return true; + } + } + if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) { + specialists[specialistCount++] = f | aint; + return true; + } + return false; + } + + /** * Network ID that this configuration applies to */ uint64_t networkId; @@ -661,14 +474,14 @@ public: Address issuedTo; /** - * Maximum number of recipients per multicast (not including active bridges) + * Flags (64-bit) */ - unsigned int multicastLimit; + uint64_t flags; /** - * Flags (32-bit) + * Maximum number of recipients per multicast (not including active bridges) */ - unsigned int flags; + unsigned int multicastLimit; /** * Number of specialists diff --git a/zerotierone/node/NetworkConfigRequestMetaData.hpp b/zerotierone/node/NetworkConfigRequestMetaData.hpp deleted file mode 100644 index 2516e5e..0000000 --- a/zerotierone/node/NetworkConfigRequestMetaData.hpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ - * - * 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/>. - */ - -#ifndef ZT_NETWORKCONFIGREQUESTMETADATA_HPP -#define ZT_NETWORKCONFIGREQUESTMETADATA_HPP - -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - -#include "Constants.hpp" -#include "NetworkConfig.hpp" -#include "Buffer.hpp" -#include "Packet.hpp" - -#include "../version.h" - -/** - * Maximum length of the auth field (including terminating NULL, since it's a C-style string) - * - * Actual max length not including NULL is this minus one. - */ -#define ZT_NETWORK_CONFIG_REQUEST_METADATA_MAX_AUTH_LENGTH 2048 - -namespace ZeroTier { - -/** - * Network configuration request meta data - */ -class NetworkConfigRequestMetaData -{ -public: - /** - * Construct an empty meta-data object with zero/null values - */ - NetworkConfigRequestMetaData() - { - memset(this,0,sizeof(NetworkConfigRequestMetaData)); - } - - /** - * Initialize with defaults from this node's config and version - */ - inline void initWithDefaults() - { - memset(this,0,sizeof(NetworkConfigRequestMetaData)); - vendor = ZT_VENDOR_ZEROTIER; - platform = ZT_PLATFORM_UNSPECIFIED; - architecture = ZT_ARCHITECTURE_UNSPECIFIED; - majorVersion = ZEROTIER_ONE_VERSION_MAJOR; - minorVersion = ZEROTIER_ONE_VERSION_MINOR; - revision = ZEROTIER_ONE_VERSION_REVISION; - protocolVersion = ZT_PROTO_VERSION; - } - - /** - * Zero/null everything - */ - inline void clear() - { - memset(this,0,sizeof(NetworkConfigRequestMetaData)); - } - - template<unsigned int C> - inline void serialize(Buffer<C> &b) const - { - /* Unlike network config we always send the old fields. Newer network - * controllers will detect the presence of the new serialized data by - * detecting extra data after the terminating NULL. But always sending - * these maintains backward compatibility with old controllers. This - * appends a terminating NULL which seperates the old legacy meta-data - * from the new packed binary format that we send after. */ - b.appendCString("majv=" ZEROTIER_ONE_VERSION_MAJOR_S_HEX "\nminv=" ZEROTIER_ONE_VERSION_MINOR_S_HEX "\nrevv=" ZEROTIER_ONE_VERSION_REVISION_S_HEX "\n"); - - b.append((uint16_t)1); // serialization version - - b.append((uint64_t)buildId); - b.append((uint64_t)flags); - b.append((uint16_t)vendor); - b.append((uint16_t)platform); - b.append((uint16_t)architecture); - b.append((uint16_t)majorVersion); - b.append((uint16_t)minorVersion); - b.append((uint16_t)revision); - b.append((uint16_t)protocolVersion); - - const unsigned int tl = strlen(auth); - b.append((uint16_t)tl); - b.append((const void *)auth,tl); - - b.append((uint16_t)0); // extended bytes, currently 0 since unused - } - - template<unsigned int C> - inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) - { - memset(this,0,sizeof(NetworkConfigRequestMetaData)); - - unsigned int p = startAt; - - // Seek past old style meta-data - while (b[p]) ++p; - ++p; - - if (b.template at<uint16_t>(p) != 1) - throw std::invalid_argument("unrecognized version"); - p += 2; - - buildId = b.template at<uint64_t>(p); p += 8; - flags = b.template at<uint64_t>(p); p += 8; - vendor = (ZT_Vendor)b.template at<uint16_t>(p); p += 2; - platform = (ZT_Platform)b.template at<uint16_t>(p); p += 2; - architecture = (ZT_Architecture)b.template at<uint16_t>(p); p += 2; - majorVersion = b.template at<uint16_t>(p); p += 2; - minorVersion = b.template at<uint16_t>(p); p += 2; - revision = b.template at<uint16_t>(p); p += 2; - protocolVersion = b.template at<uint16_t>(p); p += 2; - - const unsigned int tl = b.template at<uint16_t>(p); p += 2; - memcpy(auth,b.field(p,tl),std::max(tl,(unsigned int)(ZT_NETWORK_CONFIG_REQUEST_METADATA_MAX_AUTH_LENGTH - 1))); - p += tl; - - p += b.template at<uint16_t>(p) + 2; - - return (p - startAt); - } - - /** - * Authentication data (e.g. bearer=<token>) as a C-style string (always null terminated) - */ - char auth[ZT_NETWORK_CONFIG_REQUEST_METADATA_MAX_AUTH_LENGTH]; - - /** - * Build ID (currently unused, must be 0) - */ - uint64_t buildId; - - /** - * Flags (currently unused, must be 0) - */ - uint64_t flags; - - /** - * ZeroTier vendor or 0 for unspecified - */ - ZT_Vendor vendor; - - /** - * ZeroTier platform or 0 for unspecified - */ - ZT_Platform platform; - - /** - * ZeroTier architecture or 0 for unspecified - */ - ZT_Architecture architecture; - - /** - * ZeroTier software major version - */ - unsigned int majorVersion; - - /** - * ZeroTier software minor version - */ - unsigned int minorVersion; - - /** - * ZeroTier software revision - */ - unsigned int revision; - - /** - * ZeroTier protocol version - */ - unsigned int protocolVersion; -}; - -} // namespace ZeroTier - -#endif diff --git a/zerotierone/node/NetworkController.hpp b/zerotierone/node/NetworkController.hpp index 4ab6403..fa90fb7 100644 --- a/zerotierone/node/NetworkController.hpp +++ b/zerotierone/node/NetworkController.hpp @@ -22,15 +22,15 @@ #include <stdint.h> #include "Constants.hpp" -#include "InetAddress.hpp" -#include "Address.hpp" -#include "Identity.hpp" -#include "NetworkConfigRequestMetaData.hpp" -#include "Buffer.hpp" +#include "Dictionary.hpp" +#include "NetworkConfig.hpp" namespace ZeroTier { class RuntimeEnvironment; +class Identity; +class Address; +struct InetAddress; /** * Interface for network controller implementations @@ -67,16 +67,16 @@ public: * @param identity Originating peer ZeroTier identity * @param nwid 64-bit network ID * @param metaData Meta-data bundled with request (if any) - * @param result Buffer to receive serialized network configuration data (any existing data in buffer is preserved) - * @return Returns NETCONF_QUERY_OK if result dictionary is valid, or an error code on error + * @param nc NetworkConfig to fill with results + * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error */ virtual NetworkController::ResultCode doNetworkConfigRequest( const InetAddress &fromAddr, const Identity &signingId, const Identity &identity, uint64_t nwid, - const NetworkConfigRequestMetaData &metaData, - Buffer<8194> &result) = 0; + const Dictionary<ZT_NETWORKCONFIG_DICT_CAPACITY> &metaData, + NetworkConfig &nc) = 0; }; } // namespace ZeroTier diff --git a/zerotierone/node/Node.cpp b/zerotierone/node/Node.cpp index bedbba9..1308502 100644 --- a/zerotierone/node/Node.cpp +++ b/zerotierone/node/Node.cpp @@ -447,6 +447,7 @@ ZT_PeerList *Node::peers() const p->paths[p->pathCount].lastReceive = path->lastReceived(); p->paths[p->pathCount].active = path->active(_now) ? 1 : 0; p->paths[p->pathCount].preferred = ((bestPath)&&(*path == *bestPath)) ? 1 : 0; + p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust(path->address()); ++p->pathCount; } } @@ -745,6 +746,11 @@ void Node::postCircuitTestReport(const ZT_CircuitTestReport *report) (reinterpret_cast<void (*)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)>((*i)->_internalPtr))(reinterpret_cast<ZT_Node *>(this),*i,report); } +void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count) +{ + RR->topology->setTrustedPaths(reinterpret_cast<const InetAddress *>(networks),ids,count); +} + } // namespace ZeroTier /****************************************************************************/ @@ -1014,6 +1020,13 @@ void ZT_Node_clusterStatus(ZT_Node *node,ZT_ClusterStatus *cs) } catch ( ... ) {} } +void ZT_Node_setTrustedPaths(ZT_Node *node,const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count) +{ + try { + reinterpret_cast<ZeroTier::Node *>(node)->setTrustedPaths(networks,ids,count); + } catch ( ... ) {} +} + void ZT_Node_backgroundThreadMain(ZT_Node *node) { try { diff --git a/zerotierone/node/Node.hpp b/zerotierone/node/Node.hpp index 6ac23ca..0a39d1e 100644 --- a/zerotierone/node/Node.hpp +++ b/zerotierone/node/Node.hpp @@ -248,26 +248,15 @@ public: */ inline int configureVirtualNetworkPort(uint64_t nwid,void **nuptr,ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nc) { return _virtualNetworkConfigFunction(reinterpret_cast<ZT_Node *>(this),_uPtr,nwid,nuptr,op,nc); } - /** - * @return True if we appear to be online - */ inline bool online() const throw() { return _online; } #ifdef ZT_TRACE void postTrace(const char *module,unsigned int line,const char *fmt,...); #endif - /** - * @return Next 64-bit random number (not for cryptographic use) - */ uint64_t prng(); - - /** - * Post a circuit test report to any listeners for a given test ID - * - * @param report Report (includes test ID) - */ void postCircuitTestReport(const ZT_CircuitTestReport *report); + void setTrustedPaths(const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count); private: inline SharedPtr<Network> _network(uint64_t nwid) const diff --git a/zerotierone/node/Packet.hpp b/zerotierone/node/Packet.hpp index b367ec6..3d95b0b 100644 --- a/zerotierone/node/Packet.hpp +++ b/zerotierone/node/Packet.hpp @@ -34,7 +34,11 @@ #include "Utils.hpp" #include "Buffer.hpp" +#ifdef ZT_USE_SYSTEM_LZ4 +#include <lz4.h> +#else #include "../ext/lz4/lz4.h" +#endif /** * Protocol version -- incremented only for major changes @@ -48,13 +52,18 @@ * + New crypto completely changes key agreement cipher * 4 - 0.6.0 ... 1.0.6 * + New identity format based on hashcash design - * 5 - 1.1.0 ... CURRENT + * 5 - 1.1.0 ... 1.1.5 * + Supports circuit test, proof of work, and echo * + Supports in-band world (root server definition) updates * + Clustering! (Though this will work with protocol v4 clients.) * + Otherwise backward compatible with protocol v4 + * 6 - 1.1.5 ... 1.1.10 + * + Deprecate old dictionary-based network config format + * + Introduce new binary serialized network config and meta-data + * 7 - 1.1.10 -- CURRENT + * + Introduce trusted paths for local SDN use */ -#define ZT_PROTO_VERSION 5 +#define ZT_PROTO_VERSION 7 /** * Minimum supported protocol version @@ -93,10 +102,21 @@ #define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 1 /** - * DEPRECATED payload encrypted flag, will be removed for re-use soon. + * Cipher suite: NONE + * + * This differs from POLY1305/NONE in that *no* crypto is done, not even + * authentication. This is for trusted local LAN interconnects for internal + * SDN use within a data center. * - * This has been replaced by the two-bit cipher suite selection field where - * a value of 0 indicates unencrypted (but authenticated) messages. + * For this mode the MAC field becomes a trusted path ID and must match the + * configured ID of a trusted path or the packet is discarded. + */ +#define ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH 2 + +/** + * DEPRECATED payload encrypted flag, may be re-used in the future. + * + * This has been replaced by the three-bit cipher suite selection field. */ #define ZT_PROTO_FLAG_ENCRYPTED 0x80 @@ -330,7 +350,7 @@ namespace ZeroTier { * <[5] destination ZT address> * <[5] source ZT address> * <[1] flags/cipher/hops> - * <[8] 64-bit MAC> + * <[8] 64-bit MAC (or trusted path ID in trusted path mode)> * [... -- begin encryption envelope -- ...] * <[1] encrypted flags (MS 3 bits) and verb (LS 5 bits)> * [... verb-specific payload ...] @@ -1211,7 +1231,6 @@ public: */ inline unsigned int cipher() const { - // Note: this uses the new cipher spec field, which is incompatible with <1.0.0 peers return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3); } @@ -1222,13 +1241,31 @@ public: { unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH - // DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers + // Set DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) b |= ZT_PROTO_FLAG_ENCRYPTED; else b &= (~ZT_PROTO_FLAG_ENCRYPTED); } /** + * Get the trusted path ID for this packet (only meaningful if cipher is trusted path) + * + * @return Trusted path ID (from MAC field) + */ + inline uint64_t trustedPathId() const { return at<uint64_t>(ZT_PACKET_IDX_MAC); } + + /** + * Set this packet's trusted path ID and set the cipher spec to trusted path + * + * @param tpid Trusted path ID + */ + inline void setTrusted(const uint64_t tpid) + { + setCipher(ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH); + setAt(ZT_PACKET_IDX_MAC,tpid); + } + + /** * Get this packet's unique ID (the IV field interpreted as uint64_t) * * @return Packet ID @@ -1271,6 +1308,10 @@ public: /** * Verify and (if encrypted) decrypt packet * + * This does not handle trusted path mode packets and will return false + * for these. These are handled in IncomingPacket if the sending physical + * address and MAC field match a trusted path. + * * @param key 32-byte key * @return False if packet is invalid or failed MAC authenticity check */ diff --git a/zerotierone/node/Path.hpp b/zerotierone/node/Path.hpp index c88c295..ecf4be2 100644 --- a/zerotierone/node/Path.hpp +++ b/zerotierone/node/Path.hpp @@ -252,7 +252,7 @@ public: */ inline bool reliable() const throw() { - if (_addr.ss_family == AF_INET) + if ((_addr.ss_family == AF_INET)||(_addr.ss_family == AF_INET6)) return ((_ipScope != InetAddress::IP_SCOPE_GLOBAL)&&(_ipScope != InetAddress::IP_SCOPE_PSEUDOPRIVATE)); return true; } diff --git a/zerotierone/node/Switch.cpp b/zerotierone/node/Switch.cpp index e7cda1b..bf3afe3 100644 --- a/zerotierone/node/Switch.cpp +++ b/zerotierone/node/Switch.cpp @@ -351,68 +351,87 @@ void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,c return; } } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(len >= (40 + 8 + 16))) { - /* IPv6 NDP emulation on ZeroTier-RFC4193 addressed networks! This allows - * for multicast-free operation in IPv6 networks, which both improves - * performance and is friendlier to mobile and (especially) IoT devices. - * In the future there may be a no-multicast build option for embedded - * and IoT use and this will be the preferred addressing mode. Note that - * it plays nice with our L2 emulation philosophy and even with bridging. - * While "real" devices behind the bridge can't have ZT-RFC4193 addresses - * themselves, they can look these addresses up with NDP and it will - * work just fine. */ - if ((reinterpret_cast<const uint8_t *>(data)[6] == 0x3a)&&(reinterpret_cast<const uint8_t *>(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + // IPv6 NDP emulation for certain very special patterns of private IPv6 addresses -- if enabled + if ((network->config().ndpEmulation())&&(reinterpret_cast<const uint8_t *>(data)[6] == 0x3a)&&(reinterpret_cast<const uint8_t *>(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + Address v6EmbeddedAddress; + const uint8_t *const pkt6 = reinterpret_cast<const uint8_t *>(data) + 40 + 8; + const uint8_t *my6 = (const uint8_t *)0; + + // ZT-RFC4193 address: fdNN:NNNN:NNNN:NNNN:NN99:93DD:DDDD:DDDD / 88 (one /128 per actual host) + + // ZT-6PLANE address: fcXX:XXXX:XXDD:DDDD:DDDD:####:####:#### / 40 (one /80 per actual host) + // (XX - lower 32 bits of network ID XORed with higher 32 bits) + + // For these to work, we must have a ZT-managed address assigned in one of the + // above formats, and the query must match its prefix. for(unsigned int sipk=0;sipk<network->config().staticIpCount;++sipk) { - const InetAddress *sip = &(network->config().staticIps[sipk]); - if ((sip->ss_family == AF_INET6)&&(Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_port) == 88)) { - const uint8_t *my6 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_addr.s6_addr); - if ((my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 == fd__:____:____:____:__99:93__:____:____ / 88 - const uint8_t *pkt6 = reinterpret_cast<const uint8_t *>(data) + 40 + 8; + const InetAddress *const sip = &(network->config().staticIps[sipk]); + if (sip->ss_family == AF_INET6) { + my6 = reinterpret_cast<const uint8_t *>(reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_addr.s6_addr); + const unsigned int sipNetmaskBits = Utils::ntoh((uint16_t)reinterpret_cast<const struct sockaddr_in6 *>(&(*sip))->sin6_port); + if ((sipNetmaskBits == 88)&&(my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 /88 ??? unsigned int ptr = 0; while (ptr != 11) { if (pkt6[ptr] != my6[ptr]) break; ++ptr; } - if (ptr == 11) { // /88 matches an assigned address on this network - const Address atPeer(pkt6 + ptr,5); - if (atPeer != RR->identity.address()) { - const MAC atPeerMac(atPeer,network->id()); - TRACE("ZT-RFC4193 NDP emulation: %.16llx: forging response for %s/%s",network->id(),atPeer.toString().c_str(),atPeerMac.toString().c_str()); - - uint8_t adv[72]; - adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; - adv[4] = 0x00; adv[5] = 0x20; - adv[6] = 0x3a; adv[7] = 0xff; - for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; - for(int i=0;i<16;++i) adv[24 + i] = my6[i]; - adv[40] = 0x88; adv[41] = 0x00; - adv[42] = 0x00; adv[43] = 0x00; // future home of checksum - adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; - for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; - adv[64] = 0x02; adv[65] = 0x01; - adv[66] = atPeerMac[0]; adv[67] = atPeerMac[1]; adv[68] = atPeerMac[2]; adv[69] = atPeerMac[3]; adv[70] = atPeerMac[4]; adv[71] = atPeerMac[5]; - - uint16_t pseudo_[36]; - uint8_t *const pseudo = reinterpret_cast<uint8_t *>(pseudo_); - for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; - pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; - pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; - for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; - uint32_t checksum = 0; - for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); - while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); - checksum = ~checksum; - adv[42] = (checksum >> 8) & 0xff; - adv[43] = checksum & 0xff; - - RR->node->putFrame(network->id(),network->userPtr(),atPeerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72); - return; // stop processing: we have handled this frame with a spoofed local reply so no need to send it anywhere + if (ptr == 11) { // prefix match! + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; + } + } else if (sipNetmaskBits == 40) { // ZT-6PLANE /40 ??? + const uint32_t nwid32 = (uint32_t)((network->id() ^ (network->id() >> 32)) & 0xffffffff); + if ( (my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) { + unsigned int ptr = 0; + while (ptr != 5) { + if (pkt6[ptr] != my6[ptr]) + break; + ++ptr; + } + if (ptr == 5) { // prefix match! + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; } } } } } - } + + if ((v6EmbeddedAddress)&&(v6EmbeddedAddress != RR->identity.address())) { + const MAC peerMac(v6EmbeddedAddress,network->id()); + TRACE("IPv6 NDP emulation: %.16llx: forging response for %s/%s",network->id(),v6EmbeddedAddress.toString().c_str(),peerMac.toString().c_str()); + + uint8_t adv[72]; + adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; + adv[4] = 0x00; adv[5] = 0x20; + adv[6] = 0x3a; adv[7] = 0xff; + for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; + for(int i=0;i<16;++i) adv[24 + i] = my6[i]; + adv[40] = 0x88; adv[41] = 0x00; + adv[42] = 0x00; adv[43] = 0x00; // future home of checksum + adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; + for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; + adv[64] = 0x02; adv[65] = 0x01; + adv[66] = peerMac[0]; adv[67] = peerMac[1]; adv[68] = peerMac[2]; adv[69] = peerMac[3]; adv[70] = peerMac[4]; adv[71] = peerMac[5]; + + uint16_t pseudo_[36]; + uint8_t *const pseudo = reinterpret_cast<uint8_t *>(pseudo_); + for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; + pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; + pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; + for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; + uint32_t checksum = 0; + for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); + while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); + checksum = ~checksum; + adv[42] = (checksum >> 8) & 0xff; + adv[43] = checksum & 0xff; + + RR->node->putFrame(network->id(),network->userPtr(),peerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72); + return; // NDP emulation done. We have forged a "fake" reply, so no need to send actual NDP query. + } // else no NDP emulation + } // else no NDP emulation } /* Learn multicast groups for bridged-in hosts. @@ -830,7 +849,12 @@ bool Switch::_trySend(const Packet &packet,bool encrypt,uint64_t nwid) unsigned int chunkSize = std::min(tmp.size(),(unsigned int)ZT_UDP_DEFAULT_PAYLOAD_MTU); tmp.setFragmented(chunkSize < tmp.size()); - tmp.armor(peer->key(),encrypt); + const uint64_t trustedPathId = RR->topology->getOutboundPathTrust(viaPath->address()); + if (trustedPathId) { + tmp.setTrusted(trustedPathId); + } else { + tmp.armor(peer->key(),encrypt); + } if (viaPath->send(RR,tmp.data(),chunkSize,now)) { if (chunkSize < tmp.size()) { diff --git a/zerotierone/node/Topology.cpp b/zerotierone/node/Topology.cpp index 4105eae..6e96f2e 100644 --- a/zerotierone/node/Topology.cpp +++ b/zerotierone/node/Topology.cpp @@ -44,6 +44,7 @@ static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01,0x0 Topology::Topology(const RuntimeEnvironment *renv) : RR(renv), + _trustedPathCount(0), _amRoot(false) { std::string alls(RR->node->dataStoreGet("peers.save")); diff --git a/zerotierone/node/Topology.hpp b/zerotierone/node/Topology.hpp index 86fbb01..03c491e 100644 --- a/zerotierone/node/Topology.hpp +++ b/zerotierone/node/Topology.hpp @@ -28,6 +28,7 @@ #include <utility> #include "Constants.hpp" +#include "../include/ZeroTierOne.h" #include "Address.hpp" #include "Identity.hpp" @@ -252,12 +253,64 @@ public: */ inline bool amRoot() const throw() { return _amRoot; } + /** + * Get the outbound trusted path ID for a physical address, or 0 if none + * + * @param physicalAddress Physical address to which we are sending the packet + * @return Trusted path ID or 0 if none (0 is not a valid trusted path ID) + */ + inline uint64_t getOutboundPathTrust(const InetAddress &physicalAddress) + { + for(unsigned int i=0;i<_trustedPathCount;++i) { + if (_trustedPathNetworks[i].containsAddress(physicalAddress)) + return _trustedPathIds[i]; + } + return 0; + } + + /** + * Check whether in incoming trusted path marked packet is valid + * + * @param physicalAddress Originating physical address + * @param trustedPathId Trusted path ID from packet (from MAC field) + */ + inline bool shouldInboundPathBeTrusted(const InetAddress &physicalAddress,const uint64_t trustedPathId) + { + for(unsigned int i=0;i<_trustedPathCount;++i) { + if ((_trustedPathIds[i] == trustedPathId)&&(_trustedPathNetworks[i].containsAddress(physicalAddress))) + return true; + } + return false; + } + + /** + * Set trusted paths in this topology + * + * @param networks Array of networks (prefix/netmask bits) + * @param ids Array of trusted path IDs + * @param count Number of trusted paths (if larger than ZT_MAX_TRUSTED_PATHS overflow is ignored) + */ + inline void setTrustedPaths(const InetAddress *networks,const uint64_t *ids,unsigned int count) + { + if (count > ZT_MAX_TRUSTED_PATHS) + count = ZT_MAX_TRUSTED_PATHS; + Mutex::Lock _l(_lock); + for(unsigned int i=0;i<count;++i) { + _trustedPathIds[i] = ids[i]; + _trustedPathNetworks[i] = networks[i]; + } + _trustedPathCount = count; + } + private: Identity _getIdentity(const Address &zta); void _setWorld(const World &newWorld); const RuntimeEnvironment *const RR; + uint64_t _trustedPathIds[ZT_MAX_TRUSTED_PATHS]; + InetAddress _trustedPathNetworks[ZT_MAX_TRUSTED_PATHS]; + unsigned int _trustedPathCount; World _world; Hashtable< Address,SharedPtr<Peer> > _peers; std::vector< Address > _rootAddresses; diff --git a/zerotierone/node/Utils.cpp b/zerotierone/node/Utils.cpp index 00aeea3..2d9515e 100644 --- a/zerotierone/node/Utils.cpp +++ b/zerotierone/node/Utils.cpp @@ -262,6 +262,24 @@ std::vector<std::string> Utils::split(const char *s,const char *const sep,const return fields; } +bool Utils::scopy(char *dest,unsigned int len,const char *src) +{ + if (!len) + return false; // sanity check + if (!src) { + *dest = (char)0; + return true; + } + char *end = dest + len; + while ((*dest++ = *src++)) { + if (dest == end) { + *(--dest) = (char)0; + return false; + } + } + return true; +} + unsigned int Utils::snprintf(char *buf,unsigned int len,const char *fmt,...) throw(std::length_error) { diff --git a/zerotierone/node/Utils.hpp b/zerotierone/node/Utils.hpp index 3f4cc76..cfe5650 100644 --- a/zerotierone/node/Utils.hpp +++ b/zerotierone/node/Utils.hpp @@ -49,7 +49,6 @@ public: * @return True if strings are equal */ static inline bool secureEq(const void *a,const void *b,unsigned int len) - throw() { uint8_t diff = 0; for(unsigned int i=0;i<len;++i) @@ -225,27 +224,17 @@ public: } /** - * Perform a safe C string copy + * Perform a safe C string copy, ALWAYS null-terminating the result * - * @param dest Destination buffer - * @param len Length of buffer - * @param src Source string + * This will never ever EVER result in dest[] not being null-terminated + * regardless of any input parameter (other than len==0 which is invalid). + * + * @param dest Destination buffer (must not be NULL) + * @param len Length of dest[] (if zero, false is returned and nothing happens) + * @param src Source string (if NULL, dest will receive a zero-length string and true is returned) * @return True on success, false on overflow (buffer will still be 0-terminated) */ - static inline bool scopy(char *dest,unsigned int len,const char *src) - throw() - { - if (!len) - return false; // sanity check - char *end = dest + len; - while ((*dest++ = *src++)) { - if (dest == end) { - *(--dest) = (char)0; - return false; - } - } - return true; - } + static bool scopy(char *dest,unsigned int len,const char *src); /** * Variant of snprintf that is portable and throws an exception @@ -269,7 +258,6 @@ public: * @return Number of bits set in this integer (0-32) */ static inline uint32_t countBits(uint32_t v) - throw() { v = v - ((v >> 1) & (uint32_t)0x55555555); v = (v & (uint32_t)0x33333333) + ((v >> 2) & (uint32_t)0x33333333); @@ -284,7 +272,6 @@ public: * @return True if memory is all zero */ static inline bool isZero(const void *p,unsigned int len) - throw() { for(unsigned int i=0;i<len;++i) { if (((const unsigned char *)p)[i]) |
