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.
What happened
Section titled “What happened”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.
How it was found
Section titled “How it was found”- Confirmed QEMU was running with the TAP:
ps aux | grep qemu | grep tapF994C870 - Confirmed TAP was in the OVS bridge:
sudo ovs-vsctl list-ports br-provision— port was listed - Traced pivirtd logs:
Notice:6:33PM INF OVS manager ready bridge=br-int gateway=10.0.0.1
bridge=br-int, notbr-provision— the OVS manager was using the wrong default. - Searched source code:
ovs.NewManager(ovs.Config{})inmain.go— empty config defaultsBridgetobr-int. - Dug deeper into
prepareVMNetworks: confirmed thenetsslice was empty for Hades VMs, so the entire bridge-attachment loop was skipped.
The fix
Section titled “The fix”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.
How to check
Section titled “How to check”sudo cat /var/lib/pivirt/network/ipam.jsonExpected 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" }}How to check the MAC in the VM config
Section titled “How to check the MAC in the VM config”sudo python3 -c "import yamlfor 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:
# Edit /var/lib/pivirt/network/ipam.json and add the entry:sudo nano /var/lib/pivirt/network/ipam.jsonThen restart pivirtd so it reloads:
sudo systemctl restart pivirtdRoot 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.
How to check
Section titled “How to check”sudo journalctl -u pivirtd | grep -E 'dhcp|DHCP|bridge not found' | head -20Look for:
ERR dhcp: bridge not found error="route ip+net: no such network interface" bridge=br-provision# Verify the OVS bridge exists and is upsudo ovs-vsctl showsudo ip link show br-provision
# If missing, recreate it:sudo ovs-vsctl add-br br-provisionsudo ip addr add 10.0.0.1/24 dev br-provisionsudo ip link set br-provision up
# Restart pivirtdsudo systemctl restart pivirtdVerification Steps
Section titled “Verification Steps”After applying any fix, verify networking end-to-end:
# 1. TAP is in OVS bridgesudo ovs-vsctl list-ports br-provision
# 2. Host can ping all VMsfor 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 grantsudo journalctl -u pivirtd --since '5 minutes ago' | grep -i 'dhcp\|lease\|assigned'
# 4. Check VM console for assigned addresssudo tail -f /var/lib/pivirt/vms/talos-worker-4/run/console.log | grep -i 'assigned address'