blob: 01a4512b95a1e1319b5ee4609269a7c5afb68266 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "supp_events.h"
#include "includes.h"
#include "common.h"
#include "common/ieee802_11_defs.h"
#include "wpa_supplicant_i.h"
#ifdef CONFIG_AP
#include "ap/sta_info.h"
#include "ap/ieee802_11.h"
#include "ap/hostapd.h"
#endif /* CONFIG_AP */
#include <zephyr/net/wifi_mgmt.h>
/* Re-defines MAC2STR with address of the element */
#define MACADDR2STR(a) &(a)[0], &(a)[1], &(a)[2], &(a)[3], &(a)[4], &(a)[5]
static const struct wpa_supp_event_info {
const char *event_str;
enum supplicant_event_num event;
} wpa_supp_event_info[] = {
{ "CTRL-EVENT-CONNECTED", SUPPLICANT_EVENT_CONNECTED },
{ "CTRL-EVENT-DISCONNECTED", SUPPLICANT_EVENT_DISCONNECTED },
{ "CTRL-EVENT-ASSOC-REJECT", SUPPLICANT_EVENT_ASSOC_REJECT },
{ "CTRL-EVENT-AUTH-REJECT", SUPPLICANT_EVENT_AUTH_REJECT },
{ "CTRL-EVENT-SSID-TEMP-DISABLED", SUPPLICANT_EVENT_SSID_TEMP_DISABLED },
{ "CTRL-EVENT-SSID-REENABLED", SUPPLICANT_EVENT_SSID_REENABLED },
{ "CTRL-EVENT-BSS-ADDED", SUPPLICANT_EVENT_BSS_ADDED },
{ "CTRL-EVENT-BSS-REMOVED", SUPPLICANT_EVENT_BSS_REMOVED },
{ "CTRL-EVENT-TERMINATING", SUPPLICANT_EVENT_TERMINATING },
{ "CTRL-EVENT-SCAN-STARTED", SUPPLICANT_EVENT_SCAN_STARTED },
{ "CTRL-EVENT-SCAN-FAILED", SUPPLICANT_EVENT_SCAN_FAILED },
{ "CTRL-EVENT-NETWORK-NOT-FOUND", SUPPLICANT_EVENT_NETWORK_NOT_FOUND },
{ "CTRL-EVENT-NETWORK-ADDED", SUPPLICANT_EVENT_NETWORK_ADDED },
{ "CTRL-EVENT-NETWORK-REMOVED", SUPPLICANT_EVENT_NETWORK_REMOVED },
{ "CTRL-EVENT-DSCP-POLICY", SUPPLICANT_EVENT_DSCP_POLICY },
};
static void copy_mac_addr(const unsigned int *src, uint8_t *dst)
{
int i;
for (i = 0; i < ETH_ALEN; i++) {
dst[i] = src[i];
}
}
static enum wifi_conn_status wpas_to_wifi_mgmt_conn_status(int status)
{
switch (status) {
case WLAN_STATUS_SUCCESS:
return WIFI_STATUS_CONN_SUCCESS;
case WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT:
return WIFI_STATUS_CONN_WRONG_PASSWORD;
/* Handle non-supplicant errors */
case -ETIMEDOUT:
return WIFI_STATUS_CONN_TIMEOUT;
default:
return WIFI_STATUS_CONN_FAIL;
}
}
static enum wifi_disconn_reason wpas_to_wifi_mgmt_diconn_status(int status)
{
switch (status) {
case WLAN_REASON_DEAUTH_LEAVING:
return WIFI_REASON_DISCONN_AP_LEAVING;
case WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY:
return WIFI_REASON_DISCONN_INACTIVITY;
case WLAN_REASON_UNSPECIFIED:
/* fall through */
default:
return WIFI_REASON_DISCONN_UNSPECIFIED;
}
}
static int supplicant_process_status(struct supplicant_int_event_data *event_data,
char *supplicant_status)
{
int ret = 1; /* For cases where parsing is not being done*/
int i;
union supplicant_event_data *data;
unsigned int tmp_mac_addr[ETH_ALEN];
unsigned int event_prefix_len;
char *event_no_prefix;
struct wpa_supp_event_info event_info;
data = (union supplicant_event_data *)event_data->data;
for (i = 0; i < ARRAY_SIZE(wpa_supp_event_info); i++) {
if (strncmp(supplicant_status, wpa_supp_event_info[i].event_str,
strlen(wpa_supp_event_info[i].event_str)) == 0) {
event_info = wpa_supp_event_info[i];
break;
}
}
if (i >= ARRAY_SIZE(wpa_supp_event_info)) {
/* This is not a bug but rather implementation gap (intentional or not) */
wpa_printf(MSG_DEBUG, "Event not supported: %s", supplicant_status);
return -ENOTSUP;
}
/* Skip the event prefix and a space */
event_prefix_len = strlen(event_info.event_str) + 1;
event_no_prefix = supplicant_status + event_prefix_len;
event_data->event = event_info.event;
switch (event_data->event) {
case SUPPLICANT_EVENT_CONNECTED:
ret = sscanf(event_no_prefix, "- Connection to"
MACSTR, MACADDR2STR(tmp_mac_addr));
event_data->data_len = sizeof(data->connected);
copy_mac_addr(tmp_mac_addr, data->connected.bssid);
break;
case SUPPLICANT_EVENT_DISCONNECTED:
ret = sscanf(event_no_prefix,
MACSTR" reason=%d", MACADDR2STR(tmp_mac_addr),
&data->disconnected.reason_code);
event_data->data_len = sizeof(data->disconnected);
copy_mac_addr(tmp_mac_addr, data->disconnected.bssid);
break;
case SUPPLICANT_EVENT_ASSOC_REJECT:
/* TODO */
break;
case SUPPLICANT_EVENT_AUTH_REJECT:
ret = sscanf(event_no_prefix, MACSTR
" auth_type=%u auth_transaction=%u status_code=%u",
MACADDR2STR(tmp_mac_addr),
&data->auth_reject.auth_type,
&data->auth_reject.auth_transaction,
&data->auth_reject.status_code);
event_data->data_len = sizeof(data->auth_reject);
copy_mac_addr(tmp_mac_addr, data->auth_reject.bssid);
break;
case SUPPLICANT_EVENT_SSID_TEMP_DISABLED:
ret = sscanf(event_no_prefix,
"id=%d ssid=%s auth_failures=%u duration=%d reason=%s",
&data->temp_disabled.id, data->temp_disabled.ssid,
&data->temp_disabled.auth_failures,
&data->temp_disabled.duration,
data->temp_disabled.reason_code);
event_data->data_len = sizeof(data->temp_disabled);
break;
case SUPPLICANT_EVENT_SSID_REENABLED:
ret = sscanf(event_no_prefix,
"id=%d ssid=%s", &data->reenabled.id,
data->reenabled.ssid);
event_data->data_len = sizeof(data->reenabled);
break;
case SUPPLICANT_EVENT_BSS_ADDED:
ret = sscanf(event_no_prefix, "%u "MACSTR,
&data->bss_added.id,
MACADDR2STR(tmp_mac_addr));
copy_mac_addr(tmp_mac_addr, data->bss_added.bssid);
event_data->data_len = sizeof(data->bss_added);
break;
case SUPPLICANT_EVENT_BSS_REMOVED:
ret = sscanf(event_no_prefix,
"%u "MACSTR,
&data->bss_removed.id,
MACADDR2STR(tmp_mac_addr));
event_data->data_len = sizeof(data->bss_removed);
copy_mac_addr(tmp_mac_addr, data->bss_removed.bssid);
break;
case SUPPLICANT_EVENT_TERMINATING:
case SUPPLICANT_EVENT_SCAN_STARTED:
case SUPPLICANT_EVENT_SCAN_FAILED:
case SUPPLICANT_EVENT_NETWORK_NOT_FOUND:
case SUPPLICANT_EVENT_NETWORK_ADDED:
case SUPPLICANT_EVENT_NETWORK_REMOVED:
strncpy(data->supplicant_event_str, event_info.event_str,
sizeof(data->supplicant_event_str));
event_data->data_len = strlen(data->supplicant_event_str) + 1;
case SUPPLICANT_EVENT_DSCP_POLICY:
/* TODO */
break;
default:
break;
}
if (ret <= 0) {
wpa_printf(MSG_ERROR, "%s Parse failed: %s",
event_info.event_str, strerror(errno));
}
return ret;
}
int supplicant_send_wifi_mgmt_conn_event(void *ctx, int status_code)
{
struct wpa_supplicant *wpa_s = ctx;
int status = wpas_to_wifi_mgmt_conn_status(status_code);
enum net_event_wifi_cmd event;
if (!wpa_s || !wpa_s->current_ssid) {
return -EINVAL;
}
if (wpa_s->current_ssid->mode == WPAS_MODE_AP) {
event = NET_EVENT_WIFI_CMD_AP_ENABLE_RESULT;
} else {
event = NET_EVENT_WIFI_CMD_CONNECT_RESULT;
}
return supplicant_send_wifi_mgmt_event(wpa_s->ifname,
event,
(void *)&status,
sizeof(int));
}
int supplicant_send_wifi_mgmt_disc_event(void *ctx, int reason_code)
{
struct wpa_supplicant *wpa_s = ctx;
int status = wpas_to_wifi_mgmt_diconn_status(reason_code);
enum net_event_wifi_cmd event;
if (!wpa_s || !wpa_s->current_ssid) {
return -EINVAL;
}
if (wpa_s->wpa_state >= WPA_COMPLETED) {
if (wpa_s->current_ssid->mode == WPAS_MODE_AP) {
event = NET_EVENT_WIFI_CMD_AP_DISABLE_RESULT;
} else {
event = NET_EVENT_WIFI_CMD_DISCONNECT_RESULT;
}
} else {
if (wpa_s->current_ssid->mode == WPAS_MODE_AP) {
event = NET_EVENT_WIFI_CMD_AP_ENABLE_RESULT;
} else {
event = NET_EVENT_WIFI_CMD_CONNECT_RESULT;
}
}
return supplicant_send_wifi_mgmt_event(wpa_s->ifname,
event,
(void *)&status,
sizeof(int));
}
#ifdef CONFIG_AP
static enum wifi_link_mode get_sta_link_mode(struct wpa_supplicant *wpa_s, struct sta_info *sta)
{
if (sta->flags & WLAN_STA_HE) {
return WIFI_6;
} else if (sta->flags & WLAN_STA_VHT) {
return WIFI_5;
} else if (sta->flags & WLAN_STA_HT) {
return WIFI_4;
} else if (sta->flags & WLAN_STA_NONERP) {
return WIFI_1;
} else if (wpa_s->assoc_freq > 4000) {
return WIFI_2;
} else if (wpa_s->assoc_freq > 2000) {
return WIFI_3;
} else {
return WIFI_LINK_MODE_UNKNOWN;
}
}
static bool is_twt_capable(struct wpa_supplicant *wpa_s, struct sta_info *sta)
{
return hostapd_get_he_twt_responder(wpa_s->ap_iface->bss[0], IEEE80211_MODE_AP);
}
int supplicant_send_wifi_mgmt_ap_status(void *ctx,
enum net_event_wifi_cmd event,
enum wifi_ap_status ap_status)
{
struct wpa_supplicant *wpa_s = ctx;
int status = ap_status;
return supplicant_send_wifi_mgmt_event(wpa_s->ifname,
event,
(void *)&status,
sizeof(int));
}
int supplicant_send_wifi_mgmt_ap_sta_event(void *ctx,
enum net_event_wifi_cmd event,
void *data)
{
struct sta_info *sta = data;
struct wpa_supplicant *wpa_s = ctx;
struct wifi_ap_sta_info sta_info = { 0 };
if (!wpa_s || !sta) {
return -EINVAL;
}
memcpy(sta_info.mac, sta->addr, sizeof(sta_info.mac));
if (event == NET_EVENT_WIFI_CMD_AP_STA_CONNECTED) {
sta_info.link_mode = get_sta_link_mode(wpa_s, sta);
sta_info.twt_capable = is_twt_capable(wpa_s, sta);
}
return supplicant_send_wifi_mgmt_event(wpa_s->ifname,
event,
(void *)&sta_info,
sizeof(sta_info));
}
#endif /* CONFIG_AP */
int supplicant_send_wifi_mgmt_event(const char *ifname, enum net_event_wifi_cmd event,
void *supplicant_status, size_t len)
{
struct net_if *iface = net_if_get_by_index(net_if_get_by_name(ifname));
union supplicant_event_data data;
struct supplicant_int_event_data event_data;
if (!iface) {
wpa_printf(MSG_ERROR, "Could not find iface for %s", ifname);
return -ENODEV;
}
switch (event) {
case NET_EVENT_WIFI_CMD_CONNECT_RESULT:
wifi_mgmt_raise_connect_result_event(
iface,
*(int *)supplicant_status);
break;
case NET_EVENT_WIFI_CMD_DISCONNECT_RESULT:
wifi_mgmt_raise_disconnect_result_event(
iface,
*(int *)supplicant_status);
break;
#ifdef CONFIG_AP
case NET_EVENT_WIFI_CMD_AP_ENABLE_RESULT:
wifi_mgmt_raise_ap_enable_result_event(iface,
*(int *)supplicant_status);
break;
case NET_EVENT_WIFI_CMD_AP_DISABLE_RESULT:
wifi_mgmt_raise_ap_disable_result_event(iface,
*(int *)supplicant_status);
break;
case NET_EVENT_WIFI_CMD_AP_STA_CONNECTED:
wifi_mgmt_raise_ap_sta_connected_event(iface,
(struct wifi_ap_sta_info *)supplicant_status);
break;
case NET_EVENT_WIFI_CMD_AP_STA_DISCONNECTED:
wifi_mgmt_raise_ap_sta_disconnected_event(iface,
(struct wifi_ap_sta_info *)supplicant_status);
break;
#endif /* CONFIG_AP */
case NET_EVENT_SUPPLICANT_CMD_INT_EVENT:
event_data.data = &data;
if (supplicant_process_status(&event_data, (char *)supplicant_status) > 0) {
net_mgmt_event_notify_with_info(NET_EVENT_SUPPLICANT_INT_EVENT,
iface, &event_data, sizeof(event_data));
}
break;
default:
wpa_printf(MSG_ERROR, "Unsupported event %d", event);
return -EINVAL;
}
return 0;
}
int supplicant_generate_state_event(const char *ifname,
enum net_event_supplicant_cmd event,
int status)
{
struct net_if *iface;
iface = net_if_get_by_index(net_if_get_by_name(ifname));
if (!iface) {
wpa_printf(MSG_ERROR, "Could not find iface for %s", ifname);
return -ENODEV;
}
switch (event) {
case NET_EVENT_SUPPLICANT_CMD_READY:
net_mgmt_event_notify(NET_EVENT_SUPPLICANT_READY, iface);
break;
case NET_EVENT_SUPPLICANT_CMD_NOT_READY:
net_mgmt_event_notify(NET_EVENT_SUPPLICANT_NOT_READY, iface);
break;
case NET_EVENT_SUPPLICANT_CMD_IFACE_ADDED:
net_mgmt_event_notify(NET_EVENT_SUPPLICANT_IFACE_ADDED, iface);
break;
case NET_EVENT_SUPPLICANT_CMD_IFACE_REMOVING:
net_mgmt_event_notify(NET_EVENT_SUPPLICANT_IFACE_REMOVING, iface);
break;
case NET_EVENT_SUPPLICANT_CMD_IFACE_REMOVED:
net_mgmt_event_notify_with_info(NET_EVENT_SUPPLICANT_IFACE_REMOVED,
iface, &status, sizeof(status));
break;
default:
wpa_printf(MSG_ERROR, "Unsupported event %d", event);
return -EINVAL;
}
return 0;
}