gpt4 book ai didi

linux - syn队列和accept队列的混淆

转载 作者:行者123 更新时间:2023-12-03 11:59:03 25 4
gpt4 key购买 nike

在阅读TCP源码的时候,我发现了一个迷茫的地方:

我知道 TCP 在 3 次握手中有两个队列:

  • 第一个队列存储服务器已收到SYN并发送回ACK + SYN的连接,我们称之为syn队列
  • 第二个队列存储3WHS成功并建立连接的连接,我们称之为接受队列

但是在阅读代码时,我发现 listen() 会调用 inet_csk_listen_start(),这会调用 reqsk_queue_alloc() 来创建 icsk_accept_queue。而那个队列在accept()中使用,当我们发现队列不为空时,我们会从中获取一个连接并返回。

此外,跟踪接收过程后,调用堆栈是这样的

tcp_v4_rcv()->tcp_v4_do_rcv()->tcp_rcv_state_process()

第一次握手时服务器状态为LISTEN。所以它会调用

`tcp_v4_conn_request()->tcp_conn_request()`

tcp_conn_request()

if (!want_cookie)
// Add the req into the queue
inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));

但这里的队列正是 icsk_accept_queue,而不是 syn 队列。

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk);
}

static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}

accept()会返回建立的连接,也就是说icsk_accept_queue是第二个队列,但是第一个队列在哪里呢?

从第一个队列到第二个队列的连接在哪里变化?

为什么 Linux 将新请求添加到 icsk_accept_queue 中?

最佳答案

在下文中,我们将遵循最典型的代码路径,并忽略由丢包、重传和使用 TCP 快速打开(代码注释中的 TFO)等非典型特性引起的问题。

accept 调用由 intet_csk_accept 处理,它调用 reqsk_queue_remove从接受队列中获取套接字 &icsk->icsk_accept_queue来自监听套接字:

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;
int error;

lock_sock(sk);

[...]

req = reqsk_queue_remove(queue, sk);
newsk = req->sk;

[...]

return newsk;

[...]
}

reqsk_queue_remove , 它使用 rskq_accept_headrskq_accept_tail从队列中拉出套接字并调用 sk_acceptq_removed :

static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
struct sock *parent)
{
struct request_sock *req;

spin_lock_bh(&queue->rskq_lock);
req = queue->rskq_accept_head;
if (req) {
sk_acceptq_removed(parent);
WRITE_ONCE(queue->rskq_accept_head, req->dl_next);
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
}
spin_unlock_bh(&queue->rskq_lock);
return req;
}

sk_acceptq_removed减少等待在 sk_ack_backlog 中接受的套接字队列的长度:

static inline void sk_acceptq_removed(struct sock *sk)
{
WRITE_ONCE(sk->sk_ack_backlog, sk->sk_ack_backlog - 1);
}

我认为,提问者完全理解这一点。现在让我们看看收到 SYN 以及 3WH 的最终 ACK 到达时会发生什么。

首先收到SYN。同样,我们假设 TFO 和 SYN cookie 没有发挥作用,并查看最常见的路径(至少在 SYN 泛滥时不会)。

SYN 在tcp_conn_request 中处理通过调用 inet_csk_reqsk_queue_hash_add 存储连接请求(不是完整的套接字)的位置(我们很快就会看到)然后调用send_synack响应 SYN:

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 (!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);

[...]

return 0;

[...]
}

inet_csk_reqsk_queue_hash_add电话 reqsk_queue_hash_reqinet_csk_reqsk_queue_added存储请求。

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk);
}

reqsk_queue_hash_req将请求放入哈希

static void reqsk_queue_hash_req(struct request_sock *req,
unsigned long timeout)
{
[...]

inet_ehash_insert(req_to_sk(req), NULL);

[...]
}

然后 inet_csk_reqsk_queue_added电话 reqsk_queue_addedicsk_accept_queue :

static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}

这会增加 qlen (不是 sk_ack_backlog ):

static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
atomic_inc(&queue->young);
atomic_inc(&queue->qlen);
}

ehash 是存储所有 ESTABLISHED 和 TIMEWAIT 套接字的地方,最近也是存储 SYN“队列”的地方。

请注意,将到达的连接请求存储在适当的队列中实际上没有任何意义。它们的顺序无关紧要(最终的 ACK 可以以任何顺序到达)并且通过将它们移出监听套接字,无需锁定监听套接字来处理最终的 ACK。

参见 this commit对于影响此更改的代码。

最后,我们可以看到请求从 ehash 中删除并作为完整套接字添加到接受队列中。

3WH的最终ACK由tcp_check_req处理它创建一个完整的子套接字然后调用 inet_csk_complete_hashdance :

struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
bool fastopen, bool *req_stolen)
{

[...]

/* 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,
req, &own_req);

[...]

return inet_csk_complete_hashdance(sk, child, req, own_req);

[...]

}

然后inet_csk_complete_hashdance电话 inet_csk_reqsk_queue_dropreqsk_queue_removed根据要求,和inet_csk_reqsk_queue_add关于 child :

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;
}
[...]
}

inet_csk_reqsk_queue_drop电话 reqsk_queue_unlink ,它从 ehash 中删除了请求,并且 reqsk_queue_removed减少 qlen:

void inet_csk_reqsk_queue_drop(struct sock *sk, struct request_sock *req)
{
if (reqsk_queue_unlink(req)) {
reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
reqsk_put(req);
}
}

最后,inet_csk_reqsk_queue_add将完整套接字添加到接受队列。

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;

spin_lock(&queue->rskq_lock);
if (unlikely(sk->sk_state != TCP_LISTEN)) {
inet_child_forget(sk, req, child);
child = NULL;
} else {
req->sk = child;
req->dl_next = NULL;
if (queue->rskq_accept_head == NULL)
WRITE_ONCE(queue->rskq_accept_head, req);
else
queue->rskq_accept_tail->dl_next = req;
queue->rskq_accept_tail = req;
sk_acceptq_added(sk);
}
spin_unlock(&queue->rskq_lock);
return child;
}

TL;DR 在ehash中,这样的SYN数量是qlen (而不是 sk_ack_backlog ,它保存接受队列中的套接字数量)。

关于linux - syn队列和accept队列的混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63232891/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com