6.4.3 wpa_supplicant中的WSC处理

最后更新于:2022-04-02 06:04:33

WPAS中,WSC的处理就不像在App及Framework WifiService中那么简单。先来看WSC的初始化流程。 **1、WSC模块初始化** WSC模块的初始化工作位于wpa_supplicant_init_iface(不熟悉的读者请参考4.3.4节“wpa_supplicant_init_iface分析之五”)函数的最后几行中,相关代码如下所示。 **wpa_supplicant.c::wpa_supplicant_init_iface** ~~~ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,struct wpa_interface *iface) { ......// 其他代码。可参考4.3.4节“wpa_supplicant_init_iface分析之五” // 调用wpas_wps_init函数初始化WSC相关模块 if (wpas_wps_init(wpa_s)) return -1; if (wpa_supplicant_init_eapol(wpa_s) < 0) return -1; wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol); ...... } ~~~ 注意,在WPAS代码中WSC称为WPS2。为了行文方便,以后将不再区分WPS和WSC。 wpas_wps_init的代码如下所示。 **wps_supplicant.c::wpas_wps_init** ~~~ int wpas_wps_init(struct wpa_supplicant *wpa_s) { struct wps_context *wps; // wps_context是WPS模块的核心数据结构 // wps_registrar_config代表Registrar的配置信息 struct wps_registrar_config rcfg; struct hostapd_hw_modes *modes; u16 m; wps = os_zalloc(sizeof(*wps)); ...... /* 设置两个重要的回调函数。其中,cred_cb在EAP-WSC模块解析credential属性集时使用。后面将见到其用法。 event_cb用于通知WSC模块发生的一些事件。例如“WSC-SUCCESS”就在wpa_supplicant_wps_event 中处理。 */ wps->cred_cb = wpa_supplicant_wps_cred; wps->event_cb = wpa_supplicant_wps_event; wps->cb_ctx = wpa_s; // 初始化设备信息,这些信息来自图6-33中的配置 wps->dev.device_name = wpa_s->conf->device_name; wps->dev.manufacturer = wpa_s->conf->manufacturer; wps->dev.model_name = wpa_s->conf->model_name; wps->dev.model_number = wpa_s->conf->model_number; wps->dev.serial_number = wpa_s->conf->serial_number; wps->config_methods = // 将字符串描述的WSC方法转换成对应的标志位 wps_config_methods_str2bin(wpa_s->conf->config_methods); ......// 配置参数检查,Label和Display不能同时配置。即设备不能同时使用静态PIN码和动态PIN码 /* WSC规范新增了Virtual Push Button和Virtual Display两种方法,wps_fix_config_methods 函数将判断设备是否支持Push Button或者Display。如果二者支持,需要为WSC添加对应的Virtual 方法。下面这个函数仅在CONFIG_WPS2宏被定义的情况下有实际作用。 */ wps->config_methods = wps_fix_config_methods(wps->config_methods); wps->dev.config_methods = wps->config_methods; os_memcpy(wps->dev.pri_dev_type, wpa_s->conf->device_type, WPS_DEV_TYPE_LEN); wps->dev.num_sec_dev_types = wpa_s->conf->num_sec_device_types; os_memcpy(wps->dev.sec_dev_type, wpa_s->conf->sec_device_type, WPS_DEV_TYPE_LEN * wps->dev.num_sec_dev_types); wps->dev.os_version = WPA_GET_BE32(wpa_s->conf->os_version); modes = wpa_s->hw.modes; // 设置RF Bands相关信息 if (modes) { for (m = 0; m < wpa_s->hw.num_modes; m++) { if (modes[m].mode == HOSTAPD_MODE_IEEE80211B || modes[m].mode == HOSTAPD_MODE_IEEE80211G){ wps->dev.rf_bands |= WPS_RF_24GHZ; } else if (modes[m].mode == HOSTAPD_MODE_IEEE80211A){ wps->dev.rf_bands |= WPS_RF_50GHZ; } } } if (wps->dev.rf_bands == 0) wps->dev.rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ; os_memcpy(wps->dev.mac_addr, wpa_s->own_addr, ETH_ALEN); wpas_wps_set_uuid(wpa_s, wps); // 设置uuid,如果没有配置的话,则利用MAC地址生成UUID // STA默认支持的认证算法和加密算法 // 结合前面介绍的理论知识,读者能想起来对应的Attribute是什么吗 wps->auth_types = WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK; wps->encr_types = WPS_ENCR_AES | WPS_ENCR_TKIP; os_memset(&rcfg, 0, sizeof(rcfg)); rcfg.new_psk_cb = wpas_wps_new_psk_cb; rcfg.pin_needed_cb = wpas_wps_pin_needed_cb; rcfg.set_sel_reg_cb = wpas_wps_set_sel_reg_cb; rcfg.cb_ctx = wpa_s; /* 创建一个wps_registrar对象,该对象代表Registrar。不过Enrollee中用不到它,故此处不开展 分析。感兴趣的读者请在学完本章后再自行研究相关内容。 */ wps->registrar = wps_registrar_init(wps, &rcfg); ...... wpa_s->wps = wps; return 0; } ~~~ 图6-33为Galaxy Note 2中wpa_supplicant.conf的配置文件。 :-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/7530cc5c12effc8636ae777ebf17b5aa_860x290.jpg) 图6-33 wpa_supplicant.conf示例 下面来看WPS_PIN命令的处理流程。 **2、WPS_PIN命令处理** 根据前文介绍,WifiStateMachine将发送"WPS_PIN any"命令给WPAS以触发WSC的工作流程。该命令的处理代码如下所示。 **ctrl_iface.c::wpa_supplicant_ctrl_iface_wps_pin** ~~~ static int wpa_supplicant_ctrl_iface_wps_pin(struct wpa_supplicant *wpa_s, char *cmd,char *buf, size_t buflen) { u8 bssid[ETH_ALEN], *_bssid = bssid; char *pin; int ret; /* cmd传入此函数时已经将“WPS_PIN any”中的“WPS_PIN ”(注意右边引号前的空格)子串忽略了, 所以cmd的取值是“any”。 */ pin = os_strchr(cmd, ' '); if (pin) *pin++ = '\0'; if (os_strcmp(cmd, "any") == 0) _bssid = NULL; ...... if (pin) { // 上层没有传入PIN码,故略去此段代码 } // 启动WPS流程。详情见下文 ret = wpas_wps_start_pin(wpa_s, _bssid, NULL, 0, DEV_PW_DEFAULT); ...... done: ret = os_snprintf(buf, buflen, "%08d", ret); // 将PIN码转成字符串返回给WifiStateMachine return ret; } ~~~ 上述代码中,wpas_wps_start_pin函数将被调用以开始WPS流程。调用该函数时,相关的参数取值情况是:_bssid为NULL,DEV_PW_DEFAULT值为0。 来看wpas_wps_start_pin的代码,如下所示。 **wps_supplicant.c::wpas_wps_start_pin** ~~~ int wpas_wps_start_pin(struct wpa_supplicant *wpa_s, const u8 *bssid,const char *pin, int p2p_group, u16 dev_pw_id) { struct wpa_ssid *ssid; char val[128]; unsigned int rpin = 0; wpas_clear_wps(wpa_s); // 清空之前的WPS信息 /* wpas_wps_add_network将创建一个wpa_ssid对象,它用于保存一个无线网络的配置信息。 在wpas_wps_add_network中,该网络的key_mgmt将被为WPA_KEY_MGMT_WPS。另外,每一个wpa_ssid对象 都有一个类型为eap_peer_config的成员,该成员用于保存EAP Supplicant的配置信息。对于WPS来说, 该配置信息的Method被设置为EAP-WSC,identity被设为“WFA-SimpleConfig-Enrollee-1-0”。 wpas_wps_add_network函数比较简单,请读者自行研究。 */ ssid = wpas_wps_add_network(wpa_s, 0, bssid); ...... ssid->temporary = 1; ssid->p2p_group = p2p_group; ......// CONFIG_P2P的处理 if (pin){ os_snprintf(val, sizeof(val), "\"pin=%s dev_pw_id=%u\"",pin, dev_pw_id); }else { /* 生成一个随机PIN码。WSC对PIN码格式有所规定。PIN码一共包含8个数字,最后一个数字(即最右边的 一个数字是前7个数字的校验和)。 */ rpin = wps_generate_pin(); // 以图6-2为例,PIN码为“01308204”,所以下面val的值为“pin=01308204 dev_pw_id=0” os_snprintf(val, sizeof(val), "\"pin=%08d dev_pw_id=%u\"",rpin, dev_pw_id); } // 将value值保存到wpa_ssid中eap成员变量的phase1中 wpa_config_set(ssid, "phase1", val, 0); if (wpa_s->wps_fragment_size) ssid->eap.fragment_size = wpa_s->wps_fragment_size; // 注册一个WSC超时任务,超时时间是120秒。该时间也是由WSC规范规定的 eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout, wpa_s, NULL); // 重新关联并发起扫描。该函数比较简单,请读者自行研究。该函数内部将发起扫描请求 wpas_wps_reassoc(wpa_s, ssid, bssid); return rpin; } ~~~ 由上述代码可知,wpas_wps_start_pin添加了一个潜在的和WPS相关的无线网络配置项。接下来的工作自然是需要扫描周围的无线网络以搜索那些支持WSC功能的AP。这一工作正是属于前文介绍的WSC Discovery Phase。 **3、发起扫描请求** 根据前文对WSC基础知识的介绍,STA发起扫描请求时需要在Probe Request帧中添加WSC IE。WPAS中,扫描工作的代码在wpa_supplicant_scan函数中,我们重点关注其中和WSC相关的部分,如下所示。 **scan.c::wpa_supplicant_scan** ~~~ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx){ ......// 该函数的详情请参考4.5.3节“无线网络扫描流程分析” wpa_supplicant_optimize_freqs(wpa_s, ¶ms); // 下面这个函数将处理WSC IE extra_ie = wpa_supplicant_extra_ies(wpa_s, ¶ms); } ~~~ **scan.c::wpa_supplicant_extra_ies** ~~~ static struct wpabuf *wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s, struct wpa_driver_scan_params *params){ struct wpabuf *extra_ie = NULL; #ifdef CONFIG_WPS // 处理WPS int wps = 0; enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO; #endif /* CONFIG_WPS */ #ifdef CONFIG_WPS /* wpas_wps_in_use判断是否需要在Probe Request中添加WSC IE。WPAS的判断标准比较简单, 就是查询所有的wpa_ssid对象,判断它们的key_mgmt是否设置了WPA_KEY_MGMT_WPS。如果有, 表明搜索的时候需要支持WSC IE。我们在介绍“WPS_PIN命令处理”时曾说过,WPAS将添加一个 wpa_ssid对象,并设置key_mgmt为WPA_KEY_MGMT_WPS。 wpas_wps_in_use的返回值也有含义,返回1表明使用PIN方法,返回2表明使用PBC方法。 wpas_wps_in_use函数比较简单,请读者自行阅读。 */ wps = wpas_wps_in_use(wpa_s, &req_type); if (wps) { struct wpabuf *wps_ie; // 构造Probe Request中的WSC IE wps_ie = wps_build_probe_req_ie(wps == 2, &wpa_s->wps->dev, wpa_s->wps->uuid, req_type, 0, NULL); if (wps_ie) { if (wpabuf_resize(&extra_ie, wpabuf_len(wps_ie)) == 0) wpabuf_put_buf(extra_ie, wps_ie); wpabuf_free(wps_ie); } } ......// CONFIG_P2P处理 #endif /* CONFIG_WPS */ return extra_ie; } ~~~ wps_build_probe_req_ie用于构造WSC IE,读者可简单了解一下该函数,相关代码如下所示。 **wps.c::wps_build_probe_req_ie** ~~~ struct wpabuf * wps_build_probe_req_ie(int pbc, struct wps_device_data *dev, const u8 *uuid, enum wps_request_type req_type,unsigned int num_req_dev_types, const u8 *req_dev_types) { struct wpabuf *ie; ie = wpabuf_alloc(500); ...... if (wps_build_version(ie) ||wps_build_req_type(ie, req_type) || wps_build_config_methods(ie, dev->config_methods) ||wps_build_uuid_e(ie, uuid) || wps_build_primary_dev_type(dev, ie) || wps_build_rf_bands(dev, ie) || wps_build_assoc_state(NULL, ie) || wps_build_config_error(ie, WPS_CFG_NO_ERROR) || wps_build_dev_password_id(ie, pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT) || #ifdef CONFIG_WPS2 // WPS2即WSC wps_build_manufacturer(dev, ie) || wps_build_model_name(dev, ie) || wps_build_model_number(dev, ie) || wps_build_dev_name(dev, ie) || wps_build_wfa_ext(ie, req_type == WPS_REQ_ENROLLEE, NULL, 0) || #endif /* CONFIG_WPS2 */ wps_build_req_dev_type(dev, ie, num_req_dev_types, req_dev_types) ||wps_build_secondary_dev_type(dev, ie)) { ...... // 错误处理 } ...... return wps_ie_encapsulate(ie); } ~~~ 最后,WPAS将发送携带了WSC IE的Probe Request帧。对于Galaxy Note 2来说,其发送的WSC IE信息可参考图6-18。 **4、处理扫描结果** 发送扫描请求后,WPAS下一步的工作就是处理搜索到的扫描结果。这部分的流程在4.5.3节扫描结果处理流程中有详细分析。在该流程中,和WSC相关的工作如下。 * wpa_supplicant_get_scan_results:获取扫描结果。该函数内部将对搜索到的AP进行排序,它对WSC有特殊处理。 * wpa_supplicant_pick_network:选择合适的AP作为目标AP。如果使用WSC的话,该函数将优先选择支持WSC的AP。 下面将分别介绍上述两个函数中和WSC处理相关的流程。 **①、wpa_supplicant_get_scan_results处理** 代码如下。 **scan.c::wpa_supplicant_get_scan_results** ~~~ struct wpa_scan_results * wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,struct scan_info *info, int new_scan) { ...... // 获取扫描结果 scan_res = wpa_drv_get_scan_results2(wpa_s); #ifdef CONFIG_WPS // WPAS当前处于WPS处理过程中,设置排序函数为wpa_scan_result_wps_compar if (wpas_wps_in_progress(wpa_s)) compar = wpa_scan_result_wps_compar; #endif /* CONFIG_WPS */ // 利用qsort函数对扫描结果进行升序排序。排序时将使用compar函数将较两个元素A、B的大小 // compar返回负数,表示A < B,compar返回0,表示A=B,compar返回正数,表示A > B qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),compar); ......// 更新BSS return scan_res; } struct wpa_scan_results * wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,struct scan_info *info, int new_scan) { ...... // 获取扫描结果 scan_res = wpa_drv_get_scan_results2(wpa_s); #ifdef CONFIG_WPS // WPAS当前处于WPS处理过程中,设置排序函数为wpa_scan_result_wps_compar if (wpas_wps_in_progress(wpa_s)) compar = wpa_scan_result_wps_compar; #endif /* CONFIG_WPS */ // 利用qsort函数对扫描结果进行升序排序。排序时将使用compar函数将较两个元素A、B的大小 // compar返回负数,表示A < B,compar返回0,表示A=B,compar返回正数,表示A > B qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),compar); ......// 更新BSS return scan_res; } ~~~ 由上述代码可知,当WPAS正处于WPS处理流程中,搜索到的AP将通过qsort以及wpa_scan_result_wps_compar进行排序比较。wpa_scan_result_wps_compar的代码如下所示。 **scan.c::wpa_scan_result_wps_compar** ~~~ static int wpa_scan_result_wps_compar(const void *a, const void *b) { struct wpa_scan_res **_wa = (void *) a; struct wpa_scan_res **_wb = (void *) b; struct wpa_scan_res *wa = *_wa; struct wpa_scan_res *wb = *_wb; int uses_wps_a, uses_wps_b; struct wpabuf *wps_a, *wps_b; int res; /* wpa_scan_get_vendor_ie返回值的类型为u8*,它指向wpa_scan_res中指定IE(此处是 WSC IE)所在的内存位置。如果wpa_scan_res中没有WSC IE,则返回为空。 */ uses_wps_a = wpa_scan_get_vendor_ie(wa, WPS_IE_VENDOR_TYPE) != NULL; uses_wps_b = wpa_scan_get_vendor_ie(wb, WPS_IE_VENDOR_TYPE) != NULL; // 无线网络A支持WPS,而B不支持,则返回-1。这样,在“排座位”的时候,A将排在前面(A < B) if (uses_wps_a && !uses_wps_b) return -1; // 无线网络A不支持WPS,而B支持,则B排在前面(B < A) if (!uses_wps_a && uses_wps_b) return 1; // 如果无线网络A和B均支持WPS,则还需要进一步判断 if (uses_wps_a && uses_wps_b) { /* wpa_scan_get_vendor_ie_multi将从拷贝指定IE的内容复制到一块新的内存中,该内存地址即 wpa_scan_get_vendor_ie_multi的返回值。 */ wps_a = wpa_scan_get_vendor_ie_multi(wa, WPS_IE_VENDOR_TYPE); wps_b = wpa_scan_get_vendor_ie_multi(wb, WPS_IE_VENDOR_TYPE); /* 如果周围有多个支持WPS的无线网络,则设置Selected Registrar属性(而且值为1)的AP 将位于前排。 */ res = wps_ap_priority_compar(wps_a, wps_b); wpabuf_free(wps_a); // 释放wpa_scan_get_vendor_ie_multi创建的新内存 wpabuf_free(wps_b); if (res) return res; } // 对于没有WSC支持的AP,其排座顺序仅考虑它们的信号强度和质量 if (wb->level == wa->level) return wb->qual - wa->qual; return wb->level - wa->level; } ~~~ 根据上面的代码可知,WSC的AP扫描结果“排座”规则如下。 * 支持WSC功能的AP排在不支持WSC的AP之前。 * 对于两个同时支持WSC功能的AP来说,Selected Regsitrar值为1的AP排在前面。 * 对于不支持WSC的AP来说,信号强度和质量好的AP排在前面。 当扫描结果排完序后,WPAS的下一步工作就是从众多搜索到的AP中挑选一个作为目标AP以发起关联请求。该工作由wpa_supplicant_pick_network完成。 **2、wpa_supplicant_pick_network处理** wpa_supplicant_pick_nework的代码比较简单,如下所示。 **events.c::wpa_supplicant_pick_nework** ~~~ static struct wpa_bss * wpa_supplicant_pick_network(struct wpa_supplicant *wpa_s, struct wpa_scan_results *scan_res, struct wpa_ssid **selected_ssid) { struct wpa_bss *selected = NULL; int prio; while (selected == NULL) { // 按照优先级搜索扫描结果 for (prio = 0; prio < wpa_s->conf->num_prio; prio++) { selected = wpa_supplicant_select_bss(wpa_s, scan_res, wpa_s->conf->pssid[prio], selected_ssid); if (selected) break; } ......// 其他处理 } return selected; } ~~~ wpa_supplicant_select_bss内部将通过调用wpa_scan_res_match的函数来选取一个合适的无线网络。该函数的代码如下所示。 **events.c::wpa_scan_res_match** ~~~ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, int i, struct wpa_scan_res *bss,struct wpa_ssid *group) { const u8 *ssid_; u8 wpa_ie_len, rsn_ie_len, ssid_len; int wpa; struct wpa_blacklist *e; const u8 *ie; struct wpa_ssid *ssid; ie = wpa_scan_get_ie(bss, WLAN_EID_SSID); ssid_ = ie ? ie + 2 : (u8 *) ""; ssid_len = ie ? ie[1] : 0; ...... for (ssid = group; ssid; ssid = ssid->pnext) { int check_ssid = wpa ? 1 : (ssid->ssid_len != 0); ...... #ifdef CONFIG_WPS if ((ssid->key_mgmt & WPA_KEY_MGMT_WPS) && e && e->count > 0) continue; /* 通过“WPS_PIN any”命令创建的wpa_ssid还没有设置ssid。wpas_wps_ssid_wildcard_ok 用于判断是否需要进行ssid检查。该函数内部将利用下文代码中提到的wps_is_addr_authorized函数。 由于笔者测试用的AP仅支持WPS,所以wpas_wps_ssid_wildcard_ok返回非零值。这样,if条件生效, check_ssid被设置为0。 */ if (wpa && ssid->ssid_len == 0 && wpas_wps_ssid_wildcard_ok(wpa_s, ssid, bss)) check_ssid = 0; ...... #endif /* CONFIG_WPS */ ......// 其他判断 // 该函数内部先调用wpas_wps_ssid_bss_match函数以判断是否有合适的AP,如果有则选中它 if (!wpa_supplicant_ssid_bss_match(wpa_s, ssid, bss)) continue; ......// 其他判断。例如检查wpa_ssid中的ssid是否匹配搜索结果中的ssid。如果不匹配,则不能选择该AP return ssid; } return NULL; } ~~~ 直接来看wpas_wps_ssid_bss_match函数,代码如下所示。 **wpa_supplicant.c::wpas_wps_ssid_bss_match** ~~~ int wpas_wps_ssid_bss_match(struct wpa_supplicant *wpa_s,struct wpa_ssid *ssid, struct wpa_scan_res *bss) { struct wpabuf *wps_ie; if (!(ssid->key_mgmt & WPA_KEY_MGMT_WPS)) return -1; // 获取该WSC IE信息 wps_ie = wpa_scan_get_vendor_ie_multi(bss, WPS_IE_VENDOR_TYPE); if (eap_is_wps_pbc_enrollee(&ssid->eap)) { ...... // PBC处理 } // 判断eap_peer_config设置的identity是否为“WFA-SimpleConfig-Enrollee-1-0” if (eap_is_wps_pin_enrollee(&ssid->eap)) { ....... /* 笔者使用的AP没有包含AuthorizedMACs子属性,并且该AP也不支持WSC。所以下面这个函数将返回1, 如此,if判断失败。读者可参考图6-19。 */ // AP返回的Probe Response帧信息 if (!wps_is_addr_authorized(wps_ie, wpa_s->own_addr, 1)) ...... else { wpa_printf(MSG_DEBUG, " selected based on WPS IE " "(Authorized MAC or Active PIN)"); } wpabuf_free(wps_ie); return 1; // 选中 } ...... return -1; } ~~~ 总之,在周围空间有很多无线网络的情况下,笔者测试WSC时使用的AP将会被选中作为目标AP。接下来的流程就和4.5.3节关联无线网络处理流程分析的内容一样,STA将关联到目标AP。对于非WSC来说,AP和STA将开展4-Way Handshake流程,而对于WSC来说,AP和STA将开展EAP-WSC流程。 马上来看EAP-WSC处理流程,它也是整个WSC流程的核心内容。
';