From 61821b94d4be53e1a391b571a054e07fc2d7f8bb Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 19 Sep 2025 02:42:27 +0100 Subject: [PATCH 1/5] lib/tools: introduce string to vector tokenizer function Signed-off-by: Maxim Menshikov Acked-by: Elena Vengerova --- lib/tools/te_vector.c | 33 +++++++++++++++++++++++++++++++++ lib/tools/te_vector.h | 22 ++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/lib/tools/te_vector.c b/lib/tools/te_vector.c index 374a4df19..14a433295 100644 --- a/lib/tools/te_vector.c +++ b/lib/tools/te_vector.c @@ -274,6 +274,39 @@ te_vec_split_string(const char *str, te_vec *strvec, char sep, return 0; } +/* See the description in te_vector.h */ +te_errno +te_vec_tokenize_string(const char *str, te_vec *strvec, const char *sep_symbols) +{ + char *str_copy; + char *part; + char *ptr = NULL; + + assert(strvec != NULL); + assert(strvec->element_size == sizeof(char *)); + assert(sep_symbols != NULL); + + te_vec_set_destroy_fn_safe(strvec, te_vec_item_free_ptr); + + if (str == NULL || *str == '\0') + return 0; + + str_copy = TE_STRDUP(str); + part = strtok_r(str_copy, sep_symbols, &ptr); + while (part != NULL) + { + char *tmp_part; + + tmp_part = TE_STRDUP(part); + + TE_VEC_APPEND(strvec, tmp_part); + part = strtok_r(NULL, sep_symbols, &ptr); + } + free(str_copy); + + return 0; +} + void te_vec_sort(te_vec *vec, int (*compar)(const void *, const void *)) { diff --git a/lib/tools/te_vector.h b/lib/tools/te_vector.h index 77cf52607..f4a77525d 100644 --- a/lib/tools/te_vector.h +++ b/lib/tools/te_vector.h @@ -589,6 +589,28 @@ te_vec_get_index(const te_vec *vec, const void *ptr) extern te_errno te_vec_split_string(const char *str, te_vec *strvec, char sep, bool empty_is_none); +/** + * Tokenize a string into chunks separated by symbols passed as @p sep_symbols. + * + * The copies of the chunks are pushed into the @p strvec. + * + * @note The element size of @p strvec must be `sizeof(char *)`. + * + * @note Adjacent separators are skipped, so e.g. + * @c ':::' would be split into zero chunks when using colon as + * a separator. + * + * @param[in] str String to split. + * @param[in,out] strvec Target vector for string chunks. + * The original content is **not** destroyed, + * new items are added to the end. + * @param[in] sep_symbols String with separator characters. + * + * @return Status code (always 0). + */ +extern te_errno te_vec_tokenize_string(const char *str, te_vec *strvec, + const char *sep_symbols); + /** * Sort the elements of @p vec in place according to @p compar. * From 77e37c1ffff5b0cf9a00d14680938395a720307d Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 19 Sep 2025 03:38:10 +0100 Subject: [PATCH 2/5] doc: introduce WiFi configurator model Signed-off-by: Maxim Menshikov Acked-by: Elena Vengerova --- doc/cm/cm_wifi.yml | 288 +++++++++++++++++++++++++++++++++++++++++++++ doc/cm/meson.build | 1 + 2 files changed, 289 insertions(+) create mode 100644 doc/cm/cm_wifi.yml diff --git a/doc/cm/cm_wifi.yml b/doc/cm/cm_wifi.yml new file mode 100644 index 000000000..81d80097e --- /dev/null +++ b/doc/cm/cm_wifi.yml @@ -0,0 +1,288 @@ +--- +# SPDX-License-Identifier: Apache-2.0 + +- comment: | + WiFi AP and STA settings on Agents. + + Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. + + +- register: + + - oid: "/agent/wifi" + access: read_only + type: none + d: | + Root object of the WiFi configuration tree. + Name: empty + + - oid: "/agent/wifi/configurator" + access: read_write + type: string + d: | + Select configurator + Name: empty + Value: "uci" or "hostapd_wpa_supplicant" + + - oid: "/agent/wifi/enable" + access: read_write + type: int32 + d: | + Enable or disable WiFi + Name: empty + Value: 0 or 1 + + - oid: "/agent/wifi/status" + access: read_only + type: int32 + volatile: true + d: | + Is WiFi enabled or not + Name: empty + Value: 0 or 1 + + - oid: "/agent/wifi/port" + access: read_create + type: none + d: | + WiFi port instance. + Name: any, typically 2_4 or 5 + + - oid: "/agent/wifi/port/enable" + access: read_write + type: int32 + d: | + Port enable state + Name: empty + Value: 1 (true) or 0 (false) + + - oid: "/agent/wifi/port/ifname" + access: read_write + type: string + d: | + Interface name + Name: empty + Value: Any supported by Linux + + - oid: "/agent/wifi/port/standard" + access: read_write + type: string + d: | + WiFi standard. + Name: empty + Value: g, n, ac, ax + + - oid: "/agent/wifi/port/channel" + access: read_write + type: uint8 + d: | + WiFi channel. + Name: empty + Value: Channel number + + - oid: "/agent/wifi/port/width" + access: read_write + type: uint32 + d: | + Bandwidth + Name: empty + Value: 20, 40, 80, 160, 320 + + - oid: "/agent/wifi/port/max_a_msdu" + access: read_write + type: uint32 + d: | + Max A-MSDU + Name: empty + Value: any supported value + + - oid: "/agent/wifi/port/max_a_mpdu" + access: read_write + type: uint32 + d: | + Max A-MPDU + Name: empty + Value: any supported value + + - oid: "/agent/wifi/port/frag_threshold" + access: read_write + type: uint32 + d: | + Fragmentation threshold. + Name: empty + Value: 0 (disable fragmentation) or 256 .. 2346 + + - oid: "/agent/wifi/port/rts_threshold" + access: read_write + type: uint32 + d: | + RTS threshold. + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/short_retry_limit" + access: read_write + type: uint32 + d: | + Short retry limit. + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/long_retry_limit" + access: read_write + type: uint32 + d: | + Long retry limit. + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/guard_interval" + access: read_write + type: uint32 + d: | + Guard interval + Name: empty + Value: Any supported value (in ns) + + - oid: "/agent/wifi/port/aifs" + access: read_write + type: uint32 + d: | + Arbitration Inter-Frame Space + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/contention_window_min" + access: read_write + type: uint32 + d: | + Contention window minimum + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/contention_window_max" + access: read_write + type: uint32 + d: | + Contention window maximum + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/txop_limit" + access: read_write + type: uint32 + d: | + TXOP Limit + Name: empty + Value: Any supported value (in microseconds) + + - oid: "/agent/wifi/port/tx_power" + access: read_write + type: uint32 + d: | + TX power + Name: empty + Value: Any supported value (in DBM) + + - oid: "/agent/wifi/port/max_nss" + access: read_write + type: uint32 + d: | + Maximum number of spatial streams + Name: empty + Value: Any supported value + + - oid: "/agent/wifi/port/option" + access: read_create + type: none + d: | + Additional options passed to underlying configurator. + Name: any + Value: none + + - oid: "/agent/wifi/port/option/value" + access: read_write + type: string + d: | + Value of the option passed to underlying configurator. + Name: empty + Value: any option permitted by underlying configurator. + + - oid: "/agent/wifi/port/ssid" + access: read_create + type: none + d: | + SSID instance. + Name: Internal SSID name + + - oid: "/agent/wifi/port/ssid/enable" + access: read_write + type: int32 + d: | + SSID enable status + Name: empty + Value: 1 (true) or 0 (false) + + - oid: "/agent/wifi/port/ssid/ifname" + access: read_write + type: string + d: | + Interface name + Name: empty + Value: Any supported by Linux + + - oid: "/agent/wifi/port/ssid/name" + access: read_write + type: string + d: | + SSID name. + Name: empty + Value: SSID name + + - oid: "/agent/wifi/port/ssid/mode" + access: read_write + type: string + d: | + SSID mode + Name: empty + Value: ap/sta + + - oid: "/agent/wifi/port/ssid/security" + access: read_write + type: string + d: | + SSID security. + Name: empty + Value: open, wpa, wpa2, wpa3 + + - oid: "/agent/wifi/port/ssid/protocol" + access: read_write + type: string + d: | + SSID protocol. + Name: empty + Value: ccmp, tkip + + - oid: "/agent/wifi/port/ssid/passphrase" + access: read_write + type: string + d: | + SSID passphrase. + Name: empty + Value: Passphrase 8 to 63 characters long + + - oid: "/agent/wifi/port/ssid/option" + access: read_create + type: none + d: | + Additional options passed to underlying configurator. + Name: any + Value: none + + - oid: "/agent/wifi/port/ssid/option/value" + access: read_write + type: string + d: | + Value of the option passed to underlying configurator. + Name: empty + Value: any option permitted by underlying configurator. diff --git a/doc/cm/meson.build b/doc/cm/meson.build index c380fb95c..34e229cbc 100644 --- a/doc/cm/meson.build +++ b/doc/cm/meson.build @@ -69,6 +69,7 @@ cm_files = files( 'cm_vnc.yml', 'cm_volatile.yml', 'cm_vtun.yml', + 'cm_wifi.yml', 'cm_xvfb.yml', 'cm_xen.yml' ) From 82548c082e919b60e260c2a35c31e321ddadda04 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 19 Sep 2025 03:43:47 +0100 Subject: [PATCH 3/5] lib/tapi_cfg_wifi: introduce WiFi configurator TAPI This is the first bit to enable configuration of WiFi - generic definitions that should be used in both tests and the agent implementation. Signed-off-by: Maxim Menshikov Acked-by: Elena Vengerova --- lib/meson.build | 1 + lib/tapi_cfg_wifi/meson.build | 32 +++++ lib/tapi_cfg_wifi/tapi_cfg_wifi.c | 149 +++++++++++++++++++++++ lib/tapi_cfg_wifi/tapi_cfg_wifi.h | 190 ++++++++++++++++++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 lib/tapi_cfg_wifi/meson.build create mode 100644 lib/tapi_cfg_wifi/tapi_cfg_wifi.c create mode 100644 lib/tapi_cfg_wifi/tapi_cfg_wifi.h diff --git a/lib/meson.build b/lib/meson.build index b07a73cba..2bf255b64 100755 --- a/lib/meson.build +++ b/lib/meson.build @@ -40,6 +40,7 @@ te_common_libs = [ 'rpc_dpdk', 'rpc_types', 'ovs_flow', + 'tapi_cfg_wifi', ] # Test Engine libraries diff --git a/lib/tapi_cfg_wifi/meson.build b/lib/tapi_cfg_wifi/meson.build new file mode 100644 index 000000000..a81dab208 --- /dev/null +++ b/lib/tapi_cfg_wifi/meson.build @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. + +#tapi_cfg_wifi_include = include_directories('') + +#lib_tapi_cfg_wifi = static_library('tapi_cfg_wifi', 'tapi_cfg_wifi.c', +# install: install_lib, +# include_directories: [includes, tapi_cfg_wifi_include]) +#dep_lib_tapi_cfg_wifi = declare_dependency(link_with: lib_tapi_cfg_wifi, +# include_directories: [includes, tapi_cfg_wifi_include]) +#dep_lib_static_ta_cfg_wifi = dep_lib_tapi_cfg_wifi + +#headers += files( +# 'tapi_cfg_wifi.h' +#) + +#sources += files( +# 'tapi_cfg_wifi.c' +#) + + +headers += files( + 'tapi_cfg_wifi.h', +) + +sources += files( + 'tapi_cfg_wifi.c', +) + +te_libs += [ + 'tools' +] diff --git a/lib/tapi_cfg_wifi/tapi_cfg_wifi.c b/lib/tapi_cfg_wifi/tapi_cfg_wifi.c new file mode 100644 index 000000000..5227edf6e --- /dev/null +++ b/lib/tapi_cfg_wifi/tapi_cfg_wifi.c @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi configuration TAPI + */ + +#define TE_LGR_USER "TAPI CFG WIFI" + +#include "te_config.h" +#include "te_defs.h" +#include "te_enum.h" +#include "tapi_cfg_wifi.h" + +/** Mapping of supported WiFi configurators */ +static const te_enum_map wifi_configurator_mapping[] = { + { .name = "auto", .value = TAPI_CFG_WIFI_CFG_AUTO }, + { .name = "hostapd_wpa_supplicant", + .value = TAPI_CFG_WIFI_CFG_HOSTAPD_WPA_SUPPLICANT }, + { .name = "uci", .value = TAPI_CFG_WIFI_CFG_UCI }, + TE_ENUM_MAP_END +}; + +/** Mapping of supported WiFi standards */ +static const te_enum_map wifi_standard_mapping[] = { + { .name = "g", .value = TAPI_CFG_WIFI_STANDARD_G }, + { .name = "n", .value = TAPI_CFG_WIFI_STANDARD_N }, + { .name = "ac", .value = TAPI_CFG_WIFI_STANDARD_AC }, + { .name = "ax", .value = TAPI_CFG_WIFI_STANDARD_AX }, + TE_ENUM_MAP_END +}; + +/** Mapping of supported widths */ +static const te_enum_map wifi_width_mapping[] = { + { .name = "0", .value = TAPI_CFG_WIFI_WIDTH_NOT_SET }, + { .name = "20", .value = TAPI_CFG_WIFI_WIDTH_20 }, + { .name = "40", .value = TAPI_CFG_WIFI_WIDTH_40 }, + { .name = "80", .value = TAPI_CFG_WIFI_WIDTH_80 }, + { .name = "160", .value = TAPI_CFG_WIFI_WIDTH_160 }, + { .name = "320", .value = TAPI_CFG_WIFI_WIDTH_320 }, + TE_ENUM_MAP_END +}; + +/** Mapping of supported WiFi security */ +static const te_enum_map wifi_security_mapping[] = { + { .name = "open", .value = TAPI_CFG_WIFI_SECURITY_OPEN }, + { .name = "wep", .value = TAPI_CFG_WIFI_SECURITY_WEP }, + { .name = "wpa", .value = TAPI_CFG_WIFI_SECURITY_WPA }, + { .name = "wpa2", .value = TAPI_CFG_WIFI_SECURITY_WPA2 }, + { .name = "wpa3", .value = TAPI_CFG_WIFI_SECURITY_WPA3 }, + TE_ENUM_MAP_END +}; + +/** Mapping of supported WiFi modes */ +static const te_enum_map wifi_mode_mapping[] = { + { .name = "ap", .value = TAPI_CFG_WIFI_MODE_AP }, + { .name = "sta", .value = TAPI_CFG_WIFI_MODE_STA }, + TE_ENUM_MAP_END +}; + +/** Mapping of supported WiFi protocols */ +static const te_enum_map wifi_protocol_mapping[] = { + { .name = "ccmp", .value = TAPI_CFG_WIFI_PROTOCOL_CCMP }, + { .name = "tkip", .value = TAPI_CFG_WIFI_PROTOCOL_TKIP }, + TE_ENUM_MAP_END +}; + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_configurator_from_value(tapi_cfg_wifi_configurator value) +{ + return te_enum_map_from_value(wifi_configurator_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_configurator +tapi_cfg_wifi_configurator_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_configurator_mapping, str, -1); +} + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_standard_from_value(tapi_cfg_wifi_standard value) +{ + return te_enum_map_from_value(wifi_standard_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_standard +tapi_cfg_wifi_standard_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_standard_mapping, str, -1); +} + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_width_from_value(tapi_cfg_wifi_width value) +{ + return te_enum_map_from_value(wifi_width_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_standard +tapi_cfg_wifi_width_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_width_mapping, str, -1); +} + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_mode_from_value(tapi_cfg_wifi_mode value) +{ + return te_enum_map_from_value(wifi_mode_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_mode +tapi_cfg_wifi_mode_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_mode_mapping, str, -1); +} + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_security_from_value(tapi_cfg_wifi_security value) +{ + return te_enum_map_from_value(wifi_security_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_security +tapi_cfg_wifi_security_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_security_mapping, str, -1); +} + +/* See the description in tapi_cfg_wifi.h */ +const char * +tapi_cfg_wifi_protocol_from_value(tapi_cfg_wifi_protocol value) +{ + return te_enum_map_from_value(wifi_protocol_mapping, value); +} + +/* See the description in tapi_cfg_wifi.h */ +tapi_cfg_wifi_mode +tapi_cfg_wifi_protocol_from_str(const char *str) +{ + return te_enum_map_from_str(wifi_protocol_mapping, str, -1); +} diff --git a/lib/tapi_cfg_wifi/tapi_cfg_wifi.h b/lib/tapi_cfg_wifi/tapi_cfg_wifi.h new file mode 100644 index 000000000..801fcbc9d --- /dev/null +++ b/lib/tapi_cfg_wifi/tapi_cfg_wifi.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi configuration TAPI + * + * @defgroup tapi_cfg_wifi WiFi configuration TAPI + * @ingroup ta_wifi + * @{ + * + * Helpers + */ + +#ifndef __TAPI_CFG_WIFI_H__ +#define __TAPI_CFG_WIFI_H__ + +#include "te_config.h" +#include "te_defs.h" +#include "te_enum.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Supported WiFi configurators */ +typedef enum tapi_cfg_wifi_configurator { + TAPI_CFG_WIFI_CFG_AUTO = 0, /**< Automatic detection of + configurator */ + TAPI_CFG_WIFI_CFG_HOSTAPD_WPA_SUPPLICANT, /**< hostapd/wpa_supplicant + configurator */ + TAPI_CFG_WIFI_CFG_UCI, /**< UCI configurator */ +} tapi_cfg_wifi_configurator; + +/** Supported WiFi standards */ +typedef enum tapi_cfg_wifi_standard { + TAPI_CFG_WIFI_STANDARD_G = 0, /**< G standard (2.4GHz) */ + TAPI_CFG_WIFI_STANDARD_N, /**< N standard (2.4GHz) */ + TAPI_CFG_WIFI_STANDARD_AC, /**< AC standard (5GHz) */ + TAPI_CFG_WIFI_STANDARD_AX, /**< AX standard (2.4/5GHz) */ + TAPI_CFG_WIFI_STANDARD_BE, /**< BE standard (2.4/5/6GHz) */ +} tapi_cfg_wifi_standard; + +/** Supported WiFi bandwidths */ +typedef enum tapi_cfg_wifi_width { + TAPI_CFG_WIFI_WIDTH_NOT_SET = 0, /**< Not set */ + TAPI_CFG_WIFI_WIDTH_20 = 20, /**< 20 MHz */ + TAPI_CFG_WIFI_WIDTH_40 = 40, /**< 40 MHz */ + TAPI_CFG_WIFI_WIDTH_80 = 80, /**< 80 MHz */ + TAPI_CFG_WIFI_WIDTH_160 = 160, /**< 160 MHz */ + TAPI_CFG_WIFI_WIDTH_320 = 320, /**< 320 MHz */ +} tapi_cfg_wifi_width; + +/** Supported WiFi modes */ +typedef enum tapi_cfg_wifi_mode { + TAPI_CFG_WIFI_MODE_AP = 0, /**< Access point mode */ + TAPI_CFG_WIFI_MODE_STA, /**< STA mode */ +} tapi_cfg_wifi_mode; + +/** Supported WiFi security */ +typedef enum tapi_cfg_wifi_security { + TAPI_CFG_WIFI_SECURITY_OPEN = 0, /**< No security (no password) */ + TAPI_CFG_WIFI_SECURITY_WEP, /**< WEP security */ + TAPI_CFG_WIFI_SECURITY_WPA, /**< WPA security */ + TAPI_CFG_WIFI_SECURITY_WPA2, /**< WPA2 security */ + TAPI_CFG_WIFI_SECURITY_WPA3, /**< WPA3 security */ +} tapi_cfg_wifi_security; + +/** Supported WiFi protocols */ +typedef enum tapi_cfg_wifi_protocol { + TAPI_CFG_WIFI_PROTOCOL_CCMP = 0, /**< CCMP protocol */ + TAPI_CFG_WIFI_PROTOCOL_TKIP, /**< TKIP protocol */ +} tapi_cfg_wifi_protocol; + +/** + * Get string representation of the configurator + * + * @param value Numeric representation of the configurator + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_configurator_from_value( + tapi_cfg_wifi_configurator value); + +/** + * Convert string representation of the configurator to the numeric one + * + * @param str Pointer to string representation + * + * @return The security mode of @p tapi_cfg_wifi_configurator enumeration + */ +extern tapi_cfg_wifi_configurator tapi_cfg_wifi_configurator_from_str( + const char *str); + +/** + * Get string representation of the WiFi standard + * + * @param value Numeric representation of the standard + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_standard_from_value( + tapi_cfg_wifi_standard value); + +/** + * Convert string representation of the standard to a numeric one + * + * @param str Pointer to string representation + * + * @return The WiFi standard of @p tapi_cfg_wifi_standard enumeration + */ +extern tapi_cfg_wifi_standard tapi_cfg_wifi_standard_from_str(const char *str); + +/** + * Get string representation of the band width + * + * @param value Numeric representation of the width + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_width_from_value(tapi_cfg_wifi_width value); + +/** + * Convert string representation of the width to a numeric one + * + * @param str Pointer to string representation + * + * @return The band width of @p tapi_cfg_wifi_width enumeration + */ +extern tapi_cfg_wifi_standard tapi_cfg_wifi_width_from_str(const char *str); + +/** + * Get string representation of the WiFi operation mode + * + * @param value Numeric representation of the mode + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_mode_from_value(tapi_cfg_wifi_mode value); + +/** + * Convert string representation of the operation mode to the numeric one + * + * @param str Pointer to string representation + * + * @return The security mode of @p tapi_cfg_wifi_mode enumeration + */ +extern tapi_cfg_wifi_mode tapi_cfg_wifi_mode_from_str(const char *str); + +/** + * Get string representation of the WiFi security mode + * + * @param value Numeric representation of the security mode + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_security_from_value( + tapi_cfg_wifi_security value); + +/** + * Convert string representation of the security mode to a numeric one + * + * @param str Pointer to string representation + * + * @return The security mode of @p tapi_cfg_wifi_security enumeration + */ +extern tapi_cfg_wifi_security tapi_cfg_wifi_security_from_str(const char *str); + +/** + * Get string representation of the protocol + * + * @param value Numeric representation of the protocol + * + * @return The pointer to the string representation + */ +extern const char *tapi_cfg_wifi_protocol_from_value( + tapi_cfg_wifi_protocol value); + +/** + * Convert string representation of the protocol to the numeric one + * + * @param str Pointer to string representation + * + * @return The security mode of @p tapi_cfg_wifi_protocol enumeration + */ +extern tapi_cfg_wifi_mode tapi_cfg_wifi_protocol_from_str(const char *str); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* !__TAPI_CFG_WIFI_H__ */ From c598955e9c68820c0302fae4eef2a176a86244d5 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 19 Sep 2025 09:37:54 +0100 Subject: [PATCH 4/5] lib/ta_wifi: introduce WiFi configurator tree TA WiFi is based on several concepts: - It tries to be backend-agnostic. Right now, UCI and wpa_supplicant are supported. - It still supports backend-specific optinons. It is clear that real-world devices require unique options that can't be covered by unified set of options. - The backend is auto-detected by default but can be fixed to one of them. For UCI, there are several implications: - The configuration is first read, then augmented. To generate it correctly, the port interface name must match the one in the configuration. - The SSID configuration is based on the first SSID from the configuration that matches the band of the port. - The configuration must reside in /etc/config/wireless. - The script to up/down the interfaces is 'wifi up'. For wpa_supplicant, the binary must reside in /usr/sbin/wpa_supplicant. For hostapd, the binary must reside in /usr/sbin/hostapd Signed-off-by: Maxim Menshikov Acked-by: Elena Vengerova --- agents/unix/conf/base/conf.c | 12 + agents/unix/conf/meson.build | 4 + engine/builder/te_meson_build | 2 + lib/meson.build | 1 + lib/ta_wifi/meson.build | 23 + lib/ta_wifi/ta_wifi.c | 1354 +++++++++++++++++++++++++++++ lib/ta_wifi/ta_wifi.h | 34 + lib/ta_wifi/ta_wifi_internal.c | 176 ++++ lib/ta_wifi/ta_wifi_internal.h | 162 ++++ lib/ta_wifi/ta_wifi_uci.c | 106 +++ lib/ta_wifi/ta_wifi_uci.h | 144 +++ lib/ta_wifi/ta_wifi_uci_apply.c | 429 +++++++++ lib/ta_wifi/ta_wifi_uci_parser.c | 227 +++++ lib/ta_wifi/ta_wifi_wh.c | 760 ++++++++++++++++ lib/ta_wifi/ta_wifi_wh.h | 44 + lib/tapi_cfg_wifi/meson.build | 18 - lib/tapi_cfg_wifi/tapi_cfg_wifi.c | 1 + meson_options.txt | 2 +- 18 files changed, 3480 insertions(+), 19 deletions(-) create mode 100644 lib/ta_wifi/meson.build create mode 100644 lib/ta_wifi/ta_wifi.c create mode 100644 lib/ta_wifi/ta_wifi.h create mode 100644 lib/ta_wifi/ta_wifi_internal.c create mode 100644 lib/ta_wifi/ta_wifi_internal.h create mode 100644 lib/ta_wifi/ta_wifi_uci.c create mode 100644 lib/ta_wifi/ta_wifi_uci.h create mode 100644 lib/ta_wifi/ta_wifi_uci_apply.c create mode 100644 lib/ta_wifi/ta_wifi_uci_parser.c create mode 100644 lib/ta_wifi/ta_wifi_wh.c create mode 100644 lib/ta_wifi/ta_wifi_wh.h diff --git a/agents/unix/conf/base/conf.c b/agents/unix/conf/base/conf.c index 0217c84f9..59c44ab90 100644 --- a/agents/unix/conf/base/conf.c +++ b/agents/unix/conf/base/conf.c @@ -333,6 +333,10 @@ extern te_errno ta_unix_conf_loadavg_init(void); # include "conf_upnp_cp.h" #endif /* WITH_UPNP_CP */ +#ifdef WITH_WIFI +#include "ta_wifi.h" +#endif /* WITH_WIFI */ + /** * Determine family of the address in string representation. * @@ -1470,6 +1474,14 @@ rcf_ch_conf_init(void) } #endif /* WITH_UPNP_CP */ +#ifdef WITH_WIFI + if (ta_unix_conf_wifi_init() != 0) + { + ERROR("Failed to add WiFi configuration subtree"); + goto fail; + } +#endif /* WITH_WIFI */ + #ifdef WITH_SOCKS if (ta_unix_conf_socks_init() != 0) { diff --git a/agents/unix/conf/meson.build b/agents/unix/conf/meson.build index 9d4f9b6d6..dfbe7f20f 100644 --- a/agents/unix/conf/meson.build +++ b/agents/unix/conf/meson.build @@ -121,6 +121,10 @@ endif if conf.contains('upnp_cp') c_args += [ '-DWITH_UPNP_CP' ] endif +if conf.contains('wifi') + c_args += [ '-DWITH_WIFI' ] +endif + if conf.contains('tc') c_args += [ '-DWITH_TC' ] includes += include_directories('tc') diff --git a/engine/builder/te_meson_build b/engine/builder/te_meson_build index 9c88396be..99e1ddc04 100755 --- a/engine/builder/te_meson_build +++ b/engine/builder/te_meson_build @@ -348,6 +348,8 @@ process_agent_unix_parms() { conf="${conf} upnp_cp" ;; --with-vcm) conf="${conf} vcm" ;; + --with-wifi) + conf="${conf} wifi" ;; --without-static-libc) # default, in fact --with-static-libc is not supported yet ;; diff --git a/lib/meson.build b/lib/meson.build index 2bf255b64..832018078 100755 --- a/lib/meson.build +++ b/lib/meson.build @@ -93,6 +93,7 @@ te_agent_libs = [ 'tad', 'ta_job', 'ta_restconf', + 'ta_wifi', 'rpcs_bpf', 'rpcs_dpdk', 'rpcs_job', diff --git a/lib/ta_wifi/meson.build b/lib/ta_wifi/meson.build new file mode 100644 index 000000000..17d449a71 --- /dev/null +++ b/lib/ta_wifi/meson.build @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. + +headers += files( + 'ta_wifi.h', +) + +sources += files( + 'ta_wifi.c', + 'ta_wifi_internal.c', + 'ta_wifi_uci.c', + 'ta_wifi_uci_apply.c', + 'ta_wifi_uci_parser.c', + 'ta_wifi_wh.c' +) + +te_libs += [ + 'agentlib', + 'rpc_types', + 'tools', + 'rcfpch', + 'tapi_cfg_wifi', +] diff --git a/lib/ta_wifi/ta_wifi.c b/lib/ta_wifi/ta_wifi.c new file mode 100644 index 000000000..6bedd013a --- /dev/null +++ b/lib/ta_wifi/ta_wifi.c @@ -0,0 +1,1354 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * Basic WiFi agent tree implementation. + */ + +#define TE_LGR_USER "TA WiFi" + +#include "te_config.h" + +#include "te_alloc.h" +#include "te_str.h" +#include "te_string.h" +#include "logger_api.h" +#include "te_queue.h" +#include "tq_string.h" +#include "rcf_pch.h" +#include "ta_wifi.h" +#include "ta_wifi_internal.h" +#include "ta_wifi_uci.h" +#include "ta_wifi_wh.h" + +#if HAVE_SYS_STAT_H +#include +#endif + +static rcf_pch_cfg_object node_wifi; + +static te_errno ta_unix_conf_wifi_apply(void); +static te_errno ta_unix_conf_wifi_cancel(void); + +static te_errno +node_wifi_ssid_passphrase_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + snprintf(value, RCF_MAX_VAL, "%s", + ssid->passphrase != NULL ? ssid->passphrase : ""); + return 0; +} + +static te_errno +node_wifi_ssid_passphrase_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(ssid->passphrase); + ssid->passphrase = TE_STRDUP(value); + return 0; +} + +static te_errno +node_wifi_ssid_mode_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + te_errno rc; + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_mode_from_value(ssid->mode)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_ssid_mode_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + mapped = tapi_cfg_wifi_mode_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + ssid->mode = mapped; + return 0; +} + +static te_errno +node_wifi_ssid_security_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + te_errno rc; + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_security_from_value(ssid->security)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_ssid_security_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + mapped = tapi_cfg_wifi_security_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + ssid->security = mapped; + return 0; +} + +static te_errno +node_wifi_ssid_protocol_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + te_errno rc; + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_protocol_from_value(ssid->protocol)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_ssid_protocol_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + mapped = tapi_cfg_wifi_protocol_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + ssid->protocol = mapped; + return 0; +} + +static te_errno +node_wifi_ssid_name_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + snprintf(value, RCF_MAX_VAL, "%s", + ssid->name != NULL ? ssid->name : ""); + return 0; +} + +static te_errno +node_wifi_ssid_name_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(ssid->name); + ssid->name = TE_STRDUP(value); + return 0; +} + +static te_errno +node_wifi_ssid_ifname_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + snprintf(value, RCF_MAX_VAL, "%s", + ssid->ifname != NULL ? ssid->ifname : ""); + return 0; +} + +static te_errno +node_wifi_ssid_ifname_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(ssid->ifname); + ssid->ifname = TE_STRDUP(value); + return 0; +} + +static te_errno +node_wifi_ssid_enable_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + sprintf(value, "%d", ssid->enable ? 1 : 0); + return 0; +} + +static te_errno +node_wifi_ssid_enable_set(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + te_errno ret; + ta_wifi_ssid *ssid; + bool result; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ssid = ta_wifi_port_find_ssid(ta_wifi_find_port(port_name), ssid_name); + if (ssid == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + ret = te_strtol_bool(value, &result); + if (ret != 0) + return ret; + + ssid->enable = result; + + return 0; +} + +static te_errno +node_wifi_port_ssid_add(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if (ta_wifi_port_find_ssid(port, ssid_name) != NULL) + return TE_RC(TE_TA_UNIX, TE_EEXIST); + + ssid = TE_ALLOC(sizeof(*ssid)); + + ssid->instance_name = TE_STRDUP(ssid_name); + ssid->security = TAPI_CFG_WIFI_SECURITY_WPA2; + ssid->protocol = TAPI_CFG_WIFI_PROTOCOL_CCMP; + + SLIST_INSERT_HEAD(&port->ssids, ssid, links); + return 0; +} + +static te_errno +node_wifi_port_ssid_del(unsigned int gid, const char *oid, + const char *empty, const char *port_name, const char *ssid_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_REMOVE(&port->ssids, ssid, ta_wifi_ssid, links); + + ta_wifi_ssid_free(ssid); + return 0; +} + +static te_errno +node_wifi_port_ssid_list(unsigned int gid, const char *oid, const char *sub_id, + char **list, const char *empty, const char *port_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + te_string str = TE_STRING_INIT; + + UNUSED(gid); + UNUSED(oid); + UNUSED(sub_id); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_FOREACH(ssid, &port->ssids, links) + { + te_string_append(&str, "%s%s", + (str.ptr != NULL) ? " " : "", ssid->instance_name); + } + + *list = str.ptr; + return 0; +} + +/** Set custom option value */ +static te_errno +node_wifi_port_option_value_set(unsigned int gid, const char *oid, + const char *value, const char *empty, const char *port_name, + const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_port_find_option(port, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(option->value); + option->value = TE_STRDUP(value); + + return 0; +} + +/** Get custom option value */ +static te_errno +node_wifi_port_option_value_get(unsigned int gid, const char *oid, char *value, + const char *empty, + const char *port_name, + const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_port_find_option(port, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + strcpy(value, option->value); + + return 0; +} + +static te_errno +node_wifi_port_option_add(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name, const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if (ta_wifi_port_find_option(port, option_name) != NULL) + return TE_RC(TE_TA_UNIX, TE_EEXIST); + + option = TE_ALLOC(sizeof(*option)); + + option->name = TE_STRDUP(option_name); + option->value = TE_STRDUP(value); + + SLIST_INSERT_HEAD(&port->options, option, links); + + return 0; +} + +static te_errno +node_wifi_port_option_del(unsigned int gid, const char *oid, + const char *empty, const char *port_name, const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_port_find_option(port, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_REMOVE(&port->options, option, ta_wifi_option, links); + + ta_wifi_option_free(option); + + return 0; +} + +static te_errno +node_wifi_port_option_list(unsigned int gid, const char *oid, + const char *sub_id, char **list, const char *empty, const char *port_name) +{ + ta_wifi_port *port; + ta_wifi_option *option; + te_string str = TE_STRING_INIT; + + UNUSED(gid); + UNUSED(oid); + UNUSED(sub_id); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_FOREACH(option, &port->options, links) + { + te_string_append(&str, "%s%s", + (str.ptr != NULL) ? " " : "", option->name); + } + + *list = str.ptr; + return 0; +} + + +/** Set custom option value */ +static te_errno +node_wifi_ssid_option_value_set(unsigned int gid, const char *oid, + const char *value, const char *empty, const char *port_name, + const char *ssid_name, const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_ssid_find_option(ssid, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(option->value); + option->value = TE_STRDUP(value); + + return 0; +} + +/** Get custom option value */ +static te_errno +node_wifi_ssid_option_value_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name, const char *ssid_name, + const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_ssid_find_option(ssid, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + strcpy(value, option->value); + + return 0; +} + +static te_errno +node_wifi_ssid_option_add(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name, const char *ssid_name, + const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if (ta_wifi_ssid_find_option(ssid, option_name) != NULL) + return TE_RC(TE_TA_UNIX, TE_EEXIST); + + option = TE_ALLOC(sizeof(*option)); + + option->name = TE_STRDUP(option_name); + option->value = TE_STRDUP(value); + + SLIST_INSERT_HEAD(&ssid->options, option, links); + + return 0; +} + +static te_errno +node_wifi_ssid_option_del(unsigned int gid, const char *oid, const char *empty, + const char *port_name, const char *ssid_name, const char *option_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((option = ta_wifi_ssid_find_option(ssid, option_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_REMOVE(&ssid->options, option, ta_wifi_option, links); + + ta_wifi_option_free(option); + + return 0; +} + +static te_errno +node_wifi_ssid_option_list(unsigned int gid, const char *oid, + const char *sub_id, char **list, const char *empty, const char *port_name, + const char *ssid_name) +{ + ta_wifi_port *port; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + te_string str = TE_STRING_INIT; + + UNUSED(gid); + UNUSED(oid); + UNUSED(sub_id); + UNUSED(empty); + + if ((port = ta_wifi_find_port(port_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if ((ssid = ta_wifi_port_find_ssid(port, ssid_name)) == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + SLIST_FOREACH(option, &ssid->options, links) + { + te_string_append(&str, "%s%s", + (str.ptr != NULL) ? " " : "", option->name); + } + + *list = str.ptr; + return 0; +} + +static te_errno +node_wifi_port_channel_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ENTRY("%s", port_name); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%u", (unsigned int)port->channel); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_port_channel_set(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ENTRY("%s", port_name); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_strtou_size(value, 0, &port->channel, sizeof(port->channel)); + + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_port_enable_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ENTRY("%s", port_name); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%u", port->enable ? 1U : 0U); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_port_enable_set(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ENTRY("%s", port_name); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_strtou_size(value, 0, &port->enable, sizeof(port->enable)); + + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + + +static te_errno +node_wifi_configurator_get(unsigned int gid, const char *oid, char *value, + const char *empty) +{ + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_configurator_from_value( + ta_wifi_get_node()->configurator)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_configurator_set(unsigned int gid, const char *oid, + const char *value, const char *empty) +{ + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + mapped = tapi_cfg_wifi_configurator_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + ta_wifi_get_node()->configurator = mapped; + return 0; +} + +static te_errno +node_wifi_enable_get(unsigned int gid, const char *oid, char *value, + const char *empty) +{ + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + sprintf(value, "%d", ta_wifi_get_node()->enable ? 1 : 0); + + return 0; +} + +static te_errno +node_wifi_enable_set(unsigned int gid, const char *oid, + const char *value, const char *empty) +{ + te_errno ret; + bool result; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ret = te_strtol_bool(value, &result); + if (ret != 0) + return ret; + + ta_wifi_get_node()->enable = result; + + return 0; +} + +static te_errno +node_wifi_status_get(unsigned int gid, const char *oid, char *value, + const char *empty) +{ + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + sprintf(value, "%d", ta_wifi_get_node()->status ? 1 : 0); + + return 0; +} + +static te_errno +node_wifi_port_ifname_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + if (port->ifname != NULL) + strcpy(value, port->ifname); + else + strcpy(value, ""); + + return 0; +} + +static te_errno +node_wifi_port_ifname_set(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + free(port->ifname); + port->ifname = TE_STRDUP(value); + + return 0; +} + +static te_errno +node_wifi_port_standard_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_standard_from_value(port->standard)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_port_standard_set(unsigned int gid, const char *oid, + const char *value, const char *empty, const char *port_name) +{ + ta_wifi_port *port; + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + mapped = tapi_cfg_wifi_standard_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + port->standard = mapped; + + return 0; +} + +static te_errno +node_wifi_port_width_get(unsigned int gid, const char *oid, char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + te_errno rc; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + rc = te_snprintf(value, RCF_MAX_VAL, "%s", + tapi_cfg_wifi_width_from_value(port->width)); + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); +} + +static te_errno +node_wifi_port_width_set(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *port_name) +{ + ta_wifi_port *port; + int mapped; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + port = ta_wifi_find_port(port_name); + if (port == NULL) + return TE_RC(TE_TA_UNIX, TE_ENOENT); + + mapped = tapi_cfg_wifi_width_from_str(value); + if (mapped < 0) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + port->width = mapped; + + return 0; +} + +static te_errno +port_add(unsigned int gid, const char *oid, const char *value, + const char *empty, const char *name) +{ + ta_wifi_port *port; + + UNUSED(gid); + UNUSED(oid); + UNUSED(value); + UNUSED(empty); + + ENTRY("%s", name); + + port = ta_wifi_find_port(name); + if (port != NULL) + { + ERROR("WiFi port with such name already exists: '%s'", name); + return TE_RC(TE_TA_UNIX, TE_EEXIST); + } + + port = TE_ALLOC(sizeof(*port)); + + port->instance_name = TE_STRDUP(name); + port->standard = TAPI_CFG_WIFI_STANDARD_G; + SLIST_INIT(&port->ssids); + + SLIST_INSERT_HEAD(&ta_wifi_get_node()->ports, port, links); + + return 0; +} + +static te_errno +port_del(unsigned int gid, const char *oid, const char *empty, const char *name) +{ + ta_wifi_port *port; + + UNUSED(gid); + UNUSED(oid); + UNUSED(empty); + + ENTRY("%s", name); + + port = ta_wifi_find_port(name); + if (port == NULL) + { + ERROR("Instance with such name doesn't exist: '%s'", name); + return TE_RC(TE_TA_UNIX, TE_ENOENT); + } + + SLIST_REMOVE(&ta_wifi_get_node()->ports, port, ta_wifi_port, links); + + ta_wifi_port_free(port); + return 0; +} + +static te_errno +port_list(unsigned int gid, const char *oid, const char *sub_id, char **list) +{ + ta_wifi_port *port; + te_string str = TE_STRING_INIT; + + UNUSED(gid); + UNUSED(oid); + UNUSED(sub_id); + + SLIST_FOREACH(port, &ta_wifi_get_node()->ports, links) + { + te_string_append(&str, "%s%s", + (str.ptr != NULL) ? " " : "", port->instance_name); + } + + *list = str.ptr; + return 0; +} + + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_option_value, "value", + NULL, NULL, + node_wifi_ssid_option_value_get, + node_wifi_ssid_option_value_set, + &node_wifi); + +RCF_PCH_CFG_NODE_COLLECTION(node_wifi_ssid_option, "option", + &node_wifi_ssid_option_value, NULL, + node_wifi_ssid_option_add, + node_wifi_ssid_option_del, + node_wifi_ssid_option_list, NULL); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_passphrase, "passphrase", + NULL, &node_wifi_ssid_option, + node_wifi_ssid_passphrase_get, + node_wifi_ssid_passphrase_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_protocol, "protocol", + NULL, &node_wifi_ssid_passphrase, + node_wifi_ssid_protocol_get, node_wifi_ssid_protocol_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_security, "security", + NULL, &node_wifi_ssid_protocol, + node_wifi_ssid_security_get, node_wifi_ssid_security_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_mode, "mode", + NULL, &node_wifi_ssid_security, + node_wifi_ssid_mode_get, node_wifi_ssid_mode_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_name, "name", + NULL, &node_wifi_ssid_mode, + node_wifi_ssid_name_get, node_wifi_ssid_name_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_ifname, "ifname", + NULL, &node_wifi_ssid_name, + node_wifi_ssid_ifname_get, node_wifi_ssid_ifname_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_ssid_enable, "enable", + NULL, &node_wifi_ssid_ifname, + node_wifi_ssid_enable_get, node_wifi_ssid_enable_set, + &node_wifi); + +RCF_PCH_CFG_NODE_COLLECTION(node_wifi_ssid, "ssid", + &node_wifi_ssid_enable, NULL, + node_wifi_port_ssid_add, + node_wifi_port_ssid_del, + node_wifi_port_ssid_list, NULL); + +RCF_PCH_CFG_NODE_RWC(node_wifi_option_value, "value", + NULL, NULL, + node_wifi_port_option_value_get, + node_wifi_port_option_value_set, + &node_wifi); + +RCF_PCH_CFG_NODE_COLLECTION(node_wifi_port_option, "option", + &node_wifi_option_value, &node_wifi_ssid, + node_wifi_port_option_add, + node_wifi_port_option_del, + node_wifi_port_option_list, NULL); + +#define UINT32_VAL_NODE(__name, __next) \ +static te_errno \ +node_wifi_port_ ##__name## _get(unsigned int gid, const char *oid, \ + char *value, const char *empty, const char *port_name) \ +{ \ + ta_wifi_port *port; \ + te_errno rc; \ + \ + UNUSED(gid); \ + UNUSED(oid); \ + UNUSED(empty); \ + \ + ENTRY("%s", port_name); \ + \ + port = ta_wifi_find_port(port_name); \ + if (port == NULL) \ + return TE_RC(TE_TA_UNIX, TE_ENOENT); \ + \ + rc = te_snprintf(value, RCF_MAX_VAL, "%u", \ + (unsigned int)port->__name); \ + \ + return TE_RC_UPSTREAM(TE_TA_UNIX, rc); \ +} \ + \ +static te_errno \ +node_wifi_port_ ##__name## _set(unsigned int gid, const char *oid, \ + const char *value, const char *empty, const char *port_name) \ +{ \ + ta_wifi_port *port; \ + te_errno rc; \ + uint16_t val; \ + \ + UNUSED(gid); \ + UNUSED(oid); \ + UNUSED(empty); \ + \ + ENTRY("%s", port_name); \ + \ + port = ta_wifi_find_port(port_name); \ + if (port == NULL) \ + return TE_RC(TE_TA_UNIX, TE_ENOENT); \ + \ + rc = te_strtou_size(value, 0, &val, sizeof(val)); \ + if (rc != 0) \ + return TE_RC(TE_TA_UNIX, rc); \ + \ + port->__name = val; \ + \ + return 0; \ +} \ + \ +RCF_PCH_CFG_NODE_RWC(node_wifi_port_ ## __name, #__name, \ + NULL, &node_wifi_port_ ##__next, \ + node_wifi_port_ ##__name## _get, \ + node_wifi_port_ ##__name## _set, \ + &node_wifi); + + +UINT32_VAL_NODE(max_nss, option); +UINT32_VAL_NODE(tx_power, max_nss); +UINT32_VAL_NODE(txop_limit, tx_power); +UINT32_VAL_NODE(contention_window_max, txop_limit); +UINT32_VAL_NODE(contention_window_min, contention_window_max); +UINT32_VAL_NODE(aifs, contention_window_min); +UINT32_VAL_NODE(guard_interval, aifs); +UINT32_VAL_NODE(long_retry_limit, guard_interval); +UINT32_VAL_NODE(short_retry_limit, long_retry_limit); +UINT32_VAL_NODE(rts_threshold, short_retry_limit); +UINT32_VAL_NODE(frag_threshold, rts_threshold); +UINT32_VAL_NODE(max_a_mpdu, frag_threshold); +UINT32_VAL_NODE(max_a_msdu, max_a_mpdu); + +#undef UINT32_VAL_NODE + +RCF_PCH_CFG_NODE_RWC(node_wifi_port_channel, "channel", + NULL, &node_wifi_port_max_a_msdu, + node_wifi_port_channel_get, node_wifi_port_channel_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_port_standard, "standard", + NULL, &node_wifi_port_channel, + node_wifi_port_standard_get, node_wifi_port_standard_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_port_width, "width", + NULL, &node_wifi_port_standard, + node_wifi_port_width_get, node_wifi_port_width_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_port_ifname, "ifname", + NULL, &node_wifi_port_width, + node_wifi_port_ifname_get, node_wifi_port_ifname_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_port_enable, "enable", + NULL, &node_wifi_port_ifname, + node_wifi_port_enable_get, node_wifi_port_enable_set, + &node_wifi); + +RCF_PCH_CFG_NODE_COLLECTION(node_wifi_port, "port", + &node_wifi_port_enable, NULL, + port_add, port_del, port_list, NULL); + +RCF_PCH_CFG_NODE_RO(node_wifi_status, "status", + NULL, &node_wifi_port, + node_wifi_status_get); + +RCF_PCH_CFG_NODE_RWC(node_wifi_enable, "enable", + NULL, &node_wifi_status, + node_wifi_enable_get, node_wifi_enable_set, + &node_wifi); + +RCF_PCH_CFG_NODE_RWC(node_wifi_configurator, "configurator", + NULL, &node_wifi_enable, + node_wifi_configurator_get, node_wifi_configurator_set, + &node_wifi); + +/* Commit WiFi node */ +static te_errno +node_wifi_commit(unsigned int gid, const cfg_oid *p_oid) +{ + te_errno ret; + + ta_unix_conf_wifi_cancel(); + ta_wifi_get_node()->status = 0; + + if (ta_wifi_get_node()->enable) + { + ret = ta_unix_conf_wifi_apply(); + ta_wifi_get_node()->status = (ret == 0); + } + + return 0; +} + +RCF_PCH_CFG_NODE_NA_COMMIT(node_wifi, "wifi", &node_wifi_configurator, NULL, + node_wifi_commit); + +/* Infer the configurator from the system */ +static tapi_cfg_wifi_configurator +infer_configurator(tapi_cfg_wifi_configurator cfg) +{ + switch (cfg) + { + case TAPI_CFG_WIFI_CFG_AUTO: + { + struct stat st; + + if (stat("/sbin/uci", &st) == 0) + { + return TAPI_CFG_WIFI_CFG_UCI; + } + + return TAPI_CFG_WIFI_CFG_HOSTAPD_WPA_SUPPLICANT; + } + default: + { + return cfg; + } + } +} + +/* Apply WiFi configuration */ +static te_errno +ta_unix_conf_wifi_apply(void) +{ + te_errno rc; + + rc = ta_unix_conf_wifi_cancel(); + if (rc != 0) + return rc; + + if (ta_wifi_get_node()->enable) + { + switch (infer_configurator(ta_wifi_get_node()->configurator)) + { + case TAPI_CFG_WIFI_CFG_UCI: + { + rc = ta_wifi_uci_apply(ta_wifi_get_node()); + if (rc != 0) + return rc; + + return 0; + } + case TAPI_CFG_WIFI_CFG_HOSTAPD_WPA_SUPPLICANT: + { + rc = ta_wifi_wh_apply(ta_wifi_get_node()); + if (rc != 0) + return rc; + + return 0; + } + default: + { + /* not implemented */ + return TE_ENOSYS; + } + } + } + + return 0; +} + +/* Cancel WiFi configuration */ +static te_errno +ta_unix_conf_wifi_cancel(void) +{ + switch (infer_configurator(ta_wifi_get_node()->configurator)) + { + case TAPI_CFG_WIFI_CFG_UCI: + return ta_wifi_uci_cancel(ta_wifi_get_node()); + case TAPI_CFG_WIFI_CFG_HOSTAPD_WPA_SUPPLICANT: + return ta_wifi_wh_cancel(ta_wifi_get_node()); + default: + /* not implemented */ + return TE_ENOSYS; + } +} + +/* See the description in ta_wifi.h */ +te_errno +ta_unix_conf_wifi_init(void) +{ + te_errno rc; + + rc = rcf_pch_rsrc_info("/agent/wifi", + rcf_pch_rsrc_grab_dummy, + rcf_pch_rsrc_release_dummy); + if (rc != 0) + return rc; + + rc = rcf_pch_add_node("/agent", &node_wifi); + if (rc != 0) + return rc; + + return 0; +} diff --git a/lib/ta_wifi/ta_wifi.h b/lib/ta_wifi/ta_wifi.h new file mode 100644 index 000000000..61399506b --- /dev/null +++ b/lib/ta_wifi/ta_wifi.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * @defgroup ta_wifi_agent WiFi control library agent side (ta_wifi) + * @ingroup ta_wifi + * @{ + * + * Agent-side library to use to control WiFi on hosts. + */ + +#ifndef __TA_WIFI_H__ +#define __TA_WIFI_H__ + +#include "te_errno.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize WiFi configuration. + * + * @return Status code. + */ +extern te_errno ta_unix_conf_wifi_init(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !__TA_WIFI_H__ */ + +/**@} */ diff --git a/lib/ta_wifi/ta_wifi_internal.c b/lib/ta_wifi/ta_wifi_internal.c new file mode 100644 index 000000000..f6446ceed --- /dev/null +++ b/lib/ta_wifi/ta_wifi_internal.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * Internal WiFi agent library functions and helpers + */ + +#define TE_LGR_USER "TA WiFi" + +#include "te_config.h" + +#include "te_alloc.h" +#include "te_str.h" +#include "te_string.h" +#include "logger_api.h" +#include "te_queue.h" +#include "tq_string.h" +#include "te_enum.h" +#include "rcf_pch.h" +#include "ta_wifi.h" +#include "ta_wifi_internal.h" + +/* Main WiFi agent's node */ +static ta_wifi wifi_node = { + .ports = SLIST_HEAD_INITIALIZER(ports), + .configurator = TAPI_CFG_WIFI_CFG_AUTO, +}; + +/* See the description in ta_wifi_internal.h */ +void +ta_wifi_option_free(ta_wifi_option *option) +{ + if (option != NULL) + { + free(option->name); + free(option->value); + } + free(option); +} + +/* See the description in ta_wifi_internal.h */ +void +ta_wifi_ssid_free(ta_wifi_ssid *ssid) +{ + if (ssid != NULL) + { + free(ssid->instance_name); + free(ssid->name); + free(ssid->ifname); + free(ssid->passphrase); + } + free(ssid); +} + +/* See the description in ta_wifi_internal.h */ +void +ta_wifi_port_free(ta_wifi_port *port) +{ + if (port != NULL) + { + ta_wifi_ssid *ssid; + ta_wifi_ssid *ssid_tmp; + ta_wifi_option *option; + ta_wifi_option *option_tmp; + + SLIST_FOREACH_SAFE(ssid, &port->ssids, links, ssid_tmp) + { + SLIST_REMOVE(&port->ssids, ssid, ta_wifi_ssid, links); + ta_wifi_ssid_free(ssid); + } + + SLIST_FOREACH_SAFE(option, &port->options, links, option_tmp) + { + SLIST_REMOVE(&port->options, option, ta_wifi_option, links); + ta_wifi_option_free(option); + } + + free(port->instance_name); + free(port->ifname); + } + + free(port); +} + +/* See the description in ta_wifi_internal.h */ +ta_wifi_option * +ta_wifi_ssid_find_option(const ta_wifi_ssid *ssid, const char *name) +{ + ta_wifi_option *option; + + if (ssid == NULL) + return NULL; + + assert(name != NULL); + + SLIST_FOREACH(option, &ssid->options, links) + { + assert(option->name != NULL); + + if (strcmp(name, option->name) == 0) + return option; + } + + return NULL; +} + +/* See the description in ta_wifi_internal.h */ +ta_wifi_option * +ta_wifi_port_find_option(const ta_wifi_port *port, const char *name) +{ + ta_wifi_option *option; + + if (port == NULL) + return NULL; + + assert(name != NULL); + + SLIST_FOREACH(option, &port->options, links) + { + assert(option->name != NULL); + + if (strcmp(name, option->name) == 0) + return option; + } + + return NULL; +} + +/* See the description in ta_wifi_internal.h */ +ta_wifi_ssid * +ta_wifi_port_find_ssid(const ta_wifi_port *port, const char *name) +{ + ta_wifi_ssid *ssid; + + if (port == NULL) + return NULL; + + assert(name != NULL); + + SLIST_FOREACH(ssid, &port->ssids, links) + { + assert(ssid->instance_name != NULL); + + if (strcmp(name, ssid->instance_name) == 0) + return ssid; + } + + return NULL; +} + +/* See the description in ta_wifi_internal.h */ +ta_wifi * +ta_wifi_get_node(void) +{ + return &wifi_node; +} + +/* See the description in ta_wifi_internal.h */ +ta_wifi_port * +ta_wifi_find_port(const char *name) +{ + ta_wifi_port *port; + + assert(name != NULL); + + SLIST_FOREACH(port, &wifi_node.ports, links) + { + assert(port->instance_name != NULL); + + if (strcmp(name, port->instance_name) == 0) + return port; + } + + return NULL; +} diff --git a/lib/ta_wifi/ta_wifi_internal.h b/lib/ta_wifi/ta_wifi_internal.h new file mode 100644 index 000000000..3458dee91 --- /dev/null +++ b/lib/ta_wifi/ta_wifi_internal.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * Internal WiFi agent library functions and helpers. + */ + +#ifndef __TA_WIFI_INTERNAL_H__ +#define __TA_WIFI_INTERNAL_H__ + +#include "te_config.h" +#include "te_defs.h" +#include "config.h" +#include "rcf_common.h" + +#include "tapi_cfg_wifi.h" + +#if HAVE_SYS_QUEUE_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** List of SSIDs */ +typedef SLIST_HEAD(wifi_ssids, ta_wifi_ssid) wifi_ssids; + +/** Extra option */ +typedef struct ta_wifi_option { + SLIST_ENTRY(ta_wifi_option) links; /**< Single-linked list connector */ + char *name; /**< Name of the option */ + char *value; /**< Value of the option */ +} ta_wifi_option; + +/** WiFi SSID */ +typedef struct ta_wifi_ssid { + SLIST_ENTRY(ta_wifi_ssid) links; /**< Single-linked list connector */ + bool enable; /**< Enable state */ + char *instance_name; /**< Instance name of the SSID */ + char *name; /**< Name of the SSID */ + char *ifname; /**< Interface name */ + tapi_cfg_wifi_mode mode; /**< WiFi mode */ + tapi_cfg_wifi_security security; /**< WiFi security */ + tapi_cfg_wifi_protocol protocol; /**< WiFi protocol */ + char *passphrase; /**< Passphrase */ + SLIST_HEAD(, ta_wifi_option) options; /**< Embedded options */ +} ta_wifi_ssid; + +/** WiFi port */ +typedef struct ta_wifi_port { + SLIST_ENTRY(ta_wifi_port) links; /**< Single-linked list connector */ + bool enable; /**< Enable state */ + char *instance_name; /**< Internal port name */ + char *ifname; /**< Device name */ + tapi_cfg_wifi_standard standard; /**< WiFi standard */ + uint8_t channel; /**< Channel of the port */ + tapi_cfg_wifi_width width; /**< Bandwidth */ + + uint32_t max_a_msdu; /**< Max A-MSDU */ + uint32_t max_a_mpdu; /**< Max A-MPDU */ + uint32_t frag_threshold; /**< Fragment threshold */ + uint32_t rts_threshold; /**< RTS threshold */ + uint32_t short_retry_limit; /**< Short retry limit */ + uint32_t long_retry_limit; /**< Long retry limit */ + uint32_t guard_interval; /**< Guard interval */ + uint32_t aifs; /**< Arbitration Inter-Frame Space */ + uint32_t contention_window_min; /**< Contention window (min) */ + uint32_t contention_window_max; /**< Contention window (max) */ + uint32_t txop_limit; /**< TXOP limit */ + uint32_t tx_power; /**< TX Power */ + uint32_t max_nss; /**< Max number of spatial streams */ + + wifi_ssids ssids; /**< SSID list */ + SLIST_HEAD(, ta_wifi_option) options; /**< Embedded port options */ +} ta_wifi_port; + +/** Main wifi object */ +typedef struct ta_wifi { + SLIST_HEAD(, ta_wifi_port) ports; /**< Port list */ + bool enable; /**< Enable configurator or not */ + bool status; /**< Current status */ + tapi_cfg_wifi_configurator configurator; /**< Configurator selection */ +} ta_wifi; + +/** + * Free up the WiFi option + * + * @param option The option to free up + */ +extern void ta_wifi_option_free(ta_wifi_option *option); + +/** + * Free up the SSID + * + * @param ssid The ssid to free up + */ +extern void ta_wifi_ssid_free(ta_wifi_ssid *ssid); + +/** + * Free up the port + * + * @param port The port to free up + */ +extern void ta_wifi_port_free(ta_wifi_port *port); + +/** + * Find the option among SSID options + * + * @param[in] ssid The SSID to find option in + * @param[in] name The name of the option + * + * @return The found option pointer in case of success, @c NULL in case of + * failure + */ +extern ta_wifi_option *ta_wifi_ssid_find_option(const ta_wifi_ssid *ssid, + const char *name); + +/** + * Find the option among port options + * + * @param[in] port The port to find option in + * @param[in] name The name of the option + * + * @return The found option pointer in case of success, @c NULL in case of + * failure + */ +extern ta_wifi_option *ta_wifi_port_find_option(const ta_wifi_port *port, + const char *name); + +/** + * Find the SSID among port SSIDs + * + * @param[in] port The port + * @param[in] ssid_name The SSID name + * + * @return The found SSID pointer in case of success, @C NULL in case of failure + */ +extern ta_wifi_ssid *ta_wifi_port_find_ssid(const ta_wifi_port *port, + const char *ssid_name); + +/** + * Find the port among the global list of ports + * + * @param[in] name The port name + * + * @return Pointer to WiFi port, or @c NULL in case of failure. + */ +extern ta_wifi_port *ta_wifi_find_port(const char *port_name); + +/** + * Get main WiFi node + * + * @return Pointer to WiFi node (always non-null) + */ +extern ta_wifi *ta_wifi_get_node(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !__TA_WIFI_INTERNAL_H__ */ diff --git a/lib/ta_wifi/ta_wifi_uci.c b/lib/ta_wifi/ta_wifi_uci.c new file mode 100644 index 000000000..8d73b09ef --- /dev/null +++ b/lib/ta_wifi/ta_wifi_uci.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library - UCI generic + * + * The library provides basics of the UCI support in WiFi agent library + */ + +#define TE_LGR_USER "TA WiFi UCI" + +#include "te_config.h" + +#include "te_alloc.h" +#include "te_str.h" +#include "te_string.h" + +#include "ta_wifi.h" +#include "ta_wifi_internal.h" +#include "ta_wifi_uci.h" + +#include "logger_api.h" + +#if HAVE_UNISTD_H +#include +#endif + +#include "te_queue.h" + +/* See the description in ta_wifi_uci_internal.h */ +void +ta_wifi_tmpl_node_init(ta_wifi_tmpl_node *node) +{ + memset(node, 0, sizeof(ta_wifi_tmpl_node)); + SLIST_INIT(&node->options); +} + +/* See the description in ta_wifi_uci_internal.h */ +void +ta_wifi_tmpl_node_free(ta_wifi_tmpl_node *node) +{ + ta_wifi_option *opt; + ta_wifi_option *opt_tmp; + + if (node == NULL) + return; + + free(node->raw_type); + free(node->raw_value); + free(node->device); + + SLIST_FOREACH_SAFE(opt, &node->options, links, opt_tmp) + { + SLIST_REMOVE(&node->options, opt, ta_wifi_option, links); + ta_wifi_option_free(opt); + } + + free(node); +} + +/* See the description in ta_wifi_uci_internal.h */ +void +ta_wifi_tmpl_data_init(ta_wifi_tmpl_data *data) +{ + memset(data, 0, sizeof(*data)); +} + +/* See the description in ta_wifi_uci_internal.h */ +void +ta_wifi_tmpl_data_fini(ta_wifi_tmpl_data *data) +{ + ta_wifi_tmpl_node *node; + ta_wifi_tmpl_node *node_tmp; + + if (data == NULL) + return; + + SLIST_FOREACH_SAFE(node, &data->templates, links, node_tmp) + { + SLIST_REMOVE(&data->templates, node, ta_wifi_tmpl_node, links); + ta_wifi_tmpl_node_free(node); + } +} + +/* See the description in ta_wifi_uci_parser.h */ +ta_wifi_tmpl_node * +ta_wifi_tmpl_data_get_tmpl_by_device(ta_wifi_tmpl_data *data, + ta_wifi_tmpl_type type, + const char *device) +{ + struct ta_wifi_tmpl_node *entry; + + if (data == NULL || device == NULL) + return NULL; + + SLIST_FOREACH(entry, &data->templates, links) + { + if (entry->type == type && + entry->device != NULL && + strcmp(entry->device, device) == 0) + { + return entry; + } + } + + return NULL; +} diff --git a/lib/ta_wifi/ta_wifi_uci.h b/lib/ta_wifi/ta_wifi_uci.h new file mode 100644 index 000000000..8eba396ee --- /dev/null +++ b/lib/ta_wifi/ta_wifi_uci.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * The library provides internal WiFi agent library definitions + * related to UCI parser. + */ + +#ifndef __TA_WIFI_UCI_H__ +#define __TA_WIFI_UCI_H__ + +#include "te_config.h" +#include "te_defs.h" +#include "config.h" +#include "rcf_common.h" +#include "ta_wifi_internal.h" + +#if HAVE_SYS_QUEUE_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** Template type, used for easy access to UCI templates */ +typedef enum ta_wifi_tmpl_type { + TA_WIFI_TMPL_TYPE_NONE, /**< Unknown template type */ + TA_WIFI_TMPL_TYPE_DEVICE, /**< The template is for the device */ + TA_WIFI_TMPL_TYPE_IFACE, /**< The template is for the interface */ + TA_WIFI_TMPL_TYPE_OTHER, /**< The template is for other undefined + entity that should be indexed by raw + type and raw value */ +} ta_wifi_tmpl_type; + +/** + * Template node. It might contain the list of options and a few parsed + * options. + */ +typedef struct ta_wifi_tmpl_node { + SLIST_ENTRY(ta_wifi_tmpl_node) links; /**< Single-linked list connector */ + ta_wifi_tmpl_type type; /**< Parsed template type */ + char *raw_type; /**< Raw template type as written in UCI */ + char *raw_value;/**< Raw value as written in UCI */ + char *device; /**< Parsed device. This field is + propagated for + @c TA_WIFI_TEMPLATE_TYPE_DEVICE and + @c TA_WIFI_TEMPLATE_TYPE_IFACE */ + SLIST_HEAD(, ta_wifi_option) options; /**< Options from UCI file */ +} ta_wifi_tmpl_node; + +/** + * Template container + */ +typedef struct ta_wifi_tmpl_data { + SLIST_HEAD(, ta_wifi_tmpl_node) templates; /**< Templates parsed from + UCI file */ +} ta_wifi_tmpl_data; + + +/* Generic functions */ + +/** + * Initialize template node + * + * @param node The pointer to the uninitialized template node + */ +extern void ta_wifi_tmpl_node_init(ta_wifi_tmpl_node *node); + +/** + * Free the template node completely and all resources used by it. + * + * @param node The pointer to the template node + */ +extern void ta_wifi_tmpl_node_free(ta_wifi_tmpl_node *node); + + +/** + * Initialize the template data + * + * @param data The pointer to the data to initialize. + */ +extern void ta_wifi_tmpl_data_init(ta_wifi_tmpl_data *data); + +/** + * Release all resources used by template data + * + * @param data The pointer to the data to free up. + */ +extern void ta_wifi_tmpl_data_fini(ta_wifi_tmpl_data *data); + +/** + * Get node template by device + * + * @param data The template data from which to get data + * @param type The type of the target template + * @param device The device associated with the target template + * + * @return Template with @c type type and @c device device name, + * or @c NULL if not found. + */ +extern ta_wifi_tmpl_node *ta_wifi_tmpl_data_get_tmpl_by_device( + ta_wifi_tmpl_data *data, + ta_wifi_tmpl_type type, + const char *device); + +/* Apply functions */ + +/** + * Apply WiFi configuration to UCI + * + * @param node The WiFi node + * + * @return Status code + */ +extern te_errno ta_wifi_uci_apply(ta_wifi *node); + +/** + * Cancel/remove applied WiFi UCI configuration + * + * @param node The WiFi node + * + * @return Status code + */ +extern te_errno ta_wifi_uci_cancel(ta_wifi *node); + +/* Parsing functions */ + +/** + * Parse UCI configuration and fill data with it. + * + * @param[in] uci_conf_path The path to UCI configuration + * @param[out] data The fetched data. It must be non-initialized. + * + * @return Status code. + */ +extern te_errno ta_wifi_uci_parser_parse(const char *uci_conf_path, + ta_wifi_tmpl_data *data); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !__TA_WIFI_UCI_H__ */ diff --git a/lib/ta_wifi/ta_wifi_uci_apply.c b/lib/ta_wifi/ta_wifi_uci_apply.c new file mode 100644 index 000000000..e72584516 --- /dev/null +++ b/lib/ta_wifi/ta_wifi_uci_apply.c @@ -0,0 +1,429 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library - UCI support + * + * The library provides ability to write UCI configurations + */ + +#define TE_LGR_USER "TA WiFi UCI Apply" + +#include "ta_wifi.h" +#include "ta_wifi_internal.h" +#include "ta_wifi_uci.h" +#include "logger_api.h" +#include "agentlib.h" +#include "te_enum.h" +#include "te_str.h" + +#if HAVE_UNISTD_H +#include +#endif + +#if defined(HAVE_SYS_STAT_H) +#include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +/** UCI wireless configuration */ +#define OPENWRT_CONFIG "/etc/config/wireless" + +/** Size of ht_mode field in bytes */ +#define HT_MODE_SIZE (3 + 3 + 1) + +/** Configuration context */ +typedef struct ta_wifi_cfg_context { + FILE *f; /**< File where to write data */ + int radio_instance; /**< Current radio instance */ + int iface_instance; /**< Current interface instance */ + ta_wifi_port *port; /**< Currently processed port */ + ta_wifi_tmpl_data *tmpl_data; /**< Template data */ +} ta_wifi_cfg_context; + +/** Initialize configuration context */ +#define TA_WIFI_CFG_CONTEXT_INIT() { \ + .f = NULL, \ + .radio_instance = 0, \ + .iface_instance = 0, \ + .port = NULL, \ + .tmpl_data = NULL, \ + } + +extern const te_enum_map wifi_htmode_mapping[]; + +/* AP modes */ +static const char *wifi_mode2str[] = { + [TAPI_CFG_WIFI_MODE_AP] = "ap", + [TAPI_CFG_WIFI_MODE_STA] = "sta", +}; + +static void +infer_ht_mode(tapi_cfg_wifi_standard std, tapi_cfg_wifi_width width, + char *htmode) +{ + switch (std) + { + default: + case TAPI_CFG_WIFI_STANDARD_G: + case TAPI_CFG_WIFI_STANDARD_N: + strcpy(htmode, "HT"); + break; + case TAPI_CFG_WIFI_STANDARD_AC: + strcpy(htmode, "VHT"); + break; + case TAPI_CFG_WIFI_STANDARD_AX: + strcpy(htmode, "HE"); + break; + case TAPI_CFG_WIFI_STANDARD_BE: + strcpy(htmode, "EHT"); + break; + } + + sprintf(htmode + strlen(htmode), "%d", width); +} + +/* HW modes */ +static const char *wifi_standard2hwmode[] = { + [TAPI_CFG_WIFI_STANDARD_G] = "11g", + [TAPI_CFG_WIFI_STANDARD_N] = "11n", + [TAPI_CFG_WIFI_STANDARD_AC] = "11ac", + [TAPI_CFG_WIFI_STANDARD_AX] = "11ax", + [TAPI_CFG_WIFI_STANDARD_BE] = "11be", +}; + +/* Security modes */ +static const char *wifi_security2enc[] = { + [TAPI_CFG_WIFI_SECURITY_OPEN] = "none", + [TAPI_CFG_WIFI_SECURITY_WEP] = "wep", + [TAPI_CFG_WIFI_SECURITY_WPA] = "psk", + [TAPI_CFG_WIFI_SECURITY_WPA2] = "psk2", + [TAPI_CFG_WIFI_SECURITY_WPA3] = "sae-mixed", +}; + +/* fprintf that would propagate error to TE */ +#define CHECKED_FPRINTF(__x...) do { \ + int ___retval = fprintf(__x); \ + if (___retval < 0) \ + { \ + int __err = errno; \ + ERROR("Failed to write configuration: %s", strerror(__err)); \ + ret = TE_OS_RC(TE_TA_UNIX, __err); \ + goto err; \ + } \ + } while (0) + +/* Write ssid to wireless configuration */ +static te_errno +ta_wifi_uci_apply_ssid(ta_wifi_cfg_context *ctx, ta_wifi_ssid *node) +{ + ta_wifi_tmpl_node *tmpl_node; + ta_wifi_option *option; + te_errno ret; + + assert(ctx != NULL); + assert(ctx->f != NULL); + assert(node != NULL); + + ctx->iface_instance++; + + tmpl_node = ta_wifi_tmpl_data_get_tmpl_by_device(ctx->tmpl_data, + TA_WIFI_TMPL_TYPE_IFACE, ctx->port->ifname); + + if (tmpl_node != NULL) + RING("Found template node for SSID '%s'", node->instance_name); + else + WARN("No template node for SSID '%s'", node->instance_name); + + CHECKED_FPRINTF(ctx->f, "config wifi-iface '%s'\n", node->instance_name); + if (!ctx->port->enable || !node->enable) + CHECKED_FPRINTF(ctx->f, "\toption disabled '1'\n"); + if (ctx->port->ifname != NULL) + CHECKED_FPRINTF(ctx->f, "\toption device '%s'\n", ctx->port->ifname); + if (node->ifname != NULL && strlen(node->ifname) != 0) + CHECKED_FPRINTF(ctx->f, "\toption ifname '%s'\n", node->ifname); + CHECKED_FPRINTF(ctx->f, "\toption mode '%s'\n", wifi_mode2str[node->mode]); + CHECKED_FPRINTF(ctx->f, "\toption ssid '%s'\n", node->name); + CHECKED_FPRINTF(ctx->f, "\toption encryption '%s'\n", + wifi_security2enc[node->security]); + if (!te_str_is_null_or_empty(node->passphrase)) + CHECKED_FPRINTF(ctx->f, "\toption key '%s'\n", node->passphrase); + CHECKED_FPRINTF(ctx->f, "\toption wifi_iface_instance '%d'\n", + ctx->iface_instance); + + SLIST_FOREACH(option, &node->options, links) + { + assert(option->name != NULL); + assert(option->value != NULL); + + CHECKED_FPRINTF(ctx->f, "\toption %s '%s'\n", option->name, + option->value); + } + + if (tmpl_node != NULL) + { + /* + * Go over template options and add them as long as they don't conflict + * with our options. + */ + SLIST_FOREACH(option, &tmpl_node->options, links) + { + ta_wifi_option *option2; + bool add = true; + + assert(option->name != NULL); + assert(option->value != NULL); + + SLIST_FOREACH(option2, &node->options, links) + { + assert(option2->name != NULL); + assert(option2->value != NULL); + + if (strcmp(option->name, option2->name) == 0) + { + add = false; + break; + } + } + if (add) + { + CHECKED_FPRINTF(ctx->f, "\toption %s '%s'\n", option->name, + option->value); + RING("Adding option '%s' to SSID configuration", option->name); + } + else + { + RING("Template option '%s' is skipped because it is present " + "among node options", option->name); + } + } + } + + ret = 0; +err: + return ret; +} + +/* Write port to wireless configuration */ +static te_errno +ta_wifi_uci_apply_port(ta_wifi_cfg_context *ctx, ta_wifi_port *node) +{ + te_errno ret; + ta_wifi_ssid *ssid; + ta_wifi_option *option; + ta_wifi_tmpl_node *tmpl_node; + char htmode[HT_MODE_SIZE]; + + assert(ctx != NULL); + assert(ctx->f != NULL); + assert(node != NULL); + + ctx->radio_instance++; + ctx->port = node; + + tmpl_node = ta_wifi_tmpl_data_get_tmpl_by_device(ctx->tmpl_data, + TA_WIFI_TMPL_TYPE_DEVICE, node->ifname); + + if (tmpl_node != NULL) + RING("Found template node for port '%s'", node->ifname); + else + WARN("No template node for port '%s'", node->ifname); + + CHECKED_FPRINTF(ctx->f, "config wifi-device '%s'\n", node->ifname); + if (!node->enable) + CHECKED_FPRINTF(ctx->f, "\toption disabled '1'\n"); + if (node->channel == 0) + CHECKED_FPRINTF(ctx->f, "\toption channel 'auto'\n"); + else + CHECKED_FPRINTF(ctx->f, "\toption channel '%d'\n", node->channel); + + if (node->width != TAPI_CFG_WIFI_WIDTH_NOT_SET) + { + infer_ht_mode(node->standard, node->width, htmode); + CHECKED_FPRINTF(ctx->f, "\toption htmode '%s'\n", htmode); + } + CHECKED_FPRINTF(ctx->f, "\toption hwmode '%s'\n", + wifi_standard2hwmode[node->standard]); + CHECKED_FPRINTF(ctx->f, "\toption wifi_radio_instance '%d'\n", + ctx->radio_instance); + if (node->tx_power != 0) + CHECKED_FPRINTF(ctx->f, "\toption txpower '%u'\n", + (unsigned)node->tx_power); + if (node->frag_threshold != 0) + CHECKED_FPRINTF(ctx->f, "\toption frag '%u'\n", + (unsigned)node->frag_threshold); + if (node->rts_threshold != 0) + CHECKED_FPRINTF(ctx->f, "\toption rts '%u'\n", + (unsigned)node->rts_threshold); + + SLIST_FOREACH(option, &node->options, links) + { + CHECKED_FPRINTF(ctx->f, "\toption %s '%s'\n", option->name, + option->value); + } + + if (tmpl_node != NULL) + { + /* + * Go over template options and add them as long as they don't conflict + * with our options. + */ + SLIST_FOREACH(option, &tmpl_node->options, links) + { + ta_wifi_option *option2; + bool add = true; + + assert(option->name != NULL); + assert(option->value != NULL); + + SLIST_FOREACH(option2, &node->options, links) + { + assert(option2->name != NULL); + assert(option2->value != NULL); + + if (strcmp(option->name, option2->name) == 0) + { + add = false; + break; + } + } + if (add) + { + CHECKED_FPRINTF(ctx->f, "\toption %s '%s'\n", option->name, + option->value); + RING("Adding option '%s' to port configuration", option->name); + } + else + { + RING("Template option '%s' is skipped because it is present " + "among node options", option->name); + } + } + } + + SLIST_FOREACH(ssid, &node->ssids, links) + { + CHECKED_FPRINTF(ctx->f, "\n"); + ret = ta_wifi_uci_apply_ssid(ctx, ssid); + if (ret != 0) + goto err; + } + + ret = 0; + +err: + return ret; +} + +/* Write other sections to wireless configuration */ +static te_errno +ta_wifi_uci_apply_other(ta_wifi_cfg_context *ctx) +{ + te_errno ret = 0; + ta_wifi_tmpl_node *tmpl_node; + ta_wifi_option *option; + + assert(ctx != NULL); + assert(ctx->f != NULL); + + if (ctx->tmpl_data == NULL) + return 0; + + SLIST_FOREACH(tmpl_node, &ctx->tmpl_data->templates, links) + { + if (tmpl_node->type != TA_WIFI_TMPL_TYPE_OTHER) + continue; + + CHECKED_FPRINTF(ctx->f, "config %s '%s'\n", + tmpl_node->raw_type, + tmpl_node->raw_value); + + SLIST_FOREACH(option, &tmpl_node->options, links) + { + CHECKED_FPRINTF(ctx->f, "\toption %s '%s'\n", option->name, + option->value); + } + } + + ret = 0; + +err: + return ret; +} + +/* See the description in ta_wifi_uci.h */ +te_errno +ta_wifi_uci_apply(ta_wifi *node) +{ + te_errno ret; + ta_wifi_port *port; + ta_wifi_cfg_context ctx = TA_WIFI_CFG_CONTEXT_INIT(); + ta_wifi_tmpl_data tmpl_data; + + assert(node != NULL); + + ta_wifi_tmpl_data_init(&tmpl_data); + ret = ta_wifi_uci_parser_parse(OPENWRT_CONFIG, + &tmpl_data); + if (ret != 0) + { + ERROR("Failed to parse UCI configuration"); + goto err; + } + + ctx.tmpl_data = &tmpl_data; + + ctx.f = fopen(OPENWRT_CONFIG, "w"); + if (ctx.f == NULL) + return TE_OS_RC(TE_TA_UNIX, errno); + + /* Apply ports */ + SLIST_FOREACH(port, &node->ports, links) + { + ret = ta_wifi_uci_apply_port(&ctx, port); + if (ret != 0) + goto err; + } + + /* Apply other sections */ + ret = ta_wifi_uci_apply_other(&ctx); + if (ret != 0) + goto err; + + fclose(ctx.f); + ctx.f = NULL; + + /* Restart WiFi */ + if (ta_system("wifi up") != 0) + { + ERROR("Failed to restart WiFi"); + ret = TE_RC(TE_TA_UNIX, TE_ESHCMD); + goto err; + } + else + { + RING("WiFi is restarted successfully"); + } + + ret = 0; + +err: + ta_wifi_tmpl_data_fini(&tmpl_data); + if (ctx.f != NULL) + fclose(ctx.f); + + return ret; +} + +/* See the description in ta_wifi_uci.h */ +te_errno +ta_wifi_uci_cancel(ta_wifi *node) +{ + UNUSED(node); + + ta_system("wifi down"); + + return 0; +} diff --git a/lib/ta_wifi/ta_wifi_uci_parser.c b/lib/ta_wifi/ta_wifi_uci_parser.c new file mode 100644 index 000000000..ce54f9ac8 --- /dev/null +++ b/lib/ta_wifi/ta_wifi_uci_parser.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library - UCI parsing support + * + * The library parses UCI network configuration. + */ + +#define TE_LGR_USER "TA WiFi UCI Parser" + +#include "te_config.h" + +#include "te_alloc.h" +#include "te_str.h" +#include "te_string.h" +#include "te_vector.h" + +#include "ta_wifi.h" +#include "ta_wifi_internal.h" +#include "ta_wifi_uci.h" + +#include "logger_api.h" + +#if HAVE_UNISTD_H +#include +#endif + +#include "te_queue.h" + +#define MAX_LINE (1024) + +/** Parsing context */ +typedef struct ta_wifi_tmpl_parse_context { + ta_wifi_tmpl_node *current_node; /**< Current node (shouldn't be freed) */ +} ta_wifi_tmpl_parse_context; + +/* Initialize the parsing context */ +static void +ta_wifi_tmpl_parse_context_init(ta_wifi_tmpl_parse_context *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); +} + +/* + * Test if the specific option should be ignored + * for the given node type + */ +static bool +should_ignore(ta_wifi_tmpl_type type, const char *opt) +{ + +#define MATCH(__a) (strcmp(opt, __a) == 0) + switch (type) + { + case TA_WIFI_TMPL_TYPE_DEVICE: + { + return MATCH("channel") || + MATCH("htmode") || + MATCH("hwmode") || + MATCH("wifi_radio_instance") || + MATCH("disabled") || + MATCH("txpower") || + MATCH("frag") || + MATCH("rts"); + } + case TA_WIFI_TMPL_TYPE_IFACE: + { + return MATCH("device") || + MATCH("ifname") || + MATCH("mode") || + MATCH("ssid") || + MATCH("encryption") || + MATCH("key") || + MATCH("wifi_iface_instance") || + MATCH("disabled"); + } + default: + { + break; + } + } +#undef MATCH + + return false; +} + +/* + * Process three tokens of the UCI configuration and fill the + * template data + */ +static te_errno +ta_wifi_process_tokens(ta_wifi_tmpl_parse_context *ctx, + ta_wifi_tmpl_data *data, + const char *type, + const char *opt, + const char *value) +{ + if (strcmp(type, "config") == 0) + { + /* Create a new template object */ + ctx->current_node = TE_ALLOC(sizeof(ta_wifi_tmpl_node)); + + ta_wifi_tmpl_node_init(ctx->current_node); + + ctx->current_node->raw_type = TE_STRDUP(opt); + ctx->current_node->raw_value = TE_STRDUP(value); + + SLIST_INSERT_HEAD(&data->templates, ctx->current_node, links); + if (strcmp(opt, "wifi-device") == 0) + { + /* This is the wifi-device - remember device from value */ + ctx->current_node->type = TA_WIFI_TMPL_TYPE_DEVICE; + ctx->current_node->device = TE_STRDUP(value); + RING("Added WiFi device '%s'", value); + } + else if (strcmp(opt, "wifi-iface") == 0) + { + /* This is the interface - we'll remember device later */ + ctx->current_node->type = TA_WIFI_TMPL_TYPE_IFACE; + RING("Added WiFi interface '%s'", value); + } + else + { + /* This is the other entry - remember options standalone */ + ctx->current_node->type = TA_WIFI_TMPL_TYPE_OTHER; + RING("Added other section '%s'", value); + } + } + else if (strcmp(type, "option") == 0) + { + /* Create a new option */ + + if (ctx->current_node == NULL) + { + ERROR("Option in an unknown context: '%s'='%s'", opt, value); + return TE_RC(TE_TA_UNIX, TE_EINVAL); + } + + if (strcmp(opt, "device") == 0 && + ctx->current_node->type == TA_WIFI_TMPL_TYPE_IFACE) + { + /* This is the interface, therefore we need to remember device */ + free(ctx->current_node->device); + ctx->current_node->device = strdup(value); + RING("Template node device updated: '%s'", value); + } + else + { + ta_wifi_option *option; + + /* This is the option of the other type - just remember it */ + if (should_ignore(ctx->current_node->type, opt)) + { + RING("Template option '%s' ignored", opt); + return 0; + } + + option = TE_ALLOC(sizeof(ta_wifi_option)); + option->name = TE_STRDUP(opt); + option->value = TE_STRDUP(value); + + SLIST_INSERT_HEAD(&ctx->current_node->options, option, links); + + RING("Template option '%s'='%s' added", opt, value); + } + } + + return 0; +} + +/* See the description in ta_wifi_uci.h */ +te_errno +ta_wifi_uci_parser_parse(const char *path, ta_wifi_tmpl_data *data) +{ + te_errno ret; + FILE *f; + char line[MAX_LINE]; + ta_wifi_tmpl_parse_context ctx; + + ta_wifi_tmpl_parse_context_init(&ctx); + + f = fopen(path, "r"); + if (f == NULL) + { + int saved_errno = errno; + + ERROR("Failed to open UCI configuration '%s': %s", path, + strerror(errno)); + return TE_OS_RC(TE_TA_UNIX, saved_errno); + } + + while (fgets(line, sizeof(line), f)) + { + int n = 0; + char *tok[3] = {0}; + te_vec linevec = TE_VEC_INIT(char *); + const char * const *p; + + line[sizeof(line) - 1] = '\0'; + + if (te_vec_tokenize_string(line, &linevec, " \t\r\n'") != 0) + continue; + + if (te_vec_size(&linevec) == 3) + { + TE_VEC_FOREACH(&linevec, p) + { + tok[n] = (char *)*p; + n++; + if (n == 3) + break; + } + + ret = ta_wifi_process_tokens(&ctx, data, tok[0], tok[1], tok[2]); + if (ret != 0) + goto err; + } + + te_vec_deep_free(&linevec); + } + + ret = 0; +err: + if (f != NULL) + fclose(f); + return ret; +} diff --git a/lib/ta_wifi/ta_wifi_wh.c b/lib/ta_wifi/ta_wifi_wh.c new file mode 100644 index 000000000..60574b567 --- /dev/null +++ b/lib/ta_wifi/ta_wifi_wh.c @@ -0,0 +1,760 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library - WPA supplicant/hostapd support + * + * The library provides ability to write & apply WPA supplicant configuration + */ + +#define TE_LGR_USER "TA WiFi WPAS/HA" + +#include "ta_wifi.h" +#include "ta_wifi_internal.h" +#include "ta_wifi_uci.h" +#include "te_str.h" +#include "logger_api.h" +#include "agentlib.h" + +#if HAVE_UNISTD_H +#include +#endif + +#if defined(HAVE_SYS_STAT_H) +#include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#if HAVE_SIGNAL_H +#include +#endif + +/** Path to wpa_supplicant */ +#define WPA_SUPPLICANT_PATH "/usr/sbin/wpa_supplicant" +/** WPA supplicant configuration format */ +#define WPA_SUPPLICANT_CONF_FMT "/tmp/wpa_supplicant.%s.conf" +/** WPA supplicant pid file format */ +#define WPA_SUPPLICANT_PID_FMT "/var/run/wpa_supplicant.%s.pid" + +/** Path to hostapd */ +#define HOSTAPD_PATH "/usr/sbin/hostapd" +/** hostapd configuration format */ +#define HOSTAPD_CONF_FMT "/tmp/hostapd.%s.conf" +/** hostapd pid file format */ +#define HOSTAPD_PID_FMT "/var/run/hostapd.%s.pid" + +#define FILE_PATH_SIZE (200) +#define CMD_SIZE (500) + +/** WPA supplicant/hostapd configuration generation context */ +typedef struct ta_wifi_wh_context { + FILE *f; /**< File where to put the context data */ + int ssid_instance; /**< Index of the SSID within current port */ + ta_wifi_port *port; /**< WiFi port */ +} ta_wifi_wh_context; + +/* WPA supplicant/hostapd context initializer */ +#define TA_WH_CONTEXT_INIT() { \ + NULL, NULL \ +} + +/* fprintf that would propagate error to TE */ +#define CHECKED_FPRINTF(__x...) do { \ + int ___retval = fprintf(__x); \ + if (___retval < 0) \ + { \ + int __err = errno; \ + ERROR("Failed to write configuration: %s", strerror(__err)); \ + ret = TE_OS_RC(TE_TA_UNIX, __err); \ + goto err; \ + } \ + } while (0) + +/* + * Convert channel to an actual frequency + * + * @param standard Target WiFi standard + * @param ch Target channel + * + * @return The frequency, or @c -1 in case of unspecified conversion. + */ +static int +wifi_channel_to_freq(tapi_cfg_wifi_standard standard, unsigned ch) +{ + if (ch <= 0) + return -1; + + switch (standard) + { + case TAPI_CFG_WIFI_STANDARD_G: + case TAPI_CFG_WIFI_STANDARD_N: + { + if (ch >= 1 && ch <= 13) + return 2412 + 5 * (ch - 1); + if (ch == 14) + return 2484; + return -1; + } + + case TAPI_CFG_WIFI_STANDARD_AC: + case TAPI_CFG_WIFI_STANDARD_AX: + { + if (ch >= 7 && ch <= 196) + return 5000 + 5 * ch; + return -1; + } + case TAPI_CFG_WIFI_STANDARD_BE: + { + if (ch >= 1 && ch <= 233) + return 5950 + 5 * ch; + return -1; + } + default: + { + return -1; + } + } +} + +/* Write out SSID to wpa supplicant configuration */ +static te_errno +ta_wifi_wh_apply_wpas_ssid(ta_wifi_wh_context *ctx, ta_wifi_ssid *ssid) +{ + te_errno ret; + ta_wifi_port *port; + int freq; + + assert(ctx != NULL); + assert(ctx->port != NULL); + assert(ssid != NULL); + assert(ssid->name != NULL); + + port = ctx->port; + + CHECKED_FPRINTF(ctx->f, "network={\n"); + CHECKED_FPRINTF(ctx->f, "ssid=\"%s\"\n", ssid->name); + + if (ssid->security == TAPI_CFG_WIFI_SECURITY_OPEN) + { + CHECKED_FPRINTF(ctx->f, "key_mgmt=NONE\n"); + } + else if (ssid->security == TAPI_CFG_WIFI_SECURITY_WPA3 || + port->standard == TAPI_CFG_WIFI_STANDARD_BE) + { + CHECKED_FPRINTF(ctx->f, "key_mgmt=SAE\n"); + CHECKED_FPRINTF(ctx->f, "sae_password=\"%s\"\n", ssid->passphrase); + CHECKED_FPRINTF(ctx->f, "ieee80211w=2\n"); + CHECKED_FPRINTF(ctx->f, "proto=RSN\n"); + } + else if (ssid->security == TAPI_CFG_WIFI_SECURITY_WEP) + { + CHECKED_FPRINTF(ctx->f, "key_mgmt=NONE\n"); + CHECKED_FPRINTF(ctx->f, "wep_key0=\"%s\"\n", ssid->passphrase); + CHECKED_FPRINTF(ctx->f, "wep_tx_keyidx=0\n"); + } + else + { + /* PSK variants */ + CHECKED_FPRINTF(ctx->f, "key_mgmt=WPA-PSK\n"); + CHECKED_FPRINTF(ctx->f, "psk=\"%s\"\n", ssid->passphrase); + if (ssid->security == TAPI_CFG_WIFI_SECURITY_WPA) + { + CHECKED_FPRINTF(ctx->f, "proto=WPA\n"); + } + else if (ssid->security == TAPI_CFG_WIFI_SECURITY_WPA2) + { + CHECKED_FPRINTF(ctx->f, "proto=RSN\n"); + } + } + + + switch (ssid->protocol) + { + case TAPI_CFG_WIFI_PROTOCOL_TKIP: + { + if (ssid->security == TAPI_CFG_WIFI_SECURITY_WPA3 || + port->standard == TAPI_CFG_WIFI_STANDARD_BE) + { + ERROR("Invalid specification of protocol: TKIP can't be used " + "with WPA3"); + ret = -1; + goto err; + } + + CHECKED_FPRINTF(ctx->f, "pairwise=TKIP\n"); + CHECKED_FPRINTF(ctx->f, "group=TKIP\n"); + break; + } + case TAPI_CFG_WIFI_PROTOCOL_CCMP: + { + CHECKED_FPRINTF(ctx->f, "pairwise=CCMP\n"); + CHECKED_FPRINTF(ctx->f, "group=CCMP\n"); + break; + } + } + + CHECKED_FPRINTF(ctx->f, "scan_ssid=1\n"); + + freq = wifi_channel_to_freq(port->standard, port->channel); + if (freq != -1) + { + CHECKED_FPRINTF(ctx->f, "freq_list=%d\n", freq); + } + + /* TODO: support HT modes */ + switch (port->standard) + { + case TAPI_CFG_WIFI_STANDARD_G: + { + if (freq == -1) + { + CHECKED_FPRINTF(ctx->f, + "freq_list=2412 2417 2422 2427 2432 2437 2442 " + "2447 2452 2457 2462 2467 2472\n"); + } + CHECKED_FPRINTF(ctx->f, "disable_ht=1\n"); + CHECKED_FPRINTF(ctx->f, "disable_vht=1\n"); + break; + } + case TAPI_CFG_WIFI_STANDARD_N: + { + if (freq == -1) + { + CHECKED_FPRINTF(ctx->f, + "freq_list=2412 2417 2422 2427 2432 " + "2437 2442 2447 2452 2457 2462 2467 2472\n"); + } + CHECKED_FPRINTF(ctx->f, "disable_ht=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_vht=1\n"); + break; + } + case TAPI_CFG_WIFI_STANDARD_AC: + { + if (freq == -1) + { + CHECKED_FPRINTF(ctx->f, "freq_list=5180 5200 5220 5240 5260 " + "5280 5300 5320 5500 5520 5540 5560 5580 5600 5620 5640 " + "5660 5680 5700 5720 5745 5765 5785 5805 5825\n"); + } + CHECKED_FPRINTF(ctx->f, "disable_ht=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_vht=0\n"); + break; + } + case TAPI_CFG_WIFI_STANDARD_AX: + { + if (freq == -1) + { + CHECKED_FPRINTF(ctx->f, "freq_list=5180 5200 5220 5240 5260 " + "5280 5300 5320 5500 5520 5540 5560 5580 5600 5620 5640 " + "5660 5680 5700 5720 5745 5765 5785 5805 5825\n"); + } + CHECKED_FPRINTF(ctx->f, "disable_ht=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_vht=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_he=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_eht=1\n"); + break; + } + case TAPI_CFG_WIFI_STANDARD_BE: + { + if (freq == -1) + { + CHECKED_FPRINTF(ctx->f, "freq_list=5935 5955 5975 5995 " + "6015 6035 6055 6075 6095 6115 6135 6155 6175 6195 " + "6215 6235 6255 6275 6295 6315 6335 6355 6375 6395 " + "6415 6435 6455 6475 6495 6515 6535 6555 6575 6595 " + "6615 6635 6655 6675 6695 6715 6735 6755 6775 6795 " + "6815 6835 6855 6875 6895 6915 6935 6955 6975 6995 " + "7015 7035 7055 7075 7095 7115\n"); + } + CHECKED_FPRINTF(ctx->f, "disable_ht=0\n"); + CHECKED_FPRINTF(ctx->f, "disable_vht=0\n"); + break; + } + } + + CHECKED_FPRINTF(ctx->f, "}\n"); + + ret = 0; + +err: + return ret; +} + +/* Write out SSID to hostapd configuration */ +static te_errno +ta_wifi_wh_apply_ha_ssid(ta_wifi_wh_context *ctx, ta_wifi_ssid *ssid) +{ + te_errno ret; + ta_wifi_port *port; + int freq; + + assert(ctx != NULL); + assert(ctx->port != NULL); + assert(ssid != NULL); + assert(ssid->name != NULL); + + port = ctx->port; + + if (ctx->ssid_instance != 0) + { + CHECKED_FPRINTF(ctx->f, "bss=%s_%d\n", port->ifname, + ctx->ssid_instance); + } + CHECKED_FPRINTF(ctx->f, "ssid=%s\n", ssid->name); + + switch (ssid->security) + { + case TAPI_CFG_WIFI_SECURITY_OPEN: + { + CHECKED_FPRINTF(ctx->f, "auth_algs=1\n"); + break; + } + case TAPI_CFG_WIFI_SECURITY_WEP: + { + CHECKED_FPRINTF(ctx->f, "auth_algs=1\n"); + if (ssid->passphrase && strlen(ssid->passphrase) > 0) + { + CHECKED_FPRINTF(ctx->f, "wep_default_key=0\n"); + CHECKED_FPRINTF(ctx->f, "wep_key0=%s\n", ssid->passphrase); + } + break; + } + case TAPI_CFG_WIFI_SECURITY_WPA: + { + CHECKED_FPRINTF(ctx->f, "wpa=1\n"); + CHECKED_FPRINTF(ctx->f, "wpa_key_mgmt=WPA-PSK\n"); + if (ssid->protocol == TAPI_CFG_WIFI_PROTOCOL_TKIP) + { + CHECKED_FPRINTF(ctx->f, "wpa_pairwise=TKIP\n"); + } + else + { + CHECKED_FPRINTF(ctx->f, "wpa_pairwise=CCMP\n"); + } + if (ssid->passphrase && strlen(ssid->passphrase) > 0) + CHECKED_FPRINTF(ctx->f, "wpa_passphrase=%s\n", + ssid->passphrase); + + break; + } + case TAPI_CFG_WIFI_SECURITY_WPA2: + { + CHECKED_FPRINTF(ctx->f, "wpa=2\n"); + CHECKED_FPRINTF(ctx->f, "wpa_key_mgmt=WPA-PSK\n"); + if (ssid->protocol == TAPI_CFG_WIFI_PROTOCOL_TKIP) + { + CHECKED_FPRINTF(ctx->f, "rsn_pairwise=TKIP\n"); + } + else + { + CHECKED_FPRINTF(ctx->f, "rsn_pairwise=CCMP\n"); + } + if (ssid->passphrase && strlen(ssid->passphrase) > 0) + CHECKED_FPRINTF(ctx->f, "wpa_passphrase=%s\n", + ssid->passphrase); + break; + } + case TAPI_CFG_WIFI_SECURITY_WPA3: + { + CHECKED_FPRINTF(ctx->f, "wpa=2\n"); + CHECKED_FPRINTF(ctx->f, "wpa_key_mgmt=SAE\n"); + CHECKED_FPRINTF(ctx->f, "ieee80211w=2\n"); + CHECKED_FPRINTF(ctx->f, "rsn_pairwise=CCMP\n"); + if (ssid->passphrase && strlen(ssid->passphrase) > 0) + CHECKED_FPRINTF(ctx->f, "sae_password=%s\n", ssid->passphrase); + break; + } + default: + { + break; + } + } + + ret = 0; +err: + return ret; +} + +/* Kill the pid by given pid file */ +static te_errno +try_kill_pid(const char *pid_path) +{ + FILE *f; + int pid; + int ret; + + assert(pid_path != NULL); + + f = fopen(pid_path, "r"); + if (f == NULL) + { + /* No pid file - no issue */ + return 0; + } + + if (fscanf(f, "%d", &pid) != 1) + { + fclose(f); + return TE_RC(TE_TA_UNIX, TE_EIO); + } + + fclose(f); + + ret = kill(pid, SIGTERM); + if (ret != 0) + { + ret = kill(pid, SIGKILL); + if (ret != 0) + { + return TE_OS_RC(TE_TA_UNIX, errno); + } + } + + unlink(pid_path); + + return 0; +} + +/* Apply port's configuration and run necessary wpa_supplicant or hostapd */ +static te_errno +ta_wifi_wh_apply_port(ta_wifi_wh_context *ctx, ta_wifi_port *port) +{ + te_errno ret; + char conf_file[FILE_PATH_SIZE] = { 0 }; + char pid_file[FILE_PATH_SIZE] = { 0 }; + char cmd[CMD_SIZE] = { 0 }; + bool found_wpas = false; + bool found_ha = false; + ta_wifi_ssid *ssid; + + assert(port != NULL); + + if (port->ifname == NULL) + return TE_RC(TE_TA_UNIX, TE_EINVAL); + + SLIST_FOREACH(ssid, &port->ssids, links) + { + assert(ssid != NULL); + + if (!ssid->enable) + continue; + + if (ssid->mode == TAPI_CFG_WIFI_MODE_STA) + { + found_wpas = true; + } + else if (ssid->mode == TAPI_CFG_WIFI_MODE_AP) + { + found_ha = true; + } + } + + if (!found_wpas && !found_ha) + return TE_RC(TE_TA_UNIX, TE_ESKIP); + + if (found_wpas) + { + /* WPA supplicant configuration */ + ret = te_snprintf(conf_file, + sizeof(conf_file), + WPA_SUPPLICANT_CONF_FMT, + port->ifname); + if (ret != 0) + goto err; + + ret = te_snprintf(pid_file, + sizeof(pid_file), + WPA_SUPPLICANT_PID_FMT, + port->ifname); + if (ret != 0) + goto err; + + ctx->f = fopen(conf_file, "w"); + if (ctx->f == NULL) + return TE_OS_RC(TE_TA_UNIX, errno); + + CHECKED_FPRINTF(ctx->f, "ctrl_interface=/var/run/wpa_supplicant\n"); + CHECKED_FPRINTF(ctx->f, "ap_scan=1\n"); + + if (port->standard == TAPI_CFG_WIFI_STANDARD_BE) + { + CHECKED_FPRINTF(ctx->f, "sae_pwe=2\n"); + } + + ctx->port = port; + + SLIST_FOREACH(ssid, &port->ssids, links) + { + assert(ssid != NULL); + + if (!ssid->enable) + continue; + + CHECKED_FPRINTF(ctx->f, "\n"); + if (ssid->mode == TAPI_CFG_WIFI_MODE_STA) + { + ret = ta_wifi_wh_apply_wpas_ssid(ctx, ssid); + if (ret != 0) + goto err; + } + } + + if (ctx->f != NULL) + fclose(ctx->f); + ctx->f = NULL; + + /* Kill existing instance */ + try_kill_pid(pid_file); + + /* Run a new wpa_supplicant instance */ + ret = te_snprintf(cmd, sizeof(cmd), + "%s -i %s -B -c %s -P %s", + WPA_SUPPLICANT_PATH, port->ifname, conf_file, pid_file); + if (ret != 0) + goto err; + + if (ta_system(cmd) != 0) + { + ERROR("Failed to start WPA Supplicant"); + ret = TE_RC(TE_TA_UNIX, TE_ESHCMD); + goto err; + } + } + + if (found_ha) + { + /* hostapd configuration */ + ret = te_snprintf(conf_file, + sizeof(conf_file), + HOSTAPD_CONF_FMT, + port->ifname); + if (ret != 0) + goto err; + + ret = te_snprintf(pid_file, + sizeof(pid_file), + HOSTAPD_PID_FMT, + port->ifname); + if (ret != 0) + goto err; + + ctx->f = fopen(conf_file, "w"); + if (ctx->f == NULL) + return TE_OS_RC(TE_TA_UNIX, errno); + + CHECKED_FPRINTF(ctx->f, "ctrl_interface=/var/run/hostapd\n"); + CHECKED_FPRINTF(ctx->f, "ctrl_interface_group=0\n"); + + ctx->port = port; + + CHECKED_FPRINTF(ctx->f, "interface=%s\n", port->ifname); + /* FIXME: not sure how to handle different drivers */ + CHECKED_FPRINTF(ctx->f, "driver=nl80211\n"); + CHECKED_FPRINTF(ctx->f, "channel=%d\n", port->channel); + + if (port->tx_power > 0) + CHECKED_FPRINTF(ctx->f, "tx_power=%u\n", port->tx_power); + + /* Add standard-specific options */ + switch (port->standard) + { + case TAPI_CFG_WIFI_STANDARD_G: + case TAPI_CFG_WIFI_STANDARD_N: + { + CHECKED_FPRINTF(ctx->f, "hw_mode=g\n"); + if (port->standard == TAPI_CFG_WIFI_STANDARD_N) + { + CHECKED_FPRINTF(ctx->f, "ieee80211n=1\n"); + } + break; + } + + case TAPI_CFG_WIFI_STANDARD_AC: + { + CHECKED_FPRINTF(ctx->f, "hw_mode=a\n"); + CHECKED_FPRINTF(ctx->f, "ieee80211ac=1\n"); + break; + } + case TAPI_CFG_WIFI_STANDARD_AX: + case TAPI_CFG_WIFI_STANDARD_BE: + { + CHECKED_FPRINTF(ctx->f, "hw_mode=a\n"); + CHECKED_FPRINTF(ctx->f, "ieee80211ax=1\n"); + if (port->standard == TAPI_CFG_WIFI_STANDARD_BE) + { + CHECKED_FPRINTF(ctx->f, "ieee80211be=1\n"); + } + break; + } + default: + { + break; + } + } + + /* Convert port width */ + switch (port->width) + { + case TAPI_CFG_WIFI_WIDTH_20: + { + break; + } + case TAPI_CFG_WIFI_WIDTH_40: + { + CHECKED_FPRINTF(ctx->f, "ht_capab=[HT40+]\n"); + break; + } + case TAPI_CFG_WIFI_WIDTH_80: + { + CHECKED_FPRINTF(ctx->f, "vht_oper_chwidth=1\n"); + break; + } + case TAPI_CFG_WIFI_WIDTH_160: + { + CHECKED_FPRINTF(ctx->f, "vht_oper_chwidth=2\n"); + break; + } + case TAPI_CFG_WIFI_WIDTH_320: + { + CHECKED_FPRINTF(ctx->f, "ieee80211be=1\n"); + CHECKED_FPRINTF(ctx->f, "eht_oper_chwidth=3\n"); + break; + } + default: + { + break; + } + } + + /* Transfer SSIDs */ + ctx->ssid_instance = 0; + SLIST_FOREACH(ssid, &port->ssids, links) + { + assert(ssid != NULL); + + if (!ssid->enable) + continue; + + CHECKED_FPRINTF(ctx->f, "\n"); + if (ssid->mode == TAPI_CFG_WIFI_MODE_AP) + { + ret = ta_wifi_wh_apply_ha_ssid(ctx, ssid); + if (ret != 0) + goto err; + ctx->ssid_instance++; + } + } + + if (ctx->f != NULL) + fclose(ctx->f); + ctx->f = NULL; + + /* Kill existing instance */ + try_kill_pid(pid_file); + + /* Run a new hostapd instance */ + ret = te_snprintf(cmd, sizeof(cmd), + "%s -P %s %s", + HOSTAPD_PATH, pid_file, conf_file); + if (ret != 0) + goto err; + + if (ta_system(cmd) != 0) + { + ERROR("Failed to start hostapd"); + ret = TE_RC(TE_TA_UNIX, TE_ESHCMD); + goto err; + } + } + +err: + if (ctx->f != NULL) + fclose(ctx->f); + ctx->f = NULL; + + return ret; +} + +/* See the description in ta_wifi_wh.h */ +te_errno +ta_wifi_wh_apply(ta_wifi *node) +{ + te_errno ret; + ta_wifi_wh_context ctx = TA_WH_CONTEXT_INIT(); + ta_wifi_port *port; + + assert(node != NULL); + + /* Apply ports */ + SLIST_FOREACH(port, &node->ports, links) + { + assert(port != NULL); + + if (!port->enable) + continue; + + ret = ta_wifi_wh_apply_port(&ctx, port); + if (TE_RC_GET_ERROR(ret) == TE_ESKIP) + { + ret = 0; + continue; + } + else if (ret != 0) + { + goto err; + } + } + + ret = 0; + +err: + + return ret; +} + +/* See the description in ta_wifi_wh.h */ +te_errno +ta_wifi_wh_cancel(ta_wifi *node) +{ + int ret; + char pid_file[FILE_PATH_SIZE]; + ta_wifi_port *port; + + /* Kill all supplicants */ + + SLIST_FOREACH(port, &node->ports, links) + { + RING("Cancel WiFi port %s", + port->ifname != NULL ? port->ifname : "none"); + if (port->ifname == NULL) + continue; + + ret = te_snprintf(pid_file, + sizeof(pid_file), + WPA_SUPPLICANT_PID_FMT, + port->ifname); + if (ret != 0) + { + ERROR("Couldn't build pid file path (wpa_supplicant)"); + return ret; + } + + try_kill_pid(pid_file); + + ret = te_snprintf(pid_file, + sizeof(pid_file), + HOSTAPD_PID_FMT, + port->ifname); + if (ret != 0) + { + ERROR("Couldn't build pid file path (hostapd)"); + return ret; + } + + try_kill_pid(pid_file); + } + + return 0; +} diff --git a/lib/ta_wifi/ta_wifi_wh.h b/lib/ta_wifi/ta_wifi_wh.h new file mode 100644 index 000000000..b9adad836 --- /dev/null +++ b/lib/ta_wifi/ta_wifi_wh.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. */ +/** @file + * @brief WiFi agent library + * + * The library provides internal WiFi agent library definitions + * related to WPA Supplicant and hostapd + */ + +#ifndef __TA_WIFI_WH_H__ +#define __TA_WIFI_WH_H__ + +#include "te_config.h" +#include "te_defs.h" +#include "config.h" +#include "rcf_common.h" +#include "ta_wifi_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Apply WiFi configuration to WPA Supplicant and hostpad + * + * @param node The WiFi node + * + * @return Status code + */ +extern te_errno ta_wifi_wh_apply(ta_wifi *node); + +/** + * Stop WPA Supplicant/hostapd + * + * @param node The WiFi node + * + * @return Status code + */ +extern te_errno ta_wifi_wh_cancel(ta_wifi *node); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* !__TA_WIFI_WH_H__ */ diff --git a/lib/tapi_cfg_wifi/meson.build b/lib/tapi_cfg_wifi/meson.build index a81dab208..b724640c2 100644 --- a/lib/tapi_cfg_wifi/meson.build +++ b/lib/tapi_cfg_wifi/meson.build @@ -1,24 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. -#tapi_cfg_wifi_include = include_directories('') - -#lib_tapi_cfg_wifi = static_library('tapi_cfg_wifi', 'tapi_cfg_wifi.c', -# install: install_lib, -# include_directories: [includes, tapi_cfg_wifi_include]) -#dep_lib_tapi_cfg_wifi = declare_dependency(link_with: lib_tapi_cfg_wifi, -# include_directories: [includes, tapi_cfg_wifi_include]) -#dep_lib_static_ta_cfg_wifi = dep_lib_tapi_cfg_wifi - -#headers += files( -# 'tapi_cfg_wifi.h' -#) - -#sources += files( -# 'tapi_cfg_wifi.c' -#) - - headers += files( 'tapi_cfg_wifi.h', ) diff --git a/lib/tapi_cfg_wifi/tapi_cfg_wifi.c b/lib/tapi_cfg_wifi/tapi_cfg_wifi.c index 5227edf6e..f9e9ebe9d 100644 --- a/lib/tapi_cfg_wifi/tapi_cfg_wifi.c +++ b/lib/tapi_cfg_wifi/tapi_cfg_wifi.c @@ -26,6 +26,7 @@ static const te_enum_map wifi_standard_mapping[] = { { .name = "n", .value = TAPI_CFG_WIFI_STANDARD_N }, { .name = "ac", .value = TAPI_CFG_WIFI_STANDARD_AC }, { .name = "ax", .value = TAPI_CFG_WIFI_STANDARD_AX }, + { .name = "be", .value = TAPI_CFG_WIFI_STANDARD_BE }, TE_ENUM_MAP_END }; diff --git a/meson_options.txt b/meson_options.txt index ed6143dbf..4f2d33b29 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -78,7 +78,7 @@ option('agent-unix-platform', type: 'string', value: '', option('agent-unix-conf', type: 'array', value: [], choices: ['8021x', 'aggr', 'bpf', 'disable-networkmanager-check', 'iptables', 'ntpd', 'ovs', 'pam', 'pci', 'serial', - 'serial-parser', 'sfptpd', 'tc', 'upnp_cp', 'vcm'], + 'serial-parser', 'sfptpd', 'tc', 'upnp_cp', 'vcm', 'wifi'], description: 'Unix Test Agent configuration support') option('agent-unix-daemons', type: 'array', value: [], choices: ['dhcp', 'dns', 'echo', 'ftp', 'isc-dhcp-server', 'l2tp', 'named', 'nginx', 'openvpn', From fba6d9724f7fb674dd6288cd4bc5ae2c4d110493 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 19 Sep 2025 09:48:33 +0100 Subject: [PATCH 5/5] doc: document lib/ta_wifi and lib/tapi_cfg_wifi Signed-off-by: Maxim Menshikov Acked-by: Elena Vengerova --- doc/sphinx/pages/group_ta_wifi.rst | 15 +++++++++++++++ lib/mainpage.dox | 1 + 2 files changed, 16 insertions(+) create mode 100644 doc/sphinx/pages/group_ta_wifi.rst diff --git a/doc/sphinx/pages/group_ta_wifi.rst b/doc/sphinx/pages/group_ta_wifi.rst new file mode 100644 index 000000000..bb54dba3e --- /dev/null +++ b/doc/sphinx/pages/group_ta_wifi.rst @@ -0,0 +1,15 @@ +.. + SPDX-License-Identifier: Apache-2.0 + Copyright (C) 2025 Interpretica, Unipessoal Lda. All rights reserved. + +.. index:: pair: group; WiFi control agent library +.. _doxid-group__ta__wifi: + +WiFi control agent library +========================== + +.. toctree:: + :hidden: + + /generated/group_tapi_cfg_wifi.rst + /generated/group_ta_wifi_agent.rst diff --git a/lib/mainpage.dox b/lib/mainpage.dox index 778aa58cc..8fcfab3dd 100644 --- a/lib/mainpage.dox +++ b/lib/mainpage.dox @@ -37,4 +37,5 @@ Questions regarding this document may be forwarded to support@oktetlabs.ru - @ref tapi_tcp_states - @ref tapi_upnp - @ref tapi_wifi +- @ref ta_wifi */