diff --git a/hostapd/config_file.c b/hostapd/config_file.c index e91c86c03..336eba99c 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3399,6 +3399,10 @@ static int hostapd_config_fill(struct hostapd_config *conf, } conf->fst_cfg.llt = (u32) val; #endif /* CONFIG_FST */ + } else if (os_strcmp(buf, "track_sta_max_num") == 0) { + conf->track_sta_max_num = atoi(pos); + } else if (os_strcmp(buf, "track_sta_max_age") == 0) { + conf->track_sta_max_age = atoi(pos); } else { wpa_printf(MSG_ERROR, "Line %d: unknown configuration item '%s'", diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 306c53ca3..cb6fb1757 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -2007,6 +2007,39 @@ static int hostapd_ctrl_iface_log_level(struct hostapd_data *hapd, char *cmd, } +#ifdef NEED_AP_MLME +static int hostapd_ctrl_iface_track_sta_list(struct hostapd_data *hapd, + char *buf, size_t buflen) +{ + struct hostapd_iface *iface = hapd->iface; + char *pos, *end; + struct hostapd_sta_info *info; + struct os_reltime now; + + sta_track_expire(iface, 0); + + pos = buf; + end = buf + buflen; + + os_get_reltime(&now); + dl_list_for_each_reverse(info, &iface->sta_seen, + struct hostapd_sta_info, list) { + struct os_reltime age; + int ret; + + os_reltime_sub(&now, &info->last_seen, &age); + ret = os_snprintf(pos, end - pos, MACSTR " %u\n", + MAC2STR(info->addr), (unsigned int) age.sec); + if (os_snprintf_error(end - pos, ret)) + break; + pos += ret; + } + + return pos - buf; +} +#endif /* NEED_AP_MLME */ + + static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd, char *buf, char *reply, int reply_size, @@ -2238,6 +2271,11 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd, } else if (os_strncmp(buf, "LOG_LEVEL", 9) == 0) { reply_len = hostapd_ctrl_iface_log_level( hapd, buf + 9, reply, reply_size); +#ifdef NEED_AP_MLME + } else if (os_strcmp(buf, "TRACK_STA_LIST") == 0) { + reply_len = hostapd_ctrl_iface_track_sta_list( + hapd, reply, reply_size); +#endif /* NEED_AP_MLME */ } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 4a829ead4..d9121973c 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1279,6 +1279,15 @@ own_ip_addr=127.0.0.1 # default: 60 #ap_table_expiration_time=3600 +# Maximum number of stations to track on the operating channel +# This can be used to detect dualband capable stations before they have +# associated, e.g., to provide guidance on which colocated BSS to use. +# Default: 0 (disabled) +#track_sta_max_num=100 + +# Maximum age of a station tracking entry in seconds +# Default: 180 +#track_sta_max_age=180 ##### Wi-Fi Protected Setup (WPS) ############################################# diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 872737549..be2fa8ba6 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -172,6 +172,7 @@ struct hostapd_config * hostapd_config_defaults(void) conf->ap_table_max_size = 255; conf->ap_table_expiration_time = 60; + conf->track_sta_max_age = 180; #ifdef CONFIG_TESTING_OPTIONS conf->ignore_probe_probability = 0.0; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index c9a37643c..343421424 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -586,6 +586,9 @@ struct hostapd_config { int ap_table_max_size; int ap_table_expiration_time; + unsigned int track_sta_max_num; + unsigned int track_sta_max_age; + char country[3]; /* first two octets: country code as described in * ISO/IEC 3166-1. Third octet: * ' ' (ascii 32): all environments diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 98d683294..46fff82c7 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -552,6 +552,76 @@ static enum ssid_match_result ssid_match(struct hostapd_data *hapd, } +void sta_track_expire(struct hostapd_iface *iface, int force) +{ + struct os_reltime now; + struct hostapd_sta_info *info; + + if (!iface->num_sta_seen) + return; + + os_get_reltime(&now); + while ((info = dl_list_first(&iface->sta_seen, struct hostapd_sta_info, + list))) { + if (!force && + !os_reltime_expired(&now, &info->last_seen, + iface->conf->track_sta_max_age)) + break; + force = 0; + + wpa_printf(MSG_MSGDUMP, "%s: Expire STA tracking entry for " + MACSTR, iface->bss[0]->conf->iface, + MAC2STR(info->addr)); + dl_list_del(&info->list); + iface->num_sta_seen--; + os_free(info); + } +} + + +static struct hostapd_sta_info * sta_track_get(struct hostapd_iface *iface, + const u8 *addr) +{ + struct hostapd_sta_info *info; + + dl_list_for_each(info, &iface->sta_seen, struct hostapd_sta_info, list) + if (os_memcmp(addr, info->addr, ETH_ALEN) == 0) + return info; + + return NULL; +} + + +static void sta_track_add(struct hostapd_iface *iface, const u8 *addr) +{ + struct hostapd_sta_info *info; + + info = sta_track_get(iface, addr); + if (info) { + /* Move the most recent entry to the end of the list */ + dl_list_del(&info->list); + dl_list_add_tail(&iface->sta_seen, &info->list); + os_get_reltime(&info->last_seen); + return; + } + + /* Add a new entry */ + info = os_zalloc(sizeof(*info)); + os_memcpy(info->addr, addr, ETH_ALEN); + os_get_reltime(&info->last_seen); + + if (iface->num_sta_seen >= iface->conf->track_sta_max_num) { + /* Expire oldest entry to make room for a new one */ + sta_track_expire(iface, 1); + } + + wpa_printf(MSG_MSGDUMP, "%s: Add STA tracking entry for " + MACSTR, iface->bss[0]->conf->iface, MAC2STR(addr)); + dl_list_add_tail(&iface->sta_seen, &info->list); + iface->num_sta_seen++; +} + + void handle_probe_req(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, int ssi_signal) @@ -567,6 +637,8 @@ void handle_probe_req(struct hostapd_data *hapd, ie = mgmt->u.probe_req.variable; if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req)) return; + if (hapd->iconf->track_sta_max_num) + sta_track_add(hapd->iface, mgmt->sa); ie_len = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req)); for (i = 0; hapd->probereq_cb && i < hapd->num_probereq_cb; i++) diff --git a/src/ap/beacon.h b/src/ap/beacon.h index 722159a75..21df9fae8 100644 --- a/src/ap/beacon.h +++ b/src/ap/beacon.h @@ -21,5 +21,6 @@ int ieee802_11_update_beacons(struct hostapd_iface *iface); int ieee802_11_build_ap_params(struct hostapd_data *hapd, struct wpa_driver_ap_params *params); void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params); +void sta_track_expire(struct hostapd_iface *iface, int force); #endif /* BEACON_H */ diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index cf8c93d51..e4d7bfc9b 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -355,6 +355,22 @@ static void hostapd_cleanup(struct hostapd_data *hapd) } +static void sta_track_deinit(struct hostapd_iface *iface) +{ + struct hostapd_sta_info *info; + + if (!iface->num_sta_seen) + return; + + while ((info = dl_list_first(&iface->sta_seen, struct hostapd_sta_info, + list))) { + dl_list_del(&info->list); + iface->num_sta_seen--; + os_free(info); + } +} + + static void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) { wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); @@ -370,6 +386,7 @@ static void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) os_free(iface->basic_rates); iface->basic_rates = NULL; ap_list_deinit(iface); + sta_track_deinit(iface); } @@ -1623,6 +1640,7 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) hostapd_tx_queue_params(iface); ap_list_init(iface); + dl_list_init(&iface->sta_seen); hostapd_set_acl(hapd); diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 89a1e8b5d..dcf51f00f 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -281,6 +281,12 @@ struct hostapd_data { }; +struct hostapd_sta_info { + struct dl_list list; + u8 addr[ETH_ALEN]; + struct os_reltime last_seen; +}; + /** * struct hostapd_iface - hostapd per-interface data structure */ @@ -409,6 +415,9 @@ struct hostapd_iface { void (*scan_cb)(struct hostapd_iface *iface); int num_ht40_scan_tries; + + struct dl_list sta_seen; /* struct hostapd_sta_info */ + unsigned int num_sta_seen; }; /* hostapd.c */