hostapd启动流程(二)

  • acs_init
    • acs_request_scan
      • hostapd_driver_scan
        • driver_nl80211_scan2
          • wpa_driver_nl80211_scan
  • 事件NL80211_CMD_TRIGGER_SCAN
  • 事件NL80211_CMD_NEW_SCAN_RESULTS
  • ieee80211n_check_scan函数,检查信道
    • hostapd_driver_get_scan_results
      • wpa_driver_nl80211_get_scan_results
    • ieee80211n_check_40mhz_2g4
      • check_40mhz_2g4
        • check_20mhz_bss
    • ieee80211n_allowed_ht40_channel_pair
      • allowed_ht40_channel_pair

继续上篇:https://blog.csdn.net/krokodil98/article/details/116601818。上篇写了不需要acs扫描,固定信道启动的流程,本篇分析另一条路径,即acs扫描信道流程。

hostapd_setup_interface -》setup_interface-》setup_interface2-》1. hostapd_select_hw_mode-》hostapd_check_chans-》acs_init,本篇从这里开始
... ... ... ...  ... ... ... ...

第二部分:
这部分从acs_init开始。

acs_init

enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
{int err;wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {... ...}if (!iface->current_mode)return HOSTAPD_CHAN_INVALID;acs_cleanup(iface);//清除掉iface里的acs扫描流程用到的变量,如acs_num_completed_scanserr = acs_request_scan(iface);//开启acs扫描流程if (err < 0)return HOSTAPD_CHAN_INVALID;hostapd_set_state(iface, HAPD_IFACE_ACS);//设置接口状态wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_STARTED);return HOSTAPD_CHAN_ACS;//让上层函数知道需要等到acs扫描流程
}

真正实现扫描操作的是acs_request_scan函数。

acs_request_scan

static int acs_request_scan(struct hostapd_iface *iface)
{struct wpa_driver_scan_params params;struct hostapd_channel_data *chan;int i, *freq;os_memset(&params, 0, sizeof(params));//创建存储channel信息用的变量params.freqs = os_calloc(iface->current_mode->num_channels + 1, sizeof(params.freqs[0]));if (params.freqs == NULL)return -1;freq = params.freqs;for (i = 0; i < iface->current_mode->num_channels; i++) {//将iface支持的信道号转成对应的信道频率存储到params.freqs里chan = &iface->current_mode->channels[i];if (chan->flag & HOSTAPD_CHAN_DISABLED)continue;if (!is_in_chanlist(iface, chan))continue;*freq++ = chan->freq;}*freq = 0;iface->scan_cb = acs_scan_complete;//将scan函数赋值成acs_scan_complete,之后执行scan_cb的时候就会调acs_scan_completewpa_printf(MSG_DEBUG, "ACS: Scanning %d / %d", iface->acs_num_completed_scans + 1, iface->conf->acs_num_scans);//这里输出当前执行到第几次acs扫描,指定次数通过在hostapd.conf里设置acs_num_scans = 6实现if (hostapd_driver_scan(iface->bss[0], &params) < 0) {wpa_printf(MSG_ERROR, "ACS: Failed to request initial scan");acs_cleanup(iface);os_free(params.freqs);return -1;}os_free(params.freqs);return 0;
}

其中,每次扫描完后,acs_scan_complete最终会被调用到。在acs_scan_complete里,会判断是否已完成了acs_num_completed_scans 次扫描。若次数不够,则再次调用acs_request_scan。acs_scan_complete中的这部分代码如下:

if (++iface->acs_num_completed_scans < iface->conf->acs_num_scans) {err = acs_request_scan(iface);if (err) {wpa_printf(MSG_ERROR, "ACS: Failed to request scan");goto fail;}return;}

开始单次acs扫描,进入hostapd_driver_scan函数。

hostapd_driver_scan

int hostapd_driver_scan(struct hostapd_data *hapd,struct wpa_driver_scan_params *params)
{if (hapd->driver && hapd->driver->scan2)//如果scan2注册了,则调用该函数return hapd->driver->scan2(hapd->drv_priv, params);return -1;
}

