内核网络命名空间

网络命名空间将内核网络协议栈(路由、流控、Netfilter、网桥等系统)虚拟成多个。内核默认创建的网络命名空间init_net。

struct net init_net = {
    .count      = ATOMIC_INIT(1),
    .dev_base_head  = LIST_HEAD_INIT(init_net.dev_base_head),
};


网络命名空间创建

iproute2工具集通过unshare(CLONE_NEWNET)系统调用创建新的网络命名空间。到Linux内核中,由函数copy_net_ns处理。其一分配网络命名空间内存;其二调用setup_net初始化命名空间中注册的所有协议栈模块。

struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net)
{
    struct net *net;
	
    if (!(flags & CLONE_NEWNET))
        return get_net(old_net);
    net = net_alloc();
    rv = setup_net(net, user_ns);
}

内核初始化时,各个协议栈模块通过register_pernet_subsys或者register_pernet_device注册其初始化函数到网络命名空间系统中。在新的命名空间创建时,遍历全局链表pernet_list,执行每个子模块注册的初始化函数。

static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
{   
    idr_init(&net->netns_ids);
    
    list_for_each_entry(ops, &pernet_list, list)
        error = ops_init(ops, net);
}

所有命名空间中注册的子模块(struct pernet_operations)都链接在全局链表pernet_list上。两个命名空间子模块注册函数register_pernet_device和register_pernet_subsys用于注册子模块,功能基本相同,差别在于子模块插入的位置。register_pernet_device将子模块添加在pernet_list的尾部,而register_pernet_subsys将子模块添加在第一个使用register_pernet_device注册的子模块在链表中的尾部。

                            |--<-----| prev
                            |        |
        head-->submod0-->submod1-->subdev0
          |                          | 
          |---<----------------------| next
        
        
                                prev
                            |--<------|  
                            |         |--<-----| prev
                            |         |        |
        head-->submod0-->submod1-->submod2-->subdev0
          |                                    | 
          |---<--------------------------------| next

如上图所示,如果链表中已经插入了一个device子模块(subdev0),之后插入的subsys子模块(submod2)将插入在subdev0之前。最终pernet_list链表中的元素排列为,所有使用register_pernet_subsys注册的子模块按照注册顺序位于链表头,而使用register_pernet_device注册的子模块按顺序位于链表尾部。目前内核中注册的device子模块为gre隧道处理、fou、vti、ipip等隧道设备模块,由于位于链表尾部,这些设备模块在创建命名空间时排在最后初始化。

对于采用模块形式动态加载的网络子模块,其在注册命名空间操作时,内核将遍历所有已经创建的网络命名空间,针对每一个命名空间执行其初始化函数。

static int __register_pernet_operations(struct list_head *list, struct pernet_operations *ops)
{
    list_add_tail(&ops->list, list);
    if (ops->init || (ops->id && ops->size)) {
        for_each_net(net)
            error = ops_init(ops, net);
    }
}

网络命名空间通用数据

网络协议栈的各个模块都会有私有的数据需要保存,网络命名空间提供了net_generic结构可用来保存模块私有数据的地址(指针)。这些私有数据基于命名空间,不同的命名空间保存有不同的模块私有数据,每个模块的私有数据指针按照id为索引保存在net_generic的指针数组成员ptr中。模块的id值在函数register_pernet_device或者register_pernet_subsys的调用中生成,之后返回给调用模块,并且根据参数中提供的私有数据大小,在返回前分配好模块所需的私有数据空间。

static int register_pernet_operations(struct list_head *list, struct pernet_operations *ops)
{
    if (ops->id) {
again:
        error = ida_get_new_above(&net_generic_ids, MIN_PERNET_OPS_ID, ops->id);
        max_gen_ptrs = max(max_gen_ptrs, *ops->id + 1);
    }
}

函数ida_get_new_above获取id值,max_gen_ptrs记录系统中最大的id值,以便在分配net_generic成员ptr空间时,分配合适的大小。max_gen_ptrs初始值为INITIAL_NET_GEN_PTRS(13),随着各个网络命名空间中的子模块注册而递增。

网络各个子模块可使用net_generic函数,指定参数net(网络命名空间)和id值,即可取到私有数据空间的首指针,进行必要的初始化工作已经进行特定命名空间的操作。

struct net_generic {
    union {
        void *ptr[0];
    };
};
static inline void *net_generic(const struct net *net, unsigned int id)
{
    struct net_generic *ng;
    ng = rcu_dereference(net->gen);
    ptr = ng->ptr[id];
}

例如对于ipgre_net_ops子模块,在调用register_pernet_device之后,网络命名空间系统将分配的id值保存到变量ipgre_net_id中,并且已分配好大小为sizeof(struct ip_tunnel_net)的内存空间,之后像ipgre_rcv等函数,可使用net_generic获取此空间。

static struct pernet_operations ipgre_net_ops = {
    .init = ipgre_init_net,
    .id   = &ipgre_net_id,
    .size = sizeof(struct ip_tunnel_net),
};
static int __init ipgre_init(void)
{
    err = register_pernet_device(&ipgre_net_ops);
}
static int ipgre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi, int hdr_len)
{
    struct net *net = dev_net(skb->dev);
    struct ip_tunnel_net *itn = net_generic(net, ipgre_net_id);
}

命名空间添加设备


内核为每个新建的网络命名空间创建一个回环接口lo。使用ip命令添加设备到命名空间中,如将网络设备eth1添加到网络命名空间netns1中。对于回环接口、网桥设备、聚合设备bond和一些隧道设备,不能使用IP命名将其在命名空间之间移动,内核在设备的features中设置了NETIF_F_NETNS_LOCAL标志来标识这类设备,其仅属于创建时的命名空间。

ip link set eth1 netns netns1

内核中dev_change_net_namespace函数处理设备在命名空间中的移动。目的是更改网络设备net_device结构体中的成员nd_net指向新的网络命名空间,但是在更改前后需要做一些清理和初始化工作。 首先在更改之前关闭设备,清理kobject、流控队列,清空地址等,发送NETDEV_DOWN、NETDEV_GOING_DOWN、NETDEV_UNREGISTER等消息通知旧的命名空间的协议栈,如路由系统接收到NETDEV_DOWN消息将会清除此接口上的IP地址对应的路由信息。其次更改设备到新的命名空间,修改设备id,在新命名空间发送通知消息NETDEV_REGISTER等通知新命名空间的其它模块。

鉴于以上操作,移动设备之后,之前配置的IP地址等信息将会被清除。


内核版本

Linux-4.15

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值