Linux虚拟网络设备之tun/tap和veth设备的特点-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

在现在的云时代到处都是虚拟机和容器它们背后的网络管理都离不开虚拟网络设备所以了解虚拟网络设备有利于我们更好的理解云时代的网络结构。从本篇开始将介绍Linux下的虚拟网络设备。

虚拟设备和物理设备的区别

Linux网络数据包的接收过程数据包的发送过程这两篇文章中介绍了数据包的收发流程知道了Linux内核中有一个网络设备管理层处于网络设备驱动和协议栈之间负责衔接它们之间的数据交互。驱动不需要了解协议栈的细节协议栈也不需要了解设备驱动的细节。

对于一个网络设备来说就像一个管道pipe一样有两端从其中任意一端收到的数据将从另一端发送出去。

比如一个物理网卡eth0它的两端分别是内核协议栈通过内核网络设备管理模块间接的通信和外面的物理网络从物理网络收到的数据会转发给内核协议栈而应用程序从协议栈发过来的数据将会通过物理网络发送出去。

那么对于一个虚拟网络设备呢首先它也归内核的网络设备管理子系统管理对于Linux内核网络设备管理模块来说虚拟设备和物理设备没有区别都是网络设备都能配置IP从网络设备来的数据都会转发给协议栈协议栈过来的数据也会交由网络设备发送出去至于是怎么发送出去的发到哪里去那是设备驱动的事情跟Linux内核就没关系了所以说虚拟网络设备的一端也是协议栈而另一端是什么取决于虚拟网络设备的驱动实现。

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

上图中有两个应用程序A和B都在用户层而其它的socket、协议栈Newwork Protocol Stack和网络设备eth0和tun0部分都在内核层其实socket是协议栈的一部分这里分开来的目的是为了看的更直观。

tun0是一个Tun/Tap虚拟设备从上图中可以看出它和物理设备eth0的差别它们的一端虽然都连着协议栈但另一端不一样eth0的另一端是物理网络这个物理网络可能就是一个交换机而tun0的另一端是一个用户层的程序协议栈发给tun0的数据包能被这个应用程序读取到并且应用程序能直接向tun0写数据。

这里假设eth0配置的IP是10.32.0.11而tun0配置的IP是192.168.3.11.

这里列举的是一个典型的tun/tap设备的应用场景发到192.168.3.0/24网络的数据通过程序B这个隧道利用10.32.0.11发到远端网络的10.33.0.1再由10.33.0.1转发给相应的设备从而实现VPN。

下面来看看数据包的流程

  1. 应用程序A是一个普通的程序通过socket A发送了一个数据包假设这个数据包的目的IP地址是192.168.3.1

  2. socket将这个数据包丢给协议栈

  3. 协议栈根据数据包的目的IP地址匹配本地路由规则知道这个数据包应该由tun0出去于是将数据包交给tun0

  4. tun0收到数据包之后发现另一端被进程B打开了于是将数据包丢给了进程B

  5. 进程B收到数据包之后做一些跟业务相关的处理然后构造一个新的数据包将原来的数据包嵌入在新的数据包中最后通过socket B将数据包转发出去这时候新数据包的源地址变成了eth0的地址而目的IP地址变成了一个其它的地址比如是10.33.0.1.

  6. socket B将数据包丢给协议栈

  7. 协议栈根据本地路由发现这个数据包应该要通过eth0发送出去于是将数据包交给eth0

  8. eth0通过物理网络将数据包发送出去

10.33.0.1收到数据包之后会打开数据包读取里面的原始数据包并转发给本地的192.168.3.1然后等收到192.168.3.1的应答后再构造新的应答包并将原始应答包封装在里面再由原路径返回给应用程序B应用程序B取出里面的原始应答包最后返回给应用程序A

这里不讨论Tun/Tap设备tun0是怎么和用户层的进程B进行通信的对于Linux内核来说有很多种办法来让内核空间和用户空间的进程交换数据。

从上面的流程中可以看出数据包选择走哪个网络设备完全由路由表控制所以如果我们想让某些网络流量走应用程序B的转发流程就需要配置路由表让这部分数据走tun0。

un/tap设备有什么用

从上面介绍过的流程可以看出来tun/tap设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序给用户空间的程序一个处理数据包的机会。于是比较常用的数据压缩加密等功能就可以在应用程序B里面做进去tun/tap设备最常用的场景是VPN包括tunnel以及应用层的IPSec等比较有名的项目是VTun有兴趣可以去了解一下。

tun和tap的区别

