最近看到了一个大神的博客,结合自己学习 wpa_supplicant 的体验,有了一些感悟。
ok, 总结完毕。
这一期,打算从一个具体的动作 assoc 入手,去看 wpa_supplicant 与 driver 的交互,简单点说,assoc 这个动作怎么调起的driver
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 队列
一个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 就会被调用
简单说下做的一些关键动作
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, ¶ms);
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, ¶ms); 这一步,继续看
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-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
(坏了,越看越觉得有点不对劲,,先写到这里,缓缓)