An overview of iptables

July 22, 2021

A common misconception is that iptables is the name of the Linux firewall. In reality, the name of the Linux firewall is netfilter, and every Linux distro has it built in. What we know is that iptables is just one of several command-line utilities that we can use to manage netfilter. It was originally introduced as a feature of Linux kernel version 2.6, so it’s been around for a long time. With iptables, you do have a few advantages:

  • It’s been around long enough that most Linux admins already know how to use it.
  • It’s easy to use iptables commands in shell scripts to create your own custom firewall configuration.
  • It has great flexibility in that you can use it to set up a simple port filter, a router, or a virtual private network.
  • It comes pre-installed on pretty much every Linux distro, although most distros don’t come with it preconfigured.
  • It’s very well documented and has free of charge, book-length tutorials available on the internet.

However, as you might know, there are also a few disadvantages:

  • IPv4 and IPv6 each require their own special implementation of iptables. So, if your organization still needs to run IPv4 while in the process of migrating to IPv6, you’ll have to configure two firewalld on each server and run a separate daemon for each (one for IPv4, the other for IPv6).
  • If you need to do MAC bridging, that requires ebtables, which is the third component of iptables, with its own unique syntax.
  • arptables, the fourth component of iptables, also requires its own daemon and syntax.
  • Whenever you add a rule to a running iptables firewall, the entire iptables ruleset has to be reloaded, which can have a huge impact on performance.

Until recently, just plain iptables was the default firewall manager on every Linux distro. It still is on most distros, but Red Hat Enterprise Linux 7 and all of its offspring now use the new firewalld as an easier-to-use frontend for configuring iptables rules. Ubuntu comes with Uncomplicated Firewall (ufw), which is also an easy to use frontend for iptables. An even newer technology that we’ll explore is nftables, which is available as an option on Debian/Ubuntu systems. On Red Hat 8/CentOS 8 systems, nftables has replaced iptables as the default backend for firewalld. (Don’t worry if this all sounds confusing – it will all become clear in good time.)

In this tutorial, we’ll discuss setting up iptables firewall rules for both IPv4 and IPv6.

Mastering the basics of iptables

iptables consists of five tables of rules, each with its own distinct purpose:

  • Filter table: For basic protection of our servers and clients, this might be the only table that we use.
  • Network Address Translation (NAT) table: NAT is used to connect the public internet to private networks.
  • Mangle table: This is used to alter network packets as they go through the firewall.
  • Raw table: This is for packets that don’t require connection tracking.
  • Security table: The security table is only used for systems that have SELinux installed.

Since we’re currently only interested in basic host protection, we will only look at the filter table for the time being. (In a few moments, I’ll show you a couple of fancy tricks that we can do with the mangle table.) Each table consists of chains of rules, and the filter table consists of the INPUTFORWARD, and OUTPUT chains. Since our CentOS machines use Red Hat’s firewalld, we’ll look at this on our Ubuntu machine.

First, we’ll look at our current configuration by using the sudo iptables -L command:

donnie@ubuntu:~$ sudo iptables -L
[sudo] password for donnie:
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Remember that we said that you need a separate component of iptables to deal with IPv6? Here, we’ll use the sudo ip6tables -L command:

donnie@ubuntu:~$ sudo ip6tables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

In both cases, you can see that there are no rules and that the machine is wide open. Unlike the SUSE and Red Hat folk, the Ubuntu folk expect you to do all the work of setting up a firewall. We’ll start by creating a rule that will allow us to pass incoming packets from servers that our host has requested a connection to:

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Here’s the breakdown of this command:

  • -A INPUT-A places a rule at the end of the specified chain, which in this case is the INPUT chain. We would have used -I had we wanted to place the rule at the beginning of the chain.
  • -m: This calls in an iptables module. In this case, we’re calling in the conntrack module to track connection states. This module allows iptables to determine whether our client has made a connection to another machine, for example.
  • --ctstate: The ctstate, or connection state, portion of our rule is looking for two things. First, it’s looking for a connection that the client established with a server. Then, it looks for the related connection that’s coming back from the server in order to allow it to connect to the client. So, if a user were to use a web browser to connect to a website, this rule would allow packets from the web server to pass through the firewall to get to the user’s browser.
  • -j: This stands for jump. Rules jump to a specific target, which in this case is ACCEPT. (Please don’t ask me who came up with this terminology.) So, this rule will accept packets that have been returned from the server that the client has requested a connection to.

