上一篇 下一篇 分享链接 返回 返回顶部

香港服务器运行 RHEL 系统时,如何用 iptables 与 tc 限制恶意高频连接

发布人:Minchunlin 发布时间:2025-09-22 12:21 阅读量:738


香港葵涌机房的监控短信刚把我从沙发上弹起来:443 端口新建连接激增,SYN_RECV 飙升,CPU softirq 90%+。运营同事在电话那端只有一句话:“香港站的支付回调卡死了,能顶住吗?”

我背着工具包在机柜前蹲下,一边用耳朵分辨哪台机器在“尖叫”,一边把控制台切到这台 RHEL 服务器上。那晚我把这套 iptables + tc 的组合拳落了地,次日早高峰照常,业务没再抖。下面就是那次处置后的完整复盘与可复制的步骤。

1. 现场环境与目标

硬件与系统(涉敏信息做了脱敏/同类替换,但参数真实可用)

配置
机型 Dell R650xs(1U)
CPU 2× Intel Xeon Silver 4310(10C/20T, 2.1GHz)
内存 128GB ECC
系统盘 2× NVMe Samsung PM9A3(RAID1)
网卡 Intel X710-DA2(10GbE ×2,主用 eth0)
OS RHEL 8.8(内核 4.18,iptables-nft 后端)
业务 NGINX 反向代理(80/443),gRPC(50051),SSH(22)
公网 /29 段,BGP 上游支持黑洞社区但该次未申请

威胁画像(当晚抓包与日志统计)

现象 数字
峰值 SYN pps ~120k pps(集中 443)
恶意源 IP 特征 多源分布,/16 段聚集明显
单源新建连接 50~300 conn/s 不等,短时峰值 800+
攻击载荷 绝大多数仅 SYN,不完成握手;少量完成握手后 1RTT 内断开

目标

  • 限制单源高频新建连接(per-src conn/s 与并发数)。
  • 对已识别恶意源快速衰减/丢弃。
  • 在内核入口(ingress)就做减压,降低 softirq 与 conntrack 压力。
  • 不明显伤害正常用户(移动端突发、CDN 回源、支付回调等)。

2. 上线前的基线与内核调优(5 分钟内完成)

在“拉栓”前,先把地基垫实。以下 sysctl 以 RHEL 8 为例,可写入 /etc/sysctl.d/99-hardening.conf。

# 连接跟踪与队列
sysctl -w net.netfilter.nf_conntrack_max=$((1024*1024))            # 视内存上调到 1M~4M
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_syn_recv=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sysctl -w net.core.netdev_max_backlog=250000
sysctl -w net.core.somaxconn=65535

# SYN 防护与路由健壮性
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=262144
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.tcp_timestamps=1  # 便于启用 SYNPROXY 的时间戳校验

# 队列/中断
sysctl -w net.core.rmem_default=262144
sysctl -w net.core.wmem_default=262144
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728

网卡微调(X710 常规操作):

ethtool -G eth0 rx 4096 tx 4096
ethtool -K eth0 gro on lro off gso on tso on

如果有 RSS/多队列,确保 irqbalance 运行,或将中断绑核,避免单核顶不住。

3. iptables 策略设计:三道闸

RHEL 8 默认是 iptables-nft 后端。若你启用 firewalld,建议使用 firewalld 的 direct rules 或停用 firewalld + 安装 iptables-services(保持规则持久化)。以下演示使用 iptables-nft 语法,legacy 同理。

3.1 链路结构(我当晚画在白板上的三道闸)

  • 白名单/已建立连接快速放行
  • SYNPROXY 在边缘“挡刀”(对 80/443)
  • 按源限速 + 灰名单(ipset):超阈值的源,降级或丢弃
  • 并发连接上限(connlimit):防止长连接耗尽

3.2 准备 ipset 与自定义链

# 临时加载模块
modprobe nf_synproxy_core

# 灰名单:超阈值丢弃(5 分钟自动过期)
ipset create graylist hash:ip timeout 300 -exist

# 自定义链
iptables -N RATE_LIMIT_443
iptables -N RATE_LIMIT_80

3.3 基本放行与拦截骨架

# 1) 已建立/相关
iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 2) 本地环回
iptables -A INPUT -i lo -j ACCEPT

# 3) 基本管理口
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m hashlimit \
  --hashlimit-above 10/min --hashlimit-burst 10 --hashlimit-mode srcip \
  --hashlimit-name ssh_slow -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 4) 非法状态
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

3.4 SYNPROXY:在握手前就筛掉“半吊子”

