写点什么

用户空间协议栈设计和 netmap 综合指南

  • 2023-08-10
    广东
  • 本文字数:13153 字

    阅读完需:约 43 分钟

用户空间协议栈设计和netmap综合指南

本文分享自华为云社区《用户空间协议栈设计和netmap综合指南,将网络效率提升到新高度》,作者:Lion Long 。

协议概念

1.1、七层网络模型和五层网络模型




应用层: 最接近用户的一层,为用户程序提供网络服务。主要协议有 HTTP、FTP、TFTP、SMTP、DNS、POP3、DHCP 等。


表示层: 数据的表示、安全、压缩。管理数据的解密和加密。


会话层: 负责在网络中的两个节点之间的建立、维持和终止通信。


传输层: 模型中最重要的一层,负责传输协议的流控和差错校验。数据包离开网卡后进入的就是传输层;主要协议有:TCP、UDP 等。


网络层: 将网络地址翻译成对应的物理地址。主要协议有:ICMP、IP 等。


数据链路层: 建立逻辑连接、进行硬件地址寻址、差错校验等功能,解决两台相连主机之间的通信问题。主要协议有 SLIP、以太网协议/MAC 帧协议、ARP 和 RARP 等。


物理层: 模型的最低层,建立、维护、断开物理连接,传输比特流。常见的物理媒介有光纤、电缆、中继器等。主要协议有 RS232 等。

1.2、以太网


以太网不是一种网络,而是一种局域网技术,它既有数据链路层内容,也有一些物理层内容。局域网技术除了以太网外,还有令牌环网、无线 LAN/WAN 等。


以太网的网线必须是双绞线,以太网中的所有主机共享一个通信通道; 当局域网中一台主机发送数据后,该局域网的所有设备都会收到该数据。因为共用一个通信通道,因此同一时刻只允许一台主机发送数据;如果同一时刻不只有一个主机发送数据,为避免干扰,该主机会执行碰撞避免算法(等待一段时间后再进行数据重发)。


以太网帧格式如下:



源地址和目的地址是指网卡 MAC 地址,长度是 48 bit(6 字节)。帧协议类型字段有三种,分别对应 IP 协议、ARP 协议和 RARP 协议。帧末尾是 CRC 校验码。


定义一个以太网头结构体示例代码:


#define ETHER_ADDR_LEN 6
struct etherhdr {
unsigned char dst_mac[ETHER_ADDR_LEN];
unsigned char src_mac[ETHER_ADDR_LEN];
unsigned short protocol;
};
复制代码

输出它的大小:

sizeof(struct etherhdr) = 14
复制代码

1.3、IP 协议


IP 协议全称 Internet Protocol,即网际互连协议,存在于网络层,负责数据在网络中传输。


IP 协议格式如下:


0 |1 |2 |3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------+-------+---------------+-------------------------------+
|version|hdr_len| Type Of Server| total length |
+-------------------------------+-----+-------------------------+
| ID |flag | framegament offset |
+---------------+---------------+-------------------------------+
| TTL | Protocol | header CRC |
+---------------------------------------------------------------+
| Source IP |
+---------------------------------------------------------------+
| Destination IP |
+---------------------------------------------------------------+
| Option (if have) |
+---------------------------------------------------------------+
| Data |
| ... |
+---------------------------------------------------------------+
复制代码


定义一个 IP 协议头结构体示例代码:


struct iphdr {
unsigned char version : 4,
hdrlen : 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;
};
复制代码

1.4、ARP 协议


ARP 协议全称 Address Resolution Protocol,即地址解析协议,是根据 IP 地址获取 MAC 地址的一个 TCP/IP 协议。


ARP 协议的作用:在同一个局域网中要给对方发消息,就必须得知道对方的 MAC 地址,而实际大部分情况下只知道对方的 IP 地址,因此需要通过 ARP 协议来根据 IP 地址来获取目标主机的 MAC 地址。


ARP 的数据格式如下:


