应用层roam过程分析

在应用层使用下面的命令进行roam的时候,应用层干了什么事情?本文重点分析应用层wpa程序roam过程。

 wpa_cli -p/var/run/wpa_supplicant-ath0 -iath0 roam 00:0f:ff:01:40:12

wpa_supplicant 漫游过程分析

当我们主动发送roam命令的时候,wpa_s收到wpa_cli命令时候进入wpa_supplicant_ctrl_iface_process函数

char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
					 char *buf, size_t *resp_len)
{
    if (os_strncmp(buf, "ROAM ", 5) == 0) {
            if (wpa_supplicant_ctrl_iface_roam(wpa_s, buf + 5))
                reply_len = -1;
    }
}					 

接着调用wpa_supplicant_ctrl_iface_roam函数

wpa_supplicant_ctrl_iface_roam

static int wpa_supplicant_ctrl_iface_roam(struct wpa_supplicant *wpa_s,
					  char *addr)
{

	u8 bssid[ETH_ALEN];
	struct wpa_bss *bss;
	struct wpa_ssid *ssid = wpa_s->current_ssid;

    // 漫游目标addr转换为bssid
    if (hwaddr_aton(addr, bssid)) {
		wpa_printf(MSG_DEBUG, "CTRL_IFACE ROAM: invalid "
			   "address '%s'", addr);
		return -1;
	}
    
	// 如果当前连接为空,则直接返回,不允许roam
	if (!ssid) {
		wpa_printf(MSG_DEBUG, "CTRL_IFACE ROAM: No network "
			   "configuration known for the target AP");
		return -1;
	}
	//根据传入的bssid 得到bss
	bss = wpa_bss_get(wpa_s, bssid, ssid->ssid, ssid->ssid_len);
 

	/*
	 * TODO: Find best network configuration block from configuration to
	 * allow roaming to other networks
	 */

	wpa_s->reassociate = 1;
	wpa_supplicant_connect(wpa_s, bss, ssid);// 继续往下分析

	return 0;

}

接下来分析wpa_supplicant_connect函数

wpa_supplicant_connect

int wpa_supplicant_connect(struct wpa_supplicant *wpa_s,
			   struct wpa_bss *selected,
			   struct wpa_ssid *ssid)
{
	
	// 一段调试信息
	wpa_msg(wpa_s, MSG_DEBUG,
		"Considering connect request: reassociate: %d  selected: "
		MACSTR "  bssid: " MACSTR "  pending: " MACSTR
		"  wpa_state: %s  ssid=%p  current_ssid=%p",
		wpa_s->reassociate, MAC2STR(selected->bssid),
		MAC2STR(wpa_s->bssid), MAC2STR(wpa_s->pending_bssid),
		wpa_supplicant_state_txt(wpa_s->wpa_state),
		ssid, wpa_s->current_ssid);

	/*
	 * Do not trigger new association unless the BSSID has changed or if
	 * reassociation is requested. If we are in process of associating with
	 * the selected BSSID, do not trigger new attempt.
	 */
    // 除非BSSID已更改或请求重新关联,否则不要触发新的关联。
    // 如果我们正在与所选BSSID关联,请不要触发新的尝试。
	if (wpa_s->reassociate ||
	    (os_memcmp(selected->bssid, wpa_s->bssid, ETH_ALEN) != 0 &&
	     ((wpa_s->wpa_state != WPA_ASSOCIATING &&
	       wpa_s->wpa_state != WPA_AUTHENTICATING) ||
	      (!is_zero_ether_addr(wpa_s->pending_bssid) &&
	       os_memcmp(selected->bssid, wpa_s->pending_bssid, ETH_ALEN) !=
	       0) ||
	      (is_zero_ether_addr(wpa_s->pending_bssid) &&
	       ssid != wpa_s->current_ssid)))) {
		if (wpa_supplicant_scard_init(wpa_s, ssid)) {
			wpa_supplicant_req_new_scan(wpa_s, 10, 0);
			return 0;
		}
		wpa_msg(wpa_s, MSG_DEBUG, "Request association with " MACSTR,
			MAC2STR(selected->bssid));
        // 重点 开始关联
		wpa_supplicant_associate(wpa_s, selected, ssid);
	} else {
		wpa_dbg(wpa_s, MSG_DEBUG, "Already associated or trying to "
			"connect with the selected AP");
	}

