【C语言】tcp_transmit_skb

一、__tcp_transmit_skb讲解

这个函数 __tcp_transmit_skb() 是 Linux 内核中 TCP/IP 协议栈的一部分,负责处理传输控制协议(TCP)数据包的发送。具体来说,这个函数将 TCP 头部添加到一个没有任何头部信息的 socket buffer (sk_buff) 数据包上,然后使用 IP 层进行进一步处理,并将数据包发送到网络设备。接下来用中文逐段说明函数的工作过程:
1. 函数接收一些参数:`sk`是指向socket结构的指针,`skb`是待发送的数据包,`clone_it`指示是否需要克隆`skb`,`gfp_mask`定义了内存分配标志,`rcv_nxt`是接收方期望收到的下一个序列号。
2. 它初始化了一些 TCP 和 IP 的相关结构,并检查传入的`skb`必须有有效的数据。
3. 如果`clone_it`为真(非0),函数将复制原始的`skb`。复制可能通过克隆(仅复制数据包头部)或完整复制来实现,具体取决于原始`skb`是否已被其他代码克隆。复制失败,函数返回错误:`-ENOBUFS`表示没有足够的缓冲区。
4. 函数计算了 TCP 头部大小,这包括 TCP 选项(如最大段大小、时间戳等的选项)。
5. 函数设置了一些标记来指示数据包是否可以被重排(`ooo_okay`)和是否因为使用了保留内存而可能会被丢弃(`pfmemalloc`)。
6. 通过`skb_push`向数据包添加 TCP 头部,并重置传输层头部指针。
7. 设置数据包的一些属性,如所有权、销毁函数和哈希值。
8. 把 TCP 头部中的字段(source port、destination port、sequence number、acknowledgement number 等)填充到`skb->data`指向的内存中。
9. 如果启用了 TCP MD5 签名选项并且是必要的话,计算 MD5 哈希。
10. 通过`icsk->icsk_af_ops->send_check` 计算发送校验和。
11. 如果数据包是一个确认 ACK,那么通过`tcp_event_ack_sent()`更新相关统计信息。
12. 如果`skb`包含数据(不只是一个 TCP 头部),那么函数将更新发送的数据统计信息,并可能进行 TCP 内部流控。
13. 更新 TCP 状态统计,并且,如果数据包是 GSO(Generic Segmentation Offload)类型的话,设置 GSO 相关字段。
14. 清除`skb->cb`(控制缓冲区)中可能由底层 IP 代码使用过的任何信息。
15. 最后,函数通过`icsk->icsk_af_ops->queue_xmit`函数将数据包发送到网络队列,等待网络设备处理。
16. 如果`queue_xmit`返回错误,那么更新 TCP 状态以响应网络拥塞,并找出实际的错误代码。
17. 如果已经成功地发送了数据包并且有一个原始数据包 oskb,那么更新它的状态并记录发送速率。
整体来说,`__tcp_transmit_skb()` 是一个在内核中发送 TCP 数据包的核心函数,涉及到从 TCP 头部创建,选项处理,到最终数据包发送到网络设备的很多步骤。

二、__tcp_transmit_skb中文注释

/* 这个例程实际上是发送由tcp_do_sendmsg()排队的TCP数据包。
 * 它被用于初始传输和可能的后续重传。
 * 这里看到的所有SKB(socket缓冲区)都是完全无头的。我们的任务是构建TCP头,
 * 然后把数据包传递给IP层,以便它也能做相同的操作,并且将数据包交给
 * 物理设备。
 *
 * 我们在这里工作的是原始SKB的一个克隆,或者是由重传引擎制作的
 * 一个全新的独立副本。
 */
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
                  int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    // 与此套接字相关的TCP连接的inet结构体。
    const struct inet_connection_sock *icsk = inet_csk(sk);
    struct inet_sock *inet;
    struct tcp_sock *tp;
    struct tcp_skb_cb *tcb;
    // 定义TCP头部选项。
    struct tcp_out_options opts;
    unsigned int tcp_options_size, tcp_header_size;
    struct sk_buff *oskb = NULL;
    struct tcp_md5sig_key *md5;
    struct tcphdr *th;
    int err;
    // 如果没有skb或者skb中没有计数的TCP段,则抛出bug。
    BUG_ON(!skb || !tcp_skb_pcount(skb));
    tp = tcp_sk(sk);
    // 如果需要克隆skb。
    if (clone_it) {
        // 记录在飞数据量。
        TCP_SKB_CB(skb)->tx.in_flight = TCP_SKB_CB(skb)->end_seq
            - tp->snd_una;
        oskb = skb;
        // 尝试保存TCP段排序状态,克隆或者复制skb,然后恢复排序状态。
        tcp_skb_tsorted_save(oskb) {
            if (unlikely(skb_cloned(oskb)))
                skb = pskb_copy(oskb, gfp_mask);
            else
                skb = skb_clone(oskb, gfp_mask);
        } tcp_skb_tsorted_restore(oskb);
        // 如果skb克隆/复制失败,则返回内存不足。
        if (unlikely(!skb))
            return -ENOBUFS;
    }
    // 记录skb的时间戳。
    skb->skb_mstamp = tp->tcp_mstamp;
    inet = inet_sk(sk);
    tcb = TCP_SKB_CB(skb);
    // 清除opts内存区域。
    memset(&opts, 0, sizeof(opts));
    // 如果skb是SYN包,则根据SYN包设置TCP选项,否则根据已建立连接设置TCP选项。
    if (unlikely(tcb->tcp_flags & TCPHDR_SYN))
        tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);
    else
        tcp_options_size = tcp_established_options(sk, skb, &opts,
                               &md5);
    // 计算TCP头部大小。
    tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
    // 如果qdisc/设备队列中没有数据包,则XPS可以选择另一个队列。
    // 我们可能从持有对sk的引用的tcp_tsq_handler()调用它。
    skb->ooo_okay = sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1);
    // 如果我们使用了内存保留区来分配这个skb,如果数据包被回环了可能会造成丢包:
    // 其他的套接字可能没有设置SOCK_MEMALLOC。非回环的数据包则不论pfmemalloc。
    skb->pfmemalloc = 0;
    // 在skb头部加上TCP头部大小的数据,并重新设置传输层头部。
    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);
    // 将skb设为孤立状态。
    skb_orphan(skb);
    // 将skb和sock关联起来,并设置skb的析构函数。
    skb->sk = sk;
    skb->destructor = skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;
    // 从sock中设定skb的哈希值。
    skb_set_hash_from_sk(skb, sk);
    // 增加skb占用的内存大小计数。
    refcount_add(skb->truesize, &sk->sk_wmem_alloc);
    // 设置skb等待确认的状态。
    skb_set_dst_pending_confirm(skb, sk->sk_dst_pending_confirm);
    // 构建TCP头并校验它。
    th = (struct tcphdr *)skb->data;
    // 设置源端口和目的端口。
    th->source        = inet->inet_sport;
    th->dest        = inet->inet_dport;
    // 设置序列号和确认号。
    th->seq            = htonl(tcb->seq);
    th->ack_seq        = htonl(rcv_nxt);
    // 设置TCP头部中除了前面两个字节之外的字段。
    (((__be16 )th) + 6)    = htons(((tcp_header_size >> 2) << 12) |
                    tcb->tcp_flags);
    th->check        = 0;
    th->urg_ptr        = 0;
    // 如果当前包含紧急指针的包在snd_una探测窗口之下,
    // 则需要设置紧急指针。
    if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {
        if (before(tp->snd_up, tcb->seq + 0x10000)) {
            th->urg_ptr = htons(tp->snd_up - tcb->seq);
            th->urg = 1;
        } else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {
            th->urg_ptr = htons(0xFFFF);
            th->urg = 1;
        }
    }

    // 将TCP选项复制到TCP头之后的空间。
    tcp_options_write((__be32 *)(th + 1), tp, &opts);
    // 设置skb的GSO类型。
    skb_shinfo(skb)->gso_type = sk->sk_gso_type;
    // 如果不是SYN包,设置TCP窗口大小,并在需要时标记ECN标志;如果是SYN包,设置初始窗口大小。
    if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
        th->window      = htons(tcp_select_window(sk));
        // 如果可能,发送ECN通告。
        tcp_ecn_send(sk, skb, th, tcp_header_size);
    } else {
        /* RFC1323: 在SYN和SYN/ACK段中的窗口大小
         * 是不会被扩大的。
         */
        th->window    = htons(min(tp->rcv_wnd, 65535U));
    }
