Skip to content

Latest commit

 

History

History
432 lines (335 loc) · 16 KB

09-firewall.md

File metadata and controls

432 lines (335 loc) · 16 KB

09. 防火墙与内核网络参数

防火墙在 Linux 上不是一个软件,而是内核里的一套框架(netfilter)+ 若干种不同的操作界面。

这一层解决什么问题

网络通了以后,必须回答:谁允许连我、我允许连谁、包的地址要不要改写。

这三件事(过滤、NAT、转发)都由内核的 netfilter 框架完成。用户态的 iptables、nftables、firewalld、ufw 只是不同的操作界面。

一、先理清关系(最重要的一节)

初学者最大的困惑是"到底该学哪个"。答案:它们不是竞争关系,而是分层。

内核态:netfilter(钩子点 + 规则引擎)
           ↑
           │ 同一条规则链的两套语法
     ┌─────┴─────┐
   iptables     nftables          ← 底层工具(直接操作规则)
   (legacy)    (现代)
     └─────┬─────┘
           │ 上层封装
   ┌───────┼────────┬──────────┐
 firewalld  ufw   fail2ban   云安全组
(RHEL 系)(Debian 系)(动态封 IP)

实践建议:

场景 选择
服务器要精细控制 nftables(现代)或 iptables(老系统兼容)
桌面/简单服务器 ufw(Debian 系)或 firewalld(RHEL 系)
Docker/K8s 环境 不要手动管 iptables,Docker 自己会写规则

⚠️ 绝对不要混用多套工具。ufw 和 iptables 同时开,规则互相覆盖,结果无法预测。一台机器只用一套。

二、netfilter 的内核架构

2.1 五个钩子点(hook)

数据包在协议栈里流动时,会经过固定的几个检查点:

钩子 时机 典型规则
PREROUTING 刚进入协议栈,路由决策前 DNAT(端口转发)
INPUT 目标是本机 过滤入站
FORWARD 目标是其他机器(转发) 过滤转发、容器流量
OUTPUT 本机发出 过滤出站
POSTROUTING 即将离开协议栈 SNAT / MASQUERADE

数据包流向图(记住这张图,规则就不会写错位置):

flowchart LR
    N["网卡收到包"] --> PRE["PREROUTING<br/>改目标地址 DNAT"]
    PRE --> RT{"路由决策"}
    RT -->|目标是本机| IN["INPUT"]
    RT -->|目标是他机| FW["FORWARD"]
    IN --> APP["本机进程"]
    APP --> OUT["OUTPUT"]
    OUT --> POST
    FW --> POST["POSTROUTING<br/>改源地址 SNAT"]
    POST --> W["网卡发出"]
Loading

2.2 核心概念

概念 含义
表(table) 按功能分类:filter(过滤)、nat(地址转换)、mangle(改包)、raw(连接跟踪豁免)
链(chain) 挂在钩子上的规则列表,也支持自定义链
规则(rule) 匹配条件 + 动作(target)
target ACCEPT、DROP、REJECT、LOG、SNAT、DNAT、MASQUERADE、RETURN
conntrack 连接跟踪:记住"这条连接是已建立的",返回包自动放行

DROP 和 REJECT 的区别很实用:DROP 静默丢弃(对方超时,像遇到黑洞),REJECT 回一个拒绝包(对方立刻知道)。想隐藏主机用 DROP,想让对方快速失败用 REJECT。

三、iptables

3.1 命令结构

iptables [-t 表] -命令 链 [匹配条件] -j 动作
            默认 filter
操作 含义
-L -n -v 列出规则(数字显示、带计数)
-A 追加到链尾
-I [n] 插入到链首(默认第 1 条)
-D 删除
-F / -X 清空链 / 删自定义链
-P 设置链的默认策略
-Z 清零计数器
$ sudo iptables -L -n -v --line-numbers
$ sudo iptables -t nat -L -n -v
$ sudo iptables -S                       # 以可重放格式输出 ← 备份用这个

3.2 一个标准的服务器规则集

# ① 默认策略:丢弃所有入站/转发,允许出站
#    注意:必须在放行 SSH 之后再设默认 DROP,否则会把自己锁在外面!
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT

# ② 放行本机回环
sudo iptables -A INPUT -i lo -j ACCEPT

# ③ 放行已建立的连接 ← 这条最关键,否则返回包全丢
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ④ 放行具体服务
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# ⑤ 防暴力破解:限制 SSH 新连接频率
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
    -m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
    -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP

# ⑥ 丢弃无效包
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# ⑦ 记录被拒绝的包(排障用,上线后建议关掉避免刷日志)
sudo iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4

3.3 NAT:内网共享上网与端口转发

# 开启转发(内核层面)
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# 持久化写 /etc/sysctl.d/99-net.conf: net.ipv4.ip_forward = 1

# 出口 SNAT / MASQUERADE:内网经本机上网
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# 端口转发:外部 8080 → 内网 192.168.1.10:80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT \
    --to-destination 192.168.1.10:80
