0%

Linux Network Namespace

Linux命名空间

Linux命名空间是一种资源隔离机制,Docker for Linux就是主要利用Linux命名空间实现资源隔离的。

目前有八种Linux命名空间。下表列出了这些命名空间及其隔离的资源类型,Flag是用于一些系统调用的标志,Page指示如何找到相关命名空间的详细文档。

Namespace Flag Page Isolates
Cgroup CLONE_NEWCGROUP cgroup_namespaces(7) Cgroup root directory
IPC CLONE_NEWIPC ipc_namespaces(7) System V IPC,
POSIX message queues
Network CLONE_NEWNET network_namespaces(7) Network devices,
stacks, ports, etc.
Mount CLONE_NEWNS mount_namespaces(7) Mount points
PID CLONE_NEWPID pid_namespaces(7) Process IDs
Time CLONE_NEWTIME time_namespaces(7) Boot and monotonic clocks
User CLONE_NEWUSER user_namespaces(7) User and group IDs
UTS CLONE_NEWUTS uts_namespaces(7) Hostname and NIS domain name

网络命名空间与veth

本文重点探究Linux网络命名空间,Linux网络命名空间可以隔离网络设备、套接字(端口号)、网络协议栈、路由表、防火墙规则等网络资源。也可以为每个网络命名空间提供不同的DNS配置,比如假如存在一个名为 nstest 的网络命名空间,则对于nstest, DNS解析将会首先参照 /etc/netns/nstest/resolv.conf 中的DNS配置,其次才是全局DNS配置 /etc/resolv.conf

虚拟网络设备 veth 可用于连接两个网络命名空间,可以为隔离于网络命名空间中的进程提供访问外部网络的能力。
虚拟网络设备 veth 总是成对出现的,两个 veth 一端互相连接在一起,另一端与网络协议栈相连,一个 veth 收到的数据包会立即被转发到与之相连的 veth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+----------------------------------------------------------------+
| |
| +------------------------------------------------+ |
| | Newwork Protocol Stack | |
| +------------------------------------------------+ |
| ↑ ↑ ↑ |
|..............|...............|...............|.................|
| ↓ ↓ ↓ |
| +----------+ +-----------+ +-----------+ |
| | eth0 | | veth0 | | veth1 | |
| +----------+ +-----------+ +-----------+ |
|192.168.1.11 ↑ ↑ ↑ |
| | +---------------+ |
| | 192.168.2.11 192.168.2.1 |
+--------------|-------------------------------------------------+

Physical Network

网络命名空间的介绍参考network_namespaces(7),网络命名空间的的创建、删除与操纵,参考ip-netns(8)veth的介绍参考veth(4)veth of Linux Virtual Network Device

网络命名空间相关操作

可以用 ip netns 进行网络命名空间的创建、删除、在特定网络命名空间执行进程等操作,用 ip link 可以进行在特定命名空间创建接口,为接口变更命名空间等操作。下面给出几个示例:

  • 创建网络命名空间 nstest
    1
    ip netns add nstest
  • 删除命名空间 nstest
    1
    ip netns del nstest
  • 在网络命名空间 nstest 中执行命令
    1
    ip netns exec nstest COMMAND
  • 创建虚拟网络接口对 veth0s0veth0s1
    1
    ip link add veth0s0 type veth peer veth0s1
  • 将虚拟网络接口 veth0s1 移动至网络命名空间 nstest
    1
    2
    3
    # 一旦将网络接口移动至 nstest ,就无法在默认网络命名空间中看到该网络接口
    # 因为一个网络接口只能属于一个网络命名空间,物理网络接口也是如此!!!
    ip link set veth0s1 netns nstest
  • 将虚拟网络接口 veth0s1 移动至默认网络命名空间
    1
    2
    3
    4
    # 由于网络接口 veth0s1 属于 nstest ,所以需要在该网络命名空间中操作
    # 因为各个网络命名空间中的接口时相互隔离的
    # 其中 1 指的是默认网络命名空间的 id
    ip netns exec nstest ip link set veth0s1 netns 1

实验测试

实验一:利用veth连通两个网络命名空间

大致流程为:

  • 创建新的网络命名空间nstest
  • 利用 veth 虚拟网络接口对连接默认网络命名空间和 nstest
  • veth 配置IP进行测试

配置命令如下:

1
2
3
4
5
6
7
ip netns add nstest
ip link add veth0s0 type veth peer veth0s1
ip link set veth0s1 netns nstest
ip link set veth0s0 up
ip addr add 192.168.11.100/24 dev veth0s0
ip netns exec nstest ip link set veth0s1 up
ip netns exec nstest ip addr add 192.168.11.101/24 dev veth0s1

配置好之后,可以使用 ip addr 确认一下网络情况:

下图是我电脑上的情况,可以看到 nstest 只存在 loveth0s1,它与默认网络命名空间的接口是互相隔离的。

然后就可以实现默认网络命名空间与 nstest 之间的通信。

可以通过ping测试:

1
2
ping 192.168.11.101
ip netns exec nstest ping 192.168.11.100

实验一拓展配置

首先介绍一下如何捕获经过网络接口的数据包,在Linux上可以使用 tcpdump 捕获数据包,如果只是想要捕获特定接口的数据包,可以使用 tcpdump -lni interface_name

下面是实验一进行ping测试的抓包截图:

现在nstest和默认网络命名空间可以互相ping通了,那么nstest能否访问外部网络呢?结果是不可以。

首先,如果想要访问 baidu.com,需要配置好网关,并且,由于我们希望访问域名,也就需要配置好DNS解析服务。
那么,新建的网络命名空间中的网关和DNS配置又是怎么的呢?可以通过 ip route 查看网关。而DNS配置仍是通过 /etc/resolv.conf 文件控制,如果仅仅使用 /etc/resolv.conf 控制DNS,那么理论上 nstest 中的DNS配置是与默认网络命名空间相同的,不过如今大部分Linux发行版默认情况下都默认安装了网络服务相关守护进程,Ubuntu Server 20.04中使用的是netplan服务,所以说了这么多,我也不太清楚nstest中默认的DNS配置是怎么的,可能与网络接口的IP地址配置有关系,如果采用DHCP,应该会根据DHCP服务器返回的结果设置DNS,具体可以查看netplan配置文件。

首先查看 nstest 的网关配置,下图顺便展示了默认网络命名空间的路由信息,用于突出网络命名空间之间路由表的隔离性:

可以看到, nstest 中未配置默认路由,所以自然无法访问 baidu.com 了,下面为它配置默认路由,这里我们将 veth0s0 的IP地址 192.168.11.100 设为 nstest 中的默认网关。

1
ip netns exec nstest ip route add default via 192.168.11.100

然后检查 nstest 的路由表就会看到默认路由,那么我们现在ping 114.114.114.114baidu.com,看能否通过 tcpdump 抓到包。

可以看到发往 114.114.114.114ICMP echo request,但是没有回应,也有发往 127.0.0.53 的DNS查询,同样没有回应。(DNS查询应该和DNS配置有关系,这里不深究了)

这里要关注的一点是,veth0s0 收到了来自 nstestveth0s1 的数据包,如果进一步配置NAT,veth0s1 就可以访问外部网络了。其实使用桥接网络模式的docker容器就是利用类似的技术访问外部网络的,只不过docekr容器并非直接配置NAT,而是将对端veth接入了docker网桥,再进一步通过网桥访问外部网络。

接下来,使用iptables为配置NAT:

1
2
3
4
5
IFACE=ens33 # 可以访问外网的接口名称
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 192.168.11.0/24 -j MASQUERADE
iptables -A FORWARD -i ${IFACE} -o veth0s0 -j ACCEPT
iptables -A FORWARD -o ${IFACE} -i veth0s0 -j ACCEPT

然后 nstest 就可以访问外部网络了。

参考资源

namespaces(7) — Linux manual page
network_namespaces(7) — Linux manual page
ip-netns(8) — Linux manual page
veth(4) — Linux manual page
veth of Linux Virtual Network Device
Setup a network namespace with Internet access
A tcpdump Tutorial with Examples — 50 Ways to Isolate Traffic