1 |2 |3 |4 |5
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
+-----------------------------------------------------------------------------------------------+
| Ethernet Destination IP |
+-----------------------------------------------------------------------------------------------+
| Ethernet Source IP |
+-------------------------------+-------------------------------+-------------------------------+
| framegament type | harware address tpye | Protocol address type |
+---------------+---------------+-------------------------------+-------------------------------+
| HW_addr_length|Pro_addr_len | op code | Source MAC |
+---------------------------------------------------------------+-------------------------------|
| Source MAC | Source IP |
+-------------------------------+---------------------------------------------------------------|
| Source IP | Destination MAC |
+-------------------------------+---------------------------------------------------------------+
| Destination MAC | Destination IP |
+-----------------------------------------------------------------------------------------------+
| |
| PAD |
| |
+-----------------------------------------------------------------------------------------------+
复制代码


可以看出,ARP 是 MAC 帧协议的上层协议,前 3 个字段和最后一个字段对应的就是以太网头部。由于 ARP 数据包的长度不足 46 字节,因此 ARP 数据包在封装成为 MAC 帧时还需要补上 18 字节的填充字段。


定义一个 arp 协议头结构体示例代码:


struct arphdr{
unsigned short h_type;
unsigned short h_proto;
unsigned char h_addrlen;
unsigned char protolen;
unsigned short oper;
unsigned char smac[ETH_ALEN];
unsigned int sip;
unsigned char dmac[ETH_ALEN];
unsigned int dip;
// pad
};
复制代码

1.4.1、ARP 攻击原理


arp 攻击得到主要目的是使网络无法正常通信。 向局域网中的所有主机发送 ARP 应答,其中包含网关 IP 地址和虚假的 MAC 地址。局域网中的主机收到 ARP 应答跟新 ARP 表后,再发送数据时,就会发送到虚假的 MAC 地址导致通信故障,就无法和网关正常通信,导致无法访问互联网。

1.4.2、ARP 欺骗原理


ARP 欺骗并不会使网络无法正常通信,而是通过冒充网关或其他主机 使得 到达网关或主机的数据流量通过攻击主机进行转发。


比如冒充网关:ARP 欺骗发送 arp 应答给局域网中其他的主机,其中包含网关的 IP 地址和进行 ARP 欺骗的主机 MAC 地址;并且也发送了 ARP 应答给网关,其中包含局域网中所有主机的 IP 地址和进行 arp 欺骗的主机 MAC 地址。当局域网中主机和网关收到 ARP 应答跟新 ARP 表后,主机和网关之间的流量就需要通过攻击主机进行转发。


冒充主机的过程和冒充网关相同。

1.5、ICMP 协议


ICMP 全称 Internet Control Message Protocol,即互联网控制消息协议,位于 IP 报文的数据段。虽然 ICMP 是网络层协议,但它不直接传递数据到数据链路层,而是封装成 IP 数据包再传递到数据链路层,IP 数据包中的协议类型字段为 1 就表示 ICMP 报文。ICMP 协议的类型主要有两类:查询报文和差错报文。


ICMP 报文格式如下:


0 |1 |2 |3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------------------------------+
| type | code | CRC |
+-------------------------------+-------------------------------+
| ID | Sequence Number |
+---------------------------------------------------------------+
| mask |
+---------------------------------------------------------------+
复制代码



定义一个 ICMP 协议头结构体示例代码:


// ICMP
struct icmphdr {
unsigned char type;
unsigned char code;
unsigned short check;
unsigned short identifier;
unsigned short sep;
unsigned cahr data[32];
};
复制代码


ICMP 的应用:


(1)ping 命令。向目的服务器发送回显请求,目的服务器发送回显应答;计算发送回显请求数据包的时间与接收到回显应答数据包的时间差,就是数据包一去一回所需要的时间。


(2)traceroute 命令。traceroute 命令利用 ICMP 差错报文类型,用作追踪路由信息。前提条件是路由器没有禁用 ICMP。

1.6、MTU 概念


MTU,全称 Maximum Transmission Unit,即最大传输单元。说明一次数据帧可以发送或接收的最大数据量;以字节为单位,一般是是 1500,不同网络类型的 MTU 不同。


