changed camouflaging scheme

This commit is contained in:
eerieaerial 2025-05-14 22:26:08 +04:00
parent 235addc585
commit 98ccedecac
5 changed files with 235 additions and 319 deletions

View file

@ -6,58 +6,44 @@
## Принцип работы ## Принцип работы
### Узоры камуфлирования
Основу механизма камуфлирования составляют 16 случайных 32-битных узоров, которые:
- Генерируются при инициализации стандартизованным ГСЧ (вихрь Мерсенна MT19937, определённый в стандарте C++11) с фиксированным порождающим словом
- Одинаковы для всех узлов с одинаковым порождающим словом
- Используются для XOR-преобразования содержимого пакета
- Служат признаком распознавания камуфлированных пакетов
Размер списка в 16 узоров выбран из соображений баланса между разнообразием узоров и вероятностью коллизии между признаком камуфлирования и случайно сгенерированным ID некамуфлированного пакета. Размер списка можно изменить путём задания нового значения макроса `PATTERN_COUNT` в файле `CamoPattern.hpp`. Рекомендуется использовать значения, равные степеням 2.
### Алгоритм камуфлирования ### Алгоритм камуфлирования
1. 64-битный ID в заголовке пакета разделяется на два 32-битных сегмента 1. 64-битный ID в заголовке пакета дублируется в конец пакета
2. Младший сегмент перемещается в конец пакета 2. Данные от дубликата до конца пакета обрабатываются XOR со случайным 32-битным числом ("камуфляжем")
3. Старший сегмент дублируется на место младшего 3. Раскамуфлирование выполняется в обратном порядке после распознавания камуфляжа
4. Данные от дубликата до конца пакета обрабатываются XOR со случайно выбранным узором
5. Раскамуфлирование выполняется в обратном порядке после распознавания узора
### Распознавание камуфлированных пакетов ### Распознавание камуфляжа
Для распознавания того, камуфлирован пакет или нет, используется XOR между старшим и младшим сегментами ID. Если результат обнаружен в списке узоров, то это означает, что пакет закамуфлирован этим узором. Для распознавания того, камуфлирован пакет или нет, используется равенство `a ^ b = x ^ y`, где `a` и `b` - старший и младший 32-битные сегменты ID, `x` и `y` - старший и младший 32-битные сегменты последних 64 бит пакета. Если равенство соблюдается, то это означает, что пакет камуфлирован, значение камуфляжа определяется формулой `c = a ^ x`.
### Правила обработки пакетов ### Правила обработки пакетов
Пакеты обрабатываются на основе сравнения уровня камуфлирования адресата и отправителя. Уровень адресата определяется либо автоматически, либо заданием в настройках. Уровень отправителя определяется настройкой и относится только непосредственно к тому узлу, на котором производится обработка пакета в данный момент. Пакеты обрабатываются на основе класса адресата и заданной настройки включения камуфлирования для каждого из классов. Класс адресата присваивается ему либо автоматически, либо заданием в настройках.
- _Входящие_: всегда автоматически раскамуфлируются для обеспечения работы внутренних механизмов - _Входящие_: всегда автоматически раскамуфлируются для обеспечения работы внутренних механизмов
- сходящие_: камуфлируются, если уровень адресата равен уровню отправителя или меньше - сходящие_: камуфлируются, если камуфлирование включено для класса адресата
- ересылаемые_: обрабатываются согласно выбранному правилу пересылки - ересылаемые_: обрабатываются согласно выбранному правилу пересылки
### Уровни камуфлирования ### Классы камуфлирования
Уровни камуфлирования имеют следующие названия от низшего к высшему: Классы камуфлирования имеют следующие названия:
- `NONE`
- `NODE` - `NODE`
- `CONTROLLER` - `CONTROLLER`
- `MOON` - `MOON`
- `PLANET` - `ALWAYS`
- `INAPPLICABLE` - `NEVER`
Особое значение имеют два уровня: Особое значение имеют два класса:
- `NONE` - не назначается автоматически в качестве уровня адресата. Выбор этого уровня в качестве уровня отправителя выключает камуфлирование пакетов при автоматическом выборе уровня адресата. - `ALWAYS` - не присваивается адресату автоматически. Ручное присвоение этого класса адресату включает безусловное камуфлирование пакетов для него.
- `INAPPLICABLE` - в качестве уровня адресата назначается автоматически глобальным корневым серверам. Для выбора в качестве уровня отправителя недоступен. - `NEVER` - в качестве класса адресата присваивается автоматически глобальным корневым серверам (планетам). Ручное присвоение этого класса адресату безусловно выключает камуфлирование пакетов для него.
Прочие уровни назначаются автоматически согласно роли адресата. Прочие классы назначаются автоматически согласно роли адресата.
### Список известных адресатов ### Список известных адресатов
При определении уровня камуфлирования для адресата проводится поиск в списке известных адресатов. Если адресат там обнаружен, то используется уровень, который присвоен в списке. Если нет - то производится автоматическое определение уровня адресата и результат заносится в список. Настройки позволяют задать изначальное содержимое списка для принудительного назначения адресатам требуемого уровня. При определении класса адресата проводится поиск в списке известных адресатов. Если адресат там обнаружен, то используется класс, который присвоен в списке. Если нет - то производится автоматическое определение класса адресата и результат заносится в список. Настройки позволяют задать изначальное содержимое списка для принудительного назначения адресатам требуемого класса.
### Правила пересылки ### Правила пересылки
@ -74,11 +60,16 @@
{ {
"settings": { "settings": {
"camo": { "camo": {
"level": "none", // Уровень отправителя "autoApply": [ // Включает камуфлирование для перечисленных классов
"word": "DLGE", // Порождающее слово для узоров "node",
"controller",
"moon"
],
"relayRule": "leave", // Правило пересылки "relayRule": "leave", // Правило пересылки
"knownHosts": { // Предустановленные уровни адресата для узлов "knownHosts": { // Предустановленные классы адресата для узлов
"none" : [] "never" : [
"aaaaaaaaaa"
]
} }
} }
} }
@ -87,10 +78,9 @@
### Параметры ### Параметры
- `"level"`: регистронезависимая строка с названием уровня отправителя. По умолчанию: `"none"`. Любые значения, не соответствующие названию существующих уровней, интерпретируются как `"none"`. Значение `"inapplicable"` также интерпретируется как `"none"`, т.к. этот уровень недоступен для выбора. - `"autoApply"`: массив регистронезависимых строк с названиями классов адресата, для которых включается автоматическое камуфлирование. По умолчанию: `[]`. Допустимо задание строк `node`, `controller` и `moon`, прочие значения игнорируются.
- `"word"`: строка, содержащая порождающее слово для списка узоров. Если в строке меньше 4 символов, она дополняется до 4 символов последовательностью `"DLGE"`. Строка длиннее 4 символов обрезается. Если строка начинается с `"0x"`, она интерпретируется как 32-битное шестнадцатеричное число. По умолчанию: `"DLGE"`.
- `"relayRule"`: регистронезависимая строка, содержащая название правила пересылки. По умолчанию: `"leave"`. Любые значения, не соответствующие названию существующих правил, интерпретируются как `"leave"`. - `"relayRule"`: регистронезависимая строка, содержащая название правила пересылки. По умолчанию: `"leave"`. Любые значения, не соответствующие названию существующих правил, интерпретируются как `"leave"`.
- `"knownHosts"`: объект для предустановки уровней для конкретных узлов. Названия полей объекта соответствуют уровням камуфлирования. Названия полей, не соответствующие существующим уровням, интерпретируются как `"inapplicable"`. Значение каждого поля - массив строк с адресами. - `"knownHosts"`: объект для предустановки классов адресата для конкретных узлов. Названия полей объекта регистронезависимы и соответствуют классам адресатов. Названия полей, не соответствующие существующим классам, интерпретируются как `"never"`. Значение каждого поля - массив строк с адресами. При включении одного и того же адреса в несколько разных классов, адресату будет назначен класс, идущий последним.
## Примеры конфигураций ## Примеры конфигураций
@ -100,16 +90,16 @@
{ {
"settings": { "settings": {
"camo": { "camo": {
"level": "none", "autoApply": [],
"knownHosts": { "knownHosts": {
"none": ["aaaaaaaaaa"] "always": ["aaaaaaaaaa"]
} }
} }
} }
} }
``` ```
Эта настройка выбирает уровень отправителя `NONE`, который автоматически не назначается адресатам. Это отключает камуфлирование для всех автоматически определяемых адресатов. Адресату `aaaaaaaaaa` принудительно назначается уровень `NONE`, и т.к. этот уровень равен выбранному, пакеты для него будут камуфлироваться. Эта настройка отключает автоматическое камуфлирование. При этом, адресату `aaaaaaaaaa` принудительно назначается класс `ALWAYS`, и поэтому пакеты для него (и только для него) будут камуфлироваться.
### "Чёрный список" ### "Чёрный список"
@ -117,16 +107,16 @@
{ {
"settings": { "settings": {
"camo": { "camo": {
"level": "node", "autoApply": [ "node" ],
"knownHosts": { "knownHosts": {
"inapplicable": ["aaaaaaaaaa"] "never": ["aaaaaaaaaa"]
} }
} }
} }
} }
``` ```
Здесь выбран уровень отправителя `NODE`, что включает камуфлирование для адресатов этого уровня и ниже. Адресату `aaaaaaaaaa` назначен уровень `INAPPLICABLE`, поэтому вне зависимости от выбранного уровня, пакеты для него камуфлироваться не будут. Здесь включается автоматическое камуфлирование для обычных узлов. Узлу `aaaaaaaaaa` назначен класс `NEVER`, и вне зависимости от настроек автоматического камуфлирования, пакеты для него камуфлироваться не будут.
### Принудительное включение для специфических узлов ### Принудительное включение для специфических узлов
@ -134,7 +124,7 @@
{ {
"settings": { "settings": {
"camo": { "camo": {
"level": "node", "level": [ "node" ],
"knownHosts": { "knownHosts": {
"node": ["aaaaaaaaaa"] "node": ["aaaaaaaaaa"]
} }
@ -143,11 +133,11 @@
} }
``` ```
В этом примере корневому серверу `aaaaaaaaaa`, поддерживающему работу с камуфлированными пакетами, принудительно присваивается уровень `NODE`, чтобы пакеты для него камуфлировались наряду с обычными узлами. В этом примере корневому серверу `aaaaaaaaaa`, поддерживающему работу с камуфлированными пакетами, принудительно присваивается класс `NODE`, чтобы пакеты для него камуфлировались наряду с обычными узлами.
### Комментарий ### Комментарий
По большому счёту, в `"knownHosts"` имеет смысл использовать только уровни `NONE` и `INAPPLICABLE`. Первый - принудительно включает камуфлирование для указанных адресатов, второй - выключает. По большому счёту, в `"knownHosts"` имеет смысл использовать только классы `ALWAYS` и `NEVER`, т.к. в этом случае поведение не будет зависеть от настоек автоматического камуфлирования.
## Перевод сети на камуфлирование ## Перевод сети на камуфлирование

View file

@ -9,39 +9,34 @@
* On the date above, in accordance with the Business Source License, use * 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. * of this software will be governed by version 2.0 of the Apache License.
*/ */
/****/
#include "CamoPattern.hpp" #include "CamoPattern.hpp"
#include <ctime> #include <ctime>
#include "RuntimeEnvironment.hpp"
#include "Topology.hpp" #include "Topology.hpp"
namespace ZeroTier { namespace ZeroTier {
// Initialize static members of CamoPattern // Initialize static members of CamoPattern
bool CamoPattern::isInitialized = false; bool CamoPattern::isInitialized = false;
CamoLevel CamoPattern::camoLevel;
uint32_t CamoPattern::camoWord;
CamoRelayRule CamoPattern::relayRule; CamoRelayRule CamoPattern::relayRule;
CamoPatternArray CamoPattern::camoValues;
CamoIndexMap CamoPattern::camoIndices;
std::mutex CamoPattern::camoMutex; std::mutex CamoPattern::camoMutex;
KnownHostsMap CamoPattern::knownHosts; KnownHostsMap CamoPattern::knownHosts;
CamoAutoApplyBits CamoPattern::camoAutoApply;
std::mt19937 CamoPattern::rng(std::random_device{}());
CamoClass CamoPattern::getCamoClass(const Address host, const RuntimeEnvironment * const RR)
// Implementation of getCamoLevel
CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment * const RR)
{ {
CamoLevel result = CamoLevel::INAPPLICABLE; CamoClass result = CamoClass::NEVER;
// First check if we already know this host's camo level
if (isInitialized) if (isInitialized)
{ {
char buf[64]; char buf[64];
host.toString(buf); host.toString(buf);
CT("GETTING CAMO LEVEL FOR HOST %s", buf); CT("GETTING CAMO CLASS FOR HOST %s", buf);
auto it = knownHosts.find(host); auto it = knownHosts.find(host);
if (it != knownHosts.end()) { if (it != knownHosts.end()) {
result = it->second; result = it->second;
CT("HOST IS KNOWN, LEVEL: %u", result); CT("HOST IS KNOWN, CLASS: %u", result);
} }
else else
{ {
@ -52,25 +47,22 @@ CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment
it = knownHosts.find(host); it = knownHosts.find(host);
if (it != knownHosts.end()) { if (it != knownHosts.end()) {
result = it->second; result = it->second;
CT("HOST IS KNOWN AFTER LOCK WAITING"); CT("HOST IS KNOWN AFTER LOCK WAITING, CLASS: %u", result);
} }
else else
{ {
CT("HOST IS NOT KNOWN"); 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: case ZT_PEER_ROLE_PLANET:
CT("HOST IS A PLANET"); CT("HOST IS A PLANET");
result = CamoLevel::PLANET;
break; break;
case ZT_PEER_ROLE_MOON: case ZT_PEER_ROLE_MOON:
CT("HOST IS A MOON"); CT("HOST IS A MOON");
result = CamoLevel::MOON; result = CamoClass::MOON;
break; break;
default: default:
result = CamoLevel::NODE; result = CamoClass::NODE;
Mutex::Lock _l(RR->node->_networks_m); Mutex::Lock _l(RR->node->_networks_m);
Hashtable<uint64_t, SharedPtr<Network>>::Iterator i(RR->node->_networks); Hashtable<uint64_t, SharedPtr<Network>>::Iterator i(RR->node->_networks);
uint64_t * k = (uint64_t *)0; uint64_t * k = (uint64_t *)0;
@ -80,21 +72,16 @@ CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment
if (host == ((*v)->controller())) if (host == ((*v)->controller()))
{ {
CT("HOST IS A CONTROLLER"); CT("HOST IS A CONTROLLER");
result = CamoLevel::CONTROLLER; result = CamoClass::CONTROLLER;
break; break;
} }
} }
if (result == CamoLevel::NODE) if (result == CamoClass::NODE)
{ {
CT("HOST IS A SIMPLE NODE"); CT("HOST IS A SIMPLE NODE");
} }
break; break;
} }
}
else
{
CT("HOST IS A ZT GLOBAL ROOT");
}
knownHosts[host] = result; knownHosts[host] = result;
} }
} }
@ -102,10 +89,18 @@ CamoLevel CamoPattern::getCamoLevel(const Address host, const RuntimeEnvironment
return result; 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 CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo, const bool isRelay)
{ {
bool result = false; 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) if (isInitialized && isRelay)
{ {
switch(relayRule) switch(relayRule)
@ -116,7 +111,7 @@ bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment *
break; break;
case CamoRelayRule::KNOWNHOSTS: case CamoRelayRule::KNOWNHOSTS:
CT("IS RELAY, APPLYING KNOWNHOSTS RULE"); CT("IS RELAY, APPLYING KNOWNHOSTS RULE");
result = getCamoLevel(host, RR) <= camoLevel; result = isRequiredByClass(host, RR);
break; break;
case CamoRelayRule::STRIP: case CamoRelayRule::STRIP:
CT("IS RELAY, APPLYING STRIP RULE"); CT("IS RELAY, APPLYING STRIP RULE");
@ -130,39 +125,24 @@ bool CamoPattern::isCamoRequired(const Address host, const RuntimeEnvironment *
} }
else if (isInitialized) else if (isInitialized)
{ {
result = getCamoLevel(host, RR) <= camoLevel; result = isRequiredByClass(host, RR);
CT("IS CAMO REQUIRED: %b", result); CT("IS CAMO REQUIRED: %b", result);
} }
return result; return result;
} }
// Implementation of init - initializes the camouflage system with the specified settings
// Implementation of init void CamoPattern::init(CamoAutoApplyBits autoApply, KnownHostsMap hosts, CamoRelayRule rule)
void CamoPattern::init(CamoLevel level, uint32_t word, KnownHostsMap hosts, CamoRelayRule rule)
{ {
std::lock_guard<std::mutex> lock(camoMutex); std::lock_guard<std::mutex> lock(camoMutex);
if (!isInitialized) if (!isInitialized)
{ {
camoLevel = level; camoAutoApply = autoApply;
camoWord = word;
knownHosts = hosts; knownHosts = hosts;
relayRule = rule; relayRule = rule;
CT("CAMO LEVEL: %u, WORD: %08x, KNOWN HOSTS COUNT: %lu, RELAY RULE: %u", level, word, hosts.size(), rule); CT("KNOWN HOSTS COUNT: %lu, RELAY RULE: %u", 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; isInitialized = true;
} }
} }
} // namespace ZeroTier } // namespace ZeroTier

View file

@ -9,7 +9,6 @@
* On the date above, in accordance with the Business Source License, use * 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. * of this software will be governed by version 2.0 of the Apache License.
*/ */
/****/
#ifndef ZT_N_CAMOPATTERN_HPP #ifndef ZT_N_CAMOPATTERN_HPP
#define ZT_N_CAMOPATTERN_HPP #define ZT_N_CAMOPATTERN_HPP
@ -19,20 +18,19 @@
#include <unordered_map> #include <unordered_map>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <bitset>
#include "Address.hpp" #include "Address.hpp"
#include "Buffer.hpp" #include "Buffer.hpp"
#include "RuntimeEnvironment.hpp" #include "RuntimeEnvironment.hpp"
#define BYTES_IN_WORD (sizeof(uint32_t) / sizeof(uint8_t)) #define BYTES_IN_WORD (sizeof(uint32_t) / sizeof(uint8_t))
#define PATTERN_COUNT 16 #define ID_SIZE (BYTES_IN_WORD * 2)
#define NO_CAMO PATTERN_COUNT
#define ZT_CAMO_DEFAULT_WORDSTR "DLGE"
/** /**
* Camo functions debug trace macro * Camo functions debug trace macro
*/ * Enables debug output when CAMO_TRACE is defined
*/
#ifdef CAMO_TRACE #ifdef CAMO_TRACE
#define CT(...) do { \ #define CT(...) do { \
printf("%s:%d %s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__); \ printf("%s:%d %s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__); \
@ -43,35 +41,29 @@
#define CT(...) ((void)0) #define CT(...) ((void)0)
#endif #endif
/**
* Type shorthands
*/
namespace ZeroTier { namespace ZeroTier {
enum class CamoLevel; /**
* 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::array<uint8_t, BYTES_IN_WORD> CamoPatternBytes;
typedef std::array<CamoPatternBytes, PATTERN_COUNT> CamoPatternArray; typedef std::unordered_map<Address, CamoClass> KnownHostsMap;
typedef std::unordered_map<CamoPatternBytes, size_t> CamoIndexMap;
typedef std::unordered_map<Address, CamoLevel> KnownHostsMap;
} }
/** /**
* Hash functions for the respective unordered_maps * Hash function for the KnownHostsMap
*/ */
namespace std { namespace std {
template<>
struct hash<ZeroTier::CamoPatternBytes> {
size_t operator()(const ZeroTier::CamoPatternBytes& bytes) const {
uint32_t word = 0;
for (const auto& byte : bytes) {
word <<= 8;
word |= byte;
}
return std::hash<uint32_t>{}(word);
}
};
template<> template<>
struct hash<ZeroTier::Address> { struct hash<ZeroTier::Address> {
size_t operator()(const ZeroTier::Address& address) const { size_t operator()(const ZeroTier::Address& address) const {
@ -82,18 +74,6 @@ namespace std {
namespace ZeroTier { namespace ZeroTier {
/**
* Camouflage level enum
*/
enum class CamoLevel {
NONE,
NODE,
CONTROLLER,
MOON,
PLANET,
INAPPLICABLE
};
enum class CamoRelayRule { enum class CamoRelayRule {
LEAVE, LEAVE,
KNOWNHOSTS, KNOWNHOSTS,
@ -107,146 +87,153 @@ enum class CamoRelayRule {
class CamoPattern class CamoPattern
{ {
/** /**
* Check if buffer has camouflage applied * Check if the buffer has camo
* *
* @param buffer Buffer to check * @param buffer Buffer to check
* @return True if buffer has camouflage * @return True if the buffer has camo
*/ */
template<unsigned int C> template<unsigned int C>
static size_t getCamoIndex(const Buffer<C> &buffer) static bool hasCamo(const Buffer<C> &buffer)
{ {
size_t result = NO_CAMO; bool result = false;
CamoPatternBytes camo; if (buffer.size() > (ID_SIZE * 2))
if (buffer.size() > BYTES_IN_WORD * 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++) for (size_t i = 0; i < BYTES_IN_WORD; i++)
{ {
camo[i] = buffer[i] ^ buffer[i + BYTES_IN_WORD]; if ((buffer[a + i] ^ buffer[b + i]) != (buffer[x + i] ^ buffer [y + i]))
}
auto it = camoIndices.find(camo);
if (it != camoIndices.end())
{ {
result = it->second; result = false;
CT("CAMO DETECTED, INDEX: %u", result); break;
}
else
{
CT("CAMO NOT DETECTED");
} }
} }
}
CT("PACKET HAS CAMO: %b", result);
return result; return result;
} }
/** /**
* Check the host camo level * Check the host camo class
* *
* @param host Destination address * @param host Destination address
* @param RR RuntimeEnvironment pointer * @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: public:
/** /**
* Determine if camouflage is required for a specific host
* *
* @param host Destination address * @param host Destination address
* @param RR RuntimeEnvironment pointer * @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); static bool isCamoRequired(const Address host, const RuntimeEnvironment * const RR, const bool hadCamo = false, const bool isRelay = false);
/** /**
* Apply camouflage to buffer * 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 * @param buffer Buffer to apply camouflage to
*/ */
template<unsigned int C> template<unsigned int C>
static void applyCamo(Buffer<C> &buffer) static void applyCamo(Buffer<C> &buffer)
{ {
size_t camoIndex = getCamoIndex(buffer); CT("APPLYING CAMO");
if (isInitialized && (camoIndex == NO_CAMO)) { // Only apply if not already applied if (isInitialized && !hasCamo(buffer))
CT("PACKET CONTENTS BEFORE APPLYING CAMO:"); {
buffer.dump(); // load random number into an array
camoIndex = std::minstd_rand(std::time(nullptr))() % PATTERN_COUNT; uint32_t camo = rng();
CamoPatternBytes camo = camoValues[camoIndex]; 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<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(); size_t originalSize = buffer.size();
buffer.setSize(originalSize + BYTES_IN_WORD); buffer.setSize(originalSize + ID_SIZE);
uint8_t * const data = reinterpret_cast<uint8_t *>(buffer.unsafeData()); //copy the id
for (size_t i = 0; i < ID_SIZE; i++)
// Copy the second word to the end {
for (size_t i = 0; i < BYTES_IN_WORD; i++) { data[i + originalSize] = data[i] ^ camoBytes[i % BYTES_IN_WORD];
data[i + originalSize] = data[i + BYTES_IN_WORD];
} }
// Copy the first word on the second word's place CT("DONE");
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();
} }
} }
/** /**
* Remove camouflage from buffer * 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 * @param buffer Buffer to remove camouflage from
* @return True if buffer had camouflage and it was stripped
*/ */
template<unsigned int C> template<unsigned int C>
static bool stripCamo(Buffer<C> &buffer) static bool stripCamo(Buffer<C> &buffer)
{ {
bool result = false; bool result = false;
size_t camoIndex = NO_CAMO; if (isInitialized && hasCamo(buffer)) {
if (isInitialized) //retrieve the camo bytes
{ uint8_t * a = &buffer[0];
camoIndex = getCamoIndex(buffer); uint8_t * x = &buffer[buffer.size() - ID_SIZE];
} CamoPatternBytes camoBytes;
if (camoIndex != NO_CAMO) {
CT("PACKET CONTENTS BEFORE STRIPPING CAMO:");
buffer.dump();
uint8_t * const data = reinterpret_cast<uint8_t *>(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;
for (size_t i = 0; i < BYTES_IN_WORD; i++) 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<uint8_t * const>(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; result = true;
CT("PACKET CONTENTS AFTER STRIPPING CAMO:");
buffer.dump();
} }
CT("CAMO STRIPPED: %d", result);
return 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: private:
static bool isInitialized; static bool isInitialized;
static CamoLevel camoLevel; static CamoAutoApplyBits camoAutoApply;
static uint32_t camoWord;
static CamoRelayRule relayRule; static CamoRelayRule relayRule;
static CamoPatternArray camoValues;
static CamoIndexMap camoIndices;
static std::mutex camoMutex; static std::mutex camoMutex;
static KnownHostsMap knownHosts; static KnownHostsMap knownHosts;
static std::mt19937 rng;
}; };
} // namespace ZeroTier } // namespace ZeroTier

View file

@ -232,7 +232,7 @@
/** /**
* Packet buffer size (can be changed) * 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) #define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_DEFAULT_PHYSMTU)

View file

@ -1456,14 +1456,7 @@ public:
} }
// Camo settings defaults // Camo settings defaults
CamoLevel camoLevel = CamoLevel::NONE; CamoAutoApplyBits camoAutoApply;
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];
}
CamoRelayRule relayRule = CamoRelayRule::LEAVE; CamoRelayRule relayRule = CamoRelayRule::LEAVE;
KnownHostsMap knownHosts; KnownHostsMap knownHosts;
@ -1506,58 +1499,55 @@ public:
if (camo.is_object()) if (camo.is_object())
{ {
CT("CAMO CONFIG SECTION FOUND"); CT("CAMO CONFIG SECTION FOUND");
std::string camoLevelStr = OSUtils::jsonString(camo["level"], "none"); auto stringToClass = [](std::string &str) -> CamoClass {
std::transform(camoLevelStr.begin(), camoLevelStr.end(), camoLevelStr.begin(), [](unsigned char c) CamoClass result = CamoClass::NEVER;
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c)
{ {
return std::tolower(c); return std::tolower(c);
}); });
CT("CAMO LEVEL SETTING: %s", camoLevelStr.c_str()); if (str == "always")
if (camoLevelStr == "node")
{ {
camoLevel = CamoLevel::NODE; CT("CAMO CLASS: ALWAYS");
result = CamoClass::ALWAYS;
} }
else if (camoLevelStr == "controller") else if (str == "node")
{ {
camoLevel = CamoLevel::CONTROLLER; CT("CAMO CLASS: NODE");
result = CamoClass::NODE;
} }
else if (camoLevelStr == "moon") else if (str == "controller")
{ {
camoLevel = CamoLevel::MOON; CT("CAMO CLASS: CONTROLLER");
result = CamoClass::CONTROLLER;
} }
else if (camoLevelStr == "planet") else if (str == "moon")
{ {
camoLevel = CamoLevel::PLANET; CT("CAMO CLASS: MOON");
} result = CamoClass::MOON;
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 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())
{ {
char c = ' '; CT("AUTO APPLY SETTING FOUND");
if (i < temp.size()) for (const auto &entry: autoApply)
{ {
c = temp[i]; std::string entryStr = OSUtils::jsonString(entry, "");
}
camoWord |= c; CT("AUTO APPLY SETTING: %s", entryStr.c_str());
camoWord <<= 8; CamoClass camoClass = stringToClass(entryStr);
if (camoClass < CamoClass::AUTO_APPLY_COUNT)
{
camoAutoApply[camoClass] = true;
} }
} }
} }
CT("CAMO WORD SETTING: %x", camoWord);
std::string relayRuleStr = OSUtils::jsonString(camo["relayRule"], "leave"); std::string relayRuleStr = OSUtils::jsonString(camo["relayRule"], "leave");
std::transform(relayRuleStr.begin(), relayRuleStr.end(), relayRuleStr.begin(), [](unsigned char c) std::transform(relayRuleStr.begin(), relayRuleStr.end(), relayRuleStr.begin(), [](unsigned char c)
@ -1582,11 +1572,15 @@ public:
if (knownHostsSection.is_object()) if (knownHostsSection.is_object())
{ {
CT("KNOWN HOSTS SECTION FOUND"); CT("KNOWN HOSTS SECTION FOUND");
auto processLevelSection = [&knownHosts](const json &section, 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, ""); std::string addr = OSUtils::jsonString(addrStr, "");
if (!addr.empty()) if (!addr.empty())
@ -1595,46 +1589,11 @@ public:
if (host) if (host)
{ {
CT("VALID HOST FOUND: %s", addr.c_str()); 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"); CT("CAMO CONFIG SECTION NOT FOUND");
} }
} }
CamoPattern::init(camoLevel, camoWord, knownHosts, relayRule); CamoPattern::init(camoAutoApply, knownHosts, relayRule);
// Set trusted paths if there are any // Set trusted paths if there are any
if (!ppc.empty()) { if (!ppc.empty()) {