/* * Generic advertisement service (GAS) server * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include "common.h" #include "common/ieee802_11_defs.h" #include "common/gas.h" #include "utils/eloop.h" #include "hostapd.h" #include "ap_config.h" #include "ap_drv_ops.h" #include "sta_info.h" #include "gas_serv.h" static struct gas_dialog_info * gas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token) { struct sta_info *sta; struct gas_dialog_info *dia = NULL; int i, j; sta = ap_get_sta(hapd, addr); if (!sta) { /* * We need a STA entry to be able to maintain state for * the GAS query. */ wpa_printf(MSG_DEBUG, "ANQP: Add a temporary STA entry for " "GAS query"); sta = ap_sta_add(hapd, addr); if (!sta) { wpa_printf(MSG_DEBUG, "Failed to add STA " MACSTR " for GAS query", MAC2STR(addr)); return NULL; } sta->flags |= WLAN_STA_GAS; /* * The default inactivity is 300 seconds. We don't need * it to be that long. */ ap_sta_session_timeout(hapd, sta, 5); } if (sta->gas_dialog == NULL) { sta->gas_dialog = os_zalloc(GAS_DIALOG_MAX * sizeof(struct gas_dialog_info)); if (sta->gas_dialog == NULL) return NULL; } for (i = sta->gas_dialog_next, j = 0; j < GAS_DIALOG_MAX; i++, j++) { if (i == GAS_DIALOG_MAX) i = 0; if (sta->gas_dialog[i].valid) continue; dia = &sta->gas_dialog[i]; dia->valid = 1; dia->index = i; dia->dialog_token = dialog_token; sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i; return dia; } wpa_msg(hapd->msg_ctx, MSG_ERROR, "ANQP: Could not create dialog for " MACSTR " dialog_token %u. Consider increasing " "GAS_DIALOG_MAX.", MAC2STR(addr), dialog_token); return NULL; } struct gas_dialog_info * gas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token) { struct sta_info *sta; int i; sta = ap_get_sta(hapd, addr); if (!sta) { wpa_printf(MSG_DEBUG, "ANQP: could not find STA " MACSTR, MAC2STR(addr)); return NULL; } for (i = 0; sta->gas_dialog && i < GAS_DIALOG_MAX; i++) { if (sta->gas_dialog[i].dialog_token != dialog_token || !sta->gas_dialog[i].valid) continue; return &sta->gas_dialog[i]; } wpa_printf(MSG_DEBUG, "ANQP: Could not find dialog for " MACSTR " dialog_token %u", MAC2STR(addr), dialog_token); return NULL; } void gas_serv_dialog_clear(struct gas_dialog_info *dia) { wpabuf_free(dia->sd_resp); os_memset(dia, 0, sizeof(*dia)); } static void gas_serv_free_dialogs(struct hostapd_data *hapd, const u8 *sta_addr) { struct sta_info *sta; int i; sta = ap_get_sta(hapd, sta_addr); if (sta == NULL || sta->gas_dialog == NULL) return; for (i = 0; i < GAS_DIALOG_MAX; i++) { if (sta->gas_dialog[i].valid) return; } os_free(sta->gas_dialog); sta->gas_dialog = NULL; } static void anqp_add_capab_list(struct hostapd_data *hapd, struct wpabuf *buf) { u8 *len; len = gas_anqp_add_element(buf, ANQP_CAPABILITY_LIST); wpabuf_put_le16(buf, ANQP_CAPABILITY_LIST); if (hapd->conf->venue_name) wpabuf_put_le16(buf, ANQP_VENUE_NAME); if (hapd->conf->network_auth_type) wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE); if (hapd->conf->roaming_consortium) wpabuf_put_le16(buf, ANQP_ROAMING_CONSORTIUM); if (hapd->conf->ipaddr_type_configured) wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY); if (hapd->conf->domain_name) wpabuf_put_le16(buf, ANQP_DOMAIN_NAME); gas_anqp_set_element_len(buf, len); } static void anqp_add_venue_name(struct hostapd_data *hapd, struct wpabuf *buf) { if (hapd->conf->venue_name) { u8 *len; unsigned int i; len = gas_anqp_add_element(buf, ANQP_VENUE_NAME); wpabuf_put_u8(buf, hapd->conf->venue_group); wpabuf_put_u8(buf, hapd->conf->venue_type); for (i = 0; i < hapd->conf->venue_name_count; i++) { struct hostapd_venue_name *vn; vn = &hapd->conf->venue_name[i]; wpabuf_put_u8(buf, 3 + vn->name_len); wpabuf_put_data(buf, vn->lang, 3); wpabuf_put_data(buf, vn->name, vn->name_len); } gas_anqp_set_element_len(buf, len); } } static void anqp_add_network_auth_type(struct hostapd_data *hapd, struct wpabuf *buf) { if (hapd->conf->network_auth_type) { wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE); wpabuf_put_le16(buf, hapd->conf->network_auth_type_len); wpabuf_put_data(buf, hapd->conf->network_auth_type, hapd->conf->network_auth_type_len); } } static void anqp_add_roaming_consortium(struct hostapd_data *hapd, struct wpabuf *buf) { unsigned int i; u8 *len; len = gas_anqp_add_element(buf, ANQP_ROAMING_CONSORTIUM); for (i = 0; i < hapd->conf->roaming_consortium_count; i++) { struct hostapd_roaming_consortium *rc; rc = &hapd->conf->roaming_consortium[i]; wpabuf_put_u8(buf, rc->len); wpabuf_put_data(buf, rc->oi, rc->len); } gas_anqp_set_element_len(buf, len); } static void anqp_add_ip_addr_type_availability(struct hostapd_data *hapd, struct wpabuf *buf) { if (hapd->conf->ipaddr_type_configured) { wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY); wpabuf_put_le16(buf, 1); wpabuf_put_u8(buf, hapd->conf->ipaddr_type_availability); } } static void anqp_add_domain_name(struct hostapd_data *hapd, struct wpabuf *buf) { if (hapd->conf->domain_name) { wpabuf_put_le16(buf, ANQP_DOMAIN_NAME); wpabuf_put_le16(buf, hapd->conf->domain_name_len); wpabuf_put_data(buf, hapd->conf->domain_name, hapd->conf->domain_name_len); } } static struct wpabuf * gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, unsigned int request, struct gas_dialog_info *di) { struct wpabuf *buf; buf = wpabuf_alloc(1400); if (buf == NULL) return NULL; if (request & ANQP_REQ_CAPABILITY_LIST) anqp_add_capab_list(hapd, buf); if (request & ANQP_REQ_VENUE_NAME) anqp_add_venue_name(hapd, buf); if (request & ANQP_REQ_NETWORK_AUTH_TYPE) anqp_add_network_auth_type(hapd, buf); if (request & ANQP_REQ_ROAMING_CONSORTIUM) anqp_add_roaming_consortium(hapd, buf); if (request & ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY) anqp_add_ip_addr_type_availability(hapd, buf); if (request & ANQP_REQ_DOMAIN_NAME) anqp_add_domain_name(hapd, buf); return buf; } static void gas_serv_clear_cached_ies(void *eloop_data, void *user_ctx) { struct gas_dialog_info *dia = eloop_data; wpa_printf(MSG_DEBUG, "GAS: Timeout triggered, clearing dialog for " "dialog token %d", dia->dialog_token); gas_serv_dialog_clear(dia); } struct anqp_query_info { unsigned int request; unsigned int remote_request; const void *param; u32 param_arg; u16 remote_delay; }; static void set_anqp_req(unsigned int bit, const char *name, int local, unsigned int remote, u16 remote_delay, struct anqp_query_info *qi) { qi->request |= bit; if (local) { wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name); } else if (bit & remote) { wpa_printf(MSG_DEBUG, "ANQP: %s (remote)", name); qi->remote_request |= bit; if (remote_delay > qi->remote_delay) qi->remote_delay = remote_delay; } else { wpa_printf(MSG_DEBUG, "ANQP: %s not available", name); } } static void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id, struct anqp_query_info *qi) { switch (info_id) { case ANQP_CAPABILITY_LIST: set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1, 0, 0, qi); break; case ANQP_VENUE_NAME: set_anqp_req(ANQP_REQ_VENUE_NAME, "Venue Name", hapd->conf->venue_name != NULL, 0, 0, qi); break; case ANQP_NETWORK_AUTH_TYPE: set_anqp_req(ANQP_REQ_NETWORK_AUTH_TYPE, "Network Auth Type", hapd->conf->network_auth_type != NULL, 0, 0, qi); break; case ANQP_ROAMING_CONSORTIUM: set_anqp_req(ANQP_REQ_ROAMING_CONSORTIUM, "Roaming Consortium", hapd->conf->roaming_consortium != NULL, 0, 0, qi); break; case ANQP_IP_ADDR_TYPE_AVAILABILITY: set_anqp_req(ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY, "IP Addr Type Availability", hapd->conf->ipaddr_type_configured, 0, 0, qi); case ANQP_DOMAIN_NAME: set_anqp_req(ANQP_REQ_DOMAIN_NAME, "Domain Name", hapd->conf->domain_name != NULL, 0, 0, qi); break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u", info_id); break; } } static void rx_anqp_query_list(struct hostapd_data *hapd, const u8 *pos, const u8 *end, struct anqp_query_info *qi) { wpa_printf(MSG_DEBUG, "ANQP: %u Info IDs requested in Query list", (unsigned int) (end - pos) / 2); while (pos + 2 <= end) { rx_anqp_query_list_id(hapd, WPA_GET_LE16(pos), qi); pos += 2; } } static void gas_serv_req_local_processing(struct hostapd_data *hapd, const u8 *sa, u8 dialog_token, struct anqp_query_info *qi) { struct wpabuf *buf, *tx_buf; buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", buf); if (!buf) return; if (wpabuf_len(buf) > hapd->gas_frag_limit || hapd->conf->gas_comeback_delay) { struct gas_dialog_info *di; u16 comeback_delay = 1; if (hapd->conf->gas_comeback_delay) { /* Testing - allow overriding of the delay value */ comeback_delay = hapd->conf->gas_comeback_delay; } wpa_printf(MSG_DEBUG, "ANQP: Too long response to fit in " "initial response - use GAS comeback"); di = gas_dialog_create(hapd, sa, dialog_token); if (!di) { wpa_printf(MSG_INFO, "ANQP: Could not create dialog " "for " MACSTR " (dialog token %u)", MAC2STR(sa), dialog_token); wpabuf_free(buf); return; } di->sd_resp = buf; di->sd_resp_pos = 0; tx_buf = gas_anqp_build_initial_resp_buf( dialog_token, WLAN_STATUS_SUCCESS, comeback_delay, NULL); } else { wpa_printf(MSG_DEBUG, "ANQP: Initial response (no comeback)"); tx_buf = gas_anqp_build_initial_resp_buf( dialog_token, WLAN_STATUS_SUCCESS, 0, buf); wpabuf_free(buf); } if (!tx_buf) return; hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, wpabuf_head(tx_buf), wpabuf_len(tx_buf)); wpabuf_free(tx_buf); } static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd, const u8 *sa, const u8 *data, size_t len) { const u8 *pos = data; const u8 *end = data + len; const u8 *next; u8 dialog_token; u16 slen; struct anqp_query_info qi; const u8 *adv_proto; if (len < 1 + 2) return; os_memset(&qi, 0, sizeof(qi)); dialog_token = *pos++; wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: GAS Initial Request from " MACSTR " (dialog token %u) ", MAC2STR(sa), dialog_token); if (*pos != WLAN_EID_ADV_PROTO) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Unexpected IE in GAS Initial Request: %u", *pos); return; } adv_proto = pos++; slen = *pos++; next = pos + slen; if (next > end || slen < 2) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Invalid IE in GAS Initial Request"); return; } pos++; /* skip QueryRespLenLimit and PAME-BI */ if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) { struct wpabuf *buf; wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Unsupported GAS advertisement protocol id %u", *pos); if (sa[0] & 0x01) return; /* Invalid source address - drop silently */ buf = gas_build_initial_resp( dialog_token, WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED, 0, 2 + slen + 2); if (buf == NULL) return; wpabuf_put_data(buf, adv_proto, 2 + slen); wpabuf_put_le16(buf, 0); /* Query Response Length */ hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, wpabuf_head(buf), wpabuf_len(buf)); wpabuf_free(buf); return; } pos = next; /* Query Request */ if (pos + 2 > end) return; slen = WPA_GET_LE16(pos); pos += 2; if (pos + slen > end) return; end = pos + slen; /* ANQP Query Request */ while (pos < end) { u16 info_id, elen; if (pos + 4 > end) return; info_id = WPA_GET_LE16(pos); pos += 2; elen = WPA_GET_LE16(pos); pos += 2; if (pos + elen > end) { wpa_printf(MSG_DEBUG, "ANQP: Invalid Query Request"); return; } switch (info_id) { case ANQP_QUERY_LIST: rx_anqp_query_list(hapd, pos, pos + elen, &qi); break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query " "Request element %u", info_id); break; } pos += elen; } gas_serv_req_local_processing(hapd, sa, dialog_token, &qi); } void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, struct gas_dialog_info *dialog) { struct wpabuf *buf, *tx_buf; u8 dialog_token = dialog->dialog_token; size_t frag_len; if (dialog->sd_resp == NULL) { buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, dialog); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) goto tx_gas_response_done; dialog->sd_resp = buf; dialog->sd_resp_pos = 0; } frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; if (frag_len > hapd->gas_frag_limit || dialog->comeback_delay || hapd->conf->gas_comeback_delay) { u16 comeback_delay_tus = dialog->comeback_delay + GAS_SERV_COMEBACK_DELAY_FUDGE; u32 comeback_delay_secs, comeback_delay_usecs; if (hapd->conf->gas_comeback_delay) { /* Testing - allow overriding of the delay value */ comeback_delay_tus = hapd->conf->gas_comeback_delay; } wpa_printf(MSG_DEBUG, "GAS: Response frag_len %u (frag limit " "%u) and comeback delay %u, " "requesting comebacks", (unsigned int) frag_len, (unsigned int) hapd->gas_frag_limit, dialog->comeback_delay); tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, WLAN_STATUS_SUCCESS, comeback_delay_tus, NULL); if (tx_buf) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Initial Resp (comeback = 10TU)"); hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst, wpabuf_head(tx_buf), wpabuf_len(tx_buf)); } wpabuf_free(tx_buf); /* start a timer of 1.5 * comeback-delay */ comeback_delay_tus = comeback_delay_tus + (comeback_delay_tus / 2); comeback_delay_secs = (comeback_delay_tus * 1024) / 1000000; comeback_delay_usecs = (comeback_delay_tus * 1024) - (comeback_delay_secs * 1000000); eloop_register_timeout(comeback_delay_secs, comeback_delay_usecs, gas_serv_clear_cached_ies, dialog, NULL); goto tx_gas_response_done; } buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) + dialog->sd_resp_pos, frag_len); if (buf == NULL) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Buffer allocation " "failed"); goto tx_gas_response_done; } tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, WLAN_STATUS_SUCCESS, 0, buf); wpabuf_free(buf); if (tx_buf == NULL) goto tx_gas_response_done; wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Initial " "Response (frag_id %d frag_len %d)", dialog->sd_frag_id, (int) frag_len); dialog->sd_frag_id++; hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst, wpabuf_head(tx_buf), wpabuf_len(tx_buf)); wpabuf_free(tx_buf); tx_gas_response_done: gas_serv_clear_cached_ies(dialog, NULL); } static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, const u8 *sa, const u8 *data, size_t len) { struct gas_dialog_info *dialog; struct wpabuf *buf, *tx_buf; u8 dialog_token; size_t frag_len; int more = 0; wpa_hexdump(MSG_DEBUG, "GAS: RX GAS Comeback Request", data, len); if (len < 1) return; dialog_token = *data; wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Dialog Token: %u", dialog_token); dialog = gas_serv_dialog_find(hapd, sa, dialog_token); if (!dialog) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: No pending SD " "response fragment for " MACSTR " dialog token %u", MAC2STR(sa), dialog_token); if (sa[0] & 0x01) return; /* Invalid source address - drop silently */ tx_buf = gas_anqp_build_comeback_resp_buf( dialog_token, WLAN_STATUS_NO_OUTSTANDING_GAS_REQ, 0, 0, 0, NULL); if (tx_buf == NULL) return; goto send_resp; } if (dialog->sd_resp == NULL) { wpa_printf(MSG_DEBUG, "GAS: Remote request 0x%x received 0x%x", dialog->requested, dialog->received); if ((dialog->requested & dialog->received) != dialog->requested) { wpa_printf(MSG_DEBUG, "GAS: Did not receive response " "from remote processing"); gas_serv_dialog_clear(dialog); tx_buf = gas_anqp_build_comeback_resp_buf( dialog_token, WLAN_STATUS_GAS_RESP_NOT_RECEIVED, 0, 0, 0, NULL); if (tx_buf == NULL) return; goto send_resp; } buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, dialog); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) goto rx_gas_comeback_req_done; dialog->sd_resp = buf; dialog->sd_resp_pos = 0; } frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; if (frag_len > hapd->gas_frag_limit) { frag_len = hapd->gas_frag_limit; more = 1; } wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u", (unsigned int) frag_len); buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) + dialog->sd_resp_pos, frag_len); if (buf == NULL) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate " "buffer"); goto rx_gas_comeback_req_done; } tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token, WLAN_STATUS_SUCCESS, dialog->sd_frag_id, more, 0, buf); wpabuf_free(buf); if (tx_buf == NULL) goto rx_gas_comeback_req_done; wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Comeback Response " "(frag_id %d more=%d frag_len=%d)", dialog->sd_frag_id, more, (int) frag_len); dialog->sd_frag_id++; dialog->sd_resp_pos += frag_len; if (more) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: %d more bytes remain " "to be sent", (int) (wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos)); } else { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of " "SD response sent"); gas_serv_dialog_clear(dialog); gas_serv_free_dialogs(hapd, sa); } send_resp: hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, wpabuf_head(tx_buf), wpabuf_len(tx_buf)); wpabuf_free(tx_buf); return; rx_gas_comeback_req_done: gas_serv_clear_cached_ies(dialog, NULL); } static void gas_serv_rx_public_action(void *ctx, const u8 *buf, size_t len, int freq) { struct hostapd_data *hapd = ctx; const struct ieee80211_mgmt *mgmt; size_t hdr_len; const u8 *sa, *data; mgmt = (const struct ieee80211_mgmt *) buf; hdr_len = (const u8 *) &mgmt->u.action.u.vs_public_action.action - buf; if (hdr_len > len) return; if (mgmt->u.action.category != WLAN_ACTION_PUBLIC) return; sa = mgmt->sa; len -= hdr_len; data = &mgmt->u.action.u.public_action.action; switch (data[0]) { case WLAN_PA_GAS_INITIAL_REQ: gas_serv_rx_gas_initial_req(hapd, sa, data + 1, len - 1); break; case WLAN_PA_GAS_COMEBACK_REQ: gas_serv_rx_gas_comeback_req(hapd, sa, data + 1, len - 1); break; } } int gas_serv_init(struct hostapd_data *hapd) { hapd->public_action_cb = gas_serv_rx_public_action; hapd->public_action_cb_ctx = hapd; hapd->gas_frag_limit = 1400; if (hapd->conf->gas_frag_limit > 0) hapd->gas_frag_limit = hapd->conf->gas_frag_limit; return 0; } void gas_serv_deinit(struct hostapd_data *hapd) { }