1341618Scy/*
2341618Scy * FILS HLP request processing
3341618Scy * Copyright (c) 2017, Qualcomm Atheros, Inc.
4341618Scy *
5341618Scy * This software may be distributed under the terms of the BSD license.
6341618Scy * See README for more details.
7341618Scy */
8341618Scy
9341618Scy#include "utils/includes.h"
10341618Scy
11341618Scy#include "utils/common.h"
12341618Scy#include "utils/eloop.h"
13341618Scy#include "common/dhcp.h"
14341618Scy#include "hostapd.h"
15341618Scy#include "sta_info.h"
16341618Scy#include "ieee802_11.h"
17341618Scy#include "fils_hlp.h"
18341618Scy
19341618Scy
20341618Scystatic be16 ip_checksum(const void *buf, size_t len)
21341618Scy{
22341618Scy	u32 sum = 0;
23341618Scy	const u16 *pos;
24341618Scy
25341618Scy	for (pos = buf; len >= 2; len -= 2)
26341618Scy		sum += ntohs(*pos++);
27341618Scy	if (len)
28341618Scy		sum += ntohs(*pos << 8);
29341618Scy
30341618Scy	sum = (sum >> 16) + (sum & 0xffff);
31341618Scy	sum += sum >> 16;
32341618Scy	return htons(~sum);
33341618Scy}
34341618Scy
35341618Scy
36341618Scystatic int fils_dhcp_request(struct hostapd_data *hapd, struct sta_info *sta,
37341618Scy			     struct dhcp_data *dhcpoffer, u8 *dhcpofferend)
38341618Scy{
39341618Scy	u8 *pos, *end;
40341618Scy	struct dhcp_data *dhcp;
41341618Scy	struct sockaddr_in addr;
42341618Scy	ssize_t res;
43341618Scy	const u8 *server_id = NULL;
44341618Scy
45341618Scy	if (!sta->hlp_dhcp_discover) {
46341618Scy		wpa_printf(MSG_DEBUG,
47341618Scy			   "FILS: No pending HLP DHCPDISCOVER available");
48341618Scy		return -1;
49341618Scy	}
50341618Scy
51341618Scy	/* Convert to DHCPREQUEST, remove rapid commit option, replace requested
52341618Scy	 * IP address option with yiaddr. */
53341618Scy	pos = wpabuf_mhead(sta->hlp_dhcp_discover);
54341618Scy	end = pos + wpabuf_len(sta->hlp_dhcp_discover);
55341618Scy	dhcp = (struct dhcp_data *) pos;
56341618Scy	pos = (u8 *) (dhcp + 1);
57341618Scy	pos += 4; /* skip magic */
58341618Scy	while (pos < end && *pos != DHCP_OPT_END) {
59341618Scy		u8 opt, olen;
60341618Scy
61341618Scy		opt = *pos++;
62341618Scy		if (opt == DHCP_OPT_PAD)
63341618Scy			continue;
64341618Scy		if (pos >= end)
65341618Scy			break;
66341618Scy		olen = *pos++;
67341618Scy		if (olen > end - pos)
68341618Scy			break;
69341618Scy
70341618Scy		switch (opt) {
71341618Scy		case DHCP_OPT_MSG_TYPE:
72341618Scy			if (olen > 0)
73341618Scy				*pos = DHCPREQUEST;
74341618Scy			break;
75341618Scy		case DHCP_OPT_RAPID_COMMIT:
76341618Scy		case DHCP_OPT_REQUESTED_IP_ADDRESS:
77341618Scy		case DHCP_OPT_SERVER_ID:
78341618Scy			/* Remove option */
79341618Scy			pos -= 2;
80341618Scy			os_memmove(pos, pos + 2 + olen, end - pos - 2 - olen);
81341618Scy			end -= 2 + olen;
82341618Scy			olen = 0;
83341618Scy			break;
84341618Scy		}
85341618Scy		pos += olen;
86341618Scy	}
87341618Scy	if (pos >= end || *pos != DHCP_OPT_END) {
88341618Scy		wpa_printf(MSG_DEBUG, "FILS: Could not update DHCPDISCOVER");
89341618Scy		return -1;
90341618Scy	}
91341618Scy	sta->hlp_dhcp_discover->used = pos - (u8 *) dhcp;
92341618Scy
93341618Scy	/* Copy Server ID option from DHCPOFFER to DHCPREQUEST */
94341618Scy	pos = (u8 *) (dhcpoffer + 1);
95341618Scy	end = dhcpofferend;
96341618Scy	pos += 4; /* skip magic */
97341618Scy	while (pos < end && *pos != DHCP_OPT_END) {
98341618Scy		u8 opt, olen;
99341618Scy
100341618Scy		opt = *pos++;
101341618Scy		if (opt == DHCP_OPT_PAD)
102341618Scy			continue;
103341618Scy		if (pos >= end)
104341618Scy			break;
105341618Scy		olen = *pos++;
106341618Scy		if (olen > end - pos)
107341618Scy			break;
108341618Scy
109341618Scy		switch (opt) {
110341618Scy		case DHCP_OPT_SERVER_ID:
111341618Scy			server_id = pos - 2;
112341618Scy			break;
113341618Scy		}
114341618Scy		pos += olen;
115341618Scy	}
116341618Scy
117341618Scy	if (wpabuf_resize(&sta->hlp_dhcp_discover,
118341618Scy			  6 + 1 + (server_id ? 2 + server_id[1] : 0)))
119341618Scy		return -1;
120341618Scy	if (server_id)
121341618Scy		wpabuf_put_data(sta->hlp_dhcp_discover, server_id,
122341618Scy				2 + server_id[1]);
123341618Scy	wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_REQUESTED_IP_ADDRESS);
124341618Scy	wpabuf_put_u8(sta->hlp_dhcp_discover, 4);
125341618Scy	wpabuf_put_data(sta->hlp_dhcp_discover, &dhcpoffer->your_ip, 4);
126341618Scy	wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_END);
127341618Scy
128341618Scy	os_memset(&addr, 0, sizeof(addr));
129341618Scy	addr.sin_family = AF_INET;
130341618Scy	addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
131341618Scy	addr.sin_port = htons(hapd->conf->dhcp_server_port);
132341618Scy	res = sendto(hapd->dhcp_sock, wpabuf_head(sta->hlp_dhcp_discover),
133341618Scy		     wpabuf_len(sta->hlp_dhcp_discover), 0,
134341618Scy		     (const struct sockaddr *) &addr, sizeof(addr));
135341618Scy	if (res < 0) {
136341618Scy		wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
137341618Scy			   strerror(errno));
138341618Scy		return -1;
139341618Scy	}
140341618Scy	wpa_printf(MSG_DEBUG,
141341618Scy		   "FILS: Acting as DHCP rapid commit proxy for %s:%d",
142341618Scy		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
143341618Scy	wpabuf_free(sta->hlp_dhcp_discover);
144341618Scy	sta->hlp_dhcp_discover = NULL;
145341618Scy	sta->fils_dhcp_rapid_commit_proxy = 1;
146341618Scy	return 0;
147341618Scy}
148341618Scy
149341618Scy
150341618Scystatic void fils_dhcp_handler(int sd, void *eloop_ctx, void *sock_ctx)
151341618Scy{
152341618Scy	struct hostapd_data *hapd = sock_ctx;
153341618Scy	struct sta_info *sta;
154341618Scy	u8 buf[1500], *pos, *end, *end_opt = NULL;
155341618Scy	struct dhcp_data *dhcp;
156341618Scy	struct sockaddr_in addr;
157341618Scy	socklen_t addr_len;
158341618Scy	ssize_t res;
159341618Scy	u8 msgtype = 0;
160341618Scy	int rapid_commit = 0;
161341618Scy	struct iphdr *iph;
162341618Scy	struct udphdr *udph;
163341618Scy	struct wpabuf *resp;
164341618Scy	const u8 *rpos;
165341618Scy	size_t left, len;
166341618Scy
167341618Scy	addr_len = sizeof(addr);
168341618Scy	res = recvfrom(sd, buf, sizeof(buf), 0,
169341618Scy		       (struct sockaddr *) &addr, &addr_len);
170341618Scy	if (res < 0) {
171341618Scy		wpa_printf(MSG_DEBUG, "FILS: DHCP read failed: %s",
172341618Scy			   strerror(errno));
173341618Scy		return;
174341618Scy	}
175341618Scy	wpa_printf(MSG_DEBUG, "FILS: DHCP response from server %s:%d (len=%d)",
176341618Scy		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), (int) res);
177341618Scy	wpa_hexdump(MSG_MSGDUMP, "FILS: HLP - DHCP server response", buf, res);
178341618Scy	if ((size_t) res < sizeof(*dhcp))
179341618Scy		return;
180341618Scy	dhcp = (struct dhcp_data *) buf;
181341618Scy	if (dhcp->op != 2)
182341618Scy		return; /* Not a BOOTREPLY */
183341618Scy	if (dhcp->relay_ip != hapd->conf->own_ip_addr.u.v4.s_addr) {
184341618Scy		wpa_printf(MSG_DEBUG,
185341618Scy			   "FILS: HLP - DHCP response to unknown relay address 0x%x",
186341618Scy			   dhcp->relay_ip);
187341618Scy		return;
188341618Scy	}
189341618Scy	dhcp->relay_ip = 0;
190341618Scy	pos = (u8 *) (dhcp + 1);
191341618Scy	end = &buf[res];
192341618Scy
193341618Scy	if (end - pos < 4 || WPA_GET_BE32(pos) != DHCP_MAGIC) {
194341618Scy		wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic in response");
195341618Scy		return;
196341618Scy	}
197341618Scy	pos += 4;
198341618Scy
199341618Scy	wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options in response",
200341618Scy		    pos, end - pos);
201341618Scy	while (pos < end && *pos != DHCP_OPT_END) {
202341618Scy		u8 opt, olen;
203341618Scy
204341618Scy		opt = *pos++;
205341618Scy		if (opt == DHCP_OPT_PAD)
206341618Scy			continue;
207341618Scy		if (pos >= end)
208341618Scy			break;
209341618Scy		olen = *pos++;
210341618Scy		if (olen > end - pos)
211341618Scy			break;
212341618Scy
213341618Scy		switch (opt) {
214341618Scy		case DHCP_OPT_MSG_TYPE:
215341618Scy			if (olen > 0)
216341618Scy				msgtype = pos[0];
217341618Scy			break;
218341618Scy		case DHCP_OPT_RAPID_COMMIT:
219341618Scy			rapid_commit = 1;
220341618Scy			break;
221341618Scy		}
222341618Scy		pos += olen;
223341618Scy	}
224341618Scy	if (pos < end && *pos == DHCP_OPT_END)
225341618Scy		end_opt = pos;
226341618Scy
227341618Scy	wpa_printf(MSG_DEBUG,
228341618Scy		   "FILS: HLP - DHCP message type %u (rapid_commit=%d hw_addr="
229341618Scy		   MACSTR ")",
230341618Scy		   msgtype, rapid_commit, MAC2STR(dhcp->hw_addr));
231341618Scy
232341618Scy	sta = ap_get_sta(hapd, dhcp->hw_addr);
233341618Scy	if (!sta || !sta->fils_pending_assoc_req) {
234341618Scy		wpa_printf(MSG_DEBUG,
235341618Scy			   "FILS: No pending HLP DHCP exchange with hw_addr "
236341618Scy			   MACSTR, MAC2STR(dhcp->hw_addr));
237341618Scy		return;
238341618Scy	}
239341618Scy
240341618Scy	if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPOFFER &&
241341618Scy	    !rapid_commit) {
242341618Scy		/* Use hostapd to take care of 4-message exchange and convert
243341618Scy		 * the final DHCPACK to rapid commit version. */
244341618Scy		if (fils_dhcp_request(hapd, sta, dhcp, end) == 0)
245341618Scy			return;
246341618Scy		/* failed, so send the server response as-is */
247341618Scy	} else if (msgtype != DHCPACK) {
248341618Scy		wpa_printf(MSG_DEBUG,
249341618Scy			   "FILS: No DHCPACK available from the server and cannot do rapid commit proxying");
250341618Scy	}
251341618Scy
252341618Scy	pos = buf;
253341618Scy	resp = wpabuf_alloc(2 * ETH_ALEN + 6 + 2 +
254341618Scy			    sizeof(*iph) + sizeof(*udph) + (end - pos) + 2);
255341618Scy	if (!resp)
256341618Scy		return;
257341618Scy	wpabuf_put_data(resp, sta->addr, ETH_ALEN);
258341618Scy	wpabuf_put_data(resp, hapd->own_addr, ETH_ALEN);
259341618Scy	wpabuf_put_data(resp, "\xaa\xaa\x03\x00\x00\x00", 6);
260341618Scy	wpabuf_put_be16(resp, ETH_P_IP);
261341618Scy	iph = wpabuf_put(resp, sizeof(*iph));
262341618Scy	iph->version = 4;
263341618Scy	iph->ihl = sizeof(*iph) / 4;
264341618Scy	iph->tot_len = htons(sizeof(*iph) + sizeof(*udph) + (end - pos));
265341618Scy	iph->ttl = 1;
266341618Scy	iph->protocol = 17; /* UDP */
267341618Scy	iph->saddr = hapd->conf->dhcp_server.u.v4.s_addr;
268341618Scy	iph->daddr = dhcp->client_ip;
269341618Scy	iph->check = ip_checksum(iph, sizeof(*iph));
270341618Scy	udph = wpabuf_put(resp, sizeof(*udph));
271341618Scy	udph->uh_sport = htons(DHCP_SERVER_PORT);
272341618Scy	udph->uh_dport = htons(DHCP_CLIENT_PORT);
273341618Scy	udph->uh_ulen = htons(sizeof(*udph) + (end - pos));
274341618Scy	udph->uh_sum = htons(0x0000); /* TODO: calculate checksum */
275341618Scy	if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPACK &&
276341618Scy	    !rapid_commit && sta->fils_dhcp_rapid_commit_proxy && end_opt) {
277341618Scy		/* Add rapid commit option */
278341618Scy		wpabuf_put_data(resp, pos, end_opt - pos);
279341618Scy		wpabuf_put_u8(resp, DHCP_OPT_RAPID_COMMIT);
280341618Scy		wpabuf_put_u8(resp, 0);
281341618Scy		wpabuf_put_data(resp, end_opt, end - end_opt);
282341618Scy	} else {
283341618Scy		wpabuf_put_data(resp, pos, end - pos);
284341618Scy	}
285341618Scy	if (wpabuf_resize(&sta->fils_hlp_resp, wpabuf_len(resp) +
286341618Scy			  2 * wpabuf_len(resp) / 255 + 100)) {
287341618Scy		wpabuf_free(resp);
288341618Scy		return;
289341618Scy	}
290341618Scy
291341618Scy	rpos = wpabuf_head(resp);
292341618Scy	left = wpabuf_len(resp);
293341618Scy
294341618Scy	wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXTENSION); /* Element ID */
295341618Scy	if (left <= 254)
296341618Scy		len = 1 + left;
297341618Scy	else
298341618Scy		len = 255;
299341618Scy	wpabuf_put_u8(sta->fils_hlp_resp, len); /* Length */
300341618Scy	/* Element ID Extension */
301341618Scy	wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXT_FILS_HLP_CONTAINER);
302341618Scy	/* Destination MAC Address, Source MAC Address, HLP Packet.
303341618Scy	 * HLP Packet is in MSDU format (i.e., including the LLC/SNAP header
304341618Scy	 * when LPD is used). */
305341618Scy	wpabuf_put_data(sta->fils_hlp_resp, rpos, len - 1);
306341618Scy	rpos += len - 1;
307341618Scy	left -= len - 1;
308341618Scy	while (left) {
309341618Scy		wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_FRAGMENT);
310341618Scy		len = left > 255 ? 255 : left;
311341618Scy		wpabuf_put_u8(sta->fils_hlp_resp, len);
312341618Scy		wpabuf_put_data(sta->fils_hlp_resp, rpos, len);
313341618Scy		rpos += len;
314341618Scy		left -= len;
315341618Scy	}
316341618Scy	wpabuf_free(resp);
317341618Scy
318341618Scy	if (sta->fils_drv_assoc_finish)
319341618Scy		hostapd_notify_assoc_fils_finish(hapd, sta);
320341618Scy	else
321341618Scy		fils_hlp_finish_assoc(hapd, sta);
322341618Scy}
323341618Scy
324341618Scy
325341618Scystatic int fils_process_hlp_dhcp(struct hostapd_data *hapd,
326341618Scy				 struct sta_info *sta,
327341618Scy				 const u8 *msg, size_t len)
328341618Scy{
329341618Scy	const struct dhcp_data *dhcp;
330341618Scy	struct wpabuf *dhcp_buf;
331341618Scy	struct dhcp_data *dhcp_msg;
332341618Scy	u8 msgtype = 0;
333341618Scy	int rapid_commit = 0;
334341618Scy	const u8 *pos = msg, *end;
335341618Scy	struct sockaddr_in addr;
336341618Scy	ssize_t res;
337341618Scy
338341618Scy	if (len < sizeof(*dhcp))
339341618Scy		return 0;
340341618Scy	dhcp = (const struct dhcp_data *) pos;
341341618Scy	end = pos + len;
342341618Scy	wpa_printf(MSG_DEBUG,
343341618Scy		   "FILS: HLP request DHCP: op=%u htype=%u hlen=%u hops=%u xid=0x%x",
344341618Scy		   dhcp->op, dhcp->htype, dhcp->hlen, dhcp->hops,
345341618Scy		   ntohl(dhcp->xid));
346341618Scy	pos += sizeof(*dhcp);
347341618Scy	if (dhcp->op != 1)
348341618Scy		return 0; /* Not a BOOTREQUEST */
349341618Scy
350341618Scy	if (end - pos < 4)
351341618Scy		return 0;
352341618Scy	if (WPA_GET_BE32(pos) != DHCP_MAGIC) {
353341618Scy		wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic");
354341618Scy		return 0;
355341618Scy	}
356341618Scy	pos += 4;
357341618Scy
358341618Scy	wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options", pos, end - pos);
359341618Scy	while (pos < end && *pos != DHCP_OPT_END) {
360341618Scy		u8 opt, olen;
361341618Scy
362341618Scy		opt = *pos++;
363341618Scy		if (opt == DHCP_OPT_PAD)
364341618Scy			continue;
365341618Scy		if (pos >= end)
366341618Scy			break;
367341618Scy		olen = *pos++;
368341618Scy		if (olen > end - pos)
369341618Scy			break;
370341618Scy
371341618Scy		switch (opt) {
372341618Scy		case DHCP_OPT_MSG_TYPE:
373341618Scy			if (olen > 0)
374341618Scy				msgtype = pos[0];
375341618Scy			break;
376341618Scy		case DHCP_OPT_RAPID_COMMIT:
377341618Scy			rapid_commit = 1;
378341618Scy			break;
379341618Scy		}
380341618Scy		pos += olen;
381341618Scy	}
382341618Scy
383341618Scy	wpa_printf(MSG_DEBUG, "FILS: HLP - DHCP message type %u", msgtype);
384341618Scy	if (msgtype != DHCPDISCOVER)
385341618Scy		return 0;
386341618Scy
387341618Scy	if (hapd->conf->dhcp_server.af != AF_INET ||
388341618Scy	    hapd->conf->dhcp_server.u.v4.s_addr == 0) {
389341618Scy		wpa_printf(MSG_DEBUG,
390341618Scy			   "FILS: HLP - no DHCPv4 server configured - drop request");
391341618Scy		return 0;
392341618Scy	}
393341618Scy
394341618Scy	if (hapd->conf->own_ip_addr.af != AF_INET ||
395341618Scy	    hapd->conf->own_ip_addr.u.v4.s_addr == 0) {
396341618Scy		wpa_printf(MSG_DEBUG,
397341618Scy			   "FILS: HLP - no IPv4 own_ip_addr configured - drop request");
398341618Scy		return 0;
399341618Scy	}
400341618Scy
401341618Scy	if (hapd->dhcp_sock < 0) {
402341618Scy		int s;
403341618Scy
404341618Scy		s = socket(AF_INET, SOCK_DGRAM, 0);
405341618Scy		if (s < 0) {
406341618Scy			wpa_printf(MSG_ERROR,
407341618Scy				   "FILS: Failed to open DHCP socket: %s",
408341618Scy				   strerror(errno));
409341618Scy			return 0;
410341618Scy		}
411341618Scy
412341618Scy		if (hapd->conf->dhcp_relay_port) {
413341618Scy			os_memset(&addr, 0, sizeof(addr));
414341618Scy			addr.sin_family = AF_INET;
415341618Scy			addr.sin_addr.s_addr =
416341618Scy				hapd->conf->own_ip_addr.u.v4.s_addr;
417341618Scy			addr.sin_port = htons(hapd->conf->dhcp_relay_port);
418341618Scy			if (bind(s, (struct sockaddr *) &addr, sizeof(addr))) {
419341618Scy				wpa_printf(MSG_ERROR,
420341618Scy					   "FILS: Failed to bind DHCP socket: %s",
421341618Scy					   strerror(errno));
422341618Scy				close(s);
423341618Scy				return 0;
424341618Scy			}
425341618Scy		}
426341618Scy		if (eloop_register_sock(s, EVENT_TYPE_READ,
427341618Scy					fils_dhcp_handler, NULL, hapd)) {
428341618Scy			close(s);
429341618Scy			return 0;
430341618Scy		}
431341618Scy
432341618Scy		hapd->dhcp_sock = s;
433341618Scy	}
434341618Scy
435341618Scy	dhcp_buf = wpabuf_alloc(len);
436341618Scy	if (!dhcp_buf)
437341618Scy		return 0;
438341618Scy	dhcp_msg = wpabuf_put(dhcp_buf, len);
439341618Scy	os_memcpy(dhcp_msg, msg, len);
440341618Scy	dhcp_msg->relay_ip = hapd->conf->own_ip_addr.u.v4.s_addr;
441341618Scy	os_memset(&addr, 0, sizeof(addr));
442341618Scy	addr.sin_family = AF_INET;
443341618Scy	addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
444341618Scy	addr.sin_port = htons(hapd->conf->dhcp_server_port);
445341618Scy	res = sendto(hapd->dhcp_sock, dhcp_msg, len, 0,
446341618Scy		     (const struct sockaddr *) &addr, sizeof(addr));
447341618Scy	if (res < 0) {
448341618Scy		wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
449341618Scy			   strerror(errno));
450341618Scy		wpabuf_free(dhcp_buf);
451341618Scy		/* Close the socket to try to recover from error */
452341618Scy		eloop_unregister_read_sock(hapd->dhcp_sock);
453341618Scy		close(hapd->dhcp_sock);
454341618Scy		hapd->dhcp_sock = -1;
455341618Scy		return 0;
456341618Scy	}
457341618Scy
458341618Scy	wpa_printf(MSG_DEBUG,
459341618Scy		   "FILS: HLP relayed DHCP request to server %s:%d (rapid_commit=%d)",
460341618Scy		   inet_ntoa(addr.sin_addr), ntohs(addr.sin_port),
461341618Scy		   rapid_commit);
462341618Scy	if (hapd->conf->dhcp_rapid_commit_proxy && rapid_commit) {
463341618Scy		/* Store a copy of the DHCPDISCOVER for rapid commit proxying
464341618Scy		 * purposes if the server does not support the rapid commit
465341618Scy		 * option. */
466341618Scy		wpa_printf(MSG_DEBUG,
467341618Scy			   "FILS: Store DHCPDISCOVER for rapid commit proxy");
468341618Scy		wpabuf_free(sta->hlp_dhcp_discover);
469341618Scy		sta->hlp_dhcp_discover = dhcp_buf;
470341618Scy	} else {
471341618Scy		wpabuf_free(dhcp_buf);
472341618Scy	}
473341618Scy
474341618Scy	return 1;
475341618Scy}
476341618Scy
477341618Scy
478341618Scystatic int fils_process_hlp_udp(struct hostapd_data *hapd,
479341618Scy				struct sta_info *sta, const u8 *dst,
480341618Scy				const u8 *pos, size_t len)
481341618Scy{
482341618Scy	const struct iphdr *iph;
483341618Scy	const struct udphdr *udph;
484341618Scy	u16 sport, dport, ulen;
485341618Scy
486341618Scy	if (len < sizeof(*iph) + sizeof(*udph))
487341618Scy		return 0;
488341618Scy	iph = (const struct iphdr *) pos;
489341618Scy	udph = (const struct udphdr *) (iph + 1);
490341618Scy	sport = ntohs(udph->uh_sport);
491341618Scy	dport = ntohs(udph->uh_dport);
492341618Scy	ulen = ntohs(udph->uh_ulen);
493341618Scy	wpa_printf(MSG_DEBUG,
494341618Scy		   "FILS: HLP request UDP: sport=%u dport=%u ulen=%u sum=0x%x",
495341618Scy		   sport, dport, ulen, ntohs(udph->uh_sum));
496341618Scy	/* TODO: Check UDP checksum */
497341618Scy	if (ulen < sizeof(*udph) || ulen > len - sizeof(*iph))
498341618Scy		return 0;
499341618Scy
500341618Scy	if (dport == DHCP_SERVER_PORT && sport == DHCP_CLIENT_PORT) {
501341618Scy		return fils_process_hlp_dhcp(hapd, sta, (const u8 *) (udph + 1),
502341618Scy					     ulen - sizeof(*udph));
503341618Scy	}
504341618Scy
505341618Scy	return 0;
506341618Scy}
507341618Scy
508341618Scy
509341618Scystatic int fils_process_hlp_ip(struct hostapd_data *hapd,
510341618Scy			       struct sta_info *sta, const u8 *dst,
511341618Scy			       const u8 *pos, size_t len)
512341618Scy{
513341618Scy	const struct iphdr *iph;
514341618Scy	u16 tot_len;
515341618Scy
516341618Scy	if (len < sizeof(*iph))
517341618Scy		return 0;
518341618Scy	iph = (const struct iphdr *) pos;
519341618Scy	if (ip_checksum(iph, sizeof(*iph)) != 0) {
520341618Scy		wpa_printf(MSG_DEBUG,
521341618Scy			   "FILS: HLP request IPv4 packet had invalid header checksum - dropped");
522341618Scy		return 0;
523341618Scy	}
524341618Scy	tot_len = ntohs(iph->tot_len);
525341618Scy	if (tot_len > len)
526341618Scy		return 0;
527341618Scy	wpa_printf(MSG_DEBUG,
528341618Scy		   "FILS: HLP request IPv4: saddr=%08x daddr=%08x protocol=%u",
529341618Scy		   iph->saddr, iph->daddr, iph->protocol);
530341618Scy	switch (iph->protocol) {
531341618Scy	case 17:
532341618Scy		return fils_process_hlp_udp(hapd, sta, dst, pos, len);
533341618Scy	}
534341618Scy
535341618Scy	return 0;
536341618Scy}
537341618Scy
538341618Scy
539341618Scystatic int fils_process_hlp_req(struct hostapd_data *hapd,
540341618Scy				struct sta_info *sta,
541341618Scy				const u8 *pos, size_t len)
542341618Scy{
543341618Scy	const u8 *pkt, *end;
544341618Scy
545341618Scy	wpa_printf(MSG_DEBUG, "FILS: HLP request from " MACSTR " (dst=" MACSTR
546341618Scy		   " src=" MACSTR " len=%u)",
547341618Scy		   MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
548341618Scy		   (unsigned int) len);
549341618Scy	if (os_memcmp(sta->addr, pos + ETH_ALEN, ETH_ALEN) != 0) {
550341618Scy		wpa_printf(MSG_DEBUG,
551341618Scy			   "FILS: Ignore HLP request with unexpected source address"
552341618Scy			   MACSTR, MAC2STR(pos + ETH_ALEN));
553341618Scy		return 0;
554341618Scy	}
555341618Scy
556341618Scy	end = pos + len;
557341618Scy	pkt = pos + 2 * ETH_ALEN;
558341618Scy	if (end - pkt >= 6 &&
559341618Scy	    os_memcmp(pkt, "\xaa\xaa\x03\x00\x00\x00", 6) == 0)
560341618Scy		pkt += 6; /* Remove SNAP/LLC header */
561341618Scy	wpa_hexdump(MSG_MSGDUMP, "FILS: HLP request packet", pkt, end - pkt);
562341618Scy
563341618Scy	if (end - pkt < 2)
564341618Scy		return 0;
565341618Scy
566341618Scy	switch (WPA_GET_BE16(pkt)) {
567341618Scy	case ETH_P_IP:
568341618Scy		return fils_process_hlp_ip(hapd, sta, pos, pkt + 2,
569341618Scy					   end - pkt - 2);
570341618Scy	}
571341618Scy
572341618Scy	return 0;
573341618Scy}
574341618Scy
575341618Scy
576341618Scyint fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta,
577341618Scy		     const u8 *pos, int left)
578341618Scy{
579341618Scy	const u8 *end = pos + left;
580341618Scy	u8 *tmp, *tmp_pos;
581341618Scy	int ret = 0;
582341618Scy
583346981Scy	if (sta->fils_pending_assoc_req &&
584346981Scy	    eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta)) {
585346981Scy		/* Do not process FILS HLP request again if the station
586346981Scy		 * retransmits (Re)Association Request frame before the previous
587346981Scy		 * HLP response has either been received or timed out. */
588346981Scy		wpa_printf(MSG_DEBUG,
589346981Scy			   "FILS: Do not relay another HLP request from "
590346981Scy			   MACSTR
591346981Scy			   " before processing of the already pending one has been completed",
592346981Scy			   MAC2STR(sta->addr));
593346981Scy		return 1;
594346981Scy	}
595346981Scy
596341618Scy	/* Old DHCPDISCOVER is not needed anymore, if it was still pending */
597341618Scy	wpabuf_free(sta->hlp_dhcp_discover);
598341618Scy	sta->hlp_dhcp_discover = NULL;
599341618Scy	sta->fils_dhcp_rapid_commit_proxy = 0;
600341618Scy
601341618Scy	/* Check if there are any FILS HLP Container elements */
602341618Scy	while (end - pos >= 2) {
603341618Scy		if (2 + pos[1] > end - pos)
604341618Scy			return 0;
605341618Scy		if (pos[0] == WLAN_EID_EXTENSION &&
606341618Scy		    pos[1] >= 1 + 2 * ETH_ALEN &&
607341618Scy		    pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER)
608341618Scy			break;
609341618Scy		pos += 2 + pos[1];
610341618Scy	}
611341618Scy	if (end - pos < 2)
612341618Scy		return 0; /* No FILS HLP Container elements */
613341618Scy
614341618Scy	tmp = os_malloc(end - pos);
615341618Scy	if (!tmp)
616341618Scy		return 0;
617341618Scy
618341618Scy	while (end - pos >= 2) {
619341618Scy		if (2 + pos[1] > end - pos ||
620341618Scy		    pos[0] != WLAN_EID_EXTENSION ||
621341618Scy		    pos[1] < 1 + 2 * ETH_ALEN ||
622341618Scy		    pos[2] != WLAN_EID_EXT_FILS_HLP_CONTAINER)
623341618Scy			break;
624341618Scy		tmp_pos = tmp;
625341618Scy		os_memcpy(tmp_pos, pos + 3, pos[1] - 1);
626341618Scy		tmp_pos += pos[1] - 1;
627341618Scy		pos += 2 + pos[1];
628341618Scy
629341618Scy		/* Add possible fragments */
630341618Scy		while (end - pos >= 2 && pos[0] == WLAN_EID_FRAGMENT &&
631341618Scy		       2 + pos[1] <= end - pos) {
632341618Scy			os_memcpy(tmp_pos, pos + 2, pos[1]);
633341618Scy			tmp_pos += pos[1];
634341618Scy			pos += 2 + pos[1];
635341618Scy		}
636341618Scy
637341618Scy		if (fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp) > 0)
638341618Scy			ret = 1;
639341618Scy	}
640341618Scy
641341618Scy	os_free(tmp);
642341618Scy
643341618Scy	return ret;
644341618Scy}
645341618Scy
646341618Scy
647341618Scyvoid fils_hlp_deinit(struct hostapd_data *hapd)
648341618Scy{
649341618Scy	if (hapd->dhcp_sock >= 0) {
650341618Scy		eloop_unregister_read_sock(hapd->dhcp_sock);
651341618Scy		close(hapd->dhcp_sock);
652341618Scy		hapd->dhcp_sock = -1;
653341618Scy	}
654341618Scy}
655