wpa_supplicant与内核nl80211通信之Generic Netlink

代码位置:

kernel/net/netlink/genetlink.c

kernel/include/net/genetlink.h

GENL简介

netlink仅支持32种协议类型,这在实际应用中可能并不足够。因此产生了generic netlink(以下简称为genl)。

generic netlink支持1023(前10个保留不用)个子协议号,弥补了netlink协议类型较少的缺陷。

1 架构及工作原理:

Generic Netlink是以自定义的genl_family为基本单位的,每一个想使用genl通信的模块都要自己创建一个genl_family然后注册到genl模块(总线)中,最多可以添加1024个。

用户空间在使用genl时,可以将指定将数据发送给你挂载到genl上的family,family再做具体的处理

每个family在注册时都被分配了唯一的family_id,用户空间在发送数据时,将family_id写入nlmsghdr.nlmsg_type字段,genl就可以对应的family。

genl中共分配了16个链表,每个链表都可以挂载自定义的family

static struct list_head family_ht[GENL_FAM_TAB_SIZE=16];

这16个链表中元素的总个数是1024.

结构体中重要参数意义如下:

  1. id: 注册时指定,若为0则有genl分配一个未占用的id,id的范围是10~1023,0~10保留未使用

    id和链表数组family_ht[16]的对应关系首先对id取16的余数 id&0x1111,然后根据余数确定添加到哪一条链表中。这样的方式,比单独一条链表搜索时要节省时间,比建一个1024的数组要节省空间。看函数名字是genl_family_hash(),不知道hash表是不是这么创建的。

  2. name: 一个字符串,当内核模块和用户空间采用同样的name,才可以建立通信
  3. ops_list:该family的操纵函数
  4. mcast_groups:多播组链表,可以往该family中再注册多播组
  5. family_list:该family在genl模块中的链表索引
struct genl_family {
    unsigned int        id;
    unsigned int        hdrsize;
    char            name[GENL_NAMSIZ];
    unsigned int        version;
    unsigned int        maxattr;
    bool            netnsok;
    bool            parallel_ops;
    int         (*pre_doit)(struct genl_ops *ops,
                        struct sk_buff *skb,
                        struct genl_info *info);
    void            (*post_doit)(struct genl_ops *ops,
                         struct sk_buff *skb,
                         struct genl_info *info);
    struct nlattr **    attrbuf;    /* private */
    struct list_head    ops_list;   /* private */
    struct list_head    family_list;    /* private */
    struct list_head    mcast_groups;   /* private */
    struct module       *module;
};

genl_family 操作函数

cmd:是个整形,用户空间发送消息时,首先定义好faimily的name,然后再定义好cmd,就可以调用相应的处理函数doit();

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

2 genl初始化过程

链表的初始化和消息接收函数

将创建的netlnik socket挂载到net_namespace(不懂这个东东)中

static int __init genl_init(void)
{
    int i, err;

    //初始化16条链表
    for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
        INIT_LIST_HEAD(&family_ht[i]);

    //注册一个自己用的family,共上层使用
    err = genl_register_family_with_ops(&genl_ctrl, &genl_ctrl_ops, 1);

    //真正的初始化函数,其中的genl_pernet_init()会创建netlink,定义接收函数genl_rcv()
    err = register_pernet_subsys(&genl_pernet_ops);
    genl_pernet_init()
    {
        struct netlink_kernel_cfg cfg = {
        .input      = genl_rcv, //接收函数
        .flags      = NL_CFG_F_NONROOT_RECV,
        };

        /* we‘ll bump the group number right afterwards */
        net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
    }

    //注册到多播组中
    err = genl_register_mc_group(&genl_ctrl, &notify_grp);
}

2.1 接收数据genl_rcv_msg

genl_rcv()接收到数据会直接调用genl_rcv_msg()

然后根据nlmsghdr中的family_id(type)找到对应的family,

再根据genlmsghdr中的cmd找到family中对应的ops,然后调用doit做相应的处理

