代码位置:

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.

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

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

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

name: 一个字符串,当内核模块和用户空间采用同样的name,才可以建立通信

ops_list:该family的操纵函数

mcast_groups:多播组链表,可以往该family中再注册多播组

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

官方文档 外链网址已屏蔽

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

4.1 生成socket

#include

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源码分析–扫描连接过程

linux4.4 内核 netlink,wpa_supplicant与内核nl80211通信之Generic Netlink相关推荐

  1. Android wpa_supplicant源码分析---nl80211内核通信Generic Netlink

    代码位置: kernel/net/netlink/genetlink.c kernel/include/net/genetlink.h GENL简介 netlink仅支持32种协议类型,这在实际应用中 ...

  2. linux generic netlink实现机制:注册、创建

    目录 1 Generic Netlink 概述 2 Generic Netlink相关结构体 2.1 Generic Netlink消息头结构:struct genlmsghdr 2.2 Generi ...

  3. Generic Netlink机制

    文章目录 数据结构 genl_family genl_ops 多播组: genl_multicast_group Generic Netlink框架 Generic Netlink消息 消息头部 消息 ...

  4. Generic Netlink内核实现分析(一):初始化

    Generic Netlink 是内核专门为了扩展netlink协议簇而设计的"通用netlink协议簇".由于netlink协议最多支持32个协议簇,目前Linux4.1的内核中 ...

  5. linux内核工程导论,Linux内核工程导论–网络:TCP:netlink与tcp_diag编程

    概览 http://m.oschina.net/blog/351007有一个示例程序,但是它用的v1的接口. http://kristrev.github.io/2013/07/26/passive- ...

  6. netlink怎么读_如何用netlink接口读取内核路由表

    展开全部 获取内核路由62616964757a686964616fe58685e5aeb931333335343937表以及操作内核路由表有几种方法:读proc 或者用ioctl(sock_fd, S ...

  7. 使用netlink机制在内核与应用程序之间通信

    使用netlink机制在内核与应用程序之间通信 前一段时间,在开发一个驱动程序的过程中,需要在驱动程序与应用程序之间进行通信.其中驱动程序在接收到一个硬件中断之后通知应用程序进行相应的处理.为 解决此 ...

  8. linux内核报告,Linux升级内核报告.docx

    Linux升级内核报告精要 我的Linux 内核升级记录 准备工作内核安装包的下载下载地址为: HYPERLINK "/pub/linux/kernel/" /pub/linux/ ...

  9. linux 内核网络协议栈--linux内核路由机制(一)

    内核的路由部分是是网络中重要部分,目前在Linux内核中默认的路由查找算法使用的是Hash查找,所以你会看到很多的数据结构是XXX_hash什么之类(例如fn_hash).Linux内核从2.1开始就 ...

最新文章

  1. 36招搞定电脑一切难题
  2. MultiByteToWideChar和WideCharToMultiByte
  3. zabbix源码安装 令人窒息的操作
  4. 专访 | PP云技术副总监:如何使用机器学习算法优化分发链路
  5. c#求三角形面积周长公式_此题求三角形的面积,多数学生完全没思路,解题关键是用该知识点...
  6. 身份证号码有效性检测算法 ( js版 转 C#版 )
  7. java中如何调用属性_java – 如何从属性文件导入值并在注释中使用它?
  8. 1024 许个愿吧,万一实现了呢?
  9. 安卓学习笔记35:广播接收者
  10. 第13章 线程安全与锁优化
  11. 就数据平台建设,80%的500强企业都有一个共性
  12. mysql单机在线迁移_MySQL 不停服务 在线进行100亿数据迁移切换
  13. [R语言绘图]条状图barplot
  14. java条件触发,触发器触发条件是什么?更改数据时实现方法是什么?
  15. linux 彻底删除oracle,Linux下完美卸载Oracle
  16. 最后剩下的,只有随遇而安,偶尔我会想起你:伤感日志
  17. 安卓仿微信录音功能实现
  18. 计算机桌面有去不掉的框,电脑右下角有个白色方框去不掉
  19. 电容介绍|电容的种类和作用
  20. 110配线架打法图解_配线架打线的方法以及110配线架的按照流程

热门文章

  1. 剩余空间,自由再生——城市高架桥下空间的活化再生研究
  2. CVPR 2021 | Involution:超越卷积和自注意力的神经网络新算子
  3. 数字图像处理2021.9.27—空域方法-滤波处理Filter subimage
  4. docker与宿主机共享内存通信
  5. 医学图像分割方法及卷积神经网络在医学图像分割上的应用
  6. 笑能降血压,笑还能释放压力,减轻沮丧感;笑可以刺激人体分泌多巴胺,使人产生欣快感
  7. R语言 自定义函数之趣味程序--老虎机
  8. 大二第二次月赛--买水果
  9. 腾讯、新浪、淘宝、搜狐的IP库接口,根据IP显示当地的天气功能
  10. Fabric ca学习笔记