PROC文件somaxconn默认为128,意味着单个套接口队列的长度,可最大监听128个连接。net_defaults_init_net函数初始化此值为宏SOMAXCONN(128)。此处的套接口意指完成TCP三次握手,连接已建立起来的套接口,用户层也还未accept此套接口(此处的套接为子套接口,即子套接口队列)。另外,连接未完成的套接口的数量由/proc/sys/net/ipv4/tcp_max_syn_backlog控制。
# cat /proc/sys/net/core/somaxconn
128
static int __net_init net_defaults_init_net(struct net *net)
{
net->core.sysctl_somaxconn = SOMAXCONN;
}
连接监听套接口保存在以request_sock_queue结构的成员rskq_accept_head为首的链表中,rskq_accept_tail成员指向链表尾部。这是一个先入先出(FIFO)类型链表。
struct request_sock_queue {
struct request_sock *rskq_accept_head;
struct request_sock *rskq_accept_tail;
};
套接口函数listen的系统调用指定最大的连接套接口数量(backlog),由其内容可见,用户指定的参数backlog不能大于sysctl_somaxconn的值,否则使用sysctl_somaxconn的值。如需要增大监听的套接口数量,可预先修改PROC文件somaxconn的值。
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
}
}
最终,backlog的值赋给了套接口对应的sock结构的sk_max_ack_backlog成员。
int inet_listen(struct socket *sock, int backlog)
{
if (old_state != TCP_LISTEN) {
err = inet_csk_listen_start(sk, backlog);
}
sk->sk_max_ack_backlog = backlog;
}
int inet_csk_listen_start(struct sock *sk, int backlog)
{
struct inet_connection_sock *icsk = inet_csk(sk);
reqsk_queue_alloc(&icsk->icsk_accept_queue);
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
}
另外,sock结构中的sk_ack_backlog用于记录当前的队列长度,在inet_csk_listen_start函数中将其初始化为0,最重要的是初始化ACCEPT队列(icsk_accetp_queue)。
一、加入ACCEPT队列
接收到客户端的第三个握手ACK报文之后,在函数tcp_check_req中,如果一切检查正常的话,使用回调syn_recv_sock处理创建子套接口,之后由函数inet_csk_complete_hashdance将子套接口添加到ACCEPT队列中。
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, bool fastopen)
{
struct sock *child;
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
if (!child)
goto listen_overflow;
sock_rps_save_rxhash(child, skb);
tcp_synack_rtt_meas(child, req);
return inet_csk_complete_hashdance(sk, child, req, own_req);
}
如下所示,添加到ACCEPT队列的条件是own_req变量为真,此变量在回调函数syn_recv_sock中赋值,其为真意味着此子套接口已经成功添加到全局的TCP套接口队列中(TCP套接口全局哈希链表为tcp_hashinfo),否则,有一种情况是客户端进行的两次TCP握手,在第二次握手完成之时,第一次握手创建的子接口已添加到全局套接口链表中,导致第二次握手的子套接口添加不成功,own_req为空时需要释放第二次握手创建的子套接口。
struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child, struct request_sock *req, bool own_req)
{
if (own_req) {
inet_csk_reqsk_queue_drop(sk, req);
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
if (inet_csk_reqsk_queue_add(sk, req, child))
return child;
}
/* Too bad, another child took ownership of the request, undo. */
bh_unlock_sock(child);
sock_put(child);
return NULL;
}
函数inet_csk_reqsk_queue_add完成具体的添加工作,新添加进来的子套接口加到ACCEPT队列的尾部。由函数可见ACCEPT队列实际上存储的是请求套接口(request_sock),不过每个请求套接口对应一个子套接口(req->sk),两者是对应的。
struct sock *inet_csk_reqsk_queue_add(struct sock *sk, struct request_sock *req, struct sock *child)
{
struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
if (unlikely(sk->sk_state != TCP_LISTEN)) {
} else {
req->sk = child;
req->dl_next = NULL;
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_head = req;
else
queue->rskq_accept_tail->dl_next = req;
queue->rskq_accept_tail = req;
sk_acceptq_added(sk);
}
}
函数sk_acceptq_added递增ACCEPT队列长度。
static inline void sk_acceptq_added(struct sock *sk)
{
sk->sk_ack_backlog++;
}
二、移出ACCEPT队列
当用户层调用accept套接口函数时,使用reqsk_queue_remove函数将套接口(已是子套接口)从icsk_accept_queue队列中删除。此处的newsk为新TCP连接的子套接口,返回给用户层。
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
struct request_sock *req;
struct sock *newsk;
req = reqsk_queue_remove(queue, sk);
newsk = req->sk;
}
函数reqsk_queue_remove为简单的链表移除单个元素的操作,rskq_accept_head为链表的头,注意ACCEPT队列总是从头部开始移除队列中的子套接口元素,即用户层的accept操作总是取走队列中的第一个子套接口。
static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue, struct sock *parent)
{
struct request_sock *req;
req = queue->rskq_accept_head;
if (req) {
sk_acceptq_removed(parent);
queue->rskq_accept_head = req->dl_next;
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
}
}
递减队列长度计数。
static inline void sk_acceptq_removed(struct sock *sk)
{
sk->sk_ack_backlog--;
}
三、ACCEPT队列满
队列满的判断函数sk_acceptq_is_full如下,
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}
队列满的判断在内核中有两处,一处位于TCP三次握手完成,接收到客户端的ACK报文时,见函数tcp_v4_syn_recv_sock中所示,由于此处TCP连接建立完成,需要创建新的子套接口,之后将此子套接口加入到accept队列中,所以先判断accept队列是否已满。满的话不进行后续的子接口创建等操作。
struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, struct request_sock *req, ...)
{
if (sk_acceptq_is_full(sk))
goto exit_overflow;
}
函数tcp_v4_syn_recv_sock由TCP入口函数tcp_v4_rcv调用,要求此时的套接口处于TCP_NEW_SYN_RECV状态,即套接口在等待三次握手的最后一个ACK报文。
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, bool fastopen)
{
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
}
int tcp_v4_rcv(struct sk_buff *skb)
{
if (sk->sk_state == TCP_NEW_SYN_RECV) {
struct request_sock *req = inet_reqsk(sk);
if (!tcp_filter(sk, skb))
nsk = tcp_check_req(sk, skb, req, false);
}
}
第二个队列满的判断位于函数tcp_conn_request中。此处是在接收到一个新的TCP连接请求之时,判断ACCEPT队列是否已满,满的话就不再继续处理,假如继续处理,比如回复客户端SYN+ACK报文,之后客户端发送ACK再次到达之后,就到了前面讲述的第一个队列满的判断点,最终也是不能处理。
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)
{
if (sk_acceptq_is_full(sk)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
}
函数tcp_conn_request在TCP接收状态机处理函数tcp_rcv_state_process中调用,要求套接口在LISTEN状态接收到SYN报文。
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcphdr *th = tcp_hdr(skb);
switch (sk->sk_state) {
case TCP_LISTEN:
if (th->syn) {
acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;
return 0;
}
}
针对第一处ACCEPT队列满的处理,PROC文件tcp_abort_on_overflow指定了要进行的操作。其默认值为0,置位acked后直接返回;如果不为0,将重置此连接发送TCP的reset报文到对端。如果确认本机的应用程序不能够更快的处理(accept)新进的连接,可置位tcp_abort_on_overflow,但是发送的reset报文会对客户端造成伤害。如果队列满的原因是由于突发的大量连接所导致,之后其会恢复,不建议置位tcp_abort_on_overflow。
$ cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, bool fastopen)
{
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
if (!child)
goto listen_overflow;
listen_overflow:
if (!sock_net(sk)->ipv4.sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}
embryonic_reset:
if (!(flg & TCP_FLAG_RST)) {
req->rsk_ops->send_reset(sk, skb);
}
if (!fastopen) {
inet_csk_reqsk_queue_drop(sk, req);
__NET_INC_STATS(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
}
}
四、补充
以上讨论的是TCP连接建立后的ACCEPT子套接口队列,但是在这之前(还未创建子套接口)的请求套接口保存在哪里呢?参见文章 https://blog.csdn.net/sinat_20184565/article/details/87709636 讨论的是tcp_max_syn_backlog控制的还未完全建立连接的TCP请求套接口队列的最大长度。但是内核中并没有此队列,所有的请求套接口保存到全局的TCP套接口链表中,使用ACCEPT队列icsk_accept_queue(结构为request_sock_queue)的成员qlen表示此队列的长度。
struct request_sock_queue {
atomic_t qlen;
}
在接收到SYN报文后,使用函数inet_csk_reqsk_queue_hash_add处理请求套接口队列,最终由函数reqsk_queue_added增加队列计数。
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)
{
if (fastopen_sk) {
} else {
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));
af_ops->send_synack(sk, dst, &fl, req, &foc, !want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE);
}
}
static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
atomic_inc(&queue->young);
atomic_inc(&queue->qlen);
}
另外一个问题是,在TCP连接建立之后,将子套接口添加到ACCEPT队列之前,需要将全局TCP套接口链表中删除请求套接口(由函数reqsk_queue_unlink完成),并且要递减请求套接口队列的长度(由函数reqsk_queue_removed完成)。
void inet_csk_reqsk_queue_drop(struct sock *sk, struct request_sock *req)
{
if (reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req)) {
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
reqsk_put(req);
}
}
static inline void reqsk_queue_removed(struct request_sock_queue *queue, const struct request_sock *req)
{
if (req->num_timeout == 0)
atomic_dec(&queue->young);
atomic_dec(&queue->qlen);
}