/* * 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 #include #include #include #include #include #include #include "Address.hpp" #include "Buffer.hpp" #include "RuntimeEnvironment.hpp" #define BYTES_IN_WORD (sizeof(uint32_t) / sizeof(uint8_t)) #define ID_SIZE (BYTES_IN_WORD * 2) /** * Camo functions debug trace macro * Enables debug output when CAMO_TRACE is defined */ #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 class enum */ enum CamoClass { NODE, CONTROLLER, MOON, AUTO_APPLY_COUNT, ALWAYS = AUTO_APPLY_COUNT, NEVER }; typedef std::bitset CamoAutoApplyBits; typedef std::array CamoPatternBytes; typedef std::unordered_map KnownHostsMap; } /** * Hash function for the KnownHostsMap */ namespace std { template<> struct hash { size_t operator()(const ZeroTier::Address& address) const { return std::hash{}(address.toInt()); } }; } namespace ZeroTier { enum class CamoRelayRule { LEAVE, KNOWNHOSTS, STRIP, APPLY }; /** * Helper class for packet camouflage operations */ class CamoPattern { /** * Check if the buffer has camo * * @param buffer Buffer to check * @return True if the buffer has camo */ template static bool hasCamo(const Buffer &buffer) { bool result = false; if (buffer.size() > (ID_SIZE * 2)) { size_t a = 0; size_t b = BYTES_IN_WORD; size_t x = buffer.size() - ID_SIZE; size_t y = buffer.size() - BYTES_IN_WORD; result = true; for (size_t i = 0; i < BYTES_IN_WORD; i++) { if ((buffer[a + i] ^ buffer[b + i]) != (buffer[x + i] ^ buffer [y + i])) { result = false; break; } } } CT("PACKET HAS CAMO: %b", result); return result; } /** * Check the host camo class * * @param host Destination address * @param RR RuntimeEnvironment pointer * @return Camo class for this host */ static CamoClass getCamoClass(const Address host, const RuntimeEnvironment * const RR); public: /** * Determine if camouflage is required for a specific host * * @param host Destination address * @param RR RuntimeEnvironment pointer * @param hadCamo Whether the packet previously had camouflage applied * @param isRelay Whether this is a relay operation * @return True if host requires camouflage */ static bool isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo = false, const bool isRelay = false); /** * Apply camouflage to buffer * * This increases buffer length by adding ID to the end * * @param buffer Buffer to apply camouflage to */ template static void applyCamo(Buffer &buffer) { CT("APPLYING CAMO"); if (isInitialized && !hasCamo(buffer)) { // load random number into an array uint32_t camo = rng(); CamoPatternBytes camoBytes; for (size_t i = 0; i < BYTES_IN_WORD; i++) { camoBytes[i] = camo >> 24; CT("CAMO BYTE %u: %02x", i, camoBytes[i]); camo <<= 8; } //camouflage all the data uint8_t * const data = reinterpret_cast(buffer.unsafeData()); for (size_t i = ID_SIZE; i < buffer.size(); i++) { data[i] ^= camoBytes[i % BYTES_IN_WORD]; } // expand the buffer size_t originalSize = buffer.size(); buffer.setSize(originalSize + ID_SIZE); //copy the id for (size_t i = 0; i < ID_SIZE; i++) { data[i + originalSize] = data[i] ^ camoBytes[i % BYTES_IN_WORD]; } CT("DONE"); } } /** * Remove camouflage from buffer * * This decreases buffer length by removing stored ID from the end * * @param buffer Buffer to remove camouflage from * @return True if buffer had camouflage and it was stripped */ template static bool stripCamo(Buffer &buffer) { bool result = false; if (isInitialized && hasCamo(buffer)) { //retrieve the camo bytes uint8_t * a = &buffer[0]; uint8_t * x = &buffer[buffer.size() - ID_SIZE]; CamoPatternBytes camoBytes; for (size_t i = 0; i < BYTES_IN_WORD; i++) { camoBytes[i] = a[i] ^ x[i]; CT("CAMO BYTE %u: %02x", i, camoBytes[i]); } //remove the duplicated id buffer.setSize(buffer.size() - ID_SIZE); //strip camo uint8_t * const data = reinterpret_cast(buffer.unsafeData()); for (size_t i = ID_SIZE; i < buffer.size(); i++) { data[i] ^= camoBytes[i % BYTES_IN_WORD]; } result = true; } CT("CAMO STRIPPED: %d", result); return result; } /** * Initialize the camo system * * @param autoApply Bits controlling automatic application to different host classes * @param hosts knownHosts preloading * @param relayRule Packet relay rule */ static void init(CamoAutoApplyBits autoApply, KnownHostsMap hosts, CamoRelayRule rule); private: static bool isInitialized; static CamoAutoApplyBits camoAutoApply; static CamoRelayRule relayRule; static std::mutex camoMutex; static KnownHostsMap knownHosts; static std::mt19937 rng; }; } // 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