用户态协议栈的实现

本文介绍了用户态协议栈的概念,以及为何需要它来解决C10M问题。通过使用Netmap库,可以绕过内核协议栈,减少数据拷贝,提高性能。详细讲解了Netmap的工作原理,如利用mmap实现零拷贝,以及如何处理UDP和ARP包。还展示了利用Netmap实现用户态协议栈的代码示例,包括解析和注入网络包。最后提到了ARP攻击的可能性及防范措施。

协议栈

协议栈,指的是TCP/IP协议栈。linux系统中,协议栈是内核实现的。

协议,是通信双方对包格式的一种约定。

为什么是栈呢?因为对于包的组织,类似于栈的数据结构。发送端组织包的顺序是应用层->传输层->网络层->数据链路层,之后通过网卡将数字信号转换成光电信号,发送给接收端;接收端的网卡将光电信号转换成数字信号,解包的顺序是数据链路层->网络层->传输层->应用层。

img

如何拿到最原始的数据?

  1. raw socket,socket的第二个参数,可以设置SOCK_STREAM,SOCK_DGRAM. SOCK_RAW就可以拿到以太网数据。tcpdump、wireshark就是利用这种方法。
  2. netmap
  3. dpdk

网卡的作用

Client发送数据给server,数据首先到达网卡,经过两步到达应用程序
1)将数据从网卡的内存copy到内核协议栈,内核协议栈对数据包进行解析;
2)应用程序通过调用recv函数,将数据从内核copy进用户空间,得到应用层的数据包。
网卡的作用,接收的时候,是将光电信号转换成数字信号;发送的时候,将数字信号转换成光电信号。

什么是用户态协议栈?

就是将协议栈,做到应用程序。为什么要这么做呢?减少了一次数据copy的过程,绕过内核,数据可以直接从网卡copy到应用程序,对于性能会有很大的提升。
img

为什么要有用户态协议栈呢?

是为了解决C10M的问题。

之前说过C10K的问题,使用epoll可以解决C10K的问题。现在epoll已经可以支持两三百万的并发了。
什么是C10M问题?
实现10M(即1千万)的并发连接挑战意味着什么:(网上找的)
1)1千万的并发连接数;
2)100万个连接/秒:每个连接以这个速率持续约10秒;
3)10GB/秒的连接:快速连接到互联网;
4)1千万个数据包/秒:据估计目前的服务器每秒处理50K数据包,以后会更多;
5)10微秒的延迟:可扩展服务器也许可以处理这个规模(但延迟可能会飙升);
6)10微秒的抖动:限制最大延迟;
7)并发10核技术:软件应支持更多核的服务器(通常情况下,软件能轻松扩展到四核,服务器可以扩展到更多核,因此需要重写软件,以支持更多核的服务器).

我们来计算一下,单机承载1000万连接,需要的硬件资源:
内存:1个连接,大概需要4k recvbuffer,4k sendbuffer,一共需要10M * 8k = 80G
CPU:10M 除以 50K = 200核
只是支持这么多连接,还没有做其他事情,就需要这么多的资源,如果在加上其他的限制,加上业务的处理,资源肯定会更多。使用用户态协议栈,可以减少一次数据的copy,可以节省很大一部分资源。

要实现用户态协议栈,很关键的一个问题,是网络数据怎么才能绕过内核,直接到达用户空间?netmap、dpdk为用户态协议栈的实现,提供了可能。

这次我们使用了netmap实现用户态协议栈,后面会介绍dpdk。

netmap原理

netmap主要利用了mmap,将网卡中数据,直接映射到内存。netmap直接接管网卡数据,可以绕过内核协议栈。我们直接在应用程序中实现协议栈,对协议进行解析,就可以获取到网络数据了。

零拷贝,使用的是mmap方式,本质是DMA的方式,不需要CPU参与。普通copy,从磁盘copy数据到内存,需要CPU的move指令。sendfile使用的是mmap方式。**零拷贝主要是说CPU有没有参与,而不是说有没有copy。**是由主板上的DMA芯片将外设的数据copy到内存。
img

netmap可以在github上下载,按照上面的readme编译安装,使用比较方便。
https://github.com/luigirizzo/netmap

利用netmap实现用户态协议栈

以太网头定义

img

typedef struct _ethhdr {
   
   

    unsigned char h_dst[ETH_ADDR_LENGTH];
    unsigned char h_src[ETH_ADDR_LENGTH];
    unsigned short h_proto;

} ethhdr;

IP头定义

img

typedef struct _iphdr {
   
   

    unsigned char hdrlen:4, // ip头长度,最大15*4=60字节
                  version:4;
    unsigned char tos;
    unsigned short length;
    unsigned short id;
    unsigned short flag_offset;
    unsigned char ttl; // time to live ping的ttl就是ip头里面的ttl
    unsigned char type;
    unsigned short check;
    unsigned int sip;
    unsigned int dip;

} iphdr;

UDP头定义

img

UDP比TCP要简单很多,没有序列号,无法保证消息必达;也做不了重传;没有拥塞控制。

typedef struct _udphdr {
   
   

    unsigned short sport;
    unsigned short dport;
    unsigned short length;
    unsigned short check;

} udphdr;

UDP包定义

UDP包组成:以太网头 + IP头 + UDP头 + UDP数据。

以太网头、IP头、UDP头都已经定义好了,这里有一个问题,UDP数据怎么定义?

用指针是不合适的,这里引入了零长数组,也叫柔性数组。

typedef struct _udppkt {
   
   

    ethhdr eh; // 14
    iphdr ip; // 20
    udphdr udp; // 8

    unsigned char data[0];

} udppkt;

柔性数组的好处,是不占空间,只是占了一个位置,作用相当于标签。

零长数组使用条件:

  1. 不关心长度,可以通过某种方法计算出它的长度,比如通过udp头的length能够计算出来用户数据的长度;
  2. 它的内存是提前分配好的,不会越界。

因为4字节对齐的关系,sizeof(udppkt)的结果是44,所以需要使用单字节对齐,结果就是42。

#pragma pack(1)

使用netmap处理UDP包

nm_open主要做了两个事情:

  1. 把网卡的内存映射到内存;
  2. 把fd指向eth0对应的设备文件。

通过检测fd,就可以判断网卡是否有数据,有数据就可以直接操作内存。

img

如果来了一个数据,如果数据很多,网卡需要进行模拟信号和数字信号转换,DMA也需要不断把数据映射到内存,怎么把多个数据包给组织起来?比如一下来100个包,怎么把这100个包组织起来?

使用ringbuff。

对于大量数据,从网卡将数据取到内存中,CPU有两种做法:

  1. 轮询
  2. 事件

对于大量数据,使用轮询方式比较好。这就是网络这一层,从网卡里面取数据的两种方法。事件的方式针对稀疏型的数据。

nm_nextpkt是操作内存,取出来的就是一个完整的包。

之后通过去掉以太网头,IP头,UDP头,得到用户数据。

img

int main() {
   
   
    struct nm_pkthdr h; // ringbuff的指针
    struct nm_desc *nmr = nm_open("netmap:eth1", 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); // ringbuff

            ethhdr *eh = (ethhdr*)stream;
            if (ntohs(eh->h_proto) == PROTO_IP) {
   
   

                udppkt *udp = (udppkt*)stream;

                if (udp->ip.type == PROTO_UDP) 
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值