On Thu, Dec 11, 2025 at 03:00:49PM +0100, Dion Bosschieter wrote:
> This series aims to implement nftables as a backend driver for
> the nwfilter feature. The idea is that eventually it will replace
> the ebiptables driver and provide an easy way for users to switch
> from one driver to another.
> 
> The first 2 patches are moving of functions and renames, meant to decouple
> nwfilter from the currently only existing ebiptables driver.
> 
> The 3rd patch introduces the new nwfilter driver. After which nwfilter allows
> users to choose it in the 4th patch.
> 
> The last patch introduces unit testing of the new nftables driver.
> 

> - Resolves issue https://gitlab.com/libvirt/libvirt/-/issues/603
>   benchmarks showed that the amount of iifname jumps for each
>   interface with 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 a performance issue,
>   the vmap instead solves this.

That's good.

> - Split rules into separate tables: "libvirt-nwfilter-ethernet" and
>   "libvirt-nwfilter-other" to preserve existing firewall behavior.

> - Stuck with prerouting and postrouting as hooks for input / output
>   on the -ethernet and -other table. This makes it easier to merge
>   the tables in the future. Saving management of two tables and
>   decreasing the amount of tables a packet sees. Currently ebtables
>   filtering happens via PREROUTING and POSTROUTING hooks, while
>   ip/ip6tables filtering happens in the output/forward hooks.
> - Stuck with 2 tables for compatibility reasons with eb iptables,
>   unifying into 1 table will break users firewall definitions, which
>   depend on being able to do accepts on ethernet rules
>   (which currently get defined via ebtables) and additional filtering
>   via the ip rules (which currently get defined via ip(6)tables).
>   The nwfilter_nftables_driver keeps splitting the ethernet and
>   non ethernet (other) rules in seperate tables
>   "libvirt-nwfilter-ethernet" and "libvirt-nwfilter-other".

I guess with xtables, we would have effectively three - ebtables,
iptables and ip6tables. "other" here covers both iptables and
ip6tables which is fine because rules for those are mutually
exclusive for any single packet.  Perhaps call it "-inet" instead
of '-other'  ? 



> Unsupported nwfilter features (for now):
> - STP filtering
> - Gratuitous ARP filtering
> - IPSets (potential future support via nft sets)
> - Reject due to filtering in pre/postrouting, using drop instead
>   of reject, copying logic from existing ebiptables ebtables actions

I don't know if it is related, but when I tried this patch series
on an existing VM configured with the "clean-traffic" filter I
get a failure:


  2026-01-16 16:24:14.492+0000: 749946: error : virFirewallCmdNftablesApply:748 
: internal error: Failed to apply firewall command 'nft add rule bridge 
libvirt-nwfilter-ethernet n-vnet0-rarp-out ether saddr == 52:54:00:5e:58:55 
ether daddr == ff:ff:ff:ff:ff:ff ether type 0x8035 'arp operation' 3 arp saddr 
ip 0.0.0.0/32 arp daddr ip 0.0.0.0/32 'ether saddr' 52:54:00:5e:58:55 'ether 
daddr' 52:54:00:5e:58:55 accept comment '"priority=500"'': Error: conflicting 
statements


  nft add rule bridge libvirt-nwfilter-ethernet n-vnet0-rarp-out \
     ether saddr == 52:54:00:5e:58:55 \
     ether daddr == ff:ff:ff:ff:ff:ff \
     ether type 0x8035 \
     'arp operation' 3 \
     arp saddr ip 0.0.0.0/32 \
     arp daddr ip 0.0.0.0/32 \
     'ether saddr' 52:54:00:5e:58:55 \
     'ether daddr' 52:54:00:5e:58:55 \
     accept comment '"priority=500"'


We seem to have generated the MAC address matches twice resulting
in the 'conflicting statements' message.

Also those extra single quotes are a sign of a bug in passing the
args. Two nft args are being passed as a single argv element.

Interestingly that didn't seem to cause any problem, making me
think nft is munging all argv back to a single string and then
parsing it.

The following would fix it

diff --git a/src/nwfilter/nwfilter_nftables_driver.c 
b/src/nwfilter/nwfilter_nftables_driver.c
index 36a6c63f22..0ccb765a4e 100644
--- a/src/nwfilter/nwfilter_nftables_driver.c
+++ b/src/nwfilter/nwfilter_nftables_driver.c
@@ -460,7 +460,8 @@ insertRuleArg2Param(virFirewall *fw,
                     virNWFilterVarCombIter *vars,
                     nwItemDesc *itemLow,
                     nwItemDesc *itemHigh,
-                    const char *argument,
+                    const char *argument1,
+                    const char *argument2,
                     const char *seperator)
 {
     char field[VIR_INT64_STR_BUFLEN];
@@ -471,7 +472,8 @@ insertRuleArg2Param(virFirewall *fw,
                           field, sizeof(field),
                           itemLow) < 0)
             return -1;
-        virFirewallCmdAddArg(fw, fwrule, argument);
+        virFirewallCmdAddArg(fw, fwrule, argument1);
+        virFirewallCmdAddArg(fw, fwrule, argument2);
         if (ENTRY_WANT_NEG_SIGN(itemLow))
             virFirewallCmdAddArg(fw, fwrule, "!=");
         if (HAS_ENTRY_ITEM(itemHigh)) {
@@ -497,21 +499,15 @@ nftablesHandlePortData(virFirewall *fw,
                        portDataDef *portData,
                        bool reverseRule)
 {
-    char dport[VIR_INT64_STR_BUFLEN];
-    char sport[VIR_INT64_STR_BUFLEN];
-
-    g_snprintf(dport, sizeof(dport), reverseRule ? "%s sport" : "%s dport",
-               protocol);
-    g_snprintf(sport, sizeof(sport), reverseRule ? "%s dport": "%s sport",
-               protocol);
-
     if (insertRuleArg2Param(fw, fwrule, vars,
                             &portData->dataDstPortStart,
-                            &portData->dataDstPortEnd, dport, "-") < 0)
+                            &portData->dataDstPortEnd, protocol,
+                            reverseRule ? "sport" : "dport", "-") < 0)
         return -1;
     if (insertRuleArg2Param(fw, fwrule, vars,
                             &portData->dataSrcPortStart,
-                            &portData->dataSrcPortEnd, sport, "-") < 0)
+                            &portData->dataSrcPortEnd, protocol,
+                            reverseRule ? "dport" : "sport", "-") < 0)
         return -1;
 
     return 0;
@@ -522,7 +518,8 @@ nftablesHandleMacAddr(virFirewall *fw,
                       virFirewallCmd *fwrule,
                       virNWFilterVarCombIter *vars,
                       nwItemDesc *macaddr,
-                      const char *argument)
+                      const char *argument1,
+                      const char *argument2)
 {
     char macstr[VIR_MAC_STRING_BUFLEN];
 
@@ -532,7 +529,8 @@ nftablesHandleMacAddr(virFirewall *fw,
                           macaddr) < 0)
             return -1;
 
-        virFirewallCmdAddArg(fw, fwrule, argument);
+        virFirewallCmdAddArg(fw, fwrule, argument1);
+        virFirewallCmdAddArg(fw, fwrule, argument2);
         if (ENTRY_WANT_NEG_SIGN(macaddr))
             virFirewallCmdAddArg(fw, fwrule, "!=");
         virFirewallCmdAddArg(fw, fwrule, macstr);
@@ -547,7 +545,7 @@ nftablesHandleSrcMacAddr(virFirewall *fw,
                          virNWFilterVarCombIter *vars,
                          nwItemDesc *srcMacAddr)
 {
-    return nftablesHandleMacAddr(fw, fwrule, vars, srcMacAddr, "ether saddr");
+    return nftablesHandleMacAddr(fw, fwrule, vars, srcMacAddr, "ether", 
"saddr");
 }
 
 static void
@@ -893,7 +891,8 @@ insertRuleArgParam(virFirewall *fw,
                     virFirewallCmd *fwrule,
                     virNWFilterVarCombIter *vars,
                     nwItemDesc *item,
-                    const char *argument)
+                   const char *argument1,
+                   const char *argument2)
 {
     char field[VIR_INT64_STR_BUFLEN];
 
@@ -902,7 +901,8 @@ insertRuleArgParam(virFirewall *fw,
                           field, sizeof(field),
                           item) < 0)
             return -1;
-        virFirewallCmdAddArg(fw, fwrule, argument);
+        virFirewallCmdAddArg(fw, fwrule, argument1);
+        virFirewallCmdAddArg(fw, fwrule, argument2);
         if (ENTRY_WANT_NEG_SIGN(item))
             virFirewallCmdAddArg(fw, fwrule, "!=");
 
@@ -917,7 +917,8 @@ insertRuleArgParamHex(virFirewall *fw,
                       virFirewallCmd *fwrule,
                       virNWFilterVarCombIter *vars,
                       nwItemDesc *item,
-                      const char *argument)
+                      const char *argument1,
+                      const char *argument2)
 {
     char field[VIR_INT64_STR_BUFLEN];
 
@@ -926,7 +927,8 @@ insertRuleArgParamHex(virFirewall *fw,
                           field, sizeof(field),
                           item) < 0)
             return -1;
-        virFirewallCmdAddArg(fw, fwrule, argument);
+        virFirewallCmdAddArg(fw, fwrule, argument1);
+        virFirewallCmdAddArg(fw, fwrule, argument2);
         if (ENTRY_WANT_NEG_SIGN(item))
             virFirewallCmdAddArg(fw, fwrule, "!=");
 
@@ -971,7 +973,7 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (insertRuleArgParamHex(fw, fwrule, vars,
                                   &rule->p.ethHdrFilter.dataProtocolID,
-                                  "ether type") < 0)
+                                  "ether", "type") < 0)
             return -1;
         break;
     case VIR_NWFILTER_RULE_PROTOCOL_IP:
