923 lines
21 KiB
C
923 lines
21 KiB
C
|
/*
|
||
|
* FST module - Control Interface implementation
|
||
|
* Copyright (c) 2014, Qualcomm Atheros, Inc.
|
||
|
*
|
||
|
* This software may be distributed under the terms of the BSD license.
|
||
|
* See README for more details.
|
||
|
*/
|
||
|
|
||
|
#include "utils/includes.h"
|
||
|
#include "utils/common.h"
|
||
|
#include "common/defs.h"
|
||
|
#include "list.h"
|
||
|
#include "fst/fst.h"
|
||
|
#include "fst/fst_internal.h"
|
||
|
#include "fst_ctrl_defs.h"
|
||
|
#include "fst_ctrl_iface.h"
|
||
|
|
||
|
|
||
|
static struct fst_group * get_fst_group_by_id(const char *id)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
|
||
|
foreach_fst_group(g) {
|
||
|
const char *group_id = fst_group_get_id(g);
|
||
|
|
||
|
if (os_strncmp(group_id, id, os_strlen(group_id)) == 0)
|
||
|
return g;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* notifications */
|
||
|
Boolean format_session_state_extra(const union fst_event_extra *extra,
|
||
|
char *buffer, int size)
|
||
|
{
|
||
|
int len;
|
||
|
char reject_str[32] = FST_CTRL_PVAL_NONE;
|
||
|
const char *initiator = FST_CTRL_PVAL_NONE;
|
||
|
const struct fst_event_extra_session_state *ss;
|
||
|
|
||
|
if (!extra)
|
||
|
return TRUE;
|
||
|
|
||
|
ss = &extra->session_state;
|
||
|
if (ss->new_state != FST_SESSION_STATE_INITIAL)
|
||
|
return TRUE;
|
||
|
|
||
|
switch (ss->extra.to_initial.reason) {
|
||
|
case REASON_REJECT:
|
||
|
if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS)
|
||
|
os_snprintf(reject_str, sizeof(reject_str), "%u",
|
||
|
ss->extra.to_initial.reject_code);
|
||
|
/* no break */
|
||
|
case REASON_TEARDOWN:
|
||
|
case REASON_SWITCH:
|
||
|
switch (ss->extra.to_initial.initiator) {
|
||
|
case FST_INITIATOR_LOCAL:
|
||
|
initiator = FST_CS_PVAL_INITIATOR_LOCAL;
|
||
|
break;
|
||
|
case FST_INITIATOR_REMOTE:
|
||
|
initiator = FST_CS_PVAL_INITIATOR_REMOTE;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
len = os_snprintf(buffer, size,
|
||
|
FST_CES_PNAME_REASON "=%s "
|
||
|
FST_CES_PNAME_REJECT_CODE "=%s "
|
||
|
FST_CES_PNAME_INITIATOR "=%s",
|
||
|
fst_reason_name(ss->extra.to_initial.reason),
|
||
|
reject_str, initiator);
|
||
|
|
||
|
return !os_snprintf_error(size, len);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void fst_ctrl_iface_notify(u32 session_id,
|
||
|
enum fst_event_type event_type,
|
||
|
const union fst_event_extra *extra)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *f;
|
||
|
char extra_str[128] = "";
|
||
|
const struct fst_event_extra_session_state *ss;
|
||
|
const struct fst_event_extra_iface_state *is;
|
||
|
const struct fst_event_extra_peer_state *ps;
|
||
|
|
||
|
/*
|
||
|
* FST can use any of interface objects as it only sends messages
|
||
|
* on global Control Interface, so we just pick the 1st one.
|
||
|
*/
|
||
|
|
||
|
g = fst_first_group();
|
||
|
if (!g)
|
||
|
return;
|
||
|
|
||
|
f = fst_group_first_iface(g);
|
||
|
if (!f)
|
||
|
return;
|
||
|
|
||
|
WPA_ASSERT(f->iface_obj.ctx);
|
||
|
|
||
|
switch (event_type) {
|
||
|
case EVENT_FST_IFACE_STATE_CHANGED:
|
||
|
if (!extra)
|
||
|
return;
|
||
|
is = &extra->iface_state;
|
||
|
wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO,
|
||
|
FST_CTRL_EVENT_IFACE " %s "
|
||
|
FST_CEI_PNAME_IFNAME "=%s "
|
||
|
FST_CEI_PNAME_GROUP "=%s",
|
||
|
is->attached ? FST_CEI_PNAME_ATTACHED :
|
||
|
FST_CEI_PNAME_DETACHED,
|
||
|
is->ifname, is->group_id);
|
||
|
break;
|
||
|
case EVENT_PEER_STATE_CHANGED:
|
||
|
if (!extra)
|
||
|
return;
|
||
|
ps = &extra->peer_state;
|
||
|
wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
|
||
|
FST_CTRL_EVENT_PEER " %s "
|
||
|
FST_CEP_PNAME_IFNAME "=%s "
|
||
|
FST_CEP_PNAME_ADDR "=" MACSTR,
|
||
|
ps->connected ? FST_CEP_PNAME_CONNECTED :
|
||
|
FST_CEP_PNAME_DISCONNECTED,
|
||
|
ps->ifname, MAC2STR(ps->addr));
|
||
|
break;
|
||
|
case EVENT_FST_SESSION_STATE_CHANGED:
|
||
|
if (!extra)
|
||
|
return;
|
||
|
if (!format_session_state_extra(extra, extra_str,
|
||
|
sizeof(extra_str))) {
|
||
|
fst_printf(MSG_ERROR,
|
||
|
"CTRL: Cannot format STATE_CHANGE extra");
|
||
|
extra_str[0] = 0;
|
||
|
}
|
||
|
ss = &extra->session_state;
|
||
|
wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
|
||
|
FST_CTRL_EVENT_SESSION " "
|
||
|
FST_CES_PNAME_SESSION_ID "=%u "
|
||
|
FST_CES_PNAME_EVT_TYPE "=%s "
|
||
|
FST_CES_PNAME_OLD_STATE "=%s "
|
||
|
FST_CES_PNAME_NEW_STATE "=%s %s",
|
||
|
session_id,
|
||
|
fst_session_event_type_name(event_type),
|
||
|
fst_session_state_name(ss->old_state),
|
||
|
fst_session_state_name(ss->new_state),
|
||
|
extra_str);
|
||
|
break;
|
||
|
case EVENT_FST_ESTABLISHED:
|
||
|
case EVENT_FST_SETUP:
|
||
|
wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
|
||
|
FST_CTRL_EVENT_SESSION " "
|
||
|
FST_CES_PNAME_SESSION_ID "=%u "
|
||
|
FST_CES_PNAME_EVT_TYPE "=%s",
|
||
|
session_id,
|
||
|
fst_session_event_type_name(event_type));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* command processors */
|
||
|
|
||
|
/* fst session_get */
|
||
|
static int session_get(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
struct fst_iface *new_iface, *old_iface;
|
||
|
const u8 *old_peer_addr, *new_peer_addr;
|
||
|
u32 id;
|
||
|
|
||
|
id = strtoul(session_id, NULL, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
old_peer_addr = fst_session_get_peer_addr(s, TRUE);
|
||
|
new_peer_addr = fst_session_get_peer_addr(s, FALSE);
|
||
|
new_iface = fst_session_get_iface(s, FALSE);
|
||
|
old_iface = fst_session_get_iface(s, TRUE);
|
||
|
|
||
|
return os_snprintf(buf, buflen,
|
||
|
FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n"
|
||
|
FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n"
|
||
|
FST_CSG_PNAME_NEW_IFNAME "=%s\n"
|
||
|
FST_CSG_PNAME_OLD_IFNAME "=%s\n"
|
||
|
FST_CSG_PNAME_LLT "=%u\n"
|
||
|
FST_CSG_PNAME_STATE "=%s\n",
|
||
|
MAC2STR(old_peer_addr),
|
||
|
MAC2STR(new_peer_addr),
|
||
|
new_iface ? fst_iface_get_name(new_iface) :
|
||
|
FST_CTRL_PVAL_NONE,
|
||
|
old_iface ? fst_iface_get_name(old_iface) :
|
||
|
FST_CTRL_PVAL_NONE,
|
||
|
fst_session_get_llt(s),
|
||
|
fst_session_state_name(fst_session_get_state(s)));
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_set */
|
||
|
static int session_set(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
char *p, *q;
|
||
|
u32 id;
|
||
|
int ret;
|
||
|
|
||
|
id = strtoul(session_id, &p, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (*p != ' ' || !(q = os_strchr(p + 1, '=')))
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
p++;
|
||
|
|
||
|
if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) {
|
||
|
ret = fst_session_set_str_ifname(s, q + 1, TRUE);
|
||
|
} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) {
|
||
|
ret = fst_session_set_str_ifname(s, q + 1, FALSE);
|
||
|
} else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) {
|
||
|
ret = fst_session_set_str_peer_addr(s, q + 1, TRUE);
|
||
|
} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) {
|
||
|
ret = fst_session_set_str_peer_addr(s, q + 1, FALSE);
|
||
|
} else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) {
|
||
|
ret = fst_session_set_str_llt(s, q + 1);
|
||
|
} else {
|
||
|
fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_add/remove */
|
||
|
static int session_add(const char *group_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
struct fst_session *s;
|
||
|
|
||
|
g = get_fst_group_by_id(group_id);
|
||
|
if (!g) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
|
||
|
group_id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
s = fst_session_create(g);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_ERROR,
|
||
|
"CTRL: Cannot create session for group '%s'",
|
||
|
group_id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s));
|
||
|
}
|
||
|
|
||
|
|
||
|
static int session_remove(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
struct fst_group *g;
|
||
|
u32 id;
|
||
|
|
||
|
id = strtoul(session_id, NULL, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
g = fst_session_get_group(s);
|
||
|
fst_session_reset(s);
|
||
|
fst_session_delete(s);
|
||
|
fst_group_delete_if_empty(g);
|
||
|
|
||
|
return os_snprintf(buf, buflen, "OK\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_initiate */
|
||
|
static int session_initiate(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
u32 id;
|
||
|
|
||
|
id = strtoul(session_id, NULL, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (fst_session_initiate_setup(s)) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
return os_snprintf(buf, buflen, "OK\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_respond */
|
||
|
static int session_respond(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
char *p;
|
||
|
u32 id;
|
||
|
u8 status_code;
|
||
|
|
||
|
id = strtoul(session_id, &p, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (*p != ' ')
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
p++;
|
||
|
|
||
|
if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) {
|
||
|
status_code = WLAN_STATUS_SUCCESS;
|
||
|
} else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) {
|
||
|
status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
|
||
|
} else {
|
||
|
fst_printf(MSG_WARNING,
|
||
|
"CTRL: session %u: unknown response status: %s",
|
||
|
id, p);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (fst_session_respond(s, status_code)) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u",
|
||
|
id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
fst_printf(MSG_INFO, "CTRL: session %u responded", id);
|
||
|
|
||
|
return os_snprintf(buf, buflen, "OK\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_transfer */
|
||
|
static int session_transfer(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
u32 id;
|
||
|
|
||
|
id = strtoul(session_id, NULL, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (fst_session_initiate_switch(s)) {
|
||
|
fst_printf(MSG_WARNING,
|
||
|
"CTRL: Cannot initiate ST for session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
return os_snprintf(buf, buflen, "OK\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst session_teardown */
|
||
|
static int session_teardown(const char *session_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_session *s;
|
||
|
u32 id;
|
||
|
|
||
|
id = strtoul(session_id, NULL, 0);
|
||
|
|
||
|
s = fst_session_get_by_id(id);
|
||
|
if (!s) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
if (fst_session_tear_down_setup(s)) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u",
|
||
|
id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
return os_snprintf(buf, buflen, "OK\n");
|
||
|
}
|
||
|
|
||
|
/* fst list_sessions */
|
||
|
struct list_sessions_cb_ctx {
|
||
|
char *buf;
|
||
|
size_t buflen;
|
||
|
size_t reply_len;
|
||
|
};
|
||
|
|
||
|
|
||
|
static void list_session_enum_cb(struct fst_group *g, struct fst_session *s,
|
||
|
void *ctx)
|
||
|
{
|
||
|
struct list_sessions_cb_ctx *c = ctx;
|
||
|
int ret;
|
||
|
|
||
|
ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s));
|
||
|
|
||
|
c->buf += ret;
|
||
|
c->buflen -= ret;
|
||
|
c->reply_len += ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int list_sessions(const char *group_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct list_sessions_cb_ctx ctx;
|
||
|
struct fst_group *g;
|
||
|
|
||
|
g = get_fst_group_by_id(group_id);
|
||
|
if (!g) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
|
||
|
group_id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
ctx.buf = buf;
|
||
|
ctx.buflen = buflen;
|
||
|
ctx.reply_len = 0;
|
||
|
|
||
|
fst_session_enum(g, list_session_enum_cb, &ctx);
|
||
|
|
||
|
ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n");
|
||
|
|
||
|
return ctx.reply_len;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst iface_peers */
|
||
|
static int iface_peers(const char *group_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
const char *ifname;
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *f;
|
||
|
struct fst_get_peer_ctx *ctx;
|
||
|
const u8 *addr;
|
||
|
unsigned found = 0;
|
||
|
int ret = 0;
|
||
|
|
||
|
g = get_fst_group_by_id(group_id);
|
||
|
if (!g) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
|
||
|
group_id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
ifname = os_strchr(group_id, ' ');
|
||
|
if (!ifname)
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
ifname++;
|
||
|
|
||
|
foreach_fst_group_iface(g, f) {
|
||
|
const char *in = fst_iface_get_name(f);
|
||
|
|
||
|
if (os_strncmp(ifname, in, os_strlen(in)) == 0) {
|
||
|
found = 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!found)
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
|
||
|
addr = fst_iface_get_peer_first(f, &ctx, FALSE);
|
||
|
for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, FALSE)) {
|
||
|
int res;
|
||
|
|
||
|
res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n",
|
||
|
MAC2STR(addr));
|
||
|
if (os_snprintf_error(buflen - ret, res))
|
||
|
break;
|
||
|
ret += res;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int get_peer_mbies(const char *params, char *buf, size_t buflen)
|
||
|
{
|
||
|
char *endp;
|
||
|
char ifname[FST_MAX_INTERFACE_SIZE];
|
||
|
u8 peer_addr[ETH_ALEN];
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *iface = NULL;
|
||
|
struct wpabuf *mbies;
|
||
|
int ret;
|
||
|
|
||
|
if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) ||
|
||
|
!*ifname)
|
||
|
goto problem;
|
||
|
|
||
|
while (isspace(*endp))
|
||
|
endp++;
|
||
|
if (fst_read_peer_addr(endp, peer_addr))
|
||
|
goto problem;
|
||
|
|
||
|
foreach_fst_group(g) {
|
||
|
iface = fst_group_get_iface_by_name(g, ifname);
|
||
|
if (!iface)
|
||
|
continue;
|
||
|
}
|
||
|
if (!iface)
|
||
|
goto problem;
|
||
|
|
||
|
mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
|
||
|
if (!mbies)
|
||
|
goto problem;
|
||
|
|
||
|
ret = print_mb_ies(mbies, buf, buflen);
|
||
|
if ((size_t) ret != wpabuf_len(mbies) * 2)
|
||
|
fst_printf(MSG_WARNING, "MB IEs copied only partially");
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
problem:
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst list_ifaces */
|
||
|
static int list_ifaces(const char *group_id, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *f;
|
||
|
int ret = 0;
|
||
|
|
||
|
g = get_fst_group_by_id(group_id);
|
||
|
if (!g) {
|
||
|
fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
|
||
|
group_id);
|
||
|
return os_snprintf(buf, buflen, "FAIL\n");
|
||
|
}
|
||
|
|
||
|
foreach_fst_group_iface(g, f) {
|
||
|
int res;
|
||
|
const u8 *iface_addr = fst_iface_get_addr(f);
|
||
|
|
||
|
res = os_snprintf(buf + ret, buflen - ret,
|
||
|
"%s|" MACSTR "|%u|%u\n",
|
||
|
fst_iface_get_name(f),
|
||
|
MAC2STR(iface_addr),
|
||
|
fst_iface_get_priority(f),
|
||
|
fst_iface_get_llt(f));
|
||
|
if (os_snprintf_error(buflen - ret, res))
|
||
|
break;
|
||
|
ret += res;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst list_groups */
|
||
|
static int list_groups(const char *cmd, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
int ret = 0;
|
||
|
|
||
|
foreach_fst_group(g) {
|
||
|
int res;
|
||
|
|
||
|
res = os_snprintf(buf + ret, buflen - ret, "%s\n",
|
||
|
fst_group_get_id(g));
|
||
|
if (os_snprintf_error(buflen - ret, res))
|
||
|
break;
|
||
|
ret += res;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
int print_mb_ies(struct wpabuf *mbies, char *buf, size_t buflen)
|
||
|
{
|
||
|
const u8 *p = wpabuf_head(mbies);
|
||
|
size_t s = wpabuf_len(mbies);
|
||
|
int ret = 0;
|
||
|
|
||
|
while ((size_t) ret < buflen && s--) {
|
||
|
int res;
|
||
|
|
||
|
res = os_snprintf(buf + ret, buflen - ret, "%02x", *p++);
|
||
|
if (os_snprintf_error(buflen - ret, res))
|
||
|
break;
|
||
|
ret += res;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static const char * band_freq(enum mb_band_id band)
|
||
|
{
|
||
|
static const char *band_names[] = {
|
||
|
[MB_BAND_ID_WIFI_2_4GHZ] "2.4GHZ",
|
||
|
[MB_BAND_ID_WIFI_5GHZ] "5GHZ",
|
||
|
[MB_BAND_ID_WIFI_60GHZ] "60GHZ",
|
||
|
};
|
||
|
|
||
|
return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names));
|
||
|
}
|
||
|
|
||
|
|
||
|
static int print_band(unsigned num, struct fst_iface *iface, const u8 *addr,
|
||
|
char *buf, size_t buflen)
|
||
|
{
|
||
|
struct wpabuf *wpabuf;
|
||
|
enum hostapd_hw_mode hw_mode;
|
||
|
u8 channel;
|
||
|
int ret = 0;
|
||
|
|
||
|
fst_iface_get_channel_info(iface, &hw_mode, &channel);
|
||
|
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n",
|
||
|
num, band_freq(fst_hw_mode_to_band(hw_mode)));
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n",
|
||
|
num, fst_iface_get_name(iface));
|
||
|
wpabuf = fst_iface_get_peer_mb_ie(iface, addr);
|
||
|
if (wpabuf) {
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=",
|
||
|
num);
|
||
|
ret += print_mb_ies(wpabuf, buf + ret, buflen - ret);
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "\n");
|
||
|
}
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n",
|
||
|
num, fst_iface_get_group_id(iface));
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n",
|
||
|
num, fst_iface_get_priority(iface));
|
||
|
ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n",
|
||
|
num, fst_iface_get_llt(iface));
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
|
||
|
Boolean attached)
|
||
|
{
|
||
|
union fst_event_extra extra;
|
||
|
|
||
|
os_memset(&extra, 0, sizeof(extra));
|
||
|
extra.iface_state.attached = attached;
|
||
|
os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i),
|
||
|
sizeof(extra.iface_state.ifname));
|
||
|
os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i),
|
||
|
sizeof(extra.iface_state.group_id));
|
||
|
|
||
|
fst_ctrl_iface_notify(FST_INVALID_SESSION_ID,
|
||
|
EVENT_FST_IFACE_STATE_CHANGED, &extra);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int fst_ctrl_iface_on_iface_added(struct fst_iface *i)
|
||
|
{
|
||
|
fst_ctrl_iface_on_iface_state_changed(i, TRUE);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void fst_ctrl_iface_on_iface_removed(struct fst_iface *i)
|
||
|
{
|
||
|
fst_ctrl_iface_on_iface_state_changed(i, FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void fst_ctrl_iface_on_event(enum fst_event_type event_type,
|
||
|
struct fst_iface *i, struct fst_session *s,
|
||
|
const union fst_event_extra *extra)
|
||
|
{
|
||
|
u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID;
|
||
|
|
||
|
fst_ctrl_iface_notify(session_id, event_type, extra);
|
||
|
}
|
||
|
|
||
|
|
||
|
static const struct fst_ctrl ctrl_cli = {
|
||
|
.on_iface_added = fst_ctrl_iface_on_iface_added,
|
||
|
.on_iface_removed = fst_ctrl_iface_on_iface_removed,
|
||
|
.on_event = fst_ctrl_iface_on_event,
|
||
|
};
|
||
|
|
||
|
const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
|
||
|
|
||
|
|
||
|
int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *f;
|
||
|
unsigned num = 0;
|
||
|
int ret = 0;
|
||
|
|
||
|
foreach_fst_group(g) {
|
||
|
foreach_fst_group_iface(g, f) {
|
||
|
if (fst_iface_is_connected(f, addr)) {
|
||
|
ret += print_band(num++, f, addr,
|
||
|
buf + ret, buflen - ret);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst ctrl processor */
|
||
|
int fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size)
|
||
|
{
|
||
|
static const struct fst_command {
|
||
|
const char *name;
|
||
|
unsigned has_param;
|
||
|
int (*process)(const char *group_id, char *buf, size_t buflen);
|
||
|
} commands[] = {
|
||
|
{ FST_CMD_LIST_GROUPS, 0, list_groups},
|
||
|
{ FST_CMD_LIST_IFACES, 1, list_ifaces},
|
||
|
{ FST_CMD_IFACE_PEERS, 1, iface_peers},
|
||
|
{ FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies},
|
||
|
{ FST_CMD_LIST_SESSIONS, 1, list_sessions},
|
||
|
{ FST_CMD_SESSION_ADD, 1, session_add},
|
||
|
{ FST_CMD_SESSION_REMOVE, 1, session_remove},
|
||
|
{ FST_CMD_SESSION_GET, 1, session_get},
|
||
|
{ FST_CMD_SESSION_SET, 1, session_set},
|
||
|
{ FST_CMD_SESSION_INITIATE, 1, session_initiate},
|
||
|
{ FST_CMD_SESSION_RESPOND, 1, session_respond},
|
||
|
{ FST_CMD_SESSION_TRANSFER, 1, session_transfer},
|
||
|
{ FST_CMD_SESSION_TEARDOWN, 1, session_teardown},
|
||
|
{ NULL, 0, NULL }
|
||
|
};
|
||
|
const struct fst_command *c;
|
||
|
const char *p;
|
||
|
const char *temp;
|
||
|
Boolean non_spaces_found;
|
||
|
|
||
|
for (c = commands; c->name; c++) {
|
||
|
if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0)
|
||
|
continue;
|
||
|
p = cmd + os_strlen(c->name);
|
||
|
if (c->has_param) {
|
||
|
if (!isspace(p[0]))
|
||
|
return os_snprintf(reply, reply_size, "FAIL\n");
|
||
|
p++;
|
||
|
temp = p;
|
||
|
non_spaces_found = FALSE;
|
||
|
while (*temp) {
|
||
|
if (!isspace(*temp)) {
|
||
|
non_spaces_found = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
temp++;
|
||
|
}
|
||
|
if (!non_spaces_found)
|
||
|
return os_snprintf(reply, reply_size, "FAIL\n");
|
||
|
}
|
||
|
return c->process(p, reply, reply_size);
|
||
|
}
|
||
|
|
||
|
return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
int fst_read_next_int_param(const char *params, Boolean *valid, char **endp)
|
||
|
{
|
||
|
int ret = -1;
|
||
|
const char *curp;
|
||
|
|
||
|
*valid = FALSE;
|
||
|
*endp = (char *) params;
|
||
|
curp = params;
|
||
|
if (*curp) {
|
||
|
ret = (int) strtol(curp, endp, 0);
|
||
|
if (!**endp || isspace(**endp))
|
||
|
*valid = TRUE;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
|
||
|
char **endp)
|
||
|
{
|
||
|
size_t max_chars_to_copy;
|
||
|
char *cur_dest;
|
||
|
|
||
|
if (buflen <= 1)
|
||
|
return EINVAL;
|
||
|
|
||
|
*endp = (char *) params;
|
||
|
while (isspace(**endp))
|
||
|
(*endp)++;
|
||
|
if (!**endp)
|
||
|
return EINVAL;
|
||
|
|
||
|
max_chars_to_copy = buflen - 1;
|
||
|
/* We need 1 byte for the terminating zero */
|
||
|
cur_dest = buf;
|
||
|
while (**endp && !isspace(**endp) && max_chars_to_copy > 0) {
|
||
|
*cur_dest = **endp;
|
||
|
(*endp)++;
|
||
|
cur_dest++;
|
||
|
max_chars_to_copy--;
|
||
|
}
|
||
|
*cur_dest = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int fst_read_peer_addr(const char *mac, u8 *peer_addr)
|
||
|
{
|
||
|
if (hwaddr_aton(mac, peer_addr)) {
|
||
|
fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string",
|
||
|
mac);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (is_zero_ether_addr(peer_addr) ||
|
||
|
is_multicast_ether_addr(peer_addr)) {
|
||
|
fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr",
|
||
|
mac);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
|
||
|
struct fst_iface_cfg *cfg)
|
||
|
{
|
||
|
char *pos;
|
||
|
char *endp;
|
||
|
Boolean is_valid;
|
||
|
int val;
|
||
|
|
||
|
if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) ||
|
||
|
fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id),
|
||
|
&endp))
|
||
|
return EINVAL;
|
||
|
|
||
|
cfg->llt = FST_DEFAULT_LLT_CFG_VALUE;
|
||
|
cfg->priority = 0;
|
||
|
pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT);
|
||
|
if (pos) {
|
||
|
pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT);
|
||
|
if (*pos == '=') {
|
||
|
val = fst_read_next_int_param(pos + 1, &is_valid,
|
||
|
&endp);
|
||
|
if (is_valid)
|
||
|
cfg->llt = val;
|
||
|
}
|
||
|
}
|
||
|
pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY);
|
||
|
if (pos) {
|
||
|
pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY);
|
||
|
if (*pos == '=') {
|
||
|
val = fst_read_next_int_param(pos + 1, &is_valid,
|
||
|
&endp);
|
||
|
if (is_valid)
|
||
|
cfg->priority = (u8) val;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size)
|
||
|
{
|
||
|
char *endp;
|
||
|
|
||
|
if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp))
|
||
|
return EINVAL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* fst iface_detach */
|
||
|
int fst_iface_detach(const char *ifname)
|
||
|
{
|
||
|
struct fst_group *g;
|
||
|
struct fst_iface *f;
|
||
|
|
||
|
foreach_fst_group(g) {
|
||
|
f = fst_group_get_iface_by_name(g, ifname);
|
||
|
if (f) {
|
||
|
fst_detach(f);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return EINVAL;
|
||
|
}
|