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, ¶ms); //將以上引數傳遞給驅動程式
那麼,問題來了,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\n",
vif->type);
ret = -EOPNOTSUPP;
goto out;
}
ath_dbg(common, ATH_DBG_CONFIG,
"Attach a VIF of type: %d\n", 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
版權宣告:本文為博主原創文章,轉載請附上博文連結!