11 udp 发送数据的流程梳理

news/2024/5/18 13:17:08 标签: udp, tcp/ip, 网络

前言

呵呵 之前曾经看到过 湖光大佬 的 tcp 的流程梳理

呵呵 很高深 有很多不明白的地方, 不光是涉及到 linux 网络处理本身的东西, 还涉及到了 tcp协议 的一些具体的实现, 是非常的复杂

这里之前 在 0voice/linux_kernel_wiki 上面看到了网络协议栈部分的梳理

呵呵 自己也稍微走了一下 流程, 这里稍微记录一下

主要核心的内容包含了, 用户数据传递过来, 数据包的封装, 然后到 数据包发送到驱动层 的这个流程, 当然 是没有上面的 0voice/linux_kernel_wiki 网络协议栈 部分内容详细, 以及准确 

记录于 2022.05.02 

测试环境 : linux 4.10.14 + qemu 2.5.0 

测试用例

测试用例, 拷贝自网络, 但是忘机记录具体的地址信息了, ^_^ 

udp 服务器 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8082
#define BUFSIZE 512
char buf[BUFSIZE+1];
int main()
{
  //第 1 步 创建套接字
  int sockfd=socket(AF_INET,SOCK_DGRAM,0);
  //第 2 步 设置地址结构体
  struct sockaddr_in svraddr;
  svraddr.sin_family=AF_INET;//使用 internet 协议
  svraddr.sin_port=htons(PORT);
//  inet_aton("0.0.0.0",&svraddr.sin_addr);
  inet_aton("192.168.0.103",&svraddr.sin_addr);
  //第 3 步 绑定
  int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));
  if(ret<0){printf("cannot bind!\r\n");exit(-1);};
  while(1)
  {
        struct sockaddr_in cli;
        int len=sizeof(cli);
    int z=recvfrom(sockfd,buf,BUFSIZE,0,(struct sockaddr*)&cli,&len);//第 6 步 读取套接字
    buf[z]='\0';
    printf("%s\r\n",buf);//打印
  }

}

udp 客户端 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8082
#define BUFSIZE 512
char buf[BUFSIZE+1];
int main()
{
  //第 1 步 创建一个体套接字
  int sockfd=socket(AF_INET,SOCK_DGRAM,0);
  //第 2 步 设置 addr 结构体
  struct sockaddr_in svraddr;
  svraddr.sin_family=AF_INET;//使用 internet 协议
  svraddr.sin_port=htons(PORT);
  inet_aton("0.0.0.0",&svraddr.sin_addr);
  //第 3 步 连接服务器
  //connect(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));
  buf[0] = '1';
  buf[1] = '2';
  buf[2] = '3';
//  while(1)
//  {
  sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&svraddr,sizeof(svraddr)); //第 4 步 向套接字中写入字符串
//  }
}

如上 客户端发送了 三个字节 的数据 

以太网头部, ip头部, udp 头部 合计为 14 + 20 + 8 = 42 字节, 因此 客户端给服务器发送的数据报文长度为 45 字节

//Mac头部,总长度14字节  
typedef struct _eth_hdr  
{  
    unsigned char dstmac[6]; //目标mac地址  
    unsigned char srcmac[6]; //源mac地址  
    unsigned short eth_type; //以太网类型  
} eth_hdr;
//IP头部,总长度20字节  
typedef struct _ip_hdr  
{  
    #if LITTLE_ENDIAN  
    unsigned char ihl:4;     //首部长度  
    unsigned char version:4, //版本   
    #else  
    unsigned char version:4, //版本  
    unsigned char ihl:4;     //首部长度  
    #endif  
    unsigned char tos;       //服务类型  
    unsigned short tot_len;  //总长度  
    unsigned short id;       //标志  
    unsigned short frag_off; //分片偏移  
    unsigned char ttl;       //生存时间  
    unsigned char protocol;  //协议  
    unsigned short chk_sum;  //检验和  
    struct in_addr srcaddr;  //源IP地址  
    struct in_addr dstaddr;  //目的IP地址  
} ip_hdr;
//UDP头部,总长度8字节  
typedef struct _udp_hdr  
{  
    unsigned short src_port; //远端口号  
    unsigned short dst_port; //目的端口号  
    unsigned short uhl;      //udp头部长度  
    unsigned short chk_sum;  //16位udp检验和  
} udp_hdr;

