Generic Netlink(genl)介绍与例子

本文介绍了GenericNetlink的基本概念及其在Linux内核与用户空间间通信的应用。深入剖析了GenericNetlink的数据包结构、架构原理及实现流程,并提供了两个完整的编程示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Generic Netlink介绍

介绍Generic Netlink之前,不得不说Netlink。Netlink是一种灵活的并且健壮的通讯方式,可以用于kernel to user、user to kernel、kernel to kernel甚至user to user的通讯。Netlink的通道是通过Family来组织的,但随着使用越来越多,Family ID已经不够分配了,所以才有了Generic Netlink。

所以,Generic Netlink其实是对Netlink报文中的数据进行了再次封装。


二、genl架构

    +---------------------+      +---------------------+
     | (3) application "A" |      | (3) application "B" |
     +------+--------------+      +--------------+------+
            |                                    |
            \                                    /
             \                                  /
              |                                |
      +-------+--------------------------------+-------+
      |        :                               :       |   user-space
 =====+        :   (5)  kernel socket API      :       +================
      |        :                               :       |   kernel-space
      +--------+-------------------------------+-------+
               |                               |
         +-----+-------------------------------+----+
         |        (1)  Netlink subsystem            |
         +---------------------+--------------------+
                               |
         +---------------------+--------------------+
         |       (2) Generic Netlink bus            |
         +--+--------------------------+-------+----+
            |                          |       |
    +-------+---------+                |       |
    |  (4) controller |               /         \
    +-----------------+              /           \
                                     |           |
                  +------------------+--+     +--+------------------+
                  | (3) kernel user "X" |     | (3) kernel user "Y" |
                  +---------------------+     +---------------------+
genl架构图,https://wiki.linuxfoundation.org/networking/generic_netlink_howto#architectural-overview


三、genl数据包结构

要理解genl的程序,必须先了解genl数据包的结构。数据包的结构如下图所示


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


四、流程说明

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


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


五、示例

网上很多例子都不是很完整,这里贴一个完整的例子源码供参考。

例子1:user到kernel,user program通过发送一个命令到内核,内核相应的模块接收到命令以后,执行对应的回调函数

kernel端:

#include <net/netlink.h>
#include <net/genetlink.h>
#include <linux/version.h>

#define TEST_GENL_MSG_FROM_KERNEL   "Hello from kernel space!!!"

/* handler
 * message handling code goes here; return 0 on success, negative
 * values on failure
 */
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info);


/* netlink attributes */
enum {
      DOC_EXMPL_A_UNSPEC,
      DOC_EXMPL_A_MSG,
      __DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)

/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
      [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};

/* commands 定义命令类型,用户空间以此来表明需要执行的命令 */
enum {
    DOC_EXMPL_C_UNSPEC,
    DOC_EXMPL_C_ECHO,
    __DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)

/* family definition */
static struct genl_family doc_exmpl_genl_family = {
      .id = GENL_ID_GENERATE,   //这里不指定family ID,由内核进行分配
      .hdrsize = 0,             //自定义的头部长度,参考genl数据包结构
      .name = "DOC_EXMPL",      //这里定义family的名称,user program需要根据这个名字来找到对应的family ID。
      .version = 1,
      .maxattr = DOC_EXMPL_A_MAX,
};

/* operation definition 将命令command echo和具体的handler对应起来 */
static struct genl_ops doc_exmpl_genl_ops_echo = {
        .cmd = DOC_EXMPL_C_ECHO,
        .flags = 0,
        .policy = doc_exmpl_genl_policy,
        .doit = doc_exmpl_echo,
        .dumpit = NULL,
};

static struct genl_multicast_group doc_exmpl_genl_mcgrp = {
        .name = "DOC_EXMPL_GRP",
};

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, &doc_exmpl_genl_family, 0, cmd);

    *skbp = skb;
    return 0;
}

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
*
* return:
*    0:       成功
*    -1:      失败
*/
int genl_msg_send_to_user(void *data, int len, pid_t pid)
{
    struct sk_buff *skb;
    size_t size;
    void *head;
    int rc;

    size = nla_total_size(len); /* total length of attribute including padding */

    rc = genl_msg_prepare_usr_msg(DOC_EXMPL_C_ECHO, size, pid, &skb);
    if (rc) {
        return rc;
    }

    rc = genl_msg_mk_usr_msg(skb, DOC_EXMPL_A_MSG, data, len);
    if (rc) {
        kfree_skb(skb);
        return rc;
    }

    head = genlmsg_data(nlmsg_data(nlmsg_hdr(skb)));

    rc = genlmsg_end(skb, head);
    if (rc < 0) {
        kfree_skb(skb);
        return rc;
    }

    rc = genlmsg_unicast(&init_net, skb, pid);
    if (rc < 0) {
        return rc;
    }

    return 0;
}

