Linux Wlan 无线网络驱动开发-scan协议全流程详解

任务

  • 下面我们将用应用层下发 iwlist wlan1 scan 来进行扫描协议(管理帧)的学习

扫描协议标准(有点枯燥)


如下是翻译

实战内容分为三个内容流程

  1. 无线驱动注册相关流程
  2. iwlist工具调用扫描发送 probe req流程
  3. 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扫描协议相关流程)

驱动和数据包处理函数注册

  1. 初始化 PCIe 无线网卡驱动,注册必要的内核接口和资源


  2. 设备探测

  3. 包含硬件初始化和数据包处理

  4. 核心接收数据包处理函数 rtw_core_rx_process
  5. 对管理帧进行处理

  6. 管理帧(Management Frame)分发器函数 mgt_dispatcher,主要用于处理接收到的 Wi-Fi 管理帧

iwlist wlan1 scan 命令下发 ioctl 流程

  1. 为设备对象分配并注册 OS 层网络设备及相关结构
  2. cfg80211 资源分配

  3. 默认使用驱动常规的cfg80211操作集
  4. 注册的ioctl扫描函数



  5. 扫描操作回调函数集合,调用到驱动扫描


  6. 构造并发送 Probe Request(探测请求帧)
  7. 发送帧

_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包流程

  1. 在MLME STA模式下的处理函数表中,已经注册了对probe rsp的接收函数


  2. 将扫描到的无线网络(BSS)信息封装为 "扫描事件(Survey Event)" 并向上层报告
  3. 从信标帧(Beacon)和探测请求 / 响应帧(Probe request/response frames)中收集基本服务集(BSS)信息


  4. 将网络信息添加到扫描结果列表

  5. 负责维护扫描到的网络列表并处理网络信息更新
  6. 当新扫描到一个无线网络(target)时,检查该网络是否已存在于队列中;若存在则更新其信息(如信号强度、IE
    元素),若不存在则添加到队列

  7. 更新网络信息(信号、IE、时间戳等)到队列
  8. ioctl转化模块扫描队列结果,当用户执行 iwlist wlan1 scan 等命令时,该函数会将驱动内部维护的扫描网络队列(scanned_queue)中的信息转换为用户空间可识别的格式,最终展示为用户可见的 WiFi 列表

  9. 获取当前遍历的网络,将网络信息转换为用户空间格式(如iwlist可解析的字符串)
  10. 结果显示

希望这个实例demo能够抛砖引玉,让读者能够自行探索有趣的无线网络驱动,并加入其中的开发应用

纸上得来终觉浅,绝知此事要躬行!

相关推荐
java叶新东老师5 小时前
git stash 命令详解
linux·运维·flink
写bug的羊羊6 小时前
CentOS 9 配置国内 YUM 源
linux·运维·centos
Johny_Zhao8 小时前
CentOS Stream 9上部署FTP应用服务的两种方法(传统安装和docker-compose)
linux·网络安全·信息安全·kubernetes·云计算·containerd·ftp·yum源·系统运维
守望时空339 小时前
RustDesk搭建指南
linux
C++ 老炮儿的技术栈9 小时前
在 Scintilla 中为 Squirrel 语言设置语法解析器的方法
linux·运维·c++·git·ubuntu·github·visual studio
白鹭10 小时前
基于LNMP架构的分布式个人博客搭建
linux·运维·服务器·网络·分布式·apache
java叶新东老师10 小时前
linux 部署 flink 1.15.1 并提交作业
linux·运维·flink
程序员JerrySUN11 小时前
Linux系统架构核心全景详解
linux·运维·系统架构
无敌的牛11 小时前
Linux文件理解,基础IO理解
linux·运维·服务器
未来之窗软件服务11 小时前
跨平台 WebSocket 服务器的设计与实现 —— 基于.NET 8 的跨操作系统解决方案linux,macos,windows——开发工具
linux·服务器·websocket·仙盟创梦ide·东方仙盟