首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >关于syn队列和接受队列的混淆

关于syn队列和接受队列的混淆
EN

Stack Overflow用户
提问于 2020-08-03 16:01:26
回答 2查看 2.4K关注 0票数 2

在阅读TCP源代码时,我发现一件令人困惑的事情:

我知道TCP有两个以3种方式握手的队列:

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

但是在读取代码时,我发现listen()会调用inet_csk_listen_start(),后者将调用reqsk_queue_alloc()来创建icsk_accept_queue。这个队列在accept()中使用,当我们发现队列不是空的时候,我们将从它得到一个连接并返回。

更重要的是,在跟踪接收进程之后,调用堆栈就像

代码语言:javascript
运行
复制
tcp_v4_rcv()->tcp_v4_do_rcv()->tcp_rcv_state_process()

当接收到第一次握手时,服务器状态是侦听。所以它会召唤

代码语言:javascript
运行
复制
`tcp_v4_conn_request()->tcp_conn_request()`

tcp_conn_request()

代码语言:javascript
运行
复制
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队列。

代码语言:javascript
运行
复制
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中添加新的req?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-08-10 18:07:49

在下面的内容中,我们将遵循最典型的代码路径,并将忽略数据包丢失、重传和使用诸如TCP快速打开(在代码注释中的TFO)等非典型功能所产生的问题。

要接受的调用由intet_csk_accept处理,它调用reqsk_queue_remove从接收队列&icsk->icsk_accept_queue中从侦听套接字获取一个套接字:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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中被接受的套接字队列的长度。

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
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来存储请求。

代码语言:javascript
运行
复制
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将请求放入e散列中。

代码语言:javascript
运行
复制
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_addedicsk_accept_queue调用reqsk_queue_added

代码语言:javascript
运行
复制
static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
    reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}

它增加qlen (而不是sk_ack_backlog):

代码语言:javascript
运行
复制
static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
    atomic_inc(&queue->young);
    atomic_inc(&queue->qlen);
}

E散列是存储所有已建立和TIMEWAIT套接字的地方,最近还存储了SYN“队列”。

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

有关影响此更改的代码,请参见此承诺

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

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

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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 (从e散列中移除请求)和reqsk_queue_removed (减少qlen):

代码语言:javascript
运行
复制
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将完整的套接字添加到接受队列。

代码语言:javascript
运行
复制
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在e散列中,这类SYNs的数目是qlen (而不是sk_ack_backlog,它保存接受队列中的套接字数)。

票数 5
EN

Stack Overflow用户

发布于 2020-08-09 08:34:26

简而言之,SYN队列是危险的。它们之所以危险,是因为通过发送单个数据包( SYN ),发送方可以让接收方提交资源(SYN队列条目的内存)。如果发送足够快的此类数据包(可能带有伪造的起始地址),则将导致接收方耗尽其内存资源或开始拒绝接受合法连接。

由于这个原因,现代操作系统没有SYN队列。相反,它们将使用各种技术(最常见的称为SYN ),这些技术将允许它们只为已经响应了初始SYN数据包的连接提供一个队列,从而证明它们本身就有用于此连接的专用资源。

所以,您是对的-没有SYN队列。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63232891

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档