Our new ruleset looks like this:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Next, we’ll open up port 22 so that we can connect through Secure Shell:

sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT

Here’s the breakdown:

  • -A INPUT: As before, we want to place this rule at the end of the INPUT chain with -A.
  • -p tcp-p indicates the protocol that this rule affects. This rule affects the TCP protocol, which Secure Shell is a part of.
  • --dport ssh: When an option name consists of more than one letter, we need to precede it with two dashes, instead of just one. The --dport option specifies the destination port that we want this rule to operate on. (Note that we could have also listed this portion of the rule as --dport 22 since 22 is the number of the SSH port.)
  • -j ACCEPT: If we put this all together with -j ACCEPT, then we have a rule that allows other machines to connect to this one through Secure Shell.

Now, let’s say that we want this machine to be a DNS server. For that, we need to open port 53 for both the TCP and the UDP protocols:

sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT

Finally, we have an almost complete, usable ruleset for our INPUT chain:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere ctstate
RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
DROP all -- anywhere anywhere
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

However, this is only almost complete, because there’s still one little thing that we forgot. That is, we need to allow traffic for the loopback interface. This is okay because it gives us a good chance to see how to insert a rule where we want it if we don’t want it at the end. In this case, we’ll insert the rule at INPUT 1, which is the first position of the INPUT chain:

sudo iptables -I INPUT 1 -i lo -j ACCEPT

Note:

Before you inserted the ACCEPT rule for the lo interface, you may have noticed that sudo commands were taking a long time to complete and that you were getting sudo: unable to resolve host. . .Resource temporarily unavailable messages. That’s because sudo needs to know the machine’s hostname so that it can know which rules are allowed to run on a particular machine. It uses the loopback interface to help resolve the hostname. If the lo interface is blocked, it takes longer for sudo to resolve the hostname.

Our ruleset now looks like this:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp dpt:domain

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Note how port 53 is listed as the domain port. To see port numbers instead of port names, we can use the -n switch:

donnie@ubuntu3:~$ sudo iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu3:~$

Now, as things currently stand, we’re still allowing everything to get through, because we still haven’t created a rule that blocks what we haven’t specifically allowed. Before we do that, though, let’s look at a few more things that we might want to allow.

Blocking ICMP with iptables

The conventional wisdom that you may have heard for most of your career is that we need to block all the packets from the Internet Control Message Protocol (ICMP). The idea you may have been told is to make your server invisible to hackers by blocking ping packets. Of course, there are some vulnerabilities that are associated with ICMP, such as the following:

  • By using a botnet, a hacker could inundate your server with ping packets from multiple sources at once, exhausting your server’s ability to cope. 
  • Certain vulnerabilities that are associated with the ICMP protocol can allow a hacker to either gain administrative privileges on your system, redirect your traffic to a malicious server, or crash your operating system.
  • By using some simple hacking tools, someone could embed sensitive data in the data field of an ICMP packet in order to secretly exfiltrate it from your organization.

However, while blocking certain types of ICMP packets is good, blocking all ICMP packets is bad. The harsh reality is that certain types of ICMP messages are necessary for the proper functionality of the network. Since the drop all that’s not allowed rule that we’ll eventually create also blocks ICMP packets, we’ll need to create some rules that allow the types of ICMP messages that we have to have. So, here goes:

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 3 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 11 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 12 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

Here’s the breakdown:

  • -m conntrack: As before, we’re using the conntrack module to allow packets that are in a certain state. This time, though, instead of just allowing packets from a host that our server has connected to (ESTABLISHED,RELATED), we’re also allowing NEW packets that other hosts are sending to our server.
  • -p icmp: This refers to the ICMP protocol.
  • --icmp-type: There are quite a few types of ICMP messages, which we’ll outline next.

The three types of ICMP messages that we want to allow are as follows:

  • type 3: These are the destination unreachable messages. Not only can they tell your server that it can’t reach a certain host, but they can also tell it why. For example, if the server has sent out a packet that’s too large for a network switch to handle, the switch will send back an ICMP message that tells the server to fragment that large packet. Without ICMP, the server would have connectivity problems every time it tries to send out a large packet that needs to be broken up into fragments.
  • type 11: Time exceeded messages let your server know that a packet that it has sent out has either exceeded its Time-to-Live (TTL) value before it could reach its destination, or that a fragmented packet couldn’t be reassembled before the TTL expiration date.
  • type 12: Parameter problem messages indicate that the server had sent a packet with a bad IP header. In other words, the IP header is either missing an option flag, or it’s of an invalid length.

Three common message types are conspicuously absent from our list:

  • type 0 and type 8: These are the infamous ping packets. Actually, type 8 is the echo request packet that you would send out to ping a host, while type 0 is the echo reply that the host would return to let you know that it’s alive. Of course, allowing ping packets to get through could be a big help with troubleshooting network problems. If that scenario ever comes up, you could just add a couple of iptables rules to temporarily allow pings.
  • type 5: Now, we have the infamous redirect messages. Allowing these could be handy if you have a router that can suggest more efficient paths for the server to use, but hackers can also use them to redirect you to someplace that you don’t want to go. So, just block them.

There are lots more ICMP message types than I’ve shown here, but these are the only ones that we need to worry about for now.

When we use sudo iptables -L, we’ll see our new ruleset, as things currently stand:

Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp dpt:domain
ACCEPT icmp -- anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp destination-unreachable
ACCEPT icmp -- anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp source-quench
ACCEPT icmp -- anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp time-exceeded
ACCEPT icmp -- anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Looks good, eh? Well, not really. We haven’t blocked anything with this ruleset yet. So, let’s take care of that.

Blocking everything that isn’t allowed with iptables

To start blocking stuff that we don’t want, we have to do one of two things. We can set a default DROP or REJECT policy for the INPUT chain, or we can leave the policy set to ACCEPT and create a DROP or REJECT rule at the end of the INPUT chain. Which one you choose is really a matter of preference. (Of course, before you choose one over the other, you might want to check your organization’s policy manual to see if your employer has a preference.)

The difference between DROP and REJECT is that DROP blocks packets without sending any message back to the sender. REJECT blocks packets, and then sends a message back to the sender about why the packets were blocked. For our present purposes, let’s say that we just want to DROP packets that we don’t want to get through. 

Note:

There are times when DROP is better, and times when REJECT is better. Use DROP if it’s important to make your host invisible. (Although, even that isn’t that effective, because there are other ways to discover hosts.) If you need your hosts to inform other hosts about why they can’t make a connection, then use REJECT. The big advantage of REJECT is that it will let connecting hosts know that their packets are being blocked so that they will know to immediately quit trying to make a connection. With DROP, the host that’s trying to make the connection will just keep trying to make the connection until it times out.

To create a DROP rule at the end of the INPUT chain, use the following code:

donnie@ubuntu:~$ sudo iptables -A INPUT -j DROP
donnie@ubuntu:~$

To set a default DROP policy instead, we can use the following code:

donnie@ubuntu:~$ sudo iptables -P INPUT DROP
donnie@ubuntu:~$

The big advantage of setting up a default DROP or REJECT policy is that it makes it easier to add new ACCEPT rules if need be. This is because if we decide to keep the default ACCEPT policy and create a DROP or REJECT rule instead, that rule has to be at the bottom of the list.

Since iptables rules are processed in order, from top to bottom, any ACCEPT rules that come after that DROP or REJECT rule would have no effect. You would need to insert any new ACCEPT rules above that final DROP or REJECT rule, which is just a tiny bit less convenient than just being able to append them to the end of the list. For now, in order to illustrate my next point, I’ve just left the default ACCEPT policy and added the DROP rule.

When we look at our new ruleset, we’ll see something that’s rather strange:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
. . .
. . .
ACCEPT icmp -- anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
DROP all -- anywhere anywhere

Chain FORWARD (policy ACCEPT)
target prot opt source destination
. . .
. . .

The first rule and the last rule of the INPUT chain look the same, except that one is a DROP and the other is an ACCEPT. Let’s look at it again with the -v (verbose) option:

donnie@ubuntu:~$ sudo iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
67 4828 ACCEPT all -- lo any anywhere anywhere
828 52354 ACCEPT all -- any any anywhere anywhere ctstate RELATED,ESTABLISHED
. . .
. . .
0 0 ACCEPT icmp -- any any anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
251 40768 DROP all -- any any anywhere anywhere

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
. . .
. . .

