Table of Contents

EVE-KVM: Network Instance lifecycle (Local & Switch)

This page documents exactly what EVE-OS does in KVM (hypervisor) mode when a Network Instance (NI) of type Local and type Switch is created.

Important — this is the EVE-KVM datapath, not EVE-K.
In EVE-K, NIs are wired through Multus + a NetworkAttachmentDefinition calling
the eve-bridge CNI plugin. In EVE-KVM there is no Multus, no NAD, no CNI.
The pillar agent zedrouter programs the Linux network stack directly: Linux
bridges, dnsmasq, iptables, policy routing tables and tc. Everything
below is the bridge/iptables world, not the Kubernetes world.

Source of truth: lf-edge/eve → pkg/pillar (cmd/zedrouter and nireconciler). File/line references are given per section so claims are verifiable.

NI types (enum)

EVE's internal NI type enum (mirrors zconfig.ZNetworkInstType from the controller API):

NetworkInstanceTypeSwitch      = 1   // L2 bridge, no EVE-side L3
NetworkInstanceTypeLocal       = 2   // L3 NAT'd bridge with DHCP/DNS
NetworkInstanceTypeCloud       = 3   // (VPN)
NetworkInstanceTypeHoneyPot    = 5
NetworkInstanceTypeTransparent = 6

pkg/pillar/types/zedroutertypes.go (NetworkInstanceType const block).

Address type drives L2 vs L3:

AddressTypeNone = 0   // Switch NI (no EVE-managed addressing)
AddressTypeIPV4 = 1   // Local NI
AddressTypeIPV6 = 2

Actors

Control-plane flow (how the config arrives)

controller (ZEDEDA Cloud)
    |  device config (protobuf)
    v
zedagent  --- publishes types.NetworkInstanceConfig --->  (pubsub)
    |
    v
zedrouter  subscribes NetworkInstanceConfig
    |   handleNetworkInstanceCreate()           cmd/zedrouter/pubsubhandlers.go:183
    |   -> parse/validate, allocate BridgeNum, bridge MAC, subnet
    |   -> doActivateNetworkInstance()           cmd/zedrouter/networkinstance.go:526
    |        -> niReconciler.AddNI(config, NIBridge)
    v
LinuxNIReconciler   builds intended dependency graph, reconciles to kernel
    |   creates bridge / dnsmasq / iptables / routes / ip rules / tc
    v
zedrouter  publishes types.NetworkInstanceStatus  (Activated=true)
    |
    v
niStateCollector.StartCollectingForNI()   (metrics, learned IPs, flow logs)

AddNI is the single entry point that turns NI config into kernel state (cmd/zedrouter/networkinstance.go:531).

The reconciler model (dependency graph)

nireconciler does not run imperative “create bridge, then add IP” steps. It builds an intended graph of typed config items and a reconciliation engine makes the kernel match it. The top-level graph (from the big ASCII map at the head of nireconciler/linux_config.go) has:

Branch point (linux_config.go:600):

if !ni.bridge.IPConflict {
    PutSubGraph(getIntendedNIL2Cfg(niID))   // always
    PutSubGraph(getIntendedNIL3Cfg(niID))   // empty body for Switch
    if ni.config.Type == NetworkInstanceTypeSwitch {
        PutSubGraph(getIntendedNIMirroring(niID))
    }
}

Interface naming

nireconciler/linux_config.go:303-304, generateBridgeIfName() (line ~1678):

“Created by NIM” rule (cmd/zedrouter/networkinstance.go:722): only a Switch NI with a single physical port whose Dhcp is Static or Client has its bridge owned by NIM. Local NIs are always bridged by zedrouter.

Type LOCAL (type = 2) — step by step

A Local NI is an L3, NAT'd, EVE-managed network: EVE owns the bridge IP, runs DHCP/DNS, and SNATs app traffic out the uplink port.

1. Bridge (L2 sub-graph)

zedrouter creates a managed Linux bridge bnN, assigns it the gateway IP (the bridge IP = the NI gateway, e.g. 10.10.1.1/24) and a deterministic MAC. (getIntendedNIL2Cfg, linux_config.go:623.) For Local NI the L2 sub-graph returns right after the bridge — no VLAN/STP/BridgePort items (those are Switch-only).

ip link add bn1 type bridge
ip addr add 10.10.1.1/24 dev bn1     # bridge IP == NI gateway
ip link set bn1 up

2. L3 sub-graph (Routes / IP rules / SNAT)

getIntendedNIL3Cfg (linux_config.go:756) — only runs for non-Switch:

# nat POSTROUTING (per uplink port of a Local NI)
-o eth0 -s 10.10.1.0/24 -j MASQUERADE

(linux_config.go:972 — guarded by Type == NetworkInstanceTypeLocal and IPv4 subnet.)

3. Services sub-graph: dnsmasq (DHCP + DNS)

getIntendedDnsmasqCfg (linux_config.go:1185). For Local NI EVE runs a per-NI dnsmasq bound to the bridge IP, serving DHCP + DNS to the apps:

# one dnsmasq per Local NI, e.g.
dnsmasq --conf-file=/run/zedrouter/dnsmasq.bn1.conf
#   interface=bn1, listen-address=10.10.1.1, dhcp-range=...

4. Services sub-graph: metadata server

getIntendedMetadataSrvCfg (linux_config.go:1123). EVE starts an HTTP server on the bridge IPv4:80 and DNATs the well-known metadata IP to it:

# nat PREROUTING
-i bn1 -p tcp -d 169.254.169.254/32 --dport 80 -j DNAT --to-destination 10.10.1.1:80

This is the EVE app-metadata endpoint (169.254.169.254, metadataSrvIP).

