From e49ba3f2aa3b5e6d5d80f4a81cf3f27ed6e7caa2 Mon Sep 17 00:00:00 2001 From: David Weidenkopf Date: Thu, 14 Jul 2016 10:54:06 -0700 Subject: [PATCH] Steering: Implement network steering capability for multi-AP deployments. Requires station blacklist support and BSS Transition (802.11v). Signed-off-by: David Weidenkopf --- hostapd/Makefile | 5 + hostapd/config_file.c | 6 + hostapd/defconfig | 7 + hostapd/main.c | 7 +- src/ap/ap_config.h | 5 + src/ap/hostapd.c | 13 + src/ap/ieee802_11.c | 17 +- src/ap/net_steering.c | 1528 +++++++++++++++++++++++++++++++++++++++++++++++++ src/ap/net_steering.h | 13 + src/ap/sta_info.c | 5 + src/utils/wpa_debug.h | 9 +- 11 files changed, 1605 insertions(+), 10 deletions(-) create mode 100644 src/ap/net_steering.c create mode 100644 src/ap/net_steering.h diff --git a/hostapd/Makefile b/hostapd/Makefile index baa7819..1b31cdf 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -74,6 +74,11 @@ OBJS += ../src/ap/ieee802_1x.o OBJS += ../src/ap/ap_config.o OBJS += ../src/ap/eap_user_db.o OBJS += ../src/ap/ieee802_11_auth.o +ifdef CONFIG_NET_STEERING +CFLAGS += -DCONFIG_NET_STEERING +OBJS += ../src/ap/net_steering.o +endif + OBJS += ../src/ap/sta_info.o OBJS += ../src/ap/wpa_auth.o OBJS += ../src/ap/tkip_countermeasures.o diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 6dc7e8c..eed617b 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2558,6 +2558,12 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "ft_over_ds") == 0) { bss->ft_over_ds = atoi(pos); #endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_NET_STEERING + } else if (os_strcmp(buf, "net_steering_mode") == 0) { + os_free(bss->net_steeering_mode); + /* can be "off", "suggest", or "force" */ + bss->net_steeering_mode = os_strdup(pos); +#endif /* CONFIG_NET_STEERING */ #ifndef CONFIG_NO_CTRL_IFACE } else if (os_strcmp(buf, "ctrl_interface") == 0) { os_free(bss->ctrl_interface); diff --git a/hostapd/defconfig b/hostapd/defconfig index f7b60e0..6108138 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -337,3 +337,10 @@ CONFIG_IPV6=y # These extentions facilitate efficient use of multiple frequency bands # available to the AP and the devices that may associate with it. #CONFIG_MBO=y + + +# Network Steering support +# The network steering system steers client STAs to the infrastructure AP with the +# best RSSI. APs collaborate and can use several methods to direct client STAs to +# transition to the best AP. +#CONFIG_NET_STEERING=y diff --git a/hostapd/main.c b/hostapd/main.c index 2c8dbd3..edd572b 100644 --- a/hostapd/main.c +++ b/hostapd/main.c @@ -85,6 +85,11 @@ static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module, case HOSTAPD_MODULE_MLME: module_str = "MLME"; break; +#ifdef CONFIG_NET_STEERING + case HOSTAPD_MODULE_NET_STEERING: + module_str = "STEER"; + break; +#endif default: module_str = NULL; break; @@ -144,7 +149,7 @@ static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module, /** - * hostapd_driver_init - Preparate driver interface + * hostapd_driver_init - Prepare driver interface */ static int hostapd_driver_init(struct hostapd_iface *iface) { diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 0ae9a6e..bee724a 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -340,6 +340,11 @@ struct hostapd_bss_config { int ft_over_ds; #endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_NET_STEERING + /* can be "off", "suggest", or "force" */ + char *net_steeering_mode; +#endif /* CONFIG_NET_STEERING */ + char *ctrl_interface; /* directory for UNIX domain sockets */ #ifndef CONFIG_NATIVE_WINDOWS gid_t ctrl_interface_gid; diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 30f57f4..980a409 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -42,6 +42,7 @@ #include "bss_load.h" #include "x_snoop.h" #include "dhcp_snoop.h" +#include "net_steering.h" #include "ndisc_snoop.h" #include "neighbor_db.h" #include "rrm.h" @@ -296,6 +297,11 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd) hapd->radius_das = NULL; #endif /* CONFIG_NO_RADIUS */ +#ifdef CONFIG_NET_STEERING + net_steering_deinit(hapd); +#endif + + hostapd_deinit_wps(hapd); authsrv_deinit(hapd); @@ -1077,6 +1083,13 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) wpa_printf(MSG_ERROR, "Accounting initialization failed."); return -1; } +#ifdef CONFIG_NET_STEERING + if (net_steering_init(hapd) < 0) { + wpa_printf(MSG_ERROR, "Failed to initialize net steering"); + return -1; + } +#endif /* CONFIG_NET_STEERING */ + if (conf->ieee802_11f && (hapd->iapp = iapp_init(hapd, conf->iapp_iface)) == NULL) { diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 11f12f9..4a0c182 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -43,9 +43,9 @@ #include "ieee802_11.h" #include "dfs.h" #include "mbo_ap.h" +#include "net_steering.h" #include "rrm.h" - u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) { u8 *pos = eid; @@ -2036,7 +2036,7 @@ static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, static void handle_assoc(struct hostapd_data *hapd, const struct ieee80211_mgmt *mgmt, size_t len, - int reassoc) + int ssi_signal, int reassoc) { u16 capab_info, listen_interval, seq_ctrl, fc; u16 resp = WLAN_STATUS_SUCCESS, reply_res; @@ -2227,8 +2227,8 @@ static void handle_assoc(struct hostapd_data *hapd, #endif /* CONFIG_IEEE80211N */ hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, - HOSTAPD_LEVEL_DEBUG, - "association OK (aid %d)", sta->aid); + HOSTAPD_LEVEL_DEBUG, "association OK (aid %d) on channel %d BSSID "MACSTR, + sta->aid, hapd->iconf->channel, MAC2STR(mgmt->bssid)); /* Station will be marked associated, after it acknowledges AssocResp */ sta->flags |= WLAN_STA_ASSOC_REQ_OK; @@ -2246,6 +2246,11 @@ static void handle_assoc(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_NET_STEERING + /* TODO pass in ssi_signal */ + net_steering_association(hapd, sta, ssi_signal); +#endif /* CONFIG_NET_STEERING */ + /* Make sure that the previously registered inactivity timer will not * remove the STA immediately. */ sta->timeout_next = STA_NULLFUNC; @@ -2665,12 +2670,12 @@ int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len, break; case WLAN_FC_STYPE_ASSOC_REQ: wpa_printf(MSG_DEBUG, "mgmt::assoc_req"); - handle_assoc(hapd, mgmt, len, 0); + handle_assoc(hapd, mgmt, len, fi->ssi_signal, 0); ret = 1; break; case WLAN_FC_STYPE_REASSOC_REQ: wpa_printf(MSG_DEBUG, "mgmt::reassoc_req"); - handle_assoc(hapd, mgmt, len, 1); + handle_assoc(hapd, mgmt, len, fi->ssi_signal, 1); ret = 1; break; case WLAN_FC_STYPE_DISASSOC: diff --git a/src/ap/net_steering.c b/src/ap/net_steering.c new file mode 100644 index 0000000..cab3203 --- /dev/null +++ b/src/ap/net_steering.c @@ -0,0 +1,1528 @@ +#include "net_steering.h" +#include "utils/includes.h" +#include "utils/state_machine.h" +#include "utils/common.h" +#include "utils/wpa_debug.h" +#include "utils/wpabuf.h" +#include "utils/list.h" +#include "utils/eloop.h" +#include "utils/os.h" +#include "hostapd.h" +#include "ap_config.h" +#include "sta_blacklist.h" +#include "sta_info.h" +#include "wpa_auth.h" +#include "wnm_ap.h" +#include "ap_drv_ops.h" +#include "ctrl_iface_ap.h" +#include "common/defs.h" +#include "l2_packet/l2_packet.h" + +#include +#include + + +#define MAX_FRAME_SIZE 1024 +#define MACSTRLEN 18 /* 6 * 2 + 5 seps + NULL */ + +static const u16 proto = 0x8267; /* chosen at random from unassigned */ +static const u8 tlv_magic = 48; +static const u8 tlv_version = 1; +static const u16 max_score = -1; + +static const u32 flood_timeout_secs = 1; +static const u32 client_timeout_secs = 10; +static const u32 probe_timeout_secs = 34; + +static const char* mode_off = "off"; +static const char* mode_suggest = "suggest"; +static const char* mode_force = "force"; + +/* Can't change the values of these without bumping the tlv version */ +enum { + TLV_SCORE = 0, + TLV_CLOSE_CLIENT = 1, + TLV_CLOSED_CLIENT = 2, + TLV_MAP = 3, + TLV_CLIENT_FLAGS = 4, +}; + +/* Pre decls */ +struct net_steering_client; +struct net_steering_bss; +static void do_flood_score(struct net_steering_client *client); +static void flood_score(void *eloop_data, void *user_ctx); +static void client_timeout(void *eloop_data, void *user_ctx); +static void probe_timeout(void *eloop_data, void *user_ctx); + + +/* + * Use this so we can track additional data for stas and avoid adding more members to sta_info + * It does mean that we need to be concerned about the lifetime of sta_info objects tracked + * by hapd + * Also, this struct is used to track stas we hear about from other APs via score messages + */ +struct net_steering_client +{ + struct dl_list list; + /* + * This will point to a sta in the hapd list pointed to by the nsb. + * May be NULL if client is not associated + */ + struct sta_info* sta; + struct net_steering_bss* nsb; + u16 score; + + enum { + /* + * AP will allow the client to associate with it. + */ + STEERING_IDLE, + /* + * AP has told another AP to blacklist the client and is waiting for it + * to tell us that it has blacklisted the client. + */ + STEERING_CONFIRMING, + /* + * A remote AP has confirmed that it has blacklisted the client; AP is + * now waiting on an associate. + */ + STEERING_ASSOCIATING, + /* + * The client is using this AP to communicate with other devices. + */ + STEERING_ASSOCIATED, + /* + * The AP has blacklisted the client is waiting on a disassociate and will + * then send out a closed packet to remotes. + */ + STEERING_REJECTING, + /* + * The client is blacklisted and disassociated. + */ + STEERING_REJECTED, + } STEERING_state; + + enum { + /* + * The client has started to use this AP to communicate with other devices. + * Note that we don't attempt to explicitly model the client so (dis)associate + * events just appear on APs. + */ + STEERING_E_ASSOCIATED, + /* + * The client has either gone away or associated with a different AP. + */ + STEERING_E_DISASSOCIATED, + /* + * A remote AP sent a client score packet with a score worse than our local score. + */ + STEERING_E_PEER_IS_WORSE, + /* + * A remote AP sent a client score packet with a score the same as (or better) than our local score. + */ + STEERING_E_PEER_NOT_WORSE, + /* + * A remote AP sent a client score that is the maximum possible. + */ + STEERING_E_PEER_LOST_CLIENT, + /* + * The AP has been told to blacklist/transition the client. + */ + STEERING_E_CLOSE_CLIENT, + /* + * A remote AP has confirmed that it has blacklisted/transitioned the client. + */ + STEERING_E_CLOSED_CLIENT, + /* + * Used to limit how long an AP waits on an event (e.g. ClosedClientPacket). + */ + STEERING_E_TIMEOUT, + } STEERING_event; + + /* state machine support */ + unsigned int changed; + + /* The mac addr of the client. This is necessary since we may not have a sta_info for this client */ + u8 addr[ETH_ALEN]; + + /* + * tracks the sender bssid for close messages + */ + u8 close_bssid[ETH_ALEN]; + + /* + * tracks the remote bssid for received scores + */ + u8 remote_bssid[ETH_ALEN]; + + /* + * tracks the locally adjusted association timer for the remote ap that has the client associated + */ + struct os_time remote_time; + + /* + * tracks the time of association of the client + */ + struct os_time association_time; + + /* + * channel used for Fast BSS Transition + */ + u8 remote_channel; +}; + +/* One context per bss */ +struct net_steering_bss { + /* supports a dl_list of net_steering_bss */ + struct dl_list list; + /* contains the list of clients */ + struct dl_list clients; + /* bss data structure */ + struct hostapd_data *hapd; + /* frame serial number TODO can we get rid of this, else use it and manage wraparound? */ + u16 frame_sn; + /* the steering control channel */ + struct l2_packet_data *control; + + enum { + MODE_OFF = 0, + MODE_SUGGEST = 1, + MODE_FORCE = 2, + } mode; +}; + +static struct dl_list nsb_list = DL_LIST_HEAD_INIT(nsb_list); + +static const char* state_to_str(int state) +{ + switch(state) { + case STEERING_IDLE: return "IDLE"; + case STEERING_CONFIRMING: return "CONFIRMING"; + case STEERING_ASSOCIATING: return "ASSOCIATING"; + case STEERING_ASSOCIATED: return "ASSOCIATED"; + case STEERING_REJECTING: return "REJECTING"; + case STEERING_REJECTED: return "REJECTED"; + default: return "UNKNOWN"; + } +} + +static const char* event_to_str(int event) +{ + switch (event) { + case STEERING_E_ASSOCIATED: return "E_ASSOCIATED"; + case STEERING_E_DISASSOCIATED: return "E_DISASSOCIATED"; + case STEERING_E_PEER_IS_WORSE: return "E_PEER_IS_WORSE"; + case STEERING_E_PEER_NOT_WORSE: return "E_PEER_NOT_WORSE"; + case STEERING_E_PEER_LOST_CLIENT: return "E_PEER_LOST_CLIENT"; + case STEERING_E_CLOSE_CLIENT: return "E_CLOSE_CLIENT"; + case STEERING_E_CLOSED_CLIENT: return "E_CLOSED_CLIENT"; + case STEERING_E_TIMEOUT: return "E_TIMEOUT"; + default: return "UNKNOWN"; + } +} + + +static void client_set_remote_bssid(struct net_steering_client* client, const u8* bssid) +{ + os_memcpy(client->remote_bssid, bssid, ETH_ALEN); +} + +static const u8* client_get_remote_bssid(struct net_steering_client* client) +{ + return client->remote_bssid; +} + +static void client_set_close_bssid(struct net_steering_client* client, const u8* bssid) +{ + os_memcpy(client->close_bssid, bssid, ETH_ALEN); +} + +static const u8* client_get_close_bssid(struct net_steering_client* client) +{ + return client->close_bssid; +} + +static void client_clear_close_bssid(struct net_steering_client* client) +{ + os_memset(client->close_bssid, 0, ETH_ALEN); +} + +static const u8* client_get_local_bssid(struct net_steering_client* client) +{ + return client->nsb->hapd->conf->bssid; +} + +static const u8* client_get_mac(struct net_steering_client* client) +{ + /* assumption is that this is always filled in from sta or via received tlv */ + return client->addr; +} + +static struct net_steering_client* client_create(struct net_steering_bss* nsb, const u8* addr) +{ + assert(nsb->hapd->conf->bssid); + if (!nsb->hapd || !nsb->hapd->conf || !nsb->hapd->conf->bssid) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "hapd pointers! are suspect %p\n", + nsb->hapd); + } + + struct net_steering_client* client = (struct net_steering_client*) os_zalloc(sizeof(*client)); + if (!client) + { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed to create client "MACSTR" for bssid "MACSTR"\n", + MAC2STR(addr), MAC2STR(nsb->hapd->conf->bssid)); + return NULL; + } + + client->nsb = nsb; + client->remote_time.sec = 0; + client->remote_time.usec = 0; + client->score = max_score; + client->STEERING_state = STEERING_IDLE; + os_memcpy(client->addr, addr, ETH_ALEN); + + dl_list_add(&nsb->clients, &client->list); + + return client; +} + +static void start_flood_timer(struct net_steering_client *client) +{ + if (eloop_register_timeout(flood_timeout_secs, 0, flood_score, client, NULL)) { + hostapd_logger(client->nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "client "MACSTR" failed to schedule flood\n", + MAC2STR(client_get_mac(client))); + } +} + +static void stop_flood_timer(struct net_steering_client *client) +{ + client->score = max_score; + // It is safe if a timer is already canceled + eloop_cancel_timeout(flood_score, client, NULL); +} + +static void client_start_timer(struct net_steering_client *client) +{ + if (eloop_register_timeout(client_timeout_secs, 0, client_timeout, client, NULL)) { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "client "MACSTR" failed to schedule timeout\n", + MAC2STR(client_get_mac(client))); + } +} + +static void client_stop_timer(struct net_steering_client *client) +{ + // It is safe if a timer is already canceled + eloop_cancel_timeout(client_timeout, client, NULL); +} + + +static void client_start_probe_timer(struct net_steering_client *client) +{ + if (eloop_register_timeout(probe_timeout_secs, 0, probe_timeout, client, NULL)) { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "client "MACSTR" failed to schedule probe timeout\n", + MAC2STR(client_get_mac(client))); + } +} + +static void client_stop_probe_timer(struct net_steering_client *client) +{ + // It is safe if a timer is already canceled + eloop_cancel_timeout(probe_timeout, client, NULL); +} + +static Boolean client_supports_bss_transition(struct net_steering_client *client) +{ + return (client->sta->dot11MgmtOptionBSSTransitionActivated == 1); +} + +static void client_associate(struct net_steering_client* client, struct sta_info* sta) +{ + client->sta = sta; + os_memcpy(client->addr, client->sta->addr, ETH_ALEN); + + /* now that the client is associated, cancel probe timer */ + client_stop_probe_timer(client); +} + +static void client_disassociate(struct net_steering_client* client) +{ + client->sta = NULL; + os_memset(client->remote_bssid, 0, ETH_ALEN); + client->remote_time.sec = 0; + client->remote_time.usec = 0; + + /* now that the client is disassociated, set probe timer */ + client_start_probe_timer(client); +} + +static Boolean client_is_associated(struct net_steering_client* client) +{ + return (client->sta && client->STEERING_state == STEERING_ASSOCIATED) ? TRUE : FALSE; +} + +static void client_delete(struct net_steering_client* client) +{ + stop_flood_timer(client); + client_stop_timer(client); + client_stop_probe_timer(client); + + dl_list_del(&client->list); + os_memset(client, 0, sizeof(*client)); + os_free(client); +} + +static u16 compute_score(int rssi) +{ + return (u16) abs(rssi); +} + +static struct net_steering_client* client_find(struct net_steering_bss* nsb, const u8* sta) +{ + struct net_steering_client *client = NULL; + + dl_list_for_each(client, &nsb->clients, struct net_steering_client, list) { + if (os_memcmp(sta, client->addr, ETH_ALEN) == 0) { + return client; + } + } + return NULL; +} + +static size_t parse_header(const u8* buf, size_t len, u8* magic, u8* version, u16* packet_len, u16* sn) +{ + static u16 header_len = sizeof(*magic) + sizeof(*version) + sizeof(*sn) + sizeof(*packet_len); + + /* TODO maybe using wpabuf would make this code simpler */ + if (len < header_len) return 0; + + const u8* tmp = buf; + os_memcpy(magic, tmp, sizeof(*magic)); + tmp += sizeof(*magic); + + os_memcpy(version, tmp, sizeof(*version)); + tmp += sizeof(*version); + + os_memcpy(packet_len, tmp, sizeof(*packet_len)); + *packet_len = ntohs(*packet_len); + tmp += sizeof(*packet_len); + + os_memcpy(sn, tmp, sizeof(*sn)); + *sn = ntohs(*sn); + tmp += sizeof(*sn); + + return tmp - buf; +} + +static void put_tlv_header(struct wpabuf* buf, u8 tlv_type, u8 tlv_len) +{ + wpabuf_put_u8(buf, tlv_type); + wpabuf_put_u8(buf, tlv_len); +} + +static size_t parse_tlv_header(const u8* buf, size_t len, u8* tlv_type, u8* tlv_len) +{ + static const size_t header_len = sizeof(*tlv_type) + sizeof(*tlv_len); + const u8* tmp = buf; + if (len < header_len) return 0; + + os_memcpy(tlv_type, tmp, sizeof(*tlv_type)); + tmp += sizeof(*tlv_type); + + os_memcpy(tlv_len, tmp, sizeof(*tlv_len)); + tmp += sizeof(*tlv_len); + + return tmp - buf; +} + +static void put_score(struct wpabuf* buf, const u8* sta, const u8* bssid, u16 score, u32 association_msecs) +{ + static u8 score_len = ETH_ALEN + ETH_ALEN + sizeof(score) + sizeof(association_msecs); + + put_tlv_header(buf, TLV_SCORE, score_len); + wpabuf_put_data(buf, sta, ETH_ALEN); + wpabuf_put_data(buf, bssid, ETH_ALEN); + score = htons(score); + wpabuf_put_data(buf, &score, sizeof(score)); + association_msecs = htonl(association_msecs); + wpabuf_put_data(buf, &association_msecs, sizeof(association_msecs)); +} + +static void put_close_client(struct wpabuf* buf, const u8* sta, const u8* bssid, const u8* remote_bssid, u8 channel) +{ + static u8 close_len = ETH_ALEN + ETH_ALEN + ETH_ALEN + sizeof(channel); + + put_tlv_header(buf, TLV_CLOSE_CLIENT, close_len); + wpabuf_put_data(buf, sta, ETH_ALEN); + wpabuf_put_data(buf, bssid, ETH_ALEN); + wpabuf_put_data(buf, remote_bssid, ETH_ALEN); + wpabuf_put_u8(buf, channel); +} + +static void put_closed_client(struct wpabuf* buf, const u8* sta, const u8* bssid) +{ + static u8 close_len = ETH_ALEN + ETH_ALEN; + + put_tlv_header(buf, TLV_CLOSED_CLIENT, close_len); + wpabuf_put_data(buf, sta, ETH_ALEN); + wpabuf_put_data(buf, bssid, ETH_ALEN); +} + +static size_t parse_score(const u8* buf, size_t len, u8* sta, u8* bssid, u16* score, u32* association_msecs) +{ + static u8 score_len = ETH_ALEN + ETH_ALEN + sizeof(*score) + sizeof(*association_msecs); + const u8* tmp = buf; + + if (len < score_len) return 0; + + os_memcpy(sta, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(bssid, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(score, tmp, sizeof(*score)); + *score = ntohs(*score); + tmp += sizeof(*score); + os_memcpy(association_msecs, tmp, sizeof(*association_msecs)); + *association_msecs = ntohl(*association_msecs); + tmp += sizeof(*association_msecs); + return tmp - buf; +} + +static size_t parse_close_client(const u8* buf, size_t len, u8* sta, u8* bssid, u8* target_bssid, u8* channel) +{ + static u8 close_len = ETH_ALEN + ETH_ALEN + ETH_ALEN + sizeof(*channel); + const u8* tmp = buf; + + if (len < close_len) return 0; + + os_memcpy(sta, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(bssid, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(target_bssid, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(channel, tmp, sizeof(*channel)); + tmp += sizeof(*channel); + + return tmp - buf; +} + +static size_t parse_closed_client(const u8* buf, size_t len, u8* sta, u8* target_bssid) +{ + static u8 closed_len = ETH_ALEN + ETH_ALEN; + const u8* tmp = buf; + + if (len < closed_len) return 0; + + os_memcpy(sta, tmp, ETH_ALEN); + tmp += ETH_ALEN; + os_memcpy(target_bssid, tmp, ETH_ALEN); + tmp += ETH_ALEN; + + return tmp - buf; +} + + +int probe_req_cb(void *ctx, const u8 *sa, const u8 *da, const u8 *bssid, + const u8 *ie, size_t ie_len, int ssi_signal) +{ + /* unused */ + (void) da; + (void) ie; + (void) ie_len; + + struct net_steering_bss* nsb = ctx; + struct net_steering_client *client = NULL; + + assert(nsb->hapd); + /* look up the client in our list */ + client = client_find(nsb, sa); + + /* if we found the client, or this probe is directed at this bss */ + if (client || os_memcmp(nsb->hapd->conf->bssid, bssid, ETH_ALEN) == 0) { + if (!client) client = client_create(nsb, sa); + if (!client) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, + "Failed to create client for "MACSTR" on received probe\n", + MAC2STR(sa)); + return 0; + } + u16 score = compute_score(ssi_signal); + + if (score != client->score) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Probe request from "MACSTR" RSSI=%d\n", + MAC2STR(client_get_mac(client)), ssi_signal); + /* if client is associated, publish score changes immediately */ + client->score = score; + + /* don't flood until the score is updated */ + if (client_is_associated(client)) do_flood_score(client); + } + + if (!client_is_associated(client)) { + /* set our timer for the next probe */ + client_stop_probe_timer(client); + client_start_probe_timer(client); + } + } + return 0; +} + +static void header_put(struct wpabuf* buf, u16 sn) +{ + u16 len = 0; + wpabuf_put_u8(buf, tlv_magic); + wpabuf_put_u8(buf, tlv_version); + wpabuf_put_data(buf, &len, sizeof(len)); + sn = htons(sn); + wpabuf_put_data(buf, &sn, sizeof(sn)); +} + +// write the total length into the header +static void header_finalize(struct wpabuf* buf) +{ + u16* p = (u16*)(wpabuf_mhead_u8(buf) + (sizeof(tlv_magic) + sizeof(tlv_version))); + *p = htons(wpabuf_len(buf)); +} + +static void flood_message(struct net_steering_bss* nsb, const struct wpabuf* buf) +{ + struct ft_remote_r0kh *r0kh = nsb->hapd->conf->r0kh_list; + int ret; + + assert(nsb->hapd->own_addr); + assert(buf); + + // TODO use broadcast, but figure out how to handle multiple ess + while (r0kh) { + u8* dst = r0kh->addr; + // don't send to ourself + if (os_memcmp(dst, nsb->hapd->own_addr, ETH_ALEN) != 0) { + + ret = l2_packet_send(nsb->control, dst, proto, wpabuf_head(buf), wpabuf_len(buf)); + if (ret < 0) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed send to "MACSTR" : error %d\n", + MAC2STR(dst), ret); + } + } else { + /* + hostapd_logger(nsb->hapd, nsb->hapd->own_addr, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Don't send to myself "MACSTR"\n", + MAC2STR(nsb->hapd->own_addr)); + */ + } + r0kh = r0kh->next; + } +} + +static void flood_closed_client(struct net_steering_client *client) +{ + struct net_steering_bss* nsb = client->nsb; + struct wpabuf* buf; + + buf = wpabuf_alloc(MAX_FRAME_SIZE); + header_put(buf, nsb->frame_sn++); + put_closed_client(buf, client_get_mac(client), client_get_local_bssid(client)); + header_finalize(buf); + + hostapd_logger(nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "sending closed client "MACSTR" to "MACSTR"\n", + MAC2STR(client_get_mac(client)), MAC2STR(client_get_close_bssid(client))); + + flood_message(nsb, buf); + wpabuf_free(buf); + + client_clear_close_bssid(client); +} + +static void flood_close_client(struct net_steering_client *client) +{ + struct net_steering_bss* nsb = client->nsb; + struct wpabuf* buf; + + buf = wpabuf_alloc(MAX_FRAME_SIZE); + header_put(buf, nsb->frame_sn++); + put_close_client(buf, client_get_mac(client), client_get_local_bssid(client), + client_get_remote_bssid(client), client->nsb->hapd->iconf->channel); + header_finalize(buf); + + hostapd_logger(nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "sending close client "MACSTR" for "MACSTR"\n", + MAC2STR(client_get_mac(client)), + MAC2STR(client_get_remote_bssid(client))); + + flood_message(nsb, buf); + wpabuf_free(buf); +} + +static void do_flood_score(struct net_steering_client *client) +{ + struct net_steering_bss* nsb = client->nsb; + struct wpabuf* buf; + + if (client->score == max_score) { + hostapd_logger(nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "skip flooding "MACSTR" max score %d\n", + MAC2STR(client_get_mac(client)), client->score); + } else { + struct os_time now_t; + struct os_time delta_t; + os_memset(&delta_t, 0, sizeof(delta_t)); + + os_get_time(&now_t); + os_time_sub(&now_t, &client->association_time, &delta_t); + // TODO need to deal with wraparound, currently about 49 days + u32 associated_msecs = (delta_t.sec * 1000) + (delta_t.usec / 1000); + + hostapd_logger(nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "sending "MACSTR" score %d associated %lu\n", + MAC2STR(client_get_mac(client)), (int)client->score, (unsigned long)associated_msecs); + + buf = wpabuf_alloc(MAX_FRAME_SIZE); + header_put(buf, nsb->frame_sn++); + put_score(buf, client_get_mac(client), client_get_local_bssid(client), client->score, associated_msecs); + header_finalize(buf); + + flood_message(nsb, buf); + wpabuf_free(buf); + } +} + +static void flood_score(void *eloop_data, void *user_ctx) +{ + (void) user_ctx; + struct net_steering_client* client = (struct net_steering_client*) eloop_data; + do_flood_score(client); + start_flood_timer(client); +} + +static void do_client_disassociate(struct net_steering_client* client) +{ + static const int transition_timeout = 0; + + char mac[MACSTRLEN]; + if (!snprintf(mac, MACSTRLEN, MACSTR, MAC2STR(client_get_mac(client)))) return; + + if (client_is_associated(client)) + { + if (client->nsb->mode == MODE_SUGGEST || client_supports_bss_transition(client)) { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, "Fast BSS transition for "MACSTR" to "MACSTR" on channel %d\n", + MAC2STR(client_get_mac(client)), MAC2STR(client_get_close_bssid(client)), client->remote_channel); + + wnm_send_bss_tm_req2(client->nsb->hapd, client->sta, transition_timeout, + client_get_close_bssid(client), client->remote_channel); + } else { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, "Disassociate "MACSTR"\n", MAC2STR(client_get_mac(client))); + + if (hostapd_ctrl_iface_disassociate(client->nsb->hapd, mac)) + { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed to disassociate %s\n", mac); + } + } + } else { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Cannot disassociate "MACSTR", not associated\n", + MAC2STR(client_get_mac(client))); + } +} + +static void do_client_blacklist_add(struct net_steering_client* client) +{ + if (client->nsb->mode == MODE_FORCE) { + char mac[MACSTRLEN]; + if (!snprintf(mac, MACSTRLEN, MACSTR, MAC2STR(client_get_mac(client)))) return; + + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Blacklist add "MACSTR"\n", + MAC2STR(client_get_mac(client))); + + if (hostapd_ctrl_iface_blacklist_add(client->nsb->hapd, mac)) { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed to blacklist %s\n", mac); + } + } +} + +static void do_client_blacklist_rm(struct net_steering_client* client) +{ + if (client->nsb->mode == MODE_FORCE) { + char mac[MACSTRLEN]; + if (!snprintf(mac, MACSTRLEN, MACSTR, MAC2STR(client_get_mac(client)))) return; + + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Blacklist remove "MACSTR"\n", MAC2STR(client_get_mac(client))); + + if (hostapd_ctrl_iface_blacklist_rm(client->nsb->hapd, mac)) + { + hostapd_logger(client->nsb->hapd, client_get_local_bssid(client), HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed to remove %s from blacklist\n", mac); + } + } +} + +#define STATE_MACHINE_DATA struct net_steering_client +#define STATE_MACHINE_DEBUG_PREFIX "STEERING" +#define STATE_MACHINE_ADDR sm->addr + +#define SM_EVENT(machine, fromstate, e_event, tostate) \ +static void sm_ ## machine ## _ ## fromstate ## _on_ ## e_event ## _ ## tostate(STATE_MACHINE_DATA *sm, \ + int global) + +/* TODO maybe use a switch, but that would take a more complex set of macros */ +#define SM_TRANSITION(machine, fromstate, e_event, tostate) \ + if (sm->machine ## _state == machine ## _ ## fromstate && event == machine ## _ ## e_event) { \ + hostapd_logger(sm->nsb->hapd, sm->nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, \ + HOSTAPD_LEVEL_DEBUG, "%s => %s "MACSTR" %s\n",\ + state_to_str(sm->machine ## _state), state_to_str(machine ## _ ## tostate), \ + MAC2STR(client_get_mac(sm)), event_to_str(machine ## _ ## e_event)); \ + sm_ ## machine ## _ ## fromstate ## _on ## _ ## e_event ## _ ## tostate(sm, 0); \ + SM_ENTER(STEERING, tostate); \ + return; \ + } + +#define SM_TRANS_NOOP(machine, fromstate, e_event, tostate) \ + if (sm->machine ## _state == machine ## _ ## fromstate && event == machine ## _ ## e_event) { \ + hostapd_logger(sm->nsb->hapd, sm->nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, \ + HOSTAPD_LEVEL_DEBUG, "%s => %s "MACSTR" %s noop\n",\ + state_to_str(sm->machine ## _state), state_to_str(machine ## _ ## tostate), \ + MAC2STR(client_get_mac(sm)), event_to_str(machine ## _ ## e_event)); \ + SM_ENTER(STEERING, tostate); \ + return; \ + } + +#define SM_STEP_EVENT(machine) \ +static void sm_ ## machine ## _do_Event(STATE_MACHINE_DATA *sm, int event) + +#define SM_STEP_EVENT_RUN(machine, event, sm) \ + sm_ ## machine ## _do_Event(sm, machine ## _ ## event); + +SM_STATE(STEERING, IDLE) { SM_ENTRY(STEERING, IDLE); } +SM_STATE(STEERING, CONFIRMING) { SM_ENTRY(STEERING, CONFIRMING); } +SM_STATE(STEERING, ASSOCIATING) { SM_ENTRY(STEERING, ASSOCIATING); } +SM_STATE(STEERING, ASSOCIATED) { SM_ENTRY(STEERING, ASSOCIATED); } +SM_STATE(STEERING, REJECTING) { SM_ENTRY(STEERING, REJECTING); } +SM_STATE(STEERING, REJECTED) { SM_ENTRY(STEERING, REJECTED); } + +SM_EVENT(STEERING, IDLE, E_ASSOCIATED, ASSOCIATED) +{ + start_flood_timer(sm); +} + +SM_EVENT(STEERING, IDLE, E_PEER_IS_WORSE, CONFIRMING) +{ + flood_close_client(sm); +} + +SM_EVENT(STEERING, IDLE, E_PEER_NOT_WORSE, REJECTED) +{ + do_client_blacklist_add(sm); + client_start_timer(sm); +} + +SM_EVENT(STEERING, IDLE, E_CLOSE_CLIENT, REJECTED) +{ + flood_close_client(sm); + do_client_blacklist_add(sm); + client_start_timer(sm); +} + +SM_EVENT(STEERING, CONFIRMING, E_PEER_IS_WORSE, CONFIRMING) +{ + flood_close_client(sm); +} + +SM_EVENT(STEERING, CONFIRMING, E_ASSOCIATED, ASSOCIATED) +{ + start_flood_timer(sm); +} + +SM_EVENT(STEERING, ASSOCIATING, E_ASSOCIATED, ASSOCIATED) +{ + start_flood_timer(sm); +} + +SM_EVENT(STEERING, ASSOCIATING, E_PEER_IS_WORSE, ASSOCIATING) +{ + flood_close_client(sm); +} + +SM_EVENT(STEERING, ASSOCIATING, E_CLOSE_CLIENT, REJECTED) +{ + flood_closed_client(sm); + do_client_blacklist_add(sm); + client_start_timer(sm); +} + + +SM_EVENT(STEERING, ASSOCIATED, E_CLOSE_CLIENT, REJECTING) +{ + do_client_blacklist_add(sm); + do_client_disassociate(sm); + client_start_timer(sm); + stop_flood_timer(sm); +} + +SM_EVENT(STEERING, ASSOCIATED, E_DISASSOCIATED, IDLE) +{ + stop_flood_timer(sm); +} + +SM_EVENT(STEERING, ASSOCIATED, E_PEER_IS_WORSE, ASSOCIATED) +{ + flood_close_client(sm); +} + +SM_EVENT(STEERING, REJECTING, E_DISASSOCIATED, REJECTED) +{ + flood_closed_client(sm); + client_stop_timer(sm); /* exiting REJECTING */ + client_start_timer(sm); /* enter REJECTED */ +} + +SM_EVENT(STEERING, REJECTING, E_PEER_IS_WORSE, CONFIRMING) +{ + do_client_blacklist_rm(sm); + flood_close_client(sm); + client_stop_timer(sm); +} + +SM_EVENT(STEERING, REJECTING, E_PEER_LOST_CLIENT, CONFIRMING) +{ + do_client_blacklist_rm(sm); + client_stop_timer(sm); +} + +SM_EVENT(STEERING, REJECTING, E_TIMEOUT, ASSOCIATING) +{ + do_client_blacklist_rm(sm); + client_stop_timer(sm); +} + +SM_EVENT(STEERING, REJECTED, E_PEER_IS_WORSE, CONFIRMING) +{ + do_client_blacklist_rm(sm); + flood_close_client(sm); + client_stop_timer(sm); +} + +SM_EVENT(STEERING, REJECTED, E_PEER_LOST_CLIENT, CONFIRMING) +{ + do_client_blacklist_rm(sm); + flood_close_client(sm); + client_stop_timer(sm); +} + +SM_EVENT(STEERING, REJECTED, E_CLOSE_CLIENT, REJECTED) +{ + flood_close_client(sm); +} + +SM_EVENT(STEERING, REJECTED, E_TIMEOUT, ASSOCIATING) +{ + do_client_blacklist_rm(sm); + client_stop_timer(sm); +} + + +/* + +From original Alloy specification +Old State Event New State +-------------------------------------------------- +Idle, Associated, Associated +Idle, PeerIsWorse, Confirming +Idle, PeerNotWorse, Rejected +Idle, PeerLostClient, Associating +Idle, CloseClient, Rejected + +Confirming, ClosedClient, Associating +Confirming, Associated, Associated +Confirming, TimeOut, Idle +Confirming, PeerIsWorse, Confirming +Confirming, PeerNotWorse, Rejected + +Associating, Associated, Associated +Associating, Disassociated, Idle +Associating, PeerIsWorse, Associating +Associating, CloseClient, Rejected + +Associated, CloseClient, Rejecting +Associated, Disassociated, Idle +Associated, PeerIsWorse, Associated +Associated, Timer, Associated + +Rejecting, CloseClient, Rejecting +Rejecting, Disassociated, Rejected +Rejecting, PeerIsWorse, Confirming +Rejecting, PeerLostClient, Confirming +Rejecting, TimeOut, Associating + +Rejected, PeerIsWorse, Confirming +Rejected, PeerLostClient, Confirming +Rejected, CloseClient, Rejected +Rejected, TimeOut, Associating + + -- NOTEs: + -- 1) The client is only blacklisted in Rejecting and Rejected. + -- 2) The Associated timer should fire immediately after transitioning to associated and then on an interval. + -- 3) The TimeOutEvent in Rejecting+Rejected fires if we haven't gotten a score recently. + -- 4) Events that don't match the above should be no-ops. Note that this does happen, for example if a MAP is in + -- Confirming and gets PeerIsWorsePacket it will send out CloseClientPacket which means it will get two + -- ClosedClientPacket, one of which should be ignored. + -- 5) The fsm should pop into existence when a MAP links up with the client and go away after being inactive. +*/ + +/* +Idle +{ + associated Associated {} + peer_is_worse Confirming {unicast_close_client();} + peer_not_worse Rejected {blacklist();} + peer_lost_client Associating {} + close_client Rejected {unicast_closed_client(); blacklist();} + Default Idle {} +} + +Confirming +{ + closed_client Associating {} + associated Associated {} + time_out Idle {} + peer_is_worse Confirming {unicast_close_client();} + peer_not_worse Rejected {blacklist();} + Default Confirming {} +} + +Associating +{ + associated Associated {} + disassociated Idle {} + peer_is_worse Associating {unicast_close_client();} + close_client Rejected {unicast_closed_client(); blacklist();} + Default Associating {} +} + +Associated +Entry {fast_flooding(); flood_score();} +Exit {slow_flooding();} +{ + close_client Rejecting {blacklist(); disassociate(); clear_remotes();} + disassociated Idle {flood_peer_lost_client();} + peer_is_worse Associated {unicast_close_client();} + Default Associated {} +} + +Rejecting +Entry {start_timeout();} +Exit {stop_timeout();} +{ + close_client Rejecting {} + disassociated Rejected {unicast_closed_client();} + peer_is_worse Confirming {unicast_close_client(); unblacklist();} + peer_lost_client Confirming {unblacklist();} + time_out Associating {unblacklist();} + Default Rejecting {} +} + +Rejected +Entry {start_timeout();} +Exit {stop_timeout();} +{ + peer_is_worse Confirming {unicast_close_client(); unblacklist();} + peer_lost_client Confirming {unblacklist();} + close_client Rejected {unicast_closed_client();} + time_out Associating {unblacklist();} + Default Rejected {} +} +*/ + +SM_STEP_EVENT(STEERING) +{ + /* Define transitions for every event that has an action to perform */ + /* Use no-ops for cases where only a state change is required */ + /* Beware of states that have entry/exit actions defined */ + SM_TRANSITION(STEERING, IDLE, E_ASSOCIATED, ASSOCIATED); + SM_TRANSITION(STEERING, IDLE, E_PEER_IS_WORSE, CONFIRMING); + SM_TRANSITION(STEERING, IDLE, E_PEER_NOT_WORSE, REJECTED); + SM_TRANS_NOOP(STEERING, IDLE, E_PEER_LOST_CLIENT, ASSOCIATING); + SM_TRANSITION(STEERING, IDLE, E_CLOSE_CLIENT, REJECTED); + + SM_TRANS_NOOP(STEERING, CONFIRMING, E_CLOSED_CLIENT, ASSOCIATING); + SM_TRANSITION(STEERING, CONFIRMING, E_ASSOCIATED, ASSOCIATED); + SM_TRANS_NOOP(STEERING, CONFIRMING, E_TIMEOUT, IDLE); + SM_TRANSITION(STEERING, CONFIRMING, E_PEER_IS_WORSE, CONFIRMING); + // This transition is invalid because if we are confirming, we have closed the client + // and therefore we don't want to blacklist via rejected, so ignore this event + //SM_TRANSITION(STEERING, CONFIRMING, E_PEER_NOT_WORSE, REJECTED); + + SM_TRANSITION(STEERING, ASSOCIATING, E_ASSOCIATED, ASSOCIATED); + SM_TRANS_NOOP(STEERING, ASSOCIATING, E_DISASSOCIATED, IDLE); + SM_TRANSITION(STEERING, ASSOCIATING, E_PEER_IS_WORSE, ASSOCIATING); + SM_TRANSITION(STEERING, ASSOCIATING, E_CLOSE_CLIENT, REJECTED); + + SM_TRANSITION(STEERING, ASSOCIATED, E_CLOSE_CLIENT, REJECTING); + SM_TRANSITION(STEERING, ASSOCIATED, E_DISASSOCIATED, IDLE); + SM_TRANSITION(STEERING, ASSOCIATED, E_PEER_IS_WORSE, ASSOCIATED); + + SM_TRANSITION(STEERING, REJECTING, E_DISASSOCIATED, REJECTED); + SM_TRANSITION(STEERING, REJECTING, E_PEER_IS_WORSE, CONFIRMING); + SM_TRANSITION(STEERING, REJECTING, E_PEER_LOST_CLIENT, CONFIRMING); + SM_TRANSITION(STEERING, REJECTING, E_TIMEOUT, ASSOCIATING); + + SM_TRANSITION(STEERING, REJECTED, E_PEER_IS_WORSE, CONFIRMING); + SM_TRANSITION(STEERING, REJECTED, E_PEER_LOST_CLIENT, CONFIRMING); + SM_TRANSITION(STEERING, REJECTED, E_CLOSE_CLIENT, REJECTED); + SM_TRANSITION(STEERING, REJECTED, E_TIMEOUT, ASSOCIATING); + + /* By design, the default response is no state change */ + hostapd_logger(sm->nsb->hapd, sm->nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG_VERBOSE, + "Client "MACSTR" default handler for %s - %s\n", + MAC2STR(sm->addr), state_to_str(sm->STEERING_state), event_to_str(event)); +} + +static void client_timeout(void *eloop_data, void *user_ctx) +{ + (void) user_ctx; + struct net_steering_client* client = (struct net_steering_client*) eloop_data; + + SM_STEP_EVENT_RUN(STEERING, E_TIMEOUT, client); +} + +static void probe_timeout(void *eloop_data, void *user_ctx) +{ + (void) user_ctx; + struct net_steering_client* client = (struct net_steering_client*) eloop_data; + + hostapd_logger(client->nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, + "Probe timeout for client "MACSTR" score=%d\n", + MAC2STR(client_get_mac(client)), client->score); + + client->score = max_score; +} +static void compare_scores(struct net_steering_client *client, u16 score) +{ + /* now consider the score if it is from the true owner of the client */ + if (client->score < score) { + SM_STEP_EVENT_RUN(STEERING, E_PEER_IS_WORSE, client); + } else { + SM_STEP_EVENT_RUN(STEERING, E_PEER_NOT_WORSE, client); + } +} + +static void receive_score(struct net_steering_bss* nsb, const u8* sta, const u8* bssid, + u16 score, u32 association_msecs) +{ + struct net_steering_client *client = NULL; + + client = client_find(nsb, sta); + if (!client) { + client = client_create(nsb, sta); + if (!client) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, + "Failed to create client for "MACSTR"\n", + MAC2STR(sta)); + return; + } + } + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, MACSTR" sent score for "MACSTR" %d %lu local %d\n", + MAC2STR(bssid), MAC2STR(client_get_mac(client)), score, (unsigned long) association_msecs, client->score); + + /* we only care about scores when the client is not associated */ + if (client_is_associated(client)) return; + + /* if we receive a score from a new AP for this client */ + /* Establish if the AP has newer information than the current one */ + if (os_memcmp(bssid, client_get_remote_bssid(client), ETH_ALEN) != 0) { + struct os_time now_t; + struct os_time association_t; + struct os_time local_t; + + os_memset(&now_t, 0, sizeof(now_t)); + os_memset(&association_t, 0, sizeof(association_t)); + os_memset(&local_t, 0, sizeof(local_t)); + + os_get_time(&now_t); + + association_t.sec = association_msecs / 1000; + association_t.usec = (association_msecs % 1000) * 1000; + + /* + * Compute a local time that is corrected with associated time from remote. + * This allows us to determine the remote AP with the most recent information + * regarding the client, and consequently which AP's scores should be evaluated. + */ + os_time_sub(&now_t, &association_t, &local_t); + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, MACSTR" current %ld %ld received %ld %ld\n", + MAC2STR(bssid), client->remote_time.sec, client->remote_time.usec, local_t.sec, local_t.usec); + + /* should we switch which AP is believed to be associated with the client? */ + /* only if last remote time is before local time (giving us newer info) */ + if (os_time_before(&client->remote_time, &local_t)) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, MACSTR" is associated with client "MACSTR"\n", + MAC2STR(bssid), MAC2STR(client_get_mac(client))); + + client->remote_time.sec = local_t.sec; + client->remote_time.usec = local_t.usec; + client_set_remote_bssid(client, bssid); + compare_scores(client, score); + } + } else { + /* if this score is from the same AP, then check it */ + compare_scores(client, score); + } +} + +static void receive_close_client(struct net_steering_bss* nsb, const u8* sta, + const u8* bssid, const u8* target_bssid, u8 ap_channel) +{ + struct net_steering_client *client = NULL; + + if (os_memcmp(nsb->hapd->conf->bssid, target_bssid, ETH_ALEN) == 0) { + + client = client_find(nsb, sta); + if (!client) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, + "Close client can't find client "MACSTR"\n", + MAC2STR(sta)); + return; + } + + client->remote_channel = ap_channel; + client_set_close_bssid(client, bssid); + + SM_STEP_EVENT_RUN(STEERING, E_CLOSE_CLIENT, client); + } +} + +static void receive_closed_client(struct net_steering_bss* nsb, const u8* sta, const u8* target_bssid) +{ + struct net_steering_client *client = NULL; + + if (os_memcmp(nsb->hapd->conf->bssid, target_bssid, ETH_ALEN) == 0) { + + client = client_find(nsb, sta); + if (!client) { + return; + } + + SM_STEP_EVENT_RUN(STEERING, E_CLOSED_CLIENT, client); + } +} + +static void receive(void *ctx, const u8 *src_addr, const u8 *buf, size_t len) +{ + struct net_steering_bss* nsb = ctx; + u16 sn = 0; + u8 magic, version, type_tlv, tlv_len = 0; + u16 packet_len = 0; + u16 score = 0; + u32 association_msecs = 0; + u8 sta[ETH_ALEN]; + u8 bssid[ETH_ALEN]; + u8 target_bssid[ETH_ALEN]; + u8 ap_channel = 0; + size_t num_read = 0; + const u8* buf_pos = buf; + + num_read = parse_header(buf_pos, len, &magic, &version, &packet_len, &sn); + if (!num_read) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, + "Dropping short message from "MACSTR": %d bytes\n", + MAC2STR(src_addr), len); + return; + } + + if (len < packet_len) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, + "Dropping short message from "MACSTR": recv %d bytes, expected %d\n", + MAC2STR(src_addr), len, packet_len); + return; + } + + if (tlv_version != version || tlv_magic != magic) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, + "Dropping invalid message from "MACSTR": magic %d version %d\n", + MAC2STR(src_addr), magic, version); + return; + } + buf_pos += num_read; + + while (buf_pos < buf + packet_len) { + os_memset(sta, 0, ETH_ALEN); + os_memset(bssid, 0, ETH_ALEN); + os_memset(target_bssid, 0, ETH_ALEN); + + num_read = parse_tlv_header(buf_pos, packet_len-(buf_pos-buf), &type_tlv, &tlv_len); + if (!num_read) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Could not parse tlv header from "MACSTR"\n", + MAC2STR(src_addr)); + return; + } + buf_pos += num_read; + + switch (type_tlv) + { + case TLV_SCORE: + num_read = parse_score(buf_pos, tlv_len, sta, bssid, &score, &association_msecs); + if (!num_read) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Could not parse score from "MACSTR"\n", + MAC2STR(src_addr)); + return; + } + buf_pos += num_read; + + receive_score(nsb, sta, bssid, score, association_msecs); + break; + case TLV_CLOSE_CLIENT: + num_read = parse_close_client(buf_pos, tlv_len, sta, bssid, target_bssid, &ap_channel); + if (!num_read) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Could not parse close client from "MACSTR"\n", + MAC2STR(src_addr)); + return; + } + buf_pos += num_read; + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, MACSTR" says "MACSTR" should close client "MACSTR"\n", + MAC2STR(bssid), MAC2STR(target_bssid), MAC2STR(sta)); + + receive_close_client(nsb, sta, bssid, target_bssid, ap_channel); + break; + case TLV_CLOSED_CLIENT: + num_read = parse_closed_client(buf_pos, tlv_len, sta, target_bssid); + if (!num_read) { + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Could not parse closed client from "MACSTR"\n", + MAC2STR(src_addr)); + return; + } + buf_pos += num_read; + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, MACSTR" closed client "MACSTR"\n", + MAC2STR(target_bssid), MAC2STR(sta)); + + receive_closed_client(nsb, sta, target_bssid); + break; + default: + // skip unknown tlvs + buf_pos += tlv_len; + hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Dropping unknown tlv type %d len %d from "MACSTR" : %d\n", + type_tlv, tlv_len, MAC2STR(src_addr), ntohs(sn)); + break; + } + } + + //hostapd_logger(nsb->hapd, NULL, HOSTAPD_MODULE_NET_STEERING, + // HOSTAPD_LEVEL_DEBUG, "Received %d bytes from "MACSTR" : %d\n", + // len, MAC2STR(src_addr), ntohs(sn)); +} + + +void net_steering_disassociation(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct net_steering_bss* nsb = NULL; + struct net_steering_client *client, *ctmp; + + if (dl_list_empty(&nsb_list)) return; + + // find the context + dl_list_for_each(nsb, &nsb_list, struct net_steering_bss, list) { + if (nsb->hapd == hapd) break; + } + + if (!nsb) { + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Association to unknown bss "MACSTR"\n", + MAC2STR(hapd->conf->bssid)); + return; + } + + // find the client and clean it up + dl_list_for_each_safe(client, ctmp, &nsb->clients, struct net_steering_client, list) { + if (os_memcmp(client_get_mac(client), sta->addr, ETH_ALEN) == 0) { + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, MACSTR" disassociated from "MACSTR" remote is "MACSTR"\n", + MAC2STR(sta->addr), MAC2STR(nsb->hapd->conf->bssid), + MAC2STR(client_get_close_bssid(client))); + + SM_STEP_EVENT_RUN(STEERING, E_DISASSOCIATED, client); + // DO NOT clean up until after the disassociate event has been processed + client_disassociate(client); + break; + } + } +} + +void net_steering_association(struct hostapd_data *hapd, struct sta_info *sta, int rssi) +{ + struct net_steering_bss* nsb = NULL; + struct net_steering_client* client = NULL; + + if (dl_list_empty(&nsb_list)) return; + + dl_list_for_each(nsb, &nsb_list, struct net_steering_bss, list) { + if (nsb->hapd == hapd) break; + } + + if (!nsb) { + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Association to unknown bss "MACSTR"\n", + MAC2STR(hapd->conf->bssid)); + return; + } + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, MACSTR" associated to "MACSTR" signal=%d\n", + MAC2STR(sta->addr), MAC2STR(nsb->hapd->conf->bssid), rssi); + + if (sta->dot11MgmtOptionBSSTransitionActivated) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_DEBUG, "Client "MACSTR" supports Fast BSS transition\n", + MAC2STR(sta->addr)); + } + + client = client_find(nsb, sta->addr); + if (!client) + { + client = client_create(nsb, sta->addr); + if (!client) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "Failed to create client "MACSTR" on bssid "MACSTR"\n", + MAC2STR(sta->addr), MAC2STR(hapd->conf->bssid)); + + return; + } + } + + os_memset(&client->remote_time, 0, sizeof(client->remote_time)); + os_memset(&client->remote_bssid, 0, ETH_ALEN); + + os_get_time(&client->association_time); + client->score = compute_score(rssi); + client_associate(client, sta); + do_flood_score(client); + SM_STEP_EVENT_RUN(STEERING, E_ASSOCIATED, client); +} + +void net_steering_deinit(struct hostapd_data *hapd) +{ + struct net_steering_bss *nsb, *tmp; + + if (dl_list_empty(&nsb_list)) return; + + dl_list_for_each_safe(nsb, tmp, &nsb_list, struct net_steering_bss, list) { + if (nsb->hapd == hapd) { + struct net_steering_client *client, *ctmp; + if (nsb->control != NULL) { + l2_packet_deinit(nsb->control); + wpa_printf(MSG_DEBUG, "net_steering_deinit - l2_packet_deinit"); + } + + // free all clients + dl_list_for_each_safe(client, ctmp, &nsb->clients, struct net_steering_client, list) { + client_delete(client); + } + + dl_list_del(&nsb->list); + os_memset(nsb, 0, sizeof(*nsb)); + os_free(nsb); + break; + } + } +} + +int net_steering_init(struct hostapd_data *hapd) +{ + struct net_steering_bss* nsb = NULL; + + /* see if there is any configuration */ + if (!hapd->conf->net_steeering_mode) { + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "no configuration, steering disabled.\n"); + return 0; + } + /* if configuration is off, do nothing */ + if (os_strcmp(hapd->conf->net_steeering_mode, mode_off) == 0) { + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "configured off, steering disabled.\n"); + return 0; + } + +#ifdef CONFIG_IEEE80211R + // We piggy-back on fast transition configuration, and use that config to identify our peer APs + if (!hapd->conf->r0kh_list) { + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "No FT key holders configured, steering disabled.\n"); + return 0; + } +#else + hostapd_logger(hapd, hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "FT feature not included in this build, steering disabled.\n"); + /* Signaling an error since user enabled steering, but R is not included */ + return -1; +#endif + + nsb = (struct net_steering_bss*) os_zalloc(sizeof(*nsb)); + + if (!nsb) return -1; + + // TODO: what if there is no bridge in use? use iface? + nsb->control = l2_packet_init(hapd->conf->bridge, NULL, proto, receive, nsb, 0); + if (nsb->control == NULL) { + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_WARNING, "net_steering_init - l2_packet_init failed for %s\n", + hapd->conf->bridge); + + os_memset(nsb, 0, sizeof(*nsb)); + os_free(nsb); + return -1; + } + + nsb->hapd = hapd; + dl_list_init(&nsb->clients); + + // add the context to the end of the list + dl_list_add(&nsb_list, &nsb->list); + hostapd_register_probereq_cb(hapd, probe_req_cb, nsb); + + hostapd_logger(nsb->hapd, nsb->hapd->conf->bssid, HOSTAPD_MODULE_NET_STEERING, + HOSTAPD_LEVEL_INFO, "ready on %s, own addr "MACSTR": mode: %s\n", + hapd->conf->bridge, MAC2STR(nsb->hapd->own_addr), + hapd->conf->net_steeering_mode); + + if (os_strcmp(hapd->conf->net_steeering_mode, mode_suggest) == 0) nsb->mode = MODE_SUGGEST; + else if (os_strcmp(hapd->conf->net_steeering_mode, mode_force) == 0) nsb->mode = MODE_FORCE; + else nsb->mode = MODE_FORCE; + + return 0; +} + diff --git a/src/ap/net_steering.h b/src/ap/net_steering.h new file mode 100644 index 0000000..4274528 --- /dev/null +++ b/src/ap/net_steering.h @@ -0,0 +1,13 @@ +#ifndef NETSTEERING_H +#define NETSTEERING_H + +struct sta_info; +struct hostapd_data; + +int net_steering_init(struct hostapd_data *hapd); +void net_steering_deinit(struct hostapd_data *hapd); +void net_steering_association(struct hostapd_data *hapd, struct sta_info *sta, int rssi); +void net_steering_disassociation(struct hostapd_data *hapd, struct sta_info *sta); + + +#endif diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index c36842b..b2ad573 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -34,6 +34,7 @@ #include "wnm_ap.h" #include "mbo_ap.h" #include "ndisc_snoop.h" +#include "net_steering.h" #include "sta_info.h" #include "vlan.h" @@ -297,6 +298,10 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) p2p_group_notif_disassoc(hapd->p2p_group, sta->addr); #endif /* CONFIG_P2P */ +#ifdef CONFIG_NET_STEERING + net_steering_disassociation(hapd, sta); +#endif /* CONFIG_NET_STEERING */ + #ifdef CONFIG_INTERWORKING if (sta->gas_dialog) { int i; diff --git a/src/utils/wpa_debug.h b/src/utils/wpa_debug.h index 17d8f96..480e11b 100644 --- a/src/utils/wpa_debug.h +++ b/src/utils/wpa_debug.h @@ -300,10 +300,13 @@ void hostapd_logger_register_cb(hostapd_logger_cb_func func); #define HOSTAPD_MODULE_IEEE80211 0x00000001 #define HOSTAPD_MODULE_IEEE8021X 0x00000002 #define HOSTAPD_MODULE_RADIUS 0x00000004 -#define HOSTAPD_MODULE_WPA 0x00000008 +#define HOSTAPD_MODULE_WPA 0x00000008 #define HOSTAPD_MODULE_DRIVER 0x00000010 -#define HOSTAPD_MODULE_IAPP 0x00000020 -#define HOSTAPD_MODULE_MLME 0x00000040 +#define HOSTAPD_MODULE_IAPP 0x00000020 +#define HOSTAPD_MODULE_MLME 0x00000040 +#ifdef CONFIG_NET_STEERING +#define HOSTAPD_MODULE_NET_STEERING 0x00000080 +#endif enum hostapd_logger_level { HOSTAPD_LEVEL_DEBUG_VERBOSE = 0, -- 1.9.1