【C语言】tcp_transmit_skb

2024-03-10 08:20
文章标签 语言 tcp transmit skb

本文主要是介绍【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);elseskb = 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);elsetcp_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 决定的,通常表示发送操作的成功与否。

这篇关于【C语言】tcp_transmit_skb的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/793637

相关文章

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

C 语言的基本数据类型

C 语言的基本数据类型 注:本文面向 C 语言初学者,如果你是熟手,那就不用看了。 有人问我,char、short、int、long、float、double 等这些关键字到底是什么意思,如果说他们是数据类型的话,那么为啥有这么多数据类型呢? 如果写了一句: int a; 那么执行的时候在内存中会有什么变化呢? 橡皮泥大家都玩过吧,一般你买橡皮泥的时候,店家会赠送一些模板。 上

【Go】go连接clickhouse使用TCP协议

离开你是傻是对是错 是看破是软弱 这结果是爱是恨或者是什么 如果是种解脱 怎么会还有眷恋在我心窝 那么爱你为什么                      🎵 黄品源/莫文蔚《那么爱你为什么》 package mainimport ("context""fmt""log""time""github.com/ClickHouse/clickhouse-go/v2")func main(