From: Mathias Dufresne <mathias.dufresne@gmail.com>
To: Florian Westphal <fw@strlen.de>
Cc: netfilter@vger.kernel.org
Subject: Fwd: [nftables] is it possible to declare multiple tables for a given family type?
Date: Sun, 1 Mar 2026 19:59:39 +0100 [thread overview]
Message-ID: <fc37db9e-39ed-4478-bb53-887156e309b2@gmail.com> (raw)
In-Reply-To: <752a3ecd-b379-4b82-8ac1-a64450e14167@gmail.com>
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
next parent reply other threads:[~2026-03-01 18:59 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <752a3ecd-b379-4b82-8ac1-a64450e14167@gmail.com>
2026-03-01 18:59 ` Mathias Dufresne [this message]
2026-03-01 23:53 ` Fwd: [nftables] is it possible to declare multiple tables for a given family type? Florian Westphal
2026-03-02 15:32 ` Mathias Dufresne
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=fc37db9e-39ed-4478-bb53-887156e309b2@gmail.com \
--to=mathias.dufresne@gmail.com \
--cc=fw@strlen.de \
--cc=netfilter@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox