您的位置:首页 > 其它

四、无线信息传递——Generic netlink(二、通信)

2016-09-09 14:34 387 查看

系列说明

  仍旧是系列二:

1、无线驱动信息传递框架:说明无线信息传递的步骤流程以及各程序块之间的联系;

2、generic Netlink信号传递机制:hostapd与无线驱动之间的信息传递机制

3、以ssid为例说明用户将user space中的ssid配置内容传递至kernel space的流程:以此系统地了解整个无线信息传递流程。

  上一篇文章是对generic netlink的初始化和主要结构体,接口的认识。这一篇文章将主要以一个例子来系统地说明Generic netlink代码实现和原理,以便后续实际工作中的应用。

!!!这一篇文章同样也是参考前一篇文章中的链接内容加上一些自己的理解,需要真正了解的可以点击上一章的链接。

这一章的主要说明内容:

1、实现例子功能说明;

2、内核代码中Generic netlink的创建;

3、应用层中Generic netlink的代码创建;

4、应用层与内核层的通信代码流程说明。

例子代码附件:

http://download.csdn.net/detail/xpbob/9619686

一、例子功能说明

  应用层程序接收用户的输入“字符串”和“数据”向内核发送,内核接收后回发应用层,应用层通过终端打印输出。

二、内核代码中Generic netlink的创建示例

  generic netlink在内核代码中的实现分为以下几步:

1、定义Genetlink——主要定义上文中的两个结构体,分别为genl_family和genl_ops(这里先对内核为发送端,封包进行说明;接收后续说明);

2、内核注册Genetlink——主要对内核中Genetlink的接口调用流程说明;(同样也仅对内核为发送端,封包的接口说明)

1.2、定义Genetlink结构体

  在前文中已经对主要的4个Genetlink结构体进行了说明,genlmsghdr,genl_family,genl_ops和genl_info。

  因为这里只说明内核端作为发送端的结构体定义,所以用于解析genetlink消息头的genlmsghdr和用于处理接收消息的这里在先不进行说明。主要是对genl_family和genl_ops两个接口说明。

定义例子使用的family:

enum {
DEMO_CMD_ATTR_UNSPEC = 0,
DEMO_CMD_ATTR_MESG,  /* demo message  */        //msg属性
DEMO_CMD_ATTR_DATA,  /* demo data */            //data属性
__DEMO_CMD_ATTR_MAX,
};
#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1)

static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX+1] = {
[DEMO_CMD_ATTR_MESG] = { .type = NLA_STRING },      //定义msg类型为NLA_STRING(字符串类型)
[DEMO_CMD_ATTR_DATA] = { .type = NLA_S32 },         //定义data类型为NLA_S32(有符号32位数)
};

static struct genl_family demo_family = {
.id   = GENL_ID_GENERATE,               //表示由内核统一分配
.name  = DEMO_GENL_NAME,                //内核的唯一genetlink标志名
.version = DEMO_GENL_VERSION,               //版本号(可以为1,2)
.maxattr = DEMO_CMD_ATTR_MAX,               //内核由此决定为其分配缓存空间大小
};


定义例子使用的genl_ops:

enum {
DEMO_CMD_UNSPEC = 0, /* Reserved */
DEMO_CMD_ECHO,   /* user->kernel request/get-response */    //定义显示cmd的id号
DEMO_CMD_REPLY,   /* kernel->user event */          //定义返回cmd的id号
__DEMO_CMD_MAX,
};
#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)

static const struct genl_ops demo_ops[] = {
{
.cmd  = DEMO_CMD_ECHO,                  //接收到的判断id(DEMO_CMD_ECHO)
.doit  = demo_echo_cmd,                 //内核对应命令的执行回调函数
.policy  = demo_cmd_policy,             //执行策略,上一模块的demo_cmd_policy中有说明
.flags  = GENL_ADMIN_PERM,              //定义所需权限为admin
},
};


1.2、内核注册Genetlink

  内核注册中genl_register_family_with_ops为主要入口。

static int __init demo_genetlink_init(void)
{
int ret;
pr_info("demo generic netlink module %d init...\n", DEMO_GENL_VERSION);

ret = genl_register_family_with_ops(&demo_family, demo_ops);            //注册family和ops(由于genl_register_family_with_ops在前一文中已经说明,相同的不再赘述,所以这里主要说明不是组播发送的操作)
if (ret != 0) {
pr_info("failed to init demo generic netlink example module\n");
return ret;
}
pr_info("demo generic netlink module init success\n");
return 0;
}
//其后genl_register_family_with_ops调用genl_register_family和genl_register_ops
//而genl_register_family和genl_register_ops中调用genl_ctrl_event,这个接口对是不是组播发送会有所区别,在下面的代码中进行说明


/*
* 在发送组播时:
*        _genl_register_family_with_ops_grps调用genl_ctrl_event的方式是
*
*           genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);
*           for (i = 0; i < family->n_mcgrps; i++)
*                 genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family, &family->mcgrps[i], family->mcgrp_offset + i);
* 发送指定程序时只调用用一次的genl_ctrl_event()
*/
static int genl_ctrl_event(int event, struct genl_family *family,
const struct genl_multicast_group *grp,
int grp_id)
{
struct sk_buff *msg;
/* genl is still initialising */
if (!init_net.genl_sock)                    //判断是否已经注册了控制器CTRL簇
return 0;
switch (event) {
case CTRL_CMD_NEWFAMILY:
case CTRL_CMD_DELFAMILY:
WARN_ON(grp);
msg = ctrl_build_family_msg(family, 0, 0, event);   //创建单播控制器family
break;
case CTRL_CMD_NEWMCAST_GRP:
case CTRL_CMD_DELMCAST_GRP:
BUG_ON(!grp);
msg = ctrl_build_mcgrp_msg(family, grp, grp_id, 0, 0, event);   //创建组播控制器family
break;
default:
return -EINVAL;

//下面的genlmsg_multicast_netns和genlmsg_multicast_allns接口,不论哪一种情况,最后都是调用nlmsg_multicast()函数。不过这里有一点需要注意的就是这里的第三个入参portid为0,这是为了防止向发送端发送报文,这也就表明内核控制器簇套接字是不会接受该广播报文的(内核也不应该接收,否则会让内核“恐慌”)。
if (IS_ERR(msg))
return PTR_ERR(msg);
if (!family->netnsok) {                     //根据是否支持net命名空间来选择发送的流程
genlmsg_multicast_netns(&genl_ctrl, &init_net, msg, 0, 0, GFP_KERNEL);  //指定了向init_net发送
} else {
rcu_read_lock();
genlmsg_multicast_allns(&genl_ctrl, msg, 0, 0, GFP_ATOMIC); //会向所有命名空间的控制器簇发送消息
rcu_read_unlock();
}
}


static struct sk_buff *ctrl_build_family_msg(struct genl_family *family,
u32 portid, int seq, u8 cmd)
{
struct sk_buff *skb;
int err;
skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); //创建netlink类型的skb,参数分别为消息长度和内存空间分配类型
if (skb == NULL)
return ERR_PTR(-ENOBUFS);
err = ctrl_fill_info(family, portid, seq, 0, skb, cmd); //填充数据包数据
if (err < 0) {
nlmsg_free(skb);
return ERR_PTR(err);
}
return skb;
}

//从下面的下层函数调用中可以看出:总共预留的空间为NLMSG_ALIGN(NLMSG_HDRLEN+NLMSG_DEFAULT_SIZE)
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
return alloc_skb(nlmsg_total_size(payload), flags);
}

static inline int nlmsg_total_size(int payload)
{
return NLMSG_ALIGN(nlmsg_msg_size(payload));
}

static inline int nlmsg_msg_size(int payload)
{
return NLMSG_HDRLEN + payload;
}


  下面进行说明主要的接口ctrl_fill_info进行数据包的填充数据的代码分段说明,程序说明时中间会穿插说明调用到的接口。最后结合数据包框图。

/*
* 参数讲解:
*       family:为新注册的demo_family;
*       portid: 表示消息的发送端为内核;
*       seq:  发送消息序号为0;
*       cmd:   命令id号,当前调用到的命令号为CTRL_CMD_NEWFAMILY
*/
static int ctrl_fill_info(struct genl_family *family, u32 portid, u32 seq,
u32 flags, struct sk_buff *skb, u8 cmd)
{
void *hdr;
hdr = genlmsg_put(skb, portid, seq, &genl_ctrl, flags, cmd);        //初始化netlink消息头和genetlink消息头(下部分代码说明)
if (hdr == NULL)
return -1;


  穿插说明genlmsg_put接口

void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
struct genl_family *family, int flags, u8 cmd)
{
struct nlmsghdr *nlh;
struct genlmsghdr *hdr;
nlh = nlmsg_put(skb, portid, seq, family->id, GENL_HDRLEN + family->hdrsize, flags); //向skb缓冲区中获取消息头空间并且初始化netlink消息头,第5个参数为genetlink消息头和用户私有消息头(这里并未使用)的总空间,,实际调用的函数为__nlmsg_put()
if (nlh == NULL)
return NULL;

hdr = nlmsg_data(nlh); //通过宏nlmsg_data获取genetlink消息头的地址,前文说过genetlink消息头中包含genl_family内容
hdr->cmd = cmd; //消息的cmd命令,例子中为CTRL_CMD_NEWFAMILY;
hdr->version = family->version; //genl_ctrl family簇的version
hdr->reserved = 0; //0
return (char *) hdr + GENL_HDRLEN; //返回消息用户私有头(若有)或实际载荷的首地址

}

struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags)
{
struct nlmsghdr *nlh;
int size = nlmsg_msg_size(len);
nlh = (struct nlmsghdr *)skb_put(skb, NLMSG_ALIGN(size)); //分配的空间大小为size = 传入的len长度 + netlink消息头的长度
nlh->nlmsg_type = type; //例子中:内核genl_ctrl family簇的ID 号GENL_ID_CTRL
nlh->nlmsg_len = size; //例子中:消息长度,即genetlink头+用户私有头+netlink头的长度总和
nlh->nlmsg_flags = flags; //例子中:0
nlh->nlmsg_pid = portid; //例子中:发送端的ID号为0,表示由内核发送
nlh->nlmsg_seq = seq; //例子中:0
if (!__builtin_constant_p(size) || NLMSG_ALIGN(size) - size != 0) //将内存对齐用的空白区刷为0
memset(nlmsg_data(nlh) + len, 0, NLMSG_ALIGN(size) - size);
return nlh;
}


  至此完成数据包的netlink头和genetlink头填充操作,如下图所示:



  接下来进行填充实际数据,仍然是ctrl_fill_info接口接下来的代码。

//将新注册的family结构中的几个字段都填充到了消息中,包括name、id号、版本号、私有头长度以及maxattr(注意属性需要一一对应)
if (nla_put_string(skb, CTRL_ATTR_FAMILY_NAME, family->name) || //调用的函数nla_put_string、nla_put_u16和nla_put_u32都是nla_put()的封装
nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, family->id) || //nla_put实际调用的是__nla_put(),下面进行说明
nla_put_u32(skb, CTRL_ATTR_VERSION, family->version) ||
nla_put_u32(skb, CTRL_ATTR_HDRSIZE, family->hdrsize) ||
nla_put_u32(skb, CTRL_ATTR_MAXATTR, family->maxattr))
goto nla_put_failure;


  穿插说明nla_put接口

int nla_put(struct sk_buff *skb, int attrtype, int attrlen, const void *data)
{
if (unlikely(skb_tailroom(skb) < nla_total_size(attrlen)))
return -EMSGSIZE;
__nla_put(skb, attrtype, attrlen, data);
return 0;
}

//作用是向skb中添加一个netlink attr属性
//参数说明:
// 1、skb地址
// 2、要添加的attr属性类型
// 3、属性长度
// 4、属性实际数据
void __nla_put(struct sk_buff *skb, int attrtype, int attrlen,
const void *data)
{
struct nlattr *nla;
nla = __nla_reserve(skb, attrtype, attrlen); //在skb中预留出attr属性的内存空间
memcpy(nla_data(nla), data, attrlen);
}

//调用完此接口后,一个attr属性就添加完成了
struct nlattr *__nla_reserve(struct sk_buff *skb, int attrtype, int attrlen)
{
struct nlattr *nla;
nla = (struct nlattr *) skb_put(skb, nla_total_size(attrlen)); //首先预留空间长度为nla_total_size(attrlen),即attrlen+NLA_HDRLEN(属性头长度)+对齐用内存空白
nla->nla_type = attrtype; //attr属性,即前文中的CTRL_ATTR_FAMILY_NAME等
nla->nla_len = nla_attr_size(attrlen); //attr属性长度(attrlen+NLA_HDRLEN);
memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); //将attr属性中的实际数据拷贝到预留测空间中
return nla;
}


  如此,一个数据包中的attr内容添加完成,如下图所示:



  回到ctrl_fill_info接口中:

if (family->n_ops) {                        //如果新注册的family簇也同时注册了操作接口operations
struct nlattr *nla_ops;
int i;
nla_ops = nla_nest_start(skb, CTRL_ATTR_OPS);       //追加上对应的attr属性参数,但同前面不同的是,这里追加的attr参数是“打包”在一起的,使用的属性为CTRL_ATTR_OPS,接下来没调用nla_put接口添加cmd或者flag数据.由于netlink的attr属性是支持多级嵌套的,所以这里的“打包”指的就是新建一级嵌套,调用nla_nest_start接口就是这个目的(下面说明)
if (nla_ops == NULL)
goto nla_put_failure;
for (i = 0; i < family->n_ops; i++) {
struct nlattr *nest;
const struct genl_ops *ops = &family->ops[i];

//根据operations中实现的回调函数封装flag
u32 op_flags = ops->flags;
if (ops->dumpit)
op_flags |= GENL_CMD_CAP_DUMP;
if (ops->doit)
op_flags |= GENL_CMD_CAP_DO;
if (ops->policy)
op_flags |= GENL_CMD_CAP_HASPOL;
nest = nla_nest_start(skb, i + 1);      //与上面的nla_nest_start不同,这里调用nla_nest_start再次创建新的一级嵌套,结合下面调用的nla_put_u32可以看出,一个attrtype后面跟一个命令和一个flag
if (nest == NULL)
goto nla_put_failure;
if (nla_put_u32(skb, CTRL_ATTR_OP_ID, ops->cmd) || nla_put_u32(skb, CTRL_ATTR_OP_FLAGS, op_flags))
goto nla_put_failure;
nla_nest_end(skb, nest);            //调用nla_nest_end结束这一层attr嵌套
}
nla_nest_end(skb, nla_ops);             //依旧调用nla_nest_end来结束CTRL_ATTR_OPS的那一层attr嵌套
}


  中间穿插nla_nest_start接口

static inline struct nlattr *nla_nest_start(struct sk_buff *skb, int attrtype)
{
struct nlattr *start = (struct nlattr *)skb_tail_pointer(skb);
if (nla_put(skb, attrtype, 0, NULL) < 0)            //依然是nla_put()函数,不过这里的入参中指定的attr长度为0,然后数据为NULL,这里其实就是向skb中添加了一段attr属性头,然后指定它的属性nla_type为CTRL_ATTR_OPS,属性nla_len为0
return NULL;
return start;                           //注意函数返回的是添加嵌套attr头之前的消息有效数据末尾地址
}


//更新这个嵌套的attr属性头的nla_len字段为本嵌套属性的实际长度,实现的方式为当前的消息末尾地址减去创建该级嵌套之前的消息末尾地址(这就是nla_nest_start()函数要返回start地址的原因了)。
static inline int nla_nest_end(struct sk_buff *skb, struct nlattr *start)
{
start->nla_len = skb_tail_pointer(skb) - (unsigned char *)start;
return skb->len;
}


  此时,完成了数据包中的attr数据填充,如下图所示:



  接下来,ctrl_fill_info会再进行判断family->n_mcgrps字段,若存在组播组,会同operations一样增加一级和operations平级的attr嵌套然后添加CTRL_ATTR_MCAST_GROUPS属性。如下代码所示,这里就不赘述了。

if (!list_empty(&family->mcast_groups)) {
struct genl_multicast_group *grp;
struct nlattr *nla_grps;
int idx = 1;
nla_grps = nla_nest_start(skb, CTRL_ATTR_MCAST_GROUPS);
if (nla_grps == NULL)
goto nla_put_failure;
list_for_each_entry(grp, &family->mcast_groups, list) {
struct nlattr *nest;
nest = nla_nest_start(skb, idx++);
if (nest == NULL)
goto nla_put_failure;
NLA_PUT_U32(skb, CTRL_ATTR_MCAST_GRP_ID, grp->id);
NLA_PUT_STRING(skb, CTRL_ATTR_MCAST_GRP_NAME, grp->name);
nla_nest_end(skb, nest);
}
nla_nest_end(skb, nla_grps);
}
return genlmsg_end(skb, hdr); //最后调用genlmsg_end完成本次封装
nla_put_failure:
genlmsg_cancel(skb, hdr);
return -EMSGSIZE;
}


static inline void genlmsg_end(struct sk_buff *skb, void *hdr)
{
nlmsg_end(skb, hdr - GENL_HDRLEN - NLMSG_HDRLEN); //第二个入参为消息attr载荷的首地址减去2个头的长度(netlilnk头和genetlink头长度),即netlink消息头的首地址
}

static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh)
{
nlh->nlmsg_len = skb_tail_pointer(skb) - (unsigned char *)nlh; //填充nlh->nlmsg_len为整个消息的长度(包括attr载荷部分和所有的消息头部分)
}


  至此,向CTRL控制器簇发送的消息就已经封装完成。

2、应用层中Generic netlink的代码创建

  应用层相对于内核的创建会比较简单一点。示例代码如下:

/*<----------main函数------------->*/
int main(int argc, char* argv[])
{

......

/* 初始化socket */
nl_fd = demo_create_nl_socket(NETLINK_GENERIC); //指定协议类型为NETLINK_GENERIC
if (nl_fd < 0) {
fprintf(stderr, "failed to create netlink socket\n");
return 0;
}

......

}


/*<------------demo_create_nl_socket函数---------------->*/
static int demo_create_nl_socket(int protocol)
{
int fd;
struct sockaddr_nl local;
/* 创建socket */
fd = socket(AF_NETLINK, SOCK_RAW, protocol); //通过socket系统调用创建AF_NETLINK地址簇的SOCK_RAW套接字,指定协议类型为NETLINK_GENERIC
if (fd < 0)
return -1;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
/* 使用本进程的pid进行绑定 */
if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0)
goto error;
return fd;

error:
close(fd);
return -1;
}


3、应用层与内核层的通信代码流程说明

  用户与内核空间之间的通信包括:

1、用户查询Demo Family ID;

2、向内核Demo Family发送消息;

3、内核Demo Family 回发消息;

4、应用层接收内核Demo Family回发消息。

3.1、用户查询Demo Family ID

用户空间如果向内核发送数据的话,用户空间是通过什么来识别出内核中对应处理部分的唯一性呢?

  通过内核分配的demo family的family id号。用户空间想要发送消息到内核的demo genelink套接字,它首先得知道内核分配的demo family的family id号,因为genelink子系统是根据该id号来区分不同family簇的genelink套接字和分发消息的。

怎么去获取这个唯一的family id呢?

  前文中的ctrl就用于用户空间获取family id的目的,它可以将family name转换为对应的family id,用户空间也通过family name向ctrl簇查询对应的family id。在应用层序获取了family id后它就可以像内核发送消息,该消息分别包含了字符串和数据,同时内核也在接受后进行回发操作。

  family id的获取程序流程图如下所示:



  从用户空间的main函数开始,介绍用户空间下怎么获取family id。

/*<-------------main函数-------------->*/
int main(int argc, char* argv[])
{
......

/* 获取family id */
nl_family_id = demo_get_family_id(nl_fd); //获取family id接口
if (!nl_family_id) {
fprintf(stderr, "Error getting family id, errno %d\n", errno);
goto out;
}
PRINTF("family id %d\n", nl_family_id);
......
}


/*<----------------demo_get_family_id函数------------->*/
static int demo_get_family_id(int sd)
{
struct msgtemplate ans;

char name[100];
int id = 0, ret;
struct nlattr *na;
int rep_len;
/* 根据gen family name查询family id */
//封装查询消息并向内核的ctrl簇发送
strcpy(name, DEMO_GENL_NAME);
ret = demo_send_cmd(sd, GENL_ID_CTRL, getpid(), CTRL_CMD_GETFAMILY,         //发送,代码如下说明
CTRL_ATTR_FAMILY_NAME, (void *)name, strlen(DEMO_GENL_NAME)+1);
if (ret < 0)
return 0;

/* 接收内核消息 */
rep_len = recv(sd, &ans, sizeof(ans), 0);       //接收内核的回发结果然后解析出其中的family id
if (ans.n.nlmsg_type == NLMSG_ERROR || (rep_len < 0) || !NLMSG_OK((&ans.n), rep_len))
return 0;
/* 解析family id */
//找到回发消息中的第二个attr
na = (struct nlattr *) GENLMSG_DATA(&ans);
na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len));
if (na->nla_type == CTRL_ATTR_FAMILY_ID) {
id = *(__u16 *) NLA_DATA(na);           //获取出其中的family id号
}

return id;                      //至此用户程序成功获取的了demo family的id号,接下来就可以向他发送消息了
}


//消息的封装过程同内核态消息封装过程类似,需严格按照genelink消息格式进行封装
static int demo_send_cmd(int sd, __u16 nlmsg_type, __u32 nlmsg_pid,
__u8 genl_cmd, __u16 nla_type,
void *nla_data, int nla_len)
{
struct nlattr *na;
struct sockaddr_nl nladdr;
int r, buflen;
char *buf;
struct msgtemplate msg;
/* 填充msg (本函数发送的msg只填充一个attr) */
//首先填充netlink消息头
msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
msg.n.nlmsg_type = nlmsg_type;
msg.n.nlmsg_flags = NLM_F_REQUEST;
msg.n.nlmsg_seq = 0;
msg.n.nlmsg_pid = nlmsg_pid;

//然后填充genetlink消息头

msg.g.cmd = genl_cmd; //消息cmd字段为CTRL_CMD_GETFAMILY
msg.g.version = DEMO_GENL_VERSION; //version字段为DEMO_GENL_VERSION(同内核保持一致)

//填充一个attr属性
na = (struct nlattr *) GENLMSG_DATA(&msg);
na->nla_type = nla_type;                                                                                  //例子中为CTRL_ATTR_FAMILY_NAME
na->nla_len = nla_len + 1 + NLA_HDRLEN;
memcpy(NLA_DATA(na), nla_data, nla_len);                                                  //将传入的family name拷贝到属性attr的payload载荷中
msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len);                                       //更新各个消息头中的长度字段
buf = (char *) &msg;
buflen = msg.n.nlmsg_len;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
/* 循环发送直到发送完成 */
while ((r = sendto(sd, buf, buflen, 0, (struct sockaddr *) &nladdr, sizeof(nladdr))) < buflen) { //启动发送流程,指定目的地址的地址簇为AF_NETLINK,ID号为0(表示内核)。
if (r > 0) {
buf += r;
buflen -= r;
} else if (errno != EAGAIN)
return -1;
}

return 0;
}


  了解了用户端的获取代码流程,接下来看内核端的Ctrl簇是如何处理接收到的查询消息的。

  在netlink函数调用流程的最后会调用具体协议类型的netlink_rcv()回调函数,其中genetlink的回调函数在前文中已经看到为genl_rcv():

static void genl_rcv(struct sk_buff *skb)
{
down_read(&cb_lock);
//netlink_rcv_skb()函数会对消息进行一些通用性的处理,将用户消息封装成genl_info结构,最后会把消息控制权交给genl_rcv_msg()回调函数
netlink_rcv_skb(skb, &genl_rcv_msg); //内核端接收,参数中第一个为消息skb,第二个为genl_rcv_msg回调函数。
up_read(&cb_lock);
}


  这里出现了又一个主要的结构体:genl_info。

//目前由于设定的nlmsg_flags为NLM_F_REQUEST、nlmsg_type为GENL_ID_CTRL(即NLMSG_MIN_TYPE),因此调用genl_rcv_msg()回调函数开始消息处理流程
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *,
struct nlmsghdr *))
{
struct nlmsghdr *nlh;
int err;
while (skb->len >= nlmsg_total_size(0)) { //判断消息的长度是否不小于netlink消息头的长度
int msglen;
nlh = nlmsg_hdr(skb);
err = 0;

//进行一些基本的数据长度判断
if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
return 0;
/* Only requests are handled by the kernel */
//根据nlmsg_flags和nlmsg_type字段判断是否跳过消息处理流程、以及是否回发ACK相应
if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
goto ack;
/* Skip control messages */
if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
goto ack;
err = cb(skb, nlh);
if (err == -EINTR)
goto skip;
ack:
if (nlh->nlmsg_flags & NLM_F_ACK || err)
netlink_ack(skb, nlh, err);
skip:
msglen = NLMSG_ALIGN(nlh->nlmsg_len);
if (msglen > skb->len)
msglen = skb->len;
skb_pull(skb, msglen);
}
return 0;
}


static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct genl_family *family;
int err;
family = genl_family_find_byid(nlh->nlmsg_type); //通过nlmsg_type字段(即family id号)在散列表中查找到对应的注册family
if (family == NULL)
return -ENOENT;
if (!family->parallel_ops) //如果消息处理不可重入,则这里会上锁
genl_lock();
err = genl_family_rcv_msg(family, skb, nlh); //调用genl_family_rcv_msg()函数(下面代码说明)
if (!family->parallel_ops)
genl_unlock();
return err;
}

static int genl_family_rcv_msg(struct genl_family *family,
struct sk_buff *skb,
struct nlmsghdr *nlh)
{
const struct genl_ops *ops;
struct net *net = sock_net(skb->sk);
struct genl_info info;
struct genlmsghdr *hdr = nlmsg_data(nlh);
struct nlattr **attrbuf;
int hdrlen, err;
/* this family doesn't exist in this netns */
if (!family->netnsok && !net_eq(net, &init_net)) //函数首先判断网络命名空间,若不支持则当前消息的网络空间必须为init_net
return -ENOENT;
hdrlen = GENL_HDRLEN + family->hdrsize; //判断消息的长度
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
return -EINVAL;

ops = genl_get_cmd(hdr->cmd, family); //这里找到消息cmd命令对应的处理函数并保存于ops变量中,查找的方式是通过cmd字段的匹配类型来找的,这里找到的就是前文中注册的demo_ops结构了
if (ops == NULL)
return -EOPNOTSUPP;

if ((ops->flags & GENL_ADMIN_PERM) && !netlink_capable(skb, CAP_NET_ADMIN)) //判断权限,这里由于已经在demo_ops中设置了GENL_ADMIN_PERM标识,因此本命令操作需要具有CAP_NET_ADMIN权限
return -EPERM;

//如果用户设定了NLM_F_DUMP标识,这里就会调用启动dump流程,回填skb消息(这里的skb将不再是用户下发的消息了)。
if ((nlh->nlmsg_flags & NLM_F_DUMP) == NLM_F_DUMP) {
int rc;
if (ops->dumpit == NULL)
return -EOPNOTSUPP;
if (!family->parallel_ops) {
struct netlink_dump_control c = {
.module = family->module,
/* we have const, but the netlink API doesn't */
.data = (void *)ops,
.dump = genl_lock_dumpit,
.done = genl_lock_done,
};
genl_unlock();
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
genl_lock();
} else {
struct netlink_dump_control c = {
.module = family->module,
.dump = ops->dumpit,
.done = ops->done,
};
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
}
return rc;
}

//为attr属性指定接收缓存
//!!!需要注意的是这里的内存其实只是一个指针数组,用来存放attr属性的地址,并不会存放实际的属性数据。
if (family->maxattr && family->parallel_ops) { //在支持重入的情况下这里会另行动态分配内存,否则使用在注册family的__genl_register_family函数中分配的内存空间
attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);
if (attrbuf == NULL)
return -ENOMEM;
} else
attrbuf = family->attrbuf;

if (attrbuf) {
err = nlmsg_parse(nlh, hdrlen, attrbuf, family->maxattr, ops->policy); //将消息的数据拷贝到缓存空间中去(在下面代码中进行具体说明)
//参数:
// nlh:netlink消息头
// hdrlen:genelink消息头长度(其实也包括了用户私有头,只不过这里为0罢了)
// attrbuf:数据属性缓存地址
// family->maxattr:缓存空间大小
// ops->policy:属性有效性策略结构
if (err < 0)
goto out;
}

//开始封装genl_info消息结构,填充对应的字段
info.snd_seq = nlh->nlmsg_seq;
info.snd_portid = NETLINK_CB(skb).portid; //发送端的套接字ID号
info.nlhdr = nlh;
info.genlhdr = nlmsg_data(nlh);
info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
info.attrs = attrbuf; //为前文中分配的attr缓存空间首地址
info.dst_sk = skb->sk;
genl_info_net_set(&info, net);
memset(&info.user_ptr, 0, sizeof(info.user_ptr));

//如果在注册family时指定了pre_doit和post_doit回调函数,将在分别调用ops->doit()函数的前后调用他们。
//在当前获取family id的操作中,对于Ctrl簇而言并没有定义,这里会直接调用ops->doit()回调函数,对于CTRL_CMD_GETFAMILY来说就是ctrl_getfamily()(在下面代码中具体说明)
if (family->pre_doit) {
err = family->pre_doit(ops, skb, &info);
if (err)
goto out;
}
err = ops->doit(skb, &info);
if (family->post_doit)
family->post_doit(ops, skb, &info);
...
}


static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct genl_family *family;
int err;
family = genl_family_find_byid(nlh->nlmsg_type); //通过nlmsg_type字段(即family id号)在散列表中查找到对应的注册family
if (family == NULL)
return -ENOENT;
if (!family->parallel_ops) //如果消息处理不可重入,则这里会上锁
genl_lock();
err = genl_family_rcv_msg(family, skb, nlh); //调用genl_family_rcv_msg()函数(下面代码说明)
if (!family->parallel_ops)
genl_unlock();
return err;
}

static int genl_family_rcv_msg(struct genl_family *family,
struct sk_buff *skb,
struct nlmsghdr *nlh)
{
const struct genl_ops *ops;
struct net *net = sock_net(skb->sk);
struct genl_info info;
struct genlmsghdr *hdr = nlmsg_data(nlh);
struct nlattr **attrbuf;
int hdrlen, err;
/* this family doesn't exist in this netns */
if (!family->netnsok && !net_eq(net, &init_net)) //函数首先判断网络命名空间,若不支持则当前消息的网络空间必须为init_net
return -ENOENT;
hdrlen = GENL_HDRLEN + family->hdrsize; //判断消息的长度
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
return -EINVAL;

ops = genl_get_cmd(hdr->cmd, family); //这里找到消息cmd命令对应的处理函数并保存于ops变量中,查找的方式是通过cmd字段的匹配类型来找的,这里找到的就是前文中注册的demo_ops结构了
if (ops == NULL)
return -EOPNOTSUPP;

if ((ops->flags & GENL_ADMIN_PERM) && !netlink_capable(skb, CAP_NET_ADMIN)) //判断权限,这里由于已经在demo_ops中设置了GENL_ADMIN_PERM标识,因此本命令操作需要具有CAP_NET_ADMIN权限
return -EPERM;

//如果用户设定了NLM_F_DUMP标识,这里就会调用启动dump流程,回填skb消息(这里的skb将不再是用户下发的消息了)。
if ((nlh->nlmsg_flags & NLM_F_DUMP) == NLM_F_DUMP) {
int rc;
if (ops->dumpit == NULL)
return -EOPNOTSUPP;
if (!family->parallel_ops) {
struct netlink_dump_control c = {
.module = family->module,
/* we have const, but the netlink API doesn't */
.data = (void *)ops,
.dump = genl_lock_dumpit,
.done = genl_lock_done,
};
genl_unlock();
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
genl_lock();
} else {
struct netlink_dump_control c = {
.module = family->module,
.dump = ops->dumpit,
.done = ops->done,
};
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
}
return rc;
}

//为attr属性指定接收缓存
//!!!需要注意的是这里的内存其实只是一个指针数组,用来存放attr属性的地址,并不会存放实际的属性数据。
if (family->maxattr && family->parallel_ops) { //在支持重入的情况下这里会另行动态分配内存,否则使用在注册family的__genl_register_family函数中分配的内存空间
attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);
if (attrbuf == NULL)
return -ENOMEM;
} else
attrbuf = family->attrbuf;

if (attrbuf) {
err = nlmsg_parse(nlh, hdrlen, attrbuf, family->maxattr, ops->policy); //将消息的数据拷贝到缓存空间中去(在下面代码中进行具体说明)
//参数:
// nlh:netlink消息头
// hdrlen:genelink消息头长度(其实也包括了用户私有头,只不过这里为0罢了)
// attrbuf:数据属性缓存地址
// family->maxattr:缓存空间大小
// ops->policy:属性有效性策略结构
if (err < 0)
goto out;
}

//开始封装genl_info消息结构,填充对应的字段
info.snd_seq = nlh->nlmsg_seq;
info.snd_portid = NETLINK_CB(skb).portid; //发送端的套接字ID号
info.nlhdr = nlh;
info.genlhdr = nlmsg_data(nlh);
info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
info.attrs = attrbuf; //为前文中分配的attr缓存空间首地址
info.dst_sk = skb->sk;
genl_info_net_set(&info, net);
memset(&info.user_ptr, 0, sizeof(info.user_ptr));