//echo command handler, 命令处理函数,当接收到user program发出的命令后,这个函数会被内核调用
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info)
{
    /* message handling code goes here; return 0 on success, negative values on failure */
    struct nlmsghdr *nlhdr;
    struct genlmsghdr *genlhdr;
    struct nlattr *nlh;
    char *str;
    int ret;

    nlhdr = nlmsg_hdr(skb);
    genlhdr = nlmsg_data(nlhdr);
    nlh = genlmsg_data(genlhdr);
    str = nla_data(nlh);
    printk("doc_exmpl_echo get: %s\n", str);

    ret = genl_msg_send_to_user(TEST_GENL_MSG_FROM_KERNEL,
            strlen(TEST_GENL_MSG_FROM_KERNEL) + 1,  nlhdr->nlmsg_pid);

    return ret;
}

static int genetlink_init(void)
{
    int rc;

    /**
     * 1. Registering A Family
     * This function doesn't exist past linux 3.12
     */
    rc = genl_register_family(&doc_exmpl_genl_family);
    if (rc != 0)
        goto err_out1;

    rc = genl_register_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
    if (rc != 0)
        goto err_out2;

    /*
     * for multicast
     */
    rc = genl_register_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);
    if (rc != 0)
        goto err_out3;

    LOGC("doc_exmpl_genl_mcgrp.id=%d", doc_exmpl_genl_mcgrp.id);
    LOGC("genetlink_init OK");
    return 0;

err_out3:
    genl_unregister_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
err_out2:
    genl_unregister_family(&doc_exmpl_genl_family);
err_out1:
    LOGC("Error occured while inserting generic netlink example module\n");
    return rc;
}

static void genetlink_exit(void)
{
    LOGC("Generic Netlink Example Module unloaded.");

    genl_unregister_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);
    genl_unregister_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
    genl_unregister_family(&doc_exmpl_genl_family);
}


user program

#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 GENLMSG_DATA(glh)       ((void*)(((char*)glh) + GENL_HDRLEN))
#define NLA_DATA(nla)           ((void *)((char*)(nla) + NLA_HDRLEN))
#define NLA_NEXT(nla,len)       ((len) -= NLA_ALIGN((nla)->nla_len), \
                                      (struct nlattr*)(((char*)(nla)) + NLA_ALIGN((nla)->nla_len)))
#define NLA_OK(nla,len)         ((len) >= (int)sizeof(struct nlattr) && \
                                    (nla)->nla_len >= sizeof(struct nlattr) && \
                                    (nla)->nla_len <= (len))

//copy from kernel driver genl_ops's cmd
enum {
    DOC_EXMPL_C_UNSPEC,
    DOC_EXMPL_C_ECHO,
    __DOC_EXMPL_C_MAX,
};

//copy from kernel driver netlink attribute
enum {
    DOC_EXMPL_A_UNSPEC,
    DOC_EXMPL_A_MSG,
    __DOC_EXMPL_A_MAX,
};


#define MESSAGE_TO_KERNEL   "Hello World from user space!"



/**
 * nla_attr_size - length of attribute size, NOT including padding
 * @param payload   length of payload
 * @return
 */
static inline int nla_attr_size(int payload)
{
    return NLA_HDRLEN + payload;
}

/**
 * nla_total_size - total length of attribute including padding
 * @param payload   length of payload, NOT including NLA_HDR
 */
static inline int nla_total_size(int payload)
{
    return NLA_ALIGN(nla_attr_size(payload));
}

static int genlmsg_open(void)
{
    int sockfd;
    struct sockaddr_nl nladdr;
    int ret;

    sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
    if (sockfd < 0)
    {
        LOGC("socket: %m");
        return -1;
    }

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff; //这个是mask值,如果family ID & nl_groups为0,
                                   //则这个family的广播就接收不到,所以这里设为0xffffffff就可以接收所有的family消息

    ret = bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));
    if (ret < 0)
    {
        LOGC("bind: %m");
        ret = -1;
        goto err_out;
    }

    return sockfd;

err_out:
    close(sockfd);
    return ret;
}

static void *genlmsg_alloc(int *size)
{
    unsigned char *buf;
    int len;

    /*
     * attribute len
     * attr len = (nla_hdr + pad) + (payload(user data) + pad)
     */
    len = nla_total_size(*size);
    /*
     * family msg len,
     * but actually we have NOT custom family header
     * family msg len = family_hdr + payload(attribute)
     */
    len += 0;
    /*
     * generic netlink msg len
     * genlmsg len = (genlhdr + pad) + payload(family msg)
     */
    len += GENL_HDRLEN;
    /*
     * netlink msg len
     * nlmsg len = (nlmsghdr + pad) + (payload(genlmsg) + pad)
     */
    len = NLMSG_SPACE(len);

    buf = malloc(len);
    if (!buf)
        return NULL;

    memset(buf, 0, len);
    *size = len;

    return buf;
}

static void genlmsg_free(void *buf)
{
    if (buf)
        free(buf);
}

