linux中通用Netlink详解及使用剖析(附源码)

Linux 网络 专栏收录该内容
43 篇文章 2 订阅

相关部分可以参考之前文章:《netlink机制

因为Netlink协议簇不能超过32个,而且添加协议簇需要在include/linux/netlink.h中添加协议簇定义。因为开发了通用netlink,类似netlink的多路复用器,因为提供了通用的渠道,所以可以在其他子系统使用,例如ACPI、任务统计信息、过热事件等。

1.1.1 协议族

通用link协议由family来管理,其中genl_ctrl是特殊的family由通用netlink自己注册和实现,并用来查询Family列表、管理添加、删除等事件。

通用link的协议族结构体如下:

struct genl_family {

        int                     id;             /* private */

        unsigned int            hdrsize;

        char                    name[GENL_NAMSIZ];

        unsigned int            version;

        unsigned int            maxattr;

        bool                    netnsok;

        bool                    parallel_ops;

        int                     (*pre_doit)(const struct genl_ops *ops,

                                            struct sk_buff *skb,

                                            struct genl_info *info);

        void                    (*post_doit)(const struct genl_ops *ops,

                                             struct sk_buff *skb,

                                             struct genl_info *info);

        int                     (*mcast_bind)(struct net *net, int group);

        void                    (*mcast_unbind)(struct net *net, int group);

        struct nlattr **        attrbuf;        /* private */

        const struct genl_ops * ops;

        const struct genl_multicast_group *mcgrps;

        unsigned int            n_ops;

        unsigned int            n_mcgrps;

        unsigned int            mcgrp_offset;   /* private */

        struct module           *module;

};

       定义在include/net/genetlink.h

1.1.2 命令处理结构

位于文件:include/net/genetlink.h 

定义如下:

/**

 * struct genl_ops - generic netlink operations

 * @cmd: command identifier

 * @internal_flags: flags used by the family

 * @flags: flags        

 * @policy: attribute validation policy

 * @doit: standard command callback

 * @start: start callback for dumps

 * @dumpit: callback for dumpers

 * @done: completion callback for dumps

 */                     

struct genl_ops {

        const struct nla_policy *policy;

        int                    (*doit)(struct sk_buff *skb,

                                       struct genl_info *info); 

        int                    (*start)(struct netlink_callback *cb);

        int                    (*dumpit)(struct sk_buff *skb,

                                         struct netlink_callback *cb);

        int                    (*done)(struct netlink_callback *cb);

        u8                      cmd;                     

        u8                      internal_flags;           

        u8                      flags;                   

};

1.1.3 内核接收消息结构

内核在接收到用户的genetlink消息后,会对消息解析并封装成genl_info结构,便于命令回校函数进行处理

/**

 * struct genl_info - receiving information

 * @snd_seq: sending sequence number

 * @snd_portid: netlink portid of sender

 * @nlhdr: netlink message header

 * @genlhdr: generic netlink message header

 * @userhdr: user specific header

 * @attrs: netlink attributes

 * @_net: network namespace

 * @user_ptr: user pointers

 * @extack: extended ACK report struct

 */                     

struct genl_info {

        u32                     snd_seq;

        u32                     snd_portid;

        struct nlmsghdr *       nlhdr;

        struct genlmsghdr *     genlhdr;

        void *                  userhdr;

        struct nlattr **        attrs;  

        possible_net_t          _net;

        void *                  user_ptr[2];             

        struct netlink_ext_ack *extack;

};    

1.1.4 初始化过程

通用netlink初始化函数为genl_init(),位于net/netlink/genetlink.c文件。

static int __init genl_init(void)

{                      

        int err;

 

        err = genl_register_family(&genl_ctrl);

        if (err < 0)

                goto problem;

               

        err = register_pernet_subsys(&genl_pernet_ops);

        if (err)                                         

                goto problem;

                        

        return 0;

 

problem:

        panic("GENL: Cannot register controller: %d\n", err);

1.1.4.1  genl_register_family

该函数先通过genl_validate_ops来检测协议的合法性,

对于每一个注册的genl_ops结构,其中doitdumpit回调函数必须至少实现一个,然后其针对的cmd命令不可以出现重复,否则返回错误,注册失败。

接着上锁启动链表操作,寻找是否已经有同名协议注册,然后分配ID,如果是GENL_ID_CTRL就是0X10

接着根据注册的最大attr参数maxattr,这里对于genl_ctrl来说一共分配了CTRL_ATTR_MAX+1个指针内存空间,以后用于缓存attr属性地址。

调用genl_validate_assign_mc_groups()函数判断新增组播地址空间,判断注册familygroup组播名的有效性;为该family分配组播地址比特位并将bit偏移保存到family->mcgrp_offset变量中。

最后调用genl_ctrl_event()函数向内核的控制器family发送CTRL_CMD_NEWFAMILYCTRL_CMD_NEWMCAST_GRP命令消息。

1.1.4.2  register_pernet_subsys

register_pernet_subsys方法为在系统中注册网络命名空间子系统

1.1.4.3  相关结构体

其中genl_pernet_ops结构体如下:

static struct pernet_operations genl_pernet_ops = {

        .init = genl_pernet_init,

        .exit = genl_pernet_exit,

};

其中genl_ctrl协议族的ID是固定的为0x10。

static struct genl_family genl_ctrl __ro_after_init = {

        .module = THIS_MODULE,

        .ops = genl_ctrl_ops,

        .n_ops = ARRAY_SIZE(genl_ctrl_ops),

        .mcgrps = genl_ctrl_groups,

        .n_mcgrps = ARRAY_SIZE(genl_ctrl_groups),

        .id = GENL_ID_CTRL,

        .name = "nlctrl",

        .version = 0x2,

        .maxattr = CTRL_ATTR_MAX,

        .netnsok = true,

};

       其中CTRL_ATTR_MAX表示支持的attr属性最大个数。属性类型定义如下:

enum { 

        CTRL_ATTR_UNSPEC,

        CTRL_ATTR_FAMILY_ID,

        CTRL_ATTR_FAMILY_NAME,

        CTRL_ATTR_VERSION,

        CTRL_ATTR_HDRSIZE,

        CTRL_ATTR_MAXATTR,

        CTRL_ATTR_OPS,

        CTRL_ATTR_MCAST_GROUPS,

        __CTRL_ATTR_MAX,

}; 

其中genl_multicast_group结构体如下,添加了name为”notify“的组播组:

static const struct genl_multicast_group genl_ctrl_groups[] = {

        { .name = "notify", },

};

其中genl_ctrlgenl_pernet_ops结构体为操作函数集合,如下:

static const struct genl_ops genl_ctrl_ops[] = {

        {

                .cmd            = CTRL_CMD_GETFAMILY,

                .doit           = ctrl_getfamily,

                .dumpit         = ctrl_dumpfamily,

                .policy         = ctrl_policy,

        },

};              

       这里协议定义了内核操作接口为CTRL_CMD_GETFAMILY,用于应用空间从内核中获取指定family名称的ID号。attr有效性策略为ctrl_policy。

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

};

关于genl_init()函数在系统启动过程中合适被调用此文就不展开,后续作为专题来描述。

1.1.5 使用

使用通用netlink套接字可以采取如下:

l   创建一个genl_family对象,并调用genl_register_family来注册

l   创建一个genl_ops对象,并调用genl_register_ops来注册。

或者直接使用genl_register_family_with_ops,并传递一个genl_family对象、genl_ops数组及长度。

创建通用netlink内核套接字后,需要注册控制器簇(genl_ctrl),id是固定的0x10,它也是genl_family的对象。其他的簇的id都是初始化为GENL_ID_GENERATE,并动态分配。

用户空间如果需要和内核空间交互,需要先根据family name请求到相应的family ID,而后进行相互沟通。

1.1.6 消息结构详细信息

更加详细信息可以观察文件include/net/netlink.h

/* ========================================================================

 *         Netlink Messages and Attributes Interface (As Seen On TV)

 * ------------------------------------------------------------------------

 *                          Messages Interface

 * ------------------------------------------------------------------------

 *

 * Message Format:

 *    <--- nlmsg_total_size(payload)  --->

 *    <-- nlmsg_msg_size(payload) ->

 *   +----------+- - -+-------------+- - -+-------- - -

 *   | nlmsghdr | Pad |   Payload   | Pad | nlmsghdr

 *   +----------+- - -+-------------+- - -+-------- - -

 *   nlmsg_data(nlh)---^                   ^

 *   nlmsg_next(nlh)-----------------------+

 *

 * Payload Format:

 *    <---------------------- nlmsg_len(nlh) --------------------->

 *    <------ hdrlen ------>       <- nlmsg_attrlen(nlh, hdrlen) ->

 *   +----------------------+- - -+--------------------------------+

 *   |     Family Header    | Pad |           Attributes           |

 *   +----------------------+- - -+--------------------------------+

 *   nlmsg_attrdata(nlh, hdrlen)---^

 *

 * Data Structures:

 *   struct nlmsghdr                    netlink message header

 *

 * Message Construction:

 *   nlmsg_new()                        create a new netlink message

 *   nlmsg_put()                        add a netlink message to an skb

 *   nlmsg_put_answer()                 callback based nlmsg_put()

 *   nlmsg_end()                        finalize netlink message

 *   nlmsg_get_pos()                    return current position in message

 *   nlmsg_trim()                       trim part of message

 *   nlmsg_cancel()                     cancel message construction

 *   nlmsg_free()                       free a netlink message

 *

 * Message Sending:

 *   nlmsg_multicast()                  multicast message to several groups

 *   nlmsg_unicast()                    unicast a message to a single socket

 *   nlmsg_notify()                     send notification message

 *

 * Message Length Calculations:

 *   nlmsg_msg_size(payload)            length of message w/o padding

 *   nlmsg_total_size(payload)          length of message w/ padding

 *   nlmsg_padlen(payload)              length of padding at tail

*

 * Message Payload Access:

 *   nlmsg_data(nlh)                    head of message payload

 *   nlmsg_len(nlh)                     length of message payload

 *   nlmsg_attrdata(nlh, hdrlen)        head of attributes data

 *   nlmsg_attrlen(nlh, hdrlen)         length of attributes data

 *

 * Message Parsing:

 *   nlmsg_ok(nlh, remaining)           does nlh fit into remaining bytes?

 *   nlmsg_next(nlh, remaining)         get next netlink message

 *   nlmsg_parse()                      parse attributes of a message

 *   nlmsg_find_attr()                  find an attribute in a message

 *   nlmsg_for_each_msg()               loop over all messages

 *   nlmsg_validate()                   validate netlink message incl. attrs

 *   nlmsg_for_each_attr()              loop over all attributes

 *

 * Misc:

 *   nlmsg_report()                     report back to application?

 *

 * ------------------------------------------------------------------------

 *                          Attributes Interface

 * ------------------------------------------------------------------------

 *

 * Attribute Format:

 *    <------- nla_total_size(payload) ------->

 *    <---- nla_attr_size(payload) ----->

 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -

 *   |  Header  | Pad |     Payload      | Pad |  Header

 *   +----------+- - -+- - - - - - - - - +- - -+-------- - -

 *                     <- nla_len(nla) ->      ^

 *   nla_data(nla)----^                        |

 *   nla_next(nla)-----------------------------'

 *

 * Data Structures:

 *   struct nlattr                      netlink attribute header

 *

 * Attribute Construction:

 *   nla_reserve(skb, type, len)        reserve room for an attribute

 *   nla_reserve_nohdr(skb, len)        reserve room for an attribute w/o hdr

 *   nla_put(skb, type, len, data)      add attribute to skb

 *   nla_put_nohdr(skb, len, data)      add attribute w/o hdr

 *   nla_append(skb, len, data)         append data to skb

 *

 * Attribute Construction for Basic Types:

 *   nla_put_u8(skb, type, value)       add u8 attribute to skb

 *   nla_put_u16(skb, type, value)      add u16 attribute to skb

 *   nla_put_u32(skb, type, value)      add u32 attribute to skb

 *   nla_put_u64_64bit(skb, type,

*                     value, padattr)  add u64 attribute to skb

 *   nla_put_s8(skb, type, value)       add s8 attribute to skb

 *   nla_put_s16(skb, type, value)      add s16 attribute to skb

 *   nla_put_s32(skb, type, value)      add s32 attribute to skb

 *   nla_put_s64(skb, type, value,

 *               padattr)               add s64 attribute to skb

 *   nla_put_string(skb, type, str)     add string attribute to skb

 *   nla_put_flag(skb, type)            add flag attribute to skb

 *   nla_put_msecs(skb, type, jiffies,

 *                 padattr)             add msecs attribute to skb

 *   nla_put_in_addr(skb, type, addr)   add IPv4 address attribute to skb

 *   nla_put_in6_addr(skb, type, addr)  add IPv6 address attribute to skb

 *

 * Nested Attributes Construction:

 *   nla_nest_start(skb, type)          start a nested attribute

 *   nla_nest_end(skb, nla)             finalize a nested attribute

 *   nla_nest_cancel(skb, nla)          cancel nested attribute construction

 *

 * Attribute Length Calculations:

 *   nla_attr_size(payload)             length of attribute w/o padding

 *   nla_total_size(payload)            length of attribute w/ padding

 *   nla_padlen(payload)                length of padding

 *

 * Attribute Payload Access:

 *   nla_data(nla)                      head of attribute payload

 *   nla_len(nla)                       length of attribute payload

 *

 * Attribute Payload Access for Basic Types:

 *   nla_get_u8(nla)                    get payload for a u8 attribute

 *   nla_get_u16(nla)                   get payload for a u16 attribute

 *   nla_get_u32(nla)                   get payload for a u32 attribute

 *   nla_get_u64(nla)                   get payload for a u64 attribute

 *   nla_get_s8(nla)                    get payload for a s8 attribute

 *   nla_get_s16(nla)                   get payload for a s16 attribute

 *   nla_get_s32(nla)                   get payload for a s32 attribute

 *   nla_get_s64(nla)                   get payload for a s64 attribute

 *   nla_get_flag(nla)                  return 1 if flag is true

 *   nla_get_msecs(nla)                 get payload for a msecs attribute

 *

 * Attribute Misc:

 *   nla_memcpy(dest, nla, count)       copy attribute into memory

 *   nla_memcmp(nla, data, size)        compare attribute with memory area

 *   nla_strlcpy(dst, nla, size)        copy attribute to a sized string

 *   nla_strcmp(nla, str)               compare attribute with string

 *

 * Attribute Parsing:

 *   nla_ok(nla, remaining)             does nla fit into remaining bytes?

*   nla_next(nla, remaining)           get next netlink attribute

 *   nla_validate()                     validate a stream of attributes

 *   nla_validate_nested()              validate a stream of nested attributes

 *   nla_find()                         find attribute in stream of attributes

 *   nla_find_nested()                  find attribute in nested attributes

 *   nla_parse()                        parse and validate stream of attrs

 *   nla_parse_nested()                 parse nested attribuets

 *   nla_for_each_attr()                loop over all attributes

 *   nla_for_each_nested()              loop over the nested attributes

 *=========================================================================

genl数据包的结构如下图:


genl机制的数据包分了4层,用户的实际数据封装在attribute里,一个或多个attribute可以被封装在用户自定义的一个family报文里,一个family报文又被封装在genlmsg里,最后genlmsg被封装在nlmsg里,总共4层。

 

 

 

1.1.7 源码一

1.1.7.1  内核代码

几个重要的数据数据结构关系如下:


内核代码中初始化一个自定义协议NETLINK_USER并初始化,然后指定数据回调函数hello_nl_recv_msg,该函数只是简单发送一个字符串“hello,from kernel”,如下:

#include <linux/module.h>

#include <net/sock.h>

#include <linux/netlink.h>

#include <linux/skbuff.h>

 

#define NETLINK_USER 31     //the user defined channel, the key factor

 

struct sock *nl_sk = NULL;

 

static void hello_nl_recv_msg(struct sk_buff *skb)

{

    struct nlmsghdr *nlh;

    int pid;

    struct sk_buff *skb_out;

    int msg_size;

    char *msg="hello,from kernel";

    int res;

 

    printk(KERN_INFO "Entering: %s\n", __FUNCTION__);

 

    msg_size=strlen(msg);

    //for receiving...

    nlh=(struct nlmsghdr*)skb->data;    //nlh message comes from skb's data... (sk_buff: unsigned char *data)

    /*  static inline void *nlmsg_data(const struct nlmsghdr *nlh)

        {

                return (unsigned char *) nlh + NLMSG_HDRLEN;

        }

    nlmsg_data - head of message payload */

    printk(KERN_INFO "Netlink received msg payload: %s\n",(char*)nlmsg_data(nlh));

 

    //for sending...

    pid = nlh->nlmsg_pid; // Sending process port ID, will send new message back to the 'user space sender'

 

    skb_out = nlmsg_new(msg_size,0);    //nlmsg_new - Allocate a new netlink message: skb_out

 

    if(!skb_out)

    {

        printk(KERN_ERR "Failed to allocate new skb\n");

        return;

    }

 

    nlh=nlmsg_put(skb_out,0,0,NLMSG_DONE,msg_size,0);  

