OpenWRT WiFi添加探针-基于AR71XX平台

it2022-05-05  164

背景

最近基于OpenWRT开发了WiFi探针的功能,可以实现基本的客户端MAC及客户端的信号强度搜集,搜集结束之后通过MQTT上传至后台服务进行分析,可实现更多的扩展功能,如人流量统计,粗略定位等. 本文描述不涉及后台的分析功能,只是通过路由器提供的AP功能,将搜集到的信息提交给服务后台. 好,正式开始.

开发思路

如果熟悉802.11协议的话,开发思路比较明确了,先上802.11报文格式,如下: 其中Address2为发送端的MAC地址,同时,基于802.11协议的行为模式,802.11协议规定:

AP基站每个100ms发送一次信标Beacon帧,用来提供BSS网络基础架构;Station客户端,在WiFi功能开启状态下,每隔一段时间会向附近所有能收到Beacon帧的AP(即使客户端没有链接到AP),发Probe Request报文,基站收到 此报文后会回应Probe Response报文;

而Probe Request报文属于管理帧(Type=00)的一种,子类型为Probe Request(SubType=0100),了解这些,要实现探针的思路就很明确了.主要分两个步骤:

WiFi驱动层,抓取到我们想要的信息(内核空间),通过某种方式传递给用户空间;用户空间接收到内核空间传递的信息,进行数据的提取及发送到云端;

1.内核空间代码

我使用AR71XX系列设备,其WiFi驱动的源码,在OpenWRT编译之后的build_dir/target-mips_34kc_uClibc-0.9.33.2/linux-ar71xx_generic/compat-wireless-2016-01-10目录下,不同版本可能会有区别,所有收到的WiFi数据包,都会通过net/mac80211/rx.c的__ieee80211_rx_handle_packet函数进行处理,因此检测Probe Request帧就在这里处理,添加代码片段如下:

--- compat-wireless-2016-01-10/net/mac80211/rx.c 2019-07-18 17:43:35.176544318 +0800 +++ compat-wireless-2016-01-10-mod/net/mac80211/rx.c 2019-07-18 16:05:46.538622629 +0800 @@ -31,6 +31,7 @@ #include "tkip.h" #include "wme.h" #include "rate.h" +#include "probe_nl.h" static inline void ieee80211_rx_stats(struct net_device *dev, u32 len) { @@ -3429,6 +3430,36 @@ ieee80211_invoke_rx_handlers(rx); return true; } +void collect_probe_info(u8 *addr,s8 signal) +{ + if(g_probed_unsend_num >= MAX_PROBED_NUM) + { + g_probed_unsend_num = 0; + return; + } + else + { + int i = 0; + bool flag =1; + for(i=0; i<g_probed_unsend_num; i++) + { + if(memcmp(g_probed_info[i],addr,6)==0) + { + flag = 0; + //update signal + //memcpy(g_probed_info[i], addr, 6); + memcpy(g_probed_info[i]+6, &signal, 1); + break; + } + } + if(flag) + { + memcpy(g_probed_info[g_probed_unsend_num], addr, 6); + memcpy(g_probed_info[g_probed_unsend_num]+6, &signal, 1); + g_probed_unsend_num++; + } + } +} /* * This is the actual Rx frames handler. as it belongs to Rx path it must @@ -3453,7 +3484,7 @@ rx.skb = skb; rx.local = local; rx.napi = napi; - + if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc)) I802_DEBUG_INC(local->dot11ReceivedFragmentCount); @@ -3512,6 +3543,11 @@ prev = NULL; + if (ieee80211_is_probe_req(fc)/* && g_is_probe*/){ + struct ieee80211_rx_status *sta = IEEE80211_SKB_RXCB(skb); + collect_probe_info(hdr->addr2,sta->signal); + } + list_for_each_entry_rcu(sdata, &local->interfaces, list) { if (!ieee80211_sdata_running(sdata))

以上完成数据的收集,并且存储在g_probed_info全局变量中,然后通过Generic Netlink方式传递给用户空间. Generic Netlink的使用,需要在net/mac80211/下添加probe_nl.c和probe_nl.h,下面有源码,这两个文件主要处理用户态进程和内核态进程之间的通信,将上一步g_probed_info搜集到的数据传递给用户空间.

Generic netlink socket是一种用于用户态进程和内核态进程之间的通信机制。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket接口的方式,实现了全双工的通讯连接。使用方式也很简单,内核空间分为3步:

定义genl_family结构体实例定义genl_family实例的操作方法将实例方法注册到Generic Netlink

以上内核空间代码,如果感兴趣,请参考git kernel-space代码,使用方法:

加入rx.c的patch文件,用于搜集客户端信息;net/mac80211/下添加probe_nl.c和probe_nl.h;net/mac80211/main.c添加probe_nl的注册;Makefile添加编译probe_nl模块;

2.用户空间代码

用户空间就比较单纯,主要是与内核空间的Generic Netlink建立socket链接,定期触发内核空间向用户空间发送搜集的数据即可,分为以下:

sock_fd = genl_socket_init(); 初始化Generic Netlink的socket链接genl_get_family_id();获取Generic Netlink family idgenl_send_msg();发送数据到内核空间,触发内核将搜集到的数据进行向用户空间的下发genl_rcv_msg();接收内核下发的数据format_mac_info();根据定义的数据格式,格式化数据send_json_by_mosquitto();将数据信息发送给mqtt server

代码片段如下:

int main() { memset(kernel_sended_mac_list,0,sizeof(kernel_sended_mac_list)); int sock_fd; sock_fd = genl_socket_init(); if(sock_fd < 0){ printf("create_nl_socket create failure\n"); return 0; } int family_id = genl_get_family_id(sock_fd, "ProbeMacList"); for(;;){ genl_send_msg(sock_fd, family_id, DOC_EXMPL_C_ECHO, DOC_EXMPL_A_MSG, "s"); genl_rcv_msg(family_id,sock_fd); format_mac_info(); send_json_by_mosquitto(); sleep(5); } }

以上用户空间代码,如果感兴趣,可参考git app-space代码,使用方法:

直接将wifi-probe这一包放入OpenWRT源码目录下的packages/下;make menuconfig -> Utilities -> wifi-probe选中;make编译即可;

结尾

至此,所有开发工作基本完成,只不过我实现的上传给MQTT服务器的部分,为了省事直接用的shell脚本实现,如果有时间,还是直接使用c库实现比较好. 以上所有代码都在我的GitHub上,大家可下载调试. https://github.com/wesley-fly/wifi-probe.git 看下效果结束吧!


最新回复(0)