Linux ns 6. Network Namespace 详解
文章目录
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 详解的更多相关文章
- 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 ...
- Linux ns 5. IPC Namespace 详解
文章目录 1. 简介 2. 源码分析 2.1 copy_ipcs() 2.2 ipcget() 2.3 ipc_check_perms() 2.4 相关系统调用 参考文档: 1. 简介 进程间通讯的机 ...
- Linux ns 3. Mnt Namespace 详解
1. 文件系统层次化 对 Linux 系统来说一切皆文件,Linux 使用树形的层次化结构来管理所有的文件对象. 完整的Linux文件系统,是由多种设备.多种文件系统组成的一个混合的树形结构.我们首先 ...
- Linux文件系统的目录结构详解
Linux文件系统的目录结构详解 一.前 言 文章对Linux下所有目录一一说明,对比较重要的目录加以重点解说,以帮助初学者熟练掌握Linux的目录结构. 二.目 录 1.什么是文件系统 2.文件 ...
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- linux route命令的使用详解 添加永久静态路由 tracert traceroute
linux route命令的使用详解 添加永久静态路由 tracert traceroute route -n Linuxroute print Windows traceroute ...
- Linux下DNS服务器搭建详解
Linux下DNS服务器搭建详解 DNS 即Domain Name System(域名系统)的缩写,它是一种将ip地址转换成对应的主机名或将主机名转换成与之相对应ip地址的一种机制.其中通过域名解析 ...
- Linux双网卡绑定bond详解--单网卡绑定多个IP
Linux双网卡绑定bond详解 1 什么是bond 网卡bond是通过多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡,在生产场景中是一种常用的技术.Kernels 2.4.12及 ...
- Linux上的free命令详解、swap机制
Linux上的free命令详解 解释一下Linux上free命令的输出. 下面是free的运行结果,一共有4行.为了方便说明,我加上了列号.这样可以把free的输出看成一个二维数组FO(Free ...
随机推荐
- 修改CentOS ll命令显示时间格式
临时更改显示样式,当会话结束后恢复原来的样式: export TIME_STYLE='+%Y-%m-%d %H:%M:%S' 永久改变显示样式,更改后的效果会保存下来 修改/etc/profile文件 ...
- 51nod1229-序列求和V2【数学,拉格朗日插值】
正题 题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1229 题目大意 给出\(n,k,r\)求 \[\sum_{i=1}^ni ...
- 生动直观的Gif图告诉你如何安装Python安装第3方库,在线安装离线安装全都搞定
前言 学Python的小伙伴都知道,Python学习过程中需要装不少的第3方的库,今天就和大家一起分享下第3方库的安装方法 在线安装(推荐安装式式) 点开Pycharm--file--Project- ...
- React-高阶函数_函数柯里化
高阶函数_函数柯里化 高阶函数(定义) 如果一个函数符合下面两个规范,就是高阶函数: 如果A函数,接收的参数是一个函数,那么A就是一个高阶函数(比如数组方法arr.map()接收的就是一个处理item ...
- Python isinstance() 函数 Python 内置函数 Python 内置函数
描述 isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type(). isinstance() 与 type() 区别: type() 不会认为子类是一种父类类型,不考虑继承关 ...
- 浅谈机(J)惨(C)技巧——从入门到精通(?)
JC总是无聊的机房中有意思的一个瞬间... 比如: 这个杰作由 ZSWBWYX 完成 你认为这只是AK-IOI吗?不!注意用户名...也是此人的杰作. 所以,在险恶的机房里,一定要保护好自己的账号. ...
- instanceof和类型转换
什么是instanceof 判断一个对象是什么类型 注意点 X 和 Y 必须要有父子关系 否则编译都会失败 X对象只要是Y的子类(无论 是 儿子 还是 孙子 还是 曾孙....)X instanceo ...
- Idea生成JavaDoc文档
什么是JavaDoc javadoc是Sun公司提供的一个技术 它从程序源代码中抽取类.方法.成员等注释形成一个和源代码配套的API帮助文档 实现方式 命令行方式 javadoc -encoding ...
- node-gyp项目命名BUG
当我们编写node原生模块的时候,免不了对node-gyp项目进行命名,在node-gyp进行build的时候,会跟binding.gyp配置文件中的target_name生成对应的原生模块.但是,如 ...
- After Effects 图层属性及属性组结构详解
根据结构类型的属性分类 在 After Effects 的脚本开发中,图层的属性可被区分为三种类型:PROPERTY.INDEXED_GROUP 和 NAMED_GROUP .通过使用app.proj ...