	return 0;
}

接下来分析wpa_supplicant_associate

wpa_supplicant_associate
void wpa_supplicant_associate(struct wpa_supplicant *wpa_s,
			      struct wpa_bss *bss, struct wpa_ssid *ssid)
{
	struct wpa_connect_work *cwork;
	int rand_style;

	wpa_s->own_disconnect_req = 0;
	wpa_s->own_reconnect_req = 0;

	/*
	 * If we are starting a new connection, any previously pending EAPOL
	 * RX cannot be valid anymore.
	 */
	wpabuf_free(wpa_s->pending_eapol_rx);
	wpa_s->pending_eapol_rx = NULL;

	if (ssid->mac_addr == -1)
		rand_style = wpa_s->conf->mac_addr;
	else
		rand_style = ssid->mac_addr;

	wpa_s->multi_ap_ie = 0;
	wmm_ac_clear_saved_tspecs(wpa_s);
	wpa_s->reassoc_same_bss = 0;
	wpa_s->reassoc_same_ess = 0;
#ifdef CONFIG_TESTING_OPTIONS
	wpa_s->testing_resend_assoc = 0;
#endif /* CONFIG_TESTING_OPTIONS */

	/*
	 * Get wideband support for the interface.
	 * It is disabled by default unless explicitly enabled.
	 */
	wpa_s->wideband_support = 0;
	wpa_drv_get_wideband_support(wpa_s);

	if (wpa_s->last_ssid == ssid) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Re-association to the same ESS");
		wpa_s->reassoc_same_ess = 1;
		if (wpa_s->current_bss && wpa_s->current_bss == bss) {
			wmm_ac_save_tspecs(wpa_s);
			wpa_s->reassoc_same_bss = 1;
		} else if (wpa_s->current_bss && wpa_s->current_bss != bss) {
			os_get_reltime(&wpa_s->roam_start);
		}
	} else {
#ifdef CONFIG_SAE
		wpa_s_clear_sae_rejected(wpa_s);
		wpa_s_setup_sae_pt(wpa_s->conf, ssid);
#endif /* CONFIG_SAE */
	}

	if (rand_style > 0 && !wpa_s->reassoc_same_ess) {
		if (wpas_update_random_addr(wpa_s, rand_style) < 0)
			return;
		wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
	} else if (rand_style == 0 && wpa_s->mac_addr_changed) {
		if (wpa_drv_set_mac_addr(wpa_s, NULL) < 0) {
			wpa_msg(wpa_s, MSG_INFO,
				"Could not restore permanent MAC address");
			return;
		}
		wpa_s->mac_addr_changed = 0;
		if (wpa_supplicant_update_mac_addr(wpa_s) < 0) {
			wpa_msg(wpa_s, MSG_INFO,
				"Could not update MAC address information");
			return;
		}
		wpa_msg(wpa_s, MSG_DEBUG, "Using permanent MAC address");
	}
	wpa_s->last_ssid = ssid;

#ifdef CONFIG_IBSS_RSN
	ibss_rsn_deinit(wpa_s->ibss_rsn);
	wpa_s->ibss_rsn = NULL;
#else /* CONFIG_IBSS_RSN */
	if (ssid->mode == WPAS_MODE_IBSS &&
	    !(ssid->key_mgmt & (WPA_KEY_MGMT_NONE | WPA_KEY_MGMT_WPA_NONE))) {
		wpa_msg(wpa_s, MSG_INFO,
			"IBSS RSN not supported in the build");
		return;
	}
