From 0d7e5a3a29efd4bc138e74b19657e750d22c2887 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Dec 2010 17:15:01 +0200 Subject: [PATCH] Allow AP mode to disconnect STAs based on low ACK condition The nl80211 driver can report low ACK condition (in fact it reports complete loss right now only). Use that, along with a config option, to disconnect stations when the data connection is not working properly, e.g., due to the STA having went outside the range of the AP. This is disabled by default and can be enabled with disassoc_low_ack=1 in hostapd or wpa_supplicant configuration file. Signed-off-by: Johannes Berg --- hostapd/config_file.c | 2 ++ hostapd/hostapd.conf | 5 +++++ src/ap/ap_config.h | 2 ++ src/ap/drv_callbacks.c | 21 +++++++++++++++++++++ src/ap/hostapd.h | 1 + src/common/ieee802_11_defs.h | 2 ++ src/drivers/driver.h | 17 +++++++++++++++++ src/drivers/driver_nl80211.c | 14 ++++++++++++-- wpa_supplicant/ap.c | 2 ++ wpa_supplicant/config.c | 3 ++- wpa_supplicant/config.h | 5 +++++ wpa_supplicant/config_file.c | 2 ++ wpa_supplicant/config_winreg.c | 4 ++++ wpa_supplicant/events.c | 7 +++++++ 14 files changed, 84 insertions(+), 3 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 1651e2578..493861188 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2032,6 +2032,8 @@ struct hostapd_config * hostapd_config_read(const char *fname) else bss->p2p &= ~P2P_ALLOW_CROSS_CONNECTION; #endif /* CONFIG_P2P_MANAGER */ + } else if (os_strcmp(buf, "disassoc_low_ack") == 0) { + bss->disassoc_low_ack = atoi(pos); } else { wpa_printf(MSG_ERROR, "Line %d: unknown configuration " "item '%s'", line, buf); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index fe97ea87e..2090e9faa 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -342,6 +342,11 @@ wmm_ac_vo_acm=0 # default: 300 (i.e., 5 minutes) #ap_max_inactivity=300 +# Disassociate stations based on excessive transmission failures or other +# indications of connection loss. This depends on the driver capabilities and +# may not be available with all drivers. +#disassoc_low_ack=1 + # Maximum allowed Listen Interval (how many Beacon periods STAs are allowed to # remain asleep). Default: 65535 (no limit apart from field size) #max_listen_interval=100 diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index a95ebe9ac..0a929d66e 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -323,6 +323,8 @@ struct hostapd_bss_config { #define P2P_MANAGE BIT(3) #define P2P_ALLOW_CROSS_CONNECTION BIT(4) int p2p; + + int disassoc_low_ack; }; diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 5469d05c0..a49248fde 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -231,6 +231,22 @@ void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr) } +void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (!sta || !hapd->conf->disassoc_low_ack) + return; + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "disconnected due to excessive " + "missing ACKs"); + hostapd_drv_sta_disassoc(hapd, addr, WLAN_REASON_DISASSOC_LOW_ACK); + if (sta) + ap_sta_disassociate(hapd, sta, WLAN_REASON_DISASSOC_LOW_ACK); +} + + #ifdef HOSTAPD #ifdef NEED_AP_MLME @@ -500,6 +516,11 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, if (data) hostapd_notif_disassoc(hapd, data->deauth_info.addr); break; + case EVENT_STATION_LOW_ACK: + if (!data) + break; + hostapd_event_sta_low_ack(hapd, data->low_ack.addr); + break; default: wpa_printf(MSG_DEBUG, "Unknown event %d", event); break; diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index e2182cc0c..03464089f 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -247,5 +247,6 @@ void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr); int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, const u8 *ie, size_t ielen); void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr); +void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr); #endif /* HOSTAPD_H */ diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index c10ea13ea..3f5dc7ae9 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -174,6 +174,8 @@ #define WLAN_REASON_INVALID_RSN_IE_CAPAB 22 #define WLAN_REASON_IEEE_802_1X_AUTH_FAILED 23 #define WLAN_REASON_CIPHER_SUITE_REJECTED 24 +/* IEEE 802.11e */ +#define WLAN_REASON_DISASSOC_LOW_ACK 34 /* Information Element IDs */ diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 036092489..728ef79ca 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2287,6 +2287,15 @@ enum wpa_event_type { * details of the frame. */ EVENT_UNPROT_DISASSOC, + + /** + * EVENT_STATION_LOW_ACK + * + * Driver generates this event whenever it detected that a particular + * station was lost. Detection can be through massive transmission + * failures for example. + */ + EVENT_STATION_LOW_ACK }; @@ -2732,6 +2741,14 @@ union wpa_event_data { const u8 *da; u16 reason_code; } unprot_disassoc; + + /** + * struct low_ack - Data for EVENT_STATION_LOW_ACK events + * @addr: station address + */ + struct low_ack { + u8 addr[ETH_ALEN]; + } low_ack; }; /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index e0f8b1101..f7920990e 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -1210,6 +1210,7 @@ static void nl80211_cqm_event(struct wpa_driver_nl80211_data *drv, [NL80211_ATTR_CQM_RSSI_THOLD] = { .type = NLA_U32 }, [NL80211_ATTR_CQM_RSSI_HYST] = { .type = NLA_U8 }, [NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT] = { .type = NLA_U32 }, + [NL80211_ATTR_CQM_PKT_LOSS_EVENT] = { .type = NLA_U32 }, }; struct nlattr *cqm[NL80211_ATTR_CQM_MAX + 1]; enum nl80211_cqm_rssi_threshold_event event; @@ -1224,12 +1225,21 @@ static void nl80211_cqm_event(struct wpa_driver_nl80211_data *drv, return; } + os_memset(&ed, 0, sizeof(ed)); + + if (cqm[NL80211_ATTR_CQM_PKT_LOSS_EVENT]) { + if (!tb[NL80211_ATTR_MAC]) + return; + os_memcpy(ed.low_ack.addr, nla_data(tb[NL80211_ATTR_MAC]), + ETH_ALEN); + wpa_supplicant_event(drv->ctx, EVENT_STATION_LOW_ACK, &ed); + return; + } + if (cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT] == NULL) return; event = nla_get_u32(cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]); - os_memset(&ed, 0, sizeof(ed)); - if (event == NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH) { wpa_printf(MSG_DEBUG, "nl80211: Connection quality monitor " "event: RSSI high"); diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index bb2f2f296..26ccdeab2 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -191,6 +191,8 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s, else bss->max_num_sta = wpa_s->conf->max_num_sta; + bss->disassoc_low_ack = wpa_s->conf->disassoc_low_ack; + return 0; } diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 081f7e065..1ea37d4bb 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2408,7 +2408,8 @@ static const struct global_parse_data global_fields[] = { { FUNC(country), CFG_CHANGED_COUNTRY }, { INT(bss_max_count), 0 }, { INT_RANGE(filter_ssids, 0, 1), 0 }, - { INT(max_num_sta), 0 } + { INT(max_num_sta), 0 }, + { INT_RANGE(disassoc_low_ack, 0, 1), 0 } }; #undef FUNC diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index f288b2c97..c0ac41fa1 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -400,6 +400,11 @@ struct wpa_config { * changed_parameters - Bitmap of changed parameters since last update */ unsigned int changed_parameters; + + /** + * disassoc_low_ack - disassocenticate stations with massive packet loss + */ + int disassoc_low_ack; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 8715a46f0..09c99e372 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -690,6 +690,8 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) fprintf(f, "filter_ssids=%d\n", config->filter_ssids); if (config->max_num_sta != DEFAULT_MAX_NUM_STA) fprintf(f, "max_num_sta=%u\n", config->max_num_sta); + if (config->disassoc_low_ack) + fprintf(f, "disassoc_low_ack=%u\n", config->disassoc_low_ack); } #endif /* CONFIG_NO_CONFIG_WRITE */ diff --git a/wpa_supplicant/config_winreg.c b/wpa_supplicant/config_winreg.c index ef95a2945..6b2096f50 100644 --- a/wpa_supplicant/config_winreg.c +++ b/wpa_supplicant/config_winreg.c @@ -269,6 +269,8 @@ static int wpa_config_read_global(struct wpa_config *config, HKEY hk) &config->filter_ssids); wpa_config_read_reg_dword(hk, TEXT("max_num_sta"), (int *) &config->max_num_sta); + wpa_config_read_reg_dword(hk, TEXT("disassoc_low_ack"), + (int *) &config->disassoc_low_ack); return errors ? -1 : 0; } @@ -609,6 +611,8 @@ static int wpa_config_write_global(struct wpa_config *config, HKEY hk) config->filter_ssids, 0); wpa_config_write_reg_dword(hk, TEXT("max_num_sta"), config->max_num_sta, DEFAULT_MAX_NUM_STA); + wpa_config_write_reg_dword(hk, TEXT("disassoc_low_ack"), + config->disassoc_low_ack, 0); return 0; } diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index c48365e5d..bcd59d94e 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -2030,6 +2030,13 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, wpa_supplicant_event_unprot_disassoc(wpa_s, &data->unprot_disassoc); break; + case EVENT_STATION_LOW_ACK: +#ifdef CONFIG_AP + if (wpa_s->ap_iface && data) + hostapd_event_sta_low_ack(wpa_s->ap_iface->bss[0], + data->low_ack.addr); +#endif /* CONFIG_AP */ + break; default: wpa_printf(MSG_INFO, "Unknown event %d", event); break;