sudo iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 80 -j ACCEPT
动作 说明
MASQUERADE 动态源地址转换(出口 IP 会变时用,如拨号)
SNAT --to-source IP 固定源地址转换(性能更好,出口 IP 固定时用)
DNAT --to-destination IP:PORT 目标地址转换(端口转发)
REDIRECT 重定向到本机其他端口(透明代理)

3.4 持久化

iptables 的规则默认不持久,重启就没了。

# Debian/Ubuntu
$ sudo apt install iptables-persistent
$ sudo netfilter-persistent save      # 存到 /etc/iptables/rules.v4
$ sudo netfilter-persistent reload

# RHEL/Fedora
$ sudo dnf install iptables-services
$ sudo service iptables save          # 存到 /etc/sysconfig/iptables

⚠️ 改防火墙最危险的操作是设默认 DROP。远程操作时请务必:

  1. 先加放行 SSH 的规则;
  2. 用 at 或定时任务做"10 分钟后自动恢复"的兜底;
  3. 或先在本地/控制台会话测试。

一条命令把自己锁在服务器外面的案例,每个运维都有一两个故事。

四、nftables(现代替代品)

nftables 从内核 3.13 起就在,现在几乎所有发行版都默认它。优势:统一了 ip/ip6/arp/ebtables,性能更好,语法更一致,原生支持集合。

4.1 与 iptables 的概念映射

iptables nftables
表 + 链分离 表包含链,链指定 type 和 hook
隐式优先级 显式 priority
-j ACCEPT accept
多条 -p tcp --dport 80 集合 { 80, 443 }
无原生集合 set、map、vmap
ipset 插件 内建

4.2 常用命令

$ sudo nft list ruleset                    # 看全部规则 ← 排障第一命令
$ sudo nft list tables
$ sudo nft add table inet filter
$ sudo nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
$ sudo nft add rule inet filter input tcp dport 22 accept
$ sudo nft delete rule inet filter input handle 5
$ sudo nft flush ruleset                   # ⚠️ 清空所有规则(等于关防火墙)

handle 是删除规则时需要的编号:nft -a list ruleset 能看到。

4.3 一份完整配置

#!/usr/sbin/nft -f
# /etc/nftables.conf  ⚠️ 改前备份,并留好 SSH 退路

flush ruleset

