KVM 虚拟机 DHCP 排查
这次在本机装了一台 AlmaLinux 9.8 KVM 虚拟机,虚拟机网卡接在 libvirt 默认网络上,但没有拿到预期的 192.168.122.0/24 地址。
最后排下来,问题不在 AlmaLinux 本身,也不是 libvirt 的 default 网络没启动,而是宿主机上的 UFW 把来自 virbr0 的 DHCP/DNS 流量拦住了。
现象
虚拟机名是 almalinux9,期望它从 libvirt 默认 NAT 网络获取地址。
libvirt 默认网络一般长这样:
- 网桥:
virbr0 - 网关:
192.168.122.1/24 - DHCP 地址池:
192.168.122.2 - 192.168.122.254
但虚拟机启动后,在宿主机上查不到租约:
virsh -c qemu:///system domifaddr almalinux9 --source lease输出为空:
名称 MAC 地址 协议 地址--------------------------------环境
宿主机环境:
OS: CachyOS LinuxKernel: 7.0.12-1-cachyos虚拟化: KVM / QEMU / libvirt虚拟机: AlmaLinux 9.8宿主机网络里已经能看到 virbr0:
ip -br addr关键输出:
virbr0 UP 192.168.122.1/24vnet4 UNKNOWN fe80::fc54:ff:fefc:cda3/64这说明 libvirt 的网桥已经存在,虚拟机网卡也已经挂到了宿主机侧的 vnet4 上。
检查 libvirt 默认网络
一开始直接执行 virsh net-list --all,结果没有看到任何网络。
原因是普通 virsh 默认可能连到会话连接,而这台虚拟机实际在系统连接里。后续命令要显式指定:
virsh -c qemu:///system net-list --all输出:
名称 状态 自动开始 持久-----------------------------------default 活动 是 是继续看 default 网络配置:
virsh -c qemu:///system net-dumpxml default关键配置如下:
<network connections='1'> <name>default</name> <forward mode='nat'> <nat> <port start='1024' end='65535'/> </nat> </forward> <bridge name='virbr0' stp='on' delay='0'/> <ip address='192.168.122.1' netmask='255.255.255.0'> <dhcp> <range start='192.168.122.2' end='192.168.122.254'/> </dhcp> </ip></network>这一步可以确认:
default网络是活动状态virbr0地址正确- DHCP 地址池存在
- NAT 模式正常开启
检查虚拟机网卡
再看虚拟机接到哪里:
virsh -c qemu:///system domiflist almalinux9输出:
接口 类型 源 型号 MAC---------------------------------------------------------vnet4 network default virtio 52:54:00:fc:cd:a3虚拟机 XML 里的网卡片段也正常:
<interface type='network'> <mac address='52:54:00:fc:cd:a3'/> <source network='default' bridge='virbr0'/> <target dev='vnet4'/> <model type='virtio'/></interface>到这里,libvirt 侧的连接关系没有明显问题:
almalinux9 -> vnet4 -> virbr0 -> default NAT network检查租约与 ARP
继续查虚拟机地址:
virsh -c qemu:///system domifaddr almalinux9 --source leasevirsh -c qemu:///system domifaddr almalinux9 --source arp两者都没有结果。
再看 libvirt dnsmasq 的租约文件:
sudo sed -n '1,120p' /var/lib/libvirt/dnsmasq/virbr0.status当时也没有有效租约。
这说明问题不是“地址拿到了但 virsh 没显示”,而是虚拟机确实没有从 libvirt DHCP 成功拿到地址。
发现 UFW 拦截线索
查看最近的系统日志时,出现了 UFW 拦截 virbr0 流量的记录:
sudo journalctl --since '2 hours ago' --no-pager | grep -Ei 'dnsmasq|virbr0|dhcp|52:54:00:fc:cd:a3|almalinux'关键日志类似这样:
kernel: virbr0: port 1(vnet4) entered forwarding statekernel: [UFW BLOCK] IN=virbr0 OUT= MAC= SRC=192.168.122.1 DST=224.0.0.252 PROTO=UDP SPT=5355 DPT=5355虽然这条日志本身是 LLMNR 多播,不是 DHCP,但它说明一个事实:
UFW 确实在处理并拦截 virbr0 上的入站流量。
于是继续检查 UFW 状态:
sudo ufw status verbose输出:
Status: activeLogging: on (low)Default: deny (incoming), allow (outgoing), deny (routed)这里最关键的是两点:
- incoming 默认拒绝
- routed 默认拒绝
对 libvirt NAT 网络来说,虚拟机 DHCP 请求会从 virbr0 进宿主机,虚拟机出网还需要转发/NAT。如果 UFW 没有相应规则,就很容易把 libvirt 默认网络打断。
继续看 UFW 规则计数器,可以看到 DHCP 端口被命中到默认丢弃路径:
sudo iptables-save -c | grep -Ei 'ufw|dpt:67|dpt:68|virbr0'关键线索是 udp dport 67 已经有计数,并被送到 UFW 的默认 input policy:
-A ufw-after-input -p udp -m udp --dport 67 -j ufw-skip-to-policy-input-A ufw-skip-to-policy-input -j DROP这基本就把根因锁定了:
宿主机 UFW 没有允许 virbr0 上访问 libvirt dnsmasq 的 DHCP/DNS 流量。
根因
根因是宿主机 UFW 与 libvirt 默认 NAT 网络的规则没有配合好。
libvirt 的 default 网络本身是正常的:
virbr0正常存在dnsmasq监听了 DHCP- 虚拟机网卡接到了
default - 虚拟机 MAC 也能在 bridge FDB 里看到
但 UFW 默认拒绝 incoming/routed,而没有为 virbr0 放行 DHCP、DNS 和转发流量。
因此虚拟机发出的 DHCP 请求无法被宿主机上的 libvirt dnsmasq 正常处理,表现出来就是虚拟机拿不到 192.168.122.0/24 地址。
修复方式
针对 virbr0 添加最小范围的 UFW 规则。
允许虚拟机访问宿主机上的 DHCP:
sudo ufw allow in on virbr0 to any port 67 proto udp comment 'libvirt DHCP on virbr0'允许虚拟机访问宿主机上的 DNS:
sudo ufw allow in on virbr0 to any port 53 comment 'libvirt DNS on virbr0'允许 virbr0 通过当前宿主机上行网卡转发出网:
sudo ufw route allow in on virbr0 out on enp4s0f3u1u4 comment 'libvirt NAT via wired uplink'sudo ufw route allow in on virbr0 out on wlan0 comment 'libvirt NAT via wifi uplink'最后重载 UFW:
sudo ufw reload重载后查看规则:
sudo ufw status verbose关键结果:
67/udp on virbr0 ALLOW IN Anywhere # libvirt DHCP on virbr053 on virbr0 ALLOW IN Anywhere # libvirt DNS on virbr0Anywhere on enp4s0f3u1u4 ALLOW FWD Anywhere on virbr0Anywhere on wlan0 ALLOW FWD Anywhere on virbr0这里的 enp4s0f3u1u4 和 wlan0 是我当时宿主机的两个上行接口。你自己的机器上要按实际默认路由接口调整。
可以用下面命令确认当前默认出口:
ip route show default触发虚拟机重新 DHCP
添加防火墙规则后,可以重启虚拟机,也可以只断开/接回虚拟机网卡来触发 DHCP。
这次我选择后者:
virsh -c qemu:///system domif-setlink almalinux9 52:54:00:fc:cd:a3 downsleep 2virsh -c qemu:///system domif-setlink almalinux9 52:54:00:fc:cd:a3 up等待几秒后再查租约:
virsh -c qemu:///system domifaddr almalinux9 --source lease虚拟机成功拿到地址:
名称 MAC 地址 协议 地址--------------------------------------------------------vnet4 52:54:00:fc:cd:a3 ipv4 192.168.122.232/24租约文件里也能看到记录:
[ { "ip-address": "192.168.122.232", "mac-address": "52:54:00:fc:cd:a3", "client-id": "01:52:54:00:fc:cd:a3" }]验证
从宿主机 ping 虚拟机:
ping -c 3 -W 2 192.168.122.232结果正常:
3 packets transmitted, 3 received, 0% packet lossrtt min/avg/max/mdev = 0.244/0.327/0.400/0.064 ms再看邻居表:
ip neigh show dev virbr0能看到虚拟机 MAC:
192.168.122.232 lladdr 52:54:00:fc:cd:a3 REACHABLEUFW 规则计数器也证明新规则确实被命中:
-A ufw-user-input -i virbr0 -p udp -m udp --dport 67 -j ACCEPT-A ufw-user-input -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT-A ufw-user-input -i virbr0 -p udp -m udp --dport 53 -j ACCEPT-A ufw-user-forward -i virbr0 -o enp4s0f3u1u4 -j ACCEPTlibvirt 的 NAT 规则也已经有转发计数:
ip saddr 192.168.122.0/24 iif "virbr0" counter packets 19 bytes 1444 acceptmeta l4proto udp ip saddr 192.168.122.0/24 ip daddr != 192.168.122.0/24 counter packets 19 bytes 1444 masquerade到这里,虚拟机 DHCP 和宿主到虚拟机连通性已经恢复。
关于 qemu-guest-agent
这次排查时,宿主机上查询 guest agent 返回:
QEMU 客户机代理未连接所以没有直接从宿主机执行虚拟机内部命令来验证外网访问。
这不影响本次 DHCP 问题的修复。qemu-guest-agent 更适合后续统一配置虚拟机时一起安装,例如通过 Ansible 批量做:
sudo dnf install -y qemu-guest-agentsudo systemctl enable --now qemu-guest-agent装好之后,宿主机可以这样验证:
virsh -c qemu:///system qemu-agent-command almalinux9 '{"execute":"guest-ping"}'排查顺序总结
这类 KVM/libvirt 虚拟机拿不到 192.168.122.0/24 地址的问题,可以按这个顺序查:
- 确认使用的是
qemu:///system还是 session 连接 - 检查
virsh -c qemu:///system net-list --all - 检查
default网络 XML 里的 bridge、DHCP range - 检查虚拟机
domiflist是否接到network default - 检查
domifaddr --source lease和 dnsmasq 租约文件 - 检查宿主机防火墙,尤其是 UFW/firewalld 是否拦截
virbr0 - 放行
virbr0上的 DHCP/DNS 和必要转发 - 重新触发虚拟机 DHCP
- 用
ping、ip neigh、租约文件和防火墙计数器验证
这次最容易误判的点是:virbr0 存在、default 网络活动、虚拟机也接到了 default,看起来 libvirt 侧一切正常。
但只要宿主机防火墙拦住了 virbr0 的 DHCP 请求,虚拟机依旧拿不到地址。排查时不要只看 libvirt,也要把宿主机防火墙纳入链路里。