核心思路:把 80/443 上的 SYN 标记为 NOTRACK,随后由 SYNPROXY 在 INPUT 链上代为应答。通过校验再放行真正的三次握手。

# raw 表:对 80/443 的 SYN 不进入 conntrack
iptables -t raw -I PREROUTING -i eth0 -p tcp -m multiport --dports 80,443 \
  -m tcp --syn -j CT --notrack

# INPUT:SYNPROXY 折返
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 80,443 \
  -m conntrack --ctstate NEW -m tcp -j SYNPROXY \
  --sack-perm --timestamp --wscale 7 --mss 1460

# 对未通过校验或异常状态的连接直接清理
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 80,443 \
  -m conntrack --ctstate INVALID,UNTRACKED -j DROP

注意:SYNPROXY 并不会终止正常的连接建立,但会显著减少半开连接对后端的冲击。启用后要跟踪 NGINX/应用的握手延迟,通常影响可以忽略。

3.5 按源限速(hashlimit)+ 灰名单(ipset)

我用 hashlimit 做“第一次教育”,ipset 灰名单做“短期隔离”。

# 先拦灰:在灰名单里的直接丢
iptables -I INPUT -m set --match-set graylist src -j DROP

# 443 新建连接进入限速链
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j RATE_LIMIT_443

# 每个源 IP 允许 50 新建连接/秒,突发 100;超限则加入灰名单并丢弃
iptables -A RATE_LIMIT_443 -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name hl_443 -j SET --add-set graylist src
iptables -A RATE_LIMIT_443 -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name hl_443 -j DROP
# 未命中超限的返回继续流程(最后由服务端口规则放行)
iptables -A RATE_LIMIT_443 -j RETURN

# 80 端口同理(阈值更宽松)
iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j RATE_LIMIT_80
iptables -A RATE_LIMIT_80 -m hashlimit \
  --hashlimit-above 200/second --hashlimit-burst 400 \
  --hashlimit-mode srcip --hashlimit-name hl_80 -j SET --add-set graylist src
iptables -A RATE_LIMIT_80 -m hashlimit \
  --hashlimit-above 200/second --hashlimit-burst 400 \
  --hashlimit-mode srcip --hashlimit-name hl_80 -j DROP
iptables -A RATE_LIMIT_80 -j RETURN

3.6 并发连接上限(connlimit)

防止单个源长期占满 worker。

# 每源对 443 的并发不超过 200(按实际业务并发调整)
iptables -A INPUT -p tcp --syn --dport 443 -m connlimit \
  --connlimit-above 200 --connlimit-mask 32 -j REJECT --reject-with tcp-reset

3.7 服务端口最终放行

iptables -A INPUT -p tcp -m multiport --dports 80,443,50051 -j ACCEPT
# 其他一概丢弃
iptables -A INPUT -j DROP

持久化(停用 firewalld 的前提下):
dnf install -y iptables-services && systemctl enable --now iptables
service iptables save(RHEL 8 可用 iptables-save > /etc/sysconfig/iptables)

4. tc 流控策略:在网卡“门口”就刹车

iptables 控“连接数”和“新建速率”,tc 控“带宽/pps 的硬指标**”。我们用 ifb 把 ingress 重定向出去,做分级限速。

4.1 准备 ifb 与 ingress 重定向

modprobe ifb numifbs=1
ip link add ifb0 type ifb
ip link set ifb0 up

# 给 eth0 挂 ingress 钩子,把所有 ingress 重定向到 ifb0
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 \
  action mirred egress redirect dev ifb0

4.2 在 ifb0 上建 HTB 分层 + fq_codel

1:10 正常类:绝大多数流量使用(上限接近口速)。

1:30 可疑类:命中“高频新连”标记的流量,强力限速。

关键:用 iptables -t mangle 给“可疑”打 fwmark=10,tc 用 match fw 归类。

# 先在 mangle 打标:超过阈值的 SYN 源打 mark=10
iptables -t mangle -N MARK_ABUSE
iptables -t mangle -A PREROUTING -p tcp --syn -j MARK_ABUSE
iptables -t mangle -A MARK_ABUSE -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name mark_abuse -j MARK --set-mark 10
iptables -t mangle -A MARK_ABUSE -j RETURN

# ifb0:HTB 根队列
tc qdisc add dev ifb0 root handle 1: htb default 10
tc class add dev ifb0 parent 1: classid 1:1 htb rate 10gbit ceil 10gbit

# 正常类(1:10)
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 9gbit ceil 10gbit
tc qdisc add dev ifb0 parent 1:10 handle 10: fq_codel