#ifdef CONFIG_TCP_MD5SIG
    // 如果启用了MD5签名,计算MD5哈希,因为我们现在有了所需的全部数据。
    if (md5) {
        sk_nocaps_add(sk, NETIF_F_GSO_MASK);
        tp->af_specific->calc_md5_hash(opts.hash_location,
                           md5, sk, skb);
    }
#endif

    // 由底层网络函数完成skb的校验和。
    icsk->icsk_af_ops->send_check(sk, skb);

    // 如果skb中设置了TCPHDR_ACK标志,则记录ACK已发送的事件。
    if (likely(tcb->tcp_flags & TCPHDR_ACK))
        tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);

    // 如果skb的长度不只是TCP头部的长度,表示有数据被发送,
    // 更新统计信息。
    if (skb->len != tcp_header_size) {
        tcp_event_data_sent(tp, sk);
        tp->data_segs_out += tcp_skb_pcount(skb);
        tp->bytes_sent += skb->len - tcp_header_size;
        // 基于内部节流算法对发送的数据包进行速度控制。
        tcp_internal_pacing(sk, skb);
    }

    // 如果当前数据段的结束序列号在snd_nxt之后或与之相等,
    // 或者如果是一个单独的序列号,更新发送的数据包统计。
    if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
        TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
                  tcp_skb_pcount(skb));

    tp->segs_out += tcp_skb_pcount(skb);
    // 设置skb中GSO(分段卸载)相关的字段。
    skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
    skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);

    // 我们使用的时间戳应保持私有。
    skb->tstamp = 0;

    // 清理我们对IP栈的"痕迹",重置skb的控制块。
    memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
                   sizeof(struct inet6_skb_parm)));

    // 通过网络函数将skb发送出去。
    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);

    // 如果出现错误,调用tcp_enter_cwr函数进入拥塞窗口减少状态,并评估错误。
    if (unlikely(err > 0)) {
        tcp_enter_cwr(sk);
        err = net_xmit_eval(err);
    }
    // 如果发送成功并且有原始的skb,更新相关统计数据。
    if (!err && oskb) {
        tcp_update_skb_after_send(tp, oskb);
        tcp_rate_skb_sent(sk, oskb);
    }
    // 返回错误码。
    return err;
}

这段代码是Linux内核中处理TCP数据包发送的例程,其中使用了网络编程和内核编程的深层次的知识,包括:对socket缓冲区(SKB)的操作,网络协议栈(如TCP/IP)的详细处理,以及用于数据包发送的内核API。

三、tcp_transmit_skb

这个函数 tcp_transmit_skb 是用于发送一个TCP数据包的函数,它是一个简单的包装函数,调用了内部实现 __tcp_transmit_skb。
参数解释:
- struct sock *sk: 这代表一个TCP套接字,包含了TCP连接的状态信息。
- struct sk_buff *skb: 这是要发送的数据包缓冲区,它不包含TCP头部,由 __tcp_transmit_skb 函数负责构建。
- int clone_it: 这是一个标志,用来决定是否需要复制(或者说克隆)给定的SKB数据包。
- gfp_t gfp_mask: 这是内存分配标志,决定了函数在需要内存时的行为。
函数的主体调用 __tcp_transmit_skb,传递了给定的参数,并在调用时,获取了当前的TCP套接字状态,尤其是 tcp_sk(sk)->rcv_nxt。`rcv_nxt` 代表了下一个期望接收的序列号,这通常要作为ACK在TCP头部中回复给发送方。
在函数调用 __tcp_transmit_skb 后,任何实际的数据包传输、重传和解包处理都是在 __tcp_transmit_skb 函数中完成的,而 tcp_transmit_skb 本身的作用主要是一个中间调用层。

这个函数是Linux内核网络协议栈中的一部分,特定于TCP协议的传输控制块(Transmission Control Block)的处理程序。它的作用是发送一个TCP段。以下是该函数的中文注释:

