云原生虚拟网络 tun/tap & veth-pair( 二 )


flannel UDP 模式使用 tun 设备收发数据早期 flannel 利用 tun 设备实现了 UDP 模式下的跨主网络相互访问,实际上原理和上面的 OpenVPN 是差不多的 。
在 flannel 中 flannel0 是一个三层的 tun 设备,用作在操作系统内核和用户应用程序之间传递 IP 包 。当操作系统将一个 IP 包发送给 flannel0 设备之后,flannel0 就会把这个 IP 包,交给创建这个设备的应用程序,也就是 flanneld 进程,flanneld 进程是一个 UDP 进程 , 负责处理 flannel0 发送过来的数据包:

云原生虚拟网络 tun/tap & veth-pair

文章插图
flanneld 进程会根据目的 IP 的地址匹配到对应的子网,从 Etcd 中找到这个子网对应的宿主机 Node2 的 IP 地址,然后将这个数据包直接封装在 UDP 包里面,然后发送给 Node 2 。由于每台宿主机上的 flanneld 都监听着一个 8285 端口 , 所以 Node2 机器上 flanneld 进程会从 8285 端口获取到传过来的数据 , 解析出封装在里面的发给 ContainerA 的 IP 地址 。
flanneld 会直接把这个 IP 包发送给它所管理的 TUN 设备,即 flannel0 设备 。然后网络栈会将这个数据包根据路由发送到 docker0 网桥,docker0 网桥会扮演二层交换机的角色,将数据包发送给正确的端口,进而通过 veth pair 设备进入到 containerA 的 Network Namespace 里 。
上面所讲的 Flannel UDP 模式现在已经废弃,原因就是因为它经过三次用户态与内核态之间的数据拷贝 。容器发送数据包经过 docker0 网桥进入内核态一次;数据包由 flannel0 设备进入到 flanneld 进程又一次;第三次是 flanneld 进行 UDP 封包之后重新进入内核态,将 UDP 包通过宿主机的 eth0 发出去 。
tap 设备作为虚拟机网卡上面我们也说了 , tap 设备是一个二层链路层设备,通常用作实现虚拟网卡 。以 qemu-kvm 为例,它利用 tap 设备和 Bridge 配合使用拥有极大的灵活性,可以实现各种各样的网络拓扑 。
云原生虚拟网络 tun/tap & veth-pair

文章插图
在 qume-kvm 开启 tap 模式之后,在启动的时候会向内核注册了一个tap类型虚拟网卡 tapx,x 代表依次递增的数字; 这个虚拟网卡 tapx 是绑定在 Bridge 上面的,是它上面的一个接口 , 最终数据会通过 Bridge 来进行转发 。
qume-kvm 会通过其网卡 eth0 向外发送数据 , 从 Host 角度看就是用户层程序 qume-kvm 进程将字符设备写入数据;然后 tapx 设备会收到数据后由 Bridge 决定数据包如何转发 。如果 qume-kvm 要和外界通信 , 那么数据包会被发送到物理网卡,最终实现与外部通信 。
从上面的图也可以看出 qume-kvm 发出的数据包通过 tap 设备先到达 Bridge ,然后到物理网络中,数据包不需要经过主机的的协议栈,这样效率也比较高 。
veth-pairveth-pair 就是一对的虚拟设备接口,它是成对出现的,一端连着协议栈 , 一端彼此相连着,在 veth 设备的其中一端输入数据,这些数据就会从设备的另外一端原样不变地流出:
云原生虚拟网络 tun/tap & veth-pair

文章插图
利用它可以连接各种虚拟设备,两个 namespace 设备之间的连接就可以通过 veth-pair 来传输数据 。
下面我们做一下实验 , 构造一个 ns1 和 ns2 利用 veth 通信的过程,看看veth是如何收发请求包的 。
# 创建两个namespaceip netns add ns1ip netns add ns2# 通过ip link命令添加vethDemo0和vethDemo1ip link add vethDemo0 type veth peer name vethDemo1# 将 vethDemo0 vethDemo1 分别加入两个 nsip link set vethDemo0 netns ns1ip link set vethDemo1 netns ns2# 给两个 vethDemo0 vethDemo1配上 IP 并启用ip netns exec ns1 ip addr add 10.1.1.2/24 dev vethDemo0ip netns exec ns1 ip link set vethDemo0 upip netns exec ns2 ip addr add 10.1.1.3/24 dev vethDemo1ip netns exec ns2 ip link set vethDemo1 up然后我们可以看到 namespace 里面设置好了各自的虚拟网卡以及对应的ip:
~ # ip netns exec ns1 ip addrroot@VM_243_186_centos1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group defaultlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:007: vethDemo0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000link/ether d2:3f:ea:b1:be:57 brd ff:ff:ff:ff:ff:ffinet 10.1.1.2/24 scope global vethDemo0valid_lft forever preferred_lft foreverinet6 fe80::d03f:eaff:feb1:be57/64 scope linkvalid_lft forever preferred_lft forever然后我们 ping vethDemo1 设备的 ip:
ip netns exec ns1 ping 10.1.1.3对两个网卡进行抓包:
~ # ip netns exec ns1 tcpdump -n -i vethDemo0root@VM_243_186_centostcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on vethDemo0, link-type EN10MB (Ethernet), capture size 262144 bytes20:19:14.339853 ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 2820:19:14.339877 ARP, Reply 10.1.1.3 is-at 0e:2f:e6:ce:4b:36, length 2820:19:14.339880 IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 27402, seq 1, length 6420:19:14.339894 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 27402, seq 1, length 64~ # ip netns exec ns2 tcpdump -n -i vethDemo1root@VM_243_186_centostcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on vethDemo1, link-type EN10MB (Ethernet), capture size 262144 bytes20:19:14.339862 ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 2820:19:14.339877 ARP, Reply 10.1.1.3 is-at 0e:2f:e6:ce:4b:36, length 2820:19:14.339881 IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 27402, seq 1, length 6420:19:14.339893 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 27402, seq 1, length 64

推荐阅读