(1)如果一次发送要发送的数据超过 MTU,需要在 IP 层对数据进行分片。数据分片和组装在 IP 层,因为不同网络的 MTU 不同,不仅源主机可能需要对数据进行分片,数据传输过程中的路由器也可能对数据分片。


(2)以太网规定数据的最小长度为 46 字节,如果发送数据小于 46 字节,需要填充,比如 ARP 数据包就需要填充才能发送。


(3)对于 UDP,是定长的 8 字节报头,如果 IP 报头没有携带可选项字段,那么 UDP 一次携带的数据最大为 1500-20-8=1472 字节,如果超出这个大小就需要在 IP 层进行分片。分片带来的后果是增加 UDP 的丢包率。


(4)分片也会增加 TCP 的丢包率,不过 TCP 有重传机制;因此需要尽可能避免分片,降低 TCP 重传次数。

1.7、MSS 概念


MSS,全称 Maximum Segment Size,即最大报文段大小。表示 TCP 传往另一端的最大块数据的长度。


当一个连接建立时,连接的双方都要提供各自的 MSS。通过协商确定 MSS 的值(双方 MSS 的最小值)以避免 TCP 分片。如果没有分段发生, MSS 越大越好。

1.8、TTL 概念


TTL,全称 Time To Live,即存活时间;指一个数据包可传递的最长距离(跃点数)。


当一个数据包经过一个路由器时,TTL 减一;当 TTL=0 时路由器就会取消数据包的转发。


我们知道网络是有 环 存在的,设计 TTL 的目的是防止数据包因为不正确的路由表等原因造成无线循环而无法送达导致耗尽网络资源。

二、数据传输框图


网络上所有的数据传输都要经过网卡,网卡将模拟信号转换为数字信号,也就是将物理层信号转换为数据链路层信号。



注意:


(1)send()返回成功不代表发送成功,send()只是包数据拷贝到写缓冲区,真正发送数据由协议栈完成。如果客户端宕机而服务端一直执行 send(),那么在一段时间后 send()会返回-1;因为写缓冲区中的数据没有发送出去导致写缓冲区爆满。


(2)协议栈就是数据根据七层网络模型,自顶向下一层一层的协议头包住数据;接收端也是根据七层网络模型,自底向上一层层的解析协议。


(3)驱动如何把数据传递到协议栈?


在 Linux kernel 有一个 sk_buffer 结构,sk_buffer 将驱动获取的数据通过 sk_buffer 传递到协议栈中。

三、校验和 checksum 的计算方法


(1) 先将需要计算 checksum 数据中的 checksum 字段设为 0;


(2) 将 checksum 的数据按 2 byte(16 bit)划分,如果最后有单个 byte 的数据,则在其后面补 1 byte 的 0 构成 2 byte;


(3) 将所有的 2 byte(16 bit)z 值累加,得到一个 4 byte(32 bit)的值;


(4)将得到的 4 byte(32 bit)的值的高 16bit 与低 16bit 相加得到一个新的 4 byte(32 bit)值;若新值大于 0xFFFF,再将新值的高 16bit 与低 16bit 相加;


(5)将上一步计算所得的值按位取反,即得到 checksum 值,保存到 checksum 字段即可。


示例代码:


unsigned short in_cksum(unsigned short *addr,int len)
{
register int nleft = len;
register unsigned short *w = addr;
register int sum = 0;//32bit
unsigned short answer = 0;//16bit
while (nleft > 1)
{
sum += *w++;//16bit为一组累加
nleft -= 2;
}
if (nleft == 1)//存在单个byte情况
{
*(u_char*)(&answer) = *(u_char*)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);// 高16bit与低16bit相加
sum += (sum >> 16);//防止值大于0xffff
//结果
answer = ~sum;
return (answer);
}
复制代码

四、协议栈设计–netmap


要实现一个协议栈,那么就需要获得原始的协议数据。


4.1、获取原始协议数据的方法


