1. 简介

1.1 Docker Network 桥接模式配置

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

pwl@ubuntu:~$ sudo unshare --net /bin/bash
[sudo] password for pwl:
root@ubuntu:~# ll /proc/$$/ns
total 0
dr-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 show
pwl@ubuntu:~$ sudo mkdir /var/run/netns
[sudo] password for pwl:
pwl@ubuntu:~$ ln -s /proc/6700/ns/net /var/run/netns/4026532598
ln: failed to create symbolic link '/var/run/netns/4026532598': Permission denied
pwl@ubuntu:~$ sudo ln -s /proc/6700/ns/net /var/run/netns/4026532598
pwl@ubuntu:~$ ip netns show
4026532598

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

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

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

root@ubuntu:~# ifconfig
veth10: flags=4099<UP,BROADCAST,MULTICAST> 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 0 root@ubuntu:~# ping 10.1.1.2
PING 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 ms
64 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 1027ms
rtt min/avg/max/mdev = 0.040/0.053/0.066/0.013 ms

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

pwl@ubuntu:~$ sudo brctl addbr br00
pwl@ubuntu:~$ sudo brctl addif br00 veth00
pwl@ubuntu:~$ brctl show
bridge name bridge id STP enabled interfaces
br-79007a57f712 8000.0242ce463a6b no
br-cf283e550e84 8000.02420cae85cc no vethc5bcf22
br00 8000.6e8e9290533f no veth00
docker0 8000.024293d86502 no
pwl@ubuntu:~$ sudo ifconfig veth00 0.0.0.0
pwl@ubuntu:~$ sudo ifconfig br00 10.1.1.3/24 up

6、增加配置,让新的 netns 能 ping 通外网:(注意:Docker并不会把物理网卡加到网桥中,它是利用 IP Forward 功能把网桥数据转发到物理网卡的,参考Linux虚拟网络设备之bridge(桥)模拟 Docker网桥连接外网

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

pwl@ubuntu:~$ sysctl -w net.ipv4.ip_forward=1
pwl@ubuntu:~$ sudo iptables -A FORWARD --out-interface ens33 --in-interface br00 -j ACCEPT
pwl@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 veth10
root@ubuntu:~# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default _gateway 0.0.0.0 UG 0 0 0 veth10
10.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth10
root@ubuntu:~# ping 10.91.47.97
PING 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 ms
64 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.Linux Namespace
2.Docker容器网络-基础篇
3.Docker容器网络-实现篇
4.Linux内核命名空间之(3)net namespace
5.Linux namespace
6.查看 Docker 容器的名字空间
7.Linux虚拟网络设备之bridge(桥)
8.模拟 Docker网桥连接外网
9.socket编程
10.struct socket 结构详解
11.iptables零基础快速入门系列

Linux ns 6. Network Namespace 详解的更多相关文章

  1. Linux ns 4. UTS Namespace 详解

    目录 1. 使用简介 1.1 hostname 1.2 domainname 1.3 uname 2. 代码分析 2.1 copy_utsname() 2.2 sethostname() 2.3 ge ...

  2. Linux ns 5. IPC Namespace 详解

    文章目录 1. 简介 2. 源码分析 2.1 copy_ipcs() 2.2 ipcget() 2.3 ipc_check_perms() 2.4 相关系统调用 参考文档: 1. 简介 进程间通讯的机 ...

  3. Linux ns 3. Mnt Namespace 详解

    1. 文件系统层次化 对 Linux 系统来说一切皆文件,Linux 使用树形的层次化结构来管理所有的文件对象. 完整的Linux文件系统,是由多种设备.多种文件系统组成的一个混合的树形结构.我们首先 ...

  4. Linux文件系统的目录结构详解

    Linux文件系统的目录结构详解   一.前 言 文章对Linux下所有目录一一说明,对比较重要的目录加以重点解说,以帮助初学者熟练掌握Linux的目录结构. 二.目 录 1.什么是文件系统 2.文件 ...

  5. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html   IT168 技术文档]在开始步入L ...

  6. linux route命令的使用详解 添加永久静态路由 tracert traceroute

    linux route命令的使用详解 添加永久静态路由  tracert  traceroute route -n    Linuxroute  print  Windows traceroute  ...

  7. Linux下DNS服务器搭建详解

    Linux下DNS服务器搭建详解 DNS  即Domain Name System(域名系统)的缩写,它是一种将ip地址转换成对应的主机名或将主机名转换成与之相对应ip地址的一种机制.其中通过域名解析 ...

  8. Linux双网卡绑定bond详解--单网卡绑定多个IP

    Linux双网卡绑定bond详解 1 什么是bond 网卡bond是通过多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡,在生产场景中是一种常用的技术.Kernels 2.4.12及 ...

  9. Linux上的free命令详解、swap机制

    Linux上的free命令详解   解释一下Linux上free命令的输出. 下面是free的运行结果,一共有4行.为了方便说明,我加上了列号.这样可以把free的输出看成一个二维数组FO(Free ...

随机推荐

  1. Kafka分区策略

    Kafka分区策略 所谓分区策略是决定生产者将消息发送到哪个分区的算法.Kafka 为我们提供了默认的分区策略,同时它也支持你自定义分区策略. 常见的分区策略包含以下几种:轮询策略.随机策略 .按消息 ...

  2. 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 百篇博客分析OpenHarmony源码 | v14.14

    百篇博客系列篇.本篇为: v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...

  3. AT2363-[AGC012C]Tautonym Puzzle【构造】

    正题 题目链接:https://www.luogu.com.cn/problem/AT2363 题目大意 给出\(n\),要求构造一个字符串\(s\),使得能够找出恰好\(n\)个子序列使得这个子序列 ...

  4. MySQL的本地事务、全局事务、分布式事务

    本地事务 事务特性:ACID,其中C一致性是目的,AID是手段. 实现隔离性 写锁:数据加了写锁,其他事务不能写也不能读. 读锁:数据加了读锁,其他事务不能加写锁可以加读锁,可以允许自己升级为写锁. ...

  5. 2021record

    2021-10-14 P2577 [ZJOI2004]午餐 2021-10-13 CF815C Karen and Supermarket(小小紫题,可笑可笑) P6748 『MdOI R3』Fall ...

  6. spoj2 prime1 (区间筛)

    给定t组询问,每组询问包括一个l和r,要求\([l,r]\)的素数有哪些 其中\(t \le 10,1 \le l \le r \le 1000000000 , r-l \le 100000\) Qw ...

  7. Java(14)面向对象之封装

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201610.html 博客主页:https://www.cnblogs.com/testero ...

  8. 40个Python入门小程序

    有不少同学学完Python后仍然很难将其灵活运用.我整理 37 个Python入门的小程序.在实践中应用Python会有事半功倍的效果. 分享 Github 项目,里面收集了 Python 学习资料 ...

  9. Unity——计时器功能实现

    Unity计时器 Demo展示 介绍 游戏中有非常多的计时功能,比如:各种cd,以及需要延时调用的方法: 一般实现有一下几种方式: 1.手动计时 float persistTime = 10f flo ...

  10. Noip模拟16 2021.7.15

    题目真是越来越变态了 T1 Star Way To Heaven 首先,你要看出这是一个最小生成树的题(妙吧?) 为什么可以呢? 我们发现从两点连线的中点过是最优的,但是上下边界怎么办呢? 我们把上下 ...