Generic Netlink机制

本文详细介绍了Linux内核的Generic Netlink机制,包括genl_family、genl_ops和多播组的数据结构,以及Generic Netlink框架、消息处理、协议初始化、家庭管理和控制命令等方面。内容涵盖了从消息的封装、解析、发送到接收的全过程。
摘要由CSDN通过智能技术生成


系统中有已经有很多具体的Netlink协议了,不过它们都是针对某个子系统的,功能已经比较固定,如果我们需要扩展新的Netlink协议,那么就需要对内核进行侵入式修改(最起码要扩展MAX_LINKS),为此,Netlink框架还提供了一个通用的Netlink协议: Generic Netlink。通过它,内核模块可以在无需扩充协议的基础上使用Netlink机制。

这篇笔记分析了内核Generic Netlink核心部分的实现,官方参考资料见这里

文件说明
net/netlink/genetlink.cGeneric Netlink协议框架实现文件
include/net/genetlink.h仅内核态可见的Generic Netlink协议头文件
include/linux/genetlink.h用户态也可见的Generic Netlink协议头文件

数据结构

genl_family

/**
 * struct genl_family - generic netlink family
 * @id: protocol family idenfitier
 * @hdrsize: length of user specific header in bytes
 * @name: name of family
 * @version: protocol version
 * @maxattr: maximum number of attributes supported
 * @attrbuf: buffer to store parsed attributes
 * @ops_list: list of all assigned operations
 * @family_list: family list
 * @mcast_groups: multicast groups list
 */
struct genl_family
{
	unsigned int id;
	unsigned int hdrsize;
	char name[GENL_NAMSIZ]; // 每个family也拥有一个全局唯一的名字
	unsigned int version;
	unsigned int maxattr;
	struct nlattr ** attrbuf;	/* private */
	struct list_head ops_list;	/* private */ // 该family的命令操作集链表
	struct list_head family_list;	/* private */ // 将family对象保存在全局哈希表中
	struct list_head mcast_groups;	/* private */
};
  • id

每个family拥有一个全局唯一的ID,该ID可以由faimily的实现指定(比较难保证唯一性),也可由内核自动分配(推荐做法),可用范围为(GENL_MIN_ID(0x10), GENL_MAX_ID(1023)];

  • hdrsize

每个Generic Netlink消息首部除了标准的genlmsghdr外,family还可以追加自己的首部,只需要通过该字段指定自己追加的首部大小即可;

  • maxattr/attrbuf

maxattr字段记录了该faimily支持的最大属性个数,如果指定了属性个数,Generic Netlink框架会负责分配attrbuf,该区域用于消息接收过程中的属性解析,具体见genl_rcv_msg()。

genl_ops

genl_ops定义一个family支持的命令。如genl_family的定义,每个family可以支持多个命令,这些命令被组织到genl_family.ops_list中。

/**
 * struct genl_ops - generic netlink operations
 * @cmd: command identifier
 * @flags: flags
 * @policy: attribute validation policy
 * @doit: standard command callback
 * @dumpit: callback for dumpers
 * @done: completion callback for dumps
 * @ops_list: operations list
 */
struct genl_ops
{
	u8 cmd; // 命令ID,在family内部唯一标识命令
	unsigned int flags;
	const struct nla_policy	*policy; // 属性策略,nlmsg_parse()会根据该策略对属性格式做基本的校验
	int	(*doit)(struct sk_buff *skb, struct genl_info *info);
	int	(*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
	int	(*done)(struct netlink_callback *cb);
	struct list_head ops_list; // 将ops对象放入family->ops_list链表中
};
  • flags

取值来自标准的Netlink消息中的NLM_F_XXX,不过Generic Netlink只用了其中部分标记,这些标记会影响消息的接收流程。

flags含义
GENL_ADMIN_PERM0x1表示该消息的发送需要管理员权限
GENL_CMD_CAP_DO0x2表示该操作集指定了doit()回调
GENL_CMD_CAP_DUMP0x4表示该操作集指定了dumpit()回调
GENL_CMD_CAP_HASPOL0x8表示该操作集指定了属性策略

多播组: genl_multicast_group

/**
 * struct genl_multicast_group - generic netlink multicast group
 * @name: name of the multicast group, names are per-family
 * @id: multicast group ID, assigned by the core, to use with
 *      genlmsg_multicast().
 * @list: list entry for linking
 * @family: pointer to family, need not be set before registering
 */
struct genl_multicast_group
{
	struct genl_family	*family;	/* private */
	struct list_head	list;		/* private */
	char name[GENL_NAMSIZ]; // 多播组名字
	u32	id; // 多播组ID,在整个Generic Netlink层面唯一
};

/*
 * Bitmap of multicast groups that are currently in use.
 *
 * To avoid an allocation at boot of just one unsigned long,
 * declare it global instead.
 * Bit 0 is marked as already used since group 0 is invalid.
 */
static unsigned long mc_group_start = 0x1;
static unsigned long *mc_groups = &mc_group_start;
// 按位表示所有多播组需要的unsigned long整数个数
static unsigned long mc_groups_longs = 1;

Generic Netlink框架

     +---------------------+      +---------------------+
     | (3) application "A" |      | (3) application "B" |
     +------+--------------+      +--------------+------+
            |                                    |
            \                                    /
             \                                  /
              |                                |
      +-------+--------------------------------+-------+
      |        :                               :       |   user-space
 =====+        :   (5)  kernel socket API      :       +================
      |        :                               :       |   kernel-space
      +--------+-------------------------------+-------+
               |                               |
         +-----+-------------------------------+----+
         |        (1)  Netlink subsystem            |
         +---------------------+--------------------+
                               |
         +---------------------+--------------------+
         |       (2) Generic Netlink bus            |
         +--+--------------------------+-------+----+
            |                          |       |
    +-------+---------+                |       |
    |  (4) controller |               /         \
    +-----------------+              /           \
                                     |           |
                  +------------------+--+     +--+------------------+
                  | (3) kernel user "X" |     | (3) kernel user "Y" |
                  +---------------------+     +---------------------+

需要解释几个核心关键点:

  1. Generic Netlink是基于Netlink框架的一种协议;
  2. 基于Generic Netlink的模块在Generic Netlink来看是一个个的family,对应到代码就是一个个的genl_family对象,特殊的,Generic Netlink框架实现了一个特殊的控制family,它用来辅助管理所有的普通family。

Generic Netlink消息

Generic Netlink消息就是在通用的Netlink消息基础上扩展的,其基本格式如下:

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                Netlink message header (nlmsghdr)              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Generic Netlink message header (genlmsghdr)         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |             Optional user specific message header             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Optional Generic Netlink message payload            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

消息头部

如上,除了通用的Netlink消息首部外,Generic Netlink消息有固定首部genlmsghdr,其格式如下:

struct nlmsghdr {
...
    __u16 nlmsg_type; // Generic Netlink消息时,该字段必须设置为Generic Netlink的famlyId
...
}
struct genlmsghdr {
	__u8 cmd; // family内部唯一标识命令的ID
	__u8 version;
	__u16 reserved;
};

此外,如果具体的family在实现时需要有自己的标准首部,那么可以通过genl_family.hdrsize指定其大小,这样框架在需要时就可以做出正确的处理了。

消息API

为了方便编程,Generic Netlink框架在Netlink协议消息API的基础上,实现了一组包装接口,核心API介绍如下。

消息的封装

使用genlmsg_new()分配一个Generic Netlink消息的skb。

/**
 * genlmsg_new - Allocate a new generic netlink message
 * @payload: size of the message payload
 * @flags: the type of memory to allocate.
 */
static inline struct sk_buff *genlmsg_new(size_t payload, gfp_t flags);

然后使用genlmsg_put()填充消息首部,包括Netlink消息首部和Generic Netlink消息首部。

/**
 * genlmsg_put - Add generic netlink header to netlink message
 * @skb: socket buffer holding the message
 * @pid: netlink pid the message is addressed to(发送方pid)
 * @seq: sequence number (usually the one of the sender)
 * @family: generic netlink family
 * @flags netlink message flags
 * @cmd: generic netlink command
 *
 * Returns pointer to user specific header
 */
static inline void *genlmsg_put(struct sk_buff *skb, u32 pid, u32 seq,
				struct genl_family *family, int flags, u8 cmd);

之后便可以使用Netlink消息属性相关的API填充消息的payload,在填充完毕后可以使用genlmsg_end()结束消息的封装。

/**
 * genlmsg_end - Finalize a generic netlink message
 * @skb: socket buffer the message is stored in
 * @hdr: user specific header,来自genlmsg_put()的返回值
 */
static inline int genlmsg_end(struct sk_buff *skb, void *hdr);

消息的解析

可以使用genlmsg_data()获得消息得payload指针(如果family有自己的首部,返回的是指向该首部的指针)。

/**
 * gennlmsg_data - head of message payload
 * @gnlh: genetlink messsage header
 */
static inline void *genlmsg_data(const struct genlmsghdr *gnlh)
{
	return ((unsigned char *) gnlh + GENL_HDRLEN);
}

可以使用genlmsg_len()获取到消息payload得长度。

/**
 * genlmsg_len - length of message payload
 * @gnlh: genetlink message header
 */
static inline int genlmsg_len(const struct genlmsghdr *gnlh)

可以使用下面两个api计算出长度为payload的Generic Netlink消息得长度。

/**
 * genlmsg_msg_size - length of genetlink message not including padding
 * @payload: length of message payload
 */
static inline int genlmsg_msg_size(int payload);

/**
 * genlmsg_total_size - length of genetlink message including padding
 * @payload: length of message payload
 */
static inline int genlmsg_total_size(int payload);

消息的发送

内核态可以使用如下接口进行单播和组播消息发送。

/**
 * genlmsg_unicast - unicast a netlink message
 * @skb: netlink message as socket buffer
 * @pid: netlink pid of the destination socket
 */
static inline int genlmsg_unicast(struct sk_buff *skb, u32 pid);

/**
 * genlmsg_multicast - multicast a netlink message
 * @skb: netlink message as socket buffer
 * @pid: own netlink pid to avoid sending to yourself
 * @group: multicast group id
 * @flags: allocation flags
 */
static inline int genlmsg_multicast(struct sk_buff *skb, u32 pid,
				    unsigned int group, gfp_t flags);

协议初始化

在Generic Netlink初始化时,向Netlink注册了NETLINK_GENERIC协议类型。

static int __init genl_init(void)
{
	int i, err;

    // 初始化全局family对象哈希表
	for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
		INIT_LIST_HEAD(&family_ht[i]);
    // 注册控制family
	err = genl_register_family(&genl_ctrl);
	if (err < 0)
		goto errout;
    // 为控制family注册命令操作集
	err = genl_register_ops(&genl_ctrl, &genl_ctrl_ops);
	if (err < 0)
		goto errout_register;

    // 设置非root用户也可以接收GeNetlink组播消息
	netlink_set_nonroot(NETLINK_GENERIC, NL_NONROOT_RECV);
	// 向Netlink注册了NETLINK_GENERIC协议类型,对应的数据接收函数为genl_rcv()
	genl_sock = netlink_kernel_create(&init_net, NETLINK_GENERIC, 0,
					  genl_rcv, &genl_mutex, THIS_MODULE);
	if (genl_sock == NULL)
		panic("GENL: Cannot initialize generic netlink\n");

	err = genl_register_mc_group(&genl_ctrl, &notify_grp);
	if (err < 0)
		goto errout_register;

	return 0;

errout_register:
	genl_unregister_family(&genl_ctrl);
errout:
	panic("GENL: Cannot register controller: %d\n", err);
}
subsys_initcall(genl_init);

family的管理

Generic Netlink将内部更具体的协议称为family,每个family对应一个genl_family对象,所有的family对象被保存在全局哈希表中。

#define GENL_FAM_TAB_SIZE	16
#define GENL_FAM_TAB_MASK	(GENL_FAM_TAB_SIZE - 1)

static struct list_head family_ht[GENL_FAM_TAB_SIZE];
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */

family的注册&去注册

#define GENL_MIN_ID	NLMSG_MIN_TYPE
#define GENL_MAX_ID	1023

int genl_register_family(struct genl_family *family)
{
	int err = -EINVAL;

    // 检查指定的family ID是否在有效范围内
	if (family->id && family->id < GENL_MIN_ID)
		goto errout;
	if (family->id > GENL_MAX_ID)
		goto errout;

    // 初始化操作链表和多播链表
	INIT_LIST_HEAD(&family->ops_list);
	INIT_LIST_HEAD(&family->mcast_groups);

	genl_lock();
    // 确保family的名字全局唯一
	if (genl_family_find_byname(family->name)) {
		err = -EEXIST;
		goto errout_locked;
	}
    // 确保family的ID全局唯一
	if (genl_family_find_byid(family->id)) {
		err = -EEXIST;
		goto errout_locked;
	}

    // 当指定的id为GENL_ID_GENERATE时,内核会自动分配一个(很实用的功能)
	if (family->id == GENL_ID_GENERATE) {
		u16 newid = genl_generate_id();
		if (!newid) {
			err = -ENOMEM;
			goto errout_locked;
		}
		family->id = newid;
	}

    // 如果指定了支持的属性个数,则分配属性buffer,方便后面接收到消息后的属性解析
	if (family->maxattr) {
		family->attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);
		if (family->attrbuf == NULL) {
			err = -ENOMEM;
			goto errout_locked;
		}
	} else
		family->attrbuf = NULL;

    // 将新的family保存到全局哈希表中
	list_add_tail(&family->family_list, genl_family_chain(family->id));
	genl_unlock();

    // 发布一个CTRL_CMD_NEWFAMILY组播事件
	genl_ctrl_event(CTRL_CMD_NEWFAMILY, family);
	return 0;

errout_locked:
	genl_unlock();
errout:
	return err;
}

对应的去注册函数为genl_unregister_family(),这里不再展开。

family操作集的注册&去注册

每个family能够支持多个命令,每个命令对应一个genl_ops对象,family将其支持的命令组织到family->ops_list链表中。对应的注册函数为genl_register_ops(),去注册函数为genl_unregister_ops(),这里不再展开去注册函数。

int genl_register_ops(struct genl_family *family, struct genl_ops *ops)
{
	int err = -EINVAL;

    // 命令至少指定dumpit()和doit()回调中的一个
	if (ops->dumpit == NULL && ops->doit == NULL)
		goto errout;

    // 命令ID在family内要保持唯一
	if (genl_get_cmd(ops->cmd, family)) {
		err = -EEXIST;
		goto errout;
	}

    // 设置回调函数标记
	if (ops->dumpit)
		ops->flags |= GENL_CMD_CAP_DUMP;
	if (ops->doit)
		ops->flags |= GENL_CMD_CAP_DO;
	if (ops->policy)
		ops->flags |= GENL_CMD_CAP_HASPOL;

    // 将ops对象放入family->ops_list链表中
	genl_lock();
	list_add_tail(&ops->ops_list, &family->ops_list);
	genl_unlock();

    // 向外发送CTRL_CMD_NEWOPS组播事件
	genl_ctrl_event(CTRL_CMD_NEWOPS, ops);
	err = 0;
errout:
	return err;
}

控制family

Generic Netlink框架的核心是其控制family,其family对象实例化如下:

#define GENL_ID_CTRL NLMSG_MIN_TYPE // 0x10,控制family的familyID是固定的

static struct genl_family genl_ctrl = {
	.id = GENL_ID_CTRL,
	.name = "nlctrl",
	.version = 0x2,
	.maxattr = CTRL_ATTR_MAX,
};

其命令操作集如下。可见控制family只支持接收一个CTRL_CMD_GETFAMILY命令(其它命令是向外发送的),用户态程序可以通过该命令查询已注册family的信息。

static struct genl_ops genl_ctrl_ops = {
	.cmd		= CTRL_CMD_GETFAMILY,
	.doit		= ctrl_getfamily,
	.dumpit		= ctrl_dumpfamily,
	.policy		= ctrl_policy,
};

family查询命令: ctrl_getfamily()

static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info)
{
	struct sk_buff *msg;
	struct genl_family *res = NULL;
	int err = -EINVAL;

    // 通过family ID查询
	if (info->attrs[CTRL_ATTR_FAMILY_ID]) {
		u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]);
		res = genl_family_find_byid(id);
	}
    // 通过family名字查询,显然名字的优先级更高
	if (info->attrs[CTRL_ATTR_FAMILY_NAME]) {
		char *name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]);
		res = genl_family_find_byname(name);
	}
    // 没有找到
	if (res == NULL) {
		err = -ENOENT;
		goto errout;
	}
    // 填充该family的信息到skb
	msg = ctrl_build_family_msg(res, info->snd_pid, info->snd_seq, CTRL_CMD_NEWFAMILY);
	if (IS_ERR(msg)) {
		err = PTR_ERR(msg);
		goto errout;
	}
	// 将封装好的skb返回给用户态
	err = genlmsg_reply(msg, info);
errout:
	return err;
}

可见,内核支持通过名字和ID来查询family的信息,不过大多数的使用场景是通过名字查询。ctrl_build_family_msg()函数不再展开,从中可以看到通过该命令可以查询到一个family的所有信息,包括名字、ID、版本、支持的命令列表等等。

此外,控制family还支持一个组播,通过该组播,控制family向外广播family的变化以及family组播的变化情况。

消息接收: genl_rcv()

如协议初始化部分分析,当Netlink框架收到发送往Generic Netlink的消息时,会调用Generic Netlink提供的回调函数: genl_rcv()。

static void genl_rcv(struct sk_buff *skb)
{
	genl_lock();
	netlink_rcv_skb(skb, &genl_rcv_msg);
	genl_unlock();
}

持锁后,调用Netlink框架提供的netlink_rcv_skb()函数对Netlink消息进行接收,该函数的核心思想就是循环处理skb中的Netlink消息,对于合法的消息,将其交给回调函数处理。

int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *))
{
	struct nlmsghdr *nlh;
	int err;

    // 循环解析Netlink消息
	while (skb->len >= nlmsg_total_size(0)) { // 消息长度满足一个Netlink消息头部
		int msglen;

		nlh = nlmsg_hdr(skb); // Netlink消息首部
		err = 0;
        // 检查skb中是否够一个完整的Netlink消息
		if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
			return 0;

		// 发往内核的Netlink消息必须设置REQUEST标记
		if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
			goto ack;

		// 跳过控制类消息:NLMSG_NOOP等属于控制消息
		if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
			goto ack;
        // 将消息传给回调函数处理
		err = cb(skb, nlh);
		if (err == -EINTR)
			goto skip;

ack:
        // 可见,对于错误消息,内核总是会ACK的
		if (nlh->nlmsg_flags & NLM_F_ACK || err)
			netlink_ack(skb, nlh, err);

skip:
        // skb指针向前偏移指定长度,继续处理下一个Netlink消息
		msglen = NLMSG_ALIGN(nlh->nlmsg_len);
		if (msglen > skb->len)
			msglen = skb->len;
		skb_pull(skb, msglen);
	}
	return 0;
}

genl_rcv_msg()

该函数用于接收一个Generic Netlink消息,消息数据已经在参数nlh中了。

