——linux版本: 3.14.38
netlink支持用户进程和内核相互交互(两边都可以主动发起),同时还支持用户进程之间相互交互(虽然这种应用通常都采用unix-sock)
但是有一点需要注意,内核不支持接收netlink组播消息
本文将从用户进程发送一个netlink消息开始,对整个netlink消息通信原理进行展开分析
用户进程一般都通过调用sendmsg来向内核或其他进程发送netlink消息(有关sendmsg系统调用的公用部分代码解析将在另一片文章中展开)
/* 用户进程对netlink套接字调用sendmsg()系统调用后,内核执行netlink操作的总入口函数
* @sock - 指向用户进程的netlink套接字,也就是发送方的
* @msg - 承载了发送方传递的netlink消息
* @len - netlink消息长度
* @kiocb - 这个参数在最新内核中已经去掉,这里索性不再进行分析(其实是因为还没开撸这块代码~)
*
* 备注: netlink套接字在创建的过程中(具体是在__netlink_create函数开头),已经和netlink_ops(socket层netlink协议族的通用操作集合)关联,
* 其中注册的sendmsg回调就是指向本函数
*/
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,struct msghdr *msg, size_t len)
{
// 显然,这里定义的addr指向netlink消息的目的地
DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);
// netlink消息不支持传输带外数据
if (msg->msg_flags&MSG_OOB)
return -EOPNOTSUPP;
// 辅助消息处理
if (NULL == siocb->scm)
siocb->scm = &scm;
err = scm_send(sock, msg, siocb->scm, true);
if (msg->msg_namelen) {
// 如果用户进程指定了目的地址,则对其进行合法性检测,然后从中获取单播地址和组播地址
err = -EINVAL;
if (addr->nl_family != AF_NETLINK)
goto out;
dst_portid = addr->nl_pid;
dst_group = ffs(addr->nl_groups);
err = -EPERM;
// 如果有设置目的组播地址,或者目的单播地址不是发往kernel,就需要检查具体的netlink协议是否支持
if ((dst_group || dst_portid) && !netlink_allowed(sock, NL_CFG_F_NONROOT_SEND))
goto out;
netlink_skb_flags |= NETLINK_SKB_DST;
}
else{
// 如果用户进程没有指定目的地址,则使用该netlink套接字默认的(缺省都是0,可以通过connect系统调用指定)
dst_portid = nlk->dst_portid;
dst_group = nlk->dst_group;
}
// 如果该netlink套接字还没有被绑定过,首先执行动态绑定
if (!nlk->portid){
err = netlink_autobind(sock);
}
// 以下这部分只有当内核配置了CONFIG_NETLINK_MMAP选项才生效(暂不分析)
if (netlink_tx_is_mmaped(sk) && msg->msg_iov->iov_base == NULL){
err = netlink_mmap_sendmsg(sk, msg, dst_portid, dst_group,siocb);
}
// 检查需要发送的数据长度是否合法
err = -EMSGSIZE;
if (len > sk->sk_sndbuf - 32)
goto out;
// 分配skb结构空间
err = -ENOBUFS;
skb = netlink_alloc_large_skb(len, dst_group);
// 初始化这个skb中的cb字段
NETLINK_CB(skb).portid = nlk->portid;
NETLINK_CB(skb).dst_group = dst_group;
NETLINK_CB(skb).creds = siocb->scm->creds;
NETLINK_CB(skb).flags = netlink_skb_flags;
// 将数据从msg_iov拷贝到skb中
err = -EFAULT;
memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)
// 执行LSM模块,略过
err = security_netlink_send(sk, skb);
// 至此,netlink消息已经被放入新创建的sbk中,接下来,内核根据单播还是组播消息,执行了不同的发送流程
// 发送netlink组播消息
if (dst_group){
atomic_inc(&skb->users);
netlink_broadcast(sk, skb, dst_portid, dst_group, GFP_KERNEL);
}
// 发送netlink单播消息
err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT);
}
小结:从netlink_sendmsg函数的末尾可以看出,来自用户进程的netlink消息会以netlink_unicast单播方式或netlink_broadcast组播方式进行传递。
所以接下来进一步对这两种传播方式进行展开分析。
/* 以下是内核执行netlink单播消息的发送流程
* @ssk - 源sock结构
* @skb - 属于发送方的承载了netlink消息的skb
* @portid - 目的单播地址
* @nonblock - 1:非阻塞调用,2:阻塞调用
*
* 备注: 以下3种情况都会调用到本函数:
* [1]. kernel --单播--> 用户进程
* [2]. 用户进程 --单播--> kernel
* [3]. 用户进程a --单播--> 用户进程b
*/
int netlink_unicast(struct sock *ssk, struct sk_buff *skb,u32 portid, int nonblock)
{
// 重新调整skb数据区大小
skb = netlink_trim(skb, gfp_any());
// 计算发送超时时间(如果是非阻塞调用,则返回0)
timeo = sock_sndtimeo(ssk, nonblock);
retry:
// 根据源sock结构和目的单播地址,得到目的sock结构
sk = netlink_getsockbyportid(ssk, portid);
// 如果该目的netlink套接字属于内核,则进入第 [2] 种情况下的分支
if (netlink_is_kernel(sk))
return netlink_unicast_kernel(sk, skb, ssk);
// 程序运行到这里,意味着以下属于第 [1]/[3] 种情况下的分支
// 对于发往用户进程的单播消息都要调用BPF过滤
if (sk_filter(sk, skb)){
err = skb->len;
kfree_skb(skb);
sock_put(sk);
return err;
}
// 将该skb绑定到目的用户进程netlink套接字上,如果成功,skb的所有者也从这里开始变更为目的用户进程netlink套接字
err = netlink_attachskb(sk, skb, &timeo, ssk);
// 如果返回值是1,意味着要重新尝试绑定操作
if (err == 1)
goto retry;
if (err)
return err;
// 将该skb发送到目的用户进程netlink套接字
return netlink_sendskb(sk, skb);
}
/* 来自用户进程的netlink消息单播发往内核netlink套接字
* @sk - 目的sock结构
* @skb - 属于发送方的承载了netlink消息的skb
* @ssk - 源sock结构
*
* 备注:skb的所有者在本函数中发生了变化
*/
static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,struct sock *ssk)
{
// 获取目的netlink套接字,也就是内核netlink套接字
struct netlink_sock *nlk = nlk_sk(sk);
// 检查内核netlink套接字是否注册了netlink_rcv回调(就是各个协议在创建内核netlink套接字时通常会传入的input函数)
if (nlk->netlink_rcv != NULL) {
ret = skb->len;
// 设置该skb的所有者是内核的netlink套接字
netlink_skb_set_owner_r(skb, sk);
// 保存该netlink消息的源sock结构
NETLINK_CB(skb).sk = ssk;
// netlink tap机制暂略
netlink_deliver_tap_kernel(sk, ssk, skb);
// 调用内核netlink套接字的协议类型相关的netlink_rcv钩子来处理收到的消息
// 到这里,意味着发往内核的netlink消息已经被成功接收
nlk->netlink_rcv(skb);
} else {
// 如果指定的内核netlink套接字没有注册netlink_rcv回调,就直接丢弃所有收到的netlink消息
kfree_skb(skb);
}
sock_put(sk);
}
/* 将一个指定skb绑定到一个指定的属于用户进程的netlink套接字上
* @sk - 指向目的sock结构
* @skb - 属于发送方的承载了netlink消息的skb
* @timeo - 指向一个超时时间
* @ssk - 指向源sock结构
*/
int netlink_attachskb(struct sock *sk, struct sk_buff *skb,long *timeo, struct sock *ssk)
{
// 获取目的netlink套接字,也就是目的用户进程netlink套接字
nlk = nlk_sk(sk);
/* 在没有内核使能CONFIG_NETLINK_MMAP配置选项时,
* 如果目的netlink套接字上已经接收尚未处理的数据大小超过了接收缓冲区大小,或者目的netlink套接字被设置了拥挤标志,
* 意味着该sbk不能立即被目的netlink套接字接收,需要加入等待队列
*/
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED, &nlk->state)) && !netlink_skb_is_mmaped(skb)){
// 申请一个等待队列
DECLARE_WAITQUEUE(wait, current);
// 如果传入的超时时间为0,意味着非阻塞调用,则丢弃这条netlink消息,并返回EAGAIN
if (!*timeo) {
/* 如果该netlink消息对应的源sock结构不存在,或者该netlink消息来自kernel
* 则对目的netlink套接字设置缓冲区溢出状态
*/
if (!ssk || netlink_is_kernel(ssk))
netlink_overrun(sk);
sock_put(sk);
kfree_skb(skb);
return -EAGAIN;
}
// 程序运行到这里意味着是阻塞调用
// 改变当前进程状态为可中断
__set_current_state(TASK_INTERRUPTIBLE);
// 将目的netlink套接字加入等待队列(同时意味着进入睡眠,猜测)
add_wait_queue(&nlk->wait, &wait);
// 程序到这里意味着被唤醒了(猜测)
// 如果接收条件还是不满足,则要计算剩余的超时时间
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(NETLINK_CONGESTED, &nlk->state)) && !sock_flag(sk, SOCK_DEAD))
*timeo = schedule_timeout(*timeo);
// 改变当前进程状态为运行
__set_current_state(TASK_RUNNING);
// 将目的netlink套接字从等待队列中删除
remove_wait_queue(&nlk->wait, &wait);
sock_put(sk);
// 目测是信号相关处理,略
if (signal_pending(current)) {
kfree_skb(skb);
return sock_intr_errno(*timeo);
}
// 返回1,意味着还将要再次调用本函数
return 1;
}
// 设置该skb的所有者是目的用户进程netlink套接字,这里才是真正的绑定操作,上面都是前奏
netlink_skb_set_owner_r(skb, sk);
}
/* 将指定skb发送到目的用户进程netlink套接字(显然,本函数只是个封装)
* @sk - 目的用户进程netlink套接字
* @skb - 指向一个承载了netlink消息的skb
* @返回值 - 成功返回实际发送的netlink消息长度
*
* 备注:该skb调用本函数前已经绑定到该用户进程netlink套接字
*/
int netlink_sendskb(struct sock *sk, struct sk_buff *skb)
{
int len = __netlink_sendskb(sk, skb);
sock_put(sk);
return len
}
/* 将指定skb发送到指定用户进程netlink套接字,换句话说,实际也就是将该skb放入了该套接字的接收队列中
* @sk - 指向一个用户进程netlink套接字
* @skb - 指向一个承载了netlink消息的skb
*
* 备注:该skb调用本函数前已经绑定到该用户进程netlink套接字
* 之所以说组播消息不支持发往内核的根本原因就在本函数中:
* 本函数最后通过调用sk_data_ready钩子函数来通知所在套接字接收组播消息,
* 而内核netlink套接字实际屏蔽了这个钩子函数,也就意味着永远无法接收到组播消息
*/
static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb)
{
// 这里可以知道,成功时的返回值就是skb中承载的netlink消息长度
int len = skb->len;
// 目前不知道tap系列函数的用处
netlink_deliver_tap(skb);
// 将skb放入该netlink套接字接收队列末尾
skb_queue_tail(&sk->sk_receive_queue, skb);
// 执行sk_data_ready回调通知该套接字有数据可读
sk->sk_data_ready(sk, len);
return len;
}
小结:至此,一个来自用户进程的netlink单播消息的传递流程基本分析完毕
可以看出,流程的终点,一个是内核netlink套接字具体协议类型相关的netlink_rcv回调,另一个是目的用户进程netlink套接字的接收队列
还可以看出,跟内核发往用户进程的netlink单播消息流程存在部分重合
/* 发送netlink组播消息(显然这只是个封装)
* @ssk - 源sock结构
* @skb - 属于发送方的承载了netlink消息的skb
* @portid - 目的单播地址
* @group - 目的组播地址
*
* 备注: 以下2种情况都会调用到本函数:
* [1]. 用户进程 --组播--> 用户进程
* [2]. kernel --组播--> 用户进程
*/
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,u32 group, gfp_t allocation)
{
return netlink_broadcast_filtered(ssk, skb, portid, group, allocation,NULL, NULL);
}
/* 发送netlink组播消息
* @ssk - 源sock结构
* @skb - 属于发送方的承载了netlink消息的skb
* @portid - 目的单播地址
* @group - 目的组播地址
* @filter - 指向一个过滤器
* @filter_data - 指向一个传递给过滤器的参数
*/
int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb, u32 portid,u32 group, gfp_t allocation,int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),void *filter_data)
{
// 获取源sock所在net命名空间
struct net *net = sock_net(ssk);
// 重新调整skb数据区大小
skb = netlink_trim(skb, allocation);
info.exclude_sk = ssk;
info.net = net;
info.portid = portid;
info.group = group;
info.failure = 0;
info.delivery_failure = 0;
info.congested = 0;
info.delivered = 0;
info.allocation = allocation;
info.skb = skb;
info.skb2 = NULL;
info.tx_filter = filter;
info.tx_data = filter_data;
// 遍历该netlink套接字所在协议类型中所有阅订了组播功能的套接字,然后尝试向其发送该组播消息
sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list)
do_one_broadcast(sk, &info);
// 至此,netlink组播消息已经发送完毕
// 释放skb结构
consume_skb(skb);
// 发送失败处理
if (info.delivery_failure){
kfree_skb(info.skb2);
return -ENOBUFS;
}
// 释放skb2结构
consume_skb(info.skb2);
// 发送成功处理
if (info.delivered){
if (info.congested && (allocation & __GFP_WAIT))
yield();
return 0;
}
}
/* 尝试向指定用户进程netlink套接字发送组播消息
* @sk - 指向一个sock结构,对应一个用户进程netlink套接字
* @p - 指向一个netlink组播消息的管理块
*
* 备注:传入的netlink套接字跟组播消息属于同一种netlink协议类型,并且这个套接字开启了组播阅订,除了这些,其他信息(比如阅订了具体哪些组播)都是不确定的
*/
static int do_one_broadcast(struct sock *sk,struct netlink_broadcast_data *p)
{
// 如果源sock和目的sock是同一个则直接返回
if (p->exclude_sk == sk)
goto out;
// 如果目的单播地址就是该netlink套接字,或者目的组播地址超出了该netlink套接字的上限,或者该netlink套接字没有阅订这条组播消息,都直接返回
if (nlk->portid == p->portid || p->group - 1 >= nlk->ngroups || !test_bit(p->group - 1, nlk->groups))
goto out;
// 如果两者不属于同一个net命名空间,则直接返回
if (!net_eq(sock_net(sk), p->net))
goto out;
// 如果netlink组播消息的管理块携带了failure标志, 则对该netlink套接字设置缓冲区溢出状态
if (p->failure){
netlink_overrun(sk);
goto out;
}
// 设置skb2,其内容来自skb
if (p->skb2 == NULL){
if (skb_shared(p->skb))
p->skb2 = skb_clone(p->skb, p->allocation);
else{
p->skb2 = skb_get(p->skb);
skb_orphan(p->skb2);
}
}
if (p->skb2 == NULL){
// 到这里如果skb2还是NULL,意味着上一步中clone失败
netlink_overrun(sk);
p->failure = 1;
if (nlk->flags & NETLINK_BROADCAST_SEND_ERROR)
p->delivery_failure = 1;
}
else if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)){
// 如果传入了发送过滤器,但是过滤不通过,释放skb2
kfree_skb(p->skb2);
p->skb2 = NULL;
}
else if (sk_filter(sk, p->skb2)){
// 如果BPF过滤不通过,释放skb2
kfree_skb(p->skb2);
p->skb2 = NULL;
}
else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0){
// 如果将承载了组播消息的skb发送到该用户进程netlink套接字失败
netlink_overrun(sk);
if (nlk->flags & NETLINK_BROADCAST_SEND_ERROR)
p->delivery_failure = 1;
}
else{
// 发送成功最终会进入这里
p->congested |= val;
p->delivered = 1;
p->skb2 = NULL; // 这里没有释放skb2,而是在上层函数进行释放,原因是因为上层遍历时可能要反复进入本函数,所以skb2要被反复用到
}
}
/* 将携带了netlink组播消息的skb发送到指定目的用户进程netlink套接字
* @返回值 -1 :套接字接收条件不满足
* 0 :netlink组播消息发送成功,套接字已经接收但尚未处理数据长度小于等于其接收缓冲的1/2
* 1 :netlink组播消息发送成功,套接字已经接收但尚未处理数据长度大于其接收缓冲的1/2(这种情况似乎意味着套接字处于拥挤状态)
*
* 备注:到本函数这里,已经确定了传入的netlink套接字跟组播消息匹配正确;
* netlink组播消息不支持阻塞
*/
static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
{
// 判断该netlink套接字是否满足接收条件
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf && !test_bit(NETLINK_CONGESTED, &nlk->state)){
// 如果满足接收条件,则设置skb的所有者是该netlink套接字
netlink_skb_set_owner_r(skb, sk);
// 将skb发送到该netlink套接字,实际也就是将该skb放入了该套接字的接收队列中
__netlink_sendskb(sk, skb);
// 这里最后判断该netlink套接字的已经接收尚未处理数据长度是否大于其接收缓冲的1/2
return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
}
// 程序运行到这里意味着接收条件不满足
return -1;
}
小结:至此,一个来自用户进程的netlink组播消息的传递流程基本分析完毕
可以看出,流程的终点只有一个,就是目的用户进程netlink套接字的接收队列
还可以看出,跟内核发往用户进程的netlink组播消息流程存在重合
以上就是用户进程向内核或其他进程发送netlink消息的完整流程,显然,其中重合了大量由内核发送netlink消息到用户进程的流程。
所以接下来内核发送netlink消息到用户进程的分析中,重合部分的代码将不再展开。
/* 内核提供了API函数nlmsg_unicast供各个netlink协议调用,来向用户进程发送netlink单播消息(显然,这只是个封装)
* @sk - 指向源sock结构,也就是内核netlink套接字
* @skb - 指向一个承载了单播netlink消息的skb
* @portid - 目的单播地址
*
* 备注:可以看出,内核只会以非阻塞的形式往用户进程发送netlink单播消息
*/
static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)
{
netlink_unicast(sk, skb, portid, MSG_DONTWAIT);
}
/* 内核提供了API函数nlmsg_multicast供各个netlink协议调用,来向用户进程发送netlink组播消息(显然,这只是个封装)
* 指向源sock结构
* 指向一个承载了组播netlink消息的skb
* 目的单播地址
* 目的组播地址
*/
static inline int nlmsg_multicast(struct sock *sk, struct sk_buff *skb,u32 portid, unsigned int group, gfp_t flags)
{
NETLINK_CB(skb).dst_group = group;
netlink_broadcast(sk, skb, portid, group, flags);
}
小结:至此,内核发送netlink消息到用户进程的接口分析完毕
内核不管是发送单播还是组播消息,流程的终点都只有一个,就是目的用户进程netlink套接字的接收队列
本文最后要分析的就是netlink消息接收流程了,由于发往内核的netlink消息在调用协议类型相关的netlink_rcv钩子时就已经意味着接收完成了,也就没有展开分析的必要。
所以接下来实际上就是对用户进程调用recvmsg接收来自内核或者其他进程的netlink消息流程展开分析(有关recvmsg系统调用的公用部分代码解析将在另一片文章中展开)
/* 用户进程对netlink套接字调用recvmsg()系统调用后,内核执行netlink操作的总入口函数
* @sock - 指向用户进程的netlink套接字,也就是接收方的
* @msg - 用于存放接收到的netlink消息
* @len - 用户空间支持的netlink消息接收长度上限
* @flags - 跟本次接收操作有关的标志位集合(主要来源于用户空间)
*/
static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,struct msghdr *msg, size_t len,int flags)
{
int noblock = flags&MSG_DONTWAIT;
// netlink消息不支持接收带外数据
if (flags&MSG_OOB)
return -EOPNOTSUPP;
// 从套接字的接收队列中接收数据
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (skb == NULL)
goto out;
// 程序运行到这里意味着已经获取到一个skb
data_skb = skb;
// 如果收到的skb中承载的netlink消息长度大于用户空间接收缓存的最大长度,则设置MSG_TRUNC标志,并将实际接收长度改为接收缓存的长度
copied = data_skb->len;
if (len < copied){
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
// 计算transport layer相对缓冲区头部的偏移量(目前不知道干嘛用)
skb_reset_transport_header(data_skb);
// 将skb中的数据拷贝到iovec结构的数据缓冲区
err = skb_copy_datagram_iovec(data_skb, 0, msg->msg_iov, copied);
// 填充返回给用户进程的地址信息
if (msg->msg_name){
DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);
addr->nl_family = AF_NETLINK;
addr->nl_pad = 0;
addr->nl_pid = NETLINK_CB(skb).portid;
addr->nl_groups = netlink_group_mask(NETLINK_CB(skb).dst_group);
msg->msg_namelen = sizeof(*addr);
}
// 处理netlink辅助消息
if (nlk->flags & NETLINK_RECV_PKTINFO)
netlink_cmsg_recv_pktinfo(msg, skb);
if (NULL == siocb->scm){
memset(&scm, 0, sizeof(scm));
siocb->scm = &scm;
}
siocb->scm->creds = *NETLINK_CREDS(skb);
// 如果用户进程recvmsg传入了MSG_TRUNC标志,这里重新将返回的长度值改为skb实际收到的数据长度
if (flags & MSG_TRUNC)
copied = data_skb->len;
// 释放承载了该netlink消息的skb
skb_free_datagram(sk, skb);
// 如果有需要还要执行dump操作(执行dump操作的通常是内核,用户进程执行dump操作需要进行确认)
if (nlk->cb_running && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
ret = netlink_dump(sk);
scm_recv(sock, msg, siocb->scm, flags);
out:
// 正常情况下,程序运行到这里意味着一个承载了netlink消息的skb已经处理完毕
// 最后在条件合适的情况下将会唤醒该netlink套接字的等待队列中的用户进程
netlink_rcv_wake(sk);
return err ? : copied;
}
至此,netlink通信原理全部分析完毕,当然本文只是针对通用的通信流程进行了展开。
具体协议类型的netlink还拥有自己私有的消息处理方式,针对几个重要的具体协议类型(NETLINK_ROUTE、NETLINK_GENERIC),将会另写文章分别进行分析