diff --git a/src/Makefile b/src/Makefile index 10e0171b0..c9e84c11d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,5 @@ SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet p2p pae radius rsn_supp tls utils wps +SUBDIRS += fst all: for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d; done diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index cc099eb5a..f33f91472 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -1396,6 +1396,8 @@ enum mb_ctrl_sta_role { MB_STA_ROLE_NON_PCP_NON_AP = 4 }; +#define MB_CTRL_ROLE_MASK (BIT(0) | BIT(1) | BIT(2)) +#define MB_CTRL_ROLE(ctrl) ((u8) ((ctrl) & MB_CTRL_ROLE_MASK)) #define MB_CTRL_STA_MAC_PRESENT ((u8) (BIT(3))) #define MB_CTRL_PAIRWISE_CIPHER_SUITE_PRESENT ((u8) (BIT(4))) diff --git a/src/fst/Makefile b/src/fst/Makefile new file mode 100644 index 000000000..9c41962fd --- /dev/null +++ b/src/fst/Makefile @@ -0,0 +1,8 @@ +all: + @echo Nothing to be made. + +clean: + rm -f *~ *.o *.d + +install: + @echo Nothing to be made. diff --git a/src/fst/fst.c b/src/fst/fst.c new file mode 100644 index 000000000..b70ef361a --- /dev/null +++ b/src/fst/fst.c @@ -0,0 +1,227 @@ +/* + * FST module implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "fst/fst.h" +#include "fst/fst_internal.h" +#include "fst/fst_defs.h" +#include "fst/fst_ctrl_iface.h" + +struct dl_list fst_global_ctrls_list; + + +static void fst_ctrl_iface_notify_peer_state_change(struct fst_iface *iface, + Boolean connected, + const u8 *peer_addr) +{ + union fst_event_extra extra; + + extra.peer_state.connected = connected; + os_strlcpy(extra.peer_state.ifname, fst_iface_get_name(iface), + sizeof(extra.peer_state.ifname)); + os_memcpy(extra.peer_state.addr, peer_addr, ETH_ALEN); + + foreach_fst_ctrl_call(on_event, EVENT_PEER_STATE_CHANGED, + iface, NULL, &extra); +} + + +static struct fst_group * fst_find_group_by_iface(const char *ifname) +{ + struct fst_group *g; + + foreach_fst_group(g) { + if (fst_group_get_iface_by_name(g, ifname)) + return g; + } + + return NULL; +} + + +struct fst_iface * fst_attach(const char *ifname, const u8 *own_addr, + const struct fst_wpa_obj *iface_obj, + const struct fst_iface_cfg *cfg) +{ + struct fst_group *g; + struct fst_group *group = NULL; + struct fst_iface *iface = NULL; + Boolean new_group = FALSE; + + WPA_ASSERT(ifname != NULL); + WPA_ASSERT(iface_obj != NULL); + WPA_ASSERT(cfg != NULL); + + g = fst_find_group_by_iface(ifname); + if (g) { + fst_printf(MSG_ERROR, + "%s: iface is already part of an FST group: %s", + ifname, g->group_id); + return NULL; + } + + foreach_fst_group(g) { + if (os_strcmp(cfg->group_id, fst_group_get_id(g)) == 0) { + group = g; + break; + } + } + + if (!group) { + group = fst_group_create(cfg->group_id); + if (!group) { + fst_printf(MSG_ERROR, "%s: FST group cannot be created", + cfg->group_id); + return NULL; + } + new_group = TRUE; + } + + iface = fst_iface_create(group, ifname, own_addr, iface_obj, cfg); + if (!iface) { + fst_printf_group(group, MSG_ERROR, "cannot create iface for %s", + ifname); + if (new_group) + fst_group_delete(group); + return NULL; + } + + fst_group_attach_iface(group, iface); + fst_group_update_ie(group, FALSE); + + foreach_fst_ctrl_call(on_iface_added, iface); + + fst_printf_iface(iface, MSG_DEBUG, + "iface attached to group %s (prio=%d, llt=%d)", + cfg->group_id, cfg->priority, cfg->llt); + + return iface; +} + + +void fst_detach(struct fst_iface *iface) +{ + struct fst_group *group = fst_iface_get_group(iface); + + fst_printf_iface(iface, MSG_DEBUG, "iface detached from group %s", + fst_group_get_id(group)); + fst_session_global_on_iface_detached(iface); + foreach_fst_ctrl_call(on_iface_removed, iface); + fst_group_detach_iface(group, iface); + fst_iface_delete(iface); + fst_group_update_ie(group, FALSE); + fst_group_delete_if_empty(group); +} + + +int fst_global_init(void) +{ + dl_list_init(&fst_global_groups_list); + dl_list_init(&fst_global_ctrls_list); + fst_session_global_init(); + return 0; +} + + +void fst_global_deinit(void) +{ + struct fst_group *group; + + fst_session_global_deinit(); + while ((group = fst_first_group()) != NULL) + fst_group_delete(group); + while (!dl_list_empty(&fst_global_ctrls_list)) { + struct fst_ctrl_handle *h; + + h = dl_list_first(&fst_global_ctrls_list, + struct fst_ctrl_handle, global_ctrls_lentry); + fst_global_del_ctrl(h); + } +} + + +struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl) +{ + struct fst_ctrl_handle *h; + + if (!ctrl) + return NULL; + + h = os_zalloc(sizeof(*h)); + if (!h) + return NULL; + + if (ctrl->init && ctrl->init()) { + os_free(h); + return NULL; + } + + h->ctrl = *ctrl; + dl_list_add_tail(&fst_global_ctrls_list, &h->global_ctrls_lentry); + + return h; +} + + +void fst_global_del_ctrl(struct fst_ctrl_handle *h) +{ + dl_list_del(&h->global_ctrls_lentry); + if (h->ctrl.deinit) + h->ctrl.deinit(); + os_free(h); +} + + +void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt, + size_t len) +{ + if (fst_iface_is_connected(iface, mgmt->sa)) + fst_session_on_action_rx(iface, mgmt, len); +} + + +void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr) +{ + if (is_zero_ether_addr(addr)) + return; + +#ifndef HOSTAPD + fst_group_update_ie(fst_iface_get_group(iface), FALSE); +#endif /* HOSTAPD */ + + fst_printf_iface(iface, MSG_DEBUG, MACSTR " became connected", + MAC2STR(addr)); + + fst_ctrl_iface_notify_peer_state_change(iface, TRUE, addr); +} + + +void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr) +{ + if (is_zero_ether_addr(addr)) + return; + +#ifndef HOSTAPD + fst_group_update_ie(fst_iface_get_group(iface), FALSE); +#endif /* HOSTAPD */ + + fst_printf_iface(iface, MSG_DEBUG, MACSTR " became disconnected", + MAC2STR(addr)); + + fst_ctrl_iface_notify_peer_state_change(iface, FALSE, addr); +} + + +Boolean fst_are_ifaces_aggregated(struct fst_iface *iface1, + struct fst_iface *iface2) +{ + return fst_iface_get_group(iface1) == fst_iface_get_group(iface2); +} diff --git a/src/fst/fst.h b/src/fst/fst.h new file mode 100644 index 000000000..76c3823d1 --- /dev/null +++ b/src/fst/fst.h @@ -0,0 +1,296 @@ +/* + * FST module - interface definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_H +#define FST_H + +#ifdef CONFIG_FST + +#include "common/defs.h" +#include "fst/fst_ctrl_iface.h" + +/* FST module hostap integration API */ + +#define US_IN_MS 1000 +#define LLT_UNIT_US 32 /* See 10.32.2.2 Transitioning between states */ + +#define FST_LLT_MS_TO_VAL(m) (((u32) (m)) * US_IN_MS / LLT_UNIT_US) +#define FST_LLT_VAL_TO_MS(v) (((u32) (v)) * LLT_UNIT_US / US_IN_MS) + +#define FST_MAX_LLT_MS FST_LLT_VAL_TO_MS(-1) +#define FST_MAX_PRIO_VALUE ((u8) -1) +#define FST_MAX_GROUP_ID_LEN IFNAMSIZ + +#define FST_DEFAULT_LLT_CFG_VALUE 50 + +struct hostapd_hw_modes; +struct ieee80211_mgmt; +struct fst_iface; +struct fst_group; +struct fst_session; +struct fst_get_peer_ctx; +struct fst_ctrl_handle; + +struct fst_wpa_obj { + void *ctx; + + /** + * get_bssid - Get BSSID of the interface + * @ctx: User context %ctx + * Returns: BSSID for success, %NULL for failure. + * + * NOTE: For AP it returns the own BSSID, while for STA - the BSSID of + * the associated AP. + */ + const u8 * (*get_bssid)(void *ctx); + + /** + * get_channel_info - Get current channel info + * @ctx: User context %ctx + * @hw_mode: OUT, current HW mode + * @channel: OUT, current channel + */ + void (*get_channel_info)(void *ctx, enum hostapd_hw_mode *hw_mode, + u8 *channel); + + /** + * get_hw_modes - Get hardware modes + * @ctx: User context %ctx + * @modes: OUT, pointer on array of hw modes + * + * Returns: Number of hw modes available. + */ + int (*get_hw_modes)(void *ctx, struct hostapd_hw_modes **modes); + + /** + * set_ies - Set interface's MB IE + * @ctx: User context %ctx + * @fst_ies: MB IE buffer + */ + void (*set_ies)(void *ctx, struct wpabuf *fst_ies); + + /** + * send_action - Send FST Action frame via the interface + * @ctx: User context %ctx + * @addr: Address of the destination STA + * @data: Action frame buffer + * Returns: 0 for success, negative error code for failure. + */ + int (*send_action)(void *ctx, const u8 *addr, struct wpabuf *data); + + /** + * get_mb_ie - Get last MB IE received from STA + * @ctx: User context %ctx + * @addr: Address of the STA + * Returns: MB IE buffer, %NULL if no MB IE received from the STA + */ + struct wpabuf * (*get_mb_ie)(void *ctx, const u8 *addr); + + /** + * update_mb_ie - Update last MB IE received from STA + * @ctx: User context %ctx + * @addr: Address of the STA + * @buf: Buffer that contains the MB IEs data + * @size: Size of data in %buf + */ + void (*update_mb_ie)(void *ctx, const u8 *addr, + const u8 *buf, size_t size); + + /** + * get_peer_first - Get MAC address of the 1st connected STA + * @ctx: User context %ctx + * @get_ctx: Context to be used for %get_peer_next call + * @mb_only: %TRUE if only multi-band capable peer should be reported + * Returns: Address of the 1st connected STA, %NULL if no STAs connected + */ + const u8 * (*get_peer_first)(void *ctx, + struct fst_get_peer_ctx **get_ctx, + Boolean mb_only); + /** + * get_peer_next - Get MAC address of the next connected STA + * @ctx: User context %ctx + * @get_ctx: Context received from %get_peer_first or previous + * %get_peer_next call + * @mb_only: %TRUE if only multi-band capable peer should be reported + * Returns: Address of the next connected STA, %NULL if no more STAs + * connected + */ + const u8 * (*get_peer_next)(void *ctx, + struct fst_get_peer_ctx **get_ctx, + Boolean mb_only); +}; + +/** + * fst_global_init - Global FST module initiator + * Returns: 0 for success, negative error code for failure. + * Note: The purpose of this function is to allocate and initiate global + * FST module data structures (linked lists, static data etc.) + * This function should be called prior to the 1st %fst_attach call. + */ +int fst_global_init(void); + +/** + * fst_global_deinit - Global FST module de-initiator + * Note: The purpose of this function is to deallocate and de-initiate global + * FST module data structures (linked lists, static data etc.) + */ +void fst_global_deinit(void); + +/** + * struct fst_ctrl - Notification interface for FST module + */ +struct fst_ctrl { + /** + * init - Initialize the notification interface + * Returns: 0 for success, negative error code for failure. + */ + int (*init)(void); + + /** + * deinit - Deinitialize the notification interface + */ + void (*deinit)(void); + + /** + * on_group_created - Notify about FST group creation + * Returns: 0 for success, negative error code for failure. + */ + int (*on_group_created)(struct fst_group *g); + + /** + * on_group_deleted - Notify about FST group deletion + */ + void (*on_group_deleted)(struct fst_group *g); + + /** + * on_iface_added - Notify about interface addition + * Returns: 0 for success, negative error code for failure. + */ + int (*on_iface_added)(struct fst_iface *i); + + /** + * on_iface_removed - Notify about interface removal + */ + void (*on_iface_removed)(struct fst_iface *i); + + /** + * on_session_added - Notify about FST session addition + * Returns: 0 for success, negative error code for failure. + */ + int (*on_session_added)(struct fst_session *s); + + /** + * on_session_removed - Notify about FST session removal + */ + void (*on_session_removed)(struct fst_session *s); + + /** + * on_event - Notify about FST event + * @event_type: Event type + * @i: Interface object that relates to the event or NULL + * @g: Group object that relates to the event or NULL + * @extra - Event specific data (see fst_ctrl_iface.h for more info) + */ + void (*on_event)(enum fst_event_type event_type, struct fst_iface *i, + struct fst_session *s, + const union fst_event_extra *extra); +}; + +struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl); +void fst_global_del_ctrl(struct fst_ctrl_handle *h); + +/** + * NOTE: These values have to be read from configuration file + */ +struct fst_iface_cfg { + char group_id[FST_MAX_GROUP_ID_LEN + 1]; + u8 priority; + u32 llt; +}; + +/** + * fst_attach - Attach interface to an FST group according to configuration read + * @ifname: Interface name + * @own_addr: Own interface MAC address + * @iface_obj: Callbacks to be used by FST module to communicate with + * hostapd/wpa_supplicant + * @cfg: FST-related interface configuration read from the configuration file + * Returns: FST interface object for success, %NULL for failure. + */ +struct fst_iface * fst_attach(const char *ifname, + const u8 *own_addr, + const struct fst_wpa_obj *iface_obj, + const struct fst_iface_cfg *cfg); + +/** + * fst_detach - Detach an interface + * @iface: FST interface object + */ +void fst_detach(struct fst_iface *iface); + +/* FST module inputs */ +/** + * fst_rx_action - FST Action frames handler + * @iface: FST interface object + * @mgmt: Action frame arrived + * @len: Action frame length + */ +void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt, + size_t len); + +/** + * fst_notify_peer_connected - FST STA connect handler + * @iface: FST interface object + * @addr: Address of the connected STA + */ +void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr); + +/** + * fst_notify_peer_disconnected - FST STA disconnect handler + * @iface: FST interface object + * @addr: Address of the disconnected STA + */ +void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr); + +/* FST module auxiliary routines */ + +/** + * fst_are_ifaces_aggregated - Determines whether 2 interfaces belong to the + * same FST group + * @iface1: 1st FST interface object + * @iface1: 2nd FST interface object + * + * Returns: %TRUE if the interfaces belong to the same FST group, + * %FALSE otherwise + */ +Boolean fst_are_ifaces_aggregated(struct fst_iface *iface1, + struct fst_iface *iface2); + +#else /* CONFIG_FST */ + +static inline int fst_global_init(void) +{ + return 0; +} + +static inline int fst_global_start(void) +{ + return 0; +} + +static inline void fst_global_stop(void) +{ +} + +static inline void fst_global_deinit(void) +{ +} + +#endif /* CONFIG_FST */ + +#endif /* FST_H */ diff --git a/src/fst/fst_ctrl_aux.c b/src/fst/fst_ctrl_aux.c new file mode 100644 index 000000000..dc7b2a7d7 --- /dev/null +++ b/src/fst/fst_ctrl_aux.c @@ -0,0 +1,69 @@ +/* + * FST module implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include "utils/common.h" +#include "common/defs.h" +#include "fst_ctrl_defs.h" +#include "fst_ctrl_aux.h" + + +static const char *session_event_names[] = { + [EVENT_FST_ESTABLISHED] FST_PVAL_EVT_TYPE_ESTABLISHED, + [EVENT_FST_SETUP] FST_PVAL_EVT_TYPE_SETUP, + [EVENT_FST_SESSION_STATE_CHANGED] FST_PVAL_EVT_TYPE_SESSION_STATE, +}; + +static const char *reason_names[] = { + [REASON_TEARDOWN] FST_CS_PVAL_REASON_TEARDOWN, + [REASON_SETUP] FST_CS_PVAL_REASON_SETUP, + [REASON_SWITCH] FST_CS_PVAL_REASON_SWITCH, + [REASON_STT] FST_CS_PVAL_REASON_STT, + [REASON_REJECT] FST_CS_PVAL_REASON_REJECT, + [REASON_ERROR_PARAMS] FST_CS_PVAL_REASON_ERROR_PARAMS, + [REASON_RESET] FST_CS_PVAL_REASON_RESET, + [REASON_DETACH_IFACE] FST_CS_PVAL_REASON_DETACH_IFACE, +}; + +static const char *session_state_names[] = { + [FST_SESSION_STATE_INITIAL] FST_CS_PVAL_STATE_INITIAL, + [FST_SESSION_STATE_SETUP_COMPLETION] FST_CS_PVAL_STATE_SETUP_COMPLETION, + [FST_SESSION_STATE_TRANSITION_DONE] FST_CS_PVAL_STATE_TRANSITION_DONE, + [FST_SESSION_STATE_TRANSITION_CONFIRMED] + FST_CS_PVAL_STATE_TRANSITION_CONFIRMED, +}; + + +/* helpers */ +const char * fst_get_str_name(unsigned index, const char *names[], + size_t names_size) +{ + if (index >= names_size || !names[index]) + return FST_NAME_UNKNOWN; + return names[index]; +} + + +const char * fst_session_event_type_name(enum fst_event_type event) +{ + return fst_get_str_name(event, session_event_names, + ARRAY_SIZE(session_event_names)); +} + + +const char * fst_reason_name(enum fst_reason reason) +{ + return fst_get_str_name(reason, reason_names, ARRAY_SIZE(reason_names)); +} + + +const char * fst_session_state_name(enum fst_session_state state) +{ + return fst_get_str_name(state, session_state_names, + ARRAY_SIZE(session_state_names)); +} diff --git a/src/fst/fst_ctrl_aux.h b/src/fst/fst_ctrl_aux.h new file mode 100644 index 000000000..e2133f506 --- /dev/null +++ b/src/fst/fst_ctrl_aux.h @@ -0,0 +1,91 @@ +/* + * FST module - miscellaneous definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_CTRL_AUX_H +#define FST_CTRL_AUX_H + +#include "common/defs.h" + +/* FST module control interface API */ +#define FST_INVALID_SESSION_ID ((u32) -1) +#define FST_MAX_GROUP_ID_SIZE 32 +#define FST_MAX_INTERFACE_SIZE 32 + +enum fst_session_state { + FST_SESSION_STATE_INITIAL, + FST_SESSION_STATE_SETUP_COMPLETION, + FST_SESSION_STATE_TRANSITION_DONE, + FST_SESSION_STATE_TRANSITION_CONFIRMED, + FST_SESSION_STATE_LAST +}; + +enum fst_event_type { + EVENT_FST_IFACE_STATE_CHANGED, /* An interface has been either attached + * to or detached from an FST group */ + EVENT_FST_ESTABLISHED, /* FST Session has been established */ + EVENT_FST_SETUP, /* FST Session request received */ + EVENT_FST_SESSION_STATE_CHANGED,/* FST Session state has been changed */ + EVENT_PEER_STATE_CHANGED /* FST related generic event occurred, + * see struct fst_hostap_event_data for + * more info */ +}; + +enum fst_initiator { + FST_INITIATOR_UNDEFINED, + FST_INITIATOR_LOCAL, + FST_INITIATOR_REMOTE, +}; + +union fst_event_extra { + struct fst_event_extra_iface_state { + Boolean attached; + char ifname[FST_MAX_INTERFACE_SIZE]; + char group_id[FST_MAX_GROUP_ID_SIZE]; + } iface_state; /* for EVENT_FST_IFACE_STATE_CHANGED */ + struct fst_event_extra_peer_state { + Boolean connected; + char ifname[FST_MAX_INTERFACE_SIZE]; + u8 addr[ETH_ALEN]; + } peer_state; /* for EVENT_PEER_STATE_CHANGED */ + struct fst_event_extra_session_state { + enum fst_session_state old_state; + enum fst_session_state new_state; + union fst_session_state_switch_extra { + struct { + enum fst_reason { + REASON_TEARDOWN, + REASON_SETUP, + REASON_SWITCH, + REASON_STT, + REASON_REJECT, + REASON_ERROR_PARAMS, + REASON_RESET, + REASON_DETACH_IFACE, + } reason; + u8 reject_code; /* REASON_REJECT */ + /* REASON_SWITCH, + * REASON_TEARDOWN, + * REASON_REJECT + */ + enum fst_initiator initiator; + } to_initial; + } extra; + } session_state; /* for EVENT_FST_SESSION_STATE_CHANGED */ +}; + +/* helpers - prints enum in string form */ +#define FST_NAME_UNKNOWN "UNKNOWN" + +const char * fst_get_str_name(unsigned index, const char *names[], + size_t names_size); + +const char * fst_session_event_type_name(enum fst_event_type); +const char * fst_reason_name(enum fst_reason reason); +const char * fst_session_state_name(enum fst_session_state state); + +#endif /* FST_CTRL_AUX_H */ diff --git a/src/fst/fst_ctrl_defs.h b/src/fst/fst_ctrl_defs.h new file mode 100644 index 000000000..0e883b1c0 --- /dev/null +++ b/src/fst/fst_ctrl_defs.h @@ -0,0 +1,95 @@ +/* + * FST module - shared Control interface definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_CTRL_DEFS_H +#define FST_CTRL_DEFS_H + +/* Undefined value */ +#define FST_CTRL_PVAL_NONE "NONE" + +/* FST-ATTACH parameters */ +#define FST_ATTACH_CMD_PNAME_LLT "llt" /* pval = desired LLT */ +#define FST_ATTACH_CMD_PNAME_PRIORITY "priority" /* pval = desired priority */ + +/* FST-MANAGER parameters */ +/* FST Session states */ +#define FST_CS_PVAL_STATE_INITIAL "INITIAL" +#define FST_CS_PVAL_STATE_SETUP_COMPLETION "SETUP_COMPLETION" +#define FST_CS_PVAL_STATE_TRANSITION_DONE "TRANSITION_DONE" +#define FST_CS_PVAL_STATE_TRANSITION_CONFIRMED "TRANSITION_CONFIRMED" + +/* FST Session reset reasons */ +#define FST_CS_PVAL_REASON_TEARDOWN "REASON_TEARDOWN" +#define FST_CS_PVAL_REASON_SETUP "REASON_SETUP" +#define FST_CS_PVAL_REASON_SWITCH "REASON_SWITCH" +#define FST_CS_PVAL_REASON_STT "REASON_STT" +#define FST_CS_PVAL_REASON_REJECT "REASON_REJECT" +#define FST_CS_PVAL_REASON_ERROR_PARAMS "REASON_ERROR_PARAMS" +#define FST_CS_PVAL_REASON_RESET "REASON_RESET" +#define FST_CS_PVAL_REASON_DETACH_IFACE "REASON_DETACH_IFACE" + +/* FST Session responses */ +#define FST_CS_PVAL_RESPONSE_ACCEPT "ACCEPT" +#define FST_CS_PVAL_RESPONSE_REJECT "REJECT" + +/* FST Session action initiator */ +#define FST_CS_PVAL_INITIATOR_LOCAL "LOCAL" +#define FST_CS_PVAL_INITIATOR_REMOTE "REMOTE" + +/* FST-CLI subcommands and parameter names */ +#define FST_CMD_LIST_GROUPS "list_groups" +#define FST_CMD_LIST_IFACES "list_ifaces" +#define FST_CMD_IFACE_PEERS "iface_peers" +#define FST_CMD_GET_PEER_MBIES "get_peer_mbies" +#define FST_CMD_LIST_SESSIONS "list_sessions" +#define FST_CMD_SESSION_ADD "session_add" +#define FST_CMD_SESSION_REMOVE "session_remove" +#define FST_CMD_SESSION_GET "session_get" +#define FST_CSG_PNAME_OLD_PEER_ADDR "old_peer_addr" /* pval = address string */ +#define FST_CSG_PNAME_NEW_PEER_ADDR "new_peer_addr" /* pval = address string */ +#define FST_CSG_PNAME_OLD_IFNAME "old_ifname" /* pval = ifname */ +#define FST_CSG_PNAME_NEW_IFNAME "new_ifname" /* pval = ifname */ +#define FST_CSG_PNAME_LLT "llt" /* pval = numeric llt value */ +#define FST_CSG_PNAME_STATE "state" /* pval = FST_CS_PVAL_STATE_... */ +#define FST_CMD_SESSION_SET "session_set" +#define FST_CSS_PNAME_OLD_PEER_ADDR FST_CSG_PNAME_OLD_PEER_ADDR +#define FST_CSS_PNAME_NEW_PEER_ADDR FST_CSG_PNAME_NEW_PEER_ADDR +#define FST_CSS_PNAME_OLD_IFNAME FST_CSG_PNAME_OLD_IFNAME +#define FST_CSS_PNAME_NEW_IFNAME FST_CSG_PNAME_NEW_IFNAME +#define FST_CSS_PNAME_LLT FST_CSG_PNAME_LLT +#define FST_CMD_SESSION_INITIATE "session_initiate" +#define FST_CMD_SESSION_RESPOND "session_respond" +#define FST_CMD_SESSION_TRANSFER "session_transfer" +#define FST_CMD_SESSION_TEARDOWN "session_teardown" + +/* Events */ +#define FST_CTRL_EVENT_IFACE "FST-EVENT-IFACE" +#define FST_CEI_PNAME_IFNAME "ifname" +#define FST_CEI_PNAME_GROUP "group" +#define FST_CEI_PNAME_ATTACHED "attached" +#define FST_CEI_PNAME_DETACHED "detached" +#define FST_CTRL_EVENT_PEER "FST-EVENT-PEER" +#define FST_CEP_PNAME_IFNAME "ifname" +#define FST_CEP_PNAME_ADDR "peer_addr" +#define FST_CEP_PNAME_CONNECTED "connected" +#define FST_CEP_PNAME_DISCONNECTED "disconnected" +#define FST_CTRL_EVENT_SESSION "FST-EVENT-SESSION" +#define FST_CES_PNAME_SESSION_ID "session_id" +#define FST_CES_PNAME_EVT_TYPE "event_type" +#define FST_PVAL_EVT_TYPE_SESSION_STATE "EVENT_FST_SESSION_STATE" +/* old_state/new_state: pval = FST_CS_PVAL_STATE_... */ +#define FST_CES_PNAME_OLD_STATE "old_state" +#define FST_CES_PNAME_NEW_STATE "new_state" +#define FST_CES_PNAME_REASON "reason" /* pval = FST_CS_PVAL_REASON_... */ +#define FST_CES_PNAME_REJECT_CODE "reject_code" /* pval = u8 code */ +/* pval = FST_CS_PVAL_INITIATOR_... */ +#define FST_CES_PNAME_INITIATOR "initiator" +#define FST_PVAL_EVT_TYPE_ESTABLISHED "EVENT_FST_ESTABLISHED" +#define FST_PVAL_EVT_TYPE_SETUP "EVENT_FST_SETUP" + +#endif /* FST_CTRL_DEFS_H */ diff --git a/src/fst/fst_ctrl_iface.c b/src/fst/fst_ctrl_iface.c new file mode 100644 index 000000000..6d7887e56 --- /dev/null +++ b/src/fst/fst_ctrl_iface.c @@ -0,0 +1,922 @@ +/* + * FST module - Control Interface implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include "utils/common.h" +#include "common/defs.h" +#include "list.h" +#include "fst/fst.h" +#include "fst/fst_internal.h" +#include "fst_ctrl_defs.h" +#include "fst_ctrl_iface.h" + + +static struct fst_group * get_fst_group_by_id(const char *id) +{ + struct fst_group *g; + + foreach_fst_group(g) { + const char *group_id = fst_group_get_id(g); + + if (os_strncmp(group_id, id, os_strlen(group_id)) == 0) + return g; + } + + return NULL; +} + + +/* notifications */ +Boolean format_session_state_extra(const union fst_event_extra *extra, + char *buffer, int size) +{ + int len; + char reject_str[32] = FST_CTRL_PVAL_NONE; + const char *initiator = FST_CTRL_PVAL_NONE; + const struct fst_event_extra_session_state *ss; + + if (!extra) + return TRUE; + + ss = &extra->session_state; + if (ss->new_state != FST_SESSION_STATE_INITIAL) + return TRUE; + + switch (ss->extra.to_initial.reason) { + case REASON_REJECT: + if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS) + os_snprintf(reject_str, sizeof(reject_str), "%u", + ss->extra.to_initial.reject_code); + /* no break */ + case REASON_TEARDOWN: + case REASON_SWITCH: + switch (ss->extra.to_initial.initiator) { + case FST_INITIATOR_LOCAL: + initiator = FST_CS_PVAL_INITIATOR_LOCAL; + break; + case FST_INITIATOR_REMOTE: + initiator = FST_CS_PVAL_INITIATOR_REMOTE; + break; + default: + break; + } + break; + default: + break; + } + + len = os_snprintf(buffer, size, + FST_CES_PNAME_REASON "=%s " + FST_CES_PNAME_REJECT_CODE "=%s " + FST_CES_PNAME_INITIATOR "=%s", + fst_reason_name(ss->extra.to_initial.reason), + reject_str, initiator); + + return !os_snprintf_error(size, len); +} + + +static void fst_ctrl_iface_notify(u32 session_id, + enum fst_event_type event_type, + const union fst_event_extra *extra) +{ + struct fst_group *g; + struct fst_iface *f; + char extra_str[128] = ""; + const struct fst_event_extra_session_state *ss; + const struct fst_event_extra_iface_state *is; + const struct fst_event_extra_peer_state *ps; + + /* + * FST can use any of interface objects as it only sends messages + * on global Control Interface, so we just pick the 1st one. + */ + + g = fst_first_group(); + if (!g) + return; + + f = fst_group_first_iface(g); + if (!f) + return; + + WPA_ASSERT(f->iface_obj.ctx); + + switch (event_type) { + case EVENT_FST_IFACE_STATE_CHANGED: + if (!extra) + return; + is = &extra->iface_state; + wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO, + FST_CTRL_EVENT_IFACE " %s " + FST_CEI_PNAME_IFNAME "=%s " + FST_CEI_PNAME_GROUP "=%s", + is->attached ? FST_CEI_PNAME_ATTACHED : + FST_CEI_PNAME_DETACHED, + is->ifname, is->group_id); + break; + case EVENT_PEER_STATE_CHANGED: + if (!extra) + return; + ps = &extra->peer_state; + wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, + FST_CTRL_EVENT_PEER " %s " + FST_CEP_PNAME_IFNAME "=%s " + FST_CEP_PNAME_ADDR "=" MACSTR, + ps->connected ? FST_CEP_PNAME_CONNECTED : + FST_CEP_PNAME_DISCONNECTED, + ps->ifname, MAC2STR(ps->addr)); + break; + case EVENT_FST_SESSION_STATE_CHANGED: + if (!extra) + return; + if (!format_session_state_extra(extra, extra_str, + sizeof(extra_str))) { + fst_printf(MSG_ERROR, + "CTRL: Cannot format STATE_CHANGE extra"); + extra_str[0] = 0; + } + ss = &extra->session_state; + wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, + FST_CTRL_EVENT_SESSION " " + FST_CES_PNAME_SESSION_ID "=%u " + FST_CES_PNAME_EVT_TYPE "=%s " + FST_CES_PNAME_OLD_STATE "=%s " + FST_CES_PNAME_NEW_STATE "=%s %s", + session_id, + fst_session_event_type_name(event_type), + fst_session_state_name(ss->old_state), + fst_session_state_name(ss->new_state), + extra_str); + break; + case EVENT_FST_ESTABLISHED: + case EVENT_FST_SETUP: + wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, + FST_CTRL_EVENT_SESSION " " + FST_CES_PNAME_SESSION_ID "=%u " + FST_CES_PNAME_EVT_TYPE "=%s", + session_id, + fst_session_event_type_name(event_type)); + break; + } +} + + +/* command processors */ + +/* fst session_get */ +static int session_get(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + struct fst_iface *new_iface, *old_iface; + const u8 *old_peer_addr, *new_peer_addr; + u32 id; + + id = strtoul(session_id, NULL, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + old_peer_addr = fst_session_get_peer_addr(s, TRUE); + new_peer_addr = fst_session_get_peer_addr(s, FALSE); + new_iface = fst_session_get_iface(s, FALSE); + old_iface = fst_session_get_iface(s, TRUE); + + return os_snprintf(buf, buflen, + FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n" + FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n" + FST_CSG_PNAME_NEW_IFNAME "=%s\n" + FST_CSG_PNAME_OLD_IFNAME "=%s\n" + FST_CSG_PNAME_LLT "=%u\n" + FST_CSG_PNAME_STATE "=%s\n", + MAC2STR(old_peer_addr), + MAC2STR(new_peer_addr), + new_iface ? fst_iface_get_name(new_iface) : + FST_CTRL_PVAL_NONE, + old_iface ? fst_iface_get_name(old_iface) : + FST_CTRL_PVAL_NONE, + fst_session_get_llt(s), + fst_session_state_name(fst_session_get_state(s))); +} + + +/* fst session_set */ +static int session_set(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + char *p, *q; + u32 id; + int ret; + + id = strtoul(session_id, &p, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (*p != ' ' || !(q = os_strchr(p + 1, '='))) + return os_snprintf(buf, buflen, "FAIL\n"); + p++; + + if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) { + ret = fst_session_set_str_ifname(s, q + 1, TRUE); + } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) { + ret = fst_session_set_str_ifname(s, q + 1, FALSE); + } else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) { + ret = fst_session_set_str_peer_addr(s, q + 1, TRUE); + } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) { + ret = fst_session_set_str_peer_addr(s, q + 1, FALSE); + } else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) { + ret = fst_session_set_str_llt(s, q + 1); + } else { + fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK"); +} + + +/* fst session_add/remove */ +static int session_add(const char *group_id, char *buf, size_t buflen) +{ + struct fst_group *g; + struct fst_session *s; + + g = get_fst_group_by_id(group_id); + if (!g) { + fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", + group_id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + s = fst_session_create(g); + if (!s) { + fst_printf(MSG_ERROR, + "CTRL: Cannot create session for group '%s'", + group_id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s)); +} + + +static int session_remove(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + struct fst_group *g; + u32 id; + + id = strtoul(session_id, NULL, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + g = fst_session_get_group(s); + fst_session_reset(s); + fst_session_delete(s); + fst_group_delete_if_empty(g); + + return os_snprintf(buf, buflen, "OK\n"); +} + + +/* fst session_initiate */ +static int session_initiate(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + u32 id; + + id = strtoul(session_id, NULL, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (fst_session_initiate_setup(s)) { + fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + return os_snprintf(buf, buflen, "OK\n"); +} + + +/* fst session_respond */ +static int session_respond(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + char *p; + u32 id; + u8 status_code; + + id = strtoul(session_id, &p, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (*p != ' ') + return os_snprintf(buf, buflen, "FAIL\n"); + p++; + + if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) { + status_code = WLAN_STATUS_SUCCESS; + } else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) { + status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION; + } else { + fst_printf(MSG_WARNING, + "CTRL: session %u: unknown response status: %s", + id, p); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (fst_session_respond(s, status_code)) { + fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u", + id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + fst_printf(MSG_INFO, "CTRL: session %u responded", id); + + return os_snprintf(buf, buflen, "OK\n"); +} + + +/* fst session_transfer */ +static int session_transfer(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + u32 id; + + id = strtoul(session_id, NULL, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (fst_session_initiate_switch(s)) { + fst_printf(MSG_WARNING, + "CTRL: Cannot initiate ST for session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + return os_snprintf(buf, buflen, "OK\n"); +} + + +/* fst session_teardown */ +static int session_teardown(const char *session_id, char *buf, size_t buflen) +{ + struct fst_session *s; + u32 id; + + id = strtoul(session_id, NULL, 0); + + s = fst_session_get_by_id(id); + if (!s) { + fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + if (fst_session_tear_down_setup(s)) { + fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u", + id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + return os_snprintf(buf, buflen, "OK\n"); +} + +/* fst list_sessions */ +struct list_sessions_cb_ctx { + char *buf; + size_t buflen; + size_t reply_len; +}; + + +static void list_session_enum_cb(struct fst_group *g, struct fst_session *s, + void *ctx) +{ + struct list_sessions_cb_ctx *c = ctx; + int ret; + + ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s)); + + c->buf += ret; + c->buflen -= ret; + c->reply_len += ret; +} + + +static int list_sessions(const char *group_id, char *buf, size_t buflen) +{ + struct list_sessions_cb_ctx ctx; + struct fst_group *g; + + g = get_fst_group_by_id(group_id); + if (!g) { + fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", + group_id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + ctx.buf = buf; + ctx.buflen = buflen; + ctx.reply_len = 0; + + fst_session_enum(g, list_session_enum_cb, &ctx); + + ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n"); + + return ctx.reply_len; +} + + +/* fst iface_peers */ +static int iface_peers(const char *group_id, char *buf, size_t buflen) +{ + const char *ifname; + struct fst_group *g; + struct fst_iface *f; + struct fst_get_peer_ctx *ctx; + const u8 *addr; + unsigned found = 0; + int ret = 0; + + g = get_fst_group_by_id(group_id); + if (!g) { + fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", + group_id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + ifname = os_strchr(group_id, ' '); + if (!ifname) + return os_snprintf(buf, buflen, "FAIL\n"); + ifname++; + + foreach_fst_group_iface(g, f) { + const char *in = fst_iface_get_name(f); + + if (os_strncmp(ifname, in, os_strlen(in)) == 0) { + found = 1; + break; + } + } + + if (!found) + return os_snprintf(buf, buflen, "FAIL\n"); + + addr = fst_iface_get_peer_first(f, &ctx, FALSE); + for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, FALSE)) { + int res; + + res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n", + MAC2STR(addr)); + if (os_snprintf_error(buflen - ret, res)) + break; + ret += res; + } + + return ret; +} + + +static int get_peer_mbies(const char *params, char *buf, size_t buflen) +{ + char *endp; + char ifname[FST_MAX_INTERFACE_SIZE]; + u8 peer_addr[ETH_ALEN]; + struct fst_group *g; + struct fst_iface *iface = NULL; + struct wpabuf *mbies; + int ret; + + if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) || + !*ifname) + goto problem; + + while (isspace(*endp)) + endp++; + if (fst_read_peer_addr(endp, peer_addr)) + goto problem; + + foreach_fst_group(g) { + iface = fst_group_get_iface_by_name(g, ifname); + if (!iface) + continue; + } + if (!iface) + goto problem; + + mbies = fst_iface_get_peer_mb_ie(iface, peer_addr); + if (!mbies) + goto problem; + + ret = print_mb_ies(mbies, buf, buflen); + if ((size_t) ret != wpabuf_len(mbies) * 2) + fst_printf(MSG_WARNING, "MB IEs copied only partially"); + + return ret; + +problem: + return os_snprintf(buf, buflen, "FAIL\n"); +} + + +/* fst list_ifaces */ +static int list_ifaces(const char *group_id, char *buf, size_t buflen) +{ + struct fst_group *g; + struct fst_iface *f; + int ret = 0; + + g = get_fst_group_by_id(group_id); + if (!g) { + fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", + group_id); + return os_snprintf(buf, buflen, "FAIL\n"); + } + + foreach_fst_group_iface(g, f) { + int res; + const u8 *iface_addr = fst_iface_get_addr(f); + + res = os_snprintf(buf + ret, buflen - ret, + "%s|" MACSTR "|%u|%u\n", + fst_iface_get_name(f), + MAC2STR(iface_addr), + fst_iface_get_priority(f), + fst_iface_get_llt(f)); + if (os_snprintf_error(buflen - ret, res)) + break; + ret += res; + } + + return ret; +} + + +/* fst list_groups */ +static int list_groups(const char *cmd, char *buf, size_t buflen) +{ + struct fst_group *g; + int ret = 0; + + foreach_fst_group(g) { + int res; + + res = os_snprintf(buf + ret, buflen - ret, "%s\n", + fst_group_get_id(g)); + if (os_snprintf_error(buflen - ret, res)) + break; + ret += res; + } + + return ret; +} + + +int print_mb_ies(struct wpabuf *mbies, char *buf, size_t buflen) +{ + const u8 *p = wpabuf_head(mbies); + size_t s = wpabuf_len(mbies); + int ret = 0; + + while ((size_t) ret < buflen && s--) { + int res; + + res = os_snprintf(buf + ret, buflen - ret, "%02x", *p++); + if (os_snprintf_error(buflen - ret, res)) + break; + ret += res; + } + + return ret; +} + + +static const char * band_freq(enum mb_band_id band) +{ + static const char *band_names[] = { + [MB_BAND_ID_WIFI_2_4GHZ] "2.4GHZ", + [MB_BAND_ID_WIFI_5GHZ] "5GHZ", + [MB_BAND_ID_WIFI_60GHZ] "60GHZ", + }; + + return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names)); +} + + +static int print_band(unsigned num, struct fst_iface *iface, const u8 *addr, + char *buf, size_t buflen) +{ + struct wpabuf *wpabuf; + enum hostapd_hw_mode hw_mode; + u8 channel; + int ret = 0; + + fst_iface_get_channel_info(iface, &hw_mode, &channel); + + ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n", + num, band_freq(fst_hw_mode_to_band(hw_mode))); + ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n", + num, fst_iface_get_name(iface)); + wpabuf = fst_iface_get_peer_mb_ie(iface, addr); + if (wpabuf) { + ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=", + num); + ret += print_mb_ies(wpabuf, buf + ret, buflen - ret); + ret += os_snprintf(buf + ret, buflen - ret, "\n"); + } + ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n", + num, fst_iface_get_group_id(iface)); + ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n", + num, fst_iface_get_priority(iface)); + ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n", + num, fst_iface_get_llt(iface)); + + return ret; +} + + +static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i, + Boolean attached) +{ + union fst_event_extra extra; + + os_memset(&extra, 0, sizeof(extra)); + extra.iface_state.attached = attached; + os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i), + sizeof(extra.iface_state.ifname)); + os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i), + sizeof(extra.iface_state.group_id)); + + fst_ctrl_iface_notify(FST_INVALID_SESSION_ID, + EVENT_FST_IFACE_STATE_CHANGED, &extra); +} + + +static int fst_ctrl_iface_on_iface_added(struct fst_iface *i) +{ + fst_ctrl_iface_on_iface_state_changed(i, TRUE); + return 0; +} + + +static void fst_ctrl_iface_on_iface_removed(struct fst_iface *i) +{ + fst_ctrl_iface_on_iface_state_changed(i, FALSE); +} + + +static void fst_ctrl_iface_on_event(enum fst_event_type event_type, + struct fst_iface *i, struct fst_session *s, + const union fst_event_extra *extra) +{ + u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID; + + fst_ctrl_iface_notify(session_id, event_type, extra); +} + + +static const struct fst_ctrl ctrl_cli = { + .on_iface_added = fst_ctrl_iface_on_iface_added, + .on_iface_removed = fst_ctrl_iface_on_iface_removed, + .on_event = fst_ctrl_iface_on_event, +}; + +const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli; + + +int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen) +{ + struct fst_group *g; + struct fst_iface *f; + unsigned num = 0; + int ret = 0; + + foreach_fst_group(g) { + foreach_fst_group_iface(g, f) { + if (fst_iface_is_connected(f, addr)) { + ret += print_band(num++, f, addr, + buf + ret, buflen - ret); + } + } + } + + return ret; +} + + +/* fst ctrl processor */ +int fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size) +{ + static const struct fst_command { + const char *name; + unsigned has_param; + int (*process)(const char *group_id, char *buf, size_t buflen); + } commands[] = { + { FST_CMD_LIST_GROUPS, 0, list_groups}, + { FST_CMD_LIST_IFACES, 1, list_ifaces}, + { FST_CMD_IFACE_PEERS, 1, iface_peers}, + { FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies}, + { FST_CMD_LIST_SESSIONS, 1, list_sessions}, + { FST_CMD_SESSION_ADD, 1, session_add}, + { FST_CMD_SESSION_REMOVE, 1, session_remove}, + { FST_CMD_SESSION_GET, 1, session_get}, + { FST_CMD_SESSION_SET, 1, session_set}, + { FST_CMD_SESSION_INITIATE, 1, session_initiate}, + { FST_CMD_SESSION_RESPOND, 1, session_respond}, + { FST_CMD_SESSION_TRANSFER, 1, session_transfer}, + { FST_CMD_SESSION_TEARDOWN, 1, session_teardown}, + { NULL, 0, NULL } + }; + const struct fst_command *c; + const char *p; + const char *temp; + Boolean non_spaces_found; + + for (c = commands; c->name; c++) { + if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0) + continue; + p = cmd + os_strlen(c->name); + if (c->has_param) { + if (!isspace(p[0])) + return os_snprintf(reply, reply_size, "FAIL\n"); + p++; + temp = p; + non_spaces_found = FALSE; + while (*temp) { + if (!isspace(*temp)) { + non_spaces_found = TRUE; + break; + } + temp++; + } + if (!non_spaces_found) + return os_snprintf(reply, reply_size, "FAIL\n"); + } + return c->process(p, reply, reply_size); + } + + return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n"); +} + + +int fst_read_next_int_param(const char *params, Boolean *valid, char **endp) +{ + int ret = -1; + const char *curp; + + *valid = FALSE; + *endp = (char *) params; + curp = params; + if (*curp) { + ret = (int) strtol(curp, endp, 0); + if (!**endp || isspace(**endp)) + *valid = TRUE; + } + + return ret; +} + + +int fst_read_next_text_param(const char *params, char *buf, size_t buflen, + char **endp) +{ + size_t max_chars_to_copy; + char *cur_dest; + + if (buflen <= 1) + return EINVAL; + + *endp = (char *) params; + while (isspace(**endp)) + (*endp)++; + if (!**endp) + return EINVAL; + + max_chars_to_copy = buflen - 1; + /* We need 1 byte for the terminating zero */ + cur_dest = buf; + while (**endp && !isspace(**endp) && max_chars_to_copy > 0) { + *cur_dest = **endp; + (*endp)++; + cur_dest++; + max_chars_to_copy--; + } + *cur_dest = 0; + + return 0; +} + + +int fst_read_peer_addr(const char *mac, u8 *peer_addr) +{ + if (hwaddr_aton(mac, peer_addr)) { + fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string", + mac); + return -1; + } + + if (is_zero_ether_addr(peer_addr) || + is_multicast_ether_addr(peer_addr)) { + fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr", + mac); + return -1; + } + + return 0; +} + + +int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size, + struct fst_iface_cfg *cfg) +{ + char *pos; + char *endp; + Boolean is_valid; + int val; + + if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) || + fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id), + &endp)) + return EINVAL; + + cfg->llt = FST_DEFAULT_LLT_CFG_VALUE; + cfg->priority = 0; + pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT); + if (pos) { + pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT); + if (*pos == '=') { + val = fst_read_next_int_param(pos + 1, &is_valid, + &endp); + if (is_valid) + cfg->llt = val; + } + } + pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY); + if (pos) { + pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY); + if (*pos == '=') { + val = fst_read_next_int_param(pos + 1, &is_valid, + &endp); + if (is_valid) + cfg->priority = (u8) val; + } + } + + return 0; +} + + +int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size) +{ + char *endp; + + if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp)) + return EINVAL; + + return 0; +} + + +/* fst iface_detach */ +int fst_iface_detach(const char *ifname) +{ + struct fst_group *g; + struct fst_iface *f; + + foreach_fst_group(g) { + f = fst_group_get_iface_by_name(g, ifname); + if (f) { + fst_detach(f); + return 0; + } + } + + return EINVAL; +} diff --git a/src/fst/fst_ctrl_iface.h b/src/fst/fst_ctrl_iface.h new file mode 100644 index 000000000..c3b4873ee --- /dev/null +++ b/src/fst/fst_ctrl_iface.h @@ -0,0 +1,48 @@ +/* + * FST module - internal Control interface definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_CTRL_IFACE_H +#define FST_CTRL_IFACE_H + +#include "fst/fst_ctrl_aux.h" + +#ifdef CONFIG_FST + +/* receiver */ +int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen); + +int fst_ctrl_iface_receive(const char *txtaddr, char *buf, size_t buflen); + +extern const struct fst_ctrl *fst_ctrl_cli; + +#else /* CONFIG_FST */ + +static inline int +fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen) +{ + return 0; +} + +#endif /* CONFIG_FST */ + + +int print_mb_ies(struct wpabuf *mbies, char *buf, size_t buflen); + +int fst_read_next_int_param(const char *params, Boolean *valid, char **endp); +int fst_read_next_text_param(const char *params, char *buf, size_t buflen, + char **endp); +int fst_read_peer_addr(const char *mac, u8 *peer_addr); + +struct fst_iface_cfg; + +int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size, + struct fst_iface_cfg *cfg); +int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size); +int fst_iface_detach(const char *ifname); + +#endif /* CTRL_IFACE_FST_H */ diff --git a/src/fst/fst_defs.h b/src/fst/fst_defs.h new file mode 100644 index 000000000..8ddcc6137 --- /dev/null +++ b/src/fst/fst_defs.h @@ -0,0 +1,87 @@ +/* + * FST module - FST related definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE_80211_FST_DEFS_H +#define IEEE_80211_FST_DEFS_H + +/* IEEE Std 802.11ad */ + +#define MB_STA_CHANNEL_ALL 0 + +enum session_type { + SESSION_TYPE_BSS = 0, /* Infrastructure BSS */ + SESSION_TYPE_IBSS = 1, + SESSION_TYPE_DLS = 2, + SESSION_TYPE_TDLS = 3, + SESSION_TYPE_PBSS = 4 +}; + +#define SESSION_CONTROL(session_type, switch_intent) \ + (((u8) ((session_type) & 0x7)) | ((switch_intent) ? 0x10 : 0x00)) + +#define GET_SESSION_CONTROL_TYPE(session_control) \ + ((u8) ((session_control) & 0x7)) + +#define GET_SESSION_CONTROL_SWITCH_INTENT(session_control) \ + (((session_control) & 0x10) >> 4) + +/* 8.4.2.147 Session Transition element */ +struct session_transition_ie { + u8 element_id; + u8 length; + u32 fsts_id; + u8 session_control; + u8 new_band_id; + u8 new_band_setup; + u8 new_band_op; + u8 old_band_id; + u8 old_band_setup; + u8 old_band_op; +} STRUCT_PACKED; + +struct fst_setup_req { + u8 action; + u8 dialog_token; + u32 llt; + struct session_transition_ie stie; + /* Multi-band (optional) */ + /* Wakeup Schedule (optional) */ + /* Awake Window (optional) */ + /* Switching Stream (optional) */ +} STRUCT_PACKED; + +struct fst_setup_res { + u8 action; + u8 dialog_token; + u8 status_code; + struct session_transition_ie stie; + /* Multi-band (optional) */ + /* Wakeup Schedule (optional) */ + /* Awake Window (optional) */ + /* Switching Stream (optional) */ + /* Timeout Interval (optional) */ +} STRUCT_PACKED; + +struct fst_ack_req { + u8 action; + u8 dialog_token; + u32 fsts_id; +} STRUCT_PACKED; + +struct fst_ack_res { + u8 action; + u8 dialog_token; + u32 fsts_id; +} STRUCT_PACKED; + +struct fst_tear_down { + u8 action; + u32 fsts_id; +} STRUCT_PACKED; + +#endif /* IEEE_80211_FST_DEFS_H */ diff --git a/src/fst/fst_group.c b/src/fst/fst_group.c new file mode 100644 index 000000000..8e731375f --- /dev/null +++ b/src/fst/fst_group.c @@ -0,0 +1,447 @@ +/* + * FST module - FST group object implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include "utils/common.h" +#include "common/defs.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "drivers/driver.h" +#include "fst/fst_internal.h" +#include "fst/fst_defs.h" + + +struct dl_list fst_global_groups_list; + +#ifndef HOSTAPD +static Boolean fst_has_fst_peer(struct fst_iface *iface, Boolean *has_peer) +{ + const u8 *bssid; + + bssid = fst_iface_get_bssid(iface); + if (!bssid) { + *has_peer = FALSE; + return FALSE; + } + + *has_peer = TRUE; + return fst_iface_get_peer_mb_ie(iface, bssid) != NULL; +} +#endif /* HOSTAPD */ + + +static void fst_dump_mb_ies(const char *group_id, const char *ifname, + struct wpabuf *mbies) +{ + const u8 *p = wpabuf_head(mbies); + size_t s = wpabuf_len(mbies); + + while (s >= offsetof(struct multi_band_ie, mb_ctrl)) { + const struct multi_band_ie *mbie = + (const struct multi_band_ie *) p; + WPA_ASSERT(mbie->eid == WLAN_EID_MULTI_BAND); + WPA_ASSERT(IE_BUFFER_LENGTH(mbie->len) >= sizeof(*mbie)); + + fst_printf(MSG_WARNING, + "%s: %s: mb_ctrl=%u band_id=%u op_class=%u chan=%u bssid=" + MACSTR + " beacon_int=%u tsf_offs=[%u %u %u %u %u %u %u %u] mb_cc=0x%02x tmout=%u", + group_id, ifname, + mbie->mb_ctrl, mbie->band_id, mbie->op_class, + mbie->chan, MAC2STR(mbie->bssid), mbie->beacon_int, + mbie->tsf_offs[0], mbie->tsf_offs[1], + mbie->tsf_offs[2], mbie->tsf_offs[3], + mbie->tsf_offs[4], mbie->tsf_offs[5], + mbie->tsf_offs[6], mbie->tsf_offs[7], + mbie->mb_connection_capability, + mbie->fst_session_tmout); + + p += IE_BUFFER_LENGTH(mbie->len); + s -= IE_BUFFER_LENGTH(mbie->len); + } +} + + +static void fst_fill_mb_ie(struct wpabuf *buf, const u8 *bssid, + const u8 *own_addr, enum mb_band_id band, u8 channel) +{ + struct multi_band_ie *mbie; + size_t len = sizeof(*mbie); + + if (own_addr) + len += ETH_ALEN; + + mbie = wpabuf_put(buf, len); + + os_memset(mbie, 0, len); + + mbie->eid = WLAN_EID_MULTI_BAND; + mbie->len = len - IE_HEADER_SIZE; +#ifdef HOSTAPD + mbie->mb_ctrl = MB_STA_ROLE_AP; + mbie->mb_connection_capability = MB_CONNECTION_CAPABILITY_AP; +#else /* HOSTAPD */ + mbie->mb_ctrl = MB_STA_ROLE_NON_PCP_NON_AP; + mbie->mb_connection_capability = 0; +#endif /* HOSTAPD */ + if (bssid) + os_memcpy(mbie->bssid, bssid, ETH_ALEN); + mbie->band_id = band; + mbie->op_class = 0; /* means all */ + mbie->chan = channel; + mbie->fst_session_tmout = FST_DEFAULT_SESSION_TIMEOUT_TU; + + if (own_addr) { + mbie->mb_ctrl |= MB_CTRL_STA_MAC_PRESENT; + os_memcpy(&mbie[1], own_addr, ETH_ALEN); + } +} + + +static unsigned fst_fill_iface_mb_ies(struct fst_iface *f, struct wpabuf *buf) +{ + const u8 *bssid; + + bssid = fst_iface_get_bssid(f); + if (bssid) { + enum hostapd_hw_mode hw_mode; + u8 channel; + + if (buf) { + fst_iface_get_channel_info(f, &hw_mode, &channel); + fst_fill_mb_ie(buf, bssid, fst_iface_get_addr(f), + fst_hw_mode_to_band(hw_mode), channel); + } + return 1; + } else { + unsigned bands[MB_BAND_ID_WIFI_60GHZ + 1] = {}; + struct hostapd_hw_modes *modes; + enum mb_band_id b; + int num_modes = fst_iface_get_hw_modes(f, &modes); + int ret = 0; + + while (num_modes--) { + b = fst_hw_mode_to_band(modes->mode); + modes++; + if (b >= ARRAY_SIZE(bands) || bands[b]++) + continue; + ret++; + if (buf) + fst_fill_mb_ie(buf, NULL, fst_iface_get_addr(f), + b, MB_STA_CHANNEL_ALL); + } + return ret; + } +} + + +static struct wpabuf * fst_group_create_mb_ie(struct fst_group *g, + struct fst_iface *i) +{ + struct wpabuf *buf; + struct fst_iface *f; + unsigned int nof_mbies = 0; + unsigned int nof_ifaces_added = 0; +#ifndef HOSTAPD + Boolean has_peer; + Boolean has_fst_peer; + + foreach_fst_group_iface(g, f) { + has_fst_peer = fst_has_fst_peer(f, &has_peer); + if (has_peer && !has_fst_peer) + return NULL; + } +#endif /* HOSTAPD */ + + foreach_fst_group_iface(g, f) { + if (f == i) + continue; + nof_mbies += fst_fill_iface_mb_ies(f, NULL); + } + + buf = wpabuf_alloc(nof_mbies * + (sizeof(struct multi_band_ie) + ETH_ALEN)); + if (!buf) { + fst_printf_iface(i, MSG_ERROR, + "cannot allocate mem for %u MB IEs", + nof_mbies); + return NULL; + } + + /* The list is sorted in descending order by priorities, so MB IEs will + * be arranged in the same order, as required by spec (see corresponding + * comment in.fst_attach(). + */ + foreach_fst_group_iface(g, f) { + if (f == i) + continue; + + fst_fill_iface_mb_ies(f, buf); + ++nof_ifaces_added; + + fst_printf_iface(i, MSG_DEBUG, "added to MB IE"); + } + + if (!nof_ifaces_added) { + wpabuf_free(buf); + buf = NULL; + fst_printf_iface(i, MSG_INFO, + "cannot add MB IE: no backup ifaces"); + } else { + fst_dump_mb_ies(fst_group_get_id(g), fst_iface_get_name(i), + buf); + } + + return buf; +} + + +static struct fst_iface * +fst_group_get_new_iface_by_mbie_and_band_id(struct fst_group *g, + const u8 *mb_ies_buff, + size_t mb_ies_size, + u8 band_id, + u8 *iface_peer_addr) +{ + while (mb_ies_size >= offsetof(struct multi_band_ie, mb_ctrl)) { + const struct multi_band_ie *mbie = + (const struct multi_band_ie *) mb_ies_buff; + + if (mbie->eid != WLAN_EID_MULTI_BAND || + IE_BUFFER_LENGTH(mbie->len) < sizeof(*mbie)) + break; + + if (mbie->band_id == band_id) { + struct fst_iface *iface; + + foreach_fst_group_iface(g, iface) { + const u8 *peer_addr = + fst_mbie_get_peer_addr(mbie); + + if (peer_addr && + fst_iface_is_connected(iface, peer_addr) && + band_id == fst_iface_get_band_id(iface)) { + os_memcpy(iface_peer_addr, peer_addr, + ETH_ALEN); + return iface; + } + } + break; + } + + mb_ies_buff += IE_BUFFER_LENGTH(mbie->len); + mb_ies_size -= IE_BUFFER_LENGTH(mbie->len); + } + + return NULL; +} + + +struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g, + const char *ifname) +{ + struct fst_iface *f; + + foreach_fst_group_iface(g, f) { + const char *in = fst_iface_get_name(f); + + if (os_strncmp(in, ifname, os_strlen(in)) == 0) + return f; + } + + return NULL; +} + + +u8 fst_group_assign_dialog_token(struct fst_group *g) +{ + g->dialog_token++; + if (g->dialog_token == 0) + g->dialog_token++; + return g->dialog_token; +} + + +u32 fst_group_assign_fsts_id(struct fst_group *g) +{ + g->fsts_id++; + return g->fsts_id; +} + + +static Boolean +fst_group_does_iface_appear_in_other_mbies(struct fst_group *g, + struct fst_iface *iface, + struct fst_iface *other, + u8 *peer_addr) +{ + struct fst_get_peer_ctx *ctx; + const u8 *addr; + const u8 *iface_addr; + enum mb_band_id iface_band_id; + + WPA_ASSERT(g == fst_iface_get_group(iface)); + WPA_ASSERT(g == fst_iface_get_group(other)); + + iface_addr = fst_iface_get_addr(iface); + iface_band_id = fst_iface_get_band_id(iface); + + addr = fst_iface_get_peer_first(other, &ctx, TRUE); + for (; addr; addr = fst_iface_get_peer_next(other, &ctx, TRUE)) { + struct wpabuf *mbies = fst_iface_get_peer_mb_ie(other, addr); + + if (mbies) { + u8 other_iface_peer_addr[ETH_ALEN]; + struct fst_iface *other_new_iface = + fst_group_get_new_iface_by_mbie_and_band_id( + g, + wpabuf_head(mbies), wpabuf_len(mbies), + iface_band_id, other_iface_peer_addr); + if (other_new_iface == iface && + os_memcmp(iface_addr, other_iface_peer_addr, + ETH_ALEN)) { + os_memcpy(peer_addr, addr, ETH_ALEN); + return TRUE; + } + } + } + + return FALSE; +} + + +struct fst_iface * +fst_group_find_new_iface_by_stie(struct fst_group *g, + struct fst_iface *iface, + const u8 *peer_addr, + const struct session_transition_ie *stie, + u8 *iface_peer_addr) +{ + struct fst_iface *i; + + foreach_fst_group_iface(g, i) { + if (i == iface || + stie->new_band_id != fst_iface_get_band_id(i)) + continue; + if (fst_group_does_iface_appear_in_other_mbies(g, iface, i, + iface_peer_addr)) + return i; + break; + } + return NULL; +} + + +struct fst_iface * +fst_group_get_new_iface_by_stie_and_mbie( + struct fst_group *g, const u8 *mb_ies_buff, size_t mb_ies_size, + const struct session_transition_ie *stie, u8 *iface_peer_addr) +{ + return fst_group_get_new_iface_by_mbie_and_band_id( + g, mb_ies_buff, mb_ies_size, stie->new_band_id, + iface_peer_addr); +} + + +struct fst_group * fst_group_create(const char *group_id) +{ + struct fst_group *g; + + g = os_zalloc(sizeof(*g)); + if (g == NULL) { + fst_printf(MSG_ERROR, "%s: Cannot alloc group", group_id); + return NULL; + } + + dl_list_init(&g->ifaces); + os_strlcpy(g->group_id, group_id, sizeof(g->group_id)); + + dl_list_add_tail(&fst_global_groups_list, &g->global_groups_lentry); + fst_printf_group(g, MSG_DEBUG, "instance created"); + + foreach_fst_ctrl_call(on_group_created, g); + + return g; +} + + +void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i) +{ + struct dl_list *list = &g->ifaces; + struct fst_iface *f; + + /* + * Add new interface to the list. + * The list is sorted in descending order by priority to allow + * multiple MB IEs creation according to the spec (see 10.32 Multi-band + * operation, 10.32.1 General), as they should be ordered according to + * priorities. + */ + foreach_fst_group_iface(g, f) { + if (fst_iface_get_priority(f) < fst_iface_get_priority(i)) + break; + list = &f->group_lentry; + } + dl_list_add(list, &i->group_lentry); +} + + +void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i) +{ + dl_list_del(&i->group_lentry); +} + + +void fst_group_delete(struct fst_group *group) +{ + struct fst_session *s; + + dl_list_del(&group->global_groups_lentry); + WPA_ASSERT(dl_list_empty(&group->ifaces)); + foreach_fst_ctrl_call(on_group_deleted, group); + fst_printf_group(group, MSG_DEBUG, "instance deleted"); + while ((s = fst_session_global_get_first_by_group(group)) != NULL) + fst_session_delete(s); + os_free(group); +} + + +Boolean fst_group_delete_if_empty(struct fst_group *group) +{ + Boolean is_empty = !fst_group_has_ifaces(group) && + !fst_session_global_get_first_by_group(group); + + if (is_empty) + fst_group_delete(group); + + return is_empty; +} + + +void fst_group_update_ie(struct fst_group *g, Boolean cleaning_up) +{ + struct fst_iface *i; + + foreach_fst_group_iface(g, i) { + if (!cleaning_up) { + struct wpabuf *mbie = fst_group_create_mb_ie(g, i); + + if (!mbie) + fst_printf_iface(i, MSG_WARNING, + "cannot create MB IE"); + + fst_iface_attach_mbie(i, mbie); + fst_iface_set_ies(i, mbie); + fst_printf_iface(i, MSG_DEBUG, + "multi-band IE set to %p", mbie); + } else { + fst_iface_attach_mbie(i, NULL); + fst_iface_set_ies(i, NULL); + } + } +} diff --git a/src/fst/fst_group.h b/src/fst/fst_group.h new file mode 100644 index 000000000..4f33599d4 --- /dev/null +++ b/src/fst/fst_group.h @@ -0,0 +1,79 @@ +/* + * FST module - FST group object definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_GROUP_H +#define FST_GROUP_H + +struct fst_group { + char group_id[IFNAMSIZ + 1]; + struct dl_list ifaces; + u32 dialog_token; + u32 fsts_id; + struct dl_list global_groups_lentry; +}; + +struct session_transition_ie; + +#define foreach_fst_group_iface(g, i) \ + dl_list_for_each((i), &(g)->ifaces, struct fst_iface, group_lentry) + +struct fst_group * fst_group_create(const char *group_id); +void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i); +void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i); +void fst_group_delete(struct fst_group *g); + +void fst_group_update_ie(struct fst_group *g, Boolean cleaning_up); + +static inline Boolean fst_group_has_ifaces(struct fst_group *g) +{ + return !dl_list_empty(&g->ifaces); +} + +static inline struct fst_iface * fst_group_first_iface(struct fst_group *g) +{ + if (dl_list_empty(&g->ifaces)) + return NULL; + return dl_list_first(&g->ifaces, struct fst_iface, group_lentry); +} + +static inline const char * fst_group_get_id(struct fst_group *g) +{ + return g->group_id; +} + +Boolean fst_group_delete_if_empty(struct fst_group *group); +struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g, + const char *ifname); +struct fst_iface * +fst_group_find_new_iface_by_stie(struct fst_group *g, + struct fst_iface *iface, + const u8 *peer_addr, + const struct session_transition_ie *stie, + u8 *iface_peer_addr); +struct fst_iface * +fst_group_get_new_iface_by_stie_and_mbie( + struct fst_group *g, const u8 *mb_ies_buff, size_t mb_ies_size, + const struct session_transition_ie *stie, u8 *iface_peer_addr); +u8 fst_group_assign_dialog_token(struct fst_group *g); +u32 fst_group_assign_fsts_id(struct fst_group *g); + +extern struct dl_list fst_global_groups_list; + +#define foreach_fst_group(g) \ + dl_list_for_each((g), &fst_global_groups_list, \ + struct fst_group, global_groups_lentry) + +static inline struct fst_group * fst_first_group(void) +{ + if (dl_list_empty(&fst_global_groups_list)) + return NULL; + return dl_list_first(&fst_global_groups_list, struct fst_group, + global_groups_lentry); +} + +#endif /* FST_GROUP_H */ diff --git a/src/fst/fst_iface.c b/src/fst/fst_iface.c new file mode 100644 index 000000000..4fba9b1a5 --- /dev/null +++ b/src/fst/fst_iface.c @@ -0,0 +1,78 @@ +/* + * FST module - FST interface object implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include "utils/common.h" +#include "fst/fst_internal.h" +#include "fst/fst_defs.h" + + +struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname, + const u8 *own_addr, + const struct fst_wpa_obj *iface_obj, + const struct fst_iface_cfg *cfg) +{ + struct fst_iface *i; + + i = os_zalloc(sizeof(*i)); + if (!i) { + fst_printf_group(g, MSG_ERROR, "cannot allocate iface for %s", + ifname); + return NULL; + } + + i->cfg = *cfg; + i->iface_obj = *iface_obj; + i->group = g; + os_strlcpy(i->ifname, ifname, sizeof(i->ifname)); + os_memcpy(i->own_addr, own_addr, sizeof(i->own_addr)); + + if (!i->cfg.llt) { + fst_printf_iface(i, MSG_WARNING, "Zero llt adjusted"); + i->cfg.llt = FST_DEFAULT_LLT_CFG_VALUE; + } + + return i; +} + + +void fst_iface_delete(struct fst_iface *i) +{ + wpabuf_free(i->mb_ie); + os_free(i); +} + + +Boolean fst_iface_is_connected(struct fst_iface *iface, const u8 *addr) +{ + struct fst_get_peer_ctx *ctx; + const u8 *a = fst_iface_get_peer_first(iface, &ctx, TRUE); + + for (; a != NULL; a = fst_iface_get_peer_next(iface, &ctx, TRUE)) + if (os_memcmp(addr, a, ETH_ALEN) == 0) + return TRUE; + + return FALSE; +} + + +void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie) +{ + wpabuf_free(i->mb_ie); + i->mb_ie = mbie; +} + + +enum mb_band_id fst_iface_get_band_id(struct fst_iface *i) +{ + enum hostapd_hw_mode hw_mode; + u8 channel; + + fst_iface_get_channel_info(i, &hw_mode, &channel); + return fst_hw_mode_to_band(hw_mode); +} diff --git a/src/fst/fst_iface.h b/src/fst/fst_iface.h new file mode 100644 index 000000000..4ccea8eed --- /dev/null +++ b/src/fst/fst_iface.h @@ -0,0 +1,135 @@ +/* + * FST module - FST interface object definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + + +#ifndef FST_IFACE_H +#define FST_IFACE_H + +#include "utils/includes.h" +#include "utils/common.h" +#include "list.h" +#include "fst.h" + +struct fst_iface { + struct fst_group *group; + struct fst_wpa_obj iface_obj; + u8 own_addr[ETH_ALEN]; + struct wpabuf *mb_ie; + char ifname[IFNAMSIZ + 1]; + struct fst_iface_cfg cfg; + struct dl_list group_lentry; +}; + +struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname, + const u8 *own_addr, + const struct fst_wpa_obj *iface_obj, + const struct fst_iface_cfg *cfg); +void fst_iface_delete(struct fst_iface *i); + +static inline struct fst_group * fst_iface_get_group(struct fst_iface *i) +{ + return i->group; +} + +static inline const char * fst_iface_get_name(struct fst_iface *i) +{ + return i->ifname; +} + +static inline const u8 * fst_iface_get_addr(struct fst_iface *i) +{ + return i->own_addr; +} + +static inline const char * fst_iface_get_group_id(struct fst_iface *i) +{ + return i->cfg.group_id; +} + +static inline u8 fst_iface_get_priority(struct fst_iface *i) +{ + return i->cfg.priority; +} + +static inline u32 fst_iface_get_llt(struct fst_iface *i) +{ + return i->cfg.llt; +} + +static inline const struct wpabuf * fst_iface_get_mbie(struct fst_iface *i) +{ + return i->mb_ie; +} + +static inline const u8 * fst_iface_get_bssid(struct fst_iface *i) +{ + return i->iface_obj.get_bssid(i->iface_obj.ctx); +} + +static inline void fst_iface_get_channel_info(struct fst_iface *i, + enum hostapd_hw_mode *hw_mode, + u8 *channel) +{ + i->iface_obj.get_channel_info(i->iface_obj.ctx, hw_mode, channel); +} + +static inline int fst_iface_get_hw_modes(struct fst_iface *i, + struct hostapd_hw_modes **modes) +{ + return i->iface_obj.get_hw_modes(i->iface_obj.ctx, modes); +} + +static inline void fst_iface_set_ies(struct fst_iface *i, + struct wpabuf *fst_ies) +{ + i->iface_obj.set_ies(i->iface_obj.ctx, fst_ies); +} + +static inline int fst_iface_send_action(struct fst_iface *i, + const u8 *addr, struct wpabuf *data) +{ + return i->iface_obj.send_action(i->iface_obj.ctx, addr, data); +} + +static inline struct wpabuf * fst_iface_get_peer_mb_ie(struct fst_iface *i, + const u8 *addr) +{ + return i->iface_obj.get_mb_ie(i->iface_obj.ctx, addr); +} + +static inline void fst_iface_update_mb_ie(struct fst_iface *i, + const u8 *addr, + const u8 *buf, size_t size) +{ + return i->iface_obj.update_mb_ie(i->iface_obj.ctx, addr, buf, size); +} + +static inline const u8 * fst_iface_get_peer_first(struct fst_iface *i, + struct fst_get_peer_ctx **ctx, + Boolean mb_only) +{ + return i->iface_obj.get_peer_first(i->iface_obj.ctx, ctx, mb_only); +} + +static inline const u8 * fst_iface_get_peer_next(struct fst_iface *i, + struct fst_get_peer_ctx **ctx, + Boolean mb_only) +{ + return i->iface_obj.get_peer_next(i->iface_obj.ctx, ctx, mb_only); +} + +Boolean fst_iface_is_connected(struct fst_iface *iface, const u8 *addr); +void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie); +enum mb_band_id fst_iface_get_band_id(struct fst_iface *i); + +static inline void * fst_iface_get_wpa_obj_ctx(struct fst_iface *i) +{ + return i->iface_obj.ctx; +} + +#endif /* FST_IFACE_H */ diff --git a/src/fst/fst_internal.h b/src/fst/fst_internal.h new file mode 100644 index 000000000..948c5568e --- /dev/null +++ b/src/fst/fst_internal.h @@ -0,0 +1,88 @@ +/* + * FST module - auxiliary definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_INTERNAL_H +#define FST_INTERNAL_H + +#include "utils/includes.h" +#include "utils/common.h" +#include "common/defs.h" +#include "common/ieee802_11_defs.h" +#include "fst/fst_iface.h" +#include "fst/fst_group.h" +#include "fst/fst_session.h" + +#define fst_printf(level, format, ...) \ + wpa_printf((level), "FST: " format, ##__VA_ARGS__) + +#define fst_printf_group(group, level, format, ...) \ + wpa_printf((level), "FST: %s: " format, \ + fst_group_get_id(group), ##__VA_ARGS__) + +#define fst_printf_iface(iface, level, format, ...) \ + fst_printf_group(fst_iface_get_group(iface), (level), "%s: " format, \ + fst_iface_get_name(iface), ##__VA_ARGS__) + +static inline enum mb_band_id +fst_hw_mode_to_band(enum hostapd_hw_mode mode) +{ + switch (mode) { + case HOSTAPD_MODE_IEEE80211B: + case HOSTAPD_MODE_IEEE80211G: + return MB_BAND_ID_WIFI_2_4GHZ; + case HOSTAPD_MODE_IEEE80211A: + return MB_BAND_ID_WIFI_5GHZ; + case HOSTAPD_MODE_IEEE80211AD: + return MB_BAND_ID_WIFI_60GHZ; + default: + WPA_ASSERT(0); + return MB_BAND_ID_WIFI_2_4GHZ; + } +} + +#define IE_HEADER_SIZE ((u8) (2 * sizeof(u8))) +#define IE_BUFFER_LENGTH(ie_len_val) ((size_t) ((ie_len_val) + IE_HEADER_SIZE)) + +static inline const u8 * +fst_mbie_get_peer_addr(const struct multi_band_ie *mbie) +{ + const u8 *peer_addr = NULL; + + switch (MB_CTRL_ROLE(mbie->mb_ctrl)) { + case MB_STA_ROLE_AP: + peer_addr = mbie->bssid; + break; + case MB_STA_ROLE_NON_PCP_NON_AP: + if (mbie->mb_ctrl & MB_CTRL_STA_MAC_PRESENT && + IE_BUFFER_LENGTH(mbie->len) >= sizeof(*mbie) + ETH_ALEN) + peer_addr = (const u8 *) &mbie[1]; + break; + default: + break; + } + + return peer_addr; +} + +struct fst_ctrl_handle { + struct fst_ctrl ctrl; + struct dl_list global_ctrls_lentry; +}; + +extern struct dl_list fst_global_ctrls_list; + +#define foreach_fst_ctrl_call(clb, ...) \ + do { \ + struct fst_ctrl_handle *__fst_ctrl_h; \ + dl_list_for_each(__fst_ctrl_h, &fst_global_ctrls_list, \ + struct fst_ctrl_handle, global_ctrls_lentry) \ + if (__fst_ctrl_h->ctrl.clb) \ + __fst_ctrl_h->ctrl.clb(__VA_ARGS__);\ + } while (0) + +#endif /* FST_INTERNAL_H */ diff --git a/src/fst/fst_session.c b/src/fst/fst_session.c new file mode 100644 index 000000000..da994d8ea --- /dev/null +++ b/src/fst/fst_session.c @@ -0,0 +1,1274 @@ +/* + * FST module - FST Session implementation + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/defs.h" +#include "fst/fst_internal.h" +#include "fst/fst_defs.h" +#include "fst/fst_ctrl_iface.h" + +#define US_80211_TU 1024 + +#define US_TO_TU(m) ((m) * / US_80211_TU) +#define TU_TO_US(m) ((m) * US_80211_TU) + +#define FST_LLT_SWITCH_IMMEDIATELY 0 + +#define fst_printf_session(s, level, format, ...) \ + fst_printf((level), "%u (0x%08x): [" MACSTR "," MACSTR "] :" format, \ + (s)->id, (s)->data.fsts_id, \ + MAC2STR((s)->data.old_peer_addr), \ + MAC2STR((s)->data.new_peer_addr), \ + ##__VA_ARGS__) + +#define fst_printf_siface(s, iface, level, format, ...) \ + fst_printf_session((s), (level), "%s: " format, \ + fst_iface_get_name(iface), ##__VA_ARGS__) + +#define fst_printf_sframe(s, is_old, level, format, ...) \ + fst_printf_siface((s), \ + (is_old) ? (s)->data.old_iface : (s)->data.new_iface, \ + (level), format, ##__VA_ARGS__) + +#define FST_LLT_MS_DEFAULT 50 +#define FST_ACTION_MAX_SUPPORTED FST_ACTION_ON_CHANNEL_TUNNEL + +const char * const fst_action_names[] = { + [FST_ACTION_SETUP_REQUEST] = "Setup Request", + [FST_ACTION_SETUP_RESPONSE] = "Setup Response", + [FST_ACTION_TEAR_DOWN] = "Tear Down", + [FST_ACTION_ACK_REQUEST] = "Ack Request", + [FST_ACTION_ACK_RESPONSE] = "Ack Response", + [FST_ACTION_ON_CHANNEL_TUNNEL] = "On Channel Tunnel", +}; + +struct fst_session { + struct { + /* Session configuration that can be zeroed on reset */ + u8 old_peer_addr[ETH_ALEN]; + u8 new_peer_addr[ETH_ALEN]; + struct fst_iface *new_iface; + struct fst_iface *old_iface; + u32 llt_ms; + u8 pending_setup_req_dlgt; + u32 fsts_id; /* FSTS ID, see spec, 8.4.2.147 + * Session Transition element */ + } data; + /* Session object internal fields which won't be zeroed on reset */ + struct dl_list global_sessions_lentry; + u32 id; /* Session object ID used to identify + * specific session object */ + struct fst_group *group; + enum fst_session_state state; + Boolean stt_armed; +}; + +static struct dl_list global_sessions_list; +static u32 global_session_id = 0; + +#define foreach_fst_session(s) \ + dl_list_for_each((s), &global_sessions_list, \ + struct fst_session, global_sessions_lentry) + +#define foreach_fst_session_safe(s, temp) \ + dl_list_for_each_safe((s), (temp), &global_sessions_list, \ + struct fst_session, global_sessions_lentry) + + +static void fst_session_global_inc_id(void) +{ + global_session_id++; + if (global_session_id == FST_INVALID_SESSION_ID) + global_session_id++; +} + + +int fst_session_global_init(void) +{ + dl_list_init(&global_sessions_list); + return 0; +} + + +void fst_session_global_deinit(void) +{ + WPA_ASSERT(dl_list_empty(&global_sessions_list)); +} + + +static inline void fst_session_notify_ctrl(struct fst_session *s, + enum fst_event_type event_type, + union fst_event_extra *extra) +{ + foreach_fst_ctrl_call(on_event, event_type, NULL, s, extra); +} + + +static void fst_session_set_state(struct fst_session *s, + enum fst_session_state state, + union fst_session_state_switch_extra *extra) +{ + if (s->state != state) { + union fst_event_extra evext = { + .session_state = { + .old_state = s->state, + .new_state = state, + }, + }; + + if (extra) + evext.session_state.extra = *extra; + fst_session_notify_ctrl(s, EVENT_FST_SESSION_STATE_CHANGED, + &evext); + fst_printf_session(s, MSG_INFO, "State: %s => %s", + fst_session_state_name(s->state), + fst_session_state_name(state)); + s->state = state; + } +} + + +static u32 fst_find_free_session_id(void) +{ + u32 i, id = FST_INVALID_SESSION_ID; + struct fst_session *s; + + for (i = 0; i < (u32) -1; i++) { + Boolean in_use = FALSE; + + foreach_fst_session(s) { + if (s->id == global_session_id) { + fst_session_global_inc_id(); + in_use = TRUE; + break; + } + } + if (!in_use) { + id = global_session_id; + fst_session_global_inc_id(); + break; + } + } + + return id; +} + + +static void fst_session_timeout_handler(void *eloop_data, void *user_ctx) +{ + struct fst_session *s = user_ctx; + union fst_session_state_switch_extra extra = { + .to_initial = { + .reason = REASON_STT, + }, + }; + + fst_printf_session(s, MSG_WARNING, "Session State Timeout"); + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &extra); +} + + +static void fst_session_stt_arm(struct fst_session *s) +{ + eloop_register_timeout(0, TU_TO_US(FST_DEFAULT_SESSION_TIMEOUT_TU), + fst_session_timeout_handler, NULL, s); + s->stt_armed = TRUE; +} + + +static void fst_session_stt_disarm(struct fst_session *s) +{ + if (s->stt_armed) { + eloop_cancel_timeout(fst_session_timeout_handler, NULL, s); + s->stt_armed = FALSE; + } +} + + +static Boolean fst_session_is_in_transition(struct fst_session *s) +{ + /* See spec, 10.32.2.2 Transitioning between states */ + return s->stt_armed; +} + + +static int fst_session_is_in_progress(struct fst_session *s) +{ + return s->state != FST_SESSION_STATE_INITIAL; +} + + +static int fst_session_is_ready_pending(struct fst_session *s) +{ + return s->state == FST_SESSION_STATE_SETUP_COMPLETION && + fst_session_is_in_transition(s); +} + + +static int fst_session_is_ready(struct fst_session *s) +{ + return s->state == FST_SESSION_STATE_SETUP_COMPLETION && + !fst_session_is_in_transition(s); +} + + +static int fst_session_is_switch_requested(struct fst_session *s) +{ + return s->state == FST_SESSION_STATE_TRANSITION_DONE && + fst_session_is_in_transition(s); +} + + +static struct fst_session * +fst_find_session_in_progress(const u8 *peer_addr, struct fst_group *g) +{ + struct fst_session *s; + + foreach_fst_session(s) { + if (s->group == g && + (os_memcmp(s->data.old_peer_addr, peer_addr, + ETH_ALEN) == 0 || + os_memcmp(s->data.new_peer_addr, peer_addr, + ETH_ALEN) == 0) && + fst_session_is_in_progress(s)) + return s; + } + + return NULL; +} + + +static void fst_session_reset_ex(struct fst_session *s, enum fst_reason reason) +{ + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = reason, + }, + }; + + if (s->state == FST_SESSION_STATE_SETUP_COMPLETION || + s->state == FST_SESSION_STATE_TRANSITION_DONE) + fst_session_tear_down_setup(s); + fst_session_stt_disarm(s); + os_memset(&s->data, 0, sizeof(s->data)); + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); +} + + +static int fst_session_send_action(struct fst_session *s, Boolean old_iface, + const void *payload, size_t size, + const struct wpabuf *extra_buf) +{ + size_t len; + int res; + struct wpabuf *buf; + u8 action; + struct fst_iface *iface = + old_iface ? s->data.old_iface : s->data.new_iface; + + WPA_ASSERT(payload != NULL); + WPA_ASSERT(size != 0); + + action = *(const u8 *) payload; + + WPA_ASSERT(action <= FST_ACTION_MAX_SUPPORTED); + + if (!iface) { + fst_printf_session(s, MSG_ERROR, + "no %s interface for FST Action '%s' sending", + old_iface ? "old" : "new", + fst_action_names[action]); + return -1; + } + + len = sizeof(u8) /* category */ + size; + if (extra_buf) + len += wpabuf_size(extra_buf); + + buf = wpabuf_alloc(len); + if (!buf) { + fst_printf_session(s, MSG_ERROR, + "cannot allocate buffer of %zu bytes for FST Action '%s' sending", + len, fst_action_names[action]); + return -1; + } + + wpabuf_put_u8(buf, WLAN_ACTION_FST); + wpabuf_put_data(buf, payload, size); + if (extra_buf) + wpabuf_put_buf(buf, extra_buf); + + res = fst_iface_send_action(iface, + old_iface ? s->data.old_peer_addr : + s->data.new_peer_addr, buf); + if (res < 0) + fst_printf_siface(s, iface, MSG_ERROR, + "failed to send FST Action '%s'", + fst_action_names[action]); + else + fst_printf_siface(s, iface, MSG_DEBUG, "FST Action '%s' sent", + fst_action_names[action]); + wpabuf_free(buf); + + return res; +} + + +static int fst_session_send_tear_down(struct fst_session *s) +{ + struct fst_tear_down td; + int res; + + if (!fst_session_is_in_progress(s)) { + fst_printf_session(s, MSG_ERROR, "No FST setup to tear down"); + return -1; + } + + WPA_ASSERT(s->data.old_iface != NULL); + WPA_ASSERT(s->data.new_iface != NULL); + + os_memset(&td, 0, sizeof(td)); + + td.action = FST_ACTION_TEAR_DOWN; + td.fsts_id = host_to_le32(s->data.fsts_id); + + res = fst_session_send_action(s, TRUE, &td, sizeof(td), NULL); + if (!res) + fst_printf_sframe(s, TRUE, MSG_INFO, "FST TearDown sent"); + else + fst_printf_sframe(s, TRUE, MSG_ERROR, + "failed to send FST TearDown"); + + return res; +} + + +static void fst_session_handle_setup_request(struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + struct fst_session *s; + const struct fst_setup_req *req = + (const struct fst_setup_req *) &mgmt->u.action.u.fst_action; + struct fst_iface *new_iface = NULL; + struct fst_group *g; + u8 new_iface_peer_addr[ETH_ALEN]; + struct wpabuf *peer_mbies; + + if (frame_len < sizeof(*req)) { + fst_printf_iface(iface, MSG_WARNING, + "FST Request dropped: too short (%zu < %zu)", + frame_len, sizeof(*req)); + return; + } + + if (req->stie.new_band_id == req->stie.old_band_id) { + fst_printf_iface(iface, MSG_WARNING, + "FST Request dropped: new and old band IDs are the same"); + return; + } + + g = fst_iface_get_group(iface); + + if (frame_len > sizeof(*req)) { + fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1), + frame_len - sizeof(*req)); + fst_printf_iface(iface, MSG_INFO, + "FST Request: MB IEs updated for " MACSTR, + MAC2STR(mgmt->sa)); + } + + peer_mbies = fst_iface_get_peer_mb_ie(iface, mgmt->sa); + if (peer_mbies) { + new_iface = fst_group_get_new_iface_by_stie_and_mbie( + g, wpabuf_head(peer_mbies), wpabuf_len(peer_mbies), + &req->stie, new_iface_peer_addr); + if (new_iface) + fst_printf_iface(iface, MSG_INFO, + "FST Request: new iface (%s:" MACSTR + ") found by MB IEs", + fst_iface_get_name(new_iface), + MAC2STR(new_iface_peer_addr)); + } + + if (!new_iface) { + new_iface = fst_group_find_new_iface_by_stie( + g, iface, mgmt->sa, &req->stie, + new_iface_peer_addr); + if (new_iface) + fst_printf_iface(iface, MSG_INFO, + "FST Request: new iface (%s:" MACSTR + ") found by others", + fst_iface_get_name(new_iface), + MAC2STR(new_iface_peer_addr)); + } + + if (!new_iface) { + fst_printf_iface(iface, MSG_WARNING, + "FST Request dropped: new iface not found"); + return; + } + + s = fst_find_session_in_progress(mgmt->sa, g); + if (s) { + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_SETUP, + }, + }; + + /* + * 10.32.2.2 Transitioning between states: + * Upon receipt of an FST Setup Request frame, the responder + * shall respond with an FST Setup Response frame unless it has + * a pending FST Setup Request frame addressed to the initiator + * and the responder has a numerically larger MAC address than + * the initiator’s MAC address, in which case, the responder + * shall delete the received FST Setup Request. + */ + if (os_memcmp(mgmt->da, mgmt->sa, ETH_ALEN) > 0) { + fst_printf_session(s, MSG_WARNING, + "FST Request dropped due to MAC comparison (our MAC is " + MACSTR ")", + MAC2STR(mgmt->da)); + return; + } + + if (!fst_session_is_ready_pending(s)) { + fst_printf_session(s, MSG_WARNING, + "FST Request from " MACSTR + " dropped due to inappropriate state %s", + MAC2STR(mgmt->da), + fst_session_state_name(s->state)); + return; + } + + + /* + * If FST Setup Request arrived with the same FSTS ID as one we + * initialized before, it means the other side either didn't + * receive our FST Request or skipped it for some reason (for + * example, due to numerical MAC comparison). + * + * In this case, there's no need to tear down the session. + * Moreover, as FSTS ID is the same, the other side will + * associate this tear down with the session it initiated that + * will break the sync. + */ + if (le_to_host32(req->stie.fsts_id) != s->data.fsts_id) + fst_session_send_tear_down(s); + else + fst_printf_session(s, MSG_WARNING, + "Skipping TearDown as the FST request has the same FSTS ID as initiated"); + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + fst_session_stt_disarm(s); + fst_printf_session(s, MSG_WARNING, "reset due to FST request"); + } + + s = fst_session_create(g); + if (!s) { + fst_printf(MSG_WARNING, + "FST Request dropped: cannot create session for %s and %s", + fst_iface_get_name(iface), + fst_iface_get_name(new_iface)); + return; + } + + fst_session_set_iface(s, iface, TRUE); + fst_session_set_peer_addr(s, mgmt->sa, TRUE); + fst_session_set_iface(s, new_iface, FALSE); + fst_session_set_peer_addr(s, new_iface_peer_addr, FALSE); + fst_session_set_llt(s, FST_LLT_VAL_TO_MS(le_to_host32(req->llt))); + s->data.pending_setup_req_dlgt = req->dialog_token; + s->data.fsts_id = le_to_host32(req->stie.fsts_id); + + fst_session_stt_arm(s); + + fst_session_notify_ctrl(s, EVENT_FST_SETUP, NULL); + + fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION, NULL); +} + + +static void fst_session_handle_setup_response(struct fst_session *s, + struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + const struct fst_setup_res *res = + (const struct fst_setup_res *) &mgmt->u.action.u.fst_action; + enum hostapd_hw_mode hw_mode; + u8 channel; + union fst_session_state_switch_extra evext = { + .to_initial = {0}, + }; + + if (iface != s->data.old_iface) { + fst_printf_session(s, MSG_WARNING, + "FST Response dropped: %s is not the old iface", + fst_iface_get_name(iface)); + return; + } + + if (!fst_session_is_ready_pending(s)) { + fst_printf_session(s, MSG_WARNING, + "FST Response dropped due to wrong state: %s", + fst_session_state_name(s->state)); + return; + } + + if (res->dialog_token != s->data.pending_setup_req_dlgt) { + fst_printf_session(s, MSG_WARNING, + "FST Response dropped due to wrong dialog token (%u != %u)", + s->data.pending_setup_req_dlgt, + res->dialog_token); + return; + } + + if (res->status_code == WLAN_STATUS_SUCCESS && + le_to_host32(res->stie.fsts_id) != s->data.fsts_id) { + fst_printf_session(s, MSG_WARNING, + "FST Response dropped due to wrong FST Session ID (%u)", + le_to_host32(res->stie.fsts_id)); + return; + } + + fst_session_stt_disarm(s); + + if (res->status_code != WLAN_STATUS_SUCCESS) { + /* + * 10.32.2.2 Transitioning between states + * The initiator shall set the STT to the value of the + * FSTSessionTimeOut field at ... and at each ACK frame sent in + * response to a received FST Setup Response with the Status + * Code field equal to PENDING_ADMITTING_FST_SESSION or + * PENDING_GAP_IN_BA_WINDOW. + */ + evext.to_initial.reason = REASON_REJECT; + evext.to_initial.reject_code = res->status_code; + evext.to_initial.initiator = FST_INITIATOR_REMOTE; + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + fst_printf_session(s, MSG_WARNING, + "FST Setup rejected by remote side with status %u", + res->status_code); + return; + } + + fst_iface_get_channel_info(s->data.new_iface, &hw_mode, &channel); + + if (fst_hw_mode_to_band(hw_mode) != res->stie.new_band_id) { + evext.to_initial.reason = REASON_ERROR_PARAMS; + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + fst_printf_session(s, MSG_WARNING, + "invalid FST Setup parameters"); + fst_session_tear_down_setup(s); + return; + } + + fst_printf_session(s, MSG_INFO, + "%s: FST Setup established for %s (llt=%u)", + fst_iface_get_name(s->data.old_iface), + fst_iface_get_name(s->data.new_iface), + s->data.llt_ms); + + fst_session_notify_ctrl(s, EVENT_FST_ESTABLISHED, NULL); + + if (s->data.llt_ms == FST_LLT_SWITCH_IMMEDIATELY) + fst_session_initiate_switch(s); +} + + +static void fst_session_handle_tear_down(struct fst_session *s, + struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + const struct fst_tear_down *td = + (const struct fst_tear_down *) &mgmt->u.action.u.fst_action; + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_TEARDOWN, + .initiator = FST_INITIATOR_REMOTE, + }, + }; + + if (!fst_session_is_in_progress(s)) { + fst_printf_session(s, MSG_WARNING, "no FST Setup to tear down"); + return; + } + + if (le_to_host32(td->fsts_id) != s->data.fsts_id) { + fst_printf_siface(s, iface, MSG_WARNING, + "tear down for wrong FST Setup ID (%u)", + le_to_host32(td->fsts_id)); + return; + } + + fst_session_stt_disarm(s); + + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); +} + + +static void fst_session_handle_ack_request(struct fst_session *s, + struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + const struct fst_ack_req *req = + (const struct fst_ack_req *) &mgmt->u.action.u.fst_action; + struct fst_ack_res res; + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_SWITCH, + .initiator = FST_INITIATOR_REMOTE, + }, + }; + + if (!fst_session_is_ready(s) && !fst_session_is_switch_requested(s)) { + fst_printf_siface(s, iface, MSG_ERROR, + "cannot initiate switch due to wrong session state (%s)", + fst_session_state_name(s->state)); + return; + } + + WPA_ASSERT(s->data.new_iface != NULL); + + if (iface != s->data.new_iface) { + fst_printf_siface(s, iface, MSG_ERROR, + "Ack received on wrong interface"); + return; + } + + if (le_to_host32(req->fsts_id) != s->data.fsts_id) { + fst_printf_siface(s, iface, MSG_WARNING, + "Ack for wrong FST Setup ID (%u)", + le_to_host32(req->fsts_id)); + return; + } + + os_memset(&res, 0, sizeof(res)); + + res.action = FST_ACTION_ACK_RESPONSE; + res.dialog_token = req->dialog_token; + res.fsts_id = req->fsts_id; + + if (!fst_session_send_action(s, FALSE, &res, sizeof(res), NULL)) { + fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Response sent"); + fst_session_stt_disarm(s); + fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE, + NULL); + fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED, + NULL); + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + } +} + + +static void +fst_session_handle_ack_response(struct fst_session *s, + struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + const struct fst_ack_res *res = + (const struct fst_ack_res *) &mgmt->u.action.u.fst_action; + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_SWITCH, + .initiator = FST_INITIATOR_LOCAL, + }, + }; + + if (!fst_session_is_switch_requested(s)) { + fst_printf_siface(s, iface, MSG_ERROR, + "Ack Response in inappropriate session state (%s)", + fst_session_state_name(s->state)); + return; + } + + WPA_ASSERT(s->data.new_iface != NULL); + + if (iface != s->data.new_iface) { + fst_printf_siface(s, iface, MSG_ERROR, + "Ack response received on wrong interface"); + return; + } + + if (le_to_host32(res->fsts_id) != s->data.fsts_id) { + fst_printf_siface(s, iface, MSG_ERROR, + "Ack response for wrong FST Setup ID (%u)", + le_to_host32(res->fsts_id)); + return; + } + + fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED, NULL); + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + + fst_session_stt_disarm(s); +} + + +struct fst_session * fst_session_create(struct fst_group *g) +{ + struct fst_session *s; + u32 id; + + WPA_ASSERT(!is_zero_ether_addr(own_addr)); + + id = fst_find_free_session_id(); + if (id == FST_INVALID_SESSION_ID) { + fst_printf(MSG_ERROR, "Cannot assign new session ID"); + return NULL; + } + + s = os_zalloc(sizeof(*s)); + if (!s) { + fst_printf(MSG_ERROR, "Cannot allocate new session object"); + return NULL; + } + + s->id = id; + s->group = g; + s->state = FST_SESSION_STATE_INITIAL; + + s->data.llt_ms = FST_LLT_MS_DEFAULT; + + fst_printf(MSG_INFO, "Session %u created", s->id); + + dl_list_add_tail(&global_sessions_list, &s->global_sessions_lentry); + + foreach_fst_ctrl_call(on_session_added, s); + + return s; +} + + +void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface, + Boolean is_old) +{ + if (is_old) + s->data.old_iface = iface; + else + s->data.new_iface = iface; + +} + + +void fst_session_set_llt(struct fst_session *s, u32 llt) +{ + s->data.llt_ms = llt; +} + + +void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr, + Boolean is_old) +{ + u8 *a = is_old ? s->data.old_peer_addr : s->data.new_peer_addr; + + os_memcpy(a, addr, ETH_ALEN); +} + + +int fst_session_initiate_setup(struct fst_session *s) +{ + struct fst_setup_req req; + int res; + u32 fsts_id; + u8 dialog_token; + struct fst_session *_s; + + if (fst_session_is_in_progress(s)) { + fst_printf_session(s, MSG_ERROR, "Session in progress"); + return -EINVAL; + } + + if (is_zero_ether_addr(s->data.old_peer_addr)) { + fst_printf_session(s, MSG_ERROR, "No old peer MAC address"); + return -EINVAL; + } + + if (is_zero_ether_addr(s->data.new_peer_addr)) { + fst_printf_session(s, MSG_ERROR, "No new peer MAC address"); + return -EINVAL; + } + + if (!s->data.old_iface) { + fst_printf_session(s, MSG_ERROR, "No old interface defined"); + return -EINVAL; + } + + if (!s->data.new_iface) { + fst_printf_session(s, MSG_ERROR, "No new interface defined"); + return -EINVAL; + } + + if (s->data.new_iface == s->data.old_iface) { + fst_printf_session(s, MSG_ERROR, + "Same interface set as old and new"); + return -EINVAL; + } + + if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr)) { + fst_printf_session(s, MSG_ERROR, + "The preset old peer address is not connected"); + return -EINVAL; + } + + if (!fst_iface_is_connected(s->data.new_iface, s->data.new_peer_addr)) { + fst_printf_session(s, MSG_ERROR, + "The preset new peer address is not connected"); + return -EINVAL; + } + + _s = fst_find_session_in_progress(s->data.old_peer_addr, s->group); + if (_s) { + fst_printf_session(s, MSG_ERROR, + "There is another session in progress (old): %u", + _s->id); + return -EINVAL; + } + + _s = fst_find_session_in_progress(s->data.new_peer_addr, s->group); + if (_s) { + fst_printf_session(s, MSG_ERROR, + "There is another session in progress (new): %u", + _s->id); + return -EINVAL; + } + + dialog_token = fst_group_assign_dialog_token(s->group); + fsts_id = fst_group_assign_fsts_id(s->group); + + os_memset(&req, 0, sizeof(req)); + + fst_printf_siface(s, s->data.old_iface, MSG_INFO, + "initiating FST setup for %s (llt=%u ms)", + fst_iface_get_name(s->data.new_iface), s->data.llt_ms); + + req.action = FST_ACTION_SETUP_REQUEST; + req.dialog_token = dialog_token; + req.llt = host_to_le32(FST_LLT_MS_TO_VAL(s->data.llt_ms)); + /* 8.4.2.147 Session Transition element */ + req.stie.element_id = WLAN_EID_SESSION_TRANSITION; + req.stie.length = sizeof(req.stie); + req.stie.fsts_id = host_to_le32(fsts_id); + req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0); + + req.stie.new_band_id = fst_iface_get_band_id(s->data.new_iface); + req.stie.new_band_op = 1; + req.stie.new_band_setup = 0; + + req.stie.old_band_id = fst_iface_get_band_id(s->data.old_iface); + req.stie.old_band_op = 1; + req.stie.old_band_setup = 0; + + res = fst_session_send_action(s, TRUE, &req, sizeof(req), + fst_iface_get_mbie(s->data.old_iface)); + if (!res) { + s->data.fsts_id = fsts_id; + s->data.pending_setup_req_dlgt = dialog_token; + fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Request sent"); + fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION, + NULL); + + fst_session_stt_arm(s); + } + + return res; +} + + +int fst_session_respond(struct fst_session *s, u8 status_code) +{ + struct fst_setup_res res; + enum hostapd_hw_mode hw_mode; + u8 channel; + + if (!fst_session_is_ready_pending(s)) { + fst_printf_session(s, MSG_ERROR, "incorrect state: %s", + fst_session_state_name(s->state)); + return -EINVAL; + } + + if (is_zero_ether_addr(s->data.old_peer_addr)) { + fst_printf_session(s, MSG_ERROR, "No peer MAC address"); + return -EINVAL; + } + + if (!s->data.old_iface) { + fst_printf_session(s, MSG_ERROR, "No old interface defined"); + return -EINVAL; + } + + if (!s->data.new_iface) { + fst_printf_session(s, MSG_ERROR, "No new interface defined"); + return -EINVAL; + } + + if (s->data.new_iface == s->data.old_iface) { + fst_printf_session(s, MSG_ERROR, + "Same interface set as old and new"); + return -EINVAL; + } + + if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr)) { + fst_printf_session(s, MSG_ERROR, + "The preset peer address is not in the peer list"); + return -EINVAL; + } + + fst_session_stt_disarm(s); + + os_memset(&res, 0, sizeof(res)); + + res.action = FST_ACTION_SETUP_RESPONSE; + res.dialog_token = s->data.pending_setup_req_dlgt; + res.status_code = status_code; + + if (status_code == WLAN_STATUS_SUCCESS) { + res.stie.element_id = WLAN_EID_SESSION_TRANSITION; + res.stie.length = sizeof(res.stie); + res.stie.fsts_id = s->data.fsts_id; + res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0); + + fst_iface_get_channel_info(s->data.new_iface, &hw_mode, + &channel); + res.stie.new_band_id = fst_hw_mode_to_band(hw_mode); + res.stie.new_band_op = 1; + res.stie.new_band_setup = 0; + + fst_iface_get_channel_info(s->data.old_iface, &hw_mode, + &channel); + res.stie.old_band_id = fst_hw_mode_to_band(hw_mode); + res.stie.old_band_op = 1; + res.stie.old_band_setup = 0; + + fst_printf_session(s, MSG_INFO, + "%s: FST Setup Request accepted for %s (llt=%u)", + fst_iface_get_name(s->data.old_iface), + fst_iface_get_name(s->data.new_iface), + s->data.llt_ms); + } else { + fst_printf_session(s, MSG_WARNING, + "%s: FST Setup Request rejected with code %d", + fst_iface_get_name(s->data.old_iface), + status_code); + } + + if (fst_session_send_action(s, TRUE, &res, sizeof(res), + fst_iface_get_mbie(s->data.old_iface))) { + fst_printf_sframe(s, TRUE, MSG_ERROR, + "cannot send FST Setup Response with code %d", + status_code); + return -EINVAL; + } + + fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Response sent"); + + if (status_code != WLAN_STATUS_SUCCESS) { + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_REJECT, + .reject_code = status_code, + .initiator = FST_INITIATOR_LOCAL, + }, + }; + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + } + + return 0; +} + + +int fst_session_initiate_switch(struct fst_session *s) +{ + struct fst_ack_req req; + int res; + u8 dialog_token; + + if (!fst_session_is_ready(s)) { + fst_printf_session(s, MSG_ERROR, + "cannot initiate switch due to wrong setup state (%d)", + s->state); + return -1; + } + + dialog_token = fst_group_assign_dialog_token(s->group); + + WPA_ASSERT(s->data.new_iface != NULL); + WPA_ASSERT(s->data.old_iface != NULL); + + fst_printf_session(s, MSG_INFO, "initiating FST switch: %s => %s", + fst_iface_get_name(s->data.old_iface), + fst_iface_get_name(s->data.new_iface)); + + os_memset(&req, 0, sizeof(req)); + + req.action = FST_ACTION_ACK_REQUEST; + req.dialog_token = dialog_token; + req.fsts_id = host_to_le32(s->data.fsts_id); + + res = fst_session_send_action(s, FALSE, &req, sizeof(req), NULL); + if (!res) { + fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Request sent"); + fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE, + NULL); + fst_session_stt_arm(s); + } else { + fst_printf_sframe(s, FALSE, MSG_ERROR, + "Cannot send FST Ack Request"); + } + + return res; +} + + +void fst_session_handle_action(struct fst_session *s, + struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len) +{ + switch (mgmt->u.action.u.fst_action.action) { + case FST_ACTION_SETUP_REQUEST: + WPA_ASSERT(0); + break; + case FST_ACTION_SETUP_RESPONSE: + fst_session_handle_setup_response(s, iface, mgmt, frame_len); + break; + case FST_ACTION_TEAR_DOWN: + fst_session_handle_tear_down(s, iface, mgmt, frame_len); + break; + case FST_ACTION_ACK_REQUEST: + fst_session_handle_ack_request(s, iface, mgmt, frame_len); + break; + case FST_ACTION_ACK_RESPONSE: + fst_session_handle_ack_response(s, iface, mgmt, frame_len); + break; + case FST_ACTION_ON_CHANNEL_TUNNEL: + default: + fst_printf_sframe(s, FALSE, MSG_ERROR, + "Unsupported FST Action frame"); + break; + } +} + + +int fst_session_tear_down_setup(struct fst_session *s) +{ + int res; + union fst_session_state_switch_extra evext = { + .to_initial = { + .reason = REASON_TEARDOWN, + .initiator = FST_INITIATOR_LOCAL, + }, + }; + + res = fst_session_send_tear_down(s); + + fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext); + + return res; +} + + +void fst_session_reset(struct fst_session *s) +{ + fst_session_reset_ex(s, REASON_RESET); +} + + +void fst_session_delete(struct fst_session *s) +{ + fst_printf(MSG_INFO, "Session %u deleted", s->id); + dl_list_del(&s->global_sessions_lentry); + foreach_fst_ctrl_call(on_session_removed, s); + os_free(s); +} + + +struct fst_group * fst_session_get_group(struct fst_session *s) +{ + return s->group; +} + + +struct fst_iface * fst_session_get_iface(struct fst_session *s, Boolean is_old) +{ + return is_old ? s->data.old_iface : s->data.new_iface; +} + + +u32 fst_session_get_id(struct fst_session *s) +{ + return s->id; +} + + +const u8 * fst_session_get_peer_addr(struct fst_session *s, Boolean is_old) +{ + return is_old ? s->data.old_peer_addr : s->data.new_peer_addr; +} + + +u32 fst_session_get_llt(struct fst_session *s) +{ + return s->data.llt_ms; +} + + +enum fst_session_state fst_session_get_state(struct fst_session *s) +{ + return s->state; +} + + +struct fst_session * fst_session_get_by_id(u32 id) +{ + struct fst_session *s; + + foreach_fst_session(s) { + if (id == s->id) + return s; + } + + return NULL; +} + + +void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx) +{ + struct fst_session *s; + + foreach_fst_session(s) { + if (!g || s->group == g) + clb(s->group, s, ctx); + } +} + + +void fst_session_on_action_rx(struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t len) +{ + struct fst_session *s; + + if (mgmt->u.action.category != WLAN_ACTION_FST) { + fst_printf_iface(iface, MSG_ERROR, + "action frame of wrong category (%u) received!", + mgmt->u.action.category); + return; + } + + if (mgmt->u.action.u.fst_action.action <= FST_ACTION_MAX_SUPPORTED) { + fst_printf_iface(iface, MSG_DEBUG, + "FST Action '%s' received!", + fst_action_names[mgmt->u.action.u.fst_action.action]); + } else { + fst_printf_iface(iface, MSG_WARNING, + "unknown FST Action (%u) received!", + mgmt->u.action.u.fst_action.action); + return; + } + + if (mgmt->u.action.u.fst_action.action == FST_ACTION_SETUP_REQUEST) { + fst_session_handle_setup_request(iface, mgmt, len); + return; + } + + s = fst_find_session_in_progress(mgmt->sa, fst_iface_get_group(iface)); + if (s) { + fst_session_handle_action(s, iface, mgmt, len); + } else { + fst_printf_iface(iface, MSG_WARNING, + "FST Action '%s' dropped: no session in progress found", + fst_action_names[mgmt->u.action.u.fst_action.action]); + } +} + + +int fst_session_set_str_ifname(struct fst_session *s, const char *ifname, + Boolean is_old) +{ + struct fst_group *g = fst_session_get_group(s); + struct fst_iface *i; + + i = fst_group_get_iface_by_name(g, ifname); + if (!i) { + fst_printf_session(s, MSG_WARNING, + "Cannot set iface %s: no such iface within group '%s'", + ifname, fst_group_get_id(g)); + return -1; + } + + fst_session_set_iface(s, i, is_old); + + return 0; +} + + +int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac, + Boolean is_old) +{ + u8 peer_addr[ETH_ALEN]; + int res = fst_read_peer_addr(mac, peer_addr); + + if (res) + return res; + + fst_session_set_peer_addr(s, peer_addr, is_old); + + return 0; +} + + +int fst_session_set_str_llt(struct fst_session *s, const char *llt_str) +{ + char *endp; + long int llt = strtol(llt_str, &endp, 0); + + if (*endp || llt < 0 || (unsigned long int) llt > FST_MAX_LLT_MS) { + fst_printf_session(s, MSG_WARNING, + "Cannot set llt %s: Invalid llt value (1..%u expected)", + llt_str, FST_MAX_LLT_MS); + return -1; + } + fst_session_set_llt(s, (u32) llt); + + return 0; +} + + +void fst_session_global_on_iface_detached(struct fst_iface *iface) +{ + struct fst_session *s; + + foreach_fst_session(s) { + if (fst_session_is_in_progress(s) && + (s->data.new_iface == iface || + s->data.old_iface == iface)) + fst_session_reset_ex(s, REASON_DETACH_IFACE); + } +} + + +struct fst_session * fst_session_global_get_first_by_group(struct fst_group *g) +{ + struct fst_session *s; + + foreach_fst_session(s) { + if (s->group == g) + return s; + } + + return NULL; +} + diff --git a/src/fst/fst_session.h b/src/fst/fst_session.h new file mode 100644 index 000000000..4370d2cbf --- /dev/null +++ b/src/fst/fst_session.h @@ -0,0 +1,65 @@ +/* + * FST module - FST Session related definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FST_SESSION_H +#define FST_SESSION_H + +#define FST_DEFAULT_SESSION_TIMEOUT_TU 255 /* u8 */ + +struct fst_iface; +struct fst_group; +struct fst_session; +enum fst_session_state; + +int fst_session_global_init(void); +void fst_session_global_deinit(void); +void fst_session_global_on_iface_detached(struct fst_iface *iface); +struct fst_session * +fst_session_global_get_first_by_group(struct fst_group *g); + +struct fst_session * fst_session_create(struct fst_group *g); +void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface, + Boolean is_old); +void fst_session_set_llt(struct fst_session *s, u32 llt); +void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr, + Boolean is_old); +int fst_session_initiate_setup(struct fst_session *s); +int fst_session_respond(struct fst_session *s, u8 status_code); +int fst_session_initiate_switch(struct fst_session *s); +void fst_session_handle_action(struct fst_session *s, struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, + size_t frame_len); +int fst_session_tear_down_setup(struct fst_session *s); +void fst_session_reset(struct fst_session *s); +void fst_session_delete(struct fst_session *s); + +struct fst_group * fst_session_get_group(struct fst_session *s); +struct fst_iface * fst_session_get_iface(struct fst_session *s, Boolean is_old); +const u8 * fst_session_get_peer_addr(struct fst_session *s, Boolean is_old); +u32 fst_session_get_id(struct fst_session *s); +u32 fst_session_get_llt(struct fst_session *s); +enum fst_session_state fst_session_get_state(struct fst_session *s); + +struct fst_session *fst_session_get_by_id(u32 id); + +typedef void (*fst_session_enum_clb)(struct fst_group *g, struct fst_session *s, + void *ctx); + +void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx); + +void fst_session_on_action_rx(struct fst_iface *iface, + const struct ieee80211_mgmt *mgmt, size_t len); + + +int fst_session_set_str_ifname(struct fst_session *s, const char *ifname, + Boolean is_old); +int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac, + Boolean is_old); +int fst_session_set_str_llt(struct fst_session *s, const char *llt_str); + +#endif /* FST_SESSION_H */