一、网络命名空间
为了支持网络协议栈的多个实例,在Linux中引入了网络命名空间,这些独立的协议栈被隔离到不同的命名空间。处于不同空间中的网络协议中是完全隔离的,彼此之前无法通信。通过网络隔离就能在一个宿主机上虚拟出多个不同网络环境。Docker就是利用了网络的命名空间特性,实现不同容器之间的网络隔离。
在Linux命名空间中可以有自己独立的路由表及独立的iptables设置提供给包转发、NAT及IP包过滤等功能。 为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建套接字必须属于某个命名空间,套接字操作也必须在某个命名空间中进行。同样,网络设备也必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现命名空间之前移动。
二、Linux操作系统实现网络命名空间
1.网名命名空间实现
Linux的网络协议栈十分复杂,为了支持独立协议,相关的全局变量修改为协议栈私有。最好的办法就是让这些全局变量称为Net Namesapce变量的成员,然后为协议栈的函数调用加入一个Namespace参数。这个就是Linux实现网络命名空间的核心。
同时,为了保证已经开发的应用程序及内核代码的兼容性,内核代码隐式的使用了命名空间的变量。程序如果没有对命名空间有特殊的要求就不用编写额外代码,网络命名空间对应用程序而言是透明的。
在建立新的网络命名空间,并将某个进程关联到这个网络命名空间后,就出现了下图的内核数据结构,所有网络栈变量都被放入网络命名空间的数据结构中。这个网络命名空间是其进程组私有的,和其它进程组不冲突。
在新生成的私有命名空间中只有回环设备(名为"lo"且是停止状态),其它设备默认都不存在,如果需要,则要一一手工建立。Docker容器中各类网络协议栈都是Docker Daemon在其实时自动创建和配置的。
所有的网络设备(物理的或虚拟接口、桥等在内核都叫作Net Device)都只能属于一个命名空间。当然,物理设备(连接实际硬件设备的设备)通常只能关联到root这个命名空间。虚拟的网络设备(虚拟的以太网接口或者虚拟网卡对)则可以被创建并关联到一个给定的命名空间中,而且可以在这些命名空间之间移动。
由于网络设备代表的是一个独立的协议栈,所以他们之间是相互隔离的,彼此之前无法通信,在协议栈内部看不到对方,为了打破这种限制,让处于不同命名空间的网络相互通信,设备和外部网络进行通信,应用Veth设备对实现。Veth设备对的一个重要作用就是打通互相看不到的协议栈之前的壁垒,他就像一条管子,一端连接着这个网络命名空间的协议栈,另一端连接着另一个网络命名空间的协议栈。所以两个命名空间之间通信,必须有一个Veth设备对。
2. 网名命名空间的操作
ip netns add <name> #创建一个网络命名空间
ip netns exec <name> <command> #在命名空间中执行命令
ip netns exec <name> bash #先通过bash命令进入内部的shell界面,然后执行指令
3. Veth设备对
引入Veth设备对是为了在不同的网络命名空间之间通信,利用它可以之间将两个网络命名空间连接起来。由于要连接两个网络命名空间,所以Veth设备都是成对出现,所以很像一对以太网卡,并且中间有一根直连的网线,既然是一对以太网卡,将其中一端称为另一端的peer。
在Veth设备的一端发送数据时,他将数据之间发送给另一端,并触发另一端操作。
3.1 Veth设备对的操作
ip link add veth222 type veth peer name veth333 //创建veth设备对
ip link show #查看所有网络接口
有两个设备生成,一个是veth222,它的peer是veth333,现在两个设备都在自己的命名空间,如果将verh看作有两个头的网线,那么我们将另一个头甩给另一个命名空间:
ip link set veth333 netns netns1 #将设备veth333 设置的网络命名空间netns1
ip netns exec netns1 ip addr add 10.1.1.1/24 dev veth333
ip addr add 10.1.1.1/24 dev veth222
ip netns exec netns1 ip link set dev veth333 up #重启veth333
ip link set dev veth222 up #重启veth222
查看 veth属于哪个namespace可以使用 ethtool 工具
ethtool -S veth222
NIC statistics:
peer_ifindex: 1792 #另一端接口设备序列号是5,在查看序列号5代表的设备
ip link | grep 1792
# 1792: veth333@veth222: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
三、网桥
Linux可以支持多个不同网络,他们之间能够相互通信,如何将这些网络连接起来实现各网络主机的相互通信呢?使用网桥。网桥是一个二层虚拟网络设备,把若干个网络接口连接起来,以使得网络接口之前的报文能够相互转发。网桥能够解析收发的报文,读取目标MAC地址的信息,和自己记录的MAC表结合,来决策报文转发目标网络接口。为了实现这些功能,网桥会学习源MAC地址(二层网桥转发的依据就是MAC地址)。在转发报文时,网桥只需要想向特定的网口进行转发,来避免不必要的网络交互。如果它遇到一个从未学习到的地址,就无法知道这个报文应该向哪个接口转发,将报文广播给所有的网络接口(报文来源接口除外)。
在实际网络中,网络拓扑不可能是永久不变的。如果设备被移动到另一个端口上,却没有发送任何数据,网桥设备就无法感知到这个变化,网桥还会向原来的端口转发数据包,这种情况下数据就会丢失。所以网桥还要向原来的端口转发数据包,在这种情况下数据就会丢失。所以网桥还要对学习到的MAC地址表加上超时时间(默认5min)。如果网桥收到对应端口MAC地址回发的包,则重置超时时间,否则过了超时时间后,就认为设备已经不在那个端口上了,它会重新广播发送。
在Linux内部的网络栈里实现的网桥设备,和上面描述的相同。过去Linux主机一般只有一块网卡,现在多网卡的机器越来越多,而且很多虚拟网络设备的存在,所以Linux的网桥提供了在这些设备互相转发数据的二层设备。
Linux内核支持网口的桥接(目前只支持以太网接口),但是与单纯的交互就不同,单纯交换机只是一个二层设备,对于收到的报文,要么转发,要么丢弃。运行着Linux内核的机器本身就是一台主机,有可能是网络报文的目的地,其收到的报文除了转发和丢弃,还有可能被送到网络协议栈的上层(网络层),从而被自己(这台主机本身的协议栈)消化,所以既可以把网桥看作一个二层设备也可以看作一台三层设备。
1. Linux网桥实现
Linux内核是通过一个虚拟网桥设备(Net Device)来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将他们桥接起来。这种Net Device网桥和普通的设备不同,最明显的一个腾讯就是他还可以有一个IP地址。
如上图,网桥设备br0绑定了eth0和eth1。对于网络协议栈 上层来说,只看br0就行。因为桥接是在数据链路层实现的,上层不需关心桥接的细节,所以协议栈上层需要发送的报文被送到br0,网桥设备处理代码判断该报文被转发到eth0还是eth1,或者两者皆转发;反过来,从eth0或从eth1接收到的报文被提交给网桥处理代码,在这里判断报文应该被转发、丢弃还是被提交到协议栈上层。
而有时eth0和eth1也可能会被作为报文源地址或目的地址,直接参与报文的发送与接收,从而绕过网桥。
2.网桥的常用操作命令
Docker自动完成对网桥的创建和维护。
btctl addbr xxxx #新增一个网桥设备
为网桥增加网口,在Linux中网口其实就是一个物理网卡。将物理网卡和网桥连接起来。
brctl addif xxx ethx #网桥上新增加网口
ifconfig ethx 0.0.0.
网桥的物理网卡作为一个网口,由于在链路层工作,就不再需要IP地址,这样上面的IP地址自然失效:
给网桥配置一个IP地址
ifconfig brxxx xxx.xxx.xxx.xxx
这样网桥就有了一个IP地址,而连接到上面的网卡就是一个纯链路层设备