这里的scan2注册赋值在:driver_nl80211.c

const struct wpa_driver_ops wpa_driver_nl80211_ops = {.name = "nl80211",.desc = "Linux nl80211/cfg80211",... ....scan2 = driver_nl80211_scan2,... ....get_scan_results2 = wpa_driver_nl80211_get_scan_results,... ...};

知这一句scan2实际调用到的driver_nl80211_scan2函数。

driver_nl80211_scan2
static int driver_nl80211_scan2(void *priv,struct wpa_driver_scan_params *params)
{struct i802_bss *bss = priv;
#ifdef CONFIG_DRIVER_NL80211_QCA... ...
#endif /* CONFIG_DRIVER_NL80211_QCA */return wpa_driver_nl80211_scan(bss, params);
}

driver_nl80211_scan2调到下一层函数wpa_driver_nl80211_scan

wpa_driver_nl80211_scan
int wpa_driver_nl80211_scan(struct i802_bss *bss,struct wpa_driver_scan_params *params)
{struct wpa_driver_nl80211_data *drv = bss->drv;int ret = -1, timeout;struct nl_msg *msg = NULL;wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: scan request");drv->scan_for_auth = 0;if (TEST_FAIL())return -1;msg = nl80211_scan_common(bss, NL80211_CMD_TRIGGER_SCAN, params);//调用IEEE 802.11 的被动扫描。除被动扫描外,函数还支持主动扫描,即向特定目的地SSID发送probe request#if 0 其中,struct wpa_driver_scan_params {/* ssids - SSIDs to scan for */struct wpa_driver_scan_ssid ssids[WPAS_MAX_SCAN_SSIDS];/* num_ssids - Number of entries in ssids array* Zero indicates a request for a passive scan.*/size_t num_ssids;... ...}#endif//在nl80211_scan_common里,通过判断params->num_ssids是否为0来确定是主动还是被动扫描。默认不配置的话是0if (!msg)return -1;... ...ret = send_and_recv_msgs(drv, msg, NULL, NULL);msg = NULL;if (ret) {//异常情况处理wpa_printf(MSG_DEBUG, "nl80211: Scan trigger failed: ret=%d ""(%s)", ret, strerror(-ret));... ...}drv->scan_state = SCAN_REQUESTED;/* Not all drivers generate "scan completed" wireless event, so try to* read results after a timeout. */timeout = 10;if (drv->scan_complete_events) {/** The driver seems to deliver events to notify when scan is* complete, so use longer timeout to avoid race conditions* with scanning and following association request.*/timeout = 30;}wpa_printf(MSG_DEBUG, "Scan requested (ret=%d) - scan timeout %d ""seconds", ret, timeout);eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx);eloop_register_timeout(timeout, 0, wpa_driver_nl80211_scan_timeout,drv, drv->ctx);drv->last_scan_cmd = NL80211_CMD_TRIGGER_SCAN;
fail:nlmsg_free(msg);return ret;//正常情况下返回0
}

因为函数wpa_driver_nl80211_scan返回0
=》driver_nl80211_scan2返回0
=》hostapd_driver_scan返回0
=》acs_request_scan里没有进入异常条件,属于正常情况返回0

而之前在函数acs_request_scan里调用的hostapd_driver_scan,最终会触发事件NL80211_CMD_TRIGGER_SCAN

事件NL80211_CMD_TRIGGER_SCAN

函数do_process_drv_event收到了事件NL80211_CMD_TRIGGER_SCAN,会进入switch的NL80211_CMD_TRIGGER_SCAN事件分支,会在函数里输出“nl80211: Drv Event … … … …”。进而会触发进入wpa_supplicant_event函数的EVENT_SCAN_STARTED事件流程。
函数do_process_drv_event:

