Files
android_kernel_samsung_sm87…/qcom/opensource/fst-manager/fst_manager.c
2025-08-12 23:12:57 +02:00

1945 lines
48 KiB
C
Executable File

/*
* FST Manager implementation
*
* Copyright (c) 2015-2016, 2019-2020 The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "utils/includes.h"
#include "utils/common.h"
#include "utils/list.h"
#include "common/defs.h"
#include "common/ieee802_11_defs.h"
#include "fst_ctrl.h"
#include "fst_mux.h"
#include "fst/fst_ctrl_defs.h"
#include "fst_cfgmgr.h"
#define FST_MGR_COMPONENT "MGR"
#include "fst_manager.h"
#include "fst_rateupg.h"
#include <stdbool.h>
#define FST_LLT_SWITCH_IMMEDIATELY 0
#define LLT_UNIT_US 32 /* See 10.32.2.2 Transitioning between states */
#define MS_TO_LLT_VALUE(l) (((l) * 1000) / LLT_UNIT_US)
struct fst_mgr
{
struct dl_list groups;
};
struct fst_mgr_group
{
struct fst_group_info info;
struct fst_mux *drv;
struct dl_list sessions;
struct dl_list ifaces;
struct dl_list peers;
struct dl_list mgr_lentry;
};
struct fst_mgr_iface
{
struct fst_iface_info info;
struct dl_list grp_lentry;
};
enum fst_mgr_session_state
{
FST_MGR_SESSION_STATE_IDLE,
FST_MGR_SESSION_STATE_INITIATED,
FST_MGR_SESSION_STATE_ESTABLISHED,
FST_MGR_SESSION_STATE_IN_TRANSITION,
FST_MGR_SESSION_STATE_LAST
};
struct fst_mgr_session
{
enum fst_mgr_session_state state;
u32 id;
struct fst_mgr_group *group;
struct fst_mgr_iface *old_iface;
struct fst_mgr_iface *new_iface;
u32 llt;
Boolean non_compliant;
struct dl_list grp_lentry;
};
struct fst_mgr_peer_iface
{
struct fst_mgr_iface *iface;
u8 addr[ETH_ALEN];
struct dl_list peer_lentry;
};
struct fst_mgr_peer
{
struct fst_mgr_session *session;
struct fst_mgr_iface *active_iface;
struct dl_list ifaces;
struct dl_list grp_lentry;
};
extern unsigned int fst_num_of_retries;
extern Boolean fst_force_nc;
#define _fst_mgr_foreach_grp(m, g) \
dl_list_for_each((g), &(m)->groups, struct fst_mgr_group, mgr_lentry)
#define _fst_grp_foreach_iface(g, i) \
dl_list_for_each((i), &(g)->ifaces, struct fst_mgr_iface, grp_lentry)
#define _fst_grp_foreach_peer(g, p) \
dl_list_for_each((p), &(g)->peers, struct fst_mgr_peer, grp_lentry)
#define _fst_grp_foreach_session(g, s) \
dl_list_for_each((s), &(g)->sessions, struct fst_mgr_session, grp_lentry)
#define _fst_peer_foreach_iface(p, i) \
dl_list_for_each((i), &(p)->ifaces, struct fst_mgr_peer_iface, peer_lentry)
static void _fst_mgr_on_peer_connected(struct fst_mgr *mgr, const char *ifname,
const u8* addr);
static int _fst_mgr_peer_set_active_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *i,
struct fst_mux *drv);
static void _fst_mgr_peer_check_compliance(struct fst_mgr_peer *p);
static const u8 *_fst_mgr_peer_get_addr_of_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *iface);
/* helpers */
static const char *state_name(enum fst_mgr_session_state state)
{
static const char *state_names[] = {
[FST_MGR_SESSION_STATE_IDLE] = "IDLE",
[FST_MGR_SESSION_STATE_INITIATED] = "INITIATED",
[FST_MGR_SESSION_STATE_ESTABLISHED] = "ESTABLISHED",
[FST_MGR_SESSION_STATE_IN_TRANSITION] = "IN TRANSITION",
};
if (state >= sizeof(state_names)/sizeof(state_names[0]) ||
!state_names[state])
return "UNKNOWN_STATE";
return state_names[state];
}
static inline void fst_mgr_printf_session_info(struct fst_mgr_session *s)
{
if (s) {
fst_mgr_printf(MSG_INFO, "****** session %u info begin",s->id);
fst_mgr_printf(MSG_INFO, "****** old_i = %s",
s->old_iface ? s->old_iface->info.name : "NULL" );
fst_mgr_printf(MSG_INFO, "****** new_i = %s",
s->new_iface ? s->new_iface->info.name : "NULL" );
fst_mgr_printf(MSG_INFO, "****** llt = %u",
s->llt);
fst_mgr_printf(MSG_INFO, "****** state = %s", state_name(s->state));
fst_mgr_printf(MSG_INFO, "****** session %u info end", s->id);
}
}
/*
* FST Manager Session
*/
static inline Boolean _fst_mgr_session_is_in_progress(struct fst_mgr_session *s)
{
return (s->state != FST_MGR_SESSION_STATE_IDLE);
}
static inline int _fst_mgr_session_is_ready(struct fst_mgr_session *s)
{
return (s->state == FST_MGR_SESSION_STATE_ESTABLISHED);
}
static inline struct fst_mgr_iface *
_fst_mgr_session_get_old_iface(struct fst_mgr_session *s)
{
return s->old_iface;
}
static inline struct fst_mgr_iface *
_fst_mgr_session_get_new_iface(struct fst_mgr_session *s)
{
return s->new_iface;
}
static int _fst_mgr_session_set_old_iface(struct fst_mgr_session *s,
struct fst_mgr_iface *i)
{
if (!s->non_compliant)
if (fst_session_set(s->id, FST_CSS_PNAME_OLD_IFNAME, i->info.name)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set old iface to %s",
s->id, i->info.name);
return -1;
}
s->old_iface = i;
return 0;
}
static int _fst_mgr_session_set_new_iface(struct fst_mgr_session *s,
struct fst_mgr_iface *i)
{
if (!s->non_compliant)
if (fst_session_set(s->id, FST_CSS_PNAME_NEW_IFNAME, i->info.name)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set new iface to %s",
s->id, i->info.name);
return -1;
}
s->new_iface = i;
return 0;
}
static int _fst_mgr_session_set_peer_addr(struct fst_mgr_peer *p)
{
struct fst_mgr_session *s = p->session;
if (s == NULL) {
fst_mgr_printf(MSG_ERROR, "Session does not exist");
return -1;
}
if (s->non_compliant)
return 0;
char pval[] = "XX:XX:XX:XX:XX:XX";
const u8 *old_addr, *new_addr;
old_addr = _fst_mgr_peer_get_addr_of_iface(p, s->old_iface);
new_addr = _fst_mgr_peer_get_addr_of_iface(p, s->new_iface);
if (!old_addr || !new_addr) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set addr for %s and %s",
s->id, s->old_iface->info.name,
s->new_iface->info.name);
return -1;
}
snprintf(pval, sizeof(pval), MACSTR, MAC2STR(old_addr));
if (fst_session_set(s->id, FST_CSS_PNAME_OLD_PEER_ADDR, pval)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set old addr to %s",
s->id, pval);
return -1;
}
snprintf(pval, sizeof(pval), MACSTR, MAC2STR(new_addr));
if (fst_session_set(s->id, FST_CSS_PNAME_NEW_PEER_ADDR, pval)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set new addr to %s",
s->id, pval);
return -1;
}
return 0;
}
static int _fst_mgr_session_set_llt(struct fst_mgr_session *s,
u32 llt)
{
if (!s->non_compliant) {
char pval[32];
snprintf(pval, sizeof(pval), "%u", llt);
if (fst_session_set(s->id, FST_CSS_PNAME_LLT, pval)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot set LLT to %s",
s->id, pval);
return -1;
}
}
s->llt = llt;
return 0;
}
static int _fst_mgr_session_initiate_setup(struct fst_mgr_session *s)
{
WPA_ASSERT(!s->non_compliant);
if (fst_session_initiate(s->id)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot initiate setup",
s->id);
return -1;
}
fst_mgr_printf(MSG_INFO, "session %u: setup initiated",
s->id);
s->state = FST_MGR_SESSION_STATE_INITIATED;
return 0;
}
static void _fst_mgr_session_nc_transfer(struct fst_mgr_session *s,
struct fst_mgr_peer *p)
{
WPA_ASSERT(s->non_compliant);
fst_mgr_printf(MSG_INFO, "session %u: performing non-compliant transfer"
" for: old_iface=%s new_iface=%s",
s->id, s->old_iface->info.name,
s->new_iface->info.name);
_fst_mgr_peer_set_active_iface(p, s->new_iface, s->group->drv);
s->state = FST_MGR_SESSION_STATE_IDLE;
}
static void _fst_mgr_session_check_for_nc_transfer(struct fst_mgr_session *s,
struct fst_mgr_peer *p)
{
if (p->active_iface->info.priority < s->new_iface->info.priority)
_fst_mgr_session_nc_transfer(s, p);
}
static int _fst_mgr_session_transfer(struct fst_mgr_session *s)
{
WPA_ASSERT(!s->non_compliant);
if (fst_session_transfer(s->id)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot transfer",
s->id);
return -1;
}
fst_mgr_printf(MSG_INFO, "session %u: transfer initiated",
s->id);
s->state = FST_MGR_SESSION_STATE_IN_TRANSITION;
return 0;
}
static struct fst_mgr_peer *
_fst_mgr_group_peer_by_session(struct fst_mgr_group *g,
struct fst_mgr_session *s)
{
struct fst_mgr_peer *p;
_fst_grp_foreach_peer(g, p)
if (p->session == s)
return p;
return NULL;
}
static int
_fst_mgr_set_link_loss(const char *ifname, const u8 *addr, bool fst_link_loss)
{
char fname[128];
FILE *f;
if (ifname == NULL)
return -1;
if (snprintf(fname, sizeof(fname), "/sys/class/net/%s/device/wil6210/fst_link_loss",
ifname) < 0)
return -1;
f = fopen(fname, "r+");
if (!f) {
fst_mgr_printf(MSG_ERROR, "failed to open: %s", fname);
return -1;
}
if (fprintf(f, MACSTR " %d\n", MAC2STR(addr), fst_link_loss) < 0) {
fclose(f);
return -1;
}
fclose(f);
return 0;
}
static void
_fst_mgr_session_set_link_loss(struct fst_mgr_session *s, bool fst_link_loss)
{
int ret;
struct fst_mgr_peer *p = NULL;
struct fst_mgr_peer_iface *pi;
p = _fst_mgr_group_peer_by_session(s->group, s);
if (!p) {
fst_mgr_printf(MSG_WARNING, "couldn't find peer");
return;
}
_fst_peer_foreach_iface(p, pi) {
if (pi->iface == s->old_iface) {
ret = _fst_mgr_set_link_loss(s->old_iface->info.name,
pi->addr, fst_link_loss);
if (ret < 0)
fst_mgr_printf(MSG_WARNING, "failed to set fst link loss %s",
fst_link_loss ? "On" : "Off");
else
fst_mgr_printf(MSG_INFO, "fst link loss %s for peer "
MACSTR " iface %s",
fst_link_loss ? "enabled" : "disabled",
MAC2STR(pi->addr),
s->old_iface->info.name);
break;
}
}
}
static int _fst_mgr_session_respond(struct fst_mgr_session *s, Boolean accept)
{
const char *responce_status =
accept ? FST_CS_PVAL_RESPONSE_ACCEPT :
FST_CS_PVAL_RESPONSE_REJECT;
if (fst_session_respond(s->id, responce_status)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot respond",
s->id);
return -1;
}
s->state = accept ?
FST_MGR_SESSION_STATE_ESTABLISHED :
FST_MGR_SESSION_STATE_IDLE;
if (accept && s->llt > 0)
/*
* at this point the active interface is the one with highest
* priority and we have a backup interface. Set active interface
* to aggressive link loss detection for fast switching to
* backup once signal quality is low
*/
_fst_mgr_session_set_link_loss(s, true);
return 0;
}
static void _fst_mgr_session_reset(struct fst_mgr_session *s,
Boolean allow_tear_down)
{
if (!s->non_compliant && allow_tear_down && _fst_mgr_session_is_ready(s)) {
if (fst_session_teardown(s->id))
fst_mgr_printf(MSG_WARNING, "session %u: cannot reset", s->id);
}
s->state = FST_MGR_SESSION_STATE_IDLE;
}
static void _fst_mgr_session_deinit(struct fst_mgr_session *s)
{
dl_list_del(&s->grp_lentry);
fst_session_remove(s->id);
os_free(s);
}
static int _fst_mgr_session_init(struct fst_mgr_group *g,
struct fst_mgr_session **_s, u32 session_id)
{
struct fst_mgr_session *s;
if (session_id == FST_INVALID_SESSION_ID &&
fst_session_add(g->info.id, &session_id)) {
fst_mgr_printf(MSG_ERROR, "group %s: cannot add session ",
g->info.id);
goto error_add;
}
s = os_malloc(sizeof(*s));
if (!s) {
fst_mgr_printf(MSG_ERROR, "group %s: cannot allocate session ",
g->info.id);
goto error_alloc;
}
os_memset(s, 0, sizeof(*s));
s->state = FST_MGR_SESSION_STATE_IDLE;
s->group = g;
s->id = session_id;
s->non_compliant = fst_force_nc;
dl_list_add_tail(&g->sessions, &s->grp_lentry);
fst_mgr_printf(MSG_INFO, "group %s: session %u added",
g->info.id, session_id);
if (_s)
*_s = s;
return 0;
error_alloc:
fst_session_remove(session_id);
error_add:
return -1;
}
/*
* FST Manager Peer
*/
static void _fst_mgr_peer_print_connected_addr(struct fst_mgr_peer *p)
{
if (dl_list_empty(&p->ifaces)) {
fst_mgr_printf(MSG_DEBUG, "peer %p has no connections", p);
return;
}
struct fst_mgr_peer_iface *pi;
_fst_peer_foreach_iface(p, pi) {
fst_mgr_printf(MSG_DEBUG, "peer %p has addr " MACSTR " / %s",
p, MAC2STR(pi->addr), pi->iface->info.name);
}
}
static const u8 *_fst_mgr_peer_get_addr_of_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *iface)
{
struct fst_mgr_peer_iface *pi;
_fst_peer_foreach_iface(p, pi) {
if (pi->iface == iface)
return pi->addr;
}
return NULL;
}
static int _fst_mgr_peer_set_active_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *i,
struct fst_mux *drv)
{
int res = 0;
if (i && p->active_iface == i) {
fst_mgr_printf(MSG_INFO, "%s is already active for peer %p",
i->info.name, p);
return 0;
}
if (p->active_iface) {
const u8 *addr = _fst_mgr_peer_get_addr_of_iface(p, p->active_iface);
if (addr) {
fst_mux_del_map_entry(drv, addr);
fst_mgr_printf(MSG_INFO,
"Map entry removed: " MACSTR " via %s",
MAC2STR(addr),
p->active_iface->info.name);
p->active_iface = NULL;
}
}
if (!i)
return 0;
const u8 *addr = _fst_mgr_peer_get_addr_of_iface(p, i);
if (!addr) {
fst_mgr_printf(MSG_ERROR, "Peer is not connected via %s",
i->info.name);
return -1;
}
res = fst_mux_add_map_entry(drv, addr, i->info.name);
if (!res) {
/* Set iface as an active */
p->active_iface = i;
fst_mgr_printf(MSG_INFO,
"Map entry added: " MACSTR " via %s",
MAC2STR(addr), i->info.name);
} else
fst_mgr_printf(MSG_ERROR,
"Cannot add map entry: " MACSTR " via %s",
MAC2STR(addr), i->info.name);
return res;
}
static struct fst_mgr_iface *
_fst_mgr_peer_get_next_iface(struct fst_mgr_peer *p)
{
struct fst_mgr_peer_iface *pi;
struct fst_mgr_iface *_i;
struct fst_mgr_iface *i = NULL;
int max_priority = -1;
_fst_peer_foreach_iface(p, pi) {
_i = pi->iface;
if (_i == p->active_iface)
continue;
if (max_priority == -1 || max_priority < _i->info.priority) {
max_priority = _i->info.priority;
i = _i;
}
}
return i;
}
static void _fst_mgr_peer_try_to_initiate_next_setup(struct fst_mgr_peer *p,
struct fst_mgr_group *g)
{
struct fst_mgr_iface *new_i;
u32 llt;
if (!fst_is_supplicant())
/* AP doesn't initiate sessions */
return;
if (p->session && _fst_mgr_session_is_in_progress(p->session)) {
fst_mgr_printf(MSG_WARNING,
"peer %p: Cannot initiate next setup: "
"another session is in progress", p);
return;
}
if (!p->active_iface) {
fst_mgr_printf(MSG_WARNING,
"peer %p: Cannot initiate next setup: "
"no active iface", p);
return;
}
if (!p->session &&
_fst_mgr_session_init(g, &p->session, FST_INVALID_SESSION_ID)) {
fst_mgr_printf(MSG_ERROR, "group %s: cannot initialize session with peer %p",
g->info.id, p);
return;
}
new_i = _fst_mgr_peer_get_next_iface(p);
if (new_i == NULL) {
fst_mgr_printf(MSG_WARNING,
"peer %p: Cannot initiate next setup: "
"no backup iface connected", p);
return;
}
_fst_mgr_peer_check_compliance(p);
llt = MS_TO_LLT_VALUE(new_i->info.llt);
if (p->active_iface &&
p->active_iface->info.priority < new_i->info.priority) {
llt = FST_LLT_SWITCH_IMMEDIATELY;
}
if (_fst_mgr_session_set_old_iface(p->session, p->active_iface) ||
_fst_mgr_session_set_new_iface(p->session, new_i) ||
_fst_mgr_session_set_peer_addr(p) ||
_fst_mgr_session_set_llt(p->session, llt)) {
fst_mgr_printf(MSG_WARNING,
"peer %p: Cannot initiate next setup: "
"session %u configuration failed", p, p->session->id);
return;
}
if (!p->session->non_compliant) {
fst_mgr_printf(MSG_INFO,
"peer %p: session %u: initiating setup: "
"old_iface=%s new_iface=%s llt=%d",
p, p->session->id,
p->active_iface->info.name, new_i->info.name, llt);
_fst_mgr_session_initiate_setup(p->session);
}
}
static void _fst_mgr_peer_check_compliance(struct fst_mgr_peer *p)
{
struct fst_mgr_peer_iface *pi;
WPA_ASSERT(p->session != NULL);
if (p->session == NULL) {
fst_mgr_printf(MSG_ERROR, "peer session is invalid");
return;
}
if (fst_force_nc) {
p->session->non_compliant = TRUE;
return;
}
p->session->non_compliant = TRUE;
_fst_peer_foreach_iface(p, pi) {
if (fst_get_peer_mbies(pi->iface->info.name,
pi->addr, NULL) > 0) {
p->session->non_compliant = FALSE;
break;
}
}
fst_mgr_printf(MSG_INFO, "peer %p: non_compliant: %d",
p, p->session->non_compliant);
}
static Boolean _fst_mgr_peer_add_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *i, const u8 *addr)
{
struct fst_mgr_peer_iface *pi = os_zalloc(sizeof(*pi));
if (!pi)
return FALSE;
pi->iface = i;
os_memcpy(pi->addr, addr, ETH_ALEN);
dl_list_add_tail(&p->ifaces, &pi->peer_lentry);
return TRUE;
}
static void _fst_mgr_peer_del_iface(struct fst_mgr_peer *p,
struct fst_mgr_iface *i)
{
struct fst_mgr_peer_iface *pi;
dl_list_for_each(pi, &p->ifaces, struct fst_mgr_peer_iface, peer_lentry)
if (pi->iface == i) {
dl_list_del(&pi->peer_lentry);
os_free(pi);
break;
}
}
static void _fst_mgr_peer_session_deinit(struct fst_mgr_peer *p,
Boolean allow_tear_down)
{
if (p->session) {
_fst_mgr_session_reset(p->session, allow_tear_down);
_fst_mgr_session_deinit(p->session);
p->session = NULL;
}
}
static void _fst_mgr_peer_deinit(struct fst_mgr_peer *p)
{
dl_list_del(&p->grp_lentry);
while (!dl_list_empty(&p->ifaces)) {
struct fst_mgr_peer_iface *pi = dl_list_first(&p->ifaces,
struct fst_mgr_peer_iface, peer_lentry);
dl_list_del(&pi->peer_lentry);
os_free(pi);
}
if (p->session)
_fst_mgr_session_deinit(p->session);
os_free(p);
}
static int _fst_mgr_peer_init(struct fst_mgr_group *g, const u8 *addr,
struct fst_mgr_iface *i)
{
struct fst_mgr_peer *p;
struct fst_mgr_session *s;
if (_fst_mgr_session_init(g, &s, FST_INVALID_SESSION_ID)) {
fst_mgr_printf(MSG_ERROR, "group %s: cannot initialize session " MACSTR,
g->info.id, MAC2STR(addr));
goto error_session_init;
}
p = os_malloc(sizeof(*p));
if (!p) {
fst_mgr_printf(MSG_ERROR, "group %s: cannot allocate peer " MACSTR,
g->info.id, MAC2STR(addr));
goto error_alloc;
}
os_memset(p, 0, sizeof(*p));
dl_list_init(&p->ifaces);
p->active_iface = i;
p->session = s;
dl_list_add_tail(&g->peers, &p->grp_lentry);
if (!_fst_mgr_peer_add_iface(p, i, addr)) {
fst_mgr_printf(MSG_ERROR, "Peer interface allocation error");
goto error_add_iface;
}
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR ": iface %s added",
g->info.id, MAC2STR(addr), i->info.name);
_fst_mgr_peer_print_connected_addr(p);
return 0;
error_add_iface:
os_free(p);
error_alloc:
_fst_mgr_session_deinit(s);
error_session_init:
return -1;
}
/*
* FST Manager Interface
*/
static void _fst_mgr_iface_deinit(struct fst_mgr_iface *i, struct fst_mux *drv)
{
dl_list_del(&i->grp_lentry);
fst_mux_unregister_iface(drv, i->info.name);
fst_cfgmgr_on_iface_deinit(&i->info);
os_free(i);
}
static int _fst_mgr_iface_init(struct fst_mgr_group *g,
struct fst_iface_info *finfo, struct fst_mux *drv)
{
struct fst_mgr_iface *i;
if (fst_cfgmgr_on_iface_init(&g->info, finfo)) {
fst_mgr_printf(MSG_ERROR, "Cannot init iface %s", finfo->name);
goto error_init;
}
if (fst_mux_register_iface(drv, finfo->name, finfo->priority)) {
fst_mgr_printf(MSG_ERROR, "Cannot register iface %s with driver",
finfo->name);
goto error_slave;
}
i = os_malloc(sizeof(*i));
if (!i) {
fst_mgr_printf(MSG_ERROR, "Cannot allocate object for iface %s",
finfo->name);
goto error_alloc;
}
os_memset(i, 0, sizeof(*i));
i->info = *finfo;
dl_list_add_tail(&g->ifaces, &i->grp_lentry);
return 0;
error_alloc:
fst_mux_unregister_iface(drv, finfo->name);
error_slave:
fst_cfgmgr_on_iface_deinit(finfo);
error_init:
return -1;
}
/*
* FST Manager Group
*/
static struct fst_mgr_peer *_fst_mgr_group_peer_by_addr(struct fst_mgr_group *g,
const u8 *addr)
{
struct fst_mgr_peer *p;
_fst_grp_foreach_peer(g, p) {
struct fst_mgr_peer_iface *pi;
_fst_peer_foreach_iface(p, pi) {
if (!os_memcmp(addr, pi->addr, ETH_ALEN))
return p;
}
}
return NULL;
}
const u8 *fst_mgr_get_addr_from_mbie(struct multi_band_ie *mbie)
{
const u8 *addr = NULL;
switch (MB_CTRL_ROLE(mbie->mb_ctrl)) {
case MB_STA_ROLE_AP:
addr = mbie->bssid;
break;
case MB_STA_ROLE_NON_PCP_NON_AP:
if (mbie->mb_ctrl & MB_CTRL_STA_MAC_PRESENT &&
(size_t) 2 + mbie->len >= sizeof(*mbie) + ETH_ALEN)
addr = (const u8 *) &mbie[1];
break;
default:
break;
}
return addr;
}
static Boolean _fst_mgr_is_other_addr_in_mbies(struct fst_iface_info *info,
const u8 *addr, const u8 *other_addr)
{
char *str_mbies = NULL;
int str_mbies_size;
u8 *mbies = NULL, *mbies_iter;
int mbies_size;
Boolean result = FALSE;
str_mbies_size = fst_get_peer_mbies(info->name, addr, &str_mbies);
if (str_mbies_size < 2 || str_mbies_size & 1)
goto finish;
mbies_size = str_mbies_size / 2;
mbies = os_malloc(mbies_size);
if (!mbies)
goto finish;
if (hexstr2bin(str_mbies, mbies, mbies_size))
goto finish;
mbies_iter = mbies;
while (mbies_size >= 2) {
struct multi_band_ie *mbie = (struct multi_band_ie *) mbies_iter;
const u8 *mbie_addr;
if (mbie->eid != WLAN_EID_MULTI_BAND ||
(size_t) 2 + mbie->len < sizeof(*mbie))
break;
mbie_addr = fst_mgr_get_addr_from_mbie(mbie);
if (mbie_addr && !os_memcmp(mbie_addr, other_addr, ETH_ALEN)) {
result = TRUE;
break;
}
mbies_iter += mbie->len + 2;
mbies_size -= mbie->len + 2;
}
finish:
if (str_mbies)
os_free(str_mbies);
if (mbies)
os_free(mbies);
return result;
}
static struct fst_mgr_peer *
_fst_mgr_group_peer_by_other_addr(struct fst_mgr_group *g,
const u8 *other_addr,
struct fst_iface_info *other_iface_info)
{
struct fst_mgr_peer *p;
/* check if MAC address of new connection can be found in the MB IE of
* the existing connection under the peer or if the MAC address of the
* existing connections under the peer can be found in the MB IE of
* the new connection.
*/
_fst_grp_foreach_peer(g, p) {
struct fst_mgr_peer_iface *pi;
_fst_peer_foreach_iface(p, pi) {
if (os_strncmp(pi->iface->info.name,
other_iface_info->name,
FST_MAX_INTERFACE_SIZE) &&
(_fst_mgr_is_other_addr_in_mbies(
&pi->iface->info, pi->addr, other_addr) ||
_fst_mgr_is_other_addr_in_mbies(
other_iface_info, other_addr, pi->addr)))
return p;
}
}
return NULL;
}
static Boolean _fst_mgr_is_peer_connected(struct fst_mgr_group *g,
const char *ifname,
const u8 *addr)
{
struct fst_mgr_peer *p = NULL;
_fst_grp_foreach_peer(g, p) {
struct fst_mgr_peer_iface *pi;
_fst_peer_foreach_iface(p, pi) {
if (!os_strncmp(pi->iface->info.name, ifname, FST_MAX_INTERFACE_SIZE) &&
!os_memcmp(pi->addr, addr, ETH_ALEN))
return TRUE;
}
}
return FALSE;
}
static void _fst_mgr_group_deinit(struct fst_mgr_group *g)
{
fst_mux_stop(g->drv);
while (!dl_list_empty(&g->peers)) {
struct fst_mgr_peer *p = dl_list_first(&g->peers,
struct fst_mgr_peer, grp_lentry);
_fst_mgr_peer_deinit(p);
}
while (!dl_list_empty(&g->sessions)) {
struct fst_mgr_session *s = dl_list_first(&g->sessions,
struct fst_mgr_session, grp_lentry);
_fst_mgr_session_deinit(s);
}
while (!dl_list_empty(&g->ifaces)) {
struct fst_mgr_iface *i = dl_list_first(&g->ifaces,
struct fst_mgr_iface, grp_lentry);
_fst_mgr_iface_deinit(i, g->drv);
}
fst_mux_cleanup(g->drv);
dl_list_del(&g->mgr_lentry);
fst_cfgmgr_on_group_deinit(&g->info);
os_free(g);
}
static void _fst_mgr_group_check_connections(struct fst_mgr *mgr, struct fst_group_info *ginfo)
{
int i, nof_ifaces;
struct fst_iface_info *ifaces;
nof_ifaces = fst_cfgmgr_get_group_ifaces(ginfo, &ifaces);
if (nof_ifaces < 0) {
fst_mgr_printf(MSG_ERROR, "Cannot get ifaces for group %s", ginfo->id);
return;
}
for (i = 0; i < nof_ifaces; i++) {
uint8_t *peers = NULL, *p;
int nof_peers;
nof_peers = fst_get_iface_peers(ginfo, &ifaces[i], &peers);
if (nof_peers < 0) {
fst_mgr_printf(MSG_ERROR,
"Cannot get peers for iface \'%s\'",
ifaces[i].name);
continue;
}
p = peers;
while (nof_peers--) {
_fst_mgr_on_peer_connected(mgr, ifaces[i].name, p);
p += ETH_ALEN;
}
if (peers)
fst_free(peers);
}
fst_free(ifaces);
}
static int _fst_mgr_group_init(struct fst_mgr *mgr,
struct fst_group_info *ginfo)
{
int i, nof_ifaces;
struct fst_iface_info *ifaces;
struct fst_mux *drv;
struct fst_mgr_group *g;
if (fst_cfgmgr_on_group_init(ginfo)) {
fst_mgr_printf(MSG_ERROR, "Cannot init group %s", ginfo->id);
goto error_group_init;
}
nof_ifaces = fst_cfgmgr_get_group_ifaces(ginfo, &ifaces);
if (nof_ifaces < 0) {
fst_mgr_printf(MSG_ERROR, "Cannot get ifaces for group %s", ginfo->id);
goto error_group_ifaces;
}
fst_mgr_printf(MSG_DEBUG, "group %s: %d ifaces found",
ginfo->id, nof_ifaces);
drv = fst_mux_init(ginfo->id);
if (!drv) {
fst_mgr_printf(MSG_ERROR, "Cannot initiate driver for group %s",
ginfo->id);
goto error_drv;
}
g = os_malloc(sizeof(*g));
if (!g) {
fst_mgr_printf(MSG_ERROR, "Cannot allocate object for group %s",
ginfo->id);
goto error_alloc;
}
os_memset(g, 0, sizeof(*g));
dl_list_init(&g->sessions);
dl_list_init(&g->ifaces);
dl_list_init(&g->peers);
g->drv = drv;
g->info = *ginfo;
for (i = 0; i < nof_ifaces; i++)
if (!ifaces[i].manual_enslave && _fst_mgr_iface_init(g, &ifaces[i], drv)) {
fst_mgr_printf(MSG_ERROR, "Cannot init iface for group %s",
ginfo->id);
goto error_iface;
}
if (fst_mux_start(drv) != 0) {
fst_mgr_printf(MSG_ERROR, "Cannot start driver for group %s",
ginfo->id);
goto error_drv_start;
}
dl_list_add_tail(&mgr->groups, &g->mgr_lentry);
fst_mgr_printf(MSG_INFO, "group %s with %d ifaces initialized",
ginfo->id, nof_ifaces);
_fst_mgr_group_check_connections(mgr, ginfo);
fst_free(ifaces);
return 0;
error_drv_start:
error_iface:
while (!dl_list_empty(&g->ifaces)) {
struct fst_mgr_iface *i = dl_list_first(&g->ifaces,
struct fst_mgr_iface, grp_lentry);
_fst_mgr_iface_deinit(i, drv);
}
error_alloc:
fst_mux_cleanup(drv);
error_drv:
fst_free(ifaces);
error_group_ifaces:
fst_cfgmgr_on_group_deinit(ginfo);
error_group_init:
return -1;
}
/*
* FST Manager
*/
static struct fst_mgr_group *_fst_mgr_group_by_name(struct fst_mgr *mgr,
const char *name)
{
struct fst_mgr_group *g;
_fst_mgr_foreach_grp(mgr, g) {
if (!os_strcmp(g->info.id, name)) {
return g;
}
}
return NULL;
}
static struct fst_mgr_group *_fst_mgr_group_by_ifname(struct fst_mgr *mgr,
const char *ifname, struct fst_mgr_iface **iface)
{
struct fst_mgr_group *g;
_fst_mgr_foreach_grp(mgr, g) {
struct fst_mgr_iface *i;
_fst_grp_foreach_iface(g, i) {
if (!os_strcmp(ifname, i->info.name)) {
if (iface)
*iface = i;
return g;
}
}
}
return NULL;
}
static struct fst_mgr_group *_fst_mgr_group_by_session_id(struct fst_mgr *mgr,
u32 session_id, struct fst_mgr_session **session)
{
struct fst_mgr_group *g;
_fst_mgr_foreach_grp(mgr, g) {
struct fst_mgr_session *s;
_fst_grp_foreach_session(g, s) {
if (s->id == session_id) {
if (session)
*session = s;
return g;
}
}
}
return NULL;
}
static void _fst_mgr_on_peer_connected(struct fst_mgr *mgr,
const char *ifname,
const u8* addr)
{
struct fst_mgr_group *g;
struct fst_mgr_peer *p;
struct fst_mgr_iface *i, *tmp_i;
int num_ifaces = 0;
g = _fst_mgr_group_by_ifname(mgr, ifname, &i);
if (!g) {
fst_mgr_printf(MSG_ERROR, "iface %s: no group found",
ifname);
return;
}
if (_fst_mgr_is_peer_connected(g, ifname, addr)) {
fst_mgr_printf(MSG_INFO, "peer already connected on iface %s",
ifname);
return;
}
_fst_grp_foreach_iface(g, tmp_i) {
num_ifaces++;
}
if (num_ifaces < 2) {
fst_mgr_printf(MSG_INFO, "not enough group interfaces to do FST");
return;
}
if (fst_cfgmgr_on_connect(&g->info, ifname, addr))
return;
p = _fst_mgr_group_peer_by_other_addr(g, addr, &i->info);
if (!p) {
if (!fst_mux_add_map_entry(g->drv, addr, i->info.name))
_fst_mgr_peer_init(g, addr, i);
/* We have not more than 1 iface connected to this peer, so session
* cannot be established right now
*/
return;
}
if(!_fst_mgr_peer_add_iface(p, i, addr)) {
fst_mgr_printf(MSG_ERROR, "Peer interface allocation error");
return;
}
if (p->session) {
struct fst_mgr_iface *csi =
_fst_mgr_session_get_new_iface(p->session);
if (csi && (i->info.priority > csi->info.priority)) {
fst_mgr_printf(MSG_WARNING,
"iface %s: higher priority resets session",
ifname);
_fst_mgr_session_reset(p->session, TRUE);
}
}
_fst_mgr_peer_try_to_initiate_next_setup(p, g);
if (p->session && p->session->non_compliant)
_fst_mgr_session_check_for_nc_transfer(p->session, p);
_fst_mgr_peer_print_connected_addr(p);
}
static void _fst_mgr_on_peer_disconnected(struct fst_mgr *mgr,
const char *ifname,
const u8* addr)
{
struct fst_mgr_group *g;
struct fst_mgr_peer *p;
struct fst_mgr_iface *i;
Boolean switch_initiated = FALSE;
g = _fst_mgr_group_by_ifname(mgr, ifname, &i);
if (!g) {
fst_mgr_printf(MSG_ERROR, "iface %s: no group found",
ifname);
return;
}
p = _fst_mgr_group_peer_by_addr(g, addr);
if (!p) {
fst_mgr_printf(MSG_ERROR, "group %s: peer " MACSTR
": not found",
g->info.id, MAC2STR(addr));
return;
}
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": disconnected (session=%p i=%s)",
g->info.id, MAC2STR(addr),
p->session, i->info.name);
fst_mgr_printf_session_info(p->session);
if (!p->session)
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": disconnect ignored (no session)",
g->info.id, MAC2STR(addr));
else if (i == _fst_mgr_session_get_old_iface(p->session) &&
_fst_mgr_session_is_ready(p->session)) {
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": initiating switch",
g->info.id, MAC2STR(addr));
if (p->session->non_compliant) {
_fst_mgr_session_nc_transfer(p->session, p);
switch_initiated = TRUE;
}
else if (!_fst_mgr_session_transfer(p->session))
switch_initiated = TRUE;
else {
fst_mgr_printf(MSG_ERROR, "group %s: peer " MACSTR
": switch failed, deinitializing session",
g->info.id, MAC2STR(addr));
_fst_mgr_peer_session_deinit(p, TRUE);
}
} else if (i == _fst_mgr_session_get_old_iface(p->session) ||
i == _fst_mgr_session_get_new_iface(p->session)) {
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": deinitializing session",
g->info.id, MAC2STR(addr));
_fst_mgr_peer_session_deinit(p, TRUE);
}
Boolean force_set_active = FALSE;
if (i == p->active_iface) {
_fst_mgr_peer_set_active_iface(p, NULL, g->drv);
force_set_active = TRUE;
}
_fst_mgr_peer_del_iface(p, i);
_fst_mgr_peer_print_connected_addr(p);
if (switch_initiated)
_fst_mgr_peer_set_active_iface(p, p->session->new_iface,
g->drv);
else if (dl_list_empty(&p->ifaces)) {
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": deinitializing peer (no more interfaces)",
g->info.id, MAC2STR(addr));
_fst_mgr_peer_deinit(p);
fst_mux_del_map_entry(g->drv, addr);
} else {
if (force_set_active) {
struct fst_mgr_iface *new_i =
_fst_mgr_peer_get_next_iface(p);
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR
": setting new active iface to %s",
g->info.id, MAC2STR(addr),
new_i ? new_i->info.name : "NULL");
_fst_mgr_peer_set_active_iface(p, new_i, g->drv);
}
_fst_mgr_peer_try_to_initiate_next_setup(p, g);
}
fst_cfgmgr_on_disconnect(&g->info, ifname, addr);
}
static void _fst_mgr_on_peer_state_changed(struct fst_mgr *mgr,
const union fst_event_extra *data)
{
if (data->peer_state.connected)
_fst_mgr_on_peer_connected(mgr,
data->peer_state.ifname,
data->peer_state.addr);
else
_fst_mgr_on_peer_disconnected(mgr,
data->peer_state.ifname,
data->peer_state.addr);
}
static void _fst_mgr_on_ctrl_notification_state_change(struct fst_mgr *mgr,
struct fst_mgr_group *g, struct fst_mgr_session *s,
union fst_event_extra *evext)
{
static unsigned retry_counter = 0;
struct fst_mgr_peer *p;
fst_mgr_printf(MSG_INFO, "session %u: state %s => %s",
s->id,
fst_session_state_name(evext->session_state.old_state),
fst_session_state_name(evext->session_state.new_state));
if (evext->session_state.new_state != FST_SESSION_STATE_INITIAL)
return;
p = _fst_mgr_group_peer_by_session(g, s);
WPA_ASSERT(p != NULL);
if (p == NULL) {
fst_mgr_printf(MSG_ERROR, "peer not found for group %s, session %u",
g->info.id, s->id);
return;
}
switch (evext->session_state.extra.to_initial.reason) {
case REASON_SETUP:
break;
case REASON_SWITCH:
WPA_ASSERT(evext->session_state.extra.to_initial.initiator !=
FST_INITIATOR_UNDEFINED);
fst_mgr_printf(MSG_INFO, "session %u: switched by %s side",
s->id,
(evext->session_state.extra.to_initial.initiator ==
FST_INITIATOR_LOCAL) ? "local" : "remote");
_fst_mgr_peer_set_active_iface(p, s->new_iface, g->drv);
s->state = FST_MGR_SESSION_STATE_IDLE;
const u8 *old_addr = _fst_mgr_peer_get_addr_of_iface(p, s->old_iface);
if (old_addr)
fst_cfgmgr_on_switch_completed(&g->info,
s->old_iface->info.name,
s->new_iface->info.name,
old_addr);
break;
case REASON_TEARDOWN:
case REASON_STT:
case REASON_REJECT:
case REASON_ERROR_PARAMS:
case REASON_RESET:
break;
default:
fst_mgr_printf(MSG_ERROR, "session %u: unknown reset reason %d",
s->id, evext->session_state.extra.to_initial.reason);
break;
}
/* delete old session */
_fst_mgr_peer_session_deinit(p, TRUE);
if (evext->session_state.extra.to_initial.reason == REASON_STT)
retry_counter++;
else
retry_counter = 0;
if (evext->session_state.extra.to_initial.reason == REASON_SETUP)
/* session requested by peer. We're done */
return;
/* session setup (initiate_next_setup) is invoked in following cases:
* 1. following STT (retry)
* 2. following other error cases like reject (TODO: should this be
considered as retry???)
* 3. following successful session switch
*/
if (retry_counter < fst_num_of_retries) {
fst_mgr_printf(MSG_INFO, "initiating setup. retry %d", retry_counter);
_fst_mgr_peer_try_to_initiate_next_setup(p, g);
} else {
fst_mgr_printf(MSG_INFO, "no more retries. give up");
retry_counter = 0;
}
}
static void _fst_mgr_on_setup(struct fst_mgr *mgr, u32 session_id)
{
struct fst_mgr_group *g, *g_new;
struct fst_mgr_session *s;
struct fst_mgr_iface *old_i;
struct fst_mgr_iface *new_i;
struct fst_mgr_peer *p;
struct fst_session_info sinfo;
if (fst_is_supplicant()) {
fst_mgr_printf(MSG_ERROR, "drop unexpected FST setup request from AP");
return;
}
memset(&sinfo, 0, sizeof(sinfo));
if (fst_session_get_info(session_id, &sinfo)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot get info",
session_id);
}
g = _fst_mgr_group_by_ifname(mgr, sinfo.old_ifname, &old_i);
if (!g) {
fst_mgr_printf(MSG_ERROR, "session %u: no group found for iface %s",
session_id, sinfo.old_ifname);
return;
}
g_new = _fst_mgr_group_by_ifname(mgr, sinfo.new_ifname, &new_i);
if (!g_new) {
fst_mgr_printf(MSG_ERROR, "session %u: no group found for iface %s",
session_id, sinfo.new_ifname);
return;
}
if (g != g_new) {
fst_mgr_printf(MSG_ERROR, "session %u: ifaces %s and %s belong to "
"different groups",
session_id, sinfo.old_ifname, sinfo.new_ifname);
return;
}
p = _fst_mgr_group_peer_by_addr(g, sinfo.old_peer_addr);
if (!p) {
fst_mgr_printf(MSG_ERROR, "session %u: no peer found for old mac "
MACSTR, session_id, MAC2STR(sinfo.old_peer_addr));
return;
}
if (p != _fst_mgr_group_peer_by_addr(g, sinfo.new_peer_addr)) {
fst_mgr_printf(MSG_ERROR,
"session %u: mismatch of peer with old mac (" MACSTR
") and peer with new mac (" MACSTR ")", session_id,
MAC2STR(sinfo.old_peer_addr), MAC2STR(sinfo.new_peer_addr));
return;
}
if (p->active_iface != old_i) {
fst_mgr_printf(MSG_WARNING, "session %u: sync active iface: "
"%s => %s",
session_id,
p->active_iface ? p->active_iface->info.name : "NONE",
sinfo.old_ifname);
if (_fst_mgr_peer_set_active_iface(p, old_i, g->drv)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot sync "
"active iface. Rejecting.",
session_id);
return;
}
}
if (_fst_mgr_session_init(g, &s, session_id)) {
fst_mgr_printf(MSG_ERROR, "session %u: cannot init session",
session_id);
return;
}
s->old_iface = old_i;
s->new_iface = new_i;
s->llt = sinfo.llt;
s->state = FST_MGR_SESSION_STATE_INITIATED;
s->non_compliant = FALSE;
if (new_i->info.priority > old_i->info.priority) {
_fst_mgr_session_set_llt(s, FST_LLT_SWITCH_IMMEDIATELY);
s->llt = FST_LLT_SWITCH_IMMEDIATELY;
}
if (p->session) {
fst_mgr_printf(MSG_WARNING, "session %u: deinit due to new setup",
p->session->id);
_fst_mgr_peer_session_deinit(p, TRUE);
}
p->session = s;
_fst_mgr_session_respond(s , TRUE);
fst_mgr_printf(MSG_ERROR, "session %u: established (responder)",
s->id);
}
static void _fst_mgr_ctrl_notification_cb_func(void *cb_ctx,
u32 session_id, enum fst_event_type event_type, void *extra)
{
struct fst_mgr *mgr = cb_ctx;
struct fst_mgr_group *g = NULL;
struct fst_mgr_session *s = NULL;
struct fst_mgr_iface *iface;
if (event_type != EVENT_FST_SETUP &&
session_id != FST_INVALID_SESSION_ID) {
g = _fst_mgr_group_by_session_id(mgr, session_id, &s);
if (!g) {
fst_mgr_printf(MSG_ERROR, "session %u: no group found",
session_id);
return;
}
}
switch (event_type) {
case EVENT_FST_ESTABLISHED:
if (s != NULL) {
fst_mgr_printf(MSG_WARNING, "session %u: established (initiator)",
session_id);
s->state = FST_MGR_SESSION_STATE_ESTABLISHED;
if (s->llt > 0)
/*
* at this point the active interface is the one
* with highest priority and we have a backup
* interface. Set active interface to aggressive
* link loss detection for fast switching to
* backup once signal quality is low.
*/
_fst_mgr_session_set_link_loss(s, true);
}
else
fst_mgr_printf(MSG_ERROR, "Cannot find session object");
break;
case EVENT_FST_SETUP:
_fst_mgr_on_setup(mgr, session_id);
break;
case EVENT_FST_SESSION_STATE_CHANGED:
if (g != NULL && s != NULL)
_fst_mgr_on_ctrl_notification_state_change(mgr, g, s, extra);
else
fst_mgr_printf(MSG_ERROR, "Cannot find group/session object");
break;
case EVENT_PEER_STATE_CHANGED:
_fst_mgr_on_peer_state_changed(mgr, extra);
break;
case EVENT_FST_SCAN_STARTED:
fst_mgr_printf(MSG_INFO, "scan started on %s", (char *)extra);
g = _fst_mgr_group_by_ifname(mgr, extra, &iface);
if (!g) {
fst_mgr_printf(MSG_ERROR, "no group found for iface %s", extra);
break;
}
fst_cfgmgr_on_scan_started(&g->info, extra);
break;
case EVENT_FST_SCAN_COMPLETED:
fst_mgr_printf(MSG_INFO, "scan complete on %s", (char *)extra);
g = _fst_mgr_group_by_ifname(mgr, extra, &iface);
if (!g) {
fst_mgr_printf(MSG_ERROR, "no group found for iface %s", extra);
break;
}
fst_cfgmgr_on_scan_completed(&g->info, extra);
break;
case EVENT_FST_SIGNAL_CHANGE:
fst_mgr_printf(MSG_INFO, "signal change on %s", (char *)extra);
g = _fst_mgr_group_by_ifname(mgr, extra, &iface);
if (!g) {
fst_mgr_printf(MSG_ERROR, "no group found for iface %s", (char *)extra);
break;
}
fst_cfgmgr_on_signal_change(&g->info, extra);
break;
default:
fst_mgr_printf(MSG_WARNING, "session %u: unknown event #%d",
session_id, event_type);
break;
}
}
/*
* FST Manager public API
*/
static struct fst_mgr g_fst_mgr;
static int g_fst_mgr_initalized = 0;
int fst_manager_init(void)
{
int res, i, nof_groups;
struct fst_group_info *groups = NULL;
os_memset(&g_fst_mgr, 0, sizeof(g_fst_mgr));
dl_list_init(&g_fst_mgr.groups);
res = fst_set_notify_cb(_fst_mgr_ctrl_notification_cb_func, &g_fst_mgr);
if (res != 0) {
goto finish;
}
res = fst_cfgmgr_on_global_init();
if (res < 0) {
goto finish;
}
res = fst_cfgmgr_get_groups(&groups);
if (res < 0) {
goto finish;
}
nof_groups = res;
for (i = 0; i < nof_groups; i++) {
res = _fst_mgr_group_init(&g_fst_mgr, &groups[i]);
if (res < 0) {
goto finish;
}
}
res = 0;
fst_mgr_printf(MSG_INFO, "manager with %d groups initialized",
nof_groups);
g_fst_mgr_initalized = 1;
finish:
if (groups)
fst_free(groups);
if (res)
fst_manager_deinit();
return res;
}
void fst_manager_deinit(void)
{
if (g_fst_mgr_initalized) {
fst_set_notify_cb(NULL, NULL);
while (!dl_list_empty(&g_fst_mgr.groups)) {
struct fst_mgr_group *g = dl_list_first(&g_fst_mgr.groups,
struct fst_mgr_group, mgr_lentry);
_fst_mgr_group_deinit(g);
}
os_memset(&g_fst_mgr, 0, sizeof(g_fst_mgr));
g_fst_mgr_initalized = 0;
fst_cfgmgr_on_global_deinit();
}
}
static int _fst_mgr_get_iface_info(const char* ifname, struct fst_iface_info *iface)
{
struct fst_mgr_group *g;
int i, nof_ifaces;
struct fst_iface_info *ifaces = NULL;
_fst_mgr_foreach_grp(&g_fst_mgr, g) {
nof_ifaces = fst_cfgmgr_get_group_ifaces(&g->info, &ifaces);
for (i = 0; i < nof_ifaces; i++) {
if (!os_strcmp(ifaces[i].name, ifname)) {
*iface = ifaces[i];
os_free(ifaces);
return 0;
}
}
os_free(ifaces);
ifaces = NULL;
}
return -1;
}
static int _fst_mgr_group_enslaved_count(struct fst_mgr_group *g)
{
return dl_list_len(&g->ifaces);
}
int fst_manager_enslave(const char *gname, const char* ifname, Boolean enslave)
{
struct fst_mgr_group *g, *g2;
int res, enslaved_count;
if (!g_fst_mgr_initalized)
return -1;
g = _fst_mgr_group_by_name(&g_fst_mgr, gname);
if (!g) {
fst_mgr_printf(MSG_ERROR, "group %s not found", gname);
return -1;
}
g2 = _fst_mgr_group_by_ifname(&g_fst_mgr, ifname, NULL);
if (enslave && g2) {
fst_mgr_printf(MSG_DEBUG, "iface %s: already enslaved", ifname);
return 0;
} else if (!enslave && !g2) {
fst_mgr_printf(MSG_DEBUG, "iface %s: already released", ifname);
return 0;
}
if (!enslave)
fst_rate_upgrade_on_release(&g->info, ifname);
enslaved_count = _fst_mgr_group_enslaved_count(g);
res = fst_iface_enslave(&g->info, ifname, enslave);
if (res)
return res;
res = fst_cfgmgr_update_txqueuelen(&g->info);
if (res) {
fst_mgr_printf(MSG_ERROR, "group %s: could not set txqueuelen, continue anyway\n",
gname);
// continue anyway
}
if (enslave && enslaved_count == 0) {
res = fst_cfgmgr_set_mux_iface_up(&g->info, TRUE);
if (res)
return res;
} else if (!enslave && enslaved_count == 1) {
res = fst_cfgmgr_set_mux_iface_up(&g->info, FALSE);
if (res)
return res;
}
if (!enslave) {
// in manual enslave, set interface up after release
set_iface_up(ifname, TRUE);
}
if (enslave) {
struct fst_iface_info ifinfo;
res = _fst_mgr_get_iface_info(ifname, &ifinfo);
if (res) {
fst_mgr_printf(MSG_ERROR, "iface %s not found", ifname);
return -1;
}
res = _fst_mgr_iface_init(g, &ifinfo, g->drv);
if (res) {
fst_mgr_printf(MSG_ERROR, "Cannot init iface %s", ifinfo.name);
return -1;
}
} else {
struct fst_mgr_iface *iface;
struct fst_mgr_peer *p, *p2;
dl_list_for_each_safe(p, p2, &g->peers, struct fst_mgr_peer, grp_lentry) {
struct fst_mgr_peer_iface *pi, *pi2;
dl_list_for_each_safe(pi, pi2, &p->ifaces, struct fst_mgr_peer_iface, peer_lentry) {
struct fst_iface_info *ifinfo = &pi->iface->info;
if (!os_strcmp(ifinfo->name, ifname)) {
fst_mgr_printf(MSG_INFO, "cleaning peer " MACSTR, MAC2STR(pi->addr));
_fst_mgr_on_peer_disconnected(&g_fst_mgr, ifinfo->name, pi->addr);
}
}
}
_fst_grp_foreach_iface(g, iface) {
if (!os_strcmp(iface->info.name, ifname)) {
fst_mgr_printf(MSG_INFO, "iface %s deinit", ifname);
_fst_mgr_iface_deinit(iface, g->drv);
break;
}
}
}
fst_mux_stop(g->drv);
if (fst_mux_start(g->drv) != 0) {
fst_mgr_printf(MSG_ERROR, "Cannot start driver for group %s",
g->info.id);
return -1;
}
if (enslave)
_fst_mgr_group_check_connections(&g_fst_mgr, &g->info);
return 0;
}
int fst_manager_is_enslaved(const char *gname, const char* ifname,
Boolean *isEnslaved)
{
struct fst_mgr_group *g, *g2;
if (!g_fst_mgr_initalized || !isEnslaved)
return -1;
g = _fst_mgr_group_by_name(&g_fst_mgr, gname);
if (!g) {
fst_mgr_printf(MSG_ERROR, "group %s not found", gname);
return -1;
}
g2 = _fst_mgr_group_by_ifname(&g_fst_mgr, ifname, NULL);
*isEnslaved = ((g2 != NULL) && (g == g2));
return 0;
}
int fst_manager_set_mac_address(const char *gname, const u8 *mac)
{
struct fst_mgr_group *g;
struct fst_mgr_iface *i;
int res;
if (!g_fst_mgr_initalized)
return -1;
g = _fst_mgr_group_by_name(&g_fst_mgr, gname);
if (!g) {
fst_mgr_printf(MSG_ERROR, "group name %s not found", gname);
return -1;
}
_fst_grp_foreach_iface(g, i) {
set_iface_up(i->info.name, FALSE);
}
res = fst_set_mac_address(&g->info, mac);
_fst_grp_foreach_iface(g, i) {
set_iface_up(i->info.name, TRUE);
}
return res;
}
int fst_manager_set_mux_iface_name(const char *gname, const char *ifname)
{
fst_mgr_printf(MSG_ERROR, "STUB: set_mux_iface_name, group %s ifname %s",
gname, ifname);
return -1;
}
int fst_manager_rename_group_interface(const char *gname, const char *ifname,
const char *newifname)
{
struct fst_mgr_group *g;
struct fst_mgr_iface *i;
if (!g_fst_mgr_initalized)
return -1;
fst_mgr_printf(MSG_INFO, "group %s rename interface %s => %s", gname, ifname, newifname);
g = _fst_mgr_group_by_name(&g_fst_mgr, gname);
if (!g) {
fst_mgr_printf(MSG_ERROR, "group name %s not found", gname);
return -1;
}
_fst_grp_foreach_iface(g, i) {
if (!os_strcmp(ifname, i->info.name)) {
fst_mgr_printf(MSG_INFO, "cannot rename enslaved interface %s", ifname);
return -1;
}
}
return fst_cfgmgr_rename_interface(gname, ifname, newifname);
}
int fst_manager_session_transfer(const char *old_ifname, const u8 *peer_addr)
{
struct fst_mgr_group *g;
struct fst_mgr_peer *p;
struct fst_mgr_iface *i;
g = _fst_mgr_group_by_ifname(&g_fst_mgr, old_ifname, &i);
if (!g) {
fst_mgr_printf(MSG_ERROR, "iface %s: no group found", old_ifname);
return -1;
}
p = _fst_mgr_group_peer_by_addr(g, peer_addr);
if (!p) {
fst_mgr_printf(MSG_ERROR, "group %s: peer " MACSTR ": not found",
g->info.id, MAC2STR(peer_addr));
return -1;
}
if (!p->session) {
fst_mgr_printf(MSG_INFO, "no session for peer " MACSTR, MAC2STR(peer_addr));
return -1;
}
if (i != _fst_mgr_session_get_old_iface(p->session) ||
!_fst_mgr_session_is_ready(p->session)) {
fst_mgr_printf(MSG_INFO, "session doesn't match for peer " MACSTR, MAC2STR(peer_addr));
return -1;
}
fst_mgr_printf(MSG_INFO, "group %s: peer " MACSTR ": initiating switch",
g->info.id, MAC2STR(peer_addr));
fst_mgr_printf_session_info(p->session);
if (!_fst_mgr_session_transfer(p->session)) {
_fst_mgr_peer_set_active_iface(p, p->session->new_iface, g->drv);
} else {
fst_mgr_printf(MSG_ERROR, "group %s: peer " MACSTR
": switch failed, deinitializing session",
g->info.id, MAC2STR(peer_addr));
_fst_mgr_peer_session_deinit(p, TRUE);
}
_fst_mgr_peer_try_to_initiate_next_setup(p, g);
return 0;
}