虚拟网络设备概述
不管是虚拟网卡还是物理网卡,它们其实都只有一个功能:连接A端和B端,因此也只有两个方向,数据包从A端到B端或者数据包从B端到A端,也即A端的输出就是B端的输入,而B端的输出就是A端的输入,网卡只是A端和B端中间的通道罢了,因此从A端和B端以及数据包传输方向的角度摊开来讲:
- 物理网卡的两端就是协议栈和网线:协议栈的输出会成为网线另一端连接的设备的输入,而网线另一端连接设备的输出会成为协议栈的输入
- tun/tap的两端就是/dev/net/tun设备和协议栈:协议栈的输出会成为/dev/net/tun设备的输入从而被应用程序读取,/dev/net/tun设备的输出会成为协议栈的输入
- veth的两端就是另一半veth和协议栈:协议栈的输出会成为另一半veth的输入,另一半veth的输出会成为协议栈的输入
由于veth比较简单,因此本博文主要讲解Tun/Tap以及虚拟网桥
Tun/Tap
接下来主要细讲一下Tun/Tap,在计算机网络中,TUN与TAP是操作系统内核中的虚拟网络设备。不同于普通靠硬件网路板卡实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。其中TAP等同于一个以太网设备,它操作第二层数据包如以太网数据帧。TUN模拟了网络层设备,操作第三层数据包比如IP数据封包。
每个Tun/Tap虚拟设备并不会与/dev/net/tunx绑定,而是与单个/dev/net/tun字符设备文件绑定,如果程序要读取对应的虚拟设备,那么首先要打开/dev/net/tun字符设备文件获得对应的fd,然后再通过ioctl指定虚拟设备名称,之前如果已经通过ip tuntap创建过对应名称的tun/tap虚拟设备则会直接连接到对应的虚拟设备,反之则会创建对应的虚拟设备并连接
Linux Tun/Tap驱动程序为应用程序提供了两种交互方式:虚拟网络接口和字符设备文件。写入字符设备文件的数据会发送到虚拟网络接口中,然后进入内核网络栈;而经历过网络栈发送到虚拟网络接口中的数据,则会出现在对应字符设备文件上并由应用程序进行读取。
更具体地说应用程序可以通过标准的Socket API向Tun/Tap虚拟网络接口发送数据包,此时主要是通过协议栈对于数据包进行封装,然后匹配路由表再发送到了Tun/Tap接口,此时Tun/Tap驱动程序会将Tun/Tap接口收到的数据包原样写入到字符设备文件上。而应用程序也可以通过直接向字符设备文件写入数据包,这种情况下该字符设备上写入的数据包会被发送到Tun/Tap虚拟接口上,进入操作系统的TCP/IP协议栈进行相应处理,就像从物理网卡进入操作系统的数据一样。
除了应用程序以外,操作系统也会根据TCP/IP协议栈的处理向Tun/Tap接口发送IP数据包或者以太网数据包,例如ARP或者ICMP数据包。此时和之前一样,Tun/Tap驱动程序会将Tun/Tap接口收到的数据包原样写入到/dev/net/tun字符设备上,而处理Tun/Tap数据的应用程序如VPN程序可以从该设备上读取到数据包,以进行相应处理。
Tun虚拟设备和物理网卡的区别是Tun虚拟设备是IP层设备,从对应字符设备文件上读取的是IP数据包,写入的也只能是IP数据包,因此不能进行二层操作,如发送ARP请求和以太网广播。与之相对的是,Tap虚拟设备是以太网设备,处理的是二层以太网数据帧,从对应字符设备文件上读取的是以太网数据帧,写入的也只能是以太网数据帧。从这点来看,Tap虚拟设备和真实的物理网卡的能力更接近。
具体情况,如下图所示:
另外这里举例一个VPN的应用:
- 应用程序A是一个普通的程序,通过socket A发送了一个数据包,并指定其目的IP地址是192.168.3.1
- socket将这个数据包丢给协议栈
- 协议栈将数据包封装成网络包之后,它根据指定的目的IP地址,匹配本地路由规则,发现这个数据包应该发送给tun0,于是将数据包交给tun0
- tun0收到数据包之后,会把数据包复制到对应的字符设备文件上,然后进程B会从该字符设备文件上读取到对应的IP包
- 进程B收到数据包之后,通过socket B将IP包直接发送出去,并指定目的IP地址是10.33.0.1
- socket B将IP包丢给协议栈
- 协议栈将数据包再次从头开始封装成网络包(此时就是一个IP包里面嵌套一个IP包了,因为里面的IP包直接被协议栈看作成数据包),然后根据本地路由,发现这个数据包应该要通过eth0发送出去,于是将数据包交给eth0
- eth0通过物理网络将数据包发送出去
虚拟网桥
在现实世界中,局域网内多台机子要互相通信就要借助交换机进行连接,那么在虚拟网络中,靠什么连接多个虚拟机或者Net Namespace呢?答案就是虚拟网桥!
不管是物理网卡还是虚拟网卡,当它们接入了网桥之后它们就仅仅相当于端口或者网线插头了,对于虚拟网桥而言它们是没有ip的,当网卡收到了包以后,会将帧送入到网桥处理函数 br_handle_frame 中,然后再由虚拟网桥进行处理,网桥函数处理完的话,一般来说就 goto unlock 退出了,和普通的网卡数据包接收相比,并不会往下再送到协议栈了
另外在Linux中可以给虚拟网桥配置IP,这可能会令人迷惑,一个二层转发设备为什么可以配置IP呢?它配吗?其实给虚拟网桥配置IP,并不是在这个二层转发设备上配置IP,而是相当于在宿主机中创建了一张网卡,然后给将MAC地址和IP配置给了该网卡,最后再用一根虚拟网线将这张网卡连接到了虚拟网桥的端口上罢了,因此IP其实是配置给那张虚拟网卡的
因此在虚拟机桥接模式下,在将物理网卡接入虚拟交换机后,往往会把物理网卡的ip设置到虚拟交换机上,从而达到虚拟机和宿主机都能上网的目的,另外在Docker中也会将每个namespace中的路由的网关设置为Docker0网桥的ip地址,此时再在宿主机做一下NAT就可以和外网通信了,具体细节会在后面的博文中给出
参考
- 《趣谈网络协议》
- 公众号-《开发内功修炼》