Demystifying the IPTABLES NAT Table: A Comprehensive Guide
Before you begin
I would recommend that you have at least the fundamental knowledge of networking including but not limited to tcp/ip protocol, NAT, IP Packets and how networks work.
Introduction
The linux-kernel has it is own network processing subsystem called netfilter and iptables is a command to configure that. There are three tables named filter table, nat table, and mangle table. These tables have various chains namely input chains,output chain, prerouting chain and postrouting chain. For this discussion we will focus on the nat table.
NAT table
The nat table is responsible for managing the translation of ip addresses and ports between public and private networks. The NAT table changes the source or destination IP addresses of packets as they pass through a router or firewall. This allows multiple devices on a private network to share a single public IP address.
There are two types of nat namely SNAT and DNAT.
SNAT or Source NAT modifies the source of outgoing packets.
DNAT or Destination NAT modifies the destination of incoming packets.
TIP: When dealing with translations with SNAT or DNAT process it as translating from "source to source" or "destination to destination" to avoid confusion.
NAT Table Internals
Let's look at the above image, consider a packet arrives at interface 1, it will go through PREROUTING CHAIN let's assume that there are no rules set-up in the PREROUTING chain and the packet was intended for a local process with pid 1, the packet will reach its destination. If there are some rules in the PREROUTING chain for redirecting the packet it will go through the POSTROUTING chain and exit via the same or a different interface.
All incoming packets go through the PREROUTING chain and All the outgoing packets go through POSTROUTING chain, in these chains the rules for translation of destination and source are present. Now we get to the fun part we will do some hands on and will play with the nat table.
Port Forwarding using iptables
For the purpose of demonstration, I will be using an EC2 Ubuntu instance.
Why do we need port forwarding? Let us assume that I want to run a web application, conventionally the default port for HTTP requests is port 80 but it requires super user privileges to run our application on port 80. Due to security reasons it is recommended to not give your application server root privileges unless necessary, so we will run this application on port 8080, I wrote a simple Hello World application in go for this case.
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Println("Starting server at port 8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println(err)
}
}
The packet enters the network interface with source IP 192.168.1.2, source port 1234, destination port 80 and destination IP 10.0.0.2. After passing PREROUTING chain the destination port is changed to 8080 on which are application is listening.
sudo iptables --table nat \
--append PREROUTING \
--protocol tcp \
--dport 80 \
--jump REDIRECT \
--to-port 8080
The command is self explanatory, we specify the table we want to work with which is the nat table, append to PREROUTING chain for packets with protocol TCP and destination port 80 jump to REDIRECT(it is a pre-built target on your system) and translate destination port to 8080. The command translates destination port from 80 to destination port 8080.
Try reaching your application on port 80.
Redirecting traffic to another machine
Another more useful use case of NAT is to redirect traffic to another internal machine, so only a single public IP is required for multiple private machines. NAT performs this translation of public and private IP addresses.
Consider this scenario someone makes a request from 192.168.1.2 to my public instance on 10.0.0.2 port 80. The DNAT changes destination IP on the packet from 10.0.02 to 192.168.1.3 which is where my web application is hosted. Now the packet reaches the application and the server responds with its own source IP 192.168.1.2 which will fail. The source kernel is smart it recognise sending request 10.0.0.2 not to 192.168.1.3 ,thus it drops the response packets from the application. This is where SNAT steps in.
SNAT changes the source IP from 192.168.1.2 to 10.0.0.2 so that it appears to the application server that the packet is from the NAT instance, similarly when responding back to the client it appears as is the packet was sent by the NAT instance itself. Now that we have a feel for how things are working let's get our hands dirty.
IP forwarding
Before we can start playing with our nat table we need to enable IP forwarding on your intermediary instance, follow below steps if you are on an Ubuntu or Debian machine. The Steps might differ based on your Linux Distribution.
sudo sysctl -w net.ipv4.ip_forward = 1
If you want to enable ip forwarding permanently open the /etc/sysctl.conf file in your favourite editor and undo the comment on line net.ipv4.ip_forward = 1. Apply the changes with the command
sudo sysctl -p
Now we can start by configuring the NAT table on our intermediary instance.
PREROUTING (DNAT)
sudo iptables --table nat \
--append PREROUTING \
--protocol tcp \
--in-interface <NAT instance interface> \
--dport 80 \
--jump DNAT \
--to-destination <ip of the application server>
The command is pretty much the same except the the --jump target is now DNAT so we can change our destination ip to the private ip of our application.
POSTROUTING (SNAT)
sudo iptables --table nat \
--append POSTROUTING \
--protocol tcp \
--source <ip of application server> \
--out-interface <NAT instance interface> \
--jump MASQUERADE
Alternatively, but not recommended you can use the ip address of NAT instance if the ip addresses are permanent which they are typically not.
sudo iptables --table nat \
--append POSTROUTING \
--protocol tcp \
--source <ip of application server> \
--out-interface <NAT instance interface> \
--jump SNAT \
--to-source <ip of NAT instance> #assuming there is only one interface
--jump MASQUERADE automatically sets the source ip as the ip of the interface through which the packet leaves the NAT instance.
--jump SNAT it is similar to the target DNAT and you can use it to explicitly set the source ip address as the public ip of NAT instance. (Generally not recommended)
The Traffic Flow
The client sends a request to public ip of NAT instance on port 80, the packet goes through the PREROUTING chain and the destination is changed to application server's ip address by DNAT. The packet reaches POSTROUTING chain the source IP is changed to IP of NAT instance's interface (MASQUERADE). The packet reaches the application server, a response is sent to the NAT instance by application server. The NAT instance responds to client after changing the source IP to its own interface ip (MASQUERADE).
Ending Note
We have successfully managed to scratch the surface of iptables and the nat table, I would like to end on the note that there is a lot more that goes behind the scenes like Routing Rules, Firewalls, Stateful Connection Tracking which are beyond the topic of this discussion that actually makes it possible to work with NAT. I do encourage you to read further about them. Thank you for reading so far!
Subscribe to my newsletter
Read articles from Ghanatva Thakaran directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ghanatva Thakaran
Ghanatva Thakaran
I am student currently pursuing my Bachelor's in computer science. I have a keen interest in DevOps and LINUX related technologies