* nlmsg_put - Add a new netlink message to an skb

 * @skb: socket buffer to store message in

 * @portid: netlink PORTID of requesting application

 * @seq: sequence number of message

 * @type: message type

 * @payload: length of message payload

 * @flags: message flags

    //#define NETLINK_CB(skb)           (*(struct netlink_skb_parms*)&((skb)->cb))

    //cb: This is the control buffer. It is free to use for every layer. Please put your private variables there

    /* struct netlink_skb_parms {

        struct ucred            creds;          // Skb credentials

        __u32                   pid;

        __u32                   dst_group;

    }; */

    //map skb->cb (char cb[48] __aligned(8); control buffer) to "struct netlink_skb_parms", so it has field pid/dst_group

    //so there should be convention: cb[48] is divided into creds/pid/dst_group...to convey those info

    NETLINK_CB(skb_out).dst_group = 0;                  /* not in mcast group */

    strncpy(nlmsg_data(nlh),msg,msg_size); //char *strncpy(char *dest, const char *src, size_t count)

//msg "Hello from kernel" => nlh -> skb_out

    res=nlmsg_unicast(nl_sk,skb_out,pid); //nlmsg_unicast - unicast a netlink message

                                         //@pid: netlink pid of the destination socket

    if(res<0)

        printk(KERN_INFO "Error while sending bak to user\n");

}

 

static int __init hello_init(void)

{

    //struct net init_net; defined in net_namespace.c

    //unit=NETLINK_USER: refer to some kernel examples

    //groups = 0, unicast

    //nl_sk: global sock, will be sent to hello_nl_recv_msg as argument (nl_sk ->...-> skb) and return from below func, by Tom Xue, not totally verified

   struct netlink_kernel_cfg cfg = {

     .input = hello_nl_recv_msg,//该函数原型可参考内核代码,其他参数默认即可

           };

     nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);

    printk("Entering: %s\n",__FUNCTION__);

    if(!nl_sk)

    {

        printk(KERN_ALERT "Error creating socket.\n");

        return -10;

    }

 

    return 0;

}

 

static void __exit hello_exit(void)

{

    printk(KERN_INFO "exiting hello module\n");

    netlink_kernel_release(nl_sk);

}

 

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

1.1.7.2  用户态代码

如下:

#include <stdlib.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <string.h>

#include <asm/types.h>

#include <linux/netlink.h>

#include <linux/socket.h>

#include <errno.h>

 

#define NETLINK_USER 31  //self defined

#define MAX_PAYLOAD 1024 /* maximum payload size*/

 

struct sockaddr_nl src_addr, dest_addr;

struct nlmsghdr *nlh = NULL;

struct iovec iov;

int sock_fd;

struct msghdr msg;  //msghdr includes: struct iovec *   msg_iov;

 

void main()

{

    //int socket(int domain, int type, int protocol);

    sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);

    if(sock_fd<0)

        return -1;

 

    memset(&src_addr, 0sizeof(src_addr));

    src_addr.nl_family = AF_NETLINK;

    src_addr.nl_pid = getpid(); /* self pid */

 

    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));

 

    memset(&dest_addr, 0sizeof(dest_addr));

    dest_addr.nl_family = AF_NETLINK;

    dest_addr.nl_pid = 0;       /* For Linux Kernel */

    dest_addr.nl_groups = 0;    /* unicast */

    //nlh: contains "Hello"

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));

    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));

    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);

    nlh->nlmsg_pid = getpid();  //self pid

    nlh->nlmsg_flags = 0;

 

    strcpy(NLMSG_DATA(nlh), "Hello");   //put "Hello" into nlh

 

    iov.iov_base = (void *)nlh;         //iov -> nlh

    iov.iov_len = nlh->nlmsg_len;

    msg.msg_name = (void *)&dest_addr;  //msg_name is Socket name: dest

    msg.msg_namelen = sizeof(dest_addr);

    msg.msg_iov = &iov;                 //msg -> iov

    msg.msg_iovlen = 1;

 

    printf("Sending message to kernel\n");

    sendmsg(sock_fd,&msg,0);    //msg -> find the (destination) socket name: dest

                                //msg -> iov -> nlh -> "Hello"

    printf("Waiting for message from kernel\n");

 

    /* Read message from kernel */

    recvmsg(sock_fd, &msg, 0);  //msg is also receiver for read

    printf("Received message payload: %s\n", NLMSG_DATA(nlh));  //msg -> iov -> nlh

    close(sock_fd);

}

