Linux 防火墙完全指南:从 iptables 到 firewalld

Linux 防火墙完全指南:从 iptables 到 firewalld

目录

  • [Linux 防火墙完全指南:从 iptables 到 firewalld](#Linux 防火墙完全指南:从 iptables 到 firewalld)
    • 一、为什么需要防火墙
    • [二、地基:netfilter 框架](#二、地基:netfilter 框架)
      • [2.1 先用生活类比理解"数据包的旅程"](#2.1 先用生活类比理解"数据包的旅程")
      • [2.2 表与链的关系](#2.2 表与链的关系)
    • [三、iptables 深度讲解](#三、iptables 深度讲解)
      • [3.0 先跑起来:你的第一条 iptables 规则](#3.0 先跑起来:你的第一条 iptables 规则)
      • [3.1 四张表与五条链](#3.1 四张表与五条链)
      • [3.2 表-链映射关系](#3.2 表-链映射关系)
      • [3.3 规则结构与命令语法](#3.3 规则结构与命令语法)
      • [3.4 匹配条件详解](#3.4 匹配条件详解)
        • [3.4.1 基本匹配(不需要 -m 加载模块)](#3.4.1 基本匹配(不需要 -m 加载模块))
        • [3.4.2 扩展匹配(需要 -m 加载模块)](#3.4.2 扩展匹配(需要 -m 加载模块))
        • [3.4.3 连接状态详解](#3.4.3 连接状态详解)
      • [3.5 常用 Target(动作)](#3.5 常用 Target(动作))
      • [3.6 实战示例(由浅入深)](#3.6 实战示例(由浅入深))
        • [示例 1:最简 Web 服务器](#示例 1:最简 Web 服务器)
        • [示例 2:防 SSH 暴力破解](#示例 2:防 SSH 暴力破解)
        • [示例 3:端口转发(DNAT)](#示例 3:端口转发(DNAT))
        • [示例 4:内网共享上网(SNAT/MASQUERADE)](#示例 4:内网共享上网(SNAT/MASQUERADE))
      • [3.7 规则持久化](#3.7 规则持久化)
    • [四、firewalld 深度讲解](#四、firewalld 深度讲解)
      • [4.1 为什么需要 firewalld](#4.1 为什么需要 firewalld)
      • [4.2 架构概览:firewalld 怎么"坐在 iptables 上面"](#4.2 架构概览:firewalld 怎么"坐在 iptables 上面")
      • [4.3 核心概念:Zone(区域)](#4.3 核心概念:Zone(区域))
      • [4.4 核心概念:Service(服务)](#4.4 核心概念:Service(服务))
      • [4.5 Runtime vs Permanent](#4.5 Runtime vs Permanent)
      • [4.6 Rich Rules(富规则)](#4.6 Rich Rules(富规则))
      • [4.7 Direct Rules(直通规则)](#4.7 Direct Rules(直通规则))
      • [4.8 实战示例](#4.8 实战示例)
        • [示例 1:将 Web 服务器从零配置到安全上线](#示例 1:将 Web 服务器从零配置到安全上线)
        • [示例 2:工作笔记本------根据网络环境切换 zone](#示例 2:工作笔记本——根据网络环境切换 zone)
    • [五、firewalld vs iptables 全面对比](#五、firewalld vs iptables 全面对比)
    • 六、实战场景
      • [6.1 Web 服务器防火墙](#6.1 Web 服务器防火墙)
      • [6.2 NAT 网关(iptables)](#6.2 NAT 网关(iptables))
      • [6.3 Docker 与防火墙的兼容问题](#6.3 Docker 与防火墙的兼容问题)
    • 七、进阶话题
      • [7.1 调试技巧](#7.1 调试技巧)
      • [7.2 fail2ban 联动](#7.2 fail2ban 联动)
    • 八、常见问题排查
      • [问题 1:加了规则为什么还是不通?](#问题 1:加了规则为什么还是不通?)
      • [问题 2:firewalld 和 iptables 服务冲突](#问题 2:firewalld 和 iptables 服务冲突)
      • [问题 3:iptables 规则重启后丢失](#问题 3:iptables 规则重启后丢失)
      • [问题 4:firewalld reload 后 SSH 断了](#问题 4:firewalld reload 后 SSH 断了)
    • 九、总结与最佳实践

理解 netfilter 内核框架、掌握 iptables 命令与四表五链、 firewalld 的 zone/service/rich-rule 机制。

一、为什么需要防火墙

安全不是"装上去"的,是"配出来"的

Linux 系统安全有三道基础防线:

  1. 身份认证(PAM、SSH Key、sudo)------ 谁能进来
  2. 访问控制(文件权限、SELinux/AppArmor)------ 进来后能碰什么
  3. 网络防火墙(netfilter/iptables/firewalld)------ 谁能从网络访问什么端口

防火墙位于第一道防线之前------它在网络数据包到达应用程序之前就做出放行或拒绝的决定。没有防火墙,你的服务器就像一栋没有围墙的房子,每扇窗户(端口)都可能被试探。

iptables 和 firewalld 的本质

一个重要认知:iptables 和 firewalld 都不是防火墙本身,它们只是管理工具。 真正的防火墙是 Linux 内核中的 netfilter 框架。

复制代码
用户空间(你敲命令的地方)
  ┌─────────────┐     ┌──────────────┐
  │  iptables   │     │  firewalld   │  ← 管理工具
  │  (传统 CLI)  │     │  (动态守护进程) │
  └──────┬──────┘     └──────┬───────┘
         │                   │
         ▼                   ▼
    ┌─────────────────────────────┐
    │         netfilter            │  ← 内核态包过滤框架
    │   (hooks in kernel stack)    │
    └─────────────────────────────┘

一句话总结:无论你用 iptables 命令行还是 firewall-cmd 图形界面,最终都是往 netfilter 里写入过滤规则。


二、地基:netfilter 框架

理解 netfilter 是整个防火墙知识体系的地基。跳过这块直接背命令,大概率会出问题还不知道原因。

2.1 先用生活类比理解"数据包的旅程"

在接触任何命令之前,先建立一个画面感。想象你是一份快递,要从 A 城送到 B 城某栋楼里的一个人手上:

  • 快递车进入大楼分拣区 → 这是 PREROUTING(刚到,还没决定给谁)
  • 分拣员看了一眼地址:"哦,这栋楼 3 楼的" → 这是路由决策(判断是不是自己人)
  • 如果是给本楼的人,快递上 3 楼交给收件人 → 这是 INPUT
  • 如果是寄给隔壁楼的,从后门转发出去 → 这是 FORWARD
  • 楼里的人自己寄出快递,从 3 楼拿下楼 → 这是 OUTPUT
  • 快递最终装车离开大楼 → 这是 POSTROUTING

防火墙规则就相当于在分拣区、楼梯口、后门这些位置安排了安检员。你写 iptables 规则,就是在告诉每个位置的安检员:"看到来自 XX 的放行,其他一律拦下。"

有了这个画面,我们再来看技术细节。

netfilter 在 Linux 内核网络协议栈中注册了 5 个钩子点------你可以把它们理解为快递大楼里的 5 个安检关卡,每个经过的网络数据包都会依次经过这些位置:

复制代码
                     ┌──────────────┐
                     │   进入本机    │
                     │  (Local In)  │
                     └──────▲───────┘
                            │
                    ┌───────┴────────┐
                    │  INPUT 链      │  ← 钩子点3:发往本地进程
                    └───────┬────────┘
                            │
              ┌─────────────┴──────────────┐
              │       路由决策(第二次)     │
              │    "这个包是给我的吗?"      │
              └─────────────┬──────────────┘
                            │
  数据包到达网卡             │
       │                    │
       ▼                    │
┌──────────────┐    ┌───────┴────────┐    ┌──────────────┐
│ PREROUTING   │    │  FORWARD 链    │    │  OUTPUT 链   │
│ 链            │───▶│  (转发)         │───▶│  (本地发出)   │
│ 钩子点1       │    │  钩子点4        │    │  钩子点5      │
└──────────────┘    └───────┬────────┘    └──────▲───────┘
                            │                    │
                   ┌────────┴────────┐  ┌────────┴────────┐
                   │  POSTROUTING    │  │   本地进程       │
                   │  链              │  │   发出数据包      │
                   │  钩子点2         │  └─────────────────┘
                   └────────┬────────┘
                            │
                            ▼
                     ┌──────────────┐
                     │   从网卡出去  │
                     │  (Local Out) │
                     └──────────────┘

五个钩子点的职责:

钩子点 快递类比 触发时机 你什么时候用
PREROUTING 快递刚进分拣区 包刚到达,路由前 做端口映射(DNAT)
INPUT 快递上楼梯交给收件人 确定是发给本机的 过滤进站流量(最常用)
FORWARD 快递从中转通道去隔壁楼 确定是转发给他人的 路由器/NAT 网关
OUTPUT 楼里的人下楼寄快递 本机发出数据包 限制本机出站
POSTROUTING 快递装车准备离开 包即将离开网卡 内网上网(SNAT/MASQUERADE)

2.2 表与链的关系

这里有一个新手特别容易迷糊的点:"表"和"链"是什么关系?

  • 链(Chain):对应一个安检关卡,是排好顺序的"规则列表"。数据包到了这个关卡,就从头到尾逐条匹配。
  • 表(Table):按"工种"对规则分组。同一种工种(比如 NAT 工种)可以在不同的关卡(PREROUTING、OUTPUT、POSTROUTING)干活。

一句话记忆:链 = 在哪查;表 = 查什么类型的事。比如"在大楼入口(PREROUTING 链)做地址转换(nat 表)",或者"在楼道口(INPUT 链)做放行/拦截(filter 表)"。


三、iptables 深度讲解

iptables 是 netfilter 最经典的用户态管理工具,1998 年诞生,至今仍是 Linux 防火墙的基石。

3.0 先跑起来:你的第一条 iptables 规则

在深入理论之前,先动手感受一下。在你的测试机器上执行:

bash 复制代码
# 看一眼当前有没有防火墙规则
iptables -L -n

# 加一条最简单的规则:允许所有人访问本机的 80 端口(HTTP)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# 再看一眼,刚才的规则已经在列表里了
iptables -L -n

逐词拆解这条命令(这是你理解 iptables 语法的起点):

片段 大白话含义
iptables "我要写防火墙规则"
-A INPUT "追加到 INPUT 链"(INPUT 链 = 检查所有发给我自己的流量)
-p tcp "只针对 TCP 协议"
--dport 80 "目标端口是 80"(也就是 HTTP 服务的端口)
-j ACCEPT "匹配了就放行"(j 是 jump 的缩写,跳转到 ACCEPT 动作)

整条命令翻译成人话:"在检查进站流量的规则列表末尾,追加一条规则------凡是 TCP 协议、目标是 80 端口的包,通通放行。"

感受完这一条后,下面我们系统学习背后的理论。放心,你不需要一次记住所有表名和链名------先有一个印象,实践中反复查、反复用,自然就熟了。

3.1 四张表与五条链

四张表(按优先级从高到低):

表名 功能 一句话大白话 你什么时候需要用到
raw 连接追踪豁免 "这个包别跟踪了,直接过" 基本用不到,高性能场景才考虑
mangle 数据包修改 "给这个包做点手脚" 极少用,改了包头的 TTL/MARK 等字段
nat 网络地址转换 "把包的地址换一下" 做端口映射、内网上网时用
filter 包过滤 "这个包放行还是拦下?" 这就是你 90% 时间在操作的表

新手先记住-t filter 是默认值,所以你日常写的 iptables 命令 90% 都是在操作 filter 表。nat 表偶尔用(端口转发、NAT 网关场景)。raw 和 mangle 你大概率永远不会直接碰。

五条内置链PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING

3.2 表-链映射关系

并非每张表都能在每条链上注册规则------想想也对,你不可能让"NAT 换地址"这件事发生在数据包已经进了本机之后。实际映射如下(只看 filter 和 nat 就够了,其他不用记):

表 ↓ / 链 → PREROUTING INPUT FORWARD OUTPUT POSTROUTING
raw
mangle
nat
filter

记忆技巧:filter 表只管"进出本机的"和"经本机转发的",不管路由前后的地址转换。nat 表恰好相反,只管地址转换发生的点(PREROUTING 做 DNAT、POSTROUTING/OUTPUT 做 SNAT)。

3.3 规则结构与命令语法

一条 iptables 规则的结构:

复制代码
iptables [-t 表名] <操作> <链名> <匹配条件> -j <动作>

常用操作:

操作 缩写 含义
--append -A 在链末尾追加规则
--insert -I 在链开头(或指定位置)插入规则
--delete -D 删除指定规则
--replace -R 替换指定位置的规则
--flush -F 清空链中所有规则
--list -L 列出链中规则
--policy -P 设置链的默认策略

基本命令格式:

bash 复制代码
# 查看规则(推荐加 -v -n 看详细信息,不加 -n 会反向 DNS 查询,很慢)
iptables -L -n -v --line-numbers

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

# 添加规则到 filter 表的 INPUT 链(-t filter 可省略,默认就是 filter)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 指定位置插入(第 3 条之前)
iptables -I INPUT 3 -p tcp --dport 80 -j ACCEPT

# 按编号删除(先用 --line-numbers 查看编号)
iptables -D INPUT 3

# 按内容删除
iptables -D INPUT -p tcp --dport 80 -j ACCEPT

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

3.4 匹配条件详解

iptables 的匹配条件分为基本匹配扩展匹配两类。

3.4.1 基本匹配(不需要 -m 加载模块)
bash 复制代码
-p tcp|udp|icmp       # 协议
-s 192.168.1.0/24     # 源地址
-d 10.0.0.1           # 目标地址
-i eth0               # 入站网卡(用于 INPUT/PREROUTING)
-o eth1               # 出站网卡(用于 OUTPUT/POSTROUTING)
3.4.2 扩展匹配(需要 -m 加载模块)
bash 复制代码
# TCP/UDP 端口匹配
-p tcp -m tcp --dport 80          # 目标端口
-p tcp -m tcp --sport 1024:65535  # 源端口范围
-p tcp -m multiport --dports 80,443,8080  # 多端口

# 连接状态匹配(极其重要!)
-m conntrack --ctstate NEW,ESTABLISHED,RELATED
# 或者用传统写法(仍然有效)
-m state --state NEW,ESTABLISHED,RELATED

# IP 范围
-m iprange --src-range 192.168.1.100-192.168.1.200

# MAC 地址
-m mac --mac-source 00:11:22:33:44:55

# 限制速率(防暴力破解的关键模块)
-m limit --limit 5/minute --limit-burst 10

# 最近连接记录
-m recent --name ssh_attack --rcheck --seconds 60 --hitcount 5

# 注释(给自己看的备注)
-m comment --comment "Allow SSH from office"
3.4.3 连接状态详解

先看比喻 :打电话的过程------你拨号给朋友(发起连接),朋友接起来说"喂"(连接建立),你们开始聊(数据来往),最后挂断(连接结束)。netfilter 的 conntrack 模块就是"电话总机",记录每一通电话进行到哪一步了。

这是 iptables 中最重要的概念之一。理解了它,防火墙规则可以从"一条一条写"变成"两三条搞定"。

状态 含义 电话比喻 实际例子
NEW 新连接的第一个包 你刚拨出号码,对方还没接 客户端发起 TCP SYN
ESTABLISHED 已建立的连接 电话接通了,正在通话中 TCP 三次握手完成后的数据包
RELATED 与已有连接相关的 你叫朋友顺带传个话给旁边的人 FTP 数据通道、ICMP 差错报文
INVALID 无法识别的包 线路噪音,不知道谁打来的 损坏的包、状态不匹配的包

这个概念的实战价值:利用状态匹配,你的防火墙规则可以大幅简化:

bash 复制代码
# 就这两条,覆盖了 90% 的回包逻辑:
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT  # 放行所有"回话"
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP                # 丢弃垃圾包

第一条放行所有"已经建立的对话的后续回复"------比如你访问了一个网站,网站回复你的 HTML 页面就自动被放行。第二条丢弃来路不明的包。

从此你只需要为真正对外开放的服务(如 SSH 的 22 端口、HTTP 的 80 端口)添加 NEW 状态的放行规则,其他的回包和关联流量这两条都帮你搞定了。

3.5 常用 Target(动作)

Target 含义 说明
ACCEPT 放行 数据包通过,继续协议栈处理
DROP 静默丢弃 不给发送方任何回应,对方超时才知道
REJECT 拒绝并通知 丢弃同时回复 ICMP 错误(默认 icmp-port-unreachable
LOG 记录日志 不终止匹配,记录后继续匹配下一条规则
SNAT 源地址转换 修改源 IP(用于内网上网)
DNAT 目标地址转换 修改目标 IP(用于端口映射/负载均衡)
MASQUERADE 动态 SNAT 自动使用出口网卡 IP 做源地址(适用于动态 IP)
RETURN 返回 从当前链返回到调用链
REDIRECT 端口重定向 将包重定向到本机另一个端口(透明代理用)

DROP vs REJECT 的选择(用人话比喻):

  • REJECT = 门卫说"你不能进",你立刻知道被拒绝了
  • DROP = 门卫装没看见你,你站在门口等半天也不知道是被拒了还是门卫不在

实际选择:

  • 对外服务端口 :用 REJECT,让合法客户端快速知道端口不可达
  • 扫描防御/边界防护 :用 DROP,让攻击者搞不清是端口没开还是 IP 不存在
  • 内网环境 :优先 REJECT,方便排错

3.6 实战示例(由浅入深)

示例 1:最简 Web 服务器
bash 复制代码
#!/bin/bash
# 清空所有规则(安全起点)
iptables -F
iptables -X
iptables -t nat -F

# 默认策略:入站和转发 DROP,出站 ACCEPT
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

# 开放 SSH(22)和 HTTP/HTTPS(80/443)
iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -m conntrack --ctstate NEW -j ACCEPT

# 允许 ping(可选的)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
示例 2:防 SSH 暴力破解
bash 复制代码
# 60 秒内新连接超过 5 次就拉黑 300 秒
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent \
    --update --seconds 60 --hitcount 6 -j DROP
示例 3:端口转发(DNAT)
bash 复制代码
# 将访问本机 8080 端口的流量转发到内网 192.168.1.100:80
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80

# 关键:必须开启转发并允许 FORWARD 链
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -p tcp -d 192.168.1.100 --dport 80 -j ACCEPT
示例 4:内网共享上网(SNAT/MASQUERADE)
bash 复制代码
# 开启转发
sysctl -w net.ipv4.ip_forward=1

# 固定公网 IP 用 SNAT
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 203.0.113.10

# 动态公网 IP(如 PPPoE)用 MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

3.7 规则持久化

iptables 规则存储在内存中,重启丢失。持久化有多种方式:

方法一:iptables-save/restore(最通用)

bash 复制代码
# 保存当前规则
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

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

方法二:使用 netfilter-persistent 服务(Debian/Ubuntu)

bash 复制代码
apt install iptables-persistent
# 安装过程中会提示保存当前规则
# 之后手动更新:
netfilter-persistent save

方法三:使用 iptables-services(RHEL/CentOS)

bash 复制代码
yum install iptables-services
systemctl enable iptables
systemctl start iptables
# 规则保存到 /etc/sysconfig/iptables
service iptables save

四、firewalld 深度讲解

4.1 为什么需要 firewalld

iptables 有一个架构性痛点:每次修改规则都要刷新整条链。 这意味着:

  • 添加一条临时测试规则也很"重"
  • 修改规则时,极短的时间窗口内规则不完整
  • 没有"网络环境"的概念(你在咖啡厅和办公室需要用不同的规则)

firewalld 在 2011 年由 Red Hat 引入,从 RHEL 7 / CentOS 7 开始成为默认防火墙。

通俗理解 firewalld 做了什么 :如果把 iptables 比作直接操作机器指令,firewalld 就是给这套指令加了一个"图形界面"------它把 iptables 那些让人头晕的 -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT 封装成了你一看就懂的"放行 HTTP 服务"。

它的核心设计哲学是:

规则应该是动态的、可感知网络环境的、易于管理的。

4.2 架构概览:firewalld 怎么"坐在 iptables 上面"

复制代码
┌───────────────────────────────────────┐
│            firewall-config             │  ← GUI 工具(可选)
│            firewall-cmd                │  ← CLI 工具
│            firewall-offline-cmd        │  ← 离线配置工具
└────────────────┬──────────────────────┘
                 │ (D-Bus)
         ┌───────▼────────┐
         │  firewalld      │  ← 守护进程(持有内存中的规则)
         │  (daemon)       │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │   iptables      │  ← firewalld 底层仍通过 iptables 操作 netfilter
         │   (backend)     │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │   netfilter     │
         └────────────────┘

关键认知 :firewalld 底层还是调用 iptables,你可以用 iptables -L -n 看到 firewalld 生成的规则。firewalld 的价值在于它对规则进行了更高级的抽象和组织。

4.3 核心概念:Zone(区域)

Zone 是 firewalld 最核心的创新------它把"我在哪"和"放行什么"绑定在一起。

预定义 Zone 一览(按信任度从低到高):

一句话理解 zone:zone = 给网卡贴标签。一张网卡只能贴一个标签,标签决定了"默认有多信任连到这个网卡的流量"。

Zone 信任度 默认行为 你什么时候用
drop 0% 所有入站丢包,无任何回复 极致隐身,外面完全感觉不到这台机器的存在
block 10% 入站被拒,返回 ICMP 禁止 比 drop 温和,对方至少知道被拒了
public 20% 不信任,仅放行你指定的端口 ☕ 公共 WiFi、咖啡厅、云服务器默认选这个
external 30% NAT 模式,开启伪装 路由器外网口
dmz 40% 仅允许特定入站 隔离区的服务器(前后都有防火墙)
work 60% 放行多数内网服务 公司办公网
home 70% 放行多数内网服务 家里网络
internal 80% 放行更多内网服务 全是你自己机器的内部网络
trusted 100% 全部放行 绝对信任(⚠️ 慎用)

新手只需关注 public :云服务器 99% 的场景用 public 就够了。其他 zone 主要面向多网卡或频繁切换网络环境的笔记本用户。

zone 的核心行为由两个要素决定:

  1. target:对不匹配任何规则的包的最终处理

    • default:按 zone 的默认行为
    • ACCEPT:放行
    • DROP:静默丢弃
    • %%REJECT%%:拒绝并通知
  2. services/ports/rules:在 target 之前匹配的放行清单

bash 复制代码
# 查看所有 zone
firewall-cmd --get-zones

# 查看当前活动 zone(及绑定的网卡)
firewall-cmd --get-active-zones

# 查看某个 zone 的详细配置
firewall-cmd --zone=public --list-all

# 查看默认 zone
firewall-cmd --get-default-zone

# 修改默认 zone
firewall-cmd --set-default-zone=home

# 将网卡绑定到 zone(网卡只能属于一个 zone)
firewall-cmd --zone=internal --add-interface=eth1 --permanent

4.4 核心概念:Service(服务)

Service 是 firewalld 的第二大抽象------它将"开放什么端口"封装为"放行什么服务"。

预定义 service 文件位置/usr/lib/firewalld/services/

bash 复制代码
# 查看所有预定义 service
firewall-cmd --get-services

# 查看某个 service 的定义(本质是 XML 文件)
cat /usr/lib/firewalld/services/ssh.xml

一个典型的 service XML 定义:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>SSH</short>
  <description>Secure Shell (SSH) 远程登录协议</description>
  <port protocol="tcp" port="22"/>
</service>

自定义 service(以常见 Web 应用端口为例):

bash 复制代码
# 创建自定义 service 文件
cat > /etc/firewalld/services/myapp.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>My Application - Web + API ports</description>
  <port protocol="tcp" port="8080"/>
  <port protocol="tcp" port="8443"/>
</service>
EOF

# 重载让 firewalld 识别新 service
firewall-cmd --reload

# 现在可以像使用预定义服务一样使用
firewall-cmd --add-service=myapp --permanent

4.5 Runtime vs Permanent

这是 firewalld 最容易让新手踩坑的设计。Firewalld 有两套配置:

模式 生效时机 生命周期
Runtime(默认) 立即生效 重启后丢失
Permanent --reload 后生效 永久保存

三种操作模式:

bash 复制代码
# 模式1:仅修改 runtime(立即生效,重启丢失)------ 适合临时测试
firewall-cmd --add-service=http

# 模式2:仅修改 permanent(不立即生效,reload 后生效)------ 适合批量配置
firewall-cmd --add-service=http --permanent

# 模式3:同时修改 runtime 和 permanent(立即生效 + 持久化)------ 最常用
firewall-cmd --add-service=http --permanent && firewall-cmd --add-service=http
# 或者反过来,先 runtime 测试,确认无误再加 permanent
firewall-cmd --add-service=http                          # 立即测试
firewall-cmd --add-service=http --permanent               # 确认无误后持久化

常见坑 :你 --add-service=http 没加 --permanent,重启后配置丢了。或者加了 --permanent 忘了 --reload,一直等到重启才生效。建议先用 runtime 模式测试,确认无误后再加上 --permanent 持久化。

reload 的两种方式:

bash 复制代码
firewall-cmd --reload          # 重载配置,不中断现有连接(推荐)
firewall-cmd --complete-reload # 完全重载,会短暂中断连接

4.6 Rich Rules(富规则)

当 service 和 port 的粒度不够时,rich rule 提供了更精细的控制。它的语法直观、接近自然语言。

bash 复制代码
# 格式:
firewall-cmd --add-rich-rule='rule [family="ipv4|ipv6"] <匹配条件> <动作>'

# 示例1:仅允许特定 IP 访问 SSH
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.10" service name="ssh" accept' --permanent

# 示例2:允许某网段访问多个端口
firewall-cmd --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port port="3306" protocol="tcp" accept' --permanent

# 示例3:速率限制(防止暴力破解)
firewall-cmd --add-rich-rule='rule service name="ssh" limit value="5/m" accept' --permanent

# 示例4:记录日志后丢弃
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" log prefix="DROPPED: " level="warning" limit value="10/m" drop' --permanent

# 示例5:端口转发(DNAT)
firewall-cmd --add-rich-rule='rule family="ipv4" forward-port port="8080" protocol="tcp" to-port="80" to-addr="192.168.1.100"' --permanent

# 查看 rich rules
firewall-cmd --list-rich-rules

4.7 Direct Rules(直通规则)

Direct rules 是 firewalld 的"逃生舱"------当你需要的功能 firewalld 无法抽象时,可以直接写原始 iptables 语法。

bash 复制代码
# 语法:
firewall-cmd --direct --add-rule <ipv4|ipv6> <table> <chain> <priority> <args>

# 示例:在 filter 表的 INPUT 链最前面添加一条 DROP 规则
firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 23 -j DROP

# 查看 direct rules
firewall-cmd --direct --get-all-rules

使用建议:能用 rich rule 解决的不要用 direct rule。Direct rule 绕过了 firewalld 的 zone/service 抽象,相当于从高级语言掉回汇编------失去了 firewalld 的主要优势。只有在需要精细控制 conntrack、mangle 表等场景下才使用。

4.8 实战示例

示例 1:将 Web 服务器从零配置到安全上线
bash 复制代码
# 第一步:确认当前状态
firewall-cmd --state
firewall-cmd --get-default-zone
firewall-cmd --list-all

# 第二步:设置默认 zone 为 public
firewall-cmd --set-default-zone=public

# 第三步:开放必要服务
firewall-cmd --add-service=ssh --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent

# 第四步:SSH 加 IP 白名单
firewall-cmd --remove-service=ssh --permanent  # 先移除服务级放行
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept' --permanent

# 第五步:应用并验证
firewall-cmd --reload
firewall-cmd --list-all
示例 2:工作笔记本------根据网络环境切换 zone
bash 复制代码
# 办公室(信任网络)
firewall-cmd --change-zone=work
# → 自动开放更多服务

# 咖啡厅(公共网络)
firewall-cmd --change-zone=public
# → 严格限制入站,只保留基本出站

# 查看当前 zone
firewall-cmd --get-active-zones

五、firewalld vs iptables 全面对比

维度 iptables firewalld
配置方式 命令行直接操作规则 通过 zone/service/rich-rule 抽象
规则生效 立即修改内核规则表 运行时生效,支持不中断重载
持久化 需借助外部工具(iptables-save 或服务) 内置 --permanent 机制
动态修改 每次修改需刷新整条链(原子操作) 支持单条规则的增删,不影响现有连接
网络环境感知 Zone 机制,可按网络环境切换策略
学习曲线 陡峭,需理解表/链/匹配/目标 平缓,概念更接近人类思维
精细控制度 极致 高层抽象,精细场景需 rich/direct rule
性能 轻量,无守护进程开销 有守护进程和 D-Bus 通信开销(通常可忽略)
生产稳定性 20+ 年验证 10+ 年验证,RHEL/CentOS 7+ 默认

什么时候用什么

优先使用 firewalld 的场景:

  • RHEL/CentOS/Fedora 7+ 系统(它是默认防火墙)
  • 标准 Web 服务器、数据库服务器(service 抽象足够)
  • 需要在不同网络环境切换策略(笔记本)
  • 团队中有初级运维,降低学习成本

仍然需要使用 iptables 的场景:

  • 复杂的 NAT/路由场景(多出口、策略路由)
  • Docker/K8s 环境(它们直接操作 iptables 规则,与 firewalld 可能冲突)
  • 嵌入式 Linux / 容器(不想引入额外的守护进程依赖)
  • 遗留系统维护(RHEL 6 / CentOS 6 及更早)
  • 对性能极度敏感的场景(高频包处理)

共存警告 :firewalld 和 iptables 服务不能同时运行 。在 RHEL 7+ 上,如果你 systemctl start iptables,它和 firewalld 是互斥的------两个服务会争抢 netfilter 规则的控制权。


六、实战场景

6.1 Web 服务器防火墙

假设你在云上有一台 Web 服务器,需要开放 80/443,SSH 只允许公司 IP 网段访问,还要防止简单 DDoS。

firewalld 方案:

bash 复制代码
#!/bin/bash
# 设置默认 zone
firewall-cmd --set-default-zone=public

# 开放 Web
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent

# SSH 仅允许公司 IP 网段
firewall-cmd --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept' --permanent

# HTTP 连接速率限制(防简单 CC 攻击)
firewall-cmd --add-rich-rule='rule service name="http" limit value="100/s" accept' --permanent

# 丢弃无效包(通过 direct rule)
firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m conntrack --ctstate INVALID -j DROP

# 应用
firewall-cmd --reload

iptables 等价方案:

bash 复制代码
#!/bin/bash
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 -s 203.0.113.0/24 --dport 22 -m conntrack --ctstate NEW -j ACCEPT

# Web 速率限制
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -m limit --limit 100/s --limit-burst 200 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -m limit --limit 100/s --limit-burst 200 -j ACCEPT

6.2 NAT 网关(iptables)

这是 iptables 当仁不让的主场。假设一台双网卡服务器做网关:

  • eth0:外网(203.0.113.10)
  • eth1:内网(192.168.1.0/24)
bash 复制代码
#!/bin/bash
# 开启 IP 转发
sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

# 默认过滤策略
iptables -P FORWARD DROP

# 允许内网访问外网
iptables -A FORWARD -i eth1 -o eth0 -s 192.168.1.0/24 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# SNAT(内网上网)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j MASQUERADE

# 端口映射(将外网 8080 映射到内网 192.168.1.100:80)
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
iptables -A FORWARD -i eth0 -o eth1 -p tcp -d 192.168.1.100 --dport 80 -m conntrack --ctstate NEW -j ACCEPT

为什么这里不用 firewalld? firewalld 的 NAT 支持主要在 rich rule 的 forward-port 中,对于多网卡、多网段、策略路由等复杂场景,表达能力不足。直接写 iptables 更清晰、更可控。

6.3 Docker 与防火墙的兼容问题

这是一个高频踩坑点。Docker 直接操作 iptables 规则来实现容器网络,它会:

  1. DOCKERDOCKER-USER 自定义链中插入规则
  2. 默认绕过 INPUT 链的控制(因为 Docker 的转发走 FORWARD 链)

常见问题: 你明明用 firewalld 禁止了某个端口的外部访问,但 Docker 容器在那个端口上仍然可以被访问。

原因 :Docker 在 PREROUTING 做 DNAT,在 FORWARD 放行,而 firewalld 主要管 INPUT 链。两个机制在不同的网络栈层面运行,互不感知。

解决方案:

bash 复制代码
# 方案1:在 DOCKER-USER 链中加规则(Docker 保证这条链不会被自己覆盖)
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP

# 方案2:Docker daemon 配置中禁用 iptables 操作(不推荐,会破坏容器网络)
# /etc/docker/daemon.json
{
  "iptables": false
}

# 方案3(推荐):firewalld + docker 共存最佳实践
# 将 docker0 网桥加入 trusted zone(让容器间通信正常)
firewall-cmd --zone=trusted --add-interface=docker0 --permanent
firewall-cmd --reload

# 容器对外暴露的端口用 firewalld 的 direct rule 控制 FORWARD 链
firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 \
    -i eth0 -o docker0 -p tcp --dport 8080 -m conntrack --ctstate NEW -j ACCEPT

七、进阶话题

7.1 调试技巧

排查"为什么这个包被丢弃了":

bash 复制代码
# 方法1:在规则链末尾加 LOG 规则,捕获不匹配任何规则的包
iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROPPED: " --log-level 4

# 然后查看日志
tail -f /var/log/kern.log | grep IPTABLES-DROPPED
# 或者
dmesg -w | grep IPTABLES-DROPPED

firewalld 调试:

bash 复制代码
# 开启 firewalld 调试日志
firewall-cmd --set-log-denied=all

# 查看被拒绝的包
dmesg | grep "FINAL_REJECT"

# 查看 firewalld 底层生成的 iptables 规则
iptables -L -n -v
iptables -t nat -L -n -v

查看规则计数器(判断规则是否被命中):

bash 复制代码
# 重置计数器后观察
iptables -Z
# 一段时间后:
iptables -L -n -v  # pkts 和 bytes 列显示每条规则匹配的流量

7.2 fail2ban 联动

fail2ban 是一个经典的入侵防御工具------它监控日志(如 SSH 登录失败),然后动态调用防火墙封禁攻击者 IP。

bash 复制代码
# 安装
apt install fail2ban  # Debian/Ubuntu
yum install fail2ban  # RHEL/CentOS

# 基础配置 /etc/fail2ban/jail.local
[DEFAULT]
bantime = 600
findtime = 600
maxretry = 5
banaction = firewallcmd-rich-rules     # 使用 firewalld
# banaction = iptables-multiport       # 使用 iptables

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s

# 启动
systemctl enable fail2ban
systemctl start fail2ban

# 查看封禁状态
fail2ban-client status sshd

fail2ban 会自动往 firewalld 中插入 rich rule 或在 iptables 中插入 REJECT 规则来封禁攻击者 IP。封禁到期后自动解除。


八、常见问题排查

问题 1:加了规则为什么还是不通?

排查清单:

  1. 规则顺序 :iptables 规则是从上到下匹配,命中即停止。检查是不是前面的规则匹配了

    bash 复制代码
    iptables -L -n -v --line-numbers
  2. 链的默认策略iptables -L -n 看链的 policy 是不是 DROP

    bash 复制代码
    iptables -P INPUT ACCEPT  # 临时放行所有,确认是防火墙问题
  3. firewalld 的 zone 不对:网卡可能绑定到了其他 zone

    bash 复制代码
    firewall-cmd --get-active-zones
  4. firewalld runtime vs permanent

    bash 复制代码
    # runtime 有但 permanent 没有 → 重启后丢失
    firewall-cmd --list-services          # 当前生效
    firewall-cmd --permanent --list-services  # 持久化配置
  5. Docker 绕过了防火墙 :见 [6.3 节](#6.3 节)

问题 2:firewalld 和 iptables 服务冲突

bash 复制代码
# 症状:两个服务同时 running,规则混乱
systemctl status firewalld
systemctl status iptables

# 解决:禁用 iptables 服务,统一用 firewalld
systemctl stop iptables
systemctl disable iptables
systemctl restart firewalld

问题 3:iptables 规则重启后丢失

bash 复制代码
# Debian/Ubuntu
apt install iptables-persistent
netfilter-persistent save

# RHEL/CentOS 7(如果直接使用 iptables 而非 firewalld)
yum install iptables-services
service iptables save
systemctl enable iptables

问题 4:firewalld reload 后 SSH 断了

这通常是因为你在 permanent 中移除了 SSH 服务或 zone 配置有误,reload 时应用了新规则。

bash 复制代码
# 修复:通过控制台(非 SSH)登录
firewall-cmd --add-service=ssh --permanent
firewall-cmd --reload

# 预防:reload 前先在 runtime 测试
firewall-cmd --add-service=ssh
# 确认 OK 后再加 --permanent

九、总结与最佳实践

核心理念

  1. 最小权限原则:默认 DROP 一切,只开放必需的端口和服务
  2. 白名单优于黑名单:指定"谁可以"而不是"谁不可以"
  3. 分层防御:防火墙只是安全链中的一环,配合 SSH Key、SELinux、fail2ban、定期审计使用

操作 Checklist

部署新服务器时,按以下顺序配置防火墙:

  • 确认防火墙状态:firewall-cmd --statesystemctl status firewalld
  • 确认当前规则:firewall-cmd --list-alliptables -L -n -v
  • 设置默认策略:入站 DROP / 转发 DROP / 出站 ACCEPT
  • 放行回环接口(lo
  • 放行 ESTABLISHED,RELATED 状态
  • 丢弃 INVALID 状态
  • 开放 SSH(最好加 IP 白名单或速率限制)
  • 开放业务端口(HTTP/HTTPS/DB 等)
  • 持久化配置
  • 从另一个终端验证 SSH 还能登录(重要!不要关掉当前会话)
  • 配置 fail2ban(可选但强烈推荐)

推荐学习路径

复制代码
小白起步:
  1. 理解 netfilter 五个钩子点的数据包流转(本文第二章)
  2. 能用 iptables 写出"允许 SSH + 默认拒绝"的基本规则
  3. 转到 firewalld,掌握 zone / service / runtime-vs-permanent
  4. 在测试虚拟机中实践所有本文示例

中级进阶:
  5. 理解 conntrack 状态机(NEW/ESTABLISHED/RELATED/INVALID)
  6. 能用 iptables 配置 NAT 网关(SNAT/DNAT/MASQUERADE)
  7. 会排查防火墙与 Docker/K8s 的交互问题

高级深入:
  8. 自定义 iptables 链实现模块化规则管理
  9. mangle 表与 QoS(流量整形)
  10. 大规模规则集的性能优化(ipset)
  11. 阅读 netfilter 内核源码(nf_hook_ops 结构)

最后的提醒 :防火墙的配置是"差一点都不行"的。一条顺序错误或遗漏的规则,可能让你从互联网上裸奔,也可能把你自己锁在服务器外面。在测试环境充分验证,在生产环境谨慎操作,永远保留一个独立于防火墙的控制台通道(如云厂商的 VNC 控制台)。


本文基于 Linux 内核 5.x/6.x 系列、iptables 1.8.x、firewalld 1.x 版本编写,适用于 RHEL 7~9 / CentOS 7~9 / Fedora 35+ / Debian 10~12 / Ubuntu 20.04~24.04 等主流发行版。

相关推荐
月走乂山6 小时前
Linux 服务器安装 CC Switch GUI 工具 + VNC 远程桌面完整教程
linux·运维·服务器
前端 贾公子6 小时前
基于 Nginx 实现一个灰度上线系统
运维·nginx
手可摘星辰的少年6 小时前
二级指针到底在改什么?——从C语言基础到Linux内核文件系统注册机制
linux
wanQQ6 小时前
在 KDE 中将 Nemo 设为默认文件管理器后,浏览器仍调用 Dolphin 的解决方案
linux
认真的薛薛6 小时前
Linux基础:GitOps发布流程
java·linux·运维
北风toto7 小时前
Jenkins新手入门安装插件全报错
java·运维·jenkins
IpdataCloud7 小时前
企业IT管理中,如何通过IP地址查询定位快速溯源异常终端?用IP离线库实现
服务器·网络·数据库·tcp/ip
dislike_shuati7 小时前
Ubuntu18多用户情况一用户桌面卡死,鼠标能动但点击没用——解决办法
linux·运维·服务器
Yeats_Liao7 小时前
物联网接入层技术剖析(四):当epoll遇见MQTT
java·linux·服务器·网络·物联网·架构