]> git.ipfire.org Git - thirdparty/hostap.git/blame - src/common/gas_server.c
DPP: Configuration exchange
[thirdparty/hostap.git] / src / common / gas_server.c
CommitLineData
461d39af
JM
1/*
2 * Generic advertisement service (GAS) server
3 * Copyright (c) 2017, Qualcomm Atheros, Inc.
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "includes.h"
10
11#include "utils/common.h"
12#include "utils/list.h"
13#include "utils/eloop.h"
14#include "ieee802_11_defs.h"
15#include "gas.h"
16#include "gas_server.h"
17
18
19#define MAX_ADV_PROTO_ID_LEN 10
20#define GAS_QUERY_TIMEOUT 10
21
22struct gas_server_handler {
23 struct dl_list list;
24 u8 adv_proto_id[MAX_ADV_PROTO_ID_LEN];
25 u8 adv_proto_id_len;
26 struct wpabuf * (*req_cb)(void *ctx, const u8 *sa,
27 const u8 *query, size_t query_len);
28 void (*status_cb)(void *ctx, struct wpabuf *resp, int ok);
29 void *ctx;
30 struct gas_server *gas;
31};
32
33struct gas_server_response {
34 struct dl_list list;
35 size_t offset;
36 u8 frag_id;
37 struct wpabuf *resp;
38 int freq;
39 u8 dst[ETH_ALEN];
40 u8 dialog_token;
41 struct gas_server_handler *handler;
42};
43
44struct gas_server {
45 struct dl_list handlers; /* struct gas_server_handler::list */
46 struct dl_list responses; /* struct gas_server_response::list */
47 void (*tx)(void *ctx, int freq, const u8 *da, struct wpabuf *resp,
48 unsigned int wait_time);
49 void *ctx;
50};
51
52static void gas_server_free_response(struct gas_server_response *response);
53
54
55static void gas_server_response_timeout(void *eloop_ctx, void *user_ctx)
56{
57 struct gas_server_response *response = eloop_ctx;
58
59 wpa_printf(MSG_DEBUG, "GAS: Response @%p timeout for " MACSTR
60 " (dialog_token=%u freq=%d frag_id=%u sent=%lu/%lu) - drop pending data",
61 response, MAC2STR(response->dst), response->dialog_token,
62 response->freq, response->frag_id,
63 (unsigned long) response->offset,
64 (unsigned long) wpabuf_len(response->resp));
65 response->handler->status_cb(response->handler->ctx,
66 response->resp, 0);
67 response->resp = NULL;
68 dl_list_del(&response->list);
69 gas_server_free_response(response);
70}
71
72
73static void gas_server_free_response(struct gas_server_response *response)
74{
75 if (!response)
76 return;
77 wpa_printf(MSG_DEBUG, "DPP: Free GAS response @%p", response);
78 eloop_cancel_timeout(gas_server_response_timeout, response, NULL);
79 wpabuf_free(response->resp);
80 os_free(response);
81}
82
83
84static void
85gas_server_send_resp(struct gas_server *gas, struct gas_server_handler *handler,
86 const u8 *da, int freq, u8 dialog_token,
87 struct wpabuf *query_resp)
88{
89 size_t max_len = (freq > 56160) ? 928 : 1400;
90 size_t hdr_len = 24 + 2 + 5 + 3 + handler->adv_proto_id_len + 2;
91 size_t resp_frag_len;
92 struct wpabuf *resp;
93 u16 comeback_delay;
94 struct gas_server_response *response;
95
96 if (!query_resp)
97 return;
98
99 response = os_zalloc(sizeof(*response));
100 if (!response)
101 return;
102 wpa_printf(MSG_DEBUG, "DPP: Allocated GAS response @%p", response);
103 response->freq = freq;
104 response->handler = handler;
105 os_memcpy(response->dst, da, ETH_ALEN);
106 response->dialog_token = dialog_token;
107 if (hdr_len + wpabuf_len(query_resp) > max_len) {
108 /* Need to use comeback to initiate fragmentation */
109 comeback_delay = 1;
110 resp_frag_len = 0;
111 } else {
112 /* Full response fits into the initial response */
113 comeback_delay = 0;
114 resp_frag_len = wpabuf_len(query_resp);
115 }
116
117 resp = gas_build_initial_resp(dialog_token, WLAN_STATUS_SUCCESS,
118 comeback_delay,
119 handler->adv_proto_id_len +
120 resp_frag_len);
121 if (!resp) {
122 gas_server_free_response(response);
123 return;
124 }
125
126 /* Advertisement Protocol element */
127 wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
128 wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
129 wpabuf_put_u8(resp, 0x7f);
130 /* Advertisement Protocol ID */
131 wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
132
133 /* Query Response Length */
134 wpabuf_put_le16(resp, resp_frag_len);
135 if (!comeback_delay)
136 wpabuf_put_buf(resp, query_resp);
137
138 if (comeback_delay) {
139 wpa_printf(MSG_DEBUG,
140 "GAS: Need to fragment query response");
141 } else {
142 wpa_printf(MSG_DEBUG,
143 "GAS: Full query response fits in the GAS Initial Response frame");
144 }
145 response->offset = resp_frag_len;
146 response->resp = query_resp;
147 dl_list_add(&gas->responses, &response->list);
148 gas->tx(gas->ctx, freq, da, resp, comeback_delay ? 2000 : 0);
149 wpabuf_free(resp);
150 eloop_register_timeout(GAS_QUERY_TIMEOUT, 0,
151 gas_server_response_timeout, response, NULL);
152}
153
154
155static int
156gas_server_rx_initial_req(struct gas_server *gas, const u8 *da, const u8 *sa,
157 const u8 *bssid, int freq, u8 dialog_token,
158 const u8 *data, size_t len)
159{
160 const u8 *pos, *end, *adv_proto, *query_req;
161 u8 adv_proto_len;
162 u16 query_req_len;
163 struct gas_server_handler *handler;
164 struct wpabuf *resp;
165
166 wpa_hexdump(MSG_MSGDUMP, "GAS: Received GAS Initial Request frame",
167 data, len);
168 pos = data;
169 end = data + len;
170
171 if (end - pos < 2 || pos[0] != WLAN_EID_ADV_PROTO) {
172 wpa_printf(MSG_DEBUG,
173 "GAS: No Advertisement Protocol element found");
174 return -1;
175 }
176 pos++;
177 adv_proto_len = *pos++;
178 if (end - pos < adv_proto_len || adv_proto_len < 2) {
179 wpa_printf(MSG_DEBUG,
180 "GAS: Truncated Advertisement Protocol element");
181 return -1;
182 }
183
184 adv_proto = pos;
185 pos += adv_proto_len;
186 wpa_hexdump(MSG_MSGDUMP, "GAS: Advertisement Protocol element",
187 adv_proto, adv_proto_len);
188
189 if (end - pos < 2) {
190 wpa_printf(MSG_DEBUG, "GAS: No Query Request Length field");
191 return -1;
192 }
193 query_req_len = WPA_GET_LE16(pos);
194 pos += 2;
195 if (end - pos < query_req_len) {
196 wpa_printf(MSG_DEBUG, "GAS: Truncated Query Request field");
197 return -1;
198 }
199 query_req = pos;
200 pos += query_req_len;
201 wpa_hexdump(MSG_MSGDUMP, "GAS: Query Request",
202 query_req, query_req_len);
203
204 if (pos < end) {
205 wpa_hexdump(MSG_MSGDUMP,
206 "GAS: Ignored extra data after Query Request field",
207 pos, end - pos);
208 }
209
210 dl_list_for_each(handler, &gas->handlers, struct gas_server_handler,
211 list) {
212 if (adv_proto_len < 1 + handler->adv_proto_id_len ||
213 os_memcmp(adv_proto + 1, handler->adv_proto_id,
214 handler->adv_proto_id_len) != 0)
215 continue;
216
217 wpa_printf(MSG_DEBUG,
218 "GAS: Calling handler for the requested Advertisement Protocol ID");
219 resp = handler->req_cb(handler->ctx, sa, query_req,
220 query_req_len);
221 wpa_hexdump_buf(MSG_MSGDUMP, "GAS: Response from the handler",
222 resp);
223 gas_server_send_resp(gas, handler, sa, freq, dialog_token,
224 resp);
225 return 0;
226 }
227
228 wpa_printf(MSG_DEBUG,
229 "GAS: No registered handler for the requested Advertisement Protocol ID");
230 return -1;
231}
232
233
234static void
235gas_server_handle_rx_comeback_req(struct gas_server_response *response)
236{
237 struct gas_server_handler *handler = response->handler;
238 struct gas_server *gas = handler->gas;
239 size_t max_len = (response->freq > 56160) ? 928 : 1400;
240 size_t hdr_len = 24 + 2 + 6 + 3 + handler->adv_proto_id_len + 2;
241 size_t remaining, resp_frag_len;
242 struct wpabuf *resp;
243
244 remaining = wpabuf_len(response->resp) - response->offset;
245 if (hdr_len + remaining > max_len)
246 resp_frag_len = max_len - hdr_len;
247 else
248 resp_frag_len = remaining;
249 wpa_printf(MSG_DEBUG,
250 "GAS: Sending out %u/%u remaining Query Response octets",
251 (unsigned int) resp_frag_len, (unsigned int) remaining);
252
253 resp = gas_build_comeback_resp(response->dialog_token,
254 WLAN_STATUS_SUCCESS,
255 response->frag_id++,
256 resp_frag_len < remaining, 0,
257 handler->adv_proto_id_len +
258 resp_frag_len);
259 if (!resp) {
260 gas_server_free_response(response);
261 return;
262 }
263
264 /* Advertisement Protocol element */
265 wpabuf_put_u8(resp, WLAN_EID_ADV_PROTO);
266 wpabuf_put_u8(resp, 1 + handler->adv_proto_id_len); /* Length */
267 wpabuf_put_u8(resp, 0x7f);
268 /* Advertisement Protocol ID */
269 wpabuf_put_data(resp, handler->adv_proto_id, handler->adv_proto_id_len);
270
271 /* Query Response Length */
272 wpabuf_put_le16(resp, resp_frag_len);
273 wpabuf_put_data(resp, wpabuf_head_u8(response->resp) + response->offset,
274 resp_frag_len);
275
276 response->offset += resp_frag_len;
277
278 gas->tx(gas->ctx, response->freq, response->dst, resp,
279 remaining > resp_frag_len ? 2000 : 0);
280 wpabuf_free(resp);
281}
282
283
284static int
285gas_server_rx_comeback_req(struct gas_server *gas, const u8 *da, const u8 *sa,
286 const u8 *bssid, int freq, u8 dialog_token)
287{
288 struct gas_server_response *response;
289
290 dl_list_for_each(response, &gas->responses, struct gas_server_response,
291 list) {
292 if (response->dialog_token != dialog_token ||
293 os_memcmp(sa, response->dst, ETH_ALEN) != 0)
294 continue;
295 gas_server_handle_rx_comeback_req(response);
296 return 0;
297 }
298
299 wpa_printf(MSG_DEBUG, "GAS: No pending GAS response for " MACSTR
300 " (dialog token %u)", MAC2STR(sa), dialog_token);
301 return -1;
302}
303
304
305/**
306 * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame
307 * @gas: GAS query data from gas_server_init()
308 * @da: Destination MAC address of the Action frame
309 * @sa: Source MAC address of the Action frame
310 * @bssid: BSSID of the Action frame
311 * @categ: Category of the Action frame
312 * @data: Payload of the Action frame
313 * @len: Length of @data
314 * @freq: Frequency (in MHz) on which the frame was received
315 * Returns: 0 if the Public Action frame was a GAS request frame or -1 if not
316 */
317int gas_server_rx(struct gas_server *gas, const u8 *da, const u8 *sa,
318 const u8 *bssid, u8 categ, const u8 *data, size_t len,
319 int freq)
320{
321 u8 action, dialog_token;
322 const u8 *pos, *end;
323
324 if (!gas || len < 2)
325 return -1;
326
327 if (categ == WLAN_ACTION_PROTECTED_DUAL)
328 return -1; /* Not supported for now */
329
330 pos = data;
331 end = data + len;
332 action = *pos++;
333 dialog_token = *pos++;
334
335 if (action != WLAN_PA_GAS_INITIAL_REQ &&
336 action != WLAN_PA_GAS_COMEBACK_REQ)
337 return -1; /* Not a GAS request */
338
339 wpa_printf(MSG_DEBUG, "GAS: Received GAS %s Request frame DA=" MACSTR
340 " SA=" MACSTR " BSSID=" MACSTR
341 " freq=%d dialog_token=%u len=%u",
342 action == WLAN_PA_GAS_INITIAL_REQ ? "Initial" : "Comeback",
343 MAC2STR(da), MAC2STR(sa), MAC2STR(bssid), freq, dialog_token,
344 (unsigned int) len);
345
346 if (action == WLAN_PA_GAS_INITIAL_REQ)
347 return gas_server_rx_initial_req(gas, da, sa, bssid,
348 freq, dialog_token,
349 pos, end - pos);
350 return gas_server_rx_comeback_req(gas, da, sa, bssid,
351 freq, dialog_token);
352}
353
354
355static void gas_server_handle_tx_status(struct gas_server_response *response,
356 int ack)
357{
358 if (ack && response->offset < wpabuf_len(response->resp)) {
359 wpa_printf(MSG_DEBUG,
360 "GAS: More fragments remaining - keep pending entry");
361 return;
362 }
363
364 if (!ack)
365 wpa_printf(MSG_DEBUG,
366 "GAS: No ACK received - drop pending entry");
367 else
368 wpa_printf(MSG_DEBUG,
369 "GAS: Last fragment of the response sent out - drop pending entry");
370
371 response->handler->status_cb(response->handler->ctx,
372 response->resp, ack);
373 response->resp = NULL;
374 dl_list_del(&response->list);
375 gas_server_free_response(response);
376}
377
378
379void gas_server_tx_status(struct gas_server *gas, const u8 *dst, const u8 *data,
380 size_t data_len, int ack)
381{
382 const u8 *pos;
383 u8 action, code, dialog_token;
384 struct gas_server_response *response;
385
386 if (data_len < 24 + 3)
387 return;
388 pos = data + 24;
389 action = *pos++;
390 code = *pos++;
391 dialog_token = *pos++;
392 if (action != WLAN_ACTION_PUBLIC ||
393 (code != WLAN_PA_GAS_INITIAL_RESP &&
394 code != WLAN_PA_GAS_COMEBACK_RESP))
395 return;
396 wpa_printf(MSG_DEBUG, "GAS: TX status dst=" MACSTR
397 " ack=%d %s dialog_token=%u",
398 MAC2STR(dst), ack,
399 code == WLAN_PA_GAS_INITIAL_RESP ? "initial" : "comeback",
400 dialog_token);
401 dl_list_for_each(response, &gas->responses, struct gas_server_response,
402 list) {
403 if (response->dialog_token != dialog_token ||
404 os_memcmp(dst, response->dst, ETH_ALEN) != 0)
405 continue;
406 gas_server_handle_tx_status(response, ack);
407 return;
408 }
409
410 wpa_printf(MSG_DEBUG, "GAS: No pending response matches TX status");
411}
412
413
414struct gas_server * gas_server_init(void *ctx,
415 void (*tx)(void *ctx, int freq,
416 const u8 *da,
417 struct wpabuf *buf,
418 unsigned int wait_time))
419{
420 struct gas_server *gas;
421
422 gas = os_zalloc(sizeof(*gas));
423 if (!gas)
424 return NULL;
425 gas->ctx = ctx;
426 gas->tx = tx;
427 dl_list_init(&gas->handlers);
428 dl_list_init(&gas->responses);
429 return gas;
430}
431
432
433void gas_server_deinit(struct gas_server *gas)
434{
435 struct gas_server_handler *handler, *tmp;
436 struct gas_server_response *response, *tmp_r;
437
438 if (!gas)
439 return;
440
441 dl_list_for_each_safe(handler, tmp, &gas->handlers,
442 struct gas_server_handler, list) {
443 dl_list_del(&handler->list);
444 os_free(handler);
445 }
446
447 dl_list_for_each_safe(response, tmp_r, &gas->responses,
448 struct gas_server_response, list) {
449 dl_list_del(&response->list);
450 gas_server_free_response(response);
451 }
452
453 os_free(gas);
454}
455
456
457int gas_server_register(struct gas_server *gas,
458 const u8 *adv_proto_id, u8 adv_proto_id_len,
459 struct wpabuf *
460 (*req_cb)(void *ctx, const u8 *sa,
461 const u8 *query, size_t query_len),
462 void (*status_cb)(void *ctx, struct wpabuf *resp,
463 int ok),
464 void *ctx)
465{
466 struct gas_server_handler *handler;
467
468 if (!gas || adv_proto_id_len > MAX_ADV_PROTO_ID_LEN)
469 return -1;
470 handler = os_zalloc(sizeof(*handler));
471 if (!handler)
472 return -1;
473
474 os_memcpy(handler->adv_proto_id, adv_proto_id, adv_proto_id_len);
475 handler->adv_proto_id_len = adv_proto_id_len;
476 handler->req_cb = req_cb;
477 handler->status_cb = status_cb;
478 handler->ctx = ctx;
479 handler->gas = gas;
480 dl_list_add(&gas->handlers, &handler->list);
481
482 return 0;
483}