Linux Wlan 无线网络驱动开发-scan协议全流程详解
任务
- 下面我们将用应用层下发 iwlist wlan1 scan 来进行扫描协议(管理帧)的学习
扫描协议标准(有点枯燥)
如下是翻译
实战内容分为三个内容流程
- 无线驱动注册相关流程
- iwlist工具调用扫描发送 probe req流程
- probe req发出后,接收到probe rsp后的列表返回
三种类型包明确
扫描类型
802.11 定义了两种扫描方式
-
主动扫描(Active Scanning)
设备发送 Probe Request 帧(探测请求),附近的 AP(接入点)收到后回复 Probe Response(探测响应)
-
被动扫描(Passive Scanning)
设备仅监听 AP 定期广播的 Beacon 帧(信标帧),不主动发送请求
那我们现在只考虑主动扫描(Active Scanning)的情况
无线wifi的扫描在不同操作系统的分布
在 Linux 和 FreeBSD 操作系统中,Wi-Fi 扫描功能 的实现归属于不同的子系统,但都遵循 IEEE 802.11 协议
- Linux 系统中的 Wi-Fi 扫描 归属组件:cfg80211 + mac80211
- FreeBSD 系统中的 Wi-Fi 扫描 归属组件:net80211
无线wifi的扫描也算无线协议的一部分吗?
- 对,wifi扫描(Scanning)是 IEEE 802.11无线协议的核心功能之一,属于无线网络通信的标准流程。它的主要作用是帮助无线设备(如手机、笔记本电脑)发现周围的可用网络,并获取关键参数(如信道、信号强度、安全方式等),以便后续的连接或优化
基础概念
- 驱动路径:linux-5.10.x/drivers/net/wireless
- 驱动包含文件夹
core:驱动核心实现,实现802.11帧构造/解析、加密算法、硬件抽象接口等等
include:头文件集合,包含公共API、硬件寄存器、协议定义等等
os_dep:操作系统适配层,包含Linux设备注册(register_netdev)、ioctl命令转换等等
phl:协议硬件抽象层(PHY & MAC High Layer)等等
platform:平台相关代码包含SoC集成、电源管理、外设控制:SDIO/USB接口等等
profiling:性能分析工具
script: 构建与自动化脚本
调用链
前面已经有了基础的观念框架,下面我们就开始实战分析
协议分析(包含驱动注册和初始化流程,但是只关注wifi扫描协议相关流程)
驱动和数据包处理函数注册
- 初始化 PCIe 无线网卡驱动,注册必要的内核接口和资源
- 设备探测
- 包含硬件初始化和数据包处理
- 核心接收数据包处理函数 rtw_core_rx_process
- 对管理帧进行处理
- 管理帧(Management Frame)分发器函数 mgt_dispatcher,主要用于处理接收到的 Wi-Fi 管理帧
iwlist wlan1 scan 命令下发 ioctl 流程
- 为设备对象分配并注册 OS 层网络设备及相关结构
- cfg80211 资源分配
- 默认使用驱动常规的cfg80211操作集
- 注册的ioctl扫描函数
- 扫描操作回调函数集合,调用到驱动扫描
- 构造并发送 Probe Request(探测请求帧)
- 发送帧
_issue_probereq 的核心作用是根据扫描需求构造符合 802.11 规范的 Probe Request 帧,并通过硬件发送,我们这里对扫描协议进行详细的解释
int _issue_probereq(_adapter *padapter, const NDIS_802_11_SSID *pssid, const u8 *da, u8 ch, bool append_wps, int wait_ack)//构造并发送 Probe Request 帧
{int ret = _FAIL;struct xmit_frame *pmgntframe;// 管理帧发送结构体(存储帧数据和属性struct pkt_attrib *pattrib;// 帧属性(长度、序列号、速率等)unsigned char *pframe;// 帧数据缓冲区指针struct rtw_ieee80211_hdr *pwlanhdr;// 802.11帧头结构体unsigned short *fctrl;unsigned char *mac;unsigned char bssrate[NumRates];struct xmit_priv *pxmitpriv = &(padapter->xmitpriv);struct mlme_priv *pmlmepriv = &(padapter->mlmepriv);struct mlme_ext_priv *pmlmeext = &(padapter->mlmeextpriv);int bssrate_len = 0;u8 bc_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
#ifdef CONFIG_RTW_CFGVENDOR_RANDOM_MAC_OUIstruct rtw_wdev_priv *pwdev_priv = adapter_wdev_data(padapter);
#endif#ifdef CONFIG_PROBEREQ_ADD_HT_VHT_HEu8 *p;_adapter *pri_adapter = GET_PRIMARY_ADAPTER(padapter);struct mlme_ext_priv *pri_pmlmeext = &(pri_adapter->mlmeextpriv);struct mlme_ext_info *pri_pmlmeinfo = &(pri_pmlmeext->mlmext_info);WLAN_BSSID_EX *pri_cur_network = &(pri_pmlmeinfo->network);u8 *ie = pri_cur_network->IEs;uint ie_len = 0;
#endif// 检查射频是否被信道等待阻塞(如DFS信道检测中,需等待完成才能发送)if (rtw_rfctl_is_tx_blocked_by_ch_waiting(adapter_to_rfctl(padapter)))//检查射频状态----如果射频处于信道等待状态(如 DFS 信道检测),则直接退出goto exit;pmgntframe = alloc_mgtxmitframe(pxmitpriv);//分配管理帧内存----分配一个管理帧(mgntframe)缓冲区,用于构造 Probe Requestif (pmgntframe == NULL)goto exit;/* update attribute */pattrib = &pmgntframe->attrib;// 获取帧属性结构体(存储长度、序列号等)if (update_mgntframe_attrib(padapter, pattrib) != _SUCCESS) {// 更新管理帧属性(如设置发送速率、优先级等)rtw_free_xmitframe(&padapter->xmitpriv, pmgntframe);goto exit;}_rtw_memset(pmgntframe->buf_addr, 0, WLANHDR_OFFSET + TXDESC_OFFSET);// 初始化帧缓冲区(帧头+数据区域)pframe = (u8 *)(pmgntframe->buf_addr) + TXDESC_OFFSET;// 定位帧数据起始位置(跳过硬件描述符区域)pwlanhdr = (struct rtw_ieee80211_hdr *)pframe;// 802.11帧头指针// 确定源MAC地址(正常使用适配器MAC,特殊场景下使用随机MAC)
#ifdef CONFIG_RTW_CFGVENDOR_RANDOM_MAC_OUI// 如果启用随机MAC且处于未关联状态,使用PNO(主动扫描)的随机MACif ((pwdev_priv->pno_mac_addr[0] != 0xFF)&& (MLME_IS_STA(padapter))&& (check_fwstate(&padapter->mlmepriv, WIFI_ASOC_STATE) == _FALSE))mac = pwdev_priv->pno_mac_addr;else
#endifmac = adapter_mac_addr(padapter);// 默认使用适配器自身MACfctrl = &(pwlanhdr->frame_ctl);*(fctrl) = 0;/*11 管理帧通常使用 “3 地址格式”:addr1(DA,目的地址)、addr2(SA,源地址)、addr3(BSSID,基本服务集标识)*/// 设置目的地址(DA)和BSSID(A3)if (da) {/* unicast probe request frame */_rtw_memcpy(pwlanhdr->addr1, da, ETH_ALEN);// DA = 目标AP的MAC_rtw_memcpy(pwlanhdr->addr3, da, ETH_ALEN);// BSSID = 目标AP的MAC} else {/* broadcast probe request frame */_rtw_memcpy(pwlanhdr->addr1, bc_addr, ETH_ALEN);// DA = 广播地址_rtw_memcpy(pwlanhdr->addr3, bc_addr, ETH_ALEN);// BSSID = 广播地址}_rtw_memcpy(pwlanhdr->addr2, mac, ETH_ALEN);#ifdef CONFIG_RTW_CFGVENDOR_RANDOM_MAC_OUI// 设置序列号(避免重复帧,确保AP正确处理)if ((pwdev_priv->pno_mac_addr[0] != 0xFF)&& (MLME_IS_STA(padapter))&& (check_fwstate(&padapter->mlmepriv, WIFI_ASOC_STATE) == _FALSE)) {
#ifdef CONFIG_RTW_DEBUGRTW_DBG("%s pno_scan_seq_num: %d\n", __func__,pwdev_priv->pno_scan_seq_num);
#endifSetSeqNum(pwlanhdr, pwdev_priv->pno_scan_seq_num);pattrib->seqnum = pwdev_priv->pno_scan_seq_num;pattrib->qos_en = 1;pwdev_priv->pno_scan_seq_num++;// 序列号自增} else
#endif{SetSeqNum(pwlanhdr, pmlmeext->mgnt_seq);// 使用MLME层的管理帧序列号pmlmeext->mgnt_seq++;// 序列号自增}set_frame_sub_type(pframe, WIFI_PROBEREQ);//将帧类型设为 Probe Request(子类型 0x04)printk("---------将帧类型设为 Probe Request(子类型 0x04)[%d][%s]------\n",__LINE__,__FUNCTION__);pframe += sizeof(struct rtw_ieee80211_hdr_3addr);pattrib->pktlen = sizeof(struct rtw_ieee80211_hdr_3addr);if (pssid && !MLME_IS_MESH(padapter))//SSID IE(标识探测的网络名称),SSID IE 是核心 IE:携带目标网络名称(SSID)时,只有对应 AP 会回应;不携带时,所有 AP 都会回应pframe = rtw_set_ie(pframe, _SSID_IE_, pssid->SsidLength, pssid->Ssid, &(pattrib->pktlen)); // 携带指定SSID(如用户输入的网络名称),用于探测特定网络elsepframe = rtw_set_ie(pframe, _SSID_IE_, 0, NULL, &(pattrib->pktlen));// 不携带SSID(长度0),用于探测所有可用网络(AP会回应自身SSID)get_rate_set(padapter, bssrate, &bssrate_len);// 获取适配器支持的速率集/*AP 需要知道 STA 支持的速率,才能选择合适的速率回应(Probe Response)*/if (bssrate_len > 8) {// 支持速率超过8个时,分“支持速率IE”和“扩展支持速率IE”发送(802.11规范限制)pframe = rtw_set_ie(pframe, _SUPPORTEDRATES_IE_ , 8, bssrate, &(pattrib->pktlen));pframe = rtw_set_ie(pframe, _EXT_SUPPORTEDRATES_IE_ , (bssrate_len - 8), (bssrate + 8), &(pattrib->pktlen));} elsepframe = rtw_set_ie(pframe, _SUPPORTEDRATES_IE_ , bssrate_len , bssrate, &(pattrib->pktlen));if (ch) // 若指定了信道(如当前扫描的信道),添加DS设置IE,_DSSET_IE_(DS Parameter Set IE)携带当前信道号,AP 可确认 STA 在正确信道上pframe = rtw_set_ie(pframe, _DSSET_IE_, 1, &ch, &pattrib->pktlen);#ifdef CONFIG_RTW_MESHif (MLME_IS_MESH(padapter)) {if (pssid)pframe = rtw_set_ie_mesh_id(pframe, &pattrib->pktlen, pssid->Ssid, pssid->SsidLength);elsepframe = rtw_set_ie_mesh_id(pframe, &pattrib->pktlen, NULL, 0);}
#endifif (append_wps) {/* add wps_ie for wps2.0 */if (pmlmepriv->wps_probe_req_ie_len > 0 && pmlmepriv->wps_probe_req_ie) {_rtw_memcpy(pframe, pmlmepriv->wps_probe_req_ie, pmlmepriv->wps_probe_req_ie_len);pframe += pmlmepriv->wps_probe_req_ie_len;pattrib->pktlen += pmlmepriv->wps_probe_req_ie_len;/* pmlmepriv->wps_probe_req_ie_len = 0 ; */ /* reset to zero */}}#ifdef CONFIG_PROBEREQ_ADD_HT_VHT_HE//条件编译/* Parsing HT CAP IE */// 从主适配器获取当前网络的IE信息,添加到探测帧中p = rtw_get_ie(ie + _BEACON_IE_OFFSET_, EID_HTCapability, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, _HT_CAPABILITY_IE_, ie_len, p+2, &(pattrib->pktlen));}/* Parsing HT INFO IE */// 从主适配器获取当前网络的IE信息,添加到探测帧中p = rtw_get_ie(ie + _BEACON_IE_OFFSET_, EID_HTInfo, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, _HT_ADD_INFO_IE_, ie_len, p+2, &(pattrib->pktlen));}/* Parsing VHT CAP IE */p = rtw_get_ie(ie + _BEACON_IE_OFFSET_, EID_VHTCapability, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, EID_VHTCapability, ie_len, p+2, &(pattrib->pktlen));}/* Parsing VHT OP IE */p = rtw_get_ie(ie + _BEACON_IE_OFFSET_, EID_VHTOperation, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, EID_VHTOperation, ie_len, p+2, &(pattrib->pktlen));}/* Parsing HE CAP&OP IE */p = rtw_get_ie(ie + _BEACON_IE_OFFSET_, _HE_CAPABILITY_IE_, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, _HE_CAPABILITY_IE_, ie_len, p+2, &(pattrib->pktlen));p += (ie_len+2);p = rtw_get_ie(p, _HE_CAPABILITY_IE_, &ie_len, (pri_cur_network->IELength - _BEACON_IE_OFFSET_ - (ie_len + 2)));if (p && ie_len > 0) {pframe = rtw_set_ie(pframe, _HE_CAPABILITY_IE_, ie_len, p+2, &(pattrib->pktlen));}}
#endif#ifdef CONFIG_APPEND_VENDOR_IE_ENABLE// 添加厂商自定义IE(如驱动特定的扩展信息)pattrib->pktlen += rtw_build_vendor_ie(padapter , &pframe , WIFI_PROBEREQ_VENDOR_IE_BIT);
#endifpattrib->last_txcmdsz = pattrib->pktlen;if (wait_ack){ret = dump_mgntframe_and_wait_ack(padapter, pmgntframe);}else {dump_mgntframe(padapter, pmgntframe);// 发送管理帧的核心函数,主要功能是将构造好的管理帧(如 Probe Request、Beacon、Association Request 等)提交到硬件队列并触发发送ret = _SUCCESS;}#ifdef CTC_WIFI_DIAGctcwifi_diag_log(padapter, pwlanhdr->addr1/*da*/, 1, "Probe Request", (unsigned char *)pwlanhdr, pattrib->pktlen);
#endifexit:return ret;
}
ioctl 下发probe req后接收 probe rsp包流程
- 在MLME STA模式下的处理函数表中,已经注册了对probe rsp的接收函数
- 将扫描到的无线网络(BSS)信息封装为 “扫描事件(Survey Event)” 并向上层报告
- 从信标帧(Beacon)和探测请求 / 响应帧(Probe request/response frames)中收集基本服务集(BSS)信息
- 将网络信息添加到扫描结果列表
- 负责维护扫描到的网络列表并处理网络信息更新
- 当新扫描到一个无线网络(target)时,检查该网络是否已存在于队列中;若存在则更新其信息(如信号强度、IE
元素),若不存在则添加到队列
- 更新网络信息(信号、IE、时间戳等)到队列
- ioctl转化模块扫描队列结果,当用户执行 iwlist wlan1 scan 等命令时,该函数会将驱动内部维护的扫描网络队列(scanned_queue)中的信息转换为用户空间可识别的格式,最终展示为用户可见的 WiFi 列表
- 获取当前遍历的网络,将网络信息转换为用户空间格式(如iwlist可解析的字符串)
- 结果显示
希望这个实例demo能够抛砖引玉,让读者能够自行探索有趣的无线网络驱动,并加入其中的开发应用