Iptables is a common and powerful tool, and one of its use cases is to create firewalls on Linux systems. It’s syntax, from an initial glance, is very complicated. This post aims to clarify the syntax of this tool and make it easier to use. With some quick practice, using iptables become pretty easy and intuitive.
Introduction
Iptables works using “tables” and each table has their own “chains” that have “rules” which apply “actions”. There are five by default in iptables, the “Filter”, “NAT”, “Mangle”, “Raw”, and “Security”. Tables are a categorization of what the chains will do; for example, the Filter table works on filtering traffic received and sent by the local machine. The NAT table focuses on implementing network translation; the modification of the source and destination addresses. This post will only cover these two tables. As for chains, they are categorizations for certain stages of traffic flow. The rules of the chain apply actions depending if the traffic, when it reaches a certain chain, fits the criteria. There are five main chains: INPUT, OUTPUT, FORWARD, PREROUTING, and POSTROUTING.
Rule | Functionality |
---|---|
INPUT | Work with packets that have a destination for the local system. |
OUTPUT | Work with packets being sent outbound by the local system after being locally processed. |
FORWARD | Work with packets that are being routed by the local system |
PREROUTING | Work with any packets incoming to the local system. |
POSTROUTING | Works with any packets being sent out by the local system. |
This may seem a bit confusing, so here is an image for the order of which these chains are utilized in:
Traffic is first proccessed by the PREROUTING chain, and after that, is routed. If its being routed locally, it passes by the INPUT chain which eventually is passed into some local process. After being locally processed, the traffic has to be sent back, so there it passes the OUTPUT chain. However, if the traffic is being initially routed to another machine, it passes the FORWARD chain. Both of these paths converge towards the POSTROUTING chain, and then the traffic gets sent if none of these chains restrict it.
The Filter Table
This table is primarily used for host level firewalling, and it is the default table if no flags specify a table to be used. Let’s say you have a website listening on port 80/tcp and 443/tcp, but nothing else. To set a strong, default deny policy, implement these two rules.
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
A breakdown of the flags and arguments:
- -P: specify the default policy for the specified chain
- INPUT: utilize the input chain
- OUTPUT: utilize the output chain
- DROP: by default, drop all packets in the input chain
- -A: Append to the chain
- -p: specify the protocol (tcp/udp) the packet must match
- -m: specify a module. iptables can utilize modules for more specific matching
- multiport: use the multiport module
- –dports: to match this rule, the packet must have a destination port of one of the following
- conntrack: utilize the conntrack module
- –ctstate: a parameter of the “conntrack” module, it specifies the connection states as an identifier for the packet
- NEW: a packet that is new; it is starting a new connection.
- RELATED: a packet starting a new connection, but is related or spawned from another existing connection
- ESTABLISHED: a packet that is originating from some existing connection in either direction, basically a reponse
- –ctstate: a parameter of the “conntrack” module, it specifies the connection states as an identifier for the packet
- multiport: use the multiport module
- -j: The action to perform if a packet matches all of the specifications of this rule
- ACCEPT: allow the traffic to pass
These first two rules set a default deny input/output policy. The third rule sets a rule in the INPUT chain of the Filter table that allows any new incoming tcp connection to ports 80 and 443. However, communication goes two ways, so when the website receives a connection, it has to respond to it (such as displaying the correct webage). So the fourth rule goes to the OUTPUT chain of the filter table and allows any packet to go outbound as long as its sourced from an existing connection.
This is simply an example; there are many different ways to use this table to filter traffic. When creating rules, make sure that any chain that accepts new traffic will most likely need the opposite chain to accept established/related traffic (basically the response to the new traffic). Another example is an SSH server; it must accept new 22/tcp connections, but must respond back to the client, and therefore must accept related/established output traffic.
The NAT Table
This table is primarily used for translating traffic. If traffic passes through a machine, destined for it or not, it can be routed using this table. The following example is with port forwarding.
iptables -P FORWARD DROP
iptables -A FORWARD -d 172.17.0.0/24 -j ACCEPT
iptables -A FORWARD -s 172.17.0.0/24 -j ACCEPT
iptables -t nat -A PREROUTING -s 172.17.0.0/24 -p tcp --dport 80 -d 172.18.0.74 -j DNAT --to-destination 10.0.0.74:80
iptables -t nat -A POSTROUTING -p tcp --dport 80 -d 172.18.0.74 -j MASQUERADE
# You can also do this: iptables -t nat -A POSTROUTING -p tcp --dport 80 -d 172.18.0.74 -j SNAT --to-source 10.0.0.68
A breakdown of the new flags
- -t nat: use the nat table
- -A: Append to a chain (of the specified table)
- PREROUTING: use the prerouting chain
- POSTROUTING: use the postrouting chain
- -d: a destination ip or ip range
- -s: a source ip or ip range
- -j: if the packet follows the rule, apply the following action
- DNAT: destination NATting, or basically, changing the destination address to a specified one
- –to-destination: change the destination to an ip:port (port is optional)
- SNAT: source NATting, or basically, changing the source address to a specified one
- –to-source: change the source to an ip:port (port is optional)
- MASQUERADE: change the source address to a dynamically configured one (machine automatically tries to pick an available interface that works)
- DNAT: destination NATting, or basically, changing the destination address to a specified one
The first rule sets a default deny policy on the forward chain, and the following two allow forwarding to and from the 172.17.0.0/24 network. The fourth rule changes the destination address of the traffic to port 80 of 10.0.0.74, if the traffic is from the 172.17.0.0/24 network, and has a destination of port 80/tcp on the 172.18.0.74 machine. The fifth rule will automatically change the source address of the traffic to one that the destination machine can communicate with if it is destined for port 80/tcp on 172.18.0.74. The last rule is an alternative to the fifth one; rather than automatically choosing a source address, the rule will pick a specific one. What all of this will do is: port forward traffic to 10.0.0.68 on port 80/tcp if it comes from a machine on the 172.0.0/24 network that is trying to hit 172.18.0.74 on port 80/tcp.
This is great, but scaling this seems tough; how can we NAT public addresses to private ones without having to create 2 x N amount of rules? Well we can utilize this rule (assume the same topology and usage of the )
iptables -t nat -A PREROUTING -s 172.17.0.0/24 -d 172.18.0.0/24 -j NETMAP --to 10.0.0.0/24
iptables -t nat -A POSTROUTING -s 172.17.0.0/24 -j MASQUERADE
Hopefully you can understand this by now, aside from the new module being used. Basically, assuming you either are using the same Forward filtering as before or none at all, these rules will NAT traffic from the 172.18.0.0/24 network to 10.0.0.0/24 network as long as the source is from the 172.17.0.0/24 network. So basically, from the previous diagram, if the 172.17.0.2 machine were to attempt to hit 172.18.0.2, that would be routed to 10.0.0.2.
Conclusion
I actively avoided iptables for the longest time until about two weeks ago, due to a series of interests spawning from reverse proxying web servers and Docker (maybe posts on those soon). However, iptables has struck out the most to me as it was a lot less complicated than I had recalled. Hopefully this posts clarifies the syntax; after a few days, reading rules felt like reading a fluent sentence.
-Dylan Tran 3/28/22