diff options
| author | Adam Ierymenko <adam.ierymenko@gmail.com> | 2013-07-04 16:56:19 -0400 |
|---|---|---|
| committer | Adam Ierymenko <adam.ierymenko@gmail.com> | 2013-07-04 16:56:19 -0400 |
| commit | 150850b80012f852521c9935145cf966946334d5 (patch) | |
| tree | c082369f2fef2515cfa2e4acf1b83250a3963158 /node | |
| download | infinitytier-150850b80012f852521c9935145cf966946334d5.tar.gz infinitytier-150850b80012f852521c9935145cf966946334d5.zip | |
New git repository for release - version 0.2.0 tagged
Diffstat (limited to 'node')
58 files changed, 13399 insertions, 0 deletions
diff --git a/node/Address.hpp b/node/Address.hpp new file mode 100644 index 00000000..4c912540 --- /dev/null +++ b/node/Address.hpp @@ -0,0 +1,176 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ADDRESS_HPP +#define _ZT_ADDRESS_HPP + +#include <stdint.h> +#include <string> +#include "Utils.hpp" +#include "MAC.hpp" +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * ZeroTier address, which doubles as the last 5 octets of the MAC on taps + * + * Natural sort order will differ on big vs. little endian machines, but that + * won't matter when it's used as a local map/set key. + */ +class Address +{ +private: + union { + unsigned char o[ZT_ADDRESS_LENGTH]; + uint64_t v; + } _a; + +public: + Address() + throw() + { + _a.v = 0; + } + + Address(const Address &a) + throw() + { + _a.v = a._a.v; + } + + /** + * Create from a ZeroTier MAC + * + * @param m MAC (assumed to be a ZeroTier MAC) + */ + Address(const MAC &m) + throw() + { + _a.v = 0; + for(int i=0;i<ZT_ADDRESS_LENGTH;++i) + _a.o[i] = m.data[i + 1]; + } + + /** + * @param bits Raw address -- 5 bytes in length + */ + Address(const void *bits) + throw() + { + _a.v = 0; + for(int i=0;i<ZT_ADDRESS_LENGTH;++i) + _a.o[i] = ((const unsigned char *)bits)[i]; + } + + inline Address &operator=(const Address &a) + throw() + { + _a.v = a._a.v; + return *this; + } + + /** + * Derive a MAC whose first octet is the ZeroTier LAN standard + * + * @return Ethernet MAC derived from address + */ + inline MAC toMAC() const + throw() + { + MAC m; + m.data[0] = ZT_MAC_FIRST_OCTET; + for(int i=1;i<6;++i) + m.data[i] = _a.o[i - 1]; + return m; + } + + /** + * @return Hexadecimal string + */ + inline std::string toString() const + { + return Utils::hex(_a.o,ZT_ADDRESS_LENGTH); + }; + + /** + * Set address to zero + */ + inline void zero() throw() { _a.v = 0; } + + /** + * @return True if this address is not zero + */ + inline operator bool() const throw() { return (_a.v); } + + /** + * @return Sum of all bytes in address + */ + inline unsigned int sum() const + throw() + { + unsigned int s = 0; + for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) + s += _a.o[i]; + return s; + } + + /** + * Check if this address is reserved + * + * The all-zero null address and any address beginning with 0xff are + * reserved. (0xff is reserved for future use to designate possibly + * longer addresses, addresses based on IPv6 innards, etc.) + * + * @return True if address is reserved and may not be used + */ + inline bool isReserved() const + throw() + { + return ((!_a.v)||(_a.o[0] == ZT_ADDRESS_RESERVED_PREFIX)); + } + + inline unsigned char *data() throw() { return _a.o; } + inline const unsigned char *data() const throw() { return _a.o; } + + inline unsigned int size() const throw() { return ZT_ADDRESS_LENGTH; } + + inline unsigned char &operator[](unsigned int i) throw() { return _a.o[i]; } + inline unsigned char operator[](unsigned int i) const throw() { return _a.o[i]; } + + inline bool operator==(const Address &a) const throw() { return (_a.v == a._a.v); } + inline bool operator!=(const Address &a) const throw() { return (_a.v != a._a.v); } + inline bool operator<(const Address &a) const throw() { return (_a.v < a._a.v); } + inline bool operator>(const Address &a) const throw() { return (_a.v > a._a.v); } + inline bool operator<=(const Address &a) const throw() { return (_a.v <= a._a.v); } + inline bool operator>=(const Address &a) const throw() { return (_a.v >= a._a.v); } +}; + +} // namespace ZeroTier + +#endif + diff --git a/node/Array.hpp b/node/Array.hpp new file mode 100644 index 00000000..d0fe10ec --- /dev/null +++ b/node/Array.hpp @@ -0,0 +1,110 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ARRAY_HPP +#define _ZT_ARRAY_HPP + +#include <string> +#include <algorithm> + +namespace ZeroTier { + +/** + * Static array -- a simple thing that's belonged in STL since the time of the dinosaurs + */ +template<typename T,std::size_t S> +class Array +{ +public: + Array() throw() {} + + Array(const Array &a) + { + for(std::size_t i=0;i<S;++i) + data[i] = a.data[i]; + } + + Array(const T *ptr) + { + for(std::size_t i=0;i<S;++i) + data[i] = ptr[i]; + } + + inline Array &operator=(const Array &a) + { + for(std::size_t i=0;i<S;++i) + data[i] = a.data[i]; + return *this; + } + + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T* iterator; + typedef const T* const_iterator; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef std::reverse_iterator<iterator> reverse_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + + inline iterator begin() throw() { return data; } + inline iterator end() throw() { return &(data[S]); } + inline const_iterator begin() const throw() { return data; } + inline const_iterator end() const throw() { return &(data[S]); } + + inline reverse_iterator rbegin() throw() { return reverse_iterator(begin()); } + inline reverse_iterator rend() throw() { return reverse_iterator(end()); } + inline const_reverse_iterator rbegin() const throw() { return const_reverse_iterator(begin()); } + inline const_reverse_iterator rend() const throw() { return const_reverse_iterator(end()); } + + inline std::size_t size() const throw() { return S; } + inline std::size_t max_size() const throw() { return S; } + + inline reference operator[](const std::size_t n) throw() { return data[n]; } + inline const_reference operator[](const std::size_t n) const throw() { return data[n]; } + + inline reference front() throw() { return data[0]; } + inline const_reference front() const throw() { return data[0]; } + inline reference back() throw() { return data[S-1]; } + inline const_reference back() const throw() { return data[S-1]; } + + inline bool operator==(const Array &k) const throw() { return std::equal(begin(),end(),k.begin()); } + inline bool operator<(const Array &k) const throw() { return std::lexicographical_compare(begin(),end(),k.begin(),k.end()); } + inline bool operator!=(const Array &k) const throw() { return !(*this == k); } + inline bool operator>(const Array &k) const throw() { return (k < *this); } + inline bool operator<=(const Array &k) const throw() { return !(k < *this); } + inline bool operator>=(const Array &k) const throw() { return !(*this < k); } + + T data[S]; +}; + +} // namespace ZeroTier + +#endif + diff --git a/node/AtomicCounter.hpp b/node/AtomicCounter.hpp new file mode 100644 index 00000000..ebc70817 --- /dev/null +++ b/node/AtomicCounter.hpp @@ -0,0 +1,113 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ATOMICCOUNTER_HPP +#define _ZT_ATOMICCOUNTER_HPP + +#include "Mutex.hpp" +#include "NonCopyable.hpp" + +namespace ZeroTier { + +/** + * Simple atomic counter supporting increment and decrement + */ +class AtomicCounter : NonCopyable +{ +public: + /** + * Initialize counter at zero + */ + AtomicCounter() + throw() : + _v(0) + { + } + + inline int operator*() const + throw() + { +#ifdef __GNUC__ + return __sync_or_and_fetch(const_cast <int *> (&_v),0); +#else + _l.lock(); + int v = _v; + _l.unlock(); + return v; +#endif + } + + inline int operator++() + throw() + { +#ifdef __GNUC__ + return __sync_add_and_fetch(&_v,1); +#else + _l.lock(); + int v = ++_v; + _l.unlock(); + return v; +#endif + } + + inline int operator--() + throw() + { +#ifdef __GNUC__ + return __sync_sub_and_fetch(&_v,1); +#else + _l.lock(); + int v = --_v; + _l.unlock(); + return v; +#endif + } + + inline bool operator==(const AtomicCounter &i) const throw() { return (**this == *i); } + inline bool operator!=(const AtomicCounter &i) const throw() { return (**this != *i); } + inline bool operator>(const AtomicCounter &i) const throw() { return (**this > *i); } + inline bool operator<(const AtomicCounter &i) const throw() { return (**this < *i); } + inline bool operator>=(const AtomicCounter &i) const throw() { return (**this >= *i); } + inline bool operator<=(const AtomicCounter &i) const throw() { return (**this <= *i); } + + inline bool operator==(const int i) const throw() { return (**this == i); } + inline bool operator!=(const int i) const throw() { return (**this != i); } + inline bool operator>(const int i) const throw() { return (**this > i); } + inline bool operator<(const int i) const throw() { return (**this < i); } + inline bool operator>=(const int i) const throw() { return (**this >= i); } + inline bool operator<=(const int i) const throw() { return (**this <= i); } + +private: + int _v; +#ifndef __GNUC__ + Mutex _l; +#endif +}; + +} // namespace ZeroTier + +#endif diff --git a/node/BlobArray.hpp b/node/BlobArray.hpp new file mode 100644 index 00000000..d78bcffa --- /dev/null +++ b/node/BlobArray.hpp @@ -0,0 +1,94 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_BLOBARRAY_HPP +#define _ZT_BLOBARRAY_HPP + +#include <vector> +#include <string> +#include <algorithm> + +namespace ZeroTier { + +/** + * A vector of binary strings serializable in a packed format + * + * The format uses variable-length integers to indicate the length of each + * field. Each byte of the length has another byte with seven more significant + * bits if its 8th bit is set. Fields can be up to 2^28 in length. + */ +class BlobArray : public std::vector<std::string> +{ +public: + inline std::string serialize() const + { + std::string r; + for(BlobArray::const_iterator i=begin();i!=end();++i) { + unsigned int flen = (unsigned int)i->length(); + do { + unsigned char flenb = (unsigned char)(flen & 0x7f); + flen >>= 7; + flenb |= (flen) ? 0x80 : 0; + r.push_back((char)flenb); + } while (flen); + r.append(*i); + } + return r; + } + + /** + * Deserialize, replacing the current contents of this array + * + * @param data Serialized binary data + * @param len Length of serialized data + */ + inline void deserialize(const void *data,unsigned int len) + { + clear(); + for(unsigned int i=0;i<len;) { + unsigned int flen = 0; + unsigned int chunk = 0; + while (i < len) { + flen |= ((unsigned int)(((const unsigned char *)data)[i] & 0x7f)) << (7 * chunk++); + if (!(((const unsigned char *)data)[i++] & 0x80)) + break; + } + flen = std::min(flen,len - i); + push_back(std::string(((const char *)data) + i,flen)); + i += flen; + } + } + inline void deserialize(const std::string &data) + { + deserialize(data.data(),(unsigned int)data.length()); + } +}; + +} // namespace ZeroTier + +#endif + diff --git a/node/Buffer.hpp b/node/Buffer.hpp new file mode 100644 index 00000000..d3603d38 --- /dev/null +++ b/node/Buffer.hpp @@ -0,0 +1,398 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_BUFFER_HPP +#define _ZT_BUFFER_HPP + +#include <stdexcept> +#include <string> +#include <algorithm> +#include <utility> +#include <string.h> +#include <stdint.h> +#include "Utils.hpp" + +#ifdef __GNUC__ +#define ZT_VAR_MAY_ALIAS __attribute__((__may_alias__)) +#else +#define ZT_VAR_MAY_ALIAS +#endif + +namespace ZeroTier { + +/** + * A variable length but statically allocated buffer + * + * Bounds-checking is done everywhere, since this is used in security + * critical code. This supports construction and assignment from buffers + * of differing capacities, provided the data actually in them fits. + * It throws std::out_of_range on any boundary violation. + * + * The at(), append(), etc. methods encode integers larger than 8-bit in + * big-endian (network) byte order. + * + * @tparam C Total capacity + */ +template<unsigned int C> +class Buffer +{ + // I love me! + template <unsigned int C2> friend class Buffer; + +public: + // STL container idioms + typedef unsigned char value_type; + typedef unsigned char * pointer; + typedef const unsigned char * const_pointer; + typedef unsigned char & reference; + typedef const unsigned char & const_reference; + typedef unsigned char * iterator; + typedef const unsigned char * const_iterator; + typedef unsigned int size_type; + typedef int difference_type; + typedef std::reverse_iterator<iterator> reverse_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + inline iterator begin() { return _b; } + inline iterator end() { return (_b + _l); } + inline const_iterator begin() const { return _b; } + inline const_iterator end() const { return (_b + _l); } + inline reverse_iterator rbegin() { return reverse_iterator(begin()); } + inline reverse_iterator rend() { return reverse_iterator(end()); } + inline const_reverse_iterator rbegin() const { return const_reverse_iterator(begin()); } + inline const_reverse_iterator rend() const { return const_reverse_iterator(end()); } + + Buffer() + throw() : + _l(0) + { + } + + Buffer(unsigned int l) + throw(std::out_of_range) + { + if (l > C) + throw std::out_of_range("Buffer: construct with size larger than capacity"); + _l = l; + } + + template<unsigned int C2> + Buffer(const Buffer<C2> &b) + throw(std::out_of_range) + { + *this = b; + } + + Buffer(const void *b,unsigned int l) + throw(std::out_of_range) + { + copyFrom(b,l); + } + + Buffer(const std::string &s) + throw(std::out_of_range) + { + copyFrom(s.data(),s.length()); + } + + template<unsigned int C2> + inline Buffer &operator=(const Buffer<C2> &b) + throw(std::out_of_range) + { + if (b._l > C) + throw std::out_of_range("Buffer: assignment from buffer larger than capacity"); + memcpy(this,&b,sizeof(_l) + b._l); // one memcpy for all fields + return *this; + } + + inline Buffer &operator=(const std::string &s) + throw(std::out_of_range) + { + copyFrom(s.data(),s.length()); + return *this; + } + + inline void copyFrom(const void *b,unsigned int l) + throw(std::out_of_range) + { + if (l > C) + throw std::out_of_range("Buffer: set from C array larger than capacity"); + _l = l; + memcpy(_b,b,l); + } + + unsigned char operator[](const unsigned int i) const + throw(std::out_of_range) + { + if (i >= _l) + throw std::out_of_range("Buffer: [] beyond end of data"); + return (unsigned char)_b[i]; + } + + unsigned char &operator[](const unsigned int i) + throw(std::out_of_range) + { + if (i >= _l) + throw std::out_of_range("Buffer: [] beyond end of data"); + return ((unsigned char *)_b)[i]; + } + + unsigned char *data() throw() { return (unsigned char *)_b; } + const unsigned char *data() const throw() { return (const unsigned char *)_b; } + + /** + * Safe way to get a pointer to a field from data() with bounds checking + * + * @param i Index of field in buffer + * @param l Length of field in bytes + * @return Pointer to field data + * @throws std::out_of_range Field extends beyond data size + */ + unsigned char *field(unsigned int i,unsigned int l) + throw(std::out_of_range) + { + if ((i + l) > _l) + throw std::out_of_range("Buffer: field() beyond end of data"); + return (unsigned char *)(_b + i); + } + const unsigned char *field(unsigned int i,unsigned int l) const + throw(std::out_of_range) + { + if ((i + l) > _l) + throw std::out_of_range("Buffer: field() beyond end of data"); + return (const unsigned char *)(_b + i); + } + + /** + * Place a primitive integer value at a given position + * + * @param i Index to place value + * @param v Value + * @tparam T Integer type (e.g. uint16_t, int64_t) + */ + template<typename T> + inline void setAt(unsigned int i,const T v) + throw(std::out_of_range) + { + if ((i + sizeof(T)) > _l) + throw std::out_of_range("Buffer: set() beyond end of data"); + T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T *>(_b + i); + *p = Utils::hton(v); + } + + /** + * Get a primitive integer value at a given position + * + * This behaves like set() in reverse. + * + * @param i Index to get integer + * @tparam T Integer type (e.g. uint16_t, int64_t) + * @return Integer value + */ + template<typename T> + inline T at(unsigned int i) const + throw(std::out_of_range) + { + if ((i + sizeof(T)) > _l) + throw std::out_of_range("Buffer: at() beyond end of data"); + const T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast<const T *>(_b + i); + return Utils::ntoh(*p); + } + + /** + * Append an integer type to this buffer + * + * @param v Value to append + * @tparam T Integer type (e.g. uint16_t, int64_t) + * @throws std::out_of_range Attempt to append beyond capacity + */ + template<typename T> + inline void append(const T v) + throw(std::out_of_range) + { + if ((_l + sizeof(T)) > C) + throw std::out_of_range("Buffer: append beyond capacity"); + T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast<T *>(_b + _l); + *p = Utils::hton(v); + _l += sizeof(T); + } + + /** + * Append a C-array of bytes + * + * @param b Data + * @param l Length + * @throws std::out_of_range Attempt to append beyond capacity + */ + inline void append(const void *b,unsigned int l) + throw(std::out_of_range) + { + if ((_l + l) > C) + throw std::out_of_range("Buffer: append beyond capacity"); + memcpy(_b + _l,b,l); + _l += l; + } + + /** + * Append a string + * + * @param s String to append + * @throws std::out_of_range Attempt to append beyond capacity + */ + inline void append(const std::string &s) + throw(std::out_of_range) + { + append(s.data(),s.length()); + } + + /** + * Append a buffer + * + * @param b Buffer to append + * @tparam C2 Capacity of second buffer (typically inferred) + * @throws std::out_of_range Attempt to append beyond capacity + */ + template<unsigned int C2> + inline void append(const Buffer<C2> &b) + throw(std::out_of_range) + { + append(b._b,b._l); + } + + /** + * Increment size by a given number of bytes + * + * The contents of new space are undefined. + * + * @param i Bytes to increment + * @throws std::out_of_range Capacity exceeded + */ + inline void addSize(unsigned int i) + throw(std::out_of_range) + { + if ((i + _l) > C) + throw std::out_of_range("Buffer: setSize to larger than capacity"); + _l += i; + } + + /** + * Set size of data in buffer + * + * The contents of new space are undefined. + * + * @param i New size + * @throws std::out_of_range Size larger than capacity + */ + inline void setSize(const unsigned int i) + throw(std::out_of_range) + { + if (i > C) + throw std::out_of_range("Buffer: setSize to larger than capacity"); + _l = i; + } + + /** + * Set buffer data length to zero + */ + inline void clear() + throw() + { + _l = 0; + } + + /** + * Zero buffer up to size() + */ + inline void zero() + throw() + { + memset(_b,0,_l); + } + + /** + * Zero unused capacity area + */ + inline void zeroUnused() + throw() + { + memset(_b + _l,0,C - _l); + } + + /** + * @return Size of data in buffer + */ + inline unsigned int size() const throw() { return _l; } + + /** + * @return Capacity of buffer + */ + inline unsigned int capacity() const throw() { return C; } + + template<unsigned int C2> + inline bool operator==(const Buffer<C2> &b) const + throw() + { + return ((_l == b._l)&&(!memcmp(_b,b._b,_l))); + } + template<unsigned int C2> + inline bool operator!=(const Buffer<C2> &b) const + throw() + { + return ((_l != b._l)||(memcmp(_b,b._b,_l))); + } + template<unsigned int C2> + inline bool operator<(const Buffer<C2> &b) const + throw() + { + return (memcmp(_b,b._b,std::min(_l,b._l)) < 0); + } + template<unsigned int C2> + inline bool operator>(const Buffer<C2> &b) const + throw() + { + return (b < *this); + } + template<unsigned int C2> + inline bool operator<=(const Buffer<C2> &b) const + throw() + { + return !(b < *this); + } + template<unsigned int C2> + inline bool operator>=(const Buffer<C2> &b) const + throw() + { + return !(*this < b); + } + +protected: + unsigned int _l; + char ZT_VAR_MAY_ALIAS _b[C]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Condition.hpp b/node/Condition.hpp new file mode 100644 index 00000000..2ce8c98f --- /dev/null +++ b/node/Condition.hpp @@ -0,0 +1,107 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_CONDITION_HPP +#define _ZT_CONDITION_HPP + +#include "NonCopyable.hpp" + +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + +#include <time.h> +#include <stdlib.h> +#include <pthread.h> +#include "Utils.hpp" + +namespace ZeroTier { + +class Condition : NonCopyable +{ +public: + Condition() + throw() + { + pthread_mutex_init(&_mh,(const pthread_mutexattr_t *)0); + pthread_cond_init(&_cond,(const pthread_condattr_t *)0); + } + + ~Condition() + { + pthread_cond_destroy(&_cond); + pthread_mutex_destroy(&_mh); + } + + inline void wait() const + throw() + { + pthread_mutex_lock(const_cast <pthread_mutex_t *>(&_mh)); + pthread_cond_wait(const_cast <pthread_cond_t *>(&_cond),const_cast <pthread_mutex_t *>(&_mh)); + pthread_mutex_unlock(const_cast <pthread_mutex_t *>(&_mh)); + } + + inline void wait(unsigned long ms) const + throw() + { + uint64_t when = Utils::now() + (uint64_t)ms; + struct timespec ts; + ts.tv_sec = (unsigned long)(when / 1000); + ts.tv_nsec = (unsigned long)(when % 1000) * 1000000; + pthread_mutex_lock(const_cast <pthread_mutex_t *>(&_mh)); + pthread_cond_timedwait(const_cast <pthread_cond_t *>(&_cond),const_cast <pthread_mutex_t *>(&_mh),&ts); + pthread_mutex_unlock(const_cast <pthread_mutex_t *>(&_mh)); + } + + inline void signal() const + throw() + { + pthread_cond_signal(const_cast <pthread_cond_t *>(&_cond)); + } + +private: + pthread_cond_t _cond; + pthread_mutex_t _mh; +}; + +} // namespace ZeroTier + +#endif // Apple / Linux + +#ifdef _WIN32 + +#include <stdlib.h> +#include <Windows.h> + +namespace ZeroTier { + +error need windoze; +// On Windows this will probably be implemented via Semaphores + +} // namespace ZeroTier + +#endif // _WIN32 + +#endif diff --git a/node/Constants.hpp b/node/Constants.hpp new file mode 100644 index 00000000..641b25bb --- /dev/null +++ b/node/Constants.hpp @@ -0,0 +1,311 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_CONSTANTS_HPP +#define _ZT_CONSTANTS_HPP + +// Assume these are little-endian, since we don't support old PPC MACs +// and all newer Mac or Windows systems are either x86_32, x86_64, or +// ARM in little-endian mode. +#if defined(__APPLE__) || defined(_WIN32) +#undef __BYTE_ORDER +#undef __LITTLE_ENDIAN +#undef __BIG_ENDIAN +#define __BIG_ENDIAN 4321 +#define __LITTLE_ENDIAN 1234 +#define __BYTE_ORDER 1234 +#endif + +// Linux has endian.h, which should tell us +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#include <endian.h> +#endif + +#ifndef __BYTE_ORDER +error_no_byte_order_defined +#endif + +#ifndef ZT_OSNAME +error_no_ZT_OSNAME +#endif + +#ifndef ZT_ARCH +error_no_ZT_ARCH +#endif + +#ifdef _WIN32 +#define ZT_PATH_SEPARATOR '\\' +#define ZT_PATH_SEPARATOR_S "\\" +#define ZT_EOL_S "\r\n" +#else +#define ZT_PATH_SEPARATOR '/' +#define ZT_PATH_SEPARATOR_S "/" +#define ZT_EOL_S "\n" +#endif + +/** + * Length of a ZeroTier address in bytes + */ +#define ZT_ADDRESS_LENGTH 5 + +/** + * Addresses beginning with this byte are reserved for the joy of in-band signaling + */ +#define ZT_ADDRESS_RESERVED_PREFIX 0xff + +/** + * Default local UDP port + */ +#define ZT_DEFAULT_UDP_PORT 8993 + +/** + * Default payload MTU for UDP packets + * + * In the future we might support UDP path MTU discovery, but for now we + * set a maximum that is equal to 1500 minus 8 (for PPPoE overhead, common + * in some markets) minus 48 (IPv6 UDP overhead). + */ +#define ZT_UDP_DEFAULT_PAYLOAD_MTU 1444 + +/** + * MTU used for Ethernet tap device + * + * This is pretty much an unchangeable global constant. To make it change + * across nodes would require logic to send ICMP packet too big messages, + * which would complicate things. 1500 has been good enough on most LANs + * for ages, so a larger MTU should be fine for the forseeable future. This + * typically results in two UDP packets per single large frame. Experimental + * results seem to show that this is good. Larger MTUs resulting in more + * fragments seemed too brittle on slow/crummy links for no benefit. + * + * If this does change, also change it in tap.h in the tuntaposx code under + * mac-tap. + * + * Overhead for a normal frame split into two packets: + * + * 1414 = 1444 (typical UDP MTU) - 28 (packet header) - 2 (ethertype) + * 1428 = 1444 (typical UDP MTU) - 16 (fragment header) + * SUM: 2842 + * + * We use 2800, which leaves some room for other payload in other types of + * messages such as multicast propagation or future support for bridging. + */ +#define ZT_IF_MTU 2800 + +/** + * Maximum number of networks we can be a member of + * + * This is a safe value that's within the tap device limit on all known OSes. + */ +#define ZT_MAX_NETWORK_MEMBERSHIPS 16 + +/** + * Maximum number of packet fragments we'll support + * + * The actual spec allows 16, but this is the most we'll support right + * now. Packets with more than this many fragments are dropped. + */ +#define ZT_MAX_PACKET_FRAGMENTS 3 + +/** + * Timeout for receipt of fragmented packets in ms + * + * Since there's no retransmits, this is just a really bad case scenario for + * transit time. It's short enough that a DOS attack from exhausing buffers is + * very unlikely, as the transfer rate would have to be fast enough to fill + * system memory in this time. + */ +#define ZT_FRAGMENTED_PACKET_RECEIVE_TIMEOUT 1500 + +/** + * First byte of MAC addresses derived from ZeroTier addresses + * + * This has the 0x02 bit set, which indicates a locally administrered + * MAC address rather than one with a known HW ID. + */ +#define ZT_MAC_FIRST_OCTET 0x32 + +/** + * How often Topology::clean() is called in ms + */ +#define ZT_TOPOLOGY_CLEAN_PERIOD 300000 + +/** + * Delay between WHOIS retries in ms + */ +#define ZT_WHOIS_RETRY_DELAY 500 + +/** + * Maximum identity WHOIS retries + */ +#define ZT_MAX_WHOIS_RETRIES 3 + +/** + * Transmit queue entry timeout + */ +#define ZT_TRANSMIT_QUEUE_TIMEOUT (ZT_WHOIS_RETRY_DELAY * (ZT_MAX_WHOIS_RETRIES + 1)) + +/** + * Receive queue entry timeout + */ +#define ZT_RECEIVE_QUEUE_TIMEOUT (ZT_WHOIS_RETRY_DELAY * (ZT_MAX_WHOIS_RETRIES + 1)) + +/** + * Maximum number of ZT hops allowed + * + * The protocol allows up to 7, but we limit it to something smaller. + */ +#define ZT_RELAY_MAX_HOPS 3 + +/** + * Breadth of tree for rumor mill multicast propagation + */ +#define ZT_MULTICAST_PROPAGATION_BREADTH 4 + +/** + * Depth of tree for rumor mill multicast propagation + * + * The maximum number of peers who can receive a multicast is equal to + * the sum of BREADTH^i where I is from 1 to DEPTH. This ignores the effect + * of the rate limiting algorithm or bloom filter collisions. + * + * 7 results in a max of 21844 recipients for a given multicast. + */ +#define ZT_MULTICAST_PROPAGATION_DEPTH 7 + +/** + * Length of circular ring buffer history of multicast packets + */ +#define ZT_MULTICAST_DEDUP_HISTORY_LENGTH 4096 + +/** + * Expiration time in ms for multicast history items + */ +#define ZT_MULTICAST_DEDUP_HISTORY_EXPIRE 8000 + +/** + * Period between announcements of all multicast 'likes' in ms + * + * Announcement occurs when a multicast group is locally joined, but all + * memberships are periodically re-broadcast. If they're not they will + * expire. + */ +#define ZT_MULTICAST_LIKE_ANNOUNCE_ALL_PERIOD 120000 + +/** + * Expire time for multicast 'likes' in ms + */ +#define ZT_MULTICAST_LIKE_EXPIRE ((ZT_MULTICAST_LIKE_ANNOUNCE_ALL_PERIOD * 2) + 1000) + +/** + * Time between polls of local taps for multicast membership changes + */ +#define ZT_MULTICAST_LOCAL_POLL_PERIOD 10000 + +/** + * Delay between scans of the topology active peer DB for peers that need ping + */ +#define ZT_PING_CHECK_DELAY 7000 + +/** + * Delay between checks of network configuration fingerprint + */ +#define ZT_NETWORK_FINGERPRINT_CHECK_DELAY 5000 + +/** + * Delay between pings (actually HELLOs) to direct links + */ +#define ZT_PEER_DIRECT_PING_DELAY 120000 + +/** + * Period between rechecks of autoconfigure URL + * + * This is in the absence of an external message ordering a recheck. + */ +#define ZT_AUTOCONFIGURE_INTERVAL 3600000 + +/** + * Period between autoconfigure attempts if no successful autoconfig + */ +#define ZT_AUTOCONFIGURE_CHECK_DELAY 15000 + +/** + * Minimum delay in Node service loop + * + * This is the shortest of the check delays/periods. + */ +#define ZT_MIN_SERVICE_LOOP_INTERVAL ZT_NETWORK_FINGERPRINT_CHECK_DELAY + +/** + * Activity timeout for links + * + * A link that hasn't spoken in this long is simply considered inactive. + */ +#define ZT_PEER_LINK_ACTIVITY_TIMEOUT ((ZT_PEER_DIRECT_PING_DELAY * 2) + 1000) + +/** + * Delay in ms between firewall opener packets to direct links + * + * This should be lower than the UDP conversation entry timeout in most + * stateful firewalls. + */ +#define ZT_FIREWALL_OPENER_DELAY 50000 + +/** + * IP hops (a.k.a. TTL) to set for firewall opener packets + * + * 2 should permit traversal of double-NAT configurations, such as from inside + * a VM running behind local NAT on a host that is itself behind NAT. + */ +#define ZT_FIREWALL_OPENER_HOPS 2 + +/** + * Delay sleep overshoot for detection of a probable sleep/wake event + */ +#define ZT_SLEEP_WAKE_DETECTION_THRESHOLD 2000 + +/** + * Time to pause main service loop after sleep/wake detect + */ +#define ZT_SLEEP_WAKE_SETTLE_TIME 5000 + +/** + * Minimum interval between attempts by relays to unite peers + */ +#define ZT_MIN_UNITE_INTERVAL 30000 + +/** + * Delay in milliseconds between firewall opener and real packet for NAT-t + */ +#define ZT_RENDEZVOUS_NAT_T_DELAY 500 + +/** + * Generate a new ownership verify secret on launch if older than this + */ +#define ZT_OVS_GENERATE_NEW_IF_OLDER_THAN 86400000 + +#endif diff --git a/node/Defaults.cpp b/node/Defaults.cpp new file mode 100644 index 00000000..f1454796 --- /dev/null +++ b/node/Defaults.cpp @@ -0,0 +1,77 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Defaults.hpp" +#include "Constants.hpp" + +namespace ZeroTier { + +const Defaults ZT_DEFAULTS; + +static inline std::map< Identity,std::vector<InetAddress> > _mkSupernodeMap() + throw(std::runtime_error) +{ + std::map< Identity,std::vector<InetAddress> > sn; + Identity id; + std::vector<InetAddress> addrs; + + // Nothing special about a supernode... except that they are + // designated as such. + + // cthulhu.zerotier.com - New York, New York, USA + addrs.clear(); + if (!id.fromString("271ee006a0:1:AgGXs3I+9CWrEmGMxc50x3E+trwtaa2ZMXDU6ezz92fFJXzlhRKGUY/uAToHDdH9XiLxtcA+kUQAZdC4Dy2xtqXxjw==:QgH5Nlx4oWEGVrwhNocqem+3VNd4qzt7RLrmuvqZvKPRS9R70LJYJQLlKZj0ri55Pzg+Mlwy4a4nAgfnRAWA+TW6R0EjSmq72MG585XGNfWBVk3LxMvxlNWErnVNFr2BQS9yzVp4pRjPLdCW4RB3dwEHBUgJ78rwMxQ6IghVCl8CjkDapg==")) + throw std::runtime_error("invalid identity in Defaults"); + addrs.push_back(InetAddress("198.199.73.93",ZT_DEFAULT_UDP_PORT)); + sn[id] = addrs; + + // nyarlathotep.zerotier.com - San Francisco, California, USA + addrs.clear(); + if (!id.fromString("fa9be4008b:1:AwCHXEi/PJuhtOPUZxnBSMiuGvj6XeRMWu9R9aLR3JD1qluADLQzUPSP2+81Dqvgi2wkQ2cqEpOlDPeUCvtlZwdXEA==:QgH4usG/wzsoUCtO2LL3qkwugtoXEz1PUJbmUzY8vbwzc5bckmVPjMqb4q2CF71+QVPV1K6shIV2EKkBMRSS/D/44EGEwC6tjFGZqmmogaC0P1uQeukTAF4qta46YgC4YQx54/Vd/Yfl8n1Bwmgm0gBB4W1ZQir3p+wp37MGlEN0rlXxqA==")) + throw std::runtime_error("invalid identity in Defaults"); + addrs.push_back(InetAddress("198.199.97.220",ZT_DEFAULT_UDP_PORT)); + sn[id] = addrs; + + // shub-niggurath.zerotier.com - Amsterdam, Netherlands + addrs.clear(); + if (!id.fromString("48099ecd05:1:AwHO7o1FdDj1nEArfchTDa6EG7Eh2GLdiH86BhcoNv0BHJN4tmrf0Y7/2SZiQFpTTwJf93iph84Dci5+k52u/qkHTQ==:QgGbir8CNxBFFPPj8Eo3Bnp2UmbnZxu/pOq3Ke0WaLBBhHzVuwM+88g7CaDxbZ0AY2VkFc9hmE3VG+xi7g0H86yfVUIBHZnb7N+DCtf8/mphZIHNgmasakRi4hU11kGyLi1nTVTnrmCfAb7w+8SCp64Q5RNvBC/Pvz7pxSwSdjIHkVqRaeo=")) + throw std::runtime_error("invalid identity in Defaults"); + addrs.push_back(InetAddress("198.211.127.172",ZT_DEFAULT_UDP_PORT)); + sn[id] = addrs; + + return sn; +} + +Defaults::Defaults() + throw(std::runtime_error) : + supernodes(_mkSupernodeMap()), + configUrlPrefix("http://api.zerotier.com/one/nc/"), + configAuthority("f9f34184ac:1:AwGgrWjb8dARXzruqxiy1+Qf+gz4iM5IMfQTCWrJXkwERdvbvxTPZvtIyitw4gS90TGIxW+e7uJxweg9Vyq5lZJBrg==:QeEQLm9ymLC3EcnIw2OUqufUwb2wgHSAg6wQOXKyhT779p/8Hz5485PZLJCbr/aVHjwzop8APJk9B45Zm0Mb/LEhQTBMH2jvc7qqoYnMCNCO9jpADeMJwMW5e1VFgIObWl9uNjhRbf5/m8dZcn0pKKGwjSoP1QTeVWOC8GkZhE25bUWj") +{ +} + +} // namespace ZeroTier diff --git a/node/Defaults.hpp b/node/Defaults.hpp new file mode 100644 index 00000000..b9c8ecf5 --- /dev/null +++ b/node/Defaults.hpp @@ -0,0 +1,74 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_DEFAULTS_HPP +#define _ZT_DEFAULTS_HPP + +#include <stdexcept> +#include <string> +#include <vector> +#include <map> +#include "Identity.hpp" +#include "InetAddress.hpp" + +namespace ZeroTier { + +/** + * Static configuration defaults + * + * These are the default values that ship baked into the ZeroTier binary. They + * define the basic parameters required for it to connect to the rest of the + * network and obtain software updates. + */ +class Defaults +{ +public: + Defaults() + throw(std::runtime_error); + ~Defaults() {} + + /** + * Supernodes on the ZeroTier network + */ + const std::map< Identity,std::vector<InetAddress> > supernodes; + + /** + * URL prefix for autoconfiguration + */ + const std::string configUrlPrefix; + + /** + * Identity used to encrypt and authenticate configuration from URL + */ + const std::string configAuthority; +}; + +extern const Defaults ZT_DEFAULTS; + +} // namespace ZeroTier + +#endif diff --git a/node/Demarc.cpp b/node/Demarc.cpp new file mode 100644 index 00000000..ae52db48 --- /dev/null +++ b/node/Demarc.cpp @@ -0,0 +1,210 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <vector> +#include "Demarc.hpp" +#include "RuntimeEnvironment.hpp" +#include "Logger.hpp" +#include "UdpSocket.hpp" +#include "InetAddress.hpp" +#include "Switch.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +const Demarc::Port Demarc::ANY_PORT; +const Demarc::Port Demarc::NULL_PORT; + +Demarc::Demarc(const RuntimeEnvironment *renv) : + _r(renv) +{ +} + +Demarc::~Demarc() +{ + for(std::map< Port,DemarcPortObj >::iterator pe(_ports.begin());pe!=_ports.end();++pe) { + switch (pe->second.type) { + case PORT_TYPE_UDP_SOCKET_V4: + case PORT_TYPE_UDP_SOCKET_V6: + delete ((UdpSocket *)pe->second.obj); + break; + case PORT_TYPE_LOCAL_ETHERNET: + case PORT_TYPE_RELAY_TUNNEL: + break; + } + } +} + +std::string Demarc::describe(Demarc::Port p) + throw() +{ + char buf[64]; + switch ((DemarcPortType)(((uint64_t)p) >> 60)) { + case PORT_TYPE_UDP_SOCKET_V4: + sprintf(buf,"udp/4/%d",(int)((uint64_t)p & 0xffff)); + return std::string(buf); + case PORT_TYPE_UDP_SOCKET_V6: + sprintf(buf,"udp/6/%d",(int)((uint64_t)p & 0xffff)); + return std::string(buf); + case PORT_TYPE_LOCAL_ETHERNET: + return std::string("ethernet"); + case PORT_TYPE_RELAY_TUNNEL: + return std::string("relay"); + } + return std::string("(null)"); +} + +bool Demarc::has(Port p) const + throw() +{ + Mutex::Lock _l(_ports_m); + return (_ports.count(p)); +} + +bool Demarc::bindLocalUdp(unsigned int localPort) + throw() +{ + Mutex::Lock _l(_ports_m); + + uint64_t v4p = ((uint64_t)PORT_TYPE_UDP_SOCKET_V4 << 60) | (uint64_t)localPort; + uint64_t v6p = ((uint64_t)PORT_TYPE_UDP_SOCKET_V6 << 60) | (uint64_t)localPort; + if ((_ports.count((Port)v4p))||(_ports.count((Port)v6p))) + return true; + + UdpSocket *v4; + try { + DemarcPortObj *v4r = &(_ports[(Port)v4p]); + v4r->port = (Port)v4p; + v4r->parent = this; + v4r->obj = v4 = new UdpSocket(localPort,false,&Demarc::_CBudpSocketPacketHandler,v4r); + v4r->type = PORT_TYPE_UDP_SOCKET_V4; + } catch ( ... ) { + _ports.erase((Port)v4p); + return false; + } + + UdpSocket *v6; + try { + DemarcPortObj *v6r = &(_ports[(Port)v6p]); + v6r->port = (Port)v6p; + v6r->parent = this; + v6r->obj = v6 = new UdpSocket(localPort,true,&Demarc::_CBudpSocketPacketHandler,v6r); + v6r->type = PORT_TYPE_UDP_SOCKET_V6; + } catch ( ... ) { + delete v4; + _ports.erase((Port)v4p); + _ports.erase((Port)v6p); + return false; + } + + return true; +} + +Demarc::Port Demarc::pick(const InetAddress &to) const + throw() +{ + Mutex::Lock _l(_ports_m); + try { + std::vector< std::map< Port,DemarcPortObj >::const_iterator > possibilities; + for(std::map< Port,DemarcPortObj >::const_iterator pe(_ports.begin());pe!=_ports.end();++pe) { + switch (pe->second.type) { + case PORT_TYPE_UDP_SOCKET_V4: + if (to.isV4()) + possibilities.push_back(pe); + break; + case PORT_TYPE_UDP_SOCKET_V6: + if (to.isV6()) + possibilities.push_back(pe); + break; + default: + break; + } + } + if (possibilities.size()) + return possibilities[Utils::randomInt<unsigned int>() % possibilities.size()]->first; + else return NULL_PORT; + } catch ( ... ) { + return NULL_PORT; + } +} + +Demarc::Port Demarc::send(Demarc::Port fromPort,const InetAddress &to,const void *data,unsigned int len,int hopLimit) const + throw() +{ + _ports_m.lock(); + + std::map< Port,DemarcPortObj >::const_iterator pe(_ports.find(fromPort)); + if (pe == _ports.end()) { + try { + std::vector< std::map< Port,DemarcPortObj >::const_iterator > possibilities; + for(pe=_ports.begin();pe!=_ports.end();++pe) { + switch (pe->second.type) { + case PORT_TYPE_UDP_SOCKET_V4: + if (to.isV4()) + possibilities.push_back(pe); + break; + case PORT_TYPE_UDP_SOCKET_V6: + if (to.isV6()) + possibilities.push_back(pe); + break; + default: + break; + } + } + if (possibilities.size()) + pe = possibilities[Utils::randomInt<unsigned int>() % possibilities.size()]; + else { + _ports_m.unlock(); + return NULL_PORT; + } + } catch ( ... ) { + _ports_m.unlock(); + return NULL_PORT; + } + } + + switch (pe->second.type) { + case PORT_TYPE_UDP_SOCKET_V4: + case PORT_TYPE_UDP_SOCKET_V6: + _ports_m.unlock(); + if (((UdpSocket *)pe->second.obj)->send(to,data,len,hopLimit)) + return pe->first; + return NULL_PORT; + default: + break; + } + + _ports_m.unlock(); + return NULL_PORT; +} + +void Demarc::_CBudpSocketPacketHandler(UdpSocket *sock,void *arg,const InetAddress &from,const void *data,unsigned int len) +{ + ((DemarcPortObj *)arg)->parent->_r->sw->onRemotePacket(((DemarcPortObj *)arg)->port,from,Buffer<4096>(data,len)); +} + +} // namespace ZeroTier diff --git a/node/Demarc.hpp b/node/Demarc.hpp new file mode 100644 index 00000000..e13ed0cb --- /dev/null +++ b/node/Demarc.hpp @@ -0,0 +1,168 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_DEMARC_HPP +#define _ZT_DEMARC_HPP + +#include <stdlib.h> +#include <stdint.h> +#include <map> +#include <string> +#include "Mutex.hpp" +#include "InetAddress.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class UdpSocket; + +/** + * Local demarcation point + * + * This holds and provides unique identifiers for all local communication + * endpoints, such as UDP sockets, raw Ethernet sockets, tunnels to a relay + * server, etc. It permits other code to refer to these via Port and forget + * about what they actually are. + * + * All ports are closed when this class is destroyed. + */ +class Demarc +{ +public: + /** + * Local demarcation port + */ + typedef uint64_t Port; + + /** + * Port identifier used to refer to any port + */ + static const Port ANY_PORT = (Port)0xffffffffffffffffULL; + + /** + * Port identifier used to refer to null port / port not found + */ + static const Port NULL_PORT = (Port)0; + + Demarc(const RuntimeEnvironment *renv); + ~Demarc(); + + /** + * Describe a port + * + * This can describe even ports that are not bound, e.g. from serialized + * data. + * + * @param p Port + * @return Human-readable description of port + */ + static std::string describe(Port p) + throw(); + + /** + * @param p Port to check + * @return True if this port is bound/connected/etc. + */ + bool has(Port p) const + throw(); + + /** + * Bind local UDP port for both IPv4 and IPv6 traffic + * + * @param localPort Local IP port + * @return True if successfully bound, or if already bound + */ + bool bindLocalUdp(unsigned int localPort) + throw(); + + /** + * Pick a port to send to an address of a given type + * + * @param to Destination address + * @return Port or NULL_PORT if none + */ + Port pick(const InetAddress &to) const + throw(); + + /** + * Send a packet + * + * If fromPort is ANY_PORT or if the port is not found, a random port is + * chosen from those available matching the characteristics of the address + * in 'to'. + * + * @param fromPort Port to send from + * @param to Destination IP/port + * @param data Data to send + * @param len Length of data in bytes + * @param hopLimit IP hop limit for UDP packets or -1 for max/unlimited + * @return Port actually sent from or NULL_PORT on failure + */ + Port send(Port fromPort,const InetAddress &to,const void *data,unsigned int len,int hopLimit) const + throw(); + + /** + * @param p Port + * @return 64-bit integer suitable for serialization + */ + static inline uint64_t portToInt(const Port p) throw() { return (uint64_t)p; } + + /** + * @param p 64-bit integer from serialized representation + * @return Port suitable for use in code + */ + static inline Port intToPort(const uint64_t p) throw() { return (Port)p; } + +private: + const RuntimeEnvironment *_r; + + static void _CBudpSocketPacketHandler(UdpSocket *sock,void *arg,const InetAddress &from,const void *data,unsigned int len); + + enum DemarcPortType + { + PORT_TYPE_UDP_SOCKET_V4 = 1, + PORT_TYPE_UDP_SOCKET_V6 = 2, + PORT_TYPE_LOCAL_ETHERNET = 3, + PORT_TYPE_RELAY_TUNNEL = 4 + }; + + // Variant holding instances of UdpSocket, etc. + struct DemarcPortObj + { + Demarc::Port port; + Demarc *parent; + void *obj; + DemarcPortType type; + }; + + std::map< Port,DemarcPortObj > _ports; + Mutex _ports_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/EllipticCurveKey.hpp b/node/EllipticCurveKey.hpp new file mode 100644 index 00000000..5a7b895f --- /dev/null +++ b/node/EllipticCurveKey.hpp @@ -0,0 +1,124 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ELLIPTICCURVEKEY_H +#define _ZT_ELLIPTICCURVEKEY_H + +#include <string> +#include <algorithm> +#include <string.h> +#include "Utils.hpp" + +/** + * Key type ID for identifying our use of NIST-P-521 + * + * If in the future other types of keys are supported (post-quantum crypto?) + * then we'll need a key type 2, etc. When keys are stored in the database + * they are prefixed by this key type ID byte. + */ +#define ZT_KEY_TYPE 1 + +#define ZT_EC_OPENSSL_CURVE NID_secp521r1 +#define ZT_EC_CURVE_NAME "NIST-P-521" +#define ZT_EC_PRIME_BYTES 66 +#define ZT_EC_PUBLIC_KEY_BYTES (ZT_EC_PRIME_BYTES + 1) +#define ZT_EC_PRIVATE_KEY_BYTES ZT_EC_PRIME_BYTES +#define ZT_EC_MAX_BYTES ZT_EC_PUBLIC_KEY_BYTES + +namespace ZeroTier { + +class EllipticCurveKeyPair; + +/** + * An elliptic curve public or private key + */ +class EllipticCurveKey +{ + friend class EllipticCurveKeyPair; + +public: + EllipticCurveKey() + throw() : + _bytes(0) + { + } + + EllipticCurveKey(const void *data,unsigned int len) + throw() + { + if (len <= ZT_EC_MAX_BYTES) { + _bytes = len; + memcpy(_key,data,len); + } else _bytes = 0; + } + + EllipticCurveKey(const EllipticCurveKey &k) + throw() + { + _bytes = k._bytes; + memcpy(_key,k._key,_bytes); + } + + inline EllipticCurveKey &operator=(const EllipticCurveKey &k) + throw() + { + _bytes = k._bytes; + memcpy(_key,k._key,_bytes); + return *this; + } + + inline void set(const void *data,unsigned int len) + throw() + { + if (len <= ZT_EC_MAX_BYTES) { + _bytes = len; + memcpy(_key,data,len); + } else _bytes = 0; + } + + inline const unsigned char *data() const throw() { return _key; } + inline unsigned int size() const throw() { return _bytes; } + inline std::string toHex() const throw() { return Utils::hex(_key,_bytes); } + + inline unsigned char operator[](const unsigned int i) const throw() { return _key[i]; } + + inline bool operator==(const EllipticCurveKey &k) const throw() { return ((_bytes == k._bytes)&&(!memcmp(_key,k._key,_bytes))); } + inline bool operator<(const EllipticCurveKey &k) const throw() { return std::lexicographical_compare(_key,&_key[_bytes],k._key,&k._key[k._bytes]); } + inline bool operator!=(const EllipticCurveKey &k) const throw() { return !(*this == k); } + inline bool operator>(const EllipticCurveKey &k) const throw() { return (k < *this); } + inline bool operator<=(const EllipticCurveKey &k) const throw() { return !(k < *this); } + inline bool operator>=(const EllipticCurveKey &k) const throw() { return !(*this < k); } + +private: + unsigned int _bytes; + unsigned char _key[ZT_EC_MAX_BYTES]; +}; + +} // namespace ZeroTier + +#endif + diff --git a/node/EllipticCurveKeyPair.cpp b/node/EllipticCurveKeyPair.cpp new file mode 100644 index 00000000..bed0725e --- /dev/null +++ b/node/EllipticCurveKeyPair.cpp @@ -0,0 +1,374 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <iostream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <openssl/bn.h> +#include <openssl/obj_mac.h> +#include <openssl/rand.h> +#include <openssl/ec.h> +#include <openssl/ecdh.h> +#include <openssl/ecdsa.h> +#include <openssl/sha.h> + +#include "EllipticCurveKey.hpp" +#include "EllipticCurveKeyPair.hpp" + +namespace ZeroTier { + +class _EC_Group +{ +public: + _EC_Group() + { + g = EC_GROUP_new_by_curve_name(ZT_EC_OPENSSL_CURVE); + } + ~_EC_Group() {} + EC_GROUP *g; +}; +static _EC_Group ZT_EC_GROUP; + +/* Key derivation function */ +static void *_zt_EC_KDF(const void *in,size_t inlen,void *out,size_t *outlen) +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)in,inlen); + SHA256_Final(dig,&sha); + for(unsigned long i=0,k=0;i<(unsigned long)*outlen;) { + if (k == SHA256_DIGEST_LENGTH) { + k = 0; + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)in,inlen); + SHA256_Update(&sha,dig,SHA256_DIGEST_LENGTH); + SHA256_Final(dig,&sha); + } + ((unsigned char *)out)[i++] = dig[k++]; + } + + return out; +} + +EllipticCurveKeyPair::EllipticCurveKeyPair() : + _pub(), + _priv(), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::EllipticCurveKeyPair(const EllipticCurveKeyPair &pair) : + _pub(pair._pub), + _priv(pair._priv), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::EllipticCurveKeyPair(const EllipticCurveKey &pubk,const EllipticCurveKey &privk) : + _pub(pubk), + _priv(privk), + _internal_key((void *)0) +{ +} + +EllipticCurveKeyPair::~EllipticCurveKeyPair() +{ + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); +} + +const EllipticCurveKeyPair &EllipticCurveKeyPair::operator=(const EllipticCurveKeyPair &pair) +{ + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _pub = pair._pub; + _priv = pair._priv; + _internal_key = (void *)0; + return *this; +} + +bool EllipticCurveKeyPair::generate() +{ + unsigned char tmp[16384]; + EC_KEY *key; + int len; + + // Make sure OpenSSL libcrypto has sufficient randomness (on most + // platforms it auto-seeds, so this is a sanity check). + if (!RAND_status()) { +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + FILE *rf = fopen("/dev/urandom","r"); + if (rf) { + fread(tmp,sizeof(tmp),1,rf); + fclose(rf); + } else { + fprintf(stderr,"WARNING: cannot open /dev/urandom\n"); + for(unsigned int i=0;i<sizeof(tmp);++i) + tmp[i] = (unsigned char)(rand() >> 3); + } + RAND_seed(tmp,sizeof(tmp)); +#else +#ifdef _WIN32 + error need win32; +#else + error; +#endif +#endif + } + + key = EC_KEY_new(); + if (!key) return false; + + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) { + EC_KEY_free(key); + return false; + } + + if (!EC_KEY_generate_key(key)) { + EC_KEY_free(key); + return false; + } + + memset(_priv._key,0,sizeof(_priv._key)); + len = BN_num_bytes(EC_KEY_get0_private_key(key)); + if ((len > ZT_EC_PRIME_BYTES)||(len < 0)) { + EC_KEY_free(key); + return false; + } + BN_bn2bin(EC_KEY_get0_private_key(key),&(_priv._key[ZT_EC_PRIME_BYTES - len])); + _priv._bytes = ZT_EC_PRIME_BYTES; + + memset(_pub._key,0,sizeof(_pub._key)); + len = EC_POINT_point2oct(ZT_EC_GROUP.g,EC_KEY_get0_public_key(key),POINT_CONVERSION_COMPRESSED,_pub._key,sizeof(_pub._key),0); + if (len != ZT_EC_PUBLIC_KEY_BYTES) { + EC_KEY_free(key); + return false; + } + _pub._bytes = ZT_EC_PUBLIC_KEY_BYTES; + + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _internal_key = key; + + return true; +} + +bool EllipticCurveKeyPair::agree(const EllipticCurveKey &theirPublicKey,unsigned char *agreedUponKey,unsigned int agreedUponKeyLength) const +{ + if (theirPublicKey._bytes != ZT_EC_PUBLIC_KEY_BYTES) + return false; + + if (!_internal_key) { + if (!(const_cast <EllipticCurveKeyPair *> (this))->initInternalKey()) + return false; + } + + EC_POINT *pub = EC_POINT_new(ZT_EC_GROUP.g); + if (!pub) + return false; + EC_POINT_oct2point(ZT_EC_GROUP.g,pub,theirPublicKey._key,ZT_EC_PUBLIC_KEY_BYTES,0); + + int i = ECDH_compute_key(agreedUponKey,agreedUponKeyLength,pub,(EC_KEY *)_internal_key,&_zt_EC_KDF); + EC_POINT_free(pub); + + return (i == (int)agreedUponKeyLength); +} + +std::string EllipticCurveKeyPair::sign(const void *sha256) const +{ + unsigned char buf[256]; + std::string sigbin; + + if (!_internal_key) { + if (!(const_cast <EllipticCurveKeyPair *> (this))->initInternalKey()) + return std::string(); + } + + ECDSA_SIG *sig = ECDSA_do_sign((const unsigned char *)sha256,SHA256_DIGEST_LENGTH,(EC_KEY *)_internal_key); + if (!sig) + return std::string(); + + int rlen = BN_num_bytes(sig->r); + if ((rlen > 255)||(rlen <= 0)) { + ECDSA_SIG_free(sig); + return std::string(); + } + sigbin.push_back((char)rlen); + BN_bn2bin(sig->r,buf); + sigbin.append((const char *)buf,rlen); + + int slen = BN_num_bytes(sig->s); + if ((slen > 255)||(slen <= 0)) { + ECDSA_SIG_free(sig); + return std::string(); + } + sigbin.push_back((char)slen); + BN_bn2bin(sig->s,buf); + sigbin.append((const char *)buf,slen); + + ECDSA_SIG_free(sig); + + return sigbin; +} + +std::string EllipticCurveKeyPair::sign(const void *data,unsigned int len) const +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)data,len); + SHA256_Final(dig,&sha); + + return sign(dig); +} + +bool EllipticCurveKeyPair::verify(const void *sha256,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen) +{ + bool result = false; + ECDSA_SIG *sig = (ECDSA_SIG *)0; + EC_POINT *pub = (EC_POINT *)0; + EC_KEY *key = (EC_KEY *)0; + int rlen,slen; + + if (!siglen) + goto verify_sig_return; + rlen = ((const unsigned char *)sigbytes)[0]; + if (!rlen) + goto verify_sig_return; + if (siglen < (unsigned int)(rlen + 2)) + goto verify_sig_return; + slen = ((const unsigned char *)sigbytes)[rlen + 1]; + if (!slen) + goto verify_sig_return; + if (siglen < (unsigned int)(rlen + slen + 2)) + goto verify_sig_return; + + sig = ECDSA_SIG_new(); + if (!sig) + goto verify_sig_return; + + BN_bin2bn((const unsigned char *)sigbytes + 1,rlen,sig->r); + BN_bin2bn((const unsigned char *)sigbytes + (1 + rlen + 1),slen,sig->s); + + pub = EC_POINT_new(ZT_EC_GROUP.g); + if (!pub) + goto verify_sig_return; + EC_POINT_oct2point(ZT_EC_GROUP.g,pub,pk._key,ZT_EC_PUBLIC_KEY_BYTES,0); + + key = EC_KEY_new(); + if (!key) + goto verify_sig_return; + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) + goto verify_sig_return; + EC_KEY_set_public_key(key,pub); + + result = (ECDSA_do_verify((const unsigned char *)sha256,SHA256_DIGEST_LENGTH,sig,key) == 1); + +verify_sig_return: + if (key) + EC_KEY_free(key); + if (pub) + EC_POINT_free(pub); + if (sig) + ECDSA_SIG_free(sig); + + return result; +} + +bool EllipticCurveKeyPair::verify(const void *data,unsigned int len,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen) +{ + SHA256_CTX sha; + unsigned char dig[SHA256_DIGEST_LENGTH]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)data,len); + SHA256_Final(dig,&sha); + + return verify(dig,pk,sigbytes,siglen); +} + +bool EllipticCurveKeyPair::initInternalKey() +{ + EC_KEY *key; + EC_POINT *kxy; + BIGNUM *pn; + + if (_priv._bytes != ZT_EC_PRIME_BYTES) return false; + if (_pub._bytes != ZT_EC_PUBLIC_KEY_BYTES) return false; + + key = EC_KEY_new(); + if (!key) return false; + + if (!EC_KEY_set_group(key,ZT_EC_GROUP.g)) { + EC_KEY_free(key); + return false; + } + + pn = BN_new(); + if (!pn) { + EC_KEY_free(key); + return false; + } + if (!BN_bin2bn(_priv._key,ZT_EC_PRIME_BYTES,pn)) { + BN_free(pn); + EC_KEY_free(key); + return false; + } + if (!EC_KEY_set_private_key(key,pn)) { + BN_free(pn); + EC_KEY_free(key); + return false; + } + BN_free(pn); + + kxy = EC_POINT_new(ZT_EC_GROUP.g); + if (!kxy) { + EC_KEY_free(key); + return false; + } + EC_POINT_oct2point(ZT_EC_GROUP.g,kxy,_pub._key,ZT_EC_PUBLIC_KEY_BYTES,0); + if (!EC_KEY_set_public_key(key,kxy)) { + EC_POINT_free(kxy); + EC_KEY_free(key); + return false; + } + EC_POINT_free(kxy); + + if (_internal_key) + EC_KEY_free((EC_KEY *)_internal_key); + _internal_key = key; + + return true; +} + +} // namespace ZeroTier + diff --git a/node/EllipticCurveKeyPair.hpp b/node/EllipticCurveKeyPair.hpp new file mode 100644 index 00000000..2649f4c4 --- /dev/null +++ b/node/EllipticCurveKeyPair.hpp @@ -0,0 +1,128 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ELLIPTICCURVEKEYPAIR_HPP +#define _ZT_ELLIPTICCURVEKEYPAIR_HPP + +#include <string> +#include "EllipticCurveKey.hpp" + +namespace ZeroTier { + +/** + * An elliptic curve key pair supporting generation and key agreement + */ +class EllipticCurveKeyPair +{ +public: + EllipticCurveKeyPair(); + EllipticCurveKeyPair(const EllipticCurveKeyPair &pair); + EllipticCurveKeyPair(const EllipticCurveKey &pubk,const EllipticCurveKey &privk); + ~EllipticCurveKeyPair(); + + const EllipticCurveKeyPair &operator=(const EllipticCurveKeyPair &pair); + + /** + * Fill this structure with a newly generated public/private key pair + * + * @return True if key generation is successful + */ + bool generate(); + + /** + * Perform elliptic curve key agreement + * + * @param theirPublicKey Remote side's public key + * @param agreedUponKey Buffer to fill with agreed-upon symmetric key + * @param agreedUponKeyLength Number of bytes to generate + * @return True if key agreement is successful + */ + bool agree(const EllipticCurveKey &theirPublicKey,unsigned char *agreedUponKey,unsigned int agreedUponKeyLength) const; + + /** + * Sign a SHA256 hash + * + * @param sha256 Pointer to 256-bit / 32-byte SHA hash to sign + * @return ECDSA signature (r and s in binary format, each prefixed by an 8-bit size) + */ + std::string sign(const void *sha256) const; + + /** + * Sign something with this pair's private key, computing its hash first + * + * @param data Data to hash and sign + * @param len Length of data + * @return Signature bytes + */ + std::string sign(const void *data,unsigned int len) const; + + /** + * Verify a signature + * + * @param sha256 Pointer to 256-bit / 32-byte SHA hash to verify + * @param pk Public key to verify against + * @param sigbytes Signature bytes + * @param siglen Length of signature + */ + static bool verify(const void *sha256,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen); + + /** + * Verify a signature + * + * @param data Data to verify + * @param len Length of data + * @param pk Public key to verify against + * @param sigbytes Signature bytes + * @param siglen Length of signature + */ + static bool verify(const void *data,unsigned int len,const EllipticCurveKey &pk,const void *sigbytes,unsigned int siglen); + + inline bool operator==(const EllipticCurveKeyPair &kp) const + throw() + { + return ((_pub == kp._pub)&&(_priv == kp._priv)); + } + inline bool operator!=(const EllipticCurveKeyPair &kp) const + throw() + { + return ((_pub != kp._pub)||(_priv != kp._priv)); + } + + inline const EllipticCurveKey &pub() const throw() { return _pub; } + inline const EllipticCurveKey &priv() const throw() { return _priv; } + +private: + bool initInternalKey(); + + EllipticCurveKey _pub; + EllipticCurveKey _priv; + void *_internal_key; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/EthernetTap.cpp b/node/EthernetTap.cpp new file mode 100644 index 00000000..e50e48d3 --- /dev/null +++ b/node/EthernetTap.cpp @@ -0,0 +1,678 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <iostream> +#include <string> +#include "EthernetTap.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Mutex.hpp" + +/* ======================================================================== */ +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +/* ======================================================================== */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <net/if_arp.h> +#include <arpa/inet.h> + +#include <linux/if.h> +#include <linux/if_tun.h> +#include <linux/if_addr.h> +#include <linux/if_ether.h> + +#define ZT_ETHERTAP_IP_COMMAND "/sbin/ip" +#define ZT_ETHERTAP_SYSCTL_COMMAND "/sbin/sysctl" + +namespace ZeroTier { + +static Mutex __tapCreateLock; + +EthernetTap::EthernetTap(const RuntimeEnvironment *renv,const MAC &mac,unsigned int mtu) + throw(std::runtime_error) : + _mac(mac), + _mtu(mtu), + _r(renv), + _putBuf((unsigned char *)0), + _getBuf((unsigned char *)0), + _fd(0), + _isReading(false) +{ + char procpath[128]; + Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally + + _fd = ::open("/dev/net/tun",O_RDWR); + if (_fd <= 0) + throw std::runtime_error("could not open TUN/TAP device"); + + struct ifreq ifr; + memset(&ifr,0,sizeof(ifr)); + + { // pick an unused device name + int devno = 0; + struct stat sbuf; + do { + sprintf(ifr.ifr_name,"zt%d",devno++); + sprintf(procpath,"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); + } while (stat(procpath,&sbuf) == 0); + } + + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + if (ioctl(_fd,TUNSETIFF,(void *)&ifr) < 0) { + ::close(_fd); + throw std::runtime_error("unable to configure TUN/TAP device for TAP operation"); + } + + strcpy(_dev,ifr.ifr_name); + + ioctl(_fd,TUNSETPERSIST,0); // valgrind may generate a false alarm here + + // Open an arbitrary socket to talk to netlink + int sock = socket(AF_INET,SOCK_DGRAM,0); + if (sock <= 0) { + ::close(_fd); + throw std::runtime_error("unable to open netlink socket"); + } + + // Set MAC address + ifr.ifr_ifru.ifru_hwaddr.sa_family = ARPHRD_ETHER; + memcpy(ifr.ifr_ifru.ifru_hwaddr.sa_data,mac.data,6); + if (ioctl(sock,SIOCSIFHWADDR,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to configure TAP hardware (MAC) address"); + return; + } + + // Set MTU + ifr.ifr_ifru.ifru_mtu = (int)mtu; + if (ioctl(sock,SIOCSIFMTU,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to configure TAP MTU"); + } + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + /* Bring interface up */ + if (ioctl(sock,SIOCGIFFLAGS,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to get TAP interface flags"); + } + ifr.ifr_flags |= IFF_UP; + if (ioctl(sock,SIOCSIFFLAGS,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to set TAP interface flags"); + } + + ::close(sock); + + _putBuf = new unsigned char[((mtu + 16) * 2)]; + _getBuf = _putBuf + (mtu + 16); + + TRACE("tap %s created",_dev); +} + +EthernetTap::~EthernetTap() +{ + this->close(); + delete [] _putBuf; +} + +static bool ___removeIp(const char *_dev,std::set<InetAddress> &_ips,const InetAddress &ip) +{ + long cpid; + if ((cpid = (long)fork()) == 0) { + execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","del",ip.toString().c_str(),"dev",_dev,(const char *)0); + exit(1); /* not reached unless exec fails */ + } else { + int exitcode = 1; + waitpid(cpid,&exitcode,0); + if (exitcode == 0) { + _ips.erase(ip); + return true; + } else return false; + } +} + +bool EthernetTap::addIP(const InetAddress &ip) +{ + Mutex::Lock _l(_ips_m); + + if (!ip.isValid()) + return false; + if (_ips.count(ip) > 0) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::set<InetAddress>::iterator i(_ips.begin());i!=_ips.end();++i) { + if (i->ipsEqual(ip)) { + ___removeIp(_dev,_ips,*i); + break; + } + } + + int cpid; + if ((cpid = (int)fork()) == 0) { + execl(ZT_ETHERTAP_IP_COMMAND,ZT_ETHERTAP_IP_COMMAND,"addr","add",ip.toString().c_str(),"dev",_dev,(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode == 0) { + _ips.insert(ip); + return true; + } else return false; + } + + return false; +} + +bool EthernetTap::removeIP(const InetAddress &ip) +{ + Mutex::Lock _l(_ips_m); + if (_ips.count(ip) > 0) + return ___removeIp(_dev,_ips,ip); + return false; +} + +void EthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + if ((_fd > 0)&&(len <= _mtu)) { + for(int i=0;i<6;++i) + _putBuf[i] = to.data[i]; + for(int i=0;i<6;++i) + _putBuf[i+6] = from.data[i]; + *((uint16_t *)(_putBuf + 12)) = htons((uint16_t)etherType); + memcpy(_putBuf + 14,data,len); + ::write(_fd,_putBuf,len + 14); + } +} + +unsigned int EthernetTap::get(MAC &from,MAC &to,unsigned int ðerType,void *buf) +{ + for(;;) { + if (_fd > 0) { + _isReading_m.lock(); + _isReading = true; + _isReadingThreadId = pthread_self(); + _isReading_m.unlock(); + + int n = (int)::read(_fd,_getBuf,_mtu + 14); + + _isReading_m.lock(); + _isReading = false; + _isReading_m.unlock(); + + if (n > 14) { + for(int i=0;i<6;++i) + to.data[i] = _getBuf[i]; + for(int i=0;i<6;++i) + from.data[i] = _getBuf[i + 6]; + etherType = ntohs(((uint16_t *)_getBuf)[6]); + n -= 14; + memcpy(buf,_getBuf + 14,n); + return (unsigned int)n; + } else if (n < 0) { + if (_fd <= 0) + break; + else if ((errno == EINTR)||(errno == ETIMEDOUT)) + continue; + else { + TRACE("unexpected error reading from tap: %s",strerror(errno)); + ::close(_fd); + _fd = 0; + break; + } + } else { + TRACE("incomplete read from tap: %d bytes",n); + continue; + } + } + } + return 0; +} + +std::string EthernetTap::deviceName() +{ + return std::string(_dev); +} + +bool EthernetTap::open() const +{ + return (_fd > 0); +} + +void EthernetTap::close() +{ + Mutex::Lock _l(__tapCreateLock); // also prevent create during close() + if (_fd > 0) { + int f = _fd; + _fd = 0; + ::close(f); + + _isReading_m.lock(); + if (_isReading) + pthread_kill(_isReadingThreadId,SIGUSR2); + _isReading_m.unlock(); + } +} + +bool EthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + char *ptr,*ptr2; + unsigned char mac[6]; + std::set<MulticastGroup> newGroups; + + int fd = ::open("/proc/net/dev_mcast",O_RDONLY); + if (fd > 0) { + char buf[131072]; + int n = (int)::read(fd,buf,sizeof(buf)); + if ((n > 0)&&(n < (int)sizeof(buf))) { + buf[n] = (char)0; + for(char *l=strtok_r(buf,"\r\n",&ptr);(l);l=strtok_r((char *)0,"\r\n",&ptr)) { + int fno = 0; + char *devname = (char *)0; + char *mcastmac = (char *)0; + for(char *f=strtok_r(l," \t",&ptr2);(f);f=strtok_r((char *)0," \t",&ptr2)) { + if (fno == 1) + devname = f; + else if (fno == 4) + mcastmac = f; + ++fno; + } + if ((devname)&&(!strcmp(devname,_dev))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) + newGroups.insert(MulticastGroup(MAC(mac),0)); + } + } + ::close(fd); + } + + { + Mutex::Lock _l(_ips_m); + for(std::set<InetAddress>::const_iterator i(_ips.begin());i!=_ips.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if (!newGroups.count(*mg)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +} // namespace ZeroTier + +/* ======================================================================== */ +#elif defined(__APPLE__) /* ----------------------------------------------- */ +/* ======================================================================== */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <sys/sysctl.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/route.h> +#include <net/if_dl.h> +#include <ifaddrs.h> + +#define ZT_ETHERTAP_IFCONFIG "/sbin/ifconfig" +#define ZT_MAC_KEXTLOAD "/sbin/kextload" +#define ZT_MAC_IPCONFIG "/usr/sbin/ipconfig" + +namespace ZeroTier { + +static Mutex __tapCreateLock; + +EthernetTap::EthernetTap(const RuntimeEnvironment *renv,const MAC &mac,unsigned int mtu) + throw(std::runtime_error) : + _mac(mac), + _mtu(mtu), + _r(renv), + _putBuf((unsigned char *)0), + _getBuf((unsigned char *)0), + _fd(0), + _isReading(false) +{ + char devpath[64],ethaddr[64],mtustr[16]; + struct stat tmp; + Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally + + // Check for existence of ZT tap devices, try to load module if not there + if (stat("/dev/zt0",&tmp)) { + int kextpid; + char tmp[4096]; + strcpy(tmp,_r->homePath.c_str()); + if ((kextpid = (int)fork()) == 0) { + chdir(tmp); + execl(ZT_MAC_KEXTLOAD,ZT_MAC_KEXTLOAD,"-q","-repository",tmp,"tap.kext",(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(kextpid,&exitcode,0); + usleep(500); + } + } + if (stat("/dev/zt0",&tmp)) + throw std::runtime_error("/dev/zt# tap devices do not exist and unable to load kernel extension"); + + // Open the first available device (ones in use will fail with resource busy) + for(int i=0;i<256;++i) { + sprintf(devpath,"/dev/zt%d",i); + if (stat(devpath,&tmp)) + throw std::runtime_error("no more TAP devices available"); + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + sprintf(_dev,"zt%d",i); + break; + } + } + if (_fd <= 0) + throw std::runtime_error("unable to open TAP device or no more devices available"); + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + sprintf(ethaddr,"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + sprintf(mtustr,"%u",mtu); + + // Configure MAC address and MTU, bring interface up + int cpid; + if ((cpid = (int)fork()) == 0) { + execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"lladdr",ethaddr,"mtu",mtustr,"up",(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + // OSX seems to require that IPv6 be turned on on tap devices + if ((cpid = (int)fork()) == 0) { + execl(ZT_MAC_IPCONFIG,ZT_MAC_IPCONFIG,"set",_dev,"AUTOMATIC-V6",(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + _putBuf = new unsigned char[((mtu + 14) * 2)]; + _getBuf = _putBuf + (mtu + 14); +} + +EthernetTap::~EthernetTap() +{ + this->close(); + delete [] _putBuf; +} + +static bool ___removeIp(const char *_dev,std::set<InetAddress> &_ips,const InetAddress &ip) +{ + int cpid; + if ((cpid = (int)fork()) == 0) { + execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode == 0) { + _ips.erase(ip); + return true; + } else return false; + } +} + +bool EthernetTap::addIP(const InetAddress &ip) +{ + Mutex::Lock _l(_ips_m); + + if (!ip) + return false; + if (_ips.count(ip) > 0) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::set<InetAddress>::iterator i(_ips.begin());i!=_ips.end();++i) { + if (i->ipsEqual(ip)) { + ___removeIp(_dev,_ips,*i); + break; + } + } + + int cpid; + if ((cpid = (int)fork()) == 0) { + execl(ZT_ETHERTAP_IFCONFIG,ZT_ETHERTAP_IFCONFIG,_dev,ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + exit(-1); + } else { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + if (exitcode == 0) { + _ips.insert(ip); + return true; + } + } + + return false; +} + +bool EthernetTap::removeIP(const InetAddress &ip) +{ + Mutex::Lock _l(_ips_m); + if (_ips.count(ip) > 0) + return ___removeIp(_dev,_ips,ip); + return false; +} + +void EthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + if ((_fd > 0)&&(len <= _mtu)) { + for(int i=0;i<6;++i) + _putBuf[i] = to.data[i]; + for(int i=0;i<6;++i) + _putBuf[i+6] = from.data[i]; + *((uint16_t *)(_putBuf + 12)) = htons((uint16_t)etherType); + memcpy(_putBuf + 14,data,len); + len += 14; + int n = (int)::write(_fd,_putBuf,len); + if (n <= 0) { + LOG("error writing packet to Ethernet tap device: %s",strerror(errno)); + } else if (n != (int)len) { + // Saw this gremlin once, so log it if we see it again... OSX tap + // or something seems to have goofy issues with certain MTUs. + LOG("WARNING: Apple gremlin: tap write() wrote %d of %u bytes of frame",n,len); + } + } +} + +unsigned int EthernetTap::get(MAC &from,MAC &to,unsigned int ðerType,void *buf) +{ + for(;;) { + if (_fd > 0) { + _isReading_m.lock(); + _isReading = true; + _isReadingThreadId = pthread_self(); + _isReading_m.unlock(); + + int n = (int)::read(_fd,_getBuf,_mtu + 14); + + _isReading_m.lock(); + _isReading = false; + _isReading_m.unlock(); + + if (n > 14) { + for(int i=0;i<6;++i) + to.data[i] = _getBuf[i]; + for(int i=0;i<6;++i) + from.data[i] = _getBuf[i + 6]; + etherType = ntohs(((uint16_t *)_getBuf)[6]); + n -= 14; + memcpy(buf,_getBuf + 14,n); + return (unsigned int)n; + } else if (n < 0) { + if (_fd <= 0) + break; + else if ((errno == EINTR)||(errno == ETIMEDOUT)) + continue; + else { + TRACE("unexpected error reading from tap: %s",strerror(errno)); + ::close(_fd); + _fd = 0; + break; + } + } else { + TRACE("incomplete read from tap: %d bytes",n); + continue; + } + } + } + return 0; +} + +std::string EthernetTap::deviceName() +{ + return std::string(_dev); +} + +bool EthernetTap::open() const +{ + return (_fd > 0); +} + +void EthernetTap::close() +{ + Mutex::Lock _l(__tapCreateLock); // also prevent create during close() + if (_fd > 0) { + int f = _fd; + _fd = 0; + ::close(f); + + _isReading_m.lock(); + if (_isReading) + pthread_kill(_isReadingThreadId,SIGUSR2); + _isReading_m.unlock(); + } +} + +bool EthernetTap::updateMulticastGroups(std::set<MulticastGroup> &groups) +{ + std::set<MulticastGroup> newGroups; + struct ifmaddrs *ifmap = (struct ifmaddrs *)0; + if (!getifmaddrs(&ifmap)) { + struct ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= sizeof(_dev))&&(!memcmp(_dev,in->sdl_data,in->sdl_nlen))) + newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen),0)); + } + p = p->ifma_next; + } + freeifmaddrs(ifmap); + } + + { + Mutex::Lock _l(_ips_m); + for(std::set<InetAddress>::const_iterator i(_ips.begin());i!=_ips.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set<MulticastGroup>::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set<MulticastGroup>::iterator mg(groups.begin());mg!=groups.end();) { + if (!newGroups.count(*mg)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} + +} // namespace ZeroTier + +/* ======================================================================== */ +#elif defined(_WIN32) /* -------------------------------------------------- */ +/* ======================================================================== */ + +/* ======================================================================== */ +#endif +/* ======================================================================== */ diff --git a/node/EthernetTap.hpp b/node/EthernetTap.hpp new file mode 100644 index 00000000..bf1d0eef --- /dev/null +++ b/node/EthernetTap.hpp @@ -0,0 +1,199 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_ETHERNETTAP_HPP +#define _ZT_ETHERNETTAP_HPP + +#include <stdio.h> +#include <stdlib.h> +#include <map> +#include <list> +#include <vector> +#include <set> +#include <string> +#include <stdexcept> +#include "Array.hpp" +#include "Utils.hpp" +#include "InetAddress.hpp" +#include "NonCopyable.hpp" +#include "MAC.hpp" +#include "Constants.hpp" +#include "Mutex.hpp" +#include "MulticastGroup.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * System ethernet tap device + */ +class EthernetTap : NonCopyable +{ +public: + /** + * Construct a new TAP device + * + * @param renv Runtime environment + * @param mac MAC address of device + * @param mtu MTU of device + * @throws std::runtime_error Unable to allocate device + */ + EthernetTap(const RuntimeEnvironment *renv,const MAC &mac,unsigned int mtu) + throw(std::runtime_error); + + ~EthernetTap(); + + /** + * @return MAC address of this interface + */ + inline const MAC &mac() const throw() { return _mac; } + + /** + * @return MTU of this interface + */ + inline unsigned int mtu() const throw() { return _mtu; } + + /** + * Add an IP to this interface + * + * @param ip IP and netmask (netmask stored in port field) + * @return True if IP added successfully + */ + bool addIP(const InetAddress &ip); + + /** + * Remove an IP from this interface + * + * @param ip IP and netmask (netmask stored in port field) + * @return True if IP removed successfully + */ + bool removeIP(const InetAddress &ip); + + /** + * @return Set of IP addresses / netmasks + */ + inline std::set<InetAddress> ips() const + { + Mutex::Lock _l(_ips_m); + return _ips; + } + + /** + * Set this tap's IP addresses to exactly this set of IPs + * + * New IPs are created, ones not in this list are removed. + * + * @param ips IP addresses with netmask in port field + */ + inline void setIps(const std::set<InetAddress> &allIps) + { + for(std::set<InetAddress>::iterator i(allIps.begin());i!=allIps.end();++i) + addIP(*i); + std::set<InetAddress> myIps(ips()); + for(std::set<InetAddress>::iterator i(myIps.begin());i!=myIps.end();++i) { + if (!allIps.count(*i)) + removeIP(*i); + } + } + + /** + * Put a frame, making it available to the OS for processing + * + * @param from MAC address from which frame originated + * @param to MAC address of destination (typically MAC of tap itself) + * @param etherType Ethernet protocol ID + * @param data Frame payload + * @param len Length of frame + */ + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + + /** + * Get the next packet from the interface, blocking if none is available. + * + * @param from Filled with MAC address of source (normally our own) + * @param to Filled with MAC address of destination + * @param etherType Filled with Ethernet frame type + * @param buf Buffer to fill (must have room for MTU bytes) + * @return Number of bytes read or 0 if none + */ + unsigned int get(MAC &from,MAC &to,unsigned int ðerType,void *buf); + + /** + * @return OS-specific device or connection name + */ + std::string deviceName(); + + /** + * @return True if tap is open + */ + bool open() const; + + /** + * Close this tap, invalidating the object and causing get() to abort + */ + void close(); + + /** + * Fill or modify a set to contain multicast groups for this device + * + * This populates a set or, if already populated, modifies it to contain + * only multicast groups in which this device is interested. + * + * @param groups Set to modify in place + * @return True if set was changed since last call + */ + bool updateMulticastGroups(std::set<MulticastGroup> &groups); + +private: + const MAC _mac; + const unsigned int _mtu; + + const RuntimeEnvironment *_r; + + std::set<InetAddress> _ips; + Mutex _ips_m; + +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + + char _dev[16]; + unsigned char *_putBuf; + unsigned char *_getBuf; + int _fd; + + bool _isReading; + pthread_t _isReadingThreadId; + Mutex _isReading_m; + +#elif defined(_WIN32) /* -------------------------------------------------- */ + +#endif /* ----------------------------------------------------------------- */ +}; + +} // namespace ZeroTier + +#endif diff --git a/node/HMAC.cpp b/node/HMAC.cpp new file mode 100644 index 00000000..d8a756a9 --- /dev/null +++ b/node/HMAC.cpp @@ -0,0 +1,81 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "HMAC.hpp" + +#include <openssl/sha.h> + +namespace ZeroTier { + +void HMAC::sha256(const void *key,unsigned int klen,const void *message,unsigned int len,void *mac) + throw() +{ + union { + uint64_t q[12]; + uint8_t b[96]; + } key2,opad,ipad; + SHA256_CTX sha; + + if (klen == 32) { // this is what we use, so handle this quickly + key2.q[0] = ((const uint64_t *)key)[0]; + key2.q[1] = ((const uint64_t *)key)[1]; + key2.q[2] = ((const uint64_t *)key)[2]; + key2.q[3] = ((const uint64_t *)key)[3]; + key2.q[4] = 0ULL; + key2.q[5] = 0ULL; + key2.q[6] = 0ULL; + key2.q[7] = 0ULL; + } else { // for correctness and testing against test vectors + if (klen > 64) { + SHA256_Init(&sha); + SHA256_Update(&sha,key,klen); + SHA256_Final(key2.b,&sha); + klen = 32; + } else { + for(unsigned int i=0;i<klen;++i) + key2.b[i] = ((const uint8_t *)key)[i]; + } + while (klen < 64) + key2.b[klen++] = (uint8_t)0; + } + + for(unsigned int i=0;i<8;++i) + opad.q[i] = 0x5c5c5c5c5c5c5c5cULL ^ key2.q[i]; + for(unsigned int i=0;i<8;++i) + ipad.q[i] = 0x3636363636363636ULL ^ key2.q[i]; + + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)ipad.b,64); + SHA256_Update(&sha,(const unsigned char *)message,len); + SHA256_Final((unsigned char *)(opad.b + 64),&sha); + + SHA256_Init(&sha); + SHA256_Update(&sha,opad.b,96); + SHA256_Final((unsigned char *)mac,&sha); +} + +} // namespace ZeroTier diff --git a/node/HMAC.hpp b/node/HMAC.hpp new file mode 100644 index 00000000..f48c33c1 --- /dev/null +++ b/node/HMAC.hpp @@ -0,0 +1,55 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_HMAC_HPP +#define _ZT_HMAC_HPP + +#include <stdint.h> + +namespace ZeroTier { + +/** + * HMAC authenticator functions + */ +class HMAC +{ +public: + /** + * Compute HMAC-SHA256 + * + * @param key Key bytes + * @param klen Length of key + * @param len Length of message + * @param mac Buffer to receive 32-byte MAC + */ + static void sha256(const void *key,unsigned int klen,const void *message,unsigned int len,void *mac) + throw(); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Http.cpp b/node/Http.cpp new file mode 100644 index 00000000..6a79a974 --- /dev/null +++ b/node/Http.cpp @@ -0,0 +1,323 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <vector> +#include <set> +#include <list> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include "Http.hpp" +#include "Utils.hpp" +#include "InetAddress.hpp" + +static http_parser_settings _http_parser_settings; + +namespace ZeroTier { + +static bool _sendAll(int fd,const void *buf,unsigned int len) +{ + for(;;) { + int n = (int)::send(fd,buf,len,0); + if ((n < 0)&&(errno == EINTR)) + continue; + return (n == (int)len); + } +} + +const std::map<std::string,std::string> Http::EMPTY_HEADERS; + +Http::Request::Request( + Http::Method m, + const std::string &url, + const std::map<std::string,std::string> &rh, + const std::string &rb, + bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &), + void *arg) : + _url(url), + _requestHeaders(rh), + _handler(handler), + _arg(arg), + _method(m), + _fd(0) +{ + _http_parser_settings.on_message_begin = &Http::Request::_http_on_message_begin; + _http_parser_settings.on_url = &Http::Request::_http_on_url; + _http_parser_settings.on_status_complete = &Http::Request::_http_on_status_complete; + _http_parser_settings.on_header_field = &Http::Request::_http_on_header_field; + _http_parser_settings.on_header_value = &Http::Request::_http_on_header_value; + _http_parser_settings.on_headers_complete = &Http::Request::_http_on_headers_complete; + _http_parser_settings.on_body = &Http::Request::_http_on_body; + _http_parser_settings.on_message_complete = &Http::Request::_http_on_message_complete; + + start(); +} + +Http::Request::~Request() +{ + if (_fd > 0) + ::close(_fd); + join(); +} + +void Http::Request::main() + throw() +{ + char buf[131072]; + + try { + http_parser_init(&_parser,HTTP_RESPONSE); + _parser.data = this; + + http_parser_url urlParsed; + if (http_parser_parse_url(_url.c_str(),_url.length(),0,&urlParsed)) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL parse error"); + return; + } + if (!(urlParsed.field_set & (1 << UF_SCHEMA))) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL specifies no schema"); + return; + } + + std::string schema(_url.substr(urlParsed.field_data[UF_SCHEMA].off,urlParsed.field_data[UF_SCHEMA].len)); + + if (schema == "file") { + const std::string filePath(_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len)); + + uint64_t lm = Utils::getLastModified(filePath.c_str()); + if (lm) { + const std::map<std::string,std::string>::const_iterator ifModSince(_requestHeaders.find("If-Modified-Since")); + if ((ifModSince != _requestHeaders.end())&&(ifModSince->second.length())) { + uint64_t t64 = Utils::fromRfc1123(ifModSince->second); + if ((t64)&&(lm > t64)) { + suicidalThread = !_handler(this,_arg,_url,304,_responseHeaders,""); + return; + } + } + + if (Utils::readFile(filePath.c_str(),_responseBody)) { + _responseHeaders["Last-Modified"] = Utils::toRfc1123(lm); + suicidalThread = !_handler(this,_arg,_url,200,_responseHeaders,_responseBody); + return; + } + } + + suicidalThread = !_handler(this,_arg,_url,404,_responseHeaders,"file not found or not readable"); + return; + } else if (schema == "http") { + if (!(urlParsed.field_set & (1 << UF_HOST))) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL contains no host"); + return; + } + std::string host(_url.substr(urlParsed.field_data[UF_HOST].off,urlParsed.field_data[UF_HOST].len)); + + std::list<InetAddress> v4,v6; + { + struct addrinfo *res = (struct addrinfo *)0; + if (!getaddrinfo(host.c_str(),(const char *)0,(const struct addrinfo *)0,&res)) { + struct addrinfo *p = res; + do { + if (p->ai_family == AF_INET) + v4.push_back(InetAddress(p->ai_addr)); + else if (p->ai_family == AF_INET6) + v6.push_back(InetAddress(p->ai_addr)); + } while ((p = p->ai_next)); + freeaddrinfo(res); + } + } + + std::list<InetAddress> *addrList; + if (v4.empty()&&v6.empty()) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not find address for host in URL"); + return; + } else if (v4.empty()) { + addrList = &v6; + } else { + addrList = &v4; + } + InetAddress *addr; + { + addrList->sort(); + addrList->unique(); + unsigned int i = 0,k = 0; + k = Utils::randomInt<unsigned int>() % addrList->size(); + std::list<InetAddress>::iterator a(addrList->begin()); + while (i++ != k) ++a; + addr = &(*a); + } + + int remotePort = ((urlParsed.field_set & (1 << UF_PORT))&&(urlParsed.port)) ? (int)urlParsed.port : (int)80; + if ((remotePort <= 0)||(remotePort > 0xffff)) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL port out of range"); + return; + } + addr->setPort(remotePort); + + _fd = socket(addr->isV6() ? AF_INET6 : AF_INET,SOCK_STREAM,0); + if (_fd <= 0) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"could not open socket"); + return; + } + + for(;;) { + if (connect(_fd,addr->saddr(),addr->saddrLen())) { + if (errno == EINTR) + continue; + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"connection failed to remote host"); + return; + } else break; + } + + const char *mstr = "GET"; + switch(_method) { + case HTTP_METHOD_HEAD: mstr = "HEAD"; break; + default: break; + } + int mlen = (int)snprintf(buf,sizeof(buf),"%s %s HTTP/1.1\r\nAccept-Encoding: \r\nHost: %s\r\n",mstr,_url.substr(urlParsed.field_data[UF_PATH].off,urlParsed.field_data[UF_PATH].len).c_str(),host.c_str()); + if (mlen >= (int)sizeof(buf)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"URL too long"); + return; + } + if (!_sendAll(_fd,buf,mlen)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); + return; + } + + for(std::map<std::string,std::string>::const_iterator rh(_requestHeaders.begin());rh!=_requestHeaders.end();++rh) { + mlen = (int)snprintf(buf,sizeof(buf),"%s: %s\r\n",rh->first.c_str(),rh->second.c_str()); + if (mlen >= (int)sizeof(buf)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"header too long"); + return; + } + if (!_sendAll(_fd,buf,mlen)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); + return; + } + } + + if (!_sendAll(_fd,"\r\n",2)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"write error"); + return; + } + + _responseStatusCode = 0; + _messageComplete = false; + for(;;) { + mlen = (int)::recv(_fd,buf,sizeof(buf),0); + if (mlen < 0) { + if (errno != EINTR) + break; + else continue; + } + if (((int)http_parser_execute(&_parser,&_http_parser_settings,buf,mlen) != mlen)||(_parser.upgrade)) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"invalid HTTP response from server"); + return; + } + if (_messageComplete) { + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,_responseStatusCode,_responseHeaders,_responseBody); + return; + } + } + + ::close(_fd); _fd = 0; + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"empty HTTP response from server"); + return; + } else { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"only 'file' and 'http' methods are supported"); + return; + } + } catch ( ... ) { + suicidalThread = !_handler(this,_arg,_url,0,_responseHeaders,"unexpected exception retrieving URL"); + return; + } +} + +int Http::Request::_http_on_message_begin(http_parser *parser) +{ + return 0; +} +int Http::Request::_http_on_url(http_parser *parser,const char *data,size_t length) +{ + return 0; +} +int Http::Request::_http_on_status_complete(http_parser *parser) +{ + Http::Request *r = (Http::Request *)parser->data; + r->_responseStatusCode = parser->status_code; + return 0; +} +int Http::Request::_http_on_header_field(http_parser *parser,const char *data,size_t length) +{ + Http::Request *r = (Http::Request *)parser->data; + if ((r->_currentHeaderField.length())&&(r->_responseHeaders.find(r->_currentHeaderField) != r->_responseHeaders.end())) + r->_currentHeaderField.assign(""); + r->_currentHeaderField.append(data,length); + return 0; +} +int Http::Request::_http_on_header_value(http_parser *parser,const char *data,size_t length) +{ + Http::Request *r = (Http::Request *)parser->data; + if (r->_currentHeaderField.length()) + r->_responseHeaders[r->_currentHeaderField].append(data,length); + return 0; +} +int Http::Request::_http_on_headers_complete(http_parser *parser) +{ + Http::Request *r = (Http::Request *)parser->data; + return ((r->_method == Http::HTTP_METHOD_HEAD) ? 1 : 0); +} +int Http::Request::_http_on_body(http_parser *parser,const char *data,size_t length) +{ + Http::Request *r = (Http::Request *)parser->data; + r->_responseBody.append(data,length); + return 0; +} +int Http::Request::_http_on_message_complete(http_parser *parser) +{ + Http::Request *r = (Http::Request *)parser->data; + r->_messageComplete = true; + return 0; +} + +} // namespace ZeroTier diff --git a/node/Http.hpp b/node/Http.hpp new file mode 100644 index 00000000..a099b15d --- /dev/null +++ b/node/Http.hpp @@ -0,0 +1,129 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_HTTP_HPP +#define _ZT_HTTP_HPP + +#include <map> +#include <string> +#include <stdexcept> +#include "Thread.hpp" + +#include "../ext/http-parser/http_parser.h" + +namespace ZeroTier { + +class Http +{ +public: + /** + * HTTP request methods + */ + enum Method + { + HTTP_METHOD_GET, + HTTP_METHOD_HEAD + }; + + /** + * An empty headers map for convenience + */ + static const std::map<std::string,std::string> EMPTY_HEADERS; + + /** + * HTTP request + */ + class Request : protected Thread + { + public: + /** + * Create and issue an HTTP request + * + * The supplied handler is called when the request is + * complete or if an error occurs. A code of zero indicates + * that the server could not be reached, and a description + * of the error will be in 'body'. If the handler returns + * false the Request object deletes itself. Otherwise the + * object must be deleted by other code. + * + * @param m Request method + * @param url Destination URL + * @param rh Request headers + * @param rb Request body or empty string for none (currently unused) + * @param handler Request handler function + * @param arg First argument to request handler + */ + Request( + Http::Method m, + const std::string &url, + const std::map<std::string,std::string> &rh, + const std::string &rb, + bool (*handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &), + void *arg); + + /** + * Destruction cancels any in-progress request + */ + virtual ~Request(); + + protected: + virtual void main() + throw(); + + private: + // HTTP parser handlers + static int _http_on_message_begin(http_parser *parser); + static int _http_on_url(http_parser *parser,const char *data,size_t length); + static int _http_on_status_complete(http_parser *parser); + static int _http_on_header_field(http_parser *parser,const char *data,size_t length); + static int _http_on_header_value(http_parser *parser,const char *data,size_t length); + static int _http_on_headers_complete(http_parser *parser); + static int _http_on_body(http_parser *parser,const char *data,size_t length); + static int _http_on_message_complete(http_parser *parser); + + http_parser _parser; + std::string _url; + + std::map<std::string,std::string> _requestHeaders; + std::map<std::string,std::string> _responseHeaders; + + std::string _currentHeaderField; + std::string _responseBody; + + bool (*_handler)(Request *,void *,const std::string &,int,const std::map<std::string,std::string> &,const std::string &); + void *_arg; + + Http::Method _method; + int _responseStatusCode; + bool _messageComplete; + volatile int _fd; + }; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Identity.cpp b/node/Identity.cpp new file mode 100644 index 00000000..f16947a0 --- /dev/null +++ b/node/Identity.cpp @@ -0,0 +1,301 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <iostream> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <openssl/sha.h> + +#include "Identity.hpp" +#include "Salsa20.hpp" +#include "HMAC.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +void Identity::generate() +{ + delete [] _keyPair; + + // Generate key pair and derive address + do { + _keyPair = new EllipticCurveKeyPair(); + _keyPair->generate(); + _address = deriveAddress(_keyPair->pub().data(),_keyPair->pub().size()); + } while (_address.isReserved()); + _publicKey = _keyPair->pub(); + + // Sign address, key type, and public key with private key (with a zero + // byte between each field). Including this extra data means simply editing + // the address of an identity will be detected as its signature will be + // invalid. Of course, deep verification of address/key relationship is + // required to cover the more elaborate address claim jump attempt case. + SHA256_CTX sha; + unsigned char dig[32]; + unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0; + SHA256_Init(&sha); + SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,&idtype,1); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,_publicKey.data(),_publicKey.size()); + SHA256_Update(&sha,&zero,1); + SHA256_Final(dig,&sha); + _signature = _keyPair->sign(dig); +} + +bool Identity::locallyValidate(bool doAddressDerivationCheck) const +{ + SHA256_CTX sha; + unsigned char dig[32]; + unsigned char idtype = IDENTITY_TYPE_NIST_P_521,zero = 0; + SHA256_Init(&sha); + SHA256_Update(&sha,_address.data(),ZT_ADDRESS_LENGTH); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,&idtype,1); + SHA256_Update(&sha,&zero,1); + SHA256_Update(&sha,_publicKey.data(),_publicKey.size()); + SHA256_Update(&sha,&zero,1); + SHA256_Final(dig,&sha); + + return ((EllipticCurveKeyPair::verify(dig,_publicKey,_signature.data(),_signature.length()))&&((!doAddressDerivationCheck)||(deriveAddress(_publicKey.data(),_publicKey.size()) == _address))); +} + +std::string Identity::toString(bool includePrivate) const +{ + std::string r; + r.append(_address.toString()); + r.append(":1:"); // 1 == IDENTITY_TYPE_NIST_P_521 + r.append(Utils::base64Encode(_publicKey.data(),_publicKey.size())); + r.push_back(':'); + r.append(Utils::base64Encode(_signature.data(),_signature.length())); + if ((includePrivate)&&(_keyPair)) { + r.push_back(':'); + r.append(Utils::base64Encode(_keyPair->priv().data(),_keyPair->priv().size())); + } + return r; +} + +bool Identity::fromString(const char *str) +{ + delete _keyPair; + _keyPair = (EllipticCurveKeyPair *)0; + + std::vector<std::string> fields(Utils::split(Utils::trim(std::string(str)).c_str(),":","","")); + + if (fields.size() < 4) + return false; + + if (fields[1] != "1") + return false; // version mismatch + + std::string b(Utils::unhex(fields[0])); + if (b.length() != ZT_ADDRESS_LENGTH) + return false; + _address = b.data(); + + b = Utils::base64Decode(fields[2]); + if ((!b.length())||(b.length() > ZT_EC_MAX_BYTES)) + return false; + _publicKey.set(b.data(),b.length()); + + _signature = Utils::base64Decode(fields[3]); + if (!_signature.length()) + return false; + + if (fields.size() >= 5) { + b = Utils::base64Decode(fields[4]); + if ((!b.length())||(b.length() > ZT_EC_MAX_BYTES)) + return false; + _keyPair = new EllipticCurveKeyPair(_publicKey,EllipticCurveKey(b.data(),b.length())); + } + + return true; +} + +// These are core protocol parameters and can't be changed without a new +// identity type. +#define ZT_IDENTITY_DERIVEADDRESS_ROUNDS 4 +#define ZT_IDENTITY_DERIVEADDRESS_MEMORY 33554432 + +Address Identity::deriveAddress(const void *keyBytes,unsigned int keyLen) +{ + unsigned char dig[32]; + Salsa20 s20a,s20b; + SHA256_CTX sha; + + /* + * Sequential memory-hard algorithm wedding address to public key + * + * Conventional hashcash with long computations and quick verifications + * unfortunately cannot be used here. If that were used, it would be + * equivalently costly to simply increment/vary the public key and find + * a collision as it would be to find the address. We need something + * that creates a costly 1:~1 mapping from key to address, hence this odd + * algorithm. + * + * This is designed not to be parallelizable and to be resistant to + * implementation on things like GPUs with tiny-memory nodes and poor + * branching capability. Toward that end it throws branching and a large + * memory buffer into the mix. It can only be efficiently computed by a + * single core with at least ~32MB RAM. + * + * Search for "sequential memory hard algorithm" for academic references + * to similar concepts. + * + * Right now this takes ~1700ms on a 2.4ghz Intel Core i5. If this could + * be reduced to 1ms per derivation, it would take about 34 years to search + * the entire 40-bit address space for an average of ~17 years to generate + * a key colliding with a known existing address. + */ + + // Initial starting digest + SHA256_Init(&sha); + SHA256_Update(&sha,(const unsigned char *)keyBytes,keyLen); // key + SHA256_Final(dig,&sha); + + s20a.init(dig,256,"ZeroTier"); + + unsigned char *ram = new unsigned char[ZT_IDENTITY_DERIVEADDRESS_MEMORY]; + + // Encrypt and digest a large memory buffer for several rounds + for(unsigned long i=0;i<ZT_IDENTITY_DERIVEADDRESS_MEMORY;++i) + ram[i] = (unsigned char)(i & 0xff) ^ dig[i & 31]; + for(unsigned long r=0;r<ZT_IDENTITY_DERIVEADDRESS_ROUNDS;++r) { + SHA256_Init(&sha); + + SHA256_Update(&sha,(const unsigned char *)keyBytes,keyLen); + SHA256_Update(&sha,dig,32); + + for(unsigned long i=0;i<ZT_IDENTITY_DERIVEADDRESS_MEMORY;++i) { + if (ram[i] == 17) // Forces a branch to be required + ram[i] ^= dig[i & 31]; + } + s20b.init(dig,256,"ZeroTier"); + s20a.encrypt(ram,ram,ZT_IDENTITY_DERIVEADDRESS_MEMORY); + s20b.encrypt(ram,ram,ZT_IDENTITY_DERIVEADDRESS_MEMORY); + SHA256_Update(&sha,ram,ZT_IDENTITY_DERIVEADDRESS_MEMORY); + + SHA256_Final(dig,&sha); + } + + // Final digest, executed for twice our number of rounds + SHA256_Init(&sha); + for(unsigned long r=0;r<(ZT_IDENTITY_DERIVEADDRESS_ROUNDS * 2);++r) { + SHA256_Update(&sha,(const unsigned char *)keyBytes,keyLen); + SHA256_Update(&sha,ram,ZT_IDENTITY_DERIVEADDRESS_ROUNDS); + SHA256_Update(&sha,dig,32); + SHA256_Update(&sha,(const unsigned char *)keyBytes,keyLen); + } + SHA256_Final(dig,&sha); + + delete [] ram; + + return Address(dig); // first 5 bytes of dig[] +} + +std::string Identity::encrypt(const Identity &to,const void *data,unsigned int len) const +{ + unsigned char key[64]; + unsigned char mac[32]; + unsigned char iv[8]; + + if (!agree(to,key,sizeof(key))) + return std::string(); + Utils::getSecureRandom(iv,8); + for(int i=0;i<8;++i) + key[i + 32] ^= iv[i]; // perturb HMAC key with IV so IV is effectively included in HMAC + Salsa20 s20(key,256,iv); + + std::string compressed; + compressed.reserve(len); + Utils::compress((const char *)data,(const char *)data + len,Utils::StringAppendOutput(compressed)); + if (!compressed.length()) + return std::string(); + + char *encrypted = new char[compressed.length() + 16]; + try { + s20.encrypt(compressed.data(),encrypted + 16,(unsigned int)compressed.length()); + HMAC::sha256(key + 32,32,encrypted + 16,(unsigned int)compressed.length(),mac); + for(int i=0;i<8;++i) + encrypted[i] = iv[i]; + for(int i=0;i<8;++i) + encrypted[i + 8] = mac[i]; + + std::string s(encrypted,compressed.length() + 16); + delete [] encrypted; + return s; + } catch ( ... ) { + delete [] encrypted; + return std::string(); + } +} + +std::string Identity::decrypt(const Identity &from,const void *cdata,unsigned int len) const +{ + unsigned char key[64]; + unsigned char mac[32]; + + if (len < 16) + return std::string(); + + if (!agree(from,key,sizeof(key))) + return std::string(); + + for(int i=0;i<8;++i) + key[i + 32] ^= ((const unsigned char *)cdata)[i]; // apply IV to HMAC key + HMAC::sha256(key + 32,32,((const char *)cdata) + 16,(unsigned int)(len - 16),mac); + for(int i=0;i<8;++i) { + if (((const unsigned char *)cdata)[i + 8] != mac[i]) + return std::string(); + } + + char *decbuf = new char[len - 16]; + try { + Salsa20 s20(key,256,cdata); // first 8 bytes are IV + len -= 16; + s20.decrypt((const char *)cdata + 16,decbuf,len); + + std::string decompressed; + if (Utils::decompress((const char *)decbuf,(const char *)decbuf + len,Utils::StringAppendOutput(decompressed))) { + delete [] decbuf; + return decompressed; + } else { + delete [] decbuf; + return std::string(); + } + } catch ( ... ) { + delete [] decbuf; + return std::string(); + } +} + +} // namespace ZeroTier + diff --git a/node/Identity.hpp b/node/Identity.hpp new file mode 100644 index 00000000..5cdfe9f8 --- /dev/null +++ b/node/Identity.hpp @@ -0,0 +1,431 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_IDENTITY_HPP +#define _ZT_IDENTITY_HPP + +#include <stdio.h> +#include <stdlib.h> +#include <string> + +#include "EllipticCurveKey.hpp" +#include "EllipticCurveKeyPair.hpp" +#include "Array.hpp" +#include "Utils.hpp" +#include "Address.hpp" +#include "Buffer.hpp" + +/** + * Maximum length for a serialized identity + */ +#define IDENTITY_MAX_BINARY_SERIALIZED_LENGTH ((ZT_EC_MAX_BYTES * 2) + 256) + +namespace ZeroTier { + +/** + * A ZeroTier identity + * + * An identity consists of a public key, a 40-bit ZeroTier address computed + * from that key in a collision-resistant fashion, and a self-signature. + * + * The address derivation algorithm makes it computationally very expensive to + * search for a different public key that duplicates an existing address. (See + * code for deriveAddress() for this algorithm.) + * + * After derivation, the address must be checked against isReserved(). If the + * address is reserved, generation is repeated until a valid address results. + * + * Serialization of an identity: + * + * <[5] address> - 40-bit ZeroTier network address + * <[1] type> - Identity type ID (rest is type-dependent) + * <[1] key length> - Length of public key + * <[n] public key> - Elliptic curve public key + * <[1] sig length> - Length of ECDSA self-signature + * <[n] signature> - ECDSA signature of first four fields + * [<[1] key length>] - [Optional] Length of private key + * [<[n] private key>] - [Optional] Private key + * + * Local storage of an identity also requires storage of its private key. + */ +class Identity +{ +public: + /** + * Identity types + */ + enum Type + { + /* Elliptic curve NIST-P-521 and ECDSA signature */ + IDENTITY_TYPE_NIST_P_521 = 1 + /* We won't need another identity type until quantum computers with + * tens of thousands of qubits are a reality. */ + }; + + Identity() : + _keyPair((EllipticCurveKeyPair *)0) + { + } + + Identity(const Identity &id) : + _keyPair((id._keyPair) ? new EllipticCurveKeyPair(*id._keyPair) : (EllipticCurveKeyPair *)0), + _publicKey(id._publicKey), + _address(id._address), + _signature(id._signature) + { + } + + Identity(const char *str) + throw(std::invalid_argument) : + _keyPair((EllipticCurveKeyPair *)0) + { + if (!fromString(str)) + throw std::invalid_argument("invalid string-serialized identity"); + } + + Identity(const std::string &str) + throw(std::invalid_argument) : + _keyPair((EllipticCurveKeyPair *)0) + { + if (!fromString(str)) + throw std::invalid_argument("invalid string-serialized identity"); + } + + template<unsigned int C> + Identity(const Buffer<C> &b,unsigned int startAt = 0) + throw(std::out_of_range,std::invalid_argument) : + _keyPair((EllipticCurveKeyPair *)0) + { + deserialize(b,startAt); + } + + ~Identity() + { + delete _keyPair; + } + + inline Identity &operator=(const Identity &id) + { + _keyPair = (id._keyPair) ? new EllipticCurveKeyPair(*id._keyPair) : (EllipticCurveKeyPair *)0; + _publicKey = id._publicKey; + _address = id._address; + _signature = id._signature; + return *this; + } + + /** + * Generate a new identity (address, key pair) + * + * This is a somewhat time consuming operation by design, as the address + * is derived from the key using a purposefully expensive many-round + * hash/encrypt/hash operation. This took about two seconds on a 2.4ghz + * Intel Core i5 in 2013. + * + * In the very unlikely event that a reserved address is created, generate + * will automatically run again. + */ + void generate(); + + /** + * Performs local validation, with two levels available + * + * With the parameter false, this performs self-signature verification + * which checks the basic integrity of the key and identity. Setting the + * parameter to true performs a fairly time consuming computation to + * check that the address was properly derived from the key. This is + * normally not done unless a conflicting identity is received, in + * which case the invalid identity is thrown out. + * + * @param doAddressDerivationCheck If true, do the time-consuming address check + * @return True if validation check passes + */ + bool locallyValidate(bool doAddressDerivationCheck) const; + + /** + * @return Private key pair or NULL if not included with this identity + */ + inline const EllipticCurveKeyPair *privateKeyPair() const throw() { return _keyPair; } + + /** + * @return True if this identity has its private portion + */ + inline bool hasPrivate() const throw() { return (_keyPair); } + + /** + * Encrypt a block of data to send to another identity + * + * This identity must have a secret key. + * + * The encrypted data format is: + * <[8] Salsa20 initialization vector> + * <[8] first 8 bytes of HMAC-SHA-256 of ciphertext> + * <[...] encrypted compressed data> + * + * Keying is accomplished using agree() (KDF function is in the + * EllipticCurveKeyPair.cpp source) to generate 64 bytes of key. The first + * 32 bytes are used as the Salsa20 key, and the last 32 bytes are used + * as the HMAC key. + * + * @param to Identity of recipient of encrypted message + * @param data Data to encrypt + * @param len Length of data + * @return Encrypted data or empty string on failure + */ + std::string encrypt(const Identity &to,const void *data,unsigned int len) const; + + /** + * Decrypt a message encrypted with encrypt() + * + * This identity must have a secret key. + * + * @param from Identity of sender of encrypted message + * @param cdata Encrypted message + * @param len Length of encrypted message + * @return Decrypted data or empty string on failure + */ + std::string decrypt(const Identity &from,const void *cdata,unsigned int len) const; + + /** + * Shortcut method to perform key agreement with another identity + * + * This identity must have its private portion. + * + * @param id Identity to agree with + * @param key Result parameter to fill with key bytes + * @param klen Length of key in bytes + * @return Was agreement successful? + */ + inline bool agree(const Identity &id,void *key,unsigned int klen) const + { + if ((id)&&(_keyPair)) + return _keyPair->agree(id._publicKey,(unsigned char *)key,klen); + return false; + } + + /** + * Sign a hash with this identity's private key + * + * @param sha256 32-byte hash to sign + * @return ECDSA signature or empty string on failure or if identity has no private portion + */ + inline std::string sign(const void *sha256) const + { + if (_keyPair) + return _keyPair->sign(sha256); + return std::string(); + } + + /** + * Sign a block of data with this identity's private key + * + * This is a shortcut to SHA-256 hashing then signing. + * + * @param sha256 32-byte hash to sign + * @return ECDSA signature or empty string on failure or if identity has no private portion + */ + inline std::string sign(const void *data,unsigned int len) const + { + if (_keyPair) + return _keyPair->sign(data,len); + return std::string(); + } + + /** + * Verify something signed with this identity's public key + * + * @param sha256 32-byte hash to verify + * @param sigbytes Signature bytes + * @param siglen Length of signature + * @return True if signature is valid + */ + inline bool verifySignature(const void *sha256,const void *sigbytes,unsigned int siglen) const + { + return EllipticCurveKeyPair::verify(sha256,_publicKey,sigbytes,siglen); + } + + /** + * Verify something signed with this identity's public key + * + * @param data Data to verify + * @param len Length of data to verify + * @param sigbytes Signature bytes + * @param siglen Length of signature + * @return True if signature is valid + */ + inline bool verifySignature(const void *data,unsigned int len,const void *sigbytes,unsigned int siglen) const + { + return EllipticCurveKeyPair::verify(data,len,_publicKey,sigbytes,siglen); + } + + /** + * @return Public key (available in all identities) + */ + inline const EllipticCurveKey &publicKey() const throw() { return _publicKey; } + + /** + * @return Identity type + */ + inline Type type() const throw() { return IDENTITY_TYPE_NIST_P_521; } + + /** + * @return This identity's address + */ + inline const Address &address() const throw() { return _address; } + + /** + * Serialize this identity (binary) + * + * @param b Destination buffer to append to + * @param includePrivate If true, include private key component (if present) (default: false) + * @throws std::out_of_range Buffer too small + */ + template<unsigned int C> + inline void serialize(Buffer<C> &b,bool includePrivate = false) const + throw(std::out_of_range) + { + b.append(_address.data(),ZT_ADDRESS_LENGTH); + b.append((unsigned char)IDENTITY_TYPE_NIST_P_521); + b.append((unsigned char)(_publicKey.size() & 0xff)); + b.append(_publicKey.data(),_publicKey.size()); + b.append((unsigned char)(_signature.length() & 0xff)); + b.append(_signature); + if ((includePrivate)&&(_keyPair)) { + b.append((unsigned char)(_keyPair->priv().size() & 0xff)); + b.append(_keyPair->priv().data(),_keyPair->priv().size()); + } else b.append((unsigned char)0); + } + + /** + * Deserialize a binary serialized identity + * + * If an exception is thrown, the Identity object is left in an undefined + * state and should not be used. + * + * @param b Buffer containing serialized data + * @param startAt Index within buffer of serialized data (default: 0) + * @return Length of serialized data read from buffer + * @throws std::out_of_range Buffer too small + * @throws std::invalid_argument Serialized data invalid + */ + template<unsigned int C> + inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) + throw(std::out_of_range,std::invalid_argument) + { + delete _keyPair; + _keyPair = (EllipticCurveKeyPair *)0; + + unsigned int p = startAt; + + _address = b.field(p,ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + + if (b[p++] != IDENTITY_TYPE_NIST_P_521) + throw std::invalid_argument("Identity: deserialize(): unsupported identity type"); + + unsigned int publicKeyLength = b[p++]; + if (!publicKeyLength) + throw std::invalid_argument("Identity: deserialize(): no public key"); + _publicKey.set(b.field(p,publicKeyLength),publicKeyLength); + p += publicKeyLength; + + unsigned int signatureLength = b[p++]; + if (!signatureLength) + throw std::invalid_argument("Identity: deserialize(): no signature"); + _signature.assign((const char *)b.field(p,signatureLength),signatureLength); + p += signatureLength; + + unsigned int privateKeyLength = b[p++]; + if (privateKeyLength) { + _keyPair = new EllipticCurveKeyPair(_publicKey,EllipticCurveKey(b.field(p,privateKeyLength),privateKeyLength)); + p += privateKeyLength; + } + + return (p - startAt); + } + + /** + * Serialize to a more human-friendly string + * + * @param includePrivate If true, include private key (if it exists) + * @return ASCII string representation of identity + */ + std::string toString(bool includePrivate) const; + + /** + * Deserialize a human-friendly string + * + * Note: validation is for the format only. The locallyValidate() method + * must be used to check signature and address/key correspondence. + * + * @param str String to deserialize + * @return True if deserialization appears successful + */ + bool fromString(const char *str); + inline bool fromString(const std::string &str) { return fromString(str.c_str()); } + + /** + * @return True if this identity contains something + */ + inline operator bool() const throw() { return (_publicKey.size()); } + + inline bool operator==(const Identity &id) const + throw() + { + if (_address == id._address) { + if ((_keyPair)&&(id._keyPair)) + return (*_keyPair == *id._keyPair); + return (_publicKey == id._publicKey); + } + return false; + } + inline bool operator<(const Identity &id) const + throw() + { + if (_address < id._address) + return true; + else if (_address == id._address) + return (_publicKey < id._publicKey); + return false; + } + inline bool operator!=(const Identity &id) const throw() { return !(*this == id); } + inline bool operator>(const Identity &id) const throw() { return (id < *this); } + inline bool operator<=(const Identity &id) const throw() { return !(id < *this); } + inline bool operator>=(const Identity &id) const throw() { return !(*this < id); } + +private: + // Compute an address from public key bytes + static Address deriveAddress(const void *keyBytes,unsigned int keyLen); + + EllipticCurveKeyPair *_keyPair; + EllipticCurveKey _publicKey; + Address _address; + std::string _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp new file mode 100644 index 00000000..79efbaf2 --- /dev/null +++ b/node/InetAddress.cpp @@ -0,0 +1,148 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string> + +#include "InetAddress.hpp" + +namespace ZeroTier { + +const InetAddress InetAddress::LO4("127.0.0.1",0); +const InetAddress InetAddress::LO6("::1",0); + +void InetAddress::set(const std::string &ip,unsigned int port) + throw() +{ + memset(&_sa,0,sizeof(_sa)); + if (ip.find(':') != std::string::npos) { + _sa.sin6.sin6_family = AF_INET6; + _sa.sin6.sin6_port = htons((uint16_t)port); + if (inet_pton(AF_INET6,ip.c_str(),(void *)&(_sa.sin6.sin6_addr.s6_addr)) <= 0) + _sa.saddr.sa_family = 0; + } else { + _sa.sin.sin_family = AF_INET; + _sa.sin.sin_port = htons((uint16_t)port); + if (inet_pton(AF_INET,ip.c_str(),(void *)&(_sa.sin.sin_addr.s_addr)) <= 0) + _sa.saddr.sa_family = 0; + } +} + +std::string InetAddress::toString() const +{ + char buf[128],buf2[128]; + + switch(_sa.saddr.sa_family) { + case AF_INET: + if (inet_ntop(AF_INET,(const void *)&(_sa.sin.sin_addr.s_addr),buf,sizeof(buf))) { + sprintf(buf2,"%s/%u",buf,(unsigned int)ntohs(_sa.sin.sin_port)); + return std::string(buf2); + } + break; + case AF_INET6: + if (inet_ntop(AF_INET6,(const void *)&(_sa.sin6.sin6_addr.s6_addr),buf,sizeof(buf))) { + sprintf(buf2,"%s/%u",buf,(unsigned int)ntohs(_sa.sin6.sin6_port)); + return std::string(buf2); + } + break; + } + + return std::string(); +} + +void InetAddress::fromString(const std::string &ipSlashPort) +{ + std::size_t slashAt = ipSlashPort.find('/'); + if ((slashAt == std::string::npos)||(slashAt >= ipSlashPort.length())) + set(ipSlashPort,0); + else { + long p = strtol(ipSlashPort.substr(slashAt+1).c_str(),(char **)0,10); + if ((p > 0)&&(p <= 0xffff)) + set(ipSlashPort.substr(0,slashAt),(unsigned int)p); + else set(ipSlashPort.substr(0,slashAt),0); + } +} + +std::string InetAddress::toIpString() const +{ + char buf[128]; + + switch(_sa.saddr.sa_family) { + case AF_INET: + if (inet_ntop(AF_INET,(const void *)&(_sa.sin.sin_addr.s_addr),buf,sizeof(buf))) + return std::string(buf); + break; + case AF_INET6: + if (inet_ntop(AF_INET6,(const void *)&(_sa.sin6.sin6_addr.s6_addr),buf,sizeof(buf))) + return std::string(buf); + break; + } + + return std::string(); +} + +bool InetAddress::operator==(const InetAddress &a) const + throw() +{ + if (_sa.saddr.sa_family == AF_INET) { + if (a._sa.saddr.sa_family == AF_INET) + return ((_sa.sin.sin_addr.s_addr == a._sa.sin.sin_addr.s_addr)&&(_sa.sin.sin_port == a._sa.sin.sin_port)); + return false; + } else if (_sa.saddr.sa_family == AF_INET6) { + if (a._sa.saddr.sa_family == AF_INET6) { + if (_sa.sin6.sin6_port == a._sa.sin6.sin6_port) + return (!memcmp(_sa.sin6.sin6_addr.s6_addr,a._sa.sin6.sin6_addr.s6_addr,sizeof(_sa.sin6.sin6_addr.s6_addr))); + } + return false; + } else if (!_sa.saddr.sa_family) + return (!a._sa.saddr.sa_family); + return (!memcmp(&_sa,&a._sa,sizeof(_sa))); +} + +bool InetAddress::operator<(const InetAddress &a) const + throw() +{ + if (_sa.saddr.sa_family == AF_INET) { + if (a._sa.saddr.sa_family == AF_INET) + return ((ntohl(_sa.sin.sin_addr.s_addr < ntohl(a._sa.sin.sin_addr.s_addr)))||((_sa.sin.sin_addr.s_addr == a._sa.sin.sin_addr.s_addr)&&(ntohs(_sa.sin.sin_port) < ntohs(a._sa.sin.sin_port)))); + else if (a._sa.saddr.sa_family == AF_INET6) + return true; + } else if (_sa.saddr.sa_family == AF_INET6) { + if (a._sa.saddr.sa_family == AF_INET6) { + int cmp = memcmp(_sa.sin6.sin6_addr.s6_addr,a._sa.sin6.sin6_addr.s6_addr,16); + return ((cmp < 0)||((!cmp)&&(ntohs(_sa.sin6.sin6_port) < ntohs(a._sa.sin6.sin6_port)))); + } else if (a._sa.saddr.sa_family == AF_INET) + return false; + } + return (_sa.saddr.sa_family < a._sa.saddr.sa_family); +} + +} // namespace ZeroTier diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp new file mode 100644 index 00000000..42079274 --- /dev/null +++ b/node/InetAddress.hpp @@ -0,0 +1,334 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_INETADDRESS_HPP +#define _ZT_INETADDRESS_HPP + +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> +#include <string> + +namespace ZeroTier { + +/** + * Wrapper for sockaddr structures for IPV4 and IPV6 + */ +class InetAddress +{ +public: + /** + * Address type + */ + enum AddressType + { + TYPE_NULL = 0, + TYPE_IPV4 = AF_INET, + TYPE_IPV6 = AF_INET6 + }; + + /** + * Loopback IPv4 address (no port) + */ + static const InetAddress LO4; + + /** + * Loopback IPV6 address (no port) + */ + static const InetAddress LO6; + + InetAddress() + throw() + { + memset(&_sa,0,sizeof(_sa)); + } + + InetAddress(const InetAddress &a) + throw() + { + memcpy(&_sa,&a._sa,sizeof(_sa)); + } + + InetAddress(const struct sockaddr *sa) + throw() + { + this->set(sa); + } + + InetAddress(const void *ipBytes,unsigned int ipLen,unsigned int port) + throw() + { + this->set(ipBytes,ipLen,port); + } + + InetAddress(const std::string &ip,unsigned int port) + throw() + { + this->set(ip,port); + } + + InetAddress(const std::string &ipSlashPort) + throw() + { + this->fromString(ipSlashPort); + } + + inline InetAddress &operator=(const InetAddress &a) + throw() + { + memcpy(&_sa,&a._sa,sizeof(_sa)); + return *this; + } + + /** + * Set from an OS-level sockaddr structure + * + * @param sa Socket address (V4 or V6) + */ + inline void set(const struct sockaddr *sa) + throw() + { + switch(sa->sa_family) { + case AF_INET: + memcpy(&_sa.sin,sa,sizeof(struct sockaddr_in)); + break; + case AF_INET6: + memcpy(&_sa.sin6,sa,sizeof(struct sockaddr_in6)); + break; + default: + _sa.saddr.sa_family = 0; + break; + } + } + + /** + * Set from a string-format IP and a port + * + * @param ip IP address in V4 or V6 ASCII notation + * @param port Port or 0 for none + */ + void set(const std::string &ip,unsigned int port) + throw(); + + /** + * Set from a raw IP and port number + * + * @param ipBytes Bytes of IP address in network byte order + * @param ipLen Length of IP address: 4 or 16 + * @param port Port number or 0 for none + */ + inline void set(const void *ipBytes,unsigned int ipLen,unsigned int port) + throw() + { + _sa.saddr.sa_family = 0; + if (ipLen == 4) { + setV4(); + memcpy(rawIpData(),ipBytes,4); + setPort(port); + } else if (ipLen == 16) { + setV6(); + memcpy(rawIpData(),ipBytes,16); + setPort(port); + } + } + + /** + * Set the port component + * + * @param port Port, 0 to 65535 + */ + inline void setPort(unsigned int port) + throw() + { + if (_sa.saddr.sa_family == AF_INET) + _sa.sin.sin_port = htons((uint16_t)port); + else if (_sa.saddr.sa_family == AF_INET6) + _sa.sin6.sin6_port = htons((uint16_t)port); + } + + /** + * @return ASCII IP/port format representation + */ + std::string toString() const; + + /** + * @param ipSlashPort ASCII IP/port format notation + */ + void fromString(const std::string &ipSlashPort); + + /** + * @return IP portion only, in ASCII string format + */ + std::string toIpString() const; + + /** + * @return Port or 0 if no port component defined + */ + inline unsigned int port() const + throw() + { + switch(_sa.saddr.sa_family) { + case AF_INET: + return ntohs(_sa.sin.sin_port); + case AF_INET6: + return ntohs(_sa.sin6.sin6_port); + } + return 0; + } + + /** + * Alias for port() + * + * This just aliases port() to make code more readable when netmask bits + * are stuffed there, as they are in Network, EthernetTap, and a few other + * spots. + * + * @return Netmask bits + */ + inline unsigned int netmaskBits() const + throw() + { + return port(); + } + + /** + * @return True if this is an IPv4 address + */ + inline bool isV4() const throw() { return (_sa.saddr.sa_family == AF_INET); } + + /** + * @return True if this is an IPv6 address + */ + inline bool isV6() const throw() { return (_sa.saddr.sa_family == AF_INET6); } + + /** + * @return Address type or TYPE_NULL if not defined + */ + inline AddressType type() const throw() { return (AddressType)_sa.saddr.sa_family; } + + /** + * Force type to IPv4 + */ + inline void setV4() throw() { _sa.saddr.sa_family = AF_INET; } + + /** + * Force type to IPv6 + */ + inline void setV6() throw() { _sa.saddr.sa_family = AF_INET6; } + + /** + * @return Raw sockaddr structure + */ + inline struct sockaddr *saddr() throw() { return &(_sa.saddr); } + inline const struct sockaddr *saddr() const throw() { return &(_sa.saddr); } + + /** + * @return Length of sockaddr_in if IPv4, sockaddr_in6 if IPv6 + */ + inline unsigned int saddrLen() const + throw() + { + return (isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)); + } + + /** + * @return Combined length of internal structure, room for either V4 or V6 + */ + inline unsigned int saddrSpaceLen() const + throw() + { + return sizeof(_sa); + } + + /** + * @return Raw sockaddr_in structure (valid if IPv4) + */ + inline const struct sockaddr_in *saddr4() const throw() { return &(_sa.sin); } + + /** + * @return Raw sockaddr_in6 structure (valid if IPv6) + */ + inline const struct sockaddr_in6 *saddr6() const throw() { return &(_sa.sin6); } + + /** + * @return Raw IP address (4 bytes for IPv4, 16 bytes for IPv6) + */ + inline void *rawIpData() throw() { return ((_sa.saddr.sa_family == AF_INET) ? (void *)(&(_sa.sin.sin_addr.s_addr)) : (void *)_sa.sin6.sin6_addr.s6_addr); } + inline const void *rawIpData() const throw() { return ((_sa.saddr.sa_family == AF_INET) ? (void *)(&(_sa.sin.sin_addr.s_addr)) : (void *)_sa.sin6.sin6_addr.s6_addr); } + + /** + * Compare only the IP portions of addresses, ignoring port + * + * @param a Address to compare + * @return True if both addresses are of the same (valid) type and their IPs match + */ + inline bool ipsEqual(const InetAddress &a) const + throw() + { + if (_sa.saddr.sa_family == a._sa.saddr.sa_family) { + switch(_sa.saddr.sa_family) { + case AF_INET: + return (_sa.sin.sin_addr.s_addr == a._sa.sin.sin_addr.s_addr); + case AF_INET6: + return (!memcmp(_sa.sin6.sin6_addr.s6_addr,a._sa.sin6.sin6_addr.s6_addr,16)); + } + } + return false; + } + + bool operator==(const InetAddress &a) const throw(); + inline bool operator!=(const InetAddress &a) const throw() { return !(*this == a); } + bool operator<(const InetAddress &a) const throw(); + inline bool operator>(const InetAddress &a) const throw() { return (a < *this); } + inline bool operator<=(const InetAddress &a) const throw() { return !(a < *this); } + inline bool operator>=(const InetAddress &a) const throw() { return !(*this < a); } + + /** + * @return True if address family is non-zero + */ + inline operator bool() const throw() { return ((_sa.saddr.sa_family == AF_INET)||(_sa.saddr.sa_family == AF_INET6)); } + + /** + * Set to null/zero + */ + inline void zero() + throw() + { + _sa.saddr.sa_family = 0; + } + +private: + union { + struct sockaddr saddr; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } _sa; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Logger.cpp b/node/Logger.cpp new file mode 100644 index 00000000..7bed5990 --- /dev/null +++ b/node/Logger.cpp @@ -0,0 +1,141 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include "Logger.hpp" + +namespace ZeroTier { + +Logger::Logger(const char *p,const char *prefix,unsigned long maxLogSize) : + _path((p) ? p : ""), + _prefix((prefix) ? (std::string(prefix) + " ") : ""), + _maxLogSize(maxLogSize), + _log_m(), + _log((FILE *)0) +{ + if (_path.length()) + _log = fopen(_path.c_str(),"a"); + else _log = stdout; +} + +Logger::~Logger() +{ + fflush(_log); + if ((_log)&&(_log != stdout)&&(_log != stderr)) + fclose(_log); +} + +void Logger::log(const char *fmt,...) +{ + va_list ap; + char tmp[128]; + + if (_log) { + Mutex::Lock _l(_log_m); + _rotateIfNeeded(); + + if (_log) { + time_t now = time(0); + char *nowstr = ctime_r(&now,tmp); + for(char *c=nowstr;*c;++c) { + if (*c == '\n') + *c = '\0'; + } + + if (_prefix.length()) + fwrite(_prefix.data(),1,_prefix.length(),_log); + + fprintf(_log,"[%s] ",nowstr); + va_start(ap,fmt); + vfprintf(_log,fmt,ap); + va_end(ap); +#ifdef _WIN32 + fwrite("\r\n",1,2,_log); +#else + fwrite("\n",1,1,_log); +#endif + + fflush(_log); + } + } +} + +#ifdef ZT_TRACE +void Logger::trace(const char *module,unsigned int line,const char *fmt,...) +{ + va_list ap; + char tmp[128]; + + if (_log) { + Mutex::Lock _l(_log_m); + _rotateIfNeeded(); + + if (_log) { + time_t now = time(0); + char *nowstr = ctime_r(&now,tmp); + for(char *c=nowstr;*c;++c) { + if (*c == '\n') + *c = '\0'; + } + + if (_prefix.length()) + fwrite(_prefix.data(),1,_prefix.length(),_log); + + fprintf(_log,"[%s] TRACE/%s:%u ",nowstr,module,line); + va_start(ap,fmt); + vfprintf(_log,fmt,ap); + va_end(ap); +#ifdef _WIN32 + fwrite("\r\n",1,2,_log); +#else + fwrite("\n",1,1,_log); +#endif + + fflush(_log); + } + } +} +#endif + +void Logger::_rotateIfNeeded() +{ + if ((_maxLogSize)&&(_log != stdout)&&(_log != stderr)) { + long pos = ftell(_log); + if (pos > (long)_maxLogSize) { + fclose(_log); + rename(_path.c_str(),std::string(_path).append(".old").c_str()); + _log = fopen(_path.c_str(),"w"); + } + } +} + +} // namespace ZeroTier + diff --git a/node/Logger.hpp b/node/Logger.hpp new file mode 100644 index 00000000..8ecfc0d6 --- /dev/null +++ b/node/Logger.hpp @@ -0,0 +1,90 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_LOGGER_HPP +#define _ZT_LOGGER_HPP + +#include <stdio.h> +#include <string> +#include <stdexcept> +#include "NonCopyable.hpp" +#include "Mutex.hpp" + +#undef LOG +#define LOG(f,...) if (_r->log) _r->log->log(f,##__VA_ARGS__) + +#undef TRACE +#ifdef ZT_TRACE +#define TRACE(f,...) if (_r->log) _r->log->trace(__FILE__,__LINE__,f,##__VA_ARGS__) +#else +#define TRACE(f,...) {} +#endif + +namespace ZeroTier { + +/** + * Utility for outputting logs to a file or stdout/stderr + */ +class Logger : NonCopyable +{ +public: + /** + * Construct a logger to log to a file or stdout + * + * If a path is supplied to log to a file, maxLogSize indicates the size + * at which this file is closed, renamed to .old, and then a new log is + * opened (essentially a log rotation). If stdout is used, this is ignored. + * + * @param p Path to log to or NULL to use stdout + * @param prefix Prefix to prepend to log lines or NULL for none + * @param maxLogSize Maximum log size (0 for no limit) + */ + Logger(const char *p,const char *prefix,unsigned long maxLogSize); + ~Logger(); + + void log(const char *fmt,...); + +#ifdef ZT_TRACE + void trace(const char *module,unsigned int line,const char *fmt,...); +#else + inline void trace(const char *module,unsigned int line,const char *fmt,...) {} +#endif + +private: + void _rotateIfNeeded(); + + std::string _path; + std::string _prefix; + unsigned long _maxLogSize; + Mutex _log_m; + FILE *_log; +}; + +} // namespace ZeroTier + +#endif + diff --git a/node/MAC.hpp b/node/MAC.hpp new file mode 100644 index 00000000..f33277c0 --- /dev/null +++ b/node/MAC.hpp @@ -0,0 +1,160 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_MAC_HPP +#define _ZT_MAC_HPP + +#include <stdio.h> +#include <stdlib.h> +#include "Array.hpp" +#include "Constants.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +/** + * An Ethernet MAC address + */ +class MAC : public Array<unsigned char,6> +{ +public: + /** + * Create a zero/null MAC + */ + MAC() + throw() + { + for(unsigned int i=0;i<6;++i) + data[i] = 0; + } + + /** + * Create a MAC consisting of only this octet + * + * @param octet Octet to fill MAC with (e.g. 0xff for broadcast-all) + */ + MAC(const unsigned char octet) + throw() + { + for(unsigned int i=0;i<6;++i) + data[i] = octet; + } + + /** + * Create a MAC from raw bits + * + * @param bits 6 bytes of MAC address data + */ + MAC(const void *bits) + throw() + { + for(unsigned int i=0;i<6;++i) + data[i] = ((const unsigned char *)bits)[i]; + } + + /** + * @return True if non-NULL (not all zero) + */ + inline operator bool() const + throw() + { + for(unsigned int i=0;i<6;++i) { + if (data[i]) + return true; + } + return false; + } + + /** + * @return True if this is the broadcast-all MAC (0xff:0xff:...) + */ + inline bool isBroadcast() const + throw() + { + for(unsigned int i=0;i<6;++i) { + if (data[i] != 0xff) + return false; + } + return true; + } + + /** + * @return True if this is a multicast/broadcast address + */ + inline bool isMulticast() const + throw() + { + return ((data[0] & 1)); + } + + /** + * @return True if this is a ZeroTier unicast MAC + */ + inline bool isZeroTier() const + throw() + { + return (data[0] == ZT_MAC_FIRST_OCTET); + } + + /** + * Zero this MAC + */ + inline void zero() + throw() + { + for(unsigned int i=0;i<6;++i) + data[i] = 0; + } + + /** + * @param s String hex representation (with or without :'s) + * @return True if string decoded into a full-length MAC + */ + inline bool fromString(const char *s) + { + std::string b(Utils::unhex(s)); + if (b.length() == 6) { + for(unsigned int i=0;i<6;++i) + data[i] = (unsigned char)b[i]; + return true; + } + for(unsigned int i=0;i<6;++i) + data[i] = 0; + return false; + } + + inline std::string toString() const + { + char tmp[32]; + sprintf(tmp,"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)data[0],(int)data[1],(int)data[2],(int)data[3],(int)data[4],(int)data[5]); + return std::string(tmp); + } +}; + +} // namespace ZeroTier + +#endif diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp new file mode 100644 index 00000000..ac7de481 --- /dev/null +++ b/node/MulticastGroup.hpp @@ -0,0 +1,144 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_MULTICASTGROUP_HPP +#define _ZT_MULTICASTGROUP_HPP + +#include <stdint.h> +#include <string> +#include "MAC.hpp" +#include "InetAddress.hpp" + +namespace ZeroTier { + +/** + * A multicast group composed of a multicast MAC and a 64-bit ADI field + * + * ADI stands for additional distinguishing information. ADI is primarily for + * adding additional information to broadcast (ff:ff:ff:ff:ff:ff) memberships, + * since straight-up broadcast won't scale. Right now it's zero except for + * IPv4 ARP, where it holds the IPv4 address itself to make ARP into a + * selective multicast query that can scale. + * + * In the future we might add some kind of plugin architecture that can add + * ADI for things like mDNS (multicast DNS) to improve the selectivity of + * those protocols. + * + * MulticastGroup behaves as an immutable value object. + */ +class MulticastGroup +{ +public: + MulticastGroup() + throw() : + _mac(), + _adi(0) + { + } + + MulticastGroup(const MAC &m,uint32_t a) + throw() : + _mac(m), + _adi(a) + { + } + + /** + * Derive the multicast group used for address resolution (ARP/NDP) for an IP + * + * @param ip IP address (port field is ignored) + * @return Multicat group for ARP/NDP + */ + static inline MulticastGroup deriveMulticastGroupForAddressResolution(const InetAddress &ip) + throw() + { + if (ip.isV4()) { + // IPv4 wants braodcast MACs, so we shove the V4 address itself into + // the Multicast Group ADI field. Making V4 ARP work is basically why + // ADI was added, as well as handling other things that want mindless + // Ethernet broadcast to all. + return MulticastGroup(MAC((unsigned char)0xff),Utils::ntoh(*((const uint32_t *)ip.rawIpData()))); + } else if (ip.isV6()) { + // IPv6 is better designed in this respect. We can compute the IPv6 + // multicast address directly from the IP address, and it gives us + // 24 bits of uniqueness. Collisions aren't likely to be common enough + // to care about. + const unsigned char *a = (const unsigned char *)ip.rawIpData(); + MAC m; + m.data[0] = 0x33; + m.data[1] = 0x33; + m.data[2] = 0xff; + m.data[3] = a[13]; + m.data[4] = a[14]; + m.data[5] = a[15]; + return MulticastGroup(m,0); + } + return MulticastGroup(); + } + + /** + * @return Human readable string representing this group + */ + inline std::string toString() const + { + char buf[64]; + sprintf(buf,"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x/%.8lx",(unsigned int)_mac.data[0],(unsigned int)_mac.data[1],(unsigned int)_mac.data[2],(unsigned int)_mac.data[3],(unsigned int)_mac.data[4],(unsigned int)_mac.data[5],(unsigned long)_adi); + return std::string(buf); + } + + /** + * @return Multicast address + */ + inline const MAC &mac() const throw() { return _mac; } + + /** + * @return Additional distinguishing information + */ + inline uint32_t adi() const throw() { return _adi; } + + inline bool operator==(const MulticastGroup &g) const throw() { return ((_mac == g._mac)&&(_adi == g._adi)); } + inline bool operator!=(const MulticastGroup &g) const throw() { return ((_mac != g._mac)||(_adi != g._adi)); } + inline bool operator<(const MulticastGroup &g) const throw() + { + if (_mac < g._mac) + return true; + else if (_mac == g._mac) + return (_adi < g._adi); + return false; + } + inline bool operator>(const MulticastGroup &g) const throw() { return (g < *this); } + inline bool operator<=(const MulticastGroup &g) const throw() { return !(g < *this); } + inline bool operator>=(const MulticastGroup &g) const throw() { return !(*this < g); } + +private: + MAC _mac; + uint32_t _adi; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Mutex.hpp b/node/Mutex.hpp new file mode 100644 index 00000000..493cc425 --- /dev/null +++ b/node/Mutex.hpp @@ -0,0 +1,197 @@ +/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2012-2013 ZeroTier Networks LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#ifndef _ZT_MUTEX_HPP
+#define _ZT_MUTEX_HPP
+
+#include "NonCopyable.hpp"
+
+#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
+
+#include <stdlib.h>
+#include <pthread.h>
+
+namespace ZeroTier {
+
+class Mutex : NonCopyable
+{
+public:
+ Mutex()
+ throw()
+ {
+ pthread_mutex_init(&_mh,(const pthread_mutexattr_t *)0);
+ }
+
+ ~Mutex()
+ {
+ pthread_mutex_destroy(&_mh);
+ }
+
+ inline void lock()
+ throw()
+ {
+ pthread_mutex_lock(&_mh);
+ }
+
+ inline void unlock()
+ throw()
+ {
+ pthread_mutex_unlock(&_mh);
+ }
+
+ inline void lock() const
+ throw()
+ {
+ (const_cast <Mutex *> (this))->lock();
+ }
+
+ inline void unlock() const
+ throw()
+ {
+ (const_cast <Mutex *> (this))->unlock();
+ }
+
+ /**
+ * Uses C++ contexts and constructor/destructor to lock/unlock automatically
+ */
+ class Lock : NonCopyable
+ {
+ public:
+ Lock(Mutex &m)
+ throw() :
+ _m(&m)
+ {
+ m.lock();
+ }
+
+ Lock(const Mutex &m)
+ throw() :
+ _m(const_cast<Mutex *>(&m))
+ {
+ _m->lock();
+ }
+
+ ~Lock()
+ {
+ _m->unlock();
+ }
+
+ private:
+ Mutex *const _m;
+ };
+
+private:
+ pthread_mutex_t _mh;
+};
+
+} // namespace ZeroTier
+
+#endif // Apple / Linux
+
+#ifdef _WIN32
+
+#include <stdlib.h>
+#include <Windows.h>
+
+namespace ZeroTier {
+
+class Mutex : NonCopyable
+{
+public:
+ Mutex()
+ throw()
+ {
+ InitializeCriticalSection(&_cs);
+ }
+
+ ~Mutex()
+ {
+ DeleteCriticalSection(&_cs);
+ }
+
+ inline void lock()
+ throw()
+ {
+ EnterCriticalSection(&_cs);
+ }
+
+ inline void unlock()
+ throw()
+ {
+ LeaveCriticalSection(&_cs);
+ }
+
+ inline void lock() const
+ throw()
+ {
+ (const_cast <Mutex *> (this))->lock();
+ }
+
+ inline void unlock() const
+ throw()
+ {
+ (const_cast <Mutex *> (this))->unlock();
+ }
+
+ /**
+ * Uses C++ contexts and constructor/destructor to lock/unlock automatically
+ */
+ class Lock : NonCopyable
+ {
+ public:
+ Lock(Mutex &m)
+ throw() :
+ _m(&m)
+ {
+ m.lock();
+ }
+
+ Lock(const Mutex &m)
+ throw() :
+ _m(const_cast<Mutex *>(&m))
+ {
+ _m->lock();
+ }
+
+ ~Lock()
+ {
+ _m->unlock();
+ }
+
+ private:
+ Mutex *const _m;
+ };
+
+private:
+ CRITICAL_SECTION _cs;
+};
+
+} // namespace ZeroTier
+
+#endif // _WIN32
+
+#endif
diff --git a/node/Network.cpp b/node/Network.cpp new file mode 100644 index 00000000..41f04e9f --- /dev/null +++ b/node/Network.cpp @@ -0,0 +1,80 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Network.hpp" +#include "Switch.hpp" + +namespace ZeroTier { + +Network::Network(const RuntimeEnvironment *renv,uint64_t id) + throw(std::runtime_error) : + Thread(), + _r(renv), + _id(id), + _tap(renv,renv->identity.address().toMAC(),ZT_IF_MTU), + _members(), + _open(false), + _lock() +{ + TRACE("new network %llu created, TAP device: %s",id,_tap.deviceName().c_str()); + start(); +} + +Network::~Network() +{ + _tap.close(); + join(); + TRACE("network %llu (%s) closed",_id,_tap.deviceName().c_str()); +} + +void Network::main() + throw() +{ + Buffer<4096> buf; + MAC from,to; + unsigned int etherType = 0; + + while (_tap.open()) { + unsigned int len = _tap.get(from,to,etherType,buf.data()); + if (len) { + buf.setSize(len); + try { + if (!*__refCount) + break; // sanity check + _r->sw->onLocalEthernet(SharedPtr<Network>(this),from,to,etherType,buf); + } catch (std::exception &exc) { + TRACE("unexpected exception handling local packet: %s",exc.what()); + } catch ( ... ) { + TRACE("unexpected exception handling local packet"); + } + } else break; + } + + TRACE("network %llu thread terminating",_id); +} + +} // namespace ZeroTier diff --git a/node/Network.hpp b/node/Network.hpp new file mode 100644 index 00000000..1bbf0a9f --- /dev/null +++ b/node/Network.hpp @@ -0,0 +1,162 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_NETWORK_HPP +#define _ZT_NETWORK_HPP + +#include <string> +#include <set> +#include <vector> +#include <stdexcept> +#include "EthernetTap.hpp" +#include "Address.hpp" +#include "Mutex.hpp" +#include "InetAddress.hpp" +#include "Constants.hpp" +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "RuntimeEnvironment.hpp" +#include "Thread.hpp" +#include "MulticastGroup.hpp" + +namespace ZeroTier { + +class NodeConfig; + +/** + * Local network endpoint + */ +class Network : protected Thread +{ + friend class SharedPtr<Network>; + friend class NodeConfig; + +private: + virtual ~Network(); + + Network(const RuntimeEnvironment *renv,uint64_t id) + throw(std::runtime_error); + +public: + /** + * @return Network ID + */ + inline uint64_t id() const throw() { return _id; } + + /** + * @return Ethernet tap + */ + inline EthernetTap &tap() throw() { return _tap; } + + /** + * Get this network's members + * + * If this is an open network, membership isn't relevant and this doesn't + * mean much. If it's a closed network, frames will only be exchanged to/from + * members. + * + * @return Members of this network + */ + inline std::set<Address> members() const + { + Mutex::Lock _l(_lock); + return _members; + } + + /** + * @param addr Address to check + * @return True if address is a member + */ + inline bool isMember(const Address &addr) const + throw() + { + Mutex::Lock _l(_lock); + return (_members.count(addr) > 0); + } + + /** + * Shortcut to check open() and then isMember() + * + * @param addr Address to check + * @return True if network is open or if address is a member + */ + inline bool isAllowed(const Address &addr) const + throw() + { + Mutex::Lock _l(_lock); + return ((_open)||(_members.count(addr) > 0)); + } + + /** + * @return True if network is open (no membership required) + */ + inline bool open() const + throw() + { + Mutex::Lock _l(_lock); + return _open; + } + + /** + * Update internal multicast group set and return true if changed + * + * @return True if internal multicast group set has changed + */ + inline bool updateMulticastGroups() + { + Mutex::Lock _l(_lock); + return _tap.updateMulticastGroups(_multicastGroups); + } + + /** + * @return Latest set of multicast groups + */ + inline std::set<MulticastGroup> multicastGroups() const + { + Mutex::Lock _l(_lock); + return _multicastGroups; + } + +protected: + virtual void main() + throw(); + +private: + const RuntimeEnvironment *_r; + uint64_t _id; + EthernetTap _tap; + std::set<Address> _members; + std::set<MulticastGroup> _multicastGroups; + bool _open; + Mutex _lock; + + AtomicCounter __refCount; +}; + +} // naemspace ZeroTier + +#endif diff --git a/node/Node.cpp b/node/Node.cpp new file mode 100644 index 00000000..830ee354 --- /dev/null +++ b/node/Node.cpp @@ -0,0 +1,447 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <map> +#include <set> +#include <utility> +#include <algorithm> +#include <list> +#include <vector> +#include <string> + +#ifndef _WIN32 +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <sys/file.h> +#endif + +#include <openssl/sha.h> + +#include "Condition.hpp" +#include "Node.hpp" +#include "Topology.hpp" +#include "Demarc.hpp" +#include "Switch.hpp" +#include "Utils.hpp" +#include "EthernetTap.hpp" +#include "Logger.hpp" +#include "Constants.hpp" +#include "InetAddress.hpp" +#include "Pack.hpp" +#include "RuntimeEnvironment.hpp" +#include "NodeConfig.hpp" +#include "Defaults.hpp" +#include "SysEnv.hpp" +#include "Network.hpp" +#include "MulticastGroup.hpp" +#include "Mutex.hpp" + +#include "../version.h" + +namespace ZeroTier { + +struct _NodeImpl +{ + RuntimeEnvironment renv; + std::string reasonForTerminationStr; + Node::ReasonForTermination reasonForTermination; + bool started; + bool running; + volatile bool terminateNow; + + // Helper used to rapidly terminate from run() + inline Node::ReasonForTermination terminateBecause(Node::ReasonForTermination r,const char *rstr) + { + RuntimeEnvironment *_r = &renv; + LOG("terminating: %s",rstr); + + reasonForTerminationStr = rstr; + reasonForTermination = r; + running = false; + return r; + } +}; + +Node::Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) + throw() : + _impl(new _NodeImpl) +{ + _NodeImpl *impl = (_NodeImpl *)_impl; + + impl->renv.homePath = hp; + impl->renv.autoconfUrlPrefix = urlPrefix; + impl->renv.configAuthorityIdentityStr = configAuthorityIdentity; + + impl->reasonForTermination = Node::NODE_RUNNING; + impl->started = false; + impl->running = false; + impl->terminateNow = false; +} + +Node::~Node() +{ + _NodeImpl *impl = (_NodeImpl *)_impl; + + delete impl->renv.sysEnv; + delete impl->renv.topology; + delete impl->renv.sw; + delete impl->renv.demarc; + delete impl->renv.nc; + delete impl->renv.log; + + delete impl; +} + +/** + * Execute node in current thread + * + * This does not return until the node shuts down. Shutdown may be caused + * by an internally detected condition such as a new upgrade being + * available or a fatal error, or it may be signaled externally using + * the terminate() method. + * + * @return Reason for termination + */ +Node::ReasonForTermination Node::run() + throw() +{ + _NodeImpl *impl = (_NodeImpl *)_impl; + RuntimeEnvironment *_r = (RuntimeEnvironment *)&(impl->renv); + + impl->started = true; + impl->running = true; + + try { +#ifdef ZT_LOG_STDOUT + _r->log = new Logger((const char *)0,(const char *)0,0); +#else + _r->log = new Logger((_r->homePath + ZT_PATH_SEPARATOR_S + "node.log").c_str(),(const char *)0,131072); +#endif + + TRACE("initializing..."); + + if (!_r->configAuthority.fromString(_r->configAuthorityIdentityStr)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"configuration authority identity is not valid"); + + bool gotId = false; + std::string identitySecretPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.secret"); + std::string identityPublicPath(_r->homePath + ZT_PATH_SEPARATOR_S + "identity.public"); + std::string idser; + if (Utils::readFile(identitySecretPath.c_str(),idser)) + gotId = _r->identity.fromString(idser); + if (gotId) { + // Make sure identity.public matches identity.secret + idser = std::string(); + Utils::readFile(identityPublicPath.c_str(),idser); + std::string pubid(_r->identity.toString(false)); + if (idser != pubid) { + if (!Utils::writeFile(identityPublicPath.c_str(),pubid)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write identity.public (home path not writable?)"); + } + } else { + LOG("no identity found, generating one... this might take a few seconds..."); + _r->identity.generate(); + LOG("generated new identity: %s",_r->identity.address().toString().c_str()); + idser = _r->identity.toString(true); + if (!Utils::writeFile(identitySecretPath.c_str(),idser)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write identity.secret (home path not writable?)"); + idser = _r->identity.toString(false); + if (!Utils::writeFile(identityPublicPath.c_str(),idser)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write identity.public (home path not writable?)"); + } + Utils::lockDownFile(identitySecretPath.c_str(),false); + + // Generate ownership verification secret, which can be presented to + // a controlling web site (like ours) to prove ownership of a node and + // permit its configuration to be centrally modified. When ZeroTier One + // requests its config it sends a hash of this secret, and so the + // config server can verify this hash to determine if the secret the + // user presents is correct. + std::string ovsPath(_r->homePath + ZT_PATH_SEPARATOR_S + "thisdeviceismine"); + if (((Utils::now() - Utils::getLastModified(ovsPath.c_str())) >= ZT_OVS_GENERATE_NEW_IF_OLDER_THAN)||(!Utils::readFile(ovsPath.c_str(),_r->ownershipVerificationSecret))) { + _r->ownershipVerificationSecret = ""; + for(unsigned int i=0;i<24;++i) + _r->ownershipVerificationSecret.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[Utils::randomInt<unsigned int>() % 62]); + _r->ownershipVerificationSecret.append(ZT_EOL_S); + if (!Utils::writeFile(ovsPath.c_str(),_r->ownershipVerificationSecret)) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not write 'thisdeviceismine' (home path not writable?)"); + } + Utils::lockDownFile(ovsPath.c_str(),false); + _r->ownershipVerificationSecret = Utils::trim(_r->ownershipVerificationSecret); // trim off CR file is saved with + unsigned char ovsDig[32]; + SHA256_CTX sha; + SHA256_Init(&sha); + SHA256_Update(&sha,_r->ownershipVerificationSecret.data(),_r->ownershipVerificationSecret.length()); + SHA256_Final(ovsDig,&sha); + _r->ownershipVerificationSecretHash = Utils::base64Encode(ovsDig,32); + + // Create the core objects in RuntimeEnvironment: node config, demarcation + // point, switch, network topology database, and system environment + // watcher. + _r->nc = new NodeConfig(_r,_r->autoconfUrlPrefix + _r->identity.address().toString()); + _r->demarc = new Demarc(_r); + _r->sw = new Switch(_r); + _r->topology = new Topology(_r,(_r->homePath + ZT_PATH_SEPARATOR_S + "peer.db").c_str()); + _r->sysEnv = new SysEnv(_r); + + // TODO: make configurable + bool boundPort = false; + for(unsigned int p=ZT_DEFAULT_UDP_PORT;p<(ZT_DEFAULT_UDP_PORT + 128);++p) { + if (_r->demarc->bindLocalUdp(p)) { + boundPort = true; + break; + } + } + if (!boundPort) + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"could not bind any local UDP ports"); + + // TODO: bootstrap off network so we don't have to update code for + // changes in supernodes. + _r->topology->setSupernodes(ZT_DEFAULTS.supernodes); + } catch (std::bad_alloc &exc) { + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"memory allocation failure"); + } catch (std::runtime_error &exc) { + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,exc.what()); + } catch ( ... ) { + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"unknown exception during initialization"); + } + + try { + uint64_t lastPingCheck = 0; + uint64_t lastTopologyClean = Utils::now(); // don't need to do this immediately + uint64_t lastNetworkFingerprintCheck = 0; + uint64_t lastAutoconfigureCheck = 0; + uint64_t networkConfigurationFingerprint = _r->sysEnv->getNetworkConfigurationFingerprint(); + uint64_t lastMulticastCheck = 0; + uint64_t lastMulticastAnnounceAll = 0; + long lastDelayDelta = 0; + + LOG("%s starting version %s",_r->identity.address().toString().c_str(),versionString()); + + while (!impl->terminateNow) { + uint64_t now = Utils::now(); + bool pingAll = false; // set to true to force a ping of *all* known direct links + + // Detect sleep/wake by looking for delay loop pauses that are longer + // than we intended to pause. + if (lastDelayDelta >= ZT_SLEEP_WAKE_DETECTION_THRESHOLD) { + lastNetworkFingerprintCheck = 0; // force network environment check + lastMulticastCheck = 0; // force multicast group check on taps + pingAll = true; + + LOG("probable suspend/resume detected, pausing a moment for things to settle..."); + Thread::sleep(ZT_SLEEP_WAKE_SETTLE_TIME); + } + + // Periodically check our network environment, sending pings out to all + // our direct links if things look like we got a different address. + if ((now - lastNetworkFingerprintCheck) >= ZT_NETWORK_FINGERPRINT_CHECK_DELAY) { + lastNetworkFingerprintCheck = now; + uint64_t fp = _r->sysEnv->getNetworkConfigurationFingerprint(); + if (fp != networkConfigurationFingerprint) { + LOG("netconf fingerprint change: %.16llx != %.16llx, pinging all peers",networkConfigurationFingerprint,fp); + networkConfigurationFingerprint = fp; + pingAll = true; + lastAutoconfigureCheck = 0; // check autoconf after network config change + lastMulticastCheck = 0; // check multicast group membership after network config change + } + } + + if ((now - lastAutoconfigureCheck) >= ZT_AUTOCONFIGURE_CHECK_DELAY) { + // It seems odd to only do this simple check every so often, but the purpose is to + // delay between calls to refreshConfiguration() enough that the previous attempt + // has time to either succeed or fail. Otherwise we'll block the whole loop, since + // config update is guarded by a Mutex. + lastAutoconfigureCheck = now; + if ((now - _r->nc->lastAutoconfigure()) >= ZT_AUTOCONFIGURE_INTERVAL) + _r->nc->refreshConfiguration(); // happens in background + } + + // Periodically check for changes in our local multicast subscriptions and broadcast + // those changes to peers. + if ((now - lastMulticastCheck) >= ZT_MULTICAST_LOCAL_POLL_PERIOD) { + lastMulticastCheck = now; + bool announceAll = ((now - lastMulticastAnnounceAll) >= ZT_MULTICAST_LIKE_ANNOUNCE_ALL_PERIOD); + try { + std::map< SharedPtr<Network>,std::set<MulticastGroup> > toAnnounce; + { + std::vector< SharedPtr<Network> > networks(_r->nc->networks()); + for(std::vector< SharedPtr<Network> >::const_iterator nw(networks.begin());nw!=networks.end();++nw) { + if (((*nw)->updateMulticastGroups())||(announceAll)) + toAnnounce.insert(std::pair< SharedPtr<Network>,std::set<MulticastGroup> >(*nw,(*nw)->multicastGroups())); + } + } + + if (toAnnounce.size()) { + _r->sw->announceMulticastGroups(toAnnounce); + + // Only update lastMulticastAnnounceAll if we've announced something. This keeps + // the announceAll condition true during startup when there are no multicast + // groups until there is at least one. Technically this shouldn't be required as + // updateMulticastGroups() should return true on any change, but why not? + if (announceAll) + lastMulticastAnnounceAll = now; + } + } catch (std::exception &exc) { + LOG("unexpected exception announcing multicast groups: %s",exc.what()); + } catch ( ... ) { + LOG("unexpected exception announcing multicast groups: (unknown)"); + } + } + + if ((now - lastPingCheck) >= ZT_PING_CHECK_DELAY) { + lastPingCheck = now; + try { + if (_r->topology->isSupernode(_r->identity.address())) { + // The only difference in how supernodes behave is here: they only + // actively ping each other and only passively listen for pings + // from anyone else. They also don't send firewall openers, since + // they're never firewalled. + std::vector< SharedPtr<Peer> > sns(_r->topology->supernodePeers()); + for(std::vector< SharedPtr<Peer> >::const_iterator p(sns.begin());p!=sns.end();++p) { + if ((now - (*p)->lastDirectSend()) > ZT_PEER_DIRECT_PING_DELAY) + _r->sw->sendHELLO((*p)->address()); + } + } else { + std::vector< SharedPtr<Peer> > needPing,needFirewallOpener; + + if (pingAll) { + _r->topology->eachPeer(Topology::CollectPeersWithActiveDirectPath(needPing)); + } else { + _r->topology->eachPeer(Topology::CollectPeersThatNeedPing(needPing)); + _r->topology->eachPeer(Topology::CollectPeersThatNeedFirewallOpener(needFirewallOpener)); + } + + for(std::vector< SharedPtr<Peer> >::iterator p(needPing.begin());p!=needPing.end();++p) { + try { + _r->sw->sendHELLO((*p)->address()); + } catch (std::exception &exc) { + LOG("unexpected exception sending HELLO to %s: %s",(*p)->address().toString().c_str()); + } catch ( ... ) { + LOG("unexpected exception sending HELLO to %s: (unknown)",(*p)->address().toString().c_str()); + } + } + + for(std::vector< SharedPtr<Peer> >::iterator p(needFirewallOpener.begin());p!=needFirewallOpener.end();++p) { + try { + (*p)->sendFirewallOpener(_r,now); + } catch (std::exception &exc) { + LOG("unexpected exception sending firewall opener to %s: %s",(*p)->address().toString().c_str(),exc.what()); + } catch ( ... ) { + LOG("unexpected exception sending firewall opener to %s: (unknown)",(*p)->address().toString().c_str()); + } + } + } + } catch (std::exception &exc) { + LOG("unexpected exception running ping check cycle: %s",exc.what()); + } catch ( ... ) { + LOG("unexpected exception running ping check cycle: (unkonwn)"); + } + } + + if ((now - lastTopologyClean) >= ZT_TOPOLOGY_CLEAN_PERIOD) { + lastTopologyClean = now; + _r->topology->clean(); // happens in background + } + + try { + unsigned long delay = std::min((unsigned long)ZT_MIN_SERVICE_LOOP_INTERVAL,_r->sw->doTimerTasks()); + uint64_t start = Utils::now(); + Thread::sleep(delay); + lastDelayDelta = (long)(Utils::now() - start) - (long)delay; + } catch (std::exception &exc) { + LOG("unexpected exception running Switch doTimerTasks: %s",exc.what()); + } catch ( ... ) { + LOG("unexpected exception running Switch doTimerTasks: (unknown)"); + } + } + } catch ( ... ) { + return impl->terminateBecause(Node::NODE_UNRECOVERABLE_ERROR,"unexpected exception during outer main I/O loop"); + } + + return impl->terminateBecause(Node::NODE_NORMAL_TERMINATION,"normal termination"); +} + +/** + * Obtain a human-readable reason for node termination + * + * @return Reason for node termination or NULL if run() has not returned + */ +const char *Node::reasonForTermination() const + throw() +{ + if ((!((_NodeImpl *)_impl)->started)||(((_NodeImpl *)_impl)->running)) + return (const char *)0; + return ((_NodeImpl *)_impl)->reasonForTerminationStr.c_str(); +} + +/** + * Cause run() to return with NODE_NORMAL_TERMINATION + * + * This can be called from a signal handler or another thread to signal a + * running node to shut down. Shutdown may take a few seconds, so run() + * may not return instantly. Multiple calls are ignored. + */ +void Node::terminate() + throw() +{ + ((_NodeImpl *)_impl)->terminateNow = true; +} + +class _VersionStringMaker +{ +public: + char vs[32]; + _VersionStringMaker() + { + sprintf(vs,"%d.%d.%d",(int)ZEROTIER_ONE_VERSION_MAJOR,(int)ZEROTIER_ONE_VERSION_MINOR,(int)ZEROTIER_ONE_VERSION_REVISION); + } + ~_VersionStringMaker() {} +}; +static const _VersionStringMaker __versionString; + +const char *Node::versionString() throw() { return __versionString.vs; } + +unsigned int Node::versionMajor() throw() { return ZEROTIER_ONE_VERSION_MAJOR; } +unsigned int Node::versionMinor() throw() { return ZEROTIER_ONE_VERSION_MINOR; } +unsigned int Node::versionRevision() throw() { return ZEROTIER_ONE_VERSION_REVISION; } + +// Scanned for by loader and/or updater to determine a binary's version +const unsigned char EMBEDDED_VERSION_STAMP[20] = { + 0x6d,0xfe,0xff,0x01,0x90,0xfa,0x89,0x57,0x88,0xa1,0xaa,0xdc,0xdd,0xde,0xb0,0x33, + ZEROTIER_ONE_VERSION_MAJOR, + ZEROTIER_ONE_VERSION_MINOR, + (unsigned char)(((unsigned int)ZEROTIER_ONE_VERSION_REVISION) & 0xff), /* little-endian */ + (unsigned char)((((unsigned int)ZEROTIER_ONE_VERSION_REVISION) >> 8) & 0xff) +}; + +} // namespace ZeroTier diff --git a/node/Node.hpp b/node/Node.hpp new file mode 100644 index 00000000..1cc9a746 --- /dev/null +++ b/node/Node.hpp @@ -0,0 +1,128 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_NODE_HPP +#define _ZT_NODE_HPP + +namespace ZeroTier { + +/** + * A ZeroTier One node + * + * This class hides all its implementation details and all other classes in + * preparation for ZeroTier One being made available in library form for + * embedding in mobile apps. + */ +class Node +{ +public: + /** + * Returned by node main if/when it terminates + */ + enum ReasonForTermination + { + NODE_RUNNING = 0, + NODE_NORMAL_TERMINATION = 1, + NODE_RESTART_FOR_RECONFIGURATION = 2, + NODE_UNRECOVERABLE_ERROR = 3, + NODE_NEW_VERSION_AVAILABLE = 4 + }; + + /** + * Create a new node + * + * The node is not executed until run() is called. + * + * @param hp Home directory path + * @param url URL prefix for autoconfiguration (http and file permitted) + * @param configAuthorityIdentity Public identity used to encrypt/authenticate configuration from this URL (ASCII string format) + * @throws std::invalid_argument Invalid argument supplied to constructor + */ + Node(const char *hp,const char *urlPrefix,const char *configAuthorityIdentity) + throw(); + + ~Node(); + + /** + * Execute node in current thread + * + * This does not return until the node shuts down. Shutdown may be caused + * by an internally detected condition such as a new upgrade being + * available or a fatal error, or it may be signaled externally using + * the terminate() method. + * + * @return Reason for termination + */ + ReasonForTermination run() + throw(); + + /** + * Obtain a human-readable reason for node termination + * + * @return Reason for node termination or NULL if run() has not returned + */ + const char *reasonForTermination() const + throw(); + + /** + * Cause run() to return with NODE_NORMAL_TERMINATION + * + * This can be called from a signal handler or another thread to signal a + * running node to shut down. Shutdown may take a few seconds, so run() + * may not return instantly. Multiple calls are ignored. + */ + void terminate() + throw(); + + /** + * Get the ZeroTier version in major.minor.revision string format + * + * @return Version in string form + */ + static const char *versionString() + throw(); + + static unsigned int versionMajor() throw(); + static unsigned int versionMinor() throw(); + static unsigned int versionRevision() throw(); + +private: + void *const _impl; // private implementation +}; + +/** + * An embedded version code that can be searched for in the binary + * + * This shouldn't be used by users, but is exported to make certain that + * the linker actually includes it in the image. + */ +extern const unsigned char EMBEDDED_VERSION_STAMP[20]; + +} // namespace ZeroTier + +#endif + diff --git a/node/NodeConfig.cpp b/node/NodeConfig.cpp new file mode 100644 index 00000000..47310050 --- /dev/null +++ b/node/NodeConfig.cpp @@ -0,0 +1,206 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <string.h> +#include <memory> +#include <string> + +#include <json/json.h> + +#include "NodeConfig.hpp" +#include "RuntimeEnvironment.hpp" +#include "Defaults.hpp" +#include "Utils.hpp" +#include "Logger.hpp" + +namespace ZeroTier { + +NodeConfig::NodeConfig(const RuntimeEnvironment *renv,const std::string &url) : + _r(renv), + _lastAutoconfigure(0), + _lastAutoconfigureLastModified(), + _url(url), + _autoconfigureLock(), + _networks(), + _networks_m() +{ +} + +NodeConfig::~NodeConfig() +{ + _autoconfigureLock.lock(); // wait for any autoconfs to finish + _autoconfigureLock.unlock(); +} + +void NodeConfig::refreshConfiguration() +{ + _autoconfigureLock.lock(); // unlocked when handler gets called + + TRACE("refreshing autoconfigure URL %s (if modified since: '%s')",_url.c_str(),_lastAutoconfigureLastModified.c_str()); + + std::map<std::string,std::string> reqHeaders; + reqHeaders["X-ZT-ID"] = _r->identity.toString(false); + reqHeaders["X-ZT-OVSH"] = _r->ownershipVerificationSecretHash; + if (_lastAutoconfigureLastModified.length()) + reqHeaders["If-Modified-Since"] = _lastAutoconfigureLastModified; + + new Http::Request(Http::HTTP_METHOD_GET,_url,reqHeaders,std::string(),&NodeConfig::_CBautoconfHandler,this); +} + +void NodeConfig::__CBautoconfHandler(const std::string &lastModified,const std::string &body) +{ + try { + Json::Value root; + Json::Reader reader; + + std::string dec(_r->identity.decrypt(_r->configAuthority,body.data(),body.length())); + if (!dec.length()) { + LOG("autoconfigure from %s failed: data did not decrypt as from config authority %s",_url.c_str(),_r->configAuthority.address().toString().c_str()); + return; + } + TRACE("decrypted autoconf: %s",dec.c_str()); + + if (!reader.parse(dec,root,false)) { + LOG("autoconfigure from %s failed: JSON parse error: %s",_url.c_str(),reader.getFormattedErrorMessages().c_str()); + return; + } + + if (!root.isObject()) { + LOG("autoconfigure from %s failed: not a JSON object",_url.c_str()); + return; + } + + // Configure networks + const Json::Value &networks = root["_networks"]; + if (networks.isArray()) { + Mutex::Lock _l(_networks_m); + for(unsigned int ni=0;ni<networks.size();++ni) { + if (networks[ni].isObject()) { + const Json::Value &nwid_ = networks[ni]["id"]; + uint64_t nwid = nwid_.isNumeric() ? (uint64_t)nwid_.asUInt64() : (uint64_t)strtoull(networks[ni]["id"].asString().c_str(),(char **)0,10); + + if (nwid) { + SharedPtr<Network> nw; + std::map< uint64_t,SharedPtr<Network> >::iterator nwent(_networks.find(nwid)); + if (nwent != _networks.end()) + nw = nwent->second; + else { + try { + nw = SharedPtr<Network>(new Network(_r,nwid)); + _networks[nwid] = nw; + } catch (std::exception &exc) { + LOG("unable to create network %llu: %s",nwid,exc.what()); + } catch ( ... ) { + LOG("unable to create network %llu: unknown exception",nwid); + } + } + + if (nw) { + Mutex::Lock _l2(nw->_lock); + nw->_open = networks[ni]["isOpen"].asBool(); + + // Ensure that TAP device has all the right IP addresses + // TODO: IPv6 might work a tad differently + std::set<InetAddress> allIps; + const Json::Value &addresses = networks[ni]["_addresses"]; + if (addresses.isArray()) { + for(unsigned int ai=0;ai<addresses.size();++ai) { + if (addresses[ai].isString()) { + InetAddress addr(addresses[ai].asString()); + if (addr) { + TRACE("network %llu IP/netmask: %s",nwid,addr.toString().c_str()); + allIps.insert(addr); + } + } + } + } + nw->_tap.setIps(allIps); + + // NOTE: the _members field is optional for open networks, + // since members of open nets do not need to check membership + // of packet senders and mutlicasters. + const Json::Value &members = networks[ni]["_members"]; + nw->_members.clear(); + if (members.isArray()) { + for(unsigned int mi=0;mi<members.size();++mi) { + std::string rawAddr(Utils::unhex(members[mi].asString())); + if (rawAddr.length() == ZT_ADDRESS_LENGTH) { + Address addr(rawAddr.data()); + if ((addr)&&(!addr.isReserved())) { + TRACE("network %llu member: %s",nwid,addr.toString().c_str()); + nw->_members.insert(addr); + } + } + } + } + } + } else { + TRACE("ignored networks[%u], 'id' field missing"); + } + } else { + TRACE("ignored networks[%u], not a JSON object",ni); + } + } + } + + _lastAutoconfigure = Utils::now(); + _lastAutoconfigureLastModified = lastModified; + } catch (std::exception &exc) { + TRACE("exception parsing autoconf URL response: %s",exc.what()); + } catch ( ... ) { + TRACE("unexpected exception parsing autoconf URL response"); + } +} + +bool NodeConfig::_CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body) +{ +#ifdef ZT_TRACE + const RuntimeEnvironment *_r = ((NodeConfig *)arg)->_r; +#endif + + if (code == 200) { + TRACE("200 got autoconfigure response from %s: %u bytes",url.c_str(),(unsigned int)body.length()); + + std::map<std::string,std::string>::const_iterator lm(headers.find("Last-Modified")); + if (lm != headers.end()) + ((NodeConfig *)arg)->__CBautoconfHandler(lm->second,body); + else ((NodeConfig *)arg)->__CBautoconfHandler(std::string(),body); + } else if (code == 304) { + TRACE("304 autoconfigure deferred, remote URL %s not modified",url.c_str()); + ((NodeConfig *)arg)->_lastAutoconfigure = Utils::now(); // still considered a success + } else if (code == 409) { // conflict, ID address in use by another ID + TRACE("%d autoconfigure failed from %s",code,url.c_str()); + } else { + TRACE("%d autoconfigure failed from %s",code,url.c_str()); + } + + ((NodeConfig *)arg)->_autoconfigureLock.unlock(); + return false; // causes Request to delete itself +} + +} // namespace ZeroTier diff --git a/node/NodeConfig.hpp b/node/NodeConfig.hpp new file mode 100644 index 00000000..a6ea6293 --- /dev/null +++ b/node/NodeConfig.hpp @@ -0,0 +1,136 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_NODECONFIG_HPP +#define _ZT_NODECONFIG_HPP + +#include <map> +#include <set> +#include <string> +#include <stdint.h> +#include "SharedPtr.hpp" +#include "Network.hpp" +#include "Utils.hpp" +#include "Http.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Node configuration holder and fetcher + */ +class NodeConfig +{ +public: + /** + * @param renv Runtime environment + * @param url Autoconfiguration URL (http:// or file://) + */ + NodeConfig(const RuntimeEnvironment *renv,const std::string &url); + + ~NodeConfig(); + + /** + * @param nwid Network ID + * @return Network or NULL if no network for that ID + */ + inline SharedPtr<Network> network(uint64_t nwid) const + { + Mutex::Lock _l(_networks_m); + std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.find(nwid)); + return ((n == _networks.end()) ? SharedPtr<Network>() : n->second); + } + + /** + * @return Vector containing all networks + */ + inline std::vector< SharedPtr<Network> > networks() const + { + std::vector< SharedPtr<Network> > nwlist; + Mutex::Lock _l(_networks_m); + for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n) + nwlist.push_back(n->second); + return nwlist; + } + + /** + * @param nwid Network ID + * @return True if this network exists + */ + inline bool hasNetwork(uint64_t nwid) + { + Mutex::Lock _l(_networks_m); + return (_networks.count(nwid) > 0); + } + + /** + * @return Set of network tap device names + */ + inline std::set<std::string> networkTapDeviceNames() const + { + std::set<std::string> tapDevs; + Mutex::Lock _l(_networks_m); + for(std::map< uint64_t,SharedPtr<Network> >::const_iterator n(_networks.begin());n!=_networks.end();++n) + tapDevs.insert(n->second->tap().deviceName()); + return tapDevs; + } + + /** + * @return Time of last successful autoconfigure or refresh + */ + inline uint64_t lastAutoconfigure() const { return _lastAutoconfigure; } + + /** + * @return Autoconfiguration URL + */ + inline const std::string &url() const { return _url; } + + /** + * Refresh configuration from autoconf URL + */ + void refreshConfiguration(); + +private: + void __CBautoconfHandler(const std::string &lastModified,const std::string &body); + static bool _CBautoconfHandler(Http::Request *req,void *arg,const std::string &url,int code,const std::map<std::string,std::string> &headers,const std::string &body); + + const RuntimeEnvironment *_r; + + volatile uint64_t _lastAutoconfigure; + + std::string _lastAutoconfigureLastModified; + std::string _url; + Mutex _autoconfigureLock; + + std::map< uint64_t,SharedPtr<Network> > _networks; + Mutex _networks_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/NonCopyable.hpp b/node/NonCopyable.hpp new file mode 100644 index 00000000..26536a36 --- /dev/null +++ b/node/NonCopyable.hpp @@ -0,0 +1,47 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _NONCOPYABLE_HPP__ +#define _NONCOPYABLE_HPP__ + +namespace ZeroTier { + +/** + * A simple concept that belongs in the C++ language spec + */ +class NonCopyable +{ +protected: + NonCopyable() throw() {} +private: + NonCopyable(const NonCopyable&); + const NonCopyable& operator=(const NonCopyable&); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Pack.cpp b/node/Pack.cpp new file mode 100644 index 00000000..8bac1300 --- /dev/null +++ b/node/Pack.cpp @@ -0,0 +1,159 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <iostream> +#include <string.h> +#include <stdlib.h> +#include "Pack.hpp" +#include "BlobArray.hpp" +#include "Utils.hpp" + +#include <openssl/sha.h> + +namespace ZeroTier { + +std::vector<const Pack::Entry *> Pack::getAll() const +{ + std::vector<const Entry *> v; + for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) + v.push_back(&(e->second)); + return v; +} + +const Pack::Entry *Pack::get(const std::string &name) const +{ + std::map<std::string,Entry>::const_iterator e(_entries.find(name)); + return ((e == _entries.end()) ? (const Entry *)0 : &(e->second)); +} + +const Pack::Entry *Pack::put(const std::string &name,const std::string &content) +{ + SHA256_CTX sha; + + Pack::Entry &e = _entries[name]; + e.name = name; + e.content = content; + + SHA256_Init(&sha); + SHA256_Update(&sha,content.data(),content.length()); + SHA256_Final(e.sha256,&sha); + + e.signedBy.zero(); + e.signature.assign((const char *)0,0); + + return &e; +} + +void Pack::clear() +{ + _entries.clear(); +} + +std::string Pack::serialize() const +{ + BlobArray archive; + for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) { + BlobArray entry; + entry.push_back(e->second.name); + entry.push_back(e->second.content); + entry.push_back(std::string((const char *)e->second.sha256,sizeof(e->second.sha256))); + entry.push_back(std::string((const char *)e->second.signedBy.data(),e->second.signedBy.size())); + entry.push_back(e->second.signature); + archive.push_back(entry.serialize()); + } + + std::string ser(archive.serialize()); + std::string comp; + Utils::compress(ser.begin(),ser.end(),Utils::StringAppendOutput(comp)); + return comp; +} + +bool Pack::deserialize(const void *sd,unsigned int sdlen) +{ + unsigned char dig[32]; + SHA256_CTX sha; + + std::string decomp; + if (!Utils::decompress(((const char *)sd),((const char *)sd) + sdlen,Utils::StringAppendOutput(decomp))) + return false; + + BlobArray archive; + archive.deserialize(decomp.data(),decomp.length()); + clear(); + for(BlobArray::const_iterator i=archive.begin();i!=archive.end();++i) { + BlobArray entry; + entry.deserialize(i->data(),i->length()); + + if (entry.size() != 5) return false; + if (entry[2].length() != 32) return false; // SHA-256 + if (entry[3].length() != ZT_ADDRESS_LENGTH) return false; // Address + + Pack::Entry &e = _entries[entry[0]]; + e.name = entry[0]; + e.content = entry[1]; + + SHA256_Init(&sha); + SHA256_Update(&sha,e.content.data(),e.content.length()); + SHA256_Final(dig,&sha); + if (memcmp(dig,entry[2].data(),32)) return false; // integrity check failed + memcpy(e.sha256,dig,32); + + if (entry[3].length() == ZT_ADDRESS_LENGTH) + e.signedBy = entry[3].data(); + else e.signedBy.zero(); + e.signature = entry[4]; + } + return true; +} + +bool Pack::signAll(const Identity &id) +{ + for(std::map<std::string,Entry>::iterator e=_entries.begin();e!=_entries.end();++e) { + e->second.signedBy = id.address(); + e->second.signature = id.sign(e->second.sha256); + if (!e->second.signature.length()) + return false; + } + return true; +} + +std::vector<const Pack::Entry *> Pack::verifyAll(const Identity &id,bool mandatory) const +{ + std::vector<const Entry *> bad; + for(std::map<std::string,Entry>::const_iterator e=_entries.begin();e!=_entries.end();++e) { + if ((e->second.signedBy)&&(e->second.signature.length())) { + if (id.address() != e->second.signedBy) + bad.push_back(&(e->second)); + else if (!id.verifySignature(e->second.sha256,e->second.signature.data(),e->second.signature.length())) + bad.push_back(&(e->second)); + } else if (mandatory) + bad.push_back(&(e->second)); + } + return bad; +} + +} // namespace ZeroTier diff --git a/node/Pack.hpp b/node/Pack.hpp new file mode 100644 index 00000000..a0aecd6e --- /dev/null +++ b/node/Pack.hpp @@ -0,0 +1,141 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_PACK_HPP +#define _ZT_PACK_HPP + +#include <string> +#include <map> +#include <list> +#include <stdexcept> +#include "Address.hpp" +#include "Identity.hpp" + +namespace ZeroTier { + +/** + * A very simple archive format for distributing packs of files or resources + * + * This is used for things like the auto-updater. It's not suitable for huge + * files, since at present it must work in memory. Packs support signing with + * identities and signature verification. + */ +class Pack +{ +public: + /** + * Pack entry structure for looking up deserialized entries + */ + struct Entry + { + std::string name; + std::string content; + unsigned char sha256[32]; + Address signedBy; + std::string signature; + }; + + Pack() {} + ~Pack() {} + + /** + * @return Vector of all entries + */ + std::vector<const Entry *> getAll() const; + + /** + * Look up an entry + * + * @param name Name to look up + * @return Pointer to entry if it exists or NULL if not found + */ + const Entry *get(const std::string &name) const; + + /** + * Add an entry to this pack + * + * @param name Entry to add + * @param content Entry's contents + * @return The new entry + */ + const Entry *put(const std::string &name,const std::string &content); + + /** + * Remove all entries + */ + void clear(); + + /** + * @return Number of entries in pack + */ + inline unsigned int numEntries() const { return (unsigned int)_entries.size(); } + + /** + * Serialize this pack + * + * @return Serialized form (compressed with LZ4) + */ + std::string serialize() const; + + /** + * Deserialize this pack + * + * Any current contents are lost. This does not verify signatures, + * but does check SHA256 hashes for entry integrity. If the return + * value is false, the pack's contents are undefined. + * + * @param sd Serialized data + * @param sdlen Length of serialized data + * @return True on success, false on deserialization error + */ + bool deserialize(const void *sd,unsigned int sdlen); + inline bool deserialize(const std::string &sd) { return deserialize(sd.data(),sd.length()); } + + /** + * Sign all entries in this pack with a given identity + * + * @param id Identity to sign with + * @return True on signature success, false if error + */ + bool signAll(const Identity &id); + + /** + * Verify all signed entries + * + * @param id Identity to verify against + * @param mandatory If true, require that all entries be signed and fail if no signature + * @return Vector of entries that failed verification or empty vector if all passed + */ + std::vector<const Entry *> verifyAll(const Identity &id,bool mandatory) const; + +private: + std::map<std::string,Entry> _entries; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Packet.cpp b/node/Packet.cpp new file mode 100644 index 00000000..d12f396d --- /dev/null +++ b/node/Packet.cpp @@ -0,0 +1,64 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Packet.hpp" + +namespace ZeroTier { + +const char *Packet::verbString(Verb v) + throw() +{ + switch(v) { + case VERB_NOP: return "NOP"; + case VERB_HELLO: return "HELLO"; + case VERB_ERROR: return "ERROR"; + case VERB_OK: return "OK"; + case VERB_WHOIS: return "WHOIS"; + case VERB_RENDEZVOUS: return "RENDEZVOUS"; + case VERB_FRAME: return "FRAME"; + case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME"; + case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE"; + } + return "(unknown)"; +} + +const char *Packet::errorString(ErrorCode e) + throw() +{ + switch(e) { + case ERROR_NONE: return "NONE"; + case ERROR_INVALID_REQUEST: return "INVALID_REQUEST"; + case ERROR_BAD_PROTOCOL_VERSION: return "BAD_PROTOCOL_VERSION"; + case ERROR_NOT_FOUND: return "NOT_FOUND"; + case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION"; + case ERROR_IDENTITY_INVALID: return "IDENTITY_INVALID"; + case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION"; + } + return "(unknown)"; +} + +} // namespace ZeroTier diff --git a/node/Packet.hpp b/node/Packet.hpp new file mode 100644 index 00000000..3fade674 --- /dev/null +++ b/node/Packet.hpp @@ -0,0 +1,812 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_N_PACKET_HPP +#define _ZT_N_PACKET_HPP + +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <string> +#include <iostream> + +#include "Address.hpp" +#include "HMAC.hpp" +#include "Salsa20.hpp" +#include "Utils.hpp" +#include "Constants.hpp" +#include "Buffer.hpp" + +#include "../ext/lz4/lz4.h" + +/** + * Protocol version + */ +#define ZT_PROTO_VERSION 1 + +/** + * Maximum hop count allowed by packet structure (3 bits, 0-7) + * + * This is not necessarily the maximum hop counter after which + * relaying is no longer performed. + */ +#define ZT_PROTO_MAX_HOPS 7 + +/** + * Header flag indicating that a packet is encrypted with Salsa20 + * + * If this is not set, then the packet's payload is in the clear and the + * HMAC is over this (since there is no ciphertext). Otherwise the HMAC is + * of the ciphertext after encryption. + */ +#define ZT_PROTO_FLAG_ENCRYPTED 0x80 + +/** + * Header flag indicating that a packet is fragmented + * + * If this flag is set, the receiver knows to expect more than one fragment. + * See Packet::Fragment for details. + */ +#define ZT_PROTO_FLAG_FRAGMENTED 0x40 + +/** + * Verb flag indicating payload is compressed with LZ4 + */ +#define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80 + +// Indices of fields in normal packet header -- do not change as this +// might require both code rework and will break compatibility. +#define ZT_PACKET_IDX_IV 0 +#define ZT_PACKET_IDX_DEST 8 +#define ZT_PACKET_IDX_SOURCE 13 +#define ZT_PACKET_IDX_FLAGS 18 +#define ZT_PACKET_IDX_HMAC 19 +#define ZT_PACKET_IDX_VERB 27 +#define ZT_PACKET_IDX_PAYLOAD 28 + +/** + * ZeroTier packet buffer size + * + * This can be changed. This provides enough room for MTU-size packet + * payloads plus some overhead. The subtraction of sizeof(unsigned int) + * makes it an even multiple of 1024 (see Buffer), which might reduce + * memory use a little. + */ +#define ZT_PROTO_MAX_PACKET_LENGTH (3072 - sizeof(unsigned int)) + +/** + * Minimum viable packet length (also length of header) + */ +#define ZT_PROTO_MIN_PACKET_LENGTH ZT_PACKET_IDX_PAYLOAD + +// Indexes of fields in fragment header -- also can't be changed without +// breaking compatibility. +#define ZT_PACKET_FRAGMENT_IDX_PACKET_ID 0 +#define ZT_PACKET_FRAGMENT_IDX_DEST 8 +#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR 13 +#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO 14 +#define ZT_PACKET_FRAGMENT_IDX_HOPS 15 +#define ZT_PACKET_FRAGMENT_IDX_PAYLOAD 16 + +/** + * Value found at ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR in fragments + */ +#define ZT_PACKET_FRAGMENT_INDICATOR ZT_ADDRESS_RESERVED_PREFIX + +/** + * Minimum viable fragment length + */ +#define ZT_PROTO_MIN_FRAGMENT_LENGTH ZT_PACKET_FRAGMENT_IDX_PAYLOAD + +#define ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE 32 + +// Field incides for parsing verbs +#define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_REVISION (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2) +#define ZT_PROTO_VERB_HELLO_IDX_IDENTITY (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8) +#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB + 1) +#define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE (ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8) +#define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD (ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1) +#define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_OK_IDX_IN_RE_VERB + 1) +#define ZT_PROTO_VERB_OK_IDX_PAYLOAD (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8) +#define ZT_PROTO_VERB_WHOIS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN (ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT + 2) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN + 1) +#define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI + 4) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM + ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR + 2) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2) + +// Field indices for parsing OK and ERROR payloads of replies +#define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_WHOIS__ERROR__IDX_ZTADDRESS (ZT_PROTO_VERB_ERROR_IDX_PAYLOAD) + +namespace ZeroTier { + +/** + * ZeroTier packet + * + * Packet format: + * <[8] random initialization vector (doubles as 64-bit packet ID)> + * <[5] destination ZT address> + * <[5] source ZT address> + * <[1] flags (LS 5 bits) and ZT hop count (MS 3 bits)> + * <[8] first 8 bytes of 32-byte HMAC-SHA-256 MAC> + * [... -- begin encryption envelope -- ...] + * <[1] encrypted flags (MS 3 bits) and verb (LS 5 bits)> + * [... verb-specific payload ...] + * + * Packets smaller than 28 bytes are invalid and silently discarded. + * + * MAC is computed on ciphertext *after* encryption. See also: + * + * http://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken + * + * For unencrypted packets, MAC is computed on plaintext. Only HELLO is ever + * sent in the clear, as it's the "here is my public key" message. + */ +class Packet : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH> +{ +public: + /** + * A packet fragment + * + * Fragments are sent if a packet is larger than UDP MTU. The first fragment + * is sent with its normal header with the fragmented flag set. Remaining + * fragments are sent this way. + * + * The fragmented bit indicates that there is at least one fragment. Fragments + * themselves contain the total, so the receiver must "learn" this from the + * first fragment it receives. + * + * Fragments are sent with the following format: + * <[8] packet ID of packet whose fragment this belongs to> + * <[5] destination ZT address> + * <[1] 0xff, a reserved address, signals that this isn't a normal packet> + * <[1] total fragments (most significant 4 bits), fragment no (LS 4 bits)> + * <[1] ZT hop count> + * <[...] fragment data> + * + * The protocol supports a maximum of 16 fragments. If a fragment is received + * before its main packet header, it should be cached for a brief period of + * time to see if its parent arrives. Loss of any fragment constitutes packet + * loss; there is no retransmission mechanism. The receiver must wait for full + * receipt to authenticate and decrypt; there is no per-fragment MAC. (But if + * fragments are corrupt, the MAC will fail for the whole assembled packet.) + */ + class Fragment : public Buffer<ZT_PROTO_MAX_PACKET_LENGTH> + { + public: + Fragment() : + Buffer<ZT_PROTO_MAX_PACKET_LENGTH>() + { + } + + template<unsigned int C2> + Fragment(const Buffer<C2> &b) + throw(std::out_of_range) : + Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b) + { + } + + /** + * Initialize from a packet + * + * @param p Original assembled packet + * @param fragStart Start of fragment (raw index in packet data) + * @param fragLen Length of fragment in bytes + * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off) + * @param fragTotal Total number of fragments (including 0) + * @throws std::out_of_range Packet size would exceed buffer + */ + Fragment(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) + throw(std::out_of_range) + { + init(p,fragStart,fragLen,fragNo,fragTotal); + } + + /** + * Initialize from a packet + * + * @param p Original assembled packet + * @param fragStart Start of fragment (raw index in packet data) + * @param fragLen Length of fragment in bytes + * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off) + * @param fragTotal Total number of fragments (including 0) + * @throws std::out_of_range Packet size would exceed buffer + */ + inline void init(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) + throw(std::out_of_range) + { + if ((fragStart + fragLen) > p.size()) + throw std::out_of_range("Packet::Fragment: tried to construct fragment of packet past its length"); + setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH); + + // NOTE: this copies both the IV/packet ID and the destination address. + memcpy(_b + ZT_PACKET_FRAGMENT_IDX_PACKET_ID,p.data() + ZT_PACKET_IDX_IV,13); + + _b[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] = ZT_PACKET_FRAGMENT_INDICATOR; + _b[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] = (char)(((fragTotal & 0xf) << 4) | (fragNo & 0xf)); + _b[ZT_PACKET_FRAGMENT_IDX_HOPS] = 0; + + memcpy(_b + ZT_PACKET_FRAGMENT_IDX_PAYLOAD,p.data() + fragStart,fragLen); + } + + /** + * Get this fragment's destination + * + * @return Destination ZT address + */ + inline Address destination() const { return Address(_b + ZT_PACKET_FRAGMENT_IDX_DEST); } + + /** + * @return True if fragment is of a valid length + */ + inline bool lengthValid() const { return (_l >= ZT_PACKET_FRAGMENT_IDX_PAYLOAD); } + + /** + * @return ID of packet this is a fragment of + */ + inline uint64_t packetId() const { return at<uint64_t>(ZT_PACKET_FRAGMENT_IDX_PACKET_ID); } + + /** + * @return Total number of fragments in packet + */ + inline unsigned int totalFragments() const { return (((unsigned int)_b[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] >> 4) & 0xf); } + + /** + * @return Fragment number of this fragment + */ + inline unsigned int fragmentNumber() const { return ((unsigned int)_b[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] & 0xf); } + + /** + * @return Fragment ZT hop count + */ + inline unsigned int hops() const { return (unsigned int)_b[ZT_PACKET_FRAGMENT_IDX_HOPS]; } + + /** + * Increment this packet's hop count + */ + inline void incrementHops() + { + _b[ZT_PACKET_FRAGMENT_IDX_HOPS] = (_b[ZT_PACKET_FRAGMENT_IDX_HOPS] + 1) & ZT_PROTO_MAX_HOPS; + } + + /** + * @return Fragment payload + */ + inline unsigned char *payload() { return (unsigned char *)(_b + ZT_PACKET_FRAGMENT_IDX_PAYLOAD); } + inline const unsigned char *payload() const { return (const unsigned char *)(_b + ZT_PACKET_FRAGMENT_IDX_PAYLOAD); } + + /** + * @return Length of payload in bytes + */ + inline unsigned int payloadLength() const { return ((_l > ZT_PACKET_FRAGMENT_IDX_PAYLOAD) ? (_l - ZT_PACKET_FRAGMENT_IDX_PAYLOAD) : 0); } + }; + + /** + * ZeroTier protocol verbs + */ + enum Verb /* Max value: 32 (5 bits) */ + { + /* No operation, payload ignored, no reply */ + VERB_NOP = 0, + + /* Announcement of a node's existence: + * <[1] protocol version> + * <[1] software major version> + * <[1] software minor version> + * <[2] software revision> + * <[8] timestamp (ms since epoch)> + * <[...] binary serialized identity (see Identity)> + * + * OK payload: + * <[8] timestamp (echoed from original HELLO)> + * + * ERROR has no payload. + */ + VERB_HELLO = 1, + + /* Error response: + * <[1] in-re verb> + * <[8] in-re packet ID> + * <[1] error code> + * <[...] error-dependent payload> + */ + VERB_ERROR = 2, + + /* Success response: + * <[1] in-re verb> + * <[8] in-re packet ID> + * <[...] request-specific payload> + */ + VERB_OK = 3, + + /* Query an identity by address: + * <[5] address to look up> + * + * OK response payload: + * <[...] binary serialized identity> + * + * Error payload will be address queried. + */ + VERB_WHOIS = 4, + + /* Meet another node at a given protocol address: + * <[5] ZeroTier address of peer that might be found at this address> + * <[2] 16-bit protocol address port> + * <[1] protocol address length (4 for IPv4, 16 for IPv6)> + * <[...] protocol address (network byte order)> + * + * This is sent by a relaying node to initiate NAT traversal between two + * peers that are communicating by way of indirect relay. The relay will + * send this to both peers at the same time on a periodic basis, telling + * each where it might find the other on the network. + * + * Upon receipt, a peer sends a message such as NOP or HELLO to the other + * peer. Peers only "learn" one anothers' direct addresses when they + * successfully *receive* a message and authenticate it. Optionally, peers + * will usually preface these messages with one or more firewall openers + * to clear the path. + * + * Nodes should implement rate control, limiting the rate at which they + * respond to these packets to prevent their use in DDOS attacks. Nodes + * may also ignore these messages if a peer is not known or is not being + * actively communicated with. + * + * No OK or ERROR is generated. + */ + VERB_RENDEZVOUS = 5, + + /* A ZT-to-ZT unicast ethernet frame: + * <[8] 64-bit network ID> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * MAC addresses are derived from the packet's source and destination + * ZeroTier addresses. ZeroTier does not support VLANs or other extensions + * beyond core Ethernet. + * + * No OK or ERROR is generated. + */ + VERB_FRAME = 6, + + /* A multicast frame: + * <[8] 64-bit network ID> + * <[6] destination multicast Ethernet address> + * <[4] multicast additional distinguishing information (ADI)> + * <[32] multicast propagation bloom filter> + * <[1] 8-bit strict propagation hop count> + * <[2] 16-bit average peer multicast bandwidth load> + * <[6] source Ethernet address> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * No OK or ERROR is generated. + */ + VERB_MULTICAST_FRAME = 7, + + /* Announce interest in multicast group(s): + * <[8] 64-bit network ID> + * <[6] multicast Ethernet address> + * <[4] multicast additional distinguishing information (ADI)> + * [... additional tuples of network/address/adi ...] + * + * OK is generated on successful receipt. + */ + VERB_MULTICAST_LIKE = 8 + }; + + /** + * Error codes for VERB_ERROR + */ + enum ErrorCode + { + /* No error, not actually used in transit */ + ERROR_NONE = 0, + + /* Invalid request */ + ERROR_INVALID_REQUEST = 1, + + /* Bad/unsupported protocol version */ + ERROR_BAD_PROTOCOL_VERSION = 2, + + /* Unknown object queried (e.g. with WHOIS) */ + ERROR_NOT_FOUND = 3, + + /* HELLO pushed an identity whose address is already claimed */ + ERROR_IDENTITY_COLLISION = 4, + + /* Identity was not valid */ + ERROR_IDENTITY_INVALID = 5, + + /* Verb or use case not supported/enabled by this node */ + ERROR_UNSUPPORTED_OPERATION = 6 + }; + + /** + * @param v Verb + * @return String representation (e.g. HELLO, OK) + */ + static const char *verbString(Verb v) + throw(); + + /** + * @param e Error code + * @return String error name + */ + static const char *errorString(ErrorCode e) + throw(); + + template<unsigned int C2> + Packet(const Buffer<C2> &b) + throw(std::out_of_range) : + Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(b) + { + } + + /** + * Construct a new empty packet with a unique random packet ID + * + * Flags and hops will be zero. Other fields and data region are undefined. + * Use the header access methods (setDestination() and friends) to fill out + * the header. Payload should be appended; initial size is header size. + */ + Packet() : + Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH) + { + Utils::getSecureRandom(_b + ZT_PACKET_IDX_IV,8); + _b[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + } + + /** + * Construct a new empty packet with a unique random packet ID + * + * @param dest Destination ZT address + * @param source Source ZT address + * @param v Verb + */ + Packet(const Address &dest,const Address &source,const Verb v) : + Buffer<ZT_PROTO_MAX_PACKET_LENGTH>(ZT_PROTO_MIN_PACKET_LENGTH) + { + Utils::getSecureRandom(_b + ZT_PACKET_IDX_IV,8); + setDestination(dest); + setSource(source); + _b[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + setVerb(v); + } + + /** + * Reset this packet structure for reuse in place + * + * @param dest Destination ZT address + * @param source Source ZT address + * @param v Verb + */ + inline void reset(const Address &dest,const Address &source,const Verb v) + { + setSize(ZT_PROTO_MIN_PACKET_LENGTH); + Utils::getSecureRandom(_b + ZT_PACKET_IDX_IV,8); + setDestination(dest); + setSource(source); + _b[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + setVerb(v); + } + + /** + * Set this packet's destination + * + * @param dest ZeroTier address of destination + */ + inline void setDestination(const Address &dest) + { + for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) + _b[i + ZT_PACKET_IDX_DEST] = dest[i]; + } + + /** + * Set this packet's source + * + * @param source ZeroTier address of source + */ + inline void setSource(const Address &source) + { + for(unsigned int i=0;i<ZT_ADDRESS_LENGTH;++i) + _b[i + ZT_PACKET_IDX_SOURCE] = source[i]; + } + + /** + * Get this packet's destination + * + * @return Destination ZT address + */ + inline Address destination() const { return Address(_b + ZT_PACKET_IDX_DEST); } + + /** + * Get this packet's source + * + * @return Source ZT address + */ + inline Address source() const { return Address(_b + ZT_PACKET_IDX_SOURCE); } + + /** + * @return True if packet is of valid length + */ + inline bool lengthValid() const { return (_l >= ZT_PROTO_MIN_PACKET_LENGTH); } + + /** + * @return True if packet is encrypted + */ + inline bool encrypted() const { return (((unsigned char)_b[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_ENCRYPTED)); } + + /** + * @return True if packet is fragmented (expect fragments) + */ + inline bool fragmented() const { return (((unsigned char)_b[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED)); } + + /** + * Set this packet's fragmented flag + * + * @param f Fragmented flag value + */ + inline void setFragmented(bool f) + { + if (f) + _b[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED; + else _b[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } + + /** + * @return True if compressed (result only valid if unencrypted) + */ + inline bool compressed() const { return (((unsigned char)_b[ZT_PACKET_IDX_VERB] & ZT_PROTO_VERB_FLAG_COMPRESSED)); } + + /** + * @return ZeroTier forwarding hops (0 to 7) + */ + inline unsigned int hops() const { return ((unsigned int)_b[ZT_PACKET_IDX_FLAGS] & 0x07); } + + /** + * Increment this packet's hop count + */ + inline void incrementHops() + { + _b[ZT_PACKET_IDX_FLAGS] = (char)((unsigned char)_b[ZT_PACKET_IDX_FLAGS] & 0xf8) | (((unsigned char)_b[ZT_PACKET_IDX_FLAGS] + 1) & 0x07); + } + + /** + * Get this packet's unique ID (the IV field interpreted as uint64_t) + * + * @return Packet ID + */ + inline uint64_t packetId() const { return at<uint64_t>(ZT_PACKET_IDX_IV); } + + /** + * Set packet verb + * + * This also has the side-effect of clearing any verb flags, such as + * compressed, and so must only be done during packet composition. + * + * @param v New packet verb + */ + inline void setVerb(Verb v) { _b[ZT_PACKET_IDX_VERB] = (char)v; } + + /** + * @return Packet verb (not including flag bits) + */ + inline Verb verb() const { return (Verb)(_b[ZT_PACKET_IDX_VERB] & 0x1f); } + + /** + * @return Length of packet payload + */ + inline unsigned int payloadLength() const throw() { return ((_l < ZT_PROTO_MIN_PACKET_LENGTH) ? 0 : (_l - ZT_PROTO_MIN_PACKET_LENGTH)); } + + /** + * @return Packet payload + */ + inline unsigned char *payload() throw() { return (unsigned char *)(_b + ZT_PACKET_IDX_PAYLOAD); } + inline const unsigned char *payload() const throw() { return (const unsigned char *)(_b + ZT_PACKET_IDX_PAYLOAD); } + + /** + * Compute the HMAC of this packet's payload and set HMAC field + * + * For encrypted packets, this must be called after encryption. + * + * @param key 256-bit (32 byte) key + */ + inline void hmacSet(const void *key) + throw() + { + unsigned char mac[32]; + unsigned char key2[32]; + _mangleKey((const unsigned char *)key,key2); + HMAC::sha256(key2,sizeof(key2),_b + ZT_PACKET_IDX_VERB,(_l >= ZT_PACKET_IDX_VERB) ? (_l - ZT_PACKET_IDX_VERB) : 0,mac); + memcpy(_b + ZT_PACKET_IDX_HMAC,mac,8); + } + + /** + * Check the HMAC of this packet's payload + * + * For encrypted packets, this must be checked before decryption. + * + * @param key 256-bit (32 byte) key + */ + inline bool hmacVerify(const void *key) const + throw() + { + unsigned char mac[32]; + unsigned char key2[32]; + if (_l < ZT_PACKET_IDX_VERB) + return false; // incomplete packets fail + _mangleKey((const unsigned char *)key,key2); + HMAC::sha256(key2,sizeof(key2),_b + ZT_PACKET_IDX_VERB,_l - ZT_PACKET_IDX_VERB,mac); + return (!memcmp(_b + ZT_PACKET_IDX_HMAC,mac,8)); + } + + /** + * Encrypt this packet + * + * @param key 256-bit (32 byte) key + */ + inline void encrypt(const void *key) + throw() + { + _b[ZT_PACKET_IDX_FLAGS] |= ZT_PROTO_FLAG_ENCRYPTED; + unsigned char key2[32]; + _mangleKey((const unsigned char *)key,key2); + Salsa20 s20(key2,256,_b + ZT_PACKET_IDX_IV); + s20.encrypt(_b + ZT_PACKET_IDX_VERB,_b + ZT_PACKET_IDX_VERB,(_l >= ZT_PACKET_IDX_VERB) ? (_l - ZT_PACKET_IDX_VERB) : 0); + } + + /** + * Decrypt this packet + * + * @param key 256-bit (32 byte) key + */ + inline void decrypt(const void *key) + throw() + { + unsigned char key2[32]; + _mangleKey((const unsigned char *)key,key2); + Salsa20 s20(key2,256,_b + ZT_PACKET_IDX_IV); + s20.decrypt(_b + ZT_PACKET_IDX_VERB,_b + ZT_PACKET_IDX_VERB,(_l >= ZT_PACKET_IDX_VERB) ? (_l - ZT_PACKET_IDX_VERB) : 0); + _b[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_ENCRYPTED); + } + + /** + * Attempt to compress payload if not already (must be unencrypted) + * + * This requires that the payload at least contain the verb byte already + * set. The compressed flag in the verb is set if compression successfully + * results in a size reduction. If no size reduction occurs, compression + * is not done and the flag is left cleared. + * + * @return True if compression occurred + */ + inline bool compress() + throw() + { + unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH * 2]; + if ((!compressed())&&(_l > (ZT_PACKET_IDX_PAYLOAD + 32))) { + int pl = (int)(_l - ZT_PACKET_IDX_PAYLOAD); + int cl = LZ4_compress((const char *)(_b + ZT_PACKET_IDX_PAYLOAD),(char *)buf,pl); + if ((cl > 0)&&(cl < pl)) { + _b[ZT_PACKET_IDX_VERB] |= (char)ZT_PROTO_VERB_FLAG_COMPRESSED; + memcpy(_b + ZT_PACKET_IDX_PAYLOAD,buf,cl); + _l = (unsigned int)cl + ZT_PACKET_IDX_PAYLOAD; + return true; + } + } + _b[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + return false; + } + + /** + * Attempt to decompress payload if it is compressed (must be unencrypted) + * + * If payload is compressed, it is decompressed and the compressed verb + * flag is cleared. Otherwise nothing is done and true is returned. + * + * @return True if data is now decompressed and valid, false on error + */ + inline bool uncompress() + throw() + { + unsigned char buf[ZT_PROTO_MAX_PACKET_LENGTH]; + if ((compressed())&&(_l >= ZT_PROTO_MIN_PACKET_LENGTH)) { + if (_l > ZT_PACKET_IDX_PAYLOAD) { + int ucl = LZ4_uncompress_unknownOutputSize((const char *)(_b + ZT_PACKET_IDX_PAYLOAD),(char *)buf,_l - ZT_PACKET_IDX_PAYLOAD,sizeof(buf)); + if ((ucl > 0)&&(ucl <= (int)(capacity() - ZT_PACKET_IDX_PAYLOAD))) { + memcpy(_b + ZT_PACKET_IDX_PAYLOAD,buf,ucl); + _l = (unsigned int)ucl + ZT_PACKET_IDX_PAYLOAD; + } else return false; + } + _b[ZT_PACKET_IDX_VERB] &= ~ZT_PROTO_VERB_FLAG_COMPRESSED; + } + return true; + } + +private: + /** + * Deterministically mangle a 256-bit crypto key based on packet characteristics + * + * This takes the static agreed-upon input key and mangles it using + * info from the packet. This serves two purposes: + * + * (1) It reduces the (already minute) probability of a duplicate key / + * IV combo, which is good since keys are extremely long-lived. Another + * way of saying this is that it increases the effective IV size by + * using other parts of the packet as IV material. + * (2) It causes HMAC to fail should any of the following change: ordering + * of source and dest addresses, flags, IV, or packet size. HMAC has + * no explicit scheme for AAD (additional authenticated data). + * + * NOTE: this function will have to be changed if the order of any packet + * fields or their sizes/padding changes in the spec. + * + * @param in Input key (32 bytes) + * @param out Output buffer (32 bytes) + */ + inline void _mangleKey(const unsigned char *in,unsigned char *out) const + throw() + { + // Random IV (Salsa20 also uses the IV natively, but HMAC doesn't), and + // destination and source addresses. Using dest and source addresses + // gives us a (likely) different key space for a->b vs b->a. + for(unsigned int i=0;i<18;++i) // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 + out[i] = in[i] ^ (unsigned char)_b[i]; + // Flags, but masking off hop count which is altered by forwarding nodes + out[18] = in[18] ^ ((unsigned char)_b[ZT_PACKET_IDX_FLAGS] & 0xf8); + // Raw packet size in bytes -- each raw packet size defines a possibly + // different space of keys. + out[19] = in[19] ^ (unsigned char)(_l & 0xff); + out[20] = in[20] ^ (unsigned char)((_l >> 8) & 0xff); // little endian + // Rest of raw key is used unchanged + for(unsigned int i=21;i<32;++i) + out[i] = in[i]; + } +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Peer.cpp b/node/Peer.cpp new file mode 100644 index 00000000..09732947 --- /dev/null +++ b/node/Peer.cpp @@ -0,0 +1,141 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Peer.hpp" + +namespace ZeroTier { + +Peer::Peer() : + _dirty(false) +{ +} + +Peer::Peer(const Identity &myIdentity,const Identity &peerIdentity) + throw(std::runtime_error) : + _id(peerIdentity), + _dirty(true) +{ + if (!myIdentity.agree(peerIdentity,_keys,sizeof(_keys))) + throw std::runtime_error("new peer identity key agreement failed"); +} + +void Peer::onReceive(const RuntimeEnvironment *_r,Demarc::Port localPort,const InetAddress &fromAddr,unsigned int latency,unsigned int hops,Packet::Verb verb,uint64_t now) +{ + if (!hops) { // direct packet + WanPath *wp = (fromAddr.isV4() ? &_ipv4p : &_ipv6p); + + wp->lastReceive = now; + if (verb == Packet::VERB_FRAME) + wp->lastUnicastFrame = now; + if (latency) + wp->latency = latency; + wp->localPort = localPort; + if (!wp->fixed) + wp->addr = fromAddr; + + _dirty = true; + } +} + +bool Peer::send(const RuntimeEnvironment *_r,const void *data,unsigned int len,bool relay,Packet::Verb verb,uint64_t now) +{ + if ((_ipv6p.isActive(now))||((!(_ipv4p.addr))&&(_ipv6p.addr))) { + if (_r->demarc->send(_ipv6p.localPort,_ipv6p.addr,data,len,-1)) { + _ipv6p.lastSend = now; + if (verb == Packet::VERB_FRAME) + _ipv6p.lastUnicastFrame = now; + _dirty = true; + return true; + } + } + + if (_ipv4p.addr) { + if (_r->demarc->send(_ipv4p.localPort,_ipv4p.addr,data,len,-1)) { + _ipv4p.lastSend = now; + if (verb == Packet::VERB_FRAME) + _ipv4p.lastUnicastFrame = now; + _dirty = true; + return true; + } + } + + return false; +} + +bool Peer::sendFirewallOpener(const RuntimeEnvironment *_r,uint64_t now) +{ + bool sent = false; + if (_ipv4p.addr) { + if (_r->demarc->send(_ipv4p.localPort,_ipv4p.addr,"\0",1,ZT_FIREWALL_OPENER_HOPS)) { + _ipv4p.lastFirewallOpener = now; + _dirty = true; + sent = true; + } + } + if (_ipv6p.addr) { + if (_r->demarc->send(_ipv6p.localPort,_ipv6p.addr,"\0",1,ZT_FIREWALL_OPENER_HOPS)) { + _ipv6p.lastFirewallOpener = now; + _dirty = true; + sent = true; + } + } + return sent; +} + +void Peer::setPathAddress(const InetAddress &addr,bool fixed) +{ + if (addr.isV4()) { + _ipv4p.addr = addr; + _ipv4p.fixed = fixed; + _dirty = true; + } else if (addr.isV6()) { + _ipv6p.addr = addr; + _ipv6p.fixed = fixed; + _dirty = true; + } +} + +void Peer::clearFixedFlag(InetAddress::AddressType t) +{ + switch(t) { + case InetAddress::TYPE_NULL: + _ipv4p.fixed = false; + _ipv6p.fixed = false; + _dirty = true; + break; + case InetAddress::TYPE_IPV4: + _ipv4p.fixed = false; + _dirty = true; + break; + case InetAddress::TYPE_IPV6: + _ipv6p.fixed = false; + _dirty = true; + break; + } +} + +} // namespace ZeroTier diff --git a/node/Peer.hpp b/node/Peer.hpp new file mode 100644 index 00000000..5da19468 --- /dev/null +++ b/node/Peer.hpp @@ -0,0 +1,435 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_PEER_HPP +#define _ZT_PEER_HPP + +#include <algorithm> +#include <utility> +#include <stdexcept> +#include <stdint.h> +#include "Address.hpp" +#include "Utils.hpp" +#include "Identity.hpp" +#include "Constants.hpp" +#include "Logger.hpp" +#include "Demarc.hpp" +#include "RuntimeEnvironment.hpp" +#include "InetAddress.hpp" +#include "EllipticCurveKey.hpp" +#include "Packet.hpp" +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "NonCopyable.hpp" +#include "Mutex.hpp" + +/** + * Max length of serialized peer record + */ +#define ZT_PEER_MAX_SERIALIZED_LENGTH ( \ + 64 + \ + IDENTITY_MAX_BINARY_SERIALIZED_LENGTH + \ + (( \ + (sizeof(uint64_t) * 5) + \ + sizeof(uint16_t) + \ + 1 + \ + sizeof(uint16_t) + \ + 16 + \ + 1 \ + ) * 2) + \ + 64 \ +) + +namespace ZeroTier { + +/** + * A peer on the network + * + * Threading note: + * + * This structure contains no locks at the moment, but also performs no + * memory allocation or pointer manipulation. As a result is is technically + * "safe" for threads, as in won't crash. Right now it's only changed from + * the core I/O thread so this isn't an issue. If multiple I/O threads are + * introduced it ought to have a lock of some kind. + */ +class Peer : NonCopyable +{ + friend class SharedPtr<Peer>; + +private: + ~Peer() {} + +public: + Peer(); + + /** + * Construct a new peer + * + * @param myIdentity Identity of THIS node (for key agreement) + * @param peerIdentity Identity of peer + * @throws std::runtime_error Key agreement with peer's identity failed + */ + Peer(const Identity &myIdentity,const Identity &peerIdentity) + throw(std::runtime_error); + + /** + * @return This peer's ZT address (short for identity().address()) + */ + inline const Address &address() const throw() { return _id.address(); } + + /** + * @return This peer's identity + */ + inline const Identity &identity() const throw() { return _id; } + + /** + * Must be called on authenticated packet receive from this peer + * + * @param _r Runtime environment + * @param localPort Local port on which packet was received + * @param fromAddr Internet address of sender + * @param latency Latency or 0 if unknown + * @param hops ZeroTier (not IP) hops + * @param verb Packet verb + * @param now Current time + */ + void onReceive(const RuntimeEnvironment *_r,Demarc::Port localPort,const InetAddress &fromAddr,unsigned int latency,unsigned int hops,Packet::Verb verb,uint64_t now); + + /** + * Send a UDP packet to this peer + * + * If the active link is timed out (no receives for ping timeout ms), then + * the active link number is incremented after send. This causes sends to + * cycle through links if there is no clear active link. This also happens + * if the send fails for some reason. + * + * @param _r Runtime environment + * @param data Data to send + * @param len Length of packet + * @param relay This is a relay on behalf of another peer (verb is ignored) + * @param verb Packet verb (if not relay) + * @param now Current time + * @return True if packet appears to have been sent, false on local failure + */ + bool send(const RuntimeEnvironment *_r,const void *data,unsigned int len,bool relay,Packet::Verb verb,uint64_t now); + + /** + * Send firewall opener to active link + * + * @param _r Runtime environment + * @param now Current time + * @return True if send appears successful for at least one address type + */ + bool sendFirewallOpener(const RuntimeEnvironment *_r,uint64_t now); + + /** + * Set an address to reach this peer + * + * @param addr Address to set + * @param fixed If true, address is fixed (won't be changed on packet receipt) + */ + void setPathAddress(const InetAddress &addr,bool fixed); + + /** + * Clear the fixed flag for an address type + * + * @param t Type to clear, or TYPE_NULL to clear flag on all types + */ + void clearFixedFlag(InetAddress::AddressType t); + + /** + * @return Last successfully sent firewall opener + */ + uint64_t lastFirewallOpener() const + throw() + { + return std::max(_ipv4p.lastFirewallOpener,_ipv6p.lastFirewallOpener); + } + + /** + * @return Time of last direct packet receive + */ + uint64_t lastDirectReceive() const + throw() + { + return std::max(_ipv4p.lastReceive,_ipv6p.lastReceive); + } + + /** + * @return Time of last direct packet send + */ + uint64_t lastDirectSend() const + throw() + { + return std::max(_ipv4p.lastSend,_ipv6p.lastSend); + } + + /** + * @return Time of most recent unicast frame (actual data transferred) + */ + uint64_t lastUnicastFrame() const + throw() + { + return std::max(_ipv4p.lastUnicastFrame,_ipv6p.lastUnicastFrame); + } + + /** + * @return Lowest of measured latencies of all paths or 0 if unknown + */ + unsigned int latency() const + throw() + { + if (_ipv4p.latency) { + if (_ipv6p.latency) + return std::min(_ipv4p.latency,_ipv6p.latency); + else return _ipv4p.latency; + } else if (_ipv6p.latency) + return _ipv6p.latency; + return 0; + } + + /** + * @return True if this peer has at least one direct IP address path + */ + inline bool hasDirectPath() const + throw() + { + return ((_ipv4p.addr)||(_ipv6p.addr)); + } + + /** + * @param now Current time + * @return True if hasDirectPath() is true and at least one path is active + */ + inline bool hasActiveDirectPath(uint64_t now) const + throw() + { + return ((_ipv4p.isActive(now))||(_ipv6p.isActive(now))); + } + + /** + * @return 256-bit encryption key + */ + inline const unsigned char *cryptKey() const + throw() + { + return _keys; // crypt key is first 32-byte key + } + + /** + * @return 256-bit MAC (message authentication code) key + */ + inline const unsigned char *macKey() const + throw() + { + return (_keys + 32); // mac key is second 32-byte key + } + + /** + * Get and reset dirty flag + * + * @return Previous value of dirty flag before reset + */ + inline bool getAndResetDirty() + throw() + { + bool d = _dirty; + _dirty = false; + return d; + } + + /** + * @return Current value of dirty flag + */ + inline bool dirty() const throw() { return _dirty; } + + template<unsigned int C> + inline void serialize(Buffer<C> &b) + throw(std::out_of_range) + { + b.append((unsigned char)1); // version + b.append(_keys,sizeof(_keys)); + _id.serialize(b,false); + _ipv4p.serialize(b); + _ipv6p.serialize(b); + } + + template<unsigned int C> + inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) + throw(std::out_of_range,std::invalid_argument) + { + unsigned int p = startAt; + + if (b[p++] != 1) + throw std::invalid_argument("Peer: deserialize(): version mismatch"); + + memcpy(_keys,b.field(p,sizeof(_keys)),sizeof(_keys)); p += sizeof(_keys); + p += _id.deserialize(b,p); + p += _ipv4p.deserialize(b,p); + p += _ipv6p.deserialize(b,p); + + _dirty = false; + + return (p - startAt); + } + + /** + * @return True if this Peer is initialized with something + */ + inline operator bool() const throw() { return (_id); } + + /** + * Find a common set of addresses by which two peers can link, if any + * + * @param a Peer A + * @param b Peer B + * @param now Current time + * @return Pair: B's address to send to A, A's address to send to B + */ + static inline std::pair<InetAddress,InetAddress> findCommonGround(const Peer &a,const Peer &b,uint64_t now) + throw() + { + if ((a._ipv6p.isActive(now))&&(b._ipv6p.isActive(now))) + return std::pair<InetAddress,InetAddress>(b._ipv6p.addr,a._ipv6p.addr); + else if ((a._ipv4p.isActive(now))&&(b._ipv4p.isActive(now))) + return std::pair<InetAddress,InetAddress>(b._ipv4p.addr,a._ipv4p.addr); + else if ((a._ipv6p.addr)&&(b._ipv6p.addr)) + return std::pair<InetAddress,InetAddress>(b._ipv6p.addr,a._ipv6p.addr); + else if ((a._ipv4p.addr)&&(b._ipv4p.addr)) + return std::pair<InetAddress,InetAddress>(b._ipv4p.addr,a._ipv4p.addr); + return std::pair<InetAddress,InetAddress>(); + } + +private: + class WanPath + { + public: + WanPath() : + lastSend(0), + lastReceive(0), + lastUnicastFrame(0), + lastFirewallOpener(0), + localPort(Demarc::ANY_PORT), + latency(0), + addr(), + fixed(false) + { + } + + inline bool isActive(const uint64_t now) const + throw() + { + return ((addr)&&((now - lastReceive) < ZT_PEER_LINK_ACTIVITY_TIMEOUT)); + } + + template<unsigned int C> + inline void serialize(Buffer<C> &b) + throw(std::out_of_range) + { + b.append(lastSend); + b.append(lastReceive); + b.append(lastUnicastFrame); + b.append(lastFirewallOpener); + b.append(Demarc::portToInt(localPort)); + b.append((uint16_t)latency); + + b.append((unsigned char)addr.type()); + switch(addr.type()) { + case InetAddress::TYPE_NULL: + break; + case InetAddress::TYPE_IPV4: + b.append(addr.rawIpData(),4); + b.append((uint16_t)addr.port()); + break; + case InetAddress::TYPE_IPV6: + b.append(addr.rawIpData(),16); + b.append((uint16_t)addr.port()); + break; + } + + b.append(fixed ? (unsigned char)1 : (unsigned char)0); + } + + template<unsigned int C> + inline unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0) + throw(std::out_of_range,std::invalid_argument) + { + unsigned int p = startAt; + + lastSend = b.template at<uint64_t>(p); p += sizeof(uint64_t); + lastReceive = b.template at<uint64_t>(p); p += sizeof(uint64_t); + lastUnicastFrame = b.template at<uint64_t>(p); p += sizeof(uint64_t); + lastFirewallOpener = b.template at<uint64_t>(p); p += sizeof(uint64_t); + localPort = Demarc::intToPort(b.template at<uint64_t>(p)); p += sizeof(uint64_t); + latency = b.template at<uint16_t>(p); p += sizeof(uint16_t); + + switch ((InetAddress::AddressType)b[p++]) { + case InetAddress::TYPE_NULL: + addr.zero(); + break; + case InetAddress::TYPE_IPV4: + addr.set(b.field(p,4),4,b.template at<uint16_t>(p + 4)); + p += 4 + sizeof(uint16_t); + break; + case InetAddress::TYPE_IPV6: + addr.set(b.field(p,16),16,b.template at<uint16_t>(p + 16)); + p += 16 + sizeof(uint16_t); + break; + } + + fixed = (b[p++] != 0); + + return (p - startAt); + } + + uint64_t lastSend; + uint64_t lastReceive; + uint64_t lastUnicastFrame; + uint64_t lastFirewallOpener; + Demarc::Port localPort; // ANY_PORT if not defined + unsigned int latency; // 0 if never determined + InetAddress addr; // null InetAddress if path is undefined + bool fixed; // do not learn address from received packets + }; + + unsigned char _keys[32 * 2]; // crypt key[32], mac key[32] + Identity _id; + + WanPath _ipv4p; + WanPath _ipv6p; + + // Fields below this line are not persisted with serialize() + + bool _dirty; + + AtomicCounter __refCount; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp new file mode 100644 index 00000000..a253af6e --- /dev/null +++ b/node/RuntimeEnvironment.hpp @@ -0,0 +1,87 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_RUNTIMEENVIRONMENT_HPP +#define _ZT_RUNTIMEENVIRONMENT_HPP + +#include <string> +#include "Identity.hpp" + +namespace ZeroTier { + +class NodeConfig; +class Logger; +class Demarc; +class Switch; +class Topology; +class SysEnv; + +/** + * Holds global state for an instance of ZeroTier::Node + * + * I do not believe in mutable static variables, period, or in global static + * instances of objects that don't basically represent constants. It makes + * unit testing, embedding, threading, and other things hard and is poor + * practice. + * + * So we put everything that we would want to be global, like Logger, here + * and we give everybody this as _r. The Node creates and initializes this + * on startup and deletes things on shutdown. + */ +class RuntimeEnvironment +{ +public: + RuntimeEnvironment() : + identity(), + log((Logger *)0), + nc((NodeConfig *)0), + demarc((Demarc *)0), + sw((Switch *)0), + topology((Topology *)0) + { + } + + std::string homePath; + std::string autoconfUrlPrefix; + std::string configAuthorityIdentityStr; + std::string ownershipVerificationSecret; + std::string ownershipVerificationSecretHash; // base64 of SHA-256 X16 rounds + + Identity configAuthority; + Identity identity; + + Logger *log; // may be null + NodeConfig *nc; + Demarc *demarc; + Switch *sw; + Topology *topology; + SysEnv *sysEnv; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Salsa20.cpp b/node/Salsa20.cpp new file mode 100644 index 00000000..802f82e9 --- /dev/null +++ b/node/Salsa20.cpp @@ -0,0 +1,221 @@ +/* + * Based on public domain code available at: http://cr.yp.to/snuffle.html + * + * This therefore is public domain. + */ + +#include "Salsa20.hpp" + +#define ROTATE(v,c) (((v) << (c)) | ((v) >> (32 - (c)))) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) ((uint32_t)((v) + (w))) +#define PLUSONE(v) ((uint32_t)((v) + 1)) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define U8TO32_LITTLE(p) (*((const uint32_t *)((const void *)(p)))) +#define U32TO8_LITTLE(c,v) *((uint32_t *)((void *)(c))) = (v) +#else +#ifdef __GNUC__ +#define U8TO32_LITTLE(p) __builtin_bswap32(*((const uint32_t *)((const void *)(p)))) +#define U32TO8_LITTLE(c,v) *((uint32_t *)((void *)(c))) = __builtin_bswap32((v)) +#else +error need be; +#endif +#endif + +namespace ZeroTier { + +static const char *sigma = "expand 32-byte k"; +static const char *tau = "expand 16-byte k"; + +void Salsa20::init(const void *key,unsigned int kbits,const void *iv) + throw() +{ + const char *constants; + const uint8_t *k = (const uint8_t *)key; + + _state[1] = U8TO32_LITTLE(k + 0); + _state[2] = U8TO32_LITTLE(k + 4); + _state[3] = U8TO32_LITTLE(k + 8); + _state[4] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + _state[11] = U8TO32_LITTLE(k + 0); + _state[12] = U8TO32_LITTLE(k + 4); + _state[13] = U8TO32_LITTLE(k + 8); + _state[14] = U8TO32_LITTLE(k + 12); + + _state[6] = U8TO32_LITTLE(((const uint8_t *)iv) + 0); + _state[7] = U8TO32_LITTLE(((const uint8_t *)iv) + 4); + _state[8] = 0; + _state[9] = 0; + + _state[0] = U8TO32_LITTLE(constants + 0); + _state[5] = U8TO32_LITTLE(constants + 4); + _state[10] = U8TO32_LITTLE(constants + 8); + _state[15] = U8TO32_LITTLE(constants + 12); +} + +void Salsa20::encrypt(const void *in,void *out,unsigned int bytes) + throw() +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t tmp[64]; + const uint8_t *m = (const uint8_t *)in; + uint8_t *c = (uint8_t *)out; + uint8_t *ctarget = c; + unsigned int i; + + if (!bytes) return; + + j0 = _state[0]; + j1 = _state[1]; + j2 = _state[2]; + j3 = _state[3]; + j4 = _state[4]; + j5 = _state[5]; + j6 = _state[6]; + j7 = _state[7]; + j8 = _state[8]; + j9 = _state[9]; + j10 = _state[10]; + j11 = _state[11]; + j12 = _state[12]; + j13 = _state[13]; + j14 = _state[14]; + j15 = _state[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j8 = PLUSONE(j8); + if (!j8) { + j9 = PLUSONE(j9); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + _state[8] = j8; + _state[9] = j9; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} + +} // namespace ZeroTier diff --git a/node/Salsa20.hpp b/node/Salsa20.hpp new file mode 100644 index 00000000..488d8754 --- /dev/null +++ b/node/Salsa20.hpp @@ -0,0 +1,73 @@ +/* + * Based on public domain code available at: http://cr.yp.to/snuffle.html + * + * This therefore is public domain. + */ + +#ifndef _ZT_SALSA20_HPP +#define _ZT_SALSA20_HPP + +#include <stdint.h> +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * Salsa20/20 stream cipher + */ +class Salsa20 +{ +public: + Salsa20() throw() {} + + /** + * @param key Key bits + * @param kbits Number of key bits: 128 or 256 (recommended) + * @param iv 64-bit initialization vector + */ + Salsa20(const void *key,unsigned int kbits,const void *iv) + throw() + { + init(key,kbits,iv); + } + + /** + * Initialize cipher + * + * @param key Key bits + * @param kbits Number of key bits: 128 or 256 (recommended) + * @param iv 64-bit initialization vector + */ + void init(const void *key,unsigned int kbits,const void *iv) + throw(); + + /** + * Encrypt data + * + * @param in Input data + * @param out Output buffer + * @param bytes Length of data + */ + void encrypt(const void *in,void *out,unsigned int bytes) + throw(); + + /** + * Decrypt data + * + * @param in Input data + * @param out Output buffer + * @param bytes Length of data + */ + inline void decrypt(const void *in,void *out,unsigned int bytes) + throw() + { + encrypt(in,out,bytes); + } + +private: + uint32_t _state[16]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp new file mode 100644 index 00000000..014e34fa --- /dev/null +++ b/node/SharedPtr.hpp @@ -0,0 +1,133 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_SHAREDPTR_HPP +#define _ZT_SHAREDPTR_HPP + +#include "Mutex.hpp" +#include "AtomicCounter.hpp" + +namespace ZeroTier { + +/** + * Simple reference counted pointer + * + * This is an introspective shared pointer. Classes that need to be reference + * counted must list this as a 'friend' and must have a private instance of + * AtomicCounter called __refCount. They should also have private destructors, + * since only this class should delete them. + * + * Because this is introspective, it is safe to apply to a naked pointer + * multiple times provided there is always at least one holding SharedPtr. + */ +template<typename T> +class SharedPtr +{ +public: + SharedPtr() + throw() : + _ptr((T *)0) + { + } + + SharedPtr(T *obj) + throw() : + _ptr(obj) + { + ++obj->__refCount; + } + + SharedPtr(const SharedPtr &sp) + throw() : + _ptr(sp._getAndInc()) + { + } + + ~SharedPtr() + { + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + } + + inline SharedPtr &operator=(const SharedPtr &sp) + { + if (_ptr != sp._ptr) { + T *p = sp._getAndInc(); + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + _ptr = p; + } + return *this; + } + + inline operator bool() const throw() { return (_ptr); } + inline T &operator*() const throw() { return *_ptr; } + inline T *operator->() const throw() { return _ptr; } + + /** + * @return Raw pointer to held object + */ + inline T *ptr() const throw() { return _ptr; } + + /** + * Set this pointer to null + */ + inline void zero() + { + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + _ptr = (T *)0; + } + + inline bool operator==(const SharedPtr &sp) const throw() { return (_ptr == sp._ptr); } + inline bool operator!=(const SharedPtr &sp) const throw() { return (_ptr != sp._ptr); } + inline bool operator>(const SharedPtr &sp) const throw() { return (_ptr > sp._ptr); } + inline bool operator<(const SharedPtr &sp) const throw() { return (_ptr < sp._ptr); } + inline bool operator>=(const SharedPtr &sp) const throw() { return (_ptr >= sp._ptr); } + inline bool operator<=(const SharedPtr &sp) const throw() { return (_ptr <= sp._ptr); } + +private: + inline T *_getAndInc() const + throw() + { + if (_ptr) + ++_ptr->__refCount; + return _ptr; + } + + T *_ptr; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Switch.cpp b/node/Switch.cpp new file mode 100644 index 00000000..87218b0d --- /dev/null +++ b/node/Switch.cpp @@ -0,0 +1,1022 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <algorithm> +#include <utility> +#include <stdexcept> + +#include "Switch.hpp" +#include "Node.hpp" +#include "EthernetTap.hpp" +#include "InetAddress.hpp" +#include "Topology.hpp" +#include "RuntimeEnvironment.hpp" +#include "Defaults.hpp" +#include "Peer.hpp" +#include "NodeConfig.hpp" +#include "Demarc.hpp" + +#include "../version.h" + +namespace ZeroTier { + +Switch::Switch(const RuntimeEnvironment *renv) : + _r(renv) +{ + memset(_multicastHistory,0,sizeof(_multicastHistory)); +} + +Switch::~Switch() +{ +} + +void Switch::onRemotePacket(Demarc::Port localPort,const InetAddress &fromAddr,const Buffer<4096> &data) +{ + Packet packet; + + try { + if (data.size() > ZT_PROTO_MIN_FRAGMENT_LENGTH) { + // Message is long enough to be a Packet or Packet::Fragment + + if (data[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) { + // Looks like a Packet::Fragment + Packet::Fragment fragment(data); + + Address destination(fragment.destination()); + if (destination != _r->identity.address()) { + // Fragment is not for us, so try to relay it + + if (fragment.hops() < ZT_RELAY_MAX_HOPS) { + fragment.incrementHops(); + + SharedPtr<Peer> relayTo = _r->topology->getPeer(destination); + if ((!relayTo)||(!relayTo->send(_r,fragment.data(),fragment.size(),true,Packet::VERB_NOP,Utils::now()))) { + relayTo = _r->topology->getBestSupernode(); + if (relayTo) + relayTo->send(_r,fragment.data(),fragment.size(),true,Packet::VERB_NOP,Utils::now()); + } + } else { + TRACE("dropped relay [fragment](%s) -> %s, max hops exceeded",fromAddr.toString().c_str(),destination.toString().c_str()); + } + } else { + // Fragment looks like ours + + uint64_t pid = fragment.packetId(); + unsigned int fno = fragment.fragmentNumber(); + unsigned int tf = fragment.totalFragments(); + + if ((tf <= ZT_MAX_PACKET_FRAGMENTS)&&(fno < ZT_MAX_PACKET_FRAGMENTS)&&(fno > 0)&&(tf > 1)) { + // Fragment appears basically sane. Its fragment number must be + // 1 or more, since a Packet with fragmented bit set is fragment 0. + // Total fragments must be more than 1, otherwise why are we + // seeing a Packet::Fragment? + + Mutex::Lock _l(_defragQueue_m); + std::map< uint64_t,DefragQueueEntry >::iterator dqe(_defragQueue.find(pid)); + + if (dqe == _defragQueue.end()) { + // We received a Packet::Fragment without its head, so queue it and wait + + DefragQueueEntry &dq = _defragQueue[pid]; + dq.creationTime = Utils::now(); + dq.frags[fno - 1] = fragment; + dq.totalFragments = tf; // total fragment count is known + dq.haveFragments = 1 << fno; // we have only this fragment + //TRACE("fragment (%u/%u) of %.16llx from %s",fno + 1,tf,pid,fromAddr.toString().c_str()); + } else if (!(dqe->second.haveFragments & (1 << fno))) { + // We have other fragments and maybe the head, so add this one and check + + dqe->second.frags[fno - 1] = fragment; + dqe->second.totalFragments = tf; + //TRACE("fragment (%u/%u) of %.16llx from %s",fno + 1,tf,pid,fromAddr.toString().c_str()); + + if (Utils::countBits(dqe->second.haveFragments |= (1 << fno)) == tf) { + // We have all fragments -- assemble and process full Packet + + //TRACE("packet %.16llx is complete, assembling and processing...",pid); + packet = dqe->second.frag0; + for(unsigned int f=1;f<tf;++f) + packet.append(dqe->second.frags[f - 1].payload(),dqe->second.frags[f - 1].payloadLength()); + _defragQueue.erase(dqe); + + goto Switch_onRemotePacket_complete_packet_handler; + } + } // else this is a duplicate fragment, ignore + } + } + + } else if (data.size() > ZT_PROTO_MIN_PACKET_LENGTH) { + // Looks like a Packet -- either unfragmented or a fragmented packet head + packet = data; + + Address destination(packet.destination()); + if (destination != _r->identity.address()) { + // Packet is not for us, so try to relay it + + if (packet.hops() < ZT_RELAY_MAX_HOPS) { + packet.incrementHops(); + + SharedPtr<Peer> relayTo = _r->topology->getPeer(destination); + if ((relayTo)&&(relayTo->send(_r,packet.data(),packet.size(),true,Packet::VERB_NOP,Utils::now()))) { + // TODO: don't unite immediately, wait until the peers have exchanged a packet or two + unite(packet.source(),destination,false); // periodically try to get them to talk directly + } else { + relayTo = _r->topology->getBestSupernode(); + if (relayTo) + relayTo->send(_r,packet.data(),packet.size(),true,Packet::VERB_NOP,Utils::now()); + } + } else { + TRACE("dropped relay %s(%s) -> %s, max hops exceeded",packet.source().toString().c_str(),fromAddr.toString().c_str(),destination.toString().c_str()); + } + } else if (packet.fragmented()) { + // Packet is the head of a fragmented packet series + + uint64_t pid = packet.packetId(); + Mutex::Lock _l(_defragQueue_m); + std::map< uint64_t,DefragQueueEntry >::iterator dqe(_defragQueue.find(pid)); + + if (dqe == _defragQueue.end()) { + // If we have no other fragments yet, create an entry and save the head + + DefragQueueEntry &dq = _defragQueue[pid]; + dq.creationTime = Utils::now(); + dq.frag0 = packet; + dq.totalFragments = 0; // 0 == unknown, waiting for Packet::Fragment + dq.haveFragments = 1; // head is first bit (left to right) + //TRACE("fragment (0/?) of %.16llx from %s",pid,fromAddr.toString().c_str()); + } else if (!(dqe->second.haveFragments & 1)) { + // If we have other fragments but no head, see if we are complete with the head + + if ((dqe->second.totalFragments)&&(Utils::countBits(dqe->second.haveFragments |= 1) == dqe->second.totalFragments)) { + // We have all fragments -- assemble and process full Packet + + //TRACE("packet %.16llx is complete, assembling and processing...",pid); + // packet already contains head, so append fragments + for(unsigned int f=1;f<dqe->second.totalFragments;++f) + packet.append(dqe->second.frags[f - 1].payload(),dqe->second.frags[f - 1].payloadLength()); + _defragQueue.erase(dqe); + + goto Switch_onRemotePacket_complete_packet_handler; + } else { + // Still waiting on more fragments, so queue the head + + dqe->second.frag0 = packet; + } + } // else this is a duplicate head, ignore + } else { + // Packet is unfragmented, so just process it + goto Switch_onRemotePacket_complete_packet_handler; + } + + } + } + + // If we made it here and didn't jump over, we either queued a fragment + // or dropped an invalid or duplicate one. (The goto looks easier to + // understand than having a million returns up there.) + return; + +Switch_onRemotePacket_complete_packet_handler: + // Packets that get here are ours and are fully assembled. Don't worry -- if + // they are corrupt HMAC authentication will reject them later. + + { + //TRACE("%s : %s -> %s",fromAddr.toString().c_str(),packet.source().toString().c_str(),packet.destination().toString().c_str()); + PacketServiceAttemptResult r = _tryHandleRemotePacket(localPort,fromAddr,packet); + if (r != PACKET_SERVICE_ATTEMPT_OK) { + Address source(packet.source()); + { + Mutex::Lock _l(_rxQueue_m); + std::multimap< Address,RXQueueEntry >::iterator qe(_rxQueue.insert(std::pair< Address,RXQueueEntry >(source,RXQueueEntry()))); + qe->second.creationTime = Utils::now(); + qe->second.packet = packet; + qe->second.localPort = localPort; + qe->second.fromAddr = fromAddr; + } + if (r == PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN) + _requestWhois(source); + } + } + } catch (std::exception &ex) { + TRACE("dropped packet from %s: %s",fromAddr.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped packet from %s: unexpected exception",fromAddr.toString().c_str()); + } +} + +void Switch::onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data) +{ + if (from != network->tap().mac()) { + LOG("ignored tap: %s -> %s %s (bridging is not supported)",from.toString().c_str(),to.toString().c_str(),Utils::etherTypeName(etherType)); + return; + } + + if (to == network->tap().mac()) { + // Right thing to do? Will this ever happen? + TRACE("weird OS behavior: ethernet frame received from self, reflecting"); + network->tap().put(from,to,etherType,data.data(),data.size()); + return; + } + + if ((etherType != ZT_ETHERTYPE_ARP)&&(etherType != ZT_ETHERTYPE_IPV4)&&(etherType != ZT_ETHERTYPE_IPV6)) { + LOG("ignored tap: %s -> %s %s (not a supported etherType)",from.toString().c_str(),to.toString().c_str(),Utils::etherTypeName(etherType)); + return; + } + + if (to.isMulticast()) { + MulticastGroup mg(to,0); + + // Handle special cases: IPv4 ARP + if ((etherType == ZT_ETHERTYPE_ARP)&&(data.size() == 28)&&(data[2] == 0x08)&&(data[3] == 0x00)&&(data[4] == 6)&&(data[5] == 4)&&(data[7] == 0x01)) + mg = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(data.field(24,4),4,0)); + + // Remember this message's CRC, but don't drop if we've already seen it + // since it's our own. + _checkAndUpdateMulticastHistory(from,mg.mac(),data.data(),data.size(),network->id(),Utils::now()); + + // Start multicast propagation with empty bloom filter + unsigned char bloom[ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE]; + memset(bloom,0,sizeof(bloom)); + _propagateMulticast(network,bloom,mg,0,0,from,etherType,data.data(),data.size()); + } else if (to.isZeroTier()) { + // Simple unicast frame from us to another node + Address toZT(to.data + 1); + if (network->isAllowed(toZT)) { + Packet outp(toZT,_r->identity.address(),Packet::VERB_FRAME); + outp.append(network->id()); + outp.append((uint16_t)etherType); + outp.append(data); + outp.compress(); + send(outp,true); + } else { + TRACE("UNICAST: %s -> %s %s (dropped, destination not a member of closed network %llu)",from.toString().c_str(),to.toString().c_str(),Utils::etherTypeName(etherType),network->id()); + } + } else { + TRACE("UNICAST: %s -> %s %s (dropped, destination MAC not ZeroTier)",from.toString().c_str(),to.toString().c_str(),Utils::etherTypeName(etherType)); + } +} + +void Switch::send(const Packet &packet,bool encrypt) +{ + //TRACE("%.16llx %s -> %s (size: %u) (enc: %s)",packet.packetId(),Packet::verbString(packet.verb()),packet.destination().toString().c_str(),packet.size(),(encrypt ? "yes" : "no")); + + PacketServiceAttemptResult r = _trySend(packet,encrypt); + if (r != PACKET_SERVICE_ATTEMPT_OK) { + { + Mutex::Lock _l(_txQueue_m); + std::multimap< Address,TXQueueEntry >::iterator qe(_txQueue.insert(std::pair< Address,TXQueueEntry >(packet.destination(),TXQueueEntry()))); + qe->second.creationTime = Utils::now(); + qe->second.packet = packet; + qe->second.encrypt = encrypt; + } + if (r == PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN) + _requestWhois(packet.destination()); + } +} + +void Switch::sendHELLO(const Address &dest) +{ + Packet outp(dest,_r->identity.address(),Packet::VERB_HELLO); + outp.append((unsigned char)ZT_PROTO_VERSION); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); + outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); + outp.append(Utils::now()); + _r->identity.serialize(outp,false); + send(outp,false); +} + +bool Switch::unite(const Address &p1,const Address &p2,bool force) +{ + SharedPtr<Peer> p1p = _r->topology->getPeer(p1); + if (!p1p) + return false; + SharedPtr<Peer> p2p = _r->topology->getPeer(p2); + if (!p2p) + return false; + + uint64_t now = Utils::now(); + + std::pair<InetAddress,InetAddress> cg(Peer::findCommonGround(*p1p,*p2p,now)); + if (!(cg.first)) + return false; + + // Addresses are sorted in key for last unite attempt map for order + // invariant lookup: (p1,p2) == (p2,p1) + Array<Address,2> uniteKey; + if (p1 >= p2) { + uniteKey[0] = p2; + uniteKey[1] = p1; + } else { + uniteKey[0] = p1; + uniteKey[1] = p2; + } + { + Mutex::Lock _l(_lastUniteAttempt_m); + std::map< Array< Address,2 >,uint64_t >::const_iterator e(_lastUniteAttempt.find(uniteKey)); + if ((!force)&&(e != _lastUniteAttempt.end())&&((now - e->second) < ZT_MIN_UNITE_INTERVAL)) + return false; + else _lastUniteAttempt[uniteKey] = now; + } + + TRACE("unite: %s(%s) <> %s(%s)",p1.toString().c_str(),cg.second.toString().c_str(),p2.toString().c_str(),cg.first.toString().c_str()); + + { // tell p1 where to find p2 + Packet outp(p1,_r->identity.address(),Packet::VERB_RENDEZVOUS); + outp.append(p2.data(),ZT_ADDRESS_LENGTH); + outp.append((uint16_t)cg.first.port()); + if (cg.first.isV6()) { + outp.append((unsigned char)16); + outp.append(cg.first.rawIpData(),16); + } else { + outp.append((unsigned char)4); + outp.append(cg.first.rawIpData(),4); + } + outp.encrypt(p1p->cryptKey()); + outp.hmacSet(p1p->macKey()); + p1p->send(_r,outp.data(),outp.size(),false,Packet::VERB_RENDEZVOUS,now); + } + { // tell p2 where to find p1 + Packet outp(p2,_r->identity.address(),Packet::VERB_RENDEZVOUS); + outp.append(p1.data(),ZT_ADDRESS_LENGTH); + outp.append((uint16_t)cg.second.port()); + if (cg.second.isV6()) { + outp.append((unsigned char)16); + outp.append(cg.second.rawIpData(),16); + } else { + outp.append((unsigned char)4); + outp.append(cg.second.rawIpData(),4); + } + outp.encrypt(p2p->cryptKey()); + outp.hmacSet(p2p->macKey()); + p2p->send(_r,outp.data(),outp.size(),false,Packet::VERB_RENDEZVOUS,now); + } + + return true; +} + +unsigned long Switch::doTimerTasks() +{ + unsigned long nextDelay = ~((unsigned long)0); // big number, caller will cap return value + uint64_t now = Utils::now(); + + { + Mutex::Lock _l(_rendezvousQueue_m); + for(std::map< Address,RendezvousQueueEntry >::iterator i(_rendezvousQueue.begin());i!=_rendezvousQueue.end();) { + if (now >= i->second.fireAtTime) { + SharedPtr<Peer> withPeer = _r->topology->getPeer(i->first); + if (withPeer) { + TRACE("sending NAT-T NOP to %s(%s)",i->first.toString().c_str(),i->second.inaddr.toString().c_str()); + Packet outp(i->first,_r->identity.address(),Packet::VERB_NOP); + outp.append("ZT",2); // arbitrary payload + outp.hmacSet(withPeer->macKey()); + _r->demarc->send(i->second.localPort,i->second.inaddr,outp.data(),outp.size(),-1); + } + _rendezvousQueue.erase(i++); + } else { + nextDelay = std::min(nextDelay,(unsigned long)(i->second.fireAtTime - now)); + ++i; + } + } + } + + { + Mutex::Lock _l(_outstandingWhoisRequests_m); + for(std::map< Address,WhoisRequest >::iterator i(_outstandingWhoisRequests.begin());i!=_outstandingWhoisRequests.end();) { + unsigned long since = (unsigned long)(now - i->second.lastSent); + if (since >= ZT_WHOIS_RETRY_DELAY) { + if (i->second.retries >= ZT_MAX_WHOIS_RETRIES) { + TRACE("WHOIS %s timed out",i->first.toString().c_str()); + _outstandingWhoisRequests.erase(i++); + continue; + } else { + i->second.lastSent = now; + i->second.peersConsulted[i->second.retries] = _sendWhoisRequest(i->first,i->second.peersConsulted,i->second.retries); + ++i->second.retries; + TRACE("WHOIS %s (retry %u)",i->first.toString().c_str(),i->second.retries); + nextDelay = std::min(nextDelay,(unsigned long)ZT_WHOIS_RETRY_DELAY); + } + } else nextDelay = std::min(nextDelay,ZT_WHOIS_RETRY_DELAY - since); + ++i; + } + } + + { + Mutex::Lock _l(_txQueue_m); + for(std::multimap< Address,TXQueueEntry >::iterator i(_txQueue.begin());i!=_txQueue.end();) { + if (_trySend(i->second.packet,i->second.encrypt) == PACKET_SERVICE_ATTEMPT_OK) + _txQueue.erase(i++); + else if ((now - i->second.creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) { + TRACE("TX %s -> %s timed out",i->second.packet.source().toString().c_str(),i->second.packet.destination().toString().c_str()); + _txQueue.erase(i++); + } else ++i; + } + } + { + Mutex::Lock _l(_rxQueue_m); + for(std::multimap< Address,RXQueueEntry >::iterator i(_rxQueue.begin());i!=_rxQueue.end();) { + if ((now - i->second.creationTime) > ZT_RECEIVE_QUEUE_TIMEOUT) { + TRACE("RX from %s timed out waiting for WHOIS",i->second.packet.source().toString().c_str()); + _rxQueue.erase(i++); + } else ++i; + } + } + + { + Mutex::Lock _l(_defragQueue_m); + for(std::map< uint64_t,DefragQueueEntry >::iterator i(_defragQueue.begin());i!=_defragQueue.end();) { + if ((now - i->second.creationTime) > ZT_FRAGMENTED_PACKET_RECEIVE_TIMEOUT) { + TRACE("incomplete fragmented packet %.16llx timed out, fragments discarded",i->first); + _defragQueue.erase(i++); + } else ++i; + } + } + + return std::max(nextDelay,(unsigned long)50); // minimum delay +} + +void Switch::announceMulticastGroups(const std::map< SharedPtr<Network>,std::set<MulticastGroup> > &allMemberships) +{ + std::vector< SharedPtr<Peer> > directPeers; + _r->topology->eachPeer(Topology::CollectPeersWithActiveDirectPath(directPeers)); + +#ifdef ZT_TRACE + unsigned int totalMulticastGroups = 0; + for(std::map< SharedPtr<Network>,std::set<MulticastGroup> >::const_iterator i(allMemberships.begin());i!=allMemberships.end();++i) + totalMulticastGroups += (unsigned int)i->second.size(); + TRACE("announcing %u multicast groups for %u networks to %u peers",totalMulticastGroups,(unsigned int)allMemberships.size(),(unsigned int)directPeers.size()); +#endif + + for(std::vector< SharedPtr<Peer> >::iterator p(directPeers.begin());p!=directPeers.end();++p) { + Packet outp((*p)->address(),_r->identity.address(),Packet::VERB_MULTICAST_LIKE); + + for(std::map< SharedPtr<Network>,std::set<MulticastGroup> >::const_iterator nwmgs(allMemberships.begin());nwmgs!=allMemberships.end();++nwmgs) { + if ((nwmgs->first->open())||(_r->topology->isSupernode((*p)->address()))||(nwmgs->first->isMember((*p)->address()))) { + for(std::set<MulticastGroup>::iterator mg(nwmgs->second.begin());mg!=nwmgs->second.end();++mg) { + if ((outp.size() + 18) > ZT_UDP_DEFAULT_PAYLOAD_MTU) { + send(outp,true); + outp.reset((*p)->address(),_r->identity.address(),Packet::VERB_MULTICAST_LIKE); + } + + outp.append((uint64_t)nwmgs->first->id()); + outp.append(mg->mac().data,6); + outp.append((uint32_t)mg->adi()); + } + } + } + + if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) + send(outp,true); + } +} + +void Switch::_CBaddPeerFromHello(void *arg,const SharedPtr<Peer> &p,Topology::PeerVerifyResult result) +{ + _CBaddPeerFromHello_Data *req = (_CBaddPeerFromHello_Data *)arg; + const RuntimeEnvironment *_r = req->parent->_r; + + switch(result) { + case Topology::PEER_VERIFY_ACCEPTED_NEW: + case Topology::PEER_VERIFY_ACCEPTED_ALREADY_HAVE: + case Topology::PEER_VERIFY_ACCEPTED_DISPLACED_INVALID_ADDRESS: { + Packet outp(req->source,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(req->helloPacketId); + outp.append(req->helloTimestamp); + outp.encrypt(p->cryptKey()); + outp.hmacSet(p->macKey()); + req->parent->_r->demarc->send(req->localPort,req->fromAddr,outp.data(),outp.size(),-1); + } break; + case Topology::PEER_VERIFY_REJECTED_INVALID_IDENTITY: { + Packet outp(req->source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(req->helloPacketId); + outp.append((unsigned char)Packet::ERROR_IDENTITY_INVALID); + outp.encrypt(p->cryptKey()); + outp.hmacSet(p->macKey()); + req->parent->_r->demarc->send(req->localPort,req->fromAddr,outp.data(),outp.size(),-1); + } break; + case Topology::PEER_VERIFY_REJECTED_DUPLICATE: + case Topology::PEER_VERIFY_REJECTED_DUPLICATE_TRIAGED: { + Packet outp(req->source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(req->helloPacketId); + outp.append((unsigned char)Packet::ERROR_IDENTITY_COLLISION); + outp.encrypt(p->cryptKey()); + outp.hmacSet(p->macKey()); + req->parent->_r->demarc->send(req->localPort,req->fromAddr,outp.data(),outp.size(),-1); + } break; + } + + delete req; +} + +void Switch::_CBaddPeerFromWhois(void *arg,const SharedPtr<Peer> &p,Topology::PeerVerifyResult result) +{ + Switch *d = (Switch *)arg; + + switch(result) { + case Topology::PEER_VERIFY_ACCEPTED_NEW: + case Topology::PEER_VERIFY_ACCEPTED_ALREADY_HAVE: + case Topology::PEER_VERIFY_ACCEPTED_DISPLACED_INVALID_ADDRESS: + d->_outstandingWhoisRequests_m.lock(); + d->_outstandingWhoisRequests.erase(p->identity().address()); + d->_outstandingWhoisRequests_m.unlock(); + d->_retryPendingFor(p->identity().address()); + break; + default: + break; + } +} + +void Switch::_propagateMulticast(const SharedPtr<Network> &network,unsigned char *bloom,const MulticastGroup &mg,unsigned int mcHops,unsigned int mcLoadFactor,const MAC &from,unsigned int etherType,const void *data,unsigned int len) +{ + SharedPtr<Peer> propPeers[ZT_MULTICAST_PROPAGATION_BREADTH]; + unsigned int np = _r->topology->pickMulticastPropagationPeers(network->id(),Address(),bloom,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE * 8,ZT_MULTICAST_PROPAGATION_BREADTH,mg,propPeers); + + for(unsigned int i=0;i<np;++i) + Utils::bloomAdd(bloom,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE,propPeers[i]->address().sum()); + + for(unsigned int i=0;i<np;++i) { + Packet outp(propPeers[i]->address(),_r->identity.address(),Packet::VERB_MULTICAST_FRAME); + outp.append(network->id()); + outp.append(mg.mac().data,6); + outp.append((uint32_t)mg.adi()); + outp.append(bloom,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE); + outp.append((uint8_t)mcHops); + outp.append((uint16_t)mcLoadFactor); + outp.append(from.data,6); + outp.append((uint16_t)etherType); + outp.append(data,len); + outp.compress(); + send(outp,true); + } +} + +Switch::PacketServiceAttemptResult Switch::_tryHandleRemotePacket(Demarc::Port localPort,const InetAddress &fromAddr,Packet &packet) +{ + // NOTE: We assume any packet that's made it here is for us. If it's not it + // will fail HMAC validation and be discarded anyway, amounting to a second + // layer of sanity checking. + + Address source(packet.source()); + + if ((!packet.encrypted())&&(packet.verb() == Packet::VERB_HELLO)) { + // Unencrypted HELLOs are handled here since they are used to + // populate our identity cache in the first place. Thus we might get + // a HELLO for someone for whom we don't have a Peer record. + TRACE("HELLO from %s(%s)",source.toString().c_str(),fromAddr.toString().c_str()); + _doHELLO(localPort,fromAddr,packet); + return PACKET_SERVICE_ATTEMPT_OK; + } + + SharedPtr<Peer> peer = _r->topology->getPeer(source); + if (peer) { + uint64_t now = Utils::now(); + unsigned int latency = 0; + + if (!packet.hmacVerify(peer->macKey())) { + TRACE("dropped packet from %s(%s), HMAC authentication failed (size: %u)",source.toString().c_str(),fromAddr.toString().c_str(),packet.size()); + return PACKET_SERVICE_ATTEMPT_OK; + } + if (packet.encrypted()) { + packet.decrypt(peer->cryptKey()); + } else if (packet.verb() != Packet::VERB_NOP) { + TRACE("ODD: %s from %s wasn't encrypted",Packet::verbString(packet.verb()),source.toString().c_str()); + } + if (!packet.uncompress()) { + TRACE("dropped packet from %s(%s), compressed data invalid",source.toString().c_str(),fromAddr.toString().c_str()); + return PACKET_SERVICE_ATTEMPT_OK; + } + + switch(packet.verb()) { + case Packet::VERB_NOP: // these are sent for NAT-t + TRACE("NOP from %s(%s) (probably NAT-t)",source.toString().c_str(),fromAddr.toString().c_str()); + break; + case Packet::VERB_HELLO: // usually they're handled up top, but technically an encrypted HELLO is legal + _doHELLO(localPort,fromAddr,packet); + break; + case Packet::VERB_ERROR: + try { +#ifdef ZT_TRACE + Packet::Verb inReVerb = (Packet::Verb)packet[ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB]; + Packet::ErrorCode errorCode = (Packet::ErrorCode)packet[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE]; + TRACE("ERROR %s from %s in-re %s",Packet::errorString(errorCode),source.toString().c_str(),Packet::verbString(inReVerb)); +#endif + // TODO: handle key errors, such as duplicate identity + } catch (std::exception &ex) { + TRACE("dropped ERROR from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped ERROR from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + case Packet::VERB_OK: + try { + Packet::Verb inReVerb = (Packet::Verb)packet[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB]; + switch(inReVerb) { + case Packet::VERB_HELLO: + latency = std::min((unsigned int)(now - packet.at<uint64_t>(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP)),(unsigned int)0xffff); + TRACE("OK(HELLO), latency to %s: %u",source.toString().c_str(),latency); + break; + case Packet::VERB_WHOIS: + // Right now we only query supernodes for WHOIS and only accept + // OK back from them. If we query other nodes, we'll have to + // do something to prevent WHOIS cache poisoning such as + // using the packet ID field in the OK packet to match with the + // original query. Technically we should be doing this anyway. + if (_r->topology->isSupernode(source)) + _r->topology->addPeer(SharedPtr<Peer>(new Peer(_r->identity,Identity(packet,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY))),&Switch::_CBaddPeerFromWhois,this); + break; + default: + break; + } + } catch (std::exception &ex) { + TRACE("dropped OK from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped OK from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + case Packet::VERB_WHOIS: { + if (packet.payloadLength() == ZT_ADDRESS_LENGTH) { + SharedPtr<Peer> p(_r->topology->getPeer(Address(packet.payload()))); + if (p) { + Packet outp(source,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_WHOIS); + outp.append(packet.packetId()); + p->identity().serialize(outp,false); + outp.encrypt(peer->cryptKey()); + outp.hmacSet(peer->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + TRACE("sent WHOIS response to %s for %s",source.toString().c_str(),Address(packet.payload()).toString().c_str()); + } else { + Packet outp(source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_WHOIS); + outp.append(packet.packetId()); + outp.append((unsigned char)Packet::ERROR_NOT_FOUND); + outp.append(packet.payload(),ZT_ADDRESS_LENGTH); + outp.encrypt(peer->cryptKey()); + outp.hmacSet(peer->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + TRACE("sent WHOIS ERROR to %s for %s (not found)",source.toString().c_str(),Address(packet.payload()).toString().c_str()); + } + } else { + TRACE("dropped WHOIS from %s: missing or invalid address",source.toString().c_str()); + } + } break; + case Packet::VERB_RENDEZVOUS: + try { + Address with(packet.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH)); + RendezvousQueueEntry qe; + if (_r->topology->getPeer(with)) { + unsigned int port = packet.at<uint16_t>(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT); + unsigned int addrlen = packet[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN]; + if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) { + qe.inaddr.set(packet.field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); + qe.fireAtTime = now + ZT_RENDEZVOUS_NAT_T_DELAY; // then send real packet in a few ms + qe.localPort = _r->demarc->pick(qe.inaddr); + TRACE("RENDEZVOUS from %s says %s might be at %s, starting NAT-t",source.toString().c_str(),with.toString().c_str(),qe.inaddr.toString().c_str()); + _r->demarc->send(qe.localPort,qe.inaddr,"\0",1,ZT_FIREWALL_OPENER_HOPS); // start with firewall opener + { + Mutex::Lock _l(_rendezvousQueue_m); + _rendezvousQueue[with] = qe; + } + } else { + TRACE("dropped corrupt RENDEZVOUS from %s (bad address or port)",source.toString().c_str()); + } + } else { + TRACE("ignored RENDEZVOUS from %s for unknown peer %s",source.toString().c_str(),with.toString().c_str()); + } + } catch (std::exception &ex) { + TRACE("dropped RENDEZVOUS from %s: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped RENDEZVOUS from %s: unexpected exception",source.toString().c_str()); + } + break; + case Packet::VERB_FRAME: + try { + SharedPtr<Network> network(_r->nc->network(packet.at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID))); + if (network) { + if (network->isAllowed(source)) { + unsigned int etherType = packet.at<uint16_t>(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE); + if ((etherType != ZT_ETHERTYPE_ARP)&&(etherType != ZT_ETHERTYPE_IPV4)&&(etherType != ZT_ETHERTYPE_IPV6)) { + TRACE("dropped FRAME from %s: unsupported ethertype",source.toString().c_str()); + } else if (packet.size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) { + network->tap().put(source.toMAC(),network->tap().mac(),etherType,packet.data() + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD,packet.size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD); + } + } else { + TRACE("dropped FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id()); + } + } else { + TRACE("dropped FRAME from %s: network %llu unknown",source.toString().c_str(),packet.at<uint64_t>(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); + } + } catch (std::exception &ex) { + TRACE("dropped FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped FRAME from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + case Packet::VERB_MULTICAST_FRAME: + try { + SharedPtr<Network> network(_r->nc->network(packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID))); + if (network) { + if (network->isAllowed(source)) { + if (packet.size() > ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD) { + MulticastGroup mg(MAC(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_MULTICAST_MAC,6)),packet.at<uint32_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ADI)); + unsigned char bloom[ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE]; + memcpy(bloom,packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_BLOOM,ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE),ZT_PROTO_VERB_MULTICAST_FRAME_BLOOM_FILTER_SIZE); + unsigned int hops = packet[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_HOPS]; + unsigned int loadFactor = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_LOAD_FACTOR); + MAC fromMac(packet.field(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FROM_MAC,6)); + unsigned int etherType = packet.at<uint16_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE); + + if ((fromMac.isZeroTier())&&(network->isAllowed(Address(fromMac)))) { + if (_checkAndUpdateMulticastHistory(fromMac,mg.mac(),packet.data() + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,network->id(),now)) { + TRACE("dropped MULTICAST_FRAME from %s: duplicate multicast",source.toString().c_str()); + } else { + //TRACE("MULTICAST_FRAME: %s -> %s (adi: %.8lx), %u bytes, net: %llu",fromMac.toString().c_str(),mg.mac().toString().c_str(),(unsigned long)mg.adi(),packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,network->id()); + network->tap().put(fromMac,mg.mac(),etherType,packet.data() + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD); + + // TODO: implement load factor based propagation rate limitation + // How it will work: each node will adjust loadFactor based on + // its current load of multicast traffic. Then it will probabilistically + // fail to propagate, with the probability being based on load factor. + // This will need some in-the-field testing and tuning to get right. + _propagateMulticast(network,bloom,mg,hops+1,loadFactor,fromMac,etherType,packet.data() + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD,packet.size() - ZT_PROTO_VERB_MULTICAST_FRAME_IDX_PAYLOAD); + } + } else { + TRACE("dropped MULTICAST_FRAME from %s: ultimate sender %s not a member of closed network %llu",source.toString().c_str(),fromMac.toString().c_str(),network->id()); + } + } + } else { + TRACE("dropped MULTICAST_FRAME from %s: not a member of closed network %llu",source.toString().c_str(),network->id()); + } + } else { + TRACE("dropped MULTICAST_FRAME from %s: network %llu unknown",source.toString().c_str(),packet.at<uint64_t>(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID)); + } + } catch (std::exception &ex) { + TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped MULTICAST_FRAME from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + case Packet::VERB_MULTICAST_LIKE: + try { + unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; + unsigned int numAccepted = 0; + while ((ptr + 18) <= packet.size()) { + uint64_t nwid = packet.at<uint64_t>(ptr); ptr += 8; + SharedPtr<Network> network(_r->nc->network(nwid)); + if (network) { + if (network->isAllowed(source)) { + MAC mac(packet.field(ptr,6)); ptr += 6; + uint32_t adi = packet.at<uint32_t>(ptr); ptr += 4; + TRACE("peer %s likes multicast group %s:%.8lx on network %llu",source.toString().c_str(),mac.toString().c_str(),(unsigned long)adi,nwid); + _r->topology->likesMulticastGroup(nwid,MulticastGroup(mac,adi),source,now); + ++numAccepted; + } else { + TRACE("ignored MULTICAST_LIKE from %s: not a member of closed network %llu",source.toString().c_str(),nwid); + } + } else { + TRACE("ignored MULTICAST_LIKE from %s: network %llu unknown",source.toString().c_str(),nwid); + } + } + + Packet outp(source,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_MULTICAST_LIKE); + outp.append(packet.packetId()); + outp.append((uint16_t)numAccepted); + outp.encrypt(peer->cryptKey()); + outp.hmacSet(peer->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + } catch (std::exception &ex) { + TRACE("dropped MULTICAST_LIKE from %s: unexpected exception: %s",source.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped MULTICAST_LIKE from %s: unexpected exception: (unknown)",source.toString().c_str()); + } + break; + default: + TRACE("ignored unrecognized verb %.2x from %s",(unsigned int)packet.verb(),source.toString().c_str()); + break; + } + + // Update peer timestamps and learn new links + peer->onReceive(_r,localPort,fromAddr,latency,packet.hops(),packet.verb(),now); + } else return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN; + + return PACKET_SERVICE_ATTEMPT_OK; +} + +void Switch::_doHELLO(Demarc::Port localPort,const InetAddress &fromAddr,Packet &packet) +{ + Address source(packet.source()); + try { + unsigned int protoVersion = packet[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION]; + unsigned int vMajor = packet[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION]; + unsigned int vMinor = packet[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION]; + unsigned int vRevision = packet.at<uint16_t>(ZT_PROTO_VERB_HELLO_IDX_REVISION); + uint64_t timestamp = packet.at<uint64_t>(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP); + Identity id(packet,ZT_PROTO_VERB_HELLO_IDX_IDENTITY); + + SharedPtr<Peer> candidate(new Peer(_r->identity,id)); + candidate->setPathAddress(fromAddr,false); + + // Initial sniff test + if (protoVersion != ZT_PROTO_VERSION) { + TRACE("rejected HELLO from %s(%s): invalid protocol version",source.toString().c_str(),fromAddr.toString().c_str()); + Packet outp(source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(packet.packetId()); + outp.append((unsigned char)Packet::ERROR_BAD_PROTOCOL_VERSION); + outp.encrypt(candidate->cryptKey()); + outp.hmacSet(candidate->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + return; + } + if (id.address().isReserved()) { + TRACE("rejected HELLO from %s(%s): identity has reserved address",source.toString().c_str(),fromAddr.toString().c_str()); + Packet outp(source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(packet.packetId()); + outp.append((unsigned char)Packet::ERROR_IDENTITY_INVALID); + outp.encrypt(candidate->cryptKey()); + outp.hmacSet(candidate->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + return; + } + if (id.address() != source) { + TRACE("rejected HELLO from %s(%s): identity is not for sender of packet (HELLO is a self-announcement)",source.toString().c_str(),fromAddr.toString().c_str()); + Packet outp(source,_r->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(packet.packetId()); + outp.append((unsigned char)Packet::ERROR_INVALID_REQUEST); + outp.encrypt(candidate->cryptKey()); + outp.hmacSet(candidate->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + return; + } + + // Is this a HELLO for a peer we already know? If so just update its + // packet receive stats and send an OK. + SharedPtr<Peer> existingPeer(_r->topology->getPeer(id.address())); + if ((existingPeer)&&(existingPeer->identity() == id)) { + existingPeer->onReceive(_r,localPort,fromAddr,0,packet.hops(),Packet::VERB_HELLO,Utils::now()); + + Packet outp(source,_r->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append(packet.packetId()); + outp.append(timestamp); + outp.encrypt(existingPeer->cryptKey()); + outp.hmacSet(existingPeer->macKey()); + _r->demarc->send(localPort,fromAddr,outp.data(),outp.size(),-1); + return; + } + + // Otherwise we call addPeer() and set up a callback to handle the verdict + _CBaddPeerFromHello_Data *arg = new _CBaddPeerFromHello_Data; + arg->parent = this; + arg->source = source; + arg->fromAddr = fromAddr; + arg->localPort = localPort; + arg->vMajor = vMajor; + arg->vMinor = vMinor; + arg->vRevision = vRevision; + arg->helloPacketId = packet.packetId(); + arg->helloTimestamp = timestamp; + _r->topology->addPeer(candidate,&Switch::_CBaddPeerFromHello,arg); + } catch (std::exception &ex) { + TRACE("dropped HELLO from %s(%s): %s",source.toString().c_str(),fromAddr.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped HELLO from %s(%s): unexpected exception",source.toString().c_str(),fromAddr.toString().c_str()); + } +} + +void Switch::_requestWhois(const Address &addr) +{ + TRACE("requesting WHOIS for %s",addr.toString().c_str()); + _sendWhoisRequest(addr,(const Address *)0,0); + Mutex::Lock _l(_outstandingWhoisRequests_m); + std::pair< std::map< Address,WhoisRequest >::iterator,bool > entry(_outstandingWhoisRequests.insert(std::pair<Address,WhoisRequest>(addr,WhoisRequest()))); + entry.first->second.lastSent = Utils::now(); + entry.first->second.retries = 0; // reset retry count if entry already existed +} + +Address Switch::_sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted) +{ + SharedPtr<Peer> supernode(_r->topology->getBestSupernode(peersAlreadyConsulted,numPeersAlreadyConsulted)); + if (supernode) { + Packet outp(supernode->address(),_r->identity.address(),Packet::VERB_WHOIS); + outp.append(addr.data(),ZT_ADDRESS_LENGTH); + outp.encrypt(supernode->cryptKey()); + outp.hmacSet(supernode->macKey()); + supernode->send(_r,outp.data(),outp.size(),false,Packet::VERB_WHOIS,Utils::now()); + return supernode->address(); + } + return Address(); +} + +Switch::PacketServiceAttemptResult Switch::_trySend(const Packet &packet,bool encrypt) +{ + SharedPtr<Peer> peer(_r->topology->getPeer(packet.destination())); + if (peer) { + uint64_t now = Utils::now(); + + bool isRelay; + SharedPtr<Peer> via; + if ((_r->topology->isSupernode(peer->address()))||(peer->hasActiveDirectPath(now))) { + isRelay = false; + via = peer; + } else { + isRelay = true; + via = _r->topology->getBestSupernode(); + if (!via) + return PACKET_SERVICE_ATTEMPT_SEND_FAILED; + } + + Packet tmp(packet); + + unsigned int chunkSize = std::min(tmp.size(),(unsigned int)ZT_UDP_DEFAULT_PAYLOAD_MTU); + tmp.setFragmented(chunkSize < tmp.size()); + + if (encrypt) + tmp.encrypt(peer->cryptKey()); + tmp.hmacSet(peer->macKey()); + + Packet::Verb verb = packet.verb(); + if (via->send(_r,tmp.data(),chunkSize,isRelay,verb,now)) { + if (chunkSize < tmp.size()) { + // Too big for one bite, fragment the rest + unsigned int fragStart = chunkSize; + unsigned int remaining = tmp.size() - chunkSize; + unsigned int fragsRemaining = (remaining / (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + if ((fragsRemaining * (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) + ++fragsRemaining; + unsigned int totalFragments = fragsRemaining + 1; + + for(unsigned int f=0;f<fragsRemaining;++f) { + chunkSize = std::min(remaining,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + Packet::Fragment frag(tmp,fragStart,chunkSize,f + 1,totalFragments); + if (!via->send(_r,frag.data(),frag.size(),isRelay,verb,now)) { + TRACE("WARNING: packet send to %s failed on later fragment #%u (check IP layer buffer sizes?)",via->address().toString().c_str(),f + 1); + return PACKET_SERVICE_ATTEMPT_SEND_FAILED; + } + fragStart += chunkSize; + remaining -= chunkSize; + } + } + + return PACKET_SERVICE_ATTEMPT_OK; + } + return PACKET_SERVICE_ATTEMPT_SEND_FAILED; + } + return PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN; +} + +void Switch::_retryPendingFor(const Address &addr) +{ + { + Mutex::Lock _l(_txQueue_m); + std::pair< std::multimap< Address,TXQueueEntry >::iterator,std::multimap< Address,TXQueueEntry >::iterator > eqrange = _txQueue.equal_range(addr); + for(std::multimap< Address,TXQueueEntry >::iterator i(eqrange.first);i!=eqrange.second;) { + if (_trySend(i->second.packet,i->second.encrypt) == PACKET_SERVICE_ATTEMPT_OK) + _txQueue.erase(i++); + else ++i; + } + } + { + Mutex::Lock _l(_rxQueue_m); + std::pair< std::multimap< Address,RXQueueEntry >::iterator,std::multimap< Address,RXQueueEntry >::iterator > eqrange = _rxQueue.equal_range(addr); + for(std::multimap< Address,RXQueueEntry >::iterator i(eqrange.first);i!=eqrange.second;) { + if (_tryHandleRemotePacket(i->second.localPort,i->second.fromAddr,i->second.packet) == PACKET_SERVICE_ATTEMPT_OK) + _rxQueue.erase(i++); + else ++i; + } + } +} + +} // namespace ZeroTier diff --git a/node/Switch.hpp b/node/Switch.hpp new file mode 100644 index 00000000..f9244cba --- /dev/null +++ b/node/Switch.hpp @@ -0,0 +1,260 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_N_SWITCH_HPP +#define _ZT_N_SWITCH_HPP + +#include <map> +#include <set> +#include <vector> + +#include "Mutex.hpp" +#include "MAC.hpp" +#include "NonCopyable.hpp" +#include "Constants.hpp" +#include "Packet.hpp" +#include "Utils.hpp" +#include "InetAddress.hpp" +#include "Topology.hpp" +#include "Array.hpp" +#include "Network.hpp" +#include "SharedPtr.hpp" +#include "Demarc.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class EthernetTap; +class Logger; +class Node; +class Peer; + +/** + * Core of the distributed Ethernet switch and protocol implementation + */ +class Switch : NonCopyable +{ +public: + Switch(const RuntimeEnvironment *renv); + ~Switch(); + + /** + * Called when a packet is received from the real network + * + * @param localPort Local port on which packet was received + * @param fromAddr Internet IP address of origin + * @param data Packet data + */ + void onRemotePacket(Demarc::Port localPort,const InetAddress &fromAddr,const Buffer<4096> &data); + + /** + * Called when a packet comes from a local Ethernet tap + * + * @param network Which network's TAP did this packet come from? + * @param from Originating MAC address + * @param to Destination MAC address + * @param etherType Ethernet packet type + * @param data Ethernet payload + */ + void onLocalEthernet(const SharedPtr<Network> &network,const MAC &from,const MAC &to,unsigned int etherType,const Buffer<4096> &data); + + /** + * Send a packet to a ZeroTier address (destination in packet) + * + * The packet must be fully composed with source and destination but not + * yet encrypted. If the destination peer is known the packet + * is sent immediately. Otherwise it is queued and a WHOIS is dispatched. + * + * The packet may be compressed. Compression isn't done here. + * + * Needless to say, the packet's source must be this node. Otherwise it + * won't be encrypted right. (This is not used for relaying.) + * + * @param packet Packet to send + * @param encrypt Encrypt packet payload? (always true except for HELLO) + */ + void send(const Packet &packet,bool encrypt); + + /** + * Send a HELLO announcement + * + * @param dest Address of destination + */ + void sendHELLO(const Address &dest); + + /** + * Send RENDEZVOUS to two peers to permit them to directly connect + * + * This only works if both peers are known, with known working direct + * links to this peer. The best link for each peer is sent to the other. + * + * A rate limiter is in effect via the _lastUniteAttempt map. If force + * is true, a unite attempt is made even if one has been made less than + * ZT_MIN_UNITE_INTERVAL milliseconds ago. + * + * @param p1 One of two peers (order doesn't matter) + * @param p2 Second of pair + * @param force If true, send now regardless of interval + */ + bool unite(const Address &p1,const Address &p2,bool force); + + /** + * Perform retries and other periodic timer tasks + * + * @return Number of milliseconds until doTimerTasks() should be run again + */ + unsigned long doTimerTasks(); + + /** + * Announce multicast group memberships + * + * This efficiently announces memberships, sending single packets with + * many LIKEs. + * + * @param allMemberships Memberships for a number of networks + */ + void announceMulticastGroups(const std::map< SharedPtr<Network>,std::set<MulticastGroup> > &allMemberships); + +private: + // Returned by _send() and _processRemotePacket() to indicate what happened + enum PacketServiceAttemptResult + { + PACKET_SERVICE_ATTEMPT_OK, + PACKET_SERVICE_ATTEMPT_PEER_UNKNOWN, + PACKET_SERVICE_ATTEMPT_SEND_FAILED + }; + + struct _CBaddPeerFromHello_Data + { + Switch *parent; + Address source; + InetAddress fromAddr; + int localPort; + unsigned int vMajor,vMinor,vRevision; + uint64_t helloPacketId; + uint64_t helloTimestamp; + }; + static void _CBaddPeerFromHello(void *arg,const SharedPtr<Peer> &p,Topology::PeerVerifyResult result); + static void _CBaddPeerFromWhois(void *arg,const SharedPtr<Peer> &p,Topology::PeerVerifyResult result); // arg == this + + void _propagateMulticast(const SharedPtr<Network> &network,unsigned char *bloom,const MulticastGroup &mg,unsigned int mcHops,unsigned int mcLoadFactor,const MAC &from,unsigned int etherType,const void *data,unsigned int len); + PacketServiceAttemptResult _tryHandleRemotePacket(Demarc::Port localPort,const InetAddress &fromAddr,Packet &packet); + void _doHELLO(Demarc::Port localPort,const InetAddress &fromAddr,Packet &packet); + void _requestWhois(const Address &addr); + Address _sendWhoisRequest(const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted); + PacketServiceAttemptResult _trySend(const Packet &packet,bool encrypt); + void _retryPendingFor(const Address &addr); + + // Updates entry for crc in multicast history, returns true if already + // present in history and not expired. + inline bool _checkAndUpdateMulticastHistory(const MAC &fromMac,const MAC &toMulticastMac,const void *payload,unsigned int len,const uint64_t nwid,const uint64_t now) + { + uint64_t crc = Utils::crc64(0,fromMac.data,6); + crc = Utils::crc64(crc,toMulticastMac.data,6); + crc = Utils::crc64(crc,payload,len); + crc += nwid; // also include network ID + + uint64_t earliest = 0xffffffffffffffffULL; + unsigned long earliestIdx = 0; + for(unsigned int i=0;i<ZT_MULTICAST_DEDUP_HISTORY_LENGTH;++i) { + if (_multicastHistory[i][0] == crc) { + uint64_t then = _multicastHistory[i][1]; + _multicastHistory[i][1] = now; + return ((now - then) < ZT_MULTICAST_DEDUP_HISTORY_EXPIRE); + } else if (_multicastHistory[i][1] < earliest) { + earliest = _multicastHistory[i][1]; + earliestIdx = i; + } + } + + _multicastHistory[earliestIdx][0] = crc; // replace oldest entry + _multicastHistory[earliestIdx][1] = now; + + return false; + } + + const RuntimeEnvironment *const _r; + + // Multicast packet CRC64's for packets we've received recently, to reject + // duplicates during propagation. [0] is CRC64, [1] is time. + uint64_t _multicastHistory[ZT_MULTICAST_DEDUP_HISTORY_LENGTH][2]; + + struct WhoisRequest + { + uint64_t lastSent; + Address peersConsulted[ZT_MAX_WHOIS_RETRIES]; // by retry + unsigned int retries; // 0..ZT_MAX_WHOIS_RETRIES + }; + std::map< Address,WhoisRequest > _outstandingWhoisRequests; + Mutex _outstandingWhoisRequests_m; + + struct TXQueueEntry + { + uint64_t creationTime; + Packet packet; // unencrypted/untagged for TX queue + bool encrypt; + }; + std::multimap< Address,TXQueueEntry > _txQueue; // by destination address + Mutex _txQueue_m; + + struct RXQueueEntry + { + uint64_t creationTime; + Demarc::Port localPort; + Packet packet; // encrypted/tagged + InetAddress fromAddr; + }; + std::multimap< Address,RXQueueEntry > _rxQueue; // by source address + Mutex _rxQueue_m; + + struct DefragQueueEntry + { + uint64_t creationTime; + Packet frag0; + Packet::Fragment frags[ZT_MAX_PACKET_FRAGMENTS - 1]; + unsigned int totalFragments; // 0 if only frag0 received, waiting for frags + uint32_t haveFragments; // bit mask, LSB to MSB + }; + std::map< uint64_t,DefragQueueEntry > _defragQueue; + Mutex _defragQueue_m; + + std::map< Array< Address,2 >,uint64_t > _lastUniteAttempt; // key is always sorted in ascending order, for set-like behavior + Mutex _lastUniteAttempt_m; + + struct RendezvousQueueEntry + { + InetAddress inaddr; + uint64_t fireAtTime; + Demarc::Port localPort; + }; + std::map< Address,RendezvousQueueEntry > _rendezvousQueue; + Mutex _rendezvousQueue_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/SysEnv.cpp b/node/SysEnv.cpp new file mode 100644 index 00000000..016e9caa --- /dev/null +++ b/node/SysEnv.cpp @@ -0,0 +1,219 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <set> +#include <string> + +#include "SysEnv.hpp" +#include "Utils.hpp" +#include "RuntimeEnvironment.hpp" +#include "NodeConfig.hpp" + +#ifdef __APPLE__ +#include <sys/sysctl.h> +#include <sys/uio.h> +#include <sys/param.h> +#include <net/route.h> +#endif + +#ifdef _WIN32 +#include <Windows.h> +#else +#include <unistd.h> +#include <signal.h> +#endif + +namespace ZeroTier { + +SysEnv::SysEnv(const RuntimeEnvironment *renv) : + _r(renv) +{ +} + +SysEnv::~SysEnv() +{ +} + +#ifdef __APPLE__ + +uint64_t SysEnv::getNetworkConfigurationFingerprint() + throw() +{ + int mib[6]; + size_t needed; + uint64_t fingerprint = 5381; // djb2 hash algorithm is used below + + // Right now this just scans for changes in default routes. This is not + // totally robust -- it will miss cases where we switch from one 10.0.0.0/24 + // network with gateway .1 to another -- but most of the time it'll pick + // up shifts in connectivity. + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_UNSPEC; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + if (!sysctl(mib,6,NULL,&needed,NULL,0)) { + char *buf = (char *)malloc(needed); + if (buf) { + if (!sysctl(mib,6,buf,&needed,NULL,0)) { + struct rt_msghdr *rtm; + for(char *next=buf,*end=buf+needed;next<end;) { + rtm = (struct rt_msghdr *)next; + char *saptr = (char *)(rtm + 1); + char *saend = next + rtm->rtm_msglen; + if (((rtm->rtm_addrs & RTA_DST))&&((rtm->rtm_addrs & RTA_GATEWAY))) { + int sano = 0; + struct sockaddr *dst = (struct sockaddr *)0; + struct sockaddr *gateway = (struct sockaddr *)0; + while (saptr < saend) { + struct sockaddr *sa = (struct sockaddr *)saptr; + if (!sa->sa_len) + break; + if (sano == 0) + dst = sa; + else if (sano == 1) + gateway = sa; + else if (sano > 1) + break; + ++sano; + saptr += sa->sa_len; + } + if ((dst)&&(gateway)) { + if ((dst->sa_family == AF_INET)&&(gateway->sa_family == AF_INET)&&(!((struct sockaddr_in *)dst)->sin_addr.s_addr)) { + fingerprint = ((fingerprint << 5) + fingerprint) + (uint64_t)((struct sockaddr_in *)gateway)->sin_addr.s_addr; + } else if ((dst->sa_family == AF_INET6)&&(gateway->sa_family == AF_INET6)&&(Utils::isZero(((struct sockaddr_in6 *)dst)->sin6_addr.s6_addr,16))) { + for(unsigned int i=0;i<16;++i) + fingerprint = ((fingerprint << 5) + fingerprint) + (uint64_t)((struct sockaddr_in6 *)gateway)->sin6_addr.s6_addr[i]; + } + } + } + next = saend; + } + } + free(buf); + } + } + + return fingerprint; +} + +#endif // __APPLE__ + +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + +uint64_t SysEnv::getNetworkConfigurationFingerprint() + throw() +{ + char buf[16384]; + uint64_t fingerprint = 5381; // djb2 hash algorithm is used below + char *t1,*t2; + + try { + std::set<std::string> tapDevs(_r->nc->networkTapDeviceNames()); + + // Include default IPv4 route if available + int fd = open("/proc/net/route",O_RDONLY); + if (fd > 0) { + long n = read(fd,buf,sizeof(buf) - 1); + ::close(fd); + if (n > 0) { + buf[n] = 0; + for(char *line=strtok_r(buf,"\r\n",&t1);(line);line=strtok_r((char *)0,"\r\n",&t1)) { + int fno = 0; + for(char *field=strtok_r(line," \t",&t2);(field);field=strtok_r((char *)0," \t",&t2)) { + if (fno == 0) { // device name + if ((tapDevs.count(std::string(field)))||(!strcmp(field,"lo"))) + break; + } else if ((fno == 1)||(fno == 2)) { // destination, gateway + if (strlen(field) == 8) { // ignore header junk, use only hex route info + while (*field) + fingerprint = ((fingerprint << 5) + fingerprint) + (uint64_t)*(field++); + } + } else if (fno > 2) + break; + ++fno; + } + } + } + } + + // Include IPs of IPv6 enabled interfaces if available + fd = open("/proc/net/if_inet6",O_RDONLY); + if (fd > 0) { + long n = read(fd,buf,sizeof(buf) - 1); + ::close(fd); + if (n > 0) { + buf[n] = 0; + for(char *line=strtok_r(buf,"\r\n",&t1);(line);line=strtok_r((char *)0,"\r\n",&t1)) { + int fno = 0; + const char *v6ip = (const char *)0; + const char *devname = (const char *)0; + for(char *field=strtok_r(line," \t",&t2);(field);field=strtok_r((char *)0," \t",&t2)) { + switch(fno) { + case 0: + v6ip = field; + break; + case 5: + devname = field; + break; + } + ++fno; + } + + if ((v6ip)&&(devname)) { + if ((!(tapDevs.count(std::string(devname))))&&(strcmp(devname,"lo"))) { + while (*v6ip) + fingerprint = ((fingerprint << 5) + fingerprint) + (uint64_t)*(v6ip++); + } + } + } + } + } + } catch ( ... ) {} + + return fingerprint; +} + +#endif // __linux__ + +#ifdef _WIN32 + +not implemented yet; + +#endif // _WIN32 + +} // namespace ZeroTier diff --git a/node/SysEnv.hpp b/node/SysEnv.hpp new file mode 100644 index 00000000..af3efa5b --- /dev/null +++ b/node/SysEnv.hpp @@ -0,0 +1,58 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_SYSENV_HPP +#define _ZT_SYSENV_HPP + +#include <stdint.h> + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Local system environment monitoring utilities + */ +class SysEnv +{ +public: + SysEnv(const RuntimeEnvironment *renv); + ~SysEnv(); + + /** + * @return Fingerprint of currently running network environment + */ + uint64_t getNetworkConfigurationFingerprint() + throw(); + +private: + const RuntimeEnvironment *_r; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Thread.cpp b/node/Thread.cpp new file mode 100644 index 00000000..37a1a5a5 --- /dev/null +++ b/node/Thread.cpp @@ -0,0 +1,192 @@ +/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2012-2013 ZeroTier Networks LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#include "Thread.hpp"
+
+#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux)
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdexcept>
+
+extern "C" {
+static void *__m_thread_main(void *ptr)
+{
+ ((ZeroTier::Thread *)ptr)->__intl_run();
+ return (void *)0;
+}
+}
+
+namespace ZeroTier {
+
+Thread::Thread() :
+ suicidalThread(false),
+ _impl(malloc(sizeof(pthread_t))),
+ _running()
+{
+ memset(_impl,0,sizeof(pthread_t));
+}
+
+Thread::~Thread()
+{
+ free(_impl);
+}
+
+void Thread::start()
+{
+ if (!*_running) {
+ ++_running;
+ pthread_create((pthread_t *)_impl,(const pthread_attr_t *)0,&__m_thread_main,(void *)this);
+ }
+}
+
+void Thread::join()
+{
+ void *tmp;
+ if (*_running)
+ pthread_join(*((pthread_t *)_impl),&tmp);
+}
+
+void Thread::sleep(unsigned long ms)
+{
+ usleep(ms);
+}
+
+void Thread::__intl_run()
+{
+ for(;;) {
+ _notInit = false;
+ this->main();
+ if (suicidalThread) {
+ delete this;
+ return;
+ }
+ if (_notInit) // UGLY ASS HACK: see main()
+ usleep(50);
+ else break;
+ }
+ --_running;
+}
+
+void Thread::main()
+ throw()
+{
+ _notInit = true; // UGLY ASS HACK: retry if subclass has not defined virtual function pointer yet
+}
+
+} // namespace ZeroTier
+
+#endif
+
+#ifdef _WIN32
+
+#include <Windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+DWORD WINAPI __m_thread_main(LPVOID lpParam)
+{
+ ((ZeroTier::Thread *)lpParam)->__intl_run();
+ return 0;
+}
+
+struct __m_thread_info
+{
+ HANDLE threadHandle;
+ DWORD threadId;
+ bool started;
+};
+
+namespace ZeroTier {
+
+Thread::Thread() :
+ suicidalThread(false),
+ _impl(malloc(sizeof(__m_thread_info))),
+ _running()
+{
+ memset(_impl,0,sizeof(__m_thread_info));
+}
+
+Thread::~Thread()
+{
+ if (((__m_thread_info *)_impl)->started)
+ CloseHandle(((__m_thread_info *)_impl)->threadHandle);
+ free(_impl);
+}
+
+void Thread::start()
+{
+ if (!*_running) {
+ ++_running;
+ if ((((__m_thread_info *)_impl)->threadHandle = CreateThread(NULL,0,__m_thread_main,this,0,&(((__m_thread_info *)_impl)->threadId))) != NULL) {
+ ((__m_thread_info *)_impl)->started = true;
+ }
+ }
+}
+
+void Thread::join()
+{
+ if (*_running)
+ WaitForSingleObject(((__m_thread_info *)_impl)->threadHandle,INFINITE);
+}
+
+void Thread::__intl_run()
+{
+ for(;;) {
+ _notInit = false;
+ this->main();
+ if (suicidalThread) {
+ delete this;
+ return;
+ }
+ if (_notInit)
+ Thread::sleep(50);
+ else break;
+ }
+ --_running;
+}
+
+void Thread::main()
+ throw()
+{
+ _notInit = true; // HACK: retry if subclass has not defined virtual function pointer yet
+}
+
+struct _Thread_RunInBackgroundData
+{
+ void (*func)(void *);
+ void *ptr;
+ HANDLE threadHandle;
+ DWORD threadId;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/node/Thread.hpp b/node/Thread.hpp new file mode 100644 index 00000000..b023fbae --- /dev/null +++ b/node/Thread.hpp @@ -0,0 +1,94 @@ +/*
+ * ZeroTier One - Global Peer to Peer Ethernet
+ * Copyright (C) 2012-2013 ZeroTier Networks LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#ifndef _ZT_THREAD_HPP
+#define _ZT_THREAD_HPP
+
+#include "NonCopyable.hpp"
+#include "AtomicCounter.hpp"
+
+namespace ZeroTier {
+
+/**
+ * Wrapper for OS-dependent thread functions like pthread_create, etc.
+ */
+class Thread : NonCopyable
+{
+public:
+ Thread();
+ virtual ~Thread();
+
+ /**
+ * Start thread -- can only be called once
+ */
+ void start();
+
+ /**
+ * Wait for thread to terminate
+ *
+ * More than one thread should not simultaneously use join().
+ */
+ void join();
+
+ /**
+ * @return True if thread is running
+ */
+ inline bool running() const { return (*_running > 0); }
+
+ /**
+ * Internal bounce method; do not call or override
+ */
+ void __intl_run();
+
+ /**
+ * Sleep the current thread
+ *
+ * @param ms Milliseconds to sleep
+ */
+ static void sleep(unsigned long ms);
+
+protected:
+ /**
+ * Override to set a thread main function
+ */
+ virtual void main()
+ throw();
+
+ /**
+ * Subclasses can set to true to cause Thread to delete itself on exit
+ */
+ volatile bool suicidalThread;
+
+private:
+ void *_impl;
+ AtomicCounter _running;
+ volatile bool _notInit;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/node/Topology.cpp b/node/Topology.cpp new file mode 100644 index 00000000..cbe42e8f --- /dev/null +++ b/node/Topology.cpp @@ -0,0 +1,443 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "Topology.hpp" +#include "NodeConfig.hpp" + +namespace ZeroTier { + +#define ZT_KISSDB_HASH_TABLE_SIZE 131072 +#define ZT_KISSDB_KEY_SIZE ZT_ADDRESS_LENGTH +#define ZT_KISSDB_VALUE_SIZE ZT_PEER_MAX_SERIALIZED_LENGTH + +Topology::Topology(const RuntimeEnvironment *renv,const char *dbpath) + throw(std::runtime_error) : + Thread(), + _r(renv) +{ + if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWCREAT,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) { + if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWREPLACE,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) + throw std::runtime_error("unable to open peer database (rw/create)"); + } + + if ((_dbm.key_size != ZT_KISSDB_KEY_SIZE)||(_dbm.value_size != ZT_KISSDB_VALUE_SIZE)||(_dbm.hash_table_size != ZT_KISSDB_HASH_TABLE_SIZE)) { + KISSDB_close(&_dbm); + if (KISSDB_open(&_dbm,dbpath,KISSDB_OPEN_MODE_RWREPLACE,ZT_KISSDB_HASH_TABLE_SIZE,ZT_KISSDB_KEY_SIZE,ZT_KISSDB_VALUE_SIZE)) + throw std::runtime_error("unable to open peer database (recreate)"); + } + + Utils::lockDownFile(dbpath,false); // node.db caches secrets + + start(); +} + +Topology::~Topology() +{ + { + Mutex::Lock _l(_peerDeepVerifyJobs_m); + _peerDeepVerifyJobs.push_back(_PeerDeepVerifyJob()); + _peerDeepVerifyJobs.back().type = _PeerDeepVerifyJob::CLEAN_CACHE; + _peerDeepVerifyJobs.push_back(_PeerDeepVerifyJob()); + _peerDeepVerifyJobs.back().type = _PeerDeepVerifyJob::EXIT_THREAD; + } + _peerDeepVerifyJobs_c.signal(); + + while (running()) + Thread::sleep(10); // wait for thread to terminate without join() + + KISSDB_close(&_dbm); +} + +void Topology::setSupernodes(const std::map< Identity,std::vector<InetAddress> > &sn) +{ + Mutex::Lock _l(_supernodes_m); + _supernodes = sn; + _supernodeAddresses.clear(); + _supernodePeers.clear(); + for(std::map< Identity,std::vector<InetAddress> >::const_iterator i(sn.begin());i!=sn.end();++i) { + if (i->first != _r->identity) { + SharedPtr<Peer> p(getPeer(i->first.address())); + if ((!p)||(p->identity() != i->first)) { + p = SharedPtr<Peer>(new Peer(_r->identity,i->first)); + _reallyAddPeer(p); + } + for(std::vector<InetAddress>::const_iterator j(i->second.begin());j!=i->second.end();++j) + p->setPathAddress(*j,true); + _supernodePeers.push_back(p); + } + _supernodeAddresses.insert(i->first.address()); + } +} + +void Topology::addPeer(const SharedPtr<Peer> &candidate,void (*callback)(void *,const SharedPtr<Peer> &,Topology::PeerVerifyResult),void *arg) +{ + if (candidate->address() != _r->identity.address()) { + Mutex::Lock _l(_peerDeepVerifyJobs_m); + _peerDeepVerifyJobs.push_back(_PeerDeepVerifyJob()); + _PeerDeepVerifyJob &job = _peerDeepVerifyJobs.back(); + job.callback = callback; + job.arg = arg; + job.candidate = candidate; + job.type = _PeerDeepVerifyJob::VERIFY_PEER; + _peerDeepVerifyJobs_c.signal(); + } else { + TRACE("BUG: addPeer() caught and ignored attempt to add peer for self"); + if (callback) + callback(arg,candidate,PEER_VERIFY_REJECTED_DUPLICATE_TRIAGED); + } +} + +SharedPtr<Peer> Topology::getPeer(const Address &zta) +{ + if (zta == _r->identity.address()) { + TRACE("BUG: ignored attempt to getPeer() for self, returned NULL"); + return SharedPtr<Peer>(); + } + + { + Mutex::Lock _l(_activePeers_m); + std::map< Address,SharedPtr<Peer> >::const_iterator ap(_activePeers.find(zta)); + if ((ap != _activePeers.end())&&(ap->second)) + return ap->second; + } + + Buffer<ZT_KISSDB_VALUE_SIZE> b(ZT_KISSDB_VALUE_SIZE); + _dbm_m.lock(); + if (!KISSDB_get(&_dbm,zta.data(),b.data())) { + _dbm_m.unlock(); + + SharedPtr<Peer> p(new Peer()); + try { + p->deserialize(b,0); + Mutex::Lock _l(_activePeers_m); + _activePeers[zta] = p; + return p; + } catch ( ... ) { + TRACE("unexpected exception deserializing peer %s from peerdb",zta.toString().c_str()); + return SharedPtr<Peer>(); + } + } else _dbm_m.unlock(); + + return SharedPtr<Peer>(); +} + +SharedPtr<Peer> Topology::getBestSupernode(const Address *avoid,unsigned int avoidCount) const +{ + SharedPtr<Peer> bestSupernode; + unsigned long bestSupernodeLatency = 0xffff; + uint64_t now = Utils::now(); + + Mutex::Lock _l(_supernodes_m); + + for(std::vector< SharedPtr<Peer> >::const_iterator sn=_supernodePeers.begin();sn!=_supernodePeers.end();) { + for(unsigned int i=0;i<avoidCount;++i) { + if (avoid[i] == (*sn)->address()) + goto skip_and_try_next_supernode; + } + if ((*sn)->hasActiveDirectPath(now)) { // only consider those that responded to pings + unsigned int l = (*sn)->latency(); + if ((l)&&(l <= bestSupernodeLatency)) { + bestSupernodeLatency = l; + bestSupernode = *sn; + } + } +skip_and_try_next_supernode: + ++sn; + } + + if (bestSupernode) + return bestSupernode; + + for(std::vector< SharedPtr<Peer> >::const_iterator sn=_supernodePeers.begin();sn!=_supernodePeers.end();++sn) { + if ((*sn)->hasActiveDirectPath(now)) { // only consider those that responded to pings + unsigned int l = (*sn)->latency(); + if ((l)&&(l <= bestSupernodeLatency)) { + bestSupernodeLatency = l; + bestSupernode = *sn; + } + } + } + + if (bestSupernode) + return bestSupernode; + + uint64_t bestSupernodeLastDirectReceive = 0; + for(std::vector< SharedPtr<Peer> >::const_iterator sn=_supernodePeers.begin();sn!=_supernodePeers.end();++sn) { + uint64_t l = (*sn)->lastDirectReceive(); + if (l > bestSupernodeLastDirectReceive) { + bestSupernodeLastDirectReceive = l; + bestSupernode = *sn; + } + } + + return bestSupernode; +} + +void Topology::clean() +{ + { + Mutex::Lock _l(_peerDeepVerifyJobs_m); + _peerDeepVerifyJobs.push_back(_PeerDeepVerifyJob()); + _peerDeepVerifyJobs.back().type = _PeerDeepVerifyJob::CLEAN_CACHE; + } + _peerDeepVerifyJobs_c.signal(); +} + +void Topology::likesMulticastGroup(uint64_t nwid,const MulticastGroup &mg,const Address &addr,uint64_t now) +{ + Mutex::Lock _l(_multicastGroupMembers_m); + _multicastGroupMembers[nwid][mg][addr] = now; +} + +struct _PickMulticastPropagationPeersPeerPrioritySortOrder +{ + inline bool operator()(const SharedPtr<Peer> &p1,const SharedPtr<Peer> &p2) const + { + return (p1->lastUnicastFrame() >= p2->lastUnicastFrame()); + } +}; +#define _MAX_PEERS_TO_CONSIDER 256 +unsigned int Topology::pickMulticastPropagationPeers(uint64_t nwid,const Address &exclude,const void *propagationBloom,unsigned int propagationBloomSize,unsigned int count,const MulticastGroup &mg,SharedPtr<Peer> *peers) +{ + SharedPtr<Peer> possiblePeers[_MAX_PEERS_TO_CONSIDER]; + unsigned int numPossiblePeers = 0; + + if (count > _MAX_PEERS_TO_CONSIDER) + count = _MAX_PEERS_TO_CONSIDER; + + Mutex::Lock _l1(_activePeers_m); + Mutex::Lock _l2(_supernodes_m); + + // Grab known non-supernode peers in multicast group, excluding 'exclude' + // Also lazily clean up the _multicastGroupMembers structure + { + Mutex::Lock _l3(_multicastGroupMembers_m); + std::map< uint64_t,std::map< MulticastGroup,std::map< Address,uint64_t > > >::iterator mgm(_multicastGroupMembers.find(nwid)); + if (mgm != _multicastGroupMembers.end()) { + std::map< MulticastGroup,std::map< Address,uint64_t > >::iterator g(mgm->second.find(mg)); + if (g != mgm->second.end()) { + uint64_t now = Utils::now(); + for(std::map< Address,uint64_t >::iterator m(g->second.begin());m!=g->second.end();) { + if ((now - m->second) < ZT_MULTICAST_LIKE_EXPIRE) { + std::map< Address,SharedPtr<Peer> >::const_iterator p(_activePeers.find(m->first)); + if (p != _activePeers.end()) { + possiblePeers[numPossiblePeers++] = p->second; + if (numPossiblePeers > _MAX_PEERS_TO_CONSIDER) + break; + } + ++m; + } else g->second.erase(m++); + } + if (!g->second.size()) + mgm->second.erase(g); + } + } + } + + // Sort non-supernode peers in descending order of most recent data + // exchange timestamp. This sorts by implicit social relationships -- who + // you are talking to are the people who get multicasts first. + std::sort(&(possiblePeers[0]),&(possiblePeers[numPossiblePeers]),_PickMulticastPropagationPeersPeerPrioritySortOrder()); + + // Tack on a supernode peer to the end if we don't have enough regular + // peers, using supernodes to bridge gaps in sparse multicast groups. + if (numPossiblePeers < count) { + SharedPtr<Peer> bestSupernode; + unsigned int bestSupernodeLatency = 0xffff; + for(std::vector< SharedPtr<Peer> >::const_iterator sn(_supernodePeers.begin());sn!=_supernodePeers.end();++sn) { + if (((*sn)->latency())&&((*sn)->latency() < bestSupernodeLatency)) { + bestSupernodeLatency = (*sn)->latency(); + bestSupernode = *sn; + } + } + if (bestSupernode) + possiblePeers[numPossiblePeers++] = bestSupernode; + } + + unsigned int num = 0; + + // First, try to pick peers not in the propgation bloom filter + for(unsigned int i=0;i<numPossiblePeers;++i) { + if (!Utils::bloomContains(propagationBloom,propagationBloomSize,possiblePeers[i]->address().sum())) { + peers[num++] = possiblePeers[i]; + if (num >= count) + return num; + } + } + + // Next, pick other peers until full (without duplicates) + for(unsigned int i=0;i<numPossiblePeers;++i) { + for(unsigned int j=0;j<num;++j) { + if (peers[j] == possiblePeers[i]) + goto check_next_peer; + } + peers[num++] = possiblePeers[i]; + if (num >= count) + return num; +check_next_peer: + continue; + } + + return num; +} + +void Topology::main() + throw() +{ + for(;;) { + _peerDeepVerifyJobs_m.lock(); + if (_peerDeepVerifyJobs.empty()) { + _peerDeepVerifyJobs_m.unlock(); + _peerDeepVerifyJobs_c.wait(); + continue; + } + _PeerDeepVerifyJob job(_peerDeepVerifyJobs.front()); + _peerDeepVerifyJobs.pop_front(); + unsigned long queueRemaining = _peerDeepVerifyJobs.size(); + _peerDeepVerifyJobs_m.unlock(); + + switch(job.type) { + case _PeerDeepVerifyJob::VERIFY_PEER: + /* TODO: We should really verify peers every time completely if this + * is a supernode, perhaps deferring the expensive part for new + * addresses. An attempt at claim jumping should also trigger a + * short duration ban of the originating IP address in most cases, + * since this means either malicious intent or broken software. */ + TRACE("verifying peer: %s",job.candidate->identity().address().toString().c_str()); + + if ((job.candidate->identity())&&(!job.candidate->identity().address().isReserved())&&(job.candidate->identity().locallyValidate(false))) { + // Peer passes sniff test, so check to see if we've already got + // one with the same address. + + SharedPtr<Peer> existingPeer(getPeer(job.candidate->identity().address())); + + if (existingPeer) { + if (existingPeer->identity() == job.candidate->identity()) { + // It's an *exact* duplicate, so return the existing peer + if (job.callback) + job.callback(job.arg,existingPeer,PEER_VERIFY_ACCEPTED_ALREADY_HAVE); + } else if (queueRemaining > 3) { + /* Prevents a CPU hog DOS attack, while allowing a very unlikely kind of + * DOS attack where someone knows someone else's address prior to their + * registering it and claim-jumps them and then floods with bad identities + * to hold their claim. Of the two, the latter would be infeasable + * without already having cracked the target's machine in which case + * the attacker has their private key anyway and can really steal their + * identity. So why bother.*/ + TRACE("%s is duplicate, load too high, old won",job.candidate->identity().address().toString().c_str()); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_REJECTED_DUPLICATE_TRIAGED); + } else { + // It's different so deeply validate it first, then the + // existing claimant, and toss the imposter. If both verify, the + // one we already have wins. + + if (!job.candidate->identity().locallyValidate(true)) { + LOG("Topology: IMPOSTER %s rejected",job.candidate->identity().address().toString().c_str()); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_REJECTED_INVALID_IDENTITY); + } else if (!existingPeer->identity().locallyValidate(true)) { + LOG("Topology: previous IMPOSTER %s displaced by valid identity!",job.candidate->identity().address().toString().c_str()); + _reallyAddPeer(job.candidate); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_ACCEPTED_DISPLACED_INVALID_ADDRESS); + } else { + LOG("Topology: tie between apparently valid claims on %s, oldest won",job.candidate->identity().address().toString().c_str()); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_REJECTED_DUPLICATE); + } + } + } else { + TRACE("%s accepted as new",job.candidate->identity().address().toString().c_str()); + _reallyAddPeer(job.candidate); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_ACCEPTED_NEW); + } + } else { + TRACE("%s rejected, identity failed initial checks",job.candidate->identity().address().toString().c_str()); + if (job.callback) + job.callback(job.arg,job.candidate,PEER_VERIFY_REJECTED_INVALID_IDENTITY); + } + break; + case _PeerDeepVerifyJob::CLEAN_CACHE: + TRACE("cleaning caches and flushing modified peers to disk..."); + { + Mutex::Lock _l(_activePeers_m); + for(std::map< Address,SharedPtr<Peer> >::iterator p(_activePeers.begin());p!=_activePeers.end();++p) { + if (p->second->getAndResetDirty()) { + try { + Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b; + p->second->serialize(b); + b.zeroUnused(); + _dbm_m.lock(); + if (KISSDB_put(&_dbm,p->second->identity().address().data(),b.data())) { + TRACE("error writing %s to peer.db",p->second->identity().address().toString().c_str()); + } + _dbm_m.unlock(); + } catch ( ... ) { + TRACE("unexpected exception flushing %s to peer.db",p->second->identity().address().toString().c_str()); + } + } + } + } + { + Mutex::Lock _l(_multicastGroupMembers_m); + for(std::map< uint64_t,std::map< MulticastGroup,std::map< Address,uint64_t > > >::iterator mgm(_multicastGroupMembers.begin());mgm!=_multicastGroupMembers.end();) { + if (_r->nc->hasNetwork(mgm->first)) + ++mgm; + else _multicastGroupMembers.erase(mgm++); + } + } + break; + case _PeerDeepVerifyJob::EXIT_THREAD: + TRACE("thread terminating..."); + return; + } + } +} + +void Topology::_reallyAddPeer(const SharedPtr<Peer> &p) +{ + { + Mutex::Lock _l(_activePeers_m); + _activePeers[p->identity().address()] = p; + } + try { + Buffer<ZT_PEER_MAX_SERIALIZED_LENGTH> b; + p->serialize(b); + b.zeroUnused(); + _dbm_m.lock(); + if (KISSDB_put(&_dbm,p->identity().address().data(),b.data())) { + TRACE("error writing %s to peerdb",p->address().toString().c_str()); + } else p->getAndResetDirty(); + _dbm_m.unlock(); + } catch ( ... ) { + TRACE("unexpected exception flushing to peerdb"); + } +} + +} // namespace ZeroTier diff --git a/node/Topology.hpp b/node/Topology.hpp new file mode 100644 index 00000000..95124497 --- /dev/null +++ b/node/Topology.hpp @@ -0,0 +1,339 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_TOPOLOGY_HPP +#define _ZT_TOPOLOGY_HPP + +#include <map> +#include <set> +#include <list> +#include <vector> +#include <stdexcept> + +#include "Address.hpp" +#include "Peer.hpp" +#include "Mutex.hpp" +#include "Condition.hpp" +#include "InetAddress.hpp" +#include "Constants.hpp" +#include "Thread.hpp" +#include "MulticastGroup.hpp" +#include "Utils.hpp" + +#include "../ext/kissdb/kissdb.h" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Database of network topology + */ +class Topology : protected Thread +{ +public: + /** + * Result of peer add/verify + */ + enum PeerVerifyResult + { + PEER_VERIFY_ACCEPTED_NEW, /* new peer */ + PEER_VERIFY_ACCEPTED_ALREADY_HAVE, /* we already knew ye */ + PEER_VERIFY_ACCEPTED_DISPLACED_INVALID_ADDRESS, /* you booted out an impostor */ + PEER_VERIFY_REJECTED_INVALID_IDENTITY, /* identity is invalid or validation failed */ + PEER_VERIFY_REJECTED_DUPLICATE, /* someone equally valid already has your address */ + PEER_VERIFY_REJECTED_DUPLICATE_TRIAGED /* you look duplicate and I'm too busy to deep verify */ + }; + + Topology(const RuntimeEnvironment *renv,const char *dbpath) + throw(std::runtime_error); + + virtual ~Topology(); + + /** + * Set up supernodes for this network + * + * @param sn Supernodes for this network + */ + void setSupernodes(const std::map< Identity,std::vector<InetAddress> > &sn); + + /** + * Add a peer to this network + * + * Verification and adding actually occurs in the background, since in + * rare cases it can be somewhat CPU-intensive. The callback will be + * called (from the background thread) when add is complete. + * + * The peer given to the callback may not be the same object provided + * as a candidate if the candidate was an exact duplicate of a peer we + * already have. + * + * @param candidate New candidate peer to be added + * @param callback Callback to call when peer verification is complete + * @param arg First argument to callback + * @return Verification result or PEER_VERIFY__IN_PROGRESS if occurring in background + */ + void addPeer(const SharedPtr<Peer> &candidate,void (*callback)(void *,const SharedPtr<Peer> &,PeerVerifyResult),void *arg); + + /** + * Get a peer from its address + * + * @param zta ZeroTier address of peer + * @return Peer or NULL if not found + */ + SharedPtr<Peer> getPeer(const Address &zta); + + /** + * @return Current network supernodes + */ + inline std::map< Identity,std::vector<InetAddress> > supernodes() const + { + Mutex::Lock _l(_supernodes_m); + return _supernodes; + } + + /** + * @return Vector of peers that are supernodes + */ + inline std::vector< SharedPtr<Peer> > supernodePeers() const + { + Mutex::Lock _l(_supernodes_m); + return _supernodePeers; + } + + /** + * Get the current favorite supernode + * + * @return Supernode with lowest latency or NULL if none + */ + inline SharedPtr<Peer> getBestSupernode() const + { + return getBestSupernode((const Address *)0,0); + } + + /** + * Get the best supernode, avoiding supernodes listed in an array + * + * This will get the best supernode (lowest latency, etc.) but will + * try to avoid the listed supernodes, only using them if no others + * are available. + * + * @param avoid Nodes to avoid + * @param avoidCount Number of nodes to avoid + * @return Supernode or NULL if none + */ + SharedPtr<Peer> getBestSupernode(const Address *avoid,unsigned int avoidCount) const; + + /** + * @param zta ZeroTier address + * @return True if this is a designated supernode + */ + inline bool isSupernode(const Address &zta) const + throw() + { + Mutex::Lock _l(_supernodes_m); + return (_supernodeAddresses.count(zta) > 0); + } + + /** + * Clean and flush database now (runs in the background) + */ + void clean(); + + /** + * Pick peers for multicast propagation + * + * @param nwid Network ID + * @param exclude Peer to exclude or zero address for none + * @param propagationBloom Propgation bloom filter + * @param propagationBloomSize Size of propagation bloom filter in BITS + * @param count Number of peers desired (propagation breadth) + * @param mg Multicast group + * @param peers Array to receive peers (must be at least [count]) + * @return Number of peers actually picked + */ + unsigned int pickMulticastPropagationPeers(uint64_t nwid,const Address &exclude,const void *propagationBloom,unsigned int propagationBloomSize,unsigned int count,const MulticastGroup &mg,SharedPtr<Peer> *peers); + + /** + * Add or update last 'like' time for an address's membership in a multicast group + * + * @param nwid Network ID + * @param mg Multicast group + * @param addr ZeroTier address + * @param now Current time + */ + void likesMulticastGroup(uint64_t nwid,const MulticastGroup &mg,const Address &addr,uint64_t now); + + /** + * Apply a function or function object to all peers + * + * @param f Function to apply + * @tparam F Function or function object type + */ + template<typename F> + inline void eachPeer(F f) + { + Mutex::Lock _l(_activePeers_m); + for(std::map< Address,SharedPtr<Peer> >::const_iterator p(_activePeers.begin());p!=_activePeers.end();++p) + f(*this,p->second); + } + + /** + * Function object to collect peers that need a firewall opener sent + */ + class CollectPeersThatNeedFirewallOpener + { + public: + CollectPeersThatNeedFirewallOpener(std::vector< SharedPtr<Peer> > &v) : + _now(Utils::now()), + _v(v) + { + } + + inline void operator()(Topology &t,const SharedPtr<Peer> &p) + { + if ((p->hasDirectPath())&&((_now - p->lastFirewallOpener()) >= ZT_FIREWALL_OPENER_DELAY)) + _v.push_back(p); + } + + private: + uint64_t _now; + std::vector< SharedPtr<Peer> > &_v; + }; + + /** + * Function object to collect peers that need a ping sent + */ + class CollectPeersThatNeedPing + { + public: + CollectPeersThatNeedPing(std::vector< SharedPtr<Peer> > &v) : + _now(Utils::now()), + _v(v) + { + } + + inline void operator()(Topology &t,const SharedPtr<Peer> &p) + { + if (((p->hasActiveDirectPath(_now))||(t.isSupernode(p->address())))&&((_now - p->lastDirectSend()) >= ZT_PEER_DIRECT_PING_DELAY)) + _v.push_back(p); + } + + private: + uint64_t _now; + std::vector< SharedPtr<Peer> > &_v; + }; + + /** + * Function object to collect peers with active links (and supernodes) + */ + class CollectPeersWithActiveDirectPath + { + public: + CollectPeersWithActiveDirectPath(std::vector< SharedPtr<Peer> > &v) : + _now(Utils::now()), + _v(v) + { + } + + inline void operator()(Topology &t,const SharedPtr<Peer> &p) + { + if ((p->hasActiveDirectPath(_now))||(t.isSupernode(p->address()))) + _v.push_back(p); + } + + private: + uint64_t _now; + std::vector< SharedPtr<Peer> > &_v; + }; + + /** + * Function object to collect peers with any known direct path + */ + class CollectPeersWithDirectPath + { + public: + CollectPeersWithDirectPath(std::vector< SharedPtr<Peer> > &v) : + _v(v) + { + } + + inline void operator()(Topology &t,const SharedPtr<Peer> &p) + { + if (p->hasDirectPath()) + _v.push_back(p); + } + + private: + std::vector< SharedPtr<Peer> > &_v; + }; + +protected: + virtual void main() + throw(); + +private: + void _reallyAddPeer(const SharedPtr<Peer> &p); + + // A job for the background deep verify thread (also does cache cleaning, flushing, etc.) + struct _PeerDeepVerifyJob + { + void (*callback)(void *,const SharedPtr<Peer> &,Topology::PeerVerifyResult); + void *arg; + SharedPtr<Peer> candidate; + enum { + VERIFY_PEER, + CLEAN_CACHE, + EXIT_THREAD + } type; + }; + + const RuntimeEnvironment *const _r; + + std::map< Address,SharedPtr<Peer> > _activePeers; + Mutex _activePeers_m; + + std::list< _PeerDeepVerifyJob > _peerDeepVerifyJobs; + Mutex _peerDeepVerifyJobs_m; + Condition _peerDeepVerifyJobs_c; + + std::map< Identity,std::vector<InetAddress> > _supernodes; + std::set< Address > _supernodeAddresses; + std::vector< SharedPtr<Peer> > _supernodePeers; + Mutex _supernodes_m; + + KISSDB _dbm; + Mutex _dbm_m; + + // Multicast group members by network ID, then multicast group + std::map< uint64_t,std::map< MulticastGroup,std::map< Address,uint64_t > > > _multicastGroupMembers; + Mutex _multicastGroupMembers_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/UdpSocket.cpp b/node/UdpSocket.cpp new file mode 100644 index 00000000..95156fcc --- /dev/null +++ b/node/UdpSocket.cpp @@ -0,0 +1,163 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <arpa/inet.h> + +#ifdef _WIN32 +#include <Windows.h> +#else +#include <unistd.h> +#include <signal.h> +#endif + +#include "UdpSocket.hpp" +#include "RuntimeEnvironment.hpp" +#include "Logger.hpp" +#include "Switch.hpp" + +namespace ZeroTier { + +UdpSocket::UdpSocket( + int localPort, + bool ipv6, + void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int), + void *arg) + throw(std::runtime_error) : + Thread(), + _packetHandler(packetHandler), + _arg(arg), + _localPort(localPort), + _sock(0), + _v6(ipv6) +{ + int yes,no; + + if ((localPort <= 0)||(localPort > 0xffff)) + throw std::runtime_error("port is out of range"); + + if (ipv6) { + _sock = socket(AF_INET6,SOCK_DGRAM,0); + if (_sock <= 0) + throw std::runtime_error("unable to create IPv6 SOCK_DGRAM socket"); + + yes = 1; setsockopt(_sock,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&yes,sizeof(yes)); + no = 0; setsockopt(_sock,SOL_SOCKET,SO_REUSEADDR,(void *)&no,sizeof(no)); +#ifdef IP_DONTFRAG + no = 0; setsockopt(_sock,IPPROTO_IP,IP_DONTFRAG,&no,sizeof(no)); +#endif +#ifdef IP_MTU_DISCOVER + no = 0; setsockopt(_sock,IPPROTO_IP,IP_MTU_DISCOVER,&no,sizeof(no)); +#endif +#ifdef IPV6_MTU_DISCOVER + no = 0; setsockopt(_sock,IPPROTO_IPV6,IPV6_MTU_DISCOVER,&no,sizeof(no)); +#endif + + struct sockaddr_in6 sin6; + memset(&sin6,0,sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(localPort); + memcpy(&(sin6.sin6_addr),&in6addr_any,sizeof(struct in6_addr)); + if (::bind(_sock,(const struct sockaddr *)&sin6,sizeof(sin6))) { + ::close(_sock); + throw std::runtime_error("unable to bind to port"); + } + } else { + _sock = socket(AF_INET,SOCK_DGRAM,0); + if (_sock <= 0) + throw std::runtime_error("unable to create IPv4 SOCK_DGRAM socket"); + + no = 0; setsockopt(_sock,SOL_SOCKET,SO_REUSEADDR,(void *)&no,sizeof(no)); +#ifdef IP_DONTFRAG + no = 0; setsockopt(_sock,IPPROTO_IP,IP_DONTFRAG,&no,sizeof(no)); +#endif +#ifdef IP_MTU_DISCOVER + no = 0; setsockopt(_sock,IPPROTO_IP,IP_MTU_DISCOVER,&no,sizeof(no)); +#endif + + struct sockaddr_in sin; + memset(&sin,0,sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(localPort); + sin.sin_addr.s_addr = INADDR_ANY; + if (::bind(_sock,(const struct sockaddr *)&sin,sizeof(sin))) { + ::close(_sock); + throw std::runtime_error("unable to bind to port"); + } + } + + start(); +} + +UdpSocket::~UdpSocket() +{ + close(_sock); +} + +bool UdpSocket::send(const InetAddress &to,const void *data,unsigned int len,int hopLimit) + throw() +{ + Mutex::Lock _l(_sendLock); + if (to.isV6()) { + if (!_v6) + return false; + setsockopt(_sock,IPPROTO_IPV6,IPV6_UNICAST_HOPS,&hopLimit,sizeof(hopLimit)); + return ((int)sendto(_sock,data,len,0,to.saddr(),to.saddrLen()) == (int)len); + } else { + if (_v6) + return false; + setsockopt(_sock,IPPROTO_IP,IP_TTL,&hopLimit,sizeof(hopLimit)); + return ((int)sendto(_sock,data,len,0,to.saddr(),to.saddrLen()) == (int)len); + } +} + +void UdpSocket::main() + throw() +{ + char buf[32768]; + InetAddress from; + socklen_t salen; + int n; + + for(;;) { + salen = from.saddrSpaceLen(); + n = (int)recvfrom(_sock,buf,sizeof(buf),0,from.saddr(),&salen); + if (n < 0) { + if ((errno != EINTR)&&(errno != ETIMEDOUT)) + break; + } else if (n > 0) + _packetHandler(this,_arg,from,buf,(unsigned int)n); + } +} + +} // namespace ZeroTier diff --git a/node/UdpSocket.hpp b/node/UdpSocket.hpp new file mode 100644 index 00000000..be407d56 --- /dev/null +++ b/node/UdpSocket.hpp @@ -0,0 +1,103 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_UDPSOCKET_HPP +#define _ZT_UDPSOCKET_HPP + +#include <stdexcept> +#include "Thread.hpp" +#include "InetAddress.hpp" +#include "Mutex.hpp" + +namespace ZeroTier { + +/** + * A local UDP socket + * + * The socket listens in a background thread and sends packets to Switch. + */ +class UdpSocket : protected Thread +{ +public: + /** + * Create and bind a local UDP socket + * + * @param localPort Local port to listen to + * @param ipv6 If true, bind this as an IPv6 socket, otherwise IPv4 + * @param packetHandler Function to call when packets are read + * @param arg First argument (after self) to function + * @throws std::runtime_error Unable to bind + */ + UdpSocket( + int localPort, + bool ipv6, + void (*packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int), + void *arg) + throw(std::runtime_error); + + virtual ~UdpSocket(); + + /** + * @return Locally bound port + */ + inline int localPort() const throw() { return _localPort; } + + /** + * @return True if this is an IPv6 socket + */ + inline bool v6() const throw() { return _v6; } + + /** + * Send a packet + * + * Attempt to send V6 on a V4 or V4 on a V6 socket will return false. + * + * @param to Destination IP/port + * @param data Data to send + * @param len Length of data in bytes + * @param hopLimit IP hop limit for UDP packet or -1 for max (max: 255) + * @return True if packet successfully sent to link layer + */ + bool send(const InetAddress &to,const void *data,unsigned int len,int hopLimit) + throw(); + +protected: + virtual void main() + throw(); + +private: + void (*_packetHandler)(UdpSocket *,void *,const InetAddress &,const void *,unsigned int); + void *_arg; + int _localPort; + int _sock; + bool _v6; + Mutex _sendLock; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Utils.cpp b/node/Utils.cpp new file mode 100644 index 00000000..7e55d26b --- /dev/null +++ b/node/Utils.cpp @@ -0,0 +1,558 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "Utils.hpp" +#include "Mutex.hpp" + +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#endif + +#ifdef _WIN32 +#include <Windows.h> +#endif + +#include <sys/stat.h> +#include <openssl/rand.h> + +namespace ZeroTier { + +const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; + +const uint64_t Utils::crc64Table[256] = { + 0x0000000000000000ULL,0x7ad870c830358979ULL, + 0xf5b0e190606b12f2ULL,0x8f689158505e9b8bULL, + 0xc038e5739841b68fULL,0xbae095bba8743ff6ULL, + 0x358804e3f82aa47dULL,0x4f50742bc81f2d04ULL, + 0xab28ecb46814fe75ULL,0xd1f09c7c5821770cULL, + 0x5e980d24087fec87ULL,0x24407dec384a65feULL, + 0x6b1009c7f05548faULL,0x11c8790fc060c183ULL, + 0x9ea0e857903e5a08ULL,0xe478989fa00bd371ULL, + 0x7d08ff3b88be6f81ULL,0x07d08ff3b88be6f8ULL, + 0x88b81eabe8d57d73ULL,0xf2606e63d8e0f40aULL, + 0xbd301a4810ffd90eULL,0xc7e86a8020ca5077ULL, + 0x4880fbd87094cbfcULL,0x32588b1040a14285ULL, + 0xd620138fe0aa91f4ULL,0xacf86347d09f188dULL, + 0x2390f21f80c18306ULL,0x594882d7b0f40a7fULL, + 0x1618f6fc78eb277bULL,0x6cc0863448deae02ULL, + 0xe3a8176c18803589ULL,0x997067a428b5bcf0ULL, + 0xfa11fe77117cdf02ULL,0x80c98ebf2149567bULL, + 0x0fa11fe77117cdf0ULL,0x75796f2f41224489ULL, + 0x3a291b04893d698dULL,0x40f16bccb908e0f4ULL, + 0xcf99fa94e9567b7fULL,0xb5418a5cd963f206ULL, + 0x513912c379682177ULL,0x2be1620b495da80eULL, + 0xa489f35319033385ULL,0xde51839b2936bafcULL, + 0x9101f7b0e12997f8ULL,0xebd98778d11c1e81ULL, + 0x64b116208142850aULL,0x1e6966e8b1770c73ULL, + 0x8719014c99c2b083ULL,0xfdc17184a9f739faULL, + 0x72a9e0dcf9a9a271ULL,0x08719014c99c2b08ULL, + 0x4721e43f0183060cULL,0x3df994f731b68f75ULL, + 0xb29105af61e814feULL,0xc849756751dd9d87ULL, + 0x2c31edf8f1d64ef6ULL,0x56e99d30c1e3c78fULL, + 0xd9810c6891bd5c04ULL,0xa3597ca0a188d57dULL, + 0xec09088b6997f879ULL,0x96d1784359a27100ULL, + 0x19b9e91b09fcea8bULL,0x636199d339c963f2ULL, + 0xdf7adabd7a6e2d6fULL,0xa5a2aa754a5ba416ULL, + 0x2aca3b2d1a053f9dULL,0x50124be52a30b6e4ULL, + 0x1f423fcee22f9be0ULL,0x659a4f06d21a1299ULL, + 0xeaf2de5e82448912ULL,0x902aae96b271006bULL, + 0x74523609127ad31aULL,0x0e8a46c1224f5a63ULL, + 0x81e2d7997211c1e8ULL,0xfb3aa75142244891ULL, + 0xb46ad37a8a3b6595ULL,0xceb2a3b2ba0eececULL, + 0x41da32eaea507767ULL,0x3b024222da65fe1eULL, + 0xa2722586f2d042eeULL,0xd8aa554ec2e5cb97ULL, + 0x57c2c41692bb501cULL,0x2d1ab4dea28ed965ULL, + 0x624ac0f56a91f461ULL,0x1892b03d5aa47d18ULL, + 0x97fa21650afae693ULL,0xed2251ad3acf6feaULL, + 0x095ac9329ac4bc9bULL,0x7382b9faaaf135e2ULL, + 0xfcea28a2faafae69ULL,0x8632586aca9a2710ULL, + 0xc9622c4102850a14ULL,0xb3ba5c8932b0836dULL, + 0x3cd2cdd162ee18e6ULL,0x460abd1952db919fULL, + 0x256b24ca6b12f26dULL,0x5fb354025b277b14ULL, + 0xd0dbc55a0b79e09fULL,0xaa03b5923b4c69e6ULL, + 0xe553c1b9f35344e2ULL,0x9f8bb171c366cd9bULL, + 0x10e3202993385610ULL,0x6a3b50e1a30ddf69ULL, + 0x8e43c87e03060c18ULL,0xf49bb8b633338561ULL, + 0x7bf329ee636d1eeaULL,0x012b592653589793ULL, + 0x4e7b2d0d9b47ba97ULL,0x34a35dc5ab7233eeULL, + 0xbbcbcc9dfb2ca865ULL,0xc113bc55cb19211cULL, + 0x5863dbf1e3ac9decULL,0x22bbab39d3991495ULL, + 0xadd33a6183c78f1eULL,0xd70b4aa9b3f20667ULL, + 0x985b3e827bed2b63ULL,0xe2834e4a4bd8a21aULL, + 0x6debdf121b863991ULL,0x1733afda2bb3b0e8ULL, + 0xf34b37458bb86399ULL,0x8993478dbb8deae0ULL, + 0x06fbd6d5ebd3716bULL,0x7c23a61ddbe6f812ULL, + 0x3373d23613f9d516ULL,0x49aba2fe23cc5c6fULL, + 0xc6c333a67392c7e4ULL,0xbc1b436e43a74e9dULL, + 0x95ac9329ac4bc9b5ULL,0xef74e3e19c7e40ccULL, + 0x601c72b9cc20db47ULL,0x1ac40271fc15523eULL, + 0x5594765a340a7f3aULL,0x2f4c0692043ff643ULL, + 0xa02497ca54616dc8ULL,0xdafce7026454e4b1ULL, + 0x3e847f9dc45f37c0ULL,0x445c0f55f46abeb9ULL, + 0xcb349e0da4342532ULL,0xb1eceec59401ac4bULL, + 0xfebc9aee5c1e814fULL,0x8464ea266c2b0836ULL, + 0x0b0c7b7e3c7593bdULL,0x71d40bb60c401ac4ULL, + 0xe8a46c1224f5a634ULL,0x927c1cda14c02f4dULL, + 0x1d148d82449eb4c6ULL,0x67ccfd4a74ab3dbfULL, + 0x289c8961bcb410bbULL,0x5244f9a98c8199c2ULL, + 0xdd2c68f1dcdf0249ULL,0xa7f41839ecea8b30ULL, + 0x438c80a64ce15841ULL,0x3954f06e7cd4d138ULL, + 0xb63c61362c8a4ab3ULL,0xcce411fe1cbfc3caULL, + 0x83b465d5d4a0eeceULL,0xf96c151de49567b7ULL, + 0x76048445b4cbfc3cULL,0x0cdcf48d84fe7545ULL, + 0x6fbd6d5ebd3716b7ULL,0x15651d968d029fceULL, + 0x9a0d8ccedd5c0445ULL,0xe0d5fc06ed698d3cULL, + 0xaf85882d2576a038ULL,0xd55df8e515432941ULL, + 0x5a3569bd451db2caULL,0x20ed197575283bb3ULL, + 0xc49581ead523e8c2ULL,0xbe4df122e51661bbULL, + 0x3125607ab548fa30ULL,0x4bfd10b2857d7349ULL, + 0x04ad64994d625e4dULL,0x7e7514517d57d734ULL, + 0xf11d85092d094cbfULL,0x8bc5f5c11d3cc5c6ULL, + 0x12b5926535897936ULL,0x686de2ad05bcf04fULL, + 0xe70573f555e26bc4ULL,0x9ddd033d65d7e2bdULL, + 0xd28d7716adc8cfb9ULL,0xa85507de9dfd46c0ULL, + 0x273d9686cda3dd4bULL,0x5de5e64efd965432ULL, + 0xb99d7ed15d9d8743ULL,0xc3450e196da80e3aULL, + 0x4c2d9f413df695b1ULL,0x36f5ef890dc31cc8ULL, + 0x79a59ba2c5dc31ccULL,0x037deb6af5e9b8b5ULL, + 0x8c157a32a5b7233eULL,0xf6cd0afa9582aa47ULL, + 0x4ad64994d625e4daULL,0x300e395ce6106da3ULL, + 0xbf66a804b64ef628ULL,0xc5bed8cc867b7f51ULL, + 0x8aeeace74e645255ULL,0xf036dc2f7e51db2cULL, + 0x7f5e4d772e0f40a7ULL,0x05863dbf1e3ac9deULL, + 0xe1fea520be311aafULL,0x9b26d5e88e0493d6ULL, + 0x144e44b0de5a085dULL,0x6e963478ee6f8124ULL, + 0x21c640532670ac20ULL,0x5b1e309b16452559ULL, + 0xd476a1c3461bbed2ULL,0xaeaed10b762e37abULL, + 0x37deb6af5e9b8b5bULL,0x4d06c6676eae0222ULL, + 0xc26e573f3ef099a9ULL,0xb8b627f70ec510d0ULL, + 0xf7e653dcc6da3dd4ULL,0x8d3e2314f6efb4adULL, + 0x0256b24ca6b12f26ULL,0x788ec2849684a65fULL, + 0x9cf65a1b368f752eULL,0xe62e2ad306bafc57ULL, + 0x6946bb8b56e467dcULL,0x139ecb4366d1eea5ULL, + 0x5ccebf68aecec3a1ULL,0x2616cfa09efb4ad8ULL, + 0xa97e5ef8cea5d153ULL,0xd3a62e30fe90582aULL, + 0xb0c7b7e3c7593bd8ULL,0xca1fc72bf76cb2a1ULL, + 0x45775673a732292aULL,0x3faf26bb9707a053ULL, + 0x70ff52905f188d57ULL,0x0a2722586f2d042eULL, + 0x854fb3003f739fa5ULL,0xff97c3c80f4616dcULL, + 0x1bef5b57af4dc5adULL,0x61372b9f9f784cd4ULL, + 0xee5fbac7cf26d75fULL,0x9487ca0fff135e26ULL, + 0xdbd7be24370c7322ULL,0xa10fceec0739fa5bULL, + 0x2e675fb4576761d0ULL,0x54bf2f7c6752e8a9ULL, + 0xcdcf48d84fe75459ULL,0xb71738107fd2dd20ULL, + 0x387fa9482f8c46abULL,0x42a7d9801fb9cfd2ULL, + 0x0df7adabd7a6e2d6ULL,0x772fdd63e7936bafULL, + 0xf8474c3bb7cdf024ULL,0x829f3cf387f8795dULL, + 0x66e7a46c27f3aa2cULL,0x1c3fd4a417c62355ULL, + 0x935745fc4798b8deULL,0xe98f353477ad31a7ULL, + 0xa6df411fbfb21ca3ULL,0xdc0731d78f8795daULL, + 0x536fa08fdfd90e51ULL,0x29b7d047efec8728ULL +}; + +const char Utils::base64EncMap[64] = { + 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50, + 0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5A,0x61,0x62,0x63,0x64,0x65,0x66, + 0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E, + 0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76, + 0x77,0x78,0x79,0x7A,0x30,0x31,0x32,0x33, + 0x34,0x35,0x36,0x37,0x38,0x39,0x2B,0x2F +}; + +const char Utils::base64DecMap[128] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x3F, + 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B, + 0x3C,0x3D,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06, + 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, + 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, + 0x17,0x18,0x19,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20, + 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, + 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30, + 0x31,0x32,0x33,0x00,0x00,0x00,0x00,0x00 +}; + +static const char *DAY_NAMES[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; +static const char *MONTH_NAMES[12] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; + +std::string Utils::base64Encode(const void *data,unsigned int len) +{ + if (!len) + return std::string(); + + std::string out; + unsigned int sidx = 0; + + if (len > 1) { + while (sidx < (len - 2)) { + out.push_back(base64EncMap[(((const unsigned char *)data)[sidx] >> 2) & 077]); + out.push_back(base64EncMap[((((const unsigned char *)data)[sidx + 1] >> 4) & 017) | ((((const unsigned char *)data)[sidx] << 4) & 077)]); + out.push_back(base64EncMap[((((const unsigned char *)data)[sidx + 2] >> 6) & 003) | ((((const unsigned char *)data)[sidx + 1] << 2) & 077)]); + out.push_back(base64EncMap[((const unsigned char *)data)[sidx + 2] & 077]); + sidx += 3; + } + } + if (sidx < len) { + out.push_back(base64EncMap[(((const unsigned char *)data)[sidx] >> 2) & 077]); + if (sidx < len - 1) { + out.push_back(base64EncMap[((((const unsigned char *)data)[sidx + 1] >> 4) & 017) | ((((const unsigned char *)data)[sidx] << 4) & 077)]); + out.push_back(base64EncMap[(((const unsigned char *)data)[sidx + 1] << 2) & 077]); + } else out.push_back(base64EncMap[(((const unsigned char *)data)[sidx] << 4) & 077]); + } + while (out.length() < (((len + 2) / 3) * 4)) + out.push_back('='); + + return out; +} + +std::string Utils::base64Decode(const char *data,unsigned int len) +{ + if (!len) + return std::string(); + std::string out; + + while ((len)&&(((const unsigned char *)data)[len-1] == '=')) + --len; + + for (unsigned idx=0;idx<len;idx++) { + unsigned char ch = ((const unsigned char *)data)[idx]; + if ((ch > 47 && ch < 58) || (ch > 64 && ch < 91) || (ch > 96 && ch < 123) || ch == '+' || ch == '/' || ch == '=') + out.push_back(base64DecMap[ch]); + else return std::string(); + } + + unsigned outLen = len - ((len + 3) / 4); + if ((!outLen)||((((outLen + 2) / 3) * 4) < len)) + return std::string(); + + unsigned sidx = 0; + unsigned didx = 0; + if (outLen > 1) { + while (didx < outLen - 2) { + out[didx] = (((out[sidx] << 2) & 255) | ((out[sidx + 1] >> 4) & 003)); + out[didx + 1] = (((out[sidx + 1] << 4) & 255) | ((out[sidx + 2] >> 2) & 017)); + out[didx + 2] = (((out[sidx + 2] << 6) & 255) | (out[sidx + 3] & 077)); + sidx += 4; + didx += 3; + } + } + + if (didx < outLen) + out[didx] = (((out[sidx] << 2) & 255) | ((out[sidx + 1] >> 4) & 003)); + if (++didx < outLen) + out[didx] = (((out[sidx + 1] << 4) & 255) | ((out[sidx + 2] >> 2) & 017)); + + return out.substr(0,outLen); +} + +const char *Utils::etherTypeName(const unsigned int etherType) +{ + static char tmp[6]; + switch(etherType) { + case ZT_ETHERTYPE_IPV4: + return "IPV4"; + case ZT_ETHERTYPE_ARP: + return "ARP"; + case ZT_ETHERTYPE_RARP: + return "RARP"; + case ZT_ETHERTYPE_ATALK: + return "ATALK"; + case ZT_ETHERTYPE_AARP: + return "AARP"; + case ZT_ETHERTYPE_IPX_A: + return "IPX_A"; + case ZT_ETHERTYPE_IPX_B: + return "IPX_B"; + case ZT_ETHERTYPE_IPV6: + return "IPV6"; + } + sprintf(tmp,"%.4x",etherType); + return tmp; // technically not thread safe, but we're only going to see this in debugging if ever +} + +std::string Utils::hex(const void *data,unsigned int len) +{ + std::string r; + r.reserve(len * 2); + for(unsigned int i=0;i<len;++i) { + r.push_back(HEXCHARS[(((const unsigned char *)data)[i] & 0xf0) >> 4]); + r.push_back(HEXCHARS[((const unsigned char *)data)[i] & 0x0f]); + } + return r; +} + +std::string Utils::unhex(const char *hex) +{ + int n = 1; + unsigned char c,b = 0; + std::string r; + + while ((c = (unsigned char)*(hex++))) { + if ((c >= 48)&&(c <= 57)) { // 0..9 + if ((n ^= 1)) + r.push_back((char)(b | (c - 48))); + else b = (c - 48) << 4; + } else if ((c >= 65)&&(c <= 70)) { // A..F + if ((n ^= 1)) + r.push_back((char)(b | (c - (65 - 10)))); + else b = (c - (65 - 10)) << 4; + } else if ((c >= 97)&&(c <= 102)) { // a..f + if ((n ^= 1)) + r.push_back((char)(b | (c - (97 - 10)))); + else b = (c - (97 - 10)) << 4; + } + } + + return r; +} + +unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len) +{ + int n = 1; + unsigned char c,b = 0; + unsigned int l = 0; + + while ((c = (unsigned char)*(hex++))) { + if ((c >= 48)&&(c <= 57)) { // 0..9 + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - 48)); + } else b = (c - 48) << 4; + } else if ((c >= 65)&&(c <= 70)) { // A..F + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (65 - 10))); + } else b = (c - (65 - 10)) << 4; + } else if ((c >= 97)&&(c <= 102)) { // a..f + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (97 - 10))); + } else b = (c - (97 - 10)) << 4; + } + } + + return l; +} + +void Utils::getSecureRandom(void *buf,unsigned int bytes) +{ + unsigned char tmp[16384]; + while (!RAND_bytes((unsigned char *)buf,bytes)) { +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + FILE *rf = fopen("/dev/urandom","r"); + if (rf) { + fread(tmp,sizeof(tmp),1,rf); + fclose(rf); + RAND_seed(tmp,sizeof(tmp)); + } else { + fprintf(stderr,"FATAL: could not open /dev/urandom\n"); + exit(-1); + } +#else +#ifdef _WIN32 + error need win32; +#else + error; +#endif +#endif + } +} + +void Utils::lockDownFile(const char *path,bool isDir) +{ +#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + chmod(path,isDir ? 0700 : 0600); +#else +#ifdef _WIN32 + error need win32; +#endif +#endif +} + +uint64_t Utils::getLastModified(const char *path) +{ + struct stat s; + if (stat(path,&s)) + return 0; + return (((uint64_t)s.st_mtime) * 1000ULL); +} + +std::string Utils::toRfc1123(uint64_t t64) +{ + struct tm t; + char buf[128]; + time_t utc = (time_t)(t64 / 1000ULL); + gmtime_r(&utc,&t); + sprintf(buf,"%3s, %02d %3s %4d %02d:%02d:%02d GMT",DAY_NAMES[t.tm_wday],t.tm_mday,MONTH_NAMES[t.tm_mon],t.tm_year + 1900,t.tm_hour,t.tm_min,t.tm_sec); + return std::string(buf); +} + +uint64_t Utils::fromRfc1123(const char *tstr) +{ + struct tm t; + char wdays[128],mons[128]; + + int l = strlen(tstr); + if ((l < 29)||(l > 64)) + return 0; + int assigned = sscanf(tstr,"%3s, %02d %3s %4d %02d:%02d:%02d GMT",wdays,&t.tm_mday,mons,&t.tm_year,&t.tm_hour,&t.tm_min,&t.tm_sec); + if (assigned != 7) + return 0; + + wdays[3] = '\0'; + for(t.tm_wday=0;t.tm_wday<7;++t.tm_wday) { + if (!strcasecmp(DAY_NAMES[t.tm_wday],wdays)) + break; + } + if (t.tm_wday == 7) + return 0; + mons[3] = '\0'; + for(t.tm_mon=0;t.tm_mon<12;++t.tm_mon) { + if (!strcasecmp(MONTH_NAMES[t.tm_mday],mons)) + break; + } + if (t.tm_mon == 12) + return 0; + + t.tm_wday = 0; // ignored by timegm + t.tm_yday = 0; // ignored by timegm + t.tm_isdst = 0; // ignored by timegm + + time_t utc = timegm(&t); + return ((utc > 0) ? (1000ULL * (uint64_t)utc) : 0ULL); +} + +bool Utils::readFile(const char *path,std::string &buf) +{ + char tmp[4096]; + FILE *f = fopen(path,"rb"); + if (f) { + for(;;) { + long n = (long)fread(tmp,1,sizeof(tmp),f); + if (n > 0) + buf.append(tmp,n); + else break; + } + fclose(f); + return true; + } + return false; +} + +bool Utils::writeFile(const char *path,const void *buf,unsigned int len) +{ + FILE *f = fopen(path,"wb"); + if (f) { + if ((long)fwrite(buf,1,len,f) != (long)len) { + fclose(f); + return false; + } else { + fclose(f); + return true; + } + } + return false; +} + +std::vector<std::string> Utils::split(const char *s,const char *const sep,const char *esc,const char *quot) +{ + std::vector<std::string> fields; + std::string buf; + + if (!esc) + esc = ""; + if (!quot) + quot = ""; + + bool escapeState = false; + char quoteState = 0; + while (*s) { + if (escapeState) { + escapeState = false; + buf.push_back(*s); + } else if (quoteState) { + if (*s == quoteState) { + quoteState = 0; + fields.push_back(buf); + buf.clear(); + } else buf.push_back(*s); + } else { + const char *quotTmp; + if (strchr(esc,*s)) + escapeState = true; + else if ((buf.size() <= 0)&&((quotTmp = strchr(quot,*s)))) + quoteState = *quotTmp; + else if (strchr(sep,*s)) { + if (buf.size() > 0) { + fields.push_back(buf); + buf.clear(); + } // else skip runs of seperators + } else buf.push_back(*s); + } + ++s; + } + + if (buf.size()) + fields.push_back(buf); + + return fields; +} + +std::string Utils::trim(const std::string &s) +{ + unsigned long end = s.length(); + while (end) { + char c = s[end - 1]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + --end; + else break; + } + unsigned long start = 0; + while (start < end) { + char c = s[start]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + ++start; + else break; + } + return s.substr(start,end - start); +} + +} // namespace ZeroTier diff --git a/node/Utils.hpp b/node/Utils.hpp new file mode 100644 index 00000000..b8aced63 --- /dev/null +++ b/node/Utils.hpp @@ -0,0 +1,601 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef _ZT_UTILS_HPP +#define _ZT_UTILS_HPP + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <time.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include <string> +#include <stdexcept> +#include <vector> + +#include "../ext/lz4/lz4.h" +#include "../ext/lz4/lz4hc.h" +#include "../ext/huffandpuff/huffman.h" + +#include "Constants.hpp" + +/* Ethernet frame types that might be relevant to us */ +#define ZT_ETHERTYPE_IPV4 0x0800 +#define ZT_ETHERTYPE_ARP 0x0806 +#define ZT_ETHERTYPE_RARP 0x8035 +#define ZT_ETHERTYPE_ATALK 0x809b +#define ZT_ETHERTYPE_AARP 0x80f3 +#define ZT_ETHERTYPE_IPX_A 0x8137 +#define ZT_ETHERTYPE_IPX_B 0x8138 +#define ZT_ETHERTYPE_IPV6 0x86dd + +/** + * Maximum compression/decompression block size (do not change) + */ +#define ZT_COMPRESSION_BLOCK_SIZE 16777216 + +namespace ZeroTier { + +/** + * Miscellaneous utility functions and global constants + */ +class Utils +{ +public: + /** + * @param etherType Ethernet type ID + * @return Name of Ethernet protocol (e.g. ARP, IPV4) + */ + static const char *etherTypeName(const unsigned int etherType); + + /** + * @param data Data to convert to hex + * @param len Length of data + * @return Hexadecimal string + */ + static std::string hex(const void *data,unsigned int len); + static inline std::string hex(const std::string &data) { return hex(data.data(),data.length()); } + + /** + * @param hex Hexadecimal ASCII code (non-hex chars are ignored) + * @return Binary data + */ + static std::string unhex(const char *hex); + static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str()); } + + /** + * @param hex Hexadecimal ASCII + * @param buf Buffer to fill + * @param len Length of buffer + * @return Number of characters actually written + */ + static unsigned int unhex(const char *hex,void *buf,unsigned int len); + + /** + * @param buf Buffer to fill + * @param bytes Number of random bytes to generate + */ + static void getSecureRandom(void *buf,unsigned int bytes); + + /** + * @tparam T Integer type to fill and return + * @return Random int using secure random source + */ + template<typename T> + static inline T randomInt() + { + T foo = 0; // prevents valgrind warnings + getSecureRandom(&foo,sizeof(foo)); + return foo; + } + + /** + * Set modes on a file to something secure + * + * This locks a file so that only the owner can access it. What it actually + * does varies by platform. + * + * @param path Path to lock + * @param isDir True if this is a directory + */ + static void lockDownFile(const char *path,bool isDir); + + /** + * Get file last modification time + * + * Resolution is often only second, not millisecond, but the return is + * always in ms for comparison against now(). + * + * @param path Path to file to get time + * @return Last modification time in ms since epoch or 0 if not found + */ + static uint64_t getLastModified(const char *path); + + /** + * @param t64 Time in ms since epoch + * @return RFC1123 date string + */ + static std::string toRfc1123(uint64_t t64); + + /** + * @param tstr Time in RFC1123 string format + * @return Time in ms since epoch + */ + static uint64_t fromRfc1123(const char *tstr); + static inline uint64_t fromRfc1123(const std::string &tstr) { return fromRfc1123(tstr.c_str()); } + + /** + * String append output function object for use with compress/decompress + */ + class StringAppendOutput + { + public: + StringAppendOutput(std::string &s) : _s(s) {} + inline void operator()(const void *data,unsigned int len) { _s.append((const char *)data,len); } + private: + std::string &_s; + }; + + /** + * STDIO FILE append output function object for compress/decompress + * + * Throws std::runtime_error on write error. + */ + class FILEAppendOutput + { + public: + FILEAppendOutput(FILE *f) : _f(f) {} + inline void operator()(const void *data,unsigned int len) + throw(std::runtime_error) + { + if ((int)fwrite(data,1,len,_f) != (int)len) + throw std::runtime_error("write failed"); + } + private: + FILE *_f; + }; + + /** + * Compress data + * + * O must be a function or function object that takes the following + * arguments: (const void *data,unsigned int len) + * + * @param in Input iterator that reads bytes (char, uint8_t, etc.) + * @param out Output iterator that writes bytes + */ + template<typename I,typename O> + static inline void compress(I begin,I end,O out) + { + char huffheap[HUFFHEAP_SIZE]; + unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); + char *buf = new char[bufLen * 2]; + char *buf2 = buf + bufLen; + + try { + I inp(begin); + for(;;) { + unsigned int readLen = 0; + while ((readLen < ZT_COMPRESSION_BLOCK_SIZE)&&(inp != end)) { + buf[readLen++] = (char)*inp; + ++inp; + } + if (!readLen) + break; + + uint32_t l = hton((uint32_t)readLen); + out((const void *)&l,4); // original size + + if (readLen < 32) { // don't bother compressing itty bitty blocks + l = 0; // stored + out((const void *)&l,4); + out((const void *)buf,readLen); + continue; + } + + int lz4CompressedLen = LZ4_compressHC(buf,buf2,(int)readLen); + if ((lz4CompressedLen <= 0)||(lz4CompressedLen >= (int)readLen)) { + l = 0; // stored + out((const void *)&l,4); + out((const void *)buf,readLen); + continue; + } + + unsigned long huffCompressedLen = huffman_compress((const unsigned char *)buf2,lz4CompressedLen,(unsigned char *)buf,bufLen,huffheap); + if ((!huffCompressedLen)||((int)huffCompressedLen >= lz4CompressedLen)) { + l = hton((uint32_t)lz4CompressedLen); // lz4 only + out((const void *)&l,4); + out((const void *)buf2,(unsigned int)lz4CompressedLen); + } else { + l = hton((uint32_t)0x80000000 | (uint32_t)huffCompressedLen); // lz4 with huffman + out((const void *)&l,4); + out((const void *)buf,(unsigned int)huffCompressedLen); + } + } + + delete [] buf; + } catch ( ... ) { + delete [] buf; + throw; + } + } + + /** + * Decompress data + * + * O must be a function or function object that takes the following + * arguments: (const void *data,unsigned int len) + * + * @param in Input iterator that reads bytes (char, uint8_t, etc.) + * @param out Output iterator that writes bytes + * @return False on decompression error + */ + template<typename I,typename O> + static inline bool decompress(I begin,I end,O out) + { + char huffheap[HUFFHEAP_SIZE]; + volatile char i32c[4]; + void *const i32cp = (void *)i32c; + unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); + char *buf = new char[bufLen * 2]; + char *buf2 = buf + bufLen; + + try { + I inp(begin); + while (inp != end) { + i32c[0] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[1] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[2] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[3] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + unsigned int originalSize = ntoh(*((const uint32_t *)i32cp)); + i32c[0] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[1] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[2] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + i32c[3] = (char)*inp; if (++inp == end) { delete [] buf; return false; } + uint32_t _compressedSize = ntoh(*((const uint32_t *)i32cp)); + unsigned int compressedSize = _compressedSize & 0x7fffffff; + + if (compressedSize) { + if (compressedSize > bufLen) { + delete [] buf; + return false; + } + unsigned int readLen = 0; + while ((readLen < compressedSize)&&(inp != end)) { + buf[readLen++] = (char)*inp; + ++inp; + } + if (readLen != compressedSize) { + delete [] buf; + return false; + } + + if ((_compressedSize & 0x80000000)) { // lz4 and huffman + unsigned long lz4CompressedSize = huffman_decompress((const unsigned char *)buf,compressedSize,(unsigned char *)buf2,bufLen,huffheap); + if (lz4CompressedSize) { + if (LZ4_uncompress_unknownOutputSize(buf2,buf,lz4CompressedSize,bufLen) != (int)originalSize) { + delete [] buf; + return false; + } else out((const void *)buf,(unsigned int)originalSize); + } else { + delete [] buf; + return false; + } + } else { // lz4 only + if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) { + delete [] buf; + return false; + } else out((const void *)buf2,(unsigned int)originalSize); + } + } else { // stored + if (originalSize > bufLen) { + delete [] buf; + return false; + } + unsigned int readLen = 0; + while ((readLen < originalSize)&&(inp != end)) { + buf[readLen++] = (char)*inp; + ++inp; + } + if (readLen != originalSize) { + delete [] buf; + return false; + } + + out((const void *)buf,(unsigned int)originalSize); + } + } + + delete [] buf; + return true; + } catch ( ... ) { + delete [] buf; + throw; + } + } + + /** + * @return Current time in milliseconds since epoch + */ + static inline uint64_t now() + throw() + { + struct timeval tv; + gettimeofday(&tv,(struct timezone *)0); + return ( (1000ULL * (uint64_t)tv.tv_sec) + (uint64_t)(tv.tv_usec / 1000) ); + }; + + /** + * Read the full contents of a file into a string buffer + * + * The buffer isn't cleared, so if it already contains data the file's data will + * be appended. + * + * @param path Path of file to read + * @param buf Buffer to fill + * @return True if open and read successful + */ + static bool readFile(const char *path,std::string &buf); + + /** + * Write a block of data to disk, replacing any current file contents + * + * @param path Path to write + * @param buf Buffer containing data + * @param len Length of buffer + * @return True if entire file was successfully written + */ + static bool writeFile(const char *path,const void *buf,unsigned int len); + + /** + * Write a block of data to disk, replacing any current file contents + * + * @param path Path to write + * @param s Data to write + * @return True if entire file was successfully written + */ + static inline bool writeFile(const char *path,const std::string &s) + { + return writeFile(path,s.data(),s.length()); + } + + /** + * @param data Binary data to encode + * @param len Length of data + * @return Base64-encoded string + */ + static std::string base64Encode(const void *data,unsigned int len); + inline static std::string base64Encode(const std::string &data) { return base64Encode(data.data(),data.length()); } + + /** + * @param data Base64-encoded string + * @param len Length of encoded string + * @return Decoded binary date + */ + static std::string base64Decode(const char *data,unsigned int len); + inline static std::string base64Decode(const std::string &data) { return base64Decode(data.data(),data.length()); } + + /** + * Split a string by delimiter, with optional escape and quote characters + * + * @param s String to split + * @param sep One or more separators + * @param esc Zero or more escape characters + * @param quot Zero or more quote characters + * @return Vector of tokens + */ + static std::vector<std::string> split(const char *s,const char *const sep,const char *esc,const char *quot); + + /** + * Trim whitespace from the start and end of a string + * + * @param s String to trim + * @return Trimmed string + */ + static std::string trim(const std::string &s); + + /** + * Count the number of bits set in an integer + * + * @param v 32-bit integer + * @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); + return ((((v + (v >> 4)) & (uint32_t)0xF0F0F0F) * (uint32_t)0x1010101) >> 24); + } + + /** + * Check if a memory buffer is all-zero + * + * @param p Memory to scan + * @param len Length of memory + * @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]) + return false; + } + return true; + } + + /** + * Match two strings with bits masked netmask-style + * + * @param a First string + * @param abits Number of bits in first string + * @param b Second string + * @param bbits Number of bits in second string + * @return True if min(abits,bbits) match between a and b + */ + static inline bool matchNetmask(const void *a,unsigned int abits,const void *b,unsigned int bbits) + throw() + { + const unsigned char *aptr = (const unsigned char *)a; + const unsigned char *bptr = (const unsigned char *)b; + + while ((abits >= 8)&&(bbits >= 8)) { + if (*aptr++ != *bptr++) + return false; + abits -= 8; + bbits -= 8; + } + + unsigned char mask = 0xff << (8 - ((abits > bbits) ? bbits : abits)); + return ((*aptr & mask) == (*aptr & mask)); + } + + /** + * Add a value to a bloom filter + * + * Note that bloom filter methods depend on n being evenly distributed, so + * it's the job of the caller to implement any hashing. + * + * @param bits Bloom filter data (must be filterSize / 8 bytes in length) + * @param filterSize Size of bloom filter in BITS + * @param n Number to add + */ + static inline void bloomAdd(void *bits,unsigned int filterSize,unsigned int n) + throw() + { + n %= filterSize; + ((unsigned char *)bits)[n / 8] |= (0x80 >> (n % 8)); + } + + /** + * Test for a value in a bloom filter + * + * @param bits Bloom filter data (must be filterSize / 8 bytes in length) + * @param filterSize Size of bloom filter in BITS + * @param n Number to test + * @return True if number might be in filter + */ + static inline bool bloomContains(const void *bits,unsigned int filterSize,unsigned int n) + throw() + { + n %= filterSize; + return ((((const unsigned char *)bits)[n / 8] & (0x80 >> (n % 8)))); + } + + /** + * Compute CRC64 + * + * @param crc Previous CRC (0 to start) + * @param s String to add to crc + * @param l Length of string in bytes + * @return New CRC + */ + static inline uint64_t crc64(uint64_t crc,const void *s,unsigned int l) + throw() + { + for(unsigned int i=0;i<l;++i) + crc = crc64Table[(uint8_t)crc ^ ((const uint8_t *)s)[i]] ^ (crc >> 8); + return crc; + } + + static inline uint8_t hton(uint8_t n) throw() { return n; } + static inline int8_t hton(int8_t n) throw() { return n; } + static inline uint16_t hton(uint16_t n) throw() { return htons(n); } + static inline int16_t hton(int16_t n) throw() { return (int16_t)htons((uint16_t)n); } + static inline uint32_t hton(uint32_t n) throw() { return htonl(n); } + static inline int32_t hton(int32_t n) throw() { return (int32_t)htonl((uint32_t)n); } + static inline uint64_t hton(uint64_t n) + throw() + { +#if __BYTE_ORDER == __LITTLE_ENDIAN +#ifdef __GNUC__ + return __builtin_bswap64(n); +#else + return ( + ((n & 0x00000000000000FFULL) << 56) | + ((n & 0x000000000000FF00ULL) << 40) | + ((n & 0x0000000000FF0000ULL) << 24) | + ((n & 0x00000000FF000000ULL) << 8) | + ((n & 0x000000FF00000000ULL) >> 8) | + ((n & 0x0000FF0000000000ULL) >> 24) | + ((n & 0x00FF000000000000ULL) >> 40) | + ((n & 0xFF00000000000000ULL) >> 56) + ); +#endif +#else + return n; +#endif + } + static inline int64_t hton(int64_t n) throw() { return (int64_t)hton((uint64_t)n); } + + static inline uint8_t ntoh(uint8_t n) throw() { return n; } + static inline int8_t ntoh(int8_t n) throw() { return n; } + static inline uint16_t ntoh(uint16_t n) throw() { return ntohs(n); } + static inline int16_t ntoh(int16_t n) throw() { return (int16_t)ntohs((uint16_t)n); } + static inline uint32_t ntoh(uint32_t n) throw() { return ntohl(n); } + static inline int32_t ntoh(int32_t n) throw() { return (int32_t)ntohl((uint32_t)n); } + static inline uint64_t ntoh(uint64_t n) + throw() + { +#if __BYTE_ORDER == __LITTLE_ENDIAN +#ifdef __GNUC__ + return __builtin_bswap64(n); +#else + return ( + ((n & 0x00000000000000FFULL) << 56) | + ((n & 0x000000000000FF00ULL) << 40) | + ((n & 0x0000000000FF0000ULL) << 24) | + ((n & 0x00000000FF000000ULL) << 8) | + ((n & 0x000000FF00000000ULL) >> 8) | + ((n & 0x0000FF0000000000ULL) >> 24) | + ((n & 0x00FF000000000000ULL) >> 40) | + ((n & 0xFF00000000000000ULL) >> 56) + ); +#endif +#else + return n; +#endif + } + static inline int64_t ntoh(int64_t n) throw() { return (int64_t)ntoh((uint64_t)n); } + + /** + * Hexadecimal characters 0-f + */ + static const char HEXCHARS[16]; + +private: + static const uint64_t crc64Table[256]; + static const char base64EncMap[64]; + static const char base64DecMap[128]; +}; + +} // namespace ZeroTier + +#endif + |
