From fa081c7a7dbe0ab3660f6f6d7860b5a815370c5d Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Wed, 13 May 2026 13:43:41 +0100 Subject: fix(nftables): allow DHCP/DNS and forwarding for libvirt virbr0 The host firewall has policy=drop on both input and forward chains. libvirt creates its own nftables table for virbr0 NAT, but: 1. It does not touch the input chain at all, so DHCP packets from guests (UDP/67) are dropped before reaching dnsmasq. Result: Windows guest stuck on 169.254.x APIPA forever. 2. Its forward-chain accepts have the same hook+priority as ours. In nftables, all chains at a hook+priority must accept (any drop wins), so our policy=drop would block guest egress and return traffic even though libvirt's chain explicitly accepts. Add minimal carve-outs for virbr0: DHCP+DNS in input, guest egress and return traffic in forward. --- etc/nftables.conf | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'etc') diff --git a/etc/nftables.conf b/etc/nftables.conf index 610aa7e..50bb842 100644 --- a/etc/nftables.conf +++ b/etc/nftables.conf @@ -14,11 +14,27 @@ table inet filter { ct state {established, related} accept comment "allow tracked connections" iif lo accept comment "allow from loopback" meta l4proto { icmp, icmpv6 } accept comment "allow icmp" + + # libvirt's NAT bridge: let guests reach the host's dnsmasq for DHCP+DNS. + # libvirt manages its own forward/NAT chains but does NOT touch the input + # chain, so without this rule guests get no IP (DHCP packets are dropped + # before dnsmasq sees them). + iif "virbr0" udp dport { 53, 67 } accept comment "libvirt: DHCP+DNS from guests" + iif "virbr0" tcp dport 53 accept comment "libvirt: DNS over TCP from guests" + pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited counter } chain forward { type filter hook forward priority filter policy drop + + # libvirt's NAT bridge: permit guest traffic to be forwarded. libvirt's + # own table accepts these explicitly at the same hook+priority, but with + # nftables a packet must be accepted by ALL chains at that priority, so + # our policy=drop would otherwise block all guest egress and return + # traffic. Mirror libvirt's accepts here for the default NAT bridge. + iif "virbr0" accept comment "libvirt: guest egress" + oif "virbr0" ct state established,related accept comment "libvirt: guest return" } } -- cgit v1.3.1