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. Python3入门系列之-----内置的文件操作模块OS

    前言 在自动化测试中,经常需要查找操作文件,比如说查找配置文件(从而读取配置文件的信息),查找测试报告(从而发送测试报告邮件),经常要对大量文件和大量路径进行操作,这个时候就需要用到os模块. 使用前 ...

  2. 关于Servlet

    Servlet与CGI CGI(Common Gateway Interface),早期的Web服务器技术.执行模式:将服务端的资源基于进程运行. Servlet:运行模式改为单进程多线程的形式,利用 ...

  3. B站视频:CocosCreator Bundle 特性三个实例详解,轻松实现大厅子游戏模式

    详细内容:https://forum.cocos.org/t/topic/112146

  4. Linux Bash命令杂记(cut sort uniq wc tee)

    Linux Bash命令杂记(cut sort uniq wc tee) 数据流重定向 标准输入(stdin):代码为0,使用<或<<: 标准输出(stdout):代码为1,使用&g ...

  5. 分片利器 AutoTable:为用户带来「管家式」分片配置体验

    在<DistSQL:像数据库一样使用 Apache ShardingSphere>一文中,Committer 孟浩然为大家介绍了 DistSQL 的设计初衷和语法体系,并通过实战操作展示了 ...

  6. 题解 「CTSC2018暴力写挂」

    题目传送门 题目大意 给出两个大小为 \(n\) 的树,求出: \[\max\{\text{depth}(x)+\text{depth}(y)-\text{depth}(\text{LCA}(x,y) ...

  7. 后台管理系统使用vue-element-admin搭建

    近期在搞一个会议健康申报系统时,要搞一个后台,用到了vue-element-admin模板,使用的是PanJianChen(源码地址:https://github.com/PanJiaChen/vue ...

  8. 一文了解MySQL性能测试及调优中的死锁处理方法,你还看不明白?

    一文了解MySQL性能测试及调优中的死锁处理方法,你还看不明白? 以下从死锁检测.死锁避免.死锁解决3个方面来探讨如何对MySQL死锁问题进行性能调优. 死锁检测 通过SQL语句查询锁表相关信息: ( ...

  9. 【UE4 C++】Actor 与 Component —— 创建、销毁

    Actor的生成与销毁 创建Actor实例 UClass* TSubclassOf<T> SpawnActor() UPROPERTY(EditAnywhere, Category = & ...

  10. 微信小程序的实现原理

    一.背景 网页开发,渲染线程和脚本是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应的原因,本质就是我们常说的 JS 是单线程的 而在小程序中,选择了 Hybrid 的渲染方式,将视图层和逻 ...