static int genlmsg_send(int sockfd, unsigned short nlmsg_type, unsigned int nlmsg_pid,
        unsigned char genl_cmd, unsigned char genl_version,
        unsigned short nla_type, const void *nla_data, unsigned int nla_len)
{
    struct nlmsghdr *nlh;    //netlink message header
    struct genlmsghdr *glh;  //generic netlink message header
    struct nlattr *nla;      //netlink attribute header

    struct sockaddr_nl nladdr;
    unsigned char *buf;
    int len;

    int count;
    int ret;

    if ((nlmsg_type == 0) || (!nla_data) || (nla_len <= 0))
    {
        return -1;
    }

    len = nla_len;
    buf = genlmsg_alloc(&len);
    if (!buf)
        return -1;

    nlh = (struct nlmsghdr *)buf;
    nlh->nlmsg_len = len;
    nlh->nlmsg_type = nlmsg_type;
    nlh->nlmsg_flags = NLM_F_REQUEST;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = nlmsg_pid;

    glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    glh->cmd = genl_cmd;
    glh->version = genl_version;


    nla = (struct nlattr *)GENLMSG_DATA(glh);
    nla->nla_type = nla_type;
    nla->nla_len = nla_attr_size(nla_len);
    memcpy(NLA_DATA(nla), nla_data, nla_len);

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;

    count = 0;
    ret = 0;
    do {

        ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
        if (ret < 0)
        {
            if (errno != EAGAIN)
            {
                count = -1;
                goto out;
            }
        }
        else
        {
            count += ret;
        }

    }while (count < len);


out:
    genlmsg_free(buf);

    LOGC("send return %d", count);
    return count;
}

/**
 *
 * @param sockfd    generic netlink socket fd
 * @param buf       the 'buf' is including the struct nlmsghdr,
 *                  struct genlmsghdr and struct nlattr
 * @param len       size of 'buf'
 * @return  >0      size of genlmsg
 *          <0      error occur
 */
static int genlmsg_recv(int sockfd, unsigned char *buf, unsigned int len)
{
    struct sockaddr_nl nladdr;
    struct msghdr msg;
    struct iovec iov;

    int ret;

    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;

    iov.iov_base = buf;
    iov.iov_len = len;

    msg.msg_name = (void *)&nladdr;
    msg.msg_namelen = sizeof(nladdr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;
    ret = recvmsg(sockfd, &msg, 0);
    ret = ret > 0 ? ret : -1;
    LOGC("recv return %d", ret);
    return ret;
}

static int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len,
                            int nlmsg_type, int nla_type, unsigned char *buf, int *len)
{
    struct nlmsghdr *nlh;
    struct genlmsghdr *glh;
    struct nlattr *nla;
    int nla_len;

    int l;
    int i;
    int ret = -1;

    if (!nlmsghdr || !buf || !len)
        return -1;

    LOGC("nlmsg_type = %d", nlmsghdr->nlmsg_type);
    if (nlmsg_type && (nlmsghdr->nlmsg_type != nlmsg_type))
        return -1;

    //读取到的数据流里面,可能会包含多条nlmsg
    for (nlh = nlmsghdr; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len))
    {
        /* The end of multipart message. */
        if (nlh->nlmsg_type == NLMSG_DONE)
        {
            LOGC("get NLMSG_DONE");
            ret = 0;
            break;
        }

        if (nlh->nlmsg_type == NLMSG_ERROR)
        {
            LOGC("get NLMSG_ERROR");
            ret = -1;
            break;
        }

        glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
        nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
        nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
        for (i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
        {
            //一条nlmsg里面,可能会包含多个attr
            LOGC("%d. nla->nla_type = %d", i, nla->nla_type);
            /* Match the family ID, copy the data to user */
            if (nla_type == nla->nla_type)
            {
                l = nla->nla_len - NLA_HDRLEN;  //attribute里的payload就是内核返回给用户的实际数据
                *len = *len > l ? l : *len;
                memcpy(buf, NLA_DATA(nla), *len);
                ret = 0;
                break;
            }
        }
    }

    return ret;
}

static int genlmsg_get_family_id(int sockfd, const char *family_name)
{
    void *buf;
    int len;
    __u16 id;
    int l;
    int ret;

    ret = genlmsg_send(sockfd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,
            CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1);
    if (ret < 0)
        return -1;

    len = 256;
    buf = genlmsg_alloc(&len);
    if (!buf)
        return -1;

    len = genlmsg_recv(sockfd, buf, len);
    if (len < 0)
        return len;

    id = 0;
    l = sizeof(id);
    genlmsg_dispatch((struct nlmsghdr *)buf, len, 0, CTRL_ATTR_FAMILY_ID, (unsigned char *)&id, &l);

    genlmsg_free(buf);

    return id > 0 ? id : -1;
}

static void genlmsg_close(int sockfd)
{
    if (sockfd >= 0)
        close(sockfd);
}



