diff --git a/hostapd/config_file.c b/hostapd/config_file.c index b3970345d..8cfa198c3 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3615,6 +3615,21 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "fils_realm") == 0) { if (parse_fils_realm(bss, pos) < 0) return 1; + } else if (os_strcmp(buf, "dhcp_server") == 0) { + if (hostapd_parse_ip_addr(pos, &bss->dhcp_server)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "dhcp_rapid_commit_proxy") == 0) { + bss->dhcp_rapid_commit_proxy = atoi(pos); + } else if (os_strcmp(buf, "fils_hlp_wait_time") == 0) { + bss->fils_hlp_wait_time = atoi(pos); + } else if (os_strcmp(buf, "dhcp_server_port") == 0) { + bss->dhcp_server_port = atoi(pos); + } else if (os_strcmp(buf, "dhcp_relay_port") == 0) { + bss->dhcp_relay_port = atoi(pos); #endif /* CONFIG_FILS */ } else if (os_strcmp(buf, "multicast_to_unicast") == 0) { bss->multicast_to_unicast = atoi(pos); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index c9b105aa9..314f3842b 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1316,6 +1316,34 @@ own_ip_addr=127.0.0.1 #fils_realm=example.com #fils_realm=example.org +# DHCP server for FILS HLP +# If configured, hostapd will act as a DHCP relay for all FILS HLP requests +# that include a DHCPDISCOVER message and send them to the specific DHCP +# server for processing. hostapd will then wait for a response from that server +# before replying with (Re)Association Response frame that encapsulates this +# DHCP response. own_ip_addr is used as the local address for the communication +# with the DHCP server. +#dhcp_server=127.0.0.1 + +# DHCP server UDP port +# Default: 67 +#dhcp_server_port=67 + +# DHCP relay UDP port on the local device +# Default: 67; 0 means not to bind any specific port +#dhcp_relay_port=67 + +# DHCP rapid commit proxy +# If set to 1, this enables hostapd to act as a DHCP rapid commit proxy to +# allow the rapid commit options (two message DHCP exchange) to be used with a +# server that supports only the four message DHCP exchange. This is disabled by +# default (= 0) and can be enabled by setting this to 1. +#dhcp_rapid_commit_proxy=0 + +# Wait time for FILS HLP (dot11HLPWaitTime) in TUs +# default: 30 TUs (= 30.72 milliseconds) +#fils_hlp_wait_time=30 + ##### IEEE 802.11r configuration ############################################## # Mobility Domain identifier (dot11FTMobilityDomainID, MDID) diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index e417a1247..c2b80ad97 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -13,6 +13,7 @@ #include "radius/radius_client.h" #include "common/ieee802_11_defs.h" #include "common/eapol_common.h" +#include "common/dhcp.h" #include "eap_common/eap_wsc_common.h" #include "eap_server/eap.h" #include "wpa_auth.h" @@ -100,6 +101,9 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss) #ifdef CONFIG_FILS dl_list_init(&bss->fils_realms); + bss->fils_hlp_wait_time = 30; + bss->dhcp_server_port = DHCP_SERVER_PORT; + bss->dhcp_relay_port = DHCP_SERVER_PORT; #endif /* CONFIG_FILS */ } diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 075261c74..31b1e7762 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -607,6 +607,11 @@ struct hostapd_bss_config { u8 fils_cache_id[FILS_CACHE_ID_LEN]; int fils_cache_id_set; struct dl_list fils_realms; /* list of struct fils_realm */ + struct hostapd_ip_addr dhcp_server; + int dhcp_rapid_commit_proxy; + unsigned int fils_hlp_wait_time; + u16 dhcp_server_port; + u16 dhcp_relay_port; #endif /* CONFIG_FILS */ int multicast_to_unicast; diff --git a/src/ap/fils_hlp.c b/src/ap/fils_hlp.c index 3fde71efc..c66c1f15f 100644 --- a/src/ap/fils_hlp.c +++ b/src/ap/fils_hlp.c @@ -9,13 +9,531 @@ #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 void fils_process_hlp_req(struct hostapd_data *hapd, +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 iphdr *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->version = 4; + iph->ihl = sizeof(*iph) / 4; + iph->tot_len = htons(sizeof(*iph) + sizeof(*udph) + (end - pos)); + iph->ttl = 1; + iph->saddr = hapd->conf->dhcp_server.u.v4.s_addr; + iph->daddr = dhcp->client_ip; + iph->check = 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->len = htons(sizeof(*udph) + (end - pos)); + udph->check = 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) == 0) { + 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); + fils_hlp_finish_assoc(hapd, sta); +} + + +static int fils_process_hlp_dhcp(struct hostapd_data *hapd, struct sta_info *sta, - const u8 *pos, size_t len) + 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 iphdr *iph; + const struct udphdr *udph; + u16 sport, dport, ulen; + + if (len < sizeof(*iph) + sizeof(*udph)) + return 0; + iph = (const struct iphdr *) 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 iphdr *iph; + u16 tot_len; + + if (len < sizeof(*iph)) + return 0; + iph = (const struct iphdr *) pos; + if (ip_checksum(iph, sizeof(*iph)) != 0) { + wpa_printf(MSG_DEBUG, + "FILS: HLP request IPv4 packet had invalid header checksum - dropped"); + return 0; + } + tot_len = ntohs(iph->tot_len); + if (tot_len > len) + return 0; + wpa_printf(MSG_DEBUG, + "FILS: HLP request IPv4: saddr=%08x daddr=%08x protocol=%u", + iph->saddr, iph->daddr, iph->protocol); + switch (iph->protocol) { + case 17: + return fils_process_hlp_udp(hapd, sta, dst, pos, len); + } + + 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; @@ -27,7 +545,7 @@ static void fils_process_hlp_req(struct hostapd_data *hapd, wpa_printf(MSG_DEBUG, "FILS: Ignore HLP request with unexpected source address" MACSTR, MAC2STR(pos + ETH_ALEN)); - return; + return 0; } end = pos + len; @@ -36,19 +554,36 @@ static void fils_process_hlp_req(struct hostapd_data *hapd, 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); + } + + return 0; } -void fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, - const u8 *pos, int left) +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; + + /* 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; + return 0; if (pos[0] == WLAN_EID_EXTENSION && pos[1] >= 1 + 2 * ETH_ALEN && pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER) @@ -56,11 +591,11 @@ void fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, pos += 2 + pos[1]; } if (end - pos < 2) - return; /* No FILS HLP Container elements */ + return 0; /* No FILS HLP Container elements */ tmp = os_malloc(end - pos); if (!tmp) - return; + return 0; while (end - pos >= 2) { if (2 + pos[1] > end - pos || @@ -81,8 +616,21 @@ void fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, pos += 2 + pos[1]; } - fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp); + 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; + } } diff --git a/src/ap/fils_hlp.h b/src/ap/fils_hlp.h index 98cc3195b..e14a6bf65 100644 --- a/src/ap/fils_hlp.h +++ b/src/ap/fils_hlp.h @@ -9,7 +9,19 @@ #ifndef FILS_HLP_H #define FILS_HLP_H -void fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, - const u8 *pos, int left); +int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *pos, int left); + +#ifdef CONFIG_FILS + +void fils_hlp_deinit(struct hostapd_data *hapd); + +#else /* CONFIG_FILS */ + +static inline void fils_hlp_deinit(struct hostapd_data *hapd) +{ +} + +#endif /* CONFIG_FILS */ #endif /* FILS_HLP_H */ diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 26ebbb666..47fa144c4 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -45,6 +45,7 @@ #include "ndisc_snoop.h" #include "neighbor_db.h" #include "rrm.h" +#include "fils_hlp.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -344,6 +345,7 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd) #endif /* CONFIG_MESH */ hostapd_clean_rrm(hapd); + fils_hlp_deinit(hapd); } @@ -2006,6 +2008,7 @@ hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface, hapd->ctrl_sock = -1; dl_list_init(&hapd->ctrl_dst); dl_list_init(&hapd->nr_db); + hapd->dhcp_sock = -1; return hapd; } diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index bc0ac23b0..5ab623de1 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -303,6 +303,8 @@ struct hostapd_data { u8 range_req_token; unsigned int lci_req_active:1; unsigned int range_req_active:1; + + int dhcp_sock; /* UDP socket used with the DHCP server */ }; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 129cc158d..d9bc976e3 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -2266,11 +2266,22 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ies, size_t ies_len) { int send_len; - u8 buf[sizeof(struct ieee80211_mgmt) + 1024]; + u8 *buf; + size_t buflen; struct ieee80211_mgmt *reply; u8 *p; + u16 res = WLAN_STATUS_SUCCESS; - os_memset(buf, 0, sizeof(buf)); + buflen = sizeof(struct ieee80211_mgmt) + 1024; +#ifdef CONFIG_FILS + if (sta->fils_hlp_resp) + buflen += wpabuf_len(sta->fils_hlp_resp); +#endif /* CONFIG_FILS */ + buf = os_zalloc(buflen); + if (!buf) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } reply = (struct ieee80211_mgmt *) buf; reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, @@ -2298,7 +2309,7 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, /* IEEE 802.11r: Mobility Domain Information, Fast BSS * Transition Information, RSN, [RIC Response] */ p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, p, - buf + sizeof(buf) - p, + buf + buflen - p, sta->auth_alg, ies, ies_len); } #endif /* CONFIG_IEEE80211R_AP */ @@ -2400,10 +2411,10 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, p = hostapd_eid_p2p_manage(hapd, p); #endif /* CONFIG_P2P_MANAGER */ - p = hostapd_eid_mbo(hapd, p, buf + sizeof(buf) - p); + p = hostapd_eid_mbo(hapd, p, buf + buflen - p); if (hapd->conf->assocresp_elements && - (size_t) (buf + sizeof(buf) - p) >= + (size_t) (buf + buflen - p) >= wpabuf_len(hapd->conf->assocresp_elements)) { os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements), wpabuf_len(hapd->conf->assocresp_elements)); @@ -2421,8 +2432,10 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, struct ieee802_11_elems elems; if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == - ParseFailed || !elems.fils_session) - return WLAN_STATUS_UNSPECIFIED_FAILURE; + ParseFailed || !elems.fils_session) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } /* FILS Session */ *p++ = WLAN_EID_EXTENSION; /* Element ID */ @@ -2432,22 +2445,75 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, send_len += 2 + 1 + FILS_SESSION_LEN; send_len = fils_encrypt_assoc(sta->wpa_sm, buf, send_len, - sizeof(buf)); - if (send_len < 0) - return WLAN_STATUS_UNSPECIFIED_FAILURE; + buflen, sta->fils_hlp_resp); + if (send_len < 0) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } } #endif /* CONFIG_FILS */ if (hostapd_drv_send_mlme(hapd, reply, send_len, 0) < 0) { wpa_printf(MSG_INFO, "Failed to send assoc resp: %s", strerror(errno)); - return WLAN_STATUS_UNSPECIFIED_FAILURE; + res = WLAN_STATUS_UNSPECIFIED_FAILURE; } - return WLAN_STATUS_SUCCESS; +done: + os_free(buf); + return res; } +#ifdef CONFIG_FILS + +void fils_hlp_finish_assoc(struct hostapd_data *hapd, struct sta_info *sta) +{ + u16 reply_res; + + wpa_printf(MSG_DEBUG, "FILS: Finish association with " MACSTR, + MAC2STR(sta->addr)); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + if (!sta->fils_pending_assoc_req) + return; + reply_res = send_assoc_resp(hapd, sta, sta->addr, WLAN_STATUS_SUCCESS, + sta->fils_pending_assoc_is_reassoc, + sta->fils_pending_assoc_req, + sta->fils_pending_assoc_req_len); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = NULL; + + /* + * Remove the station in case tranmission of a success response fails + * (the STA was added associated to the driver) or if the station was + * previously added unassociated. + */ + if (reply_res != WLAN_STATUS_SUCCESS || sta->added_unassoc) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } +} + + +void fils_hlp_timeout(void *eloop_ctx, void *eloop_data) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = eloop_data; + + wpa_printf(MSG_DEBUG, + "FILS: HLP response timeout - continue with association response for " + MACSTR, MAC2STR(sta->addr)); + fils_hlp_finish_assoc(hapd, sta); +} + +#endif /* CONFIG_FILS */ + + static void handle_assoc(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, int reassoc) @@ -2461,6 +2527,9 @@ static void handle_assoc(struct hostapd_data *hapd, struct hostapd_sta_wpa_psk_short *psk = NULL; char *identity = NULL; char *radius_cui = NULL; +#ifdef CONFIG_FILS + int delay_assoc = 0; +#endif /* CONFIG_FILS */ if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) : sizeof(mgmt->u.assoc_req))) { @@ -2749,8 +2818,10 @@ static void handle_assoc(struct hostapd_data *hapd, #ifdef CONFIG_FILS if (sta->auth_alg == WLAN_AUTH_FILS_SK || sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || - sta->auth_alg == WLAN_AUTH_FILS_PK) - fils_process_hlp(hapd, sta, pos, left); + sta->auth_alg == WLAN_AUTH_FILS_PK) { + if (fils_process_hlp(hapd, sta, pos, left) > 0) + delay_assoc = 1; + } #endif /* CONFIG_FILS */ fail: @@ -2779,6 +2850,29 @@ static void handle_assoc(struct hostapd_data *hapd, if (resp == WLAN_STATUS_SUCCESS && sta && add_associated_sta(hapd, sta)) resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; +#ifdef CONFIG_FILS + if (sta) { + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + } + if (sta && delay_assoc && resp == WLAN_STATUS_SUCCESS) { + sta->fils_pending_assoc_req = tmp; + sta->fils_pending_assoc_req_len = left; + sta->fils_pending_assoc_is_reassoc = reassoc; + wpa_printf(MSG_DEBUG, + "FILS: Waiting for HLP processing before sending (Re)Association Response frame to " + MACSTR, MAC2STR(sta->addr)); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + eloop_register_timeout(0, hapd->conf->fils_hlp_wait_time * 1024, + fils_hlp_timeout, hapd, sta); + return; + } +#endif /* CONFIG_FILS */ + reply_res = send_assoc_resp(hapd, sta, mgmt->sa, resp, reassoc, pos, left); os_free(tmp); diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 46c92b785..74ed69013 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -140,5 +140,7 @@ void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, struct sta_info *sta, int success, struct wpabuf *erp_resp, const u8 *msk, size_t msk_len); +void fils_hlp_timeout(void *eloop_ctx, void *eloop_data); +void fils_hlp_finish_assoc(struct hostapd_data *hapd, struct sta_info *sta); #endif /* IEEE802_11_H */ diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index b87ddeac3..af8c754d9 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -339,6 +339,13 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) mbo_ap_sta_free(sta); os_free(sta->supp_op_classes); +#ifdef CONFIG_FILS + os_free(sta->fils_pending_assoc_req); + wpabuf_free(sta->fils_hlp_resp); + wpabuf_free(sta->hlp_dhcp_discover); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); +#endif /* CONFIG_FILS */ + os_free(sta); } diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 0b44f7bf3..6f55403b0 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -225,6 +225,12 @@ struct sta_info { #ifdef CONFIG_FILS u8 fils_snonce[FILS_NONCE_LEN]; u8 fils_session[FILS_SESSION_LEN]; + u8 *fils_pending_assoc_req; + size_t fils_pending_assoc_req_len; + unsigned int fils_pending_assoc_is_reassoc:1; + unsigned int fils_dhcp_rapid_commit_proxy:1; + struct wpabuf *fils_hlp_resp; + struct wpabuf *hlp_dhcp_discover; #endif /* CONFIG_FILS */ }; diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 69e3a5ded..7372a69e6 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -2274,7 +2274,8 @@ int fils_decrypt_assoc(struct wpa_state_machine *sm, const u8 *fils_session, int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf, - size_t current_len, size_t max_len) + size_t current_len, size_t max_len, + const struct wpabuf *hlp) { u8 *end = buf + max_len; u8 *pos = buf + current_len; @@ -2334,7 +2335,9 @@ int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf, wpabuf_put_u8(plain, WLAN_EID_EXT_FILS_KEY_CONFIRM); wpabuf_put_data(plain, sm->fils_key_auth_ap, sm->fils_key_auth_len); - /* TODO: FILS HLP Container */ + /* FILS HLP Container */ + if (hlp) + wpabuf_put_buf(plain, hlp); /* TODO: FILS IP Address Assignment */ diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index a44b030b7..9cbe3889b 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -247,7 +247,7 @@ enum { WPA_MGMT_FRAME_PROTECTION_VIOLATION, WPA_INVALID_MGMT_GROUP_CIPHER, WPA_INVALID_MDIE, WPA_INVALID_PROTO }; - + int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, struct wpa_state_machine *sm, const u8 *wpa_ie, size_t wpa_ie_len, @@ -361,7 +361,8 @@ int fils_decrypt_assoc(struct wpa_state_machine *sm, const u8 *fils_session, const struct ieee80211_mgmt *mgmt, size_t frame_len, u8 *pos, size_t left); int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf, - size_t current_len, size_t max_len); + size_t current_len, size_t max_len, + const struct wpabuf *hlp); int fils_set_tk(struct wpa_state_machine *sm); #endif /* WPA_AUTH_H */