static void do_process_drv_event(struct i802_bss *bss, int cmd,struct nlattr **tb)
{struct wpa_driver_nl80211_data *drv = bss->drv;union wpa_event_data data;int external_scan_event = 0;wpa_printf(MSG_DEBUG, "nl80211: Drv Event %d (%s) received for %s",cmd, nl80211_command_to_string(cmd), bss->ifname);//输出事件信息... ... ... ...switch (cmd) {case NL80211_CMD_TRIGGER_SCAN://开始扫描wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Scan trigger");drv->scan_state = SCAN_STARTED;//设置状态if (drv->scan_for_auth) {/** Cannot indicate EVENT_SCAN_STARTED here since we skip* EVENT_SCAN_RESULTS in scan_for_auth case and the* upper layer implementation could get confused about* scanning state.*/wpa_printf(MSG_DEBUG, "nl80211: Do not indicate scan-start event due to internal scan_for_auth");break;}wpa_supplicant_event(drv->ctx, EVENT_SCAN_STARTED, NULL);//进入wpa_supplicant_event的EVENT_SCAN_STARTED事件流程break;... ... ... ...case NL80211_CMD_NEW_SCAN_RESULTS://扫描结束wpa_dbg(drv->ctx, MSG_DEBUG,"nl80211: New scan results available");drv->scan_complete_events = 1;if (drv->last_scan_cmd == NL80211_CMD_TRIGGER_SCAN) {drv->scan_state = SCAN_COMPLETED;eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout,drv, drv->ctx);drv->last_scan_cmd = 0;} else {external_scan_event = 1;}send_scan_event(drv, 0, tb, external_scan_event);//调用send_scan_event函数输出本次扫描相关信息... ... ... ...}
}

在wpa_supplicant_event函数里,先会输出“wlan0: Event SCAN_STARTED (47) received”。而事件SCAN_STARTED 不等于函数内switch (event)的任何一个case分支的值,故进入了default的分支,会输出“Unknown event 47”。

事件NL80211_CMD_NEW_SCAN_RESULTS

一段扫描流程结束后,do_process_drv_event收到了事件NL80211_CMD_NEW_SCAN_RESULTS,会在函数中输出“nl80211: Drv Event … …”。

进入switch的NL80211_CMD_NEW_SCAN_RESULTS事件分支,输出“nl80211: New scan results available”,并调用send_scan_event函数。

在函数send_scan_event里输出了本次扫描的信道频率,"nl80211: Scan included frequencies: … … ",并调用 wpa_supplicant_event(drv->ctx, EVENT_SCAN_RESULTS, &event)。

wpa_supplicant_event会收到SCAN_RESULTS 事件,此时iface->scan_cb的函数已在ieee80211n_check_40mhz里被赋值为ieee80211n_check_scan,即wpa_supplicant_event会调用ieee80211n_check_scan函数来检查扫描结果。

ieee80211n_check_scan函数,检查信道