Now, we can see that lo, for loopback, shows up under the in column of the first rule, and that any shows up under the in column of the last rule. We can also see that the -v switch shows the number of packets and bytes that have been counted by each rule. So, in the preceding example, we can see that the ctstate RELATED,ESTABLISHED rule has accepted 828 packets and 52,354 bytes. The DROP all rule has blocked 251 packets and 40,763 bytes.

This all looks great, except that if we were to reboot the machine right now, the rules would disappear. The final thing that we need to do is make them permanent. There are several ways to do this, but the simplest way to do this on an Ubuntu machine is to install the iptables-persistent package:

sudo apt install iptables-persistent

During the installation process, you’ll be presented with two screens that ask you whether you want to save the current set of iptables rules. The first screen will be for IPv4 rules, while the second will be for IPv6 rules:

iptables

You’ll now see two new rules files in the /etc/iptables directory:

donnie@ubuntu:~$ ls -l /etc/iptables*
total 8
-rw-r--r-- 1 root root 336 Oct 10 10:29 rules.v4
-rw-r--r-- 1 root root 183 Oct 10 10:29 rules.v6
donnie@ubuntu:~$

If you were to reboot the machine now, you’d see that your iptables rules are still there and in effect. The only slight problem with iptables-persistent is that it won’t save any subsequent changes that you make to the rules. That’s okay, though. I’ll show you how to deal with that in just a bit.

Hands-on lab for basic iptables usage

You’ll complete this lab on your Ubuntu virtual machine. Follow these steps to get started:

  • Shut down your Ubuntu virtual machine and create a snapshot. After you boot it back up, look at your iptables rules, or lack thereof, by using the following command:
sudo iptables -L
  • Create the rules that you need for a basic firewall, allowing for Secure Shell access, DNS queries and zone transfers, and the proper types of ICMP. Deny everything else:
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 3 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 11 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 12 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

sudo iptables -A INPUT -j DROP
  • View the results by using the following command:
sudo iptables -L
  • Oops – it looks like you forgot about that loopback interface. Add a rule for it at the top of the list:
sudo iptables -I INPUT 1 -i lo -j ACCEPT
  • View the results by using the following two commands. Note the difference between the output of each:
sudo iptables -L
sudo iptables -L -v
  • Install the iptables-persistent package and choose to save the IPv4 and IPv6 rules when prompted:
sudo apt install iptables-persistent
  • Reboot the virtual machine and verify that your rules are still active.
  • Keep this virtual machine; you’ll be adding more to it in the next hands-on lab.

That’s the end of this lab—congratulations!

Blocking invalid packets with iptables

If you’ve been in the IT business for any length of time, you’re most likely familiar with the good old TCP three-way handshake. If you’re not, no worries. Here’s the simplified explanation.

Let’s say that you’re sitting at your workstation, and you pull up Firefox to visit a website. To access that website, your workstation and the web server have to set up the connection. Here’s what happens:

  • Your workstation sends a packet with only the SYN flag set to the web server. This is your workstation’s way of saying, “Hello, Mr. Server. I’d like to make a connection with you.”
  • After receiving your workstation’s SYN packet, the web server sends back a packet with the SYN and ACK flags set. With this, the server is saying, “Yeah, bro. I’m here, and I’m willing to connect with you.”
  • Upon receipt of the SYN-ACK packet, the workstation sends back a packet with only the ACK flag set. With this, the workstation is saying, “Cool deal, dude. I’m glad to connect with you.”
  • Upon receipt of the ACK packet, the server sets up the connection with the workstation so that they can exchange information.

This sequence works the same way for setting up any kind of TCP connection. This includes connections that involve Secure Shell, telnet, and the various mail server protocols, among other things.

However, clever people can use a variety of tools to craft TCP packets with some very weird combinations of flags. With these so-called invalid packets, a few things could happen:

  • The invalid packets could be used to elicit responses from the target machine in order to find out what operating system it’s running, what services are running on it, and which versions of the services are running.
  • The invalid packets could be used to trigger certain sorts of security vulnerabilities on the target machine.
  • Some of these invalid packets require more processing power than what normal packets require, which could make them useful for performing DoS attacks.

