文章目录
这篇笔记重点介绍了Netlink协议族对典型socket操作的实现,有创建、绑定、连接、数据发送、数据接收过程,这里重点关注单播过程的实现,组播见 这里。
数据结构
传输控制块: netlink_sock
内核中的Netlink套接字对象。
struct netlink_sock {
/* struct sock has to be the first member of netlink_sock */
struct sock sk;
u32 pid; // 该套接字的标识,相当于端口号,见netlink_bind()
u32 dst_pid; // connect()调用可以设置套接字的目的PID和目的组
u32 dst_group;
u32 flags; // 见下文
u32 subscriptions; // 下面3个字段组播相关
u32 ngroups;
unsigned long *groups;
unsigned long state; // 等待队列控制消息接收过程,见下方
wait_queue_head_t wait;
struct netlink_callback *cb;
struct mutex *cb_mutex; // 协议互斥锁,见下问
struct mutex cb_def_mutex;
void (*netlink_rcv)(struct sk_buff *skb); // 消息接收回调函数,由具体协议实现提供
struct module *module;
};
- flags
可取两个值,NETLINK_KERNEL_SOCKET表示该socket是由内核创建的,NETLINK_RECV_PKTINFO的作用待确认;
- 互斥锁
可以在创建时为套接字指定一个互斥锁,这样可以实现多个socket共用一个锁,进而可以达到一定的同步效果。否则内核为该套接字使用单独的锁,即cb_def_mutex。
- 等待队列
state和wait两个字段配合使用,控制消息的接收过程。
state是个bool值,标识是否发生了接收错误,其设置见netlink_overrun()。当有人向该socket发送数据时,内核会检查该socket的接收buffer是否还有空间,如果没有则将数据发送方阻塞到该等待队列上,该过程见下面的attach_skb()。
套接字操作集: netlink_ops
Netlink协议族支持如下套接字操作,可以看出,该协议族仅支持部分标准调用。
static const struct proto_ops netlink_ops = {
.family = PF_NETLINK,
.owner = THIS_MODULE,
.release = netlink_release,
.bind = netlink_bind,
.connect = netlink_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = netlink_getname,
.poll = datagram_poll,
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = netlink_setsockopt,
.getsockopt = netlink_getsockopt,
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
协议族地址: sockaddr_nl
Netlink协议族的地址表示了Netlink消息的目的地,可以是单播地址,也可以是多播地址。
struct sockaddr_nl
{
sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* multicast groups mask */
};
- nl_pid
如果为0,那么表示目的地址为内核,否则代表某个用户态进程。
有些资料将该字段解释为进程ID是不恰当的,它标识的实际上是一个netlink套接字,如果一个进程只有一个netlink套接字,那么用进程ID来设置该字段是ok的,但是有多个netlink套接字时就会有问题,此时,应用程序可以有两种方式指定该字段:1)在bind(2)之前指定一个进程内唯一的标识即可;2)指定为0,然后调用bind(2),由内核来分配一个唯一的标识。
- nl_groups
这是一个bitmask,它按位表示多播组,其长度为32位,因此每个协议也最多可以有32个多播组。组播的好处就是消息可以同时被多个接收者接收,如果在bind(2)时指定该字段非0,那么该Netlink套接字就会监听该协议的对应组播消息;否则该字段应该保持为00,此时就不会收到组播消息。注意:组播消息的发送和接收需要有效用户ID为0,或者具有CAP_NET_ADMIN权限。
创建: netlink_create()
static int netlink_create(struct net *net, struct socket *sock, int protocol)
{
struct module *module = NULL;
struct mutex *cb_mutex;
struct netlink_sock *nlk;
int err = 0;
// 初始化socket状态为未连接状态
sock->state = SS_UNCONNECTED;
// NETLINK协议族只支持RAW和DGRAM两个类型的报文,不过内核对这两种类型并不做区分
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
return -ESOCKTNOSUPPORT;
// 协议号的合法性判断
if (protocol < 0 || protocol >= MAX_LINKS)
return -EPROTONOSUPPORT;
// 从全局的netlink_table数组中找到协议对应的互斥锁
netlink_lock_table();
#ifdef CONFIG_MODULES
// 模块动态加载
if (!nl_table[protocol].registered) {
netlink_unlock_table();
request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
netlink_lock_table();
}
#endif
if (nl_table[protocol].registered &&
try_module_get(nl_table[protocol].module))
module = nl_table[protocol].module;
cb_mutex = nl_table[protocol].cb_mutex;
netlink_unlock_table();
// 调用更底层函数创建传输控制块sock对象
err = __netlink_create(net, sock, cb_mutex, protocol);
if (err < 0)
goto out_module;
// 协议的使用计数加1
local_bh_disable();
sock_prot_inuse_add(net, &netlink_proto, 1);
local_bh_enable();
nlk = nlk_sk(sock->sk);
nlk->module = module;
out:
return err;
out_module:
module_put(module);
goto out;
}
__netlink_create()
static int __netlink_create(struct net *net, struct socket *sock,
struct mutex *cb_mutex, int protocol)
{
struct sock *sk;
struct netlink_sock *nlk;
// 这步很关键,初始化套接字操作函数集合,决定了后续socket相关调用对应的处理函数
sock->ops = &netlink_ops;
// 为传输控制块分配空间并对其进行初始化
sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto);
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk);
// 设置传输控制块的协议的互斥锁
nlk = nlk_sk(sk);
if (cb_mutex)
nlk->cb_mutex = cb_mutex;
else {
nlk->cb_mutex = &nlk->cb_def_mutex;
mutex_init(nlk->cb_mutex);
}
init_waitqueue_head(&nlk->wait);
// 传输块清理函数和协议号记录
sk->sk_destruct = netlink_sock_destruct;
sk->sk_protocol = protocol;
return 0;
}
绑定: netlink_bind()
绑定后,用户态才能够通过该socket接收指定来源的消息。
static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
{
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
int err;
// 协议族检查
if (nladdr->nl_family != AF_NETLINK)
return -EINVAL;
/* Only superuser is allowed to listen multicasts */
// 超级用户才能接收组播消息
if (nladdr->nl_groups) {
if (!netlink_capable(sock, NL_NONROOT_RECV))
return -EPERM;
err = netlink_realloc_groups(sk); // 为传输控制块分配groups内存
if (err)
return err;
}
if (nlk->pid) {
if (nladdr->nl_pid != nlk->pid) // 不允许重复绑定相同的pid
return -EINVAL;
} else {
// 用户态指定,或者由内核自动分配port id
err = nladdr->nl_pid ?
netlink_insert(sk, net, nladdr->nl_pid) :
netlink_autobind(sock);
if (err)
return err;
}
// 没有绑定组播地址,并且groups信息也为空,那么不需要更新它们,结束绑定过程
if (!nladdr->nl_groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
return 0;
// 组播相关处理
netlink_table_grab();
netlink_update_subscriptions(sk, nlk->subscriptions +
hweight32(nladdr->nl_groups) - hweight32(nlk->groups[0]));
nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | nladdr->nl_groups;
netlink_update_listeners(sk);
netlink_table_ungrab();
return 0;
}
特别注意:上述逻辑表明,netlink套接字虽然不允许重复绑定到不同的pid,但是可以通过多此调用bind()更新要接收的多播组信息。
netlink_autobind()
static int netlink_autobind(struct socket *sock)
{
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash; // 找到协议对应的pid哈希表
struct hlist_head *head;
struct sock *osk;
struct hlist_node *node;
s32 pid = current->tgid; // pid初始值为线程组ID
int err;
static s32 rover = -4097;
retry:
cond_resched();
netlink_table_grab();
// 遍历哈希表,找一个尚未被使用的标识保存到pid中
head = nl_pid_hashfn(hash, pid);
sk_for_each(osk, node, head) {
if (!net_eq(sock_net(osk), net))
continue;
if (nlk_sk(osk)->pid == pid) {
/* Bind collision, search negative pid values. */
pid = rover--;
if (rover > -4097)
rover = -4097;
netlink_table_ungrab();
goto retry;
}
}
netlink_table_ungrab();
// 找到了可用的pid,将sk插入哈希表中
err = netlink_insert(sk, net, pid);
if (err == -EADDRINUSE)
goto retry;
/* If 2 threads race to autobind, that is fine. */
if (err == -EBUSY)
err = 0;
return err;
}
netlink_insert()
该函数将一个已完成绑定的Netlink套接字的传输控制块对象加入其协议对象的pid哈希表中。
@pid:选定好的port id,该sk将会绑定到该sk上;
static int netlink_insert(struct sock *sk, struct net *net, u32 pid)
{
struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
struct hlist_head *head;
int err = -EADDRINUSE;
struct sock *osk;
struct hlist_node *node;
int len;
netlink_table_grab();
// 检查该pid是否被占用
head = nl_pid_hashfn(hash, pid);
len = 0;
sk_for_each(osk, node, head) {
if (net_eq(sock_net(osk), net) && (nlk_sk(osk)->pid == pid))
break;
len++;
}
if (node) // 如果找到,说明被占用了,返回错误
goto err;
err = -EBUSY;
if (nlk_sk(sk)->pid)
goto err;
// 检查套接字个数是否过多
err = -ENOMEM;
if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))
goto err;
// 将sk保存到哈希表中
if (len && nl_pid_hash_dilute(hash, len))
head = nl_pid_hashfn(hash, pid);
hash->entries++;
nlk_sk(sk)->pid = pid; // 将pid保存到sk对象中
sk_add_node(sk, head);
err = 0;
err:
netlink_table_ungrab();
return err;
}
从实现上可以看出,绑定过程就是为传输控制块分配一个独有的port id,然后把传输控制块对象加入协议的pid哈希表中。
连接: netlink_connect()
Netlink协议族套接字执行connect()之后,该套接字后续将只能向该目的地址发送消息。
static int netlink_connect(struct socket *sock, struct sockaddr *addr,
int alen, int flags)
{
int err = 0;
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
// 向一个空地址连接可以取消先前connect()的执行效果
if (addr->sa_family == AF_UNSPEC) {
sk->sk_state = NETLINK_UNCONNECTED;
nlk->dst_pid = 0;
nlk->dst_group = 0;
return 0;
}
if (addr->sa_family != AF_NETLINK)
return -EINVAL;
/* Only superuser is allowed to send multicasts 检查组播发送权限 */
if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
// 如果尚未绑定则先绑定
if (!nlk->pid)
err = netlink_autobind(sock);
// 保存目的地址信息
if (err == 0) {
sk->sk_state = NETLINK_CONNECTED;
nlk->dst_pid = nladdr->nl_pid;
nlk->dst_group = ffs(nladdr->nl_groups);
}
return err;
}
连接过程就是将指定的目的地址存入传输控制块的对应字段中。
消息发送: netlink_sendmsg()
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len)
{
struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *addr = msg->msg_name;
u32 dst_pid;
u32 dst_group;
struct sk_buff *skb;
int err;
struct scm_cookie scm;
if (msg->msg_flags&MSG_OOB) // 不支持OOB
return -EOPNOTSUPP;
if (NULL == siocb->scm)
siocb->scm = &scm;
err = scm_send(sock, msg, siocb->scm); // cookie数据是什么?
if (err < 0)
return err;
if (msg->msg_namelen) { // 指定了目的地,对组播发送进行权限检查
if (addr->nl_family != AF_NETLINK)
return -EINVAL;
dst_pid = addr->nl_pid;
// 如果是组播消息,那么校验其发送权限
dst_group = ffs(addr->nl_groups);
if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
} else {
// 本次发送没有指定目的地址,使用传输控制块中的目的地址,只有连接过的socket这两个字段才有值,
// 见连接调用实现。特别的,如果不指定也未连接过,那么这两个默认都为0,此时会发送消息给内核
dst_pid = nlk->dst_pid;
dst_group = nlk->dst_group;
}
// 自身尚未绑定,自动完成绑定过程
if (!nlk->pid) {
err = netlink_autobind(sock);
if (err)
goto out;
}
err = -EMSGSIZE;
if (len > sk->sk_sndbuf - 32) // 发送buffer不足
goto out;
// 分配skb
err = -ENOBUFS;
skb = alloc_skb(len, GFP_KERNEL);
if (skb == NULL)
goto out;
// 拷贝控制信息
NETLINK_CB(skb).pid = nlk->pid;
NETLINK_CB(skb).dst_group = dst_group;
NETLINK_CB(skb).loginuid = audit_get_loginuid(current);
NETLINK_CB(skb).sessionid = audit_get_sessionid(current);
security_task_getsecid(current, &(NETLINK_CB(skb).sid));
memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
/* What can I do? Netlink is asynchronous, so that
we will have to save current capabilities to
check them, when this message will be delivered
to corresponding kernel module. --ANK (980802)
*/
// 拷贝Netlink消息部分
err = -EFAULT;
if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
kfree_skb(skb);
goto out;
}
// Selinux检查
err = security_netlink_send(sk, skb);
if (err) {
kfree_skb(skb);
goto out;
}
// 先进行组播发送,然后进行单播发送
if (dst_group) {
atomic_inc(&skb->users);
netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);
}
err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
return err;
}
单播发送: netlink_unicast()
该函数可以完成如下方向上的Netlink消息单播发送:
- 用户态套接字–>内核;
- 内核–>用户态套接字;
- 用户态套接字–>用户态套接字;
@ssk: skb来源socket
@pid:消息的目的pid
@nonblock: 是否为非阻塞发送
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
{
struct sock *sk;
int err;
long timeo;
skb = netlink_trim(skb, gfp_any());
// 确定阻塞休眠时间
timeo = sock_sndtimeo(ssk, nonblock);
retry:
// 查找该skb的目的接收者对应的传输控制块对象
sk = netlink_getsockbypid(ssk, pid);
if (IS_ERR(sk)) {
kfree_skb(skb);
return PTR_ERR(sk);
}
// 如果消息的目的地是内核,那么由netlink_unicast_kernel()完成后续流程
if (netlink_is_kernel(sk))
return netlink_unicast_kernel(sk, skb);
// 后续流程处理接收者为用户态套接字的情况
// 先让消息通过传输控制看上挂接的过滤器
if (sk_filter(sk, skb)) {
err = skb->len;
kfree_skb(skb);
sock_put(sk);
return err;
}
// 将skb和接收者的传输控制块关联
err = netlink_attachskb(sk, skb, &timeo, ssk);
if (err == 1)
goto retry;
if (err)
return err;
// 将skb放入接收者sk的接收队列中,然后唤醒它
return netlink_sendskb(sk, skb);
}
单播发送给内核: netlink_unicast_kernel()
static inline int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb)
{
int ret;
struct netlink_sock *nlk = nlk_sk(sk);
ret = -ECONNREFUSED;
// 将skb交给协议的接收回调继续处理
if (nlk->netlink_rcv != NULL) {
ret = skb->len;
skb_set_owner_r(skb, sk);
nlk->netlink_rcv(skb);
}
kfree_skb(skb);
sock_put(sk);
return ret;
}
在笔记Netlink协议族中有介绍到,内核态Netlink协议在向协议族注册协议的时候都指定了一个接收回调函数,并且将该回调函数保存在了传输控制块的netlink_rcv字段中,所以当消息是发送给内核时,只需要匹配到该传输控制块,然后回调该函数即可。
单播发送给用户态套接字
netlink_attachskb()
该函数主要检查接收者的接收buffer是否能够容纳要接收的skb,如果不能,那么会休眠等待内存可用。
该函数的返回值见函数注释。
/*
* Attach a skb to a netlink socket.
* The caller must hold a reference to the destination socket. On error, the
* reference is dropped. The skb is not send to the destination, just all
* all error checks are performed and memory in the queue is reserved.
* Return values:
* < 0: error. skb freed, reference to sock dropped.
* 0: continue
* 1: repeat lookup - reference dropped while waiting for socket memory.
*/
int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
long *timeo, struct sock *ssk)
{
struct netlink_sock *nlk;
nlk = nlk_sk(sk);
if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || // 接收buffer不足
test_bit(0, &nlk->state)) { // state非0代表有错误发生
DECLARE_WAITQUEUE(wait, current);
// 调用者指定了非阻塞操作,那么返回失败
if (!*timeo) {
// 源socket为是内核态套接字时会向目的socket报告错误
if (!ssk || netlink_is_kernel(ssk))
netlink_overrun(sk);
sock_put(sk);
kfree_skb(skb);
return -EAGAIN;
}
// 调用进程将阻塞到等待队列上
__set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&nlk->wait, &wait);
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
test_bit(0, &nlk->state)) &&
!sock_flag(sk, SOCK_DEAD))
*timeo = schedule_timeout(*timeo);
// 被唤醒后重新
__set_current_state(TASK_RUNNING);
remove_wait_queue(&nlk->wait, &wait);
sock_put(sk);
if (signal_pending(current)) { // 被信号打断会返回失败
kfree_skb(skb);
return sock_intr_errno(*timeo);
}
return 1; // 返回1让调用者进行重试
}
// 一切正常,将skb的属主设置成该传输控制块,返回0继续skb的接收过程
skb_set_owner_r(skb, sk);
return 0;
}
netlink_sendskb()
int netlink_sendskb(struct sock *sk, struct sk_buff *skb)
{
int len = skb->len;
// 将skb放入接收者的接收队列中,然后唤醒它(可能正在阻塞接收)
skb_queue_tail(&sk->sk_receive_queue, skb);
// 用户态套接字使用的是标准的sock_def_readable(),内核态套接字使用的是netlink_data_ready()
sk->sk_data_ready(sk, len);
sock_put(sk);
return len;
}
消息接收: netlink_recvmsg()
static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
struct msghdr *msg, size_t len,
int flags)
{
struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
struct scm_cookie scm;
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
int noblock = flags&MSG_DONTWAIT;
size_t copied;
struct sk_buff *skb;
int err;
if (flags&MSG_OOB) // 不支持OOB数据
return -EOPNOTSUPP;
copied = 0;
// 从传输控制块的接收队列中获取skb,调用者可能会阻塞等待有数据可接收
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (skb == NULL)
goto out;
msg->msg_namelen = 0;
copied = skb->len;
if (len < copied) { // 接收buffer不足,设置截断标记
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
// 将skb中的数据拷贝到msg中
skb_reset_transport_header(skb);
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
// 设置消息来源地址
if (msg->msg_name) {
struct sockaddr_nl *addr = (struct sockaddr_nl *)msg->msg_name;
addr->nl_family = AF_NETLINK;
addr->nl_pad = 0;
addr->nl_pid = NETLINK_CB(skb).pid;
addr->nl_groups = netlink_group_mask(NETLINK_CB(skb).dst_group);
msg->msg_namelen = sizeof(*addr);
}
// 接收控制信息
if (nlk->flags & NETLINK_RECV_PKTINFO)
netlink_cmsg_recv_pktinfo(msg, skb);
// SCM不懂
if (NULL == siocb->scm) {
memset(&scm, 0, sizeof(scm));
siocb->scm = &scm;
}
siocb->scm->creds = *NETLINK_CREDS(skb);
if (flags & MSG_TRUNC)
copied = skb->len;
skb_free_datagram(sk, skb);
if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
netlink_dump(sk);
scm_recv(sock, msg, siocb->scm, flags);
out:
// 唤醒等待接收内存可用的进程
netlink_rcv_wake(sk);
return err ? : copied;
}
static inline void netlink_rcv_wake(struct sock *sk)
{
struct netlink_sock *nlk = nlk_sk(sk);
if (skb_queue_empty(&sk->sk_receive_queue)) // 接收队列为空后清除state状态
clear_bit(0, &nlk->state);
if (!test_bit(0, &nlk->state))
wake_up_interruptible(&nlk->wait); // 唤醒等待内存的任务
}
此外,内核在消息接收过程中有两个机制很常用:dump机制和ACK。
dump机制
如果消息的Netlink首部的nlmsg_flags指定了dump标记,那么业务在消息接收过程中可能会走dump流程,此时框架提供的netlink_dump_start()函数可以派上用场,使用示例见这里。
struct netlink_callback
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
int (*dump)(struct sk_buff * skb, struct netlink_callback *cb);
int (*done)(struct netlink_callback *cb);
int family;
long args[6];
};
@ssk: 将dump结果发送给该传输控制块;
@skb: 触发dump请求的skb;
int netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
struct nlmsghdr *nlh,
int (*dump)(struct sk_buff *skb,
struct netlink_callback *),
int (*done)(struct netlink_callback *))
{
struct netlink_callback *cb;
struct sock *sk;
struct netlink_sock *nlk;
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
if (cb == NULL)
return -ENOBUFS;
// 将参数记录到辅助数据结构中
cb->dump = dump;
cb->done = done;
cb->nlh = nlh;
atomic_inc(&skb->users);
cb->skb = skb;
// 这里为什么又要找一遍传输控制块,难道sk不是目的接收者吗
sk = netlink_lookup(sock_net(ssk), ssk->sk_protocol, NETLINK_CB(skb).pid);
if (sk == NULL) {
netlink_destroy_callback(cb);
return -ECONNREFUSED;
}
nlk = nlk_sk(sk);
/* A dump is in progress... */
mutex_lock(nlk->cb_mutex);
if (nlk->cb) {
mutex_unlock(nlk->cb_mutex);
netlink_destroy_callback(cb);
sock_put(sk);
return -EBUSY;
}
nlk->cb = cb;
mutex_unlock(nlk->cb_mutex);
netlink_dump(sk);
sock_put(sk);
/* We successfully started a dump, by returning -EINTR we
* signal not to send ACK even if it was requested.
*/
return -EINTR;
}
netlink_dump()
/*
* It looks a bit ugly.
* It would be better to create kernel thread.
*/
static int netlink_dump(struct sock *sk)
{
struct netlink_sock *nlk = nlk_sk(sk);
struct netlink_callback *cb;
struct sk_buff *skb;
struct nlmsghdr *nlh;
int len, err = -ENOBUFS;
// 分配一个skb
skb = sock_rmalloc(sk, NLMSG_GOODSIZE, 0, GFP_KERNEL);
if (!skb)
goto errout;
mutex_lock(nlk->cb_mutex);
cb = nlk->cb;
if (cb == NULL) {
err = -EINVAL;
goto errout_skb;
}
// 调用dump()回调
len = cb->dump(skb, cb);
if (len > 0) { // dump成功后将该skb放入目标socket的接收队列,结束处理过程
mutex_unlock(nlk->cb_mutex);
if (sk_filter(sk, skb))
kfree_skb(skb);
else {
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_data_ready(sk, skb->len);
}
return 0;
}
// dump失败,放置一个DONE消息返回,该消息携带了命令的dump返回值
nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(len), NLM_F_MULTI);
if (!nlh)
goto errout_skb;
memcpy(nlmsg_data(nlh), &len, sizeof(len));
// 将dump消息放到接收队列中
if (sk_filter(sk, skb))
kfree_skb(skb);
else {
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_data_ready(sk, skb->len);
}
// 可选的调用done()回调
if (cb->done)
cb->done(cb);
nlk->cb = NULL;
mutex_unlock(nlk->cb_mutex);
netlink_destroy_callback(cb);
return 0;
errout_skb:
mutex_unlock(nlk->cb_mutex);
kfree_skb(skb);
errout:
return err;
}
从上可以看出,dump机制的工作原理也很简单,就是预先分配一个skb,回调指定的dump()函数,使用这在该回调函数中填充该skb的数据,然后返回,如果dump过程有错误,那么框架会填充一个NLMSG_DONE类型的消息返回给目标socket。
ACK机制
Netlink框架提供消息确认机制。如果消息发送放在Netlink消息首部的nlmsg_flags字段中指定了ACK标记,那么接收方会在处理完毕后回复一个类型为NLMSG_ERROR的确认消息。此外,当内核在消息接收发生错误时,总是会回复确认消息。
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *))
{
// 循环解析Netlink消息
while (skb->len >= nlmsg_total_size(0)) { // 消息长度满足一个Netlink消息头部
nlh = nlmsg_hdr(skb); // Netlink消息首部
...
ack:
// 可见,对于错误消息,内核总是会ACK的
if (nlh->nlmsg_flags & NLM_F_ACK || err)
netlink_ack(skb, nlh, err);
...
}
return 0;
}
void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err)
{
struct sk_buff *skb;
struct nlmsghdr *rep;
struct nlmsgerr *errmsg;
size_t payload = sizeof(*errmsg); // 消息payload为错误消息首部+导致该消息的消息首部
/* error messages get the original request appened */
if (err)
payload += nlmsg_len(nlh);
// 分配消息
skb = nlmsg_new(payload, GFP_KERNEL);
if (!skb) {
struct sock *sk;
sk = netlink_lookup(sock_net(in_skb->sk),
in_skb->sk->sk_protocol,
NETLINK_CB(in_skb).pid);
if (sk) {
sk->sk_err = ENOBUFS;
sk->sk_error_report(sk);
sock_put(sk);
}
return;
}
// 填充消息并发送给目标消息发送方
rep = __nlmsg_put(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq,
NLMSG_ERROR, sizeof(struct nlmsgerr), 0);
errmsg = nlmsg_data(rep);
errmsg->error = err;
memcpy(&errmsg->msg, nlh, err ? nlh->nlmsg_len : sizeof(*nlh));
netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
}