(1)raw socket,即原始套接字,可以接收本机网卡的数据帧或数据包。有四种方式创建这类 socket。



(2)旁路。netmap、dpdk 等


(3)hook。bpf、ebpf 等

4.2、零长数组


零长数组,顾名思义,就是长度为零的数组。一般在 GUN C 中使用,其他编译器使用可能会报错或警告。


零长度数组的一个特点是它不占用内存存储空间。如下示例:


#include <stdio.h>
char test[0];
int main()
{
printf("size = %ld\n",sizeof(test));
return 0;
}
// 输出 为 0
复制代码


在结构体中使用,它同样也不占内存:


#include <stdio.h>
struct test{
int len;
int ch[0];
};
int main(void)
{
printf("size of = %ld\n",sizeof(struct test));
return 0;
}
复制代码


零长数组的使用:内存已经分配,但数据长度不确定,需要计算出数据长度的,就可以使用零长数组。零长数组在内存池中使用比较多。


使用示例:


#include <stdio.h>
struct test{
int len;
char ch[0];
};
int main(void)
{
struct test *buf;
buf = (struct test *)malloc(sizeof(struct test)+ 16);
memset(buf,0,sizeof(struct test)+ 16);


strcpy(buf->ch, "hello world\n");
puts(buf->ch);


free(buf);
return 0;
}
复制代码

4.3、修改 ens33 为 eth0


(1)打开/etc/default/grub


sudo nano /etc/default/grub
复制代码


(2)找到 GRUB_CMDLINE_LINUX=" "改为 GRUB_CMDLINE_LINUX=“net.ifnames=0 biosdevname=0”


(3)写入配置


sudo grub-mkconfig -o /boot/grub/grub.cfg
复制代码


(4)重启系统


reboot
复制代码

4.4、netmap 下载安装


以 ubuntu 为例。


(1)切换到根目录:


cd /
复制代码


(2)切换到 root 权限:


sudo su
复制代码


(3)在根目录 clone netmap:


git clone https://github.com/luigirizzo/netmap.git
复制代码


正克隆到 'netmap'...


remote: Enumerating objects: 28670, done.
remote: Counting objects: 100% (978/978), done.
remote: Compressing objects: 100% (397/397), done.
remote: Total 28670 (delta 603), reused 867 (delta 533), pack-reused 27692
复制代码


接收对象中: 100% (28670/28670), 10.13 MiB | 2.72 MiB/s, 完成.


处理 delta 中: 100% (18306/18306), 完成.


(4)安装编译环境:


apt-get install build-essential
复制代码


(5)进入 netmap/LINUX 目录


cd /netmap/LINUX/
复制代码


(6)执行配置:


./configure
复制代码


此过程会下载一些东西,然后提示耐心等待一段时间,过程有点久,请耐心等待,如下。



(7)编译和安装:


make && make install
复制代码


此过程也需要耐心等待一段时间,过程有点久。


