博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux ns 6. Network Namespace 详解
阅读量:3628 次
发布时间:2019-05-21

本文共 8209 字,大约阅读时间需要 27 分钟。

文章目录

1. 简介

1.1 Docker Network 桥接模式配置

在这里插入图片描述

1、创建一个新的 bash 运行在新的 net namespace 中:

pwl@ubuntu:~$ sudo unshare --net /bin/bash[sudo] password for pwl: root@ubuntu:~# ll /proc/$$/nstotal 0dr-x--x--x 2 root root 0 3月   7 17:34 ./dr-xr-xr-x 9 root root 0 3月   7 17:34 ../lrwxrwxrwx 1 root root 0 3月   7 17:34 cgroup -> 'cgroup:[4026531835]'lrwxrwxrwx 1 root root 0 3月   7 17:34 ipc -> 'ipc:[4026531839]'lrwxrwxrwx 1 root root 0 3月   7 17:34 mnt -> 'mnt:[4026531840]'lrwxrwxrwx 1 root root 0 3月   7 17:34 net -> 'net:[4026532598]'lrwxrwxrwx 1 root root 0 3月   7 17:34 pid -> 'pid:[4026531836]'lrwxrwxrwx 1 root root 0 3月   7 17:34 pid_for_children -> 'pid:[4026531836]'lrwxrwxrwx 1 root root 0 3月   7 17:34 user -> 'user:[4026531837]'lrwxrwxrwx 1 root root 0 3月   7 17:34 uts -> 'uts:[4026531838]'root@ubuntu:~# echo $$6700

2、需要将新的 net namespace 在 /var/run/netns文件夹下创建一个链接,才能被ip netns命令识别到:

pwl@ubuntu:~$ ip netns showpwl@ubuntu:~$ sudo mkdir /var/run/netns[sudo] password for pwl: pwl@ubuntu:~$ ln -s /proc/6700/ns/net /var/run/netns/4026532598ln: failed to create symbolic link '/var/run/netns/4026532598': Permission deniedpwl@ubuntu:~$ sudo ln -s /proc/6700/ns/net /var/run/netns/4026532598pwl@ubuntu:~$  ip netns show4026532598

3、创建一对虚拟网卡(veth pair),分别加入到旧 netns 和新 netns 中,配置对应两个同网段ip:

pwl@ubuntu:~$ sudo ip link add veth00 type veth peer name veth10pwl@ubuntu:~$ sudo ip link set dev veth10 netns 4026532598pwl@ubuntu:~$ sudo ip netns exec 4026532598 ifconfig veth10 10.1.1.1/24 uppwl@ubuntu:~$ sudo ifconfig veth00 10.1.1.2/24 uppwl@ubuntu:~$

4、从新的 netns 中可以 ping 通旧的 netns :

root@ubuntu:~# ifconfigveth10: flags=4099
mtu 1500 inet 10.1.1.1 netmask 255.255.255.0 broadcast 10.1.1.255 ether ce:d0:39:d7:1f:86 txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0root@ubuntu:~# ping 10.1.1.2PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.066 ms64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.040 ms^C--- 10.1.1.2 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 1027msrtt min/avg/max/mdev = 0.040/0.053/0.066/0.013 ms

5、增加一个网桥设备,让新的 netns 能平通外网:

pwl@ubuntu:~$ sudo brctl addbr br00pwl@ubuntu:~$ sudo brctl addif br00 veth00pwl@ubuntu:~$ brctl showbridge name     bridge id               STP enabled     interfacesbr-79007a57f712         8000.0242ce463a6b       nobr-cf283e550e84         8000.02420cae85cc       no              vethc5bcf22br00            8000.6e8e9290533f       no              veth00docker0         8000.024293d86502       nopwl@ubuntu:~$ sudo ifconfig veth00 0.0.0.0pwl@ubuntu:~$ sudo ifconfig br00 10.1.1.3/24 up

6、增加配置,让新的 netns 能 ping 通外网:(注意:Docker并不会把物理网卡加到网桥中,它是利用 IP Forward 功能把网桥数据转发到物理网卡的,参考 和 )

添加 iptables FORWARD 规则,并启动路由转发功能:

pwl@ubuntu:~$ sysctl -w net.ipv4.ip_forward=1pwl@ubuntu:~$ sudo iptables -A FORWARD --out-interface ens33 --in-interface br00 -j ACCEPTpwl@ubuntu:~$ sudo iptables -A FORWARD --in-interface ens33 --out-interface br00 -j ACCEPT

添加iptables NAT 规则:

pwl@ubuntu:~$ sudo iptables -t nat -A POSTROUTING --source 10.1.1.0/24 --out-interface ens33 -j MASQUERADE

新的 netns 中增加默认路由,通过物理网卡 ping 通外网:

root@ubuntu:~# ip route add default via 10.1.1.3 dev veth10root@ubuntu:~# routeKernel IP routing tableDestination     Gateway         Genmask         Flags Metric Ref    Use Ifacedefault         _gateway        0.0.0.0         UG    0      0        0 veth1010.1.1.0        0.0.0.0         255.255.255.0   U     0      0        0 veth10root@ubuntu:~# ping 10.91.47.97PING 10.91.47.97 (10.91.47.97) 56(84) bytes of data.64 bytes from 10.91.47.97: icmp_seq=1 ttl=127 time=2.89 ms64 bytes from 10.91.47.97: icmp_seq=2 ttl=127 time=1.32 ms

2. 代码解析

Network namespace 对应 struct net 结构。因为网络处理的复杂性,这里就不分析 net ns 对协议栈处理的影响,而是简单分析在 socket 层网卡驱动层net ns 的处理。

2.1 copy_net_ns()

clone()和unshare()时如果设置了CLONE_NEWNET标志,则会调用 copy_net_ns() 来创建一个新的 network namespace:

create_new_namespaces() → copy_net_ns() → setup_net():struct net *copy_net_ns(unsigned long flags,			struct user_namespace *user_ns, struct net *old_net){	struct ucounts *ucounts;	struct net *net;	int rv;	if (!(flags & CLONE_NEWNET))		return get_net(old_net);	ucounts = inc_net_namespaces(user_ns);	if (!ucounts)		return ERR_PTR(-ENOSPC);        /* (1) 分配一个新的 net ns */	net = net_alloc();	if (!net) {		dec_net_namespaces(ucounts);		return ERR_PTR(-ENOMEM);	}        /* (2) 设置启用新的 net ns */	net->ucounts = ucounts;	rv = setup_net(net, user_ns);	if (rv == 0) {		rtnl_lock();                /* (3) 加入全局链表 */		list_add_tail_rcu(&net->list, &net_namespace_list);		rtnl_unlock();	}}↓static __net_init int setup_net(struct net *net, struct user_namespace *user_ns){        /* (2.1) 逐个调用pernet_list链表中的ops,对新的 net ns 进行初始化 */	list_for_each_entry(ops, &pernet_list, list) {		error = ops_init(ops, net);		if (error < 0)			goto out_undo;	}}

2.2 pernet_list

全局链表 pernet_list 链接了多个 ops ,在新 net ns 初始化时逐个调用 ops->init() 。

可以使用 register_pernet_device() 函数向 pernet_list 链表中注册 ops,我们看看有哪些典型的 ops ,具体做了哪些操作。

2.2.1 loopback_net_ops

struct pernet_operations __net_initdata loopback_net_ops = {	.init = loopback_net_init,};↓static __net_init int loopback_net_init(struct net *net){	struct net_device *dev;	int err;	err = -ENOMEM;        /* (1) 给新的 net ns 分配了一个 loopback 本地环回网口 */	dev = alloc_netdev(0, "lo", NET_NAME_UNKNOWN, loopback_setup);	if (!dev)		goto out;        /* (2) 把网口设备设置为新的 net ns */	dev_net_set(dev, net);        /* (3) 注册网口设备 */	err = register_netdev(dev);	if (err)		goto out_free_netdev;	BUG_ON(dev->ifindex != LOOPBACK_IFINDEX);	net->loopback_dev = dev;	return 0;out_free_netdev:	free_netdev(dev);out:	if (net_eq(net, &init_net))		panic("loopback: Failed to register netdevice: %d\n", err);	return err;}

2.2.2 netdev_net_ops

static struct pernet_operations __net_initdata netdev_net_ops = {	.init = netdev_init,	.exit = netdev_exit,};↓static int __net_init netdev_init(struct net *net){	if (net != &init_net)		INIT_LIST_HEAD(&net->dev_base_head);        /* (1) 创建 hash 链表数组 */	net->dev_name_head = netdev_create_hash();	if (net->dev_name_head == NULL)		goto err_name;        /* (2) 创建 hash 链表数组 */	net->dev_index_head = netdev_create_hash();	if (net->dev_index_head == NULL)		goto err_idx;	return 0;err_idx:	kfree(net->dev_name_head);err_name:	return -ENOMEM;}

2.2.3 fou_net_ops

static struct pernet_operations fou_net_ops = {	.init = fou_init_net,	.exit = fou_exit_net,	.id   = &fou_net_id,	.size = sizeof(struct fou_net),};↓static __net_init int fou_init_net(struct net *net){        /* (1) 从 net->gen 中获取对应数据 */	struct fou_net *fn = net_generic(net, fou_net_id);        /* (2) 初始化相关结构 */	INIT_LIST_HEAD(&fn->fou_list);	mutex_init(&fn->fou_lock);	return 0;}

2.3 sock_net_set()

在 socket 创建时使用 sock_net_set() 函数将对应 net ns 设置成当前进程的 net ns 即 current->nsproxy->net_ns

SYSCALL_DEFINE3(socket) → sock_create()↓int sock_create(int family, int type, int protocol, struct socket **res){        /* (1) 配置 socket net ns 为当前进程的 net ns */	return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);}↓__sock_create() → pf->create() → inet_create() → sk_alloc() → sock_net_set()void sock_net_set(struct sock *sk, struct net *net){        /* (2) sk->sk_net 成员保存当前socket的 net ns */	write_pnet(&sk->sk_net, net);}

2.4 dev_net_set()

在网口设备注册时,默认加入到初始 net ns 即 init_net 中:

alloc_netdev() → alloc_netdev_mqs() struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,		unsigned char name_assign_type,		void (*setup)(struct net_device *),		unsigned int txqs, unsigned int rxqs){        /* (1) 初始化分配时,配置网络设备的 net ns 为默认的 init_net */	dev_net_set(dev, &init_net);}↓void dev_net_set(struct net_device *dev, struct net *net){	write_pnet(&dev->nd_net, net);}

后面可以通过 sudo ip link set dev veth10 netns 4026532598 之类的命令来把网口设备分配给不同的 net ns。

2.5 write_pnet()

不论是 sock_net_set() 还是 dev_net_set() 最后调用的都是 write_pnet() 函数,还有很多类似的 Linux 网络组件直接调用 write_pnet() 来更改 net ns,可以顺着这些调用来分析 net ns 对网络处理各个组件的影响。

static inline void write_pnet(possible_net_t *pnet, struct net *net){#ifdef CONFIG_NET_NS	pnet->net = net;#endif}

参考文档:

1.

2.
3.
4.
5.
6.
7.
8.
9.
10.
11.

转载地址:http://tfiun.baihongyu.com/

你可能感兴趣的文章
关于光盘刻录,重洗的一些知识
查看>>
default_Keyword
查看>>
do_Keyword
查看>>
for_Keyword
查看>>
float_Keyword
查看>>
finally_Keyword
查看>>
final_Keyword
查看>>
enum_Keyword
查看>>
extends_Keyword
查看>>
if_Keyword
查看>>
implements_Keyword
查看>>
import_Keyword
查看>>
int_Keyword
查看>>
超详细解读:神经语义解析的结构化表示学习 | 附代码分析
查看>>
在 CentOS 6 里安装 OpenJDK 1.8 和 Tomcat 8.5
查看>>
在 Debian 9 里安装 php5.3 fpm
查看>>
【干货合集】12篇文章带你读懂敏捷架构!
查看>>
手把手教你从零搭建深度学习项目(附链接)
查看>>
主题演讲:漏洞扫描在Web安全的应用
查看>>
量子计算会成为下一次革命的新机会吗?
查看>>