Now, in truth, the DROP all rule at the end of the filter table’s INPUT chain will block some of these invalid packets. However, there are some things that this rule might miss. And even if we could count on it to block all the invalid stuff, this still isn’t the most efficient way of doing things. By depending on this DROP all rule, we’re allowing these invalid packets to travel through the entire INPUT chain, looking for a rule that will let them through. When no ALLOW rule is found for them, they’ll finally get blocked by the DROP all rule, which is the last rule in the chain. So, what if we could find a more efficient solution?

Ideally, we’d like to block these invalid packets before they travel through the entire INPUT chain. We could do that with a PREROUTING chain, but the filter table doesn’t have a PREROUTING chain. Therefore, we need to use the PREROUTING chain of the mangle table instead. Let’s start by adding these two rules:

donnie@ubuntu:~$ sudo iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP
[sudo] password for donnie:
donnie@ubuntu:~$ sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
donnie@ubuntu:~$

The first of these rules will block most of what we would consider invalid. However, there are still some things that it misses. Due to this, we added the second rule, which blocks all NEW packets that are not SYN packets. Now, let’s see what we have:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
DROP all -- anywhere anywhere

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

We can’t see our new rules, can we? That’s because, by default, iptables -L only shows rules for the filter table. We need to see what we’ve just placed into the mangle table, so let’s do this, instead:

donnie@ubuntu:~$ sudo iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DROP all -- anywhere anywhere ctstate INVALID
DROP tcp -- anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Here, we used the -t mangle option to indicate that we want to see the configuration for the mangle table. Something rather curious that you may have noticed is how iptables renders the rule that was created by the sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP command. For some reason, it renders as follows:

DROP       tcp  --  anywhere             anywhere             tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

It looks strange, but don’t let that throw you. It still means that it’s blocking NEW packets that aren’t SYN packets.

Previously, I mentioned that the iptables-persistent package won’t save subsequent changes to your iptables rules. So, as things stand now, the mangle table rules that we just added will disappear once I reboot this virtual machine. To make these changes permanent, I’ll use the iptables-save command to save a new file in my own home directory. Then, I’ll copy the file over to the /etc/iptables directory, replacing the original one:

donnie@ubuntu:~$ sudo iptables-save > rules.v4
[sudo] password for donnie:
donnie@ubuntu:~$ sudo cp rules.v4 /etc/iptables/
donnie@ubuntu:~$

To test this, we’ll use a handy utility called Nmap. It’s a free utility that you can install on your Windows, Mac, or Linux workstation. Or, if you don’t want to install it on your host machine, you can install it on one of your Linux virtual machines. It’s in the normal repositories of Debian/Ubuntu, RHEL/CentOS 7, and RHEL/CentOS 8. So, just install the Nmap package with the appropriate install command for your distro. If you want to install Nmap on your Windows or Mac host machine, you’ll need to download it from the Nmap website.

With our new mangle table rules in place, let’s perform an XMAS scan of our Ubuntu machine. I have Nmap installed here on the Fedora workstation that I’m currently using, so I’ll just use this for now. I can do this like so:

[donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
[sudo] password for donnie:
Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:20 EDT
Nmap scan report for 192.168.0.15
Host is up (0.00052s latency).
All 1000 scanned ports on 192.168.0.15 are open|filtered
MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 21.41 seconds
[donnie@fedora-teaching ~]$

By default, Nmap only scans the most commonly used 1,000 ports. The XMAS scan sends invalid packets that consist of the FIN, PSH, and URG flags. The fact that all 1,000 scanned ports show as open|filtered means that the scan was blocked, and that Nmap can’t determine the true state of the ports. (In reality, port 22 is open.) We can view the result to see which rule did the blocking. (To simplify things a bit, I’ll only show the output for the PREROUTING chain since it’s the only mangle table chain that’s doing anything):

donnie@ubuntu:~$ sudo iptables -t mangle -L -v
Chain PREROUTING (policy ACCEPT 2898 packets, 434K bytes)
pkts bytes target prot opt in out source destination

2000 80000 DROP all -- any any anywhere anywhere ctstate INVALID

0 0 DROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

. . .
. . .
donnie@ubuntu:~$

Here, you can see that the first rule  the INVALID rule  blocked 2,000 packets and 80,000 bytes. Now, let’s zero out the counter so that we can do another scan:

donnie@ubuntu:~$ sudo iptables -t mangle -Z
donnie@ubuntu:~$ sudo iptables -t mangle -L -v
Chain PREROUTING (policy ACCEPT 22 packets, 2296 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP all -- any any anywhere anywhere ctstate INVALID
0 0 DROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

. . .
. . .
donnie@ubuntu:~$

This time, let’s do a Window scan, which bombards the target machine with ACK packets:

[donnie@fedora-teaching ~]$ sudo nmap -sW 192.168.0.15
Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:39 EDT
Nmap scan report for 192.168.0.15
Host is up (0.00049s latency).
All 1000 scanned ports on 192.168.0.15 are filtered
MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 21.44 seconds
[donnie@fedora-teaching ~]$

As before, the scan was blocked, as indicated by the message that all 1,000 scanned ports have been filtered. Now, let’s view what we have on the target Ubuntu machine:

donnie@ubuntu:~$ sudo iptables -t mangle -L -v
Chain PREROUTING (policy ACCEPT 45 packets, 6398 bytes)
pkts bytes target prot opt in out source destination

0 0 DROP all -- any any anywhere anywhere ctstate INVALID

2000 80000 DROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

. . .
. . .
donnie@ubuntu:~$

This time, we can see that our invalid packets got past the first rule, but were blocked by the second rule. 

Now, just for fun, let’s clear out the mangle table rules and do our scans again. We’ll use the -D option to delete the two rules from the mangle table:

donnie@ubuntu:~$ sudo iptables -t mangle -D PREROUTING 1
donnie@ubuntu:~$ sudo iptables -t mangle -D PREROUTING 1
donnie@ubuntu:~$

When you delete a rule, you have to specify the rule number, just like you do when you insert a rule. Here, I specified rule 1 twice, because deleting the first rule moved the second rule up to first place. Now, let’s verify that the rules are gone:

donnie@ubuntu:~$ sudo iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination

. . .
. . .
donnie@ubuntu:~$

Yes, they are. Cool. Now, let’s see what we get when we perform another XMAS scan:

[donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
[sudo] password for donnie:
Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:48 EDT
Nmap scan report for 192.168.0.15
Host is up (0.00043s latency).
All 1000 scanned ports on 192.168.0.15 are open|filtered
MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 21.41 seconds
[donnie@fedora-teaching ~]$

Even without the mangle table rules, it shows that my scan is still blocked. What’s up with that? This is happening because I still have the DROP all rule at the end of the INPUT table. Let’s disable that and see what we get with another scan.

First, I need to see what the rule number is:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
DROP all -- anywhere anywhere

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Counting down, I can see that it’s rule number 4, so I’ll delete it:

donnie@ubuntu:~$ sudo iptables -D INPUT 4
donnie@ubuntu:~$donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
donnie@ubuntu:~$

Now, for the XMAS scan:

[donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:49 EDT
Nmap scan report for 192.168.0.15
Host is up (0.00047s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open|filtered ssh
MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

Nmap done: 1 IP address (1 host up) scanned in 98.76 seconds
[donnie@fedora-teaching ~]$

This time, the scan shows that 999 ports are closed and that port 22, the SSH port, is either open or filtered. This shows that the scan is no longer being blocked by anything.

Restoring the deleted rules

When I used the iptables -D commands, I only deleted the rules from the runtime configuration, and not from the rules.v4 configuration file. To restore the rules that I deleted, I can either reboot the machine or restart the netfilter-persistent service. The latter choice is quicker, so I’ll activate this with the following code:

donnie@ubuntu:~$ sudo systemctl restart netfilter-persistent
[sudo] password for donnie:
donnie@ubuntu:~$

The iptables -L and iptables -t mangle -L commands will show that all the rules are now back in effect.

Hands-on lab for blocking invalid IPv4 packets

For this lab, you’ll use the same virtual machine that you used for the previous lab. You won’t replace any of the rules that you already have. Rather, you’ll just add a couple. Let’s get started:

  • Look at the rules for the filter and the mangle tables. (Note that the -v option shows you statistics about packets that were blocked by DROP and REJECT rules.) Then, zero out the blocked packets counter:
sudo iptables -L -v
sudo iptables -t mangle -L -v
sudo iptables -Z
sudo iptables -t mangle -Z
  • From either your host machine or another virtual machine, perform the NULL and Windows Nmap scans against the virtual machine:
sudo nmap -sN ip_address_of_your_VM
sudo nmap -sW ip_address_of_your_VM
  • Repeat Step 1. You should see a large jump in the number of packets that were blocked by the final DROP rule in the INPUT chain of the filter table:
sudo iptables -L -v
sudo iptables -t mangle -L -v
  • Make the firewall work more efficiently by using the PREROUTING chain of the mangle table to drop invalid packets, such as those that are produced by the two Nmap scans that we just performed. Add the two required rules with the following two commands:
sudo iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP

sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
  • Save the new configuration to your own home directory. Then, copy the file to its proper location and zero out the blocked packet counters:
sudo iptables-save > rules.v4
sudo cp rules.v4 /etc/iptables
sudo iptables -Z
sudo iptables -t mangle -Z
  • Perform only the NULL scan against the virtual machine:
sudo nmap -sN ip_address_of_your_VM
  • Look at the iptables ruleset and observe which rule was triggered by the Nmap scan:
sudo iptables -L -v
sudo iptables -t mangle -L -v
  • This time, perform just the Windows scan against the virtual machine:
sudo nmap -sW ip_address_of_your_VM
  • Observe which rule was triggered by this scan:
sudo iptables -L -v
sudo iptables -t mangle -L -v

That’s the end of this lab—congratulations!

Protecting IPv6

I know, you’re used to having all networking based on IPv4, with its nice, short, easy to use IP addresses. However, that can’t last forever, considering that the world is now out of new IPv4 addresses. IPv6 offers a much larger address space that will last for a long time to come. Some organizations, especially wireless carriers, are either in the process of switching over to IPv6 or have already switched to it.

So far, all we’ve covered is how to set up an IPv4 firewall with iptables. But remember what we said before. With iptables, you need one daemon and one set of rules for the IPv4 network, and another daemon and set of rules for IPv6. This means that when using iptables to set up a firewall, protecting IPv6 means doing everything twice. Most Linux distros come with IPv6 networking enabled by default, so you either need to protect it with a firewall or disable it. Otherwise, your IPv6 address will still be open for attack since the IPv4 firewall that you’ve just configured won’t protect it. This is true even if your server or device is facing the IPv4 internet because there are ways to tunnel IPv6 packets through an IPv4 network. Fortunately, the commands for setting up an IPv6 firewall are mostly the same as what we’ve just covered. The biggest difference is that instead of using the iptables command, you’ll use the ip6tables command. Let’s start with our basic setup, just like what we did for IPv4:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -i lo -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p tcp --dport ssh -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p tcp --dport 53 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p udp --dport 53 -j ACCEPT

The other big difference between IPv4 and IPv6 is that with IPv6, you must allow more types of ICMP messages than you need to for IPv4. This is due to the following reasons:

  • With IPv6, new types of ICMP messages have replaced the Address Resolution Protocol (ARP).
  • With IPv6, dynamic IP address assignments are normally done by exchanging ICMP discovery messages with other hosts, rather than by DHCP.
  • With IPv6, echo requests and echo replies, the infamous ping packets, are required when you need to tunnel IPv6 packets through an IPv4 network.

And of course, we still need the same types of ICMP messages that we need for IPv4. So, let’s start with them:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 1 -j ACCEPT
[sudo] password for donnie:
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 2 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 3 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 4 -j ACCEPT
donnie@ubuntu3:~$

These message types are as follows, in order of appearance:

  • Destination unreachable
  • Packet too big
  • Time exceeded
  • Parameter problem with the packet header

Next, we’ll enable echo requests (type 128) and echo responses (type 129) so that IPv6 over IPv4 tunneling will work:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 128 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 129 -j ACCEPT
donnie@ubuntu3:~$

Note:

The Teredo protocol is one of a few different ways to tunnel IPv6 packets across an IPv4 network. This protocol is what requires echo requests and echo replies, the infamous ping packets, to be allowed through a firewall. However, if you search through your distro repositories for a Teredo package, you won’t find it. That’s because the Linux implementation of the Teredo protocol is called miredo. So, when installing the Teredo protocol on a Linux machine, you’ll need to install the miredo and miredo-server packages.

The next four ICMP message types that we need are for the Link-local Multicast Receiver Notification messages:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 130
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 131
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 132
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 143
donnie@ubuntu3:~$

These are as follows, in order of appearance:

  • Listener query
  • Listener report
  • Listener done
  • Listener report v2

Next up is our neighbor and router discovery message types:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 134 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 135 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 136 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 141 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 142 -j ACCEPT
donnie@ubuntu3:~$

These are as follows, in order of appearance:

  • Router solicitation
  • Router advertisement
  • Neighbor solicitation
  • Neighbor advertisement
  • Inverse neighbor discovery solicitation
  • Inverse neighbor discovery advertisement

Space doesn’t permit me to go into the details of these message types. So, for now, let’s just say that they’re required in order for IPv6 hosts to dynamically assign themselves an IPv6 address.

For times when you’re using security certificates to authenticate the routers that are attached to your network, you’ll also need to allow Secure Neighbor Discovery (SEND) messages:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 148 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 149 -j ACCEPT
donnie@ubuntu3:~$

Are your fingers tired yet? If so, have no fear. This next group of ICMP rules is the last one. This time, we need to allow Multicast Router Discovery messages:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 151 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 152 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 153 -j ACCEPT
donnie@ubuntu3:~$

Finally, we’ll add our DROP rule to block everything else:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -j DROP
donnie@ubuntu3:~$

Now, I know you’re thinking, Wow, that’s a lot of hoops to jump through just to set up a basic firewallAnd yeah, you’re right, especially when you also need to configure rules for IPv6. Soon, I’ll show you what the Ubuntu folk came up with to make things simpler.

Hands-on lab for ip6tables

For this lab, you’ll use the same Ubuntu virtual machine that you used in the previous iptables labs. You’ll leave the IPv4 firewall setup that’s already there as is and create a new firewall for IPv6. Let’s get started:

  • View your IPv6 rules, or lack thereof, by using the following command:
sudo ip6tables -L
  • Create the IPv6 firewall. Due to formatting constraints, I can’t list the entire code block of commands here. You can find the respective commands in this chapter’s directory, in the code file that you can download from the Packt Publishing website.
  • View the new ruleset by using the following command:
sudo ip6tables -L
  • Next, set up the mangle table rules for blocking invalid packets:
sudo ip6tables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP

sudo ip6tables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
  • Save the new ruleset to a file in your own home directory, and then transfer the rules file to the proper location:
sudo ip6tables-save > rules.v6
sudo cp rules.v6 /etc/iptables/
  • Obtain the IPv6 address of the virtual machine by using the following command:
ip a
  • On the machine that you installed Nmap on, perform a Windows scan of the virtual machine’s IPv6 address. The command will look like this, except with your own IP address:
sudo nmap -6 -sW fe80::a00:27ff:fe9f:d923
  • On the virtual machine, observe which rule was triggered by using the following command:
sudo ip6tables -t mangle -L -v

You should see non-zero numbers for the packet counters for one of the rules.

  • On the machine that you installed Nmap on, perform an XMAS scan of the virtual machine’s IPv6 address. The command will look like this, except with your own IP address:
sudo nmap -6 -sX fe80::a00:27ff:fe9f:d923
  • As before, on the virtual machine, observe which rule was triggered by this scan:
sudo ip6tables -t mangle -L -v

That’s the end of this lab – congratulations!

So far, you’ve seen the good, the bad, and the ugly of iptables. It’s very flexible, and there’s a lot of power in the iptables commands. If you’re clever at shell scripting, you can create some rather complex shell scripts that you can use to deploy firewalld on the machines all across your network. 

On the other hand, getting everything right can be quite complex, especially if you need to consider that your machines have to run both IPv4 and IPv6, and that everything you do for IPv4 has to be done again for IPv6. (If you’re a masochist, you might actually enjoy it.)

Related Articles

How to add swap space on Ubuntu 21.04 Operating System

How to add swap space on Ubuntu 21.04 Operating System

The swap space is a unique space on the disk that is used by the system when Physical RAM is full. When a Linux machine runout the RAM it use swap space to move inactive pages from RAM. Swap space can be created into Linux system in two ways, one we can create a...

read more

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

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

two × two =