From 6d3cd015d528545bedbd414e06613de8901adfd3 Mon Sep 17 00:00:00 2001 From: anti Date: Sat, 18 Apr 2026 06:04:35 -0400 Subject: [PATCH] wiki(networking): add MACVLAN/IPvlan and Deployment Modes pages --- Deployment-Modes.md | 175 +++++++++++++++++++++++++++++++++++ Networking-MACVLAN-IPVLAN.md | 163 ++++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 Deployment-Modes.md create mode 100644 Networking-MACVLAN-IPVLAN.md diff --git a/Deployment-Modes.md b/Deployment-Modes.md new file mode 100644 index 0000000..811fff1 --- /dev/null +++ b/Deployment-Modes.md @@ -0,0 +1,175 @@ +# Deployment Modes: UNIHOST and SWARM + +DECNET's `deploy` command takes a `--mode` flag that selects how the fleet of +deckies is laid out across real hardware. Two modes are defined: + +- `unihost` — a single real host runs all deckies as containers. +- `swarm` — multiple real hosts each run a subset of deckies (multi-host). + +The mode is validated in `decnet/cli.py` (`deploy --mode`) and stored on the +`DecnetConfig` object consumed by `decnet/engine/deployer.py`. The orchestrator +in `decnet/composer.py` emits the Docker Compose file used by either mode. + +See also: [CLI reference](CLI-Reference), [INI format](INI-Config-Format), +[Networking: MACVLAN and IPvlan](Networking-MACVLAN-IPVLAN), +[Teardown](Teardown-and-State). + +--- + +## When to use which + +| Criterion | UNIHOST | SWARM | +|---------------------------------|----------------------------|-------------------------------------| +| Hosts involved | 1 | N | +| Setup complexity | low | higher (needs fleet orchestration) | +| Deckies per host | all of them | split across hosts | +| Fits home lab / single server | yes | overkill | +| Fits multi-subnet / multi-site | no | yes | +| Failure blast radius | whole fleet on one box | isolated per host | +| Recommended for first runs | yes | no | + +UNIHOST is the default (`--mode unihost`) and what the README's quickstart +assumes. SWARM is the deployment posture for a real honeynet spanning multiple +boxes. + +--- + +## UNIHOST + +``` + attacker LAN 192.168.1.0/24 + +---------------------------------------------------+ + | | + | [host] eth0 decnet_lan (MACVLAN / IPvlan L2) | + | |- decky-01 .10 | + | |- decky-02 .11 | + | |- decky-03 .12 | + | |- decky-04 .13 | + | '- decky-05 .14 | + +---------------------------------------------------+ + | + (isolated mgmt path) + v + logging / SIEM network +``` + +One host, one MACVLAN or IPvlan L2 network, N deckies. The host also runs the +collector/API and forwards logs out-of-band to the isolated logging network. + +### CLI + +``` +# Dry-run: generate compose, no containers +decnet deploy --mode unihost --deckies 3 --randomize-services --dry-run + +# Full deploy (root required for MACVLAN) +sudo decnet deploy \ + --mode unihost \ + --deckies 5 \ + --interface eth0 \ + --randomize-services +``` + +Flags worth knowing (full table in [CLI reference](CLI-Reference)): + +- `--deckies / -n` — number of deckies on this host. +- `--interface / -i` — parent NIC. Auto-detected via `detect_interface()`. +- `--ipvlan` — switch to IPvlan L2. Required on WiFi. +- `--services`, `--randomize-services`, `--archetype` — service selection. +- `--distro`, `--randomize-distros` — OS heterogeneity. +- `--mutate-interval` — rotate services every N minutes. +- `--dry-run` — write the compose file under `deploy/` without starting. + +### INI + +The same deployment can be expressed declaratively via `--config`: + +``` +decnet deploy --mode unihost --config ./my-fleet.ini +``` + +See [INI format](INI-Config-Format) for the full schema; `cli.py`'s config +path calls `load_ini` and `build_deckies_from_ini`. + +--- + +## SWARM (multihost) + +``` + attacker network(s) + ------------------------------ + | | | + [host-A] [host-B] [host-C] + decky-01 decky-04 decky-07 + decky-02 decky-05 decky-08 + decky-03 decky-06 decky-09 + + \__________________|__________________/ + | + isolated mgmt / SIEM +``` + +Each real host runs a UNIHOST-shaped deployment over its own slice of the IP +space. An external orchestrator (Ansible, sshpass-driven scripts, etc.) +invokes `decnet deploy --mode swarm ...` on each host in turn. The CLI +currently accepts `swarm` as a valid mode — the fleet-wide orchestration layer +lives outside the DECNET binary and is the operator's responsibility. See the +README's architecture section for the intended shape. + +### CLI + +Run on each host, coordinating IP ranges so deckies do not collide: + +``` +# host-A +sudo decnet deploy \ + --mode swarm \ + --deckies 3 \ + --interface eth0 \ + --ip-start 192.168.1.10 \ + --randomize-services + +# host-B +sudo decnet deploy \ + --mode swarm \ + --deckies 3 \ + --interface eth0 \ + --ip-start 192.168.1.20 \ + --randomize-services +``` + +`--ip-start` is the operator's primary tool for partitioning the subnet across +hosts; `allocate_ips` in `decnet/network.py` starts sequentially from that +address and skips reserved / in-use IPs. + +### INI + +For reproducible swarm rollouts, give each host its own INI and drive the +rollout from Ansible (or similar): + +``` +decnet deploy --mode swarm --config ./host-A.ini +decnet deploy --mode swarm --config ./host-B.ini +``` + +--- + +## What is common to both modes + +- Compose generation in `decnet/composer.py` is mode-agnostic; the `mode` + field is recorded on `DecnetConfig` for state/telemetry but the compose + shape (base container + service containers sharing its network namespace + via `network_mode: service:`) is identical. +- Network driver selection (MACVLAN vs IPvlan L2) is per-host, controlled by + `--ipvlan`. See [Networking: MACVLAN and IPvlan](Networking-MACVLAN-IPVLAN). +- Teardown is per-host: run `sudo decnet teardown --all` on each host in the + swarm. See [Teardown](Teardown-and-State). + +--- + +## Quick checklist + +- Single box, one LAN -> UNIHOST. +- Multiple boxes across one or more LANs -> SWARM, driven by external + orchestration, with non-overlapping `--ip-start` per host. +- Not sure -> start with UNIHOST and a small `--deckies` count. diff --git a/Networking-MACVLAN-IPVLAN.md b/Networking-MACVLAN-IPVLAN.md new file mode 100644 index 0000000..322e703 --- /dev/null +++ b/Networking-MACVLAN-IPVLAN.md @@ -0,0 +1,163 @@ +# Networking: MACVLAN and IPvlan + +DECNET's deckies must look like real, independent machines on the LAN. Each +decky owns an IP (and ideally a MAC) drawn from the same subnet as the host's +real NIC, so that attackers scanning the network see a heterogeneous fleet of +hosts rather than a single container host. + +This page covers how that is wired up: the two Docker network drivers DECNET +supports, the host-side hairpin interface it creates, and the limitations you +will hit on WiFi or WSL. + +Source of truth: `decnet/network.py` (constants `MACVLAN_NETWORK_NAME`, +`HOST_MACVLAN_IFACE`, `HOST_IPVLAN_IFACE`; functions `setup_host_macvlan`, +`teardown_host_macvlan`, `setup_host_ipvlan`, `teardown_host_ipvlan`). + +See also: [CLI reference](CLI-Reference), [INI format](INI-Config-Format), +[Teardown](Teardown-and-State). + +--- + +## Topology + +``` + Internet / attacker + | + | (public IP, port forwards) + | + +-----------------+ + | DECNET host | eth0 -> decnet_lan (MACVLAN / IPvlan L2) + +--------+--------+ + | + ==============+=================== LAN 192.168.1.0/24 + | | | + decky-01 decky-02 decky-03 (each = own IP, + .10 .11 .12 MACVLAN = own MAC too) + + | + +-------------+-------------+ + | decnet_macvlan0 (host) | hairpin: host <-> deckies reachability + +---------------------------+ + + --- isolated mgmt path --- + host -> logging/SIEM network + (not reachable from decnet_lan) +``` + +The decoy LAN is attacker-facing. The logging/aggregation network is reachable +only from the host and must not be routable from `decnet_lan`. + +--- + +## Driver 1: MACVLAN (default) + +MACVLAN is the default driver. Each decky gets a kernel-assigned MAC address on +the parent interface, so it is a distinct L2 endpoint on the LAN. + +- Docker network name: `decnet_lan` (constant `MACVLAN_NETWORK_NAME`). +- Parent NIC: `--interface` (auto-detected via `detect_interface()` if omitted). +- Mode: `macvlan / bridge` (see `create_macvlan_network` and the + `ip link add ... type macvlan mode bridge` call in `setup_host_macvlan`). +- Requires the parent NIC to support **promiscuous mode**. Most wired NICs do. + +### Host hairpin (`decnet_macvlan0`) + +MACVLAN has a well-known limitation: the host cannot talk directly to its own +MACVLAN children on the parent NIC. Without a workaround, `decnet status` and +the log collector (which run on the host) would be unable to reach deckies. + +DECNET fixes this by creating a second host-side MACVLAN interface named +`decnet_macvlan0` (constant `HOST_MACVLAN_IFACE`) attached to the same parent +NIC, assigning it a /32 on the host, and routing the decky IP range out of it. +The relevant sequence in `setup_host_macvlan`: + +``` +ip link add decnet_macvlan0 link type macvlan mode bridge +ip addr add /32 dev decnet_macvlan0 +ip link set decnet_macvlan0 up +ip route add dev decnet_macvlan0 +``` + +--- + +## Driver 2: IPvlan L2 (`--ipvlan`) + +Pass `--ipvlan` to opt into IPvlan L2. Each decky gets a unique IP but all +share the host MAC. This is the fallback for environments where MACVLAN is not +workable: + +- **WiFi.** Most access points drop frames whose source MAC is not registered + with the radio, so random MACVLAN MACs never reach the LAN. IPvlan L2 works + because every frame leaves with the (registered) host MAC. +- NICs that refuse promiscuous mode. + +### Host hairpin (`decnet_ipvlan0`) + +Same trick as MACVLAN, different link type. `setup_host_ipvlan` adds a +host-side interface named `decnet_ipvlan0` (constant `HOST_IPVLAN_IFACE`) of +type `ipvlan mode l2`, gives it a /32, and routes the decky range through it. +`teardown_host_ipvlan` removes the route and the link on teardown. + +### Trade-offs + +| Aspect | MACVLAN | IPvlan L2 | +|--------------------|----------------------------------|---------------------------------| +| Per-decky MAC | yes (unique, kernel-assigned) | no (shares host MAC) | +| Works on WiFi | usually no | yes | +| Promisc NIC needed | yes | no | +| Decoy realism | higher (distinct L2 identities) | lower (all share one MAC) | +| Attacker fingerprinting via `arp -a` | sees many MACs | sees one MAC, many IPs | + +Prefer MACVLAN on wired, bare-metal or VM deployments. Use IPvlan L2 when the +environment forces it. + +--- + +## WiFi and WSL limitations + +- **WiFi:** MACVLAN typically does not work. Use `--ipvlan`. +- **WSL2:** MACVLAN has known limitations under WSL. Bare metal or a full Linux + VM is the supported testing environment (README "Requirements" section). +- **Promiscuous mode:** some virtualization platforms (including vSphere with + default security policies) block promiscuous mode on the vNIC; either relax + the policy on the virtual switch or use IPvlan. + +--- + +## Teardown + +Teardown of the Docker network and the host hairpin interface is automatic +when you run `decnet teardown --all`. The relevant steps, from +`decnet/engine/deployer.py`: + +``` +if config.ipvlan: + teardown_host_ipvlan(decky_range) +else: + teardown_host_macvlan(decky_range) +remove_macvlan_network(client) +``` + +`teardown_host_macvlan` / `teardown_host_ipvlan` each do: + +``` +ip route del dev # decnet_macvlan0 or decnet_ipvlan0 +ip link del +``` + +Both use `check=False`, so a partial or previously-cleaned state is safe. +Removing the Docker network `decnet_lan` happens separately via +`remove_macvlan_network`. + +See [Teardown](Teardown-and-State) for the full teardown flow including +container removal and state-file cleanup. + +--- + +## Quick checklist + +- Host NIC is wired and supports promiscuous mode -> MACVLAN, no flag needed. +- Host NIC is WiFi, or promisc is blocked -> pass `--ipvlan`. +- Running under WSL -> switch to a Linux VM or bare metal. +- `decnet status` says a decky is unreachable from the host -> confirm + `decnet_macvlan0` (or `decnet_ipvlan0`) exists with `ip link show`.