zerotiertspu/node/CamoPattern.hpp

262 lines
7.2 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
#define CAMO_TRACE
#include <stdint.h>
#include <array>
#include <unordered_map>
#include <mutex>
#include <random>
#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<typename T, size_t N>
struct hash<std::array<T, N>> {
size_t operator()(const std::array<T, N>& array) const {
size_t hash = 0;
for (const auto& element : array) {
hash = hash * 31 + std::hash<T>{}(element);
}
return hash;
}
};
template<>
struct hash<ZeroTier::Address> {
size_t operator()(const ZeroTier::Address& address) const {
return std::hash<uint64_t>{}(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<unsigned int C>
static size_t getCamoIndex(const Buffer<C> &buffer)
{
size_t result = NO_CAMO;
std::array<uint8_t, BYTES_IN_WORD> 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<unsigned int C>
static void applyCamo(Buffer<C> &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<uint8_t, BYTES_IN_WORD> 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<uint8_t *>(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<unsigned int C>
static bool stripCamo(Buffer<C> &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<uint8_t *>(buffer.unsafeData());
std::array<uint8_t, BYTES_IN_WORD> 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<Address, CamoLevel> hosts, CamoRelayRule rule);
private:
static bool isInitialized;
static CamoLevel camoLevel;
static uint32_t camoWord;
static CamoRelayRule relayRule;
static std::array<std::array<uint8_t, BYTES_IN_WORD>, PATTERN_COUNT> camoValues;
static std::unordered_map<std::array<uint8_t, BYTES_IN_WORD>, size_t> camoIndices;
static std::mutex camoMutex;
static std::unordered_map<Address, CamoLevel> knownHosts;
};
} // 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