Netlink协议族核心操作

本文深入解析Netlink协议族的核心操作,包括数据结构如netlink_sock、netlink_ops和sockaddr_nl,以及创建、绑定、连接、发送和接收消息的过程。详细阐述了netlink_create()、netlink_bind()、netlink_sendmsg()等关键函数的工作原理,同时介绍了dump机制和ACK确认机制的应用。

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


这篇笔记重点介绍了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。

  • 等待队列

statewait两个字段配合使用,控制消息的接收过程。

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消息单播发送:

  1. 用户态套接字–>内核;
  2. 内核–>用户态套接字;
  3. 用户态套接字–>用户态套接字;
@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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值