diff --git a/.gitignore b/.gitignore index f7ef2551..5ef9f5d9 100755 --- a/.gitignore +++ b/.gitignore @@ -151,3 +151,4 @@ result-man #zerotier devs seem to forgot those rustybits/zerotier-cli rustybits/zerotier-idtool +.aider* diff --git a/node/Buffer.hpp b/node/Buffer.hpp index 8dbc51ef..559e9468 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -445,6 +445,11 @@ public: */ inline unsigned int capacity() const { return C; } + /** + * Dump buffer contents to stdout in hex format + */ + void dump() const; + template inline bool operator==(const Buffer &b) const { diff --git a/node/CamoPattern.cpp b/node/CamoPattern.cpp new file mode 100644 index 00000000..d06d4e66 --- /dev/null +++ b/node/CamoPattern.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#include "CamoPattern.hpp" +#include +#include "Topology.hpp" + +namespace ZeroTier { + +// Initialize static members of CamoPattern +bool CamoPattern::isInitialized = false; +CamoLevel CamoPattern::camoLevel; +uint32_t CamoPattern::camoWord; +CamoRelayRule CamoPattern::relayRule; +std::array, PATTERN_COUNT> CamoPattern::camoValues; +std::unordered_map, size_t> CamoPattern::camoIndices; +std::mutex CamoPattern::camoMutex; +std::unordered_map CamoPattern::knownHosts; + + +// Implementation of getCamoLevel +CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment * const RR) +{ + CamoLevel result = CamoLevel::INAPPLICABLE; + // First check if we already know this host's camo level + if (isInitialized) + { + char buf[64]; + host.toString(buf); + CT("GETTING CAMO LEVEL FOR HOST %s", buf); + auto it = knownHosts.find(host); + if (it != knownHosts.end()) { + result = it->second; + CT("HOST IS KNOWN, LEVEL: %u", result); + } + else + { + // Host not found in known hosts, run mutex-protected section + std::lock_guard lock(camoMutex); + + // Check again in case another thread added it while we were waiting + it = knownHosts.find(host); + if (it != knownHosts.end()) { + result = it->second; + CT("HOST IS KNOWN AFTER LOCK WAITING"); + } + else + { + CT("HOST IS NOT KNOWN"); + if (!RR->topology->isProhibitedEndpoint(host, InetAddress())) + { + switch(RR->topology->role(host)) + { + case ZT_PEER_ROLE_PLANET: + CT("HOST IS A PLANET"); + result = CamoLevel::PLANET; + break; + case ZT_PEER_ROLE_MOON: + CT("HOST IS A MOON"); + result = CamoLevel::MOON; + break; + default: + result = CamoLevel::NODE; + Mutex::Lock _l(RR->node->_networks_m); + Hashtable>::Iterator i(RR->node->_networks); + uint64_t * k = (uint64_t *)0; + SharedPtr *v = (SharedPtr *)0; + while(i.next(k, v)) + { + if (host == ((*v)->controller())) + { + CT("HOST IS A CONTROLLER"); + result = CamoLevel::CONTROLLER; + break; + } + } + if (result == CamoLevel::NODE) + { + CT("HOST IS A SIMPLE NODE"); + } + break; + } + } + else + { + CT("HOST IS A ZT GLOBAL ROOT"); + } + knownHosts[host] = result; + } + } + } + return result; +} + +// Implementation of isCamoRequired +bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo, const bool isRelay) +{ + bool result = false; + if (isInitialized && isRelay) + { + switch(relayRule) + { + case CamoRelayRule::LEAVE: + CT("IS RELAY, APPLYING LEAVE RULE"); + result = hadCamo; + break; + case CamoRelayRule::KNOWNHOSTS: + CT("IS RELAY, APPLYING KNOWNHOSTS RULE"); + result = getCamoLevel(host, RR) <= camoLevel; + break; + case CamoRelayRule::STRIP: + CT("IS RELAY, APPLYING STRIP RULE"); + result = false; + break; + case CamoRelayRule::APPLY: + CT("IS RELAY, APPLYING APPLY RULE"); + result = true; + break; + } + } + else if (isInitialized) + { + result = getCamoLevel(host, RR) <= camoLevel; + CT("IS CAMO REQUIRED: %b", result); + } + return result; +} + + +// Implementation of init +void CamoPattern::init(CamoLevel level, uint32_t word, std::unordered_map hosts, CamoRelayRule rule) +{ + std::lock_guard lock(camoMutex); + if (!isInitialized) + { + camoLevel = level; + camoWord = word; + knownHosts = hosts; + relayRule = rule; + CT("CAMO LEVEL: %u, WORD: %08x, KNOWN HOSTS COUNT: %lu, RELAY RULE: %u", level, word, hosts.size(), rule); + std::mt19937 rng(camoWord); + for (size_t i = 0; i < PATTERN_COUNT; i++) + { + uint32_t random = rng(); + CT("CAMO INDEX: %lu, VALUE: %08x", i, random); + for (size_t j = 0; j < BYTES_IN_WORD; j++) + { + camoValues[i][j] = (random >> (j * 8)) & 0xff; + } + camoIndices[camoValues[i]] = i; + } + isInitialized = true; + } +} + + + +} // namespace ZeroTier diff --git a/node/CamoPattern.hpp b/node/CamoPattern.hpp new file mode 100644 index 00000000..ff361b55 --- /dev/null +++ b/node/CamoPattern.hpp @@ -0,0 +1,262 @@ +/* + * Copyright (c)2013-2020 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + +#ifndef ZT_N_CAMOPATTERN_HPP +#define ZT_N_CAMOPATTERN_HPP + +#define CAMO_TRACE + +#include +#include +#include +#include +#include + +#include "Address.hpp" +#include "Buffer.hpp" +#include "RuntimeEnvironment.hpp" + +#define BYTES_IN_WORD 4 +#define PATTERN_COUNT 16 +#define PATTERN_INDEX_MASK 0xf +#define NO_CAMO PATTERN_COUNT + +#define ZT_CAMO_DEFAULT_WORDSTR "DLGE" + +namespace std { + template + struct hash> { + size_t operator()(const std::array& array) const { + size_t hash = 0; + for (const auto& element : array) { + hash = hash * 31 + std::hash{}(element); + } + return hash; + } + }; + + template<> + struct hash { + size_t operator()(const ZeroTier::Address& address) const { + return std::hash{}(address.toInt()); + } + }; +} + +/** + * Camo functions debug trace macro +*/ +#ifdef CAMO_TRACE + #define CT(...) do { \ + printf("%s:%d %s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } while(0) +#else + #define CT(...) ((void)0) +#endif + +namespace ZeroTier { + +/** + * Camouflage level enum + */ +enum class CamoLevel { + NONE, + NODE, + CONTROLLER, + MOON, + PLANET, + INAPPLICABLE +}; + +enum class CamoRelayRule { + LEAVE, + KNOWNHOSTS, + STRIP, + APPLY +}; + +/** + * Helper class for packet camouflage operations + */ +class CamoPattern +{ + /** + * Check if buffer has camouflage applied + * + * @param buffer Buffer to check + * @return True if buffer has camouflage + */ + template + static size_t getCamoIndex(const Buffer &buffer) + { + size_t result = NO_CAMO; + std::array camo; + if (buffer.size() > BYTES_IN_WORD * 2) + { + for (size_t i = 0; i < BYTES_IN_WORD; i++) + { + camo[i] = buffer[i] ^ buffer[i + BYTES_IN_WORD]; + } + auto it = camoIndices.find(camo); + if (it != camoIndices.end()) + { + result = it->second; + CT("CAMO DETECTED, INDEX: %u", result); + } + else + { + CT("CAMO NOT DETECTED"); + } + } + return result; + } + + /** + * Check the host camo level + * + * @param host Destination address + * @param RR RuntimeEnvironment pointer + * @return Camo Level for this host + */ + static CamoLevel getCamoLevel(const Address host, const RuntimeEnvironment * const RR); + +public: + /** + * + * @param host Destination address + * @param RR RuntimeEnvironment pointer + * @return True if host requires camo + */ + static bool isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo = false, const bool isRelay = false); + + /** + * Apply camouflage to buffer + * + * @param buffer Buffer to apply camouflage to + */ + template + static void applyCamo(Buffer &buffer) + { + size_t camoIndex = getCamoIndex(buffer); + if (isInitialized && (camoIndex == NO_CAMO)) { // Only apply if not already applied + CT("PACKET CONTENTS BEFORE APPLYING CAMO:"); + buffer.dump(); + camoIndex = std::minstd_rand(std::time(nullptr))() & PATTERN_INDEX_MASK; + std::array camo = camoValues[camoIndex]; + + // Increase buffer size first to avoid overflow + size_t originalSize = buffer.size(); + buffer.setSize(originalSize + BYTES_IN_WORD); + uint8_t * const data = reinterpret_cast(buffer.unsafeData()); + + // Copy the second word to the end + for (size_t i = 0; i < BYTES_IN_WORD; i++) { + data[i + originalSize] = data[i + BYTES_IN_WORD]; + } + + // Apply XOR to the rest of the buffer + for (size_t i = BYTES_IN_WORD * 2; i < buffer.size(); i++) { + data[i] ^= camo[i % BYTES_IN_WORD]; + } + + // Apply XOR to create the camouflage pattern in the second word + for (size_t i = 0; i < BYTES_IN_WORD; i++) { + data[i + BYTES_IN_WORD] = data[i] ^ camo[i]; + } + CT("PACKET CONTENTS AFTER APPLYING CAMO:"); + buffer.dump(); + } + } + + /** + * Remove camouflage from buffer + * + * This decreases buffer length by removing 'CAMO' from the end + * + * @param buffer Buffer to remove camouflage from + */ + template + static bool stripCamo(Buffer &buffer) + { + bool result = false; + size_t camoIndex = NO_CAMO; + if (isInitialized) + { + camoIndex = getCamoIndex(buffer); + } + if (camoIndex != NO_CAMO) { + CT("PACKET CONTENTS BEFORE STRIPPING CAMO:"); + buffer.dump(); + uint8_t * const data = reinterpret_cast(buffer.unsafeData()); + std::array camo = camoValues[camoIndex]; + for (size_t i = BYTES_IN_WORD * 2; i < buffer.size(); i++) + { + data[i] ^= camo[i % BYTES_IN_WORD]; + } + size_t storedWordIndex = buffer.size() - BYTES_IN_WORD; + for (size_t i = 0; i < BYTES_IN_WORD; i++) + { + data[i + BYTES_IN_WORD] = data[i + storedWordIndex]; + } + buffer.setSize(buffer.size() - BYTES_IN_WORD); + result = true; + CT("PACKET CONTENTS AFTER STRIPPING CAMO:"); + buffer.dump(); + } + return result; + } + + static void init(CamoLevel level, uint32_t word, std::unordered_map hosts, CamoRelayRule rule); + + +private: + static bool isInitialized; + static CamoLevel camoLevel; + static uint32_t camoWord; + static CamoRelayRule relayRule; + static std::array, PATTERN_COUNT> camoValues; + static std::unordered_map, size_t> camoIndices; + static std::mutex camoMutex; + static std::unordered_map knownHosts; +}; + +} // namespace ZeroTier + +// Implementation of Buffer::dump() method +template +void ZeroTier::Buffer::dump() const +{ +#ifdef CAMO_TRACE + const unsigned char *bytes = reinterpret_cast(data()); + const unsigned int size = this->size(); + + for (unsigned int i = 0; i < size; i++) { + printf("%02x", bytes[i]); + + if ((i + 1) % 16 == 0) { + printf("\n"); + } else if (i < size - 1) { + printf(" "); + } + } + + // Add newline if last line wasn't complete + if (size % 16 != 0) { + printf("\n"); + } +#endif +} + +#endif // ZT_CAMOPATTERN_HPP diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 2537c0fb..71fb0f5e 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -39,6 +39,7 @@ #include "Bond.hpp" #include "Metrics.hpp" #include "PacketMultiplexer.hpp" +#include "CamoPattern.hpp" namespace ZeroTier { @@ -407,6 +408,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(key,true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_identity_collision_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } else { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); @@ -565,6 +567,7 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED, packetId: %lx", outp.packetId()); _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version @@ -725,6 +728,7 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar if (count > 0) { Metrics::pkt_ok_out++; outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -955,6 +959,7 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -984,6 +989,7 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED, packetID: %lx", outp.packetId()); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -1180,6 +1186,7 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_unsupported_op_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -1204,6 +1211,7 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1247,6 +1255,7 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -1320,6 +1329,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const uint8_t *const frameData = (const uint8_t *)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,frameLen); if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) { + CT("UNPROCESSED"); RR->mc->send(tPtr,RR->node->now(),network,peer->address(),to,from,etherType,frameData,frameLen); } @@ -1351,6 +1361,7 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1493,6 +1504,7 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_need_membership_cert_out++; + CT("UNPROCESSED"); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index aa5936ff..cf80e339 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -74,6 +74,22 @@ public: { } + /** + * Create a new packet-in-decode from an existing packet + * + * @param packet Source packet + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + IncomingPacket(const Packet &packet, const SharedPtr &path, int64_t now) : + Packet(packet), + _receiveTime(now), + _path(path), + _authenticated(false) + { + } + /** * Init packet-in-decode in place * @@ -91,6 +107,22 @@ public: _authenticated = false; } + /** + * Init packet-in-decode in place from an existing packet + * + * @param packet Source packet + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + inline void init(const Packet &packet, const SharedPtr &path, int64_t now) + { + copyFrom(packet.data(), packet.size()); + _receiveTime = now; + _path = path; + _authenticated = false; + } + /** * Attempt to decode this packet * diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index 3a771972..8b9e572b 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -63,11 +63,11 @@ void Multicaster::remove(uint64_t nwid,const MulticastGroup &mg,const Address &m } } -unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const +unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const { unsigned char *p; unsigned int added = 0,i,k,rptr,totalKnown = 0; - uint64_t a,picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2]; + uint64_t a,picked[((ZT_PROTO_MAX_PACKET_LENGTH + ZT_PROTO_ADDITIONAL_CAMO_LENGTH) / 5) + 2]; if (!limit) { return 0; @@ -98,7 +98,7 @@ unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const // Members are returned in random order so that repeated gather queries // will return different subsets of a large multicast group. k = 0; - while ((added < limit)&&(k < s->members.size())&&((appendTo.size() + ZT_ADDRESS_LENGTH) <= ZT_PROTO_MAX_PACKET_LENGTH)) { + while ((added < limit)&&(k < s->members.size())&&((appendTo.size() + ZT_ADDRESS_LENGTH) <= (ZT_PROTO_MAX_PACKET_LENGTH + ZT_PROTO_ADDITIONAL_CAMO_LENGTH))) { rptr = (unsigned int)RR->node->prng(); restart_member_scan: @@ -201,6 +201,7 @@ void Multicaster::send( } outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); Metrics::pkt_multicast_frame_out++; + CT("UNPROCESSED"); bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; } diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index 38117144..34802594 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -27,6 +27,7 @@ #include "MAC.hpp" #include "MulticastGroup.hpp" #include "OutboundMulticast.hpp" +#include "Packet.hpp" #include "Utils.hpp" #include "Mutex.hpp" #include "SharedPtr.hpp" @@ -103,7 +104,7 @@ public: * @return Number of addresses appended * @throws std::out_of_range Buffer overflow writing to packet */ - unsigned int gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const; + unsigned int gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const; /** * Get subscribers to a multicast group diff --git a/node/Network.cpp b/node/Network.cpp index 1643487f..c66409b3 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -961,7 +961,7 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) } } -uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) +uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) { if (_destroyed) { return 0; diff --git a/node/Network.hpp b/node/Network.hpp index cc85b7d1..74df940a 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -28,6 +28,7 @@ #include "Hashtable.hpp" #include "Address.hpp" #include "Mutex.hpp" +#include "Packet.hpp" #include "SharedPtr.hpp" #include "AtomicCounter.hpp" #include "MulticastGroup.hpp" @@ -191,7 +192,7 @@ public: * @param ptr Index of chunk and related fields in packet * @return Update ID if update was fully assembled and accepted or 0 otherwise */ - uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); + uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); /** * Set network configuration diff --git a/node/Packet.hpp b/node/Packet.hpp index f607d1f5..0f5bd3ab 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -232,6 +232,8 @@ /** * Packet buffer size (can be changed) */ +#define ZT_PROTO_ADDITIONAL_CAMO_LENGTH 4 + #define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_DEFAULT_PHYSMTU) /** @@ -388,7 +390,7 @@ namespace ZeroTier { * 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 +class Packet : public Buffer { public: /** @@ -417,22 +419,22 @@ public: * 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 + class Fragment : public Buffer { public: Fragment() : - Buffer() + Buffer() { } template Fragment(const Buffer &b) : - Buffer(b) + Buffer(b) { } Fragment(const void *data,unsigned int len) : - Buffer(data,len) + Buffer(data,len) { } @@ -1091,12 +1093,12 @@ public: template Packet(const Buffer &b) : - Buffer(b) + Buffer(b) { } Packet(const void *data,unsigned int len) : - Buffer(data,len) + Buffer(data,len) { } @@ -1108,7 +1110,7 @@ public: * the header. Payload should be appended; initial size is header size. */ Packet() : - Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops @@ -1124,7 +1126,7 @@ public: * @param dest Destination ZeroTier address for new packet */ Packet(const Packet &prototype,const Address &dest) : - Buffer(prototype) + Buffer(prototype) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); @@ -1138,7 +1140,7 @@ public: * @param v Verb */ Packet(const Address &dest,const Address &source,const Verb v) : - Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); diff --git a/node/Peer.cpp b/node/Peer.cpp index f77b4e6f..fb6f4147 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -23,6 +23,7 @@ #include "RingBuffer.hpp" #include "Utils.hpp" #include "Metrics.hpp" +#include "Buffer.hpp" namespace ZeroTier { @@ -247,6 +248,7 @@ void Peer::received( outp->compress(); outp->armor(_key,true,aesKeysIfSupported()); Metrics::pkt_push_direct_paths_out++; + CT("UNPROCESSED, packetId: %lx", outp->packetId()); path->send(RR,tPtr,outp->data(),outp->size(),now); } delete outp; @@ -393,6 +395,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } outp.armor(_key,true,aesKeysIfSupported()); Metrics::pkt_rendezvous_out++; + CT("UNPROCESSED"); _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now); } else { Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); @@ -408,6 +411,7 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } outp.armor(other->_key,true,other->aesKeysIfSupported()); Metrics::pkt_rendezvous_out++; + CT("UNPROCESSED"); other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now); } ++alt; @@ -456,6 +460,7 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size()); } else { RR->node->expectReplyTo(outp.packetId()); + CT("UNPROCESSED"); RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC } } diff --git a/node/Peer.hpp b/node/Peer.hpp index 777a1e96..aeca75b1 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -35,6 +35,7 @@ #include "Bond.hpp" #include "AES.hpp" #include "Metrics.hpp" +#include "CamoPattern.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -143,6 +144,7 @@ public: { SharedPtr bp(getAppropriatePath(now,force)); if (bp) { + CT("UNPROCESSED"); return bp->send(RR,tPtr,data,len,now); } return false; diff --git a/node/Switch.cpp b/node/Switch.cpp index 7664f7a4..9f93d781 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -22,6 +22,7 @@ #include "../include/ZeroTierOne.h" #include "Constants.hpp" +#include "Hashtable.hpp" #include "RuntimeEnvironment.hpp" #include "Switch.hpp" #include "Node.hpp" @@ -33,6 +34,8 @@ #include "Trace.hpp" #include "Metrics.hpp" +#include "CamoPattern.hpp" + namespace ZeroTier { Switch::Switch(const RuntimeEnvironment *renv) : @@ -78,18 +81,32 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre { int32_t flowId = ZT_QOS_NO_FLOW; try { + char buf[64]; + fromAddr.toIpString(buf); + CT("INCOMING PACKET localSocket: %ld, IP: %s:%u, isDefaultRoute: %u", localSocket, buf, fromAddr.port(), fromAddr.isDefaultRoute()); + + Packet remotePacket(data, len); + CT("PACKET CONTENTS:"); + remotePacket.dump(); + bool hadCamo = CamoPattern::stripCamo(remotePacket); + if (hadCamo) + { + CT("PACKET HAS CAMO. CONTENTS WITHOUT CAMO:"); + remotePacket.dump(); + } + const int64_t now = RR->node->now(); const SharedPtr path(RR->topology->getPath(localSocket,fromAddr)); path->received(now); - if (len == 13) { + if (remotePacket.size() == 13) { /* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast * announcements on the LAN to solve the 'same network problem.' We * no longer send these, but we'll listen for them for a while to * locate peers with versions <1.0.4. */ - const Address beaconAddr(reinterpret_cast(data) + 8,5); + const Address beaconAddr(remotePacket.destination()); if (beaconAddr == RR->identity.address()) { return; } @@ -103,15 +120,19 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_nop_out++; + if (CamoPattern::isCamoRequired(beaconAddr, RR, hadCamo, true)) + { + CamoPattern::applyCamo(outp); + } path->send(RR,tPtr,outp.data(),outp.size(),now); } } } else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) { // SECURITY: min length check is important since we do some C-style stuff below! - if (reinterpret_cast(data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) { + if (reinterpret_cast(remotePacket[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR]) == ZT_PACKET_FRAGMENT_INDICATOR) { // Handle fragment ---------------------------------------------------- - Packet::Fragment fragment(data,len); + Packet::Fragment fragment(remotePacket); const Address destination(fragment.destination()); if (destination != RR->identity.address()) { @@ -128,6 +149,10 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,false))) { // Don't know peer or no direct path -- so relay via someone upstream relayTo = RR->topology->getUpstreamPeer(); + if (CamoPattern::isCamoRequired(destination, RR, hadCamo, true)) + { + CamoPattern::applyCamo(fragment); + } if (relayTo) { relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,true); } @@ -184,8 +209,8 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) { // min length check is important! // Handle packet head ------------------------------------------------- - const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); - const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); + const Address destination(remotePacket.destination()); + const Address source(remotePacket.source()); if (source == RR->identity.address()) { return; @@ -196,12 +221,14 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre return; } - Packet packet(data,len); - - if (packet.hops() < ZT_RELAY_MAX_HOPS) { - packet.incrementHops(); + if (remotePacket.hops() < ZT_RELAY_MAX_HOPS) { + remotePacket.incrementHops(); SharedPtr relayTo = RR->topology->getPeer(tPtr,destination); - if ((relayTo)&&(relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,false))) { + if (CamoPattern::isCamoRequired(destination, RR, hadCamo, true)) + { + CamoPattern::applyCamo(remotePacket); + } + if ((relayTo)&&(relayTo->sendDirect(tPtr,remotePacket.data(),remotePacket.size(),now,false))) { if ((source != RR->identity.address())&&(_shouldUnite(now,source,destination))) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); if (sourcePeer) { @@ -211,7 +238,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } else { relayTo = RR->topology->getUpstreamPeer(); if ((relayTo)&&(relayTo->address() != source)) { - if (relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,true)) { + if (relayTo->sendDirect(tPtr,remotePacket.data(),remotePacket.size(),now,true)) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); @@ -220,19 +247,10 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } } } - } else if ((reinterpret_cast(data)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0) { + } else if ((reinterpret_cast(remotePacket[ZT_PACKET_IDX_FLAGS]) & ZT_PROTO_FLAG_FRAGMENTED) != 0) { // Packet is the head of a fragmented packet series - const uint64_t packetId = ( - (((uint64_t)reinterpret_cast(data)[0]) << 56) | - (((uint64_t)reinterpret_cast(data)[1]) << 48) | - (((uint64_t)reinterpret_cast(data)[2]) << 40) | - (((uint64_t)reinterpret_cast(data)[3]) << 32) | - (((uint64_t)reinterpret_cast(data)[4]) << 24) | - (((uint64_t)reinterpret_cast(data)[5]) << 16) | - (((uint64_t)reinterpret_cast(data)[6]) << 8) | - ((uint64_t)reinterpret_cast(data)[7]) - ); + const uint64_t packetId = remotePacket.packetId(); RXQueueEntry *const rq = _findRXQueueEntry(packetId); Mutex::Lock rql(rq->lock); @@ -242,7 +260,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre rq->flowId = flowId; rq->timestamp = now; rq->packetId = packetId; - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); rq->totalFragments = 0; rq->haveFragments = 1; rq->complete = false; @@ -252,7 +270,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((rq->totalFragments > 1)&&(Utils::countBits(rq->haveFragments |= 1) == rq->totalFragments)) { // We have all fragments -- assemble and process full Packet - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); for(unsigned int f=1;ftotalFragments;++f) { rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); } @@ -264,12 +282,12 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } } else { // Still waiting on more fragments, but keep the head - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); } } // else this is a duplicate head, ignore } else { // Packet is unfragmented, so just process it - IncomingPacket packet(data,len,path,now); + IncomingPacket packet(remotePacket,path,now); if (!packet.tryDecode(RR,tPtr,flowId)) { RXQueueEntry *const rq = _nextRXQueueEntry(); Mutex::Lock rql(rq->lock); @@ -1127,6 +1145,199 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) return false; } +#define NEWOUT +#ifdef NEWOUT +void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId) +{ + unsigned int mtu = ZT_DEFAULT_PHYSMTU; + uint64_t trustedPathId = 0; + RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); + + char buf[64]; + + Address destination = packet.destination(); + destination.toString(buf); + + Identity destinationIdentity; + + RR->topology->getIdentity(&destinationIdentity, destination); + CT("OUTGOING PACKET ZT ADDRESS: %s, isUpstream: %b, isProhibitedEndpoint: %b", buf, RR->topology->isUpstream(destinationIdentity), RR->topology->isProhibitedEndpoint(destination, InetAddress())); + + + ZT_PeerRole role = RR->topology->role(destination); + + switch(role) + { + case ZT_PEER_ROLE_PLANET: + strcpy(buf, "PLANET"); + break; + case ZT_PEER_ROLE_MOON: + strcpy(buf, "MOON"); + break; + case ZT_PEER_ROLE_LEAF: + strcpy(buf, "LEAF"); + break; + default: + strcpy(buf, "UNKNOWN"); + break; + } + + CT("DESTINATION ROLE: %s", buf); + + switch(packet.verb()) + { + case Packet::VERB_NOP: + strcpy(buf, "NOP"); + break; + case Packet::VERB_HELLO: + strcpy(buf, "HELLO"); + break; + case Packet::VERB_ERROR: + strcpy(buf, "ERROR"); + break; + case Packet::VERB_OK: + strcpy(buf, "OK"); + break; + case Packet::VERB_WHOIS: + strcpy(buf, "WHOIS"); + break; + case Packet::VERB_RENDEZVOUS: + strcpy(buf, "RENDEZVOUS"); + break; + case Packet::VERB_FRAME: + strcpy(buf, "FRAME"); + break; + case Packet::VERB_EXT_FRAME: + strcpy(buf, "EXT_FRAME"); + break; + case Packet::VERB_ECHO: + strcpy(buf, "ECHO"); + break; + case Packet::VERB_MULTICAST_LIKE: + strcpy(buf, "MULTICAST_LIKE"); + break; + case Packet::VERB_NETWORK_CREDENTIALS: + strcpy(buf, "NETWORK_CREDENTIALS"); + break; + case Packet::VERB_NETWORK_CONFIG_REQUEST: + strcpy(buf, "NETWORK_CONFIG_REQUEST"); + break; + case Packet::VERB_NETWORK_CONFIG: + strcpy(buf, "NETWORK_CONFIG"); + break; + case Packet::VERB_MULTICAST_GATHER: + strcpy(buf, "MULTICAST_GATHER"); + break; + case Packet::VERB_MULTICAST_FRAME: + strcpy(buf, "MULTICAST_FRAME"); + break; + case Packet::VERB_PUSH_DIRECT_PATHS: + strcpy(buf, "PUSH_DIRECT_PATHS"); + break; + case Packet::VERB_ACK: + strcpy(buf, "ACK"); + break; + case Packet::VERB_QOS_MEASUREMENT: + strcpy(buf, "QOS_MEASUREMENT"); + break; + case Packet::VERB_USER_MESSAGE: + strcpy(buf, "USER_MESSAGE"); + break; + case Packet::VERB_REMOTE_TRACE: + strcpy(buf, "REMOTE_TRACE"); + break; + case Packet::VERB_PATH_NEGOTIATION_REQUEST: + strcpy(buf, "PATH_NEGOTIATION_REQUEST"); + break; + default: + strcpy(buf, "UNKNOWN"); + break; + }; + + CT("PACKET VERB: %s", buf); + + bool isController = false; + { + Mutex::Lock _l(RR->node->_networks_m); + Hashtable>::Iterator i(RR->node->_networks); + uint64_t * k = (uint64_t *)0; + SharedPtr *v = (SharedPtr *)0; + while(i.next(k, v)) + { + if (destination == ((*v)->controller())) + { + isController = true; + break; + } + } + } + + CT("isController: %b", isController); + + if (userSpecifiedMtu > 0) { + mtu = userSpecifiedMtu; + } + + bool isCamoRequired = CamoPattern::isCamoRequired(destination, RR); + + unsigned int camoSize = isCamoRequired ? ZT_PROTO_ADDITIONAL_CAMO_LENGTH : 0; + unsigned int packetSizeCamo = packet.size() + camoSize; + unsigned int chunkSize = std::min(packetSizeCamo, mtu); + bool isFragmented = chunkSize < (packetSizeCamo); + packet.setFragmented(isFragmented); + + CT("PACKET CONTENTS:"); + packet.dump(); + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + if (!packet.isEncrypted()) { + packet.armor(peer->key(),encrypt,peer->aesKeysIfSupported()); + } + RR->node->expectReplyTo(packet.packetId()); + } + CT("PACKET CONTENTS AFTER ENCRYPTION:"); + packet.dump(); + + peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now); + + if (!isFragmented) + { + CT("UNFRAGMENTED BRANCH"); + if (isCamoRequired) + { + CamoPattern::applyCamo(packet); + } + viaPath->send(RR,tPtr,packet.data(),chunkSize,now); + } + else + { + CT("FRAGMENTED BRANCH"); + // Too big for one packet, fragment it + unsigned int minFragmentSize = ZT_PROTO_MIN_FRAGMENT_LENGTH + camoSize; + unsigned int fragStart = 0; + unsigned int remaining = packet.size(); + unsigned int fragSize = mtu - minFragmentSize; + unsigned int fragsRemaining = (remaining / (fragSize)); + if ((fragsRemaining * fragSize) < remaining) { + ++fragsRemaining; + } + const unsigned int totalFragments = fragsRemaining; + + for(unsigned int fno=0;fnosend(RR,tPtr,frag.data(),frag.size(),now); + fragStart += chunkSize; + remaining -= chunkSize; + } + } +} +#else void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId) { unsigned int mtu = ZT_DEFAULT_PHYSMTU; @@ -1171,6 +1382,7 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr knownHosts; + json &settings = lc["settings"]; if (settings.is_object()) { // Allow controller DB path to be put somewhere else @@ -1487,7 +1500,150 @@ public: } } } + + // Camo settings + json &camo = settings["camo"]; + if (camo.is_object()) + { + CT("CAMO CONFIG SECTION FOUND"); + std::string camoLevelStr = OSUtils::jsonString(camo["level"], "none"); + std::transform(camoLevelStr.begin(), camoLevelStr.end(), camoLevelStr.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + CT("CAMO LEVEL SETTING: %s", camoLevelStr.c_str()); + + if (camoLevelStr == "node") + { + camoLevel = CamoLevel::NODE; + } + else if (camoLevelStr == "controller") + { + camoLevel = CamoLevel::CONTROLLER; + } + else if (camoLevelStr == "moon") + { + camoLevel = CamoLevel::MOON; + } + else if (camoLevelStr == "planet") + { + camoLevel = CamoLevel::PLANET; + } + + if (camo.contains("word")) + { + std::string temp = OSUtils::jsonString(camo["word"], ZT_CAMO_DEFAULT_WORDSTR); + if (temp.substr(0, 2) == "0x") + { + try + { + camoWord = std::stoul(temp.substr(2), nullptr, 16); + } + catch (...) + { + } + } + else + { + for (size_t i = 0; i < BYTES_IN_WORD; i++) + { + char c = ' '; + if (i < temp.size()) + { + c = temp[i]; + } + camoWord |= c; + camoWord <<= 8; + } + } + } + CT("CAMO WORD SETTING: %x", camoWord); + + std::string relayRuleStr = OSUtils::jsonString(camo["relayRule"], "leave"); + std::transform(relayRuleStr.begin(), relayRuleStr.end(), relayRuleStr.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + if (relayRuleStr == "knownhosts") + { + relayRule = CamoRelayRule::KNOWNHOSTS; + } + else if (relayRuleStr == "strip") + { + relayRule = CamoRelayRule::STRIP; + } + else if (relayRuleStr == "apply") + { + relayRule = CamoRelayRule::APPLY; + } + CT("CAMO RELAY RULE SETTING: %s", relayRuleStr.c_str()); + + json &knownHostsSection = camo["knownHosts"]; + if (knownHostsSection.is_object()) + { + CT("KNOWN HOSTS SECTION FOUND"); + auto processLevelSection = [&knownHosts](const json §ion, CamoLevel level) + { + if (section.is_array()) + { + for (const auto &addrStr: section) + { + std::string addr = OSUtils::jsonString(addrStr, ""); + if (!addr.empty()) + { + Address host (Utils::hexStrToU64(addr.c_str())); + if (host) + { + CT("VALID HOST FOUND: %s", addr.c_str()); + knownHosts[host] = level; + } + } + } + } + }; + + for (auto it = knownHostsSection.begin(); it != knownHostsSection.end(); it++) + { + std::string levelKey = it.key(); + std::transform(levelKey.begin(), levelKey.end(), levelKey.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + + CT("FOUND CAMO LEVEL: %s", levelKey.c_str()); + if (levelKey == "none") + { + processLevelSection(it.value(), CamoLevel::NONE); + } + else if (levelKey == "node") + { + processLevelSection(it.value(), CamoLevel::NODE); + } + else if (levelKey == "controller") + { + processLevelSection(it.value(), CamoLevel::CONTROLLER); + } + else if (levelKey == "moon") + { + processLevelSection(it.value(), CamoLevel::MOON); + } + else if (levelKey == "planet") + { + processLevelSection(it.value(), CamoLevel::PLANET); + } + else + { + processLevelSection(it.value(), CamoLevel::INAPPLICABLE); + } + } + } + } + else + { + CT("CAMO CONFIG SECTION NOT FOUND"); + } } + CamoPattern::init(camoLevel, camoWord, knownHosts, relayRule); // Set trusted paths if there are any if (!ppc.empty()) {