【WiFi软件开发】Linux 802.11 栈(nl80211, cfg80211, mac80211)

花花不吃药 2024-09-07 09:07:02 阅读 95

文章目录

前言一、linux WiFi架构和80211栈二、nl802111.介绍2.消息接收3.支持的消息和函数集nl80211_ops[]

三、cfg802111.介绍2.代码分析

四、mac802111.介绍2.代码结构3.数据结构4.主要流程1.配置2.接收路径3.接收处理钩子(invoke_rx_handlers)4.发送路径5.发送处理钩子(invoke_tx_handlers)6.mangement/MLME7.IBSS8.创建接口路径9.删除接口路径10.创建station路径11.删除station路径12.扫描请求路径13.扫描状态机路径

参考阅读


前言

本文主要介绍linux内核中和WiFi密切相关的80211栈。80211栈包含nl80211,cfg80211和mac80211,主要用于WiFi配置和管理,本文将从功能和代码实现角度对各个模块进行解读。


一、linux WiFi架构和80211栈

首先给出Linux无线设备的软件架构图:

在这里插入图片描述

可以看出80211栈主要位于内核中的最上层,同时和用户空间中的应用、底层驱动、linux内核协议栈相关联,扮演了重要的承接角色。作为这样一个角色,80211栈需要解决一系列问题以实现WiFi管理和通信:

需要能以 “数据帧” 的形式发送、接收数据;需要数据帧加密功能。802.11 标准为此设计了认证机制框架(authentication mechanism framework),使用了 “管理帧” (management frames) 负责此工作 —— 保障从客户端STA 搜索 AP ,到建立实际连接的安全;需要提供碰撞管理(collision handling)机制,以便不同设备能在同一个信道上顺畅通信(在这一点上,所有的无线通信都一样)。该功能由 “控制帧” (control frames) 负责。802.11 借助使用通信时间窗口的碰撞规避(collision avoidance)来实现这一功能;需要能在运行时重新配置设备(比如修改频道);需要能向用户推送网络相关事件(比如因为距离过远而断连)。

下文将分别介绍80211栈中的nl80211,cfg80211和mac80211模块,并基于上述问题对各个模块的功能和代码实现进行解读。

二、nl80211

1.介绍

nl80211是介于用户空间与内核空间之间的 API ,可以算是 cfg80211 的前端,也会生成 “事件” (events) 信息。该模块依赖 netlink 协议来在两个空间进行信息交互,通过socket接收上层命令,执行对应函数进行配置管理网络接口。Netlink 是一个 Linux 中的 socket 类型,用于在内核与用户空间之间传递事件。

代码参见<code>linux-5.4.196/net/wireless/nl80211.c。nl80211的main函数主要做了以下几件事:

genl_register_family函数,注册nl80211_fam结构。初始化了struct genl_family nl80211_fam结构。主要字段有op字段,值为nl80211_ops。初始化了struct genl_ops nl80211_ops[],数组定义了命令和对应的钩子函数。上层通过Generic netlink套接字通信发送命令,nl80211中执行对应的函数。

2.消息接收

linux-5.4.196/net/netlink/genetlink.c中的函数genl_family_rcv_msg会接收上层消息,进行pre_doit,doit,post_doit等处理。

代码如下:

static int genl_family_rcv_msg(const struct genl_family *family,

struct sk_buff *skb,

struct nlmsghdr *nlh,

struct netlink_ext_ack *extack)

{

...

if (family->pre_doit) {

err = family->pre_doit(ops, skb, &info);

if (err)

goto out;

}

err = ops->doit(skb, &info);

if (family->post_doit)

family->post_doit(ops, skb, &info);

...

}

3.支持的消息和函数集nl80211_ops[]

nl80211通过genl_family_rcv_msg接收来自用户态的netlink信息,通过回调函数nl80211_pre_doit, 查找cfg80211注册时的设备信息dev。 通过调用函数cfg80211函数接口或者dev钩子函数实现信息的获取或参数的设置。

其中支持的消息在nl80211_commands中给出:

enum nl80211_commands {

/* don't change the order or add anything between, this is ABI! */

NL80211_CMD_UNSPEC,

NL80211_CMD_GET_WIPHY,/* can dump */

NL80211_CMD_SET_WIPHY,

NL80211_CMD_NEW_WIPHY,

NL80211_CMD_DEL_WIPHY,

...

}

消息和对应的处理函数在nl80211_ops[]中注册:

static const struct genl_ops nl80211_ops[] = {

{

.cmd = NL80211_CMD_GET_WIPHY,

.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,

.doit = nl80211_get_wiphy,

.dumpit = nl80211_dump_wiphy,

.done = nl80211_dump_wiphy_done,

/* can be retrieved by unprivileged users */

.internal_flags = NL80211_FLAG_NEED_WIPHY,

},

#if LINUX_VERSION_IS_GEQ(5,10,0)

};

static const struct genl_small_ops nl80211_small_ops[] = {

#endif

{

.cmd = NL80211_CMD_SET_WIPHY,

.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,

.doit = nl80211_set_wiphy,

.flags = GENL_UNS_ADMIN_PERM,

},

{

.cmd = NL80211_CMD_GET_INTERFACE,

.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,

.doit = nl80211_get_interface,

.dumpit = nl80211_dump_interface,

/* can be retrieved by unprivileged users */

.internal_flags = NL80211_FLAG_NEED_WDEV,

},

...

}

需要注意的是doitdumpit的区别:

doit:标准命令回调函数,在当前族中收到数据时触发调用,函数的第一个入参skb中保存了用户下发的消息内容;

dumpit:转储回调函数,当genl_ops的flag标志被添加了NLM_F_DUMP以后,每次收到genl消息即会回触发这个函数。

dumpitdoit的区别是:dumpit的第一个参数skb不会携带从客户端发来的数据。相反地,开发者应该在skb中填入需要传给客户端的数据, 然后,将skb的数据长度(可以用skb->len)return。skb中携带的数据会被自动送到客户端。只要dumpit的返回值大于 0,dumpit函数就会再次被调用,并被要求在skb中填入数据。当服务端没有数据要传给客户端时,dumpit要返回0。如果函数中出错,要求返回一 个负值。

三、cfg80211

1.介绍

cfg80211实现了wlan设备的注册,上可接收用户态配置管理命令,下可通过mac80211进行和硬件交互。因为实现了设备注册,虚拟接口的区分也是在此定义实现的(struct wiphy,一个 wiphy 设备可以有一个对应 MAC)。

代码位置在linux-5.4.196/net/wireless/core.c, 文件主要提供一些相关接口函数,实现设备在cfg80211中设备的注册,并在链表cfg80211_rdev_list 中添加设备信息。

2.代码分析

cfg80211在函数cfg80211_init(void)中初始化,主要做了一下几件事:

register_pernet_device(&cfg80211_pernet_ops);

注册网络设备操作,该函数是register_pernet_operations的包裹函数,其中first_device的定义如下:

static struct list_head *first_device = &pernet_list; 所有要添加到网络命名空间的网络协议模块都会添加到链表first_device中。当kernel支持网络命令空间时,register_pernet_operations的定义如下:

当支持多网络命令空间时,会调用该函数,该函数会遍历目前已存在的所有网络命名空间,将网络协议模块添加到每一个网络命令空间中;并执行init操作,在每一个网络命名空间中,执行协议初始化相关的东东(生成proc相关的文件或者为协议申请缓存等)。

wiphy_sysfs_init();

在/net/wireless/sysfs.c中,该文件的作用是:当cfg80211.ko驱动模块被安装后,在终端设备的sys/class/下会出现ieee80211文件夹。

register_netdevice_notifier(&cfg80211_netdev_notifier);

实现设备cfg80211中的初始化、注册cfg80211以及相关参数的设置

nl80211_init();

nl80211初始化函数

regulatory_init();

地区码初始化

四、mac80211

1.介绍

这是最底层的模块,与 hardware offloading 关联最多。如果某些功能无法由设备硬件实现,那么就可以以纯软的方式实现在这里。另外,以软件形式实现也可以赋予开发者对逻辑有更大的控制权。其也被称为 “Soft MAC” 模块,与 “Hard MAC” (由设备固件完成所有工作)相对。实际场景中,通常是这两种方案混合使用。802.11 协议状态机就在这里,需要处理所有类型的帧。

2.代码结构

ieee80211_init

rc80211_minstrel_init //注册速率控制ops

ieee80211_iface_init //注册设备接口回调函数,用于更改相应结构体设备名称

ieee80211_i.h(主要数据结构)main.c(主函数入口) backports-5.15.81-1/net/mac80211/main.ciface.c(虚拟接口处理)key.c,key.h(密钥管理)sta_info.c,sta_info.h(用户管理)pm.c(功率管理)rate.c,rate.h(速率控制函数)rc80211*(速率控制算法)rx.c(帧接收路径代码)tx.c(帧发送路径代码)scan.c(软件扫描代码)mlme.c(station/managed模式MLME)ibss.c(IBSS MLME)cfg.c,cfg.h,wext.c(配置入口代码)aes*,tkip*,wep*,michael*,wpa*(WPA/RSN/WEP代码)wme.c,wme.h(QoS代码)util.c(公共函数)

3.数据结构

ieee80211_local/ieee80211_hw`

每个数据结构代表一个无线设备(ieee80211_hw嵌入到ieee80211_local)

ieee80211_hw是ieee80211_local在驱动中的可见部分

包含无线设备的所有操作信息sta_info/ieee80211_sta

代表每一个station

可能是mesh,IBSS,AP,WDS

ieee80211_sta是驱动可见部分ieee80211_conf

硬件配置

当前信道是最重要的字段

硬件特殊参数ieee80211_bss_conf

BSS配置

多BSSes类型(IBSS/AP/managed)

包含比如基础速率位图

per BSS parameters in case hardware supports creating/associating with multiple BSSesieee80211_key/ieee80211_key_conf

代表加密/解密密钥

ieee80211_key_conf提供给驱动用于硬件加速

ieee80211_key包含book-keeping和软件解密状态ieee80211_tx_info

大部分复杂数据结构

skb内部控制缓冲区(cb)

经历三个阶段:1、由mac80211初始化;2、由驱动使用;3、由发送状态通告使用ieee80211_rx_status

包含接收帧状态

驱动通过接收帧传给mac80211ieee80211_sub_if_data/ieee80211_vif

包含每个虚拟接口信息

ieee80211_vif is passed to driver for those virtual interfaces the driver knows about (no monitor,VLAN)

包含的sub-structures取决于模式

4.主要流程

1.配置

所有发起来自用户空间(wext或者nl80211)managed和IBSS模式:触发状态机(基于workqueue)有些操作或多或少直接通过驱动传递(比如信道设置)

2.接收路径

通过函数ieee80211_rx()接收帧调用ieee80211_rx_monitor()拷贝帧传递给所有监听接口调用invoke_rx_handlers()处理帧如果是数据帧,转换成802.3帧格式,传递给上层协议栈如果是管理帧/控制帧,传递给MLME

3.接收处理钩子(invoke_rx_handlers)

ieee80211_rx_h_passive_scanieee80211_rx_h_checkieee80211_rx_h_decryptieee80211_rx_h_check_more_dataieee80211_rx_h_sta_processieee80211_rx_h_defragmentieee80211_rx_h_ps_pollieee80211_rx_h_michael_mic_verifyieee80211_rx_h_remove_qos_controlieee80211_rx_h_amsduieee80211_rx_h_mesh_fwdingieee80211_rx_h_dataieee80211_rx_h_ctrlieee80211_rx_h_actionieee80211_rx_h_mgmt

4.发送路径

帧传递给ieee80211_subif_start_xmit()把帧转换成802.11格式,丢弃发给未认证工作站的单播包,除了来自本地的EAPOL帧如果是MONITOR接口,在帧头部增加radiotap信息调用invoke_tx_handlers()处理帧调用drv_tx(),把帧传递给驱动

5.发送处理钩子(invoke_tx_handlers)

ieee80211_tx_h_dynamic_psieee80211_tx_h_check_associeee80211_tx_h_ps_bufieee80211_tx_h_select_keyieee80211_tx_h_staieee80211_tx_h_rate_ctrlieee80211_tx_h_michael_mic_addieee80211_tx_h_sequenceieee80211_tx_h_fragmentieee80211_tx_h_statsieee80211_tx_h_encryptieee80211_tx_h_calculate_duration

6.mangement/MLME

状态机运行依赖于用户请求标准方法如下:

probe request/response

auth request/response

assoc request/response

notification request/response

7.IBSS

尝试寻找IBSS加入IBSS或者创建IBSS如果没有配对,则周期性地尝试寻找IBSS并加入

8.创建接口路径

创建接口由用户空间通过nl80211发起分配网络设备空间(包含sdata对象空间)初始化网络设备初始化sdata对象(包括设备类型,接口类型,设备操作函数等等)注册网络设备把sdata对象加入local->interfaces

9.删除接口路径

删除接口由用户空间通过nl80211发起把sdata对象从local->interfaces移除移除网络设备

10.创建station路径

创建station由用户空间通过nl80211发起

分配sta_info对象空间

初始化sta_info对象(包括侦听间隔,支持速率集等等)

初始化sta_info对象的速率控制对象

把sta_info对象加入local->sta_pending_list

调用local->ops->sta_add通知驱动创建station

把sta_info对象加入local->sta_list

11.删除station路径

删除station由用户空间通过nl80211发起删除sta_info对象的key对象把sta_info对象从local->sta_pending_list移除调用local->ops->sta_remove通知驱动移除station删除sta_info对象的速率控制对象把sta_info对象从local->sta_list移除

12.扫描请求路径

扫描请求由用户空间通过nl80211发起如果支持硬件扫描,调用local->ops->hw_scan()执行硬件扫描否则,调用ieee80211_start_sw_scan()执行软件扫描延时唤醒ieee80211_scan_work()

13.扫描状态机路径

如果存在硬件扫描请求,调用drv_hw_scan()进行扫描,如果失败,调用ieee80211_scan_completed()完成扫描如果存在扫描请求,同时未进行扫描,调用__ieee80211_start_scan()进行软件扫描,如果失败,调用ieee80211_scan_completed()完成扫描根据next_scan_state调用相应的处理函数如果next_delay==0,则继续根据next_scan_state调用相应的处理函数延时唤醒ieee80211_scan_work()


参考阅读

http://blog.chinaunix.net/uid-22510743-id-5780801.html

https://www.cnblogs.com/ink-white/p/16822559.html

https://zhuanlan.zhihu.com/p/650693108

https://blog.csdn.net/zwl1584671413/article/details/114902310

https://blog.csdn.net/zxygww/article/details/24874155



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。