--- a/src/ap/ubus.c +++ b/src/ap/ubus.c @@ -146,6 +146,49 @@ hostapd_bss_ban_client(struct hostapd_da eloop_register_timeout(0, time * 1000, hostapd_bss_del_ban, ban, hapd); } +static void +hostapd_bss_signal_check(void *eloop_data, void *user_ctx) +/* This is called by an eloop timeout. All stations in the list are checked + * for signal level. This requires calling the driver, since hostapd doesn't + * see packets from a station once it is fully authorized. + * Stations with signal level below the threshold will be dropped. + */ +{ + struct hostapd_data *hapd = user_ctx; + struct hostap_sta_driver_data data; + struct sta_info *sta, *sta_next; + u8 addr[ETH_ALEN]; /* Buffer the address for logging purposes, in case it is destroyed while dropping */ + int strikes; /* same with strike count on this station. */ + int num_sta = 0; + int num_drop = 0; + int signal; + + for (sta = hapd->sta_list; sta; sta = sta_next) { + sta_next = sta->next; + memcpy(addr, sta->addr, ETH_ALEN); + if (!hostapd_drv_read_sta_data(hapd, &data, addr)) { + signal = data.signal; + num_sta++; + strikes = sta->sig_drop_strikes; + if (signal < hapd->conf->signal_stay_min) { /* signal bad. */ + strikes = ++sta->sig_drop_strikes; + if (strikes >= hapd->conf->signal_strikes) { /* Struck out--, drop. */ + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO, + "Deauthenticating client due to low signal strength %i", data.signal); + ap_sta_deauthenticate(hapd, sta, hapd->conf->signal_drop_reason); + num_drop++; + } + } else { + sta->sig_drop_strikes = 0; /* signal OK, reset the strike counter. */ + strikes = 0; + } + } + } + + eloop_register_timeout(hapd->conf->signal_poll_time, 0, hostapd_bss_signal_check, eloop_data, hapd); + +} + static int hostapd_bss_reload(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, @@ -580,6 +623,70 @@ hostapd_vendor_elements(struct ubus_cont return UBUS_STATUS_OK; } +enum { + SIGNAL_CONNECT, + SIGNAL_STAY, + SIGNAL_STRIKES, + SIGNAL_POLL, + SIGNAL_DROP_REASON, + __SIGNAL_SETTINGS_MAX +}; + +static const struct blobmsg_policy sig_policy[__SIGNAL_SETTINGS_MAX] = { + [SIGNAL_CONNECT] = {"connect", BLOBMSG_TYPE_INT32}, + [SIGNAL_STAY] = {"stay", BLOBMSG_TYPE_INT32}, + [SIGNAL_STRIKES] = {"strikes", BLOBMSG_TYPE_INT32}, + [SIGNAL_POLL] = {"poll_time", BLOBMSG_TYPE_INT32}, + [SIGNAL_DROP_REASON] = {"reason", BLOBMSG_TYPE_INT32} +}; + +static int +hostapd_bss_set_signal(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__SIGNAL_SETTINGS_MAX]; + struct hostapd_data *hapd = get_hapd_from_object(obj); + int sig_stay; + + blobmsg_parse(sig_policy, __SIGNAL_SETTINGS_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[SIGNAL_CONNECT]) + return UBUS_STATUS_INVALID_ARGUMENT; + hapd->conf->signal_auth_min = blobmsg_get_u32(tb[SIGNAL_CONNECT]); + if (tb[SIGNAL_STAY]) { + sig_stay = blobmsg_get_u32(tb[SIGNAL_STAY]); + } else { + sig_stay = hapd->conf->signal_auth_min - 5; // Default is 5 dB lower to stay. + } + hapd->conf->signal_stay_min = sig_stay; + if (tb[SIGNAL_STRIKES]) { + hapd->conf->signal_strikes = blobmsg_get_u32(tb[SIGNAL_STRIKES]); + if (hapd->conf->signal_strikes < 1) + return UBUS_STATUS_INVALID_ARGUMENT; + } else { + hapd->conf->signal_strikes = 3; + } + if (tb[SIGNAL_POLL]) { + hapd->conf->signal_poll_time = blobmsg_get_u32(tb[SIGNAL_POLL]); + if (hapd->conf->signal_poll_time < 3) + return UBUS_STATUS_INVALID_ARGUMENT; + } else { + hapd->conf->signal_poll_time = 5; + } + if (tb[SIGNAL_DROP_REASON]) { + hapd->conf->signal_drop_reason = blobmsg_get_u32(tb[SIGNAL_DROP_REASON]); + if ((hapd->conf->signal_drop_reason < 1) || (hapd->conf->signal_drop_reason > 35)) // XXX -- look up real limit + return UBUS_STATUS_INVALID_ARGUMENT; + } else { + hapd->conf->signal_drop_reason = 3; // Local choice. 5 (AP too busy) is also a good one. + } + eloop_cancel_timeout(hostapd_bss_signal_check, ELOOP_ALL_CTX, ELOOP_ALL_CTX); + eloop_register_timeout(3, 0, hostapd_bss_signal_check, NULL, hapd); // Start up the poll timer. + + return UBUS_STATUS_OK; +} + static void hostapd_rrm_print_nr(struct hostapd_neighbor_entry *nr) { @@ -1049,6 +1156,7 @@ static const struct ubus_method bss_meth UBUS_METHOD_NOARG("rrm_nr_list", hostapd_rrm_nr_list), UBUS_METHOD("rrm_nr_set", hostapd_rrm_nr_set, nr_set_policy), UBUS_METHOD("rrm_beacon_req", hostapd_rrm_beacon_req, beacon_req_policy), + UBUS_METHOD("set_required_signal", hostapd_bss_set_signal, sig_policy), #ifdef CONFIG_WNM_AP UBUS_METHOD("wnm_disassoc_imminent", hostapd_wnm_disassoc_imminent, wnm_disassoc_policy), #endif @@ -1086,6 +1194,8 @@ void hostapd_ubus_add_bss(struct hostapd obj->n_methods = bss_object_type.n_methods; ret = ubus_add_object(ctx, obj); hostapd_ubus_ref_inc(); + if (hapd->conf->signal_stay_min > -128) + eloop_register_timeout(3, 0, hostapd_bss_signal_check, NULL, hapd); /* Start up the poll timer. */ } void hostapd_ubus_free_bss(struct hostapd_data *hapd) @@ -1174,6 +1284,15 @@ int hostapd_ubus_handle_event(struct hos addr = req->mgmt_frame->sa; else addr = req->addr; + if (req->type < ARRAY_SIZE(types)) + type = types[req->type]; + + if (req->ssi_signal && req->type != HOSTAPD_UBUS_PROBE_REQ) /* don't clutter the log with probes. */ + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO, "%s request, signal %i %s", + type, req->ssi_signal, + (req->ssi_signal >= hapd->conf->signal_auth_min) ? "(Accepted)" : "(DENIED)"); + if (req->ssi_signal && req->ssi_signal < hapd->conf->signal_auth_min) + return WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; ban = avl_find_element(&hapd->ubus.banned, addr, ban, avl); if (ban) @@ -1182,9 +1301,6 @@ int hostapd_ubus_handle_event(struct hos if (!hapd->ubus.obj.has_subscribers) return WLAN_STATUS_SUCCESS; - if (req->type < ARRAY_SIZE(types)) - type = types[req->type]; - blob_buf_init(&b, 0); blobmsg_add_macaddr(&b, "address", addr); if (req->mgmt_frame) --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3322,6 +3322,24 @@ static int hostapd_config_fill(struct ho return 1; } bss->send_probe_response = val; + } else if (os_strcmp(buf, "signal_connect") == 0) { + bss->signal_auth_min = atoi(pos); + } else if (os_strcmp(buf, "signal_stay") == 0) { + bss->signal_stay_min = atoi(pos); + } else if (os_strcmp(buf, "signal_poll_time") == 0) { + bss->signal_poll_time = atoi(pos); + if (bss->signal_poll_time < 3) { + wpa_printf(MSG_ERROR, "Line %d: invalid signal poll time", line); + return 1; + } + } else if (os_strcmp(buf, "signal_strikes") == 0) { + bss->signal_strikes = atoi(pos); + } else if (os_strcmp(buf, "signal_drop_reason") == 0) { + bss->signal_drop_reason = atoi(pos); + if (bss->signal_drop_reason < 1 || bss->signal_drop_reason > 54) { + wpa_printf(MSG_ERROR, "Line %d: invalid signal drop reason", line); + return 1; + } } else if (os_strcmp(buf, "supported_rates") == 0) { if (hostapd_parse_intlist(&conf->supported_rates, pos)) { wpa_printf(MSG_ERROR, "Line %d: invalid rate list", --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -94,6 +94,11 @@ void hostapd_config_defaults_bss(struct bss->eapol_version = EAPOL_VERSION; bss->max_listen_interval = 65535; + bss->signal_auth_min = -128; /* this is lower than any real signal, so all stations will be accepted */ + bss->signal_stay_min = -128; + bss->signal_strikes = 3; + bss->signal_poll_time = 5; + bss->signal_drop_reason = 3; /* "Local choice" */ bss->pwd_group = 19; /* ECC: GF(p=256) */ --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -351,7 +351,11 @@ struct hostapd_bss_config { int wds_sta; int isolate; int start_disabled; - + int signal_auth_min; /* Minimum signal a STA needs to authenticate */ + int signal_stay_min; /* Minimum signal needed to stay connected. */ + int signal_poll_time; /* Time in seconds between checks of connected STAs */ + int signal_strikes; /* Number of consecutive times signal can be low before dropping the STA. */ + int signal_drop_reason; /* IEEE802.11 reason code transmitted when dropping a STA. */ int auth_algs; /* bitfield of allowed IEEE 802.11 authentication * algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */ --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -732,6 +732,7 @@ struct sta_info * ap_sta_add(struct host sta_track_claim_taxonomy_info(hapd->iface, addr, &sta->probe_ie_taxonomy); #endif /* CONFIG_TAXONOMY */ + sta->sig_drop_strikes = 0; return sta; } --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -286,6 +286,7 @@ struct sta_info { unsigned int airtime_weight; struct os_reltime backlogged_until; #endif /* CONFIG_AIRTIME_POLICY */ + int sig_drop_strikes; /* Number of times signal was below threshold. */ };