On Tue, Aug 11, 2026 at 01:57:01PM +0200, Dion Bosschieter wrote:
Resolves issue: https://gitlab.com/libvirt/libvirt/-/issues/603 Benchmarks showed that the amount of iifname jumps for each interface is the cause for this. Switched the nftables driver towards a vmap (verdict map) so we can have 1 rule that jumps to the correct root input/output chain per interface. Which improves throughput as when the number of interface check and jump rules increases the throughput decreases. The issue describes the interface matching works using the interface name and the majority of the effort is the strncpy, this commit also switches nftables to an interface_index compare instead. However, just using the interface_index is not enough, the amount of oif and iif jump rules causes quite the performance degradation, the vmap instead solves this.
Split rules into separate tables: "libvirt_nwfilter_ethernet" and "libvirt_nwfilter_inet" to preserve existing ebip firewall behavior.
Reworked chain logic for clarity with root -input/-output chains per interface. input in the VM interface is filtered in the -input chain(s), output out of the VM inteface is filtered in the -output chain(s). Ethernet table has support for subchains where the inet table puts everything in one chain. This is for compatibility reasons with how nwfilter ebiptables behaved, so that users may switch to nftables and expect the same firewalling behavior.
Stuck with two tables for compatibility reasons with ebiptables. Unifying into a single table would break user firewall definitions, which depend on being able to accept traffic at the Ethernet layer (currently defined via ebtables) and apply additional filtering via IP rules (currently defined via ip(6)tables). The nwfilter_nftables_driver splits the ethernet and inet rules in seperate tables "libvirt_nwfilter_ethernet" and "libvirt_nwfilter_inet", for above mentioned compatibility reasons.
Simplified conntrack handling: rules with accept+conntrack are duplicated to the opposite chain for symmetric behavior, to support the existing ebiptables logic.
Firewall updates continue to use tmp names for atomic replacement.
Unsupported nwfilter features (for now): - IPSets (potential future support via nft sets) - rejects (not allowed in bridge table and POST/PREROUTING hook)
Signed-off-by: Dion Bosschieter <dionbosschieter@gmail.com> --- po/POTFILES | 1 + src/nwfilter/meson.build | 1 + src/nwfilter/nwfilter_nftables_driver.c | 2876 +++++++++++++++++++++++ src/nwfilter/nwfilter_nftables_driver.h | 28 + 4 files changed, 2906 insertions(+) create mode 100644 src/nwfilter/nwfilter_nftables_driver.c create mode 100644 src/nwfilter/nwfilter_nftables_driver.h +static void nftablesCreateSameIPSet(virFirewall *fw, + virFirewallLayer layer, + const char *tableName) +{ + virFirewallCmd *fwrule = NULL; + size_t i, j; + virFirewallAddCmd(fw, layer, "add", "set", "bridge", tableName, + SAME_IP_SET_NAME, "{ type ipv4_addr . ipv4_addr; }", NULL); + + fwrule = virFirewallAddCmd(fw, layer, "add", "element", "bridge", + tableName, SAME_IP_SET_NAME, "{", NULL); + + for (i = 1; i <= 4; i++) { + for (j = 0; j < 256; j++) { + virFirewallCmdAddArgFormat(fw, fwrule, "%ld.%ld.%ld.%ld", + i == 1 ? j : 0, i == 2 ? j : 0, + i == 3 ? j : 0, i == 4 ? j : 0); + virFirewallCmdAddArg(fw, fwrule, "."); + virFirewallCmdAddArgFormat(fw, fwrule, "%ld.%ld.%ld.%ld",
These needed changing to %zu to match the 'size_t' type to fix the 32-bit platform build.
+ i == 1 ? j : 0, i == 2 ? j : 0, + i == 3 ? j : 0, i == 4 ? j : 0); + virFirewallCmdAddArg(fw, fwrule, ","); + } + } + + virFirewallCmdAddArg(fw, fwrule, "}"); +}
With regards, Daniel -- |: https://berrange.com ~~ https://hachyderm.io/@berrange :| |: https://libvirt.org ~~ https://entangle-photo.org :| |: https://pixelfed.art/berrange ~~ https://fstop138.berrange.com :|