一、实验过程
1、创建2个netns
ip netns add netns1
ip netns add netns2
查看netns:ip netns list
2、核实一下2个网络命名空间里面的网络,里面都只有一个lo回环设备
ip netns exec netns1 bash
ip a
1: lo: mtu65536qdisc noop state DOWN group default qlen1000 link/loopback00:00:00:00:00:00brd00:00:00:00:00:00
exit
相同的操作,也对netns2操作
ip netns exec netns1 bash 进入netns1的bash
ip netns exec netns1 route 查看路由表
ip netns exec netns1 iptables -L 查看iptable规则
ip netns exec netns1 ip link list 查看网络接口
3、创建1对veth
ip link add veth1 type veth peer name veth2
4、使用 ip link show 可以看到2个veth
ip link show
4: veth2@veth1: mtu1500qdisc noop state DOWN mode DEFAULT group default qlen1000 link/ether02:8d:80:1d:9e:63 brd ff:ff:ff:ff:ff:ff
5: veth1@veth2: mtu1500qdisc noop state DOWN mode DEFAULT group default qlen1000 link/ether ce:6a:a5:1d:8c:6c brd ff:ff:ff:ff:ff:ff
5、把veth一头(veth1)分给netns1
ip link set veth1 netns netns1
ip link show 使用命令 ip link show 查看只剩下一个了
6、把veth一头(veth2) 分给 netns2
使用命令 ip link show 在宿主机上已经看不到veth
ip link set veth2 netns netns2
ip link show
7、登录2个netns去查看一下,都多了一个veth
ip netns exec netns1 bash
ip a
1: lo: mtu65536qdisc noop state DOWN group default qlen1000 link/loopback00:00:00:00:00:00brd00:00:00:00:00:00
13: veth1@if12: mtu1500qdisc noop state DOWN group default qlen1000 link/ether fe:5f:58:a3:43:67brd ff:ff:ff:ff:ff:ff link-netnsid1
8、网络配置
ip netns exec netns1 bash
ifconfig veth1 up //开启
ifconfig veth1 192.168.1.2 netmask 255.255.255.0 //配置ip 地址
exit
ip netns exec netns2 bash
ifconfig veth2 up //开启
ifconfig veth2 192.168.1.3 netmask 255.255.255.0 //配置ip 地址
exit
9、是否通测试
进入netns1,就可以ping通192.168.1.3
进入netns2,就可以ping通192.168.1.2
二、内核中 namespace 的定义
2.1 归属到 namespace 的东东
在 Linux 中,很多我们平常熟悉的概念都是归属到某一个特定的网络 namespace 中的,比如进程、网卡设备、socket 等等。
Linux中每个进程(线程)都是用 task_struct 来表示的。每个 task_struct 都要关联到一个 namespace 对nsproxy,而 nsproxy 又包含了 netns。
对于网卡设备和 socket 来说,通过自己的成员来直接表明自己的归属。

拿网络设备来举例,只有归属到当前 netns 下的时候才能够通过 ifconfig 看到,否则是不可见的。
先来看进程的数据结构的定义:
//file:include/linux/sched.h
struct task_struct{
/* namespaces */
struct nsproxy*nsproxy;
......
}
命名空间的核心数据结构是上面的这个 struct nsproxy。所有类型的 namespace(包括 pid、文件系统挂载点、网络栈等等)都是在这里定义的。
//file: include/linux/nsproxy.h
struct nsproxy {
struct uts_namespace *uts_ns;// 主机名
struct ipc_namespace *ipc_ns;// IPC
struct mnt_namespace *mnt_ns;// 文件系统挂载点
struct pid_namespace *pid_ns;// 进程标号
struct net *net_ns;// 网络协议栈
};
其中 struct net *net_ns 就是今天我们要讨论的网络命名空间。我们接着再看表示网络设备的 struct net_device,它也是要归属到某一个网络空间下的。
//file: include/linux/netdevice.h
struct net_device{
char name[IFNAMSIZ]; //设备名
struct net *nd_net; //网络命名空间
...
}
所有的网络设备刚创建出来都是在宿主机默认网络空间下的。可以通过ip link set 设备名 netns 网络空间名将设备移动到另外一个空间里去。
还有我们经常用的 socket,也是归属在某一个网络命名空间下的。
//file: include/net/sock.h
struct sock_common{
struct net *skc_net;
}
2.2 网络 namespace 定义
我们来看网络 namespace 的主要数据结构 struct net 的定义。

可见每个 net 下都包含了自己的路由表、iptable 以及内核参数配置等等。
//file:include/net/net_namespace.h
struct net{
struct net_device *loopback_dev; //每个 net 中都有一个回环设备
struct netns_ipv4 ipv4; //路由表、netfilter都在这里
unsigned int proc_inum;
}
由上述定义可见,每一个 netns 中都有一个 loopback_dev,这就是为什么我们看到刚创建出来的空间里就能看到一个 lo 设备的底层原因。
网络 netspace 中最核心的数据结构是struct netns_ipv4 ipv4。在这个数据结构里,定义了每一个网络空间专属的路由表、ipfilter 以及各种内核参数。
//file: include/net/netns/ipv4.h
struct netns_ipv4{
//路由表
struct fib_table *fib_local;
struct fib_table*fib_main;
struct fib_table*fib_default;
//ip表
struct xt_table*iptable_filter;
struct xt_table*iptable_raw;
structxt_table*arptable_filter;
long sysctl_tcp_mem[3];//内核参数
...
}
三、网络 namespace 的创建
3.1 进程与网络命名空间
Linux 上存在一个默认的网络命名空间,Linux 中的 1 号进程初始使用该默认空间。Linux 上其它所有进程都是由 1 号进程派生出来的,在派生 clone 的时候如果没有额外特别指定,所有的进程都将共享这个默认网络空间。

在 clone 里可以指定创建新进程时的 flag,都是 CLONE_ 开头的。 和 namespace 有关的标志位有 CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID等等。如果在创建进程时指定了CLONE_NEWNET 标记位,那么该进程将会创建并使用新的 netns。
其实内核提供了三种操作命名空间的方式,分别是 clone、setns 和 unshare。本文中我们只用 clone 来举例,ip netns add 使用的是 unshare,原理和 clone 是类似的。

我们先来看下默认的网络命名空间的初始化过程。
struct task_structinit_task=INIT_TASK(init_task);
#define INIT_TASK(tsk) \
{
...
nsproxy=&init_nsproxy,\
}
上面的代码是在初始化第1号进程。可见 nsproxy 是已经创建好的 init_nsproxy。再看init_nsproxy 是如何创建的。
struct nsproxy init_nsproxy=
{
.uts_ns=&init_uts_ns,
.ipc_ns=&init_ipc_ns,
.mnt_ns=NULL,
.pid_ns=&init_pid_ns,
.net_ns=&init_net,
};
初始的 init_nsproxy 里将多个命名空间都进行了初始化,其中我们关注的网络命名空间,用的是默认网络空间 init_net。它是系统初始化的时候就创建好的。
struct net init_net={
.dev_base_head=LIST_HEAD_INIT(init_net.dev_base_head),
};
EXPORT_SYMBOL(init_net);
//file: net/core/net_namespace.c
static int __initnet_ns_init(void){...setup_net(&init_net,&init_user_ns);...register_pernet_subsys(&net_ns_ops);return0;}
参考:http://www.manongjc.com/detail/17-nbwatevqtlvptak.html
参考:https://zhuanlan.zhihu.com/p/425747451
网友评论