如何在香港服务器 CentOS 7 环境中结合 iptables 与 DPI 防御跨境恶意流量?

我第一次在香港机房接到这个告警,是凌晨 2:17,监控屏上南向出口的 PPS 曲线突然抽成了一根针——外网跨境源的 SYN 洪泛、SSH 暴力、TLS 扫描混在一起,把一台跑 API 的节点压到了 load 30+。我拎着网线和一杯已经凉掉的美式,边看 Netflow 采样边想:先用 iptables 把粗颗粒流量挡在门口,再用 DPI 把“聪明的坏家伙”挑出来。
这篇就是那一夜到上线稳定这几周,我在 CentOS 7 上落地“iptables + DPI(Suricata)组合拳”的完整过程与坑点。写给需要马上动手的新手,也写给想要细节和落地经验的老手。
数据中心场景与基础信息
业务与威胁画像
业务:REST API + 静态资源,80/443/22 少量开放;部分域名在海外广告投放,天然会“招风”。
威胁:
- 海外肉鸡对 22/80/443 的扫描 & 暴力;
- TLS ClientHello 指纹(JA3)异常的批量探测;
- DNS 隧道与 HTTP 可疑 UA;
- 间歇性小流量 SYN/ACK 洪泛 + 连接耗尽。
机房与拓扑(简化)
[Internet]
|
[运营商汇聚]
|
[上联万兆交换机]----(旁路抓包端口)
|
[防护节点] (本教程主角)
|
[业务交换机]
|
[应用/静态节点群]
硬件配置(防护节点)
| 项目 | 参数 |
|---|---|
| 机型 | 1U 双路 |
| CPU | Intel Xeon E5-2680 v4 ×2 |
| 内存 | 128 GB |
| 硬盘 | NVMe 1TB(日志 & EVE)+ SATA 480GB(系统) |
| 网卡 | Intel X520-DA2(10G,驱动 ixgbe) |
| 带宽 | 上下行 10Gbps(95 分位计费),常态 < 1Gbps |
| 旁路镜像 | 交换机 SPAN 到防护节点第二口(做可选 IDS/回放) |
软件版本
| 组件 | 版本/说明 |
|---|---|
| OS | CentOS 7.9(3.10 内核) |
| iptables | iptables-services(传统 xtables) |
| Suricata | 6.x(IPS 模式) |
| 日志 | EVE JSON + journald,辅助用 jq/goaccess |
| 可选 | xtables-addons(xt_geoip)或 ipset + 自建地理/ASN 列表 |
设计思路:三道防线
L3/L4 粗过滤(iptables)
- 基于 ipset 的黑/白名单(恶意 ASN、临时封禁 IP、业务允许国)。
- raw 表前置拒绝,绕过 conntrack,节省 CPU。
- SYNPROXY + hashlimit 对 80/443/22 的速率与握手压力做削峰。
- “只准进的端口”做最小暴露面;健康检查与管理口走专线白名单。
L7 深度检测(DPI/Suricata)
- 通过 NFQUEUE 或 AF_PACKET inline 接入,按规则对 TLS/HTTP/DNS/SSH 进行检测与阻断。
- 规则关注:异常 JA3、恶意 UA、DNS 隧道、HTTP 方法滥用、SSH 暴力等。
自治惩罚(动态拉黑)
- Suricata 事件实时写 eve.json,用小脚本把触发特定规则的源 IP 自动加入 ipset 黑名单,带 TTL 自动过期。
- 灰度:先 IDS 观察 24–48 小时,再逐步启用 drop。
步骤一:系统与内核参数调优(CentOS 7)
创建 /etc/sysctl.d/99-net-tuning.conf:
net.core.somaxconn = 4096
net.core.netdev_max_backlog = 250000
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_sack = 0
# 连接跟踪表
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_buckets = 262144
# 路由与过滤
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
# UDP 缓冲
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
net.ipv4.udp_rmem_min = 131072
net.ipv4.udp_wmem_min = 131072
应用:
sysctl --system
坑点:多上联/异地回程时,rp_filter=1 会把合法包判成假冒丢掉,务必置 0。
步骤二:准备 iptables 与持久化
yum install -y iptables-services
systemctl disable firewalld --now
systemctl enable iptables --now
步骤三:准备 ipset 与(可选)地理库
3.1 基础集合
ipset create allow_mgmt hash:ip timeout 0
ipset create drop_temp hash:ip timeout 86400
ipset create drop_asn hash:net timeout 0
ipset create allow_geo hash:net timeout 0
把办公出口、拨测节点等加白:
ipset add allow_mgmt 203.0.113.10
3.2 可选:xtables-addons 的 GeoIP(或自建)
更稳妥:维持一份我们认可的数据源(GeoLite2/ASN 自建 CSV),每天构建 ipset 段。
注意:xt_geoip 需要手工更新国家库;库老化=误伤/漏拦的源头之一。
示例(把某些高风险地区先做速率限制而非一刀切):
# 以 ipset 承载网段(推荐)
# 列表文件到位后:每晚 cron 同步刷新 allow_geo / drop_asn
步骤四:iptables 粗过滤策略(含 raw 表与 SYNPROXY)
目标:先薄、后精。把不需要的、明显恶意的,尽量在最前端就丢掉,减少 conntrack 与 DPI 压力。
创建 /etc/sysconfig/iptables(片段,按需调整):
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# 1) 对已知恶意源直接丢弃,绕过 conntrack
-A PREROUTING -m set --match-set drop_temp src -j DROP
-A PREROUTING -m set --match-set drop_asn src -j DROP
COMMIT
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# 基础允许
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# 管理白名单(SSH/Bastion/健康检查)
-A INPUT -p tcp --dport 22 -m set --match-set allow_mgmt src -j ACCEPT
# 对外服务端口(示例 80/443)
-A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -m hashlimit \
--hashlimit-above 2000/sec --hashlimit-burst 4000 --hashlimit-mode srcip \
--hashlimit-name http_rate -j DROP
# SYNPROXY(防半开耗尽)
-A INPUT -p tcp -m tcp --syn -m multiport --dports 80,443 -m conntrack --ctstate NEW \
-j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
-A INPUT -p tcp -m tcp -m multiport --dports 80,443 -m conntrack --ctstate INVALID -j DROP
# 允许通过 DPI(NFQUEUE 模式时启用,见后文)
# -A INPUT -p tcp -m multiport --dports 80,443 -j NFQUEUE --queue-num 0
# -A INPUT -p udp --dport 53 -j NFQUEUE --queue-num 1
# SSH 基线速率限制(非白名单)
-A INPUT -p tcp --dport 22 -m recent --name ssh --set
-A INPUT -p tcp --dport 22 -m recent --name ssh --update --seconds 60 --hitcount 10 -j DROP
-A INPUT -p tcp --dport 22 -j NFQUEUE --queue-num 2
# 其他默认丢弃
-A INPUT -p tcp -m tcp --dport 0:65535 -j REJECT --reject-with tcp-reset
-A INPUT -p udp -j DROP
COMMIT
应用:
systemctl restart iptables
iptables -S | sed -n '1,80p'
坑点:SYNPROXY 对四层负载均衡/健康检查可能引发“探测失败”,先在灰度环境验证探活策略;必要时针对探测源跳过 SYNPROXY。
步骤五:DPI 接入方式二选一
方案 A:NFQUEUE(最通用、易控流量粒度)
优点:按端口/方向把特定流量送入队列,Suricata 做用户态判决;精细。
缺点:大流量时需要 多队列+CPU 绑核,不当配置会卡队列。
5.A.1 Suricata 安装与基础配置
yum install -y epel-release
yum install -y suricata jq
编辑 /etc/suricata/suricata.yaml 的 af-packet 先禁用,用 nfqueue:
nfqueue:
mode: repeat
batchcount: 20
queue: 0
verdict: true
bypass: yes
为了多核并行,可开多队列:
nfqueue:
mode: repeat
batchcount: 40
queue-balance: [0,5] # 使用 0..5 共 6 个队列
iptables 规则对齐(替换前面注释的行):
iptables -I INPUT -p tcp -m multiport --dports 80,443 -j NFQUEUE --queue-balance 0:5
iptables -I INPUT -p udp --dport 53 -j NFQUEUE --queue-balance 0:5
队列长度与内核参数(避免丢包/阻塞):
sysctl -w net.netfilter.nf_queue_qmax=4096
5.A.2 绑核运行
# 假设 CPU 0-11,为 Suricata 预留 4-8 个核
taskset -c 4-11 systemctl start suricata
systemctl enable suricata
方案 B:AF_PACKET Inline(内核态更稳、吞吐高)
优点:更接近线速,减少用户态队列瓶颈;
缺点:需要对网卡/Offload 细节把握,配置偏“硬核”。
suricata.yaml(核心片段):
af-packet:
- interface: eth0
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
tpacket-v3: yes
use-mmap: yes
ring-size: 300000
block-size: 65536
buffer-size: 64512
copy-mode: ips
copy-iface: eth0
checksum-checks: auto
# inline 模式:
# IPS: setting "inline: yes" in default-rule-path or run with --af-packet
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: /var/log/suricata/eve.json
Offload 注意(否则出现校验伪告警/掉包):
ethtool -K eth0 gro off lro off gso off tso off
启动:
suricata --af-packet -c /etc/suricata/suricata.yaml -D
步骤六:DPI 规则与示例(HTTP/TLS/DNS/SSH)
规则可用 ET/Open Rules 打底,结合业务定制;此处给几个“跨境恶意流量”高命中示例。
6.1 TLS/JA3 异常(常见扫描器)
/etc/suricata/rules/local.rules:
# 某些已知扫描器 JA3 指纹(示例哈希)
drop tls any any -> $HOME_NET any (msg:"TLS JA3 known scanner"; \
ja3_hash; content:"76972c4f4b1a..."; startswith; sid:900001; rev:1;)
# 没有 SNI 的 HTTPS 握手(大规模探测时常见,但谨慎启用)
alert tls any any -> $HOME_NET 443 (msg:"TLS no SNI"; tls_sni; content:""; \
isdataat:!1,relative; threshold:type both, track by_src, count 10, seconds 60; \
sid:900002; rev:1;)
6.2 HTTP 可疑 UA/方法滥用
drop http any any -> $HOME_NET 80 (msg:"HTTP suspicious UA masscan/zgrab"; \
http.user_agent; pcre:"/masscan|zgrab|curl\/7\.(2[0-9]|3[0-9])/Ri"; \
classtype:bad-unknown; sid:900010; rev:2;)
6.3 DNS 隧道/ANY 滥用
drop dns any any -> $HOME_NET 53 (msg:"DNS long label suspicious"; \
dns.query; pcre:"/([A-Za-z0-9]{40,})/"; sid:900020; rev:1;)
alert dns any any -> $HOME_NET 53 (msg:"DNS ANY flood"; \
dns.qtype; content:"ANY"; threshold:type both, track by_src, count 20, seconds 60; \
sid:900021; rev:1;)
6.4 SSH 暴力(借 Suricata 计数 + 动态封禁)
alert ssh any any -> $HOME_NET 22 (msg:"SSH brute force"; flow:to_server; \
threshold:type both, track by_src, count 15, seconds 120; sid:900030; rev:1;)
步骤七:事件联动 —— 自动加入 ipset 黑名单
创建 /usr/local/bin/eve2ipset.sh:
#!/usr/bin/env bash
EVE=/var/log/suricata/eve.json
SET=drop_temp
TAIL=/usr/bin/tail
JQ=/bin/jq
$TAIL -Fn0 $EVE | while read line; do
# 针对我们定义的 sid 列表做动作
echo "$line" | $JQ -r 'select(.alert.sid|inside([900001,900010,900020,900030]))
| .src_ip' | while read ip; do
# 加入黑名单 24 小时自动过期
ipset add $SET $ip timeout 86400 2>/dev/null && \
logger -t eve2ipset "blocked $ip by DPI"
done
done
服务化:
cat >/etc/systemd/system/eve2ipset.service <<EOF
[Unit]
Description=EVE to IPSet autoblocker
After=suricata.service
[Service]
ExecStart=/usr/local/bin/eve2ipset.sh
Restart=always
[Install]
WantedBy=multi-user.target
EOF
chmod +x /usr/local/bin/eve2ipset.sh
systemctl daemon-reload
systemctl enable --now eve2ipset
步骤八:灰度策略与回滚
- 第 1~2 天:Suricata 只 alert,不 drop,观察 eve.json 中业务误报;
- 第 3 天:对某一端口(如 22)启用 drop,并开启 eve2ipset;
- 第 5 天:扩大到 80/443,保持 hashlimit/SYNPROXY;
随时回滚:
- 停 Suricata:systemctl stop suricata;
- 移除 NFQUEUE 规则:iptables -D INPUT ... NFQUEUE ...;
- 清空临时黑名单:ipset flush drop_temp。
验证与基准结果(我们的实测样本)
| 指标 | 灰度前(仅 L4) | 组合拳上线后(L4+L7) |
|---|---|---|
| PPS 峰值可承载(不丢包) | ~ 600k | ~ 900k |
| 80/443 新建连接 95 分位握手时延 | 12.3 ms | 10.8 ms |
| CPU(单机总) | 42–68% | 38–55% |
| 误封率(按 7 天解除) | — | < 0.05% |
| SSH 暴力成功率 | 低但存在 | 近 0(秒级封禁) |
注:NFQUEUE 模式下,把队列扩展到 0..5 并进行绑核后,抖动明显下降;AF_PACKET inline 模式下关闭 LRO/GRO 后恢复正常。
运行与排障清单(我踩过的坑)
NFQUEUE 堵塞:
- 症状:suricata 日志出现 packet queue drop,延迟拉高;
- 处理:提高 net.netfilter.nf_queue_qmax,queue-balance 多队列 + Suricata runmode: workers,并绑核。
校验和/Offload 干扰:
- 症状:DPI 报告 checksum invalid;
- 处理:ethtool -K eth0 gro/lro/gso/tso off。
SYNPROXY 与探活冲突:
- 对健康检查源(如 100.64.0.0/10)在 raw 或 filter 前置 ACCEPT,绕开。
conntrack 表爆:
- 现象:dmesg 出现 nf_conntrack: table full, dropping packet;
- 处理:提升 nf_conntrack_max/buckets,并用 raw 表尽早丢弃无需跟踪的流。
地理库老化/ASN 变更:
- 误拦合法用户或漏拦恶意;
- 处理:不要把地理过滤当银弹。用 hashlimit/SYNPROXY 限流 + DPI 精准识别,地理库只作“权重”。
Suricata 规则误报:
- 调整为 threshold 或改为 alert 观察期,再升级到 drop;
- 给每条本地规则明确 sid,便于联动脚本精确匹配。
双网口与路由策略:
- 旁路抓包与业务口混用时注意 rp_filter 与多路由表,避免反向路径校验误封。
运维日常与可视化
eve.json 快速视图:
jq -r 'select(.alert) | "\(.timestamp)\t\(.src_ip)\t\(.alert.signature)"' /var/log/suricata/eve.json | tail
统计 top 源:
jq -r 'select(.alert) | .src_ip' /var/log/suricata/eve.json | sort | uniq -c | sort -nr | head
Nginx 访问日志与 Suricata 告警按 IP 关联,确认是否误封。
安全上线 checklist(可打印)
- sysctl 生效,rp_filter=0;
- iptables 规则含:raw 前置丢弃、SYNPROXY、hashlimit、NFQUEUE/或 AF_PACKET;
- ipset 集合及 TTL 封禁脚本运行;
- Suricata 先 IDS 再 IPS,本地规则 sid 管理清晰;
- Offload 关闭,PPS 压测通过;
- 回滚路径可一键执行(stop Suricata + 删除 NFQUEUE + flush ipset)。
参考脚本:一键部署骨架(可按需裁剪)
#!/usr/bin/env bash
set -e
echo "[1/6] Install deps"
yum install -y epel-release iptables-services suricata jq ipset
echo "[2/6] Sysctl tuning"
cat >/etc/sysctl.d/99-net-tuning.conf <<'EOF'
# (同上内容略)
EOF
sysctl --system
echo "[3/6] ipset sets"
for s in allow_mgmt drop_temp drop_asn allow_geo; do
ipset create $s hash:ip timeout 0 2>/dev/null || true
done
ipset flush drop_temp
echo "[4/6] iptables rules"
cat >/etc/sysconfig/iptables <<'EOF'
# (同上规则片段,按需粘贴完整版)
EOF
systemctl enable --now iptables
echo "[5/6] Suricata + NFQUEUE"
# 这里默认 NFQUEUE,如果用 AF_PACKET 改 suricata.yaml 即可
sed -i 's/^# *- local.rules/- local.rules/' /etc/suricata/suricata.yaml
systemctl enable --now suricata
echo "[6/6] eve2ipset autoblocker"
cat >/usr/local/bin/eve2ipset.sh <<'EOS'
# (同上脚本)
EOS
chmod +x /usr/local/bin/eve2ipset.sh
cat >/etc/systemd/system/eve2ipset.service <<'EOS'
# (同上 service)
EOS
systemctl daemon-reload
systemctl enable --now eve2ipset
echo "Done."
成本与收益
成本:一次性的规则与联动脚本投入;NFQUEUE 调优时间较长;AF_PACKET 对网卡/内核有认知门槛。
收益:把“脏流量”大头挡在 L3/L4,L7 精确识别再做自治惩罚,在不依赖昂贵硬件清洗的前提下,把稳定性和安全性拉到一个新台阶。
组合拳上线的第二周,一个跨境扫描的波峰又来了。
这回大屏上的红线很安静,Suricata 的 eve.json 一条条写着“JA3 known scanner dropped”,drop_temp 集合的计数在嗒嗒往上跳。负载稳在 0.x,NOC 小伙打了个哈欠问我:“还用不着清洗厂商吧?”
我把最后一口冷咖啡干掉,笑了:“今晚让软件先顶着。”
对我来说,这套在 CentOS 7 上打磨出来的 iptables + DPI 防御,不是某种完美主义,而是业务在风口浪尖活下来的务实主义。你也可以很快把它复刻到你的香港节点上,然后按你的业务去打磨属于你自己的本地规则集。
附:常用命令速查
# 查看队列统计
watch -n1 "nfnl_osf -l 2>/dev/null || cat /proc/net/netfilter/nfnetlink_queue"
# Suricata 运行态检查
suricata -T -c /etc/suricata/suricata.yaml # 测试配置
journalctl -u suricata -f
# ipset / iptables
ipset list drop_temp | head
iptables -L -n -v --line-numbers
# 快速回滚
systemctl stop suricata
iptables -S | grep NFQUEUE | sed 's/^-A /-D /' | bash
ipset flush drop_temp