Using nftables in Red Hat Enterprise Linux 8

This article was prepared in advance of the start of the course. Linux Administrator


In Red Hat Enterprise Linux 8, nftables is a low-priority solution. In this article we will talk about how to start using nftables. It will be most relevant for system administrators and DevOps specialists. Where this makes sense, we’ll talk about the differences between nftables and its predecessor, iptables.

To begin with, it should be noted that nftables is a userland utility, nft, and the kernel subsystem. Inside the kernel, it is built on the basis of the netfilter subsystem. In this article, we will talk about user interaction with the nft utility.

Here you will find examples of commands that you can test on the machine. They will be useful for a better understanding of the content.

Note: in this article, some output can be quite long, so we will reduce or remove useless or irrelevant parts of the output.

Let’s start

So what will the default nftables setup look like? Let’s find out by listing the whole set of rules.

# nft list ruleset

As a result, we get … nothing. Okay, this is not very interesting. What is this talking about?

By default, nftables does not create tables and chains, unlike its predecessor iptables. An empty rule set has zero resource cost. For comparison, recall iptables, where each pre-created table and chain should be taken into account, and the basic packet counters increased, even if they were empty.

Table creation

In nftables, you will need to create tables manually. Tables should define a family: ip, ip6, inet, arp, bridge or netdev. Here, inet means that the table will process ipv4 and ipv6 packets. It is this family that we will use in the article.

Note: For those transitioning from iptables, the term table may sound ambiguous. In nftables, a table is just a namespace, no more, no less. In fact, it is a set of chains, rules, sets and other objects.

Let’s create our first table and list the ruleset.

# nft add table inet my_table
# nft list ruleset
table inet my_table {
}

Great, now we have a table, but in itself it does not give much. Let’s move on to the chains.

Chaining

Chains are objects that will contain firewall rules.

Similar to tables, chains also need to be created manually. When creating a chain, you must specify which table it belongs to, as well as type, hook and priority. In this guide, we will not complicate anything and use filter, input, and priority 0 to filter packets destined for the host.

# nft add chain inet my_table my_filter_chain { type filter hook input priority 0 ; }

Note: Backslash () is needed so that the shell does not interpret the bracket as the end of the command.

Chains can also be created without specifying a hook. The chains created in this way are equivalent to the chains created by users in iptables. Rules can use jump or goto statements to execute rules in a chain. This mechanic is useful for logically separating rules or for sharing a subset of rules that would otherwise be duplicated.

# nft add chain inet my_table my_utility_chain

Create rules

Now that you have created the table and chain, you can finally add the rules for the firewall. Let’s add a rule to allow SSH.

# nft add rule inet my_table my_filter_chain tcp dport ssh accept

It should be noted here that as soon as we added it to the inet family table, this is the only rule that will handle both IPv4 packets and IPv6 packets.

Verb add will add the rule to the end of the chain. You can also use the verb insertto add a rule to the beginning of the chain.

# nft insert rule inet my_table my_filter_chain tcp dport http accept

We added two rules, and now let’s see what the full set of rules will look 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
    }
}

Please note that the rule http must work out the rules earlier sshas we used the above verb insert.

You can also add a rule to an arbitrary place in the chain. There are two ways to do this.

  1. Use indexto point to the index in the rule list. Using add will add a new rule after the specified index. If you use insert, then a new rule will be added before the rule with the given index. Values indices 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 from insert in fact equivalent to iptables option -I with an index. The first thing to keep in mind is that indexes in nftables start at 0. Second, the index must point to an existing rule. That is, writing “nft insert rule … index 0” in an empty chain is not allowed.

  1. Use handleto indicate the rule before or after which you want to insert another rule. To insert after, use the verb add. To insert before the rule, use insert. You can get handle rules by adding a flag –Handle when deriving 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, the handle of rules is stable and will not change until the rule is deleted. So the link to the rule is more reliable than just indexwhich may change when another rule is inserted.

You can also get handle rules at creation time using two flags at once. –Echo and –Handle. The rule will be displayed in the CLI along with its handle.

# 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 versions of nftables used keyword position. Since then, keyword mechanics has become outdated and handle has appeared.

Deleting Rules

Deleting rules is performed using the handle rule, similar to the add and insert commands above.

First you need 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 this handle to delete a 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 the examples above, we displayed the entire list of rules. There are many other ways to infer a subset of the rules.

Print all the rules in the 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
    }
}

Display rules in the 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

