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(&params, 0, sizeof(params));
	if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G)
		ieee80211n_scan_channels_2g4(iface, &params);//遍历一下2.4g上的信道,将结果存到params里
	else
		ieee80211n_scan_channels_5g(iface, &params);//遍历一下5g上的信道
	ret = hostapd_driver_scan(iface->bss[0], &params);
	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函数,结束接口初始化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值