//如果在注册family时指定了pre_doit和post_doit回调函数,将在分别调用ops->doit()函数的前后调用他们。
//在当前获取family id的操作中,对于Ctrl簇而言并没有定义,这里会直接调用ops->doit()回调函数,对于CTRL_CMD_GETFAMILY来说就是ctrl_getfamily()(在下面代码中具体说明)
if (family->pre_doit) {
err = family->pre_doit(ops, skb, &info);
if (err)
goto out;
}
err = ops->doit(skb, &info);
if (family->post_doit)
family->post_doit(ops, skb, &info);
...
}


//该函数间接调用netlink通用的属性拷贝函数
static inline int nlmsg_parse(const struct nlmsghdr *nlh, int hdrlen,
struct nlattr *tb[], int maxtype,
const struct nla_policy *policy)
{
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
return -EINVAL;
return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy); //第三个参数为attr参数的首地址:nlmsg_attrdata(nlh, hdrlen),第四个参数为attr属性的长度:nlmsg_attrlen(nlh, hdrlen)
}

static inline struct nlattr *nlmsg_attrdata(const struct nlmsghdr *nlh,
int hdrlen)
{
unsigned char *data = nlmsg_data(nlh); //将指针跳过netlink的头以及genelink头,指向attr的首地址
return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
}

static inline int nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
{
return nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen); //计算方式为消息除去netlink消息头的剩余长度减去genetlink消息头长度后的长度
}

static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
[CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },
[CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING,
.len = GENL_NAMSIZ - 1 },
};
//该函数会逐一的将属性的地址复制到tb指针数组中去
int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
int len, const struct nla_policy *policy)
{
const struct nlattr *nla;
int rem, err;
memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
nla_for_each_attr(nla, head, len, rem) {
u16 type = nla_type(nla);
if (type > 0 && type <= maxtype) {
if (policy) { //如果传入了有效性策略,那他就会调用validate_nla函数执行有效性判断
err = validate_nla(nla, maxtype, policy); //对于这里传入的CTRL_ATTR_FAMILY_NAME属性来说,在ctrl_policy中已经定义了有效性限制为NLA_NUL_STRING,最大长度为GENL_NAMSIZ-1(如上方ctrl_policy所示)
if (err < 0)
goto errout;
}
tb[type] = (struct nlattr *)nla;
}
}
if (unlikely(rem > 0))
pr_warn_ratelimited("netlink: %d bytes leftover after parsing attributes in process `%s'.\n", rem, current->comm);
err = 0;
errout:
return err;
}


static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
struct genl_family *res = NULL;
int err = -EINVAL;
if (info->attrs[CTRL_ATTR_FAMILY_ID]) { //匹配CTRL_ATTR_FAMILY_ID,由于并未传入该属性数据,因此这里该属性的地址为NULL,然后接着判断另一个属性类型
u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]);
res = genl_family_find_byid(id);
err = -ENOENT;
}

//开始处理CTRL_CMD_GETFAMILY属性
if (info->attrs[CTRL_ATTR_FAMILY_NAME]) {
char *name;
name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]);
res = genl_family_find_byname(name); //通过用户传入的family name获取到对应的family结构
err = -ENOENT;
}

if (res == NULL)
return err;
if (!res->netnsok && !net_eq(genl_info_net(info), &init_net)) {
/* family doesn't exist here */
return -ENOENT;
}
//!!!注意回发消息的cmd为CTRL_CMD_NEWFAMILY(它会将查询结果family的全部内容回传),指定的port_id号为消息查询端的id(并不是内核的id号0),消息的sequence也同查询消息一致
msg = ctrl_build_family_msg(res, info->snd_portid, info->snd_seq, CTRL_CMD_NEWFAMILY); //ctrl_build_family_msg()函数封装回发消息(前一篇文章中已经说明过)
if (IS_ERR(msg))
return PTR_ERR(msg);
return genlmsg_reply(msg, info); //向应用层回发消息,就是nlmsg_unicast的一个封装(如下代码所示)。
}

static inline int genlmsg_reply(struct sk_buff *skb, struct genl_info *info)
{
return genlmsg_unicast(genl_info_net(info), skb, info->snd_portid);
}


3.2、向内核Demo Family发送消息

  用户端向内核发送具体数据时,用户端代码:

/*<--------main接口内容--------->*/
/* 发送字符串消息 */
my_pid = getpid();
string = argv[1];
data = atoi(argv[2]);

ret = demo_send_cmd(nl_fd, nl_family_id, my_pid, DEMO_CMD_ECHO, DEMO_CMD_ATTR_MESG, string, strlen(string) + 1);
if (ret < 0) {
fprintf(stderr, "failed to send echo cmd\n");
goto out;
}
/* 发送数据消息 */
//参数说明:
// 1、第二个入参设置为刚刚获取的demo family id
// 2、第三个参数为当前进程的pid号
// 3、第四个参数为命令序号cmd,DEMO_CMD_ECHO
// 4、第五个参数为发送的属性DEMO_CMD_ATTR_DATA
// 5、第六哥参数为消息内容
// 6、第七个参数为消息长度
ret = demo_send_cmd(nl_fd, nl_family_id, my_pid, DEMO_CMD_ECHO, DEMO_CMD_ATTR_DATA, &data, sizeof(data));
if (ret < 0) {
fprintf(stderr, "failed to send echo cmd\n");
goto out;
}


3.3 向内核Demo Family回发消息

  应用层序向demo family发送DEMO_CMD_ECHO消息后,内核会调用到前文中注册时指定的doit回调函数demo_echo_cmd。

//判断接收的属性类型,并做出相应的处理(注意:为了简单起见,该doit回调函数最多一次只能处理一种类型的attr属性)
static int demo_echo_cmd(struct sk_buff *skb, struct genl_info *info)
{
if (info->attrs[DEMO_CMD_ATTR_MESG])
return cmd_attr_echo_message(info);
else if (info->attrs[DEMO_CMD_ATTR_DATA])
return cmd_attr_echo_data(info); //同cmd_attr_echo_message()函数基本类似,唯一的区别就是调用了内核提供的nla_get_s32()和nla_put_s32()这两个封装函数来获取和设置s32类型的attr属性
else
return -EINVAL;
}


