zl程序教程

您现在的位置是:首页 >  其他

当前栏目

learning:tap/tun(1)

2023-02-19 12:21:05 时间

在前面章节中学习两个vpp与内核协议栈建立通信实现frr/bgp、ospf动态路由的学习案例,其中vpp和kernel通信中都使用了tun/tap网络虚拟接口来进行。本人对网络设备虚拟化了解不足,也在学习之中,如有错误,欢迎指正。下面就来学习一下vpp中tap模块。

1、learning:vppsb router插件编译

2、Learning VPP: OSPF routing protocol

3、Learning VPP: linux-cp(1)

简介

TUN/TAP是linux下的虚拟网卡设备,能够被用户态的进程用来发送和接收数据包,但是与物理网卡的数据来自链路层不同,tun/tap数据的接收和发送方都是来自用户进程或内核。可以把tun/tap设备看成一个数据管道,一端连接这应用程序,一端连接内核协议栈。tun、tap设备不同之处是,前者是一个三层设备,后者是一个二层设备。

+----------------------------------------------------------------+
|                                                                |
|  +--------------------+      +--------------------+            |
|  | User Application A |      | User Application B |<-----+     |
|  +--------------------+      +--------------------+      |     |
|               | 1                    | 5                 |     |
|...............|......................|...................|.....|
|               ↓                      ↓                   |     |
|         +----------+           +----------+              |     |
|         | socket A |           | socket B |              |     |
|         +----------+           +----------+              |     |
|                 | 2               | 6                    |     |
|.................|.................|......................|.....|
|                 ↓                 ↓                      |     |
|             +------------------------+                 4 |     |
|             | Newwork Protocol Stack |                   |     |
|             +------------------------+                   |     |
|                | 7                 | 3                   |     |
|................|...................|.....................|.....|
|                ↓                   ↓                     |     |
|        +----------------+    +----------------+          |     |
|        |      eth0      |    |      tun0      |          |     |
|        +----------------+    +----------------+          |     |
|    10.32.0.11  |                   |   192.168.3.11      |     |
|                | 8                 +---------------------+     |
|                |                                               |
+----------------|-----------------------------------------------+
                 ↓
         Physical Network

tap设备的创建和工作原理:tun、tap设备都是通过linux内核的驱动程序tun来创建的,tun驱动在初始化的时候会创建一个misc设备,路径是/dev/net/tun,用来作为向用户态导出的接口,所有对tun、tap设备的操作都必须先打开/dev/net/tun设备获得一个描述符fd,然后对fd进行相应的读写操作。用户态程序通过读写/dev/net/tun来向主机内核协议栈发送数据或者接收主机协议栈的数据。

关于tun、tap的介绍可以见参考资料部分。本文主要来学习一下tap相关的处理逻辑。vpp中关于tap相关的分为三个部分, 1、tapv1 也就是tap 版本1的实现逻辑,这块代码从vpp 19.04版本以后已经删除了,但是vppsb中tap-inject功能和tapv1版本实现逻辑是一样的。 2、tuntap 这个应该是通过tap接口实现一个vpp和内核协议栈的通信通道。 3、tapv2 tap版本2实现,支持tap接口多队列模式,使用vhost-net模式减少一次用户空间和内核空间数据拷贝提升性能。

tapv1

