linux netlink详解1-netlink初始化
2021/9/7 7:10:47
本文主要是介绍linux netlink详解1-netlink初始化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
转载自:https://www.cnblogs.com/xinghuo123/p/13782009.html
Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时它也以用于进程间通信(Netlink 更多用于内核通信,进程之间通信更多使用Unix域套接字)。在一般情况下,用户态和内核态通信会使用三种传统的通信方式:
1 ioctl; 2 sysfs属性文件; 3 proc fs属性文件;
但这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。而Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,用户态则基于socket API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。Netlink 有以下优点:
1 双向全双工异步传输,支持由内核主动发起传输通信,而不需要用户空间出发(例如使用ioctl这类的单工方式)。如此用户空间在等待内核某种触发条件满足时就无需不断轮询,而异步接收内核消息即可。 2 支持组播传输,即内核态可以将消息发送给多个接收进程,这样就不用每个进程单独来查询了。
目前已经有许多内核模块使用netlink 机制,其中驱动模型中使用的uevent 就是基于netlink 实现。
netlink的架构框图如下:
netlink source code path如下:
linux-4.9.73\net\netlink;
linux-4.9.73\net\wireless;
linux-4.9.73\net\core;
linux-4.9.73\net\socket.c
include/uapi/linux/netlink.h;
linux-4.9.73\include\net
目前 netlink 协议族支持32种协议类型,它们定义在 include/uapi/linux/netlink.h 中:
1 #define NETLINK_ROUTE 0 /* 用于设置和查询路由表等网络核心模块*/ 2 #define NETLINK_UNUSED 1 /* Unused number */ 3 #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ 4 #define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ 5 #define NETLINK_SOCK_DIAG 4 /* socket monitoring */ 6 #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ 7 #define NETLINK_XFRM 6 /* ipsec */ 8 #define NETLINK_SELINUX 7 /* SELinux event notifications */ 9 #define NETLINK_ISCSI 8 /* Open-iSCSI */ 10 #define NETLINK_AUDIT 9 /* auditing */ 11 #define NETLINK_FIB_LOOKUP 10 12 #define NETLINK_CONNECTOR 11 13 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ 14 #define NETLINK_IP6_FW 13 15 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ 16 #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace 用于uevent消息通信*/ 17 #define NETLINK_GENERIC 16 //generic netlink 18 /* leave room for NETLINK_DM (DM Events) */ 19 #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ 20 #define NETLINK_ECRYPTFS 19 21 #define NETLINK_RDMA 20 22 #define NETLINK_CRYPTO 21 /* Crypto layer */
对于在实际应用中,可能会有一些定制化的需求,以上这几种专用的协议类型无法满足,这时可以在不超过最大32种类型的基础之上自行添加。但是一般情况下这样做有些不妥,于是内核开发者就设计了一种通用netlink 协议类型(Generic Netlink)NETLINK_GENERIC,它就是一个Netlink复用器,便于用户自行扩展子协议类型。
下面来分析Netlink的具体创建和通信流程。
1 内核netlink初始化
在系统启动阶段调用af_netlink.c的netlink_proto_init()函数完成内核Netlink的初始化。
code位于:linux-4.9.73\net\netlink\Af_netlink.c
1 static int __init netlink_proto_init(void) 2 { 3 int i; 4 int err = proto_register(&netlink_proto, 0);//向内核注册netlink协议 5 6 if (err != 0) 7 goto out; 8 9 BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb)); 10 11 nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);//创建并初始化了nl_table表数组 --------- 详解1 12 if (!nl_table) 13 goto panic; 14 15 for (i = 0; i < MAX_LINKS; i++) { 16 if (rhashtable_init(&nl_table[i].hash, 17 &netlink_rhashtable_params) < 0) { 18 while (--i > 0) 19 rhashtable_destroy(&nl_table[i].hash); 20 kfree(nl_table); 21 goto panic; 22 } 23 } 24 25 INIT_LIST_HEAD(&netlink_tap_all); 26 27 netlink_add_usersock_entry();//初始化应用层使用的NETLINK_USERSOCK协议类型的netlink(用于应用层进程间通信) 28 29 sock_register(&netlink_family_ops);-------------- 详解2 30 register_pernet_subsys(&netlink_net_ops);------------------ 详解3 31 /* The netlink device handler may be needed early. */ 32 rtnetlink_init();----------- 详解4 33 out: 34 return err; 35 panic: 36 panic("netlink_init: Cannot allocate nl_table\n"); 37 } 38 39 core_initcall(netlink_proto_init);//内核初始化时调用
详解1:创建并初始化了nl_table表数组,这个表是整个netlink实现的最关键的一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中,由该表统一维护,其定义如下:
1 struct netlink_table { 2 struct rhashtable hash;//hash(哈希表)用来索引同种协议类型的不同netlink套接字实例 3 struct hlist_head mc_list;//为多播使用的sock散列表 4 struct listeners __rcu *listeners;//监听者掩码 5 unsigned int flags; 6 unsigned int groups;//协议支持的最大多播组数量 7 struct mutex *cb_mutex; 8 struct module *module; 9 /*定义了一些函数指针,它们会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到*/ 10 int (*bind)(struct net *net, int group); 11 void (*unbind)(struct net *net, int group); 12 bool (*compare)(struct net *net, struct sock *sock); 13 int registered; 14 };
详解2:调用sock_register向内核注册协议处理函数,即将netlink的socket创建处理函数注册到内核中,以后应用层创建netlink类型的socket时将会调用该协议处理函数。netlink_family_ops的定义如下:
1 static const struct net_proto_family netlink_family_ops = { 2 .family = PF_NETLINK, 3 .create = netlink_create, 4 .owner = THIS_MODULE, /* for consistency 8) */ 5 };
这样应用层创建PF_NETLINK(AF_NETLINK)类型的socket()系统调用时将由netlink_create()函数负责处理。
详解3:调用register_pernet_subsys向内核所有的网络命名空间注册”子系统“的初始化和去初始化函数,这里的"子系统”并非指的是netlink子系统,而是一种通用的处理方式,在网络命名空间创建和注销时会调用这里注册的初始化和去初始化函数,当然对于已经存在的网络命名空间,在注册的过程中也会调用其初始化函数。netlink_net_ops定义如下:
1 static struct pernet_operations __net_initdata netlink_net_ops = { 2 .init = netlink_net_init, 3 .exit = netlink_net_exit, 4 };
其中netlink_net_init()会在文件系统中位每个网络命名空间创建一个proc入口,而netlink_net_exit()不用时销毁。
详解4:调用rtnetlink_init()创建NETLINK_ROUTE协议类型的netlink,该种类型的netlink才是当初内核设计netlink的初衷,它用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。至此整个netlink子系统初始化完成。
2 创建内核Netlink套接字
内核创建套接字从函数rtnetlink_init开始分析,定义位于:linux-4.9.73\net\core\rtnetlink.c
1 void __init rtnetlink_init(void) 2 { 3 if (register_pernet_subsys(&rtnetlink_net_ops)) 4 panic("rtnetlink_init: cannot initialize rtnetlink\n");//将rtnetlink的init函数和exit函数注册到内核的每个网络命名空间中 ------- 详解1 5 6 register_netdevice_notifier(&rtnetlink_dev_notifier); 7 8 rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, 9 rtnl_dump_ifinfo, rtnl_calcit); 10 rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL); 11 rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, NULL); 12 rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, NULL); 13 14 rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, NULL); 15 rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, NULL); 16 17 rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, NULL); 18 rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, NULL); 19 rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL); 20 21 rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL); 22 rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL); 23 rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL); 24 25 rtnl_register(PF_UNSPEC, RTM_GETSTATS, rtnl_stats_get, rtnl_stats_dump, 26 NULL); 27 }
详解1:将rtnetlink的init函数和exit函数注册到内核的每个网络命名空间中,对于已经存在的网络命名空间会调用其中个的init函数,即函数rtnetlink_net_init()。rtnetlink_net_ops定义如下;
1 static struct pernet_operations rtnetlink_net_ops = { 2 .init = rtnetlink_net_init, 3 .exit = rtnetlink_net_exit, 4 };
2.1 分析函数rtnetlink_net_init
1 static int __net_init rtnetlink_net_init(struct net *net) 2 { 3 struct sock *sk; 4 struct netlink_kernel_cfg cfg = {// ------- 详解1 5 .groups = RTNLGRP_MAX, 6 .input = rtnetlink_rcv, 7 .cb_mutex = &rtnl_mutex, 8 .flags = NL_CFG_F_NONROOT_RECV, 9 }; 10 11 sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);// ------- 详解2 12 if (!sk) 13 return -ENOMEM; 14 net->rtnl = sk; 15 return 0; 16 }
详解1:首先这里定义了一个 netlink_kernel_cfg结构体实例,该结构体定义位于include\linux\netlink.h如下:
1 /* optional Netlink kernel configuration parameters */ 2 struct netlink_kernel_cfg { 3 unsigned int groups; 4 unsigned int flags; 5 void (*input)(struct sk_buff *skb); 6 struct mutex *cb_mutex; 7 int (*bind)(struct net *net, int group); 8 void (*unbind)(struct net *net, int group); 9 bool (*compare)(struct net *net, struct sock *sk); 10 };
该结构包含了内核 netlink的可选参数。
groups:用于指定最大的多播组;
flags:可以为NL_CFG_F_NONROOT_RECV或NL_CFG_F_NONROOT_SEND,这两个符号前者用来限定非超级用户是否可以绑定到多播组,后者用来限定非超级用户是否可以发送组播;
input指针:用于指定回调函数,该回调函数用于接收和处理来自用户空间的消息(若无需接收来自用户空间的消息可不指定);
最后的三个函数指针实现sock的绑定和解绑定等操作,会添加到nl_table对应的项中去。
rtnetlink_net_init函数中设置groups为RTNLGRP_MAX后指定消息接收处理函数为rtnetlink_rcv,并设置flag为NL_CFG_F_NONROOT_RECV,这表明非超级用户可以绑定到多播组,但是没有设置NL_CFG_F_NONROOT_SEND,这表明非超级用户将不能发送组播消息。
详解2:调用netlink_kernel_create()向当前的网络命名空间创建NETLINK_ROUTE类型的套接字,并指定定义的那个配置结构cfg。netlink_kernel_create()函数定义如下:
1 static inline struct sock * 2 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) 3 { 4 return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);//调用__netlink_kernel_create() 5 }
2.2 函数__netlink_kernel_create:
1 struct sock * 2 __netlink_kernel_create(struct net *net, int unit, struct module *module, 3 struct netlink_kernel_cfg *cfg) 4 { 5 struct socket *sock; 6 struct sock *sk; 7 struct netlink_sock *nlk;------- 详解7 8 struct listeners *listeners = NULL; 9 struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL; 10 unsigned int groups; 11 12 BUG_ON(!nl_table); 13 14 if (unit < 0 || unit >= MAX_LINKS)//简单的参数判断 15 return NULL; 16 17 if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))//创建了一个以PF_NETLINK为地址族的 SOCK_DGRAM类型的 socket 套接字,其协议类型就是作为参数传入的NETLINK_ROUTE 18 return NULL; 19 20 if (__netlink_create(net, sock, cb_mutex, unit, 1) < 0)//向内核初始化netlink套接字 --------- 详解1 21 goto out_sock_release_nosk; 22 23 sk = sock->sk; 24 25 if (!cfg || cfg->groups < 32)---------------- 详解2 26 groups = 32; 27 else 28 groups = cfg->groups; 29 30 listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);//分配listeners内存空间,这里边保存了监听者(监听套接字)的信息 31 if (!listeners) 32 goto out_sock_release; 33 34 sk->sk_data_ready = netlink_data_ready; 35 if (cfg && cfg->input) 36 nlk_sk(sk)->netlink_rcv = cfg->input;------- 详解3 37 38 if (netlink_insert(sk, 0)) ------------ 详解4 39 goto out_sock_release; 40 41 nlk = nlk_sk(sk); 42 nlk->flags |= NETLINK_F_KERNEL_SOCKET;//设置标识NETLINK_KERNEL_SOCKET表明这个netlink套接字是一个内核套接字 43 44 netlink_table_grab(); -------- 详解5 45 if (!nl_table[unit].registered) { 46 nl_table[unit].groups = groups; 47 rcu_assign_pointer(nl_table[unit].listeners, listeners); 48 nl_table[unit].cb_mutex = cb_mutex; 49 nl_table[unit].module = module; 50 if (cfg) { 51 nl_table[unit].bind = cfg->bind; 52 nl_table[unit].unbind = cfg->unbind; 53 nl_table[unit].flags = cfg->flags; 54 if (cfg->compare) 55 nl_table[unit].compare = cfg->compare; 56 } 57 nl_table[unit].registered = 1; 58 } else { 59 kfree(listeners); 60 nl_table[unit].registered++; 61 } 62 netlink_table_ungrab(); 63 return sk; ------- 详解6 64 65 out_sock_release: 66 kfree(listeners); 67 netlink_kernel_release(sk); 68 return NULL; 69 70 out_sock_release_nosk: 71 sock_release(sock); 72 return NULL; 73 }
详解1:调用 __netlink_create()函数向内核初始化netlink套接字 ,其实在用户态创建netlink套接字也是间接调用到该函数。
函数__netlink_create:
1 static int __netlink_create(struct net *net, struct socket *sock, 2 struct mutex *cb_mutex, int protocol, 3 int kern) 4 { 5 struct sock *sk; 6 struct netlink_sock *nlk; 7 8 sock->ops = &netlink_ops;//将sock的操作函数集指针设置为netlink_ops 9 10 sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);//分配sock结构并进行初始化 11 if (!sk) 12 return -ENOMEM; 13 14 sock_init_data(sock, sk);//初始化发送接收消息队列 15 16 nlk = nlk_sk(sk);//初始化数据缓存 17 if (cb_mutex) { 18 nlk->cb_mutex = cb_mutex; 19 } else { 20 nlk->cb_mutex = &nlk->cb_def_mutex; 21 mutex_init(nlk->cb_mutex);//初始化互斥锁 22 lockdep_set_class_and_name(nlk->cb_mutex, 23 nlk_cb_mutex_keys + protocol, 24 nlk_cb_mutex_key_strings[protocol]); 25 } 26 init_waitqueue_head(&nlk->wait);//初始化等待队列 27 28 sk->sk_destruct = netlink_sock_destruct;//设置sk_destruct回调函数 29 sk->sk_protocol = protocol;//设置和协议类型 30 return 0; 31 }
netlink_ops赋值如下:
1 static const struct proto_ops netlink_ops = { 2 .family = PF_NETLINK, 3 .owner = THIS_MODULE, 4 .release = netlink_release, 5 .bind = netlink_bind, 6 .connect = netlink_connect, 7 .socketpair = sock_no_socketpair, 8 .accept = sock_no_accept, 9 .getname = netlink_getname, 10 .poll = datagram_poll, 11 .ioctl = netlink_ioctl, 12 .listen = sock_no_listen, 13 .shutdown = sock_no_shutdown, 14 .setsockopt = netlink_setsockopt, 15 .getsockopt = netlink_getsockopt, 16 .sendmsg = netlink_sendmsg, 17 .recvmsg = netlink_recvmsg, 18 .mmap = sock_no_mmap, 19 .sendpage = sock_no_sendpage, 20 };
详解2:接下来校验groups,默认最小支持32个组播地址(因为后文会看到用户层在绑定地址时最多绑定32个组播地址),但内核也有可能支持大于32个组播地址的情况(Genetlink就属于这种情况)
详解3:初始化函数指针,这里将前文中定义的rtnetlink_rcv注册到了nlk_sk(sk)->netlink_rcv中,这样就设置完了内核态的消息处理函数
详解4:然后调用netlink_insert()函数将本次创建的这个套接字添加到nl_table中去(其核心是调用__netlink_insert()),注册的套接字是通过nl_table中的哈希表来管理的。
详解5:接下来继续初始化nl_table表中对应传入 NETLINK_ROUTE协议类型的数组项,首先会判断是否已经先有同样协议类型的已经注册过了,如果有就不再初始化该表项了,直接释放刚才申请的listeners内存空间然后递增注册个数并返回。这里假定是首次注册NETLINK_ROUTE协议类型的套接字,这里依次初始化了nl_table表项中的groups、listeners、cb_mutex、module、bind、unbind、flags和compare字段。通过前文中cfg的实例分析,这里的初始化的值分别如下:
1 nl_table[NETLINK_ROUTE].groups = RTNLGRP_MAX; 2 nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex; 3 nl_table[ NETLINK_ROUTE ].module = THIS_MODULE; 4 nl_table[NETLINK_ROUTE].bind = NULL; 5 nl_table[NETLINK_ROUTE].unbind = NULL; 6 nl_table[NETLINK_ROUTE].compare = NULL; 7 nl_table[NETLINK_ROUTE].flags= NL_CFG_F_NONROOT_RECV;
这些写值在后面的通信流程中就会使用到。
详解6:在函数的最后返回成功创建的netlink套接字中的sock指针,它会在最先前的rtnetlink_net_init()函数中被保存到net->rtnl中去,注意只有NETLINK_ROUTE协议类型的套接字才会执行这个步骤,因为网络命名空间中专门为其预留了一个sock指针。
至此,内核netlink套接字创建完成。
详解7:netlink套接字结构:netlink_sock
1 struct netlink_sock { 2 /* struct sock has to be the first member of netlink_sock */ 3 struct sock sk; 4 u32 portid;//表示本套接字自己绑定的id号,对于内核来说它就是0 5 u32 dst_portid;//表示目的id号 6 u32 dst_group; 7 u32 flags; 8 u32 subscriptions; 9 u32 ngroups;//表示协议支持多播组数量 10 unsigned long *groups;//保存组位掩码 11 ... 12 void (*netlink_rcv)(struct sk_buff *skb);//保存接收到用户态数据后的处理函数 13 int (*netlink_bind)(struct net *net, int group);//用于协议子协议自身特有的绑定 14 void (*netlink_unbind)(struct net *net, int group);//用于协议子协议自身特有的解绑 15 ... 16 }
3 应用层netlink套接字创建
应用层通过标准的sock API即可使用Netlink完成通信功能,如socket()、sendto()、recv()、sendmsg()和recvmsg()等,这些都是系统调用,内核中对应的定义为sys_xxx。
3.1 创建Netlink套接字
应用层调用socket()函数系统调用创建套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK,在Linux系统中它俩实际为同一种宏;第二个参数可以是SOCK_RAW或SOCK_DGRAM,原始套接字或无连接的数据报套接字)。最后一个参为netlink.h中定义的协议类型,用户可以按需求自行创建上述不同种类的套接字。
例如调用 socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 即创建了一个NETLINK_ROUTE类型的Netlink套接字。接着来具体分析查看内核是如何为用户层创建这个套接字。
定义位于:linux-4.9.73\net\socket.c
函数socket首先做了一些参数检查之后就调用sock_create()函数创建套接字,在创建完成后向内核申请描述符并返回该描述符
1 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 2 { 3 int retval; 4 struct socket *sock; 5 int flags; 6 7 /* Check the SOCK_* constants for consistency. */ 8 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); 9 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); 10 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); 11 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); 12 13 flags = type & ~SOCK_TYPE_MASK; 14 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) 15 return -EINVAL; 16 type &= SOCK_TYPE_MASK; 17 18 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) 19 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; 20 21 retval = sock_create(family, type, protocol, &sock); 22 if (retval < 0) 23 goto out; 24 25 retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); 26 if (retval < 0) 27 goto out_release; 28 29 out: 30 /* It may be already another descriptor 8) Not kernel problem. */ 31 return retval; 32 33 out_release: 34 sock_release(sock); 35 return retval; 36 }
函数sock_create,定义位于:?
1 int sock_create(int family, int type, int protocol, struct socket **res) 2 { 3 return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0); 4 }
函数__sock_create,定义位于:?
1 int __sock_create(struct net *net, int family, int type, int protocol, 2 struct socket **res, int kern) 3 { 4 int err; 5 struct socket *sock; 6 const struct net_proto_family *pf; 7 //参数判断 8 /* 9 * Check protocol is in range 10 */ 11 if (family < 0 || family >= NPROTO) 12 return -EAFNOSUPPORT; 13 if (type < 0 || type >= SOCK_MAX) 14 return -EINVAL; 15 16 /* Compatibility. 17 This uglymoron is moved from INET layer to here to avoid 18 deadlock in module load. 19 */ 20 if (family == PF_INET && type == SOCK_PACKET) { 21 static int warned; 22 if (!warned) { 23 warned = 1; 24 pr_info("%s uses obsolete (PF_INET,SOCK_PACKET)\n", 25 current->comm); 26 } 27 family = PF_PACKET; 28 } 29 err = security_socket_create(family, type, protocol, kern);---------- 详解1 30 if (err) 31 return err; 32 33 /* 34 * Allocate the socket and allow the family to set things up. if 35 * the protocol is 0, the family is instructed to select an appropriate 36 * default. 37 */ 38 sock = sock_alloc();//分配socket实例,它会为其创建和初始化索引节点 39 if (!sock) { 40 net_warn_ratelimited("socket: no more sockets\n"); 41 return -ENFILE; /* Not exactly a match, but its the 42 closest posix thing */ 43 } 44 45 sock->type = type;//将sock->type赋值为传入的SOCK_RAW 46 #ifdef CONFIG_MODULES 47 /* Attempt to load a protocol module if the find failed. 48 * 49 * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user 50 * requested real, full-featured networking support upon configuration. 51 * Otherwise module support will break! 52 */ 53 if (rcu_access_pointer(net_families[family]) == NULL)-------- 详解2 54 request_module("net-pf-%d", family); 55 #endif 56 57 rcu_read_lock(); 58 pf = rcu_dereference(net_families[family]); 59 err = -EAFNOSUPPORT; 60 if (!pf) 61 goto out_release; 62 /* 63 * We will call the ->create function, that possibly is in a loadable 64 * module, so we have to bump that loadable module refcnt first. 65 */ 66 if (!try_module_get(pf->owner))//获取模块的引用计数 67 goto out_release; 68 69 /* Now protected by module ref count */ 70 rcu_read_unlock(); 71 72 err = pf->create(net, sock, protocol, kern);--------- 详解3 73 if (err < 0) 74 goto out_module_put; 75 76 /* 77 * Now to bump the refcnt of the [loadable] module that owns this 78 * socket at sock_release time we decrement its refcnt. 79 */ 80 if (!try_module_get(sock->ops->owner)) 81 goto out_module_busy; 82 83 /* 84 * Now that we're done with the ->create function, the [loadable] 85 * module can have its refcnt decremented 86 */ 87 module_put(pf->owner); 88 err = security_socket_post_create(sock, family, type, protocol, kern); 89 if (err) 90 goto out_sock_release; 91 *res = sock; 92 93 return 0; 94 }
详解1:对创建socket执行安全性检查,security_socket_create这个函数在内核没有启用CONFIG_SECURITY_NETWORK配置时是一个空函数直接返回0,这里可先不考虑。
详解2:在启用内核模块的情况下,这里会到内核net_families数组中查找该family(AF_NETLINK)是否已经注册,如果没有注册就会尝试加载网络子系统模块。其实在内核的netlink初始化函数中已经调用sock_register()完成注册了(见上文第一节详解2)。接下来从net_families数组中获取已经注册的struct net_proto_family结构实例,这里就是上面描述过的netlink_family_ops。
详解3:调用netlink协议的creat()钩子函数执行进一步的创建和初始化操作(即netlink_family_ops中定义的netlink_create()了,见上文第一节详解2),完成之后就释放锁同时释放当前模块的引用计数并返回创建成功的socket。
函数netlink_create,定义位于:linux-4.9.73\net\netlink\af_netlink.c
1 static int netlink_create(struct net *net, struct socket *sock, int protocol, 2 int kern) 3 { 4 struct module *module = NULL; 5 struct mutex *cb_mutex; 6 struct netlink_sock *nlk; 7 int (*bind)(struct net *net, int group); 8 void (*unbind)(struct net *net, int group); 9 int err = 0; 10 11 sock->state = SS_UNCONNECTED;//将socket的状态标记为未连接 12 13 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)//判断套接字的类型是否是SOCK_RAW或SOCK_DGRAM类型的,若不是就不能继续创建 14 return -ESOCKTNOSUPPORT; 15 16 if (protocol < 0 || protocol >= MAX_LINKS)-------- 详解1 17 return -EPROTONOSUPPORT; 18 19 netlink_lock_table(); 20 #ifdef CONFIG_MODULES 21 if (!nl_table[protocol].registered) { 22 netlink_unlock_table(); 23 request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol); 24 netlink_lock_table(); 25 } 26 #endif 27 if (nl_table[protocol].registered && 28 try_module_get(nl_table[protocol].module)) 29 module = nl_table[protocol].module; 30 else 31 err = -EPROTONOSUPPORT; 32 cb_mutex = nl_table[protocol].cb_mutex; 33 bind = nl_table[protocol].bind; 34 unbind = nl_table[protocol].unbind; 35 netlink_unlock_table(); 36 37 if (err < 0) 38 goto out; 39 40 err = __netlink_create(net, sock, cb_mutex, protocol, kern);------- 详解2 41 if (err < 0) 42 goto out_module; 43 44 local_bh_disable(); 45 sock_prot_inuse_add(net, &netlink_proto, 1);//添加协议的引用计数 46 local_bh_enable(); 47 //赋值 48 nlk = nlk_sk(sock->sk); 49 nlk->module = module; 50 nlk->netlink_bind = bind; 51 nlk->netlink_unbind = unbind; 52 out: 53 return err; 54 55 out_module: 56 module_put(module); 57 goto out; 58 }
详解1:接着判断该协议类型的netlink是否已经注册了,由于前文中内核在初始化netlink子系统时已经初始化了NETLINK_ROUTE内核套接字并向nl_table注册,所以这里的几个赋值结果如下::
1 cb_mutex = nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex; 2 module = nl_table[NETLINK_ROUTE].module = THIS_MODULE; 3 bind = nl_table[NETLINK_ROUTE].bind = NULL; 4 unbind = nl_table[NETLINK_ROUTE].unbind = NULL;
详解2:调用__netlink_create()完成核心的创建初始化,详解见上文2.1节详解2
3.2 绑定套接字系统调用
在创建完成套接字后需要调用bind()函数进行绑定,将该套接字绑定到一个特定的地址或者加入一个多播组中,以后内核或其他应用层套接字向该地址单播或向该多播组发送组播消息时即可通过recv()或recvmsg()函数接收消息了。绑定地址时需要使用到sockaddr_nl地址结构,如果使用使用单播则需要将地址本地地址信息填入nl_pid变量并设置nl_groups为0,如果使用多播则将nl_pid设置为0并填充nl_groups为多播地址,如下可将当前进程的PID号作为单播地址进行绑定:
bind(fd, (struct sockaddr *) &local, sizeof(local));
其中bind()的第一个参数:为刚创建的Netlink套接字描述符;
第二个参数:需要绑定的套接字地址;
最后一个参数:地址的长度。这个绑定操作同创建TCP套接字类似,需要制定绑定的端口。
套接字的绑定是有系统调用bind()实现:
1 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) 2 { 3 struct socket *sock; 4 struct sockaddr_storage address; 5 int err, fput_needed; 6 7 sock = sockfd_lookup_light(fd, &err, &fput_needed);//根据用户传入的fd文件描述符向内核查找对应的socket结构 8 if (sock) { 9 err = move_addr_to_kernel(umyaddr, addrlen, &address);//将用户空间传入的地址struct sockaddr拷贝到内核中(会使用到copy_from_user()) 10 if (err >= 0) { 11 err = security_socket_bind(sock, 12 (struct sockaddr *)&address, 13 addrlen);//空函数,跳过安全检查 14 if (!err) 15 err = sock->ops->bind(sock, 16 (struct sockaddr *) 17 &address, addrlen);//------ 详解1 18 } 19 fput_light(sock->file, fput_needed); 20 } 21 return err; 22 }
详解1:然后调用sock->ops->bind()。在创建套接字时调用的__netlink_create()函数中已经将sock->ops赋值为netlink_ops了,如上2.2节详解1,节netlink_bind函数。
3.3 函数netlink_bind
1 static int netlink_bind(struct socket *sock, struct sockaddr *addr, 2 int addr_len) 3 { 4 struct sock *sk = sock->sk; 5 struct net *net = sock_net(sk); 6 struct netlink_sock *nlk = nlk_sk(sk); 7 struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;//将用户传入的地址类型强制转换成了sockaddr_nl类型的地址结构 8 int err; 9 long unsigned int groups = nladdr->nl_groups; 10 bool bound; 11 12 if (addr_len < sizeof(struct sockaddr_nl)) 13 return -EINVAL; 14 15 if (nladdr->nl_family != AF_NETLINK) 16 return -EINVAL; 17 18 /* Only superuser is allowed to listen multicasts */ 19 if (groups) {//如果用户设定了需要绑定的多播地址 -------- 详解1 20 if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV)) 21 return -EPERM; 22 err = netlink_realloc_groups(sk);//-------- 详解2 23 if (err) 24 return err; 25 } 26 27 bound = nlk->bound; 28 if (bound) {//如果已绑定 29 /* Ensure nlk->portid is up-to-date. */ 30 smp_rmb(); 31 32 if (nladdr->nl_pid != nlk->portid)//检查新需要绑定的id号是否等于已经绑定的id号 33 return -EINVAL;//若不相等则返回失败 34 } 35 36 if (nlk->netlink_bind && groups) { ----- 详解3 37 int group; 38 39 for (group = 0; group < nlk->ngroups; group++) { 40 if (!test_bit(group, &groups)) 41 continue; 42 err = nlk->netlink_bind(net, group + 1); 43 if (!err) 44 continue; 45 netlink_undo_bind(group, groups, sk); 46 return err; 47 } 48 } 49 50 /* No need for barriers here as we return to user-space without 51 * using any of the bound attributes. 52 */ 53 if (!bound) {-------- 详解4 54 err = nladdr->nl_pid ? 55 netlink_insert(sk, nladdr->nl_pid) : 56 netlink_autobind(sock);------ 详解5 57 if (err) { 58 netlink_undo_bind(nlk->ngroups, groups, sk); 59 return err; 60 } 61 } 62 63 if (!groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))--------- 详解6 64 return 0; 65 66 netlink_table_grab(); 67 netlink_update_subscriptions(sk, nlk->subscriptions + 68 hweight32(groups) - 69 hweight32(nlk->groups[0])); 70 nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | groups; 71 netlink_update_listeners(sk); 72 netlink_table_ungrab(); 73 74 return 0; 75 }
详解1:如果用户设定了需要绑定的多播地址,这里会去检擦nl_table中注册的套接字是否已经设置了NL_CFG_F_NONROOT_RECV标识,如果没有设置将拒绝用户绑定到组播组,显然在前文中已经看到了NETLINK_ROUTE类型的套接字是设置了这个标识的,所以这里会调用netlink_realloc_groups分配组播空间。
详解2:函数netlink_realloc_groups:
这里会比较验证一下当前套接字中指定的组播地址上限是否大于NETLINK_ROUTE套接字支持的最大地址(这里为RTNLGRP_MAX),由于这个套接字是前面刚刚创建的,所以nlk->ngroups = 0。
然后为其分配内存空间,分配的空间大小为NLGRPSZ(groups)(这是一个取整对齐的宏),分配完成后将新分配的空间清零,首地址保存在nlk->groups中,最后更新nlk->ngroups变量。
1 static int netlink_realloc_groups(struct sock *sk) 2 { 3 struct netlink_sock *nlk = nlk_sk(sk); 4 unsigned int groups; 5 unsigned long *new_groups; 6 int err = 0; 7 8 netlink_table_grab(); 9 10 groups = nl_table[sk->sk_protocol].groups; 11 if (!nl_table[sk->sk_protocol].registered) { 12 err = -ENOENT; 13 goto out_unlock; 14 } 15 16 if (nlk->ngroups >= groups) 17 goto out_unlock; 18 19 new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); 20 if (new_groups == NULL) { 21 err = -ENOMEM; 22 goto out_unlock; 23 } 24 memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0, 25 NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); 26 27 nlk->groups = new_groups; 28 nlk->ngroups = groups; 29 out_unlock: 30 netlink_table_ungrab(); 31 return err; 32 }
详解3:如果netlink套接字子协议存在特有的bind函数且用户指定了需要绑定的组播地址,则调用之为其绑定到特定的组播组中去。现由于NETLINK_ROUTE套接字并不存在nlk->netlink_bind()函数实现,所以这里并不会调用。
详解4:如果本套接字并没有被绑定过(目前就是这种情况),这里会根据用户是否指定了单播的绑定地址来调用不同的函数。首先假定用户空间指定了单播的绑定地址,这里会调用netlink_insert()函数将这个套接字插入到nl_table[NETLINK_ROUTE]数组项的哈希表中去,同时设置nlk_sk(sk)->bound = nlk_sk(sk)->portid = nladdr->nl_pid。我们再假定用户空间没有设置单播的绑定地址,这里会调用netlink_autobind()动态的绑定一个地址。
详解5:函数netlink_autobind()动态的绑定一个地址:
1 static int netlink_autobind(struct socket *sock) 2 { 3 struct sock *sk = sock->sk; 4 struct net *net = sock_net(sk); 5 struct netlink_table *table = &nl_table[sk->sk_protocol]; 6 s32 portid = task_tgid_vnr(current); 7 int err; 8 s32 rover = -4096; 9 bool ok; 10 11 retry: 12 /*先尝试选用当前的进程ID作为端口地址,如果当前进程ID已经绑定过其他的相同protocol套接字则会选用一个负数作为ID号(查找直到存在可用的)*/ 13 cond_resched(); 14 rcu_read_lock(); 15 ok = !__netlink_lookup(table, portid, net); 16 rcu_read_unlock(); 17 if (!ok) { 18 /* Bind collision, search negative portid values. */ 19 if (rover == -4096) 20 /* rover will be in range [S32_MIN, -4097] */ 21 rover = S32_MIN + prandom_u32_max(-4096 - S32_MIN); 22 else if (rover >= -4096) 23 rover = -4097; 24 portid = rover--; 25 goto retry; 26 } 27 28 err = netlink_insert(sk, portid); 29 if (err == -EADDRINUSE) 30 goto retry; 31 32 /* If 2 threads race to autobind, that is fine. */ 33 if (err == -EBUSY) 34 err = 0; 35 36 return err; 37 }
详解6:如果没有指定组播地址且没有分配组播的内存,绑定工作到这里就已经结束了,可以直接返回了。现假定用户指定了需要绑定的组播地址,这里首先调用netlink_update_subscriptions绑定sk->sk_bind_node到nl_table[sk->sk_protocol].mc_list中,同时将加入的组播组数目记录到nlk->subscriptions中,并将组播地址保存到nlk->groups[0]中,最后更新netlink监听位掩码。至此绑定操作结束。
行胜于言,自强不息。
这篇关于linux netlink详解1-netlink初始化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法