Understanding iptables and Netfilter: The Five Chains and Four Tables in Linux Kernel Networking
This article explains how iptables, built on the Netfilter framework, processes network packets through five kernel hooks and four tables, covering the receive, send, and forward paths, and demonstrates practical NAT, DNAT, filter, raw, and mangle rules for Docker and Kubernetes environments.
Hello everyone, I am Fei!
Nowadays the iptables tool is increasingly used not only for traditional firewall and NAT functions but also in popular Docker, Kubernetes, and Istio projects, making a deep understanding of its operation highly valuable.
The Linux kernel network stack runs entirely in kernel space and is isolated from user‑space, but the kernel exposes several hooks that allow user‑space programs to influence its behavior.
Many critical points in the kernel network components are equipped with netfilter filters, and iptables is implemented on top of netfilter, so the two terms are often used interchangeably.
Below we will detail the "four tables and five chains" model of netfilter.
1. The Five Chains in iptables
Linux netfilter places five hooks at important points in the kernel protocol stack, each representing a chain of rules, commonly referred to as the "five chains". When a packet traverses these points, the corresponding rules are evaluated in order.
The key to understanding the five chains is to view the kernel's receive, send, and forward processes separately.
1.1 Receive Process
The entry point for packet reception at the IP layer is ip_rcv. The first hook encountered is PREROUTING . After PREROUTING, routing is performed; if the packet is destined for the local device, it proceeds to ip_local_deliver and encounters the INPUT hook.
Key source code for ip_rcv:
//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, ...){
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}The NF_HOOK call executes the rules registered in the PREROUTING chain of iptables. After processing, control returns to ip_rcv_finish, which performs routing and then calls dst_input.
//file: net/ipv4/ip_input.c
static int ip_rcv_finish(struct sk_buff *skb){
...
if (!skb_dst(skb)) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
...
}
...
return dst_input(skb);
}If the packet belongs to the local device, ip_local_deliver is invoked, which hits the INPUT hook:
//file: net/ipv4/ip_input.c
int ip_local_deliver(struct sk_buff *skb){
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}Summary of the receive flow: PREROUTING → routing decision (local) → INPUT → …
1.2 Send Process
When sending a packet, routing is performed first, then the first hook encountered is OUTPUT , followed by POSTROUTING .
Key source code for the sending entry point ip_queue_xmit:
//file: net/ipv4/ip_output.c
int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl){
// routing selection
// store routing info in skb
rt = (struct rtable *)__sk_dst_check(sk, 0);
if (rt == NULL) {
// no cache, look up route
rt = ip_route_output_ports(...);
sk_setup_caps(sk, &rt->dst);
}
skb_dst_set_noref(skb, &rt->dst);
...
// send
ip_local_out(skb);
}The packet then reaches __ip_local_out, which invokes the OUTPUT chain via NF_HOOK:
//file: net/ipv4/ip_output.c
int __ip_local_out(struct sk_buff *skb){
struct iphdr *iph = ip_hdr(skb);
iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
skb_dst(skb)->dev, dst_output);
}After OUTPUT , the packet proceeds to POSTROUTING via ip_output:
//file: net/ipv4/ip_output.c
int ip_output(struct sk_buff *skb){
...
return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, skb, NULL, dev,
ip_finish_output,
!(IPCB(skb)->flags & IPSKB_REROUTED));
}Summary of the send flow: routing → OUTPUT → POSTROUTING → …
1.3 Forward Process
Linux can also act as a router, forwarding packets that are not destined for the local host. The flow is: PREROUTING → routing (non‑local) → FORWARD → POSTROUTING → …
Key code snippets:
//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, ...){
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
} //file: net/ipv4/ip_forward.c
int ip_forward(struct sk_buff *skb){
...
return NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, skb, skb->dev,
rt->dst.dev, ip_forward_finish);
}After FORWARD , the packet re‑enters the send path via ip_output, encountering POSTROUTING again.
1.4 iptables Summary
Combining the three processes yields the overall packet flow diagram (image omitted for brevity).
2. The Four Tables in iptables
Each chain can contain many rules, and the rules belong to one of four tables: raw , mangle , nat , and filter . Their purposes are:
raw : highest priority; packets matching its rules skip processing in other tables.
mangle : modifies packet fields such as TTL.
nat : performs network address translation.
filter : implements basic firewall filtering.
For example, the PREROUTING chain can invoke raw , mangle , and nat tables.
Not all tables use every chain. raw only needs PREROUTING and OUTPUT; mangle can use all hooks; nat works in PREROUTING, INPUT, OUTPUT, and POSTROUTING; filter only uses INPUT, OUTPUT, and FORWARD.
3. iptables Usage Examples
3.1 NAT (SNAT)
Assume a host with IP 10.162.0.100 and a Docker network net1 with interface veth1 at 192.168.0.2. To allow 192.168.0.2 to reach external networks, a SNAT rule is added in the host namespace:
# iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADEThe rule rewrites the source address of packets from the Docker subnet to the host's external IP before they leave the host.
3.2 DNAT (Destination NAT)
To expose a service running on 192.168.0.2:80 to the outside world, a DNAT rule is added to the PREROUTING chain:
# iptables -t nat -A PREROUTING ! -i br0 -p tcp -m tcp --dport 8088 -j DNAT --to-destination 192.168.0.2:80This rewrites incoming packets destined for port 8088 on the host to the container's address and port.
3.3 Filter
The filter table is used to block malicious IPs. Example to drop traffic from 1.2.3.4:
# iptables -I INPUT -s 1.2.3.4 -j DROP // block
# iptables -D INPUT -s 1.2.3.4 -j DROP // unblockTo restrict SSH access to a single trusted IP:
# iptables -t filter -I INPUT -s <trusted_ip> -p tcp --dport 22 -j ACCEPT
# iptables -t filter -I INPUT -p tcp --dport 22 -j DROP3.4 Raw
The raw table can bypass connection tracking. To disable tracking for traffic from 1.2.3.4 on port 80:
# iptables -t raw -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j NOTRACK
# iptables -A FORWARD -m state --state UNTRACKED -j ACCEPT3.5 Mangle
To compensate for the TTL decrement performed by routers, a mangle rule can increment TTL by 1 on incoming packets on eth0:
# iptables -t mangle -A PREROUTING -i eth0 -j TTL --ttl-inc 1Summary
iptables is a fundamental tool for Linux firewalls, NAT, and many container orchestration platforms such as Docker, Kubernetes, and Istio. Understanding the five kernel hooks (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING) and the four tables (raw, mangle, nat, filter) enables precise control over packet processing, security policies, and network address translation.
We first examined the receive, send, and forward processes to locate the five chains, then described how each table maps to specific hooks, and finally demonstrated practical examples for raw, mangle, nat, and filter tables.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Refining Core Development Skills
Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
