转载自 :
http://blog.chinaunix.net/uid-28541347-id-5589868.html
https://blog.csdn.net/ty3219/article/details/63683698

一、概述

之前一篇博文讲述了Netlink如何使用,当时我们是自己定义了一种协议NETLINK_TEST进行用户进程和内核的通信,这篇文章就讲一下如何使用内核为我们提供的NETLINK_GENERIC进行通信。如果把:Netlink协议比作IP层,那么NETLINK_GENERIC可以比作UDP这一层。它们的具体关系如下图:

数据格式:

图1中NETLINK_GENERIC协议作为Netlink层的payload,当然NETLINK_GENERIC作为一个上层协议,也拥有自己的头部(图中的Family头部)和payload(图中的Family payload)。我们用户要填写的就是图中的Family payload,但是这个Family payload也是要有一定规则和格式的。

首先,Family payload同样由头部和payload构成,即图中的User msg和Msg payload,但是User msg不是必须的,可以没有,只是预留给特殊的用户需求。

其次,Msg payload也需要遵循一定格式,即TLV(Type、Length、Value),不过这并不是NETLINK_GENERIC的特殊要求,其实Netlink协议的数据部分都是这个格式。每个TLV我们称之为一个Attr

通讯流程:

对于从user to kernel的通讯,driver必须先向内核注册一个struct genl_family,并且注册一些cmd的处理函数(genl_ops),这些cmd是跟某个family关联起来的。注册family的时候内核自动为这个family分配一个ID。每个family都有一个唯一的ID,其中ID号0x10是被内核的nlctrl family(就是图3中的controller)所使用。当注册成功以后,如果user program向某个family发送cmd,那么内核就会回调对应cmd的处理函数。对于user program,使用前,除了要创建一个socket并绑定地址以外,还需要先通过family的名字获取family的ID(向nlctrl这个family查询)。有了family的ID以后,才能向该family发送cmd。

对于从kernel to user的通讯,采用的是广播的形式,只要user program监听了,都能收到。但是同样的,user program在监听前,也必须先查询到family的ID。

二、相关概念及数据结构

2.1 family

一个family即相当于我们的一个UDP addr,用以区别不同的连接。Family通过向genl控制器请求获得,至于什么是genl控制器,这个用户不用关心,对使用者是透明的。genl family的结构体如下:

/* struct genl_family - generic netlink family */
struct genl_family {unsigned int        id;//protocol family idenfitierunsigned int     hdrsize;//length of user specific header in byteschar           name[GENL_NAMSIZ];//name of familyunsigned int      version;//protocol versionunsigned int      maxattr;//maximum number of attributes supportedbool            netnsok;//set to true if the family can handle network namespaces and should be presented in all of thembool            parallel_ops;int    (*pre_doit)(const struct genl_ops *ops,struct sk_buff *skb,struct genl_info *info);//called before an operation's doit callback, it may    do additional, common, filtering and return an errorvoid (*post_doit)(const struct genl_ops *ops, struct sk_buff *skb,struct genl_info *info);//called after an operation's doit callback, it may undo operations done by pre_doit, for example release locks私有字段由系统自动配置struct nlattr **   attrbuf;    /* private */ buffer to store parsed attributes const struct genl_ops * ops;    /* private */the operations supported by this familyconst struct genl_multicast_group *mcgrps; /* private */multicast groups used by this family unsigned int       n_ops;      /* private */number of operations supported by this family unsigned int     n_mcgrps;   /* private */multicast groups used by this familyunsigned int       mcgrp_offset;   /* private */struct list_head   family_list;    /* private */family liststruct module       *module;
};

id: 即family id。用于标识一个family,当新注册一个family的时候,应该用GENL_ID_GENERATE宏(0x0),表示请控制器自动为family分配的一个id。0x00保留供genl控制器使用,其实就是GENL_ID_GENERATE。(这个和我们Netlink头部中的pid不同,对比图2可以看出family id 其实就是netlink 头部的type字段)

hdrsize: 用户自定议头部长度。即图1中User Msg的长度。如果没有用户自定义头部,这个值被赋为0。

version: 版本号,一般填1即可。

name: family名,要求不同的family使用不同的名字。以便控制器进行正确的查找。

maxattr:genl使用netlink标准的attr来传输数据。此字段定义了最大attr类型数。(注意:不是一次传输多少个attr,而是一共有多少种attr,因此,这个值可以被设为0,为0代表不区分所收到的数据的attr type)。在接收数据时,可以根据attr type,获得指定的attr type的数据在整体数据中的位置。

其他私有字段,由系统自动配置,开发者不需要做配置。

好了,介绍完family后这个family结构的主要作用就比较清晰了,family就是描述我们将要如何使用NETLINK_GENERIC的payload,以及确定Netlink头部的type信息和genl netlink头部的version信息。

2.2 genl_ops 结构体

这个结构体的作用其实就是定义当我们收到数据该怎么处理的动作。可以理解为回调函数(成员doit就是)。但是为什么要封装成一个结构,因为NETLINK_GENERIC对回调动作提供了更加细粒度的控制。从图2中我们可以看到,NETLINK_GENERIC消息中在family head中有cmd字段,这个字段就是提供给用户对不通cmd的消息采取不同的操作(operation)用的。NETLINK_GENERIC提供更细粒度的报文回调操作还不仅体现在cmd字段。我们之前说了用户数据是由attr组成的,那么这里genl_ops就能够再调用回调函数前对各个attr进行过滤,这是通过其policy字段完成的。下面我们看详细定义。

/* struct genl_ops - generic netlink operations */
struct genl_ops {const struct nla_policy    *policy;//policy定义了attr的类型和数据长度,用policy来作校验attribute validation policyint    (*doit)(struct sk_buff *skb, struct genl_info *info);//standard command callbackint (*start)(struct netlink_callback *cb);//start callback for dumpsint (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);//callback for dumpersint (*done)(struct netlink_callback *cb);//completion callback for dumpsu8            cmd;//command identifieru8          internal_flags;//flags used by the familyu8         flags;
};

cmd: 命令名。用于识别各genl_ops,根据数据包中的cmd字段会调用同名的operation处理。
flag: 各种设置属性,以“或”连接。在需要admin特权级别时,使用GENL_ADMIN_PERM。
policy:定义了attr规则。如果此指针非空,genl在触发事件处理程序之前,会使用这个字段来对帧中的attr做校验(见nlmsg_parse函数)。该字段可以为空,表示在触发事件处理程序之前,不做校验。 policy是一个struct nla_policy的数组。struct nla_policy结构体表示如下:

struct nla_policy {/*其中,type字段表示attr中的数据类型,这里一定不要搞混,这里的type和attr中的type可不是一回事,
attr中的type是用户自己定义的类型,只有用户自己能够理解。而这里的type是attr中的value中内容的类型。
可被配置为:
1) NLA_UNSPEC--未定义 ;
2) NLA_U8, NLA_U16, NLA_U32, NLA_U64为8bits, 16bits, 32bits, 64bits的无符号整型 ;
3) NLA_STRING--字符串 ;
4) NLA_NUL_STRING--空终止符字符串 ;
5) NLA_NESTED--attr流 ;*/u16      type;//attr中的数据类型/*如果在type字段配置的是字符串有关的值,要把len设置为字符串的最大长度(不包含结尾的'\0')。如果type字段未设置或被设置为NLA_UNSPEC,那么这里要设置为attr的payload部分的长度。 */u16      len;
};

doit:这是一个回调函数。在generic netlink收到数据时触发,运行在进程上下文。doit传入两个参数,skb为触发此回调函数的socket buffer。第二个参数是一个genl_info结构体,当内核调用回调函数时这个结构体会被填充,定义如下:

/* struct genl_info - receiving information*/
struct genl_info {u32           snd_seq;//sending sequence numberu32            snd_portid;//netlink portid of senderstruct nlmsghdr *  nlhdr;//netlink header的指针  netlink message headerstruct genlmsghdr *    genlhdr;//genl头部的指针(即family头部)generic netlink message headervoid *            userhdr;//用户自定义头部指针;user specific header/*netlink attributes 如果定义了genl_ops->policy,这里的attrs是被policy过滤以后的结果。在完成了操作以后,如果执行正确,返回0;否则,返回一个负数。负数的返回值会触发NLMSG_ERROR消息。当genl_ops的flag标志被添加了NLMSG_ERROR时,即使doit返回0,也会触发NLMSG_ERROR消息;*/struct nlattr **   attrs;
#ifdef CONFIG_NET_NSstruct net *        _net;//network namespace
#endifvoid *            user_ptr[2];//user pointersstruct sock *        dst_sk;//destination socket
};

