You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hostap/src/fst/fst_session.c

1610 lines
40 KiB
C

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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"
#ifdef CONFIG_FST_TEST
#include "fst/fst_ctrl_defs.h"
#endif /* CONFIG_FST_TEST */
#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
static 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;
bool 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++) {
bool 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)
{
/* Action frames sometimes get delayed. Use relaxed timeout (2*) */
eloop_register_timeout(0, 2 * 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 bool 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, bool 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;
struct fst_iface *new_iface = NULL;
struct fst_group *g;
u8 new_iface_peer_addr[ETH_ALEN];
size_t plen;
if (frame_len < IEEE80211_HDRLEN + 1 + sizeof(*req)) {
fst_printf_iface(iface, MSG_WARNING,
"FST Request dropped: too short (%zu < %zu)",
frame_len,
IEEE80211_HDRLEN + 1 + sizeof(*req));
return;
}
plen = frame_len - IEEE80211_HDRLEN - 1;
req = (const struct fst_setup_req *)
(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
if (req->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
req->stie.length < 11) {
fst_printf_iface(iface, MSG_WARNING,
"FST Request dropped: invalid STIE");
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 (plen > sizeof(*req)) {
fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1),
plen - sizeof(*req));
fst_printf_iface(iface, MSG_INFO,
"FST Request: MB IEs updated for " MACSTR,
MAC2STR(mgmt->sa));
}
new_iface = fst_group_get_peer_other_connection(iface, mgmt->sa,
req->stie.new_band_id,
new_iface_peer_addr);
if (!new_iface) {
fst_printf_iface(iface, MSG_WARNING,
"FST Request dropped: new iface not found");
return;
}
fst_printf_iface(iface, MSG_INFO,
"FST Request: new iface (%s:" MACSTR ") found",
fst_iface_get_name(new_iface),
MAC2STR(new_iface_peer_addr));
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 initiators MAC address, in which case, the responder
* shall delete the received FST Setup Request.
*/
if (fst_session_is_ready_pending(s) &&
/* waiting for Setup Response */
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;
}
/*
* State is SETUP_COMPLETION (either in transition or not) or
* TRANSITION_DONE (in transition).
* Setup Request arriving in this state could mean:
* 1. peer sent it before receiving our Setup Request (race
* condition)
* 2. peer didn't receive our Setup Response. Peer is retrying
* after STT timeout
* 3. peer's FST state machines are out of sync due to some
* other reason
*
* We will reset our session and create a new one instead.
*/
fst_printf_session(s, MSG_WARNING,
"resetting due to FST request");
/*
* If FST Setup Request arrived with the same FSTS ID as one we
* initialized before, 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);
}
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;
size_t plen = frame_len - IEEE80211_HDRLEN - 1;
enum hostapd_hw_mode hw_mode;
u8 channel;
union fst_session_state_switch_extra evext = {
.to_initial = {
.reject_code = 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 (plen < sizeof(*res)) {
fst_printf_session(s, MSG_WARNING,
"Too short FST Response dropped");
return;
}
res = (const struct fst_setup_res *)
(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
if (res->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
res->stie.length < 11) {
fst_printf_iface(iface, MSG_WARNING,
"FST Response dropped: invalid STIE");
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;
size_t plen = frame_len - IEEE80211_HDRLEN - 1;
union fst_session_state_switch_extra evext = {
.to_initial = {
.reason = REASON_TEARDOWN,
.initiator = FST_INITIATOR_REMOTE,
},
};
if (plen < sizeof(*td)) {
fst_printf_session(s, MSG_WARNING,
"Too short FST Tear Down dropped");
return;
}
td = (const struct fst_tear_down *)
(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
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;
size_t plen = frame_len - IEEE80211_HDRLEN - 1;
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 (plen < sizeof(*req)) {
fst_printf_session(s, MSG_WARNING,
"Too short FST Ack Request dropped");
return;
}
req = (const struct fst_ack_req *)
(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
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;
size_t plen = frame_len - IEEE80211_HDRLEN - 1;
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 (plen < sizeof(*res)) {
fst_printf_session(s, MSG_WARNING,
"Too short FST Ack Response dropped");
return;
}
res = (const struct fst_ack_res *)
(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
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;
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,
bool 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,
bool 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,
false)) {
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,
false)) {
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) - 2;
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, false)) {
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;
res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
res.stie.length = sizeof(res.stie) - 2;
if (status_code == WLAN_STATUS_SUCCESS) {
res.stie.fsts_id = host_to_le32(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, bool 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, bool 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 (len < IEEE80211_HDRLEN + 2 ||
mgmt->u.action.category != WLAN_ACTION_FST) {
fst_printf_iface(iface, MSG_ERROR,
"invalid Action frame received");
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,
bool 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,
bool 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;
}
#ifdef CONFIG_FST_TEST
static int get_group_fill_session(struct fst_group **g, struct fst_session *s)
{
const u8 *old_addr, *new_addr;
struct fst_get_peer_ctx *ctx;
os_memset(s, 0, sizeof(*s));
foreach_fst_group(*g) {
s->data.new_iface = fst_group_first_iface(*g);
if (s->data.new_iface)
break;
}
if (!s->data.new_iface)
return -EINVAL;
s->data.old_iface = dl_list_entry(s->data.new_iface->group_lentry.next,
struct fst_iface, group_lentry);
if (!s->data.old_iface)
return -EINVAL;
old_addr = fst_iface_get_peer_first(s->data.old_iface, &ctx, true);
if (!old_addr)
return -EINVAL;
new_addr = fst_iface_get_peer_first(s->data.new_iface, &ctx, true);
if (!new_addr)
return -EINVAL;
os_memcpy(s->data.old_peer_addr, old_addr, ETH_ALEN);
os_memcpy(s->data.new_peer_addr, new_addr, ETH_ALEN);
return 0;
}
#define FST_MAX_COMMAND_WORD_NAME_LENGTH 16
int fst_test_req_send_fst_request(const char *params)
{
int fsts_id;
bool is_valid;
char *endp;
struct fst_setup_req req;
struct fst_session s;
struct fst_group *g;
enum hostapd_hw_mode hw_mode;
u8 channel;
char additional_param[FST_MAX_COMMAND_WORD_NAME_LENGTH];
if (params[0] != ' ')
return -EINVAL;
params++;
fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return -EINVAL;
if (get_group_fill_session(&g, &s))
return -EINVAL;
req.action = FST_ACTION_SETUP_REQUEST;
req.dialog_token = g->dialog_token;
req.llt = host_to_le32(FST_LLT_MS_DEFAULT);
/* 8.4.2.147 Session Transition element */
req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
req.stie.length = sizeof(req.stie) - 2;
req.stie.fsts_id = host_to_le32(fsts_id);
req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
fst_iface_get_channel_info(s.data.new_iface, &hw_mode, &channel);
req.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
req.stie.new_band_op = 1;
req.stie.new_band_setup = 0;
fst_iface_get_channel_info(s.data.old_iface, &hw_mode, &channel);
req.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
req.stie.old_band_op = 1;
req.stie.old_band_setup = 0;
if (!fst_read_next_text_param(endp, additional_param,
sizeof(additional_param), &endp)) {
if (!os_strcasecmp(additional_param, FST_CTR_PVAL_BAD_NEW_BAND))
req.stie.new_band_id = req.stie.old_band_id;
}
return fst_session_send_action(&s, true, &req, sizeof(req),
s.data.old_iface->mb_ie);
}
int fst_test_req_send_fst_response(const char *params)
{
int fsts_id;
bool is_valid;
char *endp;
struct fst_setup_res res;
struct fst_session s;
struct fst_group *g;
enum hostapd_hw_mode hw_mode;
u8 status_code;
u8 channel;
char response[FST_MAX_COMMAND_WORD_NAME_LENGTH];
struct fst_session *_s;
if (params[0] != ' ')
return -EINVAL;
params++;
fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return -EINVAL;
if (get_group_fill_session(&g, &s))
return -EINVAL;
status_code = WLAN_STATUS_SUCCESS;
if (!fst_read_next_text_param(endp, response, sizeof(response),
&endp)) {
if (!os_strcasecmp(response, FST_CS_PVAL_RESPONSE_REJECT))
status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
}
os_memset(&res, 0, sizeof(res));
res.action = FST_ACTION_SETUP_RESPONSE;
/*
* If some session has just received an FST Setup Request, then
* use the correct dialog token copied from this request.
*/
_s = fst_find_session_in_progress(fst_session_get_peer_addr(&s, true),
g);
res.dialog_token = (_s && fst_session_is_ready_pending(_s)) ?
_s->data.pending_setup_req_dlgt : g->dialog_token;
res.status_code = status_code;
res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
res.stie.length = sizeof(res.stie) - 2;
if (res.status_code == WLAN_STATUS_SUCCESS) {
res.stie.fsts_id = host_to_le32(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;
}
if (!fst_read_next_text_param(endp, response, sizeof(response),
&endp)) {
if (!os_strcasecmp(response, FST_CTR_PVAL_BAD_NEW_BAND))
res.stie.new_band_id = res.stie.old_band_id;
}
return fst_session_send_action(&s, true, &res, sizeof(res),
s.data.old_iface->mb_ie);
}
int fst_test_req_send_ack_request(const char *params)
{
int fsts_id;
bool is_valid;
char *endp;
struct fst_ack_req req;
struct fst_session s;
struct fst_group *g;
if (params[0] != ' ')
return -EINVAL;
params++;
fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return -EINVAL;
if (get_group_fill_session(&g, &s))
return -EINVAL;
os_memset(&req, 0, sizeof(req));
req.action = FST_ACTION_ACK_REQUEST;
req.dialog_token = g->dialog_token;
req.fsts_id = host_to_le32(fsts_id);
return fst_session_send_action(&s, false, &req, sizeof(req), NULL);
}
int fst_test_req_send_ack_response(const char *params)
{
int fsts_id;
bool is_valid;
char *endp;
struct fst_ack_res res;
struct fst_session s;
struct fst_group *g;
if (params[0] != ' ')
return -EINVAL;
params++;
fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return -EINVAL;
if (get_group_fill_session(&g, &s))
return -EINVAL;
os_memset(&res, 0, sizeof(res));
res.action = FST_ACTION_ACK_RESPONSE;
res.dialog_token = g->dialog_token;
res.fsts_id = host_to_le32(fsts_id);
return fst_session_send_action(&s, false, &res, sizeof(res), NULL);
}
int fst_test_req_send_tear_down(const char *params)
{
int fsts_id;
bool is_valid;
char *endp;
struct fst_tear_down td;
struct fst_session s;
struct fst_group *g;
if (params[0] != ' ')
return -EINVAL;
params++;
fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return -EINVAL;
if (get_group_fill_session(&g, &s))
return -EINVAL;
os_memset(&td, 0, sizeof(td));
td.action = FST_ACTION_TEAR_DOWN;
td.fsts_id = host_to_le32(fsts_id);
return fst_session_send_action(&s, true, &td, sizeof(td), NULL);
}
u32 fst_test_req_get_fsts_id(const char *params)
{
int sid;
bool is_valid;
char *endp;
struct fst_session *s;
if (params[0] != ' ')
return FST_FSTS_ID_NOT_FOUND;
params++;
sid = fst_read_next_int_param(params, &is_valid, &endp);
if (!is_valid)
return FST_FSTS_ID_NOT_FOUND;
s = fst_session_get_by_id(sid);
if (!s)
return FST_FSTS_ID_NOT_FOUND;
return s->data.fsts_id;
}
int fst_test_req_get_local_mbies(const char *request, char *buf, size_t buflen)
{
char *endp;
char ifname[FST_MAX_COMMAND_WORD_NAME_LENGTH];
struct fst_group *g;
struct fst_iface *iface;
if (request[0] != ' ')
return -EINVAL;
request++;
if (fst_read_next_text_param(request, ifname, sizeof(ifname), &endp) ||
!*ifname)
goto problem;
g = dl_list_first(&fst_global_groups_list, struct fst_group,
global_groups_lentry);
if (!g)
goto problem;
iface = fst_group_get_iface_by_name(g, ifname);
if (!iface || !iface->mb_ie)
goto problem;
return wpa_snprintf_hex(buf, buflen, wpabuf_head(iface->mb_ie),
wpabuf_len(iface->mb_ie));
problem:
return os_snprintf(buf, buflen, "FAIL\n");
}
#endif /* CONFIG_FST_TEST */