Nowadays (using stream 2026.03) you can set ip/ipv6 source-validation both in firewall global context aswell as interface ethernet context like so:
set firewall global-options ipv6-source-validation 'strict'
set firewall global-options source-validation 'strict'
set interfaces ethernet eth0 ip source-validation strict
set interfaces ethernet eth0 ipv6 source-validation strict
Which one is which?
Like if I keep them for interface but disable them for firewall this would mean?
Or the other way around, I disable them for interface but keep them for firewall?
Or do I have to have this enabled at both places to have effect?
If the default is “no validation enabled”, you have the option of either enable it per interface, or globally for all interfaces, saving you to repeat it for every interface individually.
If local setting of interfaces override the global setting in firewall then the global setting in firewall wouldnt be needed because enabled in firewall but disabled in interfaces would mean that the feature for this interface is disabled as result.
Just confusing to have both settings at different places without any clear information on which is which in the docs.
Unless its something like if set then firewall will override interfaces. But if not set then its the interfaces who decides and if that isnt set either then the result is that this setting for this interface is disabled?
Because we have like these combos (ill ignore strict/loose for now):
firewall: not set + interfaces: not set = ?
firewall: not set + interfaces: disabled = ?
firewall: not set + interfaces: enabled = ?
firewall: disabled + interfaces: not set = ?
firewall: disabled + interfaces: disabled = ?
firewall: disabled + interfaces: enabled = ?
firewall: enabled + interfaces: not set = ?
firewall: enabled + interfaces: disabled = ?
firewall: enabled + interfaces: enabled = ?
Not mentioned but I assume that firewall setting is “disabled” by default when not set:
So this suggests that you either set it globally for all interfaces, OR for every interface indivudually as I don’t see an option that would add an “accept” before the global “drop”.
Because first we have this (firewall) who acts on vyos_global_rpfilter:
If loose than add this line, else if strict than add that line, else do nothing.
But then we also have this (interface) that acts on vyos_rpfilter iifname:
And not only that they seem to be independent as in will do double work but also for example at global doing loose for all traffic and at the same time doing strict for a specific interface will result in two checks being performed first loose and then in another chain strict?
But also that the logic as I interpret it seems a bit bogus in the interface code?
Its like:
if mode strict or loose then do nothing, else if mode strict add this line, else if mode loose add that line.
So like the two else if’s will never be reached or will they?
I filed that as a bug report or rather asked for a second evaluation specially regarding that interface code but also since it seems to do double the work.
Like if you add strict in firewall and strict in interface then it will check the packet for being strict two times?
Assuming the chain vyos_global_rpfilter is defined before vyos_rpfilter and you add a global rule (either loose or strict) it will add a drop rule to that chain, which means it will never get to the interface specific rules, they won’t do anything.
In other words, as soon as you add a global definition, the interface rules don’t do anything anymore.
At least, that is how I interpret the code with my limited knowledge.
Well the output of “sudo nft list ruleset” tells a different story.
This is with having “strict” both at firewall and interface level (and IPv6 routing being disabled), I would expect only one of them to be executed (wasting CPU-cycles) but clearly they are both being used?
Assuming this system doesn’t have any other interfaces than eth0 and eth1 (dummy? tunnels? etc), that might be an explanation, as nft processing stops when a drop is reached.
The return rules are an early exit mechanism, so if traffic comes in on either eth0 or eth1 and passes the uRPF check, then it won’t hit the global rule. The return will cause it to hit the “policy accept” default for the hook. You can see that it’s not being processed twice with the packet count delta between the first 4 rules and the jump rule in your ruleset output. Using return is odd since it should just be accept, but that is done a lot in VyOS’ nftables implementation, which I was told was carry overs from the iptables implementation.
With that said, there is room for improvement in the implementation. For instance, in your config, if traffic comes in on eth0 and passes the uRPF checks, then it has to hit 4 rules. This scales even worse if you had say 5 interfaces configured for ‘strict’. Then if traffic hits the last interface in the chain and passes checks, then it would have hit 10 rules before being accepted.
A better way would be to use a vmap for strict/loose. For example, this would only be hitting 1 rule (the VMAP call) for most of your traffic, but only 2 rules at most if traffic needs to hit the global rule:
So if you had 5 interfaces configured for strict, it’d only hit 1 rule instead of 10. You can save that config snippet to a file and load it with nft -f <name of file if you want to test it. You’ll see the counters incrementing correctly with nft list table ip rawtest. Here’s the output from one of my routers with that table configured: