本来我准备一篇文章写完Linux虚拟网络和Docker网络原理的,写着写着发现光Linux虚拟网络都是很长的一篇了,所以这篇准备专注于Linux虚拟网络。这篇文章主要从实践角度介绍了在一台Linux主机上,如何实用namespace,veth pair,bridge和NAT等技术搭建出来一个虚拟网络。我们知道docker网络就是基于linux这些虚拟网络技术实现的,学习这些技术对理解docker网络有很大的帮助。跟着我的步骤一步步做,最后我们会做出如图所示的一个网络拓扑结构:
在学习Linux虚拟网络之前,我们得知道现实中的网络是怎么工作的。我的网络基础非常一般,仅仅学习过HTTP协议以及TCP/IP协议的理论知识,缺乏实践知识,对实用场景下各个协议如何协作,工作细节方面知之甚少。在这里我推荐几个我学习过程中觉得很赞的视频和文章供大家参考,如果你也和我一样基础薄弱的话,可以先打打基础。
- 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
命令的执行过程可以总结如下:
- 在red的命名空间中,根据
ping
的目标地址192.168.15.2
,查询red的路由表得知,192.168.15.2
就在本地网络中,无需路由; - red先在自己的arp表中查询
192.168.15.2
的MAC地址,发现没有记录(新建的命名空间arp表都是空的),于是发起ARP广播到本地网络,询问192.168.15.2
的MAC地址; - blue收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为blue的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存blue的MAC地址,以方便以后继续访问;
- blue从veth-blue收到red发来的数据包后,也在自己的arp中缓存red的MAC地址,并且通过veth-blue给red发送ping的response。
到此为止我们的网络拓扑结构为:
red与blue的网络拓扑图
3. 使用brigde连接不同的namespace
在上文中,我们用veth pair
实现了2个namespace之间的连接。但如果2个以上的namespace 需要互相连通的,用veth pair
就有点力不从心了。在现实网络中,如果需要连通多个相同网络中的设备,我们会用到交换机,相应的,linux也提供虚拟的交换机也就是bridge 。我们可以使用bridge作为中转站来连通多个相同网络的namespace,打造出一个虚拟的LAN。首先放出我们将要实现的网络拓扑图:
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
命令的执行过程可以总结如下:
- 在red的命名空间中,根据ping的目标地址192.168.15.2,查询red的路由表得知,192.168.15.2就在本地网络中,无需路由;
- red先在自己的arp表中查询192.168.15.2的MAC地址,发现没有记录,于是发起ARP广播到本地网络,询问192.168.15.2的MAC地址;
- blue通过vbridge-0收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为blue的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存blue的MAC地址,以方便以后继续访问;
- vbridge-0从veth-red-br收到数据包后,转发(FORWARD)给veth-blue-br;
- 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
命令的执行过程可以总结如下:
- 在red的命名空间中,根据ping的目标地址172.31.47.15,查询red的路由表得知,gateway为192.168.15.5;
- red先在自己的arp表中查询192.168.15.5的MAC地址,发现没有记录,于是发起ARP广播到本地网络,询问192.168.15.5的MAC地址;
- vbridge-0收到red发起的ARP 询问,回复自己的MAC地址。red收到回复后,填上目的MAC地址为vbridge-0的MAC地址,将数据包从veth-red网卡发出,与此同时,red还在自己的arp表中缓存vbridge-0的MAC地址,以方便以后继续访问;
- vbridge-0从从veth-red-br收到数据包后再查询路由表(也就是主机的路由表),发现本机处于172.31.32.0/20网络中,因此发往172.31.47.15的数据包无需再路由,路由表显示的Iface为eth0,于是vbridge-0将数据包递给eth0;
- 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
到目前为止,我们的拓扑图为:
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。
假设现在主机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中继续探讨。
到目前为止,我们的网络拓扑图为:
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
参考文章:
- linux 网络虚拟化: network namespace 简介
- 理解Docker容器网络之Linux Network Namespace
- 探究!一个数据包在网络中的心路历程
- Linux 上的基础网络设备详解
- 云计算底层技术-netfilter框架研究