任务
- 下面我们将用应用层下发 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 帧,并通过硬件发送,我们这里对扫描协议进行详细的解释
c
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_OUI
struct rtw_wdev_priv *pwdev_priv = adapter_wdev_data(padapter);
#endif
#ifdef CONFIG_PROBEREQ_ADD_HT_VHT_HE
u8 *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 Request
if (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(主动扫描)的随机MAC
if ((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
#endif
mac = adapter_mac_addr(padapter);// 默认使用适配器自身MAC
fctrl = &(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_DEBUG
RTW_DBG("%s pno_scan_seq_num: %d\n", __func__,
pwdev_priv->pno_scan_seq_num);
#endif
SetSeqNum(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(如用户输入的网络名称),用于探测特定网络
else
pframe = 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));
} else
pframe = 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_MESH
if (MLME_IS_MESH(padapter)) {
if (pssid)
pframe = rtw_set_ie_mesh_id(pframe, &pattrib->pktlen, pssid->Ssid, pssid->SsidLength);
else
pframe = rtw_set_ie_mesh_id(pframe, &pattrib->pktlen, NULL, 0);
}
#endif
if (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);
#endif
pattrib->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_DIAG
ctcwifi_diag_log(padapter, pwlanhdr->addr1/*da*/, 1, "Probe Request", (unsigned char *)pwlanhdr, pattrib->pktlen);
#endif
exit:
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能够抛砖引玉,让读者能够自行探索有趣的无线网络驱动,并加入其中的开发应用
纸上得来终觉浅,绝知此事要躬行!
