关于TCP的半连接队列与全连接队列的所有面试题
TCP半连接与全连接队列的面试题,虽然是一个大的知识点,但是面试题是很多的,要知道问题不一样,考察的内容是一致的哦,在文章中,也会在相应的位置提示面试题。
一线大厂面试真题:
大厂面试必问:CPU使用率100%怎么办?运维高手这样回答!
字节面试真题–用户反馈网页访问慢,可能会有哪些原因
字节面试真题–TCP建立连接为什么要三次握手?为什么不是两次或者四次?
面试八股(五) TCP的三次握手与四次挥手过程【是详述可不是简单几句话哦】
面试八股(六) 腾讯面试真题:close_wait状态的原因是什么?该怎么办?
面试八股(七) TCP Keepalive和HTTP Keep-Alive有什么区别?
面试八股(八) Linux服务器拔网线或者断电,已建立的TCP连接会中断吗?
面试八股(九) 关于TCP TIME_WAIT状态的所有面试题都在这里了
面试八股(11) 字节真题,关于TCP半连接与全连接队列的所有面试题
面试八股(12) 大厂真题,你了解synflood攻击吗?该如何应对
服务端处于TCP TIME_WAIT状态的连接,客户端重用这个连接还能连上吗?
还有更多一线大厂的面试真题,关注我的公众号:Linux运维实战派
一、什么是TCP的半连接和全连接队列
半连接队列也叫SYN队列,TCP三次握手的过程中,服务端接受到SYN包后,会将这个连接放入到SYN队列,并发送SYN+ACK报文
全连接队列也叫Accept队列,三次握手完成之后,连接会从SYN队列转移到Accept队列,等待进程执行accept()调用,将连接取走。
二、半连接队列及全连接队列溢出会发生什么
如上图,我们不难看出来,SYN队列是靠着三次握手最后一个ACK
才完成转移,那如果最后一个ACK
迟迟不到,甚至是永远也不会到,会发生什么?
Accept 队列是靠着进程执行accept()
调用,完成转移,那如果业务迟迟不执行accept()
,全连接队列满了会怎么样?
答案是:
半连接队列满了之后,如果没有开启syncookie
则会直接丢弃SYN包
全连接队列满了之后,会直接丢弃(客户端反应就是连接超时)或者RST(客户端反应是 Connection reset by peer)具体是直接丢弃还是RST,取决于服务端的net.ipv4.tcp_abort_on_overflow
的配置
- tcp_abort_on_overflow == 0 的时候,会直接丢弃客户端发过来的数据包。这个是内核的默认行为,好处是丢弃客户端的报文,客户端会进行重传,假设全连接队列有空闲,请求就会被正常处理,可以增加客户端的成功率
- tcp_abort_on_overflow == 1 的时候,会直接回复客户端RST,重置连接。客户端会立马收到响应
三、全连接队列的大小配置
在Linux中全连接队列的计算逻辑为min(somaxconn, backlog)
即 net.core.somaxconn
内核参数与listen调用时传的backlog的最小值
- somaxconn 由
net.core.somaxconn
这个sysctl参数配置,默认值是128
。 - backlog 是
listen
调用的时候传递的backlog
大小,如nginx默认值是511
,nginx在配置的时listen 80 backlog=16384;
通过这样的方式配置
3.1 全连接队列大小的查询
我们可以通过netstat
和 ss
命令来查询连接生效的全连接队列的大小,但是需要注意的是,这两个命令查询出来的结果会有不同。
ss 命令
ss -ant
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
ESTAB 0 52 10.230.1.153:22 88.88.88.88:19912
ss
命令中的Send-Q
在连接处于LISTEN
状态时,这个值就表示全连接队列大小
;如果是ESTABLISHED
状态的连接,这个值表示已经发送还没有被对端确认的数据大小
netstat 命令
netstat -ant
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 2828 10.230.15.76:22 10.230.1.153:41644 ESTABLISHED
netstat
命令中的Send-Q
不管什么状态,都表示已经发送,但没有被确认的数据大小
不管是netstat
还是ss
Recv-Q
都表示接收缓冲区内还没有被应用取走的数据大小
3.2 全连接满该如何监测
netstat -s |grep -i overflowed
如果没有出现全连接队列满的情况,这个命令执行会没有输出,如果这个输出的数字在一直增长,则表明当前有因为全连接队列满引起的丢包
面试题:如果监控报ListenOverFlow的错误,是什么原因,该怎么办?
3.3 全连接队列满该如何处理
从连接转移过程可以看出,全连接队列满主要是应用不及时调用accept()
引起的。所以,根本的解决办法在于加速应用程序的处理能力。
正产环境下的实践是,默认128
是太小,推荐调整到4096
及以上,在一些高并发的场景,如Nginx负载均衡,推荐调整到16384
。需要注意的是,这个参数需要同时调整应用的Listen backlog
和somaxconn
的大小。
四、半连接队列
半连接队列是接收到SYN
包之后,处于SYN_RECV
状态的数量。如果无法及时收到客户端发来的ack
报文,SYN的半连接队列就会溢出。
4.1 半连接队列的大小
首先必须明确的是,内核参数中的tcp_max_syn_backlog
并不等于半连接队列,半连接的最大值受到多个参数的共同影响。
注意:以下逻辑适用于内核 4.2以上版本,在这之前计算逻辑是比较复杂的
SYN包正常的判断条件如下:
1)半连接队列大小不超过“全连接队列最大值【min(somaxconn, backlog)】”
2)全连接队列大小,不超过全连接队列最大值
3)半连接队列大小不超过tcp_max_syn_backlog
与tcp_max_syn_backlog/4
的差【tcp_max_syn_backlog - (tcp_max_syn_backlog>>2)】
所以综上,如果一定要说SYN半连接的最大值,公式应该是
min(min(somaxconn, backlog), (max_syn_backlog - max_syn_backlog/4))
4.2 半连接队列的调整
在生产环境中一般性我们都会将somaxconn
, listen backlog
,max_syn_backlog
配置成一样的值,如16384。
如果想配置半连接队列与全连接队列最大值相等的话,tcp_max_syn_backlog
配置为: 4*全连接队列最大值/3
echo "net.core.somaxconn=16384" >> /etc/sysctl.d/99-sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog=16384" >> /etc/sysctl.d/99-sysctl.conf
sysctl -p
4.3 半连接队列溢出
半连接队列溢出(即上边三个条件不满足任意一个),如果没有开启syncookies
,那客户端的SYN包会被直接丢弃。
查看当前半连接大小
ss -ant | grep -i SYN-RECV | wc -l
半连接队列溢出监测
netstat -s |grep -i "SYNs to LISTEN sockets dropped"
判断这个输出如果有值,并且数值一直增长,则表明SYN半连接队列溢出
面试题:监控发现有报kernel: Possible SYN flooding Sending cookies. Check SNMP counters. 相关的报错,是什么原因,该怎么办
4.4 半连接队列溢出该如何处理
1)适当调大TCP队列的配置,包括somaxconn
,listen backlog
,max_syn_backlog
2)降低syn-ack的重传次数
echo "net.ipv4.tcp_synack_retries=1" >> /etc/sysctl.d/99-sysctl.conf
sysctl -p
3)开启syncookies
,如果使用了LVS,支持synproxy
的话,开启synproxy
echo "net.ipv4.tcp_syncookies" >> /etc/sysctl.d/99-sysctl.conf
sysctl -p
五、源码分析
推荐大家在线看kernel源码的网站:https://elixir.bootlin.com/linux/v5.10/source
5.1 全连接队列大小
// kernel 5.10 net/socket.c
int __sys_listen(int fd, int backlog)
{
struct socket *sock;
int somaxconn;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
// min(backlog, somaxconn)
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
....
}
5.2 半连接大小限制
// kernel 5.10 net/ipv4/tcp_input.c
int tcp_conn_request(struct request_sock_ops *rsk_ops,
const struct tcp_request_sock_ops *af_ops,
struct sock *sk, struct sk_buff *skb)
{
// syncookies没有开启的话,半连接队列溢出的话,则会丢弃
if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
inet_csk_reqsk_queue_is_full(sk)) && !isn) {
want_cookie = tcp_syn_flood_action(sk, rsk_ops->slab_name);
if (!want_cookie)
goto drop;
}
// 全连接队列溢出,丢弃
if (sk_acceptq_is_full(sk)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
if (!want_cookie && !isn) {
// 没有开syncookies, 且(max_syn_backlog - 当前半连接大小)<(max_syn_backlog>>2)
// ==> 当前半连接队列 > (max_syn_backlog-max_syn_backlog>>2)
// C中无符号整数>>2等于除以4
if (!net->ipv4.sysctl_tcp_syncookies &&
(net->ipv4.sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
(net->ipv4.sysctl_max_syn_backlog >> 2)) &&
!tcp_peer_is_proven(req, dst)) {
.....
goto drop_and_release;
}
}
.....
}
5.3 全连接与半连接队列溢出的判断
//kernel 5.10 net/inet_connection_sock.h#L279
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
{
// 判断半连接队列溢出,实际上4.2以上版本都是直接与全连接队列最大值做的判断
// sk_max_ack_backlog是全连接队列最大值, == min(backlog,somaxconn)
return inet_csk_reqsk_queue_len(sk) >= sk->sk_max_ack_backlog;
}
// kernel 5.0 include/net/sock.h#L931
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
// sk_ack_backlog当前全连接队列大小
// sk_max_ack_backlog 全连接队列大小的最大值
return READ_ONCE(sk->sk_ack_backlog) > READ_ONCE(sk->sk_max_ack_backlog);
}
六、写在最后
因为TCP三次握手的设计,假设黑客伪造客户端IP,向服务端发起大量的SYN
包,建立连接。那很快服务端的半连接队列(SYN队列)就会溢出,这就是synflood
攻击。
那synflood
攻击该如何防护?syncookies
的原理又是什么?我们下回再说
帅锅,看到这里了,点个赞,给个关注再走呗。