1.1.8 源码二

1.1.8.1  内核

代码中定义两种类型的Genetlink cmd指令,其中DEMO_CMD_ECHO用于应用层下发数据,DEMO_CMD_REPLY用于内核向应用层回发数据;同时定义两种类型的attr属性参数,其中DEMO_CMD_ATTR_MESG表示字符串,DEMO_CMD_ATTR_DATA表示数据。

定义协议demo_family,其中ID号为GENL_ID_GENERATE,表示由内核统一分配,maxattr为DEMO_CMD_ATTR_MAX,其前文中定义的最大attr属性数,内核将为其分配缓存空间。

定义操作函数集:demo_ops,只有DEMO_CMD_ECHO类型的cmd创建消息处理函数接口(因为DEMO_CMD_REPLY类型的cmd用于内核消息,应用层不使用),指定doit消息处理回调函数为demo_echo_cmd,同时指定有效组策略为demo_cmd_policy。

static const struct genl_ops demo_ops[] = {

  {

   .cmd = DEMO_CMD_ECHO,

   .doit = demo_echo_cmd,

   .policy = demo_cmd_policy,

   .flags = GENL_ADMIN_PERM,

   },

};

static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX + 1] = {

  [DEMO_CMD_ATTR_MESG] = {.type = NLA_STRING},

  [DEMO_CMD_ATTR_DATA] = {.type = NLA_S32},

};

限定DEMO_CMD_ATTR_MESG的属性类型为NLA_STRING(字符串类型),限定DEMO_CMD_ATTR_DATA的属性类型为NLA_S32(有符号32位数)。

调用genl_register_family_with_ops()同时注册demo_family及demo_ops.

向所有的应用层加入CTRL控制器簇组播组的Generic Netlink套接字多播发送CTRL_CMD_NEWFAMILY消息,通知应用层有新的family注册了,这样应用层就可以捕获这一消息。

内核层主要就一个回发函数demo_echo_cmd, 其中入参为sk_buff和genl_info。genl_info是内核解析过的数据结构。该函数会根据属性调用cmd_attr_echo_message 或者cmd_attr_echo_data 。

cmd_attr_echo_message函数先打印应用层输入数据,然后依次调用

demo_prepare_reply、demo_mk_reply和demo_send_reply来向应用层发送信息。

其中函数nlmsg_new位于include/net/netlink.h

/**            

 * nlmsg_new - Allocate a new netlink message

 * @payload: size of the message payload

 * @flags: the type of memory to allocate.

 *             

 * Use NLMSG_DEFAULT_SIZE if the size of the payload isn't known

 * and a good default is needed.

 */                                             

static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)

{                      

        return alloc_skb(nlmsg_total_size(payload), flags);

}

内核具体源码如下:

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <net/genetlink.h>

 

#define DEMO_GENL_NAME          "DEMO_GEN_CTRL"

#define DEMO_GENL_VERSION       0x1

 

enum

{

  DEMO_CMD_UNSPEC = 0,          /* Reserved */

  DEMO_CMD_ECHO,                /* user->kernel request/get-response */

  DEMO_CMD_REPLY,               /* kernel->user event */

  __DEMO_CMD_MAX,

};

#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)

 

 

enum

{

  DEMO_CMD_ATTR_UNSPEC = 0,

  DEMO_CMD_ATTR_MESG,           /* demo message  */

  DEMO_CMD_ATTR_DATA,           /* demo data */

