用户空间协议栈设计和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的数据格式如下:
0 |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;
}
总结
要实现一个协议栈,需要清楚七层网络模型,熟悉协议标准;获得协议的原始数据包后需要一层层的拨开解析;发送数据之前需要将协议一层层的往下包装。

用户空间协议栈设计和netmap综合指南的更多相关文章
- 用户空间网络提升 NFV 的性能
本文是一篇翻译,翻译自https://software.intel.com/en-us/blogs/2015/06/12/user-space-networking-fuels-nfv-perform ...
- Linux 内核空间与用户空间
本文以 32 位系统为例介绍内核空间(kernel space)和用户空间(user space). 内核空间和用户空间 对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间)为 4 ...
- linux内存(一) 内核空间与用户空间
来自如下网站 https://www.cnblogs.com/sparkdev/p/8410350.html 内核空间和用户空间 对 32 位操作系统而言,它的寻址空间(虚拟地址空间,或叫线性地址空间 ...
- Linux操作系统,为什么需要内核空间和用户空间?
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 本文以 32 位系统为例介绍内核空间(kernel sp ...
- linux内存(三)内核与用户空间交互
来自网址http://www.kerneltravel.net/jiaoliu/005.htm 用户程序和内核的信息交换是双向的,也就是说既可以主动从用户空间向内核空间发送信息,也可以从内核空间向用户 ...
- Linux usb子系统(三):通过usbfs操作设备的用户空间驱动
内核中提供了USB设备文件系统(usbdevfs,Linux 2.6改为usbfs,即USB文件系统),它和/proc类似,都是动态产生的.通过在/etc/fstab文件中添加如下一行:none /p ...
- Linux启动时间优化-内核和用户空间启动优化实践
关键词:initcall.bootgraph.py.bootchartd.pybootchart等. 启动时间的优化,分为两大部分,分别是内核部分和用户空间两大部分. 从内核timestamp 0.0 ...
- Linux用户态驱动设计
聊聊Linux用户态驱动设计 序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都 ...
- 深入理解Linux网络技术内幕——用户空间与内核空间交互
概述: 内核空间与用户空间经常需要进行交互.举个例子:当用户空间使用一些配置命令如ifconfig或route时,内核处理程序就要响应这些处理请求. 用户空间与内核有多种交互方式,最常 ...
- 用户空间和内核空间通讯之【Netlink 中】
原文地址:用户空间和内核空间通讯之[Netlink 中] 作者:wjlkoorey258 今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的.我们依旧是在2.6 ...
随机推荐
- 【python爬虫】bilibili综合热门页面视频图片爬取
此博客仅作为交流学习 我用python来爬取bilibili综合热门页面视频图片 首先分析页面: 如上图所示,当我们想要在页面爬取图片时,往往得不到页面图片的地址,这时我们也得不到图片 开始抓包分析: ...
- 2020-10-13:hash与B+tree的区别?
福哥答案2020-10-13: [答案来自知乎:](https://www.zhihu.com/question/425378511/answer/1522000015) 这里我从数据库索引的层面回答 ...
- 2022-03-30:有m个同样的苹果,认为苹果之间无差别, 有n个同样的盘子,认为盘子之间也无差别, 还有,比如5个苹果如果放进3个盘子, 那么1、3、1和1、1、3和3、1、1的放置方法,也认为是
2022-03-30:有m个同样的苹果,认为苹果之间无差别, 有n个同样的盘子,认为盘子之间也无差别, 还有,比如5个苹果如果放进3个盘子, 那么1.3.1和1.1.3和3.1.1的放置方法,也认为是 ...
- 【HDU】1312 Red andBlack (DFS&BFS经典好题)
Red and Black 题目 我是题目链接 题解 找出所能到达的所有黑色的数量,用DFS和BFS均可. BFS: #include <iostream> #include <qu ...
- flutter填坑之旅(widget原理篇)
Flutter 的跨平台思路快速让他成为"新贵",连跨平台界的老大哥 "JS" 语言都"视而不见",大胆的选择 Dart 也让 Flutte ...
- vscode 注释快捷键 一键注释和取消注释快捷键
// 注释:ctrl+/ /**/ 注释:alt+shift+a
- Github疯传!200本计算机经典书籍!
好书在精不在多,每一本经典书籍都值得反复翻阅,温故而知新! 下面分享几本计算机经典书籍,都是我自己看过的. 重构 改善既有代码的设计 就像豆瓣评论所说的,看后有种醍醐灌顶.欲罢不能的感觉.无论你是初学 ...
- SQL基础知识扫盲
@ 目录 SQL & 数据库基础知识扫盲 SQL是什么? 数据库是什么? 挺身入局,实践出真知 DBMS初体验 MySQL:初体验 Oracle:初体验 PostgreSQL:初体验 Demo ...
- mysql 有关账号登录和重新设置密码操作
#进入mysql客户端$mysqlmysql> select user(); #查看当前用户mysql> exit # 也可以用\q quit退出 # 默认用户登陆之后并没有实际操作的权限 ...
- 终极指南!Terraform的进阶技巧
如果您已经对 Terraform 了如指掌,并期望自己的 IaC 技能有进一步提升的话,这篇文章很适合您!在本文中,我们将分享一些 Terraform 的高级使用技巧.从使用模块(module).工作 ...