5. App attach (later, when a VM is deployed)

When domainmgr starts the VM, zedrouter adds an AppConn-<UUID>-<adapter> sub-graph: a VIF nbuNxM enslaved to bnN as a BridgePort, plus the per-VIF ACL chains (iptables rules derived from the app's ACLs). The VM then gets its lease from the NI's dnsmasq.

Resulting kernel state (Local NI)

ip -br link show type bridge          # bn1 ... UP
ip addr show dev bn1                   # 10.10.1.1/24
bridge link show                       # nbu1x3 master bn1
iptables -t nat -S | grep -i masquerade
ip rule show | grep -E '9999|10000|11000'
ip route show table 801                # 800 + BrNum
ps aux | grep dnsmasq.bn1

Type SWITCH (type = 1) — step by step

A Switch NI is an L2 bridge. EVE provides Layer-2 connectivity between the app VIFs and a physical uplink port; EVE does not provide IP, DHCP, DNS, NAT or routing. The app gets its address from an external DHCP server on the wire.

1. Bridge ownership

niBridgeIsCreatedByNIM (networkinstance.go:722):

2. L2 sub-graph (the whole story for Switch)

getIntendedNIL2Cfg (linux_config.go:623). This is where Switch-specific items live:

ip link add eth1 type bridge          # (or bn1 if zedrouter-owned)
ip link set eth0 master eth1          # physical uplink enslaved
# VLAN filtering / trunk / access programmed via 'bridge vlan' when configured

3. L3 sub-graph — EMPTY

getIntendedNIL3Cfg returns immediately for Switch (linux_config.go:762):

if ni.config.Type == NetworkInstanceTypeSwitch {
    // No L3 config for switch network instance.
    return intendedL3Cfg
}

Consequence: no bridge gateway IP, no MASQUERADE/SNAT, no per-NI routing table, no NAT IP rules.

4. Services — NO dnsmasq

getIntendedDnsmasqCfg returns immediately for Switch (linux_config.go:1187):

if ni.config.Type == NetworkInstanceTypeSwitch {
    // Not running DHCP and DNS servers inside EVE for Switch network instances.
    return
}

The app must obtain its IP from an external DHCP server reachable over the bridged uplink.

5. Metadata server on a Switch NI

The metadata HTTP server only starts if the bridge has an IPv4 address; a pure Switch NI normally has none, so there is typically no metadata service. Where a bridge IP does exist, EVE adds a guard rule that DROPs metadata access arriving from the external physical port, so outside endpoints can't reach it through the L2 segment (linux_config.go, filter INPUT with physdev –physdev-in <port>).

6. Mirroring sub-graph (Switch NI)

Unlike Local NI, Switch NI explicitly adds the Mirroring sub-graph (getIntendedNIMirroring, linux_config.go:996): a DummyIf plus tc ingress + mirror rules that copy a small slice of traffic (DHCP replies, ARP, ICMPv6 NS, and DNS if flow-logging is on) so the niStateCollector can learn app IPs and log flows even though EVE isn't the DHCP/DNS server.

7. App attach

Same AppConn-<UUID> mechanism: a VIF nbuNxM enslaved to the bridge as a BridgePort. For Switch NI the per-VIF sub-graph also gets VLANPort and BPDUGuard items when VLAN/STP are configured. The VM sees a plain bridged L2 interface and behaves as if cabled directly to the uplink.

Resulting kernel state (Switch NI)

ip -br link show                       # eth1 (or bn1) UP, eth0 enslaved
bridge link show                       # eth0 + nbu1x3 master eth1
bridge vlan show                       # trunk/access VLANs if configured
iptables -t nat -S | grep -i masquerade   # (empty for Switch)
ps aux | grep dnsmasq                       # no dnsmasq for this NI

Side-by-side: Local vs Switch on EVE-KVM

Aspect Local (type 2) Switch (type 1)
OSI layer L3 (routed/NAT) L2 (bridged)
Bridge owner zedrouter (always), bnN NIM if single mgmt/Local port (= ethX); else zedrouter bnN
Bridge IP / gateway Yes — EVE owns gateway IP None (EVE assigns no L3)
DHCP/DNS (dnsmasq) Yes, per-NI on bridge IP No — external DHCP on the wire
SNAT / MASQUERADE Yes, per uplink port (IPv4) No
Per-NI routing table Yes (800 + BrNum) No
NAT IP rules (pbr) Yes (9999/10000/11000) No
VLAN / STP / BPDUGuard No Yes (STP when >1 port)
Metadata server (.169.254) Yes (DNAT to bridge IP:80) Usually none; guarded if bridge IP exists
Mirroring sub-graph Implicit via state collector Explicit (tc ingress/mirror)
App addressing Lease from EVE dnsmasq Lease from external DHCP

Inspecting a live EVE-KVM node

Via EdgeView / device debug shell:

# Which NIs exist and their bridges/types (from zedrouter status)
cat /run/zedrouter/NetworkInstanceStatus/*.json | jq '{name:.DisplayName,type:.Type,br:.BrIfName,ip:.BridgeIPAddr}'
 
# Bridges and enslaved interfaces
ip -br link show type bridge
bridge link show
 
# Local NI plumbing
ip addr show dev bn1
ip rule show
ip route show table 801                 # 800 + BridgeNum
iptables -t nat -S | grep -E 'MASQUERADE|169.254.169.254'
ls /run/zedrouter/dnsmasq.*.conf
 
# Switch NI plumbing
bridge vlan show
iptables -S | grep -i physdev           # metadata guard / L2 fwd rules
 
# VIFs of running apps
ip -br link show | grep nbu

Source references (lf-edge/eve, pkg/pillar)