// tcp_transmit_skb 是一个静态函数,其目的是发送一个 TCP 段(segment)。
// 参数:
// sk: 指向相关TCP套接字的指针。
// skb: 要发送的套接字缓冲区(sk_buff)的指针。
// clone_it: 一个整数标志,指示是否需要克隆skb,非0表示需要克隆。
// gfp_mask: 内存分配时使用的标志,确定GFP(Get Free Page)的行为。

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,
                gfp_t gfp_mask)
{
    // 调用具体的发送函数 __tcp_transmit_skb 来处理实际的发送逻辑。
    // 传入参数sk (套接字结构体指针)和skb (套接字缓冲区指针)。
    // 参数clone_it用于控制是否克隆skb。
    // 参数gfp_mask用于内存分配的GFP控制。
    // 函数内部使用tcp_sk(sk)->rcv_nxt来获取当前TCP连接的下一个期望接收的序列号,
    // 作为发送时序列号的参数。
    return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask, tcp_sk(sk)->rcv_nxt);
}

此函数实质上是个包装函数,它只是简单地把参数传递给实际的发送函数 __tcp_transmit_skb,同时取出TCP socket中的 rcv_nxt 字段(即下一个期望接收的TCP序列号)作为参数之一。在TCP通信中,发送方和接收方都需要维护对方的序列号,这对确保数据包的顺序和完整性至关重要。此函数的返回值是由 __tcp_transmit_skb 决定的,通常表示发送操作的成功与否。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/453318.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

食药物质创新 赋能中式滋补健康产业发展交流会圆满结束

3月5日&#xff0c;“食药物质创新 赋能中式滋补健康产业发展交流会”在山东国际会展中心召开。本次会议由中国生物发酵产业协会主办&#xff0c;浙江科技大学、未名太研生物科技(绍兴)有限公司承办&#xff0c;汇乐达供应链服务(常州)有限责任公司支持。本次论坛旨在加强行业创…

C语言--从零开始的扫雷游戏

C语言--从零开始的扫雷游戏 1. 游戏说明2. 总体代码3. 详细讲解3.1 菜单部分3.2 游戏主体部分3.2.1 总体分析3.2.2 棋盘初始化3.2.3 棋盘展示3.2.4 设置地雷3.2.5 扫雷阶段3.2.6 统计雷个数的代码3.2.7 使用迭代的方式进行展开&#xff1a;3.2.8 扫雷部分主体代码 4. 总结 1. 游…

SpringBoot(源码解析 + 实现底层机制)

文章目录 1.搭建SpringBoot底层机制开发环境1.创建maven项目2.使用Git管理项目&#xff08;可以略过&#xff09;1.创建一个github存储库2.克隆到本地&#xff0c;复制文件夹的内容3.粘贴到idea项目文件夹&#xff0c;将其作为本地仓库与远程仓库关联 3.pom.xml 引入父工程和场…

【算法积累】辗转相除法

【算法积累】辗转相除法&#xff0c;python实现两种 辗转相除法&#xff08;又称欧几里得算法&#xff09;减法&#xff08;不常用&#xff09;代码实现执行结果 辗转相除法代码实现执行结果 辗转相除法&#xff08;又称欧几里得算法&#xff09; 又称欧几里得算法&#xff0c…

使用Python将多个pdf指定页整合到一个pdf文件中

在工作的一些场景中&#xff0c;有时需要我们将多个pdf文件中的内容提取出来&#xff0c;比如有10个pdf文件&#xff0c;我们要统一打印pdf文件的第一页或者最后一页… 需求分析 我们需要批量提取PDF文件中的任意一页&#xff0c;可以是第一页也可以是中间某一页&#xff0c;…

【C++算法模板】图的存储-邻接矩阵

文章目录 邻接矩阵洛谷3643 图的存储 邻接矩阵 邻接矩阵相比于上一篇博客邻接表的讲解要简单得多 数据结构&#xff0c;如果将二维数组 g g g 定义为全局变量&#xff0c;那默认初始化应该为 0 0 0 &#xff0c;如果题目中存在自环&#xff0c;可以做特判&#xff0c; m e …

微信小程序云开发教程——墨刀原型工具入门(常用组件)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

docker+elasticsearch

一&#xff0c;环境准备&#xff1a;安装docker&#xff08;往期文章&#xff09; 二&#xff0c;elasticsearch简介&#xff1a; 用于储存数据 三&#xff0c;部署&#xff1a; 1&#xff09;&#xff0c;拉取镜像 使用本作者提供的java17镜像 2&#xff09;&#xff0c;…

基于大模型的Agent进行测试评估的3种方案