static int cmd_attr_echo_message(struct genl_info *info)
{
struct nlattr *na;
char *msg;
struct sk_buff *rep_skb;
size_t size;
int ret;
/* 读取用户下发的消息 */
na = info->attrs[DEMO_CMD_ATTR_MESG]; //从DEMO_CMD_ATTR_MESG属性地址处取出用户下发的消息内容
if (!na)
return -EINVAL;
msg = (char *)nla_data(na);
pr_info("demo generic netlink receive echo mesg %s\n", msg);
/* 回发消息 */
size = nla_total_size(strlen(msg)+1);

/* 准备构建消息 */
//参数说明:
// 第一个参数:为接收genl_info消息
// 第二个参数:回发cmd类型
// 第三个参数:skb指针地址
// 第四个参数:回发数据长度
ret = demo_prepare_reply(info, DEMO_CMD_REPLY, &rep_skb, size); //构建回发消息头
if (ret < 0)
return ret;
/* 填充消息 */
ret = demo_mk_reply(rep_skb, DEMO_CMD_ATTR_MESG, msg, size);
if (ret < 0)
goto err;
/* 完成构建并发送 */
return demo_send_reply(rep_skb, info);
err:
nlmsg_free(rep_skb);
return ret;
}


static int demo_prepare_reply(struct genl_info *info, u8 cmd, struct sk_buff **skbp, size_t size)
{
struct sk_buff *skb;
void *reply;
/*
* If new attributes are added, please revisit this allocation
*/
skb = genlmsg_new(size, GFP_KERNEL); //调用genlmsg_new()函数申请skb套接字缓存空间
if (!skb)
return -ENOMEM;
if (!info)
return -EINVAL;
/* 构建回发消息头 */
reply = genlmsg_put_reply(skb, info, &demo_family, 0, cmd); //构建回发消息的netlink消息头和genetlink消息头
if (reply == NULL) {
nlmsg_free(skb);
return -EINVAL;
}
*skbp = skb;
return 0;
}

static inline void *genlmsg_put_reply(struct sk_buff *skb,
struct genl_info *info,
struct genl_family *family,
int flags, u8 cmd)
{
return genlmsg_put(skb, info->snd_portid, info->snd_seq, family, flags, cmd); //!!!注意入参info->snd_portid为用户层的netlink套接字的id号
}


static int demo_mk_reply(struct sk_buff *skb, int aggr, void *data, int len)
{
/* add a netlink attribute to a socket buffer */
return nla_put(skb, aggr, len, data); //将字符串消息填充到第一个attr属性中,同时指定attr的属性类型为DEMO_CMD_ATTR_MESG
}


static int demo_send_reply(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
void *reply = genlmsg_data(genlhdr);
genlmsg_end(skb, reply); //调用genlmsg_end()更新消息头重的长度字段
return genlmsg_reply(skb, info); //启动回发流程,为genlmsg_unicast()的一个封装
}


3.4、应用层接收内核Demo Family回发消息

int main(int argc, char* argv[])
{
......
/* 接收用户消息并解析(本示例程序中仅解析2个) */
demo_msg_recv_analysis(nl_fd, argc-1);
......
}


void demo_msg_recv_analysis(int sd, int num)
{
int rep_len;
int len;
struct nlattr *na;
struct msgtemplate msg;

unsigned int data;
char *string;
while (num--) { //num表示接收消息的个数
/* 接收内核消息回显 */
rep_len = recv(sd, &msg, sizeof(msg), 0); //循环调用recv函数阻塞式的接收内核netlink消息
if (rep_len < 0 || demo_msg_check(msg, rep_len) < 0) { //demo_msg_check判断消息的有效性
fprintf(stderr, "nonfatal reply error: errno %d\n", errno);
continue;
}
PRINTF("received %d bytes\n", rep_len);
PRINTF("nlmsghdr size=%zu, nlmsg_len=%d, rep_len=%d\n", sizeof(struct nlmsghdr), msg.n.nlmsg_len, rep_len);

rep_len = GENLMSG_PAYLOAD(&msg.n);
na = (struct nlattr *) GENLMSG_DATA(&msg);
len = 0;

/* 一个msg里可能有多个attr,所以这里循环读取 */
while (len < rep_len) {
len += NLA_ALIGN(na->nla_len);
switch (na->nla_type) {
case DEMO_CMD_ATTR_MESG:
/* 接收到内核字符串回显 */
string = (char *) NLA_DATA(na);
printf("echo reply:%s\n", string);
break;
case DEMO_CMD_ATTR_DATA:
/* 接收到内核数据回显 */
data = *(int *) NLA_DATA(na);
printf("echo reply:%u\n", data);
break;
default:
fprintf(stderr, "Unknown nla_type %d\n", na->nla_type);
}
na = (struct nlattr *) (GENLMSG_DATA(&msg) + len);
}
}
}

int demo_msg_check(struct msgtemplate msg, int rep_len)
{
if (msg.n.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.n), rep_len)) { //判断消息头中的nlmsg_type字段,如果该字段为NLMSG_ERROR表示接收到了错误的消息,应该立即丢弃
//判断消息的长度是否足够,使用的是NLMSG_OK宏(见netlink.h)
struct nlmsgerr *err = NLMSG_DATA(&msg); //接收函数循环读取attr属性并根据属性的attr类型单独进行处理,本示例中仅仅在终端中打印
fprintf(stderr, "fatal reply error,  errno %d\n", err->error);
return -1;
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  kernel 通信 框架