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

打印 上一主题 下一主题

主题 517|帖子 517|积分 1551


媒介

本文告急先容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 类型,用于在内核与用户空间之间通报事件。
代码参见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等处理。
代码如下:
  1. static int genl_family_rcv_msg(const struct genl_family *family,
  2.                                struct sk_buff *skb,
  3.                                struct nlmsghdr *nlh,
  4.                                struct netlink_ext_ack *extack)
  5. {
  6.     ...
  7.         if (family->pre_doit) {
  8.                 err = family->pre_doit(ops, skb, &info);
  9.                 if (err)
  10.                         goto out;
  11.         }
  12.         err = ops->doit(skb, &info);
  13.         if (family->post_doit)
  14.                 family->post_doit(ops, skb, &info);
  15.     ...
  16. }
复制代码
3.支持的消息和函数集nl80211_ops[]

nl80211通过genl_family_rcv_msg吸收来自用户态的netlink信息,通过回调函数nl80211_pre_doit, 查找cfg80211注册时的装备信息dev。 通过调用函数cfg80211函数接口大概dev钩子函数实现信息的获取或参数的设置。
此中支持的消息在nl80211_commands中给出:
  1. enum nl80211_commands {
  2. /* don't change the order or add anything between, this is ABI! */
  3.         NL80211_CMD_UNSPEC,
  4.         NL80211_CMD_GET_WIPHY,                /* can dump */
  5.         NL80211_CMD_SET_WIPHY,
  6.         NL80211_CMD_NEW_WIPHY,
  7.         NL80211_CMD_DEL_WIPHY,
  8.         ...
  9. }
复制代码
消息和对应的处理函数在nl80211_ops[]中注册:
  1. static const struct genl_ops nl80211_ops[] = {
  2.         {
  3.                 .cmd = NL80211_CMD_GET_WIPHY,
  4.                 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
  5.                 .doit = nl80211_get_wiphy,
  6.                 .dumpit = nl80211_dump_wiphy,
  7.                 .done = nl80211_dump_wiphy_done,
  8.                 /* can be retrieved by unprivileged users */
  9.                 .internal_flags = NL80211_FLAG_NEED_WIPHY,
  10.         },
  11. #if LINUX_VERSION_IS_GEQ(5,10,0)
  12. };
  13. static const struct genl_small_ops nl80211_small_ops[] = {
  14. #endif
  15.         {
  16.                 .cmd = NL80211_CMD_SET_WIPHY,
  17.                 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
  18.                 .doit = nl80211_set_wiphy,
  19.                 .flags = GENL_UNS_ADMIN_PERM,
  20.         },
  21.         {
  22.                 .cmd = NL80211_CMD_GET_INTERFACE,
  23.                 .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
  24.                 .doit = nl80211_get_interface,
  25.                 .dumpit = nl80211_dump_interface,
  26.                 /* can be retrieved by unprivileged users */
  27.                 .internal_flags = NL80211_FLAG_NEED_WDEV,
  28.         },
  29.         ...
  30. }
复制代码
需要注意的是doit和dumpit的区别:
doit:标准下令回调函数,在当前族中收到数据时触发调用,函数的第一个入参skb中生存了用户下发的消息内容;
dumpit:转储回调函数,当genl_ops的flag标记被添加了NLM_F_DUMP以后,每次收到genl消息即会回触发这个函数。
dumpit与doit的区别是: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_i.h(告急数据结构)
  • main.c(主函数入口)
  • iface.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 BSSes
  • ieee80211_key/ieee80211_key_conf
    代表加密/解密密钥
    ieee80211_key_conf提供给驱动用于硬件加快
    ieee80211_key包含book-keeping和软件解密状态
  • ieee80211_tx_info
    大部分复杂数据结构
    skb内部控制缓冲区(cb)
    经历三个阶段:1、由mac80211初始化;2、由驱动使用;3、由发送状态告示使用
  • ieee80211_rx_status
    包含吸收帧状态
    驱动通过吸收帧传给mac80211
  • ieee80211_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_scan
  • ieee80211_rx_h_check
  • ieee80211_rx_h_decrypt
  • ieee80211_rx_h_check_more_data
  • ieee80211_rx_h_sta_process
  • ieee80211_rx_h_defragment
  • ieee80211_rx_h_ps_poll
  • ieee80211_rx_h_michael_mic_verify
  • ieee80211_rx_h_remove_qos_control
  • ieee80211_rx_h_amsdu
  • ieee80211_rx_h_mesh_fwding
  • ieee80211_rx_h_data
  • ieee80211_rx_h_ctrl
  • ieee80211_rx_h_action
  • ieee80211_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_ps
  • ieee80211_tx_h_check_assoc
  • ieee80211_tx_h_ps_buf
  • ieee80211_tx_h_select_key
  • ieee80211_tx_h_sta
  • ieee80211_tx_h_rate_ctrl
  • ieee80211_tx_h_michael_mic_add
  • ieee80211_tx_h_sequence
  • ieee80211_tx_h_fragment
  • ieee80211_tx_h_stats
  • ieee80211_tx_h_encrypt
  • ieee80211_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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表