Skip to content

VM Gets No DHCP / IP Address

Symptom: The QEMU process is running and pivirtd reports the VM as running, but the VM console shows repeated DHCP failures:

[talos] DHCP request/renew failed error="no matching response packet received" link="enp0s4"

Pinging the expected IP from the host (ping 10.0.0.19) returns “Destination Host Unreachable”.


Root Cause A — TAP not attached to OVS bridge (primary bug)

Section titled “Root Cause A — TAP not attached to OVS bridge (primary bug)”

This was the bug that broke all Talos workers during the July 2026 re-provisioning.

pivirtd creates a TAP device for each VM network interface, but it was not attaching the TAP to the OVS bridge for VMs defined in the Hades config format (devices.networkinterfaces in config.yaml).

The network setup code (prepareVMNetworks) only iterated the proto NetworkConfig slice (vmConfig.Networks). For Hades-format VMs, that slice is always empty — the actual network interfaces live in hadesVM.Devices.NetworkInterfaces. So the loop ran zero iterations and no TAP was ever added to br-provision.

Effect: The TAP device existed on the host, but was not a port on the OVS bridge. DHCP broadcasts from the VM went into the TAP and were dropped — no DHCP server on br-provision ever saw them. The VM waited forever for an offer that never came.

  1. Confirmed QEMU was running with the TAP: ps aux | grep qemu | grep tapF994C870
  2. Confirmed TAP was in the OVS bridge: sudo ovs-vsctl list-ports br-provision — port was listed
  3. Traced pivirtd logs:
    6:33PM INF OVS manager ready bridge=br-int gateway=10.0.0.1
    Notice: bridge=br-int, not br-provision — the OVS manager was using the wrong default.
  4. Searched source code: ovs.NewManager(ovs.Config{}) in main.go — empty config defaults Bridge to br-int.
  5. Dug deeper into prepareVMNetworks: confirmed the nets slice was empty for Hades VMs, so the entire bridge-attachment loop was skipped.

apps/pivirtd/internal/vm/netsetup.go

Added a second loop in prepareVMNetworks that handles Hades-format NIs when nets is empty:

if len(nets) == 0 {
for i, ni := range hadesNIs {
tapName := defaultTapName(vmUUID, i)
ni.TargetDev = tapName
host.EnsureTAP(tapName)
host.LinkUp(tapName)
host.AttachOVS(ni.Source, tapName) // ni.Source = "br-provision"
}
}

Commit: fix(pivirtd): handle Hades-format NIs in prepareVMNetworks


Root Cause B — MAC address not in ipam.json

Section titled “Root Cause B — MAC address not in ipam.json”

pivirtd’s embedded DHCP server only hands out addresses to MACs that appear in /var/lib/pivirt/network/ipam.json. If a VM’s MAC is missing, DHCP is silently ignored.

Terminal window
sudo cat /var/lib/pivirt/network/ipam.json

Expected output (all VMs must be present):

{
"leases": {
"52:54:00:ab:cd:0a": "10.0.0.10",
"52:54:00:ab:cd:0f": "10.0.0.17",
"52:54:00:ab:cd:10": "10.0.0.18",
"52:54:00:ab:cd:11": "10.0.0.19"
}
}
Terminal window
sudo python3 -c "
import yaml
for w in ['talos-cp-0', 'talos-worker-2', 'talos-worker-3', 'talos-worker-4']:
with open(f'/var/lib/pivirt/vms/{w}/config.yaml') as f:
d = yaml.safe_load(f)
for n in d.get('devices', {}).get('networkinterfaces', []):
print(w, n.get('mac','<no mac>'), n.get('source','<no source>'))
"

Add the missing MAC → IP mapping to ipam.json manually:

Terminal window
# Edit /var/lib/pivirt/network/ipam.json and add the entry:
sudo nano /var/lib/pivirt/network/ipam.json

Then restart pivirtd so it reloads:

Terminal window
sudo systemctl restart pivirtd

Root Cause C — DHCP server bound to wrong bridge

Section titled “Root Cause C — DHCP server bound to wrong bridge”

pivirtd starts a DHCP server on br-provision using an AF_PACKET raw socket. If the bridge interface name changes or does not exist at startup, the DHCP server fails silently.

Terminal window
sudo journalctl -u pivirtd | grep -E 'dhcp|DHCP|bridge not found' | head -20

Look for:

ERR dhcp: bridge not found error="route ip+net: no such network interface" bridge=br-provision
Terminal window
# Verify the OVS bridge exists and is up
sudo ovs-vsctl show
sudo ip link show br-provision
# If missing, recreate it:
sudo ovs-vsctl add-br br-provision
sudo ip addr add 10.0.0.1/24 dev br-provision
sudo ip link set br-provision up
# Restart pivirtd
sudo systemctl restart pivirtd

After applying any fix, verify networking end-to-end:

Terminal window
# 1. TAP is in OVS bridge
sudo ovs-vsctl list-ports br-provision
# 2. Host can ping all VMs
for ip in 10.0.0.10 10.0.0.17 10.0.0.18 10.0.0.19; do
ping -c 1 -W 1 $ip && echo "$ip OK" || echo "$ip UNREACHABLE"
done
# 3. Check pivirtd DHCP log for lease grant
sudo journalctl -u pivirtd --since '5 minutes ago' | grep -i 'dhcp\|lease\|assigned'
# 4. Check VM console for assigned address
sudo tail -f /var/lib/pivirt/vms/talos-worker-4/run/console.log | grep -i 'assigned address'