#!/usr/bin/nft -f # vim:set ts=2 sw=2 et: # IPv4/IPv6 Simple & Safe firewall ruleset. # More examples in /usr/share/nftables/ and /usr/share/doc/nftables/examples/. destroy table inet filter table inet filter { chain input { type filter hook input priority filter policy drop ct state invalid drop comment "early drop of invalid connections" 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). iifname "virbr0" udp dport { 53, 67 } accept comment "libvirt: DHCP+DNS from guests" iifname "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. # Use iifname/oifname (string match) instead of iif/oif so the rules # load before libvirtd has created virbr0 at boot. iifname "virbr0" accept comment "libvirt: guest egress" oifname "virbr0" ct state established,related accept comment "libvirt: guest return" # Waydroid's NAT bridge: same pattern as libvirt. Unlike libvirt, waydroid # does NOT install its own MASQUERADE rule reliably (it tries via the # legacy iptables binary which isn't present), so we both forward-accept # here AND install MASQUERADE in the ip nat table below. iifname "waydroid0" accept comment "waydroid: guest egress" oifname "waydroid0" ct state established,related accept comment "waydroid: guest return" } } # NAT for waydroid's Android container. The waydroid-container service is # supposed to add this via iptables but ships only the iptables-legacy code # path; on a pure nftables host (no iptables-nft compat shim active) the rule # never lands. Declaring it here is deterministic and survives reloads. destroy table ip nat table ip nat { chain postrouting { type nat hook postrouting priority srcnat policy accept ip saddr 192.168.240.0/24 oifname != "waydroid0" masquerade \ comment "waydroid: MASQUERADE container egress" } }