本文首发于博客 基于大模型的Agent进行测试评估的3种方案 我们都知道当前基于大模型构建的 Agent 能力极不稳定&#xff0c;而今年我司产品又在规划接入 Agent 能力&#xff0c;所以在引入之前&#xff0c;需要先设计一套测试框架&#xff0c;来看看各种场景下容错率是否能达…

Linux 基本命令

文章目录 1.echo2.cd3.find4.mkdir5.cp6.rm7.wc8.tar9.tail10.vim11.grep12.sed13 touch14 ls15 快捷键16 ln17 mv18 useradd19 usermod20 su 每天一个Linux命令 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 1.echo 中文 (Chinese): “回声” 或 “输…

分布式链路追踪(一)SkyWalking(2)使用

一、使用方法 1、简介 agent探针可以让我们不修改代码的情况下&#xff0c;对Java应用上使用到的组件进行动态监控&#xff0c;获取运行数据发送到OAP上进行统计和存储。agent探针在Java使用中是使用Java agent技术实现。不需要更改任何代码&#xff0c;Java agent会通过虚拟…

Linux虚拟机安装Qt步骤记录

(一&#xff09;安装命令&#xff0c;按照网上的教程&#xff0c;亲测可行 在终端中依次输入以下命令&#xff0c; 1.更新软件源列表&#xff1a; sudo apt-get update 2.安装Qt开发工具包,windows上我用的是Qt6,根据网上也是初次在Linux上安装Qt,安装版本5应该问题不大&…

智慧农业新篇章:DSSAT模型、APSIM模型、WOFOST与PCSE模型综合应用,引领作物生长模拟与产量预测新潮流

目录 ★WOFOST模型与PCSE模型应用 ★基于R语言APSIM模型进阶应用与参数优化、批量模拟 ★最新DSSAT作物模型建模方法及应用 ★基于Python语言快速批量运行DSSAT模型及交叉融合、扩展应用 ★R语言与作物模型&#xff08;以DSSAT模型为例&#xff09;融合应用 ★遥感数据与…

论文阅读——VSA

VSA: Learning Varied-Size Window Attention in Vision Transformers 方法&#xff1a; 给定输入特征X&#xff0c;VSA首先按照基线方法的例程&#xff0c;将这些标记划分为几个窗口Xw&#xff0c;窗口大小为预定义的w。我们将这些窗口称为默认窗口&#xff0c;并从默认窗口中…

KMP算法——解决字符串匹配问题

一般来说在你没学过KMP算法前&#xff0c;你解决字符串匹配问题会采用BF算法——BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配&#xff0c;若相等&#xff0c;…

BM1684X搭建sophon c++环境

1:首先安装编译好sophon-sail 比特大陆BM1684X开发环境搭建--SOC mode-CSDN博客 2:在将之前配置的soc-sdk拷贝一份到sdk根目录&#xff0c;将交叉编译好的sail中的build_soc拷贝至soc-sdk文件夹内&#xff1b; cp -rf build_soc/sophon-sail/inlcude soc-sdk cp -rf build_soc…

YOLOv8独家改进:backbone改进 | TransXNet:聚合全局和局部信息的全新CNN-Transformer视觉主干| CVPR2024

💡💡💡本文独家改进:CVPR2024 TransXNet助力检测,代替YOLOv8 Backbone 改进结构图如下: 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/category_12511737.html?spm=1001.2014.3001.5482 💡💡💡全网独家首发创新(原创),适合paper !!! 💡…

突发:日本火箭发射后爆炸

综合路透社、法新社13日消息&#xff0c;现场直播画面显示&#xff0c;日本初创公司Space One火箭发射失败&#xff0c;在半空中发生爆炸。 ▲日媒视频报道截图 据共同社此前介绍&#xff0c;Space One公司11日宣布&#xff0c;13日上午11点零1分将从日本首个民用火箭发射场“…

数据集成平台选型建议

一 数据集成介绍 数据集成平台是一种用于管理和协调数据流动的软件工具或服务。它的主要目标是将来自多个不同数据源的数据整合到一个统一的、易于访问和分析的数据存储库中。这些数据源可以包括数据库、云应用、传感器、日志文件、社交媒体等等。数据集成平台的关键任务是确保…

代码随想录算法训练营第五九天 | 下一个更大元素II、接雨水

目录 下一个更大元素II接雨水 LeetCode 503.下一个更大元素II LeetCode 42. 接雨水 下一个更大元素II 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一…