用户态协议栈设计实现udp,arp与icmp协议

前言内核里面已经有网络协议栈了,为什么还要实现一遍用户态协议栈呢,主要是站在一个设计者的角度,自己去尝试实现一个协议栈,那么对协议栈的理解会比较透彻,这不比背八股文强?
获取原始数据获取原始数据的三种方法介绍1、使用原始套接字raw socket , tcpdump和wireshark就是使用这个做的,raw socket主要用来抓包 。
2、dbdk,使用dbdk的话篇幅较长,这里就不展开了,有兴趣的可以看
Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
3?.NETmap是用于用户层应用程序收发原始网络数据的高性能框架,本文使用netmap进行数据的收发 。
netmap内核协议栈的数据到应用层的数据会经历两次拷贝,而netmap采用mmap的方式,直接将网卡的数据映射到一块内存中,应用程序可以直接通过mmap操作相应内存的数据 。

用户态协议栈设计实现udp,arp与icmp协议

文章插图
 
零拷贝其实通过上面的图我们就能看到,采用传统的内核协议栈的方式,会发生两次拷贝,一是网卡数据拷贝到内核协议栈;二是内核再拷贝到内存中去 。而netmap采用的则是零拷贝 。
所谓零拷贝,指的是不由CPU操作,copy这个动作是由cpu发出指令move实现的,所以零拷贝就是不由CPU管理,由DMA管理 。DMA允许外设与内存直接进行数据传输,这个过程不需要CPU的参与 。
更多的零拷贝相关内容看一下这个彻底搞懂零拷贝(Zero-Copy)技术
netmap安装与常用api介绍安装netmap单独写一篇安装教程,按照这个来即可手把手教你ubuntu18.04安装netmap
netmap的头文件#include<net/netmap_user.h>在 /netmap/sys/net/下
nm_open调用 nm_open 函数时,如:nmr = nm_open("netmap:ens33", NULL, 0, NULL); nm_open()会对传递的 ifname 指针里面的字符串进行分析,提取出网络接口名 。
nm_open() 会 对 struct nm_desc *d 申 请 内 存 空 间 , 并 通 过 d->fd =open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap 来创建文件描述符 d->fd 。
注意这个fd是/dev/netmap这个网卡设备,网卡只要来数据了,相应的这个fd就会有EPOLLIN事件,这个fd是检测网卡有没有数据的,因为是mmap,只要网卡有数据了,那么内存就有数据的 。
fd是指向网卡,操作数据是操作内存,内存和网卡数据的同步的,而我们cpu只能操作内存,不能操作外设 。
简而言之,struct nm_desc里面包含一个fd,这个fd指向/dev/netmap,用于poll、epoll等系统调用 。
一旦调用 nm_open 函数,网卡的数据就不从内核协议栈走了,这时候最好在虚拟机中建两个网卡,一个用于netmap,一个用于ssh等应用程序的正常工作 。
struct nm_desc *nm_open(const char *ifname, const struct nmreq *req, uint64_t new_flags, const struct nm_desc *arg);struct nm_desc *nmr = nm_open("netmap:ens33", NULL, 0, NULL);nm_nextpktnm_nextpkt是用来接收网卡上到来的数据包的函数 。nm_nextpkt会将所有 rx 环都检查一遍,当发现有一个 rx 环有需要接收的数据包时,得到这个数据包的地址,并返回 。所以 nm_nextpkt()每次只能取一个数据包 。因为接收到的数据包没有经过协议栈处理,因此需要在用户程序中自己解析 。rx 环:想象成一个环形队列即可,每一项就是一个数据包 。
stream即为数据在缓冲区中的首地址,struct nm_pkthdr为返回的数据包头部信息,不需要管头部的话直接从stream去取数据就行了 。stream现在就是链路层的数据
用户态协议栈设计实现udp,arp与icmp协议

文章插图
 
static u_char *nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr);unsigned* stream = nm_nextpkt(nmr, &nmhead);nm_injectnm_inject()是用来往共享内存中写入待发送的数据包数据的 。数据包经共享内存拷贝到网卡,然后发送出去 。所以 nm_inject()是用来发包的 。
nm_inject()也会查找所有的发送环(tx 环),找到一个可以发送的槽,就将数据包写入并返回,所以每次函数调用也只能发送一个包 。
static int nm_inject(struct nm_desc *d, const void *buf, size_t size);nm_inject(nmr,&arp_rt,sizeof(arp_rt));nm_closenm_close 函数就是回收动态内存,回收共享内存,关闭文件描述符什么的了 。
static int nm_close(struct nm_desc *d)nm_close(nmr);相关视频推荐
手写一个用户态协议栈以及零拷贝的实现
从netmap到dpdk,从硬件到协议栈,4个维度让网络体系构建起来
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂


推荐阅读