  __DEMO_CMD_ATTR_MAX,

};

#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1)

 

 

static struct genl_family demo_family = {

  .id = GENL_ID_GENERATE,

  .name = DEMO_GENL_NAME,

  .version = DEMO_GENL_VERSION,

  .maxattr = DEMO_CMD_ATTR_MAX,

};

 

static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX + 1] = {

  [DEMO_CMD_ATTR_MESG] = {.type = NLA_STRING},

  [DEMO_CMD_ATTR_DATA] = {.type = NLA_S32},

};

 

static int

demo_prepare_reply (struct genl_info *info, u8 cmd, struct sk_buff **skbp,

                    size_t size)

{

  struct sk_buff *skb;

  void *reply;

  skb = genlmsg_new (size, GFP_KERNEL);

  if (!skb)

    return -ENOMEM;

 

  if (!info)

    return -EINVAL;

 

  reply = genlmsg_put_reply (skb, info, &demo_family, 0, cmd);

  if (reply == NULL)

    {

      nlmsg_free (skb);

      return -EINVAL;

    }

 

  *skbp = skb;

  return 0;

}

 

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

}

 

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

 

  return genlmsg_reply (skb, info);

}

 

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

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

  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

cmd_attr_echo_data (struct genl_info *info)

{

  struct nlattr *na;

  s32 data;

  struct sk_buff *rep_skb;

  size_t size;

  int ret;

  na = info->attrs[DEMO_CMD_ATTR_DATA];

  if (!na)

    return -EINVAL;

 

  data = nla_get_s32 (info->attrs[DEMO_CMD_ATTR_DATA]);

  pr_info ("demo generic netlink receive echo data %d\n", data);

  size = nla_total_size (sizeof (s32));

 

  ret = demo_prepare_reply (info, DEMO_CMD_REPLY, &rep_skb, size);

  if (ret < 0)

    return ret;

  ret = nla_put_s32 (rep_skb, DEMO_CMD_ATTR_DATA, data);

  if (ret < 0)

    goto err;

 

  return demo_send_reply (rep_skb, info);

 

err:

  nlmsg_free (rep_skb);

  return ret;

}

 

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

  else

    return -EINVAL;

}

 

static const struct genl_ops demo_ops[] = {

  {

   .cmd = DEMO_CMD_ECHO,

   .doit = demo_echo_cmd,

   .policy = demo_cmd_policy,

   .flags = GENL_ADMIN_PERM,

   },

};

 

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

  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;

}

 

static void __exit

demo_genetlink_exit (void)

{

  int ret;

  printk ("demo generic netlink deinit.\n");

 

  ret = genl_unregister_family (&demo_family);

  if (ret != 0)

    {

      printk ("faled to unregister family:%i\n", ret);

    }

}

 

module_init (demo_genetlink_init);

module_exit (demo_genetlink_exit);

MODULE_LICENSE ("GPL");

其中Makefile如下:

obj-m := genetlink_kern.o

KERNELBUILD := /lib/modules/`uname -r`/build

default:

      @echo "BUILE Kmod"

      @make -C $(KERNELBUILD) M=$(shell pwd) modules

clean:

      @echo " CLEAN kmod"

      @rm -rf *.o

      @rm -rf .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers .*.d

 

1.1.8.2  应用层

通过函数demo_create_nl_socket创建一个netlink套接字,根据协议名字找到ID.

然后调用demo_send_cmd发送一个DEMO_CMD_ECHO类型的字符串消息,继续发送一个数据消息。

最后调用demo_msg_recv_analysis来接收内核发送的数据。

应用层代码如下:

#include <stdio.h> 

#include <stdlib.h> 

#include <errno.h> 

#include <unistd.h> 

#include <poll.h> 

#include <string.h> 

#include <fcntl.h> 

#include <sys/types.h> 

#include <sys/stat.h> 

#include <sys/socket.h> 

#include <sys/types.h> 

#include <signal.h> 

 

#include <linux/genetlink.h> 

 

 

#define DEMO_GENL_NAME          "DEMO_GEN_CTRL"

#define DEMO_GENL_VERSION       0x1

 

enum

{

        DEMO_CMD_UNSPEC = 0,          /* Reserved */

          DEMO_CMD_ECHO,                /* user->kernel request/get-response */

