/* * 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 */ static bool format_session_state_extra(const union fst_event_extra *extra, char *buffer, size_t 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; 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); /* fall through */ 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(struct fst_iface *f, u32 session_id, enum fst_event_type event_type, const union fst_event_extra *extra) { struct fst_group *g; 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. */ if (!f) { foreach_fst_group(g) { f = fst_group_first_iface(g); if (f) break; } 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"); } #ifdef CONFIG_FST_TEST /* fst test_request */ static int test_request(const char *request, char *buf, size_t buflen) { const char *p = request; int ret; if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST, os_strlen(FST_CTR_SEND_SETUP_REQUEST))) { ret = fst_test_req_send_fst_request( p + os_strlen(FST_CTR_SEND_SETUP_REQUEST)); } else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE, os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) { ret = fst_test_req_send_fst_response( p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE)); } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST, os_strlen(FST_CTR_SEND_ACK_REQUEST))) { ret = fst_test_req_send_ack_request( p + os_strlen(FST_CTR_SEND_ACK_REQUEST)); } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE, os_strlen(FST_CTR_SEND_ACK_RESPONSE))) { ret = fst_test_req_send_ack_response( p + os_strlen(FST_CTR_SEND_ACK_RESPONSE)); } else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN, os_strlen(FST_CTR_SEND_TEAR_DOWN))) { ret = fst_test_req_send_tear_down( p + os_strlen(FST_CTR_SEND_TEAR_DOWN)); } else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID, os_strlen(FST_CTR_GET_FSTS_ID))) { u32 fsts_id = fst_test_req_get_fsts_id( p + os_strlen(FST_CTR_GET_FSTS_ID)); if (fsts_id != FST_FSTS_ID_NOT_FOUND) return os_snprintf(buf, buflen, "%u\n", fsts_id); return os_snprintf(buf, buflen, "FAIL\n"); } else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES, os_strlen(FST_CTR_GET_LOCAL_MBIES))) { return fst_test_req_get_local_mbies( p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen); } else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED, os_strlen(FST_CTR_IS_SUPPORTED))) { ret = 0; } 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"); } #endif /* CONFIG_FST_TEST */ /* 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; const struct wpabuf *mbies; 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) break; } if (!iface) goto problem; mbies = fst_iface_get_peer_mb_ie(iface, peer_addr); if (!mbies) goto problem; return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies), wpabuf_len(mbies)); 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; } 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) { const 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 += wpa_snprintf_hex(buf + ret, buflen - ret, wpabuf_head(wpabuf), wpabuf_len(wpabuf)); 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, bool 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(i, 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(i, 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, true)) { 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}, #ifdef CONFIG_FST_TEST { FST_CMD_TEST_REQUEST, 1, test_request }, #endif /* CONFIG_FST_TEST */ { NULL, 0, NULL } }; const struct fst_command *c; const char *p; const char *temp; bool 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, bool *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; *endp = (char *) params; while (isspace(**endp)) (*endp)++; if (!**endp || buflen <= 1) 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; bool 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; return fst_read_next_text_param(cmd, ifname, ifname_size, &endp); } int fst_iface_detach(const char *ifname) { struct fst_group *g; foreach_fst_group(g) { struct fst_iface *f; f = fst_group_get_iface_by_name(g, ifname); if (f) { fst_detach(f); return 0; } } return -EINVAL; }