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. GDOI2021划水记

    Day0 上午有意志行,一大早就醒了,然后走了五个小时脚痛.中午洗澡,宿舍轮流看巨人最终话然后聊了一个小时? 下午老师带着我和全爷先开溜,宿舍好像很破旧还还没得充电,领了牌牌和斐爷去吃饭. 然后六点多 ...

  2. spring-data-redis 上百万的 QPS 压力太大连接失败,我 TM 人傻了

    大家好,我们最近业务量暴涨,导致我最近一直 TM 人傻了.前几天晚上,发现由于业务压力激增,某个核心微服务新扩容起来的几个实例,在不同程度上,出现了 Redis 连接失败的异常: org.spring ...

  3. HTML模板标签解析

    HTML基本模板 1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta cha ...

  4. Python爬取 | 王者荣耀英雄皮肤海报

    这里只展示代码,具体介绍请点击下方链接. Python爬取 | 王者荣耀英雄皮肤海报 import requests import re import os import time import wi ...

  5. c++中的数学函数

    math.h 数学函数库,一些数学计算的公式的具体实现是放在math.h里,具体有:1 三角函数double sin (double);double cos (double);double tan ( ...

  6. 面试官:Java从编译到执行,发生了什么?

    面试官:今天从基础先问起吧,你是怎么理解Java是一门「跨平台」的语言,也就是「一次编译,到处运行的」? 候选者:很好理解啊,因为我们有JVM. 候选者:Java源代码会被编译为class文件,cla ...

  7. 安卓开发——WebView+Recyclerview文章详情页,解决高度问题

    安卓开发--WebView+Recyclerview文章详情页,解决高度问题 最近在写一个APP时,需要显示文章详情页,准备使用WebView和RecyclerView实现上面文章,下面评论.出现了W ...

  8. QQ消算轰炸,我好无聊真的

    from pynput.keyboard import Key,Controller import time from random import choice time.sleep(5) # 创建键 ...

  9. [对对子队]会议记录4.20(Scrum Meeting11)

    今天已完成的工作 何瑞 ​ 工作内容:搭建第三关,添加了运行指令标识 ​ 相关issue:搭建关卡2.3 ​ 相关签入:4.20签入1 4.20签入2 吴昭邦 ​ 工作内容:搭建第三关 ​ 相关iss ...

  10. Linux多线程编程之详细分析

    线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步.互斥,这些东西将在本文中介绍.我见到这样一道面试题: 是否熟悉POSIX多线程 ...