Tech notes for unix-nftables

undefined

Posts

  1. NFtables basics
  2. NFTables: Allow ICMP pings and stop ping floods with rate limiting
  3. NFTables: Basic rules, inserting, deleting, established packets and saving
  4. NFTables: setup and basic tutorial in Debian

NFtables basics

NFtables replaces the iptable firewall tools in Linux. It should make things nicer. But let's step back and talk about how firewalls are organised.

Background

Firewalls are made up of rules. An example is "allow incoming connections on port 22". Rules are organised into chains. There are two main chains, input and output. Rules on the input chain relate to incoming connections. Rules on the output chain relate to outgoing connections. A table is a collection of chains.

We can make our own table and chains. Our chains will hook onto the default input or output chain. Linux will see an incoming connection. It will consult the default input chain. That input chain will then consult our chain.

Allowing ports

So let's look at the format of nft commands: nft THE_COMMAND inet THE_TABLE THE_CHAIN THE_CHAIN_COMMAND.

After nft you type the command name, then then you specify the table name (but with inet before because the table will be a ipv4/6 table), then the chain name, then the chain command. In short: the command, inet plus the table name, the chain name and the chain command.

Let's make our table and chain, the chain that will hook onto the default input chain and filter things on that.

nft add table inet mytable
nft add chain inet mytable myinputchain { type filter hook input priority 0 \; }

Let's add rules to allow SSH on port 22 and HTTPS on 442 and drop everything else. The chain command format is: type tcp, then dport and the number and finally the accept/drop.

nft add rule inet mytable myinputchain tcp dport {443, 22} accept
nft add rule inet mytable myinputchain drop

Inserting rules and allowing established connections

But we forgot to add a rule before drop. We want to add one before that but first we need to know the nft position of the rule above drop. We can do that with nft list table inet mytable -a -nn:

table inet mytable {
	chain myinputchain {
		type filter hook input priority 0; policy accept;
		tcp dport 22 accept # handle 2
		tcp dport 443 accept # handle 3
		drop # handle 4
	}
}

So we know it's handle 3 now. We will type position 3 before our chain command.

When we establish a connection to the outside world, the outside world will want to talk to us, but our firewall will reject it because it won't send us things on port 443 or 22. We need to track the connections (ct) we make, and look at the state of the connection, and if we've established it, then allow it.

nft add rule inet mytable myinputchain position 3 ct state established accept

Saving and restoring rules

Let's save our list with nft list table inet mytable -a -nn > fw.ruleset, clear our nft table with nft delete table inet mytable, see nothing is there by looking at nft list tables, then reload it with nft -f fw.ruleset and then check all the rules are there.

Allowing pings and rate limiting

We should allow pings. Our table is a ipv4/ipv6 table so we need to specify that we're interested in the icmp protocol, then the type of icmp packet that interests, then we set a rate limit to stop ping flood, then accept. After this we drop any icmp packet else the established packet rule will accept the ping floods.

nft add rule inet mytable myinputchain position <..before drop..> ip protocol icmp icmp type echo-request limit rate 1/second accept
nft add rule inet mytable myinputchain position <after the above> ip protocol icmp drop

Allowing the loop back device

Local programs communicate with the computer using the loopback device. Let's allow that. We use the meta command to look at the networking packets, and look at the interface name with iif. After this we should be able to ping localhost.

nft insert rule inet mytable myinputchain position 2 meta iif lo accept

In conclusion

If we put all this together, we get:

nft add table inet mytable
nft add chain inet mytable myinputchain { type filter hook input priority 0 \; }
nft add rule inet mytable myinputchain meta iif lo accept
nft add rule inet mytable myinputchain tcp dport {443, 22, 3000, 3002} accept
nft add rule inet mytable myinputchain ip protocol icmp icmp type echo-request limit rate 1/second accept
nft add rule inet mytable myinputchain ip protocol icmp drop
nft add rule inet mytable myinputchain ct state established accept
nft add rule inet mytable myinputchain drop
unix unix-nftables

NFTables: Allow ICMP pings and stop ping floods with rate limiting

If you want to allow ping ICMP echo-requests, and you're automatically dropping all except a few selected incoming packets, you'll need to first add a rule:

nft insert rule inet global input ip protocol icmp \
  icmp type echo-request accept

This rule is as normal as of nft insert rule inet global input but then we do ip protocol icmp to say we're interested in the ICMP packets, then icmp type echo-request accept is very similar to the previous rules tcp dport 8888 accept.

Now we can ping our host.

However, with ping -f, we're open to ping floods. So let's remove that rule with list table inet global -a to find its handle and then delete it.

nft insert rule inet global input ip protocol icmp \
  icmp type echo-request limit rate 1/second accept

This is as before, except we're only allowing 1 a second.

But there may be a problem if you also have ct state established accept anywhere in your table since now the echo-requests have an established state, for reasons of which I am not sure.

To get over this, ensure after the rate limiting rule (position 32 is below the icmp rule in my case) we explicitly drop all icmp packets.

nft insert rule inet global input position 32 ip protocol icmp \
    drop

Notes:

  1. The ip protocol icmp bit is only required in inet tables, not ip -- this may well be a bug/infleicity from what I've seen in 0.5.0 in 3.16
  2. The ct state established biit messing with rate limiting seems odd -- also a bug in 0.5 / 3.16?
unix unix-nftables

NFTables: Basic rules, inserting, deleting, established packets and saving

Now we've done the basic NFTables setup, let's make and state some basic rules.

Let's make the global table and input ruleset again if we haven't already:

nft add table inet global
nft add chain inet global input { type filter hook input priority 0 \; }

Let's first open port 22, since we're SSHing into our box:

nft add rule inet global input tcp dport 22 accept

Now we can drop all the rest:

nft add rule inet global input drop

Let's insert an ESTABLISHED rule before the drop rule, or right at the top in our case, that allows our incoming packets -- when we request a webpage for example -- to receive packets from the external server:

nft insert rule inet global input ct state established accept

(You may want to add the related state to accept if you're using FTP etc)

Finally let's insert some standard rules for allowing http etc. We'll use the positional handle indicators -- position 8 in my case -- for the insertion place via using nft list table inet global -a.

nft insert rule inet global input position 8 tcp dport {80, 8080, 443} accept

(Note the spaces after each comma)

We can now list the table and get:

table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                ct state established accept # handle 14
                tcp dport { https, http, http-alt} accept # handle 16
                tcp dport ssh accept # handle 8
                drop # handle 4
        }
}

If you wanted to delete the last rule, handle 16, we'd do nft delete rule inet global input handle 16.

Let's save the ruleset (the -n gives us numbers for the ports) and flush/delete the chain and table:

nft -n list -n table inet global -a > fw.ruleset
nft flush chain inet global input
nft delete chain inet global input
nft delete table inet global

In 3.18 of the kernel and upwards, you can just delete the table without first flushing and deleting the chain, but alas.

Now if we list the tables nft list tables we'll see nothing, which is what we want. Now let's load the saved rules back in and list them:

# nft -f fw.ruleset 
# nft list table inet global
table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                ct state established accept 
                tcp dport { https, http, http-alt} accept 
                tcp dport ssh accept 
                drop 
        }
}
unix unix-nftables

NFTables: setup and basic tutorial in Debian

First get an external host with a Linux distribution on it with a recent kernel. I'm using Debian Jessie with 3.16 (later kernels will be fine).

# uname -r
3.16.0-4-amd64

For debian jessie, we'll need the nftables packages which resides in backports. This should be in your /etc/apt/sources.list

deb http://mirrors.kernel.org/debian/ jessie-backports main contrib

Then we can install nftables. Install apache or whatever also, so we can test out filtering.

# apt-get install nftables apache2

I'm using this version of nftables (Warning: things may change with later verseions)

# nft --version
nftables v0.5 (Support Edward Snowden)

Now test we can see apache by going to the host's IP address.

Let's setup nftables by first defining a table -- we'll just call it global -- and then a chain, in which we'll place our rules:

# nft add table inet global
# nft add chain inet global input { type filter hook input priority 0 \; }

(The inet part says this is for both ipv4 and ipv6.)

The add table command is simple enough. Then the chain input is added to the global table, and within the curly brackets we say we're a filter chain that's hooking onto the input hook, with a high priority. (the semi-colon is escaped when we use bash)

Next let's add a rule to allow SSHon port 22:

# nft add rule inet global input tcp dport 22 accept

Again, we're adding a rule to the global table and the input chain. We then use the accept on the tcp destination port 22. We can how list out commands:

# nft list table inet global
table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                tcp dport ssh accept 
        }
}

Let's make sure we're dropping everything else, by adding the drop command to the end of the list:

# nft add rule inet global input drop
# nft list table inet global
table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                tcp dport ssh accept 
                drop 
        }
}

Now if we try to reach apache we'll find we can no longer. Let's add a rule that allows it.

First we'll find the handle with the -a param, then insert the http rule before that.

# nft list table inet global -a
table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                tcp dport ssh accept # handle 2
                drop # handle 3
        }
}
# nft insert rule inet global input position 3 tcp dport 80 accept
# nft list table inet global
table inet global {
        chain input {
                type filter hook input priority 0; policy accept;
                tcp dport ssh accept 
                tcp dport http accept 
                drop 
        }
}

There's obviously plenty more you can do, but this just sets things up. This also doesn't save the tables once the system goes down.

unix unix-nftables

Page 1 of 1
Click me