12 KiB
Repository Guidelines
Project Overview
BackOne — fork of ZeroTier 1.14.1: peer-to-peer SDN daemon. Builds virtual Ethernet (TUN/TAP), tunnels L2 frames over encrypted UDP mesh, issues membership certs from an embedded controller. One fat binary backone doubles as CLI (backone-cli) and identity tool (backone-idtool) via argv[0] dispatch (one.cpp:2140-2142). Local JSON API on 127.0.0.1:9993.
Architecture & Data Flow
Four layers, top → bottom:
- Entry —
one.cpp:main()→cli()/idtool()/ daemon →OneService::newInstance()+run(). - Service —
service/OneService.cpp(OneServiceImpl, line 776): owns sockets (Phy<>), httplib control plane, TAP devices,Node,EmbeddedNetworkController, main loop. - Core —
node/: OS-independent switch. Reached only through the C API (ZT_Node_*ininclude/ZeroTierOne.h, implemented at bottom ofnode/Node.cpp). - OS glue —
osdep/:Phy(select()-based reactor, the only event loop),EthernetTap(per-OS TUN/TAP),OSUtils,ManagedRoute,Thread,BlockingQueue.
Core pattern = inversion of control. OneServiceImpl fills struct ZT_Node_Callbacks (service/OneService.cpp:1045-1054) with Snode* static functions; node/ never touches OS code, it calls back. Shared context is RuntimeEnvironment passed as const RuntimeEnvironment *RR to every node/ function (node/RuntimeEnvironment.hpp).
Flows:
- Wire → app:
Phy::poll→phyOnDatagram(service/OneService.cpp:2940) →Node::processWirePacket(node/Node.cpp:206) →Switch::onRemotePacket→ verb dispatchswitch(node/IncomingPacket.cpp:94-151) → peer/network/crypto. Heavy work offloaded toPacketMultiplexerworker threads. - App → wire: TAP handler →
tapFrameHandler(service/OneService.cpp:3776) →Node::processVirtualNetworkFrame→Switch::onLocalEthernet→Peer/Path→_phy.udpSend. - Controller:
IncomingPacket::_doNETWORK_CONFIG_REQUEST→EmbeddedNetworkController::requestposts_RQEntry*toBlockingQueue→hardware_concurrency()workers →DBMirrorSet/DB→ signed cert back viaNetworkController::Sender. - Main loop (
OneServiceImpl::run,service/OneService.cpp:~1150-1310): single-threaded, time-sliced; refresh binds →processBackgroundTaskswhen due →_phy.poll(delay).Phy::whack()(self-pipe) is the only cross-thread-safe Phy call.
Key Directories
| Path | Purpose |
|---|---|
one.cpp |
Daemon/CLI/idtool entry; privilege drop, daemonize, signals |
node/ |
Overlay engine: Switch, Topology, Peer, IncomingPacket, Identity, Bond, Metrics — OS-independent by rule |
service/ |
OneService (whole runtime), SoftwareUpdater |
controller/ |
EmbeddedNetworkController + DB backends (FileDB default, LFDB, PostgreSQL, Redis) |
osdep/ |
All OS-dependent code: Phy, EthernetTap*, Binder, ManagedRoute, netlink/DNS helpers |
include/ |
Public C API (ZeroTierOne.h, 57KB) — changes ripple to Java/libzt/SDK |
ext/ |
Vendored: nlohmann/json, cpp-httplib, prometheus-cpp-lite, libpqxx, redis++, miniupnpc, ASM crypto |
rustybits/ |
Cargo workspace: zeroidc (SSO FFI), smeeclient (PostgreSQL workflow client) |
java/ |
JNI wrapper (java/jni/…Node.cpp, ant build) |
tcp-proxy/ |
Standalone TCP fallback relay; own Makefile (C++11) |
rule-compiler/ |
JS network-rules compiler (node cli.js <rules>), npm |
pkg/, debian/, windows/ |
Packaging (snap/QNAP/Synology/ASUSTOR/WD, RPM backone.spec, MSVC sln) |
Development Commands
make # = make one → ./backone + backone-cli/backone-idtool symlinks
make -j$(nproc) one
make core # libbackonecore.a
make selftest && ./backone-selftest
make debug # ZT_DEBUG=1 (adds -g, forces ZT_TRACE=1)
make ZT_SANITIZE=1 one # ASan
make central-controller # ZT_CONTROLLER=1 (needs ext/ libpqxx + hiredis + redis++)
make install DESTDIR=/tmp/root
make debian | make redhat # debuild / rpmbuild
make manpages # cd doc && ./build.sh (needs ronn or node marked-man)
cd rustybits && cargo build # zeroidc/smeeclient (also auto via `make zeroidc`)
cd java && ant build_java | build_android | build_jar
cd tcp-proxy && make
Makefile is a uname dispatcher → make-linux.mk / make-mac.mk / make-bsd.mk / make-netbsd.mk. Windows: MSBuild windows/ZeroTierOne.sln /property:Configuration=Release. Top-level CMakeLists.txt is a 323B stub — never cmake . as product build.
Env knobs (make vars): ZT_DEBUG, ZT_TRACE, ZT_SANITIZE, ZT_STATIC, ZT_OFFICIAL, ZT_CONTROLLER, ZT_SSO_SUPPORTED (0 on Linux default → no cargo needed), ZT_VAULT_SUPPORT, ZT_IA32.
Build hygiene — no header dependency tracking. The makefiles emit no -MMD .d files, so .o outputs never rebuild when a header changes. A tree that ships prebuilt objects (root one.o, node/*.o, libbackonecore.a — present in this checkout) plus any .hpp edit produces ABI-mismatched objects; symptom is an instant SIGSEGV at Topology::Topology during ./backone startup with a null call target. make clean after touching any .hpp, and verify startup after every build:
rm -rf /tmp/bohome && mkdir -p /tmp/bohome
timeout 8 ./backone -p9999 -U /tmp/bohome # exit 124 (timeout) = healthy; 139 = crash
Code Conventions & Common Patterns
- Standard: C++17 (
-std=c++17in all makefiles;tcp-proxyis C++11). Ignore stale comments claiming otherwise (node/README.md"No C++11",osdep/BlockingQueue.hpp:30). - Formatting:
.clang-format— LLVM base, tabs always, indent 4, Stroustrup braces,BinPackArguments: false,PointerAlignment: Left. Gate:make lint→tools/lint.sh(clang-format on lines changed vs base commit;--allchecks whole files — whole repo has format debt, don't reformat untouched lines). No clang-tidy, no-Werror. - Namespace: everything in
namespace ZeroTier { … }, close with} // namespace ZeroTier. File-local helpers in anonymous namespace orstatic. - Naming: classes
PascalCase; methodscamelCase; private members_camelCase; static C callbacksS+ class (SnodeWirePacketSendFunction); macros/enumsZT_UPPER_SNAKE; include guardsZT_<NAME>_HPP(no#pragma once). - Includes: quoted relative (
"../node/Constants.hpp"from subdirs,"node/Constants.hpp"from root). Third-party via angle brackets resolved by-isystem ext.node/Constants.hppFIRST innode/files — canonicalizes__LINUX__/__APPLE__/__UNIX_LIKE__/__WINDOWS__; never test raw__linux__/_WIN32. - Error handling: three regimes — (1) C API boundary returns
ZT_ResultCode, fatal = 100–999, checked viaZT_ResultCode_isFatal; (2)node/throws bare ints (ZT_EXCEPTION_OUT_OF_BOUNDS,node/Constants.hpp:757+), caught ascatch (int e)inOneServiceImpl::run; (3) service/controller usestd::exception/catch (...)at thread boundaries. No exceptions across the C API. - Threading: no global lock; per-object
ZeroTier::Mutexscoped guard (Mutex::Lock _l(_m);) orstd::mutex+lock_guard/shared_lockin controller. Cross-thread wake only viaPhy::whack(); producer/consumer viaBlockingQueue<T>. Threads:std::thread. - Async: single-threaded reactor (
Phy::pollcallbacks), not futures; nostd::async/futurein C++ paths (tokio only insiderustybits/smeeclient). - Memory: raw
new/deletein service/controller; intrusiveSharedPtr<T>(node/SharedPtr.hpp— class needsfriend class SharedPtr<X>+AtomicCounter __refCount) forPeer/Network/Path;std::shared_ptrforDBbackends. Zero secrets withUtils::burn. - DI/state: no framework. Two seams only:
ZT_Node_Callbacksfunction-pointer struct (core ↔ OS) andconst RuntimeEnvironment *RRas first param. Controller injected viaNode::setNetconfMaster. - JSON:
nlohmann::json+ typed accessorsOSUtils::jsonString/jsonInt/jsonBool(defaults); API is type-sensitive. - Metrics:
node/Metrics.hppnamespace, increment inline (Metrics::udp_recv += len;). - License header: every new
.cpp/.hpp/.hgets the 11-line BSL block verbatim (copynode/Mutex.hpp:1-12). - Placement rule: sockets/files/interfaces →
osdep/; orchestration →service/; persistence →controller/;node/stays OS-independent. New compiled file must be added toobjects.mk(CORE_OBJSorONE_OBJS) or it won't link.
Important Files
Read before editing:
include/ZeroTierOne.h— public contract (ZT_Node_Callbacks:1733,ZT_ResultCode:375); ripples to Java/SDK.node/Constants.hpp— platform macros, exception ints, constants; include first.node/RuntimeEnvironment.hpp— object graph everynode/fn navigates.service/OneService.cpplines 690–1100 (callback table) and 1150–1400 (run()loop) — the integration seam.osdep/Phy.hpplines 55–160 — handler contract for any socket behavior.objects.mk+make-linux.mk:12,62-73,370-457— what compiles with which flags.- Per-dir READMEs:
service/README.md(local.conf schema + JSON API — primary ops doc),controller/README.md,node/README.md.
Module-specific: wire protocol → node/IncomingPacket.cpp:94-151 + node/Packet.hpp; routing → node/Switch.cpp; DB backend → controller/DB.hpp + EmbeddedNetworkController.cpp:465-580; TAP → osdep/EthernetTap.cpp.
Runtime/Tooling Preferences
- Build: GNU make (g++ or clang auto-detected,
make-linux.mk:3-10); MSVC on Windows; ant + NDK for Java; cargo forrustybits/; npm forrule-compiler/. - No package manager for C++ — deps vendored in
ext/(do not add new ones casually). - Daemon paths (Linux): home
/var/lib/backone(macOS:/Library/Application Support/BackOne); port 9993; key filesidentity.secret,authtoken.secret,networks.d/,local.conf,controller.db. Unprivileged CLI:cp authtoken.secret ~/.backOneOneAuthToken(source:one.cpp:283). - CLI:
backone-cli info|listpeers|listnetworks|join <nwid>|leave <nwid>(-jJSON,-p<port>,-D<dir>);backone-idtool generate|validate|getpublic|sign|verify|mkcom. - Dangerous scripts:
update_controllers.sh,cycle_controllers.share live ZeroTier-Central kubectl ops — never run locally. - Rebrand is partial: build outputs, CI (
build.yml,validate-linux.sh),Dockerfile.ci,make-bsd.mk/make-netbsd.mk, and debian units now usebackone*. Still upstream-named:pkg/snap/snapcraft.yaml(buildsgithub.com/zerotier/zerotierone.git→ broken) and cosmetic strings (/etc/zerotier-versioninDockerfile.ci,windows/zerotier-cli.bat,pkg/vendor paths). Expect occasionalzerotier-*names in docs/scripts.
Testing & QA
No C++ unit framework. Practical loop:
make selftest && ./backone-selftest # crypto KATs, identity, certs, packet codec, Phy loopback; non-zero exit on failure
make debug # ZT_DEBUG=1 build of one + selftest
- Sections in
selftest.cpp:testCrypto,testIdentity,testCertificate,testPacket,testOther,testPhy(binds 127.0.0.1 UDP/TCP loopback; no root needed). - CI (
.github/workflows/):build.yml= build smoke (make,make selftest);validate.yml=make one ZT_COVERAGE=1 ZT_TRACE=1then.github/workflows/validate-linux.sh(two-node netns + ping + iperf3 + valgrind) andvalidate-report.sh(fails on any definite leak or non-zero test exit — the only hard gate).ZT_COVERAGE=1adds--coverageflags (make-linux.mk:322-326); coverage recorded via gcovr, never thresholded. - Lint/format:
make lint→tools/lint.sh(clang-format on changed lines vs base; CI:.github/workflows/lint.yml, clang-format pinned 23.1.1). No clang-tidy, no-Werror. Do not reformat untouched lines. - Gaps: no Rust tests, no Java tests (orphaned
java/test/deleted), nomake test/checktarget. Prefer extendingselftest.cppfor regression coverage;rule-compilerhasnpm test(real,rule-compiler/test.js). - Windows smoke (
windows/README.md): run exe-p9994 -C <tmpdir>thenzerotier-cli.bat -p9994 -D<tmpdir> info|join.