我们主要看inet_csk_reqsk_queue_is_full()函数和sk_acceptq_is_full()函数的部分,这两个函数分别用来判断半连接队列和连接队列是否已满。结合上面的代码,在两种情况下会丢掉SYN包。一种是在半连接队列已满的情况下,isn的值其实TCP_SKB_CB(skb)->when的值,when在tcp_v4_rcv()中被清零,所以!isn总是为真;第二种情况是在连接队列已满并且半连接队列中还有未重传过的半连接(通过inet_csk_reqsk_queue_young()来判断)。至于我们看到的源端口为49274的连接是在哪个位置丢掉的就不知道了,这要看但是半连接队列的情况。因为有专门的定时器函数来维护半连接队列,所以在第二次发送SYN包时,包没有丢弃,所以内核会调用__tcp_v4_send_synack()函数来发送SYN+ACK包,并且分配内存用来描述当前的半连接状态。当服务器发送的SYN+ACK包到达客户端时,客户端的状态会从SYN_SENT状态变为ESTABLISHED状态,也就是说客户端认为TCP连接已经建立,然后发送ACK给服务器端,来完成三次握手。在正常情况下,服务器端接收到客户端发送的ACK后,会将描述半连接的request_sock实例从半连接队列移除,并且建立描述连接的sock结构,但是在连接队列已满的情况下,内核并不是这样处理的。
当客户端发送的ACK到达服务器后,内核会调用tcp_check_req()来检查这个ACK包是否是正确,从TCP层的接收函数tcp_v4_rcv()到tcp_check_req()的代码流程如下图所示:
如果是正确的ACK包,tcp_check_req()会调用tcp_v4_syn_recv_sock()函数创建新的套接字,在tcp_v4_syn_recv_sock()中会首先检查连接队列是否已满,如果已满的话,会直接返回NULL。当tcp_v4_syn_recv_sock()返回NULL时,会跳转到tcp_check_req()函数的listen_overflow标签处执行,如下所示:
/*
* Process an incoming packet for SYN_RECV sockets represented
* as a request_sock.
*/
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct request_sock **prev)
{
......
/* OK, ACK is valid, create big socket and
* feed this segment to it. It will repeat all
* the tests. THIS SEGMENT MUST MOVE SOCKET TO
* ESTABLISHED STATE. If it will be dropped after
* socket is created, wait for troubles.
*/
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
if (child == NULL)
goto listen_overflow;
.......
listen_overflow:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}
......
}