dumpit:这是一个回调函数,当genl_ops的flag标志被添加了NLM_F_DUMP以后,每次收到genl消息即会回触发这个函数。dumpit与doit的区别是:dumpit的第一个参数skb不会携带从客户端发来的数据。相反地,开发者应该在skb中填入需要传给客户端的数据,然后,并skb的数据长度returnskb中携带的数据会被自动送到客户端只要dumpit的返回值大于0,dumpit函数就会再次被调用,并被要求在skb中填入数据当服务端没有数据要传给客户端时,dumpit要返回0。如果函数中出错,要求返回一个负值。关于doit和dumpit的触发过程,可以查看源码中的genl_rcv_msg函数。

2.3 genlmsghdr

这是NETLINK_GENERIC的头部结构,即图1中的family头部,图二中的黄色部分。

struct genlmsghdr {__u8  cmd;__u8    version;__u16   reserved;
};

三、服务端与客户端操作解析

3.1 服务端操作

3.1.1为注册family做准备(预定义attr类型)

/* attribute type */enum {EXMPL_A_UNSPEC,EXMPL_A_MSG,__EXMPL_A_MAX,};
#define EXMPL_A_MAX (__EXMPL_A_MAX - 1)//这个是定义的消息类型数,暂时不明白为啥是这个定义法,毕竟枚举默认从0开始,虽然例子中只用了一个attr,值虽然一样但是感觉怪怪的。

3.1.2 定义family

static struct genl_family exmpl_gnl_family = {.id = GENL_ID_GENERATE, //标示我们要控制器给我们分配.hdrsize = 0, //我们不需要用户数据头部.name = "myfamily", //我们的family名字.version = 1,.maxattr = EXMPL_A_MAX, //在我们的协议中可能用到的消息类型数};

3.1.3 注册family

state = genl_register_family(&exmpl_gnl_family);

3.1.4 为注册operation做准备(定义cmd类型、定义policy、定义回调函数)

enum {EXMPL_C_UNSPEC,EXMPL_C_ECHO,__EXMPL_C_ECHO,};
#define EXMPL_C_MAX (__EXMPL_C_MAX - 1)要提供给operation用的是一个policy数组,数组的个数要是我们之前定义的attr的数量,为什么呢?
因为要对每个attr对应提供的policy。那attr和policy怎么对应起来呢?答案是通过下标,假如有attr A,
则其对应的policy即为policys[A],所以我们定义的policy数组如下:static struct nla_policy exmpl_genl_policy[EXMPL_A_MAX + 1] = {[EXMPL_A_MSG] = { .type = NLA_NUL_STRING },};需要特别提醒的是接收回调函数同Netlink回调函数一样,用户发往内核是同步的,这个回调函数执行结束
用户态的发送函数才能够返回。我们这个例子的回调函数主要做的就是将客户端传来的数据打印出来,并向客户端回发一个字符串。所以这个回调
函数的重点就是如何根据genl netlink报文的格式收到的客户端信息进行解析,以及如何构造回发报文。注意,这里我们并没有用genl_info结构中的信息,genl_info中包含了很多系统为我们已经解析填充过的报文字段,
祥见genl_info结构的定义,其中获取payload中的attr可以直接通过genl_info获取,但是我们还是选择从最原始
的报文一段段的解析获取,就是为了让大家更加了解报文的结构。
int genl_recv_doit(struct sk_buff *skb, struct genl_info *info)
{struct nlmsghdr *nlhdr;struct genlmsghdr *genlhdr;struct nlattr *nla;int len;char* str;char* data = "I am from kernel!"; //要回发给客户端的数据int state = 0;nlhdr = nlmsg_hdr(skb); //获取netlink首部genlhdr = nlmsg_data(nlhdr); //获取genl netlink首部nla = genlmsg_data(genlhdr); //获取genl_netlink的payloadprintk ("received\n");str = (char *)NLA_DATA(nla); //用户数据是一个attr,我们要获取其valueprintk("%s\n",str);len = stringlength(data);//向客户端回发数据if ( (state = genl_msg_send_to_user(data, len, nlhdr->nlmsg_pid)) <0 ){printk(KERN_ERR "genl_msg_send_to_user error!");return 1;}return 0;
}

3.1.5 定义operation

struct genl_ops exmpl_gnl_ops_echo = {.cmd = EXMPL_C_ECHO, //我们只关心cmd为EXMPL_C_ECHO的消息.flags = 0,.policy = exmpl_genl_policy,.doit = genl_recv_doit, //后面回调函数详细分析.dumpit = NULL,};

3.1.6 注册operation

state = genl_register_ops(&exmpl_gnl_family, &exmpl_gnl_ops_echo);

3.2 客户端操作

3.2.1 创建socket

sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

3.2.2 bind socket

/*Struct sockaddr_ln为Netlink的地址,和我们通常socket编程中的sockaddr_in作用一样*/
struct sockaddr_nl src_addr;//
// To prepare binding
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
/*其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为
当前进程的进程号。前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可实现用户
空间两个进程之间,或内核空间两个进程之间的通信。该属性为0时一般指内核。*/
src_addr.nl_pid = getpid();
/*该字段指明了调用者希望加入的多播组号的掩码。如果该字段为0则表示调用者不希望加入任何多播组。
对于每个隶属于Netlink协议域的协议,最多可支持32个多播组(因为nl_groups的长度为32比特),
每个多播组用一个比特来表示。 */
src_addr.nl_groups = 0;
//Bind
retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));

3.2.3 获取family id

family_id = genl_get_family_id(sock_fd ,"myfamily");//其中myfamily是我们之前再服务端注册的family name。genl_get_family_id的定义如下:
static int genl_get_family_id(int sock_fd, char *family_name)
{msgtemplate_t ans;int id, rc;struct nlattr *na;int rep_len;//通过发送genl netlink消息获取family idrc = genl_send_msg(sock_fd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,CTRL_ATTR_FAMILY_NAME, (void *)family_name,strlen(family_name)+1);//接收返回消息,其中含有family idrep_len = recv(sock_fd, &ans, sizeof(ans), 0);if (rep_len < 0) {return 1;}if (ans.nlh.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&ans.nlh), rep_len)){return 1;}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);} else {id = 0;}return id;
}
由于family id的获取也是需要发送genl netlink消息获取的,所以我们使用稍后发送genl netlink数据同样的函数。只是这里不是向服务端发送消息,而是向控制器发送。这个函数用到的msgtemplate_t是我们自己定义的一个对Netlink和genl netlink头部的封装,定义如下:
typedef struct msgtemplate {struct nlmsghdr nlh;struct genlmsghdr gnlh;char data[MAX_MSG_SIZE];
} msgtemplate_t;

3.2.4 向服务端发送消息

   这个函数的参数比较多,如下所示:
1) sock_fd: 客户端socket
2) family_id: family id
3) nlmsg_pid: 客户端pid
4) genl_cmd: 命令类型
5) genl_version: genl版本号
6) nla_type: netlink attr类型
7) nla_data: 发送的数据
8) nla_len: 发送数据长度
int genl_send_msg(int sock_fd, u_int16_t family_id, u_int32_t nlmsg_pid,u_int8_t genl_cmd, u_int8_t genl_version, u_int16_t nla_type,void *nla_data, int nla_len)
{struct nlattr *na;struct sockaddr_nl dst_addr;int r, buflen;char *buf;msgtemplate_t msg;//0保留给控制器使用,其实就是GENL_ID_GENERATE,注册family才要if (family_id == 0) {return 0;}//构造netlink头部msg.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);msg.nlh.nlmsg_type = family_id;/*如果消息中有NLM_F_REQUEST标记位,说明这是一个请求消息。所有从用户空间到内核空间的消息都要设置该位,否则内核将向用户返回一个EINVAL无效参数的错误*/msg.nlh.nlmsg_flags = NLM_F_REQUEST;msg.nlh.nlmsg_seq = 0;msg.nlh.nlmsg_pid = nlmsg_pid;//构造genl_netlink头部msg.gnlh.cmd = genl_cmd;msg.gnlh.version = genl_version;na = (struct nlattr *) GENLMSG_DATA(&msg);na->nla_type = nla_type;na->nla_len = nla_len + 1 + NLA_HDRLEN;memcpy(NLA_DATA(na), nla_data, nla_len);msg.nlh.nlmsg_len += NLMSG_ALIGN(na->nla_len);buf = (char *) &msg;buflen = msg.nlh.nlmsg_len ;//构造目的地址memset(&dst_addr, 0, sizeof(dst_addr));dst_addr.nl_family = AF_NETLINK;dst_addr.nl_pid = 0; //向内核发送,目的pid为0dst_addr.nl_groups = 0; //单播while ((r = sendto(sock_fd, buf, buflen, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr))) < buflen) {if (r > 0) {buf += r;buflen -= r;} else if (errno != EAGAIN) {return -1;}}return 0;
}

3.2.4 从服务端接收消息

有的人可能一直对这个family id在报文中的作用比较疑惑,也就是Netlink头部的type字段,在这个函数中就能比较清晰的看出其作用了。其实如果把pid比作ip地址的话,那么family就可以看作是端口号。设想一下我们的用户进程使用pid为1234向内核发送了一个消息,然后等待接收。这时内核恰好有一个服务使用其他Netlink协议,比如自己定义的NETLINK_TEST也向pid为1234发送一个消息,我们的进程能收到吗?答案是肯定的,那我们怎么过滤这些其他协议的消息呢?这就是通过消息中的type字段。

void genl_rcv_msg(int family_id, int sock_fd, char *buf)
{int ret;struct msgtemplate msg;struct nlattr *na;ret = recv(sock_fd, &msg, sizeof(msg), 0);if (ret < 0) {return;}printf("received length %d\n", ret);if (msg.nlh.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.nlh), ret)) {return ;}//检验一下是不是我们需要的if (msg.nlh.nlmsg_type == family_id && family_id != 0) {na = (struct nlattr *) GENLMSG_DATA(&msg);strcpy(buf,(char *)NLA_DATA(na));}
}

四、服务端客户端完整代码

4.1 Kernel代码(server)

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/time.h>
#include <linux/types.h>
#include <net/sock.h>
#include <net/genetlink.h>
#define MAXLEN 256
#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))/* attribute type */enum {EXMPL_A_UNSPEC,EXMPL_A_MSG,__EXMPL_A_MAX,};
#define EXMPL_A_MAX (__EXMPL_A_MAX - 1)/* commands */enum {EXMPL_C_UNSPEC,EXMPL_C_ECHO,__EXMPL_C_ECHO,};
#define EXMPL_C_MAX (__EXMPL_C_MAX - 1)int stringlength(char *s);
int genl_recv_doit(struct sk_buff *skb, struct genl_info *info);
static inline int genl_msg_prepare_usr_msg(u8 cmd, size_t size, pid_t pid, struct sk_buff **skbp);
static inline int genl_msg_mk_usr_msg(struct sk_buff *skb, int type, void *data, int len);
int genl_msg_send_to_user(void *data, int len, pid_t pid);static struct genl_family exmpl_gnl_family = {.id = GENL_ID_GENERATE,.hdrsize = 0,.name = "myfamily",.version = 1,.maxattr = EXMPL_A_MAX,};/* attribute policy */static struct nla_policy exmpl_genl_policy[EXMPL_A_MAX + 1] = {[EXMPL_A_MSG] = { .type = NLA_NUL_STRING },};/* operation definition */struct genl_ops exmpl_gnl_ops_echo = {.cmd = EXMPL_C_ECHO,.flags = 0,.policy = exmpl_genl_policy,.doit = genl_recv_doit,.dumpit = NULL,};int stringlength(char *s)
{int slen = 0;for(; *s; s++){slen++;}return slen;
}/*
* genl_msg_prepare_usr_msg : 构建netlink及gennetlink首部
* @cmd : genl_ops的cmd
* @size : gen_netlink用户数据的长度(包括用户定义的首部)
*/
static inline int genl_msg_prepare_usr_msg(u8 cmd, size_t size, pid_t pid, struct sk_buff **skbp)
{struct sk_buff *skb;/* create a new netlink msg */skb = genlmsg_new(size, GFP_KERNEL);if (skb == NULL) {return -ENOMEM;}/* Add a new netlink message to an skb */genlmsg_put(skb, pid, 0, &exmpl_gnl_family, 0, cmd);*skbp = skb;return 0;
}/*
* 添加用户数据,及添加一个netlink addribute
*@type : nlattr的type
*@len : nlattr中的len
*@data : 用户数据
*/
static inline int genl_msg_mk_usr_msg(struct sk_buff *skb, int type, void *data, int len)
{int rc;/* add a netlink attribute to a socket buffer */if ((rc = nla_put(skb, type, len, data)) != 0) {return rc;}return 0;
}/**
* genl_msg_send_to_user - 通过generic netlink发送数据到netlink
*
* @data: 发送数据缓存
* @len: 数据长度 单位:byte
* @pid: 发送到的客户端pid
*/
int genl_msg_send_to_user(void *data, int len, pid_t pid)
{struct sk_buff *skb;size_t size;void *head;int rc;printk("begin send to user\n");size = nla_total_size(len); /* total length of attribute including padding */rc = genl_msg_prepare_usr_msg(EXMPL_C_ECHO, size, pid, &skb);printk("genl_msg_prepare_usr_msg\n");if (rc) {return rc;}rc = genl_msg_mk_usr_msg(skb, EXMPL_A_MSG, data, len);printk("genl_msg_mk_usr_msg\n");if (rc) {kfree_skb(skb);return rc;}printk("pid :%d",pid);rc = genlmsg_unicast(&init_net, skb, pid);printk("send end....\n");if (rc < 0) {return rc;}return 0;
}int genl_recv_doit(struct sk_buff *skb, struct genl_info *info)
{struct nlmsghdr *nlhdr;struct genlmsghdr *genlhdr;struct nlattr *nla;int len;char* str;char* data = "I am from kernel!";int state = 0;nlhdr = nlmsg_hdr(skb);genlhdr = nlmsg_data(nlhdr);nla = genlmsg_data(genlhdr);printk ("received\n");str = (char *)NLA_DATA(nla);printk("%s\n",str);len = stringlength(data);if ( (state = genl_msg_send_to_user(data, len, nlhdr->nlmsg_pid)) <0 ){printk(KERN_ERR "genl_msg_send_to_user error!");return 1;}return 0;
}int genetlink_init(void)
{int state=0;state = genl_register_family(&exmpl_gnl_family);if(state){printk(KERN_ERR "genl_register_family error!!!\n");return 1;}state = genl_register_ops(&exmpl_gnl_family, &exmpl_gnl_ops_echo);if(state){printk(KERN_ERR "genl_register_ops error!!!");return 1;}printk(KERN_ERR "gennetlink register success!!!\n");return 0;
}
void genetlink_exit(void)
{genl_unregister_family(&exmpl_gnl_family);printk(KERN_ERR "gennetlink unregister.....\n");
}
module_init(genetlink_init);
module_exit(genetlink_exit);
MODULE_AUTHOR("yilong");
MODULE_LICENSE("GPL");

Makefile文件:

ifneq ($(KERNELRELEASE),)
obj-m :=gen_netl.o
else
KERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

4.2用户态代码(client)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <linux/genetlink.h>#define MAX_MSG_SIZE 256
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
/* attribute type */enum {EXMPL_A_UNSPEC,EXMPL_A_MSG,__EXMPL_A_MAX,};#define EXMPL_A_MAX (__EXMPL_A_MAX - 1)/* commands */enum {EXMPL_C_UNSPEC,EXMPL_C_ECHO,__EXMPL_C_ECHO,};#define EXMPL_C_MAX (__EXMPL_C_MAX - 1)int genl_send_msg(int sock_fd, u_int16_t family_id, u_int32_t nlmsg_pid,u_int8_t genl_cmd, u_int8_t genl_version, u_int16_t nla_type,void *nla_data, int nla_len);
static int genl_get_family_id(int sock_fd, char *family_name);
void genl_rcv_msg(int family_id, int sock_fd, char *buf);typedef struct msgtemplate {struct nlmsghdr nlh;struct genlmsghdr gnlh;char data[MAX_MSG_SIZE];
} msgtemplate_t;static int genl_get_family_id(int sock_fd, char *family_name)
{msgtemplate_t ans;int id, rc;struct nlattr *na;int rep_len;rc = genl_send_msg(sock_fd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,CTRL_ATTR_FAMILY_NAME, (void *)family_name,strlen(family_name)+1);rep_len = recv(sock_fd, &ans, sizeof(ans), 0);if (rep_len < 0) {return 1;}if (ans.nlh.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&ans.nlh), rep_len)){return 1;}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);} else {id = 0;}return id;
}/**
* genl_send_msg - 通过generic netlink给内核发送数据
*
* @sock_fd: 客户端socket
* @family_id: family id
* @nlmsg_pid: 客户端pid
* @genl_cmd: 命令类型
* @genl_version: genl版本号
* @nla_type: netlink attr类型
* @nla_data: 发送的数据
* @nla_len: 发送数据长度
*
* return:
* 0: 成功
* -1: 失败
*/
int genl_send_msg(int sock_fd, u_int16_t family_id, u_int32_t nlmsg_pid,u_int8_t genl_cmd, u_int8_t genl_version, u_int16_t nla_type,void *nla_data, int nla_len)
{struct nlattr *na;struct sockaddr_nl dst_addr;int r, buflen;char *buf;msgtemplate_t msg;if (family_id == 0) {return 0;}msg.nlh.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);msg.nlh.nlmsg_type = family_id;msg.nlh.nlmsg_flags = NLM_F_REQUEST;msg.nlh.nlmsg_seq = 0;msg.nlh.nlmsg_pid = nlmsg_pid;msg.gnlh.cmd = genl_cmd;msg.gnlh.version = genl_version;na = (struct nlattr *) GENLMSG_DATA(&msg);na->nla_type = nla_type;na->nla_len = nla_len + 1 + NLA_HDRLEN;memcpy(NLA_DATA(na), nla_data, nla_len);msg.nlh.nlmsg_len += NLMSG_ALIGN(na->nla_len);buf = (char *) &msg;buflen = msg.nlh.nlmsg_len ;memset(&dst_addr, 0, sizeof(dst_addr));dst_addr.nl_family = AF_NETLINK;dst_addr.nl_pid = 0;dst_addr.nl_groups = 0;while ((r = sendto(sock_fd, buf, buflen, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr))) < buflen) {if (r > 0) {buf += r;buflen -= r;} else if (errno != EAGAIN) {return -1;}}return 0;
}void genl_rcv_msg(int family_id, int sock_fd, char *buf)
{int ret;struct msgtemplate msg;struct nlattr *na;ret = recv(sock_fd, &msg, sizeof(msg), 0);if (ret < 0) {return;}printf("received length %d\n", ret);if (msg.nlh.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.nlh), ret)) {return ;}if (msg.nlh.nlmsg_type == family_id && family_id != 0) {na = (struct nlattr *) GENLMSG_DATA(&msg);strcpy(buf,(char *)NLA_DATA(na));}
}int main(int argc, char* argv[])
{struct sockaddr_nl src_addr, dest_addr;struct nlmsghdr *nlh = NULL;int sock_fd, retval;int family_id = 0;char *data;// Create a socketsock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);if(sock_fd == -1){printf("error getting socket: %s", strerror(errno));return -1;}// To prepare bindingmemset(&src_addr, 0, sizeof(src_addr));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = 1234;src_addr.nl_groups = 0;//Bindretval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));if(retval < 0){printf("bind failed: %s", strerror(errno));close(sock_fd);return -1;}family_id = genl_get_family_id(sock_fd ,"myfamily");printf("family_id:%d\n",family_id);data =(char*)malloc(256);if(!data){perror("malloc error!");exit(1);}memset(data,0,256);strcpy(data,"Hello you!");retval = genl_send_msg(sock_fd, family_id, 1234,EXMPL_C_ECHO, 1, EXMPL_A_MSG,(void *)data, strlen(data)+1);printf("send message %d\n",retval);if(retval<0){perror("genl_send_msg error");exit(1);}memset(data,0,256);genl_rcv_msg(family_id,sock_fd,data);printf("receive:%s",data);close(sock_fd);return 0;
}

用户空间与内核空间数据交换-2-generic netlink相关推荐

  1. 用户空间与内核空间数据交换的方式(zz)

    用户空间与内核空间数据交换的方式(1)------debugfs 用户空间与内核空间数据交换的方式(2)------procfs 用户空间与内核空间数据交换的方式(3)------seq_file 用 ...

  2. linux 内核空间 sy,在 Linux 下用户空间与内核空间数据交换的方式,第 1 部分: 内核启动参数、模块参数与sysf...

    级别: 初级 燚 杨 (), 计算机科学硕士 2006 年 2 月 16 日 本系列文章包括两篇,它们文详细地介绍了 Linux 系统下用户空间与内核空间数据交换的九种方式,包括内核启动参数.模块参数 ...

  3. 用户空间与内核空间数据交换的方式(2)------procfs

    procfs是比较老的一种用户态与内核态的数据交换方式,内核的很多数据都是通过这种方式出口给用户的,内核的很多参数也是通过这种方式来让用户方便设置的.除了sysctl出口到/proc下的参数,proc ...

  4. 用户空间与内核空间数据交换的方式(9)------netlink【转】

    转自:http://www.cnblogs.com/hoys/archive/2011/04/10/2011722.html Netlink 是一种特殊的 socket,它是 Linux 所特有的,类 ...

  5. 用户空间与内核空间数据交换的方式(1)------debugfs

    内核开发者经常需要向用户空间应用输出一些调试信息,在稳定的系统中可能根本不需要这些调试信息,但是在开发过程中,为了搞清楚内核的行为,调试信息非常必要,printk可能是用的最多的,但它并不是最好的,调 ...

  6. 用户空间与内核空间数据交换的方式------seq_file

    一般地,内核通过在procfs文件系统下建立文件来向用户空间提供输出信息,用户空间可以通过任何文本阅读应用查看该文件信息,但是procfs 有一个缺陷,如果输出内容大于1个内存页,需要多次读,因此处理 ...

  7. linux 物理内存用完了_Linux用户空间与内核空间(理解高端内存)

    Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图. Linux内核地址空间划分 通常32位L ...

  8. 虚拟内存,虚拟地址空间,用户空间,内核空间

    虚拟内存,就是用磁盘作为介质,暂时性存储数据,和主存进行换入换出,使程序能够使用更多的内存.虚拟内存是单位是页,固定大小的数据方便数据的交换.如果一个应用程序要访问某段内存,通过MMU得到相应的页号, ...

  9. Linux Malloc分析-从用户空间到内核空间

    Linux Malloc分析-从用户空间到内核空间 本文介绍malloc的实现及其malloc在进行堆扩展操作,并分析了虚拟地址到物理地址是如何实现映射关系. ordeder原创,原文链接: http ...

