• hostapd源代码分析[转载]


    ref: https://blog.csdn.net/tmwiajd

    hostapd源代码分析(一):网络接口和BSS的初始化

    最近在做一个基于OpenFlow 协议的无线AP 的项目,于是就分析了hostapd 的源代码,并在原有的基础上添加上我们的代码。经过近半个月的调试和分析,算是基本上搞清楚了hostapd 的运作机制。鉴于网上对于hostapd 的具体资料甚是稀少,所以笔者在此整理学习笔记并在网上与各位读者分享,希望能对读者们有帮助。如果有分析不恰当或者错误的地方,也欢迎各位指正。另外,本文是在读者已经具有IEEE 802.11 和良好C 语言的基础的假设上写的,因此,本文将直奔主题,对于IEEE 802.11 和C 代码细节将不再赘述。  

            还有,笔者在分析hostapd 源代码的时候,hostapd/supplicant 开发文档也提供了大量的有用信息,各位读者也可以参考。(http://w1.fi/wpa_supplicant/devel/)


    一、几个重要的数据结构


            此次分析hostapd 源代码,发现了其中出现频率最高的两个数据结构——struct hostapd_iface 和struct hostapd_data。其实很简单,hostapd_iface 结构体描述了一个物理接口(比如wlan0),hostapd_data 则描述一个BSS。这两个数据结构几乎贯穿所有的源代码,所以一定要搞清楚这两个数据结构。

         struct hostapd_iface (src/ap/hostapd.h)。在这个数据结构中,主要有以下字段:
             struct hostapd_config: 保存对网络接口的配置(从配置文件hostapd.conf中加载)
             sizt_t num_bss: 此接口下辖的BSS 个数
             struct hostapd_data **bss:BSS 列表,描述各BSS 的配置

         struct hostapd_data (src/ap/hostapd.h)。在这个数据结构中,主要有以下字段:
             struct hostapd_bss_config *conf:保存BSS 的配置信息(从配置文件hostapd.conf 中加载)
             u8 own_addr[ETH_ALEN]:表示此BSS 的BSSID (ETH_ALEN 为6,u8 其实是unsigned char)
             struct wpa_driver_ops *driver:指向一组驱动程序接口,用来和内核交互。(这里是用的nl80211)

    说到这里,也许有人会问,实际源文件中有数十个字段,难道只知道这些就足够了吗?我的答案是肯定的——是的,只需要知道这么多。


    二、hostapd 初始化


             首先,我们打开hostapd/main.c,并找到主程序入口main()。直接跳转到大概659 行。看到如下代码:

        for (i = 0; i < interfaces.count; i++) {
            interfaces.iface[i] = hostapd_interface_init(&interfaces,argv[optind + i],debug);
            if (!interfaces.iface[i]) {
                wpa_printf(MSG_ERROR, "Failed to initialize interface");
                goto out;
            }
        }


    很明显, 这是对每个网络接口( 或者干脆叫物理网卡也行) 的初始化。然后找到hostapd_interface_init 的定义(大概从main.c 的第234 行开始),我们看到了如下的一句话:

    iface = hostapd_init(interfaces, config_fname);


    嗯,看来,真正的初始化工作是在hostapd_init 函数中进行,并返回一个描述网络接口的结构体(struct hostapd_iface)。然后我们再深一层的挖掘,找到hostapd_init 的定义(从src/ap/hostapd.c 的第1416 行起)。我们先看那句

    conf = interfaces->config_read_cb(hapd_iface->config_file);</span>


    这个其实就是读取hostapd 的配置文件hostapd.conf 中的配置信息,并保存到hostapd_conf的结构体中去。之后紧接着就是为每个BSS 分配内存空间:

        hapd_iface->num_bss = conf->num_bss; //从配置信息中获取BSS 的个数
        hapd_iface->bss = os_calloc(conf->num_bss, sizeof(struct hostapd_data *)); //分配BSS 列表空间
        if (hapd_iface->bss == NULL) //内存空间分配失败
            goto fail;
        for (i = 0; i < conf->num_bss; i++) {
            hapd = hapd_iface->bss[i] = hostapd_alloc_bss_data(hapd_iface, conf, conf->bss[i]); //初始化每个BSS 数据结构
            if (hapd == NULL) //内存空间分配失败
                goto fail;
            hapd->msg_ctx = hapd; //这句不知道啥意思,不过也无妨
        }


    OK,到这里,网络接口和每个BSS 的基本初始化(即为它们分配内存)的工作结束了。回到main,跳转到第715 行,我们看到了这句话:

        if (hostapd_driver_init(interfaces.iface[i])|| hostapd_setup_interface(interfaces.iface[i]))
            goto out;


    从字面上理解,那就是“初始化每个网络接口的驱动程序”和“设置每个网络接口”。好吧,我们先看hostapd_driver_init 函数是如何定义的。最关键的是在第185 行到第204 行。代码如下:

        params.bssid = b; //BSSID
        params.ifname = hapd->conf->iface; //网络接口名称(比如wlan0)
        params.ssid = hapd->conf->ssid.ssid; //SSID
        params.ssid_len = hapd->conf->ssid.ssid_len; //SSID 长度
        …………(这部分不重要。好吧,我承认我也不清楚是干嘛的……)
        params.own_addr = hapd->own_addr; //网络接口的MAC 地址
        hapd->drv_priv = hapd->driver->hapd_init(hapd, &params); //将以上参数传递给驱动程序


    那么,问题来了,hapd_init 是怎么定义的呢?由于笔者分析的hostapd 是基于nl80211 的,所以hapd_init 指向nl80211 的初始化函数i802_init(定义在src/drivers/nl80211_driver.c 中,本文只分析hostapd 在用户空间的工作原理,至于内核空间是如何工作的,不在本文讨论之列。有兴趣的读者可以查找有关Netlink 和mac80211 的资料。笔者以后也会推出关于mac80211 的学习笔记)。好了,驱动程序初始化完了,下面我们看如何设置每个网络接口。hostapd_setup_interface 函数定义在src/ap/hostapd.c,打开这个文件,跳到大概1315 行,里面就一句setup_interface……好吧,找到大概第1040 行,就是这个函数的定义。里面有一段话是这么说的:

        /*
         * Make sure that all BSSes get configured with a pointer to the same
         * driver interface.
         */
        for (i = 1; i < iface->num_bss; i++) {
            iface->bss[i]->driver = hapd->driver;
            iface->bss[i]->drv_priv = hapd->drv_priv;
        }


    还好有注释,就是确保每个BSS 都使用和物理网卡(我喜欢称呼BSS 为虚拟网卡)同一套驱动程序接口。然后直接看return 吧,哎?怎么又有个setup_interface2?好吧,去看
    看setup_interface2 是怎么回事……找到大概第1113 行,就是它的定义了。这里面主要是设置网卡的硬件模式( a,g,n 等等), 再看return , 我去, 咋还有个hostapd_setup_interface_complete 呢?( 程序作者真磨叽) 好吧,既然说了complete,那应该就是最后一步了吧?那我就硬着头皮继续看看——跳到大概1162 行,就找到了它的定义。这个过程里面主要是设置网卡的信道,速率(根据硬件模式),RTS 参数等等。最后通过hostapd_setup_bss 函数配置每个BSS 。那我们就再看看
    hostapd_setup_bss 是怎么个回事吧( 无语) — — 再跳到大概687 行, 就找到了hostapd_setup_bss 的定义。这个函数需要两个参数,第一个是hostapd_data 结构
    体,描述一个BSS 的配置信息,第二个参数first 则用来表示这个BSS 是否为第一个BSS(通常,第一个BSS 就是wlan0)。先看第一部分代码:


        if (!first || first == -1) { //此BSS 不是第一个BSS 的情况
            if (hostapd_mac_comp_empty(conf->bssid) == 0) { //配置文件没有为此BSS 指定BSSID
                /* Allocate the next available BSSID. */
                do {
                    //将上一个BSS 的BSSID 增加1 做为这个BSS 的BSSID,并且反复这一步骤直到不与其他BSSID 重复为止
                    inc_byte_array(hapd->own_addr, ETH_ALEN);
                } while (mac_in_conf(hapd->iconf, hapd->own_addr));
            } else { //配置文件已经为此BSS 指定BSSID 的情况
                /* Allocate the configured BSSID. */
                os_memcpy(hapd->own_addr, conf->bssid, ETH_ALEN);
                if (hostapd_mac_comp(hapd->own_addr, hapd->iface->bss[0]->own_addr) ==0) {
                    wpa_printf(MSG_ERROR, "BSS '%s' may not have "
                        "BSSID set to the MAC address of "
                        "the radio", conf->iface);
                    return -1;
                }
            }
            hapd->interface_added = 1; //不知道干嘛的
            //在内核中为此BSS 创建一个interface(比如wlan0_0)
            if (hostapd_if_add(hapd->iface->bss[0], WPA_IF_AP_BSS,
                    conf->iface, hapd->own_addr, hapd, &hapd->drv_priv, force_ifname, if_addr,
                    conf->bridge[0] ? conf->bridge : NULL, first == -1)) {
                wpa_printf(MSG_ERROR, "Failed to add BSS (BSSID="
                    MACSTR ")", MAC2STR(hapd->own_addr));
                hapd->interface_added = 0;
                return -1;
            }
        }


    再看第二部分:

        …… //此部分是对这个BSS 的加密方式,SSID 等参数的设置
        //将这个BSS 的interface 激活。(假设这个BSS 的interface 为wlan0_0 的话,那么这句话的意思就相当于在终端下执行“ifconfig wlan0_0 up”命令)
        if (hapd->driver && hapd->driver->set_operstate)
            hapd->driver->set_operstate(hapd->drv_priv, 1);
        return 0;


    啊,终于是return 0 了。好了,重新回到main。至此,网络接口和每个BSS 的初始化工作完成了。


    未完待续……下一篇将详细讲解hostapd的工作机制。

    hostapd源代码分析(二):hostapd的工作机制

    在我的上一篇文章《hostapd源代码分析(一):网络接口和BSS的初始化》中,介绍了两个重要的数据结构hostapd_iface和hostapd_data以及网络接口和BSS的初始化设置的过程。下面,我要在这一篇文章中详细介绍hostapd的工作机制。hostapd的模块结构如下


    从上图中可以看出,hostapd通过一个叫做“event_loop”的核心模块来处理来自各个模块的事件。一开始我觉得hostapd是以多线程的方式来异步处理各个事件的,但其实hostapd从头到尾都是单一线程——是的,我们的hostapd是移植到的MIPS的嵌入式系统上面(我们用的是RouterStation Pro),这么多的线程在嵌入式Linux上面是不现实的。其实,hostapd是通过socket来接受其他模块发来的消息的,并通过select()或者poll()系统调用来轮询各个socket的描述符,一旦发现众多socket描述符中有可用的描述符时,便调用相应的回调函数来处理相关事件。(关于select()或者poll()的用法,请各位读者参考《Advanced Programming in the UNIX Environment》和《UNIX network programming》等书籍中的相关介绍,本文不做赘述)

            首先,我们来看几个关于event loop的数据结构。打开src/utils/eloop.c,部分代码如下:

        //eloop_sock表示一个注册的socket
        struct eloop_sock {
            int sock; //socket的描述符
            void *eloop_data; //回调函数的第一个参数
            void *user_data; //回调函数的第二个参数
            eloop_sock_handler handler; //当事件发生时调用的回调函数入口
            ....
        };
         
        //eloop_sock_table表示已经注册的socket列表
        struct eloop_sock_table {
            int count; //已注册的socket个数
            struct eloop_sock *table; //具体的socket注册信息(描述符,回调函数参数,回调函数入口等)
            ....
        };
         
        //eloop_data表示所有的socket事件
        struct eloop_data {
                int max_sock; //所有socket描述符中的最大值
                int count; /* sum of all table counts */
                struct eloop_sock_table readers; //socket“读事件”列表
                struct eloop_sock_table writers; //socket“写事件”列表
                struct eloop_sock_table exceptions;//socket“意外事件”列表
                ....
        };

    再看几个关于event loop的函数:

        int eloop_register_sock(int sock, eloop_event_type type,
                    eloop_sock_handler handler,
                    void *eloop_data, void *user_data)
        {
            struct eloop_sock_table *table;
         
            assert(sock >= 0);
            table = eloop_get_sock_table(type);
            return eloop_sock_table_add_sock(table, sock, handler,
                             eloop_data, user_data);
        }
         
         
        void eloop_unregister_sock(int sock, eloop_event_type type)
        {
            struct eloop_sock_table *table;
         
            table = eloop_get_sock_table(type);
            eloop_sock_table_remove_sock(table, sock);
        }
         
        int eloop_register_read_sock(int sock, eloop_sock_handler handler,
                         void *eloop_data, void *user_data)
        {
            return eloop_register_sock(sock, EVENT_TYPE_READ, handler,
                           eloop_data, user_data);
        }
         
         
        void eloop_unregister_read_sock(int sock)
        {
            eloop_unregister_sock(sock, EVENT_TYPE_READ);
        }

    我们先看看eloop_register_read_sock函数。很明显,这个函数是把socket描述符和期相对应的回调函数注册到socket描述符表中去。这个函数会调用eloop_register_sock函数,并设置EVENT_TYPE_READ标志,表示这个socket主要用于“读”(也即“接收”)。同理,eloop_unregister_read_sock和eloop_unregister_sock是用来把某个socket描述符从表中删除(一般在hostapd退出的时候调用)。接下来再看eloop是如何执行的,找到eloop_run()函数:

        void eloop_run(void)
        {
         
            fd_set *rfds, *wfds, *efds; //读、写、意外文件描述符集合
            struct timeval _tv;
            int res;
            struct os_reltime tv, now;
         
            rfds = os_malloc(sizeof(*rfds));
            wfds = os_malloc(sizeof(*wfds));
            efds = os_malloc(sizeof(*efds));
            if (rfds == NULL || wfds == NULL || efds == NULL)
                goto out;
         
            while (!eloop.terminate &&
                   (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
                eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
                struct eloop_timeout *timeout;
                timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
                            list);
                if (timeout) {
                    os_get_reltime(&now);
                    if (os_reltime_before(&now, &timeout->time))
                        os_reltime_sub(&timeout->time, &now, &tv);
                    else
                        tv.sec = tv.usec = 0;
         
                    _tv.tv_sec = tv.sec;
                    _tv.tv_usec = tv.usec;
                }
                        //通过FD_SET宏设置“读”、“写”、“意外”的文件描述符集合
                eloop_sock_table_set_fds(&eloop.readers, rfds);
                eloop_sock_table_set_fds(&eloop.writers, wfds);
                eloop_sock_table_set_fds(&eloop.exceptions, efds);
                        //通过select()检查各个文件描述符的状态
                res = select(eloop.max_sock + 1, rfds, wfds, efds,
                         timeout ? &_tv : NULL);
         
                if (res < 0 && errno != EINTR && errno != 0) {
                    wpa_printf(MSG_ERROR, "eloop: %s: %s", <span style="font-family: Arial, Helvetica, sans-serif;">"select"</span>, strerror(errno));
                    goto out;
                }
         
                eloop_process_pending_signals();
         
                /* check if some registered timeouts have occurred */
                        //检查是否有超时发生
                timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
                            list);
                if (timeout) { //如果有超时发生,执行相应的回调函数处理超时事件
                    os_get_reltime(&now);
                    if (!os_reltime_before(&now, &timeout->time)) {
                        void *eloop_data = timeout->eloop_data;
                        void *user_data = timeout->user_data;
                        eloop_timeout_handler handler =
                            timeout->handler;
                        eloop_remove_timeout(timeout);
                        handler(eloop_data, user_data);
                    }
         
                }
         
                if (res <= 0)
                    continue;
         
                        //处理各个socket的事件
                eloop_sock_table_dispatch(&eloop.readers, rfds);
                eloop_sock_table_dispatch(&eloop.writers, wfds);
                eloop_sock_table_dispatch(&eloop.exceptions, efds);
            }
         
            eloop.terminate = 0;
        out:
            os_free(rfds);
            os_free(wfds);
            os_free(efds);
            return;
        }

    通过以上代码,我们知道了hostapd实际上是通过select()机制来轮询检查各个socket的状态,一旦发现某个socket描述符可读、可写、或者是意外的话,就会通过eloop_sock_table_dispach函数来调用相应的回调函数去处理相关事件。其实,源代码中还有用poll()机制的实现,他们的原理都差不多,各位有兴趣的读者可以自行查阅。对了,上面的代码提到了eloop_sock_table_dispatch函数来处理各个socket事件,那么它是怎么实现的呢?我们看一下下面的代码:

        static void eloop_sock_table_dispatch(struct eloop_sock_table *table,
                              fd_set *fds)
        {
            int i;
         
            if (table == NULL || table->table == NULL)
                return;
         
            table->changed = 0;
            for (i = 0; i < table->count; i++) { //检查socket表中每个描述符是否可用(读、写、意外)
                if (FD_ISSET(table->table[i].sock, fds)) {
                                //当某个socket描述符处于可用状态时,调用相应的回调函数来处理
                    table->table[i].handler(table->table[i].sock,
                                table->table[i].eloop_data,
                                table->table[i].user_data);
                    if (table->changed)
                        break;
                }
            }
        }

    怎么样?看到了熟悉的FD_ISSET宏了吧?

            在eloop_run()中,不光处理了各个socket描述符的事件,还有信号(比如按下Ctrl+C),超时(timeout)等事件的处理。这里不再赘述。到此,读者应该理解了hostapd的event loop工作机制了吧?了解了event loop的工作机制以后,我们就可以对hostapd的功能进行扩展了。比如我做的关于OpenFlow AP项目,我把hostapd和控制器(controller)之间交换数据的socket描述符和对应的回调函数加入到socket描述符表中去,hostapd就可以接收来自控制器的指令,并处理OpenFlow协议了。


             下一篇文章将介绍hostapd是如何处理IEEE802.11管理帧的。

    hostapd源代码分析(三):管理帧的收发和处理

    这篇文章我来讲解一下hostapd是如何处理IEEE 802.11管理帧的。我们知道,hostapd主要负责管理工作站(station)认证和接入。因此,它只处理管理帧(Management Frame),并不处理数据帧。802.11的管理帧主要有信标帧(beacon)、探测请求帧(probe request)、探测回应帧(probe response)、请求认证帧(authentication request)、认证回应帧(authentication response)、请求关联帧(association request)和关联回应帧(association response)等。hostapd在初始化的阶段,会将无线网卡转换为AP模式,并且建立监视接口(Monitor Interface,一般是mon.wlan0),这个监视接口主要负责接收802.11管理帧。内核收到管理帧后,就会把它送回用户空间的hostapd来处理。


    一、建立监视接口


    基于nl80211驱动的hostapd在初始化的时候,会调用位于src/driver/driver_nl80211.c的nl80211_create_monitor_interface来建立监视接口。

    drv->monitor_ifidx = nl80211_create_iface(drv, buf, NL80211_IFTYPE_MONITOR, NULL, 0, NULL, NULL, 0); //通过Netlink通知内核新建一个监视接口

    监视接口建立以后,通过socket来接收原始帧,并把socket注册到event loop中(关于event loop,请参考《hostapd源代码分析(二):》)

        //建立原始套接字
        drv->monitor_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
        ...
        //把原始套接字的描述符和回调函数handle_monitor_read注册到event loop
        if (eloop_register_read_sock(drv->monitor_sock, handle_monitor_read, drv, NULL)) {
            wpa_printf(MSG_INFO, "nl80211: Could not register monitor read socket");
            goto error;
        }


    二、接收和处理管理帧


    当原始套接字接收到802.11管理帧后,会调用handle_monitor_read来进一步处理。handle_monitor_read中,会根据Rx或者Tx标志来调用handle_frame或者handle_tx_callback函数来处理。根据我的理解和分析,一般handle_frame处理“请求”帧,比如probe request帧,authentication request帧,等等;handle_tx_callback一般用来处理“回应”帧,比如,authentication response帧,association response帧等。handle_monitor_read函数的部分代码如下。

        len = recv(sock, buf, sizeof(buf), 0); //读取从原始套接字接收的帧
        ...
        while (1) {
            ret = ieee80211_radiotap_iterator_next(&iter); //抽取radiotap报头
            if (ret == -ENOENT)
                break;
            if (ret) {
                wpa_printf(MSG_INFO, "nl80211: received invalid radiotap frame (%d)", ret);
                return;
            }
            switch (iter.this_arg_index) {
            case IEEE80211_RADIOTAP_FLAGS:
                if (*iter.this_arg & IEEE80211_RADIOTAP_F_FCS)
                    len -= 4;
                break;
            case IEEE80211_RADIOTAP_RX_FLAGS: //接收(Rx)帧(一般是“请求帧”)
                rxflags = 1;
                break;
            case IEEE80211_RADIOTAP_TX_FLAGS: //发送(Tx)帧(一般是“回应帧”)
                injected = 1;
                failed = le_to_host16((*(uint16_t *) iter.this_arg)) & IEEE80211_RADIOTAP_F_TX_FAIL;
                break;
            case IEEE80211_RADIOTAP_DATA_RETRIES:
                break;
            case IEEE80211_RADIOTAP_CHANNEL:
                /* TODO: convert from freq/flags to channel number */
                break;
            case IEEE80211_RADIOTAP_RATE:
                datarate = *iter.this_arg * 5;
                break;
            case IEEE80211_RADIOTAP_DBM_ANTSIGNAL:
                ssi_signal = (s8) *iter.this_arg;
                break;
            }
        }
         
        if (rxflags && injected)
             return;
         
        if (!injected)
            handle_frame(drv, buf + iter._max_length, len - iter._max_length, datarate, ssi_signal); //处理“请求帧”
        else
            handle_tx_callback(drv->ctx, buf + iter._max_length, len - iter._max_length, !failed); //处理“发送帧”
        }

    然后,进入handle_frame后,再调用wpa_supplicant_event(位于src/ap/drv_callbacks.c)来进一步处理。对于“请求帧”,会调用hostapd_mgmt_rx(位于src/ap/drv_callbacks.c)。hostapd_mgmt_rx的代码如下:

        static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt)
        {
            struct hostapd_iface *iface = hapd->iface;
            const struct ieee80211_hdr *hdr;
            const u8 *bssid;
            struct hostapd_frame_info fi;
            int ret;
         
            hdr = (const struct ieee80211_hdr *) rx_mgmt->frame;
            bssid = get_hdr_bssid(hdr, rx_mgmt->frame_len); //获取该帧所属的BSSID
            if (bssid == NULL)
                return 0;
         
            hapd = get_hapd_bssid(iface, bssid); //根据BSSID获取相应的BSS
            if (hapd == NULL) { //相应的BSS不存在,则抛弃不处理。
                u16 fc;
                fc = le_to_host16(hdr->frame_control);
         
                /*
                 * Drop frames to unknown BSSIDs except for Beacon frames which
                 * could be used to update neighbor information.
                 */
                if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT &&
                    WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_BEACON)
                    hapd = iface->bss[0];
                else
                    return 0;
            }
         
            os_memset(&fi, 0, sizeof(fi));
            fi.datarate = rx_mgmt->datarate;
            fi.ssi_signal = rx_mgmt->ssi_signal;
         
            if (hapd == HAPD_BROADCAST) { //广播帧
                size_t i;
                ret = 0;
                        //将广播帧发送给每一个BSS
                for (i = 0; i < iface->num_bss; i++) {
                    /* if bss is set, driver will call this function for
                     * each bss individually. */
                    if (rx_mgmt->drv_priv &&
                        (iface->bss[i]->drv_priv != rx_mgmt->drv_priv))
                        continue;
         
                    if (ieee802_11_mgmt(iface->bss[i], rx_mgmt->frame,
                                rx_mgmt->frame_len, &fi) > 0)
                        ret = 1;
                }
            } else //单播帧
                ret = ieee802_11_mgmt(hapd, rx_mgmt->frame, rx_mgmt->frame_len,
                              &fi);
         
            random_add_randomness(&fi, sizeof(fi));
         
            return ret;
        }

    接下来,继续调用ieee802_11_mgmt(位于src/ap/ieee80211.c),根据具体的帧来执行相应的操作。


    AP模式中多重基础服务集(Multi-BSS)下帧的接收

    我们知道,AP模式下的无线网卡可以创建多个基础服务集(Base Service Set, BSS),我们可以为每一个BSS赋予一个SSID,也可以为每一个BSS设置不同的加密方式和密码。通过多个创建多个BSS的方式,就可以让同一个无线路由器提供不同的无线上网服务。那么问题来了,当AP接收到一个帧的时候,如何判断这个帧是否属于这个AP呢?我一开始认为,当AP接收到一个帧的时候,驱动程序便会循环检查每一个BSS的BSSID,看看是否有和接收到的帧的BSSID字段一致的BSS——如果有就ACK,否则就drop掉。后来当在我阅读ath9k驱动程序源代码的时候,才发现我的想法太天真了——一个AP会不停的收到许多帧,如果每收到一个帧,就去轮询每个BSS来确定这个帧是否属于这个AP,那效率实在是太低了。原来AR5212或者以上的无线网卡驱动采用了一种叫做“BSSID掩码(BSSID masking)”的方式,来决定是否ACK接收到的帧。

            假如我们的无线网卡的MAC地址是0001,并且创建了两个BSS(假设为BSS_1和BSS_2),其BSSID分别为0100和1001,那么BSSID掩码就是这样计算的:


    mac = 0001   //无线网卡的MAC为0001

    bssid_mask = 1111;   //掩码的初始值为1111                        

    bssid_1 = 0100  //BSS_1的BSSID为0100

    bssid_2 = 1001  //BSS_2的BSSID为1001

    bssid_mask = bssid_mask & ~(mac ^ bssid_1)        //BSS_1被创建后的掩码

                          = 1111 & ~(0001 ^ 0100)

                          = 1111 & 1010

                          = 1010

    bssid_mask = bssid_mask & ~(mac ^ bssid_2)      //然后BSS_2被创建后的掩码

                           = 1010 & ~(0001 ^ 1001)

                           = 1010 & 0111

                           = 0010


    即,最后的掩码为0010。意思就是,当接收到一个帧的时候,只需要注意它BSSID字段的倒数第二位。假如这个AP接收到一个帧,它的BSSID字段为0110,因为它的倒数第二位是1,那么这个帧就不属于这个AP,就可以直接drop了。算法如下:

    iframe = 0110 //接收到的帧的BSSID字段是0100

    allow = (ifame & bssid_mask) == (bssid_mask & mac) ? true : false

              = (0110 & 0010) == (0010 & 0001) ? true : false

              = 0010 == 0000 ? true : false

              = false    //这个帧不属于这个AP,直接drop!

    再看另一个例子,假如接收到一个帧的BSSID字段为0001,用眼观察,它的倒数第二位是0,那就是属于这个AP的——实际是否如此呢?我们来计算一下:

    ifame = 0001 //接收到的帧的BSSID字段是0001

    allow = (ifame & bssid_mask) == (bssid_mask & mac) ? true : false

              = (0001 & 0010) == (0010 & 0001) ? true : false

              = 0000 == 0000 ? true : false

              = true //这个帧属于这个AP,ACK它!

    的确是这样的——通过BSSID掩码,驱动程序的作者用O(1)的算法秒杀了我之前幼稚又天真的O(n)算法!!


            综上所述,每当AP建立一个BSS,便要更新一下BSSID掩码。当只有一个BSS的时候,掩码就是初始掩码1111。(通常MAC地址是48位,那么初始掩码就是ff:ff:ff:ff:ff:ff)掩码的计算方法为

    bssid_mask = bssid_mask & ~(mac ^ bssid)

    每当接收到一个帧的时候,通过如下的方法来确定这个帧是否属于此AP:

    allow = (ifame & bssid_mask == bssid_mask & mac) ? true : false


    那么,代码是如何实现的呢?在drivers/net/wireless/ath/ath9k/virtual.c中,有如下代码:

        void ath9k_set_bssid_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
        {
            struct ath_wiphy *aphy = hw->priv;
            struct ath_softc *sc = aphy->sc;
            struct ath_common *common = ath9k_hw_common(sc->sc_ah);
            struct ath9k_vif_iter_data iter_data;
            int i;
         
            /*
             * Use the hardware MAC address as reference, the hardware uses it
             * together with the BSSID mask when matching addresses.
             */
            iter_data.hw_macaddr = common->macaddr;
            memset(&iter_data.mask, 0xff, ETH_ALEN);
         
            if (vif)
                ath9k_vif_iter(&iter_data, vif->addr, vif);  //这个函数来计算掩码
            
            /* Get list of all active MAC addresses */
            spin_lock_bh(&sc->wiphy_lock);
            ieee80211_iterate_active_interfaces_atomic(sc->hw, ath9k_vif_iter,
                                   &iter_data);
            for (i = 0; i < sc->num_sec_wiphy; i++) {
                if (sc->sec_wiphy[i] == NULL)
                    continue;
                ieee80211_iterate_active_interfaces_atomic(
                    sc->sec_wiphy[i]->hw, ath9k_vif_iter, &iter_data);
            }
            spin_unlock_bh(&sc->wiphy_lock);
            
            memcpy(common->bssidmask, iter_data.mask, ETH_ALEN);
            ath_hw_setbssidmask(common);
        }

    ath9k_vif_iter是这样的:

        static void ath9k_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
        {
            struct ath9k_vif_iter_data *iter_data = data;
            int i;
         
            for (i = 0; i < ETH_ALEN; i++)
                iter_data->mask[i] &= ~(iter_data->hw_macaddr[i] ^ mac[i]);  //就是上面提到的算法
        }


    在drivers/net/wireless/ath/ath9k/main.c中,有个ath9k_add_interface函数,这个函数在创建一个BSS的时候会被调用,代码如下:

        static int ath9k_add_interface(struct ieee80211_hw *hw,
                           struct ieee80211_vif *vif)
        {
            struct ath_wiphy *aphy = hw->priv;
            struct ath_softc *sc = aphy->sc;
            struct ath_hw *ah = sc->sc_ah;
            struct ath_common *common = ath9k_hw_common(ah);
            struct ath_vif *avp = (void *)vif->drv_priv;
            enum nl80211_iftype ic_opmode = NL80211_IFTYPE_UNSPECIFIED;
            int ret = 0;
         
            mutex_lock(&sc->mutex);
         
            switch (vif->type) {
            case NL80211_IFTYPE_STATION:
                ic_opmode = NL80211_IFTYPE_STATION;
                break;
            case NL80211_IFTYPE_WDS:
                ic_opmode = NL80211_IFTYPE_WDS;
                break;
            case NL80211_IFTYPE_ADHOC:
            case NL80211_IFTYPE_AP:
            case NL80211_IFTYPE_MESH_POINT:
                if (sc->nbcnvifs >= ATH_BCBUF) {
                    ret = -ENOBUFS;
                    goto out;
                }
                ic_opmode = vif->type;
                break;
            default:
                ath_err(common, "Interface type %d not yet supported ",
                    vif->type);
                ret = -EOPNOTSUPP;
                goto out;
            }
         
            ath_dbg(common, ATH_DBG_CONFIG,
                "Attach a VIF of type: %d ", ic_opmode);
         
            /* Set the VIF opmode */
            avp->av_opmode = ic_opmode;
            avp->av_bslot = -1;
         
            sc->nvifs++;
         
            ath9k_set_bssid_mask(hw, vif); //计算新的BSSID掩码
         
            if (sc->nvifs > 1)
                goto out; /* skip global settings for secondary vif */
         
            if (ic_opmode == NL80211_IFTYPE_AP) {
                ath9k_hw_set_tsfadjust(ah, 1);
                sc->sc_flags |= SC_OP_TSF_RESET;
            }
         
            /* Set the device opmode */
            ah->opmode = ic_opmode;
         
            /*
             * Enable MIB interrupts when there are hardware phy counters.
             * Note we only do this (at the moment) for station mode.
             */
            if ((vif->type == NL80211_IFTYPE_STATION) ||
                (vif->type == NL80211_IFTYPE_ADHOC) ||
                (vif->type == NL80211_IFTYPE_MESH_POINT)) {
                if (ah->config.enable_ani)
                    ah->imask |= ATH9K_INT_MIB;
                ah->imask |= ATH9K_INT_TSFOOR;
            }
         
            ath9k_hw_set_interrupts(ah, ah->imask);
         
            if (vif->type == NL80211_IFTYPE_AP    ||
                vif->type == NL80211_IFTYPE_ADHOC) {
                sc->sc_flags |= SC_OP_ANI_RUN;
                ath_start_ani(common);
            }
         
        out:
            mutex_unlock(&sc->mutex);
            return ret;
        }

    由此可见,驱动程序在每次创建一个BSS——驱动程序的层面上看就是一个网络接口(network interface)——便会更新一下BSSID掩码。


            其实,BSSID掩码也是有缺陷的。还拿刚才的例子来说,MAC地址为0001的无线网卡有两个BSS,其BSSID分别为
    0100和1001,根据上面的算法,计算的掩码为0010。假如此时收到一个BSSID字段为1100的帧,根据如下算法计算:

    allow = (ifame_bssid & bssid_mask) == (bssid_mask & mac) ? true : false

             = (1100 & 0010) == (0010 & 0001) ? true : false

             = 0000 == 0000 ? true : false

             = true

    但是,此AP根本不存在BSSID为1100的BSS!假如此时在同一个信道里面,另一个AP恰好存在一个BSSID为1100的BSS的话,那么这两个AP将同时ACK这个帧,其后果就是哪个ACK帧也不可能被客户端接收到——客户端只好重新发送这个帧——从而形成恶性循环。但是除非是管理员故意这么设置,可能性不大——一般没有人会在同一个信道和覆盖范围里面设置多个AP吧。
    ---------------------
    作者:手动阀隧道
    来源:CSDN
    原文:https://blog.csdn.net/tmwiajd/article/details/43737185
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    在Win10中通过命令行打开UWP应用
    前端学习Docker
    Puppeteer的使用
    taro教程
    22种开源Vue模板和主题框架「干货」
    36种免费React模板和主题「干货」
    移动端1px显示异常解决方案
    前端性能优化(二)
    Vue.set()和this.$set()源码解析
    2018年8月7日 乐视2 X621 刷机包下载链接
  • 原文地址:https://www.cnblogs.com/listenerln/p/10107133.html
Copyright © 2020-2023  润新知