前言
- 在实际中我们有时候会产生这样一个需求:进制某一台设备接入无线网络,如果已经接入 则让其强制下线。并且,下线后不能再次接入,除非放行该设备
- 这里产生了两个需求:1.将终端设备踢下线。 2.不能让终端设备再次上线。下面我们来分析这两个需求实现的原理
无线设备认证和交互流程
这里我就直接贴网上的链接了https://blog.csdn.net/hmxz2nn/article/details/79937344
总的来说分为三步:扫描,认证和关联。认证和关联都伴随着sta发送请求和ap回复请求。这里我们采用的就是在认证或者关联代码处动手脚
怎么才能让STA下线
- 关于这个问题,我们需要了解一下无线的帧类型。这方面相关的资料网上也是有的,这里我也不再详细介绍,只列出几个重要的知识点。
请注意这里的管理帧 ,管理帧包括STA的加入和退出。本质上来说就是解除关联的帧。(上面提到了认证和关联。这里是相反的,即解除关联)
由此我们可以得出:我们需要发送给指定的STA解除关联的数据报文(即给指定mac的设备发送解除关联的数据报文)
- 由此这里又产生了一个问题:怎么构造和发送该管理帧。其实在现在的网卡驱动程序或者嵌入式网卡模块中,都已经集成了该命令(需要注意的是每个厂商的控制指令集不一样,但是基本都是基于知名网络控制工具来实现的-wirelessTools和iw)。嵌入式可能使用的是AT指令
iw和iwpriv的区别
- iw 是一种新的基于 nl80211 的用于无线设备的CLI配置实用程序。应用层和驱动层采用的是netlink通信方式。而iwpriv是基于老的ioctl的方式进行通信。ioctl所支持的数量是有限制的。
怎么查看iwpriv支持的命令列表
- 厂商如果是用iwpriv作为无线的控制命令,那么内核一定会存在iwpriv支持的一系列命令列表。如果我们在应用层调用iwpriv命令,但是实际没有得到我们想要的效果,那么唯一存在的可能性就是厂商在内核没有实现该命令。
- 在linux内核中struct iw_priv_args结构体是作为iwpriv内核命令列表所对应的结构体。
struct iw_priv_args
{
__u32 cmd; /* Number of the ioctl to issue */
__u16 set_args; /* Type and number of args */
__u16 get_args; /* Type and number of args */
char name[IFNAMSIZ]; /* Name of the extension */
};
在厂商所对应的驱动文件中一定会定义该结构体,这里以rtl8197f芯片为例。
struct iw_priv_args privtab[] = {
#if defined(CONFIG_RTL_P2P_SUPPORT) && defined(RTK_NL80211) /*cfg p2p cfg p2p*/
{ RTL8192CD_IOCTL_FROM_ANDROID, IW_PRIV_TYPE_CHAR | 512, IW_PRIV_TYPE_BYTE | 512 , "hostapd_ioctlcmd" },
#endif
{ RTL8192CD_IOCTL_SET_MIB, IW_PRIV_TYPE_CHAR | 450, 0, "set_mib" },
{ RTL8192CD_IOCTL_GET_MIB, IW_PRIV_TYPE_CHAR | 40, IW_PRIV_TYPE_BYTE | 128, "get_mib" },
#ifdef _IOCTL_DEBUG_CMD_
{ RTL8192CD_IOCTL_WRITE_REG, IW_PRIV_TYPE_CHAR | 128, 0, "write_reg" },
{ RTL8192CD_IOCTL_READ_REG, IW_PRIV_TYPE_CHAR | 128, IW_PRIV_TYPE_BYTE | 128, "read_reg" },
{ RTL8192CD_IOCTL_WRITE_MEM, IW_PRIV_TYPE_CHAR | 128, 0, "write_mem" },
{ RTL8192CD_IOCTL_READ_MEM, IW_PRIV_TYPE_CHAR | 128, IW_PRIV_TYPE_BYTE | 128, "read_mem" },
{ RTL8192CD_IOCTL_WRITE_BB_REG, IW_PRIV_TYPE_CHAR | 128, 0, "write_bb" },
{ RTL8192CD_IOCTL_READ_BB_REG, IW_PRIV_TYPE_CHAR | 128, IW_PRIV_TYPE_BYTE | 128, "read_bb" },
{ RTL8192CD_IOCTL_WRITE_RF_REG, IW_PRIV_TYPE_CHAR | 128, 0, "write_rf" },
{ RTL8192CD_IOCTL_READ_RF_REG, IW_PRIV_TYPE_CHAR | 128, IW_PRIV_TYPE_BYTE | 128, "read_rf" },
#endif
{ RTL8192CD_IOCTL_DUMP_MIB, IW_PRIV_TYPE_CHAR | 40, 0, "dump_mib" },
{ RTL8192CD_IOCTL_DEL_STA, IW_PRIV_TYPE_CHAR | 128, 0, "del_sta" },
{ RTL8192CD_IOCTL_WRITE_EEPROM, IW_PRIV_TYPE_CHAR | 128, 0, "write_eeprom" },
{ RTL8192CD_IOCTL_READ_EEPROM, IW_PRIV_TYPE_CHAR | 128, IW_PRIV_TYPE_BYTE | 128, "read_eeprom" },
注意:一定要在对应厂商所使用的芯片驱动中查找
- 在应用层我们可以列出iwpriv所支持的命令列表。
iwpriv中有一条del_sta 这里感觉就是我们想找的了。接着我们查看内核中iwpriv的命令列表。在该结构体的定义中我们可以看到这样一句
{ RTL8192CD_IOCTL_DEL_STA, IW_PRIV_TYPE_CHAR | 128, 0, "del_sta" },
我们接着跟踪,会找到RTL8192CD_IOCTL_DEL_STA的分支处理代码
case RTL8192CD_IOCTL_DEL_STA:
if ((wrq->u.data.length > sizeof_tmpbuf) ||
ioctl_copy_from_user(tmpbuf, (void *)wrq->u.data.pointer, wrq->u.data.length))
break;
// del_sta branch 4, IOCTL "del_sta"
priv->del_sta_info.branch_code = 4;
DRV_RT_TRACE(priv, DRV_DBG_CONN_INFO, DRV_DBG_SERIOUS, " IOCTL_DEL_STA del_sta %02X%02X%02X%02X%02X%02X\n",
tmpbuf[0], tmpbuf[1], tmpbuf[2], tmpbuf[3], tmpbuf[4], tmpbuf[5]);
ret = del_sta(priv, tmpbuf);
break;
在这里我们可以看到,ioctl_copy_from_user从应用层拷贝,然后调用了del_sta。这里我们通过对代码的分析和猜想从应用层传递的拷贝到tmpbuf中的肯定是sta的mac地址(注:在网络通信中,操作sta只认mac。mac地址是唯一的。mac地址只是在局域网中有用)
所以我们只要在应用层调用iwpriv wlan0/wlan1 del_sta XXXXXXXXXXXX即可踢出设备下线(本质上发送的是解除关联的管理帧)
iw命令在内核中的实现
这里贴一个链接地址,然后结合代码就可以找到https://zhuanlan.zhihu.com/p/141630753
- 我们来看看rtl中有哪些实现,不同厂商所支持的iw命令集有可能不一样(最主要的就是实现cfg80211_ops结构体)
struct cfg80211_ops realtek_cfg80211_ops = {
.add_virtual_intf = realtek_cfg80211_add_iface,
.del_virtual_intf = realtek_cfg80211_del_iface,
.change_virtual_intf = realtek_cfg80211_change_iface,
.add_key = realtek_cfg80211_add_key,
.del_key = realtek_cfg80211_del_key,
.get_key = realtek_cfg80211_get_key,
.set_default_key = realtek_cfg80211_set_default_key,
.set_default_mgmt_key = realtek_cfg80211_set_default_mgmt_key,
//.add_beacon = realtek_cfg80211_add_beacon,
//.set_beacon = realtek_cfg80211_set_beacon,
//.del_beacon = realtek_cfg80211_del_beacon,
.add_station = realtek_cfg80211_add_station,
.del_station = realtek_cfg80211_del_station,
.change_station = realtek_cfg80211_change_station,
.get_station = realtek_cfg80211_get_station,
.dump_station = realtek_cfg80211_dump_station,
#if 0//def CONFIG_MAC80211_MESH
.add_mpath = realtek_cfg80211_add_mpath,
.del_mpath = realtek_cfg80211_del_mpath,
.change_mpath = realtek_cfg80211_change_mpath,
.get_mpath = realtek_cfg80211_get_mpath,
.dump_mpath = realtek_cfg80211_dump_mpath,
.set_mesh_params = realtek_cfg80211_set_mesh_params,
.get_mesh_params = realtek_cfg80211_get_mesh_params,
#endif
.change_bss = realtek_cfg80211_change_bss,
//.set_txq_params = realtek_cfg80211_set_txq_params,
//.set_channel = realtek_cfg80211_set_channel,
.suspend = realtek_cfg80211_suspend,
.resume = realtek_cfg80211_resume,
.scan = realtek_cfg80211_scan,
#if 0
.auth = realtek_cfg80211_auth,
.assoc = realtek_cfg80211_assoc,
.deauth = realtek_cfg80211_deauth,
.disassoc = realtek_cfg80211_disassoc,
#endif
.join_ibss = realtek_cfg80211_join_ibss,
.leave_ibss = realtek_cfg80211_leave_ibss,
.set_wiphy_params = realtek_cfg80211_set_wiphy_params,
.set_ap_chanwidth = realtek_cfg80211_set_ap_chanwidth,
.set_monitor_channel = realtek_cfg80211_set_monitor_channel,
.set_tx_power = realtek_cfg80211_set_tx_power,
.get_tx_power = realtek_cfg80211_get_tx_power,
.set_power_mgmt = realtek_cfg80211_set_power_mgmt,
.set_wds_peer = realtek_cfg80211_set_wds_peer,
.rfkill_poll = realtek_cfg80211_rfkill_poll,
//CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
.set_bitrate_mask = realtek_cfg80211_set_bitrate_mask,
.connect = realtek_cfg80211_connect,
.disconnect = realtek_cfg80211_disconnect,
#if defined(P2P_SUPPORT)
.remain_on_channel = realtek_remain_on_channel,
.cancel_remain_on_channel = realtek_cancel_remain_on_channel,
#endif
.mgmt_tx = realtek_mgmt_tx,
.mgmt_frame_register = realtek_mgmt_frame_register,
.channel_switch = realtek_cfg80211_channel_switch,
.dump_survey = realtek_dump_survey,//survey_dump
.start_ap = realtek_start_ap,
.change_beacon = realtek_change_beacon,
.stop_ap = realtek_stop_ap,
#if 0
.sched_scan_start = realtek_cfg80211_sscan_start,
.sched_scan_stop = realtek_cfg80211_sscan_stop,
.set_bitrate_mask = realtek_cfg80211_set_bitrate,
.set_cqm_txe_config = realtek_cfg80211_set_txe_config,
#endif
.set_mac_acl = realtek_set_mac_acl,
#if defined(DFS)
.start_radar_detection = realtek_start_radar_detection
#endif
};
使用iw dev [Interface] station del [MAC address]将STA踢下线
STA踢出下线后,怎样禁止该STA再次连接
说明: 按照上述的方式是可以将对应mac的sta踢出下线的。但是,这里达不到我们的需求。原因分析如下:当我们调用iwpriv或者iw命令将对应的mac终端设备踢出下线后,终端设备会再次选择加入一个无线局域网。(有可能选择的是和以前不同的局域网,也有可能仍然连接到原来的局域网,因为ap没有修改无线的配置。终端设备仍然能够扫描,认证和关联成功)
解决问题方案分析
为了解决我们上述所提出的问题,我们可以在两个地方做文章。这两个地方分别是认证和关联处,只要我们不让对应的终端设备任何和关联成功,即可不让终端连接成功(也可以在驱动收包处来做包的匹配,如果是该mac的数据报文则丢弃,但是这里我不建议这样做,在驱动做会降低数据的收发效率,因为任何一个数据报文过来都要做匹配)
怎么找到对应的认证和关联的代码
这是一个比较重要的问题,我分析厂商的源代码。最后得出的结论是:认证和关联的代码是厂商自己实现的。 这个结论意味这linux内核没有提供一个通用的关联和认证的入口函数。
为了找到这两个函数,我们需要去在对应厂商的驱动代码中找。(注:一般情况下,无线的一些列扫描,认证,关联的处理函数都会在一个文件中。通过函数的名称来判断该函数所起到的作用,然后加DEBUG信息,可以确认该函数的功能)
- 这里我也以rtl8197f驱动代码为例,我们来看看他的关联函数
static int rtl8192cd_query_psd_cfg80211(struct rtl8192cd_priv *priv, int chnl, int bw, int fft_pts);
#endif
static unsigned int OnAssocReq(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnProbeReq(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnProbeRsp(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnBeacon(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnDisassoc(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnAuth(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnDeAuth(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnWmmAction(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int DoReserved(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
#ifdef WDS
static void issue_probereq(struct rtl8192cd_priv * priv, unsigned char * ssid, int ssid_len, unsigned char * da);
#endif
#ifdef CLIENT_MODE
static unsigned int OnAssocRsp(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnBeaconClnt(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnATIM(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnDisassocClnt(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnAuthClnt(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static unsigned int OnDeAuthClnt(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo);
static void start_clnt_assoc(struct rtl8192cd_priv *priv);
从这里我们就可以看到上面我们的分析是没错的。通过这里函数的名称再结合函数的命名我们基本可以确认我们需要的函数是哪一个:**unsigned int OnAssocReq(struct rtl8192cd_priv priv, struct rx_frinfo pfrinfo)
为了进一步确认该函数是否是我们要找的关联函数,我们可以在该函数中添加打印。然后使用电脑或者手机去连接ap的无线,看看是否在连接过程中该函数会打印。
我们需要在该函数中做什么
这里最重要的一个问题就是,我们在该函数中应该怎么做?才能达到我们的需求。这里的思路就是在该函数中匹配mac,如果匹配上就直接drop掉该报文。
这里我们直接来看内核模块代码(内核模块代码是我自己写的)
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/types.h>
typedef int (*fn_copy_mac_filter)(char *dst);
extern fn_copy_mac_filter copy_mac_filter;
#define MAX_MAC_NUM 100
/* mac filter链表 */
typedef struct _mac_filter {
unsigned char mac[MAX_MAC_NUM][6];
int mac_cnt;
} MAC_FILTER;
MAC_FILTER mac_filter;
unsigned char a2x(const char c);
int do_copy_mac_filter(MAC_FILTER *dst);
spinlock_t info_lock;
int hex2num(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'z') return c - 'a' + 10;
if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
return 0;
}
int str2mac(char *pMac, const char *szMac) {
const char *pTemp = szMac;
int i = 0;
for (i = 0; i < 6; i++) {
pMac[i] = hex2num(*pTemp++) * 16;
pMac[i] += hex2num(*pTemp++);
}
return 0;
}
int do_copy_mac_filter(MAC_FILTER *dst) {
int i = 0;
int cnt = 0;
spin_lock(&info_lock);
cnt = mac_filter.mac_cnt;
if (cnt <= 0) {
spin_unlock(&info_lock);
return -1;
}
dst->mac_cnt = cnt;
#if 1
for (i = 0; i < mac_filter.mac_cnt; i++) {
memcpy(dst->mac[i], mac_filter.mac[i], 6);
#if 0
printk("%2x:%2x:%2x:%2x:%2x:%2x\n", dst->mac[i][0], dst->mac[i][1], \
dst->mac[i][2], dst->mac[i][3], dst->mac[i][4], \
dst->mac[i][5]);
#endif
}
#endif
spin_unlock(&info_lock);
return 0;
}
static int write_mac_filter_proc(struct file *file, const char __user *buffer,
unsigned long count, void *data) {
char mac[16] = {0};
char tmp[16] = {0};
int cmd = -1;
char *e = NULL;
if (count < 0) return -1;
spin_lock(&info_lock);
strncpy(tmp, buffer, 2);
cmd = simple_strtoull(tmp, &e, 10);
/* 1是添加 2是删除 重复性检测由应用层做 */
if (cmd == 1) {
strncpy(mac, buffer + 2, sizeof(mac) - 1);
str2mac(mac_filter.mac[mac_filter.mac_cnt], mac);
mac_filter.mac_cnt++;
copy_mac_filter = do_copy_mac_filter;
}
if (cmd == 2) {
copy_mac_filter = NULL;
memset(&mac_filter, 0, sizeof(mac_filter));
}
spin_unlock(&info_lock);
return count;
}
static int read_mac_filter_proc(struct file *file, char __user *buf,
size_t count, loff_t *ppos) {
int i;
for (i = 0; i < mac_filter.mac_cnt; i++) {
printk("cnt is %d\n", mac_filter.mac_cnt);
printk("%2x:%2x:%2x:%2x:%2x:%2x", mac_filter.mac[i][0],
mac_filter.mac[i][1], mac_filter.mac[i][2], mac_filter.mac[i][3],
mac_filter.mac[i][4], mac_filter.mac[i][5]);
printk("\n");
}
return 0;
}
struct file_operations mac_filter_operations = {
.open = seq_open,
.read = read_mac_filter_proc,
.write = write_mac_filter_proc,
.owner = THIS_MODULE,
};
static int mac_filter_init(void) {
struct proc_dir_entry *pro = NULL;
pro = proc_create("mac_filter", S_IFREG | S_IRWXU, 0, &mac_filter_operations);
memset(&mac_filter, 0, sizeof(mac_filter));
copy_mac_filter = do_copy_mac_filter;
spin_lock_init(&info_lock);
return 0;
}
static void mac_filter_exit(void) {
copy_mac_filter = NULL;
remove_proc_entry("mac_filter", 0);
}
module_init(mac_filter_init);
module_exit(mac_filter_exit);
MODULE_LICENSE("GPL");
- 第一步创建一个内核模块,定义MAX_MAC_NUM为最大的存储数据大小
- 创建proc文件,将传入的mac转换成小写然后存储到数组中
OnAssocReq中代码实现(在驱动中添加我们内核模块对应的全局变量)
typedef struct _mac_filter{
unsigned char mac[MAX_MAC_NUM][6];
int mac_cnt;
}MAC_FILTER;
MAC_FILTER mac_filter;
int (*copy_mac_filter)(MAC_FILTER *mac_filter) = NULL;
EXPORT_SYMBOL(copy_mac_filter);
int mac_filter_get(MAC_FILTER* mac_filter)
{
int ret = -1;
memset(mac_filter,0,sizeof(MAC_FILTER));
if (copy_mac_filter != NULL)
ret = copy_mac_filter(mac_filter);
return ret;
}
这段代码是定义在对应厂商的驱动中,主要是导出了copy_mac_filter符号。我们在内核模块加载的时候对该函数指针进行了一个赋值。如果内核模块加载,该符号就不为空。并且指向了内核模块中的do_copy_mac_filter函数。如果内核模块卸载,那么copy_mac_filter则为空。
接着我们来看看OnAssocReq中我们是怎么写的(将代码添加到驱动中)
unsigned int OnAssocReq(struct rtl8192cd_priv *priv, struct rx_frinfo *pfrinfo)
{
struct wifi_mib *pmib;
struct stat_info *pstat;
unsigned char *pframe, *p;
unsigned char rsnie_hdr[4]={0x00, 0x50, 0xf2, 0x01};
#ifdef RTL_WPA2
unsigned char rsnie_hdr_wpa2[2]={0x01, 0x00};
#endif
#ifdef HS2_SUPPORT
unsigned char rsnie_hdr_OSEN[4]={0x50, 0x6F, 0x9A, 0x12};
#endif
int len;
#ifndef SMP_SYNC
unsigned long flags;
#endif
DOT11_ASSOCIATION_IND Association_Ind;
DOT11_REASSOCIATION_IND Reassociation_Ind;
unsigned char supportRate[32];
int supportRateNum;
unsigned int status = _STATS_SUCCESSFUL_;
unsigned short frame_type, ie_offset=0, val16;
unsigned int z = 0;
#ifdef P2P_SUPPORT
unsigned char ReAssem_p2pie[MAX_REASSEM_P2P_IE];
int IEfoundtimes=0;
unsigned char *p2pIEPtr = ReAssem_p2pie ;
int p2pIElen;
#endif
pmib = GET_MIB(priv);
pframe = get_pframe(pfrinfo);
pstat = get_stainfo(priv, GetAddr2Ptr(pframe));
/* yangpf add mac_filter */
int i = 0;
int ret = mac_filter_get(&mac_filter);
if (pstat && ret == 0){
for (i=0; i<mac_filter.mac_cnt; i++){
if (memcmp(mac_filter.mac[i], pstat->hwaddr, 6) == 0)
return 0;
}
}
我们可以看到这里分离出了sta的mac。然后将sta的mac和我们的进行对比。如果相等则直接返回。不会往下走。(相当于drop掉了关联的报文)。即可实现我们需要的功能。