GENERIC NETLINK 介绍及使用

2023-11-11

 之前一篇博文讲述了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。

1. 相关概念及数据结构

1.1 genlmsghdr

这是NETLINK_GENERIC的头部结构,即图1中的family头部。

struct genlmsghdr {
        __u8 cmd; //命令,下文介绍genl_ops时再介绍
        __u8 version; //版本号
        __u16 reserved; //保留字段
};

为了更好的理解NETLINK_GENERIC用到的相关字段,以及genlmsghdr的位置,我们画出NETLINK_GENERIC报文的详细格式。如图2.

图2

1.2 family

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

struct genl_family {
unsigned int id;
unsigned int hdrsize;
char name[GENL_NAMSIZ];
unsigned int version;
unsigned int maxattr;
struct nlattr **attrbuf;
struct list_head ops_list;
struct list_head family_list;
 };

对此结构体元素具体解释如下: 

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

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

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

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

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

(6) struct nlattr **attrbuf 

(7) struct list_head ops_list 

(8) struct list_head family_list 

以上的三个字段为私有字段,由系统自动配置,开发者不需要做配置。

    好了,介绍完family后这个family结构的主要作用就比较清晰了,family就是描述我们将要如何使用NETLINK_GENERIC的payload,以及确定Netlink头部的type信息和genl netlink头部的version信息,你也可以理解为如何定义在NETLINK_GENERIC之上的一种协议。

1.3 genl_ops 结构体 

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

struct genl_ops {
 u8 cmd;
 unsigned int flags;
 struct nla_policy *policy;
 int (*doit)(structsk_buff*skb, struct genl_info *info);
 int (*dumpit)(structsk_buff*skb, struct netlink_callback *cb);
 struct list_head ops_list;
};

(1) cmd: 命令名。用于识别各genl_ops,根据数据包中的cmd字段会调用同名的operation处理。

(2) flag: 各种设置属性,以“或”连接。在需要admin特权级别时,使用GENL_ADMIN_PERM。

(3) policy:定义了attr规则。如果此指针非空,genl在触发事件处理程序之前,会使用这个字段来对帧中的attr做校验(见nlmsg_parse函数)。该字段可以为空,表示在触发事件处理程序之前,不做校验。 policy是一个struct nla_policy的数组。struct nla_policy结构体表示如下: 

struct nla_policy
{
   u16 type;
   u16 len;
};

   其中,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流 ;

     len字段的意思是:如果在type字段配置的是字符串有关的值,要把len设置为字符串的最大长度(不包含结尾的'\0')。如果type字段未设置或被设置为NLA_UNSPEC,那么这里要设置为attr的payload部分的长度。 

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

struct genl_info
{
 u32 snd_seq;
 u32 snd_pid;
 struct nlmsghdr *nlhdr;
 struct genlmsghdr *genlhdr;
 void *userhdr;
struct nlattr ** attrs;
};

1) snd_seq:发送序号;

2) snd_pid:发送客户端的PID  ;

3) nlhdr:netlink header的指针;

4) genlmsghdr:genl头部的指针(即family头部);

5) userhdr:用户自定义头部指针;

6) attrs:attrs,如果定义了genl_ops->policy,这里的attrs是被policy过滤以后的结果。在完成了操作以后,如果执行正确,返回0;否则,返回一个负数。负数的返回值会触发NLMSG_ERROR消息。当genl_ops的flag标志被添加了NLMSG_ERROR时,即使doit返回0,也会触发NLMSG_ERROR消息;

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

6) ops_list;为私有字段,由系统自动配置,开发者不需要做配置。

介绍完这些数据结构,我们总体看一下genl netlink协议由里到外的数据格式,以及于不同结构和操作函数的关系,如图3所示。

                                          图3

2. 客户端服务端操作流程

服务端的操作步骤如图4所示。

                                                  图4

客户端的操作路程如图5所示

                      图5

3. 服务端操作

3.1为注册family做准备

预定义attr类型

/* attribute type */
  enum {
        EXMPL_A_UNSPEC,
        EXMPL_A_MSG,
        __EXMPL_A_MAX,
  };
#define EXMPL_A_MAX (__EXMPL_A_MAX - 1)

 我们消息要使用的类型,我们仅仅使用EXMPL_A_MSG这一个消息EXMPL_A_MAX代表消息类型的个数。

3.2 为注册operation做准备

1. 定义cmd类型

enum {
        EXMPL_C_UNSPEC,
        EXMPL_C_ECHO,
        __EXMPL_C_ECHO,
  };
#define EXMPL_C_MAX (__EXMPL_C_MAX - 1)

     我们只使用EXMPL_C_ECHO这一种命令,EXMPL_C_MAX为cmd的种类个数。

 

2. 定义policy

     要提供给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 },
  };

(可以根据attr和policy的对应关系想一下为什么policy数组的大小要设置为“attr个数+1”)

3.3 定义family

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

3.4 定义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.5 注册family

state = genl_register_family(&exmpl_gnl_family);

3.6 注册operation

state = genl_register_ops(&exmpl_gnl_family, &exmpl_gnl_ops_echo);

3.7 定义回调函数

    下面需要定义genl_ops结构中的doit回调函数。我们的回调函数主要做的就是将客户端传来的数据打印出来,并向客户端回发一个字符串。所以回调函数的重点就是如何根据genl netlink报文的格式收到的客户端信息进行解析,以及如何构造回发报文。参考图3,下面程序并不难理解。

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的payload
    printk ("received\n");
    str = (char *)NLA_DATA(nla); //用户数据是一个attr,我们要获取其value
    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;
}

注意,这里我们并没有用genl_info结构中的信息,genl_info中包含了很多系统为我们已经解析填充过的报文字段,祥见genl_info结构的定义,其中获取payload中的attr可以直接通过genl_info获取,但是我们还是选择从最原始的报文一段段的解析获取,就是为了让大家更加了解报文的结构。

另外需要特别提醒的是接收回调函数同Netlink回调函数一样,用户发往内核是同步的,这个回调函数执行结束用户态的发送函数才能够返回。

在一个注意回发给用户的pid一定不要错了,要是从Netlink消息头中获取的pid,其含义在之前Netlink博文中已经介绍过了。

   下面看回发客户端的函数:

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 */
    //构造netlink头部和genl netlink头部
    rc = genl_msg_prepare_usr_msg(EXMPL_C_ECHO, size, pid, &skb);
    printk("genl_msg_prepare_usr_msg\n");
    if (rc) {
        return rc;
    }
    //构造genl netlink的payload
    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;
   }
    rc = genlmsg_unicast(&init_net, skb, pid); //向客户端回发数据
    printk("send end....\n");
    if (rc < 0) {
        return rc;
    }
    return 0;
}

三个参数的含义分别为data: 发送数据缓存,len: 数据长度,单位:byte,pid: 发送到的客户端pid。这个函数的重点就是构造回发的数据包,这个构造过程通过两个函数完成。具体如下:

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;
}

参数含义分别为:cmd : genl_ops的cmd,pid:客户端的pid; size : gen_netlink用户数据的长度(包括用户定义的首部),这个函数主要用来构造netlink的头部以及genl netlink的头部,以及给用户payload分配空间。需要前两个参数,是因为这两个字段要填充到报文中,见图2,最后一个参数用来分配空间。

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;
}

这个函数主要用来填充用户数据,由于用户数据是一个attr结构(TLV),所以后三个参数分别就是type、value和length。

4. 客户端操作

4.1 创建socket

sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

4.2 bind socket

struct sockaddr_nl src_addr, dest_addr;
// To prepare binding
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = 1234; //我们选用的pid
src_addr.nl_groups = 0;
//Bind
retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));

4.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 id
    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);
    //接收返回消息,其中含有family id
    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;
}

由于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;

4.4 向服务端发送消息

    这个函数的参数比较多,不过并不难理解,可以想一下假如自己写这么一个函数需要什么信息。首先,用户发送的是一个attr,所以肯定需要用户数据的type、length、value;其次对比图2中genl netlink消息的格式,我们需要填写pid、type(family id)、cmd(genl_ops.cmd)、version(family.version);最后发送消息当然还需要socket。所以所需参数就是如下这些:

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为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;
}

其实函数的关机问题还是如何去构造genl netlink格式的报文。

4.5 从服务端接收消息

    有的人可能一直对这个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));
    }
}

最后附上这个genl netlink的完整例子代码。

 

5. 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

6. 用户态代码(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 socket
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
    if(sock_fd == -1){
        printf("error getting socket: %s", strerror(errno));
        return -1;
    }
    // To prepare binding
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = 1234;
    src_addr.nl_groups = 0;
    //Bind
    retval = 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;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GENERIC NETLINK 介绍及使用 的相关文章

  • Inotify linux 监视子目录

    是否可以以这种模式监视目录 storage data usernames Download gt storage data Download 我需要监视每个用户的下载文件夹中是否进行了更改 也许我需要创建所有路径的列表 将其放入数组中 并在
  • 如何在Python中独立于语言安装(linux)获取用户桌面路径

    我找到了 如何找到用户桌面的路径 的几个问题和答案 但在我看来它们都已失效 至少我找到的那些 原因是 如果用户安装的 Linux 不是英语 他或她的桌面很可能位于除 Desktop 例如 对于瑞典语 我相信它是在 Skrivbord 谁知道
  • 劫持系统调用

    我正在编写一个内核模块 我需要劫持 包装一些系统调用 我正在暴力破解 sys call table 地址 并使用 cr0 来禁用 启用页面保护 到目前为止一切顺利 一旦完成 我将公开整个代码 因此如果有人愿意 我可以更新这个问题 无论如何
  • 如何查找连接到 AF_INET 套接字的客户端的 UID?

    有什么方法或类似的东西ucred for AF UNIX如果是AF INET插座 TCP在我的例子中 找出连接到我的套接字的客户端的UID 还有 proc net tcp但它显示了UID of the creator插座的而不是连接的cli
  • Composer 安装要求

    我正在尝试将 Composer 安装到 Laravel 项目中 当我做的时候sudo composer install在项目目录中它显示了两个错误 Problem 1 Installation request for simplesoftw
  • Docker:处理 tar 文件时出错(退出状态 1):设置枢轴目录时出错:不是目录

    我是 Docker 新手 不知道是什么原因导致此错误或如何诊断它 任何有关此问题的具体帮助或有关首先检查何处以诊断此类问题的提示将不胜感激 我的 Dockerfile FROM java 8 Install maven RUN apt ge
  • 信号处理程序有单独的堆栈吗?

    信号处理程序是否有单独的堆栈 就像每个线程都有单独的堆栈一样 这是在 Linux C 环境中 来自 Linux 手册页signal 7 http kernel org doc man pages online pages man7 sign
  • 如何更改 Ubuntu 14.04 上的 php-cli 版本?

    我是 Linux 新手 在篡改时破坏了一些 php 设置 如果我执行一个包含以下内容的 php 脚本 phpinfo 它显示 php 版本为 5 6 但通过命令行 如果我运行php v它返回 7 0 版本 我想让两个版本匹配 我怎样才能修复
  • 如何模拟ARM处理器运行环境并加载Linux内核模块?

    我尝试加载我的vmlinux into gdb并使用 ARM 内核模拟器 但我不明白为什么我会得到Undefined target command sim 这是外壳输出 arm eabi gdb vmlinux GNU gdb GDB 7
  • 在Linux上编译C# + WPF以便在Windows上运行

    我有一个 C 应用程序 其中某些部分是使用 WPF 编写的 Mono 不支持 可以在 Linux 上编译这个应用程序吗 最终 该应用程序将在 Windows 上运行 但它是更大框架的一部分 并且我们的整个构建过程在 Linux 上运行 因此
  • 在 Mac OS X 上构建 Linux 内核

    我正在做一个修改Linux内核的项目 我有一台桌面 Linux 机器 在上面构建内核没有问题 不过 我要去旅行 我想在途中工作 我只有一台 MacBook 当我尝试构建 Linux 内核时 它抱怨说elf h was not found 我
  • 使用 find - 删除除任何一个之外的所有文件/目录(在 Linux 中)

    如果我们想删除我们使用的所有文件和目录 rm rf 但是 如果我希望一次性删除除一个特定文件之外的所有文件和目录怎么办 有什么命令可以做到这一点吗 rm rf 可以轻松地一次性删除 甚至可以删除我最喜欢的文件 目录 提前致谢 find ht
  • awk 子串单个字符

    这是columns txt aaa bbb 3 ccc ddd 2 eee fff 1 3 3 g 3 hhh i jjj 3 kkk ll 3 mm nn oo 3 我可以找到第二列以 b 开头的行 awk if substr 2 1 1
  • Android:ANT 构建失败,并显示 google-play-services-lib:“解析为没有项目的 project.properties 文件的路径”

    我正在尝试使用 ANT 构建我的应用程序 但在包含 google play services lib 库项目后 我惨遭失败 Step 1 我在 project properties 文件中设置了对库项目的引用 android library
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 跟踪 Linux 程序中活跃使用的内存

    我想跟踪各种程序在特定状态下接触了多少内存 例如 假设我有一个图形程序 最小化时 它可能会使用更少的内存 因为它不会重新绘制窗口 这需要读取图像和字体并执行大量库函数 这些对象仍然可以在内存中访问 但实际上并没有被使用 类似的工具top它们
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 如何在 Linux 中编写文本模式 GUI? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 当我编写脚本 程序时 我经常想弹出一个简单的文本 gui 来提示输入 我该怎么做 例如 来自 Shel
  • 如何查明CONFIG_FANOTIFY_ACCESS_PERMISSIONS是否启用?

    我想利用fanotify 7 http man7 org linux man pages man7 fanotify 7 html我遇到的问题是在某些内核上CONFIG FANOTIFY ACCESS PERMISSIONS不起作用 虽然C

随机推荐

  • 基于Matlab的直方图、Retinex和暗通道图像去雾算法

    基于Matlab的直方图 Retinex和暗通道图像去雾算法 近年来 图像去雾技术在计算机视觉领域得到了广泛关注 由于大气雾霾和环境污染等因素 图像中会存在大量的噪点和雾霭 甚至会导致一些细节无法清晰地呈现 针对这个问题 本文将介绍一种基于
  • Kubernetes 使用 helm 部署 NFS Provisioner

    文章目录 1 介绍 2 预备条件 3 部署 nfs 4 部署 NFS subdir external provisioner 4 1 集群配置 containerd 代理 4 2 配置代理堡垒机通过 kubeconfig 部署 部署 Min
  • 使用python装饰器计算函数运行时间的实例

    使用python装饰器计算函数运行时间的实例 装饰器在python里面有很重要的作用 如果能够熟练使用 将会大大的提高工作效率 今天就来见识一下 python 装饰器 到底是怎么工作的 本文主要是利用python装饰器计算函数运行时间 一些
  • python数据分析实例:利用爬虫获取数据

    我们在工作中用到网络上发布的各种信息 如果用搜索引擎查找并整理 需要花费大量时间 现在python能够帮助我们 使用爬虫技术 提高数据查找和整理的效率 我们来找一个爬虫的案例 抓取求职招聘类网站中的数据 使用环境 win10 python3
  • 一个开源知识管理系统,满足企业定制化需求

    编者按 知识管理是企业加强竞争优势和核心竞争力的保证 开源知识管理系统更是块 香馍馍 本文分析了知识管理系统开源的意义 介绍了开源的知识管理系统 天翎KMS的特点 并进一步阐述了天翎KMS具体实现的功能 概要 1 为什么需要开源知识管理系统
  • C++虚函数表的理解、通过虚函数表访问非public成员函数

    阅读了陈皓老师的虚函数表解析 点击打开链接 以及虚函数表存放位置一文 点击打开链接 对C 如何实现多态应该有了个粗浅认识 1 虚函数表是一个数组 每个元素存储virtual函数的指针 2 如果一个类存在虚函数 编译器一般会将该类实例的前4
  • GJB 软件测试规范

    1 目的及范围 1 1 目的 本规范规定了软件测试的具体实施方法 主要从测试过程和测试管理方面论述 软件测试的目的是验证软件是否满足软件开发合同或任务书 系统 子系统设计文档 软件需求规格说明书和软件设计说明所规定的软件质量特性要求 通过测
  • Large Graph Models: A Perspective

    本文是LLM系列文章 针对 Large Graph Models A Perspective 的翻译 TOC 摘要 大型模型已成为人工智能 尤其是机器学习领域的最新突破性成就 然而 当涉及到图形时 大型模型并没有像在自然语言处理和计算机视觉
  • sqlserver用户登录失败

    问题场景描述 刚使用sql server创建用户后 重新登录时报错说登录失败 解决方案 先使用windows登录 然后进行如下操作
  • 在主函数里面调用fun函数,这样可以吗?

    Int fun Int a 5 Int p a Return p 请问 在主函数里面调用fun函数 这样可以吗 如果不可以 请说明为什么 并给出一种解决方案 a是局部变量 a 的作用域在fun内 p指向a的地址 在return的时候 a的生
  • Node.js学习笔记--fs 文件系统 writeFile函数

    前言 本章学习fs模块中的writeFile函数 即简单文件系统 所谓的简单 其实就是一种封装 把 1 打开文件 2 关闭文件 给封装到方法里面了 这样调用者使用的时候就方便了 1 使用例 先来看一段代码 var fs require fs
  • 【图像识别】基于支持向量机svm植物叶子疾病检测和分类

    最近在学习svm算法 借此文章记录自己的学习过程 在学习很多处借鉴了z老师的讲义和李航的统计 若有不足的地方 请海涵 svm算法通俗的理解在二维上 就是找一分割线把两类分开 问题是如下图三条颜色都可以把点和星划开 但哪条线是最优的呢 这就是
  • java jmap用法_java命令--jmap命令使用

    jdk安装后会自带一些小工具 jmap命令 Java Memory Map 是其中之一 主要用于打印指定Java进程 或核心文件 远程调试服务器 的共享对象内存映射或堆内存细节 jmap命令可以获得运行中的jvm的堆的快照 从而可以离线分析
  • 设计模式6-命令模式(Command)解析+案例实践+总结

    本文是对面向对象设计模式 命令模式 Command 的解析 主要分为定义解析 通过餐厅点餐案例 遥控器案例讲解命令模式 多案例练习加深对命令模式的理解 最后总结知识要点 第一篇 定义解析 命令模式是GoF四人帮整理的 设计模式 可复用面向对
  • mysql语句声明外键

    环境 在Navicat 中进行mysql的语句操作 示例 1 创建一张员工表 CREATE TABLE employ id INT PRIMARY KEY ename VARCHAR 20 deptId INT 2 插入员工数据 INSER
  • NodeJs的应用场景及实践

    前几天看到一篇文章 据说是WordPress4 3将用NodeJs重写 着实让人有点惊讶 后来了解到 其实并不是完全摒弃PHP 而是由NodeJs重写部分核心类 具体文章链接来源 附上链接 WordPress 4 3 将用 Node js
  • 驱动怎么学

    1 什么是驱动 1 1理解驱动的概念 1 驱动一词的字面意思 驱动就是让一个东西动起来 给一个东西动力 让它动起来 2 物理上的驱动 比如 一个球放在那儿没动 你踢了一下它 给了它一个力 给了力之后它就能动 就说明你驱动了它 这就是物理学上
  • 召唤神龙无敌版,轻松召唤神龙~

    还在被鲨鱼围着追吗 快来试试无敌版轻松通关 最新召唤神龙无敌版 地址 http minigame suyiboke com zhaohuanshenlongwdb index html 备用地址 推荐 https suyiboke com
  • osg与opengl中向量、矩阵的区别

    osg中的向量是行向量 矩阵相应也是与行向量对应 v M 矩阵最后一行对应的是平移 其它行列对应旋转和缩放 对角线元素值对应缩放值 inline void Matrixd preMultTranslate const Vec3d v for
  • GENERIC NETLINK 介绍及使用

    之前一篇博文讲述了Netlink如何使用 当时我们是自己定义了一种协议NETLINK TEST进行用户进程和内核的通信 这篇文章就讲一下如何使用内核为我们提供的NETLINK GENERIC进行通信 如果把Netlink协议比作IP层 那么