7.4.1 P2P模块初始化
首先来看WPAS中P2P相关模块的初始化。该初始化工作在4.3.4节“wpa_supplicant_init_iface分析之五”曾提到过,其对应的函数wpas_p2p_init如下。
[p2p_supplicant.c::wpas_p2p_init]
- int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s)
- {
- struct p2p_config p2p; // p2p变量指向一个p2p_config对象,代表P2P模块的配置信息
- unsigned int r; int i;
- // ①WPA_DRIVER_FLAGS_P2P_CAPABLE代表Wifi驱动对P2P支持的能力,详情见下文解释
- if (!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_CAPABLE)) return 0;
- if (global->p2p) return 0;
- // 如果wifi driver能完成P2P功能,就不用劳驾WPAS了
- if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_P2P_MGMT) {......}
- // ②初始化并设置p2p_config对象
- os_memset(&p2p, 0, sizeof(p2p));
- p2p.msg_ctx = wpa_s; p2p.cb_ctx = wpa_s;
- p2p.p2p_scan = wpas_p2p_scan; // P2P对应的扫描函数
- .......// 设置一些回调函数
- p2p.get_noa = wpas_get_noa; p2p.go_connected = wpas_go_connected;
- // 设置P2P Device address。
- os_memcpy(wpa_s->global->p2p_dev_addr, wpa_s->own_addr, ETH_ALEN);
- os_memcpy(p2p.dev_addr, wpa_s->global->p2p_dev_addr, ETH_ALEN);
- // 设置P2P模块配置信息,包括device name、model name、uuid等
- p2p.dev_name = wpa_s->conf->device_name;
- p2p.manufacturer = wpa_s->conf->manufacturer;
- p2p.model_name = wpa_s->conf->model_name;
- p2p.model_number = wpa_s->conf->model_number;
- p2p.serial_number = wpa_s->conf->serial_number;
- if (wpa_s->wps) {
- os_memcpy(p2p.uuid, wpa_s->wps->uuid, 16);
- p2p.config_methods = wpa_s->wps->config_methods;
- }
- // 设置Operation Channel信息和listen channel信息
- if (wpa_s->conf->p2p_listen_reg_class &&
- wpa_s->conf->p2p_listen_channel) {
- p2p.reg_class = wpa_s->conf->p2p_listen_reg_class;
- p2p.channel = wpa_s->conf->p2p_listen_channel;
- } else {......// 设置默认值}
- ......
- // 设置国家码
- if (wpa_s->conf->country[0] && wpa_s->conf->country[1]) {
- os_memcpy(p2p.country, wpa_s->conf->country, 2);
- p2p.country[2] = 0x04;
- } else// 配置文件中没有设置国家,所以取值为"XX\x04"
- os_memcpy(p2p.country, "XX\x04", 3);// 回顾图7-7
- // 判断wifi 驱动是否支持配置文件中设置的operationg channel和listen channel
- if (wpas_p2p_setup_channels(wpa_s, &p2p.channels)) {......}
- os_memcpy(p2p.pri_dev_type, wpa_s->conf->device_type,WPS_DEV_TYPE_LEN);
- p2p.num_sec_dev_types = wpa_s->conf->num_sec_device_types;
- os_memcpy(p2p.sec_dev_type, wpa_s->conf->sec_device_type,
- p2p.num_sec_dev_types * WPS_DEV_TYPE_LEN);
- // 是否支持concurrent operation
- p2p.concurrent_operations = !!(wpa_s->drv_flags&WPA_DRIVER_FLAGS_P2P_CONCURRENT);
- p2p.max_peers = 100;// 最多能保存100个对端P2P Device信息
- /*
- 配置文件中没有设置p2p_ssid_postfix,但P2pStateMachine在initializeP2pSettings函数中
- 将设置P2P SSID后缀。以笔者的Galaxy Note 2为例,其P2P SSID后缀为“Android_4aa9”。
- */
- if (wpa_s->conf->p2p_ssid_postfix) {......}
- p2p.p2p_intra_bss = wpa_s->conf->p2p_intra_bss;
- // ③global->p2p指向一个p2p_data结构体,它是WPAS中P2P模块的代表
- global->p2p = p2p_init(&p2p);
- ......
- for (i = 0; i < MAX_WPS_VENDOR_EXT; i++) {// 拷贝vendor厂商特定的WSC属性信息
- if (wpa_s->conf->wps_vendor_ext[i] == NULL) continue;
- p2p_add_wps_vendor_extension(global->p2p, wpa_s->conf->wps_vendor_ext[i]);
- }
- return 0;
- }
由上述代码可知,wpas_p2p_init的工作非常简单,主要包括:
·初始化一个p2p_config对象,然后根据p2p_supplicant.conf文件的信息来设置其中的内容,同时还需要为P2P模块设置一些回调函数。
·调用p2p_init函数以初始化P2P模块。
下面来介绍上述代码中涉及的一些知识。
1.Driver Flags和重要数据结构
先来看上述代码中提到的drv_flags变量。WPAS中,Wi-Fi驱动对P2P功能的支持情况就是由它来表达的。Galaxy Note 2中该变量取值为0x2EAC0,其表达的含义如下。
[—>driver.h]
- #define WPA_DRIVER_FLAGS_AP 0x00000040 // wifi driver支持AP。它使得P2P设备能扮演GO
- /*
- 标志标明association成功后,Kernel driver需要设置WEP key。
- 这个标志出现的原因是Kernel API发生了变动,使得只能在关联成功后才能设置key。
- */
- #define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE 0x00000080
- #define WPA_DRIVER_FLAGS_P2P_CONCURRENT 0x00000200// wifi driver支持STA和P2P的并发运行
- #define WPA_DRIVER_FLAGS_P2P_CAPABLE 0x00000800 // wifi driver支持P2P
- /*
- 7.2.2节Probe Request帧设置曾提到,P2P包含Device Address和Interface Address
- 两种类型的地址。在实际实现过程中,这两个地址分别代表两个Virtual Interface。显然,P2P中第一个
- 和一直存在的是拥有Device Address的Vitural Interface。下面这个标志表示该Virtual Interface
- 可以参与P2P管理(除P2P Group Operation之外的工作)工作以及非P2P相关的工作(例如利用这个
- Virtual Interface 加入到一个BSS)。
- */
- #define WPA_DRIVER_FLAGS_P2P_MGMT_AND_NON_P2P 0x00002000
- /*
- 该标志主要针对associate操作。当关联操作失败后,如果driver支持该选项,则表明driver能处理失败
- 之后的各种收尾工作(Key、timeout等工作)。否则,WPAS需要自己处理这些事情。
- */
- #define WPA_DRIVER_FLAGS_SANE_ERROR_CODES 0x00004000
- /*
- 下面这个标志和off channel机制有关,可参考4.3.4节关于capability的介绍。当802.11
- MAC帧通过off channel发送,下面这个标志表示driver会反馈一个发送情况(TX Report)消息给WPAS。
- */
- #define WPA_DRIVER_FLAGS_OFFCHANNEL_TX 0x00008000
- /*
- 下面这两个标志表示Kernel中的driver是否能反馈Deauthentication/Disassociation帧
- 发送情况(TX Report)。
- */
- #define WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS 0x00020000
下面来看wpas_p2p_init中出现的几个重要数据结构。首先是p2p_config和p2p_data,它们的成员如图7-29所示。
图7-29 p2p_config和p2p_data结构
图7-29展示了p2p_config和p2p_data两个数据结构中一些重要的成员。
·p2p_config定义了20个回调函数。这些回调函数定义了P2P模块和外界交互的接口。在wpas_p2p_init中,这些回调函数均指向p2p_supplicant.c中对应的函数,例如p2p_scan指向wpas_p2p_scan,dev_lost指向wpas_dev_lost。另外,由于回调函数的参数比较复杂,所以图中均省略了参数信息。
·p2p_data指向一个p2p_config对象。
下面来看另外几个重要数据结构的内容,图7-30展示了五种数据结构。
·p2p_device代表一个P2P设备。其中设备名、Device CapabilityBitmap等信息保存在一个类型为p2p_peer_info的对象中。
·p2p_group代表一个P2P Group的信息,其内部包含一个p2p_group_config对象和一个p2p_group_member链表。p2p_group_config表示该Group的配置信息,p2p_group_member代表Group Member即P2P Client的信息。
图7-30 p2p_device及其他数据结构
提示 WPAS中定义了非常多的数据结构类型,这极大增加了初学者的学习难度。根据笔者的经验,建议在学习过程中先简单了解这些数据结构的名字及作用,然后在具体代码分析时再结合代码逻辑来了解这些数据结构及其内部各个成员变量的具体作用。
下面来看p2p_init函数。
2.p2p_init函数
p2p_init函数将初始化WPAS中的P2P模块,其代码如下所示。
[—>p2p.c::p2p_init]
- struct p2p_data * p2p_init(const struct p2p_config *cfg)
- {
- struct p2p_data *p2p;
- ......
- /*
- 从下面这行代码可看出,一个p2p_data对象的内存分布,该内存将包含一个p2p_data的所有信息以及一个
- p2p_config对象的所有信息。
- */
- p2p = os_zalloc(sizeof(*p2p) + sizeof(*cfg));
- // 将p2p_data的cfg成员变量指向保存p2p_config信息的那块内存地址
- p2p->cfg = (struct p2p_config *) (p2p + 1);
- os_memcpy(p2p->cfg, cfg, sizeof(*cfg)); // 拷贝传入的p2p_config信息
- if (cfg->dev_name) p2p->cfg->dev_name = os_strdup(cfg->dev_name);
- ......// 其他信息拷贝
- #ifdef ANDROID_P2P
- p2p->min_disc_int = 2; // listen state的最小时间为200毫秒
- p2p->sd_dev_list = NULL;
- #else
- p2p->min_disc_int = 1;
- #endif
- p2p->max_disc_int = 3;
- // 随机获取next_tie_breaker的初值
- // 第二个参数1表示next_tie_breaker的字节长度,其类型是u8
- os_get_random(&p2p->next_tie_breaker, 1);
- p2p->next_tie_breaker &= 0x01;
- // 设置本机P2P Device的device capability信息
- if (cfg->sd_request) p2p->dev_capab |= P2P_DEV_CAPAB_SERVICE_DISCOVERY;
- p2p->dev_capab |= P2P_DEV_CAPAB_INVITATION_PROCEDURE;
- if (cfg->concurrent_operations)// 支持concurrent功能
- p2p->dev_capab |= P2P_DEV_CAPAB_CONCURRENT_OPER;
- p2p->dev_capab |= P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY;
- dl_list_init(&p2p->devices);
- // 注册一个超时时间(如果定义了ANDROID_P2P宏,该时间为30ms)
- // 用来检测是否有不活跃的p2p_device
- eloop_register_timeout(P2P_PEER_EXPIRATION_INTERVAL, 0,
- p2p_expiration_timeout, p2p, NULL);
- return p2p;
- }
p2p模块初始化还算比较简单。
3.注册Action帧监听事件
4.3.4节分析wpa_driver_nl80211_finish_drv_init时曾介绍过,wpa_driver_nl80211_set_mode函数和P2P关系较大。为什么这么说呢?相信代码能给出最直接的解释。
[—>driver_nl80211.c::wpa_driver_nl80211_set_mode]
- static int wpa_driver_nl80211_set_mode(struct i802_bss *bss, enum nl80211_iftype nlmode)
- {// 注意,在wpa_driver_nl80211_finish_drv_init函数中,nlmode被设置为NL80211_IFTYPE_STATION
- struct wpa_driver_nl80211_data *drv = bss->drv;
- int ret = -1; int i;
- /*
- drv->nlmode的类型为enum nl80211_iftype,4.3.4节关于wpa_driver_nl80211_finish_drv_init
- 的分析中有对该变量的解释。drv->nlmode只有为NL80211_IFTYPE_AP或NL80211_IFTYPE_P2P_GO时,
- is_ap_interface函数才返回非0值。很显然此时virtual interface的类型不可能是GO。
- */
- int was_ap = is_ap_interface(drv->nlmode);
- int res;
- // 设置虚拟interface的类型为NL80211_IFTYPE_STATION
- res = nl80211_set_mode(drv, drv->ifindex, nlmode);
- if (res == 0) {
- drv->nlmode = nlmode;
- ret = 0;
- goto done;// 设置成功,直接跳转到done处
- }
- ......
- done:
- ......
- if (is_ap_interface(nlmode)) {
- nl80211_mgmt_unsubscribe(bss, "start AP");
- if (nl80211_setup_ap(bss)) return -1;
- } else if (was_ap) {
- /* Remove additional AP mode functionality */
- nl80211_teardown_ap(bss);
- } else {
- // 本例将执行下面这个函数以取消监听Action帧事件
- // 由于之前并未注册,所以此时执行这个函数将没有实际作用
- nl80211_mgmt_unsubscribe(bss, "mode change");
- }
- if (!is_ap_interface(nlmode) &&
- nl80211_mgmt_subscribe_non_ap(bss) < 0)// 注册对Action帧的监听事件
- wpa_printf(MSG_DEBUG, "nl80211: Failed to register Action "
- "frame processing - ignore for now");
- return 0;
- }
nl80211_mgmt_subscribte_non_ap将注册对Action帧的监听事件,其作用就是当设备收到Action帧后,Wi-Fi驱动将发送对应的netlink消息给WPAS。来看nl80211_mgmt_subscribte_non_ap函数,代码如下所示。
[—>driver_nl80211.c::nl80211_mgmt_subscribte_non_ap]
- static int nl80211_mgmt_subscribe_non_ap(struct i802_bss *bss)
- {
- struct wpa_driver_nl80211_data *drv = bss->drv;
- /*
- 下面这个函数将注册libnl回调事件到event loop。在4.3.4节关于
- wpa_driver_nl80211_init_nl与nl80211_init_bss分析中曾详细介绍过该函数。
- 总之,当WPAS收到对应的netlink消息后,process_bss_event函数将被调用。
- */
- if (nl80211_alloc_mgmt_handle(bss)) return -1;
- #if defined(CONFIG_P2P) || defined(CONFIG_INTERWORKING)
- ......// 注册对GAS Public Action帧的监听,Service Discovery和它有关
- #endif /* CONFIG_P2P || CONFIG_INTERWORKING */
- #ifdef CONFIG_P2P
- /*
- 注册对P2P Public Action帧的监听,第二个参数中的04-09-50-6F-9A-09指明了P2P Public Action
- 帧Frame Body的Category、Action Field、OUI、OUI-Type(参考表7-3)的取值。即只有收到的
- Frame Body对应字段分别等于上述指定值的Action帧,Wi-Fi驱动才会发送netlink消息给WPAS。
- */
- if (nl80211_register_action_frame(bss, (u8 *) "\x04\x09\x50\x6f\x9a\x09",6) < 0)
- return -1;
- /*
- 注册对P2P Action帧的监听,第二个参数中7F-50-6F-9A-09指明了Action帧Frame Body的
- Category和OUI。根据802.11规范,7F代表Vendor Specific,50-6F-9A是WFA的OUI,最后一个
- 09代表P2P。
- */
- if (nl80211_register_action_frame(bss,(u8 *)"\x7f\x50\x6f\x9a\x09",5) < 0) return -1;
- #endif /* CONFIG_P2P */
- #ifdef CONFIG_IEEE80211W
- ......
- #endif /* CONFIG_IEEE80211W */
- #ifdef CONFIG_TDLS
- ......
- #endif /* CONFIG_TDLS */
- ......// 其他感兴趣帧的注册
- return 0;
- }
由上述代码可知nl80211_mgmt_subscribe_non_ap在P2P方面注册了两种类型的帧监听事件。
·P2P Public Action帧监听事件:根据P2P规范,目前使用的均是802.11 Public Action帧,即Category的值为0x04。目前GON、P2P Invitation、Provision Discovery以及Device Discoverability使用P2P Public Action帧。
·P2P Action帧监听事件:这种类型的帧属于802.11 Action帧的一种,其Category取值为0x7F,OUI指定为WFA的OUI(即50-6F-9A),而OUI-Type指定为P2P(取值为0x09)。目前Notice of Absence、P2P Presence、GO Discoverability使用P2P Action帧。
注意 上述注册的Action帧监听事件对应的处理函数是process_bss_event。
至此,P2P模块以及Action帧监听事件注册等工作都已完成,WPAS马上可为WifiP2pService提供P2P相关的服务了。下面将结合7.2节中介绍的如下几个重要的P2P工作流程来分析代码。
·搜索周围的P2P设备。
·向某个P2P设备发起Provision Discovery流程。
·对端设备开展Group Formation流程,重点关注其中的Group Negotiation。
提示 GON结束后,如果本机设备扮演Client,则后续工作包括Provisioning(即WSC安全配置协商,本机充当Enrollee,参考第6章)和加入Group(类似STA加入AP,参考第4章)。如果本机扮演GO,则后续工作也分为Provisioning(充当Registrar)和处理Client加入Group(扮演AP的角色)。本书不讨论和GO相关的知识,请读者在阅读完相关章节基础上自行研究它们。