防 TCP 攻击的完整方法
核心结论:Go 应用层无法防御 SYN Flood,因为攻击包在三次握手阶段就卡死在内核队列,根本到不了你的 net.Listen 。防御必须分层实施。
第一层:内核参数(抗 SYN Flood 的根本)
SYN Flood 原理:攻击者伪造大量源 IP 发送 SYN 包,但不完成第三次握手。这些半开连接占满内核的 SYN 队列,导致正常用户连不上。
必须调整的内核参数:
bash
# /etc/sysctl.conf
# 1. 启用 SYN Cookies(核心防御)
# 队列满时,内核用加密 cookie 代替存储半开连接,无需占用队列
net.ipv4.tcp_syncookies = 1
# 2. 增大 SYN 半连接队列
net.ipv4.tcp_max_syn_backlog = 8192
# 3. 增大已完成连接队列
net.core.somaxconn = 65535
# 4. 减少 SYN-ACK 重试次数(缩短半开连接存活时间)
net.ipv4.tcp_synack_retries = 2
# 5. 复用 TIME-WAIT 连接,加快资源回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
执行 sysctl -p 生效。
关键说明:tcp_syncookies=1 是自动启用的(仅检测到洪泛时激活),正常情况下不增加开销。它让服务器不存储半开连接状态,而是把状态编码进 SYN-ACK 的序列号中,收到 ACK 后再重建,从根本上避免队列耗尽。
第二层:iptables 限速(网络边界拦截)
bash
# 限制单 IP 的 SYN 包速率(每秒最多 5 个,突发 20 个)
iptables -A INPUT -p tcp --syn -m limit --limit 5/s --limit-burst 20 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP
# 限制单 IP 并发连接数
iptables -A INPUT -p tcp --dport 8080 -m connlimit --connlimit-above 50 -j DROP
注意:iptables 在协议栈中处理,CPU 开销比 XDP/BPF 高,但足够应对常见攻击。
第三层:Go 应用层(防连接后攻击)
你之前设计的“读 token 才处理”逻辑,防的是完成握手后的攻击,但必须在 Go 代码中补充连接生命周期管理。
1. 给所有连接设置超时(核心)
没有超时,攻击者连上后不发数据,goroutine 永久阻塞。
go
func handleConn(conn net.Conn) {
defer conn.Close()
// 设置读写超时(必须!)
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
// 读取 token
token, err := readToken(conn)
if err != nil {
return // 超时或读取失败,关闭连接
}
// 校验 token
if !validateToken(token) {
return
}
// 握手成功,取消超时或重置为业务超时
conn.SetReadDeadline(time.Time{}) // 取消超时
// 业务循环...
}
关键:每个连接必须有读超时,否则攻击者连上不发数据会耗尽你的 goroutine 和文件描述符。
2. 用 net.ListenConfig 控制底层 socket
go
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
// 启用 TCP Keep-Alive
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)
// 启用 SO_REUSEPORT(多进程/多协程分散连接压力,需 Go 1.19+)
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
})
},
}
ln, _ := lc.Listen(context.Background(), "tcp", ":8080")
SO_REUSEPORT 让多个监听 socket 绑定同一端口,内核负载均衡分发连接,避免单队列瓶颈。
3. 连接数上限(防耗尽)
go
var (
currentConns int64
maxConns = int64(10000)
)
func handleConn(conn net.Conn) {
n := atomic.AddInt64(¤tConns, 1)
defer atomic.AddInt64(¤tConns, -1)
if n > maxConns {
conn.Close()
return
}
// ...
}
第四层:反向代理(生产环境必用)
Nginx/HAProxy 做第一道防线:
nginx
# Nginx 限速配置
limit_req_zone $binary_remote_addr zone=conn_limit:10m rate=10r/s;
server {
listen 8080;
limit_req zone=conn_limit burst=20 nodelay;
proxy_pass http://backend;
}
优势:Nginx 有成熟的连接管理、限速、SYN Flood 防护,比裸 TCP 服务安全得多。
第五层:XDP/BPF(极致防护,可选)
XDP 在网卡驱动层丢包,不走协议栈、不建连接,单核可达百万 PPS。
c
// XDP 程序:在网卡层丢弃高频恶意包
SEC("xdp")
int xdp_drop_highfreq(struct xdp_md *ctx) {
// 查 map,超过阈值的 IP 直接 XDP_DROP
// 在协议栈之前拦截,零开销
}
适用场景:遭受持续大规模 DDoS 时,XDP 是最后的硬件级防线。
检查清单
层级 措施 是否必须
内核 tcp_syncookies=1 必须
内核 tcp_max_syn_backlog 调大 必须
iptables SYN 限速 + 连接数限制 必须
Go 所有连接设读超时 必须
Go 连接数上限 推荐
反向代理 Nginx/HAProxy 生产环境必须
XDP/BPF 网卡层丢包 大规模攻击时