hostapd启动流程(一)
总述
最近花了很多时间学习hostapd 2.6启动源码,用此blog记录下学习成果。若有分析的不对的地方,请大佬们指出一起讨论下。
本系列主要分析hostapd的interface启动流程,大致的函数调用如下:
hostapd_setup_interface ->
setup_interface->
setup_interface2->
1. hostapd_select_hw_mode
->hostapd_check_chans,函数里判断是否需要进行acs扫描流程。
需要acs扫描的话直接return
2. hostapd_check_ht_capab,不需要acs扫描流程要走的分支
3. hostapd_setup_interface_complete
->hostapd_setup_interface_complete_sync
因为涉及到的函数流程非常多,并且想分析的稍微细致点。
故将这整个流程分为两部分来分析:
第一部分只看从hostapd_setup_interface 开始的不需要acs的启动流程;
第二部分看从acs_init开始的扫描流程。
本篇只分析第一部分。
… -> setup_interface2
第一部分的起点函数,hostapd_setup_interface 会调用setup_interface,继而再调setup_interface2。
本部分从setup_interface2为起点开始分析。
static int setup_interface2(struct hostapd_iface *iface)
{
iface->wait_channel_update = 0;
if (hostapd_get_hw_features(iface)) {//获取硬件信息
/* Not all drivers support this yet, so continue without hw
* feature data. */
} else {//我的设备会走这个else分支
int ret = hostapd_select_hw_mode(iface);//选择硬件模式
if (ret < 0) {//异常情况处理
wpa_printf(MSG_ERROR, "Could not select hw_mode and "
"channel. (%d)", ret);
goto fail;
}
if (ret == 1) {//正常情况之需要进行acs扫描流程
wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback (ACS)");
return 0;
}
ret = hostapd_check_ht_capab(iface);//不需要acs扫描流程,已有选定的信道
if (ret < 0)
goto fail;
if (ret == 1) {//等待acs扫描结束后,也会调到hostapd_setup_interface_complete结束函数
wpa_printf(MSG_DEBUG, "Interface initialization will "
"be completed in a callback");
return 0;
}
if (iface->conf->ieee80211h)
wpa_printf(MSG_DEBUG, "DFS support is enabled");
}
return hostapd_setup_interface_complete(iface, 0);//初始化结束函数
fail:
hostapd_set_state(iface, HAPD_IFACE_DISABLED);//异常处理
wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
if (iface->interfaces && iface->interfaces->terminate_on_error)
eloop_terminate();
return -1;
}
先看setup_interface2调用的hostapd_select_hw_mode函数。
hostapd_select_hw_mode
int hostapd_select_hw_mode(struct hostapd_iface *iface)
{
int i;
if (iface->num_hw_features < 1)
return -1;
if ((iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211G || iface->conf->ieee80211n || iface->conf->ieee80211ac) && iface->conf->channel == 14) {
wpa_printf(MSG_INFO, "Disable OFDM/HT/VHT on channel 14");
iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211B;
iface->conf->ieee80211n = 0;
iface->conf->ieee80211ac = 0;
}//异常处理
iface->current_mode = NULL;//把值清为NULL
for (i = 0; i < iface->num_hw_features; i++) {//遍历当前接口的hw_features
/*其中iface的结构体定义如下,
struct hostapd_iface {
... ...
struct hostapd_hw_modes *hw_features;//接口支持的hw_modes的数组
int num_hw_features;//接口支持的hw_modes的个数
... ...
}
*/
struct hostapd_hw_modes *mode = &iface->hw_features[i];
if (mode->mode == iface->conf->hw_mode) {
//如果hostapd.conf中配的hw_mode与当前接口支持的模式匹配成功了,
//就将接口当前模式赋为这个值,跳出循环
iface->current_mode = mode;
break;
}
}
if (iface->current_mode == NULL) {//如果没有匹配的上的模式,则报错,返回-2进行异常处理
if (!(iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) ||
!(iface->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY))
{
wpa_printf(MSG_ERROR,"Hardware does not support configured mode");
hostapd_logger(iface->bss[0], NULL, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_WARNING,
"Hardware does not support configured mode (%d) (hw_mode in hostapd.conf)", (int) iface->conf->hw_mode);
return -2;
}
}
switch (hostapd_check_chans(iface)) {//调用hostapd_check_chans函数
//会判断是否需要acs扫描选择信道。如果hostapd.conf中channel配置成0(不配置就是0),就需要经过acs扫描算法流程
//若hostapd.conf中没配置channel,则说明已选定了信道,就判断该信道是否valid
case HOSTAPD_CHAN_VALID: //没配置channel
return 0;
case HOSTAPD_CHAN_ACS: /* ACS will run and later complete */
return 1;
case HOSTAPD_CHAN_INVALID: //异常情况条件的处理
default:
hostapd_notify_bad_chans(iface);
return -3;
}
}
看下具体是怎样检查信道的。
hostapd_select_hw_mode->hostapd_check_chans
hostapd_check_chans
static enum hostapd_chan_status hostapd_check_chans(struct hostapd_iface *iface)
{
if (iface->conf->channel) {//如果hostapd.conf配置了非零的channel值,即指定了AP所在的channel
if (hostapd_is_usable_chans(iface))//调用hostapd_is_usable_chans函数判断下是否为可用信道
return HOSTAPD_CHAN_VALID;
else
return HOSTAPD_CHAN_INVALID;
}
/*
* The user set channel=0 or channel=acs_survey
* which is used to trigger ACS.
*/
switch (acs_init(iface)) {//如果配置的channel值是0,则需要进行acs算法流程,故而要初始化acs
case HOSTAPD_CHAN_ACS: //需要进行acs算法
return HOSTAPD_CHAN_ACS;
case HOSTAPD_CHAN_VALID: //若不需要acs,则出了异常情况,返回INVALID
case HOSTAPD_CHAN_INVALID:
default:
return HOSTAPD_CHAN_INVALID;
}
}
具体判断iface->conf->channel接口当前指定channel是否可用的函数是hostapd_is_usable_chans,该函数内部判断了20M情况下的channel和40M情况下的主次channel是否可用。
hostapd_is_usable_chans
static int hostapd_is_usable_chans(struct hostapd_iface *iface)
{
if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1))//先判断primary channel是否可用
return 0;//不可用则返回0
if (!iface->conf->secondary_channel){//如果primary channel可用,判断是否配置了conf中的secondary channel
return 1;//如果secondary_channel == 0不需要报错,那就说明是20M的热点,返回正常值1
}//反之,若是40M的热点,secondary_channel!=0,并且需要判断下secondary channel是否可用
return hostapd_is_usable_chan(iface, iface->conf->channel +
iface->conf->secondary_channel * 4, 0);
//如果secondary_channel非0,则需要验证下是否可用
}
其中,iface->conf->secondary_channel的赋值在函数hostapd_config_ht_capab里。刚开始启动热点时,会读取hostapd.conf中的内容,根据ht_capab 这项配置的值来给secondary_channel赋值。
hostapd_config_ht_capab源码如下:
static int hostapd_config_ht_capab(struct hostapd_config *conf,
const char *capab)
{
if (os_strstr(capab, "[LDPC]"))
conf->ht_capab |= HT_CAP_INFO_LDPC_CODING_CAP;
if (os_strstr(capab, "[HT40-]")) {//如果配置了HT40-,就将secondary配成-1
conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
conf->secondary_channel = -1;
}
if (os_strstr(capab, "[HT40+]")) {//如果配置了HT40+,就将secondary配成1
//这个if会覆盖HT40-的修改,也就是说如果同时配置了ht_capab=[HT40-][HT40+],最终secondary会被赋为1
conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
conf->secondary_channel = 1;
}
... ...//默认情况下,是20M热点,secondary_channel = 0
return 0;
}
继续来看channel可用性判断,hostapd_is_usable_chans会调到hostapd_is_usable_chan去检查单个信道。
hostapd_is_usable_chan
static int hostapd_is_usable_chan(struct hostapd_iface *iface,
int channel, int primary)//参数primary是标识是否为primary信道,值为1是主信道
{
int i;
struct hostapd_channel_data *chan;
if (!iface->current_mode)//若current_mode == NULL,说明异常出现了
return 0;
for (i = 0; i < iface->current_mode->num_channels; i++) {//遍历当前接口的channels
chan = &iface->current_mode->channels[i];
if (chan->chan != channel)//如果当前遍历到的信道并非目的信道,跳过
continue;
if (!(chan->flag & HOSTAPD_CHAN_DISABLED))//若是目的信道,且不可用,返回错误值1
return 1;
wpa_printf(MSG_DEBUG,
"%schannel [%i] (%i) is disabled for use in AP mode, flags: 0x%x%s%s",
primary ? "" : "Configured HT40 secondary ",
i, chan->chan, chan->flag,
chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
}
return 0;
}
回到hostapd_check_chans函数:
- 信道检查完毕后,如果需要进行acs选择信道的流程就进入acs_init。
- 反之,如果指定信道可用返回VALID,不可用返回INVALID。
到上层函数hostapd_select_hw_mode里:
- valid的话返回0;
- 需要acs的话返回1;
- 有异常返回-3。
到上层setup_interface2:
- 若ret为1知要acs扫描,直接return,等待执行扫描完成以后被触发的流程。
- ret < 0,则出现了异常。
- 若ret等于0,则说明信道正常,可继续执行。
接着看无需acs扫描的流程,进入函数hostapd_check_ht_capab。
(需要acs的流程见 hostapd启动流程(二))
hostapd_check_ht_capab
int hostapd_check_ht_capab(struct hostapd_iface *iface)
{
#ifdef CONFIG_IEEE80211N
int ret;
if (!iface->conf->ieee80211n)//如果接口不支持802.11n,则直接退出
return 0;
if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211B &&
iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G &&
(iface->conf->ht_capab & HT_CAP_INFO_DSSS_CCK40MHZ)) {
wpa_printf(MSG_DEBUG,"Disable HT capability [DSSS_CCK-40] on 5 GHz band");
iface->conf->ht_capab &= ~HT_CAP_INFO_DSSS_CCK40MHZ;
}
if (!ieee80211n_supported_ht_capab(iface))
//判断iface->current_mode->ht_capab和iface->conf->ht_capab是否同时支持一些HT属性
return -1;
#ifdef CONFIG_IEEE80211AC
if (!ieee80211ac_supported_vht_capab(iface))//判断是否同时支持一些VHT属性
return -1;
#endif /* CONFIG_IEEE80211AC */
ret = ieee80211n_check_40mhz(iface);//检查是否支持40M带宽
if (ret)
return ret;
if (!ieee80211n_allowed_ht40_channel_pair(iface))
return -1;
#endif /* CONFIG_IEEE80211N */
return 0;
}
向下继续看ieee80211n_check_40mhz函数。
ieee80211n_check_40mhz
static int ieee80211n_check_40mhz(struct hostapd_iface *iface)
{
struct wpa_driver_scan_params params;
int ret;
/* Check that HT40 is used and PRI / SEC switch is allowed */
if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch)
return 0;//若hostapd.conf配置成20M带宽,则无需继续检查,直接返回。
hostapd_set_state(iface, HAPD_IFACE_HT_SCAN);
wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling "
"40 MHz channel");
os_memset(¶ms, 0, sizeof(params));
if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G)
ieee80211n_scan_channels_2g4(iface, ¶ms);//遍历一下2.4g上的信道,将结果存到params里
else
ieee80211n_scan_channels_5g(iface, ¶ms);//遍历一下5g上的信道
ret = hostapd_driver_scan(iface->bss[0], ¶ms);
os_free(params.freqs);
if (ret == -EBUSY) {
wpa_printf(MSG_ERROR,
"Failed to request a scan of neighboring BSSes ret=%d (%s) - try to scan again",
ret, strerror(-ret));
iface->num_ht40_scan_tries = 1;
eloop_cancel_timeout(ap_ht40_scan_retry, iface, NULL);
eloop_register_timeout(1, 0, ap_ht40_scan_retry, iface, NULL);
return 1;
}
if (ret < 0) {
wpa_printf(MSG_ERROR,
"Failed to request a scan of neighboring BSSes ret=%d (%s)",
ret, strerror(-ret));
return -1;
}
iface->scan_cb = ieee80211n_check_scan;
return 1;
}
来看2.4g上信道的遍历。
ieee80211n_check_40mhz->ieee80211n_scan_channels_2g4
ieee80211n_scan_channels_2g4
static void ieee80211n_scan_channels_2g4(struct hostapd_iface *iface,
struct wpa_driver_scan_params *params)
{
/* Scan only the affected frequency range */
int pri_freq, sec_freq;//主次信道频率
int affected_start, affected_end;//受影响的起始频率和终止频率
int i, pos;
struct hostapd_hw_modes *mode;
if (iface->current_mode == NULL)
return;
pri_freq = hostapd_hw_get_freq(iface->bss[0], iface->conf->channel);//获得primary_channel信道号对应的频率值
if (iface->conf->secondary_channel > 0)//如果secondary配置的是1,说明在pri_channel向上凑成40M
sec_freq = pri_freq + 20;//pri_channel和sec_channel分别占20M带宽,故而根据sec距pri的上下位置,算出sec的频率值
else//如果secondary配置的是-1,说明在pri_channel向下凑成40M
sec_freq = pri_freq - 20;
/*
* Note: Need to find the PRI channel also in cases where the affected
* channel is the SEC channel of a 40 MHz BSS, so need to include the
* scanning coverage here to be 40 MHz from the center frequency.
* 为了在受影响的信道是某个40 MHz BSS的SEC信道的这种情况下,也能找到PRI信道
* 故而需要包括从中心频率到40 MHz的扫描覆盖范围
*/
affected_start = (pri_freq + sec_freq) / 2 - 40;
affected_end = (pri_freq + sec_freq) / 2 + 40;
wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz",
affected_start, affected_end);
mode = iface->current_mode;
params->freqs = os_calloc(mode->num_channels + 1, sizeof(int));
if (params->freqs == NULL)//用params->freqs记录受影响的信道freq
return;
pos = 0;
for (i = 0; i < mode->num_channels; i++) {//遍历当前接口支持的信道列表
struct hostapd_channel_data *chan = &mode->channels[i];
if (chan->flag & HOSTAPD_CHAN_DISABLED)//如果当前遍历的信道不可用,跳过
continue;
if (chan->freq < affected_start ||
chan->freq > affected_end)//不在受影响范围内的信道,跳过
continue;
params->freqs[pos++] = chan->freq;
}
}
代码里的受影响范围的计算原理见下方:
由上可知这种计算方法可行。
ieee80211n_scan_channels_2g4返回后:
- params->freqs里存储了受当前40M热点影响的信道频率。
- 调用hostapd_driver_scan函数,将params传进去。
hostapd_driver_scan这个函数会执行扫描信道的操作。这之后,判断返回值的异常情况处理。将iface->scan操作赋值成ieee80211n_check_scan。
(详细可见文章hostapd启动流程(二)内关于hostapd_driver_scan的代码分析)
返回到上层函数hostapd_check_ht_capab:
- 除了HT40没有开启或PRI / SEC switch不允许的情况外,ret不为0,直接返回上层。
- 至于函数ieee80211n_allowed_ht40_channel_pair,一般不在此处调用。下面的流程还会调用,到时再分析。
hostapd_check_ht_capab返回上层,到函数setup_interface2里:
- 如果hostapd_check_ht_capab返回小于零的值,若是-1,说明是异常的情况,进行异常处理。
- 其他值其实大概率是ieee80211n_check_40mhz的返回值:
- 如果为1,是40M正常情况,等待之后的扫描检查流程。
- 如果是0,是20M正常情况。因为0是20M的返回值,而20M的channel已通过之前的hostapd_select_hw_mode -> hostapd_check_chans检查过是否可用了,故而返回的若是0则直接返回。
若无需扫描,则直接调用hostapd_setup_interface_complete函数,结束接口初始化。
hostapd_setup_interface_complete
int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err)
{
struct hapd_interfaces *interfaces = iface->interfaces;
struct hostapd_data *hapd = iface->bss[0];
unsigned int i;
int not_ready_in_sync_ifaces = 0;
if (!iface->need_to_start_in_sync)
return hostapd_setup_interface_complete_sync(iface, err);
... ... ...
return 0;
}
hostapd_setup_interface_complete会调用hostapd_setup_interface_complete_sync,
hostapd_setup_interface_complete_sync函数里输出"Completing interface initialization"。
到此告一段落。
若需要扫描,之前在函数ieee80211n_check_40mhz里调用的hostapd_driver_scan,最终会触发事件NL80211_CMD_TRIGGER_SCAN。函数do_process_drv_event收到了事件NL80211_CMD_TRIGGER_SCAN,会进入扫描环节的一系列函数流程。
具体的扫描流程和对扫描结果的检查流程,可见文章hostapd启动流程(二)中对应部分。
一段扫描流程结束后,wpa_supplicant_event会收到EVENT_SCAN_RESULTS事件。wpa_supplicant_event对应case的代码:
case EVENT_SCAN_RESULTS:
if (hapd->iface->scan_cb)
hapd->iface->scan_cb(hapd->iface);
break;
此时iface->scan_cb的函数已在ieee80211n_check_40mhz里被赋值为ieee80211n_check_scan,即wpa_supplicant_event会调用ieee80211n_check_scan函数来检查扫描结果。
ieee80211n_check_scan检查流程详见文章hostapd启动流程(二)中对应部分。检查完毕后,会调用hostapd_setup_interface_complete函数,结束接口初始化。