网络收发包应用之 iptables
iptables 最容易混乱的地方,是它同时在讲两件事:
- 从内核视角看,Linux 在网络包经过协议栈的几个关键位置埋了 Netfilter hook。包流到这些位置时,内核会调用已经注册好的规则处理函数。
- 从用户视角看,iptables 是配置这些规则的命令行工具。我们写的
iptables -A ... -j ...最终会变成内核里某个 hook 上的一条规则。
所以这篇笔记可以用一句话串起来:iptables 把规则按功能放进表 table,按网络包经过的位置挂到链 chain,网络包经过 Netfilter hook 时按顺序匹配规则,命中后执行 target 动作。
几个关键词先对齐:
| 名词 | 作用 | 记忆方式 |
|---|---|---|
| hook | 内核协议栈里的固定检查点 | 包走到这里,内核问一声:有没有规则要处理 |
| chain | 某个 hook 上的一串规则 | PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING |
| table | 按功能组织规则 | raw、mangle、nat、filter,现代系统还有 security |
| rule | 匹配条件和动作 | 如 -s 1.2.3.4 -j DROP |
| target | 命中后的动作 | ACCEPT、DROP、DNAT、SNAT、MASQUERADE、MARK 等 |
说明:很多文章会说“四表五链”,这里的四表通常指
raw/mangle/nat/filter。现代 iptables 还可能有security表,用于 SELinux 等强制访问控制场景。学习网络收发包和 Docker NAT 时,先掌握四表五链就够了。
一、iptables 常见应用
1. 四表的职责
iptables 从应用视角来看分为 4 表:

这张图是在回答“规则按什么功能分类”:
filter表:做包过滤,是防火墙最常见的表。例如允许、拒绝、丢弃某些包。nat表:做地址和端口转换。例如 Docker 容器访问外网时做 SNAT/MASQUERADE,外网访问容器端口时做 DNAT。mangle表:修改包的附加属性或标记。例如改 TTL、TOS,或者打MARK给策略路由、流量整形使用。raw表:主要用于连接跟踪之前的早期处理。例如配合NOTRACK跳过 conntrack,减少某些高流量场景下的状态跟踪开销。
理解这张图时不要先纠结命令,而是先想清楚:filter 决定包能不能过,nat 决定包看起来从哪里来、到哪里去,mangle 决定包带什么标记,raw 决定要不要尽早绕开连接跟踪。
2. filter:在 INPUT 链过滤本机流量
首先来看 filter 过滤:

这张图画的是一个“进入本机”的包:
- 包从网卡进入,到 IP 层入口
ip_rcv()。 - 先经过路由前的
PREROUTING。 - 内核查路由,发现目的 IP 是本机。
- 包进入
ip_local_deliver(),在真正交给 TCP/UDP 之前经过INPUT。 INPUT链上的filter规则判断是否接受这个包。
也就是说,想保护本机服务,通常在 filter/INPUT 上下规则。例如禁止某个 IP 访问本机,或者只允许白名单 IP 访问 SSH。
可以通过命令配置:

图中的两个场景:
# 场景 1:拒绝某个 IP 对本机的全部请求
iptables -I INPUT -s 1.2.3.4 -j DROP
iptables -D INPUT -s 1.2.3.4 -j DROP
# 场景 2:SSH 只允许白名单 IP 访问
iptables -t filter -I INPUT -s 1.2.3.4 -p tcp --dport 22 -j ACCEPT
iptables -t filter -I INPUT -p tcp --dport 22 -j DROP
这里有两个细节很重要:
-I INPUT是插到链头,优先级比后面的规则高;-A INPUT是追加到链尾。- 第二个场景必须先放行白名单,再丢弃其他 SSH 流量。如果顺序反了,白名单也会被前面的 DROP 拦住。
iptables 的规则匹配是“从上到下,命中就执行动作”。如果 target 是 ACCEPT/DROP/REJECT/DNAT/SNAT 这类终止动作,后续规则通常就不会继续匹配了。
3. NAT 场景一:容器或私网主机访问外网
再来看看 NAT。
场景一:请求发送。

这张图画的是 Docker 类似的网络结构:
net1是一个网络命名空间,可以理解成容器的网络世界。veth1在容器命名空间中,IP 是192.168.0.2。br0在宿主机命名空间中,IP 是192.168.0.1,连接容器 veth。- 宿主机还有外网网卡
eth0,IP 是10.162.0.100。 - 外部服务器是
10.162.0.101。
容器里的 192.168.0.2 是私有地址,外部网络不知道怎么把响应包路由回它。所以容器请求外部服务器时,宿主机必须把源地址从 192.168.0.2 改成宿主机外网地址 10.162.0.100。这就是 SNAT。
其原理如下:

对应规则:
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE
这条规则的意思是:
-t nat:使用 NAT 表。-A POSTROUTING:在路由决策之后、真正发出网卡之前处理。-s 192.168.0.0/24:只处理来自容器网段的包。! -o br0:如果包不是发回容器网桥,就说明它要出外部网络。-j MASQUERADE:把源地址伪装成出口网卡的地址。
为什么 SNAT 放在 POSTROUTING?因为此时路由已经算出来了,内核已经知道包要从哪个出口网卡出去。MASQUERADE 才能使用出口网卡的地址作为新的源地址。
回包时不需要再写一条显式 DNAT 规则。第一次出包做 SNAT 时,conntrack 已经记录了连接映射:192.168.0.2 -> 10.162.0.101 被改写成了 10.162.0.100 -> 10.162.0.101。响应包回来后,内核根据 conntrack 自动做反向转换,把目的地址从 10.162.0.100 还原成 192.168.0.2。
4. NAT 场景二:外网访问容器服务
场景二:请求接收。

这张图画的是外部机器访问宿主机的 10.162.0.100:8088,但真正提供服务的是容器里的 192.168.0.2:80。外部机器并不知道容器地址,只知道宿主机地址,所以宿主机需要把“目的地址和端口”改写到容器服务上。这就是 DNAT,也就是常说的端口映射。
原理如下:

对应规则:
iptables -t nat -A PREROUTING ! -i br0 -p tcp --dport 8088 \
-j DNAT --to-destination 192.168.0.2:80
这条规则的意思是:
-A PREROUTING:包刚进入 IP 层、路由选择之前处理。! -i br0:不是从容器网桥进来的包,通常就是外部入口流量。-p tcp --dport 8088:匹配访问宿主机 8088 端口的 TCP 包。-j DNAT --to-destination 192.168.0.2:80:把目的地址改成容器的 IP 和端口。
为什么 DNAT 放在 PREROUTING?因为路由决策依赖目的地址。只有先把目的地址从 10.162.0.100:8088 改成 192.168.0.2:80,后面的路由才能判断这个包应该转发给 br0/veth1。
回包时同样依赖 conntrack。容器回给外部机器的包,源地址原本是 192.168.0.2:80,内核会自动反向改写成 10.162.0.100:8088。这样外部机器一直以为自己在和宿主机的 8088 端口通信。
二、iptables 底层实现
上述应用都基于一个实现:五个链。它们看起来是 iptables 的概念,实际对应的是网络包接收、转发、发送过程中的 Netfilter hook。

这张图把“五链”和“四表”放到了一起:
- 纵向是链,也就是包经过的位置:
INPUT/PREROUTING/OUTPUT/FORWARD/POSTROUTING。 - 横向是表,也就是规则的功能:
raw/mangle/nat/filter。 - 某个格子里有“规则”,说明这个表可以在这个链上挂规则。
注意:不是每张表都能挂到每条链上。比如 filter 只关心最终是否放行,所以主要在 INPUT/FORWARD/OUTPUT;raw 要尽早决定是否跳过连接跟踪,所以只在 PREROUTING/OUTPUT。
1. 接收本机流量:PREROUTING -> INPUT
结合网络收包过程进行理解:

这张图对应“外部请求本机服务”的路径:
- 网卡收到包,经过设备层处理后进入 IP 层。
- IPv4 接收入口是
ip_rcv()。 ip_rcv()触发NF_INET_PRE_ROUTING,也就是PREROUTING链。- 规则处理完后,进入路由选择。
- 如果路由判断目的地址是本机,就进入
ip_local_deliver()。 ip_local_deliver()触发NF_INET_LOCAL_IN,也就是INPUT链。INPUT放行后,才会继续交给 TCP/UDP,再进入本机 socket。
其源码如下:

图中红框是 ip_rcv() 里的关键调用:
NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, ..., ip_rcv_finish)
含义是:先执行 PREROUTING hook 上注册的规则,如果规则最终允许包继续走,再回调 ip_rcv_finish()。而 ip_rcv_finish() 里会做路由选择。

这张图的红框是 ip_local_deliver() 里的关键调用:
NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, ..., ip_local_deliver_finish)
含义是:路由已经确认这是本机包,真正交给传输层之前,执行 INPUT 链规则。防火墙在这里丢包,可以避免包继续进入 TCP/UDP 层和应用 socket。
小结:
- Linux 在网络包接收的 IP 层入口函数是
ip_rcv()。 - 先执行
PREROUTING规则。 - 再进行路由选择。
- 判断是本机包,则继续执行
INPUT规则。
2. 本机发送流量:OUTPUT -> POSTROUTING
再结合发送过程:

这张图对应“本机进程主动发包”的路径:
- 用户进程调用
send/write,数据经过 TCP 层。 - TCP 调用 IP 层发送入口,常见路径会进入
ip_queue_xmit()。 - 内核先查路由,确定目的地址、下一跳、出口网卡等信息。
- 进入
ip_local_out()或__ip_local_out(),触发NF_INET_LOCAL_OUT,也就是OUTPUT链。 - 继续进入
ip_output(),触发NF_INET_POST_ROUTING,也就是POSTROUTING链。 - 再往下进入邻居子系统、设备队列、网卡驱动。
其源码如下:

图中红框表示 ip_local_out() 里触发 OUTPUT:
NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_OUT, ..., dst_output)
这里的包是本机进程产生的,所以不会经过 PREROUTING。PREROUTING 是“从外部进来的包”才会经过的链。

图中红框表示 ip_output() 里触发 POSTROUTING:
NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, ..., ip_finish_output, ...)
POSTROUTING 的位置在路由之后、发出网卡之前。因此 SNAT/MASQUERADE 很适合放在这里:此时已经知道出口设备,可以安全地改源地址。
小结:
- Linux 在网络包发送的 IP 层常见入口函数是
ip_queue_xmit()。 - 先进行路由选择。
- 在
ip_local_out()或__ip_local_out()中执行OUTPUT规则。 - 在
ip_output()中执行路由后的POSTROUTING规则。
3. 转发流量:PREROUTING -> FORWARD -> POSTROUTING
再看网络包转发过程:

这张图对应“Linux 像路由器一样工作”的路径。典型场景就是宿主机帮容器转发流量,或者一台 Linux 做网关。
- 包从网卡进入,先到
ip_rcv()。 - 执行
PREROUTING。 - 内核查路由,发现目的地址不是本机,而是应该从另一块网卡转发出去。
- 进入
ip_forward(),执行FORWARD。 - 转发通过后进入
ip_output()。 - 执行
POSTROUTING。 - 包从出口网卡发出去。
源码如下:

这张图再次对应 ip_rcv() 触发 PREROUTING。不管最终是本机接收还是转发,只要是外部进来的 IPv4 包,都会先经过这里。

这张图对应 ip_forward() 触发 FORWARD:
NF_HOOK(NFPROTO_IPV4, NF_INET_FORWARD, ..., ip_forward_finish)
FORWARD 只处理“经过本机但不属于本机”的包。如果 Linux 没有开启 IP 转发,或者路由不允许转发,包不会顺利走到这一步。开启 IPv4 转发通常需要:
sysctl -w net.ipv4.ip_forward=1

这张图对应转发包进入 ip_output() 后触发 POSTROUTING。所以转发包路径和本机发包路径的后半段是重合的:最终都要通过 POSTROUTING 后再出网卡。
小结:
- Linux 在网络包接收的 IP 层入口函数是
ip_rcv()。 - 先执行
PREROUTING规则。 - 再进行路由选择。
- 判断不是本机包,则进入
ip_forward()转发,在这里执行FORWARD规则。 - 接着进入
ip_output()发送,会执行POSTROUTING规则。
三、iptables 原理汇总
再来看其实现的五链四表:

这张图是“表和链的对应关系”:
| 链 | 典型会经过的表 | 适合做什么 |
|---|---|---|
| PREROUTING | raw、mangle、nat | 入站最早期处理,DNAT,连接跟踪前处理 |
| INPUT | mangle、nat、filter | 本机入站包过滤或改标记 |
| FORWARD | mangle、filter | 转发包过滤或改标记 |
| OUTPUT | raw、mangle、nat、filter | 本机产生的包过滤、DNAT、连接跟踪前处理 |
| POSTROUTING | mangle、nat | 出站最后阶段处理,SNAT/MASQUERADE |
实际系统会受内核版本、iptables 后端和模块影响。例如一些文档会把 nat/INPUT 列出来,一些较老文档不列。日常排查时,可以用命令看当前系统到底支持哪些链:
iptables -t raw -S
iptables -t mangle -S
iptables -t nat -S
iptables -t filter -S
iptables -t security -S

这张图再次总结了四张表的用途。把它和上一张“表链矩阵”结合起来看,就能回答两个问题:
- 这个功能应该放在哪张表?例如过滤放
filter,地址转换放nat。 - 这个规则应该挂在哪条链?例如外部访问容器端口要先改目的地址,所以放
PREROUTING;容器访问外网要最后改源地址,所以放POSTROUTING。
结合网络收发转发过程来看:

图中的 1 到 5 可以作为最终记忆版:
| 流量类型 | 经过的链 | 例子 |
|---|---|---|
| 外部访问本机 | 1 PREROUTING -> 2 INPUT | 访问本机 Nginx、SSH |
| 本机访问外部 | 4 OUTPUT -> 5 POSTROUTING | 本机 curl 外部服务 |
| 外部经本机转发 | 1 PREROUTING -> 3 FORWARD -> 5 POSTROUTING | Docker 容器出网、Linux 网关转发 |
一句话记忆:
- 入站先 PREROUTING,再根据路由分流到 INPUT 或 FORWARD。
- 本机发包从 OUTPUT 开始。
- 所有真正要从网卡出去的包,最后都会经过 POSTROUTING。
规则优先级:同一条链上谁先执行
同一个 hook 上可能有多个表的规则。大体顺序可以这样记:
raw -> conntrack -> mangle -> nat(DNAT) -> filter -> security -> nat(SNAT)
更准确地说,Netfilter 内部按 priority 数值从小到大执行。raw 通常在连接跟踪之前,mangle 用于改包和打标,filter 用于过滤,DNAT 发生在路由前,SNAT 发生在路由后。
学习时先不用背完整优先级,抓住两个关键点就很够用:
raw比 conntrack 早,所以能配合NOTRACK。- DNAT 要在路由前,SNAT 要在路由后。
NAT 和 conntrack:为什么只写一条规则,回包也能回来
NAT 不是孤立地把每个包都硬改一遍。对于一个新连接,nat 表会在连接开始时决定如何改写,并把映射写入 conntrack。后续同一连接的包,包括反方向的响应包,会根据 conntrack 自动做一致的转换。
这解释了两个常见现象:
- 做 SNAT/MASQUERADE 时,只写出方向规则,响应包能自动回到容器。
- 做 DNAT 端口映射时,只写入方向规则,容器响应包的源地址也会自动改回宿主机端口。
如果 conntrack 表满了、被关闭了,或者 raw 表里对某些包做了 NOTRACK,NAT 和有状态防火墙就可能出现异常。
四、和 Docker 网络的关系
Docker 默认 bridge 网络本质上就是把前面的两个 NAT 场景自动化:
- 容器访问外网:容器私网地址通过宿主机做
POSTROUTING MASQUERADE。 - 外部访问容器端口:宿主机对外端口通过
PREROUTING DNAT转到容器 IP 和端口。
容器网络里还会用到:
network namespace:每个容器有自己的协议栈、网卡、路由表。veth pair:一端在容器里,一端接到宿主机网桥。bridge:宿主机上的docker0或自定义 bridge 负责连接多个容器。iptables:负责 NAT、端口映射和部分转发过滤。
所以排查 Docker 网络时,要同时看四样东西:
ip netns
ip addr
ip route
iptables -t nat -S
iptables -t filter -S
conntrack -L
如果容器能 ping 宿主机但不能访问外网,通常优先看:
net.ipv4.ip_forward是否开启。POSTROUTING MASQUERADE是否存在。FORWARD链是否允许转发。- 宿主机默认路由是否正确。
如果外部访问不了容器暴露端口,通常优先看:
PREROUTING DNAT或 Docker 生成的端口映射规则是否存在。- 宿主机防火墙是否在
INPUT/FORWARD丢包。 - 容器内服务是否真的监听在目标端口。
- 回包路径是否能走回宿主机。
五、和 tcpdump 的关系
推荐文章里还有一个很实用的问题:iptables 丢掉的包,tcpdump 能不能抓到?
答案要分方向:
- 收包方向:tcpdump 通常在网络设备层就能看到包,而 Netfilter 的
PREROUTING/INPUT在 IP 层。因此外部来的包即使后面被 iptables DROP,tcpdump 也可能已经抓到了。 - 发包方向:本机发出的包先经过 IP 层的
OUTPUT/POSTROUTING,再到设备层。如果在 Netfilter 阶段被 DROP,tcpdump 可能抓不到这个发出的包。
这能帮助排查一个经典疑惑:抓包能看到入站 SYN,不代表应用一定能收到;因为它可能在 INPUT 链被丢了。抓不到出站包,也可能是 OUTPUT 或 POSTROUTING 前就被规则处理掉了。
六、常用排查命令
查看规则,推荐优先用 -S,它展示的是可复用的命令形式:
iptables -S
iptables -t nat -S
iptables -t mangle -S
iptables -t raw -S
查看计数器,确认规则有没有命中:
iptables -nvL
iptables -t nat -nvL
iptables -t filter -nvL FORWARD
看连接跟踪:
conntrack -L
conntrack -S
看路由和转发:
ip route
sysctl net.ipv4.ip_forward
定位时按包路径问问题:
- 这个包是进本机、出本机,还是经本机转发?
- 它会经过哪几条链?
- 哪张表上的规则最可能影响它?
- 规则顺序是否正确?
- 计数器有没有增长?
- conntrack 里有没有对应连接?
七、扩展阅读
- 《深入理解 Linux 网络》
- 来,今天飞哥带你理解 iptables 原理!:本文主线来源,重点是把接收、发送、转发三条路径拆开理解五链四表。可访问同步版:腾讯云开发者社区。
- 手工模拟实现 Docker 容器网络!:适合把
namespace + veth + bridge + iptables NAT串起来,理解 Docker 默认 bridge 网络。 - 图解 Linux 网络包接收过程:适合补齐网卡中断、NAPI、软中断、
ip_rcv()之前的接收路径。可访问同步版:腾讯云开发者社区。 - 25 张图,一万字,拆解 Linux 网络包发送过程:适合补齐
ip_queue_xmit()之后到邻居子系统、qdisc、网卡驱动的发送路径。可访问同步版:腾讯云开发者社区。 - 用户态 tcpdump 如何实现抓到内核网络包的?:适合理解 tcpdump 抓包点和 Netfilter 过滤点的先后关系。可访问同步版:腾讯云开发者社区。
- iptables man page:查表、链、命令参数、target 语义时看它。
- Netfilter hooks - nftables wiki:虽然讲 nftables,但它和 legacy iptables 共用 Netfilter hook、conntrack、NAT engine,适合核对 hook 路径和优先级。
- Linux NAT HOWTO:理解 SNAT、DNAT、MASQUERADE 为什么分别放在 POSTROUTING 和 PREROUTING。