#define BUF_SIZE    256
static int test_netlink_unicast(void)
{
    struct nlmsghdr *nlh = NULL;
    int sockfd = -1;
    unsigned char buf[BUF_SIZE];
    int len;
    int id;
    pid_t pid;
    int ret;

    len = BUF_SIZE;
    nlh = genlmsg_alloc(&len);
    if (!nlh)
        return -1;

    sockfd = genlmsg_open();
    if (sockfd < 0)
        return -1;

    id = genlmsg_get_family_id(sockfd, "DOC_EXMPL");  //这里必须先通过family的名字获取到family ID,名字需要与驱动里的一致
    LOGC("get family ID[%d]", id);
    if (id <= 0)
    {
        ret = -1;
        goto out;
    }

    pid = getpid();
    ret = genlmsg_send(sockfd, id, pid, DOC_EXMPL_C_ECHO, 1,
                        DOC_EXMPL_A_MSG, MESSAGE_TO_KERNEL, strlen(MESSAGE_TO_KERNEL) + 1); //向内核发送genl消息
    if (ret < 0)
    {
        goto out;
    }

    ret = genlmsg_recv(sockfd, (unsigned char *)nlh, len); //等待内核的回复
    if (ret > 0)
    {
        memset(buf, 0, sizeof(buf));
        len = sizeof(buf);
        ret = genlmsg_dispatch(nlh, ret, id, DOC_EXMPL_A_MSG, buf, &len);
        if (ret == 0)
        {
            printf("get: %s\n", buf);
        }
    }

out:
    genlmsg_close(sockfd);
    genlmsg_free(nlh);

    return ret;
}


int main(int argc, char *argv[])
{
    /*
     * test netlink unicast
     */
    test_netlink_unicast();

    return 0;
}


例子2:kernel到user,user program先创建一个socket并监听,内核发生某个事件以后,就可以发送一个广播,通知user program

kernel端:

#include <net/netlink.h>
#include <net/genetlink.h>
#include <linux/version.h>


#define TEST_GENL_FAMILY_NAME "my-test-family"
#define TEST_GENL_MCAST_GROUP_NAME "my-test-group"
#define TEST_GENL_MSG_FROM_KERNEL   "Hello from kernel space!!!"

/* handler
 * message handling code goes here; return 0 on success, negative
 * values on failure
 */
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info);



/* netlink attributes 可以通过枚举索引找到对应的类型,用户空间应用程序要传递这样的信息 */
enum {
      DOC_EXMPL_A_UNSPEC,
      DOC_EXMPL_A_MSG,
      __DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)

/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
      [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};

/* commands 定义命令类型,用户空间以此来表明需要执行的命令 */
enum {
    DOC_EXMPL_C_UNSPEC,
    DOC_EXMPL_C_ECHO,
    __DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)

#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)

/* family definition */
static struct genl_family doc_exmpl_genl_family = {
      .id = GENL_ID_GENERATE,   //request a new channel number, assigned by kernel, NOT driver specific
      .hdrsize = 0,
      .name = "DOC_EXMPL",
      .version = 1,
      .maxattr = DOC_EXMPL_A_MAX,
};

/* operation definition 将命令command echo和具体的handler对应起来 */
static struct genl_ops doc_exmpl_genl_ops_echo = {
        .cmd = DOC_EXMPL_C_ECHO,
        .flags = 0,
        .policy = doc_exmpl_genl_policy,
        .doit = doc_exmpl_echo,
        .dumpit = NULL,
};

static struct genl_multicast_group doc_exmpl_genl_mcgrp = {
        .name = "DOC_EXMPL_GRP",
};

//需要在其他地方主动调用这个函数发送广播
static int test_netlink_send(void)
{
    struct sk_buff *skb = NULL;
    void *msg_header = NULL;
    int size;
    int rc;

    /* allocate memory */
    size = nla_total_size(strlen(TEST_GENL_MSG_FROM_KERNEL) + 1) + nla_total_size(0);

    skb = genlmsg_new(size, GFP_KERNEL);
    if (!skb)
        return -ENOMEM;

    /* add the genetlink message header */
    msg_header = genlmsg_put(skb, 0, 0,
                            &doc_exmpl_genl_family, 0, DOC_EXMPL_C_ECHO);
    if (!msg_header)
    {
        rc = -ENOMEM;
        goto err_out;
    }

    /* add a DOC_EXMPL_A_MSG attribute */
    rc = nla_put_string(skb, DOC_EXMPL_A_MSG, TEST_GENL_MSG_FROM_KERNEL);
    if (rc != 0)
        goto err_out;

    /* finalize the message */
    genlmsg_end(skb, msg_header);

    //multicast is send a message to a logical group
    rc = genlmsg_multicast(skb, 0, doc_exmpl_genl_mcgrp.id, GFP_KERNEL);
    if (rc != 0 && rc != -ESRCH)
    {
        /* if NO one is waitting the message in user space,
         * genlmsg_multicast return -ESRCH
         */
        LOGC("genlmsg_multicast to user failed, return %d", rc);

        /*
         * attention:
         * If you NOT call genlmsg_unicast/genlmsg_multicast and error occurs,
         * call nlmsg_free(skb).
         * But if you call genlmsg_unicast/genlmsg_multicast, NO need to call
         * nlmsg_free(skb). If NOT, kernel crash.
         */
        return rc;
    }

    LOGC("genlmsg_multicast Success");

    /*
     * Attention:
     * Should NOT call nlmsg_free(skb) here. If NOT, kernel crash!!!
     */
    return 0;

err_out:
    if (skb)
        nlmsg_free(skb);
    return rc;
}


