linux基于802.11的wifi扫描流程

本文深入探讨Netlink协议及Generic Netlink在WiFi扫描中的应用,解析消息传递机制,包括消息回调函数类型、流程说明、WiFi扫描启动及结果接收过程。详细介绍了如何利用Netlink与内核交互,实现WiFi信号扫描及结果解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

wifi scan

netlink协议

    1. Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口
    1. 不同于ioctl的是,ioctl只能由应用将消息单向发往内核,netlink则完全支持双工通信
    1. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息
    1. 基于socket的通信
    1. Netlink的通道是通过Family来组织的,但随着使用越来越多,Family ID已经不够分配了,所以才有了Generic Netlink
    1. Generic Netlink其实是对Netlink报文中的数据进行了再次封装

依赖库

    1. libnl-3.so //netlink
    1. libnl-genl-3.so //Generic Netlink

结构

    1. 消息回调函数的类型
enum  nl_cb_kind {
  NL_CB_DEFAULT,  //默认回调处理函数
  NL_CB_VERBOSE,  //默认处理函数,在default的基础上会将错误信息、无效信息等打印到stderr,且nl_cb_set()或者nl_cl_err()中的arg参数提供FILE*替代stderr
  NL_CB_DEBUG,    //默认处理函数,在VERBOSE的基础上打印所有信息到终端
  NL_CB_CUSTOM,   //用户自定义处理函数
  __NL_CB_KIND_MAX
}
    1. 回调函数通常返回值
enum nl_cb_action {
  NL_OK,   //表示处理正常。
  NL_SKIP,  //表示停止当前netlink消息分析,转而去分析接收buffer中下一条netlink消息(消息分 片的情况)。
  NL_STOP, //表示停止此次接收buffer中的消息分析。
};
    1. type类型:处理底层不同netlink消息的情况
enum  nl_cb_type {
  NL_CB_VALID,  //有效消息
  NL_CB_FINISH, //multipart消息结尾
  NL_CB_OVERRUN, //数据丢失报告
  NL_CB_SKIPPED, //跳过处理消息
  NL_CB_ACK,      //确认消息
  NL_CB_MSG_IN,   //所有接收到的消息
  NL_CB_MSG_OUT,   //所有发出的消息,除nl_sendto()外
  NL_CB_INVALID,   //无效消息
  NL_CB_SEQ_CHECK,  
  NL_CB_SEND_ACK,
  NL_CB_DUMP_INTR,
  __NL_CB_TYPE_MAX
}

流程说明

    1. 对于从user to kernel的通讯,driver必须先向内核注册一个struct genl_family,并且注册一些cmd的处理函数。这些cmd是跟某个family关联起来的。注册family的时候我们可以让内核自动为这个family分配一个ID。每个family都有一个唯一的ID,其中ID号0x10是被内核的nlctrl family所使用。当注册成功以后,如果user program向某个family发送cmd,那么内核就会回调对应cmd的处理函数。对于user program,使用前,除了要创建一个socket并绑定地址以外,还需要先通过family的名字获取family的ID。有了family的ID以后,才能向该family发送cmd
    1. 对于从kernel to user的通讯,采用的是广播的形式,只要user program监听了,都能收到。但是同样的,user program在监听前,也必须先查询到family的ID

流程

    1. init:
wifiscan->ifname  = strdup(IFNAME);
wifiscan->ifindex = if_nametoindex(wifiscan->ifname);

