Introduction
This document is between a dirty howto and a cheat sheet. For a short description of some interesting nftables features, you can read Why you will love nftables.
For a description of architecture and ideas behind Nftables, please read the announce of the first release of nftables.
For more global information, you can also watch the talk I’ve made at Kernel Recipes: Eric Leblond, OISF – Nftables.
Building nftables
Libraries
The following libraries are needed
- libmnl: git://git.netfilter.org/libmnl
- libnftnl: git://git.netfilter.org/libnftnl
It is possible that your distribution already include libmnl. But it is easy to build both libraries as
they build with the standard:
./autogen.sh ./configure make make install ldconfig
nftables
First install dependencies:
aptitude install libgmp-dev libreadline-dev
If you want to build the documentation:
aptitude install docbook2x docbook-utils
git clone git://git.netfilter.org/nftables cd nftables ./autogen.sh ./configure make make install
kernel
If you do not have already a Linux git tree, run:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
If you already have a Linux git tree, you can just update to latest sources
cd linux git pull --rebase
Now that you have the source, you can choose nftables option:
$ make oldconfig Netfilter Xtables support (required for ip_tables) (NETFILTER_XTABLES) [M/y/?] m Netfilter nf_tables support (NF_TABLES) [N/m] (NEW) m Netfilter nf_tables payload module (NFT_PAYLOAD) [N/m] (NEW) m Netfilter nf_tables IPv6 exthdr module (NFT_EXTHDR) [N/m] (NEW) m Netfilter nf_tables meta module (NFT_META) [N/m] (NEW) m Netfilter nf_tables conntrack module (NFT_CT) [N/m] (NEW) m Netfilter nf_tables rbtree set module (NFT_RBTREE) [N/m] (NEW) m Netfilter nf_tables hash set module (NFT_HASH) [N/m] (NEW) m Netfilter nf_tables counter module (NFT_COUNTER) [N/m] (NEW) m Netfilter nf_tables log module (NFT_LOG) [N/m] (NEW) m Netfilter nf_tables limit module (NFT_LIMIT) [N/m] (NEW) m Netfilter nf_tables nat module (NFT_NAT) [N/m] (NEW) m Netfilter x_tables over nf_tables module (NFT_COMPAT) [N/m/?] (NEW) m IPv4 nf_tables support (NF_TABLES_IPV4) [N/m] (NEW) m nf_tables IPv4 reject support (NFT_REJECT_IPV4) [N/m] (NEW) m IPv4 nf_tables route chain support (NFT_CHAIN_ROUTE_IPV4) [N/m] (NEW) m IPv4 nf_tables nat chain support (NFT_CHAIN_NAT_IPV4) [N/m] (NEW) m IPv6 nf_tables support (NF_TABLES_IPV6) [M/n] m IPv6 nf_tables route chain support (NFT_CHAIN_ROUTE_IPV6) [M/n] m IPv6 nf_tables nat chain support (NFT_CHAIN_NAT_IPV6) [M/n] m Ethernet Bridge nf_tables support (NF_TABLES_BRIDGE) [N/m/y] (NEW) m
Now, you can build your kernel with the usual commands.
On a debian, you can do on a dual core machine:
make -j 2 deb-pkg
Or you can alternately use the old method:
CONCURRENCY_LEVEL=2 make-kpkg --revision 0.1 --rootcmd fakeroot --initrd --append-to-version nftables kernel_image kernel_headers
Debian users can also get kernel build from git sources:
Other related packages are available in this directory.
Running it
Initial setup
To get a iptables like chain setup, use the ipv4-filter file provided in the source
nft -f files/nftables/ipv4-filter
You can then list the resulting chain:
nft list table filter
Note that filter as well as output or input are used as chain and table name. Any other string could have been used.
Basic rule handling
To drop output to a destination
nft add rule ip filter output ip daddr 1.2.3.4 drop
Rule counters are optional with nftables and the counter keyword need to be used to activate it:
nft add rule ip filter output ip daddr 1.2.3.4 counter drop
To add a rule to a network, you can directly use:
nft add rule ip filter output ip daddr 192.168.1.0/24 counter
To drop packet to port 80 the syntax is the following:
nft add rule ip filter input tcp dport 80 drop
To accept ICMP echo request:
nft add rule filter input icmp type echo-request accept
To combine filtering, you just have to specify multiple time the ip syntax:
nft add rule ip filter output ip protocol icmp ip daddr 1.2.3.4 counter drop
To delete all rules in a chain:
nft delete rule filter output
To delete one specific rule, you need to use the -a flag on nft to get the handle number:
# nft list table filter -a table filter { chain output { ip protocol icmp ip daddr 1.2.3.4 counter packets 5 bytes 420 drop # handle 10 ...
You can then delete rule 10 with:
nft delete rule filter output handle 10
You can also flush the filter table:
nft flush table filter
It is possible to insert a rule:
nft insert rule filter input tcp dport 80 counter accept
It is possible to insert or add a rule at a specific position. To do so you need to get the handle of the rule where you want to insert or add a new one.
This is done by using the -a flag in the list operation:
# nft list table filter -n -a table filter { chain output { type filter hook output priority 0; ip protocol tcp counter packets 82 bytes 9680 # handle 8 ip saddr 127.0.0.1 ip daddr 127.0.0.6 drop # handle 7 } } # nft add rule filter output position 8 ip daddr 127.0.0.8 drop # nft list table filter -n -a table filter { chain output { type filter hook output priority 0; ip protocol tcp counter packets 190 bytes 21908 # handle 8 ip daddr 127.0.0.8 drop # handle 10 ip saddr 127.0.0.1 ip daddr 127.0.0.6 drop # handle 7 } }
Here, we’ve added a rule after the rule with handle 8. To add before the rule with a given handle, you can use:
nft insert rule filter output position 8 ip daddr 127.0.0.12 drop
If you only want to match on a protocol, you can use something like:
nft insert rule filter output ip protocol tcp counter
IPv6
Like for IPv4, you need to create some chains. For that you can use:
nft -f files/nftables/ipv6-filter
You can then add rule:
nft add rule ip6 filter output ip6 daddr home.regit.org counter
The listing of the rules can be made with:
nft list table ip6 filter
To accept dynamic IPv6 configuration and neighbor discovery, one can use:
nft add rule ip6 filter input icmpv6 type nd-neighbor-solicit accept nft add rule ip6 filter input icmpv6 type nd-router-advert accept
Connection tracking
To accept all incoming packets of an established connection:nft ins
nft insert rule filter input ct state established accept
Filter on interface
To accept all packets going out on loopback interface:
nft insert rule filter output oif lo accept
And for packet coming in on eth2:
nft insert rule filter input iif eth2 accept
Please note that oif is in reality a match on the integer which is the index of the interface inside of the kernel. Userspace is converting the given name to the interface index when the nft rule is evaluated (before being sent to kernel). A consequence of this is that the rule can not be added if the interface does not exist.
An other consequence, is that if the interface is removed and created again, the match will not occur as the index of added interfaces in kernel is monotonically increasing. Thus, oif is a fast filter but it can lead to some issues when dynamic interfaces are used.
It is possible to do a filter on interface name but it has a performance cost because a string match is done instead of an integer match. To do a filter on interface name, one has to use oifname:
nft insert rule filter input oifname ppp0 accept
Logging
Logging is made via a log
keyword. A typical log and accept rule will look like:
nft add rule filter input tcp dport 22 ct state new log prefix \"SSH for ever\" group 2 accept
With nftables, it is possible to do in one rule what was split in two with iptables (NFLOG and ACCEPT).
If the prefix
is just the standard prefix option, the group
option is containing the nfnetlink_log group if this mode is used as logging framework.
In fact, logging in nftables is using the Netfilter logging framework. This means the logging is depending on the loaded kernel module.
Kernel module available are:
- xt_LOG: printk based logging, outputting everything to syslog (same module as the one used for iptables LOG target)
- nfnetlink_log: netlink based logging requiring to setup ulogd2 to get the events (same module as the one used for iptables NFLOG target)
To use one of the two modules, load them with modprobe.
You can then setup logging on a per-protocol basis. The configuration is available in /proc:
# cat /proc/net/netfilter/nf_log 0 NONE (nfnetlink_log) 1 NONE (nfnetlink_log) 2 nfnetlink_log (nfnetlink_log,ipt_LOG) 3 NONE (nfnetlink_log) 4 NONE (nfnetlink_log) 5 NONE (nfnetlink_log) 6 NONE (nfnetlink_log) 7 nfnetlink_log (nfnetlink_log) 8 NONE (nfnetlink_log) 9 NONE (nfnetlink_log) 10 nfnetlink_log (nfnetlink_log,ip6t_LOG) 11 NONE (nfnetlink_log) 12 NONE (nfnetlink_log)
Here nfnetlink_log was loaded first and ulogd was started.
For example, if you want to use ipt_LOG for IPv4 (2 in the list), you can do:
echo "ipt_LOG" >/proc/sys/net/netfilter/nf_log/2
This will active ipt_LOG for IPv4 logging:
# cat /proc/net/netfilter/nf_log 0 NONE (nfnetlink_log) 1 NONE (nfnetlink_log) 2 ipt_LOG (nfnetlink_log,ipt_LOG) 3 NONE (nfnetlink_log) 4 NONE (nfnetlink_log) 5 NONE (nfnetlink_log) 6 NONE (nfnetlink_log) 7 nfnetlink_log (nfnetlink_log) 8 NONE (nfnetlink_log) 9 NONE (nfnetlink_log) 10 nfnetlink_log (nfnetlink_log,ip6t_LOG) 11 NONE (nfnetlink_log) 12 NONE (nfnetlink_log)
If you want to do some easy testing, simply load xt_LOG module before nfnetlink_log. It will bind to IPv4 and IPv6 protocol and provide you logging.
Using one single chain
The chains are defined by user and can be arranged in any way. For example, on a single box, it is possible for example to use one single chain for input. To do so create a file onechain with:
#! nft -f table global { chain one { type filter hook input priority 0; } }
and run
nft -f onechain
You can then add rule like:
nft add rule ip global one ip daddr 192.168.0.0/24
The advantage of this setup is that Netfilter filtering will only be active for packets coming to the box.
Set
You can used non named set with the following syntax:
nft add rule ip Filter Output ip daddr {192.168.1.1, 192.168.1.4} drop
Named set can be used in a file. For example, you can create a simple file:
define ip_set = {192.168.1.2, 192.168.2.3} add rule filter output ip daddr $ip_set counter
and running:
nft -f simple
It is also possible to use named set. To declare one set containing ipv4 address:
nft add set filter ipv4_ad { type ipv4_address\;}
To add elements to the set:
nft add element filter ipv4_ad { 192.168.3.4 } nft add element filter ipv4_ad { 192.168.1.4, 192.168.1.5 }
Listing the set is done via:
nft list set filter ipv4_ad
The set can then be used in rule:
nft add rule ip filter input ip saddr @ipv4_ad drop
It is possible to remove element from an existing set:
nft delete element filter ipv4_ad { 192.168.1.5 }
and to delete a set:
nft delete set Filter myset
Mapping
Mapping are a specific type of set which behave like a dictionary. For example, it is possible to map ipv4_address to a verdict:
# nft -i nft> add map filter verdict_map { type ipv4_address : verdict; } nft> add element filter verdict_map { 1.2.3.5 : drop} nft> add element filter verdict_map { 1.2.3.4 : accept} nft> add rule filter output ip daddr vmap @verdict_map
To delete one element of a mapping, you can use the same syntax as the set operation:
nft> delete element filter verdict_map 1.2.3.5
To delete one set you can use:
nft delete set filter verdict_map
Mapping can also be used in a anonymous way:
nft add rule filter output ip daddr vmap {192.168.0.0/24 : drop, 192.168.0.1 : accept}
To list a specific mapping:
nft list set filter nat_map -n
NAT
First of all, the nat module is needed:
modprobe nft_nat
Next, you need to make the kernel aware of NAT for the protocol (here IPv4):
modprobe nft_chain_nat_ipv4
Now, we can create NAT dedicated chain:
nft add table nat nft add chain nat post { type nat hook postrouting priority 0 \; } nft add chain nat pre { type nat hook prerouting priority 0 \; }
We can now add NAT rules:
nft add rule nat post ip saddr 192.168.56.0/24 oif wlan0 snat 192.168.1.137 nft add rule nat pre udp dport 53 ip saddr 192.168.56.0/24 dnat 8.8.8.8:53
First one is NATing all trafic from 192.168.56.0/24 outgoing to wlan0 interface to the IP 192.168.1.137. Second one is redirecting all DNS trafic from 192.168.56.0/24 to the 8.8.8.8 server.
It is possible to NAT to a range of address:
nft add rule nat post ip saddr 192.168.56.0/24 oif wlan0 snat 192.168.1.137-192.168.1.140
IPv6 NAT is possible too. First, you need to load the module to declare the NAT capability for IPv6:
modprobe nft_chain_nat_ipv6
Once done, you can add rules like:
table ip6 nat { chain postrouting { type nat hook postrouting priority -150; ip6 saddr 2::/64 snat 1::3; } }
Building a basic ruleset
The following ruleset is a typical ruleset to protect one laptop in IPv4 and IPv6:
# IPv4 filtering table Filter { chain Input { type filter hook input priority 0; ct state established accept ct state related accept iif lo accept tcp dport ssh counter accept counter log drop } chain Output { type filter hook output priority 0; ct state established accept ct state related accept oif lo accept ct state new counter accept } } #IPv6 filtering table ip6 Filter { chain Input { type filter hook input priority 0; ct state established accept ct state related accept iif lo accept tcp dport ssh counter accept icmpv6 type { nd-neighbor-solicit, echo-request, nd-router-advert, nd-neighbor-advert } accept counter log drop } chain Output { type filter hook output priority 0; ct state established accept ct state related accept oif lo accept ct state new counter accept } }
You must be joking. This got pulled in?
“Building a basic ruleset”?! Wow. The direction of Linux development never ceases to amaze me.
Curious, why was the ac_cv_ hack needed on your system?
Nice write up, thank you.
A couple of questions though:
You say:
To accept all packets going out on loopback interface:
nft insert rule filter output meta iif lo accept
And for packet coming in on eth2:
nft insert rule filter input meta oif eth2 accept
Do you have iif and oif reversed here?
In section “Building a basic ruleset”, you show what I assume is a configuration file content. But it seems to show counter values. Why would counter values appear in a ruleset? Is it to make rule listing and loading compatible, like iptables-{save,restore}?
Sound like PF / IPFW syntax. Human readable 🙂
Thanks Steve Horsley,
You are right on interface iif oand oif, I’ve fixed the text.
Regarding the counters in the ruleset, it comes from the fact that the ruleset has been generated with ‘nft list table filter’ which is equivalent to -L and thus feature the counter.
Hello j.eng,
It was necessary some times ago on my debian sid but I focus on other tasks and did not try to find a good explanation. It seems that it is not necessary anymore.
Well, pf would look like this for a roughly equivalent ruleset to the basic ruleset example:
set skip on lo
block log
pass out
pass in proto tcp from any to port ssh
pass in inet6 proto icmp6 icmp6-type \
{ neighbrsol, echoreq, routeradv, neighbradv }
So definitely a step in the right direction, but could still be somewhat more compact 😉
Is it possible to write unit tests of rules? I’d like to be able to take a set of rules and simulate sending packets through them and be able to know if the packet was sent to a particular interface or dropped. This would mean my firewall rules (which I currently install with “make install”) could have a “make test”. I’d be 100% more confident in updates if I had this feature.
Hi ralfh! Thanks for the suggestion, I’ve used a non named set to have a more compact syntax.
I’ve followed the guide above but whenever I try to run nft -f I get the following error:
nft -f files/nftables/ipv4-filter
In file included from internal:0:0-0:
files/nftables/ipv4-filter:3:1-2: Error: Could not add table: Invalid argument
table filter {
^^
Any ideas why?
iptables less complicate
Hello,
Will nftables gives the possibillity to uniquely match 3rd packet during a tcp handshake ?
The syntax looks extremely verbose (like iproute2). That makes the scripts very hard (slow) to read compared to iptables scripts. Next time you invent a language, please look at existing popular programming languages and ask yourself why they have the syntax that they have.
There is a reason why no sane language looks like: “if variable N is greater than two” but rather “if (n > 2)”.
I always found that a command like ‘ip a l’ was far too long 😛
Seriously, there is now a grammar and alibrary to interact with. So if you have something better to propose you can easily write a tool to do so.
I would like to see the official documentation for library API.
It would be very helpfull for other programmers to interact with nftables.
If we will not get complete API – then it will be very hard to keep with changes in library.
Hello y0g1, the API is not yet fully stabilized. We will do our best to provide a accurate documentation following the first stable release.
@fofofofofo: For ‘iptables less compilate’, there is already Firetable.
Hello Regit, It is nice to hear that You will provide documentation with the first stable release.
Thanks for the reply.
Why “counter packets 5 bytes 5” in the input chains’ drop lines?
This is just displayed because I’ve used output from a list command to write the post. I’ve removed them.
OK, this is going to sound like a rude comment, but I think it is worth reading.
This tutorial or whatever it tries to be SUCKS. Instead of just giving somewhat random and non-usable examples, maybe you should explain the syntax and what chains and tables are, and then go deeper into nftables by examples. All this tutorial is at the moment is a complete waste of time, not just for me, but also probably for you. I mean, the purpose is to teach us some about nftables, right?
wow. what a completely unreadable howto. is that a joke?
how do i configure a default drop on all chains?
normally this should be the most important information when building firewall rules!
LOL, quoting myself at the beginning of the document: “This document is between a dirty howto and a cheat sheet.”
ok now it gets really hilarious!
Machine A got the nftables on it, with a clear ipv4_filter table.
Machine B starts to ping Machine A, and it works.
Then i fill the table with a rule to block all traffic from Machine B.
Machine B’s ping STILL WORKS. It works as long as you leave the ping command running. If you ctrl+c the running ping command, and restart it, pings are blocked.
WTF?
Hello ulukay. Can you post you ruleset before and after the modification? It looks like you’ve got a connection tracking rule in place allowing the initial exchange to go through.
That’s what i realized when was sipping my morning coffee today.
The related, established rules should be second to last, right before the default drop, and not the first rule.
I confirmed that right now. It works as it should. 😉
As a default drop rule, i put a simple
ip saddr 0.0.0.0/0 counter drop;
as last rule.
When displaying this rule with nft list table filter, i do not get the proper network mask.
ip saddr 0.0.0.0/4294967295 counter packets 16 bytes 910 drop
4294967295 is the maximum number of ipv4 adresses in the network 0.0.0.0/0.
A simple
counter packets drop
or
packets drop
does not work for me.
Error: syntax error, unexpected packets
Hello. My bad on the log drop. I made a mistake doing a quick edit yesterday. Use: “counter log drop”
looks nice so far, however I am not sure how applicable that syntax is for e.g. automated calls via bash..looks like way more work than with iptables
Are application based filters possible?
As simple example:
usr/bin/mozilla = allow outgoing dest port 80 / 443,
all others: deny outgoing dest port 80 / 443..
(And then wondering why yast/apt-get/rpm/antivirus cannot get updates any more ; ) …)
Maps look pretty broken.
Firstly, the following will not parse as input (presumably because of a cyclic reference) although
nft list table filter
produces this as apparently valid output:table ip filter {
map iface_verdict {
type ifindex : verdict
elements = { virbr0 : jump physical, wlp3s0 : jump physical, p4p1 : jump physical}
}
chain input {
type filter hook input priority 0;
ct state { related, established} accept
ip protocol icmp icmp type { echo-request} accept
ip daddr { 192.168.50.0/24, 192.168.100.0/24, 192.168.122.0/24} iif vmap @iface_verdict
reject
}
chain virtual {
}
chain physical {
}
}
Worse, if you define a verdict map with jumps in such as above, then delete a chain being used as a verdict in the map – the kernel will panic.
Finally, (this is more of an annoyance than anything), it would seem there is no way to ‘reset’ tables in nft, that is if I have a nft declaration in a file and want to re-run it I first have to flush the table, delete the map, delete all the chains then delete the table. There should be a simple way to just clear the table definitions being used so you can reapply a saved config.
Enjoy your latest NIH fumble bullshit.
I’ll keep my PF.
This work is done without goals. This new framework unable to have all features provided by iptables, so it never will be usable with serious tasks. But for non-serious tasks iptables are enough too.
Architecture will use kernel modules and userspace modules at end anyway or it will die unborn.
But what is the difference between iptables and nftables in that case? It is no difference.
I would recommend developers to find new way to spend their time instead of writing this crap.
Hello,
is it possible to build nftables with readline 5.2?
My distro does use readline-5.2 and I can’t build nftables for it.
Is it ./configure detection problem or You use some specific function from readline-6 api ?
I found the solution to use readline-5.2 on Slackware. I need to add LIBS=”-ltermcap -lgmp” to ./configure step.
There is a problem with building documentation:
LD src/nft
SUBDIR files/
SUBDIR doc/
MAN doc/nftables.8
I/O error : Attempt to load network entity http://docbook2x.sf.net/latest/xslt/man/docbook.xsl
warning: failed to load external entity “http://docbook2x.sf.net/latest/xslt/man/docbook.xsl”
cannot parse http://docbook2x.sf.net/latest/xslt/man/docbook.xsl
Unable to recognise encoding of this document at /usr/share/perl5/vendor_perl/XML/SAX/PurePerl/EncodingDetect.pm line 100.
Document requires an element [Ln: 1, Col: 0]
make[1]: *** [doc/nftables.8] Error 255
make: *** [doc] Error 2
I had to use the keyword type instead of table inside the chains. So either the scripts aren’t tested or the syntax changed.
E.g.
table ip6 filter {
chain input {
table filter hook input priority 0;
}
}
has to be
table ip6 filter {
chain input {
type filter hook input priority 0;
}
}
here (using nftables 0.099 and libnftnl 1.0).
Thanks a lot for feedback aholler. Syntax has indeed changed since initial writing. I’ve updated the document.
How do I add rules to autostart
Hello noname, this is a distribution issue. In Debain for example, you can use bootmisc.sh or a post-up command in interfaces file.
With nft, you can just do nft -f RULE_FILE to restore the ruleset so it is integrate in the system.
1. What are the defaults in nftables, before defining any rules? Is it allow everything or is it deny everything?
2. If I only want to allow http and https connections on the output chain (forget loopback interface for now,) do I just write “tcp dport {http, https} accept; ct state established accept; ct state related accept;” or do I need “ct state new accept;” also? The confusion is, do I need to just specify it’s ok for tcp ports http/https or do I need to specify anything using “new” too?
3. What are the minimum parameters I need to add under icmpv6 type {…} accept, in order to handle the dynamic dhcp and all the other connections? Since I’m using “ct state related accept” do I still need to use all these “nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert, destination-unreachable, packet-too-big, param-problem, mld-listener-query, mld-listener-report, mld-listener-reduction, echo-request, echo-reply?”
Thanks
dumb question: why do some commands end up with semicolon “;” while other don’t?
example:
type filter hook input priority 0;
ct state established accept
Hi Sorni. That’s not a dumb question. I think in most cases we should at term get rid of the “;” which is for now used to get around limitation of current parser.
Hi Sorin. It is allowing everything.