Re: Using NFT for dynamic per-packet traffic load balancing

"Kerin Millar" <[email protected]> Wed, 17 Jun 2026 22:05:48 +0100
Newsgroups gmane.comp.security.firewalls.netfilter.general
Message-ID <[email protected]>
Hi,

On Tue, 14 Apr 2026, at 5:11 PM, Tomas Mudrunka wrote:
> Hello
> I was experimenting with using NFT to balance traffic between several 
> links, this is what i came up with:
>
> table bridge balancer {
>      chain egress_forward {
>          type filter hook forward priority filter; policy accept;
>          meta ibrname br0 drop;
>      }
> }
>
> table netdev balancer {
>      map dispatch_map {
>          typeof numgen random mod 100 : oif
>          flags interval
>          elements = {
>              0-9   : "eth0",
>              10-29 : "eth1",
>              30-99 : "eth2",
>          }
>      }
>
>      chain br_egress {
>          type filter hook egress device br0 priority filter; policy drop;
>          fwd to numgen random mod 100 map @dispatch_map
>      }
> }
>
>
> You create bridge br0 with eth0-eth2 interfaces in it.
> There is a map that sets weights of how much traffic should be directed 
> to individual links, so you can fully use multiple links even when the 
> bandwidth is not equal.
> For wireless links you can even get signal quality feedback from radio 
> hardware and live tune the map based on it. Tried up to 10 times a 
> second without any issues using following command:
>
> echo 'flush map netdev balancer dispatch_map; add element netdev 
> balancer dispatch_map { 0-32 : "eth0", 33-66 : "eth1", 67-99 : "eth2" }' 
> | sudo nft -f -
>
> Being able to dynamicaly fine tune this map from userspace in realtime 
> is killer-feature of this approach and cannot be achieved by regular 
> linux interface bonding (link aggregation). While it is very important 
> for me, i agree this is quite niche requirement.
>
> The nft ruleset prevents bridge from actualy forwarding between ports, 
> so each frame is sent only via single randomly selected interface. 
> Received packets are not forwarded anywhere except for local ip stack.
>
> I know this setup is still quite rough, but i wonder if there is 
> something that can be improved. It seems to be really interresting 
> topic.

It is interesting. What makes it rough is that it is an implementation of per-packet load balancing. It might work well enough for many short-lived TCP flows in aggregate, or traffic that is tolerant to reordering (various UDP-based protocols). Otherwise, it might under-perform.

Given that, I would suggest keeping an eye on your reordering/retransmit stats with ss -ti.

>
> Maybe i don't like using "fwd to" and should just drop frames on all 
> interfaces except for one. because right now i cannot see egress frames 
> on br0 using wireshark, only ingress.

Even so, the use of "fwd to" is probably for the best.

>
> Maybe i can ditch bridge completely and use dummy interface instead? I 
> am not sure what is cleanest way. i want to be able to use wireshark to 
> see all frames going through the load balancing on single interface, 
> which is not really happening right now. but i can observe individual 
> interfaces, which is not as useful, but allowed me to verify the 
> ballancing works properly and follows weight ratio defined in the map.

A dummy interface wouldn't pair with "fwd to". If you wish to dispense with the bridging aspect, a netdev ingress hook might work.

You could make use of counters in order to track the distribution.

meta mark set numgen random mod 100
meta mark 0-9   counter fwd to "eth0"
meta mark 10-29 counter fwd to "eth1"
meta mark 30-99 counter fwd to "eth2"

-- 
Kerin Millar