深入理解Linux TCP backlog

在linux 2.2以前,backlog大小包括了半连接状态和全连接状态两种队列大小。linux 2.2以后,分离为两个backlog来分别限制半连接SYN_RCVD状态的未完成连接队列大小跟全连接ESTABLISHED状态的已完成连接队列大小。互联网上常见的TCP SYN FLOOD恶意DOS攻击方式就是用/proc/sys/net/ipv4/tcp_max_syn_backlog来控制的,可参见《TCP洪水攻击(SYN Flood)的诊断和处理》。

在使用listen函数时,内核会根据传入参数的backlog跟系统配置参数/proc/sys/net/core/somaxconn中,二者取最小值,作为“ESTABLISHED状态之后,完成TCP连接,等待服务程序ACCEPT”的队列大小。在kernel 2.4.25之前,是写死在代码常量SOMAXCONN,默认值是128。在kernel 2.4.25之后,在配置文件/proc/sys/net/core/somaxconn (即 /etc/sysctl.conf 之类 )中可以修改。我稍微整理了流程图,如下:

How TCP backlog works in linux一文中,作者给出了比较详细的分析:

  1. 第一种实现方式在底层维护一个由backlog指定大小的队列。服务端收到SYN后,返回一个SYN/ACK,并把连接放入队列中,此时这个连接的状态是SYN_RECEIVED。当客户端返回ACK后,此连接的状态变为ESTABLISHED。队列中只有ESTABLISHED状态的连接能够交由应用处理。第一种实现方式可以简单概括为:一个队列,两种状态。

  2. 第二种实现方式在底层维护一个SYN_RECEIVED队列和一个ESTABLISHED队列,当SYN_RECEIVED队列中的连接返回ACK后,将被移动到ESTABLISHED队列中。backlog指的是ESTABLISHED队列的大小。

传统的基于BSD的tcp实现第一种方式,在linux2.2之前,内核也实现第一种方式。当队列满了以后,服务端再收到SYN时,将不会返回SYN/ACK。比较优雅的处理方法就是不处理这条连接,不返回RST,让客户端重试。

在linux2.2后,选择第二种方式实现,SYN_RECEIVED队列的大小由proc/sys/net/ipv4/tcp_max_syn_backlog系统参数指定,ESTABLISHED队列由backlog和/proc/sys/net/core/somaxconn中较小的指定。

但是在windows server中,底层选择winsock API实现,backlog的定义是represents the maximum length of the queue of pending connections for the listener(这是一个比较模糊的定义……来源于BSD),当队列满了后,将会返回RST。

if (ESTABLISHED is full) {SYN.req -> ESTABLISHED?}

考虑这样一种情况,当ESTABLISHED队列满了,此时收到一个连接的ACK,需要将此连接从SYN队列移到ESTABLISHED队列中,会发生什么?

linux底层的关键代码是:

 

listen_overflow:

if (!sysctl_tcp_abort_on_overflow) {

inet_rsk(req)->acked = 1;

return NULL;

}

除非系统的tcp_abort_on_overflow指定为1(将返回RST),否则底层将不会做任何事情……这是一种委婉的退让策略,在服务端处理不过来时,让客户端误以为ACK丢失,继续重新发送ACK。这样,当服务端的处理能力恢复时,这条连接又可以重新被移动到ESTABLISHED队列中去。

=======================================================

当应用程序调用listen系统调用让一个socket进入LISTEN状态时,需要指定一个参数:backlog。这个参数经常被描述为,新连接队列的长度限制。

tcp-state-diagram.png

由于TCP建立连接需要进行3次握手,一个新连接在到达ESTABLISHED状态可以被accept系统调用返回给应用程序前,必须经过一个中间状态SYN RECEIVED(见上图)。这意味着,TCP/IP协议栈在实现backlog队列时,有两种不同的选择:

  1. 仅使用一个队列,队列规模由listen系统调用backlog参数指定。当协议栈收到一个SYN包时,响应SYN/ACK包并且将连接加进该队列。当相应的ACK响应包收到后,连接变为ESTABLISHED状态,可以向应用程序返回。这意味着队列里的连接可以有两种不同的状态:SEND RECEIVEDESTABLISHED。只有后一种连接才能被accept系统调用返回给应用程序。

  2. 使用两个队列——SYN队列(待完成连接队列)和accept队列(已完成连接队列)。状态为SYN RECEIVED的连接进入SYN队列,后续当状态变更为ESTABLISHED时移到accept队列(即收到3次握手中最后一个ACK包)。顾名思义,accept系统调用就只是简单地从accept队列消费新连接。在这种情况下,listen系统调用backlog参数决定accept队列的最大规模。

历史上,起源于BSDTCP实现使用第一种方法。这个方案意味着,但backlog限制达到,系统将停止对SYN包响应SYN/ACK包。通常,协议栈只是丢弃SYN包(而不是回一个RST包)以便客户端可以重试(而不是异常退出)。

TCP/IP详解 卷314.5节中有提到这一点。书中作者提到,BSD实现虽然使用了两个独立的队列,但是行为跟使用一个队列并没什么区别。

Linux上,情况有所不同,情况listen系统调用man文档页:

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored.

意思是,backlog参数的行为在Linux2.2之后有所改变。现在,它指定了等待accept系统调用的已建立连接队列的长度,而不是待完成连接请求数。待完成连接队列长度由/proc/sys/net/ipv4/tcp_max_syn_backlog指定;在syncookies启用的情况下,逻辑上没有最大值限制,这个设置便被忽略。

也就是说,当前版本的Linux实现了第二种方案,使用两个队列——一个SYN队列,长度系统级别可设置以及一个accept队列长度由应用程序指定。

现在,一个需要考虑的问题是在accept队列已满而一个已完成新连接需要用SYN队列移动到accept队列(收到3次握手中最后一个ACK包),这个实现方案是什么行为。这种情况下,由net/ipv4/tcp_minisocks.ctcp_check_req函数处理:

 

    child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
    if (child == NULL)
        goto listen_overflow;

对于IPv4,第一行代码实际上调用的是net/ipv4/tcp_ipv4.c中的tcp_v4_syn_recv_sock函数,代码如下:

 

    if (sk_acceptq_is_full(sk))
        goto exit_overflow;

可以看到,这里会检查accept队列的长度。如果队列已满,跳到exit_overflow标签执行一些清理工作、更新/proc/net/netstat中的统计项ListenOverflowsListenDrops,最后返回NULL。这会触发tcp_check_req函数跳到listen_overflow标签执行代码。

 

listen_overflow:
    if (!sysctl_tcp_abort_on_overflow) {
        inet_rsk(req)->acked = 1;
        return NULL;
    }

很显然,除非/proc/sys/net/ipv4/tcp_abort_on_overflow被设置为1(这种情况下发送一个RST包),实现什么都没做。

总结一下:Linux内核协议栈在收到3次握手最后一个ACK包,确认一个新连接已完成,而accept队列已满的情况下,会忽略这个包。一开始您可能会对此感到奇怪——别忘了S

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值