这是一段相似的报文, 不过 服务器 和 客户端 主机和我们调试的主机不同 

Frame 654: 45 bytes on wire (360 bits), 45 bytes captured (360 bits) on interface en0, id 0
Ethernet II, Src: Apple_c9:48:f9 (38:f9:d3:c9:48:f9), Dst: VMware_4e:80:29 (00:0c:29:4e:80:29)
    Destination: VMware_4e:80:29 (00:0c:29:4e:80:29)
    Source: Apple_c9:48:f9 (38:f9:d3:c9:48:f9)
    Type: IPv4 (0x0800)
Internet Protocol Version 4, Src: 192.168.0.103, Dst: 192.168.0.20
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
    Total Length: 31
    Identification: 0xaf64 (44900)
    Flags: 0x00
    Fragment Offset: 0
    Time to Live: 64
    Protocol: UDP (17)
    Header Checksum: 0x499e [validation disabled]
    [Header checksum status: Unverified]
    Source Address: 192.168.0.103
    Destination Address: 192.168.0.20
User Datagram Protocol, Src Port: 53260, Dst Port: 8082
    Source Port: 53260
    Destination Port: 8082
    Length: 11
    Checksum: 0x2a3b [unverified]
    [Checksum Status: Unverified]
    [Stream index: 5]
    [Timestamps]
    UDP payload (3 bytes)
Data (3 bytes)
    Data: 313233
    [Length: 3]

udp 发送数据包的流程 

用户调用 sendto 系统调用 和 内核进行交互, 传输的 buf 的内容是用户程序中传入的 buf 前三个字节分别为 '1', '2', '3' 

我们这里不会关注 整个流程, 我们只会关注一部分的点 

1. 用户数据 封装 msghdr 的地方 

2. msghdr 封装 skb  的地方

3. 封装 用户数据 的地方

4. 封装 ip 头的地方

5. 封装 udp 头的地方 

6. 封装 eth 头的地方

7. 封装好了 skb 之后内核传递数据给 driver 的地方

1. 用户数据 封装 msghdr 的地方 

用户程序 调用系统调用传入的输入 

buf 为 0x601080, len 为 3, 这三个字节分别对应于 '1', '2', '3' 

然后根据传入的 buf 创建了 msghdr 向下交互 

(gdb) x/2gx 0x601080
0x601080:	0x0000000000333231	0x0000000000000000

2. msghdr 封装 skb  的地方 

from 对应的是传入的 msghdr 

getfrag 对应的是将数据填充到 skb 中的函数 

3. 封装 用户数据 的地方

 将 msghdr 中封装的 buf 的数据拷贝到 skb 的指定位置[预留 ip 头 + udp 头的长度] 

这里的偏移为 20 + 8 = 28 

这里 拷贝了 '1', '2', '3' 到 skb 中

4. 封装 ip 头的地方

封装了 skb 之后, 会更新 skb 的 iphdr 的相关信息 

iphdr 数据填充之后, 我们拆解一下关键的信息项, 注意 dump 出来的内存为小端序 

source_ip 在 0xffff88007f44101c 的位置, 值为 0x0100007f = 127.0.0.1 

dst_ip 在 0xffff88007f441020 的位置, 值为 0x0100007f = 127.0.0.1 

protocol 为 0xffff88007f441019 的位置 值为 0x11 = 17 = udp 

ttl 为 0xffff88007f441018 的位置 值为 0x40 = 64 

(gdb) x/20gx 0xffff88007f441000
0xffff88007f441000:	0xffff88007f441400	0x0000000000000040
0xffff88007f441010:	0x00406b6500400045	0x0100007f00401140
0xffff88007f441020:	0x000000000100007f	0x00333231000001f8
0xffff88007f441030:	0x0000000000000008	0x0000000400000003
0xffff88007f441040:	0x0000000000000238	0x0000000000400238
0xffff88007f441050:	0x0000000000400238	0x000000000000001c
0xffff88007f441060:	0x000000000000001c	0x0000000000000001
0xffff88007f441070:	0x0000000500000001	0x0000000000000000
0xffff88007f441080:	0x0000000000400000	0x0000000000400000
0xffff88007f441090:	0x0000000000000924	0x0000000000000924

5. 封装 udp 头的地方 

封装了 skb 之后会将 skb 从 udp 层传递到 ip 层, 在此之前会先封装 udphdr 

udphdr 数据填充之后, 我们拆解一下关键的信息项, 注意 dump 出来的内存为小端序 

source_port 在 0xffff88007f441024 的位置, 值为 0x9857 = 38999 

dst_port 在 0xffff88007f441026 的位置, 值为 0x1f92 = 8082 

length 在 0xffff88007f441028 的位置, 值为 0x00b = 11 

checksum 在 0xffff88007f441030 的位置, 目前还没有计算, 值为 0x0000 = 0 

(gdb) x/20gx 0xffff88007f441000
0xffff88007f441000:	0xffff88007f441400	0x0000000000000040
0xffff88007f441010:	0x00406b6500400045	0x0100007f00401140
0xffff88007f441020:	0x921f57980100007f	0x0033323100000b00
0xffff88007f441030:	0x0000000000000008	0x0000000400000003
0xffff88007f441040:	0x0000000000000238	0x0000000000400238
0xffff88007f441050:	0x0000000000400238	0x000000000000001c
0xffff88007f441060:	0x000000000000001c	0x0000000000000001
0xffff88007f441070:	0x0000000500000001	0x0000000000000000
0xffff88007f441080:	0x0000000000400000	0x0000000000400000
0xffff88007f441090:	0x0000000000000924	0x0000000000000924

计算了 checksum 之后, 填充到 skb 中 

这里的 checksum 为 0xfe1e, 其具体的值不重要 

(gdb) x/20gx 0xffff88007f441000
0xffff88007f441000:	0xffff88007f441400	0x0000000000000040
0xffff88007f441010:	0x00406b6500400045	0x0100007f00401140
0xffff88007f441020:	0x921f57980100007f	0x003332311efe0b00
0xffff88007f441030:	0x0000000000000008	0x0000000400000003
0xffff88007f441040:	0x0000000000000238	0x0000000000400238
0xffff88007f441050:	0x0000000000400238	0x000000000000001c
0xffff88007f441060:	0x000000000000001c	0x0000000000000001
0xffff88007f441070:	0x0000000500000001	0x0000000000000000
0xffff88007f441080:	0x0000000000400000	0x0000000000400000
0xffff88007f441090:	0x0000000000000924	0x0000000000000924

6. 封装 eth 头的地方

ip 层处理之后, 路由查询, 然后来到了 数据链路层, 封装 source_mac, dst_mac, 网络层标志 等 

ethhdr 数据填充之后, 我们拆解一下关键的信息项, 注意 dump 出来的内存为小端序 

source_mac 在 0xffff88007f441002 的位置, 值为 0x000000000000 

dst_mac 在 0xffff88007f441008 的位置, 值为 0x000000000000  

type 在 0xffff88007f44100e 的位置, 值为 0x000b 为 IPV4

(gdb) x/20gx 0xffff88007f441000
0xffff88007f441000:	0x0000000000000000	0x0008000000000000
0xffff88007f441010:	0x004094681f000045	0x0100007f37d41140
0xffff88007f441020:	0x921f44950100007f	0x003332311efe0b00
0xffff88007f441030:	0x0000000000000008	0x0000000400000003
0xffff88007f441040:	0x0000000000000238	0x0000000000400238
0xffff88007f441050:	0x0000000000400238	0x000000000000001c
0xffff88007f441060:	0x000000000000001c	0x0000000000000001
0xffff88007f441070:	0x0000000500000001	0x0000000000000000
0xffff88007f441080:	0x0000000000400000	0x0000000000400000
0xffff88007f441090:	0x0000000000000924	0x0000000000000924

7. 封装好了 skb 之后内核传递数据给 driver 的地方

这里两种处理方式, 一种是直接将 skb 传递给 drvier, 直接发送 数据包 

另外一种是 进入 qdisc 队列, 根据给定的策略从 队列中刷出数据到 drvier 发送数据包, 多了一层缓冲 提高效率 

不管是上面哪一种方式, 调用 driver 的入口都是 dev_hard_start_xmit, 调用链路如下  

dev_hard_start_xmit - xmit_one - netdev_start_xmit - __netdev_start_xmit - ops->ndo_start_xmit

我这里虚拟机似乎是存在问题? 一直添加到队列, 但是没有 flush 的过程, 呵呵

ops->ndo_start_xmit 就是对应的硬件驱动发送数据包的具体的实现 

本地回环 设备对应的实现是在 drivers/net/loopback.c 中, 发送数据包的实现为 loopback_xmit, 具体实现是直接调用 netif_rx 来添加数据包入队 

后记

当然 具体的实现还有更多的细节, 这里仅仅是一个 case 来梳理了一些 用户数据 到 封包 到 发送到驱动的流程 

参考

  0voice/linux_kernel_wiki 


http://www.niftyadmin.cn/n/98643.html

相关文章

python社团 培训记录(自2023年2月24日始)

在单位开设了Python社团&#xff0c;在此记录上课的有关情况&#xff1a; 课程概述&#xff1a;本社团主要针对五、六年级&#xff0c;初始招生&#xff08;上课前&#xff09;28人&#xff08;五、六年级各14人&#xff09;&#xff0c;后&#xff08;上课时&#xff09;人员…

Mysql 语句优化 (Explain)

Mysql 语句优化 &#xff08;Explain&#xff09; 1. 概述 ​ 在 select 语句之前增加 explain 关键字&#xff0c; mysql 会在查询上设置一个标记&#xff0c;返回查询执行计划信息&#xff0c;而不是执行这条sql 字段formatjson时的名称含义idselect_id该语句的唯一标识sel…

C++学习笔记-2

C学习笔记-2输入/输出控制----I/O流命名空间的定义及使用string类型函数改进域解析符::扩大全局变量的作用域形式参数可带有默认值函数重载引用的定义与应用引用的概念及使用引用作为形式参数引用与指针的比较引用作为返回值动态内存空间用new申请动态内存空间用delete释放动态…

多目标粒子群算法求解帕累托前沿Pareto,Pareto的原理,测试函数100种求解之21

目录 背影 parte前沿的定义 注意事项 基于多目标粒子群的帕累托前沿求解 主要参数 MATLAB代码 效果图 结果分析 展望 背影 在目标优化过程种,很多时候都两个或者多个目标,并且目标函数不能同时达到最优,鱼与熊掌不可兼得,这个时候可以通过求解帕累托前沿,通过帕累托前沿…

测试——基本概念

概念 测试和调试有以下几点区别&#xff1a; 测试是测试人员进行的工作&#xff0c;调试是开发人员调试是发现并解决问题&#xff0c;测试只是发现问题测试贯穿于整个项目的生命周期&#xff0c;而调试主要在编码阶段 测试人员一般有如下的工作&#xff1a; 需求分析&#x…

PowerAutomation获取邮件附件并删除这个邮件方法

这个文章是怎么来的呢&#xff1f;现在不是低代码开发平台启蒙阶段嘛&#xff1f;笔者也有幸在工作中进行了尝试&#xff0c;目前也已经在实际工作中结合Python进行了使用&#xff0c;当然&#xff0c;是可以提高IT的工作效率的。需求是这样的&#xff0c;想从公司的EBS平台报表…

MATLAB绘制泰勒图(Taylor diagram)

泰勒图&#xff08;Taylor diagram&#xff09; 泰勒图常用于评价模型的精度&#xff0c;常用的精度指标有相关系数&#xff0c;标准差以及均方根误差(RMSE)。 一般而言&#xff0c;泰勒图中的散点代表模型&#xff0c;辐射线代表相关系数&#xff0c;横纵轴代表标准差&#x…

Java - 数据结构,队列

一、什么是队列 普通队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有先进先出FIFO(FirstIn First Out) 入队列&#xff1a;进行插入操作的一端称为队尾&#xff08;Tail/Rear&#xff09; 出队列&#xf…