zerotiertspu/node/CamoPattern.hpp
2025-05-15 00:42:22 +04:00

266 lines
7 KiB
C++

/*
* 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 <stdint.h>
#include <array>
#include <unordered_map>
#include <mutex>
#include <random>
#include <bitset>
#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<CamoClass::AUTO_APPLY_COUNT> CamoAutoApplyBits;
typedef std::array<uint8_t, BYTES_IN_WORD> CamoPatternBytes;
typedef std::unordered_map<Address, CamoClass> KnownHostsMap;
}
/**
* Hash function for the KnownHostsMap
*/
namespace std {
template<>
struct hash<ZeroTier::Address> {
size_t operator()(const ZeroTier::Address& address) const {
return std::hash<uint64_t>{}(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<unsigned int C>
static bool hasCamo(const Buffer<C> &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<unsigned int C>
static void applyCamo(Buffer<C> &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<uint8_t * const>(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<unsigned int C>
static bool stripCamo(Buffer<C> &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<uint8_t * const>(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<unsigned int C>
void ZeroTier::Buffer<C>::dump() const
{
#ifdef CAMO_TRACE
const unsigned char *bytes = reinterpret_cast<const unsigned char *>(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