diff --git a/.gitignore b/.gitignore index ba8b4afc..5ef9f5d9 100755 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,15 @@ __pycache__ snap/.snapcraft tcp-proxy/tcp-proxy rustybits/target + +#direnv +.envrc + +#nix stuff +result +result-man + +#zerotier devs seem to forgot those +rustybits/zerotier-cli +rustybits/zerotier-idtool +.aider* diff --git a/Dockerfile.release b/Dockerfile.release index 87ef6534..2a289cea 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,6 +1,6 @@ # vim: ft=dockerfile -FROM debian:bullseye +FROM debian:bookworm ARG VERSION @@ -9,9 +9,9 @@ RUN mkdir -p /usr/share/zerotier && \ curl -o /usr/share/zerotier/tmp.asc "https://download.zerotier.com/contact%40zerotier.com.gpg" && \ gpg --no-default-keyring --keyring /usr/share/zerotier/zerotier.gpg --import /usr/share/zerotier/tmp.asc && \ rm -f /usr/share/zerotier/tmp.asc && \ - echo "deb [signed-by=/usr/share/zerotier/zerotier.gpg] http://download.zerotier.com/debian/bullseye bullseye main" > /etc/apt/sources.list.d/zerotier.list + echo "deb [signed-by=/usr/share/zerotier/zerotier.gpg] http://download.zerotier.com/debian/bookworm bookworm main" > /etc/apt/sources.list.d/zerotier.list -RUN apt-get update -qq && apt-get install zerotier-one=${VERSION} curl iproute2 net-tools iputils-ping openssl libssl1.1 -y +RUN apt-get update -qq && apt-get install zerotier-one=${VERSION} curl iproute2 net-tools iputils-ping openssl libssl3 -y RUN rm -rf /var/lib/zerotier-one COPY entrypoint.sh.release /entrypoint.sh diff --git a/README.camo.md b/README.camo.md new file mode 100644 index 00000000..fca52a50 --- /dev/null +++ b/README.camo.md @@ -0,0 +1,148 @@ +# Камуфлирование пакетов ZeroTier + +## Введение + +Этот патч добавляет функцию сокрытия открытых полей и данных протокола ZeroTier, условно называемую "камуфлированием". Цель операции - не криптографическая защита, а создание видимости полностью случайного содержимого пакетов. + +## Принцип работы + +### Алгоритм камуфлирования + +1. 64-битный ID в заголовке пакета дублируется в конец пакета +2. Данные от дубликата до конца пакета обрабатываются XOR со случайным 32-битным числом ("камуфляжем") +3. Раскамуфлирование выполняется в обратном порядке после распознавания камуфляжа + +### Распознавание камуфляжа + +Для распознавания того, камуфлирован пакет или нет, используется равенство `a ^ b = x ^ y`, где `a` и `b` - старший и младший 32-битные сегменты ID, `x` и `y` - старший и младший 32-битные сегменты последних 64 бит пакета. Если равенство соблюдается, то это означает, что пакет камуфлирован, значение камуфляжа определяется формулой `c = a ^ x`. + +### Правила обработки пакетов + +Пакеты обрабатываются на основе класса адресата и заданной настройки включения камуфлирования для каждого из классов. Класс адресата присваивается ему либо автоматически, либо заданием в настройках. + +- _Входящие_: всегда автоматически раскамуфлируются для обеспечения работы внутренних механизмов +- _Исходящие_: камуфлируются, если камуфлирование включено для класса адресата +- _Пересылаемые_: обрабатываются согласно выбранному правилу пересылки + +### Классы камуфлирования + +Классы камуфлирования имеют следующие названия: + +- `NODE` +- `CONTROLLER` +- `MOON` +- `ALWAYS` +- `NEVER` + +Особое значение имеют два класса: + +- `ALWAYS` - не присваивается адресату автоматически. Ручное присвоение этого класса адресату включает безусловное камуфлирование пакетов для него. +- `NEVER` - в качестве класса адресата присваивается автоматически глобальным корневым серверам (планетам). Ручное присвоение этого класса адресату безусловно выключает камуфлирование пакетов для него. + +Прочие классы назначаются автоматически согласно роли адресата. + +### Список известных адресатов + +При определении класса адресата проводится поиск в списке известных адресатов. Если адресат там обнаружен, то используется класс, который присвоен в списке. Если нет - то производится автоматическое определение класса адресата и результат заносится в список. Настройки позволяют задать изначальное содержимое списка для принудительного назначения адресатам требуемого класса. + +### Правила пересылки + +- `LEAVE` - сохраняет исходное состояние камуфлирования пересылаемого пакета +- `KNOWNHOSTS` - применяет те же правила, что и для исходящих пакетов +- `STRIP` - принудительно снимает камуфлирование со всех пересылаемых пакетов +- `APPLY` - принудительно камуфлирует все пересылаемые пакеты + +## Настройки + +Настройки задаются в файле `local.conf` в объекте `"camo"` внутри `"settings"`: + +```json +{ + "settings": { + "camo": { + "autoApply": [ // Включает камуфлирование для перечисленных классов + "node", + "controller", + "moon" + ], + "relayRule": "leave", // Правило пересылки + "knownHosts": { // Предустановленные классы адресата для узлов + "never" : [ + "aaaaaaaaaa" + ] + } + } + } +} +``` + +### Параметры + +- `"autoApply"`: массив регистронезависимых строк с названиями классов адресата, для которых включается автоматическое камуфлирование. По умолчанию: `[]`. Допустимо задание строк `node`, `controller` и `moon`, прочие значения игнорируются. +- `"relayRule"`: регистронезависимая строка, содержащая название правила пересылки. По умолчанию: `"leave"`. Любые значения, не соответствующие названию существующих правил, интерпретируются как `"leave"`. +- `"knownHosts"`: объект для предустановки классов адресата для конкретных узлов. Названия полей объекта регистронезависимы и соответствуют классам адресатов. Названия полей, не соответствующие существующим классам, интерпретируются как `"never"`. Значение каждого поля - массив строк с адресами. При включении одного и того же адреса в несколько разных классов, адресату будет назначен класс, идущий последним. + +## Примеры конфигураций + +### "Белый список" + +```json +{ + "settings": { + "camo": { + "autoApply": [], + "knownHosts": { + "always": ["aaaaaaaaaa"] + } + } + } +} +``` + +Эта настройка отключает автоматическое камуфлирование. При этом, адресату `aaaaaaaaaa` принудительно назначается класс `ALWAYS`, и поэтому пакеты для него (и только для него) будут камуфлироваться. + +### "Чёрный список" + +```json +{ + "settings": { + "camo": { + "autoApply": [ "node" ], + "knownHosts": { + "never": ["aaaaaaaaaa"] + } + } + } +} +``` + +Здесь включается автоматическое камуфлирование для обычных узлов. Узлу `aaaaaaaaaa` назначен класс `NEVER`, и вне зависимости от настроек автоматического камуфлирования, пакеты для него камуфлироваться не будут. + +### Принудительное включение для специфических узлов + +```json +{ + "settings": { + "camo": { + "level": [ "node" ], + "knownHosts": { + "node": ["aaaaaaaaaa"] + } + } + } +} +``` + +В этом примере корневому серверу `aaaaaaaaaa`, поддерживающему работу с камуфлированными пакетами, принудительно присваивается класс `NODE`, чтобы пакеты для него камуфлировались наряду с обычными узлами. + +### Комментарий + +По большому счёту, в `"knownHosts"` имеет смысл использовать только классы `ALWAYS` и `NEVER`, т.к. в этом случае поведение не будет зависеть от настоек автоматического камуфлирования. + +## Перевод сети на камуфлирование + +Для перевода всей сети на камуфлирование сначала необходимо установить службу ZeroTier с данным патчем без дополнительных настроек. При этом, служба будет работать так же, как штатная. После этого, на каждом узле производится включение камуфлирования с помощью настроек. Такой процесс позволяет перевести сеть на камуфлирование пакетов без потери работоспособности всей сети. + +## Особенности сборки + +Задание макроса `CAMO_TRACE` включает выдачу отладочных сообщений на стандартный вывод службы. Для этого нужно при сборке передать команде make параметр `DEFS="-DCAMO_TRACE"` diff --git a/README.md b/README.md index e881ce81..7d3ab29a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ZeroTier - Global Area Networking ====== +**NOTE: This project is a fork that implements packet camouflaging functionality. For usage instructions, see [README.camo.md](README.camo.md)** + *This document is written for a software developer audience. For information on using ZeroTier, see the: [Website](https://www.zerotier.com), [Documentation Site](https://docs.zerotier.com), and [Discussion Forum](https://discuss.zerotier.com).* ZeroTier is a smart programmable Ethernet switch for planet Earth. It allows all networked devices, VMs, containers, and applications to communicate as if they all reside in the same physical data center or cloud region. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 72ca1f3b..e6582207 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,6 +1,13 @@ ZeroTier Release Notes ====== +# 2024-10-23 -- Version 1.14.2 + + * Fix for missing entitlement on macOS Sequoia. + * Fix for a problem correctly parsing local.conf to enable low bandwidth mode. + * Increment versions of some dependent libraries. + * Other fixes. + # 2024-09-12 -- Version 1.14.1 * Multithreaded packet I/O support! Currently this is just for Linux and must diff --git a/debian/changelog b/debian/changelog index 660562f9..fdc85f86 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +zerotier-one (1.14.2) unstable; urgency=medium + + * See RELEASE-NOTES.md for release notes. + + -- Adam Ierymenko Wed, 23 Oct 2024 01:00:00 -0700 + zerotier-one (1.14.1) unstable; urgency=medium * See RELEASE-NOTES.md for release notes. diff --git a/ext/installfiles/mac/ZeroTier One.pkgproj b/ext/installfiles/mac/ZeroTier One.pkgproj index cf6af8e7..70cedf9f 100755 --- a/ext/installfiles/mac/ZeroTier One.pkgproj +++ b/ext/installfiles/mac/ZeroTier One.pkgproj @@ -701,7 +701,7 @@ USE_HFS+_COMPRESSION VERSION - 1.14.1 + 1.14.2 TYPE 0 diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip index 0870201e..43b3223f 100644 --- a/ext/installfiles/windows/ZeroTier One.aip +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -24,10 +24,10 @@ - + - + @@ -62,7 +62,7 @@ - + @@ -498,7 +498,7 @@ - + diff --git a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h index 39acff78..10e12ff7 100644 --- a/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h +++ b/ext/prometheus-cpp-lite-1.0/core/include/prometheus/client_metric.h @@ -8,6 +8,7 @@ namespace prometheus { // , + struct ClientMetric { // Label diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..80d833b4 --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1744157173, + "narHash": "sha256-bWSjxDwq7iVePrhmA7tY2dyMWHuNJo8knkO4y+q4ZkY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6a39c6e495eefabc935d8ddf66aa45d85b85fa3f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "nixpkgs-zerotier-base": [ + "nixpkgs" + ] + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..dd025679 --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "Custom ZeroTierOne build with private patches"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + # Pin to specific nixpkgs version for zerotierone base package + nixpkgs-zerotier-base = { + url = "github:NixOS/nixpkgs/d9d87c51960050e89c79e4025082ed965e770d68"; + follows = "nixpkgs"; + }; + + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, nixpkgs-zerotier-base, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + packages = { + zerotierone-tspu = pkgs.callPackage ./package.nix { + zerotierone = (import nixpkgs-zerotier-base { inherit system; }).zerotierone; + }; + + default = self.packages.${system}.zerotierone-tspu; + }; + + devShells.default = import ./shell.nix { + inherit pkgs; + }; + } + ); +} diff --git a/make-mac.mk b/make-mac.mk index 7af200ad..e10f9622 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -194,6 +194,9 @@ controller-run: _buildx FORCE central-controller-docker: _buildx FORCE docker buildx build --platform linux/arm64,linux/amd64 --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . --push @echo Image: registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} + +docker-release: _buildx + docker buildx build --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64,linux/mips64le,linux/ppc64le,linux/s390x -t zerotier/zerotier:${RELEASE_DOCKER_TAG} -t zerotier/zerotier:latest --build-arg VERSION=${RELEASE_VERSION} -f Dockerfile.release . --push clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* rustybits/target/ diff --git a/node/Buffer.hpp b/node/Buffer.hpp index 8dbc51ef..559e9468 100644 --- a/node/Buffer.hpp +++ b/node/Buffer.hpp @@ -445,6 +445,11 @@ public: */ inline unsigned int capacity() const { return C; } + /** + * Dump buffer contents to stdout in hex format + */ + void dump() const; + template inline bool operator==(const Buffer &b) const { diff --git a/node/CamoPattern.cpp b/node/CamoPattern.cpp new file mode 100644 index 00000000..4038ed56 --- /dev/null +++ b/node/CamoPattern.cpp @@ -0,0 +1,148 @@ +/* + * 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. + */ + +#include "CamoPattern.hpp" +#include +#include "RuntimeEnvironment.hpp" +#include "Topology.hpp" + +namespace ZeroTier { + +// Initialize static members of CamoPattern +bool CamoPattern::isInitialized = false; +CamoRelayRule CamoPattern::relayRule; +std::mutex CamoPattern::camoMutex; +KnownHostsMap CamoPattern::knownHosts; +CamoAutoApplyBits CamoPattern::camoAutoApply; +std::mt19937 CamoPattern::rng(std::random_device{}()); + +CamoClass CamoPattern::getCamoClass(const Address host, const RuntimeEnvironment * const RR) +{ + CamoClass result = CamoClass::NEVER; + if (isInitialized) + { + char buf[64]; + host.toString(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, CLASS: %u", result); + } + else + { + // Host not found in known hosts, run mutex-protected section + std::lock_guard lock(camoMutex); + + // Check again in case another thread added it while we were waiting + it = knownHosts.find(host); + if (it != knownHosts.end()) { + result = it->second; + CT("HOST IS KNOWN AFTER LOCK WAITING, CLASS: %u", result); + } + else + { + CT("HOST IS NOT KNOWN"); + switch(RR->topology->role(host)) + { + 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())) + { + CT("HOST IS A CONTROLLER"); + result = CamoClass::CONTROLLER; + break; + } + } + if (result == CamoClass::NODE) + { + CT("HOST IS A SIMPLE NODE"); + } + break; + } + knownHosts[host] = result; + } + } + } + return result; +} + +// 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) + { + case CamoRelayRule::LEAVE: + CT("IS RELAY, APPLYING LEAVE RULE"); + result = hadCamo; + break; + case CamoRelayRule::KNOWNHOSTS: + CT("IS RELAY, APPLYING KNOWNHOSTS RULE"); + result = isRequiredByClass(host, RR); + break; + case CamoRelayRule::STRIP: + CT("IS RELAY, APPLYING STRIP RULE"); + result = false; + break; + case CamoRelayRule::APPLY: + CT("IS RELAY, APPLYING APPLY RULE"); + result = true; + break; + } + } + else if (isInitialized) + { + result = isRequiredByClass(host, RR); + CT("IS CAMO REQUIRED: %b", result); + } + return result; +} + +// 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) + { + camoAutoApply = autoApply; + knownHosts = hosts; + relayRule = rule; + 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 new file mode 100644 index 00000000..638670e4 --- /dev/null +++ b/node/CamoPattern.hpp @@ -0,0 +1,266 @@ +/* + * 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 diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp index 2537c0fb..d1f71165 100644 --- a/node/IncomingPacket.cpp +++ b/node/IncomingPacket.cpp @@ -19,6 +19,7 @@ #include "../include/ZeroTierOne.h" #include "Constants.hpp" +#include "Identity.hpp" #include "RuntimeEnvironment.hpp" #include "IncomingPacket.hpp" #include "Topology.hpp" @@ -39,6 +40,7 @@ #include "Bond.hpp" #include "Metrics.hpp" #include "PacketMultiplexer.hpp" +#include "CamoPattern.hpp" namespace ZeroTier { @@ -61,6 +63,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f return true; } } else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) { + CT("INCOMING CLEARTEXT HELLO"); // Only HELLO is allowed in the clear, but will still have a MAC return _doHELLO(RR,tPtr,false); } @@ -88,66 +91,87 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f //case Packet::VERB_NOP: default: // ignore unknown verbs, but if they pass auth check they are "received" Metrics::pkt_nop_in++; + CT("UNKNOWN VERB"); peer->received(tPtr,_path,hops(),packetId(),payloadLength(),v,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); break; case Packet::VERB_HELLO: + CT("INCOMING HELLO"); r = _doHELLO(RR, tPtr, true); break; case Packet::VERB_ACK: + CT("INCOMING VERB_ACK"); r = _doACK(RR, tPtr, peer); break; case Packet::VERB_QOS_MEASUREMENT: + CT("INCOMING QOS_MEASUREMENT"); r = _doQOS_MEASUREMENT(RR, tPtr, peer); break; case Packet::VERB_ERROR: + CT("INCOMING ERROR"); r = _doERROR(RR, tPtr, peer); break; case Packet::VERB_OK: + CT("INCOMING OK"); r = _doOK(RR, tPtr, peer); break; case Packet::VERB_WHOIS: + CT("INCOMING WHOIS"); r = _doWHOIS(RR, tPtr, peer); break; case Packet::VERB_RENDEZVOUS: + CT("INCOMING RENDEZVOUS"); r = _doRENDEZVOUS(RR, tPtr, peer); break; case Packet::VERB_FRAME: + CT("INCOMING FRAME"); r = _doFRAME(RR, tPtr, peer, flowId); break; case Packet::VERB_EXT_FRAME: + CT("INCOMING EXT_FRAME"); r = _doEXT_FRAME(RR, tPtr, peer, flowId); break; case Packet::VERB_ECHO: + CT("INCOMING ECHO"); r = _doECHO(RR, tPtr, peer); break; case Packet::VERB_MULTICAST_LIKE: + CT("INCOMING MULTICAST_LIKE"); r = _doMULTICAST_LIKE(RR, tPtr, peer); break; case Packet::VERB_NETWORK_CREDENTIALS: + CT("INCOMING NETWORK_CREDENTIALS"); r = _doNETWORK_CREDENTIALS(RR, tPtr, peer); break; case Packet::VERB_NETWORK_CONFIG_REQUEST: + CT("INCOMING CONFIG_REQUEST"); r = _doNETWORK_CONFIG_REQUEST(RR, tPtr, peer); break; case Packet::VERB_NETWORK_CONFIG: + CT("INCOMING NETWORK_CONFIG"); r = _doNETWORK_CONFIG(RR, tPtr, peer); break; case Packet::VERB_MULTICAST_GATHER: + CT("INCOMING MULTICAST_GATHER"); r = _doMULTICAST_GATHER(RR, tPtr, peer); break; case Packet::VERB_MULTICAST_FRAME: + CT("INCOMING MULTICAST_FRAME"); r = _doMULTICAST_FRAME(RR, tPtr, peer); break; case Packet::VERB_PUSH_DIRECT_PATHS: + CT("INCOMING PUSH_DIRECT_PATHS"); r = _doPUSH_DIRECT_PATHS(RR, tPtr, peer); break; case Packet::VERB_USER_MESSAGE: + CT("INCOMING USER_MESSAGE"); r = _doUSER_MESSAGE(RR, tPtr, peer); break; case Packet::VERB_REMOTE_TRACE: + CT("INCOMING REMOTE_TRACE"); r = _doREMOTE_TRACE(RR, tPtr, peer); break; case Packet::VERB_PATH_NEGOTIATION_REQUEST: + CT("INCOMING PATH_NEGOTIATION_REQUEST"); r = _doPATH_NEGOTIATION_REQUEST(RR, tPtr, peer); break; } @@ -157,6 +181,7 @@ bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr,int32_t f } return false; } else { + CT("REQUESTING WHOIS"); RR->sw->requestWhois(tPtr,RR->node->now(),sourceAddress); return false; } @@ -374,7 +399,9 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool const int64_t timestamp = at(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP); Identity id; unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY); - + char buf[64]; + id.address().toString(buf); + CT("HELLO FROM %s", buf); if (protoVersion < ZT_PROTO_VERSION_MIN) { RR->t->incomingPacketDroppedHELLO(tPtr,_path,pid,fromAddress,"protocol version too old"); return true; @@ -407,6 +434,12 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(key,true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_identity_collision_out++; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } else { RR->t->incomingPacketMessageAuthenticationFailure(tPtr,_path,pid,fromAddress,hops(),"invalid MAC"); @@ -565,6 +598,12 @@ bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),now); peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version @@ -635,10 +674,14 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP if (RR->topology->isUpstream(peer->identity())) { const Identity id(*this,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY); RR->sw->doAnythingWaitingForPeer(tPtr,RR->topology->addPeer(tPtr,SharedPtr(new Peer(RR,RR->identity,id)))); + char buf[64]; + id.address().toString(buf); + CT("GOT OK REPLY TO WHOIS %s", buf); } break; case Packet::VERB_NETWORK_CONFIG_REQUEST: { + CT("GOT OK REPLY TO NETWORK_CONFIG_REQUEST"); networkId = at(ZT_PROTO_VERB_OK_IDX_PAYLOAD); const SharedPtr network(RR->node->network(networkId)); if (network) { @@ -647,6 +690,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP } break; case Packet::VERB_MULTICAST_GATHER: { + CT("GOT OK REPLY TO MULTICAST_GATHER"); networkId = at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID); const SharedPtr network(RR->node->network(networkId)); if (network) { @@ -657,6 +701,7 @@ bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedP } break; case Packet::VERB_MULTICAST_FRAME: { + CT("GOT OK REPLY TO MULTICAST_FRAME"); const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS]; networkId = at(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID); const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI)); @@ -711,6 +756,9 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar while ((ptr + ZT_ADDRESS_LENGTH) <= size()) { const Address addr(field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH; + char buf[64]; + addr.toString(buf); + CT("GOT WHOIS REQUEST ON %s", buf); const Identity id(RR->topology->getIdentity(tPtr,addr)); if (id) { @@ -725,6 +773,9 @@ bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const Shar if (count > 0) { Metrics::pkt_ok_out++; outp.armor(peer->key(),true,peer->aesKeysIfSupported()); + char buf[64]; + outp.destination().toString(buf); + CT("WHOIS REPLY, packetId: %lx, address: %s", outp.packetId(), buf); _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -955,6 +1006,13 @@ bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -984,6 +1042,13 @@ bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const Share outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); peer->received(tPtr,_path,hops(),pid,payloadLength(),Packet::VERB_ECHO,0,Packet::VERB_NOP,false,0,ZT_QOS_NO_FLOW); @@ -1180,6 +1245,13 @@ bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_unsupported_op_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } @@ -1204,6 +1276,13 @@ bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,c outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1247,6 +1326,13 @@ bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),now); } } @@ -1320,6 +1406,9 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, const uint8_t *const frameData = (const uint8_t *)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,frameLen); if ((flags & 0x08)&&(network->config().isMulticastReplicator(RR->identity.address()))) { + char buf[64]; + peer->address().toString(buf); + CT("UNPROCESSED MULTICAST, address: %s", buf); RR->mc->send(tPtr,RR->node->now(),network,peer->address(),to,from,etherType,frameData,frameLen); } @@ -1351,6 +1440,13 @@ bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr, outp.armor(peer->key(),true,peer->aesKeysIfSupported()); peer->recordOutgoingPacket(_path,outp.packetId(),outp.payloadLength(),outp.verb(),ZT_QOS_NO_FLOW,now); Metrics::pkt_ok_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } } @@ -1493,6 +1589,13 @@ void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_error_out++; Metrics::pkt_error_need_membership_cert_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); } diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp index aa5936ff..cf80e339 100644 --- a/node/IncomingPacket.hpp +++ b/node/IncomingPacket.hpp @@ -74,6 +74,22 @@ public: { } + /** + * Create a new packet-in-decode from an existing packet + * + * @param packet Source packet + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + IncomingPacket(const Packet &packet, const SharedPtr &path, int64_t now) : + Packet(packet), + _receiveTime(now), + _path(path), + _authenticated(false) + { + } + /** * Init packet-in-decode in place * @@ -91,6 +107,22 @@ public: _authenticated = false; } + /** + * Init packet-in-decode in place from an existing packet + * + * @param packet Source packet + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + inline void init(const Packet &packet, const SharedPtr &path, int64_t now) + { + copyFrom(packet.data(), packet.size()); + _receiveTime = now; + _path = path; + _authenticated = false; + } + /** * Attempt to decode this packet * diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp index 3a771972..11fb3efd 100644 --- a/node/Multicaster.cpp +++ b/node/Multicaster.cpp @@ -63,11 +63,11 @@ void Multicaster::remove(uint64_t nwid,const MulticastGroup &mg,const Address &m } } -unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const +unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const { unsigned char *p; unsigned int added = 0,i,k,rptr,totalKnown = 0; - uint64_t a,picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2]; + uint64_t a,picked[((ZT_PROTO_MAX_PACKET_LENGTH + ZT_PROTO_ADDITIONAL_CAMO_LENGTH) / 5) + 2]; if (!limit) { return 0; @@ -98,7 +98,7 @@ unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const // Members are returned in random order so that repeated gather queries // will return different subsets of a large multicast group. k = 0; - while ((added < limit)&&(k < s->members.size())&&((appendTo.size() + ZT_ADDRESS_LENGTH) <= ZT_PROTO_MAX_PACKET_LENGTH)) { + while ((added < limit)&&(k < s->members.size())&&((appendTo.size() + ZT_ADDRESS_LENGTH) <= (ZT_PROTO_MAX_PACKET_LENGTH + ZT_PROTO_ADDITIONAL_CAMO_LENGTH))) { rptr = (unsigned int)RR->node->prng(); restart_member_scan: @@ -201,6 +201,13 @@ void Multicaster::send( } outp.armor(bestMulticastReplicator->key(),true,bestMulticastReplicator->aesKeysIfSupported()); Metrics::pkt_multicast_frame_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED MULTICAST, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } bestMulticastReplicatorPath->send(RR,tPtr,outp.data(),outp.size(),now); return; } diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp index 38117144..34802594 100644 --- a/node/Multicaster.hpp +++ b/node/Multicaster.hpp @@ -27,6 +27,7 @@ #include "MAC.hpp" #include "MulticastGroup.hpp" #include "OutboundMulticast.hpp" +#include "Packet.hpp" #include "Utils.hpp" #include "Mutex.hpp" #include "SharedPtr.hpp" @@ -103,7 +104,7 @@ public: * @return Number of addresses appended * @throws std::out_of_range Buffer overflow writing to packet */ - unsigned int gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const; + unsigned int gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const; /** * Get subscribers to a multicast group diff --git a/node/Network.cpp b/node/Network.cpp index 1643487f..c66409b3 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -961,7 +961,7 @@ void Network::multicastUnsubscribe(const MulticastGroup &mg) } } -uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) +uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) { if (_destroyed) { return 0; diff --git a/node/Network.hpp b/node/Network.hpp index cc85b7d1..74df940a 100644 --- a/node/Network.hpp +++ b/node/Network.hpp @@ -28,6 +28,7 @@ #include "Hashtable.hpp" #include "Address.hpp" #include "Mutex.hpp" +#include "Packet.hpp" #include "SharedPtr.hpp" #include "AtomicCounter.hpp" #include "MulticastGroup.hpp" @@ -191,7 +192,7 @@ public: * @param ptr Index of chunk and related fields in packet * @return Update ID if update was fully assembled and accepted or 0 otherwise */ - uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); + uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); /** * Set network configuration diff --git a/node/Packet.hpp b/node/Packet.hpp index f607d1f5..8ced7f54 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -232,6 +232,8 @@ /** * Packet buffer size (can be changed) */ +#define ZT_PROTO_ADDITIONAL_CAMO_LENGTH 8 + #define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_DEFAULT_PHYSMTU) /** @@ -388,7 +390,7 @@ namespace ZeroTier { * For unencrypted packets, MAC is computed on plaintext. Only HELLO is ever * sent in the clear, as it's the "here is my public key" message. */ -class Packet : public Buffer +class Packet : public Buffer { public: /** @@ -417,22 +419,22 @@ public: * receipt to authenticate and decrypt; there is no per-fragment MAC. (But if * fragments are corrupt, the MAC will fail for the whole assembled packet.) */ - class Fragment : public Buffer + class Fragment : public Buffer { public: Fragment() : - Buffer() + Buffer() { } template Fragment(const Buffer &b) : - Buffer(b) + Buffer(b) { } Fragment(const void *data,unsigned int len) : - Buffer(data,len) + Buffer(data,len) { } @@ -1091,12 +1093,12 @@ public: template Packet(const Buffer &b) : - Buffer(b) + Buffer(b) { } Packet(const void *data,unsigned int len) : - Buffer(data,len) + Buffer(data,len) { } @@ -1108,7 +1110,7 @@ public: * the header. Payload should be appended; initial size is header size. */ Packet() : - Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops @@ -1124,7 +1126,7 @@ public: * @param dest Destination ZeroTier address for new packet */ Packet(const Packet &prototype,const Address &dest) : - Buffer(prototype) + Buffer(prototype) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); @@ -1138,7 +1140,7 @@ public: * @param v Verb */ Packet(const Address &dest,const Address &source,const Verb v) : - Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); setDestination(dest); diff --git a/node/Peer.cpp b/node/Peer.cpp index f77b4e6f..43f3b890 100644 --- a/node/Peer.cpp +++ b/node/Peer.cpp @@ -23,6 +23,7 @@ #include "RingBuffer.hpp" #include "Utils.hpp" #include "Metrics.hpp" +#include "Buffer.hpp" namespace ZeroTier { @@ -247,6 +248,14 @@ void Peer::received( outp->compress(); outp->armor(_key,true,aesKeysIfSupported()); Metrics::pkt_push_direct_paths_out++; + char buf[64]; + outp->destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp->packetId(), buf); + + if (CamoPattern::isCamoRequired(outp->destination(), RR)) + { + CamoPattern::applyCamo(*outp); + } path->send(RR,tPtr,outp->data(),outp->size(),now); } delete outp; @@ -393,6 +402,13 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } outp.armor(_key,true,aesKeysIfSupported()); Metrics::pkt_rendezvous_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } _paths[mine].p->send(RR,tPtr,outp.data(),outp.size(),now); } else { Packet outp(other->_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); @@ -408,6 +424,13 @@ void Peer::introduce(void *const tPtr,const int64_t now,const SharedPtr &o } outp.armor(other->_key,true,other->aesKeysIfSupported()); Metrics::pkt_rendezvous_out++; + char buf[64]; + outp.destination().toString(buf); + CT("PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); + if (CamoPattern::isCamoRequired(outp.destination(), RR)) + { + CamoPattern::applyCamo(outp); + } other->_paths[theirs].p->send(RR,tPtr,outp.data(),outp.size(),now); } ++alt; @@ -456,6 +479,9 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size()); } else { RR->node->expectReplyTo(outp.packetId()); + char buf[64]; + outp.destination().toString(buf); + CT("SWITCH PROCESSED, packetId: %lx, address: %s", outp.packetId(), buf); RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC } } diff --git a/node/Peer.hpp b/node/Peer.hpp index 777a1e96..aeca75b1 100644 --- a/node/Peer.hpp +++ b/node/Peer.hpp @@ -35,6 +35,7 @@ #include "Bond.hpp" #include "AES.hpp" #include "Metrics.hpp" +#include "CamoPattern.hpp" #define ZT_PEER_MAX_SERIALIZED_STATE_SIZE (sizeof(Peer) + 32 + (sizeof(Path) * 2)) @@ -143,6 +144,7 @@ public: { SharedPtr bp(getAppropriatePath(now,force)); if (bp) { + CT("UNPROCESSED"); return bp->send(RR,tPtr,data,len,now); } return false; diff --git a/node/Switch.cpp b/node/Switch.cpp index 7664f7a4..d32c3de0 100644 --- a/node/Switch.cpp +++ b/node/Switch.cpp @@ -22,6 +22,7 @@ #include "../include/ZeroTierOne.h" #include "Constants.hpp" +#include "Hashtable.hpp" #include "RuntimeEnvironment.hpp" #include "Switch.hpp" #include "Node.hpp" @@ -33,6 +34,8 @@ #include "Trace.hpp" #include "Metrics.hpp" +#include "CamoPattern.hpp" + namespace ZeroTier { Switch::Switch(const RuntimeEnvironment *renv) : @@ -78,18 +81,32 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre { int32_t flowId = ZT_QOS_NO_FLOW; try { + char buf[64]; + fromAddr.toIpString(buf); + CT("INCOMING PACKET localSocket: %ld, IP: %s:%u, isDefaultRoute: %u", localSocket, buf, fromAddr.port(), fromAddr.isDefaultRoute()); + + Packet remotePacket(data, len); + CT("PACKET CONTENTS:"); + remotePacket.dump(); + bool hadCamo = CamoPattern::stripCamo(remotePacket); + if (hadCamo) + { + CT("PACKET HAS CAMO. CONTENTS WITHOUT CAMO:"); + remotePacket.dump(); + } + const int64_t now = RR->node->now(); const SharedPtr path(RR->topology->getPath(localSocket,fromAddr)); path->received(now); - if (len == 13) { + if (remotePacket.size() == 13) { /* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast * announcements on the LAN to solve the 'same network problem.' We * no longer send these, but we'll listen for them for a while to * locate peers with versions <1.0.4. */ - const Address beaconAddr(reinterpret_cast(data) + 8,5); + const Address beaconAddr(remotePacket.destination()); if (beaconAddr == RR->identity.address()) { return; } @@ -103,15 +120,19 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); outp.armor(peer->key(),true,peer->aesKeysIfSupported()); Metrics::pkt_nop_out++; + if (CamoPattern::isCamoRequired(beaconAddr, RR, hadCamo, true)) + { + CamoPattern::applyCamo(outp); + } path->send(RR,tPtr,outp.data(),outp.size(),now); } } } else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) { // SECURITY: min length check is important since we do some C-style stuff below! - if (reinterpret_cast(data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) { + if (reinterpret_cast(remotePacket[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR]) == ZT_PACKET_FRAGMENT_INDICATOR) { // Handle fragment ---------------------------------------------------- - Packet::Fragment fragment(data,len); + Packet::Fragment fragment(remotePacket); const Address destination(fragment.destination()); if (destination != RR->identity.address()) { @@ -128,6 +149,10 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,false))) { // Don't know peer or no direct path -- so relay via someone upstream relayTo = RR->topology->getUpstreamPeer(); + if (CamoPattern::isCamoRequired(destination, RR, hadCamo, true)) + { + CamoPattern::applyCamo(fragment); + } if (relayTo) { relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,true); } @@ -184,8 +209,8 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) { // min length check is important! // Handle packet head ------------------------------------------------- - const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); - const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); + const Address destination(remotePacket.destination()); + const Address source(remotePacket.source()); if (source == RR->identity.address()) { return; @@ -196,12 +221,14 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre return; } - Packet packet(data,len); - - if (packet.hops() < ZT_RELAY_MAX_HOPS) { - packet.incrementHops(); + if (remotePacket.hops() < ZT_RELAY_MAX_HOPS) { + remotePacket.incrementHops(); SharedPtr relayTo = RR->topology->getPeer(tPtr,destination); - if ((relayTo)&&(relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,false))) { + if (CamoPattern::isCamoRequired(destination, RR, hadCamo, true)) + { + CamoPattern::applyCamo(remotePacket); + } + if ((relayTo)&&(relayTo->sendDirect(tPtr,remotePacket.data(),remotePacket.size(),now,false))) { if ((source != RR->identity.address())&&(_shouldUnite(now,source,destination))) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); if (sourcePeer) { @@ -211,7 +238,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } else { relayTo = RR->topology->getUpstreamPeer(); if ((relayTo)&&(relayTo->address() != source)) { - if (relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,true)) { + if (relayTo->sendDirect(tPtr,remotePacket.data(),remotePacket.size(),now,true)) { const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); if (sourcePeer) { relayTo->introduce(tPtr,now,sourcePeer); @@ -220,19 +247,10 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } } } - } else if ((reinterpret_cast(data)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0) { + } else if ((reinterpret_cast(remotePacket[ZT_PACKET_IDX_FLAGS]) & ZT_PROTO_FLAG_FRAGMENTED) != 0) { // Packet is the head of a fragmented packet series - const uint64_t packetId = ( - (((uint64_t)reinterpret_cast(data)[0]) << 56) | - (((uint64_t)reinterpret_cast(data)[1]) << 48) | - (((uint64_t)reinterpret_cast(data)[2]) << 40) | - (((uint64_t)reinterpret_cast(data)[3]) << 32) | - (((uint64_t)reinterpret_cast(data)[4]) << 24) | - (((uint64_t)reinterpret_cast(data)[5]) << 16) | - (((uint64_t)reinterpret_cast(data)[6]) << 8) | - ((uint64_t)reinterpret_cast(data)[7]) - ); + const uint64_t packetId = remotePacket.packetId(); RXQueueEntry *const rq = _findRXQueueEntry(packetId); Mutex::Lock rql(rq->lock); @@ -242,7 +260,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre rq->flowId = flowId; rq->timestamp = now; rq->packetId = packetId; - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); rq->totalFragments = 0; rq->haveFragments = 1; rq->complete = false; @@ -252,7 +270,7 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre if ((rq->totalFragments > 1)&&(Utils::countBits(rq->haveFragments |= 1) == rq->totalFragments)) { // We have all fragments -- assemble and process full Packet - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); for(unsigned int f=1;ftotalFragments;++f) { rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); } @@ -264,12 +282,12 @@ void Switch::onRemotePacket(void *tPtr,const int64_t localSocket,const InetAddre } } else { // Still waiting on more fragments, but keep the head - rq->frag0.init(data,len,path,now); + rq->frag0.init(remotePacket,path,now); } } // else this is a duplicate head, ignore } else { // Packet is unfragmented, so just process it - IncomingPacket packet(data,len,path,now); + IncomingPacket packet(remotePacket,path,now); if (!packet.tryDecode(RR,tPtr,flowId)) { RXQueueEntry *const rq = _nextRXQueueEntry(); Mutex::Lock rql(rq->lock); @@ -1127,6 +1145,217 @@ bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt,int32_t flowId) return false; } +#define NEWOUT +#ifdef NEWOUT +void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId) +{ + unsigned int mtu = ZT_DEFAULT_PHYSMTU; + uint64_t trustedPathId = 0; + RR->topology->getOutboundPathInfo(viaPath->address(),mtu,trustedPathId); + + char buf[64]; + + Address destination = packet.destination(); + destination.toString(buf); + + Identity destinationIdentity; + + RR->topology->getIdentity(&destinationIdentity, destination); + CT("OUTGOING PACKET ZT ADDRESS: %s, isUpstream: %b, isProhibitedEndpoint: %b", buf, RR->topology->isUpstream(destinationIdentity), RR->topology->isProhibitedEndpoint(destination, InetAddress())); + + + ZT_PeerRole role = RR->topology->role(destination); + + switch(role) + { + case ZT_PEER_ROLE_PLANET: + strcpy(buf, "PLANET"); + break; + case ZT_PEER_ROLE_MOON: + strcpy(buf, "MOON"); + break; + case ZT_PEER_ROLE_LEAF: + strcpy(buf, "LEAF"); + break; + default: + strcpy(buf, "UNKNOWN"); + break; + } + + CT("DESTINATION ROLE: %s", buf); + + switch(packet.verb()) + { + case Packet::VERB_NOP: + strcpy(buf, "NOP"); + break; + case Packet::VERB_HELLO: + strcpy(buf, "HELLO"); + break; + case Packet::VERB_ERROR: + strcpy(buf, "ERROR"); + break; + case Packet::VERB_OK: + strcpy(buf, "OK"); + break; + case Packet::VERB_WHOIS: + strcpy(buf, "WHOIS"); + break; + case Packet::VERB_RENDEZVOUS: + strcpy(buf, "RENDEZVOUS"); + break; + case Packet::VERB_FRAME: + strcpy(buf, "FRAME"); + break; + case Packet::VERB_EXT_FRAME: + strcpy(buf, "EXT_FRAME"); + break; + case Packet::VERB_ECHO: + strcpy(buf, "ECHO"); + break; + case Packet::VERB_MULTICAST_LIKE: + strcpy(buf, "MULTICAST_LIKE"); + break; + case Packet::VERB_NETWORK_CREDENTIALS: + strcpy(buf, "NETWORK_CREDENTIALS"); + break; + case Packet::VERB_NETWORK_CONFIG_REQUEST: + strcpy(buf, "NETWORK_CONFIG_REQUEST"); + break; + case Packet::VERB_NETWORK_CONFIG: + strcpy(buf, "NETWORK_CONFIG"); + break; + case Packet::VERB_MULTICAST_GATHER: + strcpy(buf, "MULTICAST_GATHER"); + break; + case Packet::VERB_MULTICAST_FRAME: + strcpy(buf, "MULTICAST_FRAME"); + break; + case Packet::VERB_PUSH_DIRECT_PATHS: + strcpy(buf, "PUSH_DIRECT_PATHS"); + break; + case Packet::VERB_ACK: + strcpy(buf, "ACK"); + break; + case Packet::VERB_QOS_MEASUREMENT: + strcpy(buf, "QOS_MEASUREMENT"); + break; + case Packet::VERB_USER_MESSAGE: + strcpy(buf, "USER_MESSAGE"); + break; + case Packet::VERB_REMOTE_TRACE: + strcpy(buf, "REMOTE_TRACE"); + break; + case Packet::VERB_PATH_NEGOTIATION_REQUEST: + strcpy(buf, "PATH_NEGOTIATION_REQUEST"); + break; + default: + strcpy(buf, "UNKNOWN"); + break; + }; + + CT("PACKET VERB: %s", buf); + + bool isController = false; + { + 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 (destination == ((*v)->controller())) + { + isController = true; + break; + } + } + } + + CT("isController: %b", isController); + + if (userSpecifiedMtu > 0) { + mtu = userSpecifiedMtu; + } + + bool isCamoRequired = CamoPattern::isCamoRequired(destination, RR); + + unsigned int camoSize = isCamoRequired ? ZT_PROTO_ADDITIONAL_CAMO_LENGTH : 0; + unsigned int packetSizeCamo = packet.size() + camoSize; + unsigned int chunkSize = std::min(packetSizeCamo, mtu); + bool isFragmented = chunkSize < (packetSizeCamo); + packet.setFragmented(isFragmented); + + CT("PACKET CONTENTS:"); + packet.dump(); + + const uint8_t * payload = reinterpret_cast(packet.payload()); + size_t payloadLength = packet.payloadLength(); + + Address addr; + switch(packet.verb()) + { + case Packet::VERB_WHOIS: + addr.setTo(packet.field(ZT_PACKET_IDX_PAYLOAD, ZT_ADDRESS_LENGTH), ZT_ADDRESS_LENGTH); + addr.toString((buf)); + CT("ASKING WHOIS %s", buf); + break; + + default: + break; + } + + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + if (!packet.isEncrypted()) { + packet.armor(peer->key(),encrypt,peer->aesKeysIfSupported()); + } + RR->node->expectReplyTo(packet.packetId()); + } + + CT("PACKET CONTENTS AFTER ENCRYPTION:"); + packet.dump(); + + peer->recordOutgoingPacket(viaPath, packet.packetId(), packet.payloadLength(), packet.verb(), flowId, now); + + if (!isFragmented) + { + CT("UNFRAGMENTED BRANCH"); + if (isCamoRequired) + { + CamoPattern::applyCamo(packet); + } + viaPath->send(RR,tPtr,packet.data(),chunkSize,now); + } + else + { + CT("FRAGMENTED BRANCH"); + // Too big for one packet, fragment it + unsigned int minFragmentSize = ZT_PROTO_MIN_FRAGMENT_LENGTH + camoSize; + unsigned int fragStart = 0; + unsigned int remaining = packet.size(); + unsigned int fragSize = mtu - minFragmentSize; + unsigned int fragsRemaining = (remaining / (fragSize)); + if ((fragsRemaining * fragSize) < remaining) { + ++fragsRemaining; + } + const unsigned int totalFragments = fragsRemaining; + + for(unsigned int fno=0;fnosend(RR,tPtr,frag.data(),frag.size(),now); + fragStart += chunkSize; + remaining -= chunkSize; + } + } +} +#else void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr viaPath,uint16_t userSpecifiedMtu, int64_t now,Packet &packet,bool encrypt,int32_t flowId) { unsigned int mtu = ZT_DEFAULT_PHYSMTU; @@ -1171,6 +1400,7 @@ void Switch::_sendViaSpecificPath(void *tPtr,SharedPtr peer,SharedPtr CamoClass { + CamoClass result = CamoClass::NEVER; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) + { + 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 + { + 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) + { + camoAutoApply[camoClass] = true; + } + } + } + + std::string relayRuleStr = OSUtils::jsonString(camo["relayRule"], "leave"); + std::transform(relayRuleStr.begin(), relayRuleStr.end(), relayRuleStr.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + if (relayRuleStr == "knownhosts") + { + relayRule = CamoRelayRule::KNOWNHOSTS; + } + else if (relayRuleStr == "strip") + { + relayRule = CamoRelayRule::STRIP; + } + else if (relayRuleStr == "apply") + { + relayRule = CamoRelayRule::APPLY; + } + CT("CAMO RELAY RULE SETTING: %s", relayRuleStr.c_str()); + + json &knownHostsSection = camo["knownHosts"]; + if (knownHostsSection.is_object()) + { + CT("KNOWN HOSTS SECTION FOUND"); + + for (auto it = knownHostsSection.begin(); it != knownHostsSection.end(); it++) + { + std::string classKey = it.key(); + + CT("FOUND CAMO CLASS: %s", classKey.c_str()); + if (it.value().is_array()) + { + for (const auto &addrStr: it.value()) + { + std::string addr = OSUtils::jsonString(addrStr, ""); + if (!addr.empty()) + { + Address host (Utils::hexStrToU64(addr.c_str())); + if (host) + { + CT("VALID HOST FOUND: %s", addr.c_str()); + knownHosts[host] = stringToClass(classKey); + } + } + } + } + } + } + } + else + { + CT("CAMO CONFIG SECTION NOT FOUND"); + } } + CamoPattern::init(camoAutoApply, knownHosts, relayRule); // Set trusted paths if there are any if (!ppc.empty()) { @@ -2599,6 +2714,7 @@ public: fprintf(stderr,"WARNING: using manually-specified secondary and/or tertiary ports. This can cause NAT issues." ZT_EOL_S); } _portMappingEnabled = OSUtils::jsonBool(settings["portMappingEnabled"],true); + _node->setLowBandwidthMode(OSUtils::jsonBool(settings["lowBandwidthMode"],false)); #if defined(__LINUX__) || defined(__FreeBSD__) _multicoreEnabled = OSUtils::jsonBool(settings["multicoreEnabled"],false); _concurrency = OSUtils::jsonInt(settings["concurrency"],1); diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..6a608869 --- /dev/null +++ b/shell.nix @@ -0,0 +1,22 @@ +{ pkgs ? import { } }: + +let + zerotieroneCustom = pkgs.callPackage ./package.nix {}; +in +pkgs.mkShell { + packages = with pkgs; [ + bear + cmake + cmake-language-server + clang-tools + pandoc + ] + ++ zerotieroneCustom.buildInputs + ++ zerotieroneCustom.nativeBuildInputs; + + SSH_AUTH_SOCK = builtins.getEnv "SSH_AUTH_SOCK"; + NIXPKGS_ALLOW_UNFREE = "1"; + + shellHook = '' + ''; +} diff --git a/version.h b/version.h index 8fff820a..6027e70e 100644 --- a/version.h +++ b/version.h @@ -27,7 +27,7 @@ /** * Revision */ -#define ZEROTIER_ONE_VERSION_REVISION 1 +#define ZEROTIER_ONE_VERSION_REVISION 2 /** * Build version diff --git a/zerotier-one.spec b/zerotier-one.spec index 414b0b62..655068bf 100644 --- a/zerotier-one.spec +++ b/zerotier-one.spec @@ -1,5 +1,5 @@ Name: zerotier-one -Version: 1.14.1 +Version: 1.14.2 Release: 1%{?dist} Summary: ZeroTier network virtualization service @@ -155,6 +155,9 @@ chmod 0755 $RPM_BUILD_ROOT/etc/init.d/zerotier-one %endif %changelog +* Wed Oct 23 2024 Adam Ierymenko - 1.14.2 +- see https://github.com/zerotier/ZeroTierOne for release notes + * Tue Mar 19 2024 Adam Ierymenko - 1.14.0 - see https://github.com/zerotier/ZeroTierOne for release notes