使用ioctl扫描wifi信号获取信号属性的实例(二)

Search for a command to run...

No comments yet. Be the first to comment.
在这个系列中,主要讨论在Linux下的socket编程,有基本的socket编程文章,有讨论raw socket编程的文章,有在应用层编程的文章,也有一些面向数据链路层的编程文章;部分文章涉及IPv6和netlink,大多数文章均附有完整的C语言源代码。
在网络编程中,当需要使用单线程处理多客户端的连接时,常使用select()或者poll()来处理,但是当并发数量非常大时,select()和poll()的性能并不好,epoll()的性能大大好于select()和poll(),在编写大并发的服务器软件时,epoll()应该是首选的方案,本文介绍epoll()在网络编程中的使用方法,本文提供了一个具体的实例,并附有完整的源代码,本文实例在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0。 1 基本概念 在『网络编程专栏』中...
双向链表是一种比单向链表更为灵活的数据结构,与单向链表相比可以有更多的应用场景,本文讨论双向链表的基本概念及实现方法,并着重介绍使用GLib的GList实现单向链表的方法及步骤,本文给出了多个实际范例源代码,旨在帮助学习基于GLib编程的读者较快地掌握GList的使用方法,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;本文适合初学者阅读。 1 双向链表及其实现 在文章《单向链表以及如何使用GLib中的GSList实现单向链表》中,介绍了单向链表以及基于 G...

C 编程框架是开发人员必不可少的工具,编程框架可以为构建强大且性能优异的应用程序提供结构化的基础,本文将对 8 个最佳 C 编程框架和库做出简要的介绍,如果您正在寻找适合初学者的 C 编程框架或旨在进行 C 编程框架比较,相信本文可以给您一定的帮助。 顶级 C 编程框架 – 概述 本文将介绍以下 8 个 C 语言编程框架: 序号框架名称主要特点易于集成下载链接 1GTK全面的小部件集,跨平台支持中等的下载 2Qt跨平台支持,集成开发环境中等的下载 3CMocka轻量级,模...

单向链表是一种基础的数据结构,也是一种简单而灵活的数据结构,本文讨论单向链表的基本概念及实现方法,并着重介绍使用GLib的GSList实现单向链表的方法及步骤,本文给出了多个实际范例源代码,旨在帮助学习基于GLib编程的读者较快地掌握GSList的使用方法,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;本文适合初学者阅读。 1 单向链表及其实现 在文章《使用GLib进行C语言编程的实例》中,简单介绍了 GLib,建议阅读本文前先阅读这篇文章; 单向链表是一...

本文将讨论使用GLib进行编程的基本步骤,GLib是一个跨平台的,用C语言编写的3个底层库(以前是5个)的集合,GLib提供了多种高级的数据结构,如内存块、双向和单向链表、哈希表等,GLib还实现了线程相关的函数、多线程编程以及相关的工具,例如原始变量访问、互斥锁、异步队列等,GLib主要由GNOME开发;本文是使用GLib编程的入门文章,旨在通过实例帮助希望学习GLib编程的读者较快地入门,本文将给出多个使用GLib库编程范例的源代码,本文程序在 ubuntu 20.04 下编译测试完成,gc...

打开电脑连接wifi是一件很平常的事情,但这些事情通常都是操作系统下的wifi管理程序替我们完成的,如何在程序中扫描wifi信号其实资料并不多,前面已经有两篇文章介绍了如何使用ioctl()扫描wifi信号,但其实在Linux下有一个简单的库对这些ioctl()的操作进行了封装,这个库就是libiw,使用libiw可以简化编程,本文介绍了如果使用libiw对wifi信号进行扫描的基本方法,本文将给出完整的源代码,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;尽...

使用工具软件扫描 wifi 信号是一件很平常的事情,在知晓 wifi 密码的前提下,通常我们会尽可能地连接信号质量比较好的 wifi 信号,但是如何通过编程来扫描 wifi 信号并获得这些信号的属性(比如信号强度等),却鲜有文章提及,本文在前面博文的基础上通过实例向读者介绍如何通过编程扫描 wifi 信号,并获得信号的一系列的属性,本文给出了完整的源代码,本文程序在 ubuntu 20.04 下编译测试完成,gcc 版本号 9.4.0;阅读本文并不需要对 IEEE802.11 协议有所了解,但本文实例中大量涉及链表和指针,所以本文可能不适合初学者阅读。
ioctl() 启动了 wifi 信号的扫描,并获取了 wifi 信号的 SSID、MAC地址、工作频率和工作信道,但有一些重要的信号属性并没有获得,比如:信号强度、信号质量、信号噪音以及加密方式等,本文将讨论如何获取这些属性;getifaddrs() 找到所有的网络接口,然后再用 ioctl() 的 SIOCGIWNAME 命令从中找到无线网卡;/proc/net/dev 中找到所有的网络接口,而不必使用 getifaddrs();/proc/net/wireless 文件中直接获得,这种方法更加简单一些,先来看一下这个文件中有什么内容: $ cat /proc/net/wireless
Inter-| sta-| Quality | Discarded packets | Missed | WE
face | tus | link level noise | nwid crypt frag retry misc | beacon | 22
wlp1s0: 0000 70. -256. -256 0 0 0 0 1 0
$
这台电脑上只有一个无线网卡,其接口名称为:wlp1s0,下面程序片段从 /proc/net/wireless 中提取出无线接口的名称:
FILE *fh;
char buff[1024];
fh = fopen("/proc/net/wireless", "r");
if (fh != NULL) {
// Skip 2 lines of header
fgets(buff, sizeof(buff), fh);
fgets(buff, sizeof(buff), fh);
// Read each device line
while (fgets(buff, sizeof(buff), fh)) {
char name[IFNAMSIZ + 1];
// Skip empty lines.
if ((buff[0] == '\0') || (buff[1] == '\0')) continue;
// Extract interface name
char *p = buff;
// Skip leading spaces
while (isspace(*p)) p++;
char *end;
end = strstr(buf, ": ");
// Not found
if (end == NULL) continue;
// Copy
memcpy(name, p, (end - p));
name[end - p] = '\0';
printf("The wireless interface name is %s\n", name);
}
fclose(fh);
} else {
printf("Can't open file /proc/net/wireless\n");
}
通过阅读文章《使用ioctl扫描wifi信号获取信号属性的一个范例(一)》,应该可以了解如何使用 ioctl() 启动 wifi 信号的扫描并获得扫描结果,在此简单回顾一下:
struct iwreq 定义,其中 struct iwreq_data 见下面定义;
struct iwreq {
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */
} ifr_ifrn;
/* Data part (defined just above) */
union iwreq_data u;
};
ioctl() 的调用方式:int ioctl(int socket, unsigned long request, struct iwreq *wrq)request=SIOCSIWSCAN,wrq->ifr_name 设置为无线接口名称,wrq->u.data.pointer=NULL,wrq->u.data.flags=0,wrq->u.data.length=0,然后调用 ioctl();request=SIOCGIWSCAN,wrq->u.data.pointer=接收数据缓冲区指针,wrq->u.data.length=接收缓冲区的长度,wrq->u.data.flags=0,然后调用 ioctl(),调用时,wrq->ifr_name 也是要设置为无线接口名称的,只是因为在启动扫描时已经设置过,所以这里通常不需要再设置;ioctl() 将返回 -1,errno 为 E2BIG,此时应该重新为缓冲区分配内存并再次调用 ioctl() 获取扫描结果;ioctl() 获取扫描结果时,扫描还没有完成,ioctl() 将返回 -1,errno 为 EAGAIN,此时应该等待一会再次调用 ioctl() 获取扫描结果;wreq->u.data.falgs 将被设为 1(调用时为 0),wreq->u.data.length 中为返回数据的实际长度,返回的数据被存放在 wreq->u.data.pointer 指向的数据缓冲区中;返回的扫描结果是一个数据流(stream),其中包含着许多的事件(event),每个 event 包含着一个属性,返回的扫描结果数据符合 struct iw_event,每个 event 数据也符合 struct iw_event,这个结构定义在 wireless.h 中:
union iwreq_data {
/* Config - generic */
char name[IFNAMSIZ];
/* Name : used to verify the presence of wireless extensions.
* Name of the protocol/provider... */
struct iw_point essid; /* Extended network name */
struct iw_param nwid; /* network id (or domain - the cell) */
struct iw_freq freq; /* frequency or channel :
* 0-1000 = channel
* > 1000 = frequency in Hz */
struct iw_param sens; /* signal level threshold */
struct iw_param bitrate; /* default bit rate */
struct iw_param txpower; /* default transmit power */
struct iw_param rts; /* RTS threshold threshold */
struct iw_param frag; /* Fragmentation threshold */
__u32 mode; /* Operation mode */
struct iw_param retry; /* Retry limits & lifetime */
struct iw_point encoding; /* Encoding stuff : tokens */
struct iw_param power; /* PM duration/timeout */
struct iw_quality qual; /* Quality part of statistics */
struct sockaddr ap_addr; /* Access point address */
struct sockaddr addr; /* Destination address (hw/mac) */
struct iw_param param; /* Other small parameters */
struct iw_point data; /* Other large parameters */
};
struct iw_event {
__u16 len; /* Real length of this stuff */
__u16 cmd; /* Wireless IOCTL */
union iwreq_data u; /* IOCTL fixed payload */
}
struct iw_event 的前两个字段,len 表明了这个 event 的数据长度,cmd 表明了这个 event 的类别,不同的 event,字段 u 中对应的数据结构也不相同;struct sockaddr,MAC 地址存放在 u.addr.sa_data 的前 6 个字节中;struct iw_essid,这个是自己定义的,在上面 union 中并没有列出,这个定义可以使问题更加简单一些;struct iw_freq freq,当计算出的频率大于 1000 时,则结果为 wifi 信号的工作频率,否则为该信号的工作信道;当 cmd 为 IWEVQUAL,获得的信息为统计数据的信号质量部分(Quality part of statistics),这部分数据中包括信号质量、信号强度、信号噪音等信息;
struct iw_quality,在 wireless.h 中定义,如下: struct iw_quality {
__u8 qual; /* link quality */
__u8 level; /* signal level (dBm) */
__u8 noise; /* noise level (dBm) */
__u8 updated; /* Flags to know if updated */
};
qual 字段为信号连接质量,先看一下使用无线工具 sudo iwlist [wifname] scan 扫描信号看到的信号连接质量是什么样子的,[wifname] 是无线网络接口的名称,在我的电脑上是 wlp1s0,不同电脑可能会不一样;

