3

通过实验学习 Linux VETH 和 Bridge

 1 year ago
source link: https://gobomb.github.io/post/learning-linux-veth-and-bridge/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

通过实验学习 Linux VETH 和 Bridge

17 Sep 2019

docker 容器的网络,以及 flannel 网络的实现都用到了 VETH 和 Bridge 这两种虚拟设备。大致原理是在主机创建一个 Bridge,每当一个新的容器创建,就创建一对 VETH,一端连接到主机的 Bridge,另一端连接到容器命名空间里,作为容器的默认网卡,并将容器的默认路由设置为 Bridge。

本文将用 Linux 的相关命令,模拟上述网络创建的过程,并通过实验和抓包对原理进行验证,加深对容器和 Linux 网络的理解。

VETH(virtual Ethernet):是 Linux 内核支持的一种虚拟网络设备,表示一对虚拟的网络接口,VETH 对的两端可以处于不同的网络命名空间,所以可以用来做主机和容器之间的网络通信。

Bridge:Bridge 类似于交换机,用来做二层的交换。可以将其他网络设备挂在 Bridge 上面,当有数据到达时,Bridge 会根据报文中的 MAC 信息进行广播、转发或丢弃。

Namespace:是 Linux 提供的一种内核级别环境隔离的方法。不同命名空间下的资源集合无法互相访问。

实验环境:

root@ubuntu:~# cat /proc/version
Linux version 4.4.0-38-generic (buildd@lgw01-58) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.2) ) #57-Ubuntu SMP Tue Sep 6 15:42:33 UTC 2016

网卡名称和 IP 地址为ens160 10.10.12.27/24

网络拓扑示意图

                           +------------------------+
                           |                        | iptables +----------+
                           |  br01 192.168.88.1/24  |          |          |
                +----------+                        <--------->+ ens160   |
                |          +------------------+-----+          |          |
                |                             |                +----------+
           +----+---------+       +-----------+-----+
           |              |       |                 |
           | br-veth01    |       |   br-veth02     |
           +--------------+       +-----------+-----+
                |                             |
+--------+------+-----------+     +-------+---+-------------+
|        |                  |     |       |                 |
|  ns01  |   veth01         |     |  ns02 |  veth01         |
|        |                  |     |       |                 |
|        |   192.168.88.11  |     |       |  192.168.88.12  |
|        |                  |     |       |                 |
|        +------------------+     |       +-----------------+
|                           |     |                         |
|                           |     |                         |
+---------------------------+     +-------------------------+