static void ieee80211n_check_scan(struct hostapd_iface *iface)
{struct wpa_scan_results *scan_res;int oper40;int res;/* Check list of neighboring BSSes (from scan) to see whether 40 MHz is* allowed per IEEE Std 802.11-2012, 10.15.3.2 */iface->scan_cb = NULL;scan_res = hostapd_driver_get_scan_results(iface->bss[0]);if (scan_res == NULL) {//结束接口初始化hostapd_setup_interface_complete(iface, 1);return;}if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A)//判断当前起的接口是2.4G还是5Goper40 = ieee80211n_check_40mhz_5g(iface, scan_res);elseoper40 = ieee80211n_check_40mhz_2g4(iface, scan_res);//检查2.4Gwpa_scan_results_free(scan_res);//释放扫描结果iface->secondary_ch = iface->conf->secondary_channel;if (!oper40) {//如果返回值oper40是0,则说明不支持40Mwpa_printf(MSG_INFO, "20/40 MHz operation not permitted on ""channel pri=%d sec=%d based on overlapping BSSes",iface->conf->channel,iface->conf->channel +iface->conf->secondary_channel * 4);iface->conf->secondary_channel = 0;if (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX) {/** TODO: Could consider scheduling another scan to check* if channel width can be changed if no coex reports* are received from associating stations.*/}}res = ieee80211n_allowed_ht40_channel_pair(iface);if (!res) {iface->conf->secondary_channel = 0;res = 1;wpa_printf(MSG_INFO, "Fallback to 20 MHz");}hostapd_setup_interface_complete(iface, !res);//结束接口初始化
}

ieee80211n_check_scan先调用hostapd_driver_get_scan_results

hostapd_driver_get_scan_results

struct wpa_scan_results * hostapd_driver_get_scan_results(struct hostapd_data *hapd)
{if (hapd->driver && hapd->driver->get_scan_results2)return hapd->driver->get_scan_results2(hapd->drv_priv);return NULL;
}

这里的get_scan_results2指的是,wpa_driver_nl80211_get_scan_results函数。具体赋值同样是在:之前提到的wpa_driver_nl80211_ops 里。

const struct wpa_driver_ops wpa_driver_nl80211_ops = {.name = "nl80211",.desc = "Linux nl80211/cfg80211",... ....scan2 = driver_nl80211_scan2,... ....get_scan_results2 = wpa_driver_nl80211_get_scan_results,... ...};

实际被调用的函数是wpa_driver_nl80211_get_scan_results

wpa_driver_nl80211_get_scan_results

struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv)
{struct i802_bss *bss = priv;struct wpa_driver_nl80211_data *drv = bss->drv;struct wpa_scan_results *res;res = nl80211_get_scan_results(drv);//若正常执行完毕,应返回非NULL的resif (res)wpa_driver_nl80211_check_bss_status(drv, res);return res;
}

而wpa_driver_nl80211_get_scan_results函数会调用nl80211_get_scan_results

函数nl80211_get_scan_results里,正常情况下会输出"nl80211: Received scan results … (BSSes)",并调用nl80211_get_noise_for_scan_results去进一步获取信道noise信息到res里。

wpa_driver_nl80211_get_scan_results返回后,返回上层函数hostapd_driver_get_scan_results,再返回到函数ieee80211n_check_scan。

如果返回值res是NULL,则异常,调用hostapd_setup_interface_complete结束。
接着判断当前接口是2.4G接口还是5G接口,分别调用检查函数。若2.4G,就调用ieee80211n_check_40mhz_2g4,检查是否能支持2.4G上运行40MHZ热点。

ieee80211n_check_40mhz_2g4

static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface,struct wpa_scan_results *scan_res)
{int pri_chan, sec_chan;pri_chan = iface->conf->channel;sec_chan = pri_chan + iface->conf->secondary_channel * 4;//secondary_channel为正,则说明是primary向上加4的channel。为负,则向下减4。return check_40mhz_2g4(iface->current_mode, scan_res, pri_chan,sec_chan);//调用check_40mhz_2g4
}

函数内,算出了primary和secondary channel值,传进函数check_40mhz_2g4

check_40mhz_2g4

int check_40mhz_2g4(struct hostapd_hw_modes *mode,struct wpa_scan_results *scan_res, int pri_chan,int sec_chan)
{int pri_freq, sec_freq;int affected_start, affected_end;size_t i;if (!mode || !scan_res || !pri_chan || !sec_chan ||pri_chan == sec_chan)//若是20M,则iface->conf->secondary_channel为0,sec_chan==pri_chan,此处退出return 0;pri_freq = hw_get_freq(mode, pri_chan);//根据channel值,算出对应信道的频率值sec_freq = hw_get_freq(mode, sec_chan);affected_start = (pri_freq + sec_freq) / 2 - 25;affected_end = (pri_freq + sec_freq) / 2 + 25;wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz",affected_start, affected_end);for (i = 0; i < scan_res->num; i++) {//遍历扫描到的BSS信息struct wpa_scan_res *bss = scan_res->res[i];int pri = bss->freq;int sec = pri;struct ieee802_11_elems elems;/* Check for overlapping 20 MHz BSS */if (check_20mhz_bss(bss, pri_freq, affected_start,affected_end)) {wpa_printf(MSG_DEBUG,"Overlapping 20 MHz BSS is found");return 0;//如果和20M的其他热点有重叠}get_pri_sec_chan(bss, &pri_chan, &sec_chan);//获得primary和secondary的信道号if (sec_chan) {//算出当前遍历到的BSS的secondary channel的信道频率if (sec_chan < pri_chan)sec = pri - 20;elsesec = pri + 20;}if ((pri < affected_start || pri > affected_end) &&(sec < affected_start || sec > affected_end))continue; /* not within affected channel range */wpa_printf(MSG_DEBUG, "Neighboring BSS: " MACSTR" freq=%d pri=%d sec=%d",MAC2STR(bss->bssid), bss->freq, pri_chan, sec_chan);if (sec_chan) {if (pri_freq != pri || sec_freq != sec) {//如果扫描到的BSS的primary和secondary不完全一致wpa_printf(MSG_DEBUG,"40 MHz pri/sec mismatch with BSS "MACSTR" <%d,%d> (chan=%d%c) vs. <%d,%d>",MAC2STR(bss->bssid),pri, sec, pri_chan,sec > pri ? '+' : '-',pri_freq, sec_freq);return 0;//报错}}ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems,0);if (elems.ht_capabilities) {struct ieee80211_ht_capabilities *ht_cap =(struct ieee80211_ht_capabilities *)elems.ht_capabilities;if (le_to_host16(ht_cap->ht_capabilities_info) &HT_CAP_INFO_40MHZ_INTOLERANT) {wpa_printf(MSG_DEBUG,"40 MHz Intolerant is set on channel %d in BSS "MACSTR, pri, MAC2STR(bss->bssid));return 0;}}}return 1;
}

其中,( pri-freq + sec-freq ) / 2 ± 30是不受影响的。故而受影响的信道频率为,这个值加减5。如果信道频率小于affected_start ,则说明它与这个40M的BSS的覆盖范围并不冲突。affected_end方向同理,若信道频率大于affected_end值,则说明它与这个40M的BSS的覆盖范围并不冲突。

调用check_20mhz_bss检查是否与某个20M BSS重叠

check_20mhz_bss
static int check_20mhz_bss(struct wpa_scan_res *bss, int pri_freq, int start,int end)
{struct ieee802_11_elems elems;struct ieee80211_ht_operation *oper;if (bss->freq < start || bss->freq > end || bss->freq == pri_freq)return 0;//当前BSS并非重叠,返回0ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0);//将wpa_scan_res里的信息分析截取到elems理if (!elems.ht_capabilities) {//如果当前BSS没有开启HT Capability Info一项wpa_printf(MSG_DEBUG, "Found overlapping legacy BSS: "MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq);return 1;//返回1就是说明有冲突}if (elems.ht_operation) {//如果BSS开启了HT Capability Info选项,并且获取到了它的HT Operation Informationoper = (struct ieee80211_ht_operation *) elems.ht_operation;if (oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)return 0;//这里判定为无冲突是因为这个函数只判断与周围20M热点是否信道重叠,如果与掩码按位与不为0,说明这是个40M热点,退出函数/*其中,HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK及相关定义如下:这个与beacon包中的内容可以对应上,详见下文抓包截图#define HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK ((u8) BIT(0) | BIT(1))#define HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE     ((u8) BIT(0))#define HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW      ((u8) BIT(0) | BIT(1))*/wpa_printf(MSG_DEBUG, "Found overlapping 20 MHz HT BSS: "MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq);return 1;//返回1说明有冲突}return 0;
}

ieee802_11_parse_elems函数中,各项info的解析代码基本类似于以下结构。

switch (id) {... ...case WLAN_EID_HT_CAP:   //#define WLAN_EID_HT_CAP 45if (elen < sizeof(struct ieee80211_ht_capabilities))break;elems->ht_capabilities = pos;break;case WLAN_EID_HT_OPERATION:   //#define WLAN_EID_HT_OPERATION 61if (elen < sizeof(struct ieee80211_ht_operation))break;elems->ht_operation = pos;break;... ...}

其中,id就对应着抓包中的Element ID。
以omnipeek抓到的某BEACON包为例:
ht capability 部分:

ht operation 部分:

这部分对应到代码里,就是elems.ht_operation->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW == 1(也等同于按位与上HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK),这个值对应到beacon包里对应Byte后就是它的bit 0和bit 1位置1。07 == 0000 0111。

check_20mhz_bss返回到check_40mhz_2g4里,如果返回值是1说明与其他20M热点有重叠,直接返回。否则,就继续判断。

判断primary和secondary的信道频率是否处于受影响范围内。若与当前遍历到的BSS不影响,则continue进行与下一个BSS的范围比较。
若处于影响范围,则输出当前这个相邻BSS的信息“Neighboring BSS … … "。

接着判断,如果当前遍历的BSS是40M的,并且当前BSS与我们的热点的primary channel和secondary channel不完全一致,则说明范围是有重叠冲突的,输出mismatch信息,返回。
如果没有冲突,解析出当前BSS的ht capabilities info。判断ht_capabilities_info里是否置上了HT_CAP_INFO_40MHZ_INTOLERANT,如果有,则说明当前BSS不支持周围BSS有40M传输。

#define HT_CAP_INFO_40MHZ_INTOLERANT     ((u16) BIT(14))

对照着上文的抓包截图,可以看到,第14bit值为0时,omnipeek对应解析是” AP does Not allow use of 40MHz Transmissions In Neighboring BSSs “。

check_40mhz_2g4返回到ieee80211n_check_40mhz_2g4里,再将返回值返回到ieee80211n_check_scan里,允许40M就为1,反之为0。这也即是oper40的值。

函数ieee80211n_check_scan里会调用ieee80211n_allowed_ht40_channel_pair

ieee80211n_allowed_ht40_channel_pair

static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface)
{int pri_chan, sec_chan;if (!iface->conf->secondary_channel)//如果是20M热点则不需检查return 1; /* HT40 not used */pri_chan = iface->conf->channel;//算出主次channel值sec_chan = pri_chan + iface->conf->secondary_channel * 4;return allowed_ht40_channel_pair(iface->current_mode, pri_chan,sec_chan);
}

调用下层函数allowed_ht40_channel_pair

allowed_ht40_channel_pair

int allowed_ht40_channel_pair(struct hostapd_hw_modes *mode, int pri_chan,int sec_chan)
{int ok, j, first;int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 140,149, 157, 184, 192 };//这802.11A模式下需要用到的变量size_t k;if (pri_chan == sec_chan || !sec_chan)//如果是20M则不需检查return 1; /* HT40 not used */wpa_printf(MSG_DEBUG,"HT40: control channel: %d  secondary channel: %d",pri_chan, sec_chan);/* Verify that HT40 secondary channel is an allowed 20 MHz channel */ok = 0;for (j = 0; j < mode->num_channels; j++) {//遍历当前接口支持的channel liststruct hostapd_channel_data *chan = &mode->channels[j];if (!(chan->flag & HOSTAPD_CHAN_DISABLED) &&chan->chan == sec_chan) {//若secondary channel在接口支持的列表里,并且该信道并未DISABLE,将OK置1ok = 1;//反之,若secondary channel不被当前接口支持或状态DISABLE,则不置1,默认值是0break;}}if (!ok) {//如果OK是0,则说明secondary不可用wpa_printf(MSG_ERROR, "HT40 secondary channel %d not allowed",sec_chan);return 0;}/** Verify that HT40 primary,secondary channel pair is allowed per* IEEE 802.11n Annex J. This is only needed for 5 GHz band since* 2.4 GHz rules allow all cases where the secondary channel fits into* the list of allowed channels (already checked above).*/if (mode->mode != HOSTAPD_MODE_IEEE80211A)//如果当前模式非802.11A,则检查完毕,40M正常返回1return 1;... ... ... ...//802.11A下的一些检查return 1;
}

最终返回到ieee80211n_check_scan里,若返回值是1,说明可以启动40M热点,反之启动20M热点,并调用hostapd_setup_interface_complete结束接口初始化。

本文到此,终于结束了acs的路径流程。

hostapd启动流程(二)相关推荐

  1. hostapd启动流程(一)

    hostapd启动流程(一) 总述 ... -> setup_interface2 hostapd_select_hw_mode hostapd_check_chans hostapd_is_u ...

  2. 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 二 | AMS 进程相关源码 | 主进程相关源码 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. 深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先 ...

  4. 龙芯PMON(2K1000)启动流程(二、汇编部分)

    1.pmon 文件相关的地址问题   cpu眼中的地址是虚拟地址,cpu 取指和取数据的地址是物理地址,经过北桥解释后的地址是总线地址,编译器产生的地址(包括解析了所有引用和重定位的符号后)为程序地址 ...

  5. Linux启动流程(二)

    //...根据grub内核映像所在路径,读取内核映像,并进行解压缩操作.并调用start_kernel()函数来启动一系列的初始化函数并初始化各种设备,完成Linux核心环境的建立 1.start_k ...

  6. uboot启动流程二

    我们可以看到在,start_armboot()函数的最后,在一个无限循环中调用了函数main_loop(),该函数在common/main.c文件中被定义,我们可以看到下面的一段代码: #if def ...

  7. 深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--A ...

  8. 【Android 逆向】加壳的 Android 应用启动流程 | 使用反射替换 LoadedApk 中的类加载器流程

    文章目录 一.加壳的 Android 应用启动流程 二.使用反射替换 LoadedApk 中的类加载器流程 一.加壳的 Android 应用启动流程 加壳的 Android 应用启动流程 : 加壳的 ...

  9. 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. 从芯片巨头沦落到收购对象 高通是怎样失去魔力的?
  2. 【bzoj5085】最大(二分+乱搞)
  3. 【STM32】I2C详解
  4. 软考网络工程师笔记-综合知识1
  5. vega56刷64_Vega56刷入BIOS跑分直逼旗舰Vega64
  6. 安卓应用安全指南 4.2.1 创建/使用广播接收器 示例代码
  7. python字典forward_《Python机器学习基础教程》
  8. 阿言学习之Hadoop fs常用命令
  9. python编写代码实现一个循环双链表类_Python双向循环链表实现方法分析
  10. 排名前20的网页爬虫工具
  11. CAD图纸转换成高质量的彩色PDF格式如何操作?
  12. iview 循环 卡片 更好图标 背景色 标题
  13. Postman下载与安装详细步骤
  14. studio3t到期解决办法
  15. mkv文件怎样转成mp4
  16. SOC课程实验——PC程序计数器设计
  17. 基于LSTM的股票预测模型_python实现_超详细
  18. 写给二线城市【Python工程师】的成长指南
  19. dsp 正弦波信号发生器matlab程序,基于DSP的正弦波信号发生器源程序(汇编语言).doc...
  20. 二叉树(从建树、遍历到存储)Java

热门文章

  1. conflicting types for错误
  2. C/C++ ifndef与头文件重复包含解析
  3. oracle hit ratio负数,5.Library Hit %
  4. Python Utils
  5. RationalDMIS 2020平面度评价
  6. 路由器实验-HSRP笔记
  7. 小蜘蛛 七乐彩随机选号器 官网
  8. 程序员如何赚到人生第一个 100 万
  9. 人工智能研究 还需打破学科壁垒的"生殖隔离"
  10. GSM模块信号强度CSQ与RSSI的对应关系,新增android的ASU (转载)