最新文章

  1. Kudu command tools
  2. 【单页应用巨坑之History】细数History带给单页应用的噩梦
  3. ElasticSearch6 查询模板的创建使用
  4. 微信有电脑客户端吗_微信电脑版有哪些功能呢
  5. 一些Cloud for Customer的客户需求
  6. python redis订阅_Python -- Redis 发布订阅
  7. c#养老院老人信息管理系统源码 论文_辽宁吃的好的社区养老院位置,爱心养老院_抚顺市望花区社会养老中心...
  8. Chrome截图扩展程序-“截图快手”
  9. Repast仿真学习
  10. 实战函数式编程:使用Ramda.js
  11. layui日期插件用法
  12. 二、Contiki移植
  13. 几张图片生成3D模型?距离真正的AI建模还有多远?
  14. 阿里云应用身份服务IDaaS新升级,云原生、高安全,极致用户体验
  15. 大气科技类企业织梦整站模板
  16. Windows——关于service
  17. 基于有序二叉树的专家系统应用——动物游戏(C++)
  18. iwrite复制粘贴方法
  19. 仿QQ相册RecyclerView滑动选中
  20. Leetcode_584. 寻找用户推荐人

热门文章

  1. MacOS 升级自带PHP5.6 升级到 PHP7.1
  2. SQL语句中,有了别名不能再用原名,需要使用别名
  3. 自动驾驶仿真软件SCANeRstudio(二)场景、仿真、分析
  4. 如何盘活新零售5大线上线下交互模式?拥抱用户与收益增长
  5. plotwidget横坐标日期_求助x轴为日期时间的图形绘制
  6. 2018年我跑去做淘宝了(续篇)
  7. 基于Redis实现在线游戏积分排行榜
  8. DS1302基本的读写
  9. 【Vuforia】最佳实践:支持作为模型目标(Model Targets)的对象和CAD模型
  10. 构之以技术,付之以匠心——读《构建之法》有感