From 98ccedecacc7e039b7fe4915fed32765908b0568 Mon Sep 17 00:00:00 2001 From: eerieaerial <205630019+eerieaerial@users.noreply.github.com> Date: Wed, 14 May 2025 22:26:08 +0400 Subject: [PATCH] changed camouflaging scheme --- README.camo.md | 82 +++++++--------- node/CamoPattern.cpp | 118 ++++++++++------------- node/CamoPattern.hpp | 209 +++++++++++++++++++---------------------- node/Packet.hpp | 2 +- service/OneService.cpp | 143 ++++++++++------------------ 5 files changed, 235 insertions(+), 319 deletions(-) diff --git a/README.camo.md b/README.camo.md index f79a84f4..fca52a50 100644 --- a/README.camo.md +++ b/README.camo.md @@ -6,58 +6,44 @@ ## Принцип работы -### Узоры камуфлирования - -Основу механизма камуфлирования составляют 16 случайных 32-битных узоров, которые: - -- Генерируются при инициализации стандартизованным ГСЧ (вихрь Мерсенна MT19937, определённый в стандарте C++11) с фиксированным порождающим словом -- Одинаковы для всех узлов с одинаковым порождающим словом -- Используются для XOR-преобразования содержимого пакета -- Служат признаком распознавания камуфлированных пакетов - -Размер списка в 16 узоров выбран из соображений баланса между разнообразием узоров и вероятностью коллизии между признаком камуфлирования и случайно сгенерированным ID некамуфлированного пакета. Размер списка можно изменить путём задания нового значения макроса `PATTERN_COUNT` в файле `CamoPattern.hpp`. Рекомендуется использовать значения, равные степеням 2. - ### Алгоритм камуфлирования -1. 64-битный ID в заголовке пакета разделяется на два 32-битных сегмента -2. Младший сегмент перемещается в конец пакета -3. Старший сегмент дублируется на место младшего -4. Данные от дубликата до конца пакета обрабатываются XOR со случайно выбранным узором -5. Раскамуфлирование выполняется в обратном порядке после распознавания узора +1. 64-битный ID в заголовке пакета дублируется в конец пакета +2. Данные от дубликата до конца пакета обрабатываются XOR со случайным 32-битным числом ("камуфляжем") +3. Раскамуфлирование выполняется в обратном порядке после распознавания камуфляжа -### Распознавание камуфлированных пакетов +### Распознавание камуфляжа -Для распознавания того, камуфлирован пакет или нет, используется XOR между старшим и младшим сегментами ID. Если результат обнаружен в списке узоров, то это означает, что пакет закамуфлирован этим узором. +Для распознавания того, камуфлирован пакет или нет, используется равенство `a ^ b = x ^ y`, где `a` и `b` - старший и младший 32-битные сегменты ID, `x` и `y` - старший и младший 32-битные сегменты последних 64 бит пакета. Если равенство соблюдается, то это означает, что пакет камуфлирован, значение камуфляжа определяется формулой `c = a ^ x`. ### Правила обработки пакетов -Пакеты обрабатываются на основе сравнения уровня камуфлирования адресата и отправителя. Уровень адресата определяется либо автоматически, либо заданием в настройках. Уровень отправителя определяется настройкой и относится только непосредственно к тому узлу, на котором производится обработка пакета в данный момент. +Пакеты обрабатываются на основе класса адресата и заданной настройки включения камуфлирования для каждого из классов. Класс адресата присваивается ему либо автоматически, либо заданием в настройках. - _Входящие_: всегда автоматически раскамуфлируются для обеспечения работы внутренних механизмов -- _Исходящие_: камуфлируются, если уровень адресата равен уровню отправителя или меньше +- _Исходящие_: камуфлируются, если камуфлирование включено для класса адресата - _Пересылаемые_: обрабатываются согласно выбранному правилу пересылки -### Уровни камуфлирования +### Классы камуфлирования -Уровни камуфлирования имеют следующие названия от низшего к высшему: +Классы камуфлирования имеют следующие названия: -- `NONE` - `NODE` - `CONTROLLER` - `MOON` -- `PLANET` -- `INAPPLICABLE` +- `ALWAYS` +- `NEVER` -Особое значение имеют два уровня: +Особое значение имеют два класса: -- `NONE` - не назначается автоматически в качестве уровня адресата. Выбор этого уровня в качестве уровня отправителя выключает камуфлирование пакетов при автоматическом выборе уровня адресата. -- `INAPPLICABLE` - в качестве уровня адресата назначается автоматически глобальным корневым серверам. Для выбора в качестве уровня отправителя недоступен. +- `ALWAYS` - не присваивается адресату автоматически. Ручное присвоение этого класса адресату включает безусловное камуфлирование пакетов для него. +- `NEVER` - в качестве класса адресата присваивается автоматически глобальным корневым серверам (планетам). Ручное присвоение этого класса адресату безусловно выключает камуфлирование пакетов для него. -Прочие уровни назначаются автоматически согласно роли адресата. +Прочие классы назначаются автоматически согласно роли адресата. ### Список известных адресатов -При определении уровня камуфлирования для адресата проводится поиск в списке известных адресатов. Если адресат там обнаружен, то используется уровень, который присвоен в списке. Если нет - то производится автоматическое определение уровня адресата и результат заносится в список. Настройки позволяют задать изначальное содержимое списка для принудительного назначения адресатам требуемого уровня. +При определении класса адресата проводится поиск в списке известных адресатов. Если адресат там обнаружен, то используется класс, который присвоен в списке. Если нет - то производится автоматическое определение класса адресата и результат заносится в список. Настройки позволяют задать изначальное содержимое списка для принудительного назначения адресатам требуемого класса. ### Правила пересылки @@ -74,11 +60,16 @@ { "settings": { "camo": { - "level": "none", // Уровень отправителя - "word": "DLGE", // Порождающее слово для узоров + "autoApply": [ // Включает камуфлирование для перечисленных классов + "node", + "controller", + "moon" + ], "relayRule": "leave", // Правило пересылки - "knownHosts": { // Предустановленные уровни адресата для узлов - "none" : [] + "knownHosts": { // Предустановленные классы адресата для узлов + "never" : [ + "aaaaaaaaaa" + ] } } } @@ -87,10 +78,9 @@ ### Параметры -- `"level"`: регистронезависимая строка с названием уровня отправителя. По умолчанию: `"none"`. Любые значения, не соответствующие названию существующих уровней, интерпретируются как `"none"`. Значение `"inapplicable"` также интерпретируется как `"none"`, т.к. этот уровень недоступен для выбора. -- `"word"`: строка, содержащая порождающее слово для списка узоров. Если в строке меньше 4 символов, она дополняется до 4 символов последовательностью `"DLGE"`. Строка длиннее 4 символов обрезается. Если строка начинается с `"0x"`, она интерпретируется как 32-битное шестнадцатеричное число. По умолчанию: `"DLGE"`. +- `"autoApply"`: массив регистронезависимых строк с названиями классов адресата, для которых включается автоматическое камуфлирование. По умолчанию: `[]`. Допустимо задание строк `node`, `controller` и `moon`, прочие значения игнорируются. - `"relayRule"`: регистронезависимая строка, содержащая название правила пересылки. По умолчанию: `"leave"`. Любые значения, не соответствующие названию существующих правил, интерпретируются как `"leave"`. -- `"knownHosts"`: объект для предустановки уровней для конкретных узлов. Названия полей объекта соответствуют уровням камуфлирования. Названия полей, не соответствующие существующим уровням, интерпретируются как `"inapplicable"`. Значение каждого поля - массив строк с адресами. +- `"knownHosts"`: объект для предустановки классов адресата для конкретных узлов. Названия полей объекта регистронезависимы и соответствуют классам адресатов. Названия полей, не соответствующие существующим классам, интерпретируются как `"never"`. Значение каждого поля - массив строк с адресами. При включении одного и того же адреса в несколько разных классов, адресату будет назначен класс, идущий последним. ## Примеры конфигураций @@ -100,16 +90,16 @@ { "settings": { "camo": { - "level": "none", + "autoApply": [], "knownHosts": { - "none": ["aaaaaaaaaa"] + "always": ["aaaaaaaaaa"] } } } } ``` -Эта настройка выбирает уровень отправителя `NONE`, который автоматически не назначается адресатам. Это отключает камуфлирование для всех автоматически определяемых адресатов. Адресату `aaaaaaaaaa` принудительно назначается уровень `NONE`, и т.к. этот уровень равен выбранному, пакеты для него будут камуфлироваться. +Эта настройка отключает автоматическое камуфлирование. При этом, адресату `aaaaaaaaaa` принудительно назначается класс `ALWAYS`, и поэтому пакеты для него (и только для него) будут камуфлироваться. ### "Чёрный список" @@ -117,16 +107,16 @@ { "settings": { "camo": { - "level": "node", + "autoApply": [ "node" ], "knownHosts": { - "inapplicable": ["aaaaaaaaaa"] + "never": ["aaaaaaaaaa"] } } } } ``` -Здесь выбран уровень отправителя `NODE`, что включает камуфлирование для адресатов этого уровня и ниже. Адресату `aaaaaaaaaa` назначен уровень `INAPPLICABLE`, поэтому вне зависимости от выбранного уровня, пакеты для него камуфлироваться не будут. +Здесь включается автоматическое камуфлирование для обычных узлов. Узлу `aaaaaaaaaa` назначен класс `NEVER`, и вне зависимости от настроек автоматического камуфлирования, пакеты для него камуфлироваться не будут. ### Принудительное включение для специфических узлов @@ -134,7 +124,7 @@ { "settings": { "camo": { - "level": "node", + "level": [ "node" ], "knownHosts": { "node": ["aaaaaaaaaa"] } @@ -143,11 +133,11 @@ } ``` -В этом примере корневому серверу `aaaaaaaaaa`, поддерживающему работу с камуфлированными пакетами, принудительно присваивается уровень `NODE`, чтобы пакеты для него камуфлировались наряду с обычными узлами. +В этом примере корневому серверу `aaaaaaaaaa`, поддерживающему работу с камуфлированными пакетами, принудительно присваивается класс `NODE`, чтобы пакеты для него камуфлировались наряду с обычными узлами. ### Комментарий -По большому счёту, в `"knownHosts"` имеет смысл использовать только уровни `NONE` и `INAPPLICABLE`. Первый - принудительно включает камуфлирование для указанных адресатов, второй - выключает. +По большому счёту, в `"knownHosts"` имеет смысл использовать только классы `ALWAYS` и `NEVER`, т.к. в этом случае поведение не будет зависеть от настоек автоматического камуфлирования. ## Перевод сети на камуфлирование diff --git a/node/CamoPattern.cpp b/node/CamoPattern.cpp index f86c4552..4038ed56 100644 --- a/node/CamoPattern.cpp +++ b/node/CamoPattern.cpp @@ -9,39 +9,34 @@ * 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 "RuntimeEnvironment.hpp" #include "Topology.hpp" namespace ZeroTier { // Initialize static members of CamoPattern bool CamoPattern::isInitialized = false; -CamoLevel CamoPattern::camoLevel; -uint32_t CamoPattern::camoWord; CamoRelayRule CamoPattern::relayRule; -CamoPatternArray CamoPattern::camoValues; -CamoIndexMap CamoPattern::camoIndices; std::mutex CamoPattern::camoMutex; KnownHostsMap CamoPattern::knownHosts; +CamoAutoApplyBits CamoPattern::camoAutoApply; +std::mt19937 CamoPattern::rng(std::random_device{}()); - -// Implementation of getCamoLevel -CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment * const RR) +CamoClass CamoPattern::getCamoClass(const Address host, const RuntimeEnvironment * const RR) { - CamoLevel result = CamoLevel::INAPPLICABLE; - // First check if we already know this host's camo level + CamoClass result = CamoClass::NEVER; if (isInitialized) { char buf[64]; host.toString(buf); - CT("GETTING CAMO LEVEL FOR HOST %s", buf); + CT("GETTING CAMO CLASS FOR HOST %s", buf); auto it = knownHosts.find(host); if (it != knownHosts.end()) { result = it->second; - CT("HOST IS KNOWN, LEVEL: %u", result); + CT("HOST IS KNOWN, CLASS: %u", result); } else { @@ -52,48 +47,40 @@ CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment it = knownHosts.find(host); if (it != knownHosts.end()) { result = it->second; - CT("HOST IS KNOWN AFTER LOCK WAITING"); + CT("HOST IS KNOWN AFTER LOCK WAITING, CLASS: %u", result); } else { CT("HOST IS NOT KNOWN"); - if (!RR->topology->isProhibitedEndpoint(host, InetAddress())) + switch(RR->topology->role(host)) { - 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)) + case ZT_PEER_ROLE_PLANET: + CT("HOST IS A PLANET"); + break; + case ZT_PEER_ROLE_MOON: + CT("HOST IS A MOON"); + result = CamoClass::MOON; + break; + default: + result = CamoClass::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())) { - if (host == ((*v)->controller())) - { - CT("HOST IS A CONTROLLER"); - result = CamoLevel::CONTROLLER; - break; - } + CT("HOST IS A CONTROLLER"); + result = CamoClass::CONTROLLER; + break; } - if (result == CamoLevel::NODE) - { - CT("HOST IS A SIMPLE NODE"); - } - break; - } - } - else - { - CT("HOST IS A ZT GLOBAL ROOT"); + } + if (result == CamoClass::NODE) + { + CT("HOST IS A SIMPLE NODE"); + } + break; } knownHosts[host] = result; } @@ -102,10 +89,18 @@ CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment return result; } -// Implementation of isCamoRequired +// Implementation of isCamoRequired - determines if camouflage should be applied based on host and rules bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo, const bool isRelay) { bool result = false; + + auto isRequiredByClass = [](const Address host, const RuntimeEnvironment * const RR) -> bool { + CamoClass camoClass = getCamoClass(host, RR); + return camoClass < CamoClass::AUTO_APPLY_COUNT ? + camoAutoApply[camoClass] : + camoClass == CamoClass::ALWAYS; + }; + if (isInitialized && isRelay) { switch(relayRule) @@ -116,7 +111,7 @@ bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment * break; case CamoRelayRule::KNOWNHOSTS: CT("IS RELAY, APPLYING KNOWNHOSTS RULE"); - result = getCamoLevel(host, RR) <= camoLevel; + result = isRequiredByClass(host, RR); break; case CamoRelayRule::STRIP: CT("IS RELAY, APPLYING STRIP RULE"); @@ -130,39 +125,24 @@ bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment * } else if (isInitialized) { - result = getCamoLevel(host, RR) <= camoLevel; + result = isRequiredByClass(host, RR); CT("IS CAMO REQUIRED: %b", result); } return result; } - -// Implementation of init -void CamoPattern::init(CamoLevel level, uint32_t word, KnownHostsMap hosts, CamoRelayRule rule) +// Implementation of init - initializes the camouflage system with the specified settings +void CamoPattern::init(CamoAutoApplyBits autoApply, KnownHostsMap hosts, CamoRelayRule rule) { std::lock_guard lock(camoMutex); if (!isInitialized) { - camoLevel = level; - camoWord = word; + camoAutoApply = autoApply; 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; - } + CT("KNOWN HOSTS COUNT: %lu, RELAY RULE: %u", hosts.size(), rule); isInitialized = true; } } - - } // namespace ZeroTier diff --git a/node/CamoPattern.hpp b/node/CamoPattern.hpp index c53e9fdc..638670e4 100644 --- a/node/CamoPattern.hpp +++ b/node/CamoPattern.hpp @@ -9,7 +9,6 @@ * 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 @@ -19,20 +18,19 @@ #include #include #include +#include #include "Address.hpp" #include "Buffer.hpp" #include "RuntimeEnvironment.hpp" #define BYTES_IN_WORD (sizeof(uint32_t) / sizeof(uint8_t)) -#define PATTERN_COUNT 16 -#define NO_CAMO PATTERN_COUNT - -#define ZT_CAMO_DEFAULT_WORDSTR "DLGE" +#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__); \ @@ -43,35 +41,29 @@ #define CT(...) ((void)0) #endif -/** - * Type shorthands -*/ namespace ZeroTier { -enum class CamoLevel; +/** + * 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::array CamoPatternArray; -typedef std::unordered_map CamoIndexMap; -typedef std::unordered_map KnownHostsMap; +typedef std::unordered_map KnownHostsMap; } /** - * Hash functions for the respective unordered_maps + * Hash function for the KnownHostsMap */ namespace std { - template<> - struct hash { - size_t operator()(const ZeroTier::CamoPatternBytes& bytes) const { - uint32_t word = 0; - for (const auto& byte : bytes) { - word <<= 8; - word |= byte; - } - return std::hash{}(word); - } - }; - template<> struct hash { size_t operator()(const ZeroTier::Address& address) const { @@ -82,18 +74,6 @@ namespace std { namespace ZeroTier { -/** - * Camouflage level enum - */ -enum class CamoLevel { - NONE, - NODE, - CONTROLLER, - MOON, - PLANET, - INAPPLICABLE -}; - enum class CamoRelayRule { LEAVE, KNOWNHOSTS, @@ -107,146 +87,153 @@ enum class CamoRelayRule { class CamoPattern { /** - * Check if buffer has camouflage applied + * Check if the buffer has camo * * @param buffer Buffer to check - * @return True if buffer has camouflage + * @return True if the buffer has camo */ template - static size_t getCamoIndex(const Buffer &buffer) + static bool hasCamo(const Buffer &buffer) { - size_t result = NO_CAMO; - CamoPatternBytes camo; - if (buffer.size() > BYTES_IN_WORD * 2) + 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++) { - 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"); + 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 level + * Check the host camo class * * @param host Destination address * @param RR RuntimeEnvironment pointer - * @return Camo Level for this host + * @return Camo class for this host */ - static CamoLevel getCamoLevel(const Address host, const RuntimeEnvironment * const RR); + 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 - * @return True if host requires camo + * @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 word to the end + * This increases buffer length by adding ID to the end * * @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_COUNT; - CamoPatternBytes camo = camoValues[camoIndex]; + 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; + } - // Increase buffer size first to avoid overflow + //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 + 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]; + 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]; } - // Copy the first word on the second word's place - for (size_t i = 0; i < BYTES_IN_WORD; i++) { - data[i + BYTES_IN_WORD] = data[i]; - } - - // Apply XOR to the rest of the buffer - for (size_t i = BYTES_IN_WORD; i < buffer.size(); i++) { - data[i] ^= camo[i % BYTES_IN_WORD]; - } - - CT("PACKET CONTENTS AFTER APPLYING CAMO:"); - buffer.dump(); + CT("DONE"); } } /** * Remove camouflage from buffer * - * This decreases buffer length by removing stored ID word from the end + * 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; - 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()); - CamoPatternBytes 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; + 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++) { - data[i + BYTES_IN_WORD] = data[i + storedWordIndex]; + 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]; } - buffer.setSize(buffer.size() - BYTES_IN_WORD); result = true; - CT("PACKET CONTENTS AFTER STRIPPING CAMO:"); - buffer.dump(); } + CT("CAMO STRIPPED: %d", result); return result; } - static void init(CamoLevel level, uint32_t word, KnownHostsMap hosts, CamoRelayRule rule); - + /** + * 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 CamoLevel camoLevel; - static uint32_t camoWord; + static CamoAutoApplyBits camoAutoApply; static CamoRelayRule relayRule; - static CamoPatternArray camoValues; - static CamoIndexMap camoIndices; static std::mutex camoMutex; static KnownHostsMap knownHosts; + static std::mt19937 rng; }; } // namespace ZeroTier diff --git a/node/Packet.hpp b/node/Packet.hpp index 0f5bd3ab..8ced7f54 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -232,7 +232,7 @@ /** * Packet buffer size (can be changed) */ -#define ZT_PROTO_ADDITIONAL_CAMO_LENGTH 4 +#define ZT_PROTO_ADDITIONAL_CAMO_LENGTH 8 #define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_DEFAULT_PHYSMTU) diff --git a/service/OneService.cpp b/service/OneService.cpp index efe8f119..def206c1 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -1456,14 +1456,7 @@ public: } // Camo settings defaults - CamoLevel camoLevel = CamoLevel::NONE; - std::string camoWordStr = ZT_CAMO_DEFAULT_WORDSTR; - uint32_t camoWord = 0; - for (size_t i = 0; i < BYTES_IN_WORD; i++) - { - camoWord <<= 8; - camoWord |= camoWordStr[i]; - } + CamoAutoApplyBits camoAutoApply; CamoRelayRule relayRule = CamoRelayRule::LEAVE; KnownHostsMap knownHosts; @@ -1506,58 +1499,55 @@ public: 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") + auto stringToClass = [](std::string &str) -> CamoClass { + CamoClass result = CamoClass::NEVER; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { - try - { - camoWord = std::stoul(temp.substr(2), nullptr, 16); - } - catch (...) - { - } + return std::tolower(c); + }); + if (str == "always") + { + CT("CAMO CLASS: ALWAYS"); + result = CamoClass::ALWAYS; + } + else if (str == "node") + { + CT("CAMO CLASS: NODE"); + result = CamoClass::NODE; + } + else if (str == "controller") + { + CT("CAMO CLASS: CONTROLLER"); + result = CamoClass::CONTROLLER; + } + else if (str == "moon") + { + CT("CAMO CLASS: MOON"); + result = CamoClass::MOON; } else { - for (size_t i = 0; i < BYTES_IN_WORD; i++) + CT("CAMO CLASS: NEVER"); + } + return result; + }; + + json &autoApply = camo["autoApply"]; + if (autoApply.is_array()) + { + CT("AUTO APPLY SETTING FOUND"); + for (const auto &entry: autoApply) + { + std::string entryStr = OSUtils::jsonString(entry, ""); + + CT("AUTO APPLY SETTING: %s", entryStr.c_str()); + CamoClass camoClass = stringToClass(entryStr); + if (camoClass < CamoClass::AUTO_APPLY_COUNT) { - char c = ' '; - if (i < temp.size()) - { - c = temp[i]; - } - camoWord |= c; - camoWord <<= 8; + camoAutoApply[camoClass] = true; } } } - 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) @@ -1582,11 +1572,15 @@ public: if (knownHostsSection.is_object()) { CT("KNOWN HOSTS SECTION FOUND"); - auto processLevelSection = [&knownHosts](const json §ion, CamoLevel level) + + for (auto it = knownHostsSection.begin(); it != knownHostsSection.end(); it++) { - if (section.is_array()) + std::string classKey = it.key(); + + CT("FOUND CAMO CLASS: %s", classKey.c_str()); + if (it.value().is_array()) { - for (const auto &addrStr: section) + for (const auto &addrStr: it.value()) { std::string addr = OSUtils::jsonString(addrStr, ""); if (!addr.empty()) @@ -1595,46 +1589,11 @@ public: if (host) { CT("VALID HOST FOUND: %s", addr.c_str()); - knownHosts[host] = level; + knownHosts[host] = stringToClass(classKey); } } } } - }; - - 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); - } } } } @@ -1643,7 +1602,7 @@ public: CT("CAMO CONFIG SECTION NOT FOUND"); } } - CamoPattern::init(camoLevel, camoWord, knownHosts, relayRule); + CamoPattern::init(camoAutoApply, knownHosts, relayRule); // Set trusted paths if there are any if (!ppc.empty()) {