#endif /* CONFIG_IBSS_RSN */

	if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO ||
	    ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) {
#ifdef CONFIG_AP
		if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP)) {
			wpa_msg(wpa_s, MSG_INFO, "Driver does not support AP "
				"mode");
			return;
		}
		if (wpa_supplicant_create_ap(wpa_s, ssid) < 0) {
			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
			if (ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION)
				wpas_p2p_ap_setup_failed(wpa_s);
			return;
		}
		wpa_s->current_bss = bss;
#else /* CONFIG_AP */
		wpa_msg(wpa_s, MSG_ERROR, "AP mode support not included in "
			"the build");
#endif /* CONFIG_AP */
		return;
	}

	if (ssid->mode == WPAS_MODE_MESH) {
#ifdef CONFIG_MESH
		if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_MESH)) {
			wpa_msg(wpa_s, MSG_INFO,
				"Driver does not support mesh mode");
			return;
		}
		if (bss)
			ssid->frequency = bss->freq;
		if (wpa_supplicant_join_mesh(wpa_s, ssid) < 0) {
			wpa_msg(wpa_s, MSG_ERROR, "Could not join mesh");
			return;
		}
		wpa_s->current_bss = bss;
		wpa_msg(wpa_s, MSG_INFO, MESH_GROUP_STARTED "ssid=\"%s\" id=%d",
			wpa_ssid_txt(ssid->ssid, ssid->ssid_len),
			ssid->id);
		wpas_notify_mesh_group_started(wpa_s, ssid);
#else /* CONFIG_MESH */
		wpa_msg(wpa_s, MSG_ERROR,
			"mesh mode support not included in the build");
#endif /* CONFIG_MESH */
		return;
	}

	/*
	 * Set WPA state machine configuration to match the selected network now
	 * so that the information is available before wpas_start_assoc_cb()
	 * gets called. This is needed at least for RSN pre-authentication where
	 * candidate APs are added to a list based on scan result processing
	 * before completion of the first association.
	 */
	wpa_supplicant_rsn_supp_set_config(wpa_s, ssid);

#ifdef CONFIG_DPP
	if (wpas_dpp_check_connect(wpa_s, ssid, bss) != 0)
		return;
#endif /* CONFIG_DPP */

#ifdef CONFIG_TDLS
	if (bss)
		wpa_tdls_ap_ies(wpa_s->wpa, (const u8 *) (bss + 1),
				bss->ie_len);
#endif /* CONFIG_TDLS */

#ifdef CONFIG_MBO
	wpas_mbo_check_pmf(wpa_s, bss, ssid);
#endif /* CONFIG_MBO */

	if ((wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) &&
	    ssid->mode == WPAS_MODE_INFRA) {
		sme_authenticate(wpa_s, bss, ssid);
		return;
	}

	if (wpa_s->connect_work) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since connect_work exist");
		return;
	}

	if (radio_work_pending(wpa_s, "connect")) {
		wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since pending work exist");
		return;
	}

#ifdef CONFIG_SME
	if (ssid->mode == WPAS_MODE_IBSS || ssid->mode == WPAS_MODE_MESH) {
		/* Clear possibly set auth_alg, if any, from last attempt. */
		wpa_s->sme.auth_alg = WPA_AUTH_ALG_OPEN;
	}
#endif /* CONFIG_SME */

	wpas_abort_ongoing_scan(wpa_s);

	cwork = os_zalloc(sizeof(*cwork));
	if (cwork == NULL)
		return;

	cwork->bss = bss;
	cwork->ssid = ssid;
	// 开启工作队列
	if (radio_add_work(wpa_s, bss ? bss->freq : 0, "connect", 1,
			   wpas_start_assoc_cb, cwork) < 0) {
		os_free(cwork);
	}
}

接下来分析wpas_start_assoc_cb函数

static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit)
{
	ret = wpa_drv_associate(wpa_s, &params);
}
/*
1. struct wpa_driver_associate_params params;  
2. wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATING);
3. 填充 params
4. ret = wpa_drv_associate(wpa_s, &params);
5. wpa_supplicant_req_auth_timeout(wpa_s, timeout, 0);
6. wpa_supplicant_initiate_eapol(wpa_s);
*/

//5.6步目前还不确定走不走,这个函数 囊括了 assoc、auth、EAPOL动作

接下来分析wpa_drv_associate函数

wpa_drv_associate
static inline int wpa_drv_associate(struct wpa_supplicant *wpa_s,
				    struct wpa_driver_associate_params *params)
{
	if (wpa_s->driver->associate) {
        // 实际就是调用wpa_driver_nl80211_ops 中的 wpa_driver_nl80211_associate
		return wpa_s->driver->associate(wpa_s->drv_priv, params);
	}
	return -1;
}

接下来分析wpa_driver_nl80211_ops

  1. wpa_driver_nl80211_associate

    static int wpa_driver_nl80211_associate(
    	void *priv, struct wpa_driver_associate_params *params)
    {
    	struct i802_bss *bss = priv;
    	struct wpa_driver_nl80211_data *drv = bss->drv;
    	int ret = -1;
    	struct nl_msg *msg;
    
    	nl80211_unmask_11b_rates(bss);
    
    	if (params->mode == IEEE80211_MODE_AP)
    		return wpa_driver_nl80211_ap(drv, params);
    
    	if (params->mode == IEEE80211_MODE_IBSS)
    		return wpa_driver_nl80211_ibss(drv, params);
    
    	if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) {
    		enum nl80211_iftype nlmode = params->p2p ?
    			NL80211_IFTYPE_P2P_CLIENT : NL80211_IFTYPE_STATION;
    
    		if (wpa_driver_nl80211_set_mode(priv, nlmode) < 0)
    			return -1;
    		if (params->key_mgmt_suite == WPA_KEY_MGMT_SAE ||
    		    params->key_mgmt_suite == WPA_KEY_MGMT_FT_SAE)
    			bss->use_nl_connect = 1;
    		else
    			bss->use_nl_connect = 0;
          // 连接
    		return wpa_driver_nl80211_connect(drv, params,
    						  get_connect_handle(bss));
    	}
    
    	nl80211_mark_disconnected(drv);
    
    	wpa_printf(MSG_DEBUG, "nl80211: Associate (ifindex=%d)",
    		   drv->ifindex);
    	//先创建一个基础的 msg NL80211_CMD_ASSOCIATE
    	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_ASSOCIATE);
    	if (!msg)
    		return -1;
    	//将 parmas 参数塞进 msg
    	ret = nl80211_connect_common(drv, params, msg);
    	if (ret)
    		goto fail;
    
    	if (params->mgmt_frame_protection == MGMT_FRAME_PROTECTION_REQUIRED &&
    	    nla_put_u32(msg, NL80211_ATTR_USE_MFP, NL80211_MFP_REQUIRED))
    		goto fail;
    
    	if (params->fils_kek) {
    		wpa_printf(MSG_DEBUG, "  * FILS KEK (len=%u)",
    			   (unsigned int) params->fils_kek_len);
    		if (nla_put(msg, NL80211_ATTR_FILS_KEK, params->fils_kek_len,
    			    params->fils_kek))
    			goto fail;
    	}
    	if (params->fils_nonces) {
    		wpa_hexdump(MSG_DEBUG, "  * FILS nonces (for AAD)",
    			    params->fils_nonces,
    			    params->fils_nonces_len);
    		if (nla_put(msg, NL80211_ATTR_FILS_NONCES,
    			    params->fils_nonces_len, params->fils_nonces))
    			goto fail;
    	}
    	//将 msg 发出
        /*
        - 放入 NL80211_CMD_ASSOCIATE
        - 把参数通过nl80211_connect_common函数在它里面put各种nl消息
        - 调用send_and_recv_msgs_owner发送出去msg
        - 
        */
    	ret = send_and_recv_msgs_owner(drv, msg,
    				       get_connect_handle(drv->first_bss), 1,
    				       NULL, NULL, NULL, NULL);
    	msg = NULL;
    	if (ret) {
    		wpa_dbg(drv->ctx, MSG_DEBUG,
    			"nl80211: MLME command failed (assoc): ret=%d (%s)",
    			ret, strerror(-ret));
    		nl80211_dump_scan(drv);
    	} else {
    		wpa_printf(MSG_DEBUG,
    			   "nl80211: Association request send successfully");
    	}
    
    fail:
    	nlmsg_free(msg);
    	return ret;
    }
    
  2. wpa_driver_nl80211_connect

    static int wpa_driver_nl80211_connect(
    	struct wpa_driver_nl80211_data *drv,
    	struct wpa_driver_associate_params *params,
    	struct nl_sock *nl_connect)
    {
    	int ret;
       // 尝试连接 下面分析该函数
    	ret = wpa_driver_nl80211_try_connect(drv, params, nl_connect);
    	if (ret == -EALREADY) {
    		/*
    		 * cfg80211 does not currently accept new connections if
    		 * we are already connected. As a workaround, force
    		 * disconnection and try again.
    		 * 如果已经连接上,就断开,从新连接
    		 */
    		if (wpa_driver_nl80211_disconnect(
    			    drv, WLAN_REASON_PREV_AUTH_NOT_VALID, nl_connect))
    			return -1;
    		ret = wpa_driver_nl80211_try_connect(drv, params, nl_connect);
    	}
    	return ret;
    }
    
  3. wpa_driver_nl80211_try_connect

    static int wpa_driver_nl80211_try_connect(
    	struct wpa_driver_nl80211_data *drv,
    	struct wpa_driver_associate_params *params,
    	struct nl_sock *nl_connect)
    {
    	struct nl_msg *msg;
    	enum nl80211_auth_type type;
    	int ret;
    	int algs;
    
    #ifdef CONFIG_DRIVER_NL80211_QCA
    	if (params->req_key_mgmt_offload && params->psk &&
    	    (params->key_mgmt_suite == WPA_KEY_MGMT_PSK ||
    	     params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256 ||
    	     params->key_mgmt_suite == WPA_KEY_MGMT_FT_PSK)) {
    		wpa_printf(MSG_DEBUG, "nl80211: Key management set PSK");
    		ret = issue_key_mgmt_set_key(drv, params->psk, 32);
    		if (ret)
    			return ret;
    	}
    #endif /* CONFIG_DRIVER_NL80211_QCA */
    
    	wpa_printf(MSG_DEBUG, "nl80211: Connect (ifindex=%d)", drv->ifindex);
        //第一步:创建消息nl消息  命令问为  NL80211_CMD_CONNECT
    	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_CONNECT);
    	if (!msg)
    		return -1;
       //第二步:传入参数放入nl attr中
    	ret = nl80211_connect_common(drv, params, msg);
       //这中间删除掉一部分,具体看代码
    	ret = nl80211_set_conn_keys(params, msg);
    
       // 发送消息
    	ret = send_and_recv_msgs_owner(drv, msg, nl_connect, 1, NULL,(void *) -1, NULL, NULL);
    	msg = NULL;
        
    fail:
    	nl80211_nlmsg_clear(msg);
    	nlmsg_free(msg);
    	return ret;
    
    }
    

    至此应用层完成工作,当驱动收到 NL80211_CMD_CONNECT 该消息后,会调用驱动的nl80211_connect函数进行处理

    qca/src/linux-4.4/net/wireless/nl80211.c文件下nl80211_ops里面定义的nl80211_connect函数,

    static const struct genl_ops nl80211_ops[] = {
    {
    		.cmd = NL80211_CMD_CONNECT,
    		.doit = nl80211_connect,
    		.policy = nl80211_policy,
    		.flags = GENL_ADMIN_PERM,
    		.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
    				  NL80211_FLAG_NEED_RTNL,
    	}
    }
    

    下面就是驱动要干的事情了

  4. 驱动层我们这里不做分析,当驱动处理后,同样会发送NL80211_CMD_CONNECT到应用层wpa,wpa

    process_global_event
    	do_process_drv_event
        {
        	case NL80211_CMD_CONNECT:
        	case NL80211_CMD_ROAM:
               mlme_event_connect(drv, cmd,xx)
               {
                   处理驱动上传的事件
               }
        }