一、無線資訊傳遞——user space下的hostapd
系列說明
大致簡單瞭解了無線通訊在底層的組成,接收和傳送之後,接下來希望能更系統地對資訊從user space至kernel space,至kernel對資訊的使用,傳送和接收等一系列步驟進行總結說明。以便後續將wifi的ssid,密碼,加密方式等需要的資訊填充入beacon幀中進行傳送端組包,接收端解包的處理。
這一章先從user space層的hostapd開始說起,主要目的在於瞭解hostapd是怎麼將hostapd.conf配置檔案下的配置內容傳遞給底層,讓底層根據這些資訊做相應處理的。
以下連結是對我瞭解hostapd過程中起到很大幫助的網址連結:
hostapd原始碼分析(一):網路介面和BSS的初始化
hostapd原始碼分析(二):hostapd的工作機制
hostapd原始碼分析(三):管理幀的收發和處理
wifi相關知識點網址連結
該篇主要對:hostapd作用,hostapd網路介面和BSS初始化,hostapd資訊傳輸機制,socket註冊回撥。三個模組進行說明。
一、hostapd作用
hostapd 是一個使用者態用於AP和認證伺服器的守護程序。它實現了IEEE 802.11相關的接入管理,IEEE 802.1X/WPA/WPA2/EAP 認證, RADIUS客戶端,EAP伺服器和RADIUS 認證伺服器。Linux下支援的驅動有:Host AP,madwifi,基於mac80211的驅動。
從上面的一段話中可以瞭解到,hostapd僅是用於當裝置處於ap模式時,進行裝置底層的初始化,對需要連線該熱點的裝置認證及管理。並沒有參與相關底層協議幀的組合和傳送。
二、hostapd的網路介面和BSS初始化
2.1、簡要說明
在該模組中,hostapd主要進行網路介面及網路網絡卡的初始化。初始化相應的接收回調函式,網絡卡相關執行資訊的設定以及建立與kernel space層的socket控制代碼。
2.2、hostapd初始化
瞭解hostapd初始化之前首先了解以下兩個結構體,因為這兩個結構體在hostapd中使用的次數最多,也就是扮演的角色最關鍵。
/*--------------hostapd中兩個關鍵的結構體----------*/
/**
* struct hostapd_iface - hostapd per-interface data structure
*/
struct hostapd_iface {
struct hostapd_config *conf: 儲存對網路介面的配置(從配置檔案hostapd.conf中載入)
sizt_t num_bss: 此介面下轄的BSS 個數
struct hostapd_data **bss:BSS 列表,描述各BSS 的配置
。。。
};
/**
* struct hostapd_data - hostapd per-BSS data structure
*/
struct hostapd_data {
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)
。。。
};
//!!!以上結構體中寫出的是最主要的結構體變數,其他的結構體變數暫時不需要重點了解。
接下來觀察main函式中的幾個重要的初始化。將以main中介面為入口進行展開說明。
/*----------------hostapd的main函式------------------*/
int main(int argc, char** argv)
{
...
if (hostapd_global_init(&interfaces, entropy_file)) //global的初始化
return -1;
/* Initialize interfaces */
for (i = 0; i < interfaces.count; i++)
{
interfaces.iface[i] = hostapd_interface_init(&interfaces, //對每個網路介面(物理網絡卡)初始化
argv[optind + i],
debug);
if (!interfaces.iface[i])
goto out;
}
if (hostapd_global_run(&interfaces, daemonize, pid_file)) //下一模組再進行說明
goto out;
...
}
/*----------------hostapd_global_init函式---------------*/
//global 初始化介面
static int hostapd_global_init(struct hapd_interfaces *interfaces,
const char *entropy_file)
{
int i;
os_memset(&global, 0, sizeof(global));
hostapd_logger_register_cb(hostapd_logger_cb);
if (eap_server_register_methods()) { //初始化認證伺服器演算法
wpa_printf(MSG_ERROR, "Failed to register EAP methods");
return -1;
}
if (eloop_init()) { //eloop初始化 主要為初始化eloop_data結構體
wpa_printf(MSG_ERROR, "Failed to initialize event loop");
return -1;
}
random_init(entropy_file);
eloop_register_signal_terminate(handle_term, interfaces); //相關訊號處理回撥初始化
for (i = 0; wpa_drivers[i]; i++)
global.drv_count++;
if (global.drv_count == 0) {
wpa_printf(MSG_ERROR, "No drivers enabled");
return -1;
}
global.drv_priv = os_calloc(global.drv_count, sizeof(void *)); //申請driver驅動私有資料空間
if (global.drv_priv == NULL)
return -1;
return 0;
}
/*-------------hostapd_interface_init函式--------------*/
//hostapd_interface_init:網絡卡初始化
static struct hostapd_iface *
hostapd_interface_init(struct hapd_interfaces *interfaces,
const char *config_fname, int debug)
{
struct hostapd_iface *iface;
int k;
wpa_printf(MSG_ERROR, "Configuration file: %s", config_fname);
iface = hostapd_init(config_fname); //用於初始化網絡卡,iface為hostapd_init的(struct hostapd_iface結構體)返回值
if (!iface)
return NULL;
iface->interfaces = interfaces;
for (k = 0; k < debug; k++) {
if (iface->bss[0]->conf->logger_stdout_level > 0)
iface->bss[0]->conf->logger_stdout_level--;
}
if (iface->conf->bss[0].iface[0] != 0 ||
hostapd_drv_none(iface->bss[0]))
{
if (hostapd_driver_init(iface) || //初始化網路驅動
hostapd_setup_interface(iface)) { //設定網路介面
hostapd_interface_deinit_free(iface);
return NULL;
}
}
return iface;
}
/*---------------hostapd_init函式----------------*/
/**
* hostapd_init - Allocate and initialize per-interface data
* @config_file: Path to the configuration file
* Returns: Pointer to the allocated interface data or %NULL on failure
*
* This function is used to allocate main data structures for per-interface
* data. The allocated data buffer will be freed by calling
* hostapd_cleanup_iface().
*/
static struct hostapd_iface * hostapd_init(const char *config_file)
{
struct hostapd_iface *hapd_iface = NULL;
struct hostapd_config *conf = NULL;
struct hostapd_data *hapd;
size_t i;
hapd_iface = os_zalloc(sizeof(*hapd_iface));
if (hapd_iface == NULL)
goto fail;
hapd_iface->config_fname = os_strdup(config_file);
if (hapd_iface->config_fname == NULL)
goto fail;
conf = hostapd_config_read(hapd_iface->config_fname); //用於讀取配置檔案中的配置資訊,一般的配置檔名(hostapd.conf)
if (conf == NULL)
goto fail;
hapd_iface->conf = conf;
hapd_iface->num_bss = conf->num_bss; //從配置中獲取BSS的個數
hapd_iface->bss = os_calloc(conf->num_bss, //分配BSS列表空間
sizeof(struct hostapd_data *));
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, //初始化每個BSS資料結構
&conf->bss[i]);
if (hapd == NULL)
goto fail;
hapd->msg_ctx = hapd;
hapd->setup_complete_cb = hostapd_setup_complete_cb;
}
return hapd_iface;
fail:
if (conf)
hostapd_config_free(conf);
if (hapd_iface) {
os_free(hapd_iface->config_fname);
os_free(hapd_iface->bss);
os_free(hapd_iface);
}
return NULL;
}
/*-------------hostapd_driver_init函式---------------*/
static int hostapd_driver_init(struct hostapd_iface *iface)
{
struct wpa_init_params params;
size_t i;
struct hostapd_data *hapd = iface->bss[0];
struct hostapd_bss_config *conf = hapd->conf;
u8 *b = conf->bssid;
struct wpa_driver_capa capa;
if (hapd->driver == NULL || hapd->driver->hapd_init == NULL) {
wpa_printf(MSG_ERROR, "No hostapd driver wrapper available");
return -1;
}
/* Initialize the driver interface */
if (!(b[0] | b[1] | b[2] | b[3] | b[4] | b[5]))
b = NULL;
os_memset(¶ms, 0, sizeof(params));
for (i = 0; wpa_drivers[i]; i++) {
if (wpa_drivers[i] != hapd->driver)
continue;
if (global.drv_priv[i] == NULL && wpa_drivers[i]->global_init) {
global.drv_priv[i] = wpa_drivers[i]->global_init(); //初始化全域性驅動私有資料
if (global.drv_priv[i] == NULL) {
wpa_printf(MSG_ERROR, "Failed to initialize "
"driver '%s'",
wpa_drivers[i]->name);
return -1;
}
}
params.global_priv = global.drv_priv[i]; //指標賦值
break;
}
params.bssid = b; //bssid地址
params.ifname = hapd->conf->iface; //網路介面名稱(比如lan0)
params.ssid = hapd->conf->ssid.ssid; //ssid名稱
params.ssid_len = hapd->conf->ssid.ssid_len; //ssid位元組度
params.test_socket = hapd->conf->test_socket;
params.use_pae_group_addr = hapd->conf->use_pae_group_addr;
params.num_bridge = hapd->iface->num_bss;
params.bridge = os_calloc(hapd->iface->num_bss, sizeof(char *));
if (params.bridge == NULL)
return -1;
for (i = 0; i < hapd->iface->num_bss; i++) {
struct hostapd_data *bss = hapd->iface->bss[i];
if (bss->conf->bridge[0])
params.bridge[i] = bss->conf->bridge;
}
params.own_addr = hapd->own_addr; //網路介面mac地址
hapd->drv_priv = hapd->driver->hapd_init(hapd, ¶ms); //通過hapd_init將以上這些配置資訊(params)傳遞給底層:後續積累netlink以及mac80211的相關操作
os_free(params.bridge);
if (hapd->drv_priv == NULL) {
wpa_printf(MSG_ERROR, "%s driver initialization failed.",
hapd->driver->name);
hapd->driver = NUL;
return -1;
}
if (hapd->driver->get_capa && hapd->driver->get_capa(hapd->drv_priv, &capa) == 0) {
iface->drv_flags = capa.flags;
iface->probe_resp_offloads = capa.probe_resp_offloads;
iface->extended_capa = capa.extended_capa;
iface->extended_capa_mask = capa.extended_capa_mask;
iface->extended_capa_len = capa.extended_capa_len;
}
return 0;
}
/*------------hostapd_setup_interface函式---------------*/
/**
* hostapd_setup_interface - Setup of an interface
* @iface: Pointer to interface data.
* Returns: 0 on success, -1 on failure
*
* Initializes the driver interface, validates the configuration,
* and sets driver parameters based on the configuration.
* Flushes old stations, sets the channel, encryption,
* beacons, and WDS links based on the configuration.
*/
int hostapd_setup_interface(struct hostapd_iface *iface)
{
int ret;
ret = setup_interface(iface); //設定BSS資訊
if (ret) {
wpa_printf(MSG_ERROR, "%s: Unable to setup interface.",
iface->bss[0]->conf->iface);
return -1;
}
return 0;
}
static int setup_interface(struct hostapd_iface *iface)
{
struct hostapd_data *hapd = iface->bss[0];
size_t i;
char country[4];
/*
* Make sure that all BSSes get configured with a pointer to the same
* driver interface.
* 即用於確保每一個bss和網路網絡卡用於同一套驅動配置,以及相同的驅動私有資料
*/
for (i = 1; i < iface->num_bss; i++) {
iface->bss[i]->driver = hapd->driver;
iface->bss[i]->drv_priv = hapd->drv_priv;
}
#if !defined(CONFIG_WAPI) // Arthur can do BSSID range, disable bitmap check, borrow CONFIG_WAPI to mark this change
if (hostapd_validate_bssid_configuration(iface))
return -1;
#endif
if (hapd->iconf->country[0] && hapd->iconf->country[1]) { //設定網絡卡country
os_memcpy(country, hapd->iconf->country, 3);
country[3] = '\0';
if (hostapd_set_country(hapd, country) < 0) {
wpa_printf(MSG_ERROR, "Failed to set country code");
return -1;
}
}
if (hostapd_get_hw_features(iface)) {
/* Not all drivers support this yet, so continue without hw
* feature data. */
} else {
int ret = hostapd_select_hw_mode(iface); //設定網絡卡模式
if (ret < 0) {
wpa_printf(MSG_ERROR, "Could not select hw_mode and "
"channel. (%d)", ret);
return -1;
}
if (ret == 1) {
wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback (ACS)");
return 0;
}
ret = hostapd_check_ht_capab(iface);
if (ret < 0)
return -1;
if (ret == 1) {
wpa_printf(MSG_DEBUG, "Interface initialization will "
"be completed in a callback");
return 0;
}
}
return hostapd_setup_interface_complete(iface, 0);
}
int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err)
{
//這裡進行設定網絡卡通道,速率,RTS引數等
for (j = 0; j < iface->num_bss; j++) {
hapd = iface->bss[j];
if (j)
os_memcpy(hapd->own_addr, prev_addr, ETH_ALEN);
if (hostapd_setup_bss(hapd, j == 0)) //配置每個bss
goto error;
if (hostapd_mac_comp_empty(hapd->conf->bssid) == 0)
prev_addr = hapd->own_addr;
}
hostapd_tx_queue_params(iface);
ap_list_init(iface); //ap列表初始化
if (hostapd_driver_commit(hapd) < 0) {
wpa_printf(MSG_ERROR, "%s: Failed to commit driver "
"configuration", __func__);
goto error;
}
/*
* WPS UPnP module can be initialized only when the "upnp_iface" is up.
* If "interface" and "upnp_iface" are the same (e.g., non-bridge
* mode), the interface is up only after driver_commit, so initialize
* WPS after driver_commit.
*/
for (j = 0; j < iface->num_bss; j++) {
if (hostapd_init_wps_complete(iface->bss[j]))
return -1;
}
if (hapd->setup_complete_cb)
hapd->setup_complete_cb(hapd->setup_complete_cb_ctx);
wpa_printf(MSG_DEBUG, "%s: Setup of interface done.",
iface->bss[0]->conf->iface);
return 0;
error:
wpa_printf(MSG_ERROR, "Interface initialization failed");
eloop_terminate();
return -1;
}
/**
* hostapd_setup_bss - Per-BSS setup (initialization)
* @hapd: Pointer to BSS data
* @first: Whether this BSS is the first BSS of an interface
*
* This function is used to initialize all per-BSS data structures and
* resources. This gets called in a loop for each BSS when an interface is
* initialized. Most of the modules that are initialized here will be
* deinitialized in hostapd_cleanup().
*/
//其中包含兩個引數
//hapd為hostapd_data結構體,描述BSS資訊
//first表示這個BSS是否為第一個BSS
static int hostapd_setup_bss(struct hostapd_data *hapd, int first)
{
...
if (!first || first == -1) { //此BSS 不是第一個BSS 的情況
if (hostapd_mac_comp_empty(conf->bssid) == 0) { //配置檔案沒有為此BSS 指定BSSID
/* Allocate the next available BSSID. */
do {
inc_byte_array(hapd->own_addr, ETH_ALEN); //將上一個BSS 的BSSID 增加1 做為這個BSS 的BSSID,並且反覆這一步驟直到不與其他BSSID 重複為止
} 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;
if (hostapd_if_add(hapd->iface->bss[0], WPA_IF_AP_BSS, //在核心中為此BSS 建立一個interface(比如wlan0_0)
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;
}
三、hostapd的訊號傳遞機制
在上一模組,我們說明了hostapd的初始化,同時藉此也對main函式中的介面呼叫有了一部分的認識。
接下來,進行main函式最後一個關鍵介面的說明,即hostapd_global_run(),該介面處於main函式中的最後一階段。
在瞭解eloop程式碼之前,先對下面一副圖進行簡單的瞭解:
從上圖中可以看出,hostapd通過一個叫做“event_loop”的核心模組來處理來自各個模組的事件。之前以為hostapd與一般的程序一樣,會使用多執行緒的方式進行各種小程式,各種認證程式的處理,但其實在上圖中可以看出其實hostapd是以一個單執行緒的方式執行的。
hostapd是通過socket來接受其他模組發來的訊息的,並通過select()或者poll()系統呼叫來輪詢各個socket的描述符,一旦發現眾多socket描述符中有可用的描述符時,便呼叫相應的回撥函式來處理相關事件。
先對hostapd訊息處理機制中的幾個關鍵結構體以及關鍵介面進行程式碼說明:
/*
* 檔案:src/utils/eloop.c
* hostapd訊號傳遞關鍵結構體
*/
//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“意外事件”列表
....
};
/*----------hostapd訊號傳遞關鍵介面-------------*/
int eloop_register_sock(int sock, eloop_event_type type,
eloop_sock_handler handler,
void *eloop_data, void *user_data)
{
truct eloop_sock_table *table;
ssert(sock >= 0);
able = eloop_get_sock_table(type);
eturn eloop_sock_table_add_sock(table, sock, handler,
oop_data, user_data);
}//socket描述符從表中刪除(一般在hostapd退出的時候呼叫)
void eloop_unregister_sock(int sock, eloop_event_type type)
{
truct eloop_sock_table *table; table = eloop_get_sock_table(type);
eloop_sock_table_remove_sock(table, sock);
} //把socket描述符和期相對應的回撥函式註冊到socket描述符表中去,//這個函式會呼叫eloop_register_sock函式,並設定EVENT_TYPE_READ標誌,表示這個socket主要用於“讀”(也即“接收”)。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);
}
接下來對hostapd程式碼說明:
int main(int argc, char** argv)
{
... //包含其他一些常用訊號的註冊處理,如ctrl+c的訊號,超時訊號等
hostapd_global_ctrl_iface_init(&interfaces); //建立socket,用於其他模組的訊息傳遞
if (hostapd_global_run(&interfaces, daemonize, pid_file)) //main函式中內容初始化完成後,執行while迴圈操作
goto out;
...
}
/*---------hostapd_global_ctrl_iface_init函式---------*/
int hostapd_global_ctrl_iface_init(struct hapd_interfaces *interface)
{
...
s = socket(PF_UNIX, SOCK_DGRAM, 0); //建立通訊socket
if (s < 0) {
perror("socket(PF_UNIX)");
goto fail;
}
os_memset(&addr, 0, sizeof(addr));
#ifdef __FreeBSD__
addr.sun_len = sizeof(addr);
#endif /* __FreeBSD__ */
addr.sun_family = AF_UNIX;
fname = hostapd_global_ctrl_iface_path(interface);
if (fname == NULL)
goto fail;
os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { //bind 描述符
wpa_printf(MSG_DEBUG, "ctrl_iface bind(PF_UNIX) failed: %s",
strerror(errno));
if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { //connect
wpa_printf(MSG_DEBUG, "ctrl_iface exists, but does not"
" allow connections - assuming it was left"
"over from forced program termination");
if (unlink(fname) < 0) {
perror("unlink[ctrl_iface]");
wpa_printf(MSG_ERROR, "Could not unlink "
"existing ctrl_iface socket '%s'",
fname);
goto fail;
}
if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("bind(PF_UNIX)");
goto fail;
}
wpa_printf(MSG_DEBUG, "Successfully replaced leftover "
"ctrl_iface socket '%s'", fname);
} else {
wpa_printf(MSG_INFO, "ctrl_iface exists and seems to "
"be in use - cannot override it");
wpa_printf(MSG_INFO, "Delete '%s' manually if it is "
"not used anymore", fname);
os_free(fname);
fname = NULL;
goto fail;
}
}
if (interface->ctrl_iface_group && chown(fname, -1, interface->ctrl_iface_group) < 0) {
perror("chown[ctrl_interface]");
goto fail;
}
if (chmod(fname, S_IRWXU | S_IRWXG) < 0) {
perror("chmod[ctrl_interface/ifname]");
goto fail;
}
os_free(fname);
interface->global_ctrl_sock = s;
eloop_register_read_sock(s, hostapd_global_ctrl_iface_receive, interface, NULL);
return 0;
fail:
if (s >= 0)
close(s);
if (fname) {
unlink(fname);
os_free(fname);
}
return -1;
}
/*-------------hostapd_global_run------------*/
static int hostapd_global_run(struct hapd_interfaces *ifaces, int daemonize,
const char *pid_file)
{
...
eloop_run();
return 0;
}
/*
說明:
*hostapd實際上是通過select()機制來輪詢檢查各個socket的狀態,一旦發現某個socket描述符可讀、可寫、或者是意外的話,就會通過eloop_sock_table_dispach函式來呼叫相應的回撥函式去處理相關事件。
*/
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)) { //執行eloop迴圈
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();
//檢查是否有超時發生
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;
}
//通過FD_ISSET巨集進行判斷是否有描述符被啟用
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;
}
}
}
四、hostapd管理幀處理回撥樣例
上面介紹了hostapd自身的初始化和外部模組聯絡的資訊傳遞機制,最後再通過hostapd對管理幀的處理簡要說明在接收到外部傳遞過來的資訊時的回撥處理機制。
首先,之前的初始化呼叫hostapd_interface_init進行網絡卡初始化,其中再呼叫hostapd_init介面,然後hostapd_config_read,最後至介面hostapd_config_fill中通過讀取配置檔案中的“driver”關鍵字。將結構體wpa_drivers賦值給conf->driver,而結構體wpa_drivers中包含函式指標hapd_init指向i802_init,該i802_init介面中進行呼叫wpa_driver_nl80211_set_mode,nl80211_setup_ap,nl80211_create_monitor_interface來建立監視介面。該監視介面建立以後,通過socket接收原始幀,並將socket註冊到event loop(eloop)中。
下圖進行具體的監視介面在程式中的建立和註冊流程說明:
下面進行更加具體的程式碼說明流程:
/*---nl80211_create_monitor_interface---*/
static int
nl80211_create_monitor_interface(struct wpa_driver_nl80211_data *drv)
{
char buf[IFNAMSIZ];
struct sockaddr_ll ll;
int optval;
socklen_t optlen;
if (drv->monitor_ifidx >= 0) {
drv->monitor_refcount++;
return 0;
}
if (os_strncmp(drv->first_bss.ifname, "p2p-", 4) == 0) {
/*
* P2P interface name is of the format p2p-%s-%d. For monitor
* interface name corresponding to P2P GO, replace "p2p-" with
* "mon-" to retain the same interface name length and to
* indicate that it is a monitor interface.
*/
snprintf(buf, IFNAMSIZ, "mon-%s", drv->first_bss.ifname + 4);
} else {
/* Non-P2P interface with AP functionality. */
snprintf(buf, IFNAMSIZ, "mon.%s", drv->first_bss.ifname);
}
buf[IFNAMSIZ - 1] = '\0';
drv->monitor_ifidx = //通過netlink新建一個監聽介面
nl80211_create_iface(drv, buf, NL80211_IFTYPE_MONITOR, NULL, 0);
if (drv->monitor_ifidx == -EOPNOTSUPP) {
/*
* This is backward compatibility for a few versions of
* the kernel only that didn't advertise the right
* attributes for the only driver that then supported
* AP mode w/o monitor -- ath6kl.
*/
wpa_printf(MSG_DEBUG, "nl80211: Driver does not support "
"monitor interface type - try to run without it");
drv->device_ap_sme = 1;
}
if (drv->monitor_ifidx < 0)
return -1;
if (linux_set_iface_flags(drv->global->ioctl_sock, buf, 1))
goto error;
memset(&ll, 0, sizeof(ll));
ll.sll_family = AF_PACKET;
ll.sll_ifindex = drv->monitor_ifidx;
drv->monitor_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); //新建socket描述符
if (drv->monitor_sock < 0) {
perror("socket[PF_PACKET,SOCK_RAW]");
goto error;
}
if (add_monitor_filter(drv->monitor_sock)) {
wpa_printf(MSG_INFO, "Failed to set socket filter for monitor "
"interface; do filtering in user space");
/* This works, but will cost in performance. */
}
if (bind(drv->monitor_sock, (struct sockaddr *) &ll, sizeof(ll)) < 0) {
perror("monitor socket bind");
goto error;
}
optlen = sizeof(optval);
optval = 20;
if (setsockopt(drv->monitor_sock, SOL_SOCKET, SO_PRIORITY, &optval, optlen)) {
perror("Failed to set socket priority");
goto error;
}
if (eloop_register_read_sock(drv->monitor_sock, handle_monitor_read, drv, NULL)) { //將原始套接字描述符和handle_monitor_read回撥介面註冊至eloop
printf("Could not register monitor read socket\n");
goto error;
}
return 0;
error:
nl80211_remove_monitor_interface(drv);
return -1;
}
/*----handle_monitor_read---*/
/*
*handle_monitor_read中,會根據IEEE80211_RADIOTAP_RX_FLAGS或者IEEE80211_RADIOTAP_TX_FLAGS標誌來呼叫handle_frame或者handle_tx_callback函式來處理。
*一般handle_frame處理“請求”幀,比如probe request幀,authentication request幀,等等;handle_tx_callback一般用來處理“迴應”幀,比如,authentication response幀,association response幀等
*/
static void handle_monitor_read(int sock, void *eloop_ctx, void *sock_ctx)
{
struct wpa_driver_nl80211_data *drv = eloop_ctx;
int len;
unsigned char buf[3000];
struct ieee80211_radiotap_iterator iter;
int ret;
int datarate = 0, ssi_signal = 0;
int injected = 0, failed = 0, rxflags = 0;
len = recv(sock, buf, sizeof(buf), 0); //接收原始套接字接收的幀資料
if (len < 0) {
perror("recv");
return;
}
if (ieee80211_radiotap_iterator_init(&iter, (void*)buf, len)) {
printf("received invalid radiotap frame\n");
return;
}
while (1) {
ret = ieee80211_radiotap_iterator_next(&iter); //抽取radiotap頭
if (ret == -ENOENT)
break;
if (ret) {
printf("received invalid radiotap frame (%d)\n", 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); //然後繼續呼叫ieee802_11_mgmt(位於src/ap/ieee80211.c),根據具體的幀來執行相應的操作
random_add_randomness(&fi, sizeof(fi));
return ret;
}