1289284Srpaulo/* 2289284Srpaulo * FST module - Control Interface implementation 3289284Srpaulo * Copyright (c) 2014, Qualcomm Atheros, Inc. 4289284Srpaulo * 5289284Srpaulo * This software may be distributed under the terms of the BSD license. 6289284Srpaulo * See README for more details. 7289284Srpaulo */ 8289284Srpaulo 9289284Srpaulo#include "utils/includes.h" 10289284Srpaulo#include "utils/common.h" 11289284Srpaulo#include "common/defs.h" 12289284Srpaulo#include "list.h" 13289284Srpaulo#include "fst/fst.h" 14289284Srpaulo#include "fst/fst_internal.h" 15289284Srpaulo#include "fst_ctrl_defs.h" 16289284Srpaulo#include "fst_ctrl_iface.h" 17289284Srpaulo 18289284Srpaulo 19289284Srpaulostatic struct fst_group * get_fst_group_by_id(const char *id) 20289284Srpaulo{ 21289284Srpaulo struct fst_group *g; 22289284Srpaulo 23289284Srpaulo foreach_fst_group(g) { 24289284Srpaulo const char *group_id = fst_group_get_id(g); 25289284Srpaulo 26289284Srpaulo if (os_strncmp(group_id, id, os_strlen(group_id)) == 0) 27289284Srpaulo return g; 28289284Srpaulo } 29289284Srpaulo 30289284Srpaulo return NULL; 31289284Srpaulo} 32289284Srpaulo 33289284Srpaulo 34289284Srpaulo/* notifications */ 35289284Srpaulostatic Boolean format_session_state_extra(const union fst_event_extra *extra, 36289284Srpaulo char *buffer, size_t size) 37289284Srpaulo{ 38289284Srpaulo int len; 39289284Srpaulo char reject_str[32] = FST_CTRL_PVAL_NONE; 40289284Srpaulo const char *initiator = FST_CTRL_PVAL_NONE; 41289284Srpaulo const struct fst_event_extra_session_state *ss; 42289284Srpaulo 43289284Srpaulo ss = &extra->session_state; 44289284Srpaulo if (ss->new_state != FST_SESSION_STATE_INITIAL) 45289284Srpaulo return TRUE; 46289284Srpaulo 47289284Srpaulo switch (ss->extra.to_initial.reason) { 48289284Srpaulo case REASON_REJECT: 49289284Srpaulo if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS) 50289284Srpaulo os_snprintf(reject_str, sizeof(reject_str), "%u", 51289284Srpaulo ss->extra.to_initial.reject_code); 52346981Scy /* fall through */ 53289284Srpaulo case REASON_TEARDOWN: 54289284Srpaulo case REASON_SWITCH: 55289284Srpaulo switch (ss->extra.to_initial.initiator) { 56289284Srpaulo case FST_INITIATOR_LOCAL: 57289284Srpaulo initiator = FST_CS_PVAL_INITIATOR_LOCAL; 58289284Srpaulo break; 59289284Srpaulo case FST_INITIATOR_REMOTE: 60289284Srpaulo initiator = FST_CS_PVAL_INITIATOR_REMOTE; 61289284Srpaulo break; 62289284Srpaulo default: 63289284Srpaulo break; 64289284Srpaulo } 65289284Srpaulo break; 66289284Srpaulo default: 67289284Srpaulo break; 68289284Srpaulo } 69289284Srpaulo 70289284Srpaulo len = os_snprintf(buffer, size, 71289284Srpaulo FST_CES_PNAME_REASON "=%s " 72289284Srpaulo FST_CES_PNAME_REJECT_CODE "=%s " 73289284Srpaulo FST_CES_PNAME_INITIATOR "=%s", 74289284Srpaulo fst_reason_name(ss->extra.to_initial.reason), 75289284Srpaulo reject_str, initiator); 76289284Srpaulo 77289284Srpaulo return !os_snprintf_error(size, len); 78289284Srpaulo} 79289284Srpaulo 80289284Srpaulo 81289284Srpaulostatic void fst_ctrl_iface_notify(struct fst_iface *f, u32 session_id, 82289284Srpaulo enum fst_event_type event_type, 83289284Srpaulo const union fst_event_extra *extra) 84289284Srpaulo{ 85289284Srpaulo struct fst_group *g; 86289284Srpaulo char extra_str[128] = ""; 87289284Srpaulo const struct fst_event_extra_session_state *ss; 88289284Srpaulo const struct fst_event_extra_iface_state *is; 89289284Srpaulo const struct fst_event_extra_peer_state *ps; 90289284Srpaulo 91289284Srpaulo /* 92289284Srpaulo * FST can use any of interface objects as it only sends messages 93289284Srpaulo * on global Control Interface, so we just pick the 1st one. 94289284Srpaulo */ 95289284Srpaulo 96289284Srpaulo if (!f) { 97289284Srpaulo foreach_fst_group(g) { 98289284Srpaulo f = fst_group_first_iface(g); 99289284Srpaulo if (f) 100289284Srpaulo break; 101289284Srpaulo } 102289284Srpaulo if (!f) 103289284Srpaulo return; 104289284Srpaulo } 105289284Srpaulo 106289284Srpaulo WPA_ASSERT(f->iface_obj.ctx); 107289284Srpaulo 108289284Srpaulo switch (event_type) { 109289284Srpaulo case EVENT_FST_IFACE_STATE_CHANGED: 110289284Srpaulo if (!extra) 111289284Srpaulo return; 112289284Srpaulo is = &extra->iface_state; 113289284Srpaulo wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO, 114289284Srpaulo FST_CTRL_EVENT_IFACE " %s " 115289284Srpaulo FST_CEI_PNAME_IFNAME "=%s " 116289284Srpaulo FST_CEI_PNAME_GROUP "=%s", 117289284Srpaulo is->attached ? FST_CEI_PNAME_ATTACHED : 118289284Srpaulo FST_CEI_PNAME_DETACHED, 119289284Srpaulo is->ifname, is->group_id); 120289284Srpaulo break; 121289284Srpaulo case EVENT_PEER_STATE_CHANGED: 122289284Srpaulo if (!extra) 123289284Srpaulo return; 124289284Srpaulo ps = &extra->peer_state; 125289284Srpaulo wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, 126289284Srpaulo FST_CTRL_EVENT_PEER " %s " 127289284Srpaulo FST_CEP_PNAME_IFNAME "=%s " 128289284Srpaulo FST_CEP_PNAME_ADDR "=" MACSTR, 129289284Srpaulo ps->connected ? FST_CEP_PNAME_CONNECTED : 130289284Srpaulo FST_CEP_PNAME_DISCONNECTED, 131289284Srpaulo ps->ifname, MAC2STR(ps->addr)); 132289284Srpaulo break; 133289284Srpaulo case EVENT_FST_SESSION_STATE_CHANGED: 134289284Srpaulo if (!extra) 135289284Srpaulo return; 136289284Srpaulo if (!format_session_state_extra(extra, extra_str, 137289284Srpaulo sizeof(extra_str))) { 138289284Srpaulo fst_printf(MSG_ERROR, 139289284Srpaulo "CTRL: Cannot format STATE_CHANGE extra"); 140289284Srpaulo extra_str[0] = 0; 141289284Srpaulo } 142289284Srpaulo ss = &extra->session_state; 143289284Srpaulo wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, 144289284Srpaulo FST_CTRL_EVENT_SESSION " " 145289284Srpaulo FST_CES_PNAME_SESSION_ID "=%u " 146289284Srpaulo FST_CES_PNAME_EVT_TYPE "=%s " 147289284Srpaulo FST_CES_PNAME_OLD_STATE "=%s " 148289284Srpaulo FST_CES_PNAME_NEW_STATE "=%s %s", 149289284Srpaulo session_id, 150289284Srpaulo fst_session_event_type_name(event_type), 151289284Srpaulo fst_session_state_name(ss->old_state), 152289284Srpaulo fst_session_state_name(ss->new_state), 153289284Srpaulo extra_str); 154289284Srpaulo break; 155289284Srpaulo case EVENT_FST_ESTABLISHED: 156289284Srpaulo case EVENT_FST_SETUP: 157289284Srpaulo wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO, 158289284Srpaulo FST_CTRL_EVENT_SESSION " " 159289284Srpaulo FST_CES_PNAME_SESSION_ID "=%u " 160289284Srpaulo FST_CES_PNAME_EVT_TYPE "=%s", 161289284Srpaulo session_id, 162289284Srpaulo fst_session_event_type_name(event_type)); 163289284Srpaulo break; 164289284Srpaulo } 165289284Srpaulo} 166289284Srpaulo 167289284Srpaulo 168289284Srpaulo/* command processors */ 169289284Srpaulo 170289284Srpaulo/* fst session_get */ 171289284Srpaulostatic int session_get(const char *session_id, char *buf, size_t buflen) 172289284Srpaulo{ 173289284Srpaulo struct fst_session *s; 174289284Srpaulo struct fst_iface *new_iface, *old_iface; 175289284Srpaulo const u8 *old_peer_addr, *new_peer_addr; 176289284Srpaulo u32 id; 177289284Srpaulo 178289284Srpaulo id = strtoul(session_id, NULL, 0); 179289284Srpaulo 180289284Srpaulo s = fst_session_get_by_id(id); 181289284Srpaulo if (!s) { 182289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 183289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 184289284Srpaulo } 185289284Srpaulo 186289284Srpaulo old_peer_addr = fst_session_get_peer_addr(s, TRUE); 187289284Srpaulo new_peer_addr = fst_session_get_peer_addr(s, FALSE); 188289284Srpaulo new_iface = fst_session_get_iface(s, FALSE); 189289284Srpaulo old_iface = fst_session_get_iface(s, TRUE); 190289284Srpaulo 191289284Srpaulo return os_snprintf(buf, buflen, 192289284Srpaulo FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n" 193289284Srpaulo FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n" 194289284Srpaulo FST_CSG_PNAME_NEW_IFNAME "=%s\n" 195289284Srpaulo FST_CSG_PNAME_OLD_IFNAME "=%s\n" 196289284Srpaulo FST_CSG_PNAME_LLT "=%u\n" 197289284Srpaulo FST_CSG_PNAME_STATE "=%s\n", 198289284Srpaulo MAC2STR(old_peer_addr), 199289284Srpaulo MAC2STR(new_peer_addr), 200289284Srpaulo new_iface ? fst_iface_get_name(new_iface) : 201289284Srpaulo FST_CTRL_PVAL_NONE, 202289284Srpaulo old_iface ? fst_iface_get_name(old_iface) : 203289284Srpaulo FST_CTRL_PVAL_NONE, 204289284Srpaulo fst_session_get_llt(s), 205289284Srpaulo fst_session_state_name(fst_session_get_state(s))); 206289284Srpaulo} 207289284Srpaulo 208289284Srpaulo 209289284Srpaulo/* fst session_set */ 210289284Srpaulostatic int session_set(const char *session_id, char *buf, size_t buflen) 211289284Srpaulo{ 212289284Srpaulo struct fst_session *s; 213289284Srpaulo char *p, *q; 214289284Srpaulo u32 id; 215289284Srpaulo int ret; 216289284Srpaulo 217289284Srpaulo id = strtoul(session_id, &p, 0); 218289284Srpaulo 219289284Srpaulo s = fst_session_get_by_id(id); 220289284Srpaulo if (!s) { 221289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 222289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 223289284Srpaulo } 224289284Srpaulo 225289284Srpaulo if (*p != ' ' || !(q = os_strchr(p + 1, '='))) 226289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 227289284Srpaulo p++; 228289284Srpaulo 229289284Srpaulo if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) { 230289284Srpaulo ret = fst_session_set_str_ifname(s, q + 1, TRUE); 231289284Srpaulo } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) { 232289284Srpaulo ret = fst_session_set_str_ifname(s, q + 1, FALSE); 233289284Srpaulo } else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) { 234289284Srpaulo ret = fst_session_set_str_peer_addr(s, q + 1, TRUE); 235289284Srpaulo } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) { 236289284Srpaulo ret = fst_session_set_str_peer_addr(s, q + 1, FALSE); 237289284Srpaulo } else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) { 238289284Srpaulo ret = fst_session_set_str_llt(s, q + 1); 239289284Srpaulo } else { 240289284Srpaulo fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p); 241289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 242289284Srpaulo } 243289284Srpaulo 244289284Srpaulo return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK"); 245289284Srpaulo} 246289284Srpaulo 247289284Srpaulo 248289284Srpaulo/* fst session_add/remove */ 249289284Srpaulostatic int session_add(const char *group_id, char *buf, size_t buflen) 250289284Srpaulo{ 251289284Srpaulo struct fst_group *g; 252289284Srpaulo struct fst_session *s; 253289284Srpaulo 254289284Srpaulo g = get_fst_group_by_id(group_id); 255289284Srpaulo if (!g) { 256289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", 257289284Srpaulo group_id); 258289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 259289284Srpaulo } 260289284Srpaulo 261289284Srpaulo s = fst_session_create(g); 262289284Srpaulo if (!s) { 263289284Srpaulo fst_printf(MSG_ERROR, 264289284Srpaulo "CTRL: Cannot create session for group '%s'", 265289284Srpaulo group_id); 266289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 267289284Srpaulo } 268289284Srpaulo 269289284Srpaulo return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s)); 270289284Srpaulo} 271289284Srpaulo 272289284Srpaulo 273289284Srpaulostatic int session_remove(const char *session_id, char *buf, size_t buflen) 274289284Srpaulo{ 275289284Srpaulo struct fst_session *s; 276289284Srpaulo struct fst_group *g; 277289284Srpaulo u32 id; 278289284Srpaulo 279289284Srpaulo id = strtoul(session_id, NULL, 0); 280289284Srpaulo 281289284Srpaulo s = fst_session_get_by_id(id); 282289284Srpaulo if (!s) { 283289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 284289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 285289284Srpaulo } 286289284Srpaulo 287289284Srpaulo g = fst_session_get_group(s); 288289284Srpaulo fst_session_reset(s); 289289284Srpaulo fst_session_delete(s); 290289284Srpaulo fst_group_delete_if_empty(g); 291289284Srpaulo 292289284Srpaulo return os_snprintf(buf, buflen, "OK\n"); 293289284Srpaulo} 294289284Srpaulo 295289284Srpaulo 296289284Srpaulo/* fst session_initiate */ 297289284Srpaulostatic int session_initiate(const char *session_id, char *buf, size_t buflen) 298289284Srpaulo{ 299289284Srpaulo struct fst_session *s; 300289284Srpaulo u32 id; 301289284Srpaulo 302289284Srpaulo id = strtoul(session_id, NULL, 0); 303289284Srpaulo 304289284Srpaulo s = fst_session_get_by_id(id); 305289284Srpaulo if (!s) { 306289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 307289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 308289284Srpaulo } 309289284Srpaulo 310289284Srpaulo if (fst_session_initiate_setup(s)) { 311289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id); 312289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 313289284Srpaulo } 314289284Srpaulo 315289284Srpaulo return os_snprintf(buf, buflen, "OK\n"); 316289284Srpaulo} 317289284Srpaulo 318289284Srpaulo 319289284Srpaulo/* fst session_respond */ 320289284Srpaulostatic int session_respond(const char *session_id, char *buf, size_t buflen) 321289284Srpaulo{ 322289284Srpaulo struct fst_session *s; 323289284Srpaulo char *p; 324289284Srpaulo u32 id; 325289284Srpaulo u8 status_code; 326289284Srpaulo 327289284Srpaulo id = strtoul(session_id, &p, 0); 328289284Srpaulo 329289284Srpaulo s = fst_session_get_by_id(id); 330289284Srpaulo if (!s) { 331289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 332289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 333289284Srpaulo } 334289284Srpaulo 335289284Srpaulo if (*p != ' ') 336289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 337289284Srpaulo p++; 338289284Srpaulo 339289284Srpaulo if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) { 340289284Srpaulo status_code = WLAN_STATUS_SUCCESS; 341289284Srpaulo } else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) { 342289284Srpaulo status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION; 343289284Srpaulo } else { 344289284Srpaulo fst_printf(MSG_WARNING, 345289284Srpaulo "CTRL: session %u: unknown response status: %s", 346289284Srpaulo id, p); 347289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 348289284Srpaulo } 349289284Srpaulo 350289284Srpaulo if (fst_session_respond(s, status_code)) { 351289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u", 352289284Srpaulo id); 353289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 354289284Srpaulo } 355289284Srpaulo 356289284Srpaulo fst_printf(MSG_INFO, "CTRL: session %u responded", id); 357289284Srpaulo 358289284Srpaulo return os_snprintf(buf, buflen, "OK\n"); 359289284Srpaulo} 360289284Srpaulo 361289284Srpaulo 362289284Srpaulo/* fst session_transfer */ 363289284Srpaulostatic int session_transfer(const char *session_id, char *buf, size_t buflen) 364289284Srpaulo{ 365289284Srpaulo struct fst_session *s; 366289284Srpaulo u32 id; 367289284Srpaulo 368289284Srpaulo id = strtoul(session_id, NULL, 0); 369289284Srpaulo 370289284Srpaulo s = fst_session_get_by_id(id); 371289284Srpaulo if (!s) { 372289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 373289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 374289284Srpaulo } 375289284Srpaulo 376289284Srpaulo if (fst_session_initiate_switch(s)) { 377289284Srpaulo fst_printf(MSG_WARNING, 378289284Srpaulo "CTRL: Cannot initiate ST for session %u", id); 379289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 380289284Srpaulo } 381289284Srpaulo 382289284Srpaulo return os_snprintf(buf, buflen, "OK\n"); 383289284Srpaulo} 384289284Srpaulo 385289284Srpaulo 386289284Srpaulo/* fst session_teardown */ 387289284Srpaulostatic int session_teardown(const char *session_id, char *buf, size_t buflen) 388289284Srpaulo{ 389289284Srpaulo struct fst_session *s; 390289284Srpaulo u32 id; 391289284Srpaulo 392289284Srpaulo id = strtoul(session_id, NULL, 0); 393289284Srpaulo 394289284Srpaulo s = fst_session_get_by_id(id); 395289284Srpaulo if (!s) { 396289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id); 397289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 398289284Srpaulo } 399289284Srpaulo 400289284Srpaulo if (fst_session_tear_down_setup(s)) { 401289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u", 402289284Srpaulo id); 403289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 404289284Srpaulo } 405289284Srpaulo 406289284Srpaulo return os_snprintf(buf, buflen, "OK\n"); 407289284Srpaulo} 408289284Srpaulo 409289284Srpaulo 410289284Srpaulo#ifdef CONFIG_FST_TEST 411289284Srpaulo/* fst test_request */ 412289284Srpaulostatic int test_request(const char *request, char *buf, size_t buflen) 413289284Srpaulo{ 414289284Srpaulo const char *p = request; 415289284Srpaulo int ret; 416289284Srpaulo 417289284Srpaulo if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST, 418289284Srpaulo os_strlen(FST_CTR_SEND_SETUP_REQUEST))) { 419289284Srpaulo ret = fst_test_req_send_fst_request( 420289284Srpaulo p + os_strlen(FST_CTR_SEND_SETUP_REQUEST)); 421289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE, 422289284Srpaulo os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) { 423289284Srpaulo ret = fst_test_req_send_fst_response( 424289284Srpaulo p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE)); 425289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST, 426289284Srpaulo os_strlen(FST_CTR_SEND_ACK_REQUEST))) { 427289284Srpaulo ret = fst_test_req_send_ack_request( 428289284Srpaulo p + os_strlen(FST_CTR_SEND_ACK_REQUEST)); 429289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE, 430289284Srpaulo os_strlen(FST_CTR_SEND_ACK_RESPONSE))) { 431289284Srpaulo ret = fst_test_req_send_ack_response( 432289284Srpaulo p + os_strlen(FST_CTR_SEND_ACK_RESPONSE)); 433289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN, 434289284Srpaulo os_strlen(FST_CTR_SEND_TEAR_DOWN))) { 435289284Srpaulo ret = fst_test_req_send_tear_down( 436289284Srpaulo p + os_strlen(FST_CTR_SEND_TEAR_DOWN)); 437289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID, 438289284Srpaulo os_strlen(FST_CTR_GET_FSTS_ID))) { 439289284Srpaulo u32 fsts_id = fst_test_req_get_fsts_id( 440289284Srpaulo p + os_strlen(FST_CTR_GET_FSTS_ID)); 441289284Srpaulo if (fsts_id != FST_FSTS_ID_NOT_FOUND) 442289284Srpaulo return os_snprintf(buf, buflen, "%u\n", fsts_id); 443289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 444289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES, 445289284Srpaulo os_strlen(FST_CTR_GET_LOCAL_MBIES))) { 446289284Srpaulo return fst_test_req_get_local_mbies( 447289284Srpaulo p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen); 448289284Srpaulo } else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED, 449289284Srpaulo os_strlen(FST_CTR_IS_SUPPORTED))) { 450289284Srpaulo ret = 0; 451289284Srpaulo } else { 452289284Srpaulo fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p); 453289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 454289284Srpaulo } 455289284Srpaulo 456289284Srpaulo return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK"); 457289284Srpaulo} 458289284Srpaulo#endif /* CONFIG_FST_TEST */ 459289284Srpaulo 460289284Srpaulo 461289284Srpaulo/* fst list_sessions */ 462289284Srpaulostruct list_sessions_cb_ctx { 463289284Srpaulo char *buf; 464289284Srpaulo size_t buflen; 465289284Srpaulo size_t reply_len; 466289284Srpaulo}; 467289284Srpaulo 468289284Srpaulo 469289284Srpaulostatic void list_session_enum_cb(struct fst_group *g, struct fst_session *s, 470289284Srpaulo void *ctx) 471289284Srpaulo{ 472289284Srpaulo struct list_sessions_cb_ctx *c = ctx; 473289284Srpaulo int ret; 474289284Srpaulo 475289284Srpaulo ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s)); 476289284Srpaulo 477289284Srpaulo c->buf += ret; 478289284Srpaulo c->buflen -= ret; 479289284Srpaulo c->reply_len += ret; 480289284Srpaulo} 481289284Srpaulo 482289284Srpaulo 483289284Srpaulostatic int list_sessions(const char *group_id, char *buf, size_t buflen) 484289284Srpaulo{ 485289284Srpaulo struct list_sessions_cb_ctx ctx; 486289284Srpaulo struct fst_group *g; 487289284Srpaulo 488289284Srpaulo g = get_fst_group_by_id(group_id); 489289284Srpaulo if (!g) { 490289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", 491289284Srpaulo group_id); 492289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 493289284Srpaulo } 494289284Srpaulo 495289284Srpaulo ctx.buf = buf; 496289284Srpaulo ctx.buflen = buflen; 497289284Srpaulo ctx.reply_len = 0; 498289284Srpaulo 499289284Srpaulo fst_session_enum(g, list_session_enum_cb, &ctx); 500289284Srpaulo 501289284Srpaulo ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n"); 502289284Srpaulo 503289284Srpaulo return ctx.reply_len; 504289284Srpaulo} 505289284Srpaulo 506289284Srpaulo 507289284Srpaulo/* fst iface_peers */ 508289284Srpaulostatic int iface_peers(const char *group_id, char *buf, size_t buflen) 509289284Srpaulo{ 510289284Srpaulo const char *ifname; 511289284Srpaulo struct fst_group *g; 512289284Srpaulo struct fst_iface *f; 513289284Srpaulo struct fst_get_peer_ctx *ctx; 514289284Srpaulo const u8 *addr; 515289284Srpaulo unsigned found = 0; 516289284Srpaulo int ret = 0; 517289284Srpaulo 518289284Srpaulo g = get_fst_group_by_id(group_id); 519289284Srpaulo if (!g) { 520289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", 521289284Srpaulo group_id); 522289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 523289284Srpaulo } 524289284Srpaulo 525289284Srpaulo ifname = os_strchr(group_id, ' '); 526289284Srpaulo if (!ifname) 527289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 528289284Srpaulo ifname++; 529289284Srpaulo 530289284Srpaulo foreach_fst_group_iface(g, f) { 531289284Srpaulo const char *in = fst_iface_get_name(f); 532289284Srpaulo 533289284Srpaulo if (os_strncmp(ifname, in, os_strlen(in)) == 0) { 534289284Srpaulo found = 1; 535289284Srpaulo break; 536289284Srpaulo } 537289284Srpaulo } 538289284Srpaulo 539289284Srpaulo if (!found) 540289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 541289284Srpaulo 542289284Srpaulo addr = fst_iface_get_peer_first(f, &ctx, FALSE); 543289284Srpaulo for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, FALSE)) { 544289284Srpaulo int res; 545289284Srpaulo 546289284Srpaulo res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n", 547289284Srpaulo MAC2STR(addr)); 548289284Srpaulo if (os_snprintf_error(buflen - ret, res)) 549289284Srpaulo break; 550289284Srpaulo ret += res; 551289284Srpaulo } 552289284Srpaulo 553289284Srpaulo return ret; 554289284Srpaulo} 555289284Srpaulo 556289284Srpaulo 557289284Srpaulostatic int get_peer_mbies(const char *params, char *buf, size_t buflen) 558289284Srpaulo{ 559289284Srpaulo char *endp; 560289284Srpaulo char ifname[FST_MAX_INTERFACE_SIZE]; 561289284Srpaulo u8 peer_addr[ETH_ALEN]; 562289284Srpaulo struct fst_group *g; 563289284Srpaulo struct fst_iface *iface = NULL; 564289284Srpaulo const struct wpabuf *mbies; 565289284Srpaulo 566289284Srpaulo if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) || 567289284Srpaulo !*ifname) 568289284Srpaulo goto problem; 569289284Srpaulo 570289284Srpaulo while (isspace(*endp)) 571289284Srpaulo endp++; 572289284Srpaulo if (fst_read_peer_addr(endp, peer_addr)) 573289284Srpaulo goto problem; 574289284Srpaulo 575289284Srpaulo foreach_fst_group(g) { 576289284Srpaulo iface = fst_group_get_iface_by_name(g, ifname); 577289284Srpaulo if (iface) 578289284Srpaulo break; 579289284Srpaulo } 580289284Srpaulo if (!iface) 581289284Srpaulo goto problem; 582289284Srpaulo 583289284Srpaulo mbies = fst_iface_get_peer_mb_ie(iface, peer_addr); 584289284Srpaulo if (!mbies) 585289284Srpaulo goto problem; 586289284Srpaulo 587289284Srpaulo return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies), 588289284Srpaulo wpabuf_len(mbies)); 589289284Srpaulo 590289284Srpauloproblem: 591289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 592289284Srpaulo} 593289284Srpaulo 594289284Srpaulo 595289284Srpaulo/* fst list_ifaces */ 596289284Srpaulostatic int list_ifaces(const char *group_id, char *buf, size_t buflen) 597289284Srpaulo{ 598289284Srpaulo struct fst_group *g; 599289284Srpaulo struct fst_iface *f; 600289284Srpaulo int ret = 0; 601289284Srpaulo 602289284Srpaulo g = get_fst_group_by_id(group_id); 603289284Srpaulo if (!g) { 604289284Srpaulo fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'", 605289284Srpaulo group_id); 606289284Srpaulo return os_snprintf(buf, buflen, "FAIL\n"); 607289284Srpaulo } 608289284Srpaulo 609289284Srpaulo foreach_fst_group_iface(g, f) { 610289284Srpaulo int res; 611289284Srpaulo const u8 *iface_addr = fst_iface_get_addr(f); 612289284Srpaulo 613289284Srpaulo res = os_snprintf(buf + ret, buflen - ret, 614289284Srpaulo "%s|" MACSTR "|%u|%u\n", 615289284Srpaulo fst_iface_get_name(f), 616289284Srpaulo MAC2STR(iface_addr), 617289284Srpaulo fst_iface_get_priority(f), 618289284Srpaulo fst_iface_get_llt(f)); 619289284Srpaulo if (os_snprintf_error(buflen - ret, res)) 620289284Srpaulo break; 621289284Srpaulo ret += res; 622289284Srpaulo } 623289284Srpaulo 624289284Srpaulo return ret; 625289284Srpaulo} 626289284Srpaulo 627289284Srpaulo 628289284Srpaulo/* fst list_groups */ 629289284Srpaulostatic int list_groups(const char *cmd, char *buf, size_t buflen) 630289284Srpaulo{ 631289284Srpaulo struct fst_group *g; 632289284Srpaulo int ret = 0; 633289284Srpaulo 634289284Srpaulo foreach_fst_group(g) { 635289284Srpaulo int res; 636289284Srpaulo 637289284Srpaulo res = os_snprintf(buf + ret, buflen - ret, "%s\n", 638289284Srpaulo fst_group_get_id(g)); 639289284Srpaulo if (os_snprintf_error(buflen - ret, res)) 640289284Srpaulo break; 641289284Srpaulo ret += res; 642289284Srpaulo } 643289284Srpaulo 644289284Srpaulo return ret; 645289284Srpaulo} 646289284Srpaulo 647289284Srpaulo 648289284Srpaulostatic const char * band_freq(enum mb_band_id band) 649289284Srpaulo{ 650289284Srpaulo static const char *band_names[] = { 651337817Scy [MB_BAND_ID_WIFI_2_4GHZ] = "2.4GHZ", 652337817Scy [MB_BAND_ID_WIFI_5GHZ] = "5GHZ", 653337817Scy [MB_BAND_ID_WIFI_60GHZ] = "60GHZ", 654289284Srpaulo }; 655289284Srpaulo 656289284Srpaulo return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names)); 657289284Srpaulo} 658289284Srpaulo 659289284Srpaulo 660289284Srpaulostatic int print_band(unsigned num, struct fst_iface *iface, const u8 *addr, 661289284Srpaulo char *buf, size_t buflen) 662289284Srpaulo{ 663289284Srpaulo const struct wpabuf *wpabuf; 664289284Srpaulo enum hostapd_hw_mode hw_mode; 665289284Srpaulo u8 channel; 666289284Srpaulo int ret = 0; 667289284Srpaulo 668289284Srpaulo fst_iface_get_channel_info(iface, &hw_mode, &channel); 669289284Srpaulo 670289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n", 671289284Srpaulo num, band_freq(fst_hw_mode_to_band(hw_mode))); 672289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n", 673289284Srpaulo num, fst_iface_get_name(iface)); 674289284Srpaulo wpabuf = fst_iface_get_peer_mb_ie(iface, addr); 675289284Srpaulo if (wpabuf) { 676289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=", 677289284Srpaulo num); 678289284Srpaulo ret += wpa_snprintf_hex(buf + ret, buflen - ret, 679289284Srpaulo wpabuf_head(wpabuf), 680289284Srpaulo wpabuf_len(wpabuf)); 681289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "\n"); 682289284Srpaulo } 683289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n", 684289284Srpaulo num, fst_iface_get_group_id(iface)); 685289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n", 686289284Srpaulo num, fst_iface_get_priority(iface)); 687289284Srpaulo ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n", 688289284Srpaulo num, fst_iface_get_llt(iface)); 689289284Srpaulo 690289284Srpaulo return ret; 691289284Srpaulo} 692289284Srpaulo 693289284Srpaulo 694289284Srpaulostatic void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i, 695289284Srpaulo Boolean attached) 696289284Srpaulo{ 697289284Srpaulo union fst_event_extra extra; 698289284Srpaulo 699289284Srpaulo os_memset(&extra, 0, sizeof(extra)); 700289284Srpaulo extra.iface_state.attached = attached; 701289284Srpaulo os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i), 702289284Srpaulo sizeof(extra.iface_state.ifname)); 703289284Srpaulo os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i), 704289284Srpaulo sizeof(extra.iface_state.group_id)); 705289284Srpaulo 706289284Srpaulo fst_ctrl_iface_notify(i, FST_INVALID_SESSION_ID, 707289284Srpaulo EVENT_FST_IFACE_STATE_CHANGED, &extra); 708289284Srpaulo} 709289284Srpaulo 710289284Srpaulo 711289284Srpaulostatic int fst_ctrl_iface_on_iface_added(struct fst_iface *i) 712289284Srpaulo{ 713289284Srpaulo fst_ctrl_iface_on_iface_state_changed(i, TRUE); 714289284Srpaulo return 0; 715289284Srpaulo} 716289284Srpaulo 717289284Srpaulo 718289284Srpaulostatic void fst_ctrl_iface_on_iface_removed(struct fst_iface *i) 719289284Srpaulo{ 720289284Srpaulo fst_ctrl_iface_on_iface_state_changed(i, FALSE); 721289284Srpaulo} 722289284Srpaulo 723289284Srpaulo 724289284Srpaulostatic void fst_ctrl_iface_on_event(enum fst_event_type event_type, 725289284Srpaulo struct fst_iface *i, struct fst_session *s, 726289284Srpaulo const union fst_event_extra *extra) 727289284Srpaulo{ 728289284Srpaulo u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID; 729289284Srpaulo 730289284Srpaulo fst_ctrl_iface_notify(i, session_id, event_type, extra); 731289284Srpaulo} 732289284Srpaulo 733289284Srpaulo 734289284Srpaulostatic const struct fst_ctrl ctrl_cli = { 735289284Srpaulo .on_iface_added = fst_ctrl_iface_on_iface_added, 736289284Srpaulo .on_iface_removed = fst_ctrl_iface_on_iface_removed, 737289284Srpaulo .on_event = fst_ctrl_iface_on_event, 738289284Srpaulo}; 739289284Srpaulo 740289284Srpauloconst struct fst_ctrl *fst_ctrl_cli = &ctrl_cli; 741289284Srpaulo 742289284Srpaulo 743289284Srpauloint fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen) 744289284Srpaulo{ 745289284Srpaulo struct fst_group *g; 746289284Srpaulo struct fst_iface *f; 747289284Srpaulo unsigned num = 0; 748289284Srpaulo int ret = 0; 749289284Srpaulo 750289284Srpaulo foreach_fst_group(g) { 751289284Srpaulo foreach_fst_group_iface(g, f) { 752337817Scy if (fst_iface_is_connected(f, addr, TRUE)) { 753289284Srpaulo ret += print_band(num++, f, addr, 754289284Srpaulo buf + ret, buflen - ret); 755289284Srpaulo } 756289284Srpaulo } 757289284Srpaulo } 758289284Srpaulo 759289284Srpaulo return ret; 760289284Srpaulo} 761289284Srpaulo 762289284Srpaulo 763289284Srpaulo/* fst ctrl processor */ 764289284Srpauloint fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size) 765289284Srpaulo{ 766289284Srpaulo static const struct fst_command { 767289284Srpaulo const char *name; 768289284Srpaulo unsigned has_param; 769289284Srpaulo int (*process)(const char *group_id, char *buf, size_t buflen); 770289284Srpaulo } commands[] = { 771289284Srpaulo { FST_CMD_LIST_GROUPS, 0, list_groups}, 772289284Srpaulo { FST_CMD_LIST_IFACES, 1, list_ifaces}, 773289284Srpaulo { FST_CMD_IFACE_PEERS, 1, iface_peers}, 774289284Srpaulo { FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies}, 775289284Srpaulo { FST_CMD_LIST_SESSIONS, 1, list_sessions}, 776289284Srpaulo { FST_CMD_SESSION_ADD, 1, session_add}, 777289284Srpaulo { FST_CMD_SESSION_REMOVE, 1, session_remove}, 778289284Srpaulo { FST_CMD_SESSION_GET, 1, session_get}, 779289284Srpaulo { FST_CMD_SESSION_SET, 1, session_set}, 780289284Srpaulo { FST_CMD_SESSION_INITIATE, 1, session_initiate}, 781289284Srpaulo { FST_CMD_SESSION_RESPOND, 1, session_respond}, 782289284Srpaulo { FST_CMD_SESSION_TRANSFER, 1, session_transfer}, 783289284Srpaulo { FST_CMD_SESSION_TEARDOWN, 1, session_teardown}, 784289284Srpaulo#ifdef CONFIG_FST_TEST 785289284Srpaulo { FST_CMD_TEST_REQUEST, 1, test_request }, 786289284Srpaulo#endif /* CONFIG_FST_TEST */ 787289284Srpaulo { NULL, 0, NULL } 788289284Srpaulo }; 789289284Srpaulo const struct fst_command *c; 790289284Srpaulo const char *p; 791289284Srpaulo const char *temp; 792289284Srpaulo Boolean non_spaces_found; 793289284Srpaulo 794289284Srpaulo for (c = commands; c->name; c++) { 795289284Srpaulo if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0) 796289284Srpaulo continue; 797289284Srpaulo p = cmd + os_strlen(c->name); 798289284Srpaulo if (c->has_param) { 799289284Srpaulo if (!isspace(p[0])) 800289284Srpaulo return os_snprintf(reply, reply_size, "FAIL\n"); 801289284Srpaulo p++; 802289284Srpaulo temp = p; 803289284Srpaulo non_spaces_found = FALSE; 804289284Srpaulo while (*temp) { 805289284Srpaulo if (!isspace(*temp)) { 806289284Srpaulo non_spaces_found = TRUE; 807289284Srpaulo break; 808289284Srpaulo } 809289284Srpaulo temp++; 810289284Srpaulo } 811289284Srpaulo if (!non_spaces_found) 812289284Srpaulo return os_snprintf(reply, reply_size, "FAIL\n"); 813289284Srpaulo } 814289284Srpaulo return c->process(p, reply, reply_size); 815289284Srpaulo } 816289284Srpaulo 817289284Srpaulo return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n"); 818289284Srpaulo} 819289284Srpaulo 820289284Srpaulo 821289284Srpauloint fst_read_next_int_param(const char *params, Boolean *valid, char **endp) 822289284Srpaulo{ 823289284Srpaulo int ret = -1; 824289284Srpaulo const char *curp; 825289284Srpaulo 826289284Srpaulo *valid = FALSE; 827289284Srpaulo *endp = (char *) params; 828289284Srpaulo curp = params; 829289284Srpaulo if (*curp) { 830289284Srpaulo ret = (int) strtol(curp, endp, 0); 831289284Srpaulo if (!**endp || isspace(**endp)) 832289284Srpaulo *valid = TRUE; 833289284Srpaulo } 834289284Srpaulo 835289284Srpaulo return ret; 836289284Srpaulo} 837289284Srpaulo 838289284Srpaulo 839289284Srpauloint fst_read_next_text_param(const char *params, char *buf, size_t buflen, 840289284Srpaulo char **endp) 841289284Srpaulo{ 842289284Srpaulo size_t max_chars_to_copy; 843289284Srpaulo char *cur_dest; 844289284Srpaulo 845289284Srpaulo *endp = (char *) params; 846289284Srpaulo while (isspace(**endp)) 847289284Srpaulo (*endp)++; 848289284Srpaulo if (!**endp || buflen <= 1) 849289284Srpaulo return -EINVAL; 850289284Srpaulo 851289284Srpaulo max_chars_to_copy = buflen - 1; 852289284Srpaulo /* We need 1 byte for the terminating zero */ 853289284Srpaulo cur_dest = buf; 854289284Srpaulo while (**endp && !isspace(**endp) && max_chars_to_copy > 0) { 855289284Srpaulo *cur_dest = **endp; 856289284Srpaulo (*endp)++; 857289284Srpaulo cur_dest++; 858289284Srpaulo max_chars_to_copy--; 859289284Srpaulo } 860289284Srpaulo *cur_dest = 0; 861289284Srpaulo 862289284Srpaulo return 0; 863289284Srpaulo} 864289284Srpaulo 865289284Srpaulo 866289284Srpauloint fst_read_peer_addr(const char *mac, u8 *peer_addr) 867289284Srpaulo{ 868289284Srpaulo if (hwaddr_aton(mac, peer_addr)) { 869289284Srpaulo fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string", 870289284Srpaulo mac); 871289284Srpaulo return -1; 872289284Srpaulo } 873289284Srpaulo 874289284Srpaulo if (is_zero_ether_addr(peer_addr) || 875289284Srpaulo is_multicast_ether_addr(peer_addr)) { 876289284Srpaulo fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr", 877289284Srpaulo mac); 878289284Srpaulo return -1; 879289284Srpaulo } 880289284Srpaulo 881289284Srpaulo return 0; 882289284Srpaulo} 883289284Srpaulo 884289284Srpaulo 885289284Srpauloint fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size, 886289284Srpaulo struct fst_iface_cfg *cfg) 887289284Srpaulo{ 888289284Srpaulo char *pos; 889289284Srpaulo char *endp; 890289284Srpaulo Boolean is_valid; 891289284Srpaulo int val; 892289284Srpaulo 893289284Srpaulo if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) || 894289284Srpaulo fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id), 895289284Srpaulo &endp)) 896289284Srpaulo return -EINVAL; 897289284Srpaulo 898289284Srpaulo cfg->llt = FST_DEFAULT_LLT_CFG_VALUE; 899289284Srpaulo cfg->priority = 0; 900289284Srpaulo pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT); 901289284Srpaulo if (pos) { 902289284Srpaulo pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT); 903289284Srpaulo if (*pos == '=') { 904289284Srpaulo val = fst_read_next_int_param(pos + 1, &is_valid, 905289284Srpaulo &endp); 906289284Srpaulo if (is_valid) 907289284Srpaulo cfg->llt = val; 908289284Srpaulo } 909289284Srpaulo } 910289284Srpaulo pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY); 911289284Srpaulo if (pos) { 912289284Srpaulo pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY); 913289284Srpaulo if (*pos == '=') { 914289284Srpaulo val = fst_read_next_int_param(pos + 1, &is_valid, 915289284Srpaulo &endp); 916289284Srpaulo if (is_valid) 917289284Srpaulo cfg->priority = (u8) val; 918289284Srpaulo } 919289284Srpaulo } 920289284Srpaulo 921289284Srpaulo return 0; 922289284Srpaulo} 923289284Srpaulo 924289284Srpaulo 925289284Srpauloint fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size) 926289284Srpaulo{ 927289284Srpaulo char *endp; 928289284Srpaulo 929289284Srpaulo return fst_read_next_text_param(cmd, ifname, ifname_size, &endp); 930289284Srpaulo} 931289284Srpaulo 932289284Srpaulo 933289284Srpauloint fst_iface_detach(const char *ifname) 934289284Srpaulo{ 935289284Srpaulo struct fst_group *g; 936289284Srpaulo 937289284Srpaulo foreach_fst_group(g) { 938289284Srpaulo struct fst_iface *f; 939289284Srpaulo 940289284Srpaulo f = fst_group_get_iface_by_name(g, ifname); 941289284Srpaulo if (f) { 942289284Srpaulo fst_detach(f); 943289284Srpaulo return 0; 944289284Srpaulo } 945289284Srpaulo } 946289284Srpaulo 947289284Srpaulo return -EINVAL; 948289284Srpaulo} 949