1
Networking MACVLAN IPVLAN
anti edited this page 2026-04-18 06:04:35 -04:00

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, INI format, Teardown.


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 <iface> type macvlan mode bridge
ip addr add <host_macvlan_ip>/32 dev decnet_macvlan0
ip link set decnet_macvlan0 up
ip route add <decky_range> 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 <decky_range> dev <iface>   # decnet_macvlan0 or decnet_ipvlan0
ip link  del <iface>

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 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.