@@ -1031,21 +1033,21 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.ipHdrFilter.ipHdr.dataProtocolID,
-                               "ip protocol") < 0)
+                               "ip", "protocol") < 0)
             return -1;
         if (insertRuleArg2Param(fw, fwrule, vars,
                                 &rule->p.ipHdrFilter.portData.dataSrcPortStart,
                                 &rule->p.ipHdrFilter.portData.dataSrcPortEnd,
-                                reverseRule ? "th dport" : "th sport", "-") < 
0)
+                                "th", reverseRule ? "dport" : "sport", "-") < 
0)
             return -1;
         if (insertRuleArg2Param(fw, fwrule, vars,
                                 &rule->p.ipHdrFilter.portData.dataDstPortStart,
                                 &rule->p.ipHdrFilter.portData.dataDstPortEnd,
-                                reverseRule ? "th sport" : "th dport", "-") < 
0)
+                                "th", reverseRule ? "sport" : "dport", "-") < 
0)
             return -1;
         if (insertRuleArgParamHex(fw, fwrule, vars,
                                   &rule->p.ipHdrFilter.ipHdr.dataDSCP,
-                                  "ip dscp") < 0)
+                                  "ip", "dscp") < 0)
             return -1;
         break;
     case VIR_NWFILTER_RULE_PROTOCOL_ARP:
@@ -1063,15 +1065,15 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.arpHdrFilter.dataHWType,
-                               "arp htype") < 0)
+                               "arp", "htype") < 0)
             return -1;
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.arpHdrFilter.dataOpcode,
-                               "arp operation") < 0)
+                               "arp", "operation") < 0)
             return -1;
         if (insertRuleArgParamHex(fw, fwrule, vars,
                                   &rule->p.arpHdrFilter.dataProtocolType,
-                                  "arp ptype") < 0)
+                                  "arp", "ptype") < 0)
             return -1;
 
         if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcIPAddr)) {
@@ -1118,11 +1120,11 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (nftablesHandleMacAddr(fw, fwrule, vars,
                                   &rule->p.arpHdrFilter.dataARPSrcMACAddr,
-                                  reverseRule ? "ether daddr": "ether saddr") 
< 0)
+                                  "ether", reverseRule ? "addr": "saddr") < 0)
             return -1;
         if (nftablesHandleMacAddr(fw, fwrule, vars,
                                   &rule->p.arpHdrFilter.dataARPDstMACAddr,
-                                  reverseRule ? "ether saddr": "ether daddr") 
< 0)
+                                  "ether", reverseRule ? "saddr": "daddr") < 0)
             return -1;
 
         if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataGratuitousARP) &&
@@ -1187,28 +1189,28 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.ipv6HdrFilter.ipHdr.dataProtocolID,
-                               "ip6 nexthdr") < 0)
+                               "ip6", "nexthdr") < 0)
             return -1;
         if (insertRuleArg2Param(fw, fwrule, vars,
                                 
&rule->p.ipv6HdrFilter.portData.dataSrcPortStart,
                                 &rule->p.ipv6HdrFilter.portData.dataSrcPortEnd,
-                                reverseRule ? "th dport" : "th sport", "-") < 
0)
+                                "th", reverseRule ? "dport" : "sport", "-") < 
0)
             return -1;
         if (insertRuleArg2Param(fw, fwrule, vars,
                                 
&rule->p.ipv6HdrFilter.portData.dataDstPortStart,
                                 &rule->p.ipv6HdrFilter.portData.dataDstPortEnd,
-                                reverseRule ? "th sport" : "th dport", "-") < 
0)
+                                "th", reverseRule ? "sport" : "dport", "-") < 
0)
             return -1;
         if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPTypeStart)  ||
             HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPCodeStart)) {
 
             if (insertRuleArgParam(fw, fwrule, vars,
                                    &rule->p.ipv6HdrFilter.dataICMPTypeStart,
-                                   "icmpv6 type") < 0)
+                                   "icmpv6", "type") < 0)
                 return -1;
             if (insertRuleArgParam(fw, fwrule, vars,
                                    &rule->p.ipv6HdrFilter.dataICMPCodeStart,
-                                   "icmpv6 code") < 0)
+                                   "icmpv6", "code") < 0)
                 return -1;
         }
         break;
@@ -1222,11 +1224,11 @@ nftablesHandleEthernetRule(virFirewall *fw,
 
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.vlanHdrFilter.dataVlanID,
-                               "vlan id") < 0)
+                               "vlan", "id") < 0)
             return -1;
         if (insertRuleArgParam(fw, fwrule, vars,
                                &rule->p.vlanHdrFilter.dataVlanEncap,
-                               "vlan type") < 0)
+                               "vlan", "type") < 0)
             return -1;
         break;
     case VIR_NWFILTER_RULE_PROTOCOL_STP:


With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

Reply via email to