1.流程图解&流程描述
上图中不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST数据包.
2.相关的几个内核参数
开启tcp_syncookies参数逻辑:tcp_syncookies参数开启,就可以在不使用syn半连接队列的情况下建立连接
参数路径:
cat /proc/sys/net/ipv4/tcp_syncookies
服务端在接收到客户端的syn报文时,计算出一个值,放到syn+ack报文中发出返回给客户端。当客户端返回ack报文时携带该值,取出该值验证,成功则建立连接。
通常见的半连接攻:
就是大量发送syn让服务资源耗尽,从而不能提供服务,也叫DDos攻击.
抵御大量半连接的方法如下:
1.增大半链接队列
通过修改内核参数增大半链接队列tcp_max_syn_backlog,还需要一同增大somaconn和backlog(增大全连接队列)
sysctl -a|grep 参数名 // 查看对应参数当前配置大小
全连接队列长度: 其最大长度是 listen 时传入的 backlog 和 net.core.somaxconn 之间较小的那个值。如果需要加大全连接队列长度,那么就是调整 backlog 和 somaxconn。
半链接队列长度: min(backlog,somaxconn,tcp_max_syn_backlog) + 1 向上取整到2的幂次。(最小不能小于16),另外线上遇到了半连接队列溢出的问题,想加大该队列长度,那么就需要同时考虑 somaxconn、backlog、和 tcp_max_syn_backlog 三个内核参数
2.减少服务端的syn+ack数据包的传递次数
修改重传次数:/proc/sys/net/ipv4/tcp_synack_retries
有个参数tcp_abort_on_overflow可以指定TCP全连接队列满了,使用什么策略来回应客户端
参数路径: /proc/sys/net/ipv4/tcp_abort_on_over_flow
0:如果全连接队列满了,那么服务端丢弃ack报文
1:如果全连接队列满了,那么服务端会想客户端发送RST报文,终止这个握手连接
注:半链接攻击跟全连接攻击,详情请见博主的“半链接攻击跟全连接攻击”介绍.
3.三次握手出现的问题
connect时异常
客户端调用connect连接时,系统会给当前客户端分配一个端口号,端口号的分配过程内核根据参数ip_local_port_range分配一个尚未使用的端口.但是如果端口号如果使用完了,再来客户端分配度端口,是无法分配成功的。在内核中,分配端口时一个循环过程,并且内部使用了自旋锁以及哈希表搜索过程。如果端口用完了,每次来新的连接,就都会进行无用的循环+自旋锁的占用cpu时间+大量的哈希查找等时间开销,从而造成cpu的时间消耗大幅度飙升。
处理方式:
可以根据实际情况,调整ip_local_port_range端口范围,避免出现端口使用完的情况发生.
尽量复用连接,使用长链接,避免过多频繁的短连接带来大量的握手从而造成cpu耗时.
可以开启tcp_tw_reuse和tcp_tw_recycle参数,但是不建议开启。
tcp_tw_reuse:开启该参数便可以重复利用处于 time_wait
状态的连接。实际场景中,客户端主动发起关闭连接,表示当前不再连接访问服务。如果有需要,重新连接即可。如果开启,处于状态为time_wait的连接仍旧能被使用,可能会出现未知的问题,所以不建议开启该参数。
tcp_tw_recycle:该参数4.x
内核版本之后已经被废弃。因为这个参数功能是快速回收处于 time_wait
状态的连接,不用等2 MSL
就关闭连接。产生的影响是会拒绝所有当前客户端的时间戳更靠前的网络包,所以不要开启该参数。不然可能会发生大量服务拒绝数据包问题。
第一次握手丢包异常
第一次握手,半连击队列满了,并且没有开启tcp_syncookies,那么服务端会直接将该握手包丢弃,此时客户端因有定时重传,会在超时后,重新发送一个握手包,同时设置下一次超时时间,如果服务端仍旧丢弃该包,那么客户端重传到一定次数后(重传的次数由于net.ipv4.tcp_syn_retries 影响,[由于重传判断逻辑比较复杂,不单单是比较次数,还有时间的比较判别,所以有时看到重传次数跟内核单数不太一致,也是正常的现象],就不再进行连接.同样全队列如果也是满了,并且有young_ack存在(尚未连接的半连接对象)也会丢弃当前的数据包。
注:客户端在高版本的服务设置的超时时间是1s,比较老的系统设置是3s,当客户是用客户端连接服务端时,如果发生上述情况丢包,如果进行重传连接,那么此时的耗时严重影响客户使用体验.
第三次握手包丢失情况
如果握手进行到该阶段,收到了服务端发的synack数据包,此时客户端认为连接好了,并且会将第一次发出握手包之后设置的定时重传器删除,再发出最有一次握手的ack数据包.此刻全队列满了,服务端将该ack数据包丢弃,服务端会发现定时时间内没收到ack包,则会重新发送一个synack数据包,如果全队列一直都是满的,那ack数据包会一直丢弃,一直持续到超多重传的次数才停止(重传次数由内核参数 net.ipv4.tcp_synack_retries控制)
处理方法:
开启tcp_syncookies参数,即便半连接队列满了,仍旧可以连接。
增大连接队列长度,调整时,根据实际需要,半连接跟全连接队列都适配的调整。可以大幅度避免发生握手异常问题.
快速的accept,避免处理其他业务逻辑导致队列满了,通常都是连接完直接accept。
结合实际场景,能使用长链接的使用长链接,避免因短连接过多的握手连接导致系统资源的消耗.(当然,一些非频繁的消息处理,使用短连接好,用完即释放,避免长时间占用资源)