static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    //nlmsghdr中的type应该和family的id一致,
    //但是内核中genl注册family时,id是自动非配的,那用户空间发送的消息怎么确认id,
    family = genl_family_find_byid(nlh->nlmsg_type);

    //根据nlh中定义的cmd类型决定
    genl_family_rcv_msg(family, skb, nlh);
    {
        //在传入的nlh的载荷中包含着geml的头genlmsghdr,
        struct genlmsghdr *hdr = nlmsg_data(nlh);

        //genl 信息,里面有netlnik head,genl head,user head等信息,最终会由用户(nl80211)定义的ops处理
        struct genl_info info;      

        //如果有family有体检需要处理的,可以放在该处
        err = family->pre_doit(ops, skb, &info);

        //通过cmd找到ops,对传入的数据进行处理
        ops = genl_get_cmd(hdr->cmd, family);
        //ops处理数据
        err = ops->doit(skb, &info);

        //family的后续处理
        family->post_doit(ops, skb, &info);
    }
}

3 内核中使用genl

3.1 genl family 注册

生成family_id,并将其加载到(android M中已经更改此API)

static inline int genl_register_family_with_ops(struct genl_family *family, struct genl_ops *ops, size_t n_ops)
{
    //将每个family按照id顺序加载到genl的16条链表中
    err = __genl_register_family(family);

    //将genl_ops注册到family的ops_list上
    for (i = 0; i < n_ops; ++i, ++ops) {
        err = genl_register_ops(family, ops);
    }
}

3.2 genl 多播组注册

family指向属于该多播组的family,

id:是多播组id,由内核分配,在genlmsg_multicast()中使用

struct genl_multicast_group {
    struct genl_family  *family;    /* private */
    struct list_head    list;       /* private */
    char            name[GENL_NAMSIZ];
    u32         id;
};

int genl_register_mc_group(struct genl_family *family, struct genl_multicast_group *grp)
{
    ....
    //有一大堆算id的代码,看不懂, 通过传入的group的name计算出唯一的一个id,作为netlink多播组的group id
    ....
    grp->id = id;
    set_bit(id, mc_groups);
    list_add_tail(&grp->list, &family->mcast_groups);
    grp->family = family;
}

3.3 发送单播数据

static inline int genlmsg_reply(struct sk_buff *skb, struct genl_info *info)
最终会调用netlnik api nlmsg_unicast()

3.4 发送多播数据

genlmsg_multicast_netns(struct net *net, struct sk_buff *skb, u32 portid, unsigned int group, gfp_t flags);

4 用户空间使用genl

代码位置:external/libnl

官方文档 http://www.carisma.slowglass.com/~tgr/libnl/doc/core.html#_socket_structure_struct_nl_sock

netlink通信时同样可以使用libnl中的api,之前看netlink那个实例的时候,就感觉代码很乱,libnl中提供了比较精简的api

4.1 生成socket

#include <netlink/socket.h>
struct nl_sock *nl_socket_alloc(void)
void nl_socket_free(struct nl_sock *sk)

//与kernel中的 genl模块相连接
genl_connect(struct nl_sk *)

//获取genl中要使用的family的id
int genl_ctrl_resolve(struct nl_sock *sk, const char *name)
{
    msg = nlmsg_alloc();
    //"nlctrl"是由kernel 的genl模块住的一个family,目前只支持获取genel中 family的id
    genlmsg_put(msg, 0, 0, genl_ctrl_resolve(global->nl, "nlctrl"), 0, 0, CTRL_CMD_GETFAMILY, 0);
}

4.2 发送数据

struct nl_msg *msg = nlmsg_alloc();
//msg中填充family ops中定义的CMD
//wpa_s 封装为 static void * nl80211_cmd(struct wpa_driver_nl80211_data *drv, struct nl_msg *msg, int flags, uint8_t cmd)
//该函数在生成nlmsg时,在其中添加genlmsghdr(genl的头)
void *genlmsg_put(struct nl_msg *msg, uint32_t pid, uint32_t seq, int family, int hdrlen, int flags, uint8_t cmd, uint8_t version)
示例:genlmsg_put(msg, 0, 0, family_id, 0, 0, cmd, 0);

//msg中放置不同类型的属性 attribute,整形或一块内存
NLA_PUT_U32(msg, attrtype, value);
NLA_PUT(msg, attrtype, attrlen, data)

4.3 发送单播

返回值为发送的字符数,若错误返回负

int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg)

该函数最终调用socket API sendmsg(),函数内部会利用nl_msg中的信息生成 sendmsg 所需的msghdr结构体

4.4 接收多播

nl_socket_add_membership(struct nl_sock *, group_id);

5 GENL 流程图

6 使用实例

只实现了用户空间向内核中的family发送单播消息,

使用在ops对应的cmd doit函数中读取上层传送下来的attr内容

内核向用户空间发送多播消息还没有实现,因为不知道如何拿到genl创建的sock,

6.1 内核空间

//genl test code by cuijiyue
#include <.h>
#define GENL_TEST_CMD 6
#define GENL_TEST_ATRR_TYPE 66

char *msg_send = "hello form genl kernel!";

static struct genl_family genl_test_fam = {
    .id = GENL_ID_GENERATE, /* don‘t bother with a hardcoded ID */
    .name = "genl_test",    /* have users key off the name instead */
    .hdrsize = 0,       /* no private header */
    .version = 1,       /* no particular meaning now */
    .maxattr = NL80211_ATTR_MAX,
    .netnsok = true,
};

//genl_info *info 由genl genl_rcv()时生成的
static int genl_test_cmd_process(struct sk_buff *skb, struct genl_info *info)
{
    struct sk_buff *msg;
    struct nlmsghdr *nlh;

    printk("%s: genl_info nlhdr: len:%u, pid%u, snd_portid:%u\n", "genl_test", info->nlhdr->nlmsg_len, info->nlhdr->nlmsg_pid, info->snd_portid);
    printk("%s: genl_info genlgdr: cmd:%u\n", "genl_test", info->genlhdr->cmd);
    //printk("%s: genl_info userhdr:%s\n", "genl_test", (char*)(info->userhdr));

    if(info->attrs[GENL_TEST_ATRR_TYPE]) {
        printk("%s: GENL_TEST_ATRR_TYPE data:%s\n", "genl_test", (char*)nla_data(info->attrs[GENL_TEST_ATRR_TYPE]));
    }
    msg = nlmsg_new(4096, GFP_KERNEL);
    if (!msg)
        return -ENOMEM;
    nlh = nlmsg_put(msg, 0, 0, NLMSG_DONE, strlen(msg_send), 0);
    strcpy(nlmsg_data(nlh), msg_send);  

    //use netlnik unicast发送消息到用户空间
    //return genlmsg_reply(msg, info);

    //multi fail, cann‘t find this genl sock, it is in first_device
    //genlmsg_multicast(&genl_test_fam, msg, 0, 0, 0);
    //nlmsg_multicast();
    genlmsg_multicast_allns(&genl_test_fam, msg, 0, 0, GFP_ATOMIC);
    return 6666;
}

static struct genl_ops genl_test_ops[] = {
    {
        .cmd = GENL_TEST_CMD,
        .doit = genl_test_cmd_process,
    },
};

static struct genl_multicast_group genl_test_group[] = {
    {.name = "genl_test_group",}
};

int __init genl_test_init(void)
{
    int err;

    //err = genl_register_family_with_ops(&genl_test_fam, genl_test_ops, ARRAY_SIZE(genl_test_ops));
    //in android M has change this api usage
    err = genl_register_family_with_ops_groups(&genl_test_fam, genl_test_ops, genl_test_group);
    if (err)
        goto err_out;
    printk("%s: genl_test_fam id:%u, mcgrp_offset:%u\n", "genl_test", genl_test_fam.id, genl_test_fam.mcgrp_offset);

    //err = genl_register_mc_group(&genl_test_fam, &genl_test_group);
    //if (err)
    //  goto err_out;
    //printk("%s: genl_test_group id:%u\n", "genl_test", genl_test_group.id);

    return 0;
err_out:
    genl_unregister_family(&genl_test_fam);
    return err;
}
module_init(genl_test_init);

void __exit genl_test_exit(void)
{
    genl_unregister_family(&genl_test_fam);
}
module_exit(genl_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cuijiyue");

6.2 用户空间

#include <.h>

#define MAX_PAYLOAD 1024  /* maximum payload size*/
#define GENL_TEST_CMD 6
#define GENL_TEST_ATRR_TYPE 66
char *attr_data = "66666";
struct nl_sock *nl_sk;

int main(int argc, char* argv[])
{
    int ret;
    int family_id, group_id;
    struct nl_msg *msg;
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    struct iovec iov;

    struct msghdr msg_rev;
    memset(&msg, 0, sizeof(msg));

    //创建sock并连接到kernel
    nl_sk = nl_socket_alloc();
    ret = genl_connect(nl_sk);
    printf("self pid:%u\n", getpid());
    if (ret != 0) {
        printf("genl_connect error%s\n", ret);
        goto err_out;
    }
    //获取自定义的family的id
    family_id = genl_ctrl_resolve(nl_sk, "genl_test");
    printf("genl_test family id:%d\n", ret);
    // multi group, get it from kernel log  group 9
    nl_socket_add_membership(nl_sk, 9);

    //生成msg
    msg = nlmsg_alloc();
    genlmsg_put(msg, 0, 0, family_id, 0, 0, GENL_TEST_CMD, 0);

    //放置attr
    nla_put(msg, GENL_TEST_ATRR_TYPE, strlen(attr_data), attr_data);

    //发送消息
    ret = nl_send_auto_complete(nl_sk, msg);
    printf("nl_send ret:%d\n", ret);

    //接收multi消息
    memset(&dest_addr, 0, sizeof(dest_addr));
    nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;
    msg_rev.msg_name = (void *)&dest_addr;
    msg_rev.msg_namelen = sizeof(dest_addr);
    msg_rev.msg_iov = &iov;
    msg_rev.msg_iovlen = 1;

    printf("waiting rev\n");
    recvmsg(nl_sk, &msg_rev, 0);
    printf(" Received message pid:%d, payload: %s\n", nlh->nlmsg_pid, NLMSG_DATA(nlh));
err_out:
    nl_socket_free(nl_sk);
    return ret;
}

7 wpa_supplicant与内核的通信

7.1 内核中nl80211模块

nl80211直接使用的是Generic Netlink接口,添加了一个name为“nl80211”的family和对应的 ops方法,同时注册了多个多播组

代码位置

kernel/net/wireless/nl80211.c

kernel/include/uapi/linux/nl80211.h

nl80211添加到genl中的family

static struct genl_family nl80211_fam = {
    .id = GENL_ID_GENERATE, /* don‘t bother with a hardcoded ID */
    .name = "nl80211",  /* have users key off the name instead */
    .hdrsize = 0,       /* no private header */
    .version = 1,       /* no particular meaning now */
    .maxattr = NL80211_ATTR_MAX,
    .netnsok = true,
    .pre_doit = nl80211_pre_doit,
    .post_doit = nl80211_post_doit,
};

static struct genl_ops nl80211_ops[]

是nl80211可以接受到的命令,每种命令都有相应的nl80211处理函数

wifi的scan,associate,connect等等,都是由此处理的

太多了,去看源码吧

7.2 wpa_supplicant中的genl socekt

wpa_s中与nl80211的通信时创建了两条socket,一条用于发送消息到内核,一条接收内核中的多播消息,

scoekt的初始化源码在wpa_driver_nl80211_init_nl_global()中

7.2.1 发送消息的socekt:

global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
global->nl = nl_create_handle(global->nl_cb, "nl");
在nl80211中,每接收到一条命令,都会通过socket发送回相应的处理结果
其中nl_cb可以自定义函数处理内核返回的消息。
在global->nl发送命令前,通过以下函数设置自定义的结果处理函数。
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data);

7.2.2接收内核多播消息的socekt:

global->nl_event = nl_create_handle(global->nl_cb, "event");
获取nl80211中各个多播组的group_id,并添加socekt到该多播组
ret = nl_get_multicast_id(global, "nl80211", "scan");
ret = nl_socket_add_membership(global->nl_event, ret);

下一篇 wpa_supplicant源码分析–扫描连接过程

时间: 2024-10-07 16:11:08

wpa_supplicant与内核nl80211通信之Generic Netlink的相关文章

linux 内核与用户空间通信之netlink使用方法

linux 内核与用户空间通信之netlink使用方法 1 引言 Linux中的进程间通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V IPC,后者则实现了基于socket的进程间通信机制.同时Linux也遵循IEEE制定的Posix IPC标准,在三者的基础之上实现了以下几种主要的IPC机制:管道(Pipe)及命名管道(Named Pipe),信号(Signal),

用户空间和内核空间通讯之【Netlink 上】

原文地址:用户空间和内核空间通讯之[Netlink 上] 作者:wjlkoorey258 引言 Alan Cox在内核1.3版本的开发阶段最先引入了Netlink,刚开始时Netlink是以字符驱动接口的方式提供内核与用户空间的双向数据通信:随后,在2.1内核开发过程中,Alexey Kuznetsov将Netlink改写成一个更加灵活.且易于扩展的基于消息通信接口,并将其应用到高级路由子系统的基础框架里.自那时起,Netlink就成了Linux内核子系统和用户态的应用程序通信的主要手段之一.

Generic netlink howto

1. Registering a family Registering a family is including four steps: define the family, define operations, register the family, register the operations. Define the family, is just creating an instance of genl_family struct. /* attributes */ enum { D

用户空间和内核空间通讯之【Netlink 中】

原文地址:用户空间和内核空间通讯之[Netlink 中] 作者:wjlkoorey258 今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的.我们依旧是在2.6.21的内核环境下进行开发. 在</usr/include/linux/netlink.h>文件里包含了Netlink协议簇已经定义好的一些预定义协议: 点击(此处)折叠或打开 #define NETLINK_ROUTE        0    /* Routing/device hook    

linux用户态和内核态通信之netlink机制【转】

本文转载自:http://blog.csdn.net/zcabcd123/article/details/8272360 这是一篇学习笔记,主要是对<Linux 系统内核空间与用户空间通信的实现与分析>中的源码imp2的分析.其中的源码,可以到以下URL下载: http://www-128.ibm.com/developerworks/cn/Linux/l-netlink/imp2.tar.gz [size=3]参考文档[/size] <linux 系统内核空间与用户空间通信的实现与分析

从socket can中断到netlink用户态内核态通信

1. Linux中的进程间的通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的System V IPC,后者则实现了基于socket的进程间通信机制.同时linux也遵循IEEE制定的posix IPC标准,在三者的基础上实现以下几种主要的IPC机制:管道(Pipe)和命名管道(Name Pipe),信号(Signal),消息队列(Message queue),共享内存(Shared

字符设备驱动ioctl实现用户层内核层通信

测试代码实现 memdev.h #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #include<linux/ioctl.h> #ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 0 #endif #ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 #endif #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif struct mem_de

linux内核---进程通信---消息队列

转自:https://blog.csdn.net/ljianhui/article/details/10287879 原文地址:https://www.cnblogs.com/qing1991/p/10182521.html

Netlink 内核实现分析(二):通信

在前一篇博文<Netlink 内核实现分析(一):创建>中已经较为具体的分析了Linux内核netlink子系统的初始化流程.内核netlink套接字的创建.应用层netlink套接字的创建和绑定流程,本文来具体的分析一下内核是怎样实现netlink消息在内核和应用进程之间全双工异步通信的. 一.netlink通信数据结构 1.netlink消息报头:struct nlmsghdr struct nlmsghdr { __u32 nlmsg_len; /* Length of message