]>
Commit | Line | Data |
---|---|---|
afc064fe JM |
1 | /* |
2 | * Interworking (IEEE 802.11u) | |
3 | * Copyright (c) 2011, Qualcomm Atheros | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * Alternatively, this software may be distributed under the terms of BSD | |
10 | * license. | |
11 | * | |
12 | * See README and COPYING for more details. | |
13 | */ | |
14 | ||
15 | #include "includes.h" | |
16 | ||
17 | #include "common.h" | |
18 | #include "common/ieee802_11_defs.h" | |
19 | #include "common/gas.h" | |
20 | #include "drivers/driver.h" | |
21 | #include "wpa_supplicant_i.h" | |
22 | #include "bss.h" | |
23 | #include "gas_query.h" | |
24 | #include "interworking.h" | |
25 | ||
26 | ||
27 | static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s); | |
28 | ||
29 | ||
30 | static struct wpabuf * anqp_build_req(u16 info_ids[], size_t num_ids, | |
31 | struct wpabuf *extra) | |
32 | { | |
33 | struct wpabuf *buf; | |
34 | size_t i; | |
35 | u8 *len_pos; | |
36 | ||
37 | buf = gas_anqp_build_initial_req(0, 4 + num_ids * 2 + | |
38 | (extra ? wpabuf_len(extra) : 0)); | |
39 | if (buf == NULL) | |
40 | return NULL; | |
41 | ||
42 | len_pos = gas_anqp_add_element(buf, ANQP_QUERY_LIST); | |
43 | for (i = 0; i < num_ids; i++) | |
44 | wpabuf_put_le16(buf, info_ids[i]); | |
45 | gas_anqp_set_element_len(buf, len_pos); | |
46 | if (extra) | |
47 | wpabuf_put_buf(buf, extra); | |
48 | ||
49 | gas_anqp_set_len(buf); | |
50 | ||
51 | return buf; | |
52 | } | |
53 | ||
54 | ||
55 | static void interworking_anqp_resp_cb(void *ctx, const u8 *dst, | |
56 | u8 dialog_token, | |
57 | enum gas_query_result result, | |
58 | const struct wpabuf *adv_proto, | |
59 | const struct wpabuf *resp, | |
60 | u16 status_code) | |
61 | { | |
62 | struct wpa_supplicant *wpa_s = ctx; | |
63 | ||
64 | anqp_resp_cb(wpa_s, dst, dialog_token, result, adv_proto, resp, | |
65 | status_code); | |
66 | interworking_next_anqp_fetch(wpa_s); | |
67 | } | |
68 | ||
69 | ||
70 | static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, | |
71 | struct wpa_bss *bss) | |
72 | { | |
73 | struct wpabuf *buf; | |
74 | int ret = 0; | |
75 | int res; | |
76 | u16 info_ids[] = { | |
77 | ANQP_CAPABILITY_LIST, | |
78 | ANQP_VENUE_NAME, | |
79 | ANQP_NETWORK_AUTH_TYPE, | |
80 | ANQP_ROAMING_CONSORTIUM, | |
81 | ANQP_IP_ADDR_TYPE_AVAILABILITY, | |
82 | ANQP_NAI_REALM, | |
83 | ANQP_3GPP_CELLULAR_NETWORK, | |
84 | ANQP_DOMAIN_NAME | |
85 | }; | |
86 | struct wpabuf *extra = NULL; | |
87 | ||
88 | wpa_printf(MSG_DEBUG, "Interworking: ANQP Query Request to " MACSTR, | |
89 | MAC2STR(bss->bssid)); | |
90 | ||
91 | buf = anqp_build_req(info_ids, sizeof(info_ids) / sizeof(info_ids[0]), | |
92 | extra); | |
93 | wpabuf_free(extra); | |
94 | if (buf == NULL) | |
95 | return -1; | |
96 | ||
97 | res = gas_query_req(wpa_s->gas, bss->bssid, bss->freq, buf, | |
98 | interworking_anqp_resp_cb, wpa_s); | |
99 | if (res < 0) { | |
100 | wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request"); | |
101 | ret = -1; | |
102 | } else | |
103 | wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token " | |
104 | "%u", res); | |
105 | ||
106 | wpabuf_free(buf); | |
107 | return ret; | |
108 | } | |
109 | ||
110 | ||
111 | static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s) | |
112 | { | |
113 | struct wpa_bss *bss; | |
114 | int found = 0; | |
115 | const u8 *ie; | |
116 | ||
117 | if (!wpa_s->fetch_anqp_in_progress) | |
118 | return; | |
119 | ||
120 | dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { | |
121 | if (!(bss->caps & IEEE80211_CAP_ESS)) | |
122 | continue; | |
123 | ie = wpa_bss_get_ie(bss, WLAN_EID_EXT_CAPAB); | |
124 | if (ie == NULL || ie[1] < 4 || !(ie[5] & 0x80)) | |
125 | continue; /* AP does not support Interworking */ | |
126 | ||
127 | if (!(bss->flags & WPA_BSS_ANQP_FETCH_TRIED)) { | |
128 | found++; | |
129 | bss->flags |= WPA_BSS_ANQP_FETCH_TRIED; | |
130 | wpa_msg(wpa_s, MSG_INFO, "Starting ANQP fetch for " | |
131 | MACSTR, MAC2STR(bss->bssid)); | |
132 | interworking_anqp_send_req(wpa_s, bss); | |
133 | break; | |
134 | } | |
135 | } | |
136 | ||
137 | if (found == 0) { | |
138 | wpa_msg(wpa_s, MSG_INFO, "ANQP fetch completed"); | |
139 | wpa_s->fetch_anqp_in_progress = 0; | |
140 | } | |
141 | } | |
142 | ||
143 | ||
144 | int interworking_fetch_anqp(struct wpa_supplicant *wpa_s) | |
145 | { | |
146 | struct wpa_bss *bss; | |
147 | ||
148 | if (wpa_s->fetch_anqp_in_progress) | |
149 | return 0; | |
150 | ||
151 | dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) | |
152 | bss->flags &= ~WPA_BSS_ANQP_FETCH_TRIED; | |
153 | ||
154 | wpa_s->fetch_anqp_in_progress = 1; | |
155 | interworking_next_anqp_fetch(wpa_s); | |
156 | ||
157 | return 0; | |
158 | } | |
159 | ||
160 | ||
161 | void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s) | |
162 | { | |
163 | if (!wpa_s->fetch_anqp_in_progress) | |
164 | return; | |
165 | ||
166 | wpa_s->fetch_anqp_in_progress = 0; | |
167 | } | |
168 | ||
169 | ||
170 | int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, | |
171 | u16 info_ids[], size_t num_ids) | |
172 | { | |
173 | struct wpabuf *buf; | |
174 | int ret = 0; | |
175 | int freq; | |
176 | struct wpa_bss *bss; | |
177 | int res; | |
178 | ||
179 | freq = wpa_s->assoc_freq; | |
180 | bss = wpa_bss_get_bssid(wpa_s, dst); | |
181 | if (bss) | |
182 | freq = bss->freq; | |
183 | if (freq <= 0) | |
184 | return -1; | |
185 | ||
186 | wpa_printf(MSG_DEBUG, "ANQP: Query Request to " MACSTR " for %u id(s)", | |
187 | MAC2STR(dst), (unsigned int) num_ids); | |
188 | ||
189 | buf = anqp_build_req(info_ids, num_ids, NULL); | |
190 | if (buf == NULL) | |
191 | return -1; | |
192 | ||
193 | res = gas_query_req(wpa_s->gas, dst, freq, buf, anqp_resp_cb, wpa_s); | |
194 | if (res < 0) { | |
195 | wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request"); | |
196 | ret = -1; | |
197 | } else | |
198 | wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token " | |
199 | "%u", res); | |
200 | ||
201 | wpabuf_free(buf); | |
202 | return ret; | |
203 | } | |
204 | ||
205 | ||
206 | static void interworking_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s, | |
207 | const u8 *sa, u16 info_id, | |
208 | const u8 *data, size_t slen) | |
209 | { | |
210 | const u8 *pos = data; | |
211 | struct wpa_bss *bss = wpa_bss_get_bssid(wpa_s, sa); | |
212 | ||
213 | switch (info_id) { | |
214 | case ANQP_CAPABILITY_LIST: | |
215 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
216 | " ANQP Capability list", MAC2STR(sa)); | |
217 | break; | |
218 | case ANQP_VENUE_NAME: | |
219 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
220 | " Venue Name", MAC2STR(sa)); | |
221 | wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Venue Name", pos, slen); | |
222 | if (bss) { | |
223 | wpabuf_free(bss->anqp_venue_name); | |
224 | bss->anqp_venue_name = wpabuf_alloc_copy(pos, slen); | |
225 | } | |
226 | break; | |
227 | case ANQP_NETWORK_AUTH_TYPE: | |
228 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
229 | " Network Authentication Type information", | |
230 | MAC2STR(sa)); | |
231 | wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Network Authentication " | |
232 | "Type", pos, slen); | |
233 | if (bss) { | |
234 | wpabuf_free(bss->anqp_network_auth_type); | |
235 | bss->anqp_network_auth_type = | |
236 | wpabuf_alloc_copy(pos, slen); | |
237 | } | |
238 | break; | |
239 | case ANQP_ROAMING_CONSORTIUM: | |
240 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
241 | " Roaming Consortium list", MAC2STR(sa)); | |
242 | wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Roaming Consortium", | |
243 | pos, slen); | |
244 | if (bss) { | |
245 | wpabuf_free(bss->anqp_roaming_consortium); | |
246 | bss->anqp_roaming_consortium = | |
247 | wpabuf_alloc_copy(pos, slen); | |
248 | } | |
249 | break; | |
250 | case ANQP_IP_ADDR_TYPE_AVAILABILITY: | |
251 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
252 | " IP Address Type Availability information", | |
253 | MAC2STR(sa)); | |
254 | wpa_hexdump(MSG_MSGDUMP, "ANQP: IP Address Availability", | |
255 | pos, slen); | |
256 | if (bss) { | |
257 | wpabuf_free(bss->anqp_ip_addr_type_availability); | |
258 | bss->anqp_ip_addr_type_availability = | |
259 | wpabuf_alloc_copy(pos, slen); | |
260 | } | |
261 | break; | |
262 | case ANQP_NAI_REALM: | |
263 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
264 | " NAI Realm list", MAC2STR(sa)); | |
265 | wpa_hexdump_ascii(MSG_DEBUG, "ANQP: NAI Realm", pos, slen); | |
266 | if (bss) { | |
267 | wpabuf_free(bss->anqp_nai_realm); | |
268 | bss->anqp_nai_realm = wpabuf_alloc_copy(pos, slen); | |
269 | } | |
270 | break; | |
271 | case ANQP_3GPP_CELLULAR_NETWORK: | |
272 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
273 | " 3GPP Cellular Network information", MAC2STR(sa)); | |
274 | wpa_hexdump_ascii(MSG_DEBUG, "ANQP: 3GPP Cellular Network", | |
275 | pos, slen); | |
276 | if (bss) { | |
277 | wpabuf_free(bss->anqp_3gpp); | |
278 | bss->anqp_3gpp = wpabuf_alloc_copy(pos, slen); | |
279 | } | |
280 | break; | |
281 | case ANQP_DOMAIN_NAME: | |
282 | wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR | |
283 | " Domain Name list", MAC2STR(sa)); | |
284 | wpa_hexdump_ascii(MSG_MSGDUMP, "ANQP: Domain Name", pos, slen); | |
285 | if (bss) { | |
286 | wpabuf_free(bss->anqp_domain_name); | |
287 | bss->anqp_domain_name = wpabuf_alloc_copy(pos, slen); | |
288 | } | |
289 | break; | |
290 | case ANQP_VENDOR_SPECIFIC: | |
291 | if (slen < 3) | |
292 | return; | |
293 | ||
294 | switch (WPA_GET_BE24(pos)) { | |
295 | default: | |
296 | wpa_printf(MSG_DEBUG, "Interworking: Unsupported " | |
297 | "vendor-specific ANQP OUI %06x", | |
298 | WPA_GET_BE24(pos)); | |
299 | return; | |
300 | } | |
301 | break; | |
302 | default: | |
303 | wpa_printf(MSG_DEBUG, "Interworking: Unsupported ANQP Info ID " | |
304 | "%u", info_id); | |
305 | break; | |
306 | } | |
307 | } | |
308 | ||
309 | ||
310 | void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, | |
311 | enum gas_query_result result, | |
312 | const struct wpabuf *adv_proto, | |
313 | const struct wpabuf *resp, u16 status_code) | |
314 | { | |
315 | struct wpa_supplicant *wpa_s = ctx; | |
316 | const u8 *pos; | |
317 | const u8 *end; | |
318 | u16 info_id; | |
319 | u16 slen; | |
320 | ||
321 | if (result != GAS_QUERY_SUCCESS) | |
322 | return; | |
323 | ||
324 | pos = wpabuf_head(adv_proto); | |
325 | if (wpabuf_len(adv_proto) < 4 || pos[0] != WLAN_EID_ADV_PROTO || | |
326 | pos[1] < 2 || pos[3] != ACCESS_NETWORK_QUERY_PROTOCOL) { | |
327 | wpa_printf(MSG_DEBUG, "ANQP: Unexpected Advertisement " | |
328 | "Protocol in response"); | |
329 | return; | |
330 | } | |
331 | ||
332 | pos = wpabuf_head(resp); | |
333 | end = pos + wpabuf_len(resp); | |
334 | ||
335 | while (pos < end) { | |
336 | if (pos + 4 > end) { | |
337 | wpa_printf(MSG_DEBUG, "ANQP: Invalid element"); | |
338 | break; | |
339 | } | |
340 | info_id = WPA_GET_LE16(pos); | |
341 | pos += 2; | |
342 | slen = WPA_GET_LE16(pos); | |
343 | pos += 2; | |
344 | if (pos + slen > end) { | |
345 | wpa_printf(MSG_DEBUG, "ANQP: Invalid element length " | |
346 | "for Info ID %u", info_id); | |
347 | break; | |
348 | } | |
349 | interworking_parse_rx_anqp_resp(wpa_s, dst, info_id, pos, | |
350 | slen); | |
351 | pos += slen; | |
352 | } | |
353 | } |