Skip to main content
Version: master

Pantavisor IPAM

Overview: IPAM explains why pools exist, how allocation works, and how IPAM coexists with backend-native static IPs.

Reference for the IP Address Management (IPAM) subsystem: device.json pool schema, per-container run.json / args.json schema, lifecycle behaviors, and backend-plugin hook.

For the narrative overview, see Technical Overview — IPAM.

device.json — Pools

Pools are declared under network.pools, keyed by pool name:

{
"network": {
"pools": {
"internal": {
"type": "bridge",
"bridge": "pvbr0",
"subnet": "10.0.5.0/24",
"gateway": "10.0.5.1",
"nat": true
},
"lab": {
"type": "bridge",
"bridge": "pvbr1",
"subnet": "10.0.6.0/24",
"gateway": "10.0.6.1",
"nat": false
}
}
}
}

Fields

FieldTypeRequiredDescription
typestringyesPool backend. "bridge" is the supported value today; "macvlan" is reserved for future use.
bridgestringfor type=bridgeHost-side bridge interface name. Pantavisor creates it if missing and assigns gateway to it.
parentstringfor type=macvlanParent netdev for macvlan.
subnetstringyesCIDR, e.g. "10.0.5.0/24".
gatewaystringyesHost-side bridge IP, within the subnet. Served as the gateway for containers on this pool.
natboolno (default false)When true, installs a MASQUERADE rule so containers in this pool can reach the external network through the host. When false, the pool is bridge-local only.

Pools are validated at parse time and registered in the in-memory IPAM registry; setup runs during pv_ipam_setup_bridges() in pantavisor init.

run.json — Per-Container Network

A container declares how it attaches to IPAM under a top-level network block:

{
"network": {
"mode": "pool",
"pool": "internal",
"hostname": "my-container"
}
}

Fields

FieldTypeRequiredDescription
modestringyesNetworking mode. "pool" opts the container into IPAM-managed networking. "host" uses the host netns. "none" leaves pantavisor out of the network setup entirely.
poolstringfor mode=poolName of a pool declared in device.json. If the pool does not exist at start time the container is refused.
hostnamestringnoValue assigned to lxc.uts.name; sets the container's hostname.
interfacesarraynoPer-interface overrides (static IP, static MAC). See below.

interfaces[] overrides

When the defaults (eth0, auto-allocated IP, derived MAC) are not enough:

FieldTypeDescription
namestringContainer-side interface name. Default "eth0".
poolstringPool name for this interface. Default: top-level pool.
ipv4_addressstringStatic IP (CIDR or bare address). Reserved in the pool; start fails if the IP is outside the subnet or already in use.
mac_addressstringStatic MAC. Default is deterministic 02:00:<ip_octets>.

args.json — PVR Template Variables

When building a container image via pvr / Yocto, these arguments are templated into the generated run.json:

ArgTarget run.json field
PV_NETWORK_POOLnetwork.pool (also sets network.mode = "pool")
PV_NETWORK_HOSTNAMEnetwork.hostname
PV_NETWORK_IPnetwork.interfaces[0].ipv4_address
PV_NETWORK_MACnetwork.interfaces[0].mac_address

Lease Lifecycle

Leases are keyed by (pool_name, container_name) and held in each pool's in-memory leases list.

EventLease behavior
First start of a pool-using containerNew lease allocated from next_ip, or reserved to a static IP. Deterministic MAC derived if none provided.
pvcontrol containers stop / startLease preserved. The start path's pv_ipam_allocate finds the existing lease and reuses it.
Auto-recovery restart (immediate and delayed)Same — lease is reused.
Platform teardown (state transition, reboot, rollback)Lease released in pv_platform_free.
IPAM alloc failure mid-start (e.g. static IP collision)Any partial leases for the platform are released in the ipam_error rollback path.

Backend plugin hooks

Two optional symbols are dlsym'd on the container-backend plugin. A backend that does not provide them simply contributes nothing.

SymbolCalled fromEffect
pv_validate_container_config(p, conf_file)pv_platform_start, before IPAM allocationNon-zero refuses the start. The LXC plugin uses it to reject a pool-using container whose lxc.container.conf bakes lxc.net.* entries. lxc.namespace.keep = net is not flagged
pv_enumerate_static_ips(p, conf_file, cb, ctx)pv_platforms_reserve_static_ips, once at startup after pv_ipam_setup_bridgesInvokes cb(ip_in_host_order, ctx) per hard-coded address found. The LXC plugin greps lxc.net.N.ipv4.address = X.Y.Z.W[/M], ignoring the auto and dhcp sentinels

Each enumerated address is routed into pv_ipam_reserve_static(ip, source):

AddressDisposition
Inside a pool's subnetLeased to that pool, tagged pv:static:<source>, so is_ip_available() skips it
Outside every pool's subnetLogged at DEBUG, ignored
Equal to a pool's gatewayLogged at WARN, not reserved
Already leasedSkipped

NAT backend

NAT is installed per pool with nat: true, using the first available backend:

OrderConditionRule installed
1nft is on PATHtable ip nat, postrouting chain at srcnat priority, one ip saddr <subnet> oifname != "<bridge>" masquerade rule per pool
2iptables is on PATHiptables -t nat -A POSTROUTING -s <subnet> ! -o <bridge> -j MASQUERADE
3neitherWARN logged; the pool runs without NAT

Bridge creation itself uses netlink.

Error Handling

ConditionBehavior
pool field references an undefined poolRefuse at pv_platform_start: refusing to start, triggering rollback if in try-boot. Bubbles up to rollback/reboot.
Baked lxc.net.* in a pool-using containerRefuse via the LXC plugin's validate_config hook. Bubbles up to rollback/reboot.
Static IP outside pool subnetRefuse at pv_platform_start. Bubbles up.
Static IP already in useRefuse at pv_platform_start. Bubbles up.
Pool exhausted (no free IPs)Refuse at pv_platform_start. Bubbles up.
NAT setup failsLogged as WARN; the pool is still usable for same-pool traffic.