Linux Network Namespace

921 阅读4分钟

1. network namespace

namespace 将全局系统资源包装在一个抽象中,使 namespace 中的进程觉得它们拥有自己的全局资源的独立实例。对全局资源的更改对作为 namespace 成员的其他进程是可见的,但对其他进程是不可见的。namespace 的一个用途是实现容器。Linux 内核共定义了 8 种 namespace,详情请参考 namespaces(7) — Linux manual page

以下主要是对《Kubernetes网络权威指南》的学习笔记。

network namespace 和其他 namespace 需要自己写 C 语言代码调用系统API才能创建不同,network namespace 的增删改查功能已经集成到 Linux的 ip 工具的 netns 子命令中,这极大的降低了我们学习的门槛。

使用 ip netns add 命令创建一个名为 netns1 的 network namespace。

$ ip add netns netns1

创建完成后,系统会在 /var/run/netns 路径下面生成一个挂载点。挂载点的作用一方面是方便对 namespace 的管理,另一方面是使 namespace 即使没有进程运行也能继续存在。

可以使用 ip netns list 查看系统有哪些 namespace。

$ ip netns list
​
netns1

想删除network namespace,可以通过以下命令实现:

ip netns delete netns1

注意,上面这条命令实际上并没有删除 netns1 这个network namespace,它只是移除了这个network namespace对应的挂载点。只要里面还有进程运行着,network namespace便会一直存在。

使用 ip netns exec 进入 network namespace,查询和配置网络设备。我们进入这个 netns1 这个namespace 查询网卡信息,目前我们没有进行任何网络配置,因此只有一块系统默认的回环设备。如下所示,

$ ip netns exec netns1 ip link list
​
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

network namespace 默认的 lo 设备状态还是 DOWN 的,因此,当尝试访问本地回环地址时,网络是不通的。

$ ip netns exec netns1 ping 127.0.0.1
​
connect: 网络不可达

所以我们进入 netns1 将 lo 设备的状态设置为 UP 再次尝试 ping 127.0.0.1,发现是通的。

$ ip netns exec netns1 ip link set dev lo up
$ ip netns exec netns1 ip link list
​
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
​
$ ip netns exec netns1 ping 127.0.0.1
   
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms

但是,仅有一个本地回环设备 namespace 是没法与外界通信的。那么这个时候我们一种名叫 Veth Pair 的虚拟设备。

Veth Pair 设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出现的。并且,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的 network namespace 里。

下面我们创建一对 veth pair 虚拟设备,并把两端分别命名为 veth0、veth1。它在主机上表现为两张网卡,我们可以通过 ip link list 命令来查看:

## 创建 veth pair,名字分别是 veth0、veth1
$ ip link add veth0 type veth peer name veth1
$ ip link list
​
106: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 1e:7d:54:97:d0:8f brd ff:ff:ff:ff:ff:ff
107: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether ae:d7:4e:7a:1f:00 brd ff:ff:ff:ff:ff:ff

新创建的 veth pair 设备的默认 mtu 是1500,设备初始状态是 DOWN。我们同样可以使用 ip link 命令将这两块网卡的状态设置为 UP。并给 veth pair设备配置 IP 地址,命令如下:

# 设置网卡状态
$ ip link set dev veth0 up
$ ip link set dev veth1 up
​
# 配置网卡ip
$ ifconfig veth0 10.1.1.1/24
$ ifconfig veth1 10.1.1.2/24

然后我们将 veth1 移动到 netns1 network namesake 中,

$ ip link set veth1 netns netns1
$ ip netns exec netns1 ip link list
​
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
106: veth1@if107: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 1e:7d:54:97:d0:8f brd ff:ff:ff:ff:ff:ff link-netnsid 0

这样我们就可以从 namespace 内的 veth1,ping 通到主机的上的 veth0了。

$ ip netns exec netns1 ping 10.1.1.1

PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.037 ms
64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.040 ms

$ ip netns exec netns1 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.1.1.0        0.0.0.0         255.255.255.0   U     0      0        0 veth1

但是现在 netns1 namesapce 依然不能发包到 internet,想连接 internet,有若干解决方法。例如,可以在主机的根 network namespace 创建一个 Linux 网桥并绑定 veth pair 的一端到网桥上;也可以通过适当的 NAT(网络地址转换)规则并辅以 Linux的 IP 转发功能(配置net.ipv4.ip_forward=1)。详情可以参阅 Networking overview

非 root 进程被分配到 network namespace 后只能访问和配置已经存在于该 network namespace的设备。root 进程可以在 network namespace 里创建新的网络设备。

network namespace 里的 root 进程还能把本 network namespace 的虚拟网络设备分配到其他network namespace——这个操作路径可以从主机的根 network namespace 到用户自定义network namespace,反之亦可。请看下面这条命令:

ip netns exec netns1 ip link set veth1 netns1
  1. ip netns exec netns1进入 netns1 network namespace。

  2. ip link set veth1 netns 1 把 netns1 network namespace下的 veth1 网卡挪到PID为 1 的进程(即init进程)所在的 network namespace。

通常,init 进程都在主机的根network namespace下运行,因此上面这条命令其实就是把 veth1 从 netns1 network namespace移动到系统根 network namespace。

对 namespace 的 root 用户而言,他们都可以把其 namespace 里的虚拟网络设备移动到其他networknamespace,甚至包括主机根 network namespace!这就带来了潜在的安全风险。如果用户希望屏蔽这一行为,则需要结合PID namespace 和 Mount namespace 的隔离特性做到 network namespace 之间的完全不可达。

2. namespace api

namespace api 请参考 namespaces(7) — Linux manual page

  1. clone() 系统调用创建一个新进程。如果调用的 flags 参数指定了上面列出的一个或多个CLONE_NEW* 标志,那么将为每个标志创建新的 namespace,并使子进程成为这些 namespace 的成员。

  2. setns() 向 namespace 添加进程。

  3. unshare() 系统调用将调用进程移动到一个新的 namespace。如果调用的 flags 参数指定了上面列出的一个或多个 CLONE_NEW* 标志,那么将为每个标志创建新的 namespace,并使调用进程成为这些 namespace 的成员。