下次一定试试。。。
在面对 SYN 攻击时,调小半连接队列(SYN Queue)的大小通常不是一种有效的防御策略。相反,这可能会使服务器更容易被攻击者利用,因为半连接队列的空间更少,容易被填满,导致合法的连接请求无法得到响应。
应对 SYN 攻击的有效策略包括:
调小半连接队列通常不会帮助缓解 SYN 攻击,反而可能使服务器的可用性降低,建议使用以上提到的其他防御措施。
如果你愿意牺牲可用性来应对 SYN 攻击,调小半连接队列的大小是可行的,但它只能作为一种极端情况下的策略。这种做法的逻辑在于:
然而,这种方法的缺点也很明显:
因此,调小半连接队列的做法更多是为了监控或紧急处理,而不是长久的防御手段。它可能在一些特殊场景下有用,比如在你已经部署了其他防御机制,并且希望尽快检测并处理攻击时。但从长期和可用性的角度来看,还是应该优先采用其他更加稳健的防御策略,比如使用 SYN Cookies、调整超时设置、或者借助专门的防御设备和服务来应对 SYN 攻击。
这个方案可以在特定情况下有效,尤其是当你有能力在攻击期间手动介入并采取措施时。具体而言,调小半连接队列可以让服务器在遭受 SYN 攻击时迅速显现问题,而通过定时启动 sshd
允许你在预定时间窗口内登录服务器,进行维护或关闭受影响的服务。
优点:
crontab
定时启动 sshd
,你可以限制攻击者在非授权时间访问服务器,减少暴露面。sshd
启动的时间段登录服务器,对系统进行手动调整或关闭服务。缺点:
sshd
非启动时间将无法访问服务器,这可能会影响正常运维和管理。sshd
启动了,登录服务器可能仍然困难,因为 SYN 攻击会影响到你连接的稳定性。这个方案适合需要极度控制风险的环境,特别是当你无法部署更先进的防御措施时。然而,作为长期解决方案,它仍然存在局限性。你可能还需要考虑结合其他自动化监控和防护工具,以减少手动干预的依赖性并提高系统的整体防护能力。
调小半连接队列的大小确实可以让你更早发现 SYN 攻击,但同时也可能导致合法连接被拒绝。队列大小的调整取决于你的服务器负载和使用情况,但如果你坚持这样做,以下是一些调整建议:
调整原则:
备用方案: 考虑到你担心登录困难,你也可以配置一个备用端口用于 SSH 登录,并通过防火墙或 IP 限制控制访问,以便在主端口被攻击时仍能进行管理。
总结: 最终的队列大小应根据你服务器的实际情况和流量特征来决定,并需要通过测试和监控不断调整。建议从较保守的设置开始,然后根据实际情况逐步优化。
DDoS(分布式拒绝服务)攻击的动机因攻击者的目的和背景而异,常见的原因包括:
经济利益:
政治和社会动机:
网络报复:
技术炫耀:
随机破坏:
掩护其他攻击:
不同的攻击者可能出于多种动机发起DDoS攻击,具体原因往往取决于攻击者的背景、目标,以及他们希望从攻击中获得的收益或效果。
直接关闭服务器确实可以使攻击者无法继续进行 DDoS 攻击,因为服务器已不再响应任何请求,这相当于完全切断了攻击目标。但这种做法有以下几点需要考虑:
优点:
缺点:
可行性:
更好的方案:
总的来说,关闭服务器是应对 DDoS 攻击的一种极端策略,适合在无法采取其他更复杂措施时使用。它能迅速解除攻击影响,但也会带来可用性上的巨大牺牲。
评估攻击者发起一次 DDoS 攻击的成本需要考虑多个因素,包括攻击的类型、使用的资源、攻击的规模以及攻击者的技术水平。以下是一些影响 DDoS 攻击成本的主要因素:
攻击类型
资源成本
技术水平
规模和持续时间
黑市行情
估算成本的示例:
总结: DDoS 攻击的成本因攻击者使用的手段和规模而异,从几百美元的小规模攻击到数万美元的大规模、复杂攻击不等。成本评估需要结合攻击类型、使用资源和技术水平来综合考虑。
确实,对于每年费用不到 100 美元的 VPS,发动大规模 DDoS 攻击在经济上对攻击者来说可能不划算。攻击者通常会选择那些能够带来更大收益或影响的目标,如金融机构、大型企业或有政治意义的网站。
然而,即便成本较低的小型攻击,攻击者有时也可能出于其他动机,如测试攻击工具、恶作剧或随机破坏。但总的来说,如果你的 VPS 不是一个高流量或高价值的目标,遭受大规模 DDoS 攻击的可能性相对较低。
你可以采取一些基本的防护措施,降低受到攻击的风险,比如启用基本的防火墙规则,限制 SSH 访问,或者使用免费的 DDoS 防护服务。这样即使遇到小规模攻击,也能最大程度地保护你的 VPS。
在 TCP 中,半连接队列和全连接队列的大小估算对系统的网络性能和抗攻击能力至关重要。以下是如何估算这两个队列大小的指导:
半连接队列(SYN Queue)
半连接队列用于存放那些正在等待完成三次握手的连接,即已经接收到客户端的 SYN 报文并发送了 SYN-ACK,但还未收到客户端的 ACK 确认。
估算方法:
流量分析:估算半连接队列的大小需要了解你的服务器通常在高峰期会处理多少并发连接请求。如果你的服务器在高峰时段每秒处理 100 个新的连接请求,并且每个连接平均需要 3 秒来完成握手,那么你需要一个至少 300 的队列大小。
公式:队列大小 = 每秒新建连接数 × 平均握手时间(秒)
硬件能力:半连接队列的大小也取决于你的系统资源,尤其是 CPU 和内存。对于 2 核 CPU 和 2 GB 内存的 VPS,建议半连接队列大小在 512 到 1024 之间。这为系统提供足够的缓冲空间以处理高峰负载。
安全性考虑:如果你担心 SYN Flood 攻击,启用 SYN Cookies 是必要的,这样即使队列大小不足,也能确保合法连接不被轻易拒绝。
全连接队列(Accept Queue)
全连接队列是已经完成三次握手的连接等待应用程序处理的地方。如果应用程序不能及时处理这些连接,队列就会变满,新的连接将会被拒绝。
估算方法:
应用处理能力:全连接队列的大小取决于应用程序处理连接的速度。如果你的应用每秒可以处理 200 个连接,且在高峰期系统最多有 2 秒的延迟,那么队列大小至少应为 400。
公式:队列大小 = 每秒处理的连接数 × 处理延迟时间(秒)
硬件与并发要求:2 核 2 GB 内存的 VPS 一般建议全连接队列大小设置为 256 到 1024,根据实际应用的并发需求进行调整。对于轻量级应用,256 可能足够,而对于更高并发的应用,1024 是一个较好的初始值。
综合考虑:
总结:
这两个队列的大小应该通过实际测试和监控来进行细化调整,确保系统在面对高负载和潜在攻击时依然能够稳定运行。
开启 SYN Cookies,并将半连接队列设置得很小、全连接队列设置得很大,这种方案确实可以在特定场景下提升性能并节约资源,但它也有一些局限性。以下是这种方案的优缺点和适用情况:
优点:
缺点和局限性:
适用场景:
总结:
开启 SYN Cookies 并结合小的半连接队列和大的全连接队列确实可以在特定场景下优化性能,节约资源,并增强抗攻击能力。然而,这种方案的效果依赖于具体的应用场景和服务器负载情况。建议在实际应用中进行测试和监控,根据负载和性能表现进一步调整配置,以找到最佳平衡点。
你的观点是合理的。在服务器的资源管理中,内存的确通常比 CPU 更为关键,尤其是对于轻量级的 VPS 配置(例如 2 核 2 GB 内存的环境)。当内存耗尽时,系统可能会触发 OOM Killer 来释放内存,这会导致进程被强制终止,甚至可能影响到系统的稳定性和可用性。
优先考虑内存的策略:
综合考虑:
总之,考虑到你的环境和需求,优先保护内存资源是一个明智的策略。通过精细的配置和调整,可以在提升系统性能的同时,确保内存占用始终在安全范围内。
即使启用了 SYN Cookies,半连接队列仍然可能丢弃 SYN 包,这通常是由以下几个原因引起的:
半连接队列的物理限制
网络拥塞或延迟
系统资源限制
攻击导致资源耗尽
配置问题
排查与优化建议:
netstat
、ss
、sar
等)查看队列使用情况、系统负载和资源使用情况,以找出潜在的瓶颈。tcp_max_syn_backlog
和 somaxconn
等参数来优化队列行为,确保系统能更高效地处理高并发连接请求。总结: 启用 SYN Cookies 能有效防御 SYN Flood 攻击,但它不能完全解决所有导致 SYN 包丢失的问题。丢包现象可能与队列设置、系统资源限制、网络环境等多种因素有关。通过针对性的监控和优化配置,可以减少 SYN 包的丢失,提升系统的抗压能力。
在启用 SYN Cookies 的情况下,tcp_max_syn_backlog
的配置仍然有一定的作用,但它的作用会有所不同。
正常情况下的作用
tcp_max_syn_backlog
仍然决定了半连接队列的大小。当收到一个 SYN 包时,内核会将该连接放入半连接队列,等待三次握手的完成。tcp_max_syn_backlog
设置的值)被填满,新来的 SYN 包就会被丢弃,除非启用了 SYN Cookies。SYN Cookies 被触发时的作用
tcp_max_syn_backlog
的大小对新 SYN 包的处理不再有直接影响,因为新的连接不再需要占用半连接队列。tcp_max_syn_backlog
又会恢复其作用,控制半连接队列的大小。实际配置建议
tcp_max_syn_backlog
不再直接限制新连接,但在系统正常运行且未触发 SYN Cookies 的情况下,tcp_max_syn_backlog
仍然决定着半连接队列的容量。因此,合理设置这个值依然很重要,特别是在轻载或正常负载下。tcp_max_syn_backlog
的值可以设为一个适中大小,例如 512 或 1024,以便在负载增加时有足够的缓冲空间,延迟 SYN Cookies 的触发时间。总结:
启用 SYN Cookies 后,tcp_max_syn_backlog
的配置在正常情况下仍然有作用,因为它决定了在未触发 SYN Cookies 时半连接队列的容量。一旦 SYN Cookies 被触发,tcp_max_syn_backlog
的影响会暂时减小,但它依然是系统配置的一部分,并在负载降低时重新发挥作用。因此,合理设置 tcp_max_syn_backlog
是必要的,即使在启用 SYN Cookies 的情况下。
编辑 /etc/sysctl.conf
,添加如下配置
SYN Cookies
shell# 默认开启
net.ipv4.tcp_syncookies = 1
# 减小 syn+ack 重发次数,默认5次
net.ipv4.tcp_synack_retries = 5
半连接队列
shellnet.ipv4.tcp_max_syn_backlog = 128
全连接队列
shell# 0 :如果 accept 队列满了,那么 server 扔掉 client 发过来的 ack ;
# 1 :如果 accept 队列满了,server 发送一个 RST 包给 client,表示废掉这个握手过程和这个连接;
net.ipv4.tcp_abort_on_overflow = 1
# 调整全连接队列长度
net.core.somaxconn = 1024
tcp挥手优化
shell# FIN_WAIT1
net.ipv4.tcp_orphan_retries = 8 # 发送fin后等待ack的时长
net.ipv4.tcp_max_orphans = 16384 # 该状态下的最大数量
# FIN_WAIT2
net.ipv4.tcp_fin_timeout = 60 # 60s,等待对端fin的时长
# TIME_WAIT
net.ipv4.tcp_max_tw_buckets = 5000# 超过后,新关闭的连接就不再经历 TIME_WAIT 而直接关闭
net.ipv4.tcp_tw_reuse = 1 # 只在客户端有用 复用处于 TIME_WAIT 状态的连接
net.ipv4.tcp_timestamps = 1 # 配合 tcp_tw_reuse 使用
使配置生效
shell$ sysctl -p
使用如下配置
shellnet.ipv4.tcp_syncookies=1 net.ipv4.tcp_synack_retries=5 net.ipv4.tcp_max_syn_backlog=128 net.core.somaxconn=4096
模拟syn flood
shell# client 在国内
$ hping -S -p 443 --flood my-ip
服务器查看半连接队列大小
shell# server 在日本
$ netstat -natp | grep SYN_RECV | wc -l
# nginx 默认 backlog 为511 < 4096(somaxconn)
511
使用top观察各种指标,确发现 dashboard 毫无波澜。。。
使用如下配置
shellnet.ipv4.tcp_syncookies=1 net.ipv4.tcp_synack_retries=5 net.ipv4.tcp_max_syn_backlog=128 net.core.somaxconn=4096
http压测(nginx直接返回200 return 200 'hello world'
)
shell# 6线程 3万个连接 持续60s
$ wrk -t 6 -c 30000 -d 60s https://my-ip:443
服务器查看全连接队列大小
shell$ ss -lnt | grep ":443"
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
# Recv-Q 始终是0
查看全连接队列溢出情况
shell$ date;netstat -s |grep overflowed
Sat Aug 17 09:14:17 AM CST 2024
1867 times the listen queue of a socket overflowed
$ date;netstat -s |grep overflowed
Sat Aug 17 09:14:28 AM CST 2024
2062 times the listen queue of a socket overflowed
# 后续查看仍是2062
从整个测试情况来看,Recv-Q 始终是0,说明 nginx accep 的速度非常快,但是全连接队列在刚启动压测的一点时间内发生溢出,后续对半连接队列的观察发现也有溢出,半/全连接队列在溢出一定数量后均没有继续增加。对于这种现象不清楚具体是什么原因,请看 ChatGPT 对话12。
经过一系列的脑补我修改了 sysctl.conf
shellnet.ipv4.tcp_syncookies = 2
经过测试,还是存在半连接队列丢syn的情况。
但是我又联想到全连接队列也出现丢包现象,我猜是因为nginx没准备好accept。我没有看过linux内核源码,也不想看,我现在怀疑半连接队列受全连接队列的影响,如果全连接队列满了,即使有syn cookie也没用,该丢还是丢,下一步操作应该增大全连接队列的最大限制(不要忘了syn cookie 的值此时是2)
shell# /etc/sysctl.conf
net.core.somaxconn=30000
# nginx
listen 443 ssl backlog=30000;
重新测试
shell# 1 测试半连接队列
# 观察半连接队列溢出
$ netstat -s | grep "SYNs to LISTEN"
4351 SYNs to LISTEN sockets dropped
# 启动http压测
$ wrk -t 6 -c 30000 -d 60s https://my-ip:443
# 再次观察半连接队列溢出
4351 SYNs to LISTEN sockets dropped
# 2 观察全连接队列
$ ss -lnt | grep ":443"
LISTEN 31 30000 0.0.0.0:443 0.0.0.0:*
# 省略http压测命令。。。
# 再次观察全连接队列
LISTEN 0 30000 0.0.0.0:443 0.0.0.0:*
# 3 观察全连接队列溢出
$ date;netstat -s |grep overflowed
Sat Aug 17 10:32:00 AM CST 2024
4337 times the listen queue of a socket overflowed
# 省略http压测命令。。。
# 再次观察全连接队列溢出
$ date;netstat -s |grep overflowed
Sat Aug 17 10:32:18 AM CST 2024
4337 times the listen queue of a socket overflowed
结果令我欣喜,半/全连接队列没有发生溢出,并且观察到全连接队列有堆积,然后堆积就消失了。我又使用free查看了内存没有明显变化,可能是因为nginx没有复杂业务逻辑的原因。
shell# 查看全连接队列
# -l 显示正在监听的socket
# -n 不解析服务名称
# -t 只显示tcp socket
# Recv-Q 全连接队列大小
# Send-Q 全连接队列最大大小
$ ss -lnt
# 查看全连接队列溢出情况
$ date;netstat -s |grep overflowed
# 查看半连接队列
$ netstat -natp | grep SYN_RECV | wc -l
# 查看半连接队列溢出
$ netstat -s | grep "SYNs to LISTEN"
# http压测
# 6线程 3万个连接 持续60s
$ wrk -t 6 -c 30000 -d 60s https://my-ip:443
# 模拟 syn flood
$ hping -S -p 443 --flood ip
Go现在还不能直接使用net包设置,以下是使用syscall包来自定义listen
gopackage main
import (
"fmt"
"net"
"os"
"syscall"
)
func main() {
// 使用 syscall 创建套接字
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
fmt.Println("Error creating socket:", err)
os.Exit(1)
}
// 绑定地址和端口
addr := syscall.SockaddrInet4{Port: 8080}
copy(addr.Addr[:], net.ParseIP("0.0.0.0").To4())
err = syscall.Bind(fd, &addr)
if err != nil {
fmt.Println("Error binding socket:", err)
os.Exit(1)
}
// 设置 backlog 大小
backlog := 1024
err = syscall.Listen(fd, backlog)
if err != nil {
fmt.Println("Error listening on socket:", err)
os.Exit(1)
}
// 将文件描述符转换为 net.Listener
f := os.NewFile(uintptr(fd), "")
ln, err := net.FileListener(f)
if err != nil {
fmt.Println("Error creating listener:", err)
os.Exit(1)
}
defer ln.Close()
fmt.Println("Listening on :8080 with backlog size", backlog)
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// 处理连接
}
本文作者:jdxj
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!