hostapd/src/ap/fils_hlp.c
Jouni Malinen 2d83d224ff Use ether_addr_equal() to compare whether two MAC addresses are equal
This was done with spatch using the following semantic patch and minor
manual edits to clean up coding style and avoid compiler warnings in
driver_wext.c:

@@
expression a,b;
@@
-	os_memcmp(a, b, ETH_ALEN) == 0
+	ether_addr_equal(a, b)

@@
expression a,b;
@@
-	os_memcmp(a, b, ETH_ALEN) != 0
+	!ether_addr_equal(a, b)

@@
expression a,b;
@@
-	!os_memcmp(a, b, ETH_ALEN)
+	ether_addr_equal(a, b)

Signed-off-by: Jouni Malinen <j@w1.fi>
2024-01-13 23:47:21 +02:00

654 lines
17 KiB
C

/*
* FILS HLP request processing
* Copyright (c) 2017, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "utils/includes.h"
#include "utils/common.h"
#include "utils/eloop.h"
#include "common/dhcp.h"
#include "hostapd.h"
#include "sta_info.h"
#include "ieee802_11.h"
#include "fils_hlp.h"
static be16 ip_checksum(const void *buf, size_t len)
{
u32 sum = 0;
const u16 *pos;
for (pos = buf; len >= 2; len -= 2)
sum += ntohs(*pos++);
if (len)
sum += ntohs(*pos << 8);
sum = (sum >> 16) + (sum & 0xffff);
sum += sum >> 16;
return htons(~sum);
}
static int fils_dhcp_request(struct hostapd_data *hapd, struct sta_info *sta,
struct dhcp_data *dhcpoffer, u8 *dhcpofferend)
{
u8 *pos, *end;
struct dhcp_data *dhcp;
struct sockaddr_in addr;
ssize_t res;
const u8 *server_id = NULL;
if (!sta->hlp_dhcp_discover) {
wpa_printf(MSG_DEBUG,
"FILS: No pending HLP DHCPDISCOVER available");
return -1;
}
/* Convert to DHCPREQUEST, remove rapid commit option, replace requested
* IP address option with yiaddr. */
pos = wpabuf_mhead(sta->hlp_dhcp_discover);
end = pos + wpabuf_len(sta->hlp_dhcp_discover);
dhcp = (struct dhcp_data *) pos;
pos = (u8 *) (dhcp + 1);
pos += 4; /* skip magic */
while (pos < end && *pos != DHCP_OPT_END) {
u8 opt, olen;
opt = *pos++;
if (opt == DHCP_OPT_PAD)
continue;
if (pos >= end)
break;
olen = *pos++;
if (olen > end - pos)
break;
switch (opt) {
case DHCP_OPT_MSG_TYPE:
if (olen > 0)
*pos = DHCPREQUEST;
break;
case DHCP_OPT_RAPID_COMMIT:
case DHCP_OPT_REQUESTED_IP_ADDRESS:
case DHCP_OPT_SERVER_ID:
/* Remove option */
pos -= 2;
os_memmove(pos, pos + 2 + olen, end - pos - 2 - olen);
end -= 2 + olen;
olen = 0;
break;
}
pos += olen;
}
if (pos >= end || *pos != DHCP_OPT_END) {
wpa_printf(MSG_DEBUG, "FILS: Could not update DHCPDISCOVER");
return -1;
}
sta->hlp_dhcp_discover->used = pos - (u8 *) dhcp;
/* Copy Server ID option from DHCPOFFER to DHCPREQUEST */
pos = (u8 *) (dhcpoffer + 1);
end = dhcpofferend;
pos += 4; /* skip magic */
while (pos < end && *pos != DHCP_OPT_END) {
u8 opt, olen;
opt = *pos++;
if (opt == DHCP_OPT_PAD)
continue;
if (pos >= end)
break;
olen = *pos++;
if (olen > end - pos)
break;
switch (opt) {
case DHCP_OPT_SERVER_ID:
server_id = pos - 2;
break;
}
pos += olen;
}
if (wpabuf_resize(&sta->hlp_dhcp_discover,
6 + 1 + (server_id ? 2 + server_id[1] : 0)))
return -1;
if (server_id)
wpabuf_put_data(sta->hlp_dhcp_discover, server_id,
2 + server_id[1]);
wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_REQUESTED_IP_ADDRESS);
wpabuf_put_u8(sta->hlp_dhcp_discover, 4);
wpabuf_put_data(sta->hlp_dhcp_discover, &dhcpoffer->your_ip, 4);
wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_END);
os_memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
addr.sin_port = htons(hapd->conf->dhcp_server_port);
res = sendto(hapd->dhcp_sock, wpabuf_head(sta->hlp_dhcp_discover),
wpabuf_len(sta->hlp_dhcp_discover), 0,
(const struct sockaddr *) &addr, sizeof(addr));
if (res < 0) {
wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
strerror(errno));
return -1;
}
wpa_printf(MSG_DEBUG,
"FILS: Acting as DHCP rapid commit proxy for %s:%d",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
wpabuf_free(sta->hlp_dhcp_discover);
sta->hlp_dhcp_discover = NULL;
sta->fils_dhcp_rapid_commit_proxy = 1;
return 0;
}
static void fils_dhcp_handler(int sd, void *eloop_ctx, void *sock_ctx)
{
struct hostapd_data *hapd = sock_ctx;
struct sta_info *sta;
u8 buf[1500], *pos, *end, *end_opt = NULL;
struct dhcp_data *dhcp;
struct sockaddr_in addr;
socklen_t addr_len;
ssize_t res;
u8 msgtype = 0;
int rapid_commit = 0;
struct ip *iph;
struct udphdr *udph;
struct wpabuf *resp;
const u8 *rpos;
size_t left, len;
addr_len = sizeof(addr);
res = recvfrom(sd, buf, sizeof(buf), 0,
(struct sockaddr *) &addr, &addr_len);
if (res < 0) {
wpa_printf(MSG_DEBUG, "FILS: DHCP read failed: %s",
strerror(errno));
return;
}
wpa_printf(MSG_DEBUG, "FILS: DHCP response from server %s:%d (len=%d)",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), (int) res);
wpa_hexdump(MSG_MSGDUMP, "FILS: HLP - DHCP server response", buf, res);
if ((size_t) res < sizeof(*dhcp))
return;
dhcp = (struct dhcp_data *) buf;
if (dhcp->op != 2)
return; /* Not a BOOTREPLY */
if (dhcp->relay_ip != hapd->conf->own_ip_addr.u.v4.s_addr) {
wpa_printf(MSG_DEBUG,
"FILS: HLP - DHCP response to unknown relay address 0x%x",
dhcp->relay_ip);
return;
}
dhcp->relay_ip = 0;
pos = (u8 *) (dhcp + 1);
end = &buf[res];
if (end - pos < 4 || WPA_GET_BE32(pos) != DHCP_MAGIC) {
wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic in response");
return;
}
pos += 4;
wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options in response",
pos, end - pos);
while (pos < end && *pos != DHCP_OPT_END) {
u8 opt, olen;
opt = *pos++;
if (opt == DHCP_OPT_PAD)
continue;
if (pos >= end)
break;
olen = *pos++;
if (olen > end - pos)
break;
switch (opt) {
case DHCP_OPT_MSG_TYPE:
if (olen > 0)
msgtype = pos[0];
break;
case DHCP_OPT_RAPID_COMMIT:
rapid_commit = 1;
break;
}
pos += olen;
}
if (pos < end && *pos == DHCP_OPT_END)
end_opt = pos;
wpa_printf(MSG_DEBUG,
"FILS: HLP - DHCP message type %u (rapid_commit=%d hw_addr="
MACSTR ")",
msgtype, rapid_commit, MAC2STR(dhcp->hw_addr));
sta = ap_get_sta(hapd, dhcp->hw_addr);
if (!sta || !sta->fils_pending_assoc_req) {
wpa_printf(MSG_DEBUG,
"FILS: No pending HLP DHCP exchange with hw_addr "
MACSTR, MAC2STR(dhcp->hw_addr));
return;
}
if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPOFFER &&
!rapid_commit) {
/* Use hostapd to take care of 4-message exchange and convert
* the final DHCPACK to rapid commit version. */
if (fils_dhcp_request(hapd, sta, dhcp, end) == 0)
return;
/* failed, so send the server response as-is */
} else if (msgtype != DHCPACK) {
wpa_printf(MSG_DEBUG,
"FILS: No DHCPACK available from the server and cannot do rapid commit proxying");
}
pos = buf;
resp = wpabuf_alloc(2 * ETH_ALEN + 6 + 2 +
sizeof(*iph) + sizeof(*udph) + (end - pos) + 2);
if (!resp)
return;
wpabuf_put_data(resp, sta->addr, ETH_ALEN);
wpabuf_put_data(resp, hapd->own_addr, ETH_ALEN);
wpabuf_put_data(resp, "\xaa\xaa\x03\x00\x00\x00", 6);
wpabuf_put_be16(resp, ETH_P_IP);
iph = wpabuf_put(resp, sizeof(*iph));
iph->ip_v = 4;
iph->ip_hl = sizeof(*iph) / 4;
iph->ip_len = htons(sizeof(*iph) + sizeof(*udph) + (end - pos));
iph->ip_ttl = 1;
iph->ip_p = 17; /* UDP */
iph->ip_src.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
iph->ip_dst.s_addr = dhcp->client_ip;
iph->ip_sum = ip_checksum(iph, sizeof(*iph));
udph = wpabuf_put(resp, sizeof(*udph));
udph->uh_sport = htons(DHCP_SERVER_PORT);
udph->uh_dport = htons(DHCP_CLIENT_PORT);
udph->uh_ulen = htons(sizeof(*udph) + (end - pos));
udph->uh_sum = htons(0x0000); /* TODO: calculate checksum */
if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPACK &&
!rapid_commit && sta->fils_dhcp_rapid_commit_proxy && end_opt) {
/* Add rapid commit option */
wpabuf_put_data(resp, pos, end_opt - pos);
wpabuf_put_u8(resp, DHCP_OPT_RAPID_COMMIT);
wpabuf_put_u8(resp, 0);
wpabuf_put_data(resp, end_opt, end - end_opt);
} else {
wpabuf_put_data(resp, pos, end - pos);
}
if (wpabuf_resize(&sta->fils_hlp_resp, wpabuf_len(resp) +
2 * wpabuf_len(resp) / 255 + 100)) {
wpabuf_free(resp);
return;
}
rpos = wpabuf_head(resp);
left = wpabuf_len(resp);
wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXTENSION); /* Element ID */
if (left <= 254)
len = 1 + left;
else
len = 255;
wpabuf_put_u8(sta->fils_hlp_resp, len); /* Length */
/* Element ID Extension */
wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXT_FILS_HLP_CONTAINER);
/* Destination MAC Address, Source MAC Address, HLP Packet.
* HLP Packet is in MSDU format (i.e., including the LLC/SNAP header
* when LPD is used). */
wpabuf_put_data(sta->fils_hlp_resp, rpos, len - 1);
rpos += len - 1;
left -= len - 1;
while (left) {
wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_FRAGMENT);
len = left > 255 ? 255 : left;
wpabuf_put_u8(sta->fils_hlp_resp, len);
wpabuf_put_data(sta->fils_hlp_resp, rpos, len);
rpos += len;
left -= len;
}
wpabuf_free(resp);
if (sta->fils_drv_assoc_finish)
hostapd_notify_assoc_fils_finish(hapd, sta);
else
fils_hlp_finish_assoc(hapd, sta);
}
static int fils_process_hlp_dhcp(struct hostapd_data *hapd,
struct sta_info *sta,
const u8 *msg, size_t len)
{
const struct dhcp_data *dhcp;
struct wpabuf *dhcp_buf;
struct dhcp_data *dhcp_msg;
u8 msgtype = 0;
int rapid_commit = 0;
const u8 *pos = msg, *end;
struct sockaddr_in addr;
ssize_t res;
if (len < sizeof(*dhcp))
return 0;
dhcp = (const struct dhcp_data *) pos;
end = pos + len;
wpa_printf(MSG_DEBUG,
"FILS: HLP request DHCP: op=%u htype=%u hlen=%u hops=%u xid=0x%x",
dhcp->op, dhcp->htype, dhcp->hlen, dhcp->hops,
ntohl(dhcp->xid));
pos += sizeof(*dhcp);
if (dhcp->op != 1)
return 0; /* Not a BOOTREQUEST */
if (end - pos < 4)
return 0;
if (WPA_GET_BE32(pos) != DHCP_MAGIC) {
wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic");
return 0;
}
pos += 4;
wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options", pos, end - pos);
while (pos < end && *pos != DHCP_OPT_END) {
u8 opt, olen;
opt = *pos++;
if (opt == DHCP_OPT_PAD)
continue;
if (pos >= end)
break;
olen = *pos++;
if (olen > end - pos)
break;
switch (opt) {
case DHCP_OPT_MSG_TYPE:
if (olen > 0)
msgtype = pos[0];
break;
case DHCP_OPT_RAPID_COMMIT:
rapid_commit = 1;
break;
}
pos += olen;
}
wpa_printf(MSG_DEBUG, "FILS: HLP - DHCP message type %u", msgtype);
if (msgtype != DHCPDISCOVER)
return 0;
if (hapd->conf->dhcp_server.af != AF_INET ||
hapd->conf->dhcp_server.u.v4.s_addr == 0) {
wpa_printf(MSG_DEBUG,
"FILS: HLP - no DHCPv4 server configured - drop request");
return 0;
}
if (hapd->conf->own_ip_addr.af != AF_INET ||
hapd->conf->own_ip_addr.u.v4.s_addr == 0) {
wpa_printf(MSG_DEBUG,
"FILS: HLP - no IPv4 own_ip_addr configured - drop request");
return 0;
}
if (hapd->dhcp_sock < 0) {
int s;
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) {
wpa_printf(MSG_ERROR,
"FILS: Failed to open DHCP socket: %s",
strerror(errno));
return 0;
}
if (hapd->conf->dhcp_relay_port) {
os_memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr =
hapd->conf->own_ip_addr.u.v4.s_addr;
addr.sin_port = htons(hapd->conf->dhcp_relay_port);
if (bind(s, (struct sockaddr *) &addr, sizeof(addr))) {
wpa_printf(MSG_ERROR,
"FILS: Failed to bind DHCP socket: %s",
strerror(errno));
close(s);
return 0;
}
}
if (eloop_register_sock(s, EVENT_TYPE_READ,
fils_dhcp_handler, NULL, hapd)) {
close(s);
return 0;
}
hapd->dhcp_sock = s;
}
dhcp_buf = wpabuf_alloc(len);
if (!dhcp_buf)
return 0;
dhcp_msg = wpabuf_put(dhcp_buf, len);
os_memcpy(dhcp_msg, msg, len);
dhcp_msg->relay_ip = hapd->conf->own_ip_addr.u.v4.s_addr;
os_memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr;
addr.sin_port = htons(hapd->conf->dhcp_server_port);
res = sendto(hapd->dhcp_sock, dhcp_msg, len, 0,
(const struct sockaddr *) &addr, sizeof(addr));
if (res < 0) {
wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s",
strerror(errno));
wpabuf_free(dhcp_buf);
/* Close the socket to try to recover from error */
eloop_unregister_read_sock(hapd->dhcp_sock);
close(hapd->dhcp_sock);
hapd->dhcp_sock = -1;
return 0;
}
wpa_printf(MSG_DEBUG,
"FILS: HLP relayed DHCP request to server %s:%d (rapid_commit=%d)",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port),
rapid_commit);
if (hapd->conf->dhcp_rapid_commit_proxy && rapid_commit) {
/* Store a copy of the DHCPDISCOVER for rapid commit proxying
* purposes if the server does not support the rapid commit
* option. */
wpa_printf(MSG_DEBUG,
"FILS: Store DHCPDISCOVER for rapid commit proxy");
wpabuf_free(sta->hlp_dhcp_discover);
sta->hlp_dhcp_discover = dhcp_buf;
} else {
wpabuf_free(dhcp_buf);
}
return 1;
}
static int fils_process_hlp_udp(struct hostapd_data *hapd,
struct sta_info *sta, const u8 *dst,
const u8 *pos, size_t len)
{
const struct ip *iph;
const struct udphdr *udph;
u16 sport, dport, ulen;
if (len < sizeof(*iph) + sizeof(*udph))
return 0;
iph = (const struct ip *) pos;
udph = (const struct udphdr *) (iph + 1);
sport = ntohs(udph->uh_sport);
dport = ntohs(udph->uh_dport);
ulen = ntohs(udph->uh_ulen);
wpa_printf(MSG_DEBUG,
"FILS: HLP request UDP: sport=%u dport=%u ulen=%u sum=0x%x",
sport, dport, ulen, ntohs(udph->uh_sum));
/* TODO: Check UDP checksum */
if (ulen < sizeof(*udph) || ulen > len - sizeof(*iph))
return 0;
if (dport == DHCP_SERVER_PORT && sport == DHCP_CLIENT_PORT) {
return fils_process_hlp_dhcp(hapd, sta, (const u8 *) (udph + 1),
ulen - sizeof(*udph));
}
return 0;
}
static int fils_process_hlp_ip(struct hostapd_data *hapd,
struct sta_info *sta, const u8 *dst,
const u8 *pos, size_t len)
{
const struct ip *iph;
uint16_t ip_len;
if (len < sizeof(*iph))
return 0;
iph = (const struct ip *) pos;
if (ip_checksum(iph, sizeof(*iph)) != 0) {
wpa_printf(MSG_DEBUG,
"FILS: HLP request IPv4 packet had invalid header checksum - dropped");
return 0;
}
ip_len = ntohs(iph->ip_len);
if (ip_len > len)
return 0;
wpa_printf(MSG_DEBUG,
"FILS: HLP request IPv4: saddr=%08x daddr=%08x protocol=%u",
iph->ip_src.s_addr, iph->ip_dst.s_addr, iph->ip_p);
switch (iph->ip_p) {
case 17:
return fils_process_hlp_udp(hapd, sta, dst, pos, len);
default:
return 0;
}
}
static int fils_process_hlp_req(struct hostapd_data *hapd,
struct sta_info *sta,
const u8 *pos, size_t len)
{
const u8 *pkt, *end;
wpa_printf(MSG_DEBUG, "FILS: HLP request from " MACSTR " (dst=" MACSTR
" src=" MACSTR " len=%u)",
MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
(unsigned int) len);
if (!ether_addr_equal(sta->addr, pos + ETH_ALEN)) {
wpa_printf(MSG_DEBUG,
"FILS: Ignore HLP request with unexpected source address"
MACSTR, MAC2STR(pos + ETH_ALEN));
return 0;
}
end = pos + len;
pkt = pos + 2 * ETH_ALEN;
if (end - pkt >= 6 &&
os_memcmp(pkt, "\xaa\xaa\x03\x00\x00\x00", 6) == 0)
pkt += 6; /* Remove SNAP/LLC header */
wpa_hexdump(MSG_MSGDUMP, "FILS: HLP request packet", pkt, end - pkt);
if (end - pkt < 2)
return 0;
switch (WPA_GET_BE16(pkt)) {
case ETH_P_IP:
return fils_process_hlp_ip(hapd, sta, pos, pkt + 2,
end - pkt - 2);
default:
return 0;
}
}
int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta,
const u8 *pos, int left)
{
const u8 *end = pos + left;
u8 *tmp, *tmp_pos;
int ret = 0;
if (sta->fils_pending_assoc_req &&
eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta)) {
/* Do not process FILS HLP request again if the station
* retransmits (Re)Association Request frame before the previous
* HLP response has either been received or timed out. */
wpa_printf(MSG_DEBUG,
"FILS: Do not relay another HLP request from "
MACSTR
" before processing of the already pending one has been completed",
MAC2STR(sta->addr));
return 1;
}
/* Old DHCPDISCOVER is not needed anymore, if it was still pending */
wpabuf_free(sta->hlp_dhcp_discover);
sta->hlp_dhcp_discover = NULL;
sta->fils_dhcp_rapid_commit_proxy = 0;
/* Check if there are any FILS HLP Container elements */
while (end - pos >= 2) {
if (2 + pos[1] > end - pos)
return 0;
if (pos[0] == WLAN_EID_EXTENSION &&
pos[1] >= 1 + 2 * ETH_ALEN &&
pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER)
break;
pos += 2 + pos[1];
}
if (end - pos < 2)
return 0; /* No FILS HLP Container elements */
tmp = os_malloc(end - pos);
if (!tmp)
return 0;
while (end - pos >= 2) {
if (2 + pos[1] > end - pos ||
pos[0] != WLAN_EID_EXTENSION ||
pos[1] < 1 + 2 * ETH_ALEN ||
pos[2] != WLAN_EID_EXT_FILS_HLP_CONTAINER)
break;
tmp_pos = tmp;
os_memcpy(tmp_pos, pos + 3, pos[1] - 1);
tmp_pos += pos[1] - 1;
pos += 2 + pos[1];
/* Add possible fragments */
while (end - pos >= 2 && pos[0] == WLAN_EID_FRAGMENT &&
2 + pos[1] <= end - pos) {
os_memcpy(tmp_pos, pos + 2, pos[1]);
tmp_pos += pos[1];
pos += 2 + pos[1];
}
if (fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp) > 0)
ret = 1;
}
os_free(tmp);
return ret;
}
void fils_hlp_deinit(struct hostapd_data *hapd)
{
if (hapd->dhcp_sock >= 0) {
eloop_unregister_read_sock(hapd->dhcp_sock);
close(hapd->dhcp_sock);
hapd->dhcp_sock = -1;
}
}