图中红线所示部分就是信号连接质量,其表达方式为 47/70,这是什么含义呢?
-110dBm ~~ -40dBm,信号质量的值为信号强度 +110 得出的,这样信号质量值的范围为 0~~70,47/70 的 47 表示信号连接质量值为 47,70 标志信号质量值最大为 70;level + 110 = qualupdated 字段是一个位掩码(bit mask),在 wireless.h 中定义了该字段每个 bit 表达的含义,如下:
/* Statistics flags (bitmask in updated) */
#define IW_QUAL_QUAL_UPDATED 0x01 /* Value was updated since last read */
#define IW_QUAL_LEVEL_UPDATED 0x02
#define IW_QUAL_NOISE_UPDATED 0x04
#define IW_QUAL_ALL_UPDATED 0x07
#define IW_QUAL_DBM 0x08 /* Level + Noise are dBm */
#define IW_QUAL_QUAL_INVALID 0x10 /* Driver doesn't provide value */
#define IW_QUAL_LEVEL_INVALID 0x20
#define IW_QUAL_NOISE_INVALID 0x40
#define IW_QUAL_RCPI 0x80 /* Level + Noise are 802.11k RCPI */
#define IW_QUAL_ALL_INVALID 0x70
/* Modes of operation */
#define IW_MODE_AUTO 0 /* Let the driver decides */
#define IW_MODE_ADHOC 1 /* Single cell network */
#define IW_MODE_INFRA 2 /* Multi cell network, roaming, ... */
#define IW_MODE_MASTER 3 /* Synchronisation master or Access Point */
#define IW_MODE_REPEAT 4 /* Wireless Repeater (forwarder) */
#define IW_MODE_SECOND 5 /* Secondary master/repeater (backup) */
#define IW_MODE_MONITOR 6 /* Passive monitor (listen only) */
#define IW_MODE_MESH 7 /* Mesh (IEEE 802.11s) network */
ioctl() 扫描无线信号时,返回的 struct iw_event 中当 cmd 字段为 SIOCGIWMODE,该事件为工作方式;struct iw_event 中的 u.mode 为该无线信号的工作方式;ioctl() 扫描无线信号时,返回的 struct iw_event 中当 cmd 字段为 SIOCGIWRATE 时,该事件中的数据为信号支持的传输速率; 0000: 28 00 21 8B 00 00 00 00 80 8D 5B 00 00 00 00 00
0010: 00 1B B7 00 00 00 00 00 00 36 6E 01 00 00 00 00
0020: 00 6C DC 02 00 00 00 00
struct iw_event 的定义,前两个字节是这个 event 的长度,为 0x0028,也就是 40 个字节,后面两个字节 0x8B21 是 cmd 字段,0x8b21 也就是 SIOCGIWRATE(见 wireless.h 中的定义),所以这个 event 的数据是传输速率;struct iw_event 中的 u.bitrate 为对应的传输率的数据结构(见第 3 节关于 union iwreq_data 的介绍),u.bitrate 是一个 struct iw_param,其定义如下(见 wireless.h): struct iw_param {
__s32 value; /* The value of the parameter itself */
__u8 fixed; /* Hardware should not use auto select */
__u8 disabled; /* Disable the feature */
__u16 flags; /* Various specifc flags (if any) */
};
u.bitrate.value 字段即为传输速率;struct iw_param 的结构数组,并通过 event 的长度和 struct iw_param 的长度计算得出这个 event 中有多少组传输速率的数据,如下: ......
struct iw_event *evp = data;
int rate_count = (evp.len - IW_EV_LCP_LEN) / sizeof(struct iw_param);
struct iw_param *rates = &evp->u.bitrate;
int i = 0;
for (i = 0; i < rate_count; ++i) {
......
printf("Bit rate: %d Mb/s\n", rates[i].value / 1000000);
}
struct iw_event 中结构头(len 和 cmd 字段)长度(包含为对齐而填充的空字符),请见文章《使用ioctl扫描wifi信号获取信号属性的一个范例(一)》中的相关解释;ioctl() 扫描无线信号时,返回的 struct iw_event 中当 cmd 字段为 IWEVCUSTOM 时,该事件在 wireless.h 中定义为 "Driver specific ascii string",意为:驱动程序特定的 ASCII 字符串;wireless.h 中并没有定义一个事件可以收到有关 beacon 帧的信息,但是我们在事件 IWEVCUSTOM 中看到了 beacon 信息;有意思的是起初并不知道这个字符串中会有什么内容,但把收到的内容显示出来后,发现是类似下面的内容:

图中红线所示就是收到的字符串,原来这个字符串中藏着与 beacon 帧有关的信息,Last beacon 是最后收到的 beacon 帧时间,TSF 是 Timing Synchronization Function 的缩写,是 beacon 帧中的一个字段,用于 AP 和 Station 之间同步时间;
ioctl() 扫描无线信号时,返回的 struct iw_event 中当 cmd 字段为 IWEVGENIE 时,该事件在 wireless.h 中定义为 "Generic IE (WPA, RSN, WMM, ..)",意为通用 IE(Information Elements);我们先来看一个实际收到的 IE 数据

数据与普通事件一样,符合 struct iw_event 结构,前两个字节是事件数据的长度,0x00D2(十进制 210)字节,第 3、4 字节为事件类型,0x8C05(IWEVGENIE) 表示这个事件中是 IE 数据;
struct iw_event 的头信息,紧跟着的 4 个字节为按 64 位对齐填充的字符,第 9、10 字节为实际 IE 数据所占的长度 ie_length,第 11-16 字节是对齐填充字节;struct iw_event 头信息的 8 个字节,而 IE 数据长度是不包括头信息的长度(仅为 IE 数据长度);802.11-2007 文档中定义了 IE 的结构,该文档的下载地址如下:
该标准的 7.3.2 Information elements 定义了 IE 的结构:

每个 IE 符合 type-length-value 格式,即:第 1 个字节表示 IE 类型 Element ID,第 2 个字节表示数据的长度 Length,后面若干字节为实际数据 Information,数据长度为 Length;
struct ieee80211_ie {
unsigned char eid;
unsigned char len;
unsigned char data[0];
};
struct iw_event_ie {
unsigned short len;
unsigned short cmd;
#ifdef __x86_64__ // 64位系统按8字节对齐
unsigned short __attribute((aligned(8)))ie_len;
struct ieee80211_ie __attribute((aligned(8)))ie[0];
#else // 32位系统按4字节对齐
unsigned short __attribute((aligned(4)))ie_len;
struct ieee80211_ie __attribute((aligned(4)))ie[0];
#endif
};
struct iw_event 是一致的,ie_len 字段将得到 ie 这个字段所对应的数据的总长度,struct ieee80211_ie 则定义了每个 IE 的结构,具体有多少个 IE 则需要在遍历 IE 时根据 ie_len 字段的值做出判断;struct ieee80211_ie 中的 eid(Element ID) 字段定义了这个 IE 的类型,这些类型在 802.11-2007 的文档中第 100 页有定义,这里摘录其中的一部分:

从上面定义可以看到,当 Element ID 为 0 时,其信息内容为 SSID,以上面的实际数据为例,数据的第二行,也就是第 1 个 IE 的数据为:
00 07 31 35 2D 31 31 30 31
31 35 2D 31 31 30 31 为实际数据;根据 Element ID 的定义,当 Element ID 为 1 时,IE 的数据为该信号所支持的传输速率,其结构在 802.11-2007 文档的第 102 页有介绍:

根据文档,一个 IE 中最多描述 8 个传输速率,单位为 500 kb/s,每个速率占用 1 个字节,其最高位(bit 7)有其它意义,(bit 0 - 6) 表示速率值,所以在计算时要将 bit 7 过滤掉;
00 00/proc/net/wireless 获取无线网络接口的列表(在文章《使用ioctl扫描wifi信号获取信号属性的一个范例(一)》中使用的是另一种方法);ioctl() 向无线网络接口发出 wifi 信号扫描指令;ioctl() 获取扫描结果,根据返回的结果生成事件链表;gcc -Wall wifi-new-scanner.c -o wifi-new-scanner -lmsudo ./wifi-new-scanner运行截图:

欢迎访问我的博客:https://whowin.cn
email: hengch@163.com