IN nftables there is native support for sets. They can be useful if you want the rule to work with multiple IP addresses, ports, interfaces, or any other criteria.

Anonymous sets

Any rule can have inline sets. This mechanic is useful for sets that you are not going to modify.

For example, the following command will allow 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 disadvantage of this method is that if you need to change the set, then the rule will need to be replaced. To get mutable sets, you need to use named sets.

As another example, instead of the first three rules, we could use an anonymous set.

# nft add rule inet my_table my_filter_chain tcp dport { http, nfs, ssh } accept

Note: iptables users are most likely used to using ipset. Since nftables has its own native set support, ipset no need to use.

Named Sets

Nftables also supports mutable named sets. To create them, you must specify the type of elements that will be contained in them. For example, the types may be: ipv4_addr, inet_service, ether_addr.

Let’s create an empty set.

# 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 refer to a set in a rule, use the symbol @ and the set name after it. The following rule will work as a blacklist for IP addresses in 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, this is not particularly effective, since the set is empty. Let’s add some elements to it.

# 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 of values ​​will result in 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 sets, you need to create a set using interval flags. So you need the kernel to know in advance what type of data will be stored in the set in order to use the appropriate data structure.

Set Intervals

Sets can also use ranges. For IP addresses, this can be extremely useful. To use ranges, a set must be created using flags of intervals.

# 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 to a range of IP addresses. Similarly, we could write 10.20.20.0-10.20.20.255 and get the same effect.

Concatenation sets

Sets also support aggregate types and matches. That is, a collection element can also contain several types, and a rule can use the concatenation operator “.” when accessing the set.

In this example, we can map IPv4 addresses, IP protocols, and port numbers at the same time.

# 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 items 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) can also be used when adding set elements.

Using a set in a rule is similar to the named set above, but the rule must perform 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
    }
}

It is also worth noting that concatenation can be used with inline sets. Here is a final example that reflects this.

# nft add rule inet my_table my_filter_chain ip saddr . meta l4proto . udp dport { 10.30.30.30 . udp . bootps } accept

Hopefully now you understand the power of nftables.

Note: concatenation of nftables sets is similar to ipset aggregate types, for example, hash: ip, port.

Verdict map

Verdict map is an interesting feature in nftables that allows you to perform an action based on the information in the package. Simply put, they map criteria to actions.

For example, to logically separate a set of rules, you need separate chains for processing TCP and UDP packets. You can use the verdict map to route packets to these chains with 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, by analogy with sets, you can create mutable verdict map.

# nft add map inet my_table my_vmap { type inet_proto : verdict ; }

Your eyes do not deceive you. The syntax with the sets is really very similar. In fact, under the hood, the sets and the verdict map are built on the same data type.

Now you can use the mutable verdict map in the rule.

# nft add rule inet my_table my_filter_chain meta l4proto vmap @my_vmap

Tables as Namespaces

Another interesting thing about tables in nftables is that they are also full namespaces. That is, two tables can create chains, sets, and other objects with 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 that applications can organize rules in their own chains without affecting other applications. In iptables, it was hard for applications to make changes to the firewall without affecting other applications.

However, there is one caveat. Each table or chain hook can be thought of as an independent and separate firewall. That is, the package must go through allto eventually be accepted. If a table_one accepts the packet, it can still be rejected table_two. This is where hook prioritization comes into play. A chain with a lower priority is guaranteed to be executed before a chain with a higher priority. If the priorities are equal, then the behavior is not defined.

Saving and restoring a rule set

Nftables rules can be easily saved and restored. Output list in nft can be used in the tool to carry out restoration. This is how the nftables systemd service works.

Save Rule Set

# nft list ruleset > /root/nftables.conf

Restore rule set

# nft -f /root/nftables.conf

Of course, you can enable the systemd service and restore the rules on reboot. The service reads the rules from /etc/sysconfig/nftables.conf.

# systemctl enable nftables
# nft list ruleset > /etc/sysconfig/nftables.conf

Note: some distributions, including RHEL-8, send the nftables predefined configuration to / etc / nftables. These samples often include iptables-style table and chain configurations. They are often specified in the file. /etc/sysconfig/nftables.confbut can be commented out.

Conclusion

I hope this article has become a good introduction and demonstration of the capabilities of nftables. However, we only touched the surface of nftables. There are many features that are not covered here. You can find more information on the documentation page. nft and in wiki. In addition to this, you can read the following several articles. in this blogin which we will talk about more advanced aspects of nftables.


Learn more about the course Linux Administrator


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *