隔离你的网络环境:Linux Network Namespace 入门
你可能想知道,在同一个 Linux 系统里,不同的程序能不能拥有各自独立的网络设置?比如,让一个程序只能访问特定网站,而另一个程序可以自由访问所有网络资源,并且它们之间互不干扰。这正是 Linux Network Namespace 的核心功能。
什么是 Network Namespace?
在 Linux 系统中,默认情况下,所有网络相关的配置,包括网卡、IP 地址、路由规则、防火墙设置等,都属于一个叫做 “init” Network Namespace 的默认环境。当你运行程序时,它们通常在这个默认空间里,共享着相同的网络配置。
Network Namespace 允许你创建完全独立且隔离的网络环境。这意味着每个 Namespace 都拥有:
- 独立的网络接口: 比如
eth0
、lo
等,在一个 Namespace 里看到的网卡,在另一个 Namespace 里是不可见的。 - 独立的 IP 地址: 不同 Namespace 可以使用相同的 IP 地址,因为它们互不影响。
- 独立的路由表: 决定数据包如何传输到目的地。
- 独立的 ARP 表: 用于将 IP 地址解析为物理 MAC 地址。
- 独立的防火墙规则 (iptables): 每个 Namespace 都能有自己一套防火墙策略,彼此独立。
为什么需要 Network Namespace?
Network Namespace 在很多场景下都非常有用:
- 容器技术: Docker、Kubernetes 等工具正是利用 Network Namespace 为每个容器提供了隔离的网络环境。
- 网络功能虚拟化 (NFV): 将路由器、防火墙等网络设备以软件形式运行,每个都在独立的 Namespace 中。
- 测试与开发: 在不影响主系统网络的情况下,安全地测试新的网络配置或应用程序。
- 安全隔离: 限制应用程序的网络访问范围,提高系统安全性。
网络配置实践解析
现在,我们一步步执行命令,了解如何建立一个独立网络环境并使其与主系统通信的。
1. 创建并进入一个独立网络环境
[root@localhost ~]# ip netns ls
这个命令用来列出当前系统中的所有 Network Namespace。刚开始执行时,可能没有输出,因为你可能只在使用默认的主 Namespace。
[root@localhost ~]# ip netns add game
这条命令创建了一个名为 “game” 的新 Network Namespace。现在,系统有了一个全新的、独立的网络配置区域。
[root@localhost ~]# ip netns ls
game
再次列出 Namespace,你会看到 “game” 已经出现在列表中。
[root@localhost ~]# ip netns exec game bash
ip netns exec
命令的作用是在指定的 Network Namespace 中执行命令。这里,我们告诉系统在 “game” Namespace 里启动一个新的 bash
会话。
此刻,你所处的是一个完全隔离的网络环境。在这个 bash
会话里,你看到和操作的所有网络设备、IP 地址、路由表等,都只属于 “game” Network Namespace。
[root@localhost ~]# ip link
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
进入 “game” Namespace 后执行 ip link
,你会发现只有一块名为 lo
(回环网卡) 的网卡。这是因为每个新 Namespace 默认只包含一个回环网卡,它是完全独立的,看不到主 Namespace 的其他网卡,比如 ens33
。
[root@localhost ~]# exit
exit
输入 exit
命令,你将退出 “game” Namespace 内的 bash
会话,返回到主 Namespace。
[root@localhost ~]# ip link
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
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:fd:5b:bb brd ff:ff:ff:ff:ff:ff
... (省略部分输出)
回到主 Namespace 后再次执行 ip link
,你会看到熟悉的 ens33
、virbr0
等网卡,这表明你已回到主系统的网络环境。
[root@localhost ~]# ip netns exec game 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
这个命令再次确认了 “game” Namespace 中只有 lo
网卡。
[root@localhost ~]# ip netns exec game route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
在 “game” Namespace 中查看路由表,发现它是空的。这意味着 “game” Namespace 目前不知道如何将数据包发送到它自己网络之外的目的地。
[root@localhost ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.2 0.0.0.0 UG 100 0 0 ens33
192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 virbr0
这是主 Namespace 的路由表,包含连接到互联网和本地网络的路由信息。
[root@localhost ~]# ip netns exec game iptables -nL
... (输出为空)
[root@localhost ~]# ip netns exec game iptables -nL -t nat
... (输出为空)
这些命令显示了 “game” Namespace 中的防火墙 (iptables) 规则,它们也是空的,进一步证明了 Namespace 的隔离性。
2. 连接两个网络环境:使用 veth 网卡对
现在,“game” Namespace 是一个独立但无法与外界通信的网络环境。为了让 “game” Namespace 能够与主 Namespace 通信,我们需要建立一个连接。这个连接就是通过 veth (Virtual Ethernet) pair 实现的。
一个 veth pair 是一对虚拟网卡。它们像一根虚拟的网线,一端在主 Namespace,另一端在 “game” Namespace。任何通过一端发送的数据包都会从另一端接收到。
[root@localhost ~]# ip link add veth1_out type veth peer name veth1_in
这条命令创建了一个 veth pair。
veth1_out
:是这个 veth pair 的一端,它将保留在主 Namespace。veth1_in
:是这个 veth pair 的另一端,我们计划将其移动到 “game” Namespace。
[root@localhost ~]# ip -br a
...
veth1_in@veth1_out DOWN
veth1_out@veth1_in DOWN
这时,你会在主 Namespace 看到新创建的 veth1_in
和 veth1_out
。它们的名字后面带有 @
和对方的名字,表示它们是相互连接的。
[root@localhost ~]# ip link set veth1_in netns game
这是关键一步!它将 veth1_in
这块网卡从主 Namespace 移动到 “game” Namespace。现在,veth1_out
仍在主 Namespace,而 veth1_in
已经进入 “game” Namespace。
[root@localhost ~]# ip link
...
6: veth1_out@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 46:dc:ea:58:9f:5a brd ff:ff:ff:ff:ff:ff link-netnsid 0
在主 Namespace 中,你现在只能看到 veth1_out
了。
[root@localhost ~]# ip netns exec game ip link ls
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
5: veth1_in@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 46:3a:95:ba:87:ae brd ff:ff:ff:ff:ff:ff link-netnsid 0
在 “game” Namespace 中,你现在不仅有 lo
网卡,还看到了 veth1_in
网卡。这表明 veth pair 的两端已经分别位于不同的 Namespace 中。
3. 配置 IP 地址并启用网卡
为了让 veth
网卡能够传输数据,我们需要为它的两端都分配 IP 地址,并将其激活。
[root@localhost ~]# ip netns exec game ip addr add 192.168.110.2/24 dev veth1_in
这条命令在 “game” Namespace 中,给 veth1_in
网卡分配了 IP 地址 192.168.110.2
,子网掩码为 /24
。
[root@localhost ~]# ip netns exec game ip link set veth1_in up
[root@localhost ~]# ip netns exec game ip link set lo up
这两条命令分别激活了 “game” Namespace 中的 veth1_in
网卡和 lo
网卡。只有激活的网卡才能发送和接收数据。
[root@localhost ~]# ip addr add 192.168.110.1/24 dev veth1_out
这条命令在主 Namespace 中,给 veth1_out
网卡分配了 IP 地址 192.168.110.1
,子网掩码为 /24
。
[root@localhost ~]# ip link set veth1_out up
激活主 Namespace 中的 veth1_out
网卡。
现在,veth1_out
(192.168.110.1) 在主 Namespace,veth1_in
(192.168.110.2) 在 “game” Namespace,它们位于同一个 192.168.110.0/24
子网,可以通过这对虚拟网卡直接通信了。
4. 测试网络连通性
[root@localhost ~]# ping -c 5 192.168.110.2
这条命令在主 Namespace 中,尝试 ping “game” Namespace 中的 192.168.110.2
。如果配置正确,你会看到 ping 成功,这表示主 Namespace 可以通过 veth1_out
与 veth1_in
通信。
[root@localhost ~]# ip netns exec game ping -c 4 192.168.110.1
这条命令在 “game” Namespace 中,尝试 ping 主 Namespace 中的 192.168.110.1
。同样,如果成功,说明 “game” Namespace 可以通过 veth1_in
与 veth1_out
通信。
5. 让独立网络环境连接外部网络:配置路由和 IP 转发
尽管 “game” Namespace 现在可以和主 Namespace 通信了,但它还不能直接访问互联网或主 Namespace 之外的其他网络。这是因为 “game” Namespace 的路由表是空的。我们需要告诉它,如果想访问它不明确目的地的网络,应该把数据包发给谁。
[root@localhost ~]# ip netns exec game ip route add default via 192.168.110.1
这条命令在 “game” Namespace 中添加了一条默认路由。它的作用是:“如果 ‘game’ Namespace 要发送数据包到一个它不明确目的地的网络,那么就把这个数据包发送给 192.168.110.1
(也就是主 Namespace 中的 veth1_out
)。”
现在,veth1_out
就像一个网关,负责将 “game” Namespace 的数据包转发出去。
[root@localhost ~]# sysctl -p
net.ipv4.ip_forward = 1
[root@localhost ~]# cat /etc/sysctl.conf
# ...
net.ipv4.ip_forward=1
net.ipv4.ip_forward
是一个内核参数,它控制着 Linux 系统是否允许进行 IP 转发。默认情况下,这个功能是关闭的。当你将其设置为 1
时,就允许你的 Linux 主机充当路由器,将从一个网络接口收到的数据包转发到另一个网络接口。
在这里,我们开启了主 Namespace 的 IP 转发功能。这意味着,当 “game” Namespace 的数据包通过 veth1_in
发送到主 Namespace 的 veth1_out
时,主 Namespace 会检查自己的路由表,并将这些数据包转发到正确的目的地(比如互联网)。
[root@localhost ~]# ip netns exec game ping -c 4 192.168.1.148
PING 192.168.1.148 (192.168.1.148) 56(84) bytes of data.
... (ping 成功)
最后,在 “game” Namespace 中 ping 主 Namespace 的 IP 地址 192.168.1.148
(你的 ens33
网卡 IP)。这次 ping 成功了!
这是因为:
- “game” Namespace 中的
ping
命令会向192.168.1.148
发送数据包。 - “game” Namespace 发现
192.168.1.148
不在192.168.110.0/24
这个子网内,所以它会根据默认路由将数据包发送给192.168.110.1
(也就是主 Namespace 的veth1_out
)。 - 主 Namespace 的
veth1_out
收到数据包后,由于net.ipv4.ip_forward
已经开启,它会查看自己的路由表。 - 主 Namespace 发现
192.168.1.148
在它的ens33
网卡所在的子网,于是将数据包通过ens33
发送出去。 192.168.1.148
收到数据包并回复,回复的数据包也会经过主 Namespace 的路由转发,最终回到 “game” Namespace。
通过这些步骤,你成功地创建了一个独立的 Network Namespace “game”,并利用 veth 网卡对和 IP 转发功能,让它能够与主系统以及外部网络进行通信。