iptables 详解:从原理、规则到 NAT 与实战

1. 什么是 iptables

1.1. iptables 是什么

iptables 是 Linux 系统中用于管理网络数据包过滤和处理规则的工具。它运行在用户空间,负责配置和维护防火墙规则,而真正对数据包进行检查和处理的是 Linux 内核中的 Netfilter 机制。通过 iptables,管理员可以告诉 Linux:哪些网络数据包允许通过,哪些需要拒绝或丢弃,以及某些数据包需要进行什么样的处理。

简单来说,iptables 可以理解为 Linux 网络防火墙的一套规则管理工具。管理员通过命令添加规则,Linux 内核根据这些规则检查经过网络协议栈的数据包,并按照规则指定的动作处理它们。

1.2. iptables 能做什么

iptables 最常见的用途是控制网络流量。例如,可以允许 SSH、HTTP、HTTPS 等指定端口的连接,同时禁止其他不需要的访问;也可以根据源 IP、目标 IP、协议、端口等条件限制网络访问。此外,iptables 还支持 NAT(网络地址转换)、端口转发、数据包标记以及网络流量日志等功能。

因此,iptables 不只是简单的“端口开关”。在 Linux 服务器、网关、路由器以及各种网络服务中,它可以承担访问控制、防火墙、NAT 和流量处理等多种角色。

1.3. iptables 是如何工作的

iptables 的基本工作方式可以概括为:数据包经过 Linux 网络协议栈时,依次经过相应的处理位置,iptables 根据预先配置的规则检查数据包是否满足指定条件,如果匹配,就执行规则规定的动作。例如,一条规则可以匹配“TCP 协议、目标端口为 22”的数据包,然后允许这些数据包通过。

后续章节会进一步介绍数据包在 Linux 内核中的实际处理路径,以及 Table、Chain、Rule、Match 和 Target 之间的关系。

1.4. iptables 能解决哪些实际问题

在实际运维中,iptables 最常见的用途就是控制服务器应该接受哪些网络连接。例如,一台 Web 服务器可以只开放 80、443 端口,并限制 SSH 只允许指定网络访问;一台数据库服务器可以禁止来自公网的直接访问;一台网关服务器则可以通过 NAT 和端口转发,让内部网络中的服务能够被其他网络访问。

除此之外,iptables 还可以用于限制特定 IP 的访问、记录网络连接、实现简单的流量控制,以及配合 Linux 的路由机制完成更加复杂的网络转发方案。后面的章节将通过实际案例逐步介绍这些功能,而不是一开始把所有概念全部展开。

2. Linux 网络数据包处理流程

在写任何 iptables 规则之前,先把数据包在内核里的真实路径搞清楚,比背一百条命令更重要,以下是一张经典的网络数据包处理流程图,理解并建立数据包模型对于掌握iptables的原理以及使用非常关键, 整个过程可用下面这张图完整表示(与附件流程图完全对应)。

Linux 网络数据包处理流程

Netfilter 在内核中定义了五个关键钩子(Hook),对应 iptables 的五条内置链:

链名触发时机主要用途
PREROUTING包刚进入网卡后、路由判断前DNAT、连接跟踪、mangle
INPUT确认是发给本机的包本机防火墙(filter)
FORWARD确认需要转发的包转发控制(路由器/网关)
OUTPUT本机进程产生的包本机发出流量控制
POSTROUTING包即将离开本机前SNAT / MASQUERADE

下面按路径详细拆解。

2.1. 一个数据包进入 Linux 后发生了什么