......
##install -D -m 644 ice.7.gz //usr/share/man/man7/ice.7.gz
/sbin/depmod -e -F /boot/System.map-4.15.0-142-generic -a 4.15.0-142-generic
Updating initramfs...
update-initramfs -u
update-initramfs: Generating /boot/initrd.img-4.15.0-142-generic
make[1]: Leaving directory '/netmap/LINUX/ice-1.7.16/src'
make -C ixgbe install INSTALL_MOD_PATH= CFLAGS_EXTRA="-Wno-unused-but-set-variable -Wno-attributes -Wno-maybe-uninitialized -Wno-unused-variable -Wno-unused-label -I/netmap/LINUX -I/netmap/LINUX -I/netmap/LINUX/../sys -I/netmap/LINUX/../sys/dev -DCONFIG_NETMAP -Wno-unused-but-set-variable -g -DCONFIG_NETMAP_NULL -DCONFIG_NETMAP_PTNETMAP -DCONFIG_NETMAP_GENERIC -DCONFIG_NETMAP_MONITOR -DCONFIG_NETMAP_PIPE -DCONFIG_NETMAP_VALE" NETMAP_DRIVER_SUFFIX= KSRC=/lib/modules/4.15.0-142-generic/build KBUILD_EXTRA_SYMBOLS=/netmap/LINUX/Module.symvers
make[1]: Entering directory '/netmap/LINUX/ixgbe-5.3.8/src'
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
Building modules, stage 2.
MODPOST 1 modules
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
Copying manpages...
Installing modules...
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
INSTALL /netmap/LINUX/ixgbe-5.3.8/src/ixgbe.ko
At main.c:160:
- SSL error:02001002:system library:fopen:No such file or directory: bss_file.c:175
- SSL error:2006D080:BIO routines:BIO_new_file:no such file: bss_file.c:178
sign-file: certs/signing_key.pem: No such file or directory
DEPMOD 4.15.0-142-generic
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
Running depmod...
make[1]: Leaving directory '/netmap/LINUX/ixgbe-5.3.8/src'
make -C igb install INSTALL_MOD_PATH= CFLAGS_EXTRA="-DDISABLE_PACKET_SPLIT -fno-pie -I/netmap/LINUX -I/netmap/LINUX -I/netmap/LINUX/../sys -I/netmap/LINUX/../sys/dev -DCONFIG_NETMAP -Wno-unused-but-set-variable -g -DCONFIG_NETMAP_NULL -DCONFIG_NETMAP_PTNETMAP -DCONFIG_NETMAP_GENERIC -DCONFIG_NETMAP_MONITOR -DCONFIG_NETMAP_PIPE -DCONFIG_NETMAP_VALE" NETMAP_DRIVER_SUFFIX= KSRC=/lib/modules/4.15.0-142-generic/build KBUILD_EXTRA_SYMBOLS=/netmap/LINUX/Module.symvers
make[1]: Entering directory '/netmap/LINUX/igb-5.3.5.20/src'
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
Building modules, stage 2.
MODPOST 1 modules
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
Copying manpages...
Installing modules...
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
INSTALL /netmap/LINUX/igb-5.3.5.20/src/igb.ko
At main.c:160:
- SSL error:02001002:system library:fopen:No such file or directory: bss_file.c:175
- SSL error:2006D080:BIO routines:BIO_new_file:no such file: bss_file.c:178
sign-file: certs/signing_key.pem: No such file or directory
DEPMOD 4.15.0-142-generic
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
Running depmod...
make[1]: Leaving directory '/netmap/LINUX/igb-5.3.5.20/src'
make -C virtio_net.c install INSTALL_MOD_PATH= EXTRA_CFLAGS="-I/netmap/LINUX -I/netmap/LINUX -I/netmap/LINUX/../sys -I/netmap/LINUX/../sys/dev -DCONFIG_NETMAP -Wno-unused-but-set-variable -g -DCONFIG_NETMAP_NULL -DCONFIG_NETMAP_PTNETMAP -DCONFIG_NETMAP_GENERIC -DCONFIG_NETMAP_MONITOR -DCONFIG_NETMAP_PIPE -DCONFIG_NETMAP_VALE" NETMAP_DRIVER_SUFFIX= KSRC=/lib/modules/4.15.0-142-generic/build
make[1]: Entering directory '/netmap/LINUX/virtio_net.c'
make -C "/lib/modules/4.15.0-142-generic/build" M=/netmap/LINUX/virtio_net.c modules_install
make[2]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
INSTALL /netmap/LINUX/virtio_net.c/virtio_net.ko
At main.c:160:
- SSL error:02001002:system library:fopen:No such file or directory: bss_file.c:175
- SSL error:2006D080:BIO routines:BIO_new_file:no such file: bss_file.c:178
sign-file: certs/signing_key.pem: No such file or directory
DEPMOD 4.15.0-142-generic
make[2]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
make[1]: Leaving directory '/netmap/LINUX/virtio_net.c'
make -C build-apps/dedup install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/dedup'
install -D dedup //usr/local/bin/dedup
install -D -m 644 /netmap/LINUX/../apps/lb/lb.8 //usr/local/share/man/man8/lb.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/dedup'
make -C build-apps/vale-ctl install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/vale-ctl'
install -D vale-ctl //usr/local/bin/vale-ctl
install -D -m 644 /netmap/LINUX/../apps/vale-ctl/vale-ctl.4 //usr/local/share/man/man4/vale-ctl.4
make[1]: Leaving directory '/netmap/LINUX/build-apps/vale-ctl'
make -C build-apps/nmreplay install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/nmreplay'
install -D nmreplay //usr/local/bin/nmreplay
install -D -m 644 /netmap/LINUX/../apps/nmreplay/nmreplay.8 //usr/local/share/man/man8/nmreplay.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/nmreplay'
make -C build-apps/tlem install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/tlem'
install -D tlem //usr/local/bin/tlem
install -D -m 644 /netmap/LINUX/../apps/tlem/tlem.8 //usr/local/share/man/man8/tlem.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/tlem'
make -C build-apps/lb install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/lb'
install -D lb //usr/local/bin/lb
install -D -m 644 /netmap/LINUX/../apps/lb/lb.8 //usr/local/share/man/man8/lb.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/lb'
make -C build-apps/bridge install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/bridge'
install -D bridge //usr/local/bin/bridge
install -D -m 644 /netmap/LINUX/../apps/bridge/bridge.8 //usr/local/share/man/man8/bridge.8
install -D bridge-b //usr/local/bin/bridge-b
install -D -m 644 /netmap/LINUX/../apps/bridge/bridge.8 //usr/local/share/man/man8/bridge.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/bridge'
make -C build-apps/pkt-gen install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-apps/pkt-gen'
install -D pkt-gen //usr/local/bin/pkt-gen
install -D -m 644 /netmap/LINUX/../apps/pkt-gen/pkt-gen.8 //usr/local/share/man/man8/pkt-gen.8
install -D pkt-gen-b //usr/local/bin/pkt-gen-b
install -D -m 644 /netmap/LINUX/../apps/pkt-gen/pkt-gen.8 //usr/local/share/man/man8/pkt-gen.8
make[1]: Leaving directory '/netmap/LINUX/build-apps/pkt-gen'
install -m 0644 -D /netmap/LINUX/../sys/net/netmap.h //usr/local/include/net/netmap.h
install -m 0644 -D /netmap/LINUX/../sys/net/netmap_user.h //usr/local/include/net/netmap_user.h
install -m 0644 -D /netmap/LINUX/../sys/net/netmap_virt.h //usr/local/include/net/netmap_virt.h
install -m 0644 -D /netmap/LINUX/../sys/net/netmap_legacy.h //usr/local/include/net/netmap_legacy.h
install -m 0644 -D /netmap/LINUX/../libnetmap/libnetmap.h //usr/local/include/libnetmap.h
install -D -m 644 /netmap/LINUX/../share/man/man4/netmap.4 //usr/local/share/man/man4/netmap.4
install -D -m 644 /netmap/LINUX/../share/man/man4/vale.4 //usr/local/share/man/man4/vale.4
install -D -m 644 /netmap/LINUX/../share/man/man4/ptnet.4 //usr/local/share/man/man4/ptnet.4
make -C build-libnetmap install SRCDIR=/netmap/LINUX/.. BUILDDIR=/netmap/LINUX DESTDIR="" PREFIX="/usr/local"
make[1]: Entering directory '/netmap/LINUX/build-libnetmap'
install -D libnetmap.a //usr/local/lib/libnetmap.a
make[1]: Leaving directory '/netmap/LINUX/build-libnetmap'
复制代码


