ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看网卡


本来我准备一篇文章写完Linux虚拟网络和Docker网络原理的,写着写着发现光Linux虚拟网络都是很长的一篇了,所以这篇准备专注于Linux虚拟网络。这篇文章主要从实践角度介绍了在一台Linux主机上,如何实用namespace,veth pair,bridge和NAT等技术搭建出来一个虚拟网络。我们知道docker网络就是基于linux这些虚拟网络技术实现的,学习这些技术对理解docker网络有很大的帮助。跟着我的步骤一步步做,最后我们会做出如图所示的一个网络拓扑结构:


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看路由_02


在学习Linux虚拟网络之前,我们得知道现实中的网络是怎么工作的。我的网络基础非常一般,仅仅学习过HTTP协议以及TCP/IP协议的理论知识,缺乏实践知识,对实用场景下各个协议如何协作,工作细节方面知之甚少。在这里我推荐几个我学习过程中觉得很赞的视频和文章供大家参考,如果你也和我一样基础薄弱的话,可以先打打基础。

  1. youtube的PowerCert频道,我觉得比较好的是这个频道,用了大量动画,外加讲解把一些网络和硬件中很晦涩的知识讲的很形象,非常好理解。

PowerCert Animated Videoswww.youtube.com

2. 这是一篇微信公众号文章,不是我写的。以浏览器访问server为背景,从url输入的那一刻,到浏览器上呈现出来页面中间经历的所有细节都罗列了出来,有一种把之前学习的HTTP与TCP/IP基础都串起来的感觉。能够理解这篇文章,Linux虚拟网络也很好理解,只需要把现实的设备与虚拟的设备一一对应即可。

探究!一个数据包在网络中的心路历程mp.weixin.qq.com


Linux网络设备与概念介绍

就像现实网络中有网线,网卡,交换机,路由器一样,linux虚拟网络中也有虚拟网线veth pair,虚拟网卡veth,虚拟交换机和虚拟路由器bridge。Linux通过network namespace,把网络划分成一个个的独立空间,再通过虚拟网络设备将这些独立空间连接起来形成一个虚拟网络。下面先给出一些基本概念:

1. Network namespace

网络命名空间,允许你在Linux创建相互隔离的网络视图,每个网络名字空间都有独立的网络配置,比如:网络设备、路由表,ARP表等。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。新建的网络名字空间与主机默认网络名字空间之间是隔离的。我们平时默认操作的是主机的默认网络名字空间

2. Linux Bridge

即Linux网桥设备,是Linux提供的一种虚拟网络设备之一。其工作方式非常类似于物理的网络交换机设备。Linux Bridge可以工作在二层,也可以工作在三层,默认工作在二层。工作在二层时,可以在同一网络的不同主机间转发以太网报文;一旦你给一个Linux Bridge分配了IP地址,也就开启了该Bridge的三层工作模式。在Linux下,你可以用iproute工具包或brctl命令对Linux bridge进行管理。

3. VETH

VETH(Virtual Ethernet )是Linux提供的另外一种特殊的网络设备,中文称为虚拟网卡接口。它总是成对出现,要创建就创建一个pair。一个Pair中的veth就像一个网络线缆的两个端点,数据从一个端点进入,必然从另外一个端点流出。每个veth都可以被赋予IP地址,并参与三层网络路由过程。

更多设备的详细介绍可以参考:

Linux 上的基础网络设备详解www.ibm.com

看定义很抽象,接下来我们直接上实践。


使用Network namespace创建虚拟网络

1. ip netns的基本操作

我们可以使用ip命令对network namespace进行操作,ip命令来自于iproute安装包,一般系统会默认安装ip命令。使用ip命令需要root权限。通过命令:


$ ip netns add red


我们就可以创建一个名为red的network namespace(以下简称 netns)。执行:


$ ip netns ls
red


便可以查看已创建的netns,此外,ip netns命令创建的 netns 会出现在/var/run/netns/目录下,如果想管理不是由ip netns管理的netns的话,需要创建文件链接,链接被管理的netns文件到/var/run/netns/目录下。

每个netns都有自己的网卡、路由表、ARP 表、iptables 等。可以通过ip netns exec NAMESPACE COMMAND 子命令进行查看,该命令允许我们在特定的netns下执行任何命令,不仅仅是网络相关的命令。我们可以利用这个命令查看red的netns下都有些什么:


# 查看red里面的路由表(目前是空的)
$ ip netns exec red route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

# 查看red里面的网卡
$ ip netns exec red ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# 查看red里面的arp表(目前是空的)
$ ip netns exec red arp
$


我们直接在主机的netns中执行这些命令,可以看到输出完全不同,表现出了red和主机网络空间是完全隔离的,两者各有自己的一套资源,互不共享。


# 查看主机网络空间的路由表
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.31.32.1     0.0.0.0         UG    100    0        0 eth0
172.31.32.0     0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.31.32.1     0.0.0.0         255.255.255.255 UH    100    0        0 eth0

# 查看主机网络空间的网卡
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 06:41:91:32:f1:84 brd ff:ff:ff:ff:ff:ff
    inet 172.31.47.15/20 brd 172.31.47.255 scope global dynamic eth0
       valid_lft 2016sec preferred_lft 2016sec
    inet6 fe80::441:91ff:fe32:f184/64 scope link 
       valid_lft forever preferred_lft forever

# 查看主机网络空间的arp表
$ arp
Address                  HWtype  HWaddress           Flags Mask            Iface
ip-172-31-32-1.ap-north  ether   06:7a:78:95:2e:5a   C                     eth0


2. namespace之间的通信

上面介绍了ip netns 的基本使用的介绍中,我们创建了一个名为red的netns。现在我们再创建一个名为blue的netns,让red与blue之间能够通信。类似的执行命令:


$ ip netns add blue
$ ip netns ls
blue
red


如何让2个netns之间可以通信?我们可以使用linxu提供的veth pair。如果把red和blue各看成是一个主机,那么veth pair就是连着这2台主机的网线,以及网线两端的网卡。执行下面的命令,我们可以创建一对veth,veth-red和veth-blue。需要注意的是 veth pair 无法单独存在,删除其中一个,另一个也会自动消失。


$ ip link add veth-red type veth peer name veth-blue


现在我们有了网线和连在两端的网卡,我们需要把两端的网卡安装到red和blue的netns中,现在执行:


$ ip link set veth-blue netns blue
$ ip link set veth-red netns red


我们来看看red和blue里面的网卡情况:


$ ip netns exec red ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: veth-red@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 12:0a:79:4f:f4:67 brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ ip netns exec blue ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: veth-blue@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether e2:4b:50:d3:14:0d brd ff:ff:ff:ff:ff:ff link-netnsid 1


可以看到,red中出现了veth-red网卡,blue中出现了veth-blue网卡,但这个网卡的状态都是DOWN,而且它们目前没有IP地址。所以接下来我们要启用这2个网卡并且赋予它们IP地址,执行命令:


# 开启veth-red,赋予它IP地址192.168.15.1,子网掩码为255.255.255.0
$ ip netns exec red ip addr add 192.168.15.1/24 dev veth-red
$ ip netns exec red ip link set veth-red up

# 开启veth-blue,赋予它IP地址192.168.15.2,子网掩码为255.255.255.0
$ ip netns exec blue ip addr add 192.168.15.2/24 dev veth-blue
$ ip netns exec blue ip link set veth-blue up


到目前为止,我们完成了red和blue的链接。查看red的路由表我们发现了一条自动添加的路由记录,gateway为0.0.0.0,表示发往192.168.15.0/24的数据包无需路由,走veth-red网卡出站。 相应的blue的路由表中也自动添加了一条类似的记录,只不过出站网卡为veth-blue。


$ ip netns exec red route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 veth-red

$ ip netns exec blue route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 veth-blue


现在我们使用ping去测试red与blue 之间的连通性:


$ ip netns exec red ping 192.168.15.2
PING 192.168.15.2 (192.168.15.2) 56(84) bytes of data.
64 bytes from 192.168.15.2: icmp_seq=1 ttl=64 time=0.046 ms
64 bytes from 192.168.15.2: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 192.168.15.2: icmp_seq=3 ttl=64 time=0.039 ms
...


分别查看red和blue的arp表,我们发现blue的arp表中出现了red的ip地址以及MAC地址记录,相应的red的arp表中也出现了blue的记录。


$ ip netns exec blue arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.15.1             ether   12:0a:79:4f:f4:67   C                     veth-blue
$ ip netns exec red arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.15.2             ether   e2:4b:50:d3:14:0d   C                   veth-red


整个ip netns exec red ping 192.168.15.2命令的执行过程可以总结如下:

  1. 在red的命名空间中,根据ping的目标地址192.168.15.2,查询red的路由表得知,192.168.15.2就在本地网络中,无需路由;
  2. red先在自己的arp表中查询192.168.15.2的MAC地址,发现没有记录(新建的命名空间arp表都是空的),于是发起ARP广播到本地网络,询问192.168.15.2的MAC地址;
  3. blue收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为blue的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存blue的MAC地址,以方便以后继续访问;
  4. blue从veth-blue收到red发来的数据包后,也在自己的arp中缓存red的MAC地址,并且通过veth-blue给red发送ping的response。

到此为止我们的网络拓扑结构为:


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看路由_03

red与blue的网络拓扑图

3. 使用brigde连接不同的namespace

在上文中,我们用veth pair实现了2个namespace之间的连接。但如果2个以上的namespace 需要互相连通的,用veth pair就有点力不从心了。在现实网络中,如果需要连通多个相同网络中的设备,我们会用到交换机,相应的,linux也提供虚拟的交换机也就是bridge 。我们可以使用bridge作为中转站来连通多个相同网络的namespace,打造出一个虚拟的LAN。首先放出我们将要实现的网络拓扑图:


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看路由_04

red和blue通过bridge连接的网络拓扑图

与bridge相关的操作可以使用iproute包的ip命令,也可以使用bridge-utils包的 brctl 命令,本文继续使用ip命令。

我们继续在上文环境的基础下,首先删除第2部分中创建的veth-blue和veth-red,断开blue和red之间的连接。需要注意的是我们仅仅需要删除veth-blue和veth-red中的两者之一,因为它俩是pair,删除一个,另一个也会自动删除。执行以下命令:


$ ip netns exec red ip link del veth-red


现在red已经无法ping到blue了:


$ ip netns exec red ping 192.168.15.2
connect: Network is unreachable


创建bridge虚拟交换机vbridge-0,并启用它。


$ ip link add vbridge-0 type bridge
$ ip link set dev vbridge-0 up


创建2个veth pair,将它们分别连接到red与vbridge-0,blue与vbridge-0的两端。


$ ip link add veth-red type veth peer name veth-red-br
$ ip link add veth-blue type veth peer name veth-blue-br
$ ip link set veth-red netns red
$ ip link set veth-red-br master vbridge-0
$ ip link set veth-blue netns blue
$ ip link set veth-blue-br master vbridge-0


通过bridge link命令查看连接到bridge的veth信息,可以看到 veth-red-br与veth-blue-br 都连接到了bridge上。


$ bridge link
9: veth-red-br state DOWN @if10: <BROADCAST,MULTICAST> mtu 1500 master vbridge-0 state disabled priority 32 cost 2 
11: veth-blue-br state DOWN @if12: <BROADCAST,MULTICAST> mtu 1500 master vbridge-0 state disabled priority 32 cost 2


给veth-blue与veth-red添加ip地址:


$ ip netns exec red ip addr add 192.168.15.1/24 dev veth-red
$ ip netns exec blue ip addr add 192.168.15.2/24 dev veth-blue


启用veth-red, veth-red-br与veth-blue,veth-blue-br:


$ ip netns exec red ip link set veth-red up
$ ip link set dev veth-red-br up
$ ip netns exec blue ip link set veth-blue up
$ ip link set dev veth-blue-br up


现在我们测试red到blue的连通性:


$ ip netns exec red ping 192.168.15.2
PING 192.168.15.2 (192.168.15.2) 56(84) bytes of data.
64 bytes from 192.168.15.2: icmp_seq=1 ttl=64 time=0.059 ms
64 bytes from 192.168.15.2: icmp_seq=2 ttl=64 time=0.052 ms
64 bytes from 192.168.15.2: icmp_seq=3 ttl=64 time=0.048 ms
...


如果你在这一步无法ping通,那是因为ping包被iptable拦截了。简单地说,当red发往blue的ping包经过bridge转发时,这个包裹会路过iptable中filter表的FORWARD链,而该链默认的policy是DROP,也就是说没有显式地增加规则去允许bridge转发的包的话,该包会被丢弃。详细可以参考:

why linux bridge doesn't worksuperuser.com

所以有3种解决方案:

1. 显示增加规则去允许转发,我们的bridge名字为vbridge-0,所以执行命令:


iptables -A FORWARD -o vbridge-0 -j ACCEPT
iptables -A FORWARD -i vbridge-0  -j ACCEPT


2. 禁用iptable对bridge包的检查:


sysctl net.bridge.bridge-nf-call-iptables=0


3. 修改filter表的FORWARD的默认policy,从DROP改成ACCEPT


iptables -P FORWARD ACCEPT


整个ip netns exec red ping 192.168.15.2命令的执行过程可以总结如下:

  1. 在red的命名空间中,根据ping的目标地址192.168.15.2,查询red的路由表得知,192.168.15.2就在本地网络中,无需路由;
  2. red先在自己的arp表中查询192.168.15.2的MAC地址,发现没有记录,于是发起ARP广播到本地网络,询问192.168.15.2的MAC地址;
  3. blue通过vbridge-0收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为blue的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存blue的MAC地址,以方便以后继续访问;
  4. vbridge-0从veth-red-br收到数据包后,转发(FORWARD)给veth-blue-br;
  5. blue从veth-blue收到red发来的数据包后,也在自己的arp中缓存red的MAC地址,并且通过veth-blue给red发送ping的response。

到目前为止,我们完成了red <--> vbridge-0 <--> blue的网络拓扑结构,有了brigde以后,如果我们想要在192.168.15.0/24网络中增加新的netns,并且使他与现有的netns能够连通,我们只需让新的netns与bridge连通即可。

4. 使用bridge的路由模式连通namespace与host

在上文中我们在主机里面创建了一个虚拟的私有网络192.168.15.0/24,里面具有2个网络空间blue和red,通过vbridge-0连接。这个网络对我们的主机来讲是无法连通,因为我们的主机与该网络不属于一个子网(本例中主机IP为:172.31.47.15,通过ip addr查看eth0网卡的ip地址):


# 从red ping主机
$ ip netns exec red ping 172.31.47.15
connect: Network is unreachable

# 从主机ping red
$ ping -c 3 192.168.15.1
PING 192.168.15.1 (192.168.15.1) 56(84) bytes of data.

--- 192.168.15.1 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2031ms


这是因为red的路由表中没有匹配主机IP的记录,主机的路由表中也有没有匹配red ip的记录。因为red的路由表中没有default gateway,所以返回ICMP host unreachable或ICMP network unreachable错误显示Network is unreachable;而主机路由表中有default gateway,发往192.168.15.1数据包走gateway发出后请求超时。


# red的路由表
$ ip netns exec red route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 veth-red

# 主机的路由表
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.31.32.1     0.0.0.0         UG    100    0        0 eth0
172.31.32.0     0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.31.32.1     0.0.0.0         255.255.255.255 UH    100    0        0 eth0


我们知道在现实网络中,可以使用路由器连通不同局域网中的设备。因此,为了连通我们的网络空间red,blue与我们的主机,我们需要启动bridge的路由功能。启用bridge的路由功能的操作很简单,我们只需要给我们的vbridge-0一个IP地址,该IP地址需要与red和blue在同一个子网中:


$ ip addr add 192.168.15.5/24 dev vbridge-0


为vbridge-0增加ip地址后,我们发现主机的路由表上自动创建了一条记录,表示主机已经处于192.168.15.0/24这个网络中了。这是因为vbridge-0虽说是一个bridge装置,但当它具有IP地址后,它也可以看作是主机的一个网卡。vbridge-0本身与red和blue相连接,所以这样一来主机通过vbridge-0也与red和blue连接起来了。


$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 vbridge-0


我们来验证一下:


$ ping 192.168.15.1
PING 192.168.15.1 (192.168.15.1) 56(84) bytes of data.
64 bytes from 192.168.15.1: icmp_seq=1 ttl=64 time=0.077 ms
64 bytes from 192.168.15.1: icmp_seq=2 ttl=64 time=0.057 ms
64 bytes from 192.168.15.1: icmp_seq=3 ttl=64 time=0.066 ms
...


OK,现在从主机可以ping通red和blue了,那么怎么让red和blue也能ping通主机呢?首先,vbridge-0启用了路由模式后,可以看作一个交换机与路由器的合体。对于blue和red而言,可以把vbridge-0作为它们通向外网的gateway,以red为例子,我们在red中增加一条default gateway的路由记录,gateway使用vbridge-0的ip:


$ ip netns exec red ip route add default via 192.168.15.5
$ ip netns exec red route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.15.5    0.0.0.0         UG    0      0        0 veth-red
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 veth-red


现在去往192.168.15.0/24范围外的ip会先发往vbridge-0,再经过vbridge-0进行下一步路由,发往host的eth0网卡,这时我们再从red ping到host:


$ ip netns exec red ping 172.31.47.15
PING 172.31.47.15 (172.31.47.15) 56(84) bytes of data.
64 bytes from 172.31.47.15: icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from 172.31.47.15: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 172.31.47.15: icmp_seq=3 ttl=64 time=0.057 ms
...


整个ip netns exec red ping 172.31.47.15命令的执行过程可以总结如下:

  1. 在red的命名空间中,根据ping的目标地址172.31.47.15,查询red的路由表得知,gateway为192.168.15.5;
  2. red先在自己的arp表中查询192.168.15.5的MAC地址,发现没有记录,于是发起ARP广播到本地网络,询问192.168.15.5的MAC地址;
  3. vbridge-0收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为vbridge-0的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存vbridge-0的MAC地址,以方便以后继续访问;
  4. vbridge-0从从veth-red-br收到数据包后再查询路由表(也就是主机的路由表),发现本机处于172.31.32.0/20网络中,因此发往172.31.47.15的数据包无需再路由,路由表显示的Iface为eth0,于是vbridge-0将数据包递给eth0;
  5. eth0收到后给red回复。

host的路由表如下,可以看到第2条规则将发往172.31.47.15的数据包导向了eth0网卡。


# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.31.32.1     0.0.0.0         UG    100    0        0 eth0
172.31.32.0     0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.31.32.1     0.0.0.0         255.255.255.255 UH    100    0        0 eth0
192.168.15.0    0.0.0.0         255.255.255.0   U     0      0        0 vbridge-0


到目前为止,我们的拓扑图为:


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看路由_05

bridge开启路由模式后的网络拓扑图

5. 使用NAT连通namespace与公网

现在我们的blue和red不仅仅可以通过bridge的二层交换访问到彼此,还可以通过三层路由与host进行交互。但是当我们尝试从blue连到internet时,我们发现还是连不通:


$ ip netns exec red ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2033ms


这又是什么回事呢?我们来分析一下,从red发往8.8.8.8(google提供的免费DNS的地址)的IP时,经过red的路由表路由后发往vbridge-0,而vbridge-0查询主机的路由表后发现,这个包应该发给default gateway 172.31.32.1,但是能发给default gateway的网卡只有eth0,所以vbridge-0需要将数据包先转发(forward)给eth0才行,称为IP forwad。

IP forwad,也就是所谓的转发,即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将包发往本机另一网卡,该网卡根据路由表继续发送数据包。这通常就是路由器所要实现的功能。

一般来讲,linux系统上出于安全考虑都禁用了IP forward功能,我们要先开启IP Forwad:


# 查看是否已经开启ip forward,0为关闭,1为开启
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

# 开启ip forward
$ sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1


很好,我们现在开启了ip forward,我们再来ping一个:


$ ip netns exec red ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2035ms


很悲剧,还是不行。这是为什么呢?我们来分析下原因,从red发往8.8.8.8的数据包源地址为veth-red的地址192.168.15.1。8.8.8.8收到我们的数据包后,想要给我们回复,但是192.168.15.1是一个私网地址没法路由。然而我们知道eth0网卡是可以联网的也可以收到回复的。所以我们要做的是在数据包经过主机的eth0网卡发送到公网前,使用SNAT将数据包的源ip改成eth0的网卡的ip,然后在主机收到回复的时候将ip改回来。

注:NAT是基于内核功能conntrack实现的,想要关闭conntrack功能可以关闭conntrack模块,或者在raw表中的PREROUTING和OUTPUT链中对想要关闭conntrack的数据包打上NOTRACK,这样conntrack就不会追踪连接状态,相应的NAT功能与有状态iptable规则都会无效。关于conntrack详情可以参考:

云计算底层技术-netfilter框架研究opengers.github.io

通过iptables增加SNAT规则,将源ip为192.168.15.0/24内的数据包的源ip修改为eth0的ip:


$ iptables -t nat -A POSTROUTING -s 192.168.15.0/24 -j MASQUERADE


现在再从red ping 8.8.8.8:


$ ip netns exec red ping  8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=102 time=3.10 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=102 time=2.91 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=102 time=3.03 ms


现在我们从我们的namespace连到公网了,但是怎么走公网连到我们的namespace呢?本文中使用的主机是AWS的EC2,它的公网ip为3.113.17.15,有账号的同学可以去AWS或者阿里云开一台带公网IP的机器实践下公网连namespace。


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux查看ip_06


假设现在主机A直接走公网ping到这EC2里面的red namespace。显然直接ping 192.168.15.1是行不通的,因为 192.168.15.1不是一个可路由的公网地址。那这该怎么办呢?可以使用端口转发(port-forwarding,DNAT),比如以下命令可以将主机从8083端口收到的请求,发送到192.168.15.1的80端口。所以我们可以通过访问3.113.17.15:8083来访问192.168.15.1:80也就是red的80端口。


$ iptables -t nat -A PREROUTING -p tcp --dport 8083 -j DNAT --to-destination 192.168.15.1:80


但由于目前red里面的里面没有任何应用,所以即使转发到80端口也没法验证。端口转发的验证将在docker network中继续探讨。

到目前为止,我们的网络拓扑图为:


ipconfig查看不到docker的虚拟网卡 查看docker使用的网卡_linux 查看路由_02

namespace能够访问公网的网络拓扑图


总结

本文从network namespace出发,使用veth pair,bridge,NAT等虚拟网络设备和技术在一个Linux主机中搭建了一个虚拟网络,并实现了如下效果:

  • 虚拟网络的设备直接可以互相访问;
  • 虚拟网络的设备与主机之间可以互相访问;
  • 虚拟网络的设备可以访问公网。

如果你也通篇跟着做下来的话,相信你现在对Linux的虚拟网络也有一定基础了,下一篇我会继续手撕docker网络,从docker网络中剥离出docker创建的namespace,veth pair和bridge,敬请期待。


附录:

本文中执行的主要命令总结如下:


# 注意:需要在root权限下运行以下命令
# tips: “ip -n 命名空间 ip系列命令” 相当于 “ip netns exec 命令空间 ip ip系列命令”

# 1. blue与red之间通过veth pair连接
ip link add veth-red type veth peer name veth-blue
ip netns add blue
ip netns add red
ip link set veth-blue netns blue
ip link set veth-red netns red
ip -n red addr add 192.168.15.1/24 dev veth-red
ip -n blue addr add 192.168.15.2/24 dev veth-blue
ip -n red link set veth-red up
ip -n blue link set veth-blue up
# 验证命令:
ip netns exec red ping -c 3 192.168.15.2

# 2. blue与red之间通过bridge连接
ip -n red link del veth-red
ip link add vbridge-0 type bridge
ip link set dev vbridge-0 up
ip link add veth-red type veth peer name veth-red-br
ip link add veth-blue type veth peer name veth-blue-br
ip link set veth-red netns red
ip link set veth-red-br master vbridge-0
ip link set veth-blue netns blue
ip link set veth-blue-br master vbridge-0
ip -n red addr add 192.168.15.1/24 dev veth-red
ip -n blue addr add 192.168.15.2/24 dev veth-blue
ip -n blue link set veth-blue up
ip link set dev veth-blue-br up
ip -n red link set veth-red up
ip link set dev veth-red-br up
iptables -A FORWARD -o vbridge-0 -j ACCEPT
iptables -A FORWARD -i vbridge-0  -j ACCEPT
# 验证命令:
ip netns exec red ping -c 3 192.168.15.2

# 3. namespace与host相互连接
ip addr add 192.168.15.5/24 dev vbridge-0
ip netns exec red ip route add default via 192.168.15.5
ip netns exec blue ip route add default via 192.168.15.5
# 验证命令(将172.31.47.15换成主机ip):
ip netns exec blue ping -c 3 172.31.47.15
ip netns exec red ping -c 3 172.31.47.15

# 4. namespace与公网连接
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 192.168.15.0/24 -j MASQUERADE
# 验证命令:
ip netns exec blue ping -c 3 8.8.8.8
ip netns exec red ping -c 3 8.8.8.8


参考文章:

  1. linux 网络虚拟化: network namespace 简介
  2. 理解Docker容器网络之Linux Network Namespace
  3. 探究!一个数据包在网络中的心路历程
  4. Linux 上的基础网络设备详解
  5. 云计算底层技术-netfilter框架研究