nl80211_init_nl_global()
    -->nl_cb_alloc()
    /*
    申请回调函数
    wifiscan->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); //NL_CB_DEFAULT:默认处理handler类型
    calloc空间,最终调用nl_cb_set()将回调函数及其参数按顺序放入nl_recvmsg_msg_cb_t cb_set[]中
    */
    -->nl_create_handle() //创建nl_cb对应的nl_handle
    /* wifiscan->nl = nl_create_handle(wifiscan->nl_cb, "nl"); */
        -->nl80211_handle_alloc(cb)
        /* struct nl_handle *handle = nl80211_handle_alloc(cb); */

            -->__alloc_socket(cb);
            /*
            struct nl_sock *sk;
            申请空间并初始化sk的参数,诸如s_local.nl_family=AF_NETLINK,s_peer,s_local.nl_pid,s_cb等信息

            ps:nl_handle即为nl_sockc
            */

        -->genl_connect(handle)
            -->nl_connect(handle, NETLINK_GENERIC);
            /*
            sk即handle,protocol即NETLINK_GENERIC

            sk->s_fd = socket(AF_NETLINK, SOCK_RAW | flags, protocol);
            bind(sk->s_fd, (struct sockaddr*) &sk->s_local,sizeof(sk->s_local)) //绑定本地信息
            */

    -->genl_ctrl_resolve() //向内核发命令,根据family name "nl80211" 获得family id (将nl连接到 内核中的nl80211模块)
    /* wifiscan->nl80211_id = genl_ctrl_resolve(wifiscan->nl, "nl80211"); */

    -->nl_create_handle() //创建接收消息的event socket
    /* wifiscan->nl_event = nl_create_handle(wifiscan->nl_cb, "event"); */

    -->nl_socket_set_nonblocking(wifiscan->nl_event); //设置该handle为非阻塞模式(fcntl)
    -->nl_get_multicast_id() //将nl_event加入到组播组scan
    /* ret = nl_get_multicast_id(wifiscan, "nl80211", "scan"); */

        -->struct nl_msg *msg = nlmsg_alloc() //申请一个nl_msg结构用于向内核发送控制消息
        -->genlmsg_put() //Add Generic Netlink headers to Netlink message
        /*
        void *genlmsg_put(struct nl_msg *msg, uint32_t port, uint32_t seq, int family, int hdrlen, int flags, uint8_t cmd, uint8_t version)

        genlmsg_put(msg, 0, 0, genl_ctrl_resolve(wifiscan->nl, "nlctrl"), 0, 0, CTRL_CMD_GETFAMILY, 0);
        //port:0,表示发非内核;family为"nlctrl",表示该消息为控制消息;cmd:CTRL_CMD_GETFAMILY,消息索引;
        */

        -->send_and_recv_msgs() //发送消息并接收返回
        /* ret = send_and_recv_msgs(wifiscan, msg, family_handler, &res); */

            -->struct nl_cb *cb = nl_cb_clone(wifiscan->nl_cb); //克隆一个nl_cb
            -->nl_send_auto_complete(wifiscan->nl, msg)
            /* 发送生成的帧到内核,内核收到后会执行该消息帧中填充的命令索引和参数,如wifi扫描*/

            -->nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); //注册出错回调
            -->nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); //注册消息完成时的回调
            -->nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); //注册接收ack回调
            -->nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,family_handler, (void *)res); //注册传入的回调函数,处理内核返回的有效消息
            -->nl_recvmsgs(wifiscan->nl, cb); 
            /* 接收内核消息,收到消息后会调用nl_cb_set()注册的函数,具体调用哪个,由消息类型(type)决定,如收到NL_CB_VALID类型会调用family_handler() */

                -->family_handler() //处理接收的消息
                /* 遍历attributes, 并比较其group与传入的group(nl_get_multicast_id()的最后一个参数),一致则用该attributes的group id填充res.id项,
                该id即为nl_get_multicast_id()的返回值
                 */

    -->nl_socket_add_membership()
    /* nl_socket_add_membership(wifiscan->nl_event, res.id) */

        -->nl_socket_add_memberships(wifiscan->nl_event, res.id, 0) //将 res.id代表的group("scan")加入多播组进行监听,监听套接字为nl_event->s_fd
        /* setsockopt(wifiscan->nl_event->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &res.id, sizeof(res.id)); */

    -->nl_socket_get_fd() //wifiscan->nl_event_fd = wifiscan->nl_event->s_fd
    /* wifiscan->nl_event_fd = nl_socket_get_fd(wifiscan->nl_event); */

    -->nl_cb_set(wifiscan->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,  no_seq_check, NULL);
    -->nl_cb_set(wifiscan->nl_cb, NL_CB_VALID, NL_CB_CUSTOM,  process_global_event, wifiscan); //设置nl_cb的回调函数 process_global_event()

    1. 设置epoll监听内核事件
struct epoll_event events[MAX_EVENTS];
wifiscan->nl_ev ==> struct epoll_event nl_ev;


wifiscan->epoll_fd = epoll_create(MAX_EVENTS);
wifiscan->nl_ev.events = EPOLLIN;
wifiscan->nl_ev.data.fd = wifiscan->nl_event_fd;
epoll_ctl(wifiscan->epoll_fd, EPOLL_CTL_ADD, wifiscan->nl_event_fd, &wifiscan->nl_ev) //监听nl_event handle上的事件