用户层程序通过tun设备只能读写IP数据包而通过tap设备能读写链路层数据包类似于普通socket和raw socket的差别一样处理数据包的格式不一样。

 veth设备的特点

  • veth和其它的网络设备都一样一端连接的是内核协议栈。
  • veth设备是成对出现的另一端两个设备彼此相连
  • 一个设备收到协议栈的数据发送请求后会将数据发送到另一个设备上去。

下面这张关系图很清楚的说明了veth设备的特点

+----------------------------------------------------------------+
|                                                                |
|       +------------------------------------------------+       |
|       |             Newwork Protocol Stack             |       |
|       +------------------------------------------------+       |
|              ↑               ↑               ↑                 |
|..............|...............|...............|.................|
|              ↓               ↓               ↓                 |
|        +----------+    +-----------+   +-----------+           |
|        |   eth0   |    |   veth0   |   |   veth1   |           |
|        +----------+    +-----------+   +-----------+           |
|192.168.1.11  ↑               ↑               ↑                 |
|              |               +---------------+                 |
|              |         192.168.2.11     192.168.2.1            |
+--------------|-------------------------------------------------+
               ↓
         Physical Network

上图中我们给物理网卡eth0配置的IP为192.168.1.11 而veth0和veth1的IP分别是192.168.2.11和192.168.2.1。

我们通过示例的方式来一步一步的看看veth设备的特点。

只给一个veth设备配置IP

先通过ip link命令添加veth0和veth1然后配置veth0的IP并将两个设备都启动起来

dev@debian:~$ sudo ip link add veth0 type veth peer name veth1
dev@debian:~$ sudo ip addr add 192.168.2.11/24 dev veth0
dev@debian:~$ sudo ip link set veth0 up
dev@debian:~$ sudo ip link set veth1 up

 

这里不给veth1设备配置IP的原因就是想看看在veth1没有IP的情况下veth0收到协议栈的数据后会不会转发给veth1。

ping一下192.168.2.1由于veth1还没配置IP所以肯定不通

dev@debian:~$ ping -c 4 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
From 192.168.2.11 icmp_seq=1 Destination Host Unreachable
From 192.168.2.11 icmp_seq=2 Destination Host Unreachable
From 192.168.2.11 icmp_seq=3 Destination Host Unreachable
From 192.168.2.11 icmp_seq=4 Destination Host Unreachable

--- 192.168.2.1 ping statistics ---
4 packets transmitted, 0 received, +4 errors, 100% packet loss, time 3015ms
pipe 3

但为什么ping不通呢是到哪一步失败的呢

先看看抓包的情况从下面的输出可以看出veth0和veth1收到了同样的ARP请求包但没有看到ARP应答包

dev@debian:~$ sudo tcpdump -n -i veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:20:18.285230 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:19.282018 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:20.282038 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:21.300320 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:22.298783 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:23.298923 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28

dev@debian:~$ sudo tcpdump -n -i veth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
20:20:48.570459 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:49.570012 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:50.570023 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:51.570023 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:52.569988 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28
20:20:53.570833 ARP, Request who-has 192.168.2.1 tell 192.168.2.11, length 28

为什么会这样呢了解ping背后发生的事情后就明白了

  1. ping进程构造ICMP echo请求包并通过socket发给协议栈
  2. 协议栈根据目的IP地址和系统路由表知道去192.168.2.1的数据包应该要由192.168.2.11口出去
  3. 由于是第一次访问192.168.2.1且目的IP和本地IP在同一个网段所以协议栈会先发送ARP出去询问192.168.2.1的mac地址
  4. 协议栈将ARP包交给veth0让它发出去
  5. 由于veth0的另一端连的是veth1所以ARP请求包就转发给了veth1
  6. veth1收到ARP包后转交给另一端的协议栈
  7. 协议栈一看自己的设备列表发现本地没有192.168.2.1这个IP于是就丢弃了该ARP请求包这就是为什么只能看到ARP请求包看不到应答包的原因

给两个veth设备都配置IP

给veth1也配置上IP

dev@debian:~$ sudo ip addr add 192.168.2.1/24 dev veth1

再ping 192.168.2.1成功由于192.168.2.1是本地IP所以默认会走lo设备为了避免这种情况这里使用ping命令带上了-I参数指定数据包走指定设备

dev@debian:~$ ping -c 4 192.168.2.1 -I veth0
PING 192.168.2.1 (192.168.2.1) from 192.168.2.11 veth0: 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=0.032 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=0.048 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64 time=0.055 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=64 time=0.050 ms

--- 192.168.2.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3002ms
rtt min/avg/max/mdev = 0.032/0.046/0.055/0.009 ms
注意对于非debian系统这里有可能ping不通主要是因为内核中的一些ARP相关配置导致veth1不返回ARP应答包如ubuntu上就会出现这种情况解决办法如下
root@ubuntu:~# echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
root@ubuntu:~# echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local
root@ubuntu:~# echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
root@ubuntu:~# echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter
root@ubuntu:~# echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter

再来看看抓包情况我们在veth0和veth1上都看到了ICMP echo的请求包但为什么没有应答包呢上面不是显示ping进程已经成功收到了应答包吗

dev@debian:~$ sudo tcpdump -n -i veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:23:43.113062 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24169, seq 1, length 64
20:23:44.112078 IP 192.168.2.11 
> 192.168.2.1: ICMP echo request, id 24169, seq 2, length 64
20:23:45.111091 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24169, seq 3, length 64
20:23:46.110082 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24169, seq 4, length 64


dev@debian:~$ sudo tcpdump -n -i veth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
20:24:12.221372 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24174, seq 1, length 64
20:24:13.222089 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24174, seq 2, length 64
20:24:14.224836 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24174, seq 3, length 64
20:24:15.223826 IP 192.168.2.11 > 192.168.2.1: ICMP echo request, id 24174, seq 4, length 64

看看数据包的流程就明白了

  1. ping进程构造ICMP echo请求包并通过socket发给协议栈
  2. 由于ping程序指定了走veth0并且本地ARP缓存里面已经有了相关记录所以不用再发送ARP出去协议栈就直接将该数据包交给了veth0
  3. 由于veth0的另一端连的是veth1所以ICMP echo请求包就转发给了veth1
  4. veth1收到ICMP echo请求包后转交给另一端的协议栈
  5. 协议栈一看自己的设备列表发现本地有192.168.2.1这个IP于是构造ICMP echo应答包准备返回
  6. 协议栈查看自己的路由表发现回给192.168.2.11的数据包应该走lo口于是将应答包交给lo设备
  7. lo接到协议栈的应答包后啥都没干转手又把数据包还给了协议栈相当于协议栈通过发送流程把数据包给lo然后lo再将数据包交给协议栈的接收流程
  8. 协议栈收到应答包后发现有socket需要该包于是交给了相应的socket
  9. 这个socket正好是ping进程创建的socket于是ping进程收到了应答包

抓一下lo设备上的数据发现应答包确实是从lo口回来的

dev@debian:~$ sudo tcpdump -n -i lo
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
20:25:49.590273 IP 192.168.2.1 > 192.168.2.11: ICMP echo reply, id 24177, seq 1, length 64
20:25:50.590018 IP 192.168.2.1 > 192.168.2.11: ICMP echo reply, id 24177, seq 2, length 64
20:25:51.590027 IP 192.168.2.1 > 192.168.2.11: ICMP echo reply, id 24177, seq 3, length 64
20:25:52.590030 IP 192.168.2.1 > 192.168.2.11: ICMP echo reply, id 24177, seq 4, length 64

试着ping下其它的IP

ping 192.168.2.0/24网段的其它IP失败ping一个公网的IP也失败

dev@debian:~$ ping -c 1 -I veth0 192.168.2.2
PING 192.168.2.2 (192.168.2.2) from 192.168.2.11 veth0: 56(84) bytes of data.
From 192.168.2.11 icmp_seq=1 Destination Host Unreachable

--- 192.168.2.2 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

dev@debian:~$ ping -c 1 -I veth0 baidu.com
PING baidu.com (111.13.101.208) from 192.168.2.11 veth0: 56(84) bytes of data.
From 192.168.2.11 icmp_seq=1 Destination Host Unreachable

--- baidu.com ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

从抓包来看和上面第一种veth1没有配置IP的情况是一样的ARP请求没人处理

dev@debian:~$ sudo tcpdump -i veth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
02:25:23.223947 ARP, Request who-has 192.168.2.2 tell 192.168.2.11, length 28
02:25:24.224352 ARP, Request who-has 192.168.2.2 tell 192.168.2.11, length 28
02:25:25.223471 ARP, Request who-has 192.168.2.2 tell 192.168.2.11, length 28
02:25:27.946539 ARP, Request who-has 123.125.114.144 tell 192.168.2.11, length 28
02:25:28.946633 ARP, Request who-has 123.125.114.144 tell 192.168.2.11, length 28
02:25:29.948055 ARP, Request who-has 123.125.114.144 tell 192.168.2.11, length 28

结束语

从上面的介绍中可以看出从veth0设备出去的数据包会转发到veth1上如果目的地址是veth1的IP的话就能被协议栈处理否则连ARP那关都过不了IP forward啥的都用不上所以不借助其它虚拟设备的话这样的数据包只能在本地协议栈里面打转转没法走到eth0上去即没法发送到外面的网络中去。

下一篇将介绍Linux下的网桥到时候veth设备就有用武之地了。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux