1945 lines
48 KiB
C
Executable File
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;
|
|
}
|