br01 是一个 bridge,连接两个 veth,两个 veth 的另一端分别在另外两个 namespace 里。ens160 是宿主机对外的网卡,namespace 对外的包会经过 snat 从宿主机网卡出去。

  1. 设置 bridge

    创建 bridge:

     $ brctl addbr br01
    

    启用 bridge

      $ ip link set dev br01 up
    

    给 bridge 分配 IP 地址

      $ ifconfig br01 192.168.88.1/24 up
    
  2. 创建 namespace

    创建两个 ns:ns01、ns02

     $ ip netns add ns01
     $ ip netns add ns02
     $ ip netns list   # 查看创建的 ns
     ns02
     ns01
    
  3. 设置 veth pair

    创建两对 veth

     $ ip link add veth01 type veth peer name br-veth01
     $ ip link add veth02 type veth peer name br-veth02
    

    将其中一端的 veth(br-veth01 / br-veth02) 挂到默认命名空间的 bridge(br01)下面

     $ brctl addif br01 br-veth01
     $ brctl addif br01 br-veth02
    

    查看 bridge 下挂的 veth

     $ brctl show
     bridge name	bridge id		STP enabled	interfaces
     br01		8000.1e72c41f55ab	no		br-veth01
                                             br-veth02
    

    启动这两个 veth

     $ ip link set dev br-veth01 up
     $ ip link set dev br-veth02 up
    

    将另一端 veth 分配给两个上一步创建好的两个 ns(ns01 / ns02)

     $ ip link set veth01 netns ns01
     $ ip link set veth02 netns ns02
    

    通过 ip netns exec [ns] [command] 命令可以在特定的网络命名空间里执行命令

    查看命令空间里的网络设备:

     $ ip netns exec ns01 ip a
     1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     34: veth01@if33: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
         link/ether 32:29:09:32:c5:91 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    

    可以看到刚刚被我们加进来的 veth01,此时还没有 IP 地址

    给两个命名空间的 veth 设置 IP 地址和默认路由,默认网关设置为 bridge 的 IP:

     $ ip netns exec ns01 ip link set dev veth01 up
     $ ip netns exec ns01 ifconfig veth01 192.168.88.11/24 up
     $ ip netns exec ns01 ip route add default via 192.168.88.1
    	
     $ ip netns exec ns02 ip link set dev veth02 up
     $ ip netns exec ns02 ifconfig veth02 192.168.88.12/24 up
     $ ip netns exec ns02 ip route add default via 192.168.88.1
    

    此时再看 ns01 的 veth01 已经被分配了 IP

     $ ip netns exec ns01 ip a
     1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     34: veth01@if33: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000
         link/ether 32:29:09:32:c5:91 brd ff:ff:ff:ff:ff:ff link-netnsid 0
         inet 192.168.88.11/24 brd 192.168.88.255 scope global veth01
         valid_lft forever preferred_lft forever
    
  4. 不同 ns 互相 ping 并抓包

    从 ns01 里 ping ns02,同时在默认 ns 里用 tcpdump 在 br01 bridge 上抓包:

    ping:

     $ ip netns exec ns01 ping 192.168.88.12 -c 1
     PING 192.168.88.12 (192.168.88.12) 56(84) bytes of data.
     64 bytes from 192.168.88.12: icmp_seq=1 ttl=64 time=0.116 ms
    	
     --- 192.168.88.12 ping statistics ---
     1 packets transmitted, 1 received, 0% packet loss, time 0ms
     rtt min/avg/max/mdev = 0.116/0.116/0.116/0.000 ms
    
     $ tcpdump  -i br01 -nn
     tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
     listening on br01, link-type EN10MB (Ethernet), capture size 262144 bytes
     15:17:57.592785 IP 192.168.88.11 > 192.168.88.12: ICMP echo request, id 17158, seq 1, length 64
     15:17:57.592868 IP 192.168.88.12 > 192.168.88.11: ICMP echo reply, id 17158, seq 1, length 64
    

    反之在 ns2 ping ns1 也是通的。

  5. 在 ns01 里执行 arp

     $ ip netns exec ns01 arp
     Address                  HWtype  HWaddress           Flags Mask            Iface
     192.168.88.12            ether   1a:43:9a:b0:31:5e   C                     veth01
     192.168.88.1             ether   1e:72:c4:1f:55:ab   C                     veth01
    

    可以看到获取到的 MAC 地址是正确的:

     $ ip link
     ...
     32: br01: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
         link/ether 1e:72:c4:1f:55:ab brd ff:ff:ff:ff:ff:ff
     ...
    	
     $ ip netns exec ns02 ip link
     1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
         link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     36: veth02@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
         link/ether 1a:43:9a:b0:31:5e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    
  6. 从 ns02 ping 外网地址

     $ ip netns exec ns02 ping 114.114.114.114
     PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
     ^C
     --- 114.114.114.114 ping statistics ---
     20 packets transmitted, 0 received, 100% packet loss, time 19151ms
    

    发现 ping 不通,继续抓包:

     $ tcpdump  -i br01 -nn
     tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
     listening on br01, link-type EN10MB (Ethernet), capture size 262144 bytes
     15:23:18.662300 IP 192.168.88.12 > 114.114.114.114: ICMP echo request, id 17919, seq 13, length 64
     15:23:19.669940 IP 192.168.88.12 > 114.114.114.114: ICMP echo request, id 17919, seq 14, length 64
    	
     $ tcpdump  -i ens160 -nn  host 114.114.114.114
     tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
     listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes
     15:41:59.533624 IP 192.168.88.12 > 114.114.114.114: ICMP echo request, id 20729, seq 1, length 64
     15:42:00.549989 IP 192.168.88.12 > 114.114.114.114: ICMP echo request, id 20729, seq 2, length 64
    

    发现只有出去的没有回来的包。原因应该是源地址是私有地址,发回来的包目的地址是私有地址的话会被丢弃,解决办法是做一下源 nat

     iptables -t nat -A POSTROUTING -s 192.168.88.0/24 -j MASQUERADE
    

    再次 ping 则可以 ping 通

     $ tcpdump  -i ens160 -nn  host 114.114.114.114
     ...
     15:45:09.815939 IP 10.10.12.27 > 114.114.114.114: ICMP echo request, id 21211, seq 1, length 64
     15:45:09.844922 IP 114.114.114.114 > 10.10.12.27: ICMP echo reply, id 21211, seq 1, length 64
     ...
    	
     $ tcpdump  -i br01 -nn -vv
     ...
     15:45:52.507058 IP (tos 0x0, ttl 64, id 24952, offset 0, flags [DF], proto ICMP (1), length 84)
         192.168.88.12 > 114.114.114.114: ICMP echo request, id 21315, seq 1, length 64
     15:45:52.535156 IP (tos 0x0, ttl 82, id 4450, offset 0, flags [none], proto ICMP (1), length 84)
         114.114.114.114 > 192.168.88.12: ICMP echo reply, id 21315, seq 1, length 64
     ...
    

    可以看到,从 ens160 出去到外网的包,source IP 变成了外网网卡的 IP

  7.  $ ip netns del ns01
     $ ip netns del ns02
     $ ifconfig br01 down
     $ brctl delbr br01
     $ iptables -t nat -D POSTROUTING -s 192.168.88.0/24 -j MASQUERADE
    

遇到的问题

设置完 brige 和 veth 之后,从 ns01 ping 不通 ns02 的网卡。在 bridge 能抓到ICMP echo request却没有ICMP echo reply,ns02 内没有抓到包。

宿主机是否开启转发,若开启了转发,iptalbes 规则是否有问题。iptables -S | less检查规则。

我在实验的时候因为主机上安装了 docker,FORWARD 链默认策略被 docker 设置成 drop。从 ns01 发出的 ICMP 报文是会被丢弃的。

解决方法是修改 FORWARD 的默认策略为 ACCEPT 放行:

iptables -P FORWARD ACCEPT
  1. 涉及到的网络主要在二层和三层,需要对二层三层网络原理和协议比较熟悉。

  2. 常用 Linux 网络命令,如 ip / iptables / brctl / tcpdump 等,也是需要掌握的,对于分析和定位网络问题很有帮助。这些命令本质上也是去 call 内核的系统调用,所以同样也可以用 go 或者 C 语言编程实现类似的功能。

Linux Switching – Interconnecting Namespaces

Using network namespaces and a virtual switch to isolate servers

Linux 上的基础网络设备详解


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK