In Red Hat Enterprise Linux 8 the preferred low level firewall solution is nftables. This post is an introduction to using nftables. This is most relevant for system administrators and DevOps practitioners. Where it makes sense we will highlight differences between nftables and its predecessor iptables.
Firstly, it must be stated that nftables is both a userland utility, nft, and a kernel subsystem. Inside the kernel it builds upon the kernel’s netfilter subsystem. In this post we will focus on the user facing nft utility.
This post includes command examples that the reader can follow along with on a test machine. This may be useful to gain a better understanding of the content.
Note: In this post some of the command output may be long. As such, we ellipse ... the irrelevant or unimportant parts of output.
Read more about optimizing performance for the open-hybrid enterprise.
Getting Started
So what does the default setup of nftables look like? Let’s find out by listing the entire rule set.
# nft list ruleset
Which results in... nothing. Well, that’s not very exciting. What gives?
By default, nftables does not pre-create tables and chains like its predecessor iptables. An empty ruleset has zero resource cost. Compare that to iptables where each pre-created table and chain must be considered and base packet counters incremented—even if they’re empty.
Creating Tables
In nftables you need to manually create tables. Tables need to qualify a family; ip, ip6, inet, arp, bridge, or netdev. inet means the table will process both ipv4 and ipv6 packets. It’s the family we’ll use throughout this post.
Note: For those coming from iptables, the term table may be a bit confusing. In nftables a table is simply a namespace—nothing more, nothing less. It’s a collection of chains, rules, and sets, and other objects.
Let’s create our first table and list the rule set.
# nft add table inet my_table
# nft list ruleset
table inet my_table {
}
So now we have a table, but by itself it won’t do much. Let’s move on to chains.
Creating Chains
Chains are the objects that will contain our firewall rules.
Just like tables, chains need to be explicitly created. When creating the chain you need to specify what table the chain belongs to as well as the type, the hook, and the priority. For this introduction we’ll keep things simple by using filter, input, and priority 0 to filter packets destined to the host.
# nft add chain inet my_table my_filter_chain { type filter hook input priority 0 \; }
Note: The backslash (\) is necessary so the shell doesn’t interpret the semicolon as the end of the command.
Chains can also be created without specifying a hook. Chains created this way are equivalent to iptables user defined chains. Rules can use the jump or goto statements to execute rules in the chain. This is useful to logically separate rules or to share a subset of rules that would otherwise be duplicated.
# nft add chain inet my_table my_utility_chain
Creating Rules
Now that you’ve created a table and a chain you can finally add some firewall rules. Let’s add a rule to accept SSH.
# nft add rule inet my_table my_filter_chain tcp dport ssh accept
One thing to note here is that since we added this to a table of the inet family a single rule will process both IPv4 and IPv6 packets.
The add
verb will append the rule to the end of the chain. You can also use the insert
verb which will prepend the rule to the head of the chain.
# nft insert rule inet my_table my_filter_chain tcp dport http accept
Having added two rules, let’s look at what the ruleset looks like.
# nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport ssh accept
}
}
Note that the http
rule occurs before the ssh
rule because we used the insert
verb above.
You can also add a rule at an arbitrary location in a chain. There are two ways to do this.
-
Use
index
to specify an index into the list of rules. Usingadd
will insert the new rule after the rule at the given index. Usinginsert
will insert the new rule before the rule at the given index.index
values start at 0.
# nft insert rule inet my_table my_filter_chain index 1 tcp dport nfs accept
# nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
}
}
# nft add rule inet my_table my_filter_chain index 0 tcp dport 1234 accept
# nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport 1234 accept
tcp dport nfs accept
tcp dport ssh accept
}
}
Note: Using index
with the insert
verb is mostly equivalent to iptables -I
option with an index. The first caveat is nftables index values start at 0. The second caveat is index has to refer to an existing rule. This means “nft insert rule … index 0” on an empty chain in invalid.
-
Use
handle
to specify the rule to insert the rule after or before. To insert after use theadd
verb. To insert before use theinsert
verb. You can get rule handles with the–handle
option when listing rules.
# nft --handle list ruleset
table inet my_table { # handle 21
chain my_filter_chain { # handle 1
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 3
tcp dport ssh accept # handle 2
}
}
# nft add rule inet my_table my_filter_chain handle 3 tcp dport 1234 accept
# nft insert rule inet my_table my_filter_chain handle 2 tcp dport nfs accept
# nft --handle list ruleset
table inet my_table { # handle 21
chain my_filter_chain { # handle 1
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 3
tcp dport 1234 accept # handle 8
tcp dport nfs accept # handle 7
tcp dport ssh accept # handle 2
}
}
In nftables a rule handle is stable and will not change until the rule is deleted. This gives a stable reference to the rule without having to rely on an index
, which may change if another rule is inserted.
You can also get the rule handle at the time of creation by using both the –echo
and –handle
options. The rule will be echoed back to the CLI with the handle included.
# nft --echo --handle add rule inet my_table my_filter_chain udp dport 3333 accept
add rule inet my_table my_filter_chain udp dport 3333 accept # handle 4
Note: Older version of nftables used the keyword position. This keyword has since been deprecated in favor of handle.
Deleting Rules
Deleting rules is done by using the rule handle similar to the add and insert commands above.
The first step is to find the handle of the rule you want to delete.
# nft --handle list ruleset
table inet my_table { # handle 21
chain my_filter_chain { # handle 1
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 3
tcp dport 1234 accept # handle 8
tcp dport nfs accept # handle 7
tcp dport ssh accept # handle 2
}
}
Then use the handle
to delete the rule.
# nft delete rule inet my_table my_filter_chain handle 8
# nft --handle list ruleset
table inet my_table { # handle 21
chain my_filter_chain { # handle 1
type filter hook input priority 0; policy accept;
tcp dport http accept # handle 3
tcp dport nfs accept # handle 7
tcp dport ssh accept # handle 2
}
}
Listing Rules
In previous examples above we listed the entire rule set. There are many other ways to list a subset of rules.
List all rules in a given table.
# nft list table inet my_table
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
}
}
List all rules in a given chain.
# nft list chain inet my_table my_other_chain
table inet my_table {
chain my_other_chain {
udp dport 12345 log prefix "UDP-12345"
}
}
Sets
nftables
has native support for sets. This can be useful if you want a rule to match multiple IP addresses, port numbers, interfaces, or any other match criteria.
Anonymous Sets
Any rule may contain inline sets. This is useful for sets that you don’t expect to change.
The following allows all traffic from 10.10.10.123 and 10.10.10.231.
# nft add rule inet my_table my_filter_chain ip saddr { 10.10.10.123, 10.10.10.231 } accept
# nft list ruleset
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
ip saddr { 10.10.10.123, 10.10.10.231 } accept
}
}
The downside to this method is if you need to alter the set you’ll need to replace the rule. For mutable sets you should use a named set.
As another example, instead of our first three rules we could have used an anonymous set.
# nft add rule inet my_table my_filter_chain tcp dport { http, nfs, ssh } accept
Note: iptables users may be accustomed to using ipset
. Since nftables has native set support the use of ipset
is not necessary.
Named Sets
nftables also has support for mutable named sets. To create them you must specify the type of elements they will contain. Some example types are; ipv4_addr, inet_service, ether_addr.
Let’s create an empty set to start.
# nft add set inet my_table my_set { type ipv4_addr \; }
# nft list sets
table inet my_table {
set my_set {
type ipv4_addr
}
}
To reference the set in a rule use the @
symbol followed by the set name. The following rule serves as a blacklist for IP addresses added to our set.
# nft insert rule inet my_table my_filter_chain ip saddr @my_set drop
# nft list chain inet my_table my_filter_chain
table inet my_table {
chain my_filter_chain {
type filter hook input priority 0; policy accept;
ip saddr @my_set drop
tcp dport http accept
tcp dport nfs accept
tcp dport ssh accept
ip saddr { 10.10.10.123, 10.10.10.231 } accept
}
}
Of course, that’s not very effective since our set is empty. Let’s add some elements.
# nft add element inet my_table my_set { 10.10.10.22, 10.10.10.33 }
# nft list set inet my_table my_set
table inet my_table {
set my_set {
type ipv4_addr
elements = { 10.10.10.22, 10.10.10.33 }
}
}
However, attempting to add a range value will yield an error.
# nft add element inet my_table my_set { 10.20.20.0-10.20.20.255 }
Error: Set member cannot be range, missing interval flag on declaration
add element inet my_table my_set { 10.20.20.0-10.20.20.255 }
To use ranges in our set we must create the set using the interval flags. This is because the kernel must know in advance what type of data the set will store in order to use the appropriate data structure.
Set Intervals
Sets can also use range values. The is very useful for IP addresses. To use ranges the set must be created with the interval
flags.
# nft add set inet my_table my_range_set { type ipv4_addr \; flags interval \; }
# nft add element inet my_table my_range_set { 10.20.20.0/24 }
# nft list set inet my_table my_range_set
table inet my_table {
set my_range_set {
type ipv4_addr
flags interval
elements = { 10.20.20.0/24 }
}
}
Note: The netmask notation was implicitly converted into a range of IP addresses. We could have also used 10.20.20.0-10.20.20.255 to achieve the same effect.
Set Concatenations
Sets also support aggregate types and matches. This means a set element can contain multiple types and a rule can use the concatenation operator .
when referencing the set.
This example will allow us to match IPv4 addresses, IP protocols, and port numbers all at once.
# nft add set inet my_table my_concat_set { type ipv4_addr . inet_proto . inet_service \; }
# nft list set inet my_table my_concat_set
table inet my_table {
set my_concat_set {
type ipv4_addr . inet_proto . inet_service
}
}
Now we can add elements to the list.
# nft add element inet my_table my_concat_set { 10.30.30.30 . tcp . telnet }
As you can see, symbolic names (tcp, telnet) are also usable when adding set elements.
Using the set in a rule is similar to the name set above, but the rule must perform the concatenation.
# nft add rule inet my_table my_filter_chain ip saddr . meta l4proto . tcp dport @my_concat_set accept
# nft list chain inet my_table my_filter_chain
table inet my_table {
chain my_filter_chain {
...
ip saddr { 10.10.10.123, 10.10.10.231 } accept
meta nfproto ipv4 ip saddr . meta l4proto . tcp dport @my_concat_set accept
}
}
Also worth noting is that concatenation can be used with inline sets. Here is one last example showing that.
# nft add rule inet my_table my_filter_chain ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept
Hopefully you now understand how powerful nftables sets are.
Note: nftables set concatenations are similar to ipset’s aggregate types, e.g. hash:ip,port.
Verdict Maps
Verdict maps are a very interesting feature in nftables that allow you to perform an action based on packet information. Said more plainly, they map match criteria to an action.
Say for example, in order to logically divide your ruleset you want dedicated chains for processing TCP and UDP packets. You can use a verdict map to steer packets to those chains using a single rule.
# nft add chain inet my_table my_tcp_chain
# nft add chain inet my_table my_udp_chain
# nft add rule inet my_table my_filter_chain meta l4proto vmap { tcp : jump my_tcp_chain, udp : jump my_udp_chain }
# nft list chain inet my_table my_filter_chain
table inet my_table {
chain my_filter_chain {
...
meta nfproto ipv4 ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept
meta l4proto vmap { tcp : jump my_tcp_chain, udp : jump my_udp_chain }
}
}
Of course, just like sets you can create mutable verdict maps.
# nft add map inet my_table my_vmap { type inet_proto : verdict \; }
Your eyes don’t deceive you. The syntax is very similar to sets. In fact, internally sets and verdict maps are built using a common data type.
Now you can use the mutable verdict map in a rule.
# nft add rule inet my_table my_filter_chain meta l4proto vmap @my_vmap
Tables Are Namespaces
One interesting thing about tables in nftables is that they’re also full namespaces. This means that two tables can create chains, sets, and other objects that have the same name.
# nft add table inet table_one
# nft add chain inet table_one my_chain
# nft add table inet table_two
# nft add chain inet table_two my_chain
# nft list ruleset
...
table inet table_one {
chain my_chain {
}
}
table inet table_two {
chain my_chain {
}
}
This property means applications can organize rules into their own table without impacting other applications. In iptables it was very difficult for applications to make firewall changes without impacting other applications.
However, there is a caveat to this. Each table and chain hook can be viewed as an independent and separate firewall. This means a packet must be accepted by all of them in order to be allowed. If table_one accepts a packet, it may still be dropped by table_two. This is where hook priorities come into play. A chain with a lower priority value is guaranteed to be executed before a chain with a higher priority value. If the priorities are equal, then the behavior is undefined.
Save and Restore a Ruleset
nftables rules can easily be saved and restored. The list
output of nft
can be fed back into the tool to restore everything. This is exactly how the nftables systemd service works.
To save your ruleset
# nft list ruleset > /root/nftables.conf
To restore your ruleset
# nft -f /root/nftables.conf
Of course, you can enable the systemd service and have your rules restored on reboot. The service reads rules from /etc/sysconfig/nftables.conf
.
# systemctl enable nftables
# nft list ruleset > /etc/sysconfig/nftables.conf
Note: Some distributions, RHEL-8 included, ship predefined nftables configuration in /etc/nftables
. These samples often include the setup of tables and chains in a manner similar to iptables. These are often listed in the existing /etc/sysconfig/nftables.conf
file, but may be commented out.
Conclusions
Hopefully this post served as a solid introduction and demonstration of nftables capabilities. We only scratched the surface of nftables. There are many features not discussed here. You can find more information in the nft
manual page and the upstream wiki. Additionally look for some follow up posts on this blog in which we’ll dig into some advanced nftables topics.
Sull'autore
Eric has been at Red Hat since 2016. In his tenure he has contributed to many open source projects: Linux, Open vSwitch, nftables and firewalld. He has been the upstream maintainer of firewalld since 2017.
Altri risultati simili a questo
Ricerca per canale
Automazione
Novità sull'automazione IT di tecnologie, team e ambienti
Intelligenza artificiale
Aggiornamenti sulle piattaforme che consentono alle aziende di eseguire carichi di lavoro IA ovunque
Hybrid cloud open source
Scopri come affrontare il futuro in modo più agile grazie al cloud ibrido
Sicurezza
Le ultime novità sulle nostre soluzioni per ridurre i rischi nelle tecnologie e negli ambienti
Edge computing
Aggiornamenti sulle piattaforme che semplificano l'operatività edge
Infrastruttura
Le ultime novità sulla piattaforma Linux aziendale leader a livello mondiale
Applicazioni
Approfondimenti sulle nostre soluzioni alle sfide applicative più difficili
Serie originali
Raccontiamo le interessanti storie di leader e creatori di tecnologie pensate per le aziende
Prodotti
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Servizi cloud
- Scopri tutti i prodotti
Strumenti
- Formazione e certificazioni
- Il mio account
- Supporto clienti
- Risorse per sviluppatori
- Trova un partner
- Red Hat Ecosystem Catalog
- Calcola il valore delle soluzioni Red Hat
- Documentazione
Prova, acquista, vendi
Comunica
- Contatta l'ufficio vendite
- Contatta l'assistenza clienti
- Contatta un esperto della formazione
- Social media
Informazioni su Red Hat
Red Hat è leader mondiale nella fornitura di soluzioni open source per le aziende, tra cui Linux, Kubernetes, container e soluzioni cloud. Le nostre soluzioni open source, rese sicure per un uso aziendale, consentono di operare su più piattaforme e ambienti, dal datacenter centrale all'edge della rete.
Seleziona la tua lingua
Red Hat legal and privacy links
- Informazioni su Red Hat
- Opportunità di lavoro
- Eventi
- Sedi
- Contattaci
- Blog di Red Hat
- Diversità, equità e inclusione
- Cool Stuff Store
- Red Hat Summit