while() {
    int nfds = epoll_wait(wifiscan->epoll_fd, events, MAX_EVENTS, 200);

    for (i = 0; i < nfds; ++i) {
        if (events[i].data.fd == wifiscan->nl_event_fd) {
            nl80211_event_receive(wifiscan);
            nl80211_get_scan_results(wifiscan);
        }
    }
}
    1. 开始扫描
wifiscan->params ==> struct nl80211_scan_params params;

wifi_scan_start(wifiscan);
    -->nl80211_scan(wifiscan); //请求驱动开始扫描
        -->nl80211_scan_common()
        /* struct nl_msg *msg = nl80211_scan_common(wifiscan, NL80211_CMD_TRIGGER_SCAN, &wifiscan->params,NULL);*/

            -->nlmsg_alloc() //申请nl_msg结构
            /* struct nl_msg *msg = nlmsg_alloc();
                struct nl_msg *ssids = nlmsg_alloc();
            */

            -->nl80211_cmd() //添加Generic Netlink头到Netlink message,参数1指定family
            /* nl80211_cmd(wifiscan->nl80211_id, msg, 0, NL80211_CMD_TRIGGER_SCAN); */

            -->NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, wifiscan->ifindex); //Add 32 bit integer attribute to netlink message,指定扫描的节点(如wlan0)
            -->NLA_PUT(ssids, 1, 0, ""); //Add unspecific attribute to netlink message.
            -->nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids); //将ssids嵌套进msg,netlink支持消息嵌套,即属性中携带的数据可以是另外一个nl_msg消息
            -->nlmsg_free(ssids); //从netlink中释放引用

            -->若需指定扫描信道
            /* if (params->num_freqs) {
                struct nlattr *freqs = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES); //启动新级别的嵌套属性NL80211_ATTR_SCAN_FREQUENCIES
                //循环嵌套所有扫描的信道
                for(i = 0; i< params->num_freqs; i++) {
                    if(nla_put_u32(msg, i + 1, params->freqs[i]) < 0)
                        goto fail;
                }
                nla_nest_end(msg, freqs);
            }
            */

        -->send_and_recv_msgs()  //发送该msg并接收返回,但不进行返回的处理(返回为ACK,无有效数据)
        /* send_and_recv_msgs(wifiscan, msg, NULL, NULL) */
    1. 扫描完成后wifiscan->nl_event->s_fd上监听的事件触发,epoll中接收
    1. 接收扫描结果
nl80211_event_receive(wifiscan)
    -->nl_recvmsgs() //接收内核数据并调用对应回调函数进行处理
    /* nl_recvmsgs(wifiscan->nl_event, wifiscan->nl_cb)
        接收到消息会调用wifiscan->nl_cb上注册的各回调函数,接收到有效消息时调用的是process_global_event()
     */
        -->process_global_event()
            -->nlmsg_data() //计算偏移数目,返回message payload的起始指针
            /* struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); */

            -->nla_parse() //解析attributes
            /* struct nlattr *tb[NL80211_ATTR_MAX + 1]; 
            nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); //保存到tb中
            */

            -->do_process_drv_event() //判断接收到的消息类型,NL80211_CMD_NEW_SCAN_RESULTS代表扫描完成
            /* do_process_drv_event(gnlh->cmd, tb,wifiscan); 
            switch (cmd) {
                ...
            case NL80211_CMD_NEW_SCAN_RESULTS:
                wifiscan->scan_results_ready = 1;
                break;
            }
            */
if(wifiscan->scan_results_ready)  //扫描完成
-->nl80211_get_scan_results(wifiscan);
    -->struct scan_results *res = nl80211_try_get_scan_results(wifiscan); //获取最新扫面结果
        -->nl80211_cmd(wifiscan->nl80211_id, msg, NLM_F_DUMP, NL80211_CMD_GET_SCAN); //添加NL80211_CMD_GET_SCAN命令到msg
        -->NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, wifiscan->ifindex);
        -->send_and_recv_msgs() //发送消息并注册回调函数bss_info_handler(),收到有效数据时触发
        /* send_and_recv_msgs(wifiscan, msg, bss_info_handler, res); */

            -->bss_info_handler() //获取bssid,ssid,freq等值

    -->check_bss(res); //将bss_info_handler()中填充的res进行最终解析,判断协议及加密类型等
    1. 扫描结果解析