            DEMO_CMD_REPLY,               /* kernel->user event */

              __DEMO_CMD_MAX,

};

#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)

 

 

enum

{

        DEMO_CMD_ATTR_UNSPEC = 0,

          DEMO_CMD_ATTR_MESG,           /* demo message  */

            DEMO_CMD_ATTR_DATA,           /* demo data */

              __DEMO_CMD_ATTR_MAX,

};

#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1)

 

/*

 * Generic macros for dealing with netlink sockets. Might be duplicated

 * elsewhere. It is recommended that commercial grade applications use

 * libnl or libnetlink and use the interfaces provided by the library

 */

#define GENLMSG_DATA(glh)   ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))

#define GENLMSG_PAYLOAD(glh)      (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)

#define NLA_DATA(na)        ((void *)((char*)(na) + NLA_HDRLEN))

#define NLA_PAYLOAD(len)    (len - NLA_HDRLEN)

 

#define MAX_MSG_SIZE  1024

#define DEBUG               1

 

#define PRINTF(fmt, arg...) {                \

          if (DEBUG) {                  \

           printf(fmt, ##arg);         \

          }                        \

      }

 

struct msgtemplate {

      struct nlmsghdr n;

      struct genlmsghdr g;

      char buf[MAX_MSG_SIZE];

};

 

 

/*

 * Create a raw netlink socket and bind

 */

static int demo_create_nl_socket(int protocol)

{

      int fd;

      struct sockaddr_nl local;

 

      fd = socket(AF_NETLINK, SOCK_RAW, protocol);

      if (fd < 0)

           return -1;

 

      memset(&local, 0, sizeof(local));

      local.nl_family = AF_NETLINK;

      local.nl_pid = getpid();

 

      if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0)

           goto error;

 

      return fd;

     

error:

      close(fd);

      return -1;

}

 

 

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

      msg.g.cmd = genl_cmd;

      msg.g.version = DEMO_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.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) {

           if (r > 0) {

                 buf += r;

                 buflen -= r;

           } else if (errno != EAGAIN)

                 return -1;

      }

     

      return 0;

}

 

 

/*

 * Probe the controller in genetlink to find the family id

 * for the DEMO_GEN_CTRL family

 */

static int demo_get_family_id(int sd)

{

      struct msgtemplate ans;

     

      char name[100];

      int id = 0, ret;

      struct nlattr *na;

      int rep_len;

 

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

      if (ans.n.nlmsg_type == NLMSG_ERROR || (rep_len < 0) || !NLMSG_OK((&ans.n), rep_len))

           return 0;

 

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

      }

     

      return id;

}

 

 

int demo_msg_check(struct msgtemplate msg, int rep_len)

{

      if (msg.n.nlmsg_type == NLMSG_ERROR || !NLMSG_OK((&msg.n), rep_len)) {

           struct nlmsgerr *err = NLMSG_DATA(&msg);

           fprintf(stderr, "fatal reply error,  errno %d\n", err->error);

           return -1;

      }

     

      return 0;

}

 

 

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--) {

          

           rep_len = recv(sd, &msg, sizeof(msg), 0);

           if (rep_len < 0 || demo_msg_check(msg, rep_len) < 0) {

                 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;

          

           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 main(int argc, char* argv[])

{

      int nl_fd;

      int nl_family_id;

      int my_pid;

      int ret;

 

      int data;

      char *string;

 

      if (argc < 3) {

           printf("invalid input! usage: ./name <char msg> <uint data>\n");

           return 0;

      }

 

      nl_fd = demo_create_nl_socket(NETLINK_GENERIC);

      if (nl_fd < 0) {

           fprintf(stderr, "failed to create netlink socket\n");

           return 0;       

      }

 

      nl_family_id = demo_get_family_id(nl_fd);

      if (!nl_family_id) {

           fprintf(stderr, "Error getting family id, errno %d\n", errno);

           goto out;

      }

      PRINTF("family id %d\n", nl_family_id);

 

      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;

      }

 

      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;

      }

 

      demo_msg_recv_analysis(nl_fd, argc-1);

 

out:

      close(nl_fd);

      return 0;

}

1.1.9 参考

generic_netlink_howto

Generic Netlink内核实现分析

  • 1
    点赞
  • 0
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值