一、一次被 DDoS 打挂的教训
去年帮一个客户排查服务器,现象很诡异:CPU 不到 20%,带宽也没跑满,但 SSH 就是连不上、网站就是 502。最后发现是海量半连接(SYN Flood)把 连接跟踪表(conntrack) 打满了——dmesg 里满屏 nf_conntrack: table full, dropping packet。
那次之后我明白:防火墙不是"挡不挡端口"的开关,而是一套需要按流量特征精细编排的防御体系。本文把 iptables、nftables、fail2ban、tc 四件套串起来讲清楚。
二、iptables 四表五链:先建立心智模型
iptables 的复杂度来自它把规则分到了"表(table)"和"链(chain)"两个维度:
数据包进入
│
┌─────────────▼─────────────┐
│ PREROUTING (路由前) │
└─────────────┬─────────────┘
│
┌────────▼────────┐
│ 路由判断 │
└───┬────────┬───┘
本机流量 │ │ 转发流量
│ │
┌─────────▼──┐ ┌─▼──────────┐
│ INPUT │ │ FORWARD │
│ (本机接收) │ │ (转发其他机)│
└─────────┬──┘ └─┬──────────┘
│ │
┌────────▼────────▼────────┐
│ POSTROUTING (路由后) │
└──────────────────────────┘
│
▼ 出网卡
五链:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING 四表(优先级从高到低):
raw:是否跳过连接跟踪mangle:修改包头(TTL、TOS、MARK)nat:地址转换(SNAT/DNAT)filter:过滤(最常用,默认表)
最常用的 filter 表三链:INPUT(进本机)、FORWARD(转发)、OUTPUT(本机出)。
三、生产级基础规则:先封锁,再按需开放
新手最容易犯的错是"先全放行再想收",结果漏配留下后门。正确姿势是默认 DROP,白名单开放:
# 1. 清空旧规则(测试时谨慎!远程执行可能把自己踢下线)
iptables -F
iptables -X
# 2. 默认策略:进和转发都 DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# 3. 允许回环
iptables -A INPUT -i lo -j ACCEPT
# 4. 允许已建立/相关的连接(关键!否则响应包也进不来)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 5. 白名单开放服务端口
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT # SSH 仅内网
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 6. 防 SYN Flood:限制单个 IP 的新连接速率
iptables -A INPUT -p tcp --syn -m limit --limit 20/s --limit-burst 50 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
# 7. 丢弃无效包
iptables -A INPUT -m state --state INVALID -j DROP
避坑 1:远程操作时千万别先执行 -P INPUT DROP 再配规则——一旦网络抖动或命令中断,你会被锁在门外。安全做法是用 iptables-apply 命令,它会在 60 秒无确认后自动回滚:
iptables-apply -t 60 your-rules.sh
四、nftables:iptables 的现代替代
iptables 实际是 nf_tables 内核框架的用户态前端,命令分散(iptables/ip6tables/ebtables),语法反人类。RHEL 8+、Debian 10+ 已默认推荐 nftables,统一语法、性能更好、支持集合(set)和字典(map)。
等价的 nftables 配置:
# 创建表与基础链
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0\; policy drop\; }
nft add chain inet filter forward { type filter hook forward priority 0\; policy drop\; }
# 允许回环与已建立连接
nft add rule inet filter input iifname "lo" accept
nft add rule inet filter input ct state established,related accept
# 用 set 管理白名单 IP,比逐条写规则优雅得多
nft add set inet filter ssh_whitelist { type ipv4_addr\; }
nft add element inet filter ssh_whitelist { 10.0.0.0/8 }
nft add rule inet filter input tcp dport 22 ip saddr @ssh_whitelist accept
# 开放 Web 端口
nft add rule inet filter input tcp dport { 80, 443 } accept
# 防 SYN Flood
nft add rule inet filter input tcp flags syn limit rate 20/second burst 50 packets accept
nft add rule inet filter input tcp flags syn drop
nftables 还支持把整个配置写成一个文件,用 nft -f ruleset.conf 原子加载,比 iptables 一行行敲可靠得多。Red Hat 系用 nft list ruleset > /etc/nftables.conf 持久化,Debian 系启用 nftables.service 即可。
避坑 2:iptables 和 nftables 同时跑会互相看不见对方的规则(虽然共享内核 nf_tables),极易造成"我明明加了规则怎么不生效"。生产机二选一,迁移时彻底禁用另一个:
# 确认没有 iptables 规则残留
iptables-save | wc -l # 应只剩默认链
systemctl disable --now iptables 2>/dev/null
五、fail2ban:自动封禁爆破 IP
防火墙白名单解决了"谁来连",但 22 端口暴露在外网时,暴力破解会刷爆日志。fail2ban 监听日志,命中失败阈值就动态加 iptables/nftables 规则封 IP:
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = nftables-multiport # 对应你的防火墙后端
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/secure
maxretry = 3
验证生效:
fail2ban-client status sshd
# Status for the jail
# |- Filter
# | |- Currently failed: 2
# | `- Total failed: 47
# `- Actions
# |- Currently banned: 3
# `- Banned IP list: 182.92.x.x 45.33.x.x 1.2.3.4
避坑 3:fail2ban 的 banaction 必须和机器实际用的防火墙匹配。用 nftables 却配了 iptables-multiport,封禁规则会写进 iptables 而你的流量走 nftables 链,等于没封。
六、tc 流量控制:给带宽做 QoS
防火墙管"能不能通",tc(Traffic Control)管"谁先走、走多快"。典型场景:服务器出口带宽 100Mbps,夜里备份把带宽占满,白天 API 响应就抖。用 tc 给备份流量限速、给业务流量保证优先级:
# 在出网卡 eth0 上建立根 qdisc(htb 分层令牌桶)
tc qdisc add dev eth0 root handle 1: htb default 30
# 总带宽 100Mbps
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit
# 业务类:保证 70Mbps,可突发到 100M
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 70mbit ceil 100mbit prio 1
# 备份类:限速 30Mbps,最低保障 5M
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 5mbit ceil 30mbit prio 2
# 用 iptables MARK 打标,把备份流量(目的端口 873 rsync)标记为 30
iptables -t mangle -A OUTPUT -p tcp --dport 873 -j MARK --set-mark 30
# 按 MARK 把流量导入对应 class
tc filter add dev eth0 parent 1: handle 30 fw classid 1:30
tc filter add dev eth0 parent 1: handle 10 fw classid 1:10
这样业务流量永远优先,备份只能在剩余带宽里"捡漏",再也不会把 API 拖垮。
避坑 4:tc 规则默认重启即失,必须写进网络启动脚本或 NetworkManager-dispatcher。一个常见的持久化方式:
# /etc/network/if-up.d/tc-limit
#!/bin/bash
[ "$IFACE" = "eth0" ] || exit 0
tc qdisc add dev eth0 root handle 1: htb default 30
# ... 其余规则
chmod +x /etc/network/if-up.d/tc-limit
七、排错全流程
规则不生效时,按这个顺序查:
# 1. 看连接跟踪表是否打满(前文 DDoS 案例)
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 2. 看包到底命中了哪条规则(iptables 计数)
iptables -L INPUT -v -n --line-numbers
# 3. 用 TRACE 追踪单个包的路径(调试神器)
iptables -t raw -A PREROUTING -p tcp --dport 443 -j TRACE
# 然后 dmesg | grep TRACE 看包走过的每个链
# 4. nftables 对应追踪
nft add rule inet filter input tcp dport 443 meta nftrace set 1
# nft monitor trace
避坑 5:conntrack_max 默认值往往偏小(默认 65536)。高并发服务器要调大,否则正常流量也会被当"表满丢包":
sysctl -w net.netfilter.nf_conntrack_max=262144
echo "net.netfilter.nf_conntrack_max=262144" >> /etc/sysctl.conf
八、小结
防火墙体系四件套的职责边界:
| 工具 | 管什么 | 典型场景 |
|---|---|---|
| iptables/nftables | 能不能通、地址怎么转 | 端口开放、黑白名单、NAT |
| fail2ban | 谁被临时拉黑 | 防 SSH/Web 暴力破解 |
| tc | 谁先走、走多快 | 带宽限速、QoS 保障 |
| conntrack | 连接状态跟踪 | 状态防火墙的底层支撑 |
把它们组合起来,一台暴露在公网的服务器就能做到:该开的端口精准开放、爆破 IP 自动封禁、关键业务带宽有保障、连接表不被打爆。这才是生产级的安全水位。
下一篇可以深入 eBPF + XDP,用更底层的手段在网卡层面直接丢弃攻击流量,把防御推进到内核协议栈的最前端。
