* Fwd: [nftables] is it possible to declare multiple tables for a given family type?
[not found] <752a3ecd-b379-4b82-8ac1-a64450e14167@gmail.com>
@ 2026-03-01 18:59 ` Mathias Dufresne
2026-03-01 23:53 ` Florian Westphal
0 siblings, 1 reply; 3+ messages in thread
From: Mathias Dufresne @ 2026-03-01 18:59 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter
Le 2026-03-01 à 14:52, Florian Westphal a écrit :
> Mathias Dufresne<mathias.dufresne@gmail.com> wrote:
>> Hi everyone,
>>
>> I'm trying to replace my very old iptables script with nftables and I'm
>> wondering if it is possible to declare several tables of the same family.
> Sure it is.
>
>> The goal would be to sort my rules among these tables...
> Why? Its awkward. In iptables its much better to place all filter rules
> in the filter table rather than spread them out over raw, mangle +
> filter.
That's the first time I read about raw tables...
> So why would you do that in nftables?
The goal would have been to have one table per service containing
everything related to that specific service and nothing else.
This simplification because the whole idea is to write a nftables rules
generator for my home firewalls. And I'm stuck on how to write the rules
(the config itself begins to please me and rather happy with the
extraction of that config, mixing it with routes to know what rules -
but not their syntax - shall be written/deployed). And with lot of
targets on each services, the result will be quickly messy to read).
It seems to me that regarding inet/ip/ip6 filtering rules, they'll have
to reside in the same table (one inet for both ip and ip6 or one for
each, ip and ip6). At the end of this mail I'll add some tests I made.
>> chain input {
>> type filter hook input priority filter; policy accept;
>> iif "eth12" ip daddr 172.16.0.1 tcp dport 22 ct state
>> new jump ipv4_log_ssh
>> oif "eth12" ip saddr 172.16.0.1 tcp sport 22 ct state
>> new jump ipv4_log_ssh
> You have lots of 'sport 22 ct state new' rules, they make no sense.
> If you already use connection tracking, why do you need statless-alike
> rule? The replies from sshd should be handled via 'ct state
> established'.
Thank you for pointing out that mistake.
>> chain forward {
>> type filter hook forward priority filter; policy accept;
>> iif "eth12" ip daddr 172.16.0.1 tcp dport 22 ct state
>> new accept
>> oif "eth12" ip saddr 172.16.0.1 tcp sport 22 ct state
>> new accept
>> }
> This entire chain has no effect whatsoever, the filter policy is accept
> so all packets are accepted, hence, the entire chain can be removed.
Thank you again, I believed a packet kept it's state and and had to pass
through all three input/forward/output chain/hook with that state.
>> chain output {
>> type filter hook output priority filter; policy accept;
>> iif "eth12" ip daddr 172.16.0.1 tcp dport 22 ct state
>> new accept
>> oif "eth12" ip saddr 172.16.0.1 tcp sport 22 ct state
>> new accept
>> }
> Same. All packets are accepted, so why not remove the entire chain?
; )
>> chain input {
>> type filter hook input priority filter; policy accept;
>> ct state { established, related } counter packets 556
>> bytes 48604 accept
>> jump ipv4_log_drop
>> }
> That makes more sense. I suggest you place your other input rules here.
>
> Just like in iptables, 'accept' in raw table just means packets
> continue to travel through the stack, you need to accept them in mangle
> and again in filter table.
Do you meant the network stack or the nftables' rules stack?
> Also, base chains (those with a line like
> 'type filter hook input priority filter; policy accept;') always cause a
> slow-down: they divert all packets into the nftables vm, so its a good idea to
> minimize the amount of times this happens per packet.
Again, I'm lost there... I'm certainly lacking the basic knowledge to
understand :/
--------------------------------------------------------------------
Here is a working single table to SSH the firewall and one LAN through
that firewall:
table ip filter_them_all {
chain ip_log_svc_administrative_services/ssh {
counter packets 0 bytes 0 log prefix "Accept ip SVC for
'administrative services/ssh': " group 2
counter packets 0 bytes 0 accept
}
chain input {
type filter hook input priority filter; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
INPUT" group 2
ct state {established,related} accept
iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct
state new jump ip_log_svc_administrative_services/ssh
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct
state new jump ip_log_svc_administrative_services/ssh
counter packets 0 bytes 0 log prefix "REFUSED INPUT packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
chain forward {
type filter hook forward priority filter; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
FORWARD" group 2
ct state {established,related} accept
# these two are working and can't be omitted
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22
log prefix "ACCEPT FORWARD for ssh" group 2
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22
accept
# these two does not work. They are NOT rejected by nft but SSH is
not working against these targets
# iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state
new log prefix "ACCEPT FORWARD for ssh" group 2
# iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state
new accept
counter packets 0 bytes 0 log prefix "REFUSED FORWARD packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
chain output {
type filter hook output priority filter; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
OUTPUT" group 2
ct state {established,related} accept
counter packets 0 bytes 0 log prefix "REFUSED OUTPUT packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
}
As said in the comments inside that table, commented lines does not
work. I would have expected they do (but my understanding of nftables is
very minimal)
Here is another test with several filter tables, playing with priority,
which does not work (I tried to use negative values for these
priorities, in case of my understanding was that bad):
table ip tracked_packets {
chain input {
type filter hook input priority 300; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
INPUT" group 2
ct state {established,related} accept
}
chain forward {
type filter hook forward priority 300; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
FORWARD" group 2
ct state {established,related} accept
}
chain output {
type filter hook output priority 300; policy accept;
ct state {established,related} counter log prefix "ACCEPT CT for
OUTPUT" group 2
ct state {established,related} accept
}
}
table ip ssh {
chain ip_log_svc_administrative_services/ssh {
counter packets 0 bytes 0 log prefix "Accept ip SVC for
'administrative services/ssh': "
counter packets 0 bytes 0 log prefix "Accept ip SVC for
'administrative services/ssh': " group 2
counter packets 0 bytes 0 accept
}
chain input {
type filter hook input priority 310; policy accept;
iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct
state new jump ip_log_svc_administrative_services/ssh
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct
state new jump ip_log_svc_administrative_services/ssh
}
chain forward {
type filter hook forward priority filter; policy accept;
# these two are working and can't be omitted
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22
log prefix "ACCEPT FORWARD for ssh" group 2
iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22
accept
# these two does not work. They are NOT rejected by nft but SSH is
not working against these targets
# iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state
new log prefix "ACCEPT FORWARD for ssh" group 2
# iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state
new accept
}
}
table ip anything_else_is_refused {
chain input {
type filter hook input priority 320; policy drop;
counter packets 0 bytes 0 log prefix "REFUSED INPUT packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
chain forward {
type filter hook forward priority 320; policy drop;
counter packets 0 bytes 0 log prefix "REFUSED FORWARD packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
chain output {
type filter hook output priority 320; policy drop;
counter packets 0 bytes 0 log prefix "REFUSED OUTPUT packet as it
reached the end of filtering rules stack." group 2
counter packets 0 bytes 0 drop
}
}
His this approach - several filtering tables - valid? I mean I could
easily have made errors in the syntax...
Sorry for all that, I'm doing my best to be clear enough ; )
Best regards,
mathais
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: Fwd: [nftables] is it possible to declare multiple tables for a given family type?
2026-03-01 18:59 ` Fwd: [nftables] is it possible to declare multiple tables for a given family type? Mathias Dufresne
@ 2026-03-01 23:53 ` Florian Westphal
2026-03-02 15:32 ` Mathias Dufresne
0 siblings, 1 reply; 3+ messages in thread
From: Florian Westphal @ 2026-03-01 23:53 UTC (permalink / raw)
To: Mathias Dufresne; +Cc: netfilter
Mathias Dufresne <mathias.dufresne@gmail.com> wrote:
> Le 2026-03-01 à 14:52, Florian Westphal a écrit :
> The goal would have been to have one table per service containing
> everything related to that specific service and nothing else.
I see. One way to do it is to include such sub-configurations
from a central ruleset file.
> > Just like in iptables, 'accept' in raw table just means packets
> > continue to travel through the stack, you need to accept them in mangle
> > and again in filter table.
> Do you meant the network stack or the nftables' rules stack?
The former. E.g. (staying with iptables, its same in nftables):
if a packet is accepted in mangle prerouting, it will continue and will
eventually make it to mangle input or forward.
> > Also, base chains (those with a line like
> > 'type filter hook input priority filter; policy accept;') always cause a
> > slow-down: they divert all packets into the nftables vm, so its a good idea to
> > minimize the amount of times this happens per packet.
>
> Again, I'm lost there... I'm certainly lacking the basic knowledge to
> understand :/
No problem. Consider an nftables ruleset that has ONLY
chain foo {
some rule ..
}
This ruleset is very fast. And useless. Because it never processes
any packets.
In iptables, this isn't possible, because all tables have
builtin-chains: PREROUTING, INPUT, and so on.
In nftables, one has to manually declare which chains are the 'starting
point' for packets passing through the networking stack.
E.g. type filter hook input priority filter; policy accept;
tells that you'd like semantics that are identical to iptables filter
INPUT chain: it will see incoming packets that have passed through the
routing step and are destined for the local host.
Makes sense so far?
This means, ALL incoming local packets will appear in this chain
(minus those that were dropped earlier on in the stack of course).
Example:
chain myinput {
type filter hook input priority filter; policy accept;
tcp dport 22 jump ssh
tcp dport { 80, 433 } jump httpd
}
...
... will be faster than making
'ssh' and 'httpd' two independent 'type filter hook input' chains.
Why? Because in the case above, all incoming packets pass though
'myinput', then only a small subset gets passed on to ssh and httpd,
respectively.
If you make the two user chains base chains ('type filter...', both reveive
all the traffic). In case you haven't tried it: See if 'nft list hooks'
provies some assistance to you.
> --------------------------------------------------------------------
>
> Here is a working single table to SSH the firewall and one LAN through
> that firewall:
>
> table ip filter_them_all {
> chain ip_log_svc_administrative_services/ssh {
> counter packets 0 bytes 0 log prefix "Accept ip SVC for
> 'administrative services/ssh': " group 2
> counter packets 0 bytes 0 accept
> }
> chain input {
> type filter hook input priority filter; policy accept;
> ct state {established,related} counter log prefix "ACCEPT CT for
> INPUT" group 2
> ct state {established,related} accept
> iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct state new jump ip_log_svc_administrative_services/ssh
> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state new jump ip_log_svc_administrative_services/ssh
> counter packets 0 bytes 0 log prefix "REFUSED INPUT packet as it reached the end of filtering rules stack." group 2
> counter packets 0 bytes 0 drop
> }
> chain forward {
> type filter hook forward priority filter; policy accept;
> ct state {established,related} counter log prefix "ACCEPT CT for > FORWARD" group 2
I'd suggest to add some rate limiting for your log lines.
> # these two are working and can't be omitted
> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 log prefix "ACCEPT FORWARD for ssh" group 2
> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 accept
If you don't want to limit, you can combine this into one line, you can
'log prefix ... accept' in nftables.
> chain output {
> type filter hook output priority filter; policy accept;
> ct state {established,related} counter log prefix "ACCEPT CT for
> OUTPUT" group 2
> ct state {established,related} accept
> counter packets 0 bytes 0 log prefix "REFUSED OUTPUT packet as it
> reached the end of filtering rules stack." group 2
> counter packets 0 bytes 0 drop
Are you sure you need output filtering? This prevents e.g. dns
resolution, ntp (time) refresh etc.
> table ip tracked_packets {
> chain input {
> type filter hook input priority 300; policy accept;
> ct state {established,related} counter log prefix "ACCEPT CT for
> INPUT" group 2
> ct state {established,related} accept
> }
> chain input {
> type filter hook input priority 310; policy accept;
> iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct
> state new jump ip_log_svc_administrative_services/ssh
> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct
> state new jump ip_log_svc_administrative_services/ssh
> }
This works, but as you found there is no 'memory' and all chains have
to accept all packets for them to make it though.
> His this approach - several filtering tables - valid? I mean I could
> easily have made errors in the syntax...
Yes, but I don't consider it a good idea, I also don't think its suited
for what you have in mind.
If this is about enabling / disabling services, I would use a set for
that.
set s {
typeof iif . oif . ip daddr . meta l4proto . th dport
flags interval
}
chain forward {
# these two are working and can't be omitted
iif . oif . ip saddr . meta l4proto . th dport @s accept
[ totally untested, for illustrative purposes ... ]
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: Fwd: [nftables] is it possible to declare multiple tables for a given family type?
2026-03-01 23:53 ` Florian Westphal
@ 2026-03-02 15:32 ` Mathias Dufresne
0 siblings, 0 replies; 3+ messages in thread
From: Mathias Dufresne @ 2026-03-02 15:32 UTC (permalink / raw)
To: Florian Westphal; +Cc: netfilter
Le 2026-03-02 à 00:53, Florian Westphal a écrit :
> Mathias Dufresne <mathias.dufresne@gmail.com> wrote:
>> Le 2026-03-01 à 14:52, Florian Westphal a écrit :
>> The goal would have been to have one table per service containing
>> everything related to that specific service and nothing else.
> I see. One way to do it is to include such sub-configurations
> from a central ruleset file.
>
>>> Just like in iptables, 'accept' in raw table just means packets
>>> continue to travel through the stack, you need to accept them in mangle
>>> and again in filter table.
>> Do you meant the network stack or the nftables' rules stack?
> The former. E.g. (staying with iptables, its same in nftables):
>
> if a packet is accepted in mangle prerouting, it will continue and will
> eventually make it to mangle input or forward.
A packet accepted in a hook is removed from that hook and it does pass
through the rules after the one it matched?
And this same packet will have to pass through the potentially remaining
hooks.
>
>>> Also, base chains (those with a line like
>>> 'type filter hook input priority filter; policy accept;') always cause a
>>> slow-down: they divert all packets into the nftables vm, so its a good idea to
>>> minimize the amount of times this happens per packet.
>> Again, I'm lost there... I'm certainly lacking the basic knowledge to
>> understand :/
> No problem. Consider an nftables ruleset that has ONLY
> chain foo {
> some rule ..
> }
>
> This ruleset is very fast. And useless. Because it never processes
> any packets.
>
> In iptables, this isn't possible, because all tables have
> builtin-chains: PREROUTING, INPUT, and so on.
>
> In nftables, one has to manually declare which chains are the 'starting
> point' for packets passing through the networking stack.
>
> E.g. type filter hook input priority filter; policy accept;
>
> tells that you'd like semantics that are identical to iptables filter
> INPUT chain: it will see incoming packets that have passed through the
> routing step and are destined for the local host.
>
> Makes sense so far?
>
> This means, ALL incoming local packets will appear in this chain
> (minus those that were dropped earlier on in the stack of course).
>
> Example:
> chain myinput {
> type filter hook input priority filter; policy accept;
>
> tcp dport 22 jump ssh
> tcp dport { 80, 433 } jump httpd
> }
> ...
>
> ... will be faster than making
> 'ssh' and 'httpd' two independent 'type filter hook input' chains.
>
> Why? Because in the case above, all incoming packets pass though
> 'myinput', then only a small subset gets passed on to ssh and httpd,
> respectively.
>
> If you make the two user chains base chains ('type filter...', both reveive
> all the traffic). In case you haven't tried it: See if 'nft list hooks'
> provies some assistance to you.
I'll try that very soon as my understanding is that a packet matching a
rule with jump is removed from the hook and passed to the jump chain. If
I understand correctly this will be an efficient way to accelerate the
filtering process while obtaining readable rules with hooks sorting
packets per service and service chains dealing with targets to accept or
refuse them. I love that : )
>> --------------------------------------------------------------------
>>
>> Here is a working single table to SSH the firewall and one LAN through
>> that firewall:
>>
>> table ip filter_them_all {
>> chain ip_log_svc_administrative_services/ssh {
>> counter packets 0 bytes 0 log prefix "Accept ip SVC for
>> 'administrative services/ssh': " group 2
>> counter packets 0 bytes 0 accept
>> }
>> chain input {
>> type filter hook input priority filter; policy accept;
>> ct state {established,related} counter log prefix "ACCEPT CT for
>> INPUT" group 2
>> ct state {established,related} accept
>> iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct state new jump ip_log_svc_administrative_services/ssh
>> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct state new jump ip_log_svc_administrative_services/ssh
>> counter packets 0 bytes 0 log prefix "REFUSED INPUT packet as it reached the end of filtering rules stack." group 2
>> counter packets 0 bytes 0 drop
>> }
>> chain forward {
>> type filter hook forward priority filter; policy accept;
>> ct state {established,related} counter log prefix "ACCEPT CT for > FORWARD" group 2
> I'd suggest to add some rate limiting for your log lines.
I'll think about it but those logs yesterday were mainly there to help
me understand what was happening with my packets.
>
>> # these two are working and can't be omitted
>> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 log prefix "ACCEPT FORWARD for ssh" group 2
>> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 accept
> If you don't want to limit, you can combine this into one line, you can
> 'log prefix ... accept' in nftables.
Did you proposed that merging for cosmetic or performance reason?
>
>> chain output {
>> type filter hook output priority filter; policy accept;
>> ct state {established,related} counter log prefix "ACCEPT CT for
>> OUTPUT" group 2
>> ct state {established,related} accept
>> counter packets 0 bytes 0 log prefix "REFUSED OUTPUT packet as it
>> reached the end of filtering rules stack." group 2
>> counter packets 0 bytes 0 drop
> Are you sure you need output filtering? This prevents e.g. dns
> resolution, ntp (time) refresh etc.
As my goal is to automate nftables rules I'll try to manage a tool
capable of filtering everything. For my home servers, most certainly I
won't...
>
>> table ip tracked_packets {
>> chain input {
>> type filter hook input priority 300; policy accept;
>> ct state {established,related} counter log prefix "ACCEPT CT for
>> INPUT" group 2
>> ct state {established,related} accept
>> }
>> chain input {
>> type filter hook input priority 310; policy accept;
>> iif vdi-lan ip daddr 10.9.9.252/32 tcp dport 22 ct
>> state new jump ip_log_svc_administrative_services/ssh
>> iif vdi-lan oif svc-web ip daddr 10.54.80.0/24 tcp dport 22 ct
>> state new jump ip_log_svc_administrative_services/ssh
>> }
> This works, but as you found there is no 'memory' and all chains have
> to accept all packets for them to make it though.
OK. Several chain using a given hook, even if a packet if matched in
early chain it will pass through the others using that same hook (as per
my understanding)
And so, in your previous example using "jump ssh", this ssh chain shall
not use hook at all (again, as per my understanding ; )
>
>> His this approach - several filtering tables - valid? I mean I could
>> easily have made errors in the syntax...
> Yes, but I don't consider it a good idea, I also don't think its suited
> for what you have in mind.
>
> If this is about enabling / disabling services, I would use a set for
> that.
>
> set s {
> typeof iif . oif . ip daddr . meta l4proto . th dport
> flags interval
> }
>
> chain forward {
> # these two are working and can't be omitted
> iif . oif . ip saddr . meta l4proto . th dport @s accept
>
> [ totally untested, for illustrative purposes ... ]
That I don't understand at all but I'll keep somewhere and I'll try to
understand... later :p
Thank you again for all these information and your time.
Best regards,
mathias
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-03-02 15:32 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <752a3ecd-b379-4b82-8ac1-a64450e14167@gmail.com>
2026-03-01 18:59 ` Fwd: [nftables] is it possible to declare multiple tables for a given family type? Mathias Dufresne
2026-03-01 23:53 ` Florian Westphal
2026-03-02 15:32 ` Mathias Dufresne
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox