syn队列和accept队列的混淆

2024-04-22

在阅读TCP源码时,我发现一个困惑的事情:

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

  • 第一个队列存储服务器收到的连接SYN并发回ACK + 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()`

In 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);
}

The accept()将返回已建立的连接,这意味着icsk_accept_queue是第二个队列,但是第一个队列在哪里呢?

连接从第一个队列更改到第二个队列的位置在哪里?

为什么Linux要添加新的req到icsk_accept_queue?


接下来,我们将遵循最典型的代码路径,并忽略丢包、重传以及使用 TCP 快速打开(代码注释中的 TFO)等非典型功能所引起的问题。

接受调用的处理由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;

    [...]
}

In reqsk_queue_remove, 它用rskq_accept_head and rskq_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;
}

And 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 calls reqsk_queue_hash_req and inet_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提出请求进入 ehash.

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 calls 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 (not sk_ack_backlog):

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

哈希是存储所有 ESTABLISHED 和 TIME_WAIT 套接字的位置,最近也是存储 SYN“队列”的位置。

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

See 这次提交 https://git.amelchem.com/amel/linux/commit/079096f103faca2dd87342cca6f23d4b34da8871影响此更改的代码。

最后,我们可以看到请求从 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);

    [...]

}

Then inet_csk_complete_hashdance calls inet_csk_reqsk_queue_drop and reqsk_queue_removed根据要求,并且inet_csk_reqsk_queue_add关于孩子:

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 calls 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,它保存接受队列中的套接字数量)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

syn队列和accept队列的混淆 的相关文章

  • x86-64 上这个语句有什么问题?

    该函数的目的是获取堆栈的起始地址 unsigned long find start void asm movq rsp eax 当我编译它时 出现错误 Error suffix or operands invalid for movq mo
  • CMake:不要为链接中使用的单个库设置 rpath

    我想要做的是配置我的 CMakeLists 文件 以便在构建我的项目时 链接器使用驻留在我的构建树中的共享库 so 的副本来链接可执行文件 但不会在中设置 rpath链接的可执行文件 以便系统必须在加载程序请求时提供该库 具体来说 我想在构
  • PHP exec - 检查是否启用或禁用

    有没有办法检查 php 脚本是否exec 在服务器上启用还是禁用 这将检查该功能是否确实有效 权限 权利等 if exec echo EXEC EXEC echo exec works
  • 健全性检查 SSH 公钥? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我已要求用户提供他们的公共 id rsa pub ssh 密钥 然后将其放入 home theiraccount ssh authorized key
  • 在 LINUX 上测量 TLB 未命中的命令

    有人可以指导我使用一个命令来测量 LINUX 上的 TLB 未命中吗 是否可以将轻微页面错误视为 TLB 未命中 您可以使用perf去做这个 前提是你的CPU支持 Use perf list了解可用的计数器 当我拿到这个列表并查找 TLB
  • 尝试从输入流检索文本时应用程序挂起

    情况 我确实查看了您的代码 正如我怀疑的那样 您的问题与您发布的代码完全无关 您的 GUI 完全忽略 Swing 线程规则 并在主 Swing 事件线程 称为Event Dispatch T螺纹或EDT 由于该线程负责所有 Swing 绘图
  • 如何永久清除 linux/ubuntu 终端或 bash 中的所有历史记录? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 当您在 Linux 终端中使用向上键时 可以再次使用之前的命令 很棒的功能 但是 我开始使用命令中的敏感详细信息将 mysql 记录到 mysql 中
  • 如何获取uinput创建的设备的名称(路径)

    我已经成功设置了一个小程序来创建uinput questions tagged uinput我计划使用它来自动测试接收键盘输入事件的应用程序 我已关注both http thiemonge org getting started with
  • 保护一个保存 MySQL 数据库的简单 Linux 服务器?

    这是一个初学者问题 但我浏览了该网站上的许多问题 但没有找到简单直接的答案 我正在设置一个运行 Ubuntu 的 Linux 服务器来存储 MySQL 数据库 该服务器尽可能安全非常重要 据我所知 我主要担心的是传入的 DoS DDoS 攻
  • 将 -1 作为文件描述符传递给 mmap

    我对 FC17 Linux 中的 ls 命令进行了 strace 以下是输出 execve usr bin ls ls 48 vars 0 brk 0 0x27c1000 mmap NULL 4096 PROT READ PROT WRIT
  • 找出某个日期时间自unix纪元以来的时间?

    我想找出 2009 年 10 月 1 日 9 00 BST 的 UNIX 时间 即自 Unix 纪元以来的秒数 我如何在 Linux 命令行上执行此操作 我知道你可以使用date UNIXTIME someformat 但是unix时间是我
  • 如何在汇编语言中换行打印多个字符串

    我试图在汇编中的不同行上打印多个字符串 但使用我的代码 它只打印最后一个字符串 我对汇编语言非常陌生 所以请耐心等待 section text global start start mov edx len mov edx len1 mov
  • ZeroMQ可以用来接受传统的套接字请求吗?

    我正在尝试使用 ZeroMQ 重写我们的旧服务器之一 现在我有以下服务器设置 适用于 Zmq 请求 using var context ZmqContext Create using var server context CreateSoc
  • GCC 4.7 字符串文字的源字符编码和执行字符编码?

    Linux x86 64 上的 GCC 4 7 是否具有默认字符编码 用于验证和解码 C 源文件中字符串文字的内容 这是可配置的吗 此外 当将字符串数据从字符串文字链接到输出的数据部分时 它是否具有默认的执行字符编码 这是可配置的吗 在任何
  • Amazon EC2 - Apache 服务器重启问题

    当我运行这个命令时 sudo etc init d httpd restart 它给出以下错误 停止 httpd 失败 启动 httpd 98 地址已在使用中 make sock 无法绑定到地址 80 98 地址已在使用 make sock
  • 如何在 Linux 上调用 Python 中的内联机器代码?

    我正在尝试从 Linux 上的纯 Python 代码调用内联机器代码 为此 我将代码嵌入到字节文字中 code b x55 x89 xe5 x5d xc3 然后打电话mprotect http www kernel org doc man
  • php中的可变长度数据包

    我正在接收通过 UDP 发送到我的服务器的数据包 我正在使用 socket read 来读取数据 它运行得很好 但是我遇到了一个错误 在我的例子中 socket read 的长度参数并不总是相同的 数据长度的范围可以是 50 150 字节
  • __libc_start_main 发生了什么?

    我真的很想理解从高级代码到可执行文件的步骤 但是遇到了一些困难 我写了一个空的int main C 文件并尝试通过以下方式破译反汇编objdump d 这是发生的事情 in start 设置对齐方式 将参数压入堆栈 调用 libc star
  • AMQP如何克服直接使用TCP的困难?

    AMQP如何克服直接使用TCP发送消息时的困难 或者更具体地说 在发布 订阅场景中 在 AMQP 中 有一个代理 该代理接收消息 然后完成将消息路由到交换器和队列的困难部分 您还可以设置持久队列 即使客户端断开连接 也可以为客户端保存消息
  • 视频流上的 TCP 与 UDP

    我刚从网络编程考试回来 他们问我们的问题之一是 如果您要传输视频 您会使用 TCP 还是 UDP 请解释一下存储视频和实时视频流 对于这个问题 他们只是希望得到一个简短的答案 TCP 用于存储视频 UDP 用于实时视频 但我在回家的路上想到

随机推荐

  • Backbone.Marionette 在路由更改时更改区域

    我的应用程序有一个主区域 有时主区域中会有一些应可通过 URL 访问的子区域 主要区域内容由应用程序路由器的功能更改 因为他知道主要区域 但是子视图中的临时区域呢 例如网址 docs将显示文档链接列表以及 doc id应在列表旁边显示文档的
  • 具有多个参数的 COM“获取属性”

    我正在尝试打电话WindowsInstaller Installer ProductsEx http msdn microsoft com en us library aa369461 28v vs 85 29 aspx来自 python
  • 导航栏是否应该始终以列表的形式实现?

    首先 非常抱歉 如果这不是一个 真正的 stackoverflow 问题 但这是我一直想知道的事情 当您为网站 html 编写导航栏时 我读到这是非常好的实践 即使不是使用列表标签实现它的唯一实践 例如 ul li Home li li A
  • 嵌套聚合物组件内容问题

    foo html
  • Yii2如何检查两个模型是否已经链接

    我有两个通过连接表关联的模型 model gt link 是用于建立两个模型之间关系的方法 它基本上用两个模型的相应键填充连接表 如果两个模型已链接并且我尝试再次链接它们 则会出现错误 因为密钥对已存在于连接表中 然后我需要在尝试链接模型之
  • 将工作簿的所有工作表复制到另一个文件

    我想对我的所有 xlsx 执行以下步骤 复印第一张纸 将复制的工作表粘贴到另一个文件 将粘贴的工作表重命名为文件名 重复步骤 1 并将工作表复制到步骤 2 的同一文件中 重复步骤 3 我可以用 R 执行此步骤吗 我的 xlsx contei
  • Jquery 下一个相邻选择器 $(this)

    我如何将相邻的选择器 与 this 一起使用 我需要有关注释行的帮助 this does not work ExpandCollapse click function if this nextUntil Collapsable is vis
  • JSONB 会让 PostgreSQL 数组变得无用吗?

    假设您想在对象 例如帖子 上存储 标签 在 9 4 版本中 您有 3 个主要选择 标签作为文本 标签为 jsonb 标签作为文本 并且您将 JSON 字符串存储为文本 在许多情况下 第三个是不可能的 因为它不允许对 标签 值进行条件查询 在
  • `istreambuf_iterator` 和 `istream_iterator` 之间的区别

    有什么区别istreambuf iterator and istream iterator 对于以下代码 istream iterator
  • 将 --net=host 传递给 docker build

    将其他选项传递给docker build 你可以指定DOCKER OPTS in etc default docker 但是 net 不可用 构建容器时是否可以使用主机的网络堆栈 我正在运行 Docker 版本 1 3 2 内部版本 39f
  • 删除“OPTIMIZE_FOR_SEQUENTIAL_KEY”会解决我的 script.sql 的失败问题还是涉及更多问题(SQL Server Express DB -> SQL Server)?

    我正在尝试在托管站点 GoDaddy Plesk 上将 SQL Server Express 数据库设置为成熟的 SQL Server DB 但在运行 script sql 文件时遇到了一系列错误消息 我正在按照步骤操作here https
  • Pandas 将值与前一行与过滤条件进行比较

    我有一个包含员工工资信息的数据框 大约有 900000 多行 Sample table num name salary 0 001234 John Johnson 1200 1 001234 John Johnson 1000 2 0012
  • 在 cartopy 墨卡托投影上绘制一个圆

    对于一个项目 我需要创建一个可视化效果 在地图上的某些位置周围绘制一个圆圈 使用的可视化卡托比 v 0 18 0 https scitools org uk cartopy docs latest index html渲染地图 它使用Goo
  • 我可以使用 display:inline 和 text-align: 对吗?

    Example td img src img src div style text align left hello world div td 从技术上讲 你可以 但不会有任何效果 Display inline 会将 div 显示为内联元素
  • python 当程序突然结束时如何保持json的完整性

    我在一个打开 10 个子进程的脚本中运行 多线程 每个子脚本都会查询 API 并将一些数据记录到 json 中 我将每个 json 保存在另一个 json 中 但是代码有时会突然结束并破坏 json 的完整性 因此无法重新打开它 例如 20
  • unity3d - 加速度计灵敏度

    我正在 Unity3D 4 3 中测试加速度计代码 我想做的就是在倾斜 ipad 的同时简单地改变物体角度 以像真实生活一样伪造视角 一切工作正常 除了加速计有点太敏感 即使我把它放在桌子上 我也可以看到游戏对象在闪烁 如何让它不那么敏感
  • 如何使用 MiniProfiler.Settings.CustomUITemplates

    再会 我在上一篇文章以及上一篇文章中都看过这里http miniprofiler com http miniprofiler com 但找不到任何有关如何使用的文档MiniProfiler Settings CustomUITemplate
  • Hibernate 排序依据最后为空

    Hibernate 与 PostgreSQL DB 一起使用时 按列对 desc 进行排序时 空值会高于非空值 SQL99 标准提供关键字 NULLS LAST 来声明空值应低于非空值 使用 Hibernate 的 Criteria API
  • 如何在 Django REST Framework 上启用 CORS

    如何在 Django REST Framework 上启用 CORS 这参考 http www django rest framework org topics ajax csrf cors 没有多大帮助 它说我可以通过中间件来完成 但我该
  • syn队列和accept队列的混淆

    在阅读TCP源码时 我发现一个困惑的事情 我知道 TCP 在 3 次握手中有两个队列 第一个队列存储服务器收到的连接SYN并发回ACK SYN 我们称之为同步队列 第二个队列存储3WHS成功并建立连接的连接 我们称之为接受队列 但在阅读代码