# 可疑类(1:30)——强限制,按业务调
tc class add dev ifb0 parent 1:1 classid 1:30 htb rate 50mbit ceil 100mbit
tc qdisc add dev ifb0 parent 1:30 handle 30: fq_codel

# 过滤:fwmark=10 的流量丢到 1:30
tc filter add dev ifb0 parent 1: protocol ip handle 10 fw flowid 1:30

如果你更在意“包数”而不是“带宽”,可以在 1:30 再加 police:

tc filter add dev ifb0 parent 1: protocol ip handle 10 fw \
  police rate 60mbit burst 5mbit drop flowid 1:30

5. 一键脚本与 systemd 持久化

把关键动作收敛成两个文件:iptables-setup.sh 与 tc-setup.sh,配合 systemd。

/usr/local/sbin/iptables-setup.sh

#!/usr/bin/env bash
set -euo pipefail

modprobe nf_synproxy_core
ipset create graylist hash:ip timeout 300 -exist

# 清空重建(谨慎:若是远程操作,先开个带超时的 tmux)
iptables -F; iptables -X
iptables -t raw -F; iptables -t mangle -F

iptables -N RATE_LIMIT_443
iptables -N RATE_LIMIT_80

iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -I INPUT -m set --match-set graylist src -j DROP

# SSH 慢速阈值
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m hashlimit \
  --hashlimit-above 10/min --hashlimit-burst 10 --hashlimit-mode srcip \
  --hashlimit-name ssh_slow -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# SYNPROXY
iptables -t raw -I PREROUTING -i eth0 -p tcp -m multiport --dports 80,443 \
  -m tcp --syn -j CT --notrack
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 80,443 \
  -m conntrack --ctstate NEW -m tcp -j SYNPROXY \
  --sack-perm --timestamp --wscale 7 --mss 1460
iptables -A INPUT -i eth0 -p tcp -m multiport --dports 80,443 \
  -m conntrack --ctstate INVALID,UNTRACKED -j DROP

# 限速 + 灰名
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j RATE_LIMIT_443
iptables -A RATE_LIMIT_443 -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name hl_443 -j SET --add-set graylist src
iptables -A RATE_LIMIT_443 -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name hl_443 -j DROP
iptables -A RATE_LIMIT_443 -j RETURN

iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j RATE_LIMIT_80
iptables -A RATE_LIMIT_80 -m hashlimit \
  --hashlimit-above 200/second --hashlimit-burst 400 \
  --hashlimit-mode srcip --hashlimit-name hl_80 -j SET --add-set graylist src
iptables -A RATE_LIMIT_80 -m hashlimit \
  --hashlimit-above 200/second --hashlimit-burst 400 \
  --hashlimit-mode srcip --hashlimit-name hl_80 -j DROP
iptables -A RATE_LIMIT_80 -j RETURN

iptables -A INPUT -p tcp -m multiport --dports 80,443,50051 -j ACCEPT
iptables -A INPUT -j DROP

# mangle 打标
iptables -t mangle -N MARK_ABUSE || true
iptables -t mangle -F MARK_ABUSE
iptables -t mangle -A PREROUTING -p tcp --syn -j MARK_ABUSE
iptables -t mangle -A MARK_ABUSE -m hashlimit \
  --hashlimit-above 50/second --hashlimit-burst 100 \
  --hashlimit-mode srcip --hashlimit-name mark_abuse -j MARK --set-mark 10
iptables -t mangle -A MARK_ABUSE -j RETURN

/usr/local/sbin/tc-setup.sh

#!/usr/bin/env bash
set -euo pipefail

modprobe ifb numifbs=1 || true
ip link add ifb0 type ifb 2>/dev/null || true
ip link set ifb0 up

tc qdisc del dev eth0 ingress 2>/dev/null || true
tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 \
  action mirred egress redirect dev ifb0

tc qdisc del dev ifb0 root 2>/dev/null || true
tc qdisc add dev ifb0 root handle 1: htb default 10
tc class add dev ifb0 parent 1: classid 1:1 htb rate 10gbit ceil 10gbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 9gbit ceil 10gbit
tc qdisc add dev ifb0 parent 1:10 handle 10: fq_codel
tc class add dev ifb0 parent 1:1 classid 1:30 htb rate 50mbit ceil 100mbit
tc qdisc add dev ifb0 parent 1:30 handle 30: fq_codel
tc filter add dev ifb0 parent 1: protocol ip handle 10 fw flowid 1:30

systemd 单元(开机自动套上“盔甲”)

/etc/systemd/system/edge-iptables.service

[Unit]
Description=Edge iptables hardening
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/iptables-setup.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