table inet filter {
    set ssh_bruteforce {
        type ipv4_addr
        flags dynamic, timeout
        timeout 10m
    }

    chain input {
        type filter hook input priority filter; policy drop;

        iif lo accept                              # 回环
        ct state established,related accept         # 已建立连接
        ct state invalid drop

        # 允许 ping(限速)
        icmp type echo-request limit rate 5/second accept

        # SSH:限速 + 自动拉黑
        tcp dport 22 ct state new \
            add @ssh_bruteforce { ip saddr limit rate 4/minute } accept
        ip saddr @ssh_bruteforce drop

        # Web
        tcp dport { 80, 443 } accept

        # 日志
        log prefix "nft-drop: " limit rate 5/minute
    }

    chain forward {
        type filter hook forward priority filter; policy drop;
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}
$ sudo nft -c -f /etc/nftables.conf      # 语法检查(-c = check)
$ sudo nft -f /etc/nftables.conf         # 应用
$ sudo nft list ruleset > /etc/nftables.rules   # 备份
$ sudo systemctl enable --now nftables

4.4 从 iptables 迁移

$ sudo iptables-save > rules.v4
$ sudo iptables-restore-translate -f rules.v4 > rules.nft   # 自动翻译
$ sudo nft -f rules.nft
$ sudo iptables-nft -L        # iptables-nft 兼容层(大多数发行版默认)

注意:现代发行版上的 iptables 命令其实就是 nftables 的兼容前端(iptables-nft)。所以 iptables -L 和 nft list ruleset 看到的是同一批规则的两种视图。这一点不搞清楚会导致严重误判。

五、上层封装工具

5.1 ufw(Ubuntu/Debian 系,简单直白)

$ sudo ufw status verbose
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw allow from 192.168.1.0/24 to any port 3306
$ sudo ufw allow 80,443/tcp
$ sudo ufw limit 22/tcp                  # 内置限速(防爆破)
$ sudo ufw delete allow 80/tcp
$ sudo ufw enable                        # ⚠️ 先确保 SSH 已放行
$ sudo ufw disable
$ sudo ufw reset
$ sudo ufw app list                      # 预定义应用配置

Docker 与 ufw 的著名冲突:Docker 直接写 iptables 的 DOCKER 链,绕过 ufw。结果是你 ufw 里禁了 3306,但发布的容器端口照样从公网可访问。解决方案:在 /etc/docker/daemon.json 里设 "iptables": false(需自己管规则)、或把容器端口绑到 127.0.0.1:3306:3306、或改用 ufw-docker 工具。这是新手最常踩的安全坑之一。

5.2 firewalld(RHEL/Fedora 系,区域模型)

核心概念是 zone(区域):不同区域有不同的默认信任级别,接口/源地址被划入某个区域。

区域 默认行为
drop 丢弃所有入站
block 拒绝所有入站(有回应)
public 默认区域,放行 ssh/dhcpv6-client
trusted 全部放行
$ sudo firewall-cmd --state
$ sudo firewall-cmd --get-active-zones
$ sudo firewall-cmd --list-all
$ sudo firewall-cmd --zone=public --add-service=http --permanent
$ sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
$ sudo firewall-cmd --reload                     # 应用 permanent 规则
$ sudo firewall-cmd --runtime-to-permanent       # 把临时规则固化
$ sudo firewall-cmd --add-masquerade --permanent
$ sudo firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080 --permanent

--permanent 与 --reload 的关系是最大坑点:加 --permanent 后必须 --reload 才生效;不加 --permanent 的规则立即生效但重启丢失。两个都有才叫"现在生效且永久"。

5.3 fail2ban(动态封 IP,不依赖具体防火墙)

# /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd

[sshd]
enabled = true
port    = 22
$ sudo fail2ban-client status
$ sudo fail2ban-client status sshd       # 看被封的 IP
$ sudo fail2ban-client set sshd unbanip 1.2.3.4

六、内核网络参数(sysctl 与防火墙的配合)

防火墙之外,内核还有一组参数直接决定"包能不能过"。完整清单在 07 章,这里只列与过滤/转发强相关的:

参数 作用 相关场景
net.ipv4.ip_forward 是否转发 NAT、路由、容器
net.ipv4.conf.all.forwarding 所有接口转发 同上
net.bridge.bridge-nf-call-iptables 桥接流量过不过 iptables Docker/K8s 必需 1
net.ipv4.conf.all.rp_filter 反向路径校验 多网卡/非对称路由会误杀
net.ipv4.tcp_syncookies SYN Flood 防护 保持 1
net.netfilter.nf_conntrack_max 连接跟踪表上限 高并发时"表满丢包"
net.ipv4.ip_local_port_range 本地端口范围 出站连接数受限
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
$ dmesg | grep -i "conntrack: table full"     # ← 高并发丢包的隐蔽元凶
$ sudo conntrack -L | wc -l                   # 当前连接跟踪条目数

七、Docker / 容器场景

关键认知:容器网络依赖 iptables/nftables 的 NAT 与 FORWARD 链。

$ sudo iptables -t nat -L DOCKER -n --line-numbers    # Docker 写的规则
$ sudo iptables -L DOCKER-USER -n -v                  # ← 你的自定义规则放这里
要点 说明
Docker 会改 FORWARD 链策略 可能覆盖你的默认策略
DOCKER-USER 链是留给你的入口 自定义规则应插在这里,且 Docker 不会覆盖它
端口发布绕过 ufw 见 5.1 的说明
重启防火墙服务可能清掉 Docker 规则 需重启 docker 服务恢复
K8s 用自己的 kube-proxy / CNI 不要手改,改 Calico/Cilium 的配置
# 只允许特定网段访问容器的 3306
$ sudo iptables -I DOCKER-USER -p tcp --dport 3306 ! -s 192.168.1.0/24 -j DROP

八、排障入口

症状 排查
规则不生效 是否 reload?iptables -L -n -v 看计数器有没有涨
规则写对了但包不匹配 位置错了(-A 在链尾,前面有 DROP 就轮不到);用 -I 插到前面
改了规则把自己锁外面 用带外/控制台,或 nft flush ruleset / 临时 -P ACCEPT
NAT 不生效 ip_forward 没开;FORWARD 链被 DROP;rp_filter 太严
返回包收不到 少了 conntrack ESTABLISHED,RELATED 放行
Docker 端口意外公网可访问 ufw 被绕过(见 5.1)
高并发下偶发丢包 conntrack 表满(dmesg 有提示)
规则重启就没了 没做持久化
# 带计数器的实时观察 ← 最有效的验证手段
$ sudo iptables -L -n -v --line-numbers
$ sudo watch -n1 'iptables -L INPUT -n -v | head -20'
$ sudo nft list ruleset -a
$ sudo conntrack -L | head

调试技巧:临时全放行 + 日志。先用 LOG target(不是 DROP)记录"本该被拦的包",观察一段时间确认规则正确,再改成 DROP。这样不会误伤。

常见误区

  • iptables 和 nftables 在多数系统上是同一套规则的不同视图(iptables-nft 兼容层)。
  • 默认策略 DROP 忘放行 SSH = 自锁。
  • ufw/firewalld 与 iptables 不要混用。
  • Docker 绕过 ufw,这是真实的安全漏洞来源。
  • 防火墙不是万能安全措施,它只管端口层,应用层漏洞照样被打。
  • 规则顺序决定一切,匹配到第一条就返回。

待补充

  • nftables 高级特性:map / vmap、动态集合、ct 对象完整用法
  • conntrack 调优与监控(高并发场景)
  • 透明代理(TPROXY)与 REDIRECT
  • 云环境安全组与主机防火墙的协作
  • nftables 与 Docker/K8s 的冲突处理
  • firewalld 高级:rich rules、direct interface
  • 网络策略(NetworkPolicy)与 CNI 层面的过滤
  • 用 bpfilter / XDP 做高性能过滤
  • DDoS 防护分层方案

← 上一章:网络服务与应用 · 返回目录 · 下一章:图形桌面栈 →