(8)使用 netmap:


insmod netmap.ko
复制代码


每次使用前都要执行 insmod netmap.ko,它在/netmap/LINUX/路径下。


(9)检查 netmap 是否 insmod 成功:


ls /dev/netmap -l
复制代码


出现如下表示成功:


crw------- 1 root root 10, 54 8月 31 12:53 /dev/netmap
复制代码


(10)编译运行自己的代码


# 头文件 #include<net/netmap_user.h> 在 /netmap/sys/目录下
# 和/usr/local/include/net/目录下
gcc -o testcode testcode.c -I /netmap/sys/
复制代码

4.5、协议栈实现代码示例


示例简单实现了 arp、icmp、udp 的协议栈;其他协议的实现类似。


//需要开启netmap的宏
#define NETMAP_WITH_LIBS
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <net/netmap_user.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#pragma pack(1)//设置一字节对齐方式
#define ETH_ALEN 6
#define PROTO_IP 0x0800 // IP 协议
#define PROTO_ARP 0x0806
#define PROTOCOL_UDP 17
#define PROTO_ICMP 1
#define PROTO_IGMP 2
#define ICMP_TYPE_ANS 0
#define ICMP_TYPE_REQ 8
#define ETHER_ADDR_LEN 6
#define MY_IP "192.168.7.146"
#define MY_MAC "00:0c:29:39:a8:c4"
// ether
struct etherhdr {
unsigned char dst_mac[ETHER_ADDR_LEN];
unsigned char src_mac[ETHER_ADDR_LEN];
unsigned short protocol;
};
// IP
struct iphdr {
unsigned char version : 4,
hdrlen : 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;
};
// UDP
struct udphdr {
unsigned short sport;
unsigned short dport;
unsigned short length;
unsigned short check;
};
struct udppkt {
struct etherhdr eth;
struct iphdr ip;
struct udphdr udp;
unsigned char payload[0];// 零长数组
};
// ARP
struct arphdr{
unsigned short h_type;
unsigned short h_proto;
unsigned char h_addrlen;
unsigned char protolen;
unsigned short oper;
unsigned char smac[ETH_ALEN];
unsigned int sip;
unsigned char dmac[ETH_ALEN];
unsigned int dip;
};
struct arppkt {
struct etherhdr eth;
struct arphdr arp;
};
// ICMP
struct icmphdr {
unsigned char type;
unsigned char code;
unsigned short check;
unsigned short identifier;
unsigned short sep;
unsigned char data[32];
};
struct icmppkt{
struct etherhdr eth;
struct iphdr ip;
struct icmphdr icmp;
};
void echo_udp_pkt(struct udppkt *udp,struct udppkt *udp_rt)
{
memcpy(udp_rt, udp, sizeof(struct udppkt));
memcpy(udp_rt->eth.dst_mac, udp->eth.src_mac, ETH_ALEN);
memcpy(udp_rt->eth.src_mac, udp->eth.dst_mac, ETH_ALEN);
udp_rt->ip.sip = udp->ip.dip;
udp_rt->ip.dip = udp->ip.sip;
udp_rt->udp.sport = udp->udp.dport;
udp_rt->udp.dport = udp->udp.sport;
}
unsigned short in_cksum(unsigned short *addr,int len)
{
register int nleft = len;
register unsigned short *w = addr;
register int sum = 0;//32bit
unsigned short answer = 0;//16bit
while (nleft > 1)
{
sum += *w++;//16bit为一组累加
nleft -= 2;
}
if (nleft == 1)//存在单个byte情况
{
*(u_char*)(&answer) = *(u_char*)w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);// 高16bit与低16bit相加
sum += (sum >> 16);//防止值大于0xffff
//结果
answer = ~sum;
return (answer);
}
void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt)
{
memcpy(icmp_rt, icmp, sizeof(struct icmppkt));
memcpy(icmp_rt->eth.dst_mac, icmp->eth.src_mac, ETH_ALEN);
memcpy(icmp_rt->eth.src_mac, icmp->eth.dst_mac, ETH_ALEN);
icmp_rt->icmp.type = ICMP_TYPE_ANS;
icmp_rt->icmp.code = 0;
icmp_rt->icmp.check = 0;
icmp_rt->ip.sip = icmp->ip.dip;
icmp_rt->ip.dip = icmp->ip.sip;
icmp_rt->icmp.check = in_cksum((unsigned short*)&icmp_rt->icmp, sizeof(struct icmphdr));
}
int str2mac(char *mac, char *str) {
char *p = str;
unsigned char value = 0x0;
int i = 0;
while (p != '\0') {
if (*p == ':') {
mac[i++] = value;
value = 0x0;
}
else {
unsigned char temp = *p;
if (temp <= '9' && temp >= '0') {
temp -= '0';
}
else if (temp <= 'f' && temp >= 'a') {
temp -= 'a';
temp += 10;
}
else if (temp <= 'F' && temp >= 'A') {
temp -= 'A';
temp += 10;
}
else {
break;
}
value <<= 4;
value |= temp;
}
p++;
}
mac[i] = value;
return 0;
}
void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *hmac) {
memcpy(arp_rt, arp, sizeof(struct arppkt));
memcpy(arp_rt->eth.dst_mac, arp->eth.src_mac, ETH_ALEN);
str2mac(arp_rt->eth.src_mac, hmac);
arp_rt->eth.protocol = arp->eth.protocol;
arp_rt->arp.h_addrlen = 6;
arp_rt->arp.protolen = 4;
arp_rt->arp.oper = htons(2);
str2mac(arp_rt->arp.smac, hmac);
arp_rt->arp.sip = arp->arp.dip;
memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ALEN);
arp_rt->arp.dip = arp->arp.sip;
}
// netmap
int main()
{
printf("length = %ld\n", sizeof(struct etherhdr));
struct pollfd pfd = { 0 };// poll
struct nm_pkthdr h;
struct etherhdr *eh;
// 打开/dev/netmap,映射网卡数据到内存空间
struct nm_desc *nmr = nm_open("netmap:eth0", NULL,0,NULL);
if (nmr == NULL)
{
printf("netmap open fail!\n");
return -1;
}
pfd.fd = nmr->fd;// 指向/dev/netmap
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);//从环形队列中取出一个数据包
eh = (struct etherhdr *)stream;
//将网络数据转换为本地字节序
if (ntohs(eh->protocol) == PROTO_IP)
{
struct udppkt *pkt = (struct udppkt *)stream;
if (pkt->ip.protocol == PROTOCOL_UDP)
{
struct in_addr addr;
addr.s_addr = pkt->ip.sip;
// udp包length字段表示的是整个UDP包的总长度(包含udp的头长度)
int length = ntohs(pkt->udp.length);
printf("%s:%d:length:%d,ip length:%d\n",
inet_ntoa(addr),
pkt->udp.sport,
length,
ntohs(pkt->ip.totlen));
pkt->payload[length - 8] = '\0';
printf("pkt: %s\n", pkt->payload);
struct udppkt udp_rt;
echo_udp_pkt(pkt, &udp_rt);
nm_inject(nmr, &udp_rt, sizeof(struct udppkt));
}
else if (pkt->ip.protocol == PROTO_ICMP)
{
struct icmppkt *icmp = (struct icmppkt*)stream;
printf("icmp------> %d,%x\n",
icmp->icmp.type,icmp->icmp.check);
if (icmp->icmp.type == ICMP_TYPE_REQ)//0 代表应答 ICMP 报文、8 代表请求 ICMP 报文。
{
struct icmppkt icmp_rt = { 0 };
echo_icmp_pkt(icmp, &icmp_rt);
nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt));
}
}
else if (pkt->ip.protocol == PROTO_IGMP)
{
printf("PROTO_IGMP packet\n");
}
else
{
printf("other ip packet\n");
}
}
else if (ntohs(eh->protocol) == PROTO_ARP)
{
struct arppkt *arp = (struct arppkt*)stream;
struct arppkt arp_rt;
if (arp->arp.dip == inet_addr(MY_IP))
{
echo_arp_pkt(arp, &arp_rt, MY_MAC);
nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
}
}
}
}
return 0;
}
复制代码

总结


要实现一个协议栈,需要清楚七层网络模型,熟悉协议标准;获得协议的原始数据包后需要一层层的拨开解析;发送数据之前需要将协议一层层的往下包装。



点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 4
用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
用户空间协议栈设计和netmap综合指南_后端_华为云开发者联盟_InfoQ写作社区