tapv1类型创建tap接口后,vpp程序打开tun设备/dev/net/tun设备后,通过ioctl(TUNSETIFF)来创建一个设备(对应下面vpp0),对应下面代码如下:

  /*打开tun的克隆设备,它被用作创建任何 tun/tap 虚拟接口的起点*/
  if ((dev_net_tun_fd = open ("/dev/net/tun", O_RDWR)) < 0)
    return VNET_API_ERROR_SYSCALL_ERROR_1;

  clib_memset (&ifr, 0, sizeof (ifr));
  /*设置tun、tap接口在linux设备名称*/
  strncpy (ifr.ifr_name, (char *) ap->intfc_name, sizeof (ifr.ifr_name) - 1);
  /*设置接口类型tun或者tap,及其他属性*/
  ifr.ifr_flags = flags;
  /*尝试创建linux设备,如果设备不存在,则创建,如果已经存在,进程会attach到该设备上*/
  if (ioctl (dev_net_tun_fd, TUNSETIFF, (void *) &ifr) < 0)
    {
      rv = VNET_API_ERROR_SYSCALL_ERROR_2;
      goto error;
    }

  /* 打开一个socket,用于设置tap接口的属性,mtu,ip地址等。 */
  if ((dev_tap_fd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0)
    {
      rv = VNET_API_ERROR_SYSCALL_ERROR_3;
      goto error;
    }

创建tapv1接口后原理图如下所示:

vpp支持tapv1接口创建、删除、修改mac地址操作,相关的cli如下:

/*创建tap接口,vpp会生成tapcli-x接口:
*intfc-name<linux创建tap接口名称> 
*address <linux tap接口ip地址>
*hwaddr: vpp tap接口mac地址
*/
tap connect <intfc-name> [address <ip-addr>/mw] [hwaddr <addr>]
/*删除tap接口:会同时删除vpp及内核接口*/
tap delete <vpp-tap-intfc-name>
/*修改vpp tap接口mac地址,代码实现好像是先删除后添加,
*这个接口存在bug,会把内核设置ip地址删除*/
tap modify <vpp-tap-intfc-name> <linux-intfc-name> [hwaddr <addr>]

下面创建tap接口并通过ping来测试tap接口转发流程,vpp通过打开tun/tap设备提供用户态接口/dev/net/tun来进行收发包处理的,当成一个socket 套接字来处理的,处理逻辑类似于vppctl 命令行的接口,这里可以阅读一下前面的两篇文章:一篇是关于vpp底层数据socket api接口vppinfra---socket api;另一篇关于vpp命令行接口处理流程learning:vpp unix cli 处理流程1。tapcli相关代码量也不是特别大,这里就不详细介绍了。

#1、创建tapv1接口
tap connect vpp0 address 100.1.1.2/24 hwaddr aa:bb:cc:dd:ee:ff
2、#设置vpp接口状态up,并设置ip地址是100.1.1.1
set interface state tapcli-0 up
set interface ip address tapcli-0 100.1.1.1/24
#设置trace抓包,
ucpe-cli-esxi# trace add tapcli-rx 10
ucpe-cli-esxi# show trace 
03:44:35:074250: tapcli-rx
  tapcli-0
03:44:35:074264: ethernet-input
  IP4: 2e:cc:db:c6:fb:f5 -> aa:bb:cc:dd:ee:ff
03:44:35:074268: ip4-input
  ICMP: 100.1.1.2 -> 100.1.1.1
    tos 0x00, ttl 64, length 84, checksum 0x8d22
    fragment id 0xe381, flags DONT_FRAGMENT
  ICMP echo_request checksum 0xa44c
03:44:35:074273: ip4-lookup
  fib 0 dpo-idx 4 flow hash: 0x00000000
  ICMP: 100.1.1.2 -> 100.1.1.1
    tos 0x00, ttl 64, length 84, checksum 0x8d22
    fragment id 0xe381, flags DONT_FRAGMENT
  ICMP echo_request checksum 0xa44c
03:44:35:074277: ip4-local
    ICMP: 100.1.1.2 -> 100.1.1.1
      tos 0x00, ttl 64, length 84, checksum 0x8d22
      fragment id 0xe381, flags DONT_FRAGMENT
    ICMP echo_request checksum 0xa44c
03:44:35:074279: ip4-icmp-input
  ICMP: 100.1.1.2 -> 100.1.1.1
    tos 0x00, ttl 64, length 84, checksum 0x8d22
    fragment id 0xe381, flags DONT_FRAGMENT
  ICMP echo_request checksum 0xa44c
03:44:35:074280: ip4-icmp-echo-request
  ICMP: 100.1.1.2 -> 100.1.1.1
    tos 0x00, ttl 64, length 84, checksum 0x8d22
    fragment id 0xe381, flags DONT_FRAGMENT
  ICMP echo_request checksum 0xa44c
03:44:35:074280: ip4-load-balance
  fib 0 dpo-idx 1 flow hash: 0x00000000
  ICMP: 100.1.1.1 -> 100.1.1.2
    tos 0x00, ttl 64, length 84, checksum 0xcb20
    fragment id 0xa583, flags DONT_FRAGMENT
  ICMP echo_reply checksum 0xac4c
03:44:35:074282: ip4-rewrite
  tx_sw_if_index 4 dpo-idx 1 : ipv4 via 100.1.1.2 tapcli-0: mtu:9000 2eccdbc6fbf5aabbccddeeff0800 flow hash: 0x00000000
  00000000: 2eccdbc6fbf5aabbccddeeff080045000054a58340004001cb20640101016401
  00000020: 01020000ac4c17f900f28ddd6a61000000007cb60700000000001011
03:44:35:074286: tapcli-0-output
  tapcli-0
  IP4: aa:bb:cc:dd:ee:ff -> 2e:cc:db:c6:fb:f5
  ICMP: 100.1.1.1 -> 100.1.1.2
    tos 0x00, ttl 64, length 84, checksum 0xcb20
    fragment id 0xa583, flags DONT_FRAGMENT
  ICMP echo_reply checksum 0xac4c

tuntap接口

此接口的代码在src\vnet\unix\tuntap(.c.h)文件,大概的工作原理和tapv1接口一样,都是vpp打开/dev/net/tun获得一个fd,通过fd接口进行报文的收发操作,但是tap接口都没有设置多队列属性。tuntap接口功能配置设置在vpp启动配置文件中,具体如下所示:

tuntap {
   mtu 1500  #设置mtu
   enable    #默认不会创建tuntap接口,需要使能
   name tap0 #linux系统显示tap接口名称
   ethernet   #默认是tun模式,配置Ethernet标识tap模式。
   have-normal-interface #是否正常的接口属性。还是用于报文上送punt接口
}

have-normal-interface:这个配置特别理解使用意图,从代码层面上,如果配置了,则不使能os_punt_frame功能,报文不会送到内核吧。如果没有配置则使能os_punt_frame(不知道理解对不对,没有搭建实际的应用场景)。

基本的转发流程同tapv1,这里就不再说明了。

tapv2

tapv2实现相对比较复杂,本人对虚拟化virtio框架不是特别了解。参考文章中《Deep dive into Virtio-networking and vhost-net》介绍了vhost-net和virtio-net通信框架。tapv2相对于tapv1使用了vhost-net协议,使用vring共享内存方式,减少了一次内核空间和用户空间数据拷贝,同时也支持的多队列模式。前面的一篇文章VPP 相关的一些开源项目介绍了Netgate公司提交了一个lcp的插件用于替代vppsb的route插件,性能比vppsb的route插件性能高30-40倍,lcp插件使用了tapv2版本实现,这可能也是性能提升比较大的原因吧。lcp插件目前vpp官方也是处于验证阶段,前面文章Learning VPP: linux-cp(1)使用lcpng插件搭建frrbgpd动态路由学习配置环境。

tapv2接口创建及转发流程后续再进行详细介绍。

create tap id 1 hw-addr aa:aa:aa:aa:aa:aa num-rx-queues 3 rx-ring-size 512 tx-ring-size 512 host-ip4-addr 100.1.1.2/24 host-if-name ens166

参考资料:

1、介绍tap/tun工作原理及使用场景。https://zhaohuabing.com/post/2020-02-24-linux-taptun/ 2、深入了解 Virtio-networking 和 vhost-net https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net?page=1&search=vhost-net 3、virtio-networking 和 vhost-net 简介 https://www.redhat.com/en/blog/introduction-virtio-networking-and-vhost-net?page=1&search=vhost-net 4、VPP和Linux内核协议栈通信的方法 https://blog.csdn.net/illina/article/details/81669944 5、云计算底层技术-虚拟网络设备(tun/tap,veth) https://opengers.github.io/openstack/openstack-base-virtual-network-devices-tuntap-veth/