static int genetlink_init(struct my_module_priv *ctx)
{
    int rc;

    /**
     * 1. Registering A Family
     * This function doesn't exist past linux 3.12
     */
    rc = genl_register_family(&doc_exmpl_genl_family);
    if (rc != 0)
        goto err_out1;

    rc = genl_register_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
    if (rc != 0)
        goto err_out2;

    /*
     * for multicast
     */
    rc = genl_register_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);
    if (rc != 0)
        goto err_out3;

    LOGC("doc_exmpl_genl_mcgrp.id=%d", doc_exmpl_genl_mcgrp.id);
    LOGC("genetlink_init OK");
    return 0;

err_out3:
    genl_unregister_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
err_out2:
    genl_unregister_family(&doc_exmpl_genl_family);
err_out1:
    LOGC("Error occured while inserting generic netlink example module\n");
    return rc;
}

static void genetlink_exit(struct my_module_priv *ctx)
{
    LOGC("Generic Netlink Example Module unloaded.");

    genl_unregister_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);
    genl_unregister_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);
    genl_unregister_family(&doc_exmpl_genl_family);
}

static struct my_module_work g_my_module_work = {
        .do_test_probe = genetlink_init,
        .do_test_remove = genetlink_exit,
};

user program

#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 GENLMSG_DATA(glh)       ((void*)(((char*)glh) + GENL_HDRLEN))
#define NLA_DATA(nla)           ((void *)((char*)(nla) + NLA_HDRLEN))
#define NLA_NEXT(nla,len)       ((len) -= NLA_ALIGN((nla)->nla_len), \
                                      (struct nlattr*)(((char*)(nla)) + NLA_ALIGN((nla)->nla_len)))
#define NLA_OK(nla,len)         ((len) >= (int)sizeof(struct nlattr) && \
                                    (nla)->nla_len >= sizeof(struct nlattr) && \
                                    (nla)->nla_len <= (len))

//copy from kernel driver genl_ops's cmd
enum {
    DOC_EXMPL_C_UNSPEC,
    DOC_EXMPL_C_ECHO,
    __DOC_EXMPL_C_MAX,
};

//copy from kernel driver netlink attribute
enum {
    DOC_EXMPL_A_UNSPEC,
    DOC_EXMPL_A_MSG,
    __DOC_EXMPL_A_MAX,
};


#define MESSAGE_TO_KERNEL   "Hello World from user space!"



/**
 * nla_attr_size - length of attribute size, NOT including padding
 * @param payload   length of payload
 * @return
 */
static inline int nla_attr_size(int payload)
{
    return NLA_HDRLEN + payload;
}

/**
 * nla_total_size - total length of attribute including padding
 * @param payload   length of payload, NOT including NLA_HDR
 */
static inline int nla_total_size(int payload)
{
    return NLA_ALIGN(nla_attr_size(payload));
}

static int genlmsg_open(void)
{
    int sockfd;
    struct sockaddr_nl nladdr;
    int ret;

    sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
    if (sockfd < 0)
    {
        LOGC("socket: %m");
        return -1;
    }

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;

    ret = bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));
    if (ret < 0)
    {
        LOGC("bind: %m");
        ret = -1;
        goto err_out;
    }

    return sockfd;

err_out:
    close(sockfd);
    return ret;
}

static void *genlmsg_alloc(int *size)
{
    unsigned char *buf;
    int len;

    /*
     * attribute len
     * attr len = (nla_hdr + pad) + (payload(user data) + pad)
     */
    len = nla_total_size(*size);
    /*
     * family msg len,
     * but actually we have NOT custom family header
     * family msg len = family_hdr + payload(attribute)
     */
    len += 0;
    /*
     * generic netlink msg len
     * genlmsg len = (genlhdr + pad) + payload(family msg)
     */
    len += GENL_HDRLEN;
    /*
     * netlink msg len
     * nlmsg len = (nlmsghdr + pad) + (payload(genlmsg) + pad)
     */
    len = NLMSG_SPACE(len);

    buf = malloc(len);
    if (!buf)
        return NULL;

    memset(buf, 0, len);
    *size = len;

    return buf;
}

static void genlmsg_free(void *buf)
{
    if (buf)
        free(buf);
}

static int genlmsg_send(int sockfd, unsigned short nlmsg_type, unsigned int nlmsg_pid,
        unsigned char genl_cmd, unsigned char genl_version,
        unsigned short nla_type, const void *nla_data, unsigned int nla_len)
{
    struct nlmsghdr *nlh;    //netlink message header
    struct genlmsghdr *glh;  //generic netlink message header
    struct nlattr *nla;      //netlink attribute header

    struct sockaddr_nl nladdr;
    unsigned char *buf;
    int len;

    int count;
    int ret;

    if ((nlmsg_type == 0) || (!nla_data) || (nla_len <= 0))
    {
        return -1;
    }

    len = nla_len;
    buf = genlmsg_alloc(&len);
    if (!buf)
        return -1;

    nlh = (struct nlmsghdr *)buf;
    nlh->nlmsg_len = len;
    nlh->nlmsg_type = nlmsg_type;
    nlh->nlmsg_flags = NLM_F_REQUEST;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = nlmsg_pid;

    glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    glh->cmd = genl_cmd;
    glh->version = genl_version;


    nla = (struct nlattr *)GENLMSG_DATA(glh);
    nla->nla_type = nla_type;
    nla->nla_len = nla_attr_size(nla_len);
    memcpy(NLA_DATA(nla), nla_data, nla_len);

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;

    count = 0;
    ret = 0;
    do {

        ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
        if (ret < 0)
        {
            if (errno != EAGAIN)
            {
                count = -1;
                goto out;
            }
        }
        else
        {
            count += ret;
        }

    }while (count < len);


out:
    genlmsg_free(buf);

    LOGC("send return %d", count);
    return count;
}

/**
 *
 * @param sockfd    generic netlink socket fd
 * @param buf       the 'buf' is including the struct nlmsghdr,
 *                  struct genlmsghdr and struct nlattr
 * @param len       size of 'buf'
 * @return  >0      size of genlmsg
 *          <0      error occur
 */
static int genlmsg_recv(int sockfd, unsigned char *buf, unsigned int len)
{
    struct sockaddr_nl nladdr;
    struct msghdr msg;
    struct iovec iov;

    int ret;

    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;

    iov.iov_base = buf;
    iov.iov_len = len;

    msg.msg_name = (void *)&nladdr;
    msg.msg_namelen = sizeof(nladdr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;
    ret = recvmsg(sockfd, &msg, 0);
    ret = ret > 0 ? ret : -1;
    LOGC("recv return %d", ret);
    return ret;
}

static int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len,
                            int nlmsg_type, int nla_type, unsigned char *buf, int *len)
{
    struct nlmsghdr *nlh;
    struct genlmsghdr *glh;
    struct nlattr *nla;
    int nla_len;

    int l;
    int i;
    int ret = -1;

    if (!nlmsghdr || !buf || !len)
        return -1;

    LOGC("nlmsg_type = %d", nlmsghdr->nlmsg_type);
    if (nlmsg_type && (nlmsghdr->nlmsg_type != nlmsg_type))
        return -1;

    for (nlh = nlmsghdr; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len))
    {
        /* The end of multipart message. */
        if (nlh->nlmsg_type == NLMSG_DONE)
        {
            LOGC("get NLMSG_DONE");
            ret = 0;
            break;
        }

        if (nlh->nlmsg_type == NLMSG_ERROR)
        {
            LOGC("get NLMSG_ERROR");
            ret = -1;
            break;
        }

        glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
        nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
        nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
        for (i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
        {
            LOGC("%d. nla->nla_type = %d", i, nla->nla_type);
            /* Match the family ID, copy the data to user */
            if (nla_type == nla->nla_type)
            {
                l = nla->nla_len - NLA_HDRLEN;  //data length in one attribute
                *len = *len > l ? l : *len;
                memcpy(buf, NLA_DATA(nla), *len);
                ret = 0;
                break;
            }
        }
    }

    return ret;
}

static int genlmsg_get_family_id(int sockfd, const char *family_name)
{
    void *buf;
    int len;
    __u16 id;
    int l;
    int ret;

    ret = genlmsg_send(sockfd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,
            CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1);
    if (ret < 0)
        return -1;

    len = 256;
    buf = genlmsg_alloc(&len);
    if (!buf)
        return -1;

    len = genlmsg_recv(sockfd, buf, len);
    if (len < 0)
        return len;

    id = 0;
    l = sizeof(id);
    genlmsg_dispatch((struct nlmsghdr *)buf, len, 0, CTRL_ATTR_FAMILY_ID, (unsigned char *)&id, &l);

    genlmsg_free(buf);

    return id > 0 ? id : -1;
}

static void genlmsg_close(int sockfd)
{
    if (sockfd >= 0)
        close(sockfd);
}



#define BUF_SIZE    256
static int test_netlink_multicast(void)
{
    int sockfd = -1;
    unsigned char buf[BUF_SIZE];
    int len;

    struct nlmsghdr *nlh = NULL;
    int id;

    int ret;

    len = BUF_SIZE;
    nlh = genlmsg_alloc(&len);
    if (!nlh)
        return -1;

    sockfd = genlmsg_open();
    if (sockfd < 0)
    {
        ret = -1;
        goto out;
    }

    id = genlmsg_get_family_id(sockfd, "DOC_EXMPL");
    LOGC("get family ID[%d]", id);
    if (id <= 0)
    {
        ret = -1;
        goto out;
    }

    ret = genlmsg_recv(sockfd, (unsigned char *)nlh, len);
    LOGC("recv return %d", ret);
    if (ret > 0)
    {
        memset(buf, 0, sizeof(buf));
        len = sizeof(buf);
        ret = genlmsg_dispatch(nlh, ret, id, DOC_EXMPL_A_MSG, buf, &len);
        if (ret == 0)
        {
            printf("get: %s\n", buf);
        }
    }

out:
    genlmsg_close(sockfd);
    genlmsg_free(nlh);

    return 0;
}

int main(int argc, char *argv[])
{
    /*
     * test netlink broadcast
     */
    test_netlink_multicast();

    return 0;
}

七、参考

https://lwn.net/Articles/208755/

https://wiki.linuxfoundation.org/networking/generic_netlink_howto#architectural-overview

http://www.tuicool.com/articles/jE7nim

<think>好的,用户现在的问题是“linuxC netlink socket如何监听WIFI设备连接情况”。我需要仔细分析这个问题,并给出详细的解决方案。首先,我需要确定用户的需求是什么。用户可能想要监控WiFi设备的连接状态变化,比如设备连接或断开,或者连接到不同的网络。首先,回顾之前的历史对话,用户之前询问过如何监听WiFi状态变化,我给出的答案是通过NETLINK_ROUTE协议,监听RTMGRP_LINK组播组来获取接口状态变化。但这次的问题更具体,是关于“设备连接情况”,这可能涉及更详细的连接信息,比如SSID变更、认证状态等,而不仅仅是接口的UP/DOWN状态。接下来,我需要确定使用哪种netlink协议和组播组。之前的回答用的是NETLINK_ROUTE,但可能对于更详细的连接信息,可能需要使用其他协议,比如NL80211(属于NETLINK_GENERIC家族)。NL80211是专门用于无线设备的netlink接口,可以获取更多无线相关的信息,比如扫描结果、连接状态、信号强度等。 因此,解决方案可能需要使用NETLINK_GENERIC,并找到对应的nl80211家族标识。这需要包含linux/nl80211.h头文件,并且可能需要查找家族ID,或者使用libnl库来简化操作。不过用户要求使用纯C代码,可能需要手动处理这些步骤。然后,考虑如何创建socket,绑定到正确的组播组。NETLINK_GENERIC需要指定正确的协议类型,并且组播组可能需要订阅特定的事件,比如NL80211_MULTICAST_GROUP_MLME,这个组播组用于管理事件,比如连接和断开。接下来,解析接收到的消息。需要处理NL80211_CMD_CONNECT和NL80211_CMD_DISCONNECT事件,从中提取SSID等信息。这需要解析netlink消息的属性,比如NL80211_ATTR_MAC(AP的MAC地址)、NL80211_ATTR_SSID等。 需要注意的是,用户可能需要root权限来执行这些操作,因为访问无线设备的信息通常需要特权。此外,代码可能需要处理多个属性,并且正确解析嵌套的属性结构。另外,可能还需要考虑如何区分不同的无线接口,比如wlan0、wlp3s0等,这可以通过检查接口名称或索引来实现。在之前的回答中,用户提到过滤接口名称以"wlan"开头,但可能需要更灵活的方法,比如动态获取所有无线接口。 然后,编写示例代码。代码需要创建netlink socket,绑定到NL80211家族,订阅相应的组播组,然后循环接收消息,解析事件类型和属性。需要处理nlmsg结构,使用nlmsg_parse等函数来提取属性。可能遇到的问题包括如何获取NL80211家族ID,这可能需要通过genl_ctrl消息来查询。或者,如果系统不支持nl80211,可能需要使用其他方法。不过大多数现代Linux系统使用nl80211作为无线设备的配置接口。另外,编译代码时需要链接libnl库,比如使用pkg-config来获取正确的编译选项。但在用户的问题中,可能需要给出纯C的实现,不依赖外部库,因此可能需要手动处理genl消息,这会比较复杂。综合考虑,给出一个使用libnl3库的例子可能更简洁,但用户可能希望避免依赖第三方库。因此,可能需要使用原始的socket操作,手动构造和解析消息。不过这会增加代码的复杂性,尤其是处理属性和嵌套结构。因此,权衡之后,决定使用libnl3库来简化代码,因为手动处理genl消息非常繁琐且容易出错。同时,说明编译时需要安装libnl3库,并链接相应的库文件。最后,确保代码示例能够正确编译和运行,并解释关键部分,比如socket创建、消息订阅、事件解析等。同时提醒用户需要root权限,以及可能的依赖安装步骤。</think>以下是使用Linux netlink socket监听WiFi连接状态的实现方法,基于`NL80211`协议实现更精细化的无线事件监控: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/genetlink.h> #include <linux/nl80211.h> #define BUFFER_SIZE 8192 // 获取NL80211家族ID int get_nl80211_family_id(int sock) { struct { struct nlmsghdr hdr; struct genlmsghdr gnlhdr; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); req.hdr.nlmsg_type = GENL_ID_CTRL; req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.gnlhdr.cmd = CTRL_CMD_GETFAMILY; req.gnlhdr.version = 1; struct nlattr *attr = (struct nlattr*)((char*)&req + NLMSG_ALIGN(req.hdr.nlmsg_len)); attr->nla_type = CTRL_ATTR_FAMILY_NAME; attr->nla_len = strlen("nl80211") + 1 + NLA_HDRLEN; strcpy((char*)attr + NLA_HDRLEN, "nl80211"); req.hdr.nlmsg_len += NLMSG_ALIGN(attr->nla_len); if (send(sock, &req, req.hdr.nlmsg_len, 0) < 0) { perror("send"); return -1; } char resp[BUFFER_SIZE]; ssize_t len = recv(sock, resp, sizeof(resp), 0); if (len < 0) { perror("recv"); return -1; } struct nlmsghdr *nlh = (struct nlmsghdr *)resp; struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; struct genlmsghdr *gnlh = NLMSG_DATA(nlh); struct nlattr *nla = (struct nlattr*)((char*)gnlh + GENL_HDRLEN); int remaining = len - NLMSG_HDRLEN - GENL_HDRLEN; nla_parse(tb, CTRL_ATTR_MAX, nla, remaining, NULL); if (tb[CTRL_ATTR_FAMILY_ID]) { return *(__u16 *)nla_data(tb[CTRL_ATTR_FAMILY_ID]); } return -1; } int main() { struct sockaddr_nl src_addr; int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (sock < 0) { perror("socket"); return EXIT_FAILURE; } memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); if (bind(sock, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0) { perror("bind"); close(sock); return EXIT_FAILURE; } int nl80211_id = get_nl80211_family_id(sock); if (nl80211_id < 0) { fprintf(stderr, "nl80211 not found\n"); close(sock); return EXIT_FAILURE; } // 订阅MLME事件(管理帧事件) struct nl_msg *msg = nlmsg_alloc(); genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id, 0, NLM_F_REQUEST, NL80211_CMD_SUBSCRIBE_MLME, 0); nla_put_u32(msg, NL80211_ATTR_IFINDEX, 1); // 监控所有接口 if (send(sock, nlmsg_hdr(msg), nlmsg_len(msg), 0) < 0) { perror("subscribe failed"); nlmsg_free(msg); close(sock); return EXIT_FAILURE; } nlmsg_free(msg); printf("Listening for WiFi connection events...\n"); char buffer[BUFFER_SIZE]; while (1) { ssize_t len = recv(sock, buffer, sizeof(buffer), 0); if (len < 0) { perror("recv"); continue; } struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { struct genlmsghdr *gnlh = NLMSG_DATA(nlh); if (gnlh->cmd == NL80211_CMD_CONNECT) { printf("Device connected to AP\n"); // 解析连接详细信息 struct nlattr *tb[NL80211_ATTR_MAX + 1]; nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (tb[NL80211_ATTR_MAC]) { unsigned char *mac = nla_data(tb[NL80211_ATTR_MAC]); printf("AP MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (tb[NL80211_ATTR_SSID]) { char *ssid = nla_data(tb[NL80211_ATTR_SSID]); int len = nla_len(tb[NL80211_ATTR_SSID]); printf("SSID: %.*s\n", len, ssid); } } else if (gnlh->cmd == NL80211_CMD_DISCONNECT) { printf("Device disconnected\n"); } } } close(sock); return EXIT_SUCCESS; } ``` 代码解析: 1. `get_nl80211_family_id` 通过GENL控制接口查询nl80211的家族ID 2. 主程序创建NETLINK_GENERIC类型的socket并绑定 3. 订阅NL80211_CMD_SUBSCRIBE_MLME事件来接收连接状态变更 4. 消息解析: - 识别NL80211_CMD_CONNECT/NL80211_CMD_DISCONNECT事件 - 解析NL80211_ATTR_MAC获取AP的MAC地址 - 解析NL80211_ATTR_SSID获取连接的网络名称 编译依赖: ```bash # 需要安装libnl-3-dev gcc -o wifi_conn_monitor wifi_conn_monitor.c -lnl-3 -lnl-genl-3 ``` 注意事项: 1. 需要root权限运行 2. 可以捕获真实连接事件(包括漫游切换) 3. 需要根据实际接口调整NL80211_ATTR_IFINDEX值 4. 完整实现需要处理更多属性(如认证状态、信号强度等)
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值