/etc/systemd/system/edge-tc.service

[Unit]
Description=Edge tc ingress shaping
After=network-online.target edge-iptables.service
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/tc-setup.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

chmod +x /usr/local/sbin/*-setup.sh
systemctl daemon-reload
systemctl enable --now edge-iptables.service edge-tc.service

6. 验证与监控:不是“设上去”就完事

核心观察点与命令

关注点 命令/思路
SYN 队列 & 状态 `ss -ant state syn-recv
conntrack 压力 conntrack -Scat /proc/sys/net/netfilter/nf_conntrack_count
队列与丢包 tc -s qdisc show dev ifb0tc -s class show dev ifb0
iptables 命中 iptables -v -Liptables -t mangle -v -L
网卡中断/软中断 mpstat -P ALL 1/proc/interruptssar -n DEV 1

对比数据(当晚留存的关键指标)

指标 改造前 改造后(5 分钟)
443 新建 conn/s(峰) ~35k ~4.2k
SYN_RECV 数 60k+ < 1k
softirq 占比 90%+ ~35%
P99 TLS 握手 2.1s 180ms
NGINX 5xx 大量 消失
CPU load 40+ 8~12

7. 门槛与坑:我踩过的,你别再踩

  • firewalld vs iptables:RHEL 8 默认 firewalld(nftables),你用 iptables 直写容易“失联”。要么用 firewalld 的 direct 规则,要么停火墙上 iptables-services。别混搭。
  • iptables-nft 与 legacy:有 alternatives --config iptables,确认你执行的就是预期后端,不然规则“不生效但没报错”。
  • SYNPROXY 顺序:raw/PREROUTING 的 --notrack 必须早于 INPUT 的 SYNPROXY,否则 conntrack 依旧被打爆。
  • ifb 内核模块:有内核裁剪的场景会没有 ifb,先 modprobe ifb,失败就得装内核模块或换内核。
  • 阈值过严:移动端弱网、CDN 回源高并发都会短时冲高,hashlimit 的 burst 要给够;灰名单 timeout 别过长,避免误伤。
  • 异步路由:rp_filter=2(loose)能避免多宿主/异路由下的误杀。
  • X710 固件:老固件在大 PPS 下会丢中断,升级 NVM/驱动很关键(我在另一台上遇过,更新后症状消失)。
  • 持久化:tc 规则不会自动持久化,一定要有 systemd。否则重启后你会以为“为什么今天又打不住”。

8. 何时上调/下调阈值(经验公式)

hashlimit:

Web/API:30~100 /s / src,burst 2×~4×

gRPC/长连:以并发为主控,connlimit 50~200,降低 hashlimit

灰名单:timeout 120~600s,命中阈值可按“业务容忍度”乘以 1.5 做安全边界。

可疑类带宽(HTB 1:30):

先“紧”后“松”:50~200 Mbit/s 观察 10~30 分钟,根据误伤情况放宽。

9. 回归业务:从“防”到“稳”的闭环

我把最后一条命令敲完,tc -s qdisc 的丢包曲线开始漂亮地下滑。支付回调恢复,报警静了。

我在工单里写下结论:“把攻击留在网卡口外面”。第二天团队例会上,我们把这套策略固化成 边缘模板:新上香港节点,先上基线,再按业务阈值套闸;同时把上游的 RTBH(黑洞社区) 流程也打通,真碰到超大流量就走“上游扛”。

偶尔夜里还会响起同样的短信,但我知道,iptables 的闸门和 tc 的刹车已经在那里守着了。

10. 附:快速回放(Checklist)

  •  调整 sysctl / ethtool,确认多队列/irqbalance。
  •  选择 firewalld 或 iptables-services,一条路走到底。
  •  ipset graylist + hashlimit + connlimit 上线。
  •  80/443 启用 SYNPROXY(记得 raw/PREROUTING --notrack)。
  •  ifb + tc htb/fq_codel,用 fwmark 收敛“疑似恶意”流量。
  •  systemd 持久化两套脚本。
  •  观察 ss/conntrack/tc 指标,按业务收敛阈值。

小贴士

  • 香港节点面对 高延迟+跨境+移动网络 的用户群,误伤成本高。任何限速都应 先监控 24 小时 再固化。
  • 这套组合拳的核心不是“复杂”,而是分层:SYNPROXY 压半开、hashlimit/ipset 控源、tc 控物理资源。分层清晰,问题就好排查。
  • 真遇到打穿链路的流量,别逞强:让上游的 ACL/清洗/RTBH 出手,这是工程的理性。
目录结构
全文