【wpa_supplicant】从 assoc 动作窥伺supplicant与driver的交互(一)

邓鸿雪
2023-12-01

最近看到了一个大神的博客,结合自己学习 wpa_supplicant 的体验,有了一些感悟。

  1. 阅读源码,特别是一整份极大的源码时,要多去揣测作者的编码心态和编码习惯;
  2. 初期走读的时候,不要去尝试做到面面俱到,每个函数,每个变量都指望知道是用来干什么的,知道个大概即可;
  3. 重点是去理解穿插其中的一些机制,比如 eloop,比如 radio_work队列;

ok, 总结完毕。

这一期,打算从一个具体的动作 assoc 入手,去看 wpa_supplicant 与 driver 的交互,简单点说,assoc 这个动作怎么调起的driver

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;

	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 {
		wpa_s_clear_sae_rejected(wpa_s);
		wpa_s_setup_sae_pt(wpa_s->conf, ssid);
	}

	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 (wpas_restore_permanent_mac_addr(wpa_s) < 0)
			return;
	}
	wpa_s->last_ssid = ssid;

	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;
	}

	if (ssid->mode == WPAS_MODE_AP || ssid->mode == WPAS_MODE_P2P_GO ||
	    ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) {
		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;
		return;
	}

	if (ssid->mode == WPAS_MODE_MESH) {
		wpa_msg(wpa_s, MSG_ERROR,
			"mesh mode support not included in the build");
		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, wpa_bss_ie_ptr(bss), 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;
	}

	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);
	}
}

wpa_supplicant_associate 需要传入两个参数 wpa_bss, wpa_ssid
可以看到 wpa_supplicant_associate 的最终操作就是把 connect 动作塞进 radio_work 队列

radio_work队列

一个wpa_supplicant实体拥有一个wpa_radio变量,wpa_radio内含一个wpa_radio_work list,表示正在排队的需要用到此radio口的动作
supplicant想要进行一些动作,都会将 动作(字符串形式)和实际动作(注册的回调函数)使用 radio_add_work 方法注入当前radio的radio_work队列,按序执行(当然,也可以插队)
注:笔者看的源码里,radio口支持同时跑两个动作,即 MAX_ACTIVE_WORKS == 2

回到上文,当 radio_work 队列空闲(轮到add的 connect 动作时),wpas_start_assoc_cb 就会被调用

wpas_start_assoc_cb

简单说下做的一些关键动作

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动作

我们只关系 ret = wpa_drv_associate(wpa_s, &params); 这一步,继续看

static inline int wpa_drv_associate(struct wpa_supplicant *wpa_s,
				    struct wpa_driver_associate_params *params)
{
	if (wpa_s->driver->associate) {
		return wpa_s->driver->associate(wpa_s->drv_priv, params);
	}
	return -1;
}

可以看到这里就是调用 wpa_driver_ops 中的 associate,这里的 wpa_driver_ops 当然是 wpa_driver_nl80211_ops 啦

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
	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 发出
	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;
}

凭借之前对 netlink 的一些了解看,supplicant执行动作时是按照 netlink 指定的消息格式(即什么字段填充什么内容,比如nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid) 就可以看出 NL80211_ATTR_MAC 应填充 params->bssid),然后通过 send_and_recv_msgs_owner 发出消息,且很快可以得到执行结果

继续看 kernel/net/wireless

以 kernel-4.19 为例,看 /kernel-4.19/net/wireless/nl80211.c 中 static const struct genl_ops nl80211_ops[] 可知

{            
	.cmd = NL80211_CMD_ASSOCIATE,
	.doit = nl80211_associate,
	.policy = nl80211_policy,
	.flags = GENL_UNS_ADMIN_PERM,
	.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
			  NL80211_FLAG_NEED_RTNL |
			  NL80211_FLAG_CLEAR_SKB,
},

NL80211_CMD_ASSOCIATE 对应处理函数为 nl80211_associate

(坏了,越看越觉得有点不对劲,,先写到这里,缓缓)

 类似资料: