博客园  :: 首页  :: 新随笔  :: 管理

2.4.1 用户态网络协议栈设计实现

Posted on 2023-06-04 22:38  wsg_blog  阅读(33)  评论(0编辑  收藏  举报

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);
                }
            }
        }
    }
}