Veth Peer 是成对出现的一种虚拟网络设备接口,一端连着网络协议栈,一端彼此相连。由于它的这个特性,常常被用于构建虚拟网络拓扑。例如连接两个不同的网络命名空间(netns),连接 docker 容器,连接网桥(Bridge)等,其中一个很常见的案例就是 OpenStack Neutron 底层用它来构建非常复杂的网络拓扑。
深入理解 Veth
基本信息简介
本机 IP:172.20.88.202/20(物理网卡 eth0)
网关 IP:172.20.80.1Veth0 IP:172.20.80.101/20
Veth1 IP:172.20.80.102/20
注意内核中的一些 ARP 相关配置导致 Veth Peer 不返回 ARP 应答包,解决方法如下:
echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter
只给一个veth设备配置IP
$ ip link add veth0 type veth peer name veth1
$ ip addr add 172.20.80.101/20 dev veth0
$ ip link set veth0 up
$ ip link set veth1 up
这里不给 veth1 设备配置 IP 的原因就是想看看在 veth1 没有IP的情况下,veth0 收到协议栈的数据后会不会转发给 veth1。
ping 一下 eth0,由于 veth1 还没配置 IP,所以肯定不通。
$ ping -c 1 -I veth1 172.20.88.216
ping: Warning: source address might be selected on device other than: veth1
PING 172.20.88.216 (172.20.88.216) from 172.20.88.216 veth1: 56(84) bytes of data.
--- 172.20.88.216 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
给两个veth设备都配置IP
给 veth1 也配置上 IP
$ ip addr add 172.20.80.102/20 dev veth1
$ ping -c 1 -I veth1 172.20.88.216
PING 172.20.88.216 (172.20.88.216) from 172.20.80.102 veth1: 56(84) bytes of data.
64 bytes from 172.20.88.216: icmp_seq=1 ttl=64 time=0.055 ms
--- 172.20.88.216 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.055/0.055/0.055/0.000 ms
再来看看抓包情况,我们在veth0和veth1上都看到了ICMP echo的请求包,但为什么没有应答包呢?上面不是显示ping进程已经成功收到了应答包吗?
$ tcpdump -eni veth0
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:39:30.814690 96:4b:d4:18:76:4b > 66:0d:24:5a:ca:32, ethertype IPv4 (0x0800), length 98: 172.20.80.102 > 172.20.88.216: ICMP echo request, id 845, seq 1, length 64
22:39:35.848014 96:4b:d4:18:76:4b > 66:0d:24:5a:ca:32, ethertype ARP (0x0806), length 42: Request who-has 172.20.88.216 tell 172.20.80.102, length 28
22:39:35.848079 66:0d:24:5a:ca:32 > 96:4b:d4:18:76:4b, ethertype ARP (0x0806), length 42: Reply 172.20.88.216 is-at 66:0d:24:5a:ca:32, length 28
$ tcpdump -eni veth1
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
22:34:58.119853 96:4b:d4:18:76:4b > 66:0d:24:5a:ca:32, ethertype IPv4 (0x0800), length 98: 172.20.80.102 > 172.20.88.216: ICMP echo request, id 811, seq 1, length 64
22:35:03.127900 96:4b:d4:18:76:4b > 66:0d:24:5a:ca:32, ethertype ARP (0x0806), length 42: Request who-has 172.20.88.216 tell 172.20.80.102, length 28
22:35:03.128021 66:0d:24:5a:ca:32 > 96:4b:d4:18:76:4b, ethertype ARP (0x0806), length 42: Reply 172.20.88.216 is-at 66:0d:24:5a:ca:32, length 28
看看数据包的流程就明白了:
- ping 进程构造 ICMP echo 请求包,并通过 socket 发给协议栈
- 由于 ping 程序指定了走 veth1,并且本地 ARP 缓存里面已经有了相关记录,所以不用再发送 ARP 出去,协议栈就直接将该数据包交给了 veth1
- 由于 veth1 的另一端连的是 veth0,所以 ICMP echo 请求包就转发给了 veth0
- veth0 收到 ICMP echo 请求包后,转交给另一端的协议栈
- 协议栈一看自己的设备列表,发现本地有 172.20.88.216 这个IP,于是构造 ICMP echo 应答包,准备返回
- 协议栈查看自己的路由表,发现回给 192.168.2.11 的数据包应该走lo口,于是将应答包交给 lo 设备
- lo 接到协议栈的应答包后,啥都没干,转手又把数据包还给了协议栈(相当于协议栈通过发送流程把数据包给 lo,然后 lo 再将数据包交给协议栈的接收流程)
- 协议栈收到应答包后,发现有 socket 需要该包,于是交给了相应的 socket
- 这个 socket 正好是 ping 进程创建的 socket,于是 ping 进程收到了应答包
抓一下 lo 设备上的数据,发现应答包确实是从 lo 口回来的:
$ tcpdump -eni lo
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
22:43:29.539583 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 98: 172.20.88.216 > 172.20.80.102: ICMP echo reply, id 913, seq 1, length 64