static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
	struct genl_ops *ops;
	struct genl_family *family;
	struct genl_info info;
	struct genlmsghdr *hdr = nlmsg_data(nlh);
	int hdrlen, err;

    // 根据family ID找到注册的family对象
	family = genl_family_find_byid(nlh->nlmsg_type);
	if (family == NULL)
		return -ENOENT;
    // 头部长度检查:消息的长度要至少包含完整的Generic Netlink消息首部
	hdrlen = GENL_HDRLEN + family->hdrsize;
	if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
		return -EINVAL;

    // 从family中查找该命令对应的genl_ops对象
	ops = genl_get_cmd(hdr->cmd, family);
	if (ops == NULL)
		return -EOPNOTSUPP;

    // 如果该操作有权限限制,则进行权限检查
	if ((ops->flags & GENL_ADMIN_PERM) && security_netlink_recv(skb, CAP_NET_ADMIN))
		return -EPERM;

    // 消息如果指定了NLM_F_DUMP标记,则调用的是ops中的dumpit()和done()回调
	if (nlh->nlmsg_flags & NLM_F_DUMP) {
		if (ops->dumpit == NULL)
			return -EOPNOTSUPP;
		genl_unlock();
		err = netlink_dump_start(genl_sock, skb, nlh, ops->dumpit, ops->done);
		genl_lock();
		return err;
	}
    // 命令必须提供doit()回调
	if (ops->doit == NULL)
		return -EOPNOTSUPP;

    // 如果family提供了属性buf,那么用该buffer空间存储解析后的消息属性
	if (family->attrbuf) {
		err = nlmsg_parse(nlh, hdrlen, family->attrbuf, family->maxattr, ops->policy);
		if (err < 0)
			return err;
	}

    // 将信息组织到info中,然后调用命令操作集的doit()回调
	info.snd_seq = nlh->nlmsg_seq;
	info.snd_pid = NETLINK_CB(skb).pid;
	info.nlhdr = nlh;
	info.genlhdr = nlmsg_data(nlh);
	info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
	info.attrs = family->attrbuf;
	return ops->doit(skb, &info);
}

Generic Netlink组播

Generic Netlink在Netlink组播的基础上,也可支持具体family实现自己的组播。

多播组的注册&去注册

具体family要支持多播组,那么需要首先向Generic Netlink框架注册,对应的函数为genl_register_mc_group(),类似的,去注册接口为genl_unregister_mc_group(),这里不再展开。

/**
 * genl_register_mc_group - register a multicast group
 *
 * Registers the specified multicast group and notifies userspace
 * about the new group.
 *
 * Returns 0 on success or a negative error code.
 *
 * @family: The generic netlink family the group shall be registered for.
 * @grp: The group to register, must have a name.
 */
int genl_register_mc_group(struct genl_family *family,
			   struct genl_multicast_group *grp)
{
	int id;
	unsigned long *new_groups;
	int err;

	BUG_ON(grp->name[0] == '\0');

	genl_lock();

	/* special-case our own group */
	if (grp == &notify_grp) // 控制family的多播组ID是固定的
		id = GENL_ID_CTRL;
	else
	    // 分配一个组ID
		id = find_first_zero_bit(mc_groups,
					 mc_groups_longs * BITS_PER_LONG);

    // 多播组掩码已经不够用了,重新分配
	if (id >= mc_groups_longs * BITS_PER_LONG) {
		size_t nlen = (mc_groups_longs + 1) * sizeof(unsigned long);

		if (mc_groups == &mc_group_start) {
			new_groups = kzalloc(nlen, GFP_KERNEL);
			if (!new_groups) {
				err = -ENOMEM;
				goto out;
			}
			mc_groups = new_groups;
			*mc_groups = mc_group_start;
		} else {
			new_groups = krealloc(mc_groups, nlen, GFP_KERNEL);
			if (!new_groups) {
				err = -ENOMEM;
				goto out;
			}
			mc_groups = new_groups;
			mc_groups[mc_groups_longs] = 0;
		}
		mc_groups_longs++;
	}
    // 让Netlink框架变更协议对象nl_table中的组播字段
	err = netlink_change_ngroups(genl_sock, mc_groups_longs * BITS_PER_LONG);
	if (err)
		goto out;
    // 设置多播组对象中的各字段
	grp->id = id;
	set_bit(id, mc_groups);
	list_add_tail(&grp->list, &family->mcast_groups);
	grp->family = family;

    // 控制family对外广播NEWMCAST_GRP事件
	genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, grp);
 out:
	genl_unlock();
	return err;
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值