/* 解析消息帧,获取ssid,bssid,freq,strength,加密类型,协议版本等信息 */
bss_info_handler(struct nl_msg *msg, struct scan_results *res)
    -->nla_parse() //解析消息到struct nlattr *tb[NL80211_ATTR_MAX + 1]中
    -->nla_parse_nested(struct nlattr *bss, ..., tb[NL80211_ATTR_BSS], ) //解析嵌套在attr中的属性,获取bss段相关信息
    -->nla_data() //获取bss段中指定段的值
    /* 
    const unsigned char *ie = nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]); //获取ie(information element)
    int ie_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]); //ie段长度
    
    const unsigned char *beacon_ie = nla_data(bss[NL80211_BSS_BEACON_IES]);  //beacon ie
    int beacon_ie_len = nla_len(bss[NL80211_BSS_BEACON_IES]); //beacon ie段长度

    int level = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]); //signal段为u32数值

    struct scan_res *r = calloc(1,sizeof(*r) + ie_len + beacon_ie_len); //存储解析结果
    r->level = level;
    memcpy(r->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);
    r->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);
    r->beacon_int = nla_get_u8(bss[NL80211_BSS_BEACON_INTERVAL]);
    r->caps = nla_get_u16(bss[NL80211_BSS_CAPABILITY]);
    r->age = nla_get_u32(bss[NL80211_BSS_SEEN_MS_AGO]);
    */

    -->解析ie中内容
    /*
    int ielen = ie_len;
    while (ielen >= 2 && ielen >= ie[1]) {
        /* ie[0]中存储当前段的类型,ie[1]为当前段的大小 */
        switch (ie[0]) {
            case WLAN_EID_SSID: //当前段为ssid段
                strncpy(r->ssid,(const char*)ie+2,ie[1]);
				r->ssid_len = ie[1];
                break;

            case WLAN_EID_RSN: //rsn,网络安全性
                r->psk_type |= 1<<PSK_RSN; //标志,r->psk_type += 1<<PSK_RSN
                wpa_parse_wpa_ie_rsn(ie,ie[1]+2,&r->rsn); //解析rsn段(具体协议解析)
                break;

            case WLAN_EID_HT_OPERATION:
                r->ht_flag = ht_secondary_value[ie[3] & 0x3];

            case 221:
                if (ie[1] >= 4 && (memcmp(&ie[2], ms_oui, 3) == 0) && (ie[5] == 1)) {
                    r->psk_type |= 1<<PSK_WPA;
                    wpa_parse_wpa_ie_wpa(ie, ie[1]+2,&r->wpa);
                }

            default:
                break;
        }

        ielen -= ie[1] + 2;
		ie += ie[1] + 2;
    }
    */

    -->将r内容给到传入的res中
    1. 协议及加密类型判断
#define BIT(x) (1ULL<<(x))
#define WPA_KEY_MGMT_PSK BIT(1)

check_bss(struct scan_results *res)
    -->psk_info(res)  //psk解析
    /*
    struct wpa_ie_data *data=NULL;
	char *wpa = NULL, *auth = NULL, *enc = NULL;
    */

        -->根据之前解析的psk_type判断协议类型
        /*
        switch(r->psk_type){
            case BIT(1): //wpa协议
                wpa = "WPA";
			    data = &r->wpa;
			    break;
            case BIT(2): //wpa2(wpa升级版,安全性更高)
                wpa = "WPA2";
			    data = &r->rsn;
			    break;
            case 6: //两种都支持
                wpa = "WPA-WPA2";
			    data = &r->rsn;
			    break;
            default:
                break;
        }
        */

        -->根据pairwise_cipher值判断加密类型
        /*
        if(data->key_mgmt & (1<< WPA_KEY_MGMT_PSK)){
			auth = "PSK";
		}else {
			auth = "8021X";
		}

        /* 具体解析原则参见下表 table2 cipher suite selectors */
        switch (data->pairwise_cipher){
            case BIT(0):
                enc = "Use group cipher suite";
				break;
            case BIT(1):
                enc = "WEP-40";
				break;
            case BIT(2):
                enc = "TKIP";
				break;
            case BIT(4):
                enc = "AES";
				break;
            case 0x14:
                enc = "AES-TKIP";
				break;
            case BIT(5):
                enc = "WEP-104";
				break;
            case BIT(6):
                enc = "AES-128-CMAC";
				break;
            case BIT(8):
                enc = "GCMP";
				break;
            default:
                enc = NULL;
                break;
        }
        */
    1. wpa_parse_wpa_ie_rsn函数

具体解析原理参见下表1 rsn ie格式部分

wpa_parse_wpa_ie_rsn(const u8 *rsn_ie, size_t rsn_ie_len, struct wpa_ie_data *data)
/*
const unsigned char * pos = (const u8 *)rsn_ie + sizeof(struct rsn_ie_hdr); //跳过头部
int left = rsn_ie_len - sizeof(struct rsn_ie_hdr); //剩余大小
*/

-->解析group data cipher suite
/*
#define RSN_SELECTOR_LEN 4

if (left >= RSN_SELECTOR_LEN) {
    data->group_cipher = 1<<pos[3];
	pos += RSN_SELECTOR_LEN;
	left -= RSN_SELECTOR_LEN;
}
*/

-->解析pairwise cipher suite count和pairwise cipher suite list
/*
data->pairwise_cipher = 0;
count = WPA_GET_LE16(pos);  //获取count
pos += 2;
left -= 2;

/* 轮寻pairwise cipher suite list,获取pairwise cipher suite */
for (i = 0; i < count; i++) {
	data->pairwise_cipher |= 1<<pos[3];
	pos += RSN_SELECTOR_LEN;
	left -= RSN_SELECTOR_LEN;
}
*/

-->解析AKM suite count和AKM suite list
/*
if (left >= 2) { //确保存在AKM suite count段
    data->key_mgmt = 0;
	count = WPA_GET_LE16(pos);
	pos += 2;
	left -= 2;
    for (i = 0; i < count; i++) {
		data->key_mgmt |= 1<<pos[3];
		pos += RSN_SELECTOR_LEN;
		left -= RSN_SELECTOR_LEN;
	}
}
*/

-->解析rsn capabilities
/*
if (left >= 2) {
	data->capabilities = WPA_GET_LE16(pos);
	pos += 2;
	left -= 2;
}
*/

-->解析PMKID count和PMKID list
/*
if (left >= 2) {
    data->num_pmkid = WPA_GET_LE16(pos);
	pos += 2;
	left -= 2;

    data->pmkid = pos;
	pos += data->num_pmkid * PMKID_LEN;
	left -= data->num_pmkid * PMKID_LEN;
}
*/
    1. wpa_parse_wpa_ie_wpa函数

基本解析原理同wpa_parse_wpa_ie_rsn,差异之处为具体协议

table

    1. RSNIE格式
      table1 RSNIE format
rsn ieelement idlengthversiongroup data cipher suitepairwise cipher suite countpairwise cipher suite list
octets112424 * m
AKM suite countAKM suite listRSN capabilitiesPMKID countPMKID listgroup management cipher suite
24 * n2216 * s4
    1. 不同cipher suite释义对照
      table2 cipher suite selectors
OUIsuite typemeaning
00-0F-AC0use group cipher suite
00-0F-AC1WEP-40
00-0F-AC2TKIP
00-0F-AC3reserved
00-0F-AC4AES
00-0F-AC5WEP-104
00-0F-AC6AES-128-CMAC
00-0F-AC7not allowed
00-0F-AC8GCMP

相关结构

    1. struct wpa_ie_data
struct wpa_ie_data {
    int proto;
    int pairwise_cipher;
    int group_cipher;
    int key_mgmt;
    int capabilities;
    size_t num_pmkid;
    const u8 *pmkid;
    int mgmt_group_cipher;
}; 
    1. struct scan_res
struct scan_res {
    unsigned int flags;
    unsigned char bssid[ETH_ALEN];
    char ssid[MAX_SSID_LEN];
    unsigned char ssid_len;
    unsigned char ht_flag;
    int freq;
    unsigned short beacon_int;
    unsigned short caps;

    int level;

    unsigned int age;
    size_t ie_len;
    size_t beacon_ie_len;

    unsigned int psk_type;
    struct wpa_ie_data rsn,wpa;
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜西海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值