0%

Linux Network Veth Peer

Veth Peer 是成对出现的一种虚拟网络设备接口,一端连着网络协议栈,一端彼此相连。由于它的这个特性,常常被用于构建虚拟网络拓扑。例如连接两个不同的网络命名空间(netns),连接 docker 容器,连接网桥(Bridge)等,其中一个很常见的案例就是 OpenStack Neutron 底层用它来构建非常复杂的网络拓扑。

深入理解 Veth

基本信息简介

本机 IP:172.20.88.202/20(物理网卡 eth0)
网关 IP:172.20.80.1

Veth0 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

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

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