数据包从网卡进来后,内核严格按照以下顺序处理:

  1. 进入 PREROUTING
    最先经过。这里做目的地址转换(DNAT)、连接跟踪、包修改等。

  2. 路由判断(Routing Decision)
    内核检查目的 IP:

    • 是本机 IP → 走向 INPUT
    • 不是本机 IP → 走向 FORWARD(必须已开启 ip_forward
  3. 本机处理 or 转发

    • 本机包:INPUT → 上层协议栈
    • 转发包:FORWARD → 再次路由判断 → POSTROUTING → 离开
  4. 本机主动发出的包
    上层协议栈 → OUTPUT → 路由判断 → POSTROUTING → 离开

2.2. PREROUTING

位置:数据包入口后的第一站,路由判断之前。

支持的表(从上到下优先级):

  • raw
  • mangle
  • nat

核心作用

  • 目的地址转换(DNAT)
  • 连接跟踪(conntrack 在此建立)
  • 修改 TTL、TOS、标记包等

典型场景

  • 公网端口映射到内网服务
  • 透明代理(把 80/443 导向本地代理)

实验验证

1
2
3
4

# 把到达本机 80 的流量 DNAT 到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

验证:

1
2
3

tcpdump -i any host 192.168.1.100 and port 8080 -nn

原理点:PREROUTING 发生在路由判断之前,所以这里改的是目的地址。改完后内核才会根据新的目的地址决定走 INPUT 还是 FORWARD。

2.3. INPUT

位置:路由判断后,确认是发给本机的数据包。
主要作用

  • 决定是否允许这个包进入本机上层协议栈
  • 本机防火墙的核心防线

常用表

  • mangle
  • filter(最常用)
  • nat(CentOS 7+ 支持,CentOS 6 不支持,图中因此用灰色标注)

典型场景

  • 只允许特定 IP 访问 SSH(22 端口)
  • 限制本机某服务只允许内网访问
  • 防暴力破解、限速等

实验验证

1
2
3
4
5

# 只允许 192.168.1.0/24 网段访问本机 22 端口
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

验证

1
2
# 从允许网段访问 → 成功
# 从其他网段访问 → 被拒绝

运维实战经验:

INPUT 链的默认策略建议设为 DROP,然后只放行明确需要的流量。这是最基础也是最有效的安全实践。

2.4. FORWARD

位置:路由判断后,确认不是发给本机,需要转发出去的数据包。

主要作用

  • 控制转发流量(路由器、网关、容器宿主机、K8s 节点等场景的核心)

常用表

  • mangle
  • filter

前提条件(非常重要)

1
2
3
4
5
6
7

# 必须开启内核转发功能
echo 1 > /proc/sys/net/ipv4/ip_forward
# 永久生效
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

如果不开启,包即使走到 FORWARD 链,最终也会被内核丢弃。

典型场景

  • 作为网关时,控制内网访问外网的权限
  • Docker / K8s 场景下控制容器间通信
  • 多网卡服务器做策略路由转发

实验验证

1
2
3
4
5

# 允许内网网段转发到外网
iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -d 192.168.1.0/24 -j ACCEPT

2.5. OUTPUT

位置:本机进程产生的数据包,在路由判断之前。

主要作用

  • 控制本机主动发出的流量
  • 做本机源地址伪装、限制本机访问某些地址等

常用表

  • raw
  • mangle
  • nat
  • filter

典型场景

  • 限制本机只能访问特定外网地址
  • 本机服务访问外部时做 SNAT(较少见,通常放在 POSTROUTING)
  • 防止本机被植入后门后主动外联

实验验证

1
2
3
4

# 禁止本机访问 8.8.8.8
iptables -A OUTPUT -d 8.8.8.8 -j DROP

2.6. POSTROUTING

位置:数据包即将离开本机之前(无论是转发的还是本机产生的)。

主要作用

  • 源地址转换(SNAT / MASQUERADE)
  • 最后一次修改包头的机会

常用表

  • mangle
  • nat(重点做 SNAT)

典型场景

  • 内网机器通过网关访问外网时,把源地址改成网关的公网 IP
  • Docker 容器访问外网时的地址伪装

实验验证(最经典的 MASQUERADE)

1
2
3
4

# 内网通过本机访问外网时做地址伪装
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

选择建议

  • 公网 IP 固定 → 用 SNAT
  • 公网 IP 动态(拨号/DHCP)→ 用 MASQUERADE

验证方法

1
2
3

# 在内网机器上访问外网,抓包看源 IP 是否已经变成网关的公网 IP

注意

SNAT 需要指定固定的源 IP,适合公网 IP 固定的场景。

MASQUERADE 会自动使用出口网卡的当前 IP,适合动态公网 IP(拨号、DHCP)场景。

理解了这张图,后面写任何规则时,你都会先问自己一个问题:

“这个包现在走到哪条链了?”

3. iptables 的表与链

iptables 的核心由 表(table)链(chain)规则(rule) 三层组成。

  • :定义“做什么类型的操作”(过滤、地址转换、修改包头等)
  • :定义“在数据包的哪个位置做操作”(PREROUTING、INPUT……)
  • 规则:具体的匹配条件 + 动作

理解它们之间的关系,是写对规则的前提。

3.1. filter 表

定位:默认表,也是最常用的表。专门负责包过滤(允许 / 拒绝)。

支持的链

  • INPUT:控制进入本机的数据包
  • FORWARD:控制转发的数据包
  • OUTPUT:控制本机发出的数据包

常用动作

  • ACCEPT:允许通过
  • DROP:直接丢弃(不回应)
  • REJECT:拒绝并返回错误信息(更友好,但会暴露主机存在)

典型场景

  • 本机防火墙(只允许特定端口访问)
  • 网关转发控制(限制内网访问外网)
  • 防止端口扫描、暴力破解

实验验证

1
2
3
4
5
6
7
# 查看 filter 表规则(默认就是 filter,可省略 -t filter)
iptables -L -n -v

# 只允许 192.168.1.0/24 访问本机 22 端口,其他全部拒绝
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

运维实战建议

filter 表的默认策略(Policy)建议设为 DROP,然后只放行明确需要的流量。这是生产环境最基础的安全实践。

3.2. nat 表

定位:负责网络地址转换(Network Address Translation)。

支持的链

  • PREROUTING:目的地址转换(DNAT),发生在路由判断前
  • INPUT:对本机包做地址转换(CentOS 7+ 支持,CentOS 6 不支持)
  • OUTPUT:对本机发出的包做地址转换
  • POSTROUTING:源地址转换(SNAT / MASQUERADE),发生在包离开前

常用动作

  • DNAT:修改目的地址
  • SNAT:修改源地址(需指定固定 IP)
  • MASQUERADE:自动使用出口网卡当前 IP 做源地址伪装(适合动态公网 IP)
  • REDIRECT:重定向到本机其他端口(透明代理常用)

典型场景

  • 端口映射(公网 80 → 内网 8080)
  • 内网通过网关访问外网(MASQUERADE)
  • 透明代理

实验验证

Bash

1
2
3
4
5
# DNAT:公网 80 映射到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# MASQUERADE:内网访问外网时做地址伪装
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

原理点
nat 表只在新建连接的第一个包上生效,后续包由连接跟踪(conntrack)自动处理,因此性能较好。

3.3. mangle 表

定位:负责修改数据包

  • mangle 在英文里有 “弄乱、损坏、篡改、修改” 的意思。在 iptables 语境中,mangle 最好理解成“修改/调整数据包属性”,而不是字面上的“弄乱”。
  • mangle 为什么不叫 modify?这是一个比较有意思的问题。modify 是比较中性的修改,而 mangle 在网络工程历史语境里表达的是:对数据包进行某种“特殊处理/篡改”,使它携带不同的属性或按照不同方式被处理。尤其早期 Netfilter/iptables 的设计中,mangle 表承担的是一组比较杂的 packet alteration 功能,而不是一个单一目的。例如:TTL、TOS、MARK、CONNMARK、DSCP、ECN;这些操作很难用一个特别具体的名字概括。
    因此使用了 mangle 这个比较宽泛的术语。
  • 很多初学者看到 mangle,会以为:mangle = 修改 IP 包里面的数据。其实不是,它更多是在修改或者设置 Linux 内核处理这个数据包时使用的属性。
  • iptables 的 mangle 操作主要针对网络层/内核处理相关属性。而真正修改 TCP/UDP Payload,并不是 mangle 表的主要设计目的。

支持的链(最全):

  • PREROUTING
  • INPUT
  • FORWARD
  • OUTPUT
  • POSTROUTING

常用动作

  • TOS:修改服务类型
  • TTL:修改生存时间
  • MARK:给包打标记(后续可被 ip rule、tc 等使用)
  • SECMARK:安全标记
  • TCPMSS:修改 TCP 最大段大小(解决某些 VPN / PPPoE MTU 问题)

典型场景

  • 策略路由(根据标记走不同路由表)
  • 流量整形 / QoS
  • 解决 PPPoE 拨号 MTU 问题
  • 修改 TTL 隐藏跳数

实验验证

Bash

1
2
3
4
5
# 给来自 192.168.1.0/24 的包打标记 100
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100

# 查看标记
iptables -t mangle -L -n -v

运维提示
mangle 表使用频率远低于 filter 和 nat,但在复杂网络(多出口、策略路由、QoS)中几乎不可或缺。

3.4. raw 表

定位:优先级最高的表,主要用来决定是否跳过连接跟踪

支持的链

  • PREROUTING
  • OUTPUT

常用动作

  • NOTRACK(或 CT –notrack):不对匹配的包做连接跟踪

典型场景

  • 高流量服务器上,对不需要状态检测的流量(如大量 DNS 查询、日志上报)关闭连接跟踪,降低 conntrack 表压力
  • 某些特殊协议不希望被跟踪

实验验证

1
2
# 对所有到达 53 端口的 UDP 包不做连接跟踪
iptables -t raw -A PREROUTING -p udp --dport 53 -j NOTRACK

原理点
连接跟踪会消耗内存和 CPU。当服务器并发连接极高时,合理使用 raw 表可以明显降低负载。

3.5. security 表

定位:用于强制访问控制(Mandatory Access Control),与 SELinux 配合使用。

支持的链

  • INPUT
  • FORWARD
  • OUTPUT

常用动作

  • SECMARK:设置安全上下文标记
  • CONNSECMARK:设置连接的安全标记

使用频率
在普通运维场景中几乎很少直接操作,主要出现在启用了 SELinux 的强制模式下,由系统自动管理。

建议
除非你明确在做 SELinux 相关的安全策略,否则日常写规则时可以忽略这个表。

3.6. 不同表有哪些链

下表清晰展示了每个表支持的链(✓ 表示支持):

链 \ 表rawmanglenatfiltersecurity
PREROUTING
INPUT✓*
FORWARD
OUTPUT
POSTROUTING

*注:nat 表的 INPUT 链在 CentOS 7 / 较新内核中才支持,CentOS 6 不支持。

优先级顺序(同一钩子点上,表的执行顺序):

text

1
raw → mangle → nat → filter → security

也就是说,raw 表最先执行,security 表最后执行。

3.7. 表、链、规则之间的关系

可以用一句话概括:

表决定“做什么”,链决定“在哪里做”,规则决定“对谁做、怎么做”。

更具体的关系如下:

  1. 一个表可以包含多条链
    例如 filter 表有 INPUT、FORWARD、OUTPUT 三条链。
  2. 一条链属于某个表
    例如 iptables -t nat -A PREROUTING … 明确指定了表和链。
  3. 规则按顺序挂在链上
    数据包到达某条链后,会从上到下依次匹配规则,一旦匹配成功就执行对应动作(并通常停止继续匹配,除非使用了 -j 跳转到自定义链)。
  4. 执行顺序由两个维度决定
    • 位置维度(链的顺序):PREROUTING → INPUT/FORWARD → OUTPUT → POSTROUTING
    • 类型维度(表的优先级):raw → mangle → nat → filter → security

完整执行流程示例(以一个进入本机的包为例):

text

1
2
3
4
5
6
7
8
9
10
11
数据包进入网卡

PREROUTING 钩子:
raw 表 → mangle 表 → nat 表

路由判断(是本机包)

INPUT 钩子:
mangle 表 → nat 表(如支持)→ filter 表 → security 表

交给上层协议栈

运维实战口诀

  • 过滤(允许/拒绝)→ 用 filter 表
  • 改地址(DNAT/SNAT)→ 用 nat 表
  • 改包头或打标 → 用 mangle 表
  • 跳过连接跟踪 → 用 raw 表
  • 写规则前先问自己:
    1. 这个包现在走到哪条链了?
    2. 我需要的是过滤、转换还是修改?

把这三点想清楚,规则就不会写错位置。

4. iptables 与 Netfilter 的关系和交互

很多运维同学把 iptables 当成“防火墙工具”来用,但真正决定数据包命运的,是内核里的 Netfilter 框架
iptables 只是用户空间的配置工具,真正干活的是内核。

理解它们之间的关系,才能真正搞清楚:

规则为什么生效、为什么不生效、为什么有时候会“丢包”、为什么性能会受影响。

以下是根据个人理解画出的 iptables 与 Netfilter 的交互图,有不准确的还望留言指正。

iptables 与 Netfilter 的交互图

4.1. iptables 与 Netfilter 是什么关系

一句话总结:

Netfilter 是内核框架,iptables 是用户空间配置工具。

组件所在位置职责
Netfilter内核空间提供钩子(Hook)、表、链、规则执行引擎
iptables用户空间向内核下发规则、查看规则、管理链
iptables 命令用户进程解析命令,通过 netlink / setsockopt 与内核通信

类比:

  • Netfilter 像是高速公路上的检查站系统
  • iptables 像是交警用来设置检查规则的电脑

没有 Netfilter,iptables 什么也做不了;没有 iptables,运维人员很难方便地配置 Netfilter。

4.2. Linux TCP/IP 协议栈与 Netfilter

Linux 网络协议栈处理数据包的大致路径如下:

1
网卡驱动 → 协议栈入口 → 路由决策 → 传输层(TCP/UDP)→ 应用层

Netfilter 在协议栈的关键位置插入了钩子(Hook),让内核可以在这些位置“拦截”数据包并执行规则。

目前最重要的五个钩子位置是:

  1. NF_INET_PRE_ROUTING(对应 PREROUTING)
  2. NF_INET_LOCAL_IN(对应 INPUT)
  3. NF_INET_FORWARD(对应 FORWARD)
  4. NF_INET_LOCAL_OUT(对应 OUTPUT)
  5. NF_INET_POST_ROUTING(对应 POSTROUTING)

这些钩子是 Netfilter 与协议栈交互的核心接口。

4.3. Netfilter Hook 与 iptables 链

iptables 的五条内置链,本质上就是对 Netfilter 五个钩子的封装:

Netfilter Hookiptables 链触发时机
NF_INET_PRE_ROUTINGPREROUTING包进入后、路由判断前
NF_INET_LOCAL_ININPUT确认是发给本机的包
NF_INET_FORWARDFORWARD确认需要转发的包
NF_INET_LOCAL_OUTOUTPUT本机进程产生的包
NF_INET_POST_ROUTINGPOSTROUTING包即将离开本机前

关系本质

  • 用户写的是 iptables 链规则
  • 内核真正执行的是注册在对应 Hook 上的回调函数
  • 规则最终会转换成 Hook 上的“匹配 + 动作”逻辑

4.4. 一条 iptables 规则是如何进入内核的

当你执行一条命令:

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

完整过程如下:

  1. 用户空间解析
    iptables 命令行工具解析参数,生成规则结构。
  2. 与内核通信
    通过 setsockopt 或 netlink(较新版本)把规则发送给内核。
  3. 内核接收并转换
    内核的 iptables 模块(ip_tables)接收规则,转换成内部数据结构。
  4. 挂载到对应链
    规则被插入到指定表的指定链中(按顺序)。
  5. 注册到 Hook
    最终这条规则会作为回调函数的一部分,注册到对应的 Netfilter Hook 上。

之后,每当有数据包经过该 Hook,内核就会依次匹配这些规则。

4.5. 一个数据包如何经过 Netfilter 与 iptables

以一个进入本机的 TCP 包为例,完整路径如下:

  1. 网卡收到数据包
  2. 进入 NF_INET_PRE_ROUTING 钩子
    → 依次执行 raw → mangle → nat 表的 PREROUTING 链
  3. 路由判断(是本机包)
  4. 进入 NF_INET_LOCAL_IN 钩子
    → 依次执行 mangle → nat(如支持)→ filter → security 表的 INPUT 链
  5. 如果全部 ACCEPT,则交给上层协议栈(TCP)
  6. 最终到达监听 22 端口的 sshd 进程

如果中途任何一条规则执行了 DROP 或 REJECT,包就会在这里被终结,不会继续往后走。

4.6. iptables 表如何参与数据包处理

不同表在同一个钩子上的执行顺序是固定的:

1
raw → mangle → nat → filter → security

这意味着:

  • raw 表最先执行:可以决定是否跳过连接跟踪
  • mangle 表次之:可以先修改包或打标记
  • nat 表再次:做地址转换(只对新建连接的第一个包生效)
  • filter 表较后:做最终的允许/拒绝决策
  • security 表最后:配合 SELinux 做强制访问控制

运维启示

  • 想改地址,用 nat 表
  • 想过滤,用 filter 表
  • 想先打标记再过滤,把标记动作放在 mangle 表

顺序错了,规则可能永远匹配不到。

4.7. conntrack 在其中扮演什么角色

conntrack(连接跟踪) 是 Netfilter 的核心组件之一,由 nf_conntrack 模块实现。

它的主要作用:

  1. 记录连接状态
    每个连接都会在内核中建立一个 conntrack 条目,记录五元组、状态(NEW、ESTABLISHED、RELATED、INVALID)等。
  2. 让状态匹配成为可能
    规则中的 -m state –state ESTABLISHED,RELATED 就是依赖 conntrack。
  3. 支持 NAT
    NAT 只对连接的第一个包做地址转换,后续包由 conntrack 自动完成映射。
  4. 性能关键点
    高并发场景下,conntrack 表可能成为瓶颈(默认大小有限)。

查看当前连接跟踪

Bash

1
2
3
4
5
# 查看当前跟踪的连接数
conntrack -C

# 查看具体连接
conntrack -L | head

运维实战

  • 服务器并发极高时,适当调大 nf_conntrack_max
  • 对不需要状态检测的流量(如大量 DNS),可用 raw 表的 NOTRACK 跳过跟踪

4.8. iptables 规则究竟在哪里执行

规则真正执行的位置是在内核的 Netfilter Hook 回调中,而不是在用户空间。

具体来说:

  1. 数据包到达某个 Hook
  2. 内核调用该 Hook 上注册的所有回调函数
  3. 回调函数按照表的优先级和链中规则的顺序,依次进行匹配
  4. 匹配成功后执行对应的 target(ACCEPT、DROP、SNAT 等)
  5. 根据返回值决定包是继续往后走,还是被丢弃/修改

因此:

  • 规则写得再多,只要包没走到对应的 Hook,就永远不会生效
  • 规则顺序很重要,因为匹配是从上到下、短路执行

4.9. iptables-legacy 与 iptables-nft

从内核 3.13 开始,Linux 引入了新的包过滤框架 nftables,并逐渐成为默认后端。

目前存在两套实现:

项目说明
iptables-legacy传统实现,基于 xtables + ip_tables 模块
iptables-nft兼容层,底层调用 nftables

如何区分当前使用的是哪一套

Bash

1
2
3
4
5
# 查看 iptables 版本信息
iptables -V

# 或者
iptables-translate -V
  • 如果输出包含 nf_tables,说明是 iptables-nft
  • 如果输出是传统版本,则是 iptables-legacy

主要区别

对比项iptables-legacyiptables-nft
后端框架xtablesnftables
性能一般更好(规则编译成字节码)
规则语法兼容原生高度兼容,但有少量差异
未来方向逐渐被淘汰官方推荐方向
与 firewalld旧版使用新版默认使用

运维建议

  • 新系统(CentOS 8+、Ubuntu 20.04+)优先使用 iptables-nft 或直接学习 nftables
  • 老系统或需要高度兼容的场景,继续使用 iptables-legacy
  • 两者规则不能混用,切换时注意清空旧规则

理解了这些,你就不再是“会写规则的人”,而是真正理解防火墙工作原理的人。

5. iptables 基本语法

掌握了表、链和数据包路径后,接下来就是真正动手写规则。
iptables 的命令结构非常规律,只要记住核心框架,绝大多数操作都能快速写出来。

5.1. iptables 命令结构

iptables 的基本命令格式如下:

1
iptables [-t 表名] 命令 链名 [规则编号] [匹配条件] [-j 动作]

各部分含义

部分说明示例
-t 表名指定表(默认是 filter)-t nat、-t mangle
命令对规则进行的操作-A、-I、-D、-R 等
链名操作的目标链INPUT、PREROUTING 等
规则编号规则在链中的位置(可选)1、3
匹配条件匹配数据包的条件-p tcp –dport 22
-j 动作匹配成功后执行的动作-j ACCEPT、-j DROP

常用命令一览

命令含义示例
-A追加规则到链尾-A INPUT
-I插入规则到指定位置-I INPUT 1
-D删除规则-D INPUT 3
-R替换(修改)规则-R INPUT 2 …
-L列出规则-L -n -v
-F清空链中所有规则-F INPUT
-P设置链的默认策略-P INPUT DROP
-N新建自定义链-N MY_CHAIN
-X删除自定义链-X MY_CHAIN

5.2. 查看规则

最常用的查看命令:

Bash

1
2
3
4
5
6
7
8
9
10
11
# 查看 filter 表所有链的规则(默认表)
iptables -L -n -v

# 查看指定表
iptables -t nat -L -n -v

# 查看时显示规则编号(非常重要!)
iptables -L -n -v --line-numbers

# 只查看某条链
iptables -L INPUT -n -v --line-numbers

参数说明

  • -n:以数字形式显示 IP 和端口(不反查域名,速度更快)
  • -v:显示详细信息(包括包计数器和字节数)
  • --line-numbers:显示规则编号,方便后续删除或修改

运维建议
日常排查问题时,一定要加 --line-numbers,否则删除规则时容易删错。

5.3. 添加规则

使用 -A(Append)把规则追加到链的末尾

Bash

1
2
3
4
5
6
7
8
# 允许 192.168.1.0/24 访问本机 22 端口
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# 允许本机 ping 响应
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 在 nat 表添加 DNAT 规则
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

注意:-A 添加的规则优先级最低(在链的最后)。如果前面已经有匹配的规则,后面的规则可能永远不会生效。

5.4. 删除规则

有两种删除方式:

方式一:按规则编号删除(推荐)

Bash

1
2
3
4
5
# 先查看带编号的规则
iptables -L INPUT -n -v --line-numbers

# 删除 INPUT 链的第 3 条规则
iptables -D INPUT 3

方式二:按规则内容删除

Bash

1
2
# 必须把匹配条件和动作写完整
iptables -D INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

运维提示
按内容删除时,规则必须与原规则完全一致,否则会报错。推荐优先使用编号删除。

5.5. 修改规则

使用 -R(Replace)替换指定编号的规则:

Bash

1
2
# 把 INPUT 链第 2 条规则替换为新规则
iptables -R INPUT 2 -p tcp --dport 22 -s 192.168.1.100 -j ACCEPT

注意

  • 必须指定规则编号
  • 新规则会完全覆盖旧规则
  • 如果只是想调整顺序,更推荐先删除再插入

5.6. 插入规则

使用 -I(Insert)把规则插入到链的指定位置(默认插入到第 1 条):

Bash

1
2
3
4
5
# 插入到 INPUT 链的最前面(最高优先级)
iptables -I INPUT -p tcp --dport 22 -j ACCEPT

# 插入到 INPUT 链的第 3 条位置
iptables -I INPUT 3 -p tcp --dport 80 -j ACCEPT

对比 -A -I

  • -A:追加到末尾,优先级最低
  • -I:插入到指定位置,可控制优先级

实战经验
需要优先匹配的规则(如白名单、管理端口)建议用 -I 插到前面。

5.7. 清空规则

Bash

1
2
3
4
5
6
7
8
9
10
11
# 清空 filter 表所有链的规则(不改变默认策略)
iptables -F

# 清空指定链
iptables -F INPUT

# 清空指定表
iptables -t nat -F

# 清空所有自定义链
iptables -X

危险操作警告

Bash

1
2
3
4
5
6
# 以下命令会清空所有表的规则,生产环境慎用!
iptables -F
iptables -X
iptables -t nat -F
iptables -t mangle -F
iptables -t raw -F

5.8. 设置默认策略

默认策略(Policy)决定了当数据包没有匹配到任何规则时的行为:

Bash

1
2
3
4
5
6
7
8
# 设置 INPUT 链默认拒绝
iptables -P INPUT DROP

# 设置 FORWARD 链默认拒绝
iptables -P FORWARD DROP

# 设置 OUTPUT 链默认允许(一般保持 ACCEPT)
iptables -P OUTPUT ACCEPT

查看当前默认策略

Bash

1
iptables -L -n | grep policy

运维最佳实践

  • INPUT 和 FORWARD 建议设为 DROP
  • OUTPUT 通常保持 ACCEPT(除非有严格的出站控制需求)
  • 设置默认策略前,务必先放行 SSH 端口,否则可能把自己锁在外面!

5.9. 规则编号

规则编号是定位规则的关键:

Bash

1
2
# 查看带编号的规则
iptables -L INPUT -n -v --line-numbers

输出示例:

text

1
2
3
num   pkts bytes target     prot opt in     out     source               destination
1 10 1200 ACCEPT tcp -- * * 192.168.1.0/24 0.0.0.0/0 tcp dpt:22
2 5 300 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22

有了编号后,删除、修改、插入都会非常精准。

5.10. 保存和恢复规则

iptables 规则默认只存在于内存中,重启后会丢失,必须手动保存。

CentOS / RHEL 系统

Bash

1
2
3
4
5
6
7
8
9
10
# 安装 iptables-services(如果没有)
yum install -y iptables-services

# 保存规则
service iptables save
# 或
iptables-save > /etc/sysconfig/iptables

# 开机自动加载
systemctl enable iptables

Ubuntu / Debian 系统

Bash

1
2
3
4
5
6
7
8
9
10
# 保存规则
iptables-save > /etc/iptables.rules

# 恢复规则
iptables-restore < /etc/iptables.rules

# 设置开机自动恢复(常用方式)
# 编辑 /etc/network/interfaces 或使用 iptables-persistent 包
apt install iptables-persistent
netfilter-persistent save

通用方法(所有发行版可用)

Bash

1
2
3
4
5
# 保存
iptables-save > /root/iptables.backup

# 恢复
iptables-restore < /root/iptables.backup

运维建议

  1. 每次修改规则后,立刻备份一份。
  2. 重要变更前先 iptables-save > backup_$(date +%F).txt。
  3. 生产环境推荐使用 iptables-persistent 或 firewalld(如果已切换),避免重启后规则丢失。

6. 常用匹配条件

在前一章中,我们掌握了 iptables 命令的基本结构(表、链、动作以及规则的增删改查)。然而,防火墙的核心能力在于精确识别数据包。只有能够精准筛选出特定流量,后续的 ACCEPTDROPDNAT 等动作才有意义。

在 Netfilter/iptables 体系中,匹配条件(Matches)分为两大类:

  1. 基本匹配条件(Generic Matches):内核原生支持,无需加载额外模块即可直接使用(如源/目的 IP、入站/出站接口、协议类型等)。
  2. 扩展匹配条件(Extended Matches):通过 -m(或 --match)显式加载内核扩展模块来使用(如端口范围、IP 地址池、MAC 地址、状态追踪等)。

本节将逐一剖析最常用的匹配条件,帮助你构建精准的数据包过滤规则。
iptables 规则的核心是「匹配条件 + 动作」。
匹配条件写得越精准,规则就越安全、越高效。下面列出最常用、也是生产环境中出现频率最高的匹配条件。

6.1. -s / –source(源地址)

匹配数据包的源 IP 地址

1
2
3
4
5
6
7
8
9
10
11
12
# 匹配单个 IP
iptables -A INPUT -s 192.168.1.100 -j ACCEPT

# 匹配网段(推荐使用 CIDR)
iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT

# 匹配多个不连续地址(需要多次写或配合 ipset)
iptables -A INPUT -s 10.0.0.1 -j ACCEPT
iptables -A INPUT -s 10.0.0.5 -j ACCEPT

# 取反:匹配除了某个地址之外的所有源
iptables -A INPUT -s ! 192.168.1.100 -j DROP

运维提示
生产环境中尽量使用网段(/24、/16)而不是单个 IP,便于管理和减少规则数量。

6.2. -d / –destination(目的地址)

匹配数据包的目的 IP 地址

Bash

1
2
3
4
5
6
7
8
# 匹配发往本机某个 IP 的包
iptables -A INPUT -d 10.0.0.10 -j ACCEPT

# 匹配发往某个网段的包(常用于 FORWARD)
iptables -A FORWARD -d 192.168.2.0/24 -j ACCEPT

# 取反
iptables -A OUTPUT -d ! 8.8.8.8 -j ACCEPT

6.3. -i / –in-interface(入接口)

匹配数据包进入本机的网卡

Bash

1
2
3
4
5
6
7
8
# 只允许从 eth0 进入的包
iptables -A INPUT -i eth0 -j ACCEPT

# 匹配从内网网卡进来的包
iptables -A INPUT -i ens33 -s 192.168.1.0/24 -j ACCEPT

# 取反:拒绝从外网网卡进来的包
iptables -A INPUT -i ! lo -j DROP # 注意:lo 是回环接口

适用链:主要用于 INPUT、PREROUTING、FORWARD。

6.4. -o / –out-interface(出接口)

匹配数据包离开本机的网卡

Bash

1
2
3
4
5
# 只允许从 eth0 出去的包
iptables -A OUTPUT -o eth0 -j ACCEPT

# 内网转发到外网时使用
iptables -t nat -A POSTROUTING -o eth0 -s 192.168.1.0/24 -j MASQUERADE

适用链:主要用于 OUTPUT、POSTROUTING、FORWARD。

6.5. -p / –protocol(协议)

匹配协议类型,是使用频率最高的条件之一。

Bash

1
2
3
4
5
6
7
8
9
10
11
# 匹配 TCP
iptables -A INPUT -p tcp -j ACCEPT

# 匹配 UDP
iptables -A INPUT -p udp --dport 53 -j ACCEPT

# 匹配 ICMP
iptables -A INPUT -p icmp -j ACCEPT

# 匹配所有协议(默认)
iptables -A INPUT -p all -j ACCEPT

常用协议数值:

  • tcp = 6
  • udp = 17
  • icmp = 1
  • all = 0(所有)

6.6. –sport / –source-port(源端口)

匹配源端口(必须配合 -p tcp 或 -p udp)。

Bash

1
2
3
4
5
# 匹配源端口为 12345 的 TCP 包
iptables -A INPUT -p tcp --sport 12345 -j ACCEPT

# 匹配源端口范围
iptables -A INPUT -p tcp --sport 1024:65535 -j ACCEPT

注意:客户端主动发起的连接,源端口通常是随机的高端口,生产中较少单独使用 --sport 做严格限制。

6.7. –dport / –destination-port(目的端口)

匹配目的端口,是防火墙规则中最核心的条件之一。

Bash

1
2
3
4
5
6
7
8
9
# 允许 SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 允许 HTTP 和 HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 端口范围
iptables -A INPUT -p tcp --dport 8000:8010 -j ACCEPT

6.8. TCP flags(TCP 标志位)

用于匹配 TCP 包头中的标志位(SYN、ACK、FIN、RST 等),常用于防扫描或精细控制。

Bash

1
2
3
4
5
6
7
8
# 只允许新连接的 SYN 包(常用)
iptables -A INPUT -p tcp --dport 22 --tcp-flags SYN,RST,ACK,FIN SYN -j ACCEPT

# 更严格的写法(推荐)
iptables -A INPUT -p tcp --dport 22 --tcp-flags ALL SYN -j ACCEPT

# 匹配已建立的连接(通常配合 state 模块,见后文)
iptables -A INPUT -p tcp --tcp-flags ALL ACK -j ACCEPT

常用组合

  • --tcp-flags SYN,ACK,FIN,RST SYN:匹配纯 SYN 包(新连接)
  • --tcp-flags ALL NONE:匹配所有标志位都为 0 的异常包(可丢弃)

6.9. ICMP

匹配 ICMP 协议及具体类型。

Bash

1
2
3
4
5
6
7
8
9
10
11
# 允许所有 ICMP
iptables -A INPUT -p icmp -j ACCEPT

# 只允许 ping 请求(echo-request)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 允许 ping 响应(echo-reply)
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# 禁止目标不可达等错误消息(谨慎使用)
iptables -A INPUT -p icmp --icmp-type destination-unreachable -j DROP

常用 ICMP 类型:

  • echo-request(8):ping 请求
  • echo-reply(0):ping 响应
  • destination-unreachable(3)
  • time-exceeded(11)

6.10. multiport(多端口匹配)

一次匹配多个端口,避免写多条规则。

Bash

1
2
3
4
5
6
7
8
# 需要加载 multiport 模块
iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT

# 同时匹配源端口和目的端口
iptables -A INPUT -p tcp -m multiport --sports 1024:2048 --dports 80,443 -j ACCEPT

# 匹配多个源端口
iptables -A INPUT -p udp -m multiport --sports 53,123,161 -j ACCEPT

限制:最多一次指定 15 个端口。

6.11. iprange(IP 范围匹配)

匹配一个连续的 IP 地址范围(当无法用 CIDR 精确表示时特别有用)。

Bash

1
2
3
4
5
# 需要加载 iprange 模块
iptables -A INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT

# 目的地址范围
iptables -A FORWARD -m iprange --dst-range 10.0.0.10-10.0.0.50 -j ACCEPT

6.12. mac(MAC 地址匹配)

匹配源 MAC 地址,常用于内网精准控制。

Bash

1
2
3
4
5
# 需要加载 mac 模块
iptables -A INPUT -m mac --mac-source 00:11:22:33:44:55 -j ACCEPT

# 取反
iptables -A INPUT -m mac ! --mac-source 00:11:22:33:44:55 -j DROP

注意
MAC 地址只在二层(同一广播域)可见,经过路由后会丢失,因此只适合在内网网关或桥接场景使用。

6.13. set / ipset(高效 IP 集合匹配)

当需要匹配大量 IP 或网段时,使用 ipset 是性能最优解(比写几百条 -s 规则高效得多)。

1. 创建并添加 IP 集合

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装 ipset(如未安装)
yum install -y ipset # CentOS
apt install ipset # Ubuntu

# 创建一个存储 IP 的集合
ipset create whitelist hash:ip

# 添加 IP
ipset add whitelist 192.168.1.100
ipset add whitelist 10.0.0.0/24

# 创建网段集合
ipset create netlist hash:net
ipset add netlist 172.16.0.0/16

2. 在 iptables 中引用

Bash

1
2
3
4
5
6
7
8
# 匹配集合中的源 IP
iptables -A INPUT -m set --match-set whitelist src -j ACCEPT

# 匹配集合中的目的 IP
iptables -A FORWARD -m set --match-set netlist dst -j DROP

# 取反
iptables -A INPUT -m set ! --match-set whitelist src -j DROP

运维实战价值

  • 黑名单/白名单达到几百上千个 IP 时,用 ipset 性能远高于直接写规则。
  • 支持动态添加/删除,无需频繁重载 iptables 规则。
  • 可与 hash:ip、hash:net、hash:mac 等多种类型配合使用。

本节匹配条件速查

条件常用场景是否需要 -m
-s / -d源/目的 IP
-i / -o入/出接口
-p协议
--sport/–dport端口否(需配合 -p)
--tcp-flagsTCP 标志位
--icmp-typeICMP 类型
multiport多端口
iprangeIP 范围
macMAC 地址
setipset 集合

把这些匹配条件组合使用,就能写出精准且高效的规则。

7. 常见 Target / 动作

匹配条件决定「哪些包会被选中」,Target(动作)则决定「选中后怎么处理」。
iptables 的动作分为两类:

  • 终止类动作:匹配后立即决定包的命运(ACCEPT、DROP、REJECT 等),不再继续匹配后续规则。
  • 非终止类动作:匹配后执行某些操作(LOG、MARK 等),然后继续匹配后续规则。

7.1. ACCEPT

含义:允许数据包通过,停止继续匹配当前链的后续规则。

1
2
3
4
5
6
# 允许 SSH 访问
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 允许已建立的连接(常配合 state)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

特点

  • 最常用的动作之一
  • 匹配成功后,包会继续在协议栈中正常处理
  • 不会产生任何响应包

7.2. DROP

含义:直接丢弃数据包,不给出任何响应。

Bash

1
2
3
4
5
# 丢弃所有来自某 IP 的包
iptables -A INPUT -s 203.0.113.10 -j DROP

# 作为默认策略
iptables -P INPUT DROP

特点

  • 静默丢弃,对方会感觉「请求超时」
  • 安全性较高(不暴露主机是否存在)
  • 对方会重试,可能产生更多流量

适用场景:黑名单、默认拒绝策略、防御扫描。

7.3. REJECT

含义:拒绝数据包,并返回一个错误响应给发送方。

Bash

1
2
3
4
5
6
7
8
# 拒绝并返回 TCP RST
iptables -A INPUT -p tcp --dport 23 -j REJECT --reject-with tcp-reset

# 拒绝并返回 ICMP 端口不可达
iptables -A INPUT -p tcp --dport 23 -j REJECT --reject-with icmp-port-unreachable

# 默认 REJECT(不指定 --reject-with)
iptables -A INPUT -p tcp --dport 23 -j REJECT

常用 --reject-with 选项

选项含义适用协议
tcp-reset发送 TCP RSTTCP
icmp-port-unreachable端口不可达通用
icmp-host-unreachable主机不可达通用
icmp-proto-unreachable协议不可达通用
icmp-net-unreachable网络不可达通用

DROP vs REJECT 对比

对比项DROPREJECT
是否响应无响应返回错误信息
对方感受超时立即收到拒绝
安全性更高(隐蔽)较低(暴露主机存在)
推荐场景黑名单、默认策略需要明确告知对方「被拒绝」

运维建议:对外服务端口推荐用 DROP,内部管理或调试场景可用 REJECT。

7.4. LOG

含义:将匹配到的数据包信息记录到系统日志,然后继续匹配后续规则(非终止动作)。

Bash

1
2
3
4
5
6
# 记录并带上自定义前缀
iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "SSH_ACCESS: " --log-level 4

# 记录后继续丢弃
iptables -A INPUT -s 203.0.113.0/24 -j LOG --log-prefix "BLOCKED: "
iptables -A INPUT -s 203.0.113.0/24 -j DROP

常用参数

  • --log-prefix “前缀”:日志前缀,方便过滤
  • --log-level 数字:日志级别(0-7,默认 4)
  • --log-tcp-sequence:记录 TCP 序列号
  • --log-tcp-options:记录 TCP 选项
  • --log-ip-options:记录 IP 选项

查看日志

Bash

1
2
3
4
5
# CentOS / RHEL
tail -f /var/log/messages | grep SSH_ACCESS

# Ubuntu
tail -f /var/log/syslog | grep SSH_ACCESS

运维提示
LOG 规则一定要放在 DROP/REJECT 之前,否则包已经被丢弃就无法记录了。生产环境建议限制日志频率,避免日志爆炸。

7.5. RETURN

含义:停止当前链的匹配,返回到调用链(或上一级链)继续处理。

Bash

1
2
3
4
5
6
# 自定义链中使用
iptables -N MY_CHAIN
iptables -A MY_CHAIN -s 192.168.1.100 -j ACCEPT
iptables -A MY_CHAIN -j RETURN # 没有匹配到就返回

iptables -A INPUT -j MY_CHAIN

使用场景

  • 自定义链中作为「默认返回」动作
  • 实现更复杂的逻辑跳转

如果在内置链(如 INPUT)中使用 RETURN,效果等同于该链的默认策略。

7.6. DNAT(目的地址转换)

含义:修改数据包的目的地址(和可选端口),主要用于端口映射和负载均衡。

只能用在 nat 表的 PREROUTING 和 OUTPUT 链

Bash

1
2
3
4
5
6
7
8
# 公网 80 端口映射到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# 只修改地址,不改端口
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100

# 映射到端口范围(较少用)
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080-8090

实验验证

Bash

1
2
3
# 在网关机器上添加规则后,从外部访问网关的 80 端口
# 观察内网机器 192.168.1.100 是否收到 8080 端口的请求
tcpdump -i any port 8080 -nn

7.7. SNAT(源地址转换)

含义:修改数据包的源地址,常用于内网主机访问外网时统一出口 IP。

只能用在 nat 表的 POSTROUTING 链

Bash

1
2
3
4
5
# 将内网 192.168.1.0/24 的源地址转换为固定公网 IP
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10

# 指定源端口范围(较少用)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j SNAT --to-source 203.0.113.10:1024-65535

适用场景:公网 IP 固定的服务器或网关。

7.8. MASQUERADE

含义:动态源地址转换,自动使用出口网卡的当前 IP 作为源地址。

只能用在 nat 表的 POSTROUTING 链

Bash

1
2
# 最经典的内网共享上网规则
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

SNAT vs MASQUERADE

对比项SNATMASQUERADE
源地址需要指定固定 IP自动使用出口网卡当前 IP
适用场景公网 IP 固定公网 IP 动态(拨号、DHCP)
性能稍高稍低(每次需获取接口 IP)
推荐程度固定 IP 时优先用 SNAT动态 IP 时必须用 MASQUERADE

运维建议
能用 SNAT 就用 SNAT,性能更好;只有出口 IP 会变时才用 MASQUERADE。

7.9. MARK

含义:给数据包打上标记(fwmark),供后续的 ip rule、tc(流量控制)、策略路由等使用。

常用在 mangle 表

Bash

1
2
3
4
5
# 给来自某网段的包打标记 100
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100

# 给特定端口的包打标记
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 200

后续使用示例(策略路由):

Bash

1
2
3
# 根据标记选择不同路由表
ip rule add fwmark 100 table 100
ip route add default via 192.168.1.1 table 100

7.10. CONNMARK

含义:对整个连接打标记(而不是单个包),标记会被连接跟踪保存,后续同一连接的包都能继承。

Bash

1
2
3
4
5
6
# 给连接打标记
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j CONNMARK --set-mark 100

# 把连接标记恢复到包标记(常用组合)
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark --mark 100 -j ACCEPT

MARK vs CONNMARK

对比项MARKCONNMARK
作用对象单个数据包整个连接
生命周期只影响当前包连接跟踪期间一直有效
典型用途临时标记、流量分类需要同一连接保持一致标记

常见经典组合(恢复连接标记):

Bash

1
2
3
4
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 100
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark

本节动作速查

动作类型主要表/链作用摘要
ACCEPT终止所有允许通过
DROP终止所有静默丢弃
REJECT终止所有拒绝并响应
LOG非终止所有记录日志
RETURN终止自定义链返回上一级链
DNAT终止nat/PREROUTING, OUTPUT修改目的地址
SNAT终止nat/POSTROUTING修改源地址(固定 IP)
MASQUERADE终止nat/POSTROUTING修改源地址(动态 IP)
MARK非终止mangle给包打标记
CONNMARK非终止mangle给连接打标记

理解这些动作的适用场景和终止/非终止特性后,写规则时就能更精准地控制数据包的命运。

8. 最常见的防火墙规则

前面学会了语法、匹配条件和动作,现在进入真正的实战环节。
以下是生产环境中出现频率最高的防火墙规则,建议直接收藏备用。

8.1. 放行 SSH

最重要也是最危险的规则。写错或顺序不对,可能导致自己被锁在服务器外面。

1
2
3
4
5
6
7
8
9
# 基础放行(不限制来源)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 推荐:只允许特定网段访问 SSH
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# 更安全:只允许特定 IP
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.10 -j ACCEPT

安全写法(推荐顺序)

1
2
3
4
5
6
7
8
# 1. 先放行已建立的连接(防止自己断连)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 2. 放行 SSH
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# 3. 最后再设置默认 DROP
iptables -P INPUT DROP

血泪教训
设置 INPUT 默认策略为 DROP 之前,必须先放行 SSH,否则规则一生效,你就连不上服务器了。

8.2. 放行 HTTP/HTTPS

1
2
3
4
5
6
7
8
# 放行 HTTP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# 放行 HTTPS
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 合并写法(使用 multiport)
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

只允许特定来源访问网站(例如仅内网或特定办公网):

1
iptables -A INPUT -p tcp -m multiport --dports 80,443 -s 192.168.0.0/16 -j ACCEPT

8.3. 禁止某个 IP

Bash

1
2
3
4
5
6
7
8
9
# 直接丢弃某 IP 的所有包
iptables -A INPUT -s 203.0.113.66 -j DROP

# 记录后再丢弃(方便后续分析)
iptables -A INPUT -s 203.0.113.66 -j LOG --log-prefix "BLOCKED_IP: "
iptables -A INPUT -s 203.0.113.66 -j DROP

# 禁止某 IP 访问特定端口
iptables -A INPUT -s 203.0.113.66 -p tcp --dport 22 -j DROP

批量禁止(推荐使用 ipset):

1
2
3
4
5
ipset create blacklist hash:ip
ipset add blacklist 203.0.113.66
ipset add blacklist 198.51.100.10

iptables -A INPUT -m set --match-set blacklist src -j DROP

8.4. 允许某个 IP 访问指定端口

1
2
3
4
5
# 只允许 203.0.113.10 访问 3306 端口(MySQL)
iptables -A INPUT -p tcp --dport 3306 -s 203.0.113.10 -j ACCEPT

# 允许整个网段访问 Redis 端口
iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT

完整安全示例(先允许,后拒绝):

Bash

1
2
iptables -A INPUT -p tcp --dport 3306 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

8.5. 限制某个网段

Bash

1
2
3
4
5
6
7
8
9
# 只允许 10.0.0.0/8 网段访问本机
iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT

# 禁止整个 172.16.0.0/12 网段
iptables -A INPUT -s 172.16.0.0/12 -j DROP

# 限制某网段只能访问特定端口
iptables -A INPUT -s 192.168.2.0/24 -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -s 192.168.2.0/24 -j DROP

8.6. 限制新建连接

使用 state 或 conntrack 模块,只允许已建立的连接,拒绝非法新建连接(有效防御端口扫描和部分攻击)。

Bash

1
2
3
4
5
6
7
8
9
# 允许所有已建立和相关的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 只允许特定端口的新建连接
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT

# 拒绝其他所有新建连接
iptables -A INPUT -m state --state NEW -j DROP

更现代的写法(推荐使用 conntrack):

Bash

1
2
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

8.7. ICMP 控制

Bash

1
2
3
4
5
6
7
8
9
10
11
12
# 允许 ping(echo-request)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# 允许本机发送 ping 响应
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# 完全禁止 ping
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

# 限制 ping 频率(防止 ping 洪水,需要 limit 模块)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 10 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

运维建议
生产服务器可根据安全要求决定是否放行 ping。很多安全规范要求对外网关闭 ICMP echo-request。

8.8. INPUT 防火墙(本机防护完整模板)

这是最常用的本机防火墙模板,建议直接使用:

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash

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

# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 1. 放行回环接口
iptables -A INPUT -i lo -j ACCEPT

# 2. 放行已建立的连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 3. 放行 SSH(请改成你的管理网段)
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# 4. 放行 HTTP/HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# 5. 放行 ping(可选)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT

# 6. 记录并丢弃其他所有包(可选,方便排查)
iptables -A INPUT -j LOG --log-prefix "INPUT_DROP: " --log-level 4
iptables -A INPUT -j DROP

8.9. OUTPUT 防火墙(出站控制)

默认情况下 OUTPUT 策略是 ACCEPT,如需严格控制本机出站流量,可按以下思路:

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 设置默认拒绝出站
iptables -P OUTPUT DROP

# 放行回环
iptables -A OUTPUT -o lo -j ACCEPT

# 放行已建立的连接
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 允许 DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT

# 允许访问 HTTP/HTTPS
iptables -A OUTPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# 允许 SSH 出站(如需跳板)
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT

# 允许 NTP
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT

注意:OUTPUT 默认 DROP 后,如果漏放 DNS 或软件源端口,会导致服务器无法解析域名或更新软件,请谨慎操作。

8.10. FORWARD 防火墙(转发控制)

当 Linux 作为网关、路由器或 Docker/K8s 节点时,需要控制转发流量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 开启内核转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 设置默认拒绝转发
iptables -P FORWARD DROP

# 放行已建立的转发连接
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 允许内网访问外网
iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT

# 允许外网返回流量(已通过 ESTABLISHED 处理,可省略)
# iptables -A FORWARD -d 192.168.1.0/24 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 配合 MASQUERADE 实现共享上网
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

Docker 环境特别注意
Docker 会自动在 filter 和 nat 表插入大量规则。手动修改 FORWARD 链时,建议加在 Docker 规则之前,或使用 Docker 的 --iptables=false 选项自行完全接管。


实战口诀总结

  1. 先放行已建立连接,再放行新建连接,最后再 DROP。
  2. SSH 规则永远优先写,防止把自己锁在外面。
  3. 能用网段就用网段,能用 ipset 就用 ipset。
  4. 修改规则后立刻 iptables-save 备份。
  5. 生产环境建议把完整规则写成脚本,方便审计和回滚。

把以上规则模板根据实际业务微调后,就能覆盖绝大多数日常防火墙需求。

9. iptables 与连接跟踪

连接跟踪(Connection Tracking,简称 conntrack)是 Netfilter 最核心的能力之一。
它让 iptables 从「无状态防火墙」变成「有状态防火墙」,同时也是 NAT 能够正确工作的基础。

没有 conntrack,就没有 -m state / -m conntrack,也没有可靠的 SNAT/DNAT。

9.1. conntrack 是什么

conntrack 是内核中的一个子系统(模块名 nf_conntrack),主要职责是:

  1. 跟踪每一条网络连接的完整生命周期
  2. 记录连接的五元组(源IP、源端口、目的IP、目的端口、协议)以及当前状态
  3. 为 iptables 的状态匹配和 NAT 提供数据支持

每一个被跟踪的连接都会在内核中生成一个 conntrack 条目,占用内存。
在高并发场景下,conntrack 表的大小直接关系到系统的稳定性和性能。

9.2. NEW

含义:一条连接的第一个包,表示「尝试建立新连接」。

  • TCP:通常是带有 SYN 标志、尚未完成三次握手的包
  • UDP / ICMP:第一个方向出现的包(因为它们本身是无连接的)
1
2
3
# 只允许新的 SSH 连接
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

特点

  • 是访问控制的关键决策点
  • 几乎所有「是否允许建立连接」的判断都应该放在 NEW 状态进行

9.3. ESTABLISHED

含义:连接已经成功建立,双方都已经有过数据交互。

  • TCP:三次握手完成后的所有后续包
  • UDP:已经观察到双向数据包之后的状态
1
2
# 允许所有已建立的连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT

特点

  • 一旦进入 ESTABLISHED,后续包都会自动匹配此状态
  • 性能关键点:只需在 NEW 时做严格检查,后续包直接快速放行

含义:与一条已经存在的连接「相关」的新连接。

典型场景:

  • FTP 数据通道(主动模式 / 被动模式)
  • ICMP 错误消息(目标不可达、超时等)
  • 部分协议的辅助连接
1
2
# 通常与 ESTABLISHED 一起放行
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

注意

  • 需要内核加载对应的辅助模块才能正确识别(如 nf_conntrack_ftp、nf_conntrack_tftp 等)
  • 如果相关模块未加载,RELATED 状态可能无法正确匹配

9.5. INVALID

含义:无法被识别或状态非法的包。

常见情况包括:

  • TCP 标志位组合错误
  • 包属于一个已经超时或不存在的连接
  • 序列号错误、状态机混乱等
1
2
# 强烈建议直接丢弃
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

运维建议
生产环境中几乎总是把 INVALID 直接 DROP,既能减少攻击面,也能避免无意义的包浪费 CPU 和内存。

9.6. –ctstate

--ctstate 是 conntrack 模块提供的匹配选项,用于匹配连接当前所处的状态。

推荐写法(现代系统优先使用):

1
2
3
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate NEW -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

旧式写法(兼容性更好,但已逐渐被替代):

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

两者功能基本一致。新系统建议优先使用 -m conntrack –ctstate。

支持同时匹配多个状态(逗号分隔):

Bash

1
2
3
--ctstate NEW,ESTABLISHED
--ctstate ESTABLISHED,RELATED
--ctstate INVALID

这是有状态防火墙最重要的最佳实践之一:

Bash

1
2
3
# 几乎所有生产规则的前两句
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

核心原因

  1. 性能
    新建连接(NEW)只做一次严格的匹配检查。
    后续所有包直接命中 ESTABLISHED,规则匹配效率极高。
  2. 安全
    只在 NEW 状态进行精细的访问控制(来源 IP、端口等)。
    已经建立的连接默认信任,避免重复检查。
  3. 功能完整性
    RELATED 能让 FTP、ICMP 错误消息等正常工作。
  4. 防止自己锁自己
    通过 SSH 登录服务器后修改规则时,ESTABLISHED 能保证当前连接不会被意外切断。

标准推荐顺序

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 丢弃非法包
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 2. 放行已建立和相关连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 3. 精细控制 NEW 连接
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT

# 4. 默认拒绝
iptables -P INPUT DROP

9.8. 查看 conntrack

1. 查看当前连接数

1
2
3
conntrack -C
# 或
cat /proc/sys/net/netfilter/nf_conntrack_count

2. 查看最大允许连接数

1
cat /proc/sys/net/netfilter/nf_conntrack_max

3. 查看具体连接条目

1
2
3
4
5
6
7
8
9
10
11
12
# 查看所有连接(数量大时慎用)
conntrack -L

# 按源或目的 IP 过滤
conntrack -L -s 192.168.1.100
conntrack -L -d 192.168.1.100

# 只看 TCP
conntrack -L -p tcp

# 实时观察连接数
watch -n 1 "conntrack -C"

4. 删除连接条目(谨慎操作):

Bash

1
2
3
4
5
# 删除与某个 IP 相关的所有连接
conntrack -D -s 203.0.113.10

# 清空全部连接跟踪(会短暂中断现有连接)
conntrack -F

5. 调整 conntrack 表大小(高并发场景必备):

Bash

1
2
3
4
5
6
# 临时生效
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max

# 永久生效
echo "net.netfilter.nf_conntrack_max = 262144" >> /etc/sysctl.conf
sysctl -p

运维监控重点

  • 关注 nf_conntrack_count 是否接近 nf_conntrack_max
  • 接近上限时会出现新连接无法建立的问题,dmesg 中通常会有 nf_conntrack: table full 日志
  • 网关、负载均衡、NAT 设备必须根据实际并发量调大此值

最佳实践一句话

  • 先 DROP INVALID → 再放行 ESTABLISHED,RELATED → 然后精细控制 NEW → 最后默认拒绝。

把这个顺序固化到你的规则模板中,防火墙就会既安全又高效。

10. NAT 详解

NAT(Network Address Translation,网络地址转换)是 iptables 中最常用、也最容易出错的功能之一。
它通过修改数据包的源地址或目的地址,实现内网与公网的互通、端口映射、负载均衡等需求。

NAT 相关规则只能写在 nat,并且只能使用特定的链。

10.1. 什么是 NAT

NAT 的核心作用是:在数据包经过防火墙时,动态修改其源 IP 或目的 IP(有时也包括端口)

根据修改的对象不同,主要分为两类:

类型全称修改对象主要使用链典型场景
SNATSource NAT源地址POSTROUTING内网主机访问外网
DNATDestination NAT目的地址PREROUTING、OUTPUT端口映射、对外发布服务
MASQUERADE动态 SNAT源地址POSTROUTING出口 IP 不固定时的共享上网

重要前提

  • 内核必须开启 IP 转发(ip_forward = 1
  • NAT 只对新建连接的第一个包生效,后续包由 conntrack 自动完成转换

10.2. SNAT

含义:修改数据包的源地址,让内网主机以网关的公网 IP 身份访问外部网络。

只能使用在 nat 表的 POSTROUTING

1
2
3
4
5
# 将 192.168.1.0/24 网段的源地址转换为固定公网 IP 203.0.113.10
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10

# 同时指定源端口范围(较少使用)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10:1024-65535

适用场景

  • 服务器拥有固定公网 IP
  • 需要明确控制出口 IP 地址

实验验证

1
2
3
# 在内网主机上访问外网,同时在网关抓包
tcpdump -i eth0 host 8.8.8.8 -nn
# 观察源 IP 是否已经变成 203.0.113.10

10.3. DNAT

含义:修改数据包的目的地址(和可选端口),实现端口映射或流量牵引。

主要使用在 nat 表的 PREROUTING 链(也可用于 OUTPUT)。

1
2
3
4
5
6
7
8
# 将到达本机 80 端口的流量转发到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# 只修改地址,不改端口
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100

# 对本机自己发出的包做 DNAT(较少用)
iptables -t nat -A OUTPUT -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

关键注意

  • DNAT 发生在路由判断之前,所以改完目的地址后,内核会重新做路由决策
  • 仅仅做 DNAT 通常还不够,还需要配合 FORWARD 链放行转发流量

10.4. MASQUERADE

含义:动态的 SNAT,自动使用出口网卡当前的 IP 地址作为源地址。

只能使用在 nat 表的 POSTROUTING 链

1
2
# 最经典的共享上网规则
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

SNAT vs MASQUERADE 对比

对比项SNATMASQUERADE
源地址需要手动指定固定 IP自动获取出口网卡当前 IP
适用场景公网 IP 固定公网 IP 动态变化(PPPoE、DHCP)
性能更高(无需每次查询接口 IP)稍低
推荐使用固定 IP 时优先使用动态 IP 时必须使用

运维建议:能用 SNAT 就用 SNAT,性能更好;只有出口 IP 会变时才使用 MASQUERADE。

10.5. 端口转发

端口转发是 DNAT 最常见的实战场景,完整规则通常需要三部分配合:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 开启转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 2. DNAT:把公网 80 映射到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# 3. 放行转发(filter 表)
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 4. (可选)如果内网主机需要主动访问外网,还要做 SNAT/MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

实验验证

1
2
3
4
5
# 从外部访问网关公网 IP 的 80 端口
curl http://网关公网IP

# 在内网目标机器上抓包确认
tcpdump -i any port 8080 -nn

10.6. 一对一 NAT

一对一 NAT(也叫静态 NAT)是将一个内网 IP 固定映射到一个公网 IP,实现双向互通。

1
2
3
4
5
6
7
8
9
10
11
# 内网 192.168.1.100 ↔ 公网 203.0.113.100

# DNAT:外部访问公网 IP 时转到内网
iptables -t nat -A PREROUTING -d 203.0.113.100 -j DNAT --to-destination 192.168.1.100

# SNAT:内网主机访问外网时使用对应的公网 IP
iptables -t nat -A POSTROUTING -s 192.168.1.100 -j SNAT --to-source 203.0.113.100

# 放行转发
iptables -A FORWARD -s 192.168.1.100 -j ACCEPT
iptables -A FORWARD -d 192.168.1.100 -j ACCEPT

适用场景

  • 需要给内网服务器分配独立公网 IP
  • 对延迟和会话保持要求较高的业务

10.7. 多公网 IP NAT

当服务器拥有多个公网 IP 时,可以根据需求做不同的地址转换。

1
2
3
4
5
6
7
8
9
# 假设 eth0 有两个公网 IP:203.0.113.10 和 203.0.113.11

# 不同内网网段使用不同出口 IP
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o eth0 -j SNAT --to-source 203.0.113.11

# 根据目的地址选择不同出口 IP
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 8.8.8.8 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j SNAT --to-source 203.0.113.11

进阶玩法:结合 MARK + 策略路由,实现更精细的多出口控制。

10.8. NAT 与 conntrack 的关系

NAT 和 conntrack 是深度绑定的:

  1. NAT 只处理连接的第一个包
    后续包的地址转换完全由 conntrack 根据第一条记录自动完成。

  2. conntrack 记录了转换前后的映射关系
    所以即使是双向流量,也能正确进行反向转换。

  3. 查看 NAT 转换记录

    1
    conntrack -L -n | grep 192.168.1
  4. 常见问题排查

    • NAT 规则写了但不生效 → 检查是否开启了 ip_forward
    • 只有单向通 → 检查 FORWARD 链是否放行,以及是否缺少 ESTABLISHED 规则
    • 连接数过高导致 NAT 异常 → 调大 nf_conntrack_max

经典完整 NAT 网关模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 开启转发
echo 1 > /proc/sys/net/ipv4/ip_forward

# 清空 nat 表
iptables -t nat -F

# 内网访问外网(MASQUERADE)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 端口转发示例
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# filter 表放行转发
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -d 192.168.1.100 -p tcp --dport 8080 -j ACCEPT

本节核心总结

记住三句话

  1. NAT 规则只能写在 nat 表。
  2. SNAT/MASQUERADE 在 POSTROUTING,DNAT 在 PREROUTING。
  3. NAT 依赖 conntrack,只处理连接的第一个包。

掌握这些后,端口转发、共享上网、一对一映射等问题都能轻松解决。

11. iptables 实现端口转发

端口转发是 NAT 最常见的实战场景之一。
很多同学只写一条 DNAT 规则就期望生效,结果发现「外部访问不通」或「内网自己访问公网端口也不通」,问题往往出在转发路径和回程路径上。

本节把端口转发相关的完整原理和坑点一次讲清楚。

11.1. DNAT

端口转发的核心是 DNAT(目的地址转换)。

它把到达网关公网 IP 某端口的流量,修改目的地址后转发到内网真实服务器。

1
2
# 将到达本机 80 端口的流量转发到内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

执行位置:nat 表的 PREROUTING 链(路由判断之前)。

效果

  • 外部客户端看到的是网关的公网 IP:80
  • 实际处理请求的是内网 192.168.1.100:8080

11.2. SNAT

在端口转发场景中,SNAT 主要用于解决回程路径问题(见 11.5)。

当内网服务器响应外部请求时,如果源地址仍是内网 IP,回程包可能无法正确回到客户端。通过 SNAT 可以把响应包的源地址改成网关的公网 IP。

1
2
# 对转发到内网的流量做 SNAT(可选,取决于网络拓扑)
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j SNAT --to-source 网关内网IP

更常见的做法是直接对整个内网做 MASQUERADE:

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

11.3. DNAT + FORWARD

完整的端口转发规则至少需要两部分

  1. nat 表做 DNAT(修改目的地址)
  2. filter 表的 FORWARD 链放行转发
1
2
3
4
5
6
7
8
9
10
11
12
# 1. 开启内核转发
echo 1 > /proc/sys/net/ipv4/ip_forward

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

# 3. 放行转发(关键!)
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 4. (推荐)内网访问外网时做地址伪装
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

实验验证

Bash

1
2
3
4
5
6
7
8
# 在网关上看 DNAT 是否生效
iptables -t nat -L -n -v

# 在目标内网机器上抓包
tcpdump -i any port 8080 -nn

# 从外部访问网关公网 IP 的 80 端口
curl http://网关公网IP

11.4. 只做 DNAT 为什么不够

很多初学者只写一条 DNAT 规则,然后发现外部无法访问,原因主要有两个:

原因一:没有开启 IP 转发

Bash

1
2
# 必须开启
echo 1 > /proc/sys/net/ipv4/ip_forward

原因二:FORWARD 链默认策略是 DROP,或者没有放行规则

DNAT 只负责「改地址」,改完之后包还要经过 FORWARD 链。
如果 FORWARD 链没有允许这个包通过,内核会直接丢弃。

完整最小规则集

Bash

1
2
3
4
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

11.5. 回程路径问题

端口转发不仅要解决「去程」,还要解决「回程」。

典型问题现象

  • 外部能看到连接建立,但数据传输中断
  • 或者完全无法建立连接

原因分析

  1. 外部客户端 → 网关(DNAT)→ 内网服务器
  2. 内网服务器响应时,源地址是内网 IP(192.168.1.100)
  3. 如果内网服务器的默认网关不是我们的这台网关,或者回程路由异常,响应包就会走错路径

解决方案

方案一:让内网服务器把网关设置为我们的转发机器(最推荐)

方案二:在网关上对回程流量做 SNAT(强制响应包从本机出去)

1
2
# 对发往内网目标端口的流量做 SNAT,把源地址改成网关自己的内网 IP
iptables -t nat -A POSTROUTING -p tcp -d 192.168.1.100 --dport 8080 -j SNAT --to-source 192.168.1.1

这样内网服务器会认为请求来自网关,响应也会回到网关,再由网关转发给外部客户端。

11.6. hairpin NAT(NAT 环回 / NAT Loopback)

问题场景
内网用户想通过「公网 IP + 端口」访问已经做了端口转发的内网服务,结果无法访问。

这就是经典的 hairpin NAT(也叫 NAT loopback)问题。

原因
内网主机访问公网 IP 时,包到达网关后被 DNAT 到内网服务器,但响应包的源地址是内网 IP,内网客户端无法把这个响应与自己发出的「公网 IP」请求对应起来。

解决方法

需要额外再加一条 SNAT 规则,让内网访问公网端口的流量在转发时也被伪装:

1
2
3
4
5
# 已有的 DNAT
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080

# 针对 hairpin 的 SNAT(关键)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 8080 -j SNAT --to-source 192.168.1.1

或者更通用的写法(对所有内网访问本机公网端口的流量做伪装):

1
2
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE
# 注意:如果公网网卡和内网网卡不同,需要根据实际出口调整

完整 hairpin 友好规则示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 开启转发
echo 1 > /proc/sys/net/ipv4/ip_forward

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

# 放行转发
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 普通出网伪装
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# hairpin 专用 SNAT(内网访问内网服务时通过公网端口)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.1.100 -p tcp --dport 8080 \
-j SNAT --to-source 192.168.1.1

端口转发完整检查清单

  1. ip_forward 是否开启?
  2. DNAT 规则是否正确写在 PREROUTING?
  3. FORWARD 链是否放行了目标地址和端口?
  4. 是否放行了 ESTABLISHED,RELATED?
  5. 回程路径是否正常?(网关设置或 SNAT)
  6. 内网用户是否需要通过公网 IP 访问?(需要 hairpin 规则)

把以上六点都覆盖到,端口转发基本就能稳定工作。

12. Linux 路由与 iptables

iptables 负责「包的过滤与地址转换」,而 Linux 路由子系统负责「包该从哪块网卡、哪条路径出去」。
两者紧密配合,尤其在多网卡、多出口、策略路由场景中,几乎总是一起出现。

理解它们的协作关系,是实现复杂网络策略的基础。

12.1. iptables 与路由的关系

简单总结两者的分工:

组件主要职责关键位置
iptables过滤、修改、标记数据包Netfilter Hook
路由子系统决定数据包的下一跳和出口网卡路由查找(Route Lookup)

协作流程(以进入本机的包为例):

  1. 包进入网卡 → PREROUTING(iptables 可在此做 DNAT、MARK)
  2. 路由判断(Route Lookup):决定是交给本机还是转发
  3. 如果是转发 → FORWARD → 再次路由判断 → POSTROUTING
  4. 如果是本机发出 → OUTPUT → 路由判断 → POSTROUTING

iptables 可以通过 MARK 给包打标记,然后路由子系统根据标记选择不同的路由表,这就是策略路由的核心。

12.2. route lookup 发生在哪里

Linux 网络栈中,路由查找主要发生在两个关键点:

  1. PREROUTING 之后
    决定包是进入本机(INPUT)还是转发(FORWARD)。

  2. OUTPUT / FORWARD 之后、POSTROUTING 之前
    决定包最终从哪块网卡、哪个下一跳出去。

对应关系如下:

数据包进入 → PREROUTING(iptables:DNAT、MARK) → 路由判断 ①(决定 INPUT 还是 FORWARD) → INPUT 或 FORWARD → 路由判断 ②(决定出口网卡和下一跳) → POSTROUTING(iptables:SNAT、MASQUERADE) → 从网卡发出

重点

  • 在 PREROUTING 中做的 MARK,可以影响第一次路由判断;
  • 在 OUTPUT 或 PREROUTING 中做的 MARK,也可以影响最终的出口选择。

12.3. ip rule

ip rule 用于管理策略路由规则,它决定「根据什么条件,去哪张路由表查询」。

查看当前策略规则:

1
ip rule show

默认规则通常是:

1
2
3
0:      from all lookup local
32766: from all lookup main
32767: from all lookup default

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
# 添加规则:来自 192.168.1.0/24 的包,使用路由表 100
ip rule add from 192.168.1.0/24 table 100

# 添加规则:带有 fwmark 100 的包,使用路由表 100
ip rule add fwmark 100 table 100

# 添加规则:目的地址是 8.8.8.8 的包,使用路由表 200
ip rule add to 8.8.8.8 table 200

# 删除规则
ip rule del from 192.168.1.0/24 table 100
ip rule del fwmark 100 table 100

优先级:数字越小,优先级越高。

12.4. ip route

ip route 用于管理路由表中的具体路由条目。

Linux 支持多张路由表,常用的有:

  • local(表 255):本地路由
  • main(表 254):主路由表(默认)
  • default(表 253):默认路由表
  • 自定义表(如 100、200)

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看主路由表
ip route show
ip route show table main

# 查看自定义路由表
ip route show table 100

# 向自定义表添加默认路由
ip route add default via 192.168.1.1 dev eth0 table 100

# 添加具体网段路由
ip route add 10.0.0.0/8 via 192.168.1.1 table 100

# 删除路由
ip route del default table 100

查看所有路由表

1
ip route show table all

12.5. fwmark

fwmark(firewall mark)是一个保存在数据包中的标记值,由 iptables 的 MARK 目标设置,供路由子系统或其他工具(如 tc)使用。

1
2
3
4
# 给匹配的包打上标记 100
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100

# 查看标记(需要连接跟踪或额外工具,通常结合策略路由验证)

常用 MARK 相关动作

动作含义
--set-mark 100直接设置标记为 100
--and-mark 0xff按位与
--or-mark 0x100按位或
--xor-mark 0x1按位异或
--set-xmark 100/0xffffffff更灵活的设置方式

标记值是一个 32 位整数,常用较小的数字(如 1、100、200)便于管理。

12.6. MARK + policy routing

这是实现策略路由最经典的组合,流程如下:

  1. 用 iptables 给特定流量打上 fwmark
  2. 用 ip rule 根据 fwmark 选择不同的路由表
  3. 在对应的路由表中设置不同的出口网关

完整示例:让内网不同网段走不同出口

假设:

  • eth0:出口 1,网关 203.0.113.1
  • eth1:出口 2,网关 198.51.100.1
  • 希望 192.168.1.0/24 走出口 1,192.168.2.0/24 走出口 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 给不同网段打标记
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100
iptables -t mangle -A PREROUTING -s 192.168.2.0/24 -j MARK --set-mark 200

# 2. 根据标记选择路由表
ip rule add fwmark 100 table 100
ip rule add fwmark 200 table 200

# 3. 在对应路由表中设置默认路由
ip route add default via 203.0.113.1 dev eth0 table 100
ip route add default via 198.51.100.1 dev eth1 table 200

# 4. (可选)刷新路由缓存
ip route flush cache

验证方法

1
2
3
4
5
6
7
8
# 查看策略规则
ip rule show

# 查看自定义路由表
ip route show table 100
ip route show table 200

# 在内网主机上 traceroute 观察出口是否符合预期

12.7. 多出口 IP

当服务器拥有多个公网 IP(多归属)时,常需要控制「不同流量使用不同出口 IP」。

场景一:不同内网网段使用不同公网 IP 出去

Bash

1
2
3
# 使用 SNAT 直接指定出口 IP(简单场景)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o eth0 -j SNAT --to-source 203.0.113.11

场景二:结合策略路由实现更灵活的控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 打标记
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100
iptables -t mangle -A PREROUTING -s 192.168.2.0/24 -j MARK --set-mark 200

# 2. 策略路由
ip rule add fwmark 100 table 100
ip rule add fwmark 200 table 200

# 3. 不同路由表使用不同出口(如果网关不同)
ip route add default via 203.0.113.1 dev eth0 table 100
ip route add default via 198.51.100.1 dev eth1 table 200

# 4. 配合 SNAT 确保源 IP 正确
iptables -t nat -A POSTROUTING -m mark --mark 100 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -m mark --mark 200 -j SNAT --to-source 203.0.113.11

场景三:本机进程使用不同出口 IP

对本机发出的流量,需要在 OUTPUT 链打标记:

1
2
3
4
# 本机访问特定目标时使用指定出口
iptables -t mangle -A OUTPUT -d 8.8.8.8 -j MARK --set-mark 100
ip rule add fwmark 100 table 100
ip route add default via 203.0.113.1 table 100

实战检查清单

  • 标记是否在正确的链(PREROUTING 或 OUTPUT)打上?
  • ip rule 优先级是否合理?
  • 自定义路由表是否有默认路由?
  • 是否需要配合 SNAT 保证源 IP 正确?
  • 修改后是否执行了 ip route flush cache?

掌握这些内容后,多网卡负载分担、主备出口、基于来源的选路等需求都能从容应对。

13. UDP 与 iptables

UDP 是无连接协议,但在 iptables 和 conntrack 的世界里,它被「模拟」成了有状态的连接。
正是这种模拟,导致 UDP 在 NAT、多公网 IP、策略路由等场景下比 TCP 更容易出问题。

理解 UDP 与 TCP 在连接跟踪上的差异,是排查 DNS、VPN、游戏、语音等 UDP 业务故障的关键。

13.1. UDP 与 TCP 的区别

对比项TCPUDP
连接导向有(三次握手、四次挥手)
状态机复杂(SYN、ESTABLISHED、FIN 等)简单(只有「看到双向包」)
conntrack 超时较长(建立后通常数小时)较短(默认 30 秒或 180 秒)
回程包识别依靠序列号 + 状态机仅依靠五元组
NAT 友好性较高较低,容易出问题
策略路由影响相对稳定更容易受源地址选择影响

核心差异

  • TCP 有明确的连接建立和拆除过程,conntrack 能精准跟踪;
  • UDP 没有连接概念,conntrack 只能通过「在超时时间内看到反向数据包」来判断一条「伪连接」是否还活着。

13.2. UDP conntrack

虽然 UDP 无连接,但 Netfilter 仍然会为它创建 conntrack 条目。

UDP 连接跟踪的典型生命周期

  1. 第一个包到达 → 创建条目,状态为 NEW
  2. 看到反向包 → 状态变为 ESTABLISHED
  3. 在超时时间内一直有双向包 → 保持 ESTABLISHED
  4. 超时无包 → 条目删除

默认超时时间(可调整)

1
2
3
# 查看 UDP 超时
cat /proc/sys/net/netfilter/nf_conntrack_udp_timeout # 默认 30 秒
cat /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream # 默认 180 秒(被认为是 stream 后)

调整示例

1
2
3
4
5
6
7
8
# 临时修改
echo 60 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream

# 永久生效
echo "net.netfilter.nf_conntrack_udp_timeout = 60" >> /etc/sysctl.conf
echo "net.netfilter.nf_conntrack_udp_timeout_stream = 180" >> /etc/sysctl.conf
sysctl -p

查看 UDP 连接

1
2
conntrack -L -p udp
conntrack -L -p udp | grep 53 # 查看 DNS 相关

13.3. UDP NAT

UDP 的 NAT 与 TCP 基本相同,但因为没有连接状态机,更容易出现回程问题

典型 DNAT 示例(DNS 转发):

1
2
3
4
5
6
# 把到达本机 53 端口的 UDP 流量转发到内网 DNS 服务器
iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination 192.168.1.100:53

# 放行转发
iptables -A FORWARD -p udp -d 192.168.1.100 --dport 53 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

SNAT / MASQUERADE 示例

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

注意

  • UDP NAT 后,conntrack 会记录「原始五元组 ↔ 转换后五元组」的映射。
  • 只要反向包能在超时时间内回来,并且五元组匹配,就能正确还原。

13.4. UDP 回包为什么容易出问题

UDP 回包出问题是生产中最常见的故障之一,主要原因有:

  1. 超时过短
    默认 30 秒,如果业务心跳间隔较长,条目提前删除,回包就无法匹配。
  2. 源端口变化
    某些应用每次发送都使用新的源端口,导致 conntrack 认为是全新连接。
  3. 多网卡 / 多公网 IP 下源地址选择错误
    回包的源 IP 与去程 NAT 后的地址不一致,客户端无法识别。
  4. 策略路由导致回程路径不一致
    去程和回程走了不同的出口,NAT 映射无法正确命中。
  5. 助手模块缺失
    虽然 UDP 本身不需要助手,但某些应用层协议(如 SIP、FTP 的 UDP 模式)需要额外模块。

排查思路

1
2
3
4
5
6
7
8
# 1. 看 conntrack 是否有对应条目
conntrack -L -p udp | grep 目标IP

# 2. 看超时是否合理
cat /proc/sys/net/netfilter/nf_conntrack_udp_timeout

# 3. 抓包对比去程和回程的五元组是否一致
tcpdump -i any udp port 目标端口 -nn

13.5. 多公网 IP 下的 UDP 源地址

当服务器有多个公网 IP 时,UDP 的源地址选择比 TCP 更敏感。

问题现象
去程用了 IP-A,回程却用了 IP-B,导致对端无法匹配会话。

解决方法一:使用 SNAT 强制指定源 IP

1
2
# 强制某类 UDP 流量使用指定公网 IP 出去
iptables -t nat -A POSTROUTING -p udp --dport 53 -o eth0 -j SNAT --to-source 203.0.113.10

解决方法二:结合 MARK + 策略路由 + SNAT

1
2
3
4
5
6
7
8
9
# 1. 打标记
iptables -t mangle -A PREROUTING -p udp --dport 53 -j MARK --set-mark 100

# 2. 策略路由
ip rule add fwmark 100 table 100
ip route add default via 203.0.113.1 dev eth0 table 100

# 3. 强制源地址
iptables -t nat -A POSTROUTING -m mark --mark 100 -j SNAT --to-source 203.0.113.10

13.6. source in / source out

在多网卡或多 IP 场景下,常需要区分「流量从哪个地址进来」和「从哪个地址出去」。

常用匹配方式

1
2
3
4
5
6
7
8
# 匹配从指定网卡进入的 UDP 包
iptables -A INPUT -p udp -i eth0 --dport 53 -j ACCEPT

# 匹配从指定网卡出去的 UDP 包
iptables -A OUTPUT -p udp -o eth1 --dport 53 -j ACCEPT

# 匹配源地址是本机某个 IP 的包(本机发出)
iptables -A OUTPUT -p udp -s 203.0.113.10 --dport 53 -j ACCEPT

结合 conntrack 查看实际使用的地址

1
2
conntrack -L -p udp -s 203.0.113.10
conntrack -L -p udp -d 203.0.113.10

在做 UDP 相关的 NAT 或策略路由时,明确「source in」和「source out」能大幅减少排查时间。

13.7. UDP 与策略路由

策略路由对 UDP 的影响比 TCP 更大,因为 UDP 没有序列号和严格的状态机来「纠正」路径不一致。

典型问题
去程通过策略路由走了出口 A,回程因为路由表优先级问题走了出口 B,导致 NAT 映射失效或对端收到错误源地址的包。

推荐做法

  1. 对去程和回程使用对称的标记与路由规则
  2. 尽量用 SNAT 固定源地址,减少路由选择的不确定性
  3. 对 UDP 超时时间适当调大,给回程包更多容忍时间

完整示例:让特定 UDP 业务走指定出口

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 给目标端口打标记
iptables -t mangle -A PREROUTING -p udp --dport 51820 -j MARK --set-mark 200 # WireGuard 示例
iptables -t mangle -A OUTPUT -p udp --dport 51820 -j MARK --set-mark 200

# 2. 策略路由
ip rule add fwmark 200 table 200
ip route add default via 198.51.100.1 dev eth1 table 200

# 3. 强制源地址(重要)
iptables -t nat -A POSTROUTING -m mark --mark 200 -p udp -j SNAT --to-source 198.51.100.10

# 4. 适当增大 UDP 超时
echo 120 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout

排查 UDP 问题的常用命令组合

1
2
3
4
5
6
7
8
9
10
11
12
# 看连接跟踪
conntrack -L -p udp | grep 端口或IP

# 看超时配置
cat /proc/sys/net/netfilter/nf_conntrack_udp_timeout*

# 抓包对比五元组
tcpdump -i any udp port 端口 -nn

# 看策略路由是否生效
ip rule show
ip route show table 对应表号

把这些点掌握后,UDP 相关的防火墙和 NAT 问题就能快速定位并解决。

14. iptables 高级功能

基础的匹配条件(源/目的 IP、端口、协议)已经能解决大部分问题,但在面对大量 IP 黑白名单、暴力破解防护、频率限制、复杂流量识别等场景时,就需要用到 iptables 的扩展模块。

这些模块通过 -m 加载,能显著提升规则的灵活性和性能。

14.1. ipset

作用:高效管理大量 IP、网段、端口集合,避免写数百条规则。

为什么需要它
直接用 -s 写 1000 个 IP,规则匹配性能会明显下降;ipset 使用哈希表,匹配速度几乎与集合大小无关。

常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装(如未安装)
yum install -y ipset # CentOS/RHEL
apt install ipset # Ubuntu/Debian

# 创建集合
ipset create whitelist hash:ip
ipset create blacklist hash:net
ipset create ports bitmap:port range 0-65535

# 添加元素
ipset add whitelist 192.168.1.100
ipset add blacklist 203.0.113.0/24
ipset add ports 22
ipset add ports 80-443

# 在 iptables 中引用
iptables -A INPUT -m set --match-set whitelist src -j ACCEPT
iptables -A INPUT -m set --match-set blacklist src -j DROP
iptables -A INPUT -p tcp -m set --match-set ports dst -j ACCEPT

# 查看与删除
ipset list
ipset destroy whitelist

运维价值
黑名单、白名单、地理 IP 库、动态封禁等场景的首选方案。支持动态添加/删除,无需重载 iptables 规则。

14.2. recent

作用:根据「最近一段时间内某 IP 的连接次数」做动态决策,常用于防暴力破解。

1
2
3
4
# 允许 SSH,但限制 60 秒内最多尝试 3 次
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --name SSH --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --name SSH --update --seconds 60 --hitcount 3 -j DROP
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

常用参数

  • --name:集合名称
  • --set:把当前 IP 加入集合
  • --update:更新命中时间
  • --seconds:时间窗口
  • --hitcount:命中次数阈值
  • --rcheck:只检查不更新

查看当前记录

1
cat /proc/net/xt_recent/SSH

14.3. limit

作用:简单的速率限制,控制单位时间内匹配的包数量。

1
2
3
4
5
6
# 限制 ping 频率:平均每秒 1 个,突发允许 5 个
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 5 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

# 限制日志记录频率,防止日志爆炸
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "DROP: "

参数说明

  • --limit 1/s、5/m、10/h:平均速率
  • --limit-burst:突发允许的数量

缺点:所有匹配的包共享同一个令牌桶,无法针对单个 IP 做限制(这是 hashlimit 的强项)。

14.4. hashlimit

作用:比 limit 更强大的限速,可按 IP、端口等维度分别限制。

1
2
3
4
5
6
7
8
# 每个源 IP 每分钟最多新建 10 个 SSH 连接
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m hashlimit --hashlimit-name ssh \
--hashlimit-above 10/min --hashlimit-mode srcip \
--hashlimit-burst 5 -j DROP

# 允许正常连接
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

常用参数

  • --hashlimit-name:名称(对应 /proc/net/ipt_hashlimit/ 下的文件)
  • --hashlimit-mode srcip / dstip / srcip-dstip 等
  • --hashlimit-above:超过此速率就匹配
  • --hashlimit-burst:突发量

运维场景:防暴力破解、防 CC 攻击、限制单 IP 连接频率的首选。

14.5. connlimit

作用:限制每个客户端 IP 的并发连接数

Bash

1
2
3
4
5
# 每个 IP 对 80 端口最多 20 个并发连接
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 20 --connlimit-mask 32 -j DROP

# 限制整个 /24 网段的总连接数
iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 100 --connlimit-mask 24 -j DROP

参数

  • --connlimit-above n:超过 n 个连接就匹配
  • --connlimit-mask:掩码(32 表示单个 IP,24 表示整个 C 段)

适用场景:防止单个 IP 占用过多连接(如爬虫、慢速攻击)。

14.6. string

作用:在数据包的应用层载荷中匹配字符串(需要内核支持)。

1
2
3
4
5
# 丢弃包含特定恶意字符串的包
iptables -A INPUT -p tcp -m string --string "malicious" --algo bm -j DROP

# 匹配十六进制
iptables -A INPUT -p tcp -m string --hex-string "|13 37|" --algo bm -j DROP

参数

  • --string:要匹配的字符串
  • --hex-string:十六进制形式
  • --algo bm / kmp:匹配算法(bm 通常更快)

注意
性能开销较大,且只能匹配未加密的明文流量。不推荐在高流量生产环境大规模使用。

14.7. u32

作用:对数据包任意位置的 32 位数据进行精细匹配(高级玩法)。

1
2
# 示例:匹配 IP 头部某个字段(较少日常使用)
iptables -A INPUT -p tcp -m u32 --u32 "0x0>>22&0x3C@0xC>>8=0x100" -j DROP

特点:功能极其强大,但语法复杂,可读性差。
通常只在需要深度包检测且不想上专业 IPS 设备时使用。

14.8. owner

作用:根据发送进程的用户 / 组 / PID 来匹配本机发出的包(仅对 OUTPUT 链有效)。

1
2
3
4
5
6
7
8
9
# 只允许 uid 为 1000 的用户访问外网
iptables -A OUTPUT -m owner --uid-owner 1000 -j ACCEPT
iptables -A OUTPUT -j DROP

# 匹配进程 GID
iptables -A OUTPUT -m owner --gid-owner 1001 -j ACCEPT

# 匹配进程 PID(较少用,PID 会变)
iptables -A OUTPUT -m owner --pid-owner 1234 -j ACCEPT

适用场景

  • 多用户服务器上限制特定用户的出站权限
  • 容器或特定服务的流量隔离

14.9. mark

作用:匹配或设置数据包的 fwmark(防火墙标记)。

1
2
3
4
5
6
7
8
9
# 设置标记(在 mangle 表)
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100

# 匹配标记
iptables -A FORWARD -m mark --mark 100 -j ACCEPT

# 常用组合:设置后立即匹配
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 200
iptables -t mangle -A PREROUTING -m mark --mark 200 -j ACCEPT

与策略路由配合是它最重要的用途(见第 12 节)。

14.10. connmark

作用:对整个连接进行标记,而不是单个包。标记会保存在 conntrack 条目中,同一连接的所有后续包都能继承。

1
2
3
4
5
6
7
8
# 给连接打标记
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j CONNMARK --set-mark 100

# 把连接标记恢复到当前包的 mark(经典用法)
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark

# 保存当前包的 mark 到连接
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark

经典组合模板(先恢复,再按需设置并保存):

Bash

1
2
3
4
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 100
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark

MARK vs CONNMARK

对比项MARKCONNMARK
作用对象单个数据包整个连接
生命周期只影响当前包连接跟踪期间有效
典型用途临时分类、流量控制需要连接级一致性标记

实战建议

  1. 能用 ipset 解决的绝不用大量 -s 规则。
  2. 防暴力破解优先用 hashlimit 或 recent + connlimit 组合。
  3. 标记相关操作统一放在 mangle 表。
  4. 所有频率限制规则都要考虑「先记录日志再拒绝」,方便后续分析。

这些高级功能组合使用后,iptables 就能应对绝大多数复杂的安全和流量控制需求。

15. 自定义 Chain

当规则数量越来越多时,把所有规则都塞在 INPUT、FORWARD、OUTPUT 这几条内置链里,会变得难以维护和阅读。
自定义 Chain(用户自定义链)就是为了解决这个问题而生的。

15.1. 为什么需要自定义 Chain

使用自定义链的主要好处:

  1. 结构清晰
    把不同类型的规则分组(如 SSH 防护、Web 服务、内网访问、日志记录等),便于理解和审计。

  2. 复用规则
    同一段规则逻辑可以在多处通过跳转复用,避免重复编写。

  3. 提升性能
    通过早期跳转,让不相关的包尽早离开当前检查路径,减少后续无效匹配。

  4. 便于维护
    修改某一类规则时,只需关注对应的自定义链,而不用在成百上千条规则中查找。

  5. 更好的权限与责任分离
    不同管理员或自动化系统可以只管理自己的链。

15.2. 创建 Chain

使用 -N(new)创建一条新的自定义链:

1
2
3
4
5
6
7
8
# 创建自定义链
iptables -N SSH_CHAIN
iptables -N WEB_CHAIN
iptables -N LOG_DROP

# 查看所有链(包括自定义链)
iptables -L -n

注意事项

  • 自定义链的名称区分大小写
  • 名称建议使用大写 + 下划线,便于与内置链区分
  • 创建后链是空的,需要手动添加规则

15.3. 跳转到 Chain

使用 -j 跳转到自定义链,效果类似于函数调用:

1
2
3
4
5
6
7
8
9
10
11
# 在 INPUT 链中,把 SSH 相关流量跳转到 SSH_CHAIN
iptables -A INPUT -p tcp --dport 22 -j SSH_CHAIN

# 把 Web 流量跳转到 WEB_CHAIN
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j WEB_CHAIN

# 在自定义链中继续写规则
iptables -A SSH_CHAIN -s 192.168.1.0/24 -j ACCEPT
iptables -A SSH_CHAIN -m conntrack --ctstate NEW -m recent --name SSH --set
iptables -A SSH_CHAIN -m conntrack --ctstate NEW -m recent --name SSH --update --seconds 60 --hitcount 5 -j LOG_DROP
iptables -A SSH_CHAIN -j ACCEPT

跳转后的行为

  • 包进入自定义链后,从该链的第一条规则开始匹配
  • 如果匹配到 ACCEPT、DROP、REJECT 等终止动作,则立即结束整个检查
  • 如果匹配到 RETURN,则返回到调用它的链,继续执行下一条规则
  • 如果自定义链所有规则都匹配完毕仍未终止,也会自动返回

15.4. RETURN

RETURN 是自定义链中非常重要的动作,表示「返回到调用链继续处理」。

1
2
3
4
5
6
7
8
9
10
11
iptables -N MY_CHAIN

# 匹配到特定 IP 就允许并结束
iptables -A MY_CHAIN -s 192.168.1.100 -j ACCEPT

# 其他情况返回到上一级链
iptables -A MY_CHAIN -j RETURN

# 在 INPUT 中调用
iptables -A INPUT -j MY_CHAIN
iptables -A INPUT -j DROP # 如果 MY_CHAIN 返回,就会执行到这里

对比

  • 在自定义链末尾写 RETURN:明确返回
  • 不写任何终止动作:链结束后也会自动返回(隐式 RETURN)
  • 在内置链(INPUT 等)中使用 RETURN:效果等同于该链的默认策略

15.5. 删除 Chain

删除自定义链有严格的前提条件:

  1. 链必须为空(没有规则)
  2. 没有被其他链引用(没有 -j 跳转到它)

正确删除步骤

1
2
3
4
5
6
7
8
# 1. 先删除所有跳转到该链的规则
iptables -D INPUT -p tcp --dport 22 -j SSH_CHAIN

# 2. 清空链中的规则
iptables -F SSH_CHAIN

# 3. 删除链本身
iptables -X SSH_CHAIN

常用清理命令

1
2
3
4
5
# 清空所有自定义链中的规则
iptables -F

# 删除所有没有被引用的自定义链
iptables -X

注意:-X 只能删除自定义链,无法删除内置链(INPUT、FORWARD、OUTPUT 等)。

15.6. 组织大型规则集

生产环境中推荐的规则组织方式:

1. 按功能划分链

1
2
3
4
5
iptables -N SSH_PROTECT
iptables -N WEB_SERVICE
iptables -N DB_ACCESS
iptables -N LOG_AND_DROP
iptables -N ALLOW_ESTABLISHED

2. 在内置链中只做「分类跳转」

1
2
3
4
5
6
7
8
9
10
# INPUT 链保持简洁
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

iptables -A INPUT -p tcp --dport 22 -j SSH_PROTECT
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j WEB_SERVICE
iptables -A INPUT -p tcp --dport 3306 -j DB_ACCESS

iptables -A INPUT -j LOG_AND_DROP

3. 在自定义链中写具体逻辑

1
2
3
4
5
6
7
8
9
# SSH 防护链
iptables -A SSH_PROTECT -s 192.168.1.0/24 -j ACCEPT
iptables -A SSH_PROTECT -m conntrack --ctstate NEW -m hashlimit \
--hashlimit-name ssh --hashlimit-above 5/min --hashlimit-mode srcip -j LOG_AND_DROP
iptables -A SSH_PROTECT -j ACCEPT

# 日志 + 丢弃链
iptables -A LOG_AND_DROP -m limit --limit 5/min -j LOG --log-prefix "DROPED: "
iptables -A LOG_AND_DROP -j DROP

4. 推荐的整体结构模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INPUT
├── 放行 lo
├── 放行 ESTABLISHED,RELATED
├── 丢弃 INVALID
├── 跳转 → SSH_PROTECT
├── 跳转 → WEB_SERVICE
├── 跳转 → DB_ACCESS
└── 跳转 → LOG_AND_DROP

SSH_PROTECT
├── 白名单 ACCEPT
├── 频率限制 → LOG_AND_DROP
└── ACCEPT

WEB_SERVICE
├── 允许特定来源
└── ACCEPT

LOG_AND_DROP
├── 限速 LOG
└── DROP

运维建议

  • 自定义链名称要有意义,见名知意
  • 保持内置链规则数量尽量少(只做分类)
  • 每条自定义链职责单一
  • 修改规则前先 iptables-save > backup.rules
  • 大型规则集建议写成脚本,并用版本管理(git)跟踪变更

最佳实践一句话

  • 内置链负责分类跳转,自定义链负责具体逻辑。

用好自定义链,规则集从「难以维护的流水账」变成「结构清晰的模块化代码」,是 iptables 进阶的重要标志。

16. 日志与排障

iptables 规则写完后不生效、包被莫名丢弃、NAT 转换异常……这些是运维中最常见的问题。
掌握系统化的排查方法,比反复试规则高效得多。

16.1. LOG target

LOG 是排障时最直接的工具,它能把匹配到的数据包信息写入内核日志,并且不会终止规则匹配

1
2
3
4
5
6
7
8
9
# 基础用法
iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "SSH: " --log-level 4

# 记录后继续丢弃(常用组合)
iptables -A INPUT -s 203.0.113.0/24 -j LOG --log-prefix "BLOCKED: "
iptables -A INPUT -s 203.0.113.0/24 -j DROP

# 记录 TCP 序列号和选项(更详细)
iptables -A INPUT -p tcp --dport 80 -j LOG --log-prefix "HTTP: " --log-tcp-sequence --log-tcp-options

常用参数

  • --log-prefix “前缀”:自定义前缀,方便过滤(建议以空格结尾)
  • --log-level 4:日志级别(0-7,4 为 warning)
  • --log-tcp-sequence:记录序列号
  • --log-tcp-options:记录 TCP 选项
  • --log-ip-options:记录 IP 选项
  • --log-uid:记录发送进程的 UID(OUTPUT 链有用)

注意:LOG 规则一定要放在 DROP/REJECT 之前,否则包已经被丢弃就无法记录。

16.2. 查看内核日志

LOG 目标写入的是内核日志,查看方式因发行版而异:

1
2
3
4
5
6
7
8
9
10
11
12
13
# CentOS / RHEL / Fedora
tail -f /var/log/messages
dmesg -w
journalctl -kf

# Ubuntu / Debian
tail -f /var/log/syslog
dmesg -w
journalctl -kf

# 只看带特定前缀的日志
tail -f /var/log/messages | grep "SSH:"
dmesg | grep "BLOCKED:"

实时监控推荐

1
2
# 持续观察内核日志
dmesg -w | grep --line-buffered -E "SSH:|BLOCKED:|DROPED:"

16.3. iptables counters

每条规则都有包计数器字节计数器,是判断规则是否命中的最快方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看规则及计数器
iptables -L -n -v

# 查看时显示规则编号
iptables -L -n -v --line-numbers

# 查看 nat 表计数器
iptables -t nat -L -n -v

# 清零计数器(方便观察增量)
iptables -Z
iptables -Z INPUT
iptables -t nat -Z

排障技巧

  1. 先 iptables -Z 清零
  2. 发起测试流量
  3. 立刻查看 iptables -L -n -v,看哪条规则的计数器增加了

如果目标规则计数器不动,说明包根本没匹配到它(可能被前面的规则拦截,或条件写错)。

16.4. tcpdump

当日志和计数器都无法定位时,用 tcpdump 直接看数据包是最可靠的手段。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 抓取特定端口
tcpdump -i any port 22 -nn

# 抓取特定主机
tcpdump -i any host 192.168.1.100 -nn

# 抓取并显示详细信息
tcpdump -i eth0 port 80 -nn -v

# 只抓 SYN 包(看新建连接)
tcpdump -i any 'tcp[tcpflags] & tcp-syn != 0' -nn

# 抓取后保存到文件,方便后续分析
tcpdump -i any port 53 -nn -w dns.pcap

结合 iptables 的排障流程

  1. 在可疑位置加 LOG 规则
  2. 用 tcpdump 确认包是否到达网卡
  3. 对比 LOG 和 tcpdump 结果,判断包在哪一环消失

16.5. conntrack

查看连接跟踪表,能帮助判断 NAT 是否生效、连接状态是否正确、是否因为表满导致新连接失败。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当前连接数
conntrack -C

# 查看最大连接数
cat /proc/sys/net/netfilter/nf_conntrack_max

# 查看具体条目
conntrack -L
conntrack -L -p tcp
conntrack -L -s 192.168.1.100
conntrack -L -d 203.0.113.10

# 查看 NAT 转换详情
conntrack -L -n | grep 192.168.1

# 删除特定连接(谨慎)
conntrack -D -s 203.0.113.10

常见问题线索

  • nf_conntrack: table full → 连接数打满,需要调大 nf_conntrack_max
  • 有去程条目但没有回程 → 回程路径或 SNAT 有问题
  • 状态一直是 NEW 却不转 ESTABLISHED → 回包没有回来或被丢弃

16.6. ss

ss 是查看本机 socket 状态的现代工具,比 netstat 更快更详细。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看所有 TCP 连接
ss -tan

# 查看监听端口
ss -tlnp

# 查看特定端口
ss -tan 'sport = :22' or 'dport = :22'

# 查看连接状态统计
ss -s

# 查看 UDP
ss -uan

排障价值
确认服务是否真的在监听、连接是否建立成功、连接卡在哪个状态(SYN-SENT、ESTABLISHED、TIME-WAIT 等)。

16.7. ip route

路由问题常被误判为 iptables 问题,尤其是在多网卡、策略路由场景。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
# 查看主路由表
ip route show

# 查看所有表
ip route show table all

# 查看特定表
ip route show table 100

# 查看到某目标的实际路径
ip route get 8.8.8.8
ip route get 8.8.8.8 from 192.168.1.100 iif eth0

重点检查

  • 默认路由是否正确
  • 自定义路由表是否有默认路由
  • ip route get 的结果是否符合预期

16.8. ip rule

策略路由规则决定了「用哪张路由表」,优先级错误会导致流量走错出口。

Bash

1
2
3
4
5
# 查看所有策略规则
ip rule show

# 按优先级排序查看
ip rule show | sort -n

排障要点

  • 规则优先级是否符合预期(数字越小优先级越高)
  • 是否有 catch-all 规则把流量提前拦截
  • 对应的 table 是否真的存在有效路由

16.9. 排查 DROP

包被 DROP 是最常见的故障,推荐按以下顺序排查:

标准排查流程

  1. 清零计数器并复现

    Bash

    1
    2
    3
    iptables -Z
    # 发起测试流量
    iptables -L -n -v --line-numbers
  2. 在可能 DROP 的位置前加 LOG

    Bash

    1
    iptables -I INPUT -j LOG --log-prefix "INPUT_CHECK: "
  3. 检查默认策略

    Bash

    1
    iptables -L -n | grep policy
  4. 检查是否有靠前的 DROP/REJECT 规则 规则是从上到下匹配的,前面的规则会优先生效。

  5. 确认包是否到达主机

    Bash

    1
    tcpdump -i any host 客户端IP -nn
  6. 检查 conntrack 状态 是否被 INVALID 规则提前丢弃。

16.10. 排查 NAT

NAT 不生效或单向通,是另一个高频问题。

完整检查清单

  1. 确认 ip_forward 已开启

    Bash

    1
    2
    cat /proc/sys/net/ipv4/ip_forward
    # 必须为 1
  2. 检查 nat 表规则和计数器

    Bash

    1
    iptables -t nat -L -n -v --line-numbers
  3. 检查 FORWARD 链是否放行

    Bash

    1
    iptables -L FORWARD -n -v
  4. 查看 conntrack 中的 NAT 映射

    Bash

    1
    conntrack -L -n | grep 内网IP或端口
  5. 抓包对比去程和回程

    Bash

    1
    2
    3
    4
    # 在网关公网网卡
    tcpdump -i eth0 port 80 -nn
    # 在内网网卡或目标服务器
    tcpdump -i eth1 port 8080 -nn
  6. 检查回程路径

    • 内网服务器网关是否指向正确
    • 是否需要额外的 SNAT/hairpin 规则
  7. 检查策略路由是否干扰

    Bash

    1
    2
    ip rule show
    ip route get 目标IP from 源IP

常见 NAT 故障速查

现象可能原因检查方向
完全不通没开转发 / DNAT 规则错误 / FORWARD 丢弃ip_forward、nat 表、FORWARD
单向通(只有去程)回程路径错误 / 缺少 SNAT内网网关、POSTROUTING
内网无法通过公网 IP 访问hairpin NAT 问题额外 SNAT 规则
连接数多了就失败conntrack 表满nf_conntrack_max
偶发失败UDP 超时过短 / 策略路由不对称conntrack 超时、ip rule

本节排障工具速查

工具主要用途关键命令示例
LOG target记录匹配到的包-j LOG –log-prefix “XXX: “
计数器判断规则是否命中iptables -L -n -v
tcpdump确认包是否到达及内容tcpdump -i any port 22 -nn
conntrack查看连接状态与 NAT 映射conntrack -L -n
ss查看本机 socket 状态ss -tlnp
ip route检查路由决策ip route get 8.8.8.8
ip rule检查策略路由ip rule show

排障总原则
先看计数器 → 再看日志 → 然后抓包 → 最后查路由和 conntrack。

按照这个顺序,绝大多数 iptables 问题都能快速定位。

17. 常见故障案例

理论讲得再多,不如真实故障来得深刻。
以下是生产环境中出现频率最高、也最容易让人踩坑的 iptables 故障案例,每个案例都给出现象、原因、排查方法和解决方案

17.1. SSH 被锁死

现象
执行完 iptables 规则后,SSH 连接立刻断开,并且无法重新连上服务器。

常见原因

  1. 设置了 INPUT 默认策略为 DROP,但忘记放行 SSH 端口
  2. 放行 SSH 的规则写在了 DROP 规则后面
  3. 只放行了 NEW,但当前连接变成了 INVALID 或没有 ESTABLISHED 规则
  4. 源地址限制写错,把自己的 IP 排除在外

排查与恢复(需通过控制台 / VNC / IPMI):

1
2
3
4
5
6
# 立即放行 SSH(临时救命)
iptables -I INPUT -p tcp --dport 22 -j ACCEPT

# 或者直接放行所有(紧急情况)
iptables -P INPUT ACCEPT
iptables -F

正确写法(推荐模板)

Bash

1
2
3
4
5
6
7
8
# 1. 先放行已建立连接(防止自己被踢)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 2. 放行 SSH(最好限制来源)
iptables -A INPUT -p tcp --dport 22 -s 你的办公网段 -j ACCEPT

# 3. 最后再设置默认 DROP
iptables -P INPUT DROP

预防措施

  • 修改规则前先开一个不会断开的备用会话
  • 使用 iptables-apply 或自己写带超时回滚的脚本
  • 云服务器尽量保留控制台访问手段

17.2. DNAT 后无法访问

现象
写了 DNAT 规则,外部访问公网 IP 对应端口完全不通。

常见原因

  1. 没有开启 ip_forward
  2. FORWARD 链默认 DROP,且没有放行规则
  3. DNAT 规则写错链或表(必须写在 nat 表的 PREROUTING)
  4. 目标内网机器防火墙拦截了流量

排查步骤

Bash

1
2
3
4
5
6
7
8
9
10
11
# 1. 确认转发已开启
cat /proc/sys/net/ipv4/ip_forward # 必须为 1

# 2. 查看 DNAT 规则是否命中
iptables -t nat -L -n -v

# 3. 查看 FORWARD 链
iptables -L FORWARD -n -v

# 4. 在目标机器上抓包
tcpdump -i any port 目标端口 -nn

完整正确规则

Bash

1
2
3
4
5
6
echo 1 > /proc/sys/net/ipv4/ip_forward

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

iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

17.3. NAT 后没有回包

现象
去程流量正常(能看到 DNAT 或 SNAT 命中),但响应包回不来,表现为连接超时或单向通信。

常见原因

  1. 内网服务器的默认网关不是当前这台 NAT 机器
  2. 回程包没有做正确的 SNAT,源地址仍是内网 IP
  3. 策略路由导致回程走了不同出口
  4. 目标机器开启了反向路径过滤(rp_filter)

排查方法

Bash

1
2
3
4
5
# 查看 conntrack 是否有完整双向记录
conntrack -L -n | grep 内网IP

# 在网关和目标机器同时抓包对比
tcpdump -i any host 客户端IP -nn

解决方案

  • 让内网服务器指向正确的网关(推荐)

  • 或在网关上对回程做 SNAT:

    Bash

    1
    iptables -t nat -A POSTROUTING -d 192.168.1.100 -j SNAT --to-source 网关内网IP
  • 检查并关闭严格反向路径过滤(如有需要):

    Bash

    1
    2
    echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
    echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter

17.4. UDP 转发异常

现象
TCP 端口转发正常,但 UDP(DNS、VPN、游戏、语音等)不通或时通时断。

常见原因

  1. UDP conntrack 超时过短(默认 30 秒)
  2. 多公网 IP 下源地址选择不一致
  3. 策略路由导致去程和回程路径不对称
  4. 没有放行 ESTABLISHED 状态的 UDP 回包

解决方法

Bash

1
2
3
4
5
6
7
8
9
# 1. 适当增大 UDP 超时
echo 120 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream

# 2. 强制固定源地址(多 IP 场景强烈推荐)
iptables -t nat -A POSTROUTING -p udp --dport 53 -j SNAT --to-source 固定公网IP

# 3. 确保放行了 ESTABLISHED
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

17.5. 多公网 IP 出口错误

现象
服务器有多个公网 IP,访问外网时源 IP 不符合预期,或回包到达错误的 IP 导致通信失败。

常见原因

  1. 没有用 SNAT 强制指定源地址,系统按路由自由选择
  2. 策略路由规则优先级或标记错误
  3. 本机发出的流量(OUTPUT)没有打标记

解决方法

Bash

1
2
3
4
5
6
7
8
# 方法一:简单粗暴用 SNAT 固定
iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 203.0.113.10

# 方法二:结合 MARK + 策略路由 + SNAT
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100
ip rule add fwmark 100 table 100
ip route add default via 203.0.113.1 table 100
iptables -t nat -A POSTROUTING -m mark --mark 100 -j SNAT --to-source 203.0.113.10

17.6. Docker 修改 iptables

现象
重启 Docker 或容器后,自己写的 iptables 规则被覆盖、顺序被打乱,或 FORWARD 链出现大量 Docker 规则导致转发异常。

原因
Docker 默认会操作 iptables(filter 和 nat 表),并在启动时重新插入自己的规则。

解决方案

方案一:让 Docker 不再管理 iptables(推荐生产)

Bash

1
2
3
4
# /etc/docker/daemon.json
{
"iptables": false
}

然后重启 Docker,自己完全接管规则。

方案二:把自定义规则加在 Docker 规则之前

Bash

1
iptables -I FORWARD -s 192.168.1.0/24 -j ACCEPT

方案三:使用 Docker 的 --iptables=false 启动参数

排查命令

Bash

1
2
iptables -L -n -v | grep -i docker
iptables -t nat -L -n -v | grep -i docker

17.7. firewalld 与 iptables 冲突

现象
规则写了不生效,或重启后规则消失,系统同时运行了 firewalld 和 iptables。

原因
CentOS 7/8、RHEL、Fedora 等系统默认使用 firewalld,它会动态管理 iptables/nftables 规则,与手动 iptables 命令冲突。

解决方法

Bash

1
2
3
4
5
6
7
8
9
10
11
# 1. 停止并禁用 firewalld
systemctl stop firewalld
systemctl disable firewalld

# 2. 安装并启用 iptables 服务(CentOS/RHEL)
yum install -y iptables-services
systemctl enable iptables
systemctl start iptables

# 3. 保存规则
service iptables save

检查是否冲突

Bash

1
2
systemctl status firewalld
iptables -L -n

17.8. iptables 规则重启后消失

现象
规则在当前生效,但服务器重启后全部丢失,恢复成空规则或默认策略。

原因
iptables 规则默认只存在于内存中,重启后不会自动加载,必须手动保存并设置开机加载。

解决方法

CentOS / RHEL

Bash

1
2
3
4
5
6
7
8
9
10
# 安装服务
yum install -y iptables-services

# 保存当前规则
service iptables save
# 或
iptables-save > /etc/sysconfig/iptables

# 开机启用
systemctl enable iptables

Ubuntu / Debian

Bash

1
2
3
4
5
6
7
# 安装持久化工具
apt install iptables-persistent

# 保存规则
netfilter-persistent save
# 或
iptables-save > /etc/iptables/rules.v4

通用方法

Bash

1
2
3
4
5
# 备份
iptables-save > /root/iptables-$(date +%F).rules

# 恢复
iptables-restore < /root/iptables-xxxx.rules

预防建议

  • 每次重要变更后立刻备份
  • 把规则写成脚本,放入 /etc/rc.local 或 systemd service
  • 使用 Ansible / Salt 等配置管理工具强制收敛

本节故障速查表

故障首要检查点关键命令 / 操作
SSH 被锁死是否放行 22 + ESTABLISHED控制台执行 -I INPUT -p tcp –dport 22 -j ACCEPT
DNAT 不通ip_forward + FORWARD 链cat /proc/sys/net/ipv4/ip_forward
NAT 无回包回程路径 / SNAT / rp_filterconntrack -L -n + 抓包
UDP 异常UDP 超时 + 源地址一致性调大 nf_conntrack_udp_timeout
多 IP 出口错误是否强制 SNAT / 策略路由ip route get + SNAT 规则
Docker 干扰Docker 是否管理 iptablesdaemon.json 中 “iptables”: false
firewalld 冲突firewalld 是否在运行systemctl stop firewalld
重启丢规则是否保存并设置开机加载iptables-save + enable 服务

排障总原则
先恢复业务(临时放行)→ 再保留现场(计数器、日志、conntrack)→ 最后系统分析原因并固化修复。

把这些案例记熟,遇到问题时就能快速对号入座,少走大量弯路。

18. iptables 与 firewalld

在 CentOS 7/8、RHEL、Fedora 等系统上,很多同学一边用 iptables 命令,一边又看到系统里跑着 firewalld,经常分不清谁才是真正在生效的规则。
本节把两者的关系、底层实现和常见困惑一次说清楚。

18.1. firewalld 是什么

firewalld 是 Red Hat 系发行版默认的动态防火墙管理工具。

它的主要特点:

  • 支持动态更新规则,不需要重启整个防火墙
  • 引入了 Zone(区域) 的概念,按网络信任级别管理规则
  • 提供 D-Bus 接口,方便图形工具和其它程序调用
  • 底层可以对接不同的包过滤后端(iptables 或 nftables)

常用命令示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看状态
firewall-cmd --state

# 查看当前区域
firewall-cmd --get-active-zones

# 开放端口(运行时生效)
firewall-cmd --add-port=80/tcp

# 永久生效
firewall-cmd --add-port=80/tcp --permanent
firewall-cmd --reload

# 查看所有规则
firewall-cmd --list-all

18.2. firewalld 与 iptables 的关系

两者不是完全对立的关系,而是管理层与执行层的关系:

层级工具职责
用户/管理层firewalld提供更友好的接口、区域、动态规则管理
执行层iptables / nftables真正在内核中生效的规则

简单理解

  • 你用 firewall-cmd 添加的规则,最终还是会被转换成底层的 iptables 或 nftables 规则。
  • 直接用 iptables 命令写的规则,firewalld 不一定知道,重启或 reload 后可能被覆盖。

冲突的典型表现

  • 手动 iptables -A 写的规则,执行 firewall-cmd –reload 后消失
  • 两边规则同时存在,优先级混乱,导致行为不符合预期

官方建议

  • 如果使用 firewalld,就尽量只用 firewall-cmd 管理,不要混用 iptables 命令
  • 如果坚持使用传统 iptables,就停用 firewalld

18.3. firewalld backend

firewalld 本身不直接操作内核,它通过 backend 把规则交给底层框架执行。

目前主要有两种后端:

  1. iptables 后端(旧)
  2. nftables 后端(新,默认)

查看当前使用的后端:

Bash

1
2
3
firewall-cmd --get-backend
# 或
cat /etc/firewalld/firewalld.conf | grep FirewallBackend

常见配置文件位置:/etc/firewalld/firewalld.conf

Bash

1
2
3
4
5
# 旧方式(使用 iptables)
FirewallBackend=iptables

# 新方式(使用 nftables,CentOS 8 / RHEL 8 后默认)
FirewallBackend=nftables

切换后端后需要重启 firewalld:

Bash

1
systemctl restart firewalld

18.4. iptables-nft

从 RHEL 8 / CentOS 8 开始,系统默认的 iptables 命令其实是 iptables-nft

它是一个兼容层:

  • 用户依然可以使用熟悉的 iptables 语法
  • 底层实际调用的是 nftables
  • 规则最终存在于 nftables 框架中,而不是传统的 xtables

查看当前 iptables 的实现:

Bash

1
2
3
4
iptables -V
# 输出示例:
# iptables v1.8.4 (nf_tables) ← 这是 iptables-nft
# iptables v1.8.4 (legacy) ← 这是传统 iptables-legacy

切换方式(以 CentOS/RHEL 为例):

Bash

1
2
3
4
5
6
7
# 切换到 legacy
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

# 切换回 nft
update-alternatives --set iptables /usr/sbin/iptables-nft
update-alternatives --set ip6tables /usr/sbin/ip6tables-nft

18.5. 为什么看到 iptables 规则但实际使用 nftables

这是现代系统中最容易让人困惑的一点。

原因解释

  1. 系统默认安装的是 iptables-nft(命令叫 iptables,后端是 nftables)
  2. 你执行 iptables -L 时,看到的是兼容层翻译出来的“伪 iptables 规则”
  3. 真正在内核中生效的是 nftables 规则
  4. firewalld 默认也使用 nftables 后端

如何确认真实后端

Bash

1
2
3
4
5
6
7
8
9
10
11
# 1. 查看 iptables 版本信息
iptables -V

# 2. 直接查看 nftables 规则
nft list ruleset

# 3. 查看 firewalld 后端
firewall-cmd --get-backend

# 4. 查看内核模块
lsmod | grep -E 'nf_tables|ip_tables'

对比

命令实际查看的内容
iptables -L兼容层翻译后的规则(可能不完整)
nft list ruleset真正在内核中生效的 nftables 规则
iptables-legacy -L传统 xtables 规则(如果还在用)

实战建议

  1. 新系统(CentOS 8+ / Fedora 等)
    优先学习并使用 nftables 或继续用 firewalld,不要强行回到 legacy iptables。
  2. 必须使用 iptables 语法时
    明确自己用的是 iptables-nft 还是 iptables-legacy,避免混用。
  3. 排查规则是否生效
    不要只看 iptables -L,一定要结合 nft list ruleset 和实际流量测试。
  4. 与 firewalld 共存时
    • 要么完全交给 firewalld 管理
    • 要么停用 firewalld,自己用 iptables/nftables 管理
    • 不要两边同时写规则

一句话建议
在现代 Linux 发行版上,把 iptables 当作“兼容命令”来看待,真正理解底层是 nftables 还是 legacy,才能避免“规则写了却不生效”的困惑。

19. iptables 与 nftables

从 RHEL 8 / CentOS 8 / Debian 10 / Ubuntu 20.04 开始,Linux 发行版逐渐把默认的包过滤框架从传统的 iptables(xtables)转向了 nftables
理解两者的关系和迁移方式,是现代 Linux 网络管理的必修课。

19.1. 为什么 Linux 转向 nftables

传统 iptables(xtables)存在几个长期痛点:

  1. 多表多链架构复杂
    filter、nat、mangle、raw、security 五张表,规则分散,管理成本高。

  2. 性能瓶颈
    规则匹配是线性的,规则数量增多后性能下降明显;每次更新规则都需要重建整个规则集。

  3. 功能扩展困难
    新功能往往需要添加新的匹配模块或目标,内核和用户空间都要改。

  4. 语法碎片化
    IPv4 用 iptables,IPv6 用 ip6tables,ARP 用 arptables,ebtables 又是另一套,命令不统一。

nftables 的优势

  • 统一框架:IPv4、IPv6、ARP、bridge 等使用同一套工具和语法
  • 规则编译成类似字节码,匹配性能更好
  • 支持真正的原子性更新(一次提交全部生效)
  • 语法更接近高级语言,支持变量、集合、映射等
  • 内核接口更简洁,用户空间工具(nft)功能更强

因此,内核社区和各大发行版把 nftables 作为长期方向。

19.2. iptables-legacy

iptables-legacy 是传统的 iptables 实现,底层直接操作 xtables / ip_tables 等内核模块。

特点:

  • 规则存储在传统的 iptables 框架中
  • 与老系统、老脚本完全兼容
  • 性能和功能已基本停滞
  • 在新发行版中仍可通过 iptables-legacy 命令使用

查看是否在使用 legacy:

1
2
3
4
5
iptables-legacy -V
# 或
iptables -V
# 如果显示 (legacy),就是传统实现

19.3. iptables-nft

iptables-nft 是一个兼容层:

  • 用户继续使用熟悉的 iptables 命令和语法
  • 底层把规则转换成 nftables 规则
  • 真正在内核中生效的是 nftables

查看方式:

Bash

1
2
iptables -V
# 输出示例:iptables v1.8.9 (nf_tables) ← 这就是 iptables-nft

重要提醒

  • iptables -L 看到的是兼容层“翻译”后的视图,不一定完整反映真实 nft 规则
  • 真正权威的规则查看方式是 nft list ruleset
  • 不要把 iptables-legacy 和 iptables-nft 的规则混用,容易出乱

19.4. nftables 的基本概念

nftables 使用了一套新的术语和结构:

iptables 概念nftables 对应概念说明
tabletable表(按 family 分类:ip、ip6、inet 等)
chainchain
rulerule规则
match + targetexpr + verdict表达式 + 判决
-A / -I / -Dadd / insert / delete操作命令

基础命令示例

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 列出所有规则
nft list ruleset

# 创建表
nft add table inet filter

# 创建链
nft add chain inet filter input { type filter hook input priority 0 \; }

# 添加规则
nft add rule inet filter input tcp dport 22 accept

# 删除规则(需要 handle 号)
nft -a list chain inet filter input
nft delete rule inet filter input handle 3

常用 family

  • ip:仅 IPv4
  • ip6:仅 IPv6
  • inet:同时支持 IPv4 + IPv6(推荐)
  • arp、bridge、netdev 等

19.5. iptables 规则如何迁移

迁移主要有三种方式:

方式一:继续使用 iptables-nft(最平滑)
不改脚本,只是底层换成 nftables。适合暂时不想大改的场景。

方式二:使用翻译工具把规则转换成 nft 语法(推荐学习)
见下一节 iptables-translate。

方式三:彻底重写为原生 nftables 规则
长期维护成本最低,也能充分发挥 nftables 的优势(集合、映射、原子更新等)。

迁移前必做

Bash

1
2
3
# 备份现有规则
iptables-save > iptables-backup-$(date +%F).rules
nft list ruleset > nft-backup-$(date +%F).nft

19.6. iptables-translate

iptables-translate 是官方提供的翻译工具,能把 iptables 命令转换成对应的 nft 语法。

Bash

1
2
3
4
5
6
7
8
9
10
# 翻译单条命令
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# 输出:
# nft add rule ip filter INPUT tcp dport 22 counter accept

# 翻译整个规则集
iptables-save | iptables-restore-translate > rules.nft

# 然后加载
nft -f rules.nft

注意

  • 翻译结果通常可用,但复杂规则(尤其是多模块组合、自定义链)可能需要人工调整
  • 翻译后建议用 nft list ruleset 检查,并做流量测试
  • 部分旧模块在 nftables 中已有更好的替代写法(如用 set 替代 ipset 的某些用法)

19.7. iptables 与 nftables 对照

功能 / 操作iptables 写法nftables 写法(inet family 示例)
允许 SSHiptables -A INPUT -p tcp –dport 22 -j ACCEPTnft add rule inet filter input tcp dport 22 accept
允许已建立连接-m conntrack –ctstate ESTABLISHED,RELATED -j ACCEPTct state established,related accept
丢弃非法包-m conntrack –ctstate INVALID -j DROPct state invalid drop
DNAT-t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to-destination 192.168.1.100:8080add rule inet nat prerouting tcp dport 80 dnat to 192.168.1.100:8080
MASQUERADE-t nat -A POSTROUTING -o eth0 -j MASQUERADEadd rule inet nat postrouting oifname “eth0” masquerade
限制来源 IP-s 192.168.1.0/24ip saddr 192.168.1.0/24
计数器默认自带,-v 查看需显式加 counter
查看规则iptables -L -n -vnft list ruleset
保存规则iptables-save > filenft list ruleset > file
恢复规则iptables-restore < filenft -f file

更现代的 nftables 写法优势示例(使用集合):

Bash

1
2
3
4
5
6
# 定义端口集合
nft add set inet filter allowed_ports { type inet_service \; }
nft add element inet filter allowed_ports { 22, 80, 443 }

# 一条规则匹配多个端口
nft add rule inet filter input tcp dport @allowed_ports accept

这在 iptables 中需要 multiport 或多条规则才能实现。


本节核心总结

  1. nftables 是未来,性能更好、语法更统一、功能更强。
  2. iptables-nft 是兼容层,让老脚本能在新系统上继续跑,但真正生效的是 nft。
  3. iptables-legacy 是传统实现,新系统中已逐渐边缘化。
  4. 迁移路径:先用 iptables-translate 翻译 → 人工优化 → 最终转向原生 nft 语法。
  5. 排障时, nft list ruleset 为准,不要只看 iptables -L。

实战建议

  • 新项目直接学习并使用 nftables。
  • 老脚本可先跑在 iptables-nft 上,再逐步翻译迁移。
  • 生产环境变更前务必备份,并用测试流量验证。

掌握本节内容后,你就能在 iptables 与 nftables 并存的过渡期从容应对,不再被“规则写了却不生效”的问题困扰。

20. 实战:构建 Linux 防火墙

前面所有章节的最终目的,都是为了能在真实场景中快速、安全地写出可用的防火墙规则。
本章给出七个最常见的生产场景完整模板,可直接复制后根据实际环境微调。

通用安全提醒

  1. 修改规则前请确保有控制台 / VNC / IPMI 等备用访问手段。
  2. 先放行 SSH,再设置默认 DROP。
  3. 每次变更后立刻备份:iptables-save > /root/iptables-$(date +%F-%H%M).rules

20.1. 基础服务器防火墙

适用于普通应用服务器(只暴露必要端口)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
# 基础服务器防火墙模板

# 清空规则
iptables -F
iptables -X
iptables -t nat -F
iptables -t mangle -F

# 默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 1. 放行回环
iptables -A INPUT -i lo -j ACCEPT

# 2. 放行已建立和相关连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 3. 丢弃非法包
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 4. 放行 SSH(请务必改成你的管理网段)
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# 5. 放行 ping(可选,生产可关闭)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 5 -j ACCEPT

# 6. 记录并丢弃其余包(可选)
iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "INPUT_DROP: "
iptables -A INPUT -j DROP

# 保存(CentOS/RHEL)
# service iptables save

20.2. Web 服务器

在基础防火墙之上,开放 HTTP/HTTPS,并做简单的连接限制。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
# Web 服务器防火墙模板

iptables -F
iptables -X
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# SSH(限制来源)
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT

# HTTP / HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j ACCEPT

# 限制单 IP 并发连接数(防简单 CC)
iptables -A INPUT -p tcp -m multiport --dports 80,443 -m connlimit --connlimit-above 50 --connlimit-mask 32 -j DROP

# 限制新建连接频率(可选)
iptables -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW \
-m hashlimit --hashlimit-name web --hashlimit-above 30/min --hashlimit-mode srcip --hashlimit-burst 20 -j DROP

# ping
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT

iptables -A INPUT -j DROP

20.3. NAT 网关

经典共享上网 + 端口转发场景。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
# NAT 网关模板
# eth0 = 公网,eth1 = 内网

# 开启转发
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# ===== INPUT(网关自身保护)=====
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -i eth1 -j ACCEPT # 允许内网访问网关
iptables -A INPUT -j DROP

# ===== FORWARD =====
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate INVALID -j DROP
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT # 内网出网
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ===== NAT =====
# 内网共享上网
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 端口转发示例:公网 80 → 内网 192.168.1.100:8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 192.168.1.100:8080
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 8080 -j ACCEPT

20.4. Relay Server

中转服务器(只做流量转发,自身不提供业务)。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
# Relay 服务器模板

echo 1 > /proc/sys/net/ipv4/ip_forward

iptables -F
iptables -t nat -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 管理端口
iptables -A INPUT -p tcp --dport 22 -s 你的管理IP -j ACCEPT

# 放行中转端口(示例)
iptables -A INPUT -p tcp --dport 10000:11000 -j ACCEPT
iptables -A INPUT -p udp --dport 10000:11000 -j ACCEPT

# 转发
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -p tcp --dport 10000:11000 -j ACCEPT
iptables -A FORWARD -p udp --dport 10000:11000 -j ACCEPT

# 如果需要做 SNAT(多 IP 或隐藏源)
# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

20.5. 多公网 IP 服务器

服务器拥有多个公网 IP,需要控制不同业务使用不同出口。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# 多公网 IP 服务器模板
# 假设 eth0 有 203.0.113.10 和 203.0.113.11

iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -p tcp --dport 22 -s 管理网段 -j ACCEPT
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -j DROP

# 不同内网网段使用不同公网 IP 出去
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o eth0 -j SNAT --to-source 203.0.113.11

# 本机服务绑定不同 IP(示例)
# 监听 203.0.113.10:80 的服务已经通过应用层绑定,防火墙只需放行即可

20.6. GOST UDP Relay

GOST / 类似 UDP 中转常见场景(需要特别注意 UDP 超时和源地址)。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
# GOST UDP Relay 模板

echo 1 > /proc/sys/net/ipv4/ip_forward

# 增大 UDP 超时(重要)
echo 120 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout
echo 180 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream

iptables -F
iptables -t nat -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# SSH
iptables -A INPUT -p tcp --dport 22 -s 管理IP -j ACCEPT

# GOST 监听端口(示例)
iptables -A INPUT -p udp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# 转发
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -p udp -j ACCEPT
iptables -A FORWARD -p tcp -j ACCEPT

# 强制固定源地址(多 IP 或稳定回程的关键)
iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 你的固定公网IP

# 如果使用策略路由,请同步配置 ip rule 和 ip route

20.7. 策略路由 + 防火墙

综合示例:不同来源走不同出口,并配合防火墙标记。

Bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/bash
# 策略路由 + 防火墙综合模板

# 假设:
# eth0 出口1,网关 203.0.113.1,公网 IP 203.0.113.10
# eth1 出口2,网关 198.51.100.1,公网 IP 198.51.100.10

echo 1 > /proc/sys/net/ipv4/ip_forward

# ===== 防火墙标记 =====
iptables -t mangle -F
iptables -t mangle -A PREROUTING -s 192.168.1.0/24 -j MARK --set-mark 100
iptables -t mangle -A PREROUTING -s 192.168.2.0/24 -j MARK --set-mark 200

# ===== 策略路由 =====
ip rule del fwmark 100 table 100 2>/dev/null
ip rule del fwmark 200 table 200 2>/dev/null
ip rule add fwmark 100 table 100
ip rule add fwmark 200 table 200

ip route flush table 100
ip route flush table 200
ip route add default via 203.0.113.1 dev eth0 table 100
ip route add default via 198.51.100.1 dev eth1 table 200

# ===== NAT =====
iptables -t nat -F
iptables -t nat -A POSTROUTING -m mark --mark 100 -j SNAT --to-source 203.0.113.10
iptables -t nat -A POSTROUTING -m mark --mark 200 -j SNAT --to-source 198.51.100.10

# ===== 常规过滤 =====
iptables -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -p tcp --dport 22 -s 管理网段 -j ACCEPT
iptables -A INPUT -j DROP

iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -s 192.168.2.0/24 -j ACCEPT
iptables -A FORWARD -j DROP

ip route flush cache

实战总结与建议

  1. 永远先保 SSH,再收紧策略

  2. 所有模板都建议改成脚本,并用 git 管理。

  3. 变更后执行:

    Bash

    1
    iptables-save > /root/iptables-latest.rules
  4. 高并发或 UDP 场景,记得检查并调大 nf_conntrack_max 和 UDP 超时。

  5. 与 Docker / Kubernetes 共存时,优先考虑关闭它们的自动 iptables 管理,改为自己完全控制。

  6. 新系统推荐逐步转向 nftables,但以上模板用 iptables-nft 也能正常工作。

把这些模板当作起点,根据实际业务端口、网段、出口情况微调,就能快速构建出稳定、可维护的 Linux 防火墙。

21. 安全实践与最佳实践

写得出规则只是第一步,能长期稳定、安全地运行才是真正的目标。
以下是经过大量生产验证的 iptables 安全与运维最佳实践。

21.1. 默认 DROP

原则:默认拒绝,只允许明确需要的流量。

1
2
3
4
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT # 出站通常保持 ACCEPT,除非有严格管控需求

为什么推荐

  • 即使规则写漏,未知流量也会被丢弃
  • 大幅减少攻击面
  • 符合「最小权限」思想

注意:设置默认 DROP 之前,必须先放行 SSH 和已建立连接,否则容易把自己锁在外面。

21.2. SSH 防锁死

SSH 是运维生命线,必须重点保护。

推荐做法

  1. 先放行已建立连接

    Bash

    1
    iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  2. 再放行 SSH(最好限制来源)

    Bash

    1
    iptables -A INPUT -p tcp --dport 22 -s 你的办公网段或跳板机IP -j ACCEPT
  3. 最后才设置默认 DROP

  4. 额外防护(可选但强烈推荐):

    Bash

    1
    2
    3
    # 限制单 IP 尝试频率
    iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
    -m hashlimit --hashlimit-name ssh --hashlimit-above 5/min --hashlimit-mode srcip -j DROP
  5. 变更前保留备用通道:控制台、VNC、IPMI、云厂商 web 终端等。

  6. 使用带超时回滚的脚本(示例思路):

    • 应用新规则
    • 睡眠 30-60 秒
    • 如果 SSH 仍可达则保存,否则自动回滚

21.3. 规则顺序

iptables 规则是从上到下依次匹配,一旦命中终止动作(ACCEPT/DROP/REJECT)就会停止。

推荐顺序

Bash

1
2
3
4
5
6
7
8
1. 放行 lo(回环)
2. 放行 ESTABLISHED,RELATED
3. 丢弃 INVALID
4. 放行管理端口(SSH 等,限制来源)
5. 放行业务端口
6. 频率限制 / 连接数限制
7. 日志记录(限速)
8. 默认 DROP

原则

  • 越精准、命中率越高的规则越靠前
  • 允许规则放在拒绝规则之前
  • 日志规则放在最终 DROP 之前

21.4. 最小权限

只开放真正需要的端口和来源。

  • 能限制来源 IP/网段就限制
  • 能使用 ipset 管理大量 IP 就使用
  • 数据库、Redis、内网服务等绝不要直接对公网开放
  • 能用内网或 VPN 访问的,就不要暴露到公网

Bash

1
2
3
# 好的例子:只允许内网访问 MySQL
iptables -A INPUT -p tcp --dport 3306 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

21.5. 日志控制

日志很有用,但失控的日志会拖垮系统。

最佳实践

Bash

1
2
3
4
5
# 限速记录
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "DROP: "

# 只记录特定关注的流量
iptables -A INPUT -p tcp --dport 22 -m limit --limit 3/min -j LOG --log-prefix "SSH: "
  • 生产环境避免对高流量端口做全量 LOG
  • 定期清理或轮转日志
  • 重要安全事件可对接 rsyslog 或外部日志系统

21.6. 规则备份

规则只存在于内存,重启即丢失,必须备份。

推荐做法

Bash

1
2
3
4
5
6
7
8
# 每次变更后立刻备份
iptables-save > /root/iptables-$(date +%F-%H%M).rules

# 保留最近多份
# 配合定时任务每天备份

# 恢复
iptables-restore < /root/iptables-xxxx.rules

CentOS/RHEL

Bash

1
2
service iptables save
systemctl enable iptables

Ubuntu/Debian

Bash

1
2
apt install iptables-persistent
netfilter-persistent save

21.7. 自动化管理

手动敲命令容易出错,也不利于审计和回滚。

推荐方式

  1. 把规则写成脚本,放入版本库(git)
  2. 使用 Ansible、SaltStack、Puppet 等配置管理工具强制收敛
  3. 变更走 Code Review
  4. 重要环境使用「先应用 → 观察 → 再持久化」的流程
  5. 新系统可逐步转向 nftables,并用同样方式管理

21.8. 不要混用多个防火墙管理器

这是最常见的混乱来源之一。

禁止同时使用

  • firewalld + 手动 iptables
  • ufw + 手动 iptables
  • Docker 默认 iptables 管理 + 手动大量自定义规则(需特别小心)
  • iptables-legacy + iptables-nft 混用

正确做法

  • 选定一个管理方式后坚持到底
  • 如果用 firewalld,就只用 firewall-cmd
  • 如果用传统 iptables,就停用 firewalld/ufw
  • 与 Docker/K8s 共存时,优先考虑关闭它们的自动规则管理,改为自己完全控制

22. 常用命令速查表

查看规则

Bash

1
2
3
4
iptables -L -n -v --line-numbers          # 查看 filter 表(带计数和编号)
iptables -t nat -L -n -v --line-numbers # 查看 nat 表
iptables -t mangle -L -n -v # 查看 mangle 表
iptables -S # 以命令形式查看

增删改

Bash

1
2
3
4
5
6
7
iptables -A INPUT ...                     # 追加
iptables -I INPUT 1 ... # 插入到第 1 条
iptables -D INPUT 3 # 删除第 3 条
iptables -R INPUT 2 ... # 替换第 2 条
iptables -F # 清空所有规则
iptables -X # 删除自定义链
iptables -Z # 计数器清零

默认策略

Bash

1
2
3
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

保存与恢复

Bash

1
2
iptables-save > rules.v4
iptables-restore < rules.v4

连接跟踪

Bash

1
2
3
4
5
6
conntrack -C                              # 当前连接数
conntrack -L # 查看条目
conntrack -L -p udp
conntrack -D -s 1.2.3.4 # 删除指定连接
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

ipset

Bash

1
2
3
4
ipset create whitelist hash:ip
ipset add whitelist 192.168.1.100
ipset list
iptables -A INPUT -m set --match-set whitelist src -j ACCEPT

策略路由

Bash

1
2
3
4
5
ip rule show
ip route show table 100
ip rule add fwmark 100 table 100
ip route add default via x.x.x.x table 100
ip route flush cache

日志与排障

Bash

1
2
3
4
dmesg -w | grep --line-buffered "PREFIX"
tcpdump -i any port 22 -nn
ss -tlnp
iptables -L -n -v | grep -E "DROP|REJECT"

常用模块快捷参考

Bash

1
2
3
4
5
6
7
-m conntrack --ctstate NEW,ESTABLISHED,RELATED,INVALID
-m multiport --dports 80,443
-m limit --limit 5/min
-m hashlimit --hashlimit-name name --hashlimit-above 10/min --hashlimit-mode srcip
-m connlimit --connlimit-above 20
-m set --match-set name src
-m recent --name SSH --set/--update --seconds 60 --hitcount 5

开关转发与关键参数

Bash

1
2
3
echo 1 > /proc/sys/net/ipv4/ip_forward
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max
echo 120 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout

全文总结一句话
默认拒绝、先放行已建立连接、SSH 永远优先、规则顺序清晰、及时备份、不要混用管理工具。

把这些实践固化到日常操作中,iptables 就能真正成为可靠、可控的安全工具,而不是让人提心吊胆的「定时炸弹」。

iptables 详解:从原理、规则到 NAT 与实战

https://pengtech.net/network/detailed-iptables-guide.html

作者

鹏叔

发布于

2026-09-19

更新于

2026-09-23

许可协议

评论