Linux C/C++服务器
用户态网络协议栈(Netmap)设计实现
通常情况下网络协议栈中的数据链路层、网络层、传输层是由内核程序完成,
而用户态网络协议栈则由我们自己来完成,后边有Udp协议栈的设计实现过程
现实工作中几乎不用我们自己去实现协议栈,一般都会用开源的已验证过的协议栈,
自己实现需要考虑到与其他框架的兼容性问题,为了更好的了解内部原理,我们这里选择实现一下
本次将使用 旁路 + Netmap 方法来实现用户态网络协议栈
物理层是指网线中的电信号或光纤中的光信号,通过网卡进入我们所使用的系统,
网卡驱动会将模拟信号转化输出为二进制数字信号,其中有个大名鼎鼎的结构体叫 sk_buff,
我们使用原生网络就是使用的这个结构体中的数据,它是内核网络数据流的开端,由内核网卡驱动eth0生成,
那么我们要实现用户态网络协议栈如何获取最原始的网络数据流呢?
1. 数据流获取
用户态网络数据流获取的几种方法
- raw socket(原生socket)
- 旁路,netmap/dpdk
- hook, bpf/ebpf
原生的socket还是使用的内核提供的网卡驱动程序
什么是旁路?旁路指屏蔽掉内核网卡驱动,使用我们自己的网卡驱动(一般使用开源的)获取数据
除了旁路这种方式,还有一种就是钩子,在netfilter或者其他地方使用hook来获取数据流
零拷贝,我们采用mmap内存映射的方式,将网卡数据直接映射到内存中,避免拷贝数据
2.Udp协议栈设计实现
一个完整的Udp数据帧包含四部分:
链路层的ether_hdr(以太网协议头)、网络层的iphdr(ip协议头)、传输层的udphdr(udp协议头)、用户层数据报文
2.1 以太网协议头
#define ETHER_ADDR_LEN 6
struct etherhdr{ //以太网头
unsigned char dst_mac[ETHER_ADDR_LEN];
unsigned char src_mac[ETHER_ADDR_LEN];
unsigned short protocol;
};
2.2 ip协议头
struct iphdr { //ip头
unsigned char version:4,
hdrlen:4; //两个4位大小类型的定义方式
unsigned char tos;
unsigned short totlen;
unsigned short id;
unsigned short flag:3,
offset:13;
unsigned char ttl;
unsigned char protocol;
unsigned short check;
unsigned int sip
unsigned int dip;
};
2.3 udp协议头
struct udphdr{ //udp头
unsigned short sport;
unsigned short dport;
unsigned short length;
unsigned short check;
};
2.4 udp数据包
struct udppkt{
struct etherhdr eth;
struct iphdr ip;
struct udphdr udp;
/*
unsigned char *payload; //不能保证连续内存空间
unsigned char payload[65535]; //可能会越界
*/
unsigned char payload[0]; //零长数组,保证连续空间而且不会越界,这个值的大小我们是可以计算出来
};
零长数组
unsigned char payload[0];
应用场景:长度不确定,但是我们可以计算出来,也就是说,内存已经分配了,我们使用不会出问题,多用于内存池这类场景中
3.Netmap协议栈 Demo
//netmap 是个人开源的代码库 适合做调研,不适合做产品
int main(){
struct nm_pkthdr h;
struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
if (nmr == NULL) return -1;
struct pollfd pfd = {0};
pfd.fd = nmr->fd;
pfd.events = POLLIN;
while(1) {
int ret = poll(&pfd, 1, -1);
if( ret < 0 ) continue;
if(pfd.events & POLLIN){
unsigned char *stream = nm_nextpkt(nmr, &h);
struct etherhdr *eh = (struct etherhdr*) stream;
if(ntohs(eh->protocol == PROTO_IP)){
struct udppkt *pkt = (struct udppkt *) stream;
if(pkt->ip.protocol == PROTO_UDP){
int length = ntohs(pkt->udp.length);
pkt->payload[length-8] = '\0';
printf("pkt: %s\n", pkt->payload);
}
}
}
}
}