iptables

基本概念

iptables其实不是防火墙本身,它只是一个用户空间的工具,真正的防火墙功能是由内核中的netfilter框架提供的。
你可以把netfilter想象成一个在内核网络栈中设置的检查站,而iptables就是用来配置这些检查站规则的工具。

netfilter是内核模块,负责实际的数据包过滤工作;iptables是用户态程序,用来管理netfilter的规则。

netfilter的五个钩子

要理解iptables,必须先搞清楚netfilter在网络栈中设置的五个钩子点(hook points)。数据包在内核中传输时,会经过这些钩子点,每个钩子点都可以对数据包进行处理。

这五个钩子分别是:

  1. PREROUTING:数据包刚进入网络栈,还没有进行路由决策的时候。在这里可以修改数据包的目标地址,也就是DNAT(目标地址转换)。

  2. INPUT:数据包经过路由决策后,如果目标是本机,就会到达INPUT钩子;数据包被路由到本地进程之前进行处理。

  3. FORWARD:如果数据包的目标不是本机,需要转发给其他主机,就会经过FORWARD钩子;数据包被路由到其他接口之前进行处理。

  4. OUTPUT:本机产生的数据包在离开本机之前会经过OUTPUT钩子。

  5. POSTROUTING:数据包离开本机之前的最后一个钩子点,通常在这里做SNAT(源地址转换)。

画个简单的图来说明数据包的流向:

外部数据包 -> PREROUTING -> 路由决策 -> INPUT -> 本机应用
                                   |
                                   v
                               FORWARD -> POSTROUTING -> 外部网络
                                   ^
本机应用 -> OUTPUT -> 路由决策 -------|

iptables的四表五链

五链的详细说明
五链就是对应netfilter的五个钩子点,每个链都是规则的集合:

  • INPUT链:处理进入本机的数据包。比如别人SSH到你的服务器,或者访问你服务器上的Web服务,这些数据包都会经过INPUT链。

  • OUTPUT链:处理从本机发出的数据包。比如你在服务器上ping别的机器,或者服务器主动连接数据库,这些数据包会经过OUTPUT链。

  • FORWARD链:处理经过本机转发的数据包。只有当你的机器作为路由器或网关时,这个链才会被用到。比如你搭建了一个NAT网关,内网机器访问外网的数据包就会经过FORWARD链。

  • PREROUTING链:在路由决策之前处理数据包。主要用于DNAT,比如把访问公网IP的80端口转发到内网服务器。

  • POSTROUTING链:在数据包离开本机之前处理。主要用于SNAT,比如把内网IP转换成公网IP。

四表的详细说明:

四个表按照功能分工,每个表包含不同的链:

  • filter表(过滤表): 这是默认表,主要用于决定数据包是否允许通过。包含三个链:

    • INPUT链:处理进入本机的数据包。

    • OUTPUT链:处理从本机发出的数据包。

    • FORWARD链:处理经过本机转发的数据包。
      我们平时说的"防火墙规则",大部分都是在filter表里。比如:

      # 这条命令默认就是在filter表的INPUT链中添加规则
      iptables -A INPUT -p tcp --dport 22 -j ACCEPT
      
  • nat表(网络地址转换表): 主要用于网络地址转换(NAT),包含两个链:

    • PREROUTING:数据包进入时,路由前进行 DNAT(目标地址转换)

    • POSTROUTING:数据包离开时,进行 SNAT(源地址转换)或 MASQUERADE

    • OUTPUT:本机产生的数据包在路由前进行 NAT
      典型的应用场景:

    # DNAT:把访问本机80端口的请求转发到内网服务器
    iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80
    
    # SNAT:把内网访问外网的数据包源地址改成公网IP
    iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 1.2.3.4
    
  • mangle表(修改表): 用于修改数据包的头部信息,比如TTL、TOS、DSCP等。包含所有五个链:

    • PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING 这个表用得相对较少,主要在需要精细控制数据包属性的场景下使用:

    # 修改数据包的TTL值
    iptables -t mangle -A OUTPUT -j TTL --ttl-set 64
    # 给数据包打标记,配合tc做流量控制
    iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 1
    
  • raw表(原始表): 主要用于配置数据包,决定是否进行连接跟踪。包含两个链:

    • PREROUTING链:在路由决策之前处理数据包。

    • OUTPUT链:处理从本机发出的数据包。 这个表的优先级最高,主要用于性能优化:

    # # 跳过连接跟踪,提高性能
    iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
    

表和链的关系矩阵

         PREROUTING  INPUT  FORWARD  OUTPUT  POSTROUTING
raw                                          ✗
mangle                                       ✓
nat                                          ✓
filter                                       

数据包处理的优先级

当数据包到达某个钩子点时,会按照表的优先级依次处理:

raw → mangle → nat → filter

这个顺序很重要!我之前就遇到过一个问题,在filter表里写了规则拒绝某个IP,但是在nat表里又写了DNAT规则转发这个IP的请求。结果发现DNAT规则生效了,因为nat表的优先级比filter表高,数据包在到达filter表之前就被转发了。

数据包在iptables四表五链中的流转流程图

这里放两张图,供大家参考:


链接:https://lewestech.com/mirrors/www.iptables.info/en/structure-of-iptables.html

连接跟踪系统

iptables有一个很重要的特性就是连接跟踪(connection tracking),它能记住网络连接的状态。这个功能是由内核模块nf_conntrack提供的。

连接状态主要有:

  • NEW:新建连接的第一个数据包

  • ESTABLISHED:已建立的连接

  • RELATED:与已有连接相关的新连接,比如FTP的数据连接

  • INVALID:无法识别的连接

利用连接跟踪,我们可以写出很简洁的规则。比如:

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

这条规则允许所有已建立的连接和相关连接通过,这样就不用为每个服务单独写返回数据包的规则了。

但是连接跟踪也有代价,它会消耗内存来存储连接信息。在高并发的环境下,连接跟踪表可能会满,导致新连接无法建立。这时候可能需要调整内核参数或者使用raw表来跳过连接跟踪。

关闭firewall,不然会冲突

# 关闭firewalld
[root@node1 ~]# systemctl stop firewalld
[root@node1 ~]# systemctl disable firewalld

规则顺序很重要。我见过不少人写规则的时候不注意顺序,把DROP规则放在前面,ACCEPT规则放在后面,结果ACCEPT规则永远不会被执行到。

比如这样的规则就有问题:

iptables -A INPUT -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

第二条规则永远不会生效,因为所有数据包都被第一条规则DROP了。

默认策略要小心设置。我刚开始学iptables的时候,经常把INPUT链的默认策略设置为DROP,然后忘记添加SSH的规则,结果把自己锁在外面,只能去机房重启服务器。

现在我的习惯是先添加必要的规则,最后再修改默认策略:

# 先允许SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
# 最后设置默认策略
iptables -P INPUT DROP

回环接口别忘了。lo接口(127.0.0.1)的流量一定要允许,很多应用都依赖本地回环通信。如果把lo接口的流量给阻断了,可能会出现各种奇怪的问题。

ICMP不要全部禁止。有些人为了"安全",把所有ICMP流量都禁止了,结果导致路径MTU发现机制失效,出现一些诡异的网络问题。至少要允许ICMP的错误消息通过。

表的选择要正确。我见过有人在filter表里写SNAT规则,当然不会生效,因为filter表根本没有POSTROUTING链。还有人在nat表里写DROP规则,这也是不对的,nat表是用来做地址转换的,不是用来过滤的。

案例

案例1:Web服务器防火墙配置

假设你有一台Web服务器,需要开放80和443端口,允许SSH管理,其他端口都要封闭:

# 清空现有规则
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X

# 设置默认策略(注意顺序)
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP

# 允许本地回环(在filter表的INPUT链)
iptables -A INPUT -i lo -j ACCEPT

# 允许已建立的连接(在filter表的INPUT链)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 允许SSH(在filter表的INPUT链)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 允许HTTP和HTTPS(在filter表的INPUT链)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 允许ping(在filter表的INPUT链)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 最后设置INPUT默认策略为DROP
iptables -P INPUT DROP

这个配置只用到了filter表的INPUT链,因为我们只需要过滤进入服务器的流量。

案例2:NAT网关配置

假设你要搭建一个NAT网关,内网是192.168.1.0/24,外网接口是eth0:

# 开启IP转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 在nat表的POSTROUTING链做SNAT
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 在filter表的FORWARD链允许内网到外网的流量
iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.0/24 -j ACCEPT

# 在filter表的FORWARD链允许已建立连接的返回流量
iptables -A FORWARD -i eth0 -o eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT

# 设置FORWARD链默认策略为DROP
iptables -P FORWARD DROP

这个配置用到了nat表的POSTROUTING链(做SNAT)和filter表的FORWARD链(控制转发)。

案例3:端口转发配置

假设你要把访问服务器8080端口的请求转发到内网192.168.1.100的80端口:

# 在nat表的PREROUTING链做DNAT
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80

# 在nat表的POSTROUTING链做SNAT(如果需要的话)
iptables -t nat -A POSTROUTING -d 192.168.1.100 -p tcp --dport 80 -j SNAT --to-source 192.168.1.1

# 在filter表的FORWARD链允许转发
iptables -A FORWARD -d 192.168.1.100 -p tcp --dport 80 -j ACCEPT
iptables -A FORWARD -s 192.168.1.100 -p tcp --sport 80 -j ACCEPT

这个配置用到了nat表的PREROUTING链(DNAT)、POSTROUTING链(SNAT)和filter表的FORWARD链(控制转发)。

案例4:高性能Web服务器配置

假设你有一台高并发的Web服务器,想要跳过连接跟踪来提高性能:

# 在raw表跳过连接跟踪
iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK

# 在filter表允许HTTP流量(注意不能使用state模块了)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT

# 在mangle表给数据包打标记,配合tc做QoS
iptables -t mangle -A OUTPUT -p tcp --sport 80 -j DSCP --set-dscp 46

这个配置用到了raw表(跳过连接跟踪)、filter表(基本过滤)和mangle表(QoS标记)。

NAT的工作原理深入解析

既然说到了四表五链,就不能不深入讲讲NAT的工作原理,因为很多人对DNAT和SNAT在哪个链生效搞不清楚。

  1. DNAT的工作流程 DNAT(目标地址转换)发生在PREROUTING链,也就是路由决策之前。这样设计是有道理的,因为要先修改目标地址,然后内核才能根据新的目标地址做路由决策。

比如这条规则:

iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

数据包的处理流程是:

1. 外部数据包到达,目标是本机的80端口
2. 在PREROUTING链,DNAT规则生效,目标地址改为192.168.1.100:8080
3. 路由决策:发现目标是192.168.1.100,需要转发
4. 数据包进入FORWARD链进行过滤
5. 如果通过过滤,数据包被转发到192.168.1.100
  1. SNAT的工作流程 SNAT(源地址转换)发生在POSTROUTING链,也就是数据包离开本机之前的最后一步。这样设计也是有道理的,因为要等路由决策完成,确定了出口接口,才能决定用哪个IP做源地址。

比如这条规则:

iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 1.2.3.4

数据包的处理流程是:

1. 内网机器192.168.1.10发出数据包,目标是外网
2. 数据包到达网关,经过路由决策,确定从eth0接口发出
3. 在POSTROUTING链,SNAT规则生效,源地址改为1.2.3.4
4. 数据包发送到外网
  1. MASQUERADE的特殊性

MASQUERADE是SNAT的一个特殊形式,它会自动使用出口接口的IP地址作为源地址。这在动态IP环境下很有用:

iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

MASQUERADE会自动获取eth0接口的IP地址,不需要手动指定。

  1. 连接跟踪在NAT中的作用

NAT的实现完全依赖连接跟踪系统。当一个连接的第一个数据包经过NAT规则时,连接跟踪系统会记录下地址转换的映射关系。后续的数据包(包括返回的数据包)都会根据这个映射关系自动进行地址转换。

这就是为什么NAT规则只需要写一条,返回的数据包会自动被转换。比如:

iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:80

当外部访问本机80端口时:

1. 请求数据包:外部IP:随机端口  本机IP:80,经过DNAT后变成:外部IP:随机端口  192.168.1.100:80
2. 响应数据包:192.168.1.100:80  外部IP:随机端口,会自动变成:本机IP:80  外部IP:随机端口

这个自动转换就是连接跟踪系统的功劳。

调试和排错技巧

iptables的调试一直是个难点,数据包在复杂的规则中穿行,很难追踪到底在哪里被处理了。

使用LOG target记录日志是最常用的调试方法:

# 在每个关键位置添加LOG规则
iptables -t nat -A PREROUTING -p tcp --dport 80 -j LOG --log-prefix "PREROUTING: "
iptables -A FORWARD -p tcp --dport 80 -j LOG --log-prefix "FORWARD: "
iptables -t nat -A POSTROUTING -p tcp --sport 80 -j LOG --log-prefix "POSTROUTING: "

然后在/var/log/messages或者/var/log/kern.log中查看日志。

使用TRACE target可以跟踪数据包的完整路径:

# 开启TRACE(需要加载xt_LOG模块)
modprobe xt_LOG
iptables -t raw -A PREROUTING -p tcp --dport 80 -j TRACE

TRACE会显示数据包经过的每个表、每个链、每条规则,非常详细,但也会产生大量日志。

查看连接跟踪表:

cat /proc/net/nf_conntrack

这个文件显示当前所有被跟踪的连接,可以帮助理解NAT的工作情况。

使用iptables-save查看所有规则:

iptables-save

这个命令会按照表和链的结构显示所有规则,比iptables -L更清晰。

我记得有一次排查NAT问题,怀疑是规则写错了,用iptables -L看了半天都没发现问题。后来用iptables-save一看,发现有人在不同的表里写了冲突的规则,把整个逻辑搞乱了。

性能优化的一些技巧

iptables在高并发环境下可能会成为性能瓶颈,这里分享一些优化技巧。

减少规则数量。规则越多,匹配越慢。可以用ipset来处理大量IP地址的匹配,ipset使用哈希表,查找效率比逐条匹配高很多。

# 创建ipset
ipset create blacklist hash:ip
ipset add blacklist 1.2.3.4
ipset add blacklist 5.6.7.8

# 在iptables中使用
iptables -A INPUT -m set --match-set blacklist src -j DROP

合理使用连接跟踪。连接跟踪虽然方便,但是会消耗内存和CPU。对于一些无状态的服务,可以考虑跳过连接跟踪:

iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK
iptables -t raw -A OUTPUT -p tcp --sport 80 -j NOTRACK

规则顺序优化。把匹配频率高的规则放在前面,把DROP规则尽量放在前面,这样可以减少不必要的匹配。

使用专用的硬件或软件。在流量很大的环境下,可能需要考虑使用专门的防火墙设备,或者基于eBPF的解决方案,比如Cilium。

与其他防火墙工具的关系

现在很多发行版都默认使用firewalld或者ufw这些高级工具,但它们底层还是基于iptables的。

firewalld是Red Hat系列发行版的默认防火墙管理工具,它提供了zone的概念,把网络接口分配到不同的安全区域。但是firewalld生成的规则最终还是通过iptables来执行的。

你可以用iptables -L看到firewalld生成的规则,通常会有很多以FWDI_、FWDO_开头的自定义链。

ufw(Uncomplicated Firewall)是Ubuntu的默认防火墙工具,设计理念是简化iptables的使用。比如:

ufw allow 22/tcp

这条命令会自动生成对应的iptables规则。

nftables是新一代的包过滤框架,设计用来替代iptables。它的语法更简洁,性能也更好。但是目前还没有完全普及,很多工具和脚本还是基于iptables的。

理解iptables的工作原理,对于使用这些高级工具也是很有帮助的。当出现问题时,你可以直接查看底层的iptables规则来排查。

实践建议

最小权限原则。默认拒绝所有流量,只开放必要的端口和服务。这样即使有新的漏洞,攻击面也比较小。

分层防护。不要把所有安全措施都寄托在iptables上,要结合应用层防护、入侵检测等多种手段。

定期审计规则。iptables规则会越来越多,要定期清理不需要的规则,避免规则冲突。

备份配置。在修改规则之前,一定要备份当前配置:

iptables-save > /etc/iptables/rules.v4

使用配置管理工具。对于多台服务器,建议使用Ansible、Puppet等工具来统一管理iptables配置,避免手工配置出错。

监控和告警。要监控iptables的DROP统计,异常的DROP数量可能表示有攻击或者配置错误:

iptables -L -v -n

测试环境验证。重要的规则变更要先在测试环境验证,确认没问题再上生产环境。

我见过太多因为iptables配置错误导致的故障,有些甚至影响了业务。所以在这方面一定要谨慎,宁可保守一些,也不要冒险。