网络命令空间inet_net

本文深入探讨了Linux网络命名空间的概念,它提供了网络协议栈的隔离,包括网络设备、IPv4/IPv6、路由等。网络命名空间通过structnet结构体实现,包含了网络设备链表、协议栈等信息。在系统初始化时,会创建初始的网络命名空间init_net,并注册网络命名空间子系统。当创建新的命名空间时,会分配structnet结构并初始化。获取网络命名空间可通过进程、套接字、设备等方式。文章还介绍了网络命名空间的注册和初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1      概述
在linux协议栈中引入网络命名空间,是为了支持网络协议栈的多个实例,而这些协议栈的隔离就是通过命名空间来实现的,一个net namespace为进程提供一个完全独立的网络协议栈的视图,包括网络设备接口、ipv4和ipv6协议栈、ip路由表、防火墙规则、sockets等。一个net namespace提供了一份独立的网络环境,就跟独立的系统一样。一个物理设备只能存在于一个net namespace中,但可以从一个net namespace移动到另一个net namespace。网络系统在初始化的时候会初始化一个初始网络命名空间,即init_net命名空间。后续创建的net namespace命名空间会和init_net一起通过list项组织起来,且每个网络设备都对应一个命名空间,同一命名空间下的网络设备通过dev_base_head组织在一起(如图1所示)。当一个netnamespace被销毁时,物理设备会被自动移回到init net namespace。

 

2      重要数据结构
2.1      struct net
struct net {

        atomic_t          passive;   /* To decided when the network

                                                   * namespace should be freed.

                                                   */

        atomic_t          count;              /*To decided when the network

                                                   * namespace should be shut down.

                                                   */

#ifdef NETNS_REFCNT_DEBUG

        atomic_t          use_count;       /* To track references we

                                                   * destroy on demand

                                                   */

#endif

        spinlock_t                rules_mod_lock;

//网络命名空间是扁平结构,采用链表连接

        structlist_head        list;          /* list of network namespaces */

//链入全局cleanup_list链表,用于表示要释放的net

        structlist_head        cleanup_list;   /* namespaces on death row */

        //连接到exit_list链表的都会被执行pernet_operations的exit函数

        structlist_head        exit_list;  /* Use only net_mutex */

        structproc_dir_entry     *proc_net; //对应/proc/net

        structproc_dir_entry     *proc_net_stat; //对应/proc/net/stat

#ifdef CONFIG_SYSCTL

        structctl_table_set  sysctls;

#endif

        structsock              *rtnl;                        /* rtnetlink socket */

        structsock               *genl_sock;

        structlist_head       dev_base_head; // netnamespace中网络设备链表

        structhlist_head     *dev_name_head;//netnamespace中网络设备名链表

        structhlist_head      *dev_index_head; //netnamespace中网络设备索引链表

        unsignedint             dev_base_seq; /* protected by rtnl_mutex */

        /*core fib_rules */  //路由、ip相关部分

        structlist_head        rules_ops;

        structnet_device       *loopback_dev;          /* The loopback */

        structnetns_core     core;

        structnetns_mib     mib;

        structnetns_packet packet;

        structnetns_unix    unx;

        structnetns_ipv4    ipv4;

#if IS_ENABLED(CONFIG_IPV6)

        structnetns_ipv6    ipv6;

#endif

#if defined(CONFIG_IP_DCCP) ||defined(CONFIG_IP_DCCP_MODULE)

        structnetns_dccp    dccp;

#endif

#ifdef CONFIG_NETFILTER

        structnetns_xt         xt;

#if defined(CONFIG_NF_CONNTRACK) ||defined(CONFIG_NF_CONNTRACK_MODULE)

        structnetns_ct         ct;

#endif

        structsock               *nfnl;

        structsock               *nfnl_stash;

#endif

#ifdef CONFIG_WEXT_CORE

        structsk_buff_head        wext_nlevents;

#endif

        structnet_generic __rcu *gen;

        /*Note : following structs are cache line aligned */

#ifdef CONFIG_XFRM

        structnetns_xfrm    xfrm;

#endif

        structnetns_ipvs     *ipvs;

};

struct net表示网络命名空间的结构体,网络命名空间就是将网络中需要局部化的信息,如设备、路由和ipv4协议等放入该结构中,每个命名空间通过空间内的相关信息进行数据报文传送。

2.2      struct pernet_operations
struct pernet_operations {

        structlist_head list; //用于链入全局链表pernet_list

        int(*init)(struct net *net); //网络命名空间每个子系统的初始化函数

        void(*exit)(struct net *net); //网络命名空间每个子系统的退出函数

        void(*exit_batch)(struct list_head *net_exit_list); //也是退出函数

        int*id; //本实例序号,标识在structnet中gen的ptr数组中位置,通过idr树管理

        size_tsize; //本结构大小

};

struct pernet_operations定义了net namespace每个子系统的操作函数集。当创建一个新的net namespace时,都会调用其中的init函数进行初始化。同理,当销毁一个net namespace时,会调用其中的exit函数退出。

3      网络命名空间初始化
网络模块的初始化中,不同的模块可能用到不同的内核初始化宏定义。比如:

1)    pure_initcall(net_ns_init);——网络命名空间初始化

2)    fs_initcall(af_unix_init);——unix模块初始化

3)    module_init(arptable_filter_init);——arp表初始化

4)    subsys_initcall(net_dev_init);——网络设备的初始化

对于这些宏定义,到底哪个先执行,哪个后执行呢?展开这些宏定义如下表:

 

在内核初始化的过程中,会按照.initcall0.init-.initcall7.init的顺序执行,即对于网络命名空间而言,其初始化函数net_ns_init()会在其各个子系统的注册函数前执行。其代码具体如下:

 

该函数主要做了一下几件事:

1)        创建网络命名空间的内存池(以后创建新的网络命名空间时,申请新的struct net结构的内存就是用这个内存池中的内存)

2)        创建工作队列

3)        初始化init_net(全局网络命名空间)的gen变量

4)        调用setup_net()初始化init_net的所有子系统(因为net_ns_init比网络命名空间各个子系统的注册时间更早,所以setup_net并没有做实质性工作)

5)        把init_net链接到系统全局链表net_namespace_list中

再来看看setup_net函数,关键操作如下:

   list_for_each_entry(ops, &pernet_list,list) {

            error = ops_init(ops, net);

            if (error < 0)

                    gotoout_undo;

   }

pernet_list是系统的全局链表,在register_pernet_subsys时就会把ops挂到这个链表上。这个函数的主要功能就是遍历已注册的网络命名空间的所有子系统,调用其初始化函数初始化每一个网络命名空间子系统。

4       注册网络命名空间子系统
网络命名空间子系统的注册可以分为两类,一类是通过register_pernet_subsys注册,一类是通过register_pernet_device注册。

4.1.1      register_pernet_subsys
内核中网络很多地方用到了该注册函数,代表了把网络系统的全局资源进行局部化。系统启动时,与网络相关的初始化中,会调用很多的register_pernet_subsys()函数,初始化很多网络子系统,比如路由子系统、防火墙、netlink等等。每当初始化一个网络子系统,就会利用该函数将子系统相应的函数操作集加入全局链表first_device中。

其中first_device有指向了另一个全局变量pernet_list,二者定义如下:

static LIST_HEAD(pernet_list);

static struct list_head *first_device =&pernet_list;

4.1.2      register_pernet_device
register_pernet_device用于注册设备相关的网络命名空间子系统。

 

这个函数和register_pernet_subsys类似,不同之处在于如果first_device指向pernet_list,则改变first_device的指针,使其指向链表中第一个设备相关的网络命名空间子系统的ops。如图所示:

 

所有网络命名空间子系统的ops都会链接到图中的双向链表中。用了上面两个不同的注册函数的目的在于,在链表中将ops归类:把subsys相关的放到一起,把device相关的放到一起,并让全局变量first_device指向第一个device相关的ops。图中的pernet_list是双向链表的头节点。整个操作系统中,双向链表pernet_list中的ops会有100来个。

register_pernet_subsys和register_pernet_device除了将网络空间子系统ops加入全局链表中,还会对现有命名空间的子系统进行初始化:

register_pernet_operations ->__register_pernet_operations:

list_add_tail(&ops->list, list);

        if(ops->init || (ops->id && ops->size)) {

                 for_each_net(net){

                         error= ops_init(ops, net);

                         if(error)

                                  gotoout_undo;

                         list_add_tail(&net->exit_list,&net_exit_list);

                 }

        }

在__register_pernet_operations中把注册的ops挂载到全局链表,并遍历系统中现有的网络命名空间进行子系统初始化操作,之所有要遍历所有的子系统,是因为操作系统中很多都是模块化的,在系统运行一段时候后才加载,这样有些命名空间在子系统加载前就创建好了,因此要对现存的所有网络命名空间进行初始化操作(init_net的子系统的初始化也在这进行)。

5      创建网络命名空间
在执行clone或者unshare系统调用时,如果有传入CLONE_NEWNET参数,即新建网络命名空间时,会调用copy_net_ns函数创建新的网络命名空间:

 

在创建网络命名空间时,和网络命名空间初始化时做的事情相似,主要就是做了以下几件事:

1.   先分配一个struct net结构(系统初始化时是使用全局结构struct net init_net)

2.   通过调用setup_net,调用网络命名空间中的各个子系统的初始化函数初始化struct net结构

3.   将新的net命名空间加入到网络命名空间全局链表net_namespace_list中

6      获取网络命名空间
一个网络命名空间提供了一份独立的网络环境,包括网络设备、进程、sockets等,因此也可以从这些资源中,获取相应的网络命名空间。

6.1.1      通过进程获取网络命名空间
命名空间在内核里被抽象成数据结构struct nsproxy,每个进程的task_struct结构中有一个struct nsproxy指针,指向进程所属的命名空间。函数get_net_ns_by_pid()便是通过进程的pid获取taks_struct结构,获取进程所在的网络命名空间:

tsk = find_task_by_vpid(pid);

        if(tsk) {

                 structnsproxy *nsproxy;

                 nsproxy= task_nsproxy(tsk);

                 if(nsproxy)

                         net= get_net(nsproxy->net_ns);

        }

6.1.2      通过套接字获取网络命名空间
对于和网络命名空间相关的文件记录其所属的命名空间,所以可以根据其获取网络命名空间。

struct net *get_net_ns_by_fd(int fd)

{

        structproc_inode *ei;

        structfile *file;

        structnet *net;

        file= proc_ns_fget(fd);

        if(IS_ERR(file))

                 returnERR_CAST(file);

        ei= PROC_I(file->f_dentry->d_inode);

        if(ei->ns_ops == &netns_operations)

                 net= get_net(ei->ns);

        else

                 net= ERR_PTR(-EINVAL);

        fput(file);

        returnnet;

}

6.1.3      通过socket获取网络命名空间
创建套接字的流程基本如下:

sock_create() --> __sock_create(current->nsproxy->net_ns,) --> pf-->create()-->sk_alloc(net, )-->sock_net_set()

在创建套接字时会通过调用协议对应注册的操作结构的pf-->create()函数,将当前进程所在的网络命名空间传递下去,并在sk_alloc()中赋给sk->sk_net指针, 因此可以通过struct sock结构获取网络命名空间,具体如下:

struct net *sock_net(const struct sock *sk)

{

        returnread_pnet(&sk->sk_net);

}

协议对应的sock操作函数,是在每个协议初始化时进行注册的,比如ipv4,其sock操作函数集如下:

static const struct net_proto_familyinet_family_ops = {

        .family= PF_INET,

        .create= inet_create,

        .owner     = THIS_MODULE,

};

在协议初始化时:

static int __init inet_init(void)

{

        ……

        sock_register(&inet_family_ops);

        ……

}

会通过sock_register()将操作函数集注册到全局数组net_families中,通过协议族便可以直接从数组中获取对应的sock操作函数结构。

6.1.4      通过网络设备获取网络命名空间
在创建/注册/初始化各种网络设备的时候,会将网络设备与其所属的网络命名空间相关联:

void dev_net_set(struct net_device *dev,struct net *net)

{

#ifdef CONFIG_NET_NS

        release_net(dev->nd_net);

        dev->nd_net= hold_net(net);

#endif

}

因此也可以通过strcutnet_device结构获取网络命名空间:

struct net *dev_net(const struct net_device*dev)

{

        returnread_pnet(&dev->nd_net);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值