From b8ff8d693eab64441afad207b863589083fa0a71 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Tue, 7 Jul 2020 16:46:36 +0300 Subject: [PATCH 01/27] prplmesh-hostapd: info: added README.md file A file explaining the content of this directory https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/README.md | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 common/beerocks/hostapd/README.md diff --git a/common/beerocks/hostapd/README.md b/common/beerocks/hostapd/README.md new file mode 100644 index 0000000000..6b38e522bf --- /dev/null +++ b/common/beerocks/hostapd/README.md @@ -0,0 +1,90 @@ + +# Directory Content +This diectory contains prplMesh utilities to work with hostapd +It has few features as listed below + +## Configuration +Manipulates hostapd configuration. +* load +* save +* add +* remove +* edit +* disable +* find + +## Process (yet to implement) +Manages hostapd process +* start +* stop +* restart + +## Commands (yet to implement) +Manages sending and receiving commands to and from ostapd + +## Events (yet to implement) +Manages events originated by hostapd itself + +# Code + +## namespace +The entire code is under prplmesh::hostapd +* Configuration: there is a class named Configuration +* Process: prplmesh::hostapd::process (yet to implement) +* Commands: prplmesh::hostapd::commands (yet to implement) +* Events: prplmesh::hostapd::event (yet to implement) + +## Limitations +We are updating hostapd's configuration files with the info that we get from +the controller. Once we changed the configuration we send UPDATE command to hostapd +making it re-read the configuration and updating the VAPs accordingly. + +With that approach there are the following limitations: + - all the VAPs are expected to be always present in the config, but to be + commented out if not active. + - any configuration change causing the hostapd config files to be re-generated + and the hostapd processes restarted will override the prplMesh configuration. + +## hostapd Configuration Format + +hostapd has a special format that is NOT an ini like format. +below, between /// BEGIN hostapd.conf /// and /// END hostapd.conf /// is the +format of the file. +note: the string "bss=" may be replaced by the +user in the call to load() with another vap-indicator (e.g. "interface=") + +/// BEGIN hostapd.conf /// + +\### "head" part + +\# everything until the first `bss=` in the file +\# is considered "head" +\# after the first `bss=` "vaps" are presented. +\# so we expect no parametrs that does not belong to vaps +\# after the first `bss=` +\# take a look below for more details +\# +\# note that we don't expect a space between the key and the equal sign +\# the code in seeks for `key=` when a key is needed. +\# therefore `key =` (with space) will fail +\# also, everything immidiatly after the equal sign (=) is +\# part of the value. the space before 11 in this line is part of the value: +bassid= 11:22:33:44:55:66 +key=value + +\### "vaps part - we expect at least one vap to be configured ## + +\# vap (bss and vap are interchangable) +bss=wlan0_0 + +\# this key (ssid) belongs to the previous vap (bss value which is wlan0_0) +ssid=test2 + +# another vap +bss=wlan0_1 + +# this key (bssid) belongs to the previous vap wlan0_1 +bssid=00:13:10:95:fe:0b + +///// END hostapd.conf /// + From 0ed7fba47414b865ba0254cad8551917228b6e69 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 8 Jul 2020 11:47:01 +0300 Subject: [PATCH 02/27] prplmesh-hostapd: added configuration class added hostapd Configuration class to be used by applications that wants to edit, add, delete, enable, disable, etc parametrs in hostapd configuration file https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 138 ++++++++++++++++++ .../beerocks/hostapd/include/configuration.h | 93 ++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 common/beerocks/hostapd/configuration.cpp create mode 100644 common/beerocks/hostapd/include/configuration.h diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp new file mode 100644 index 0000000000..adb50bb73e --- /dev/null +++ b/common/beerocks/hostapd/configuration.cpp @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2016-2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include "configuration.h" +#include +#include +#include + +namespace prplmesh { +namespace hostapd { +namespace config { + +Configuration::Configuration(const std::string& file_name) : m_configuration_file(file_name) {} + +Configuration::operator bool() const { return m_ok; } + +bool Configuration::load() +{ + // please take a look at the end of this file for + // the expected format of hostapd configuration file + // loading the file relays on the expected format + // otherwise the load fails + + std::ifstream ifs(m_configuration_file); + std::string line; + + bool parsing_vaps = false; + std::string cur_vap; + + while (getline(ifs, line)) { + + // skip empty lines + if (std::all_of(line.begin(), line.end(), isspace)) { + continue; + } + // check if the string belongs to a vap config part and capture which one. + const std::string bss_eq("bss="); + if (line.compare(0, bss_eq.length(), bss_eq) == 0) { + + // from now on we are in the vaps area, all + // key/value pairs belongs to vaps + parsing_vaps = true; + + // copy the bss (vap) value + cur_vap.assign(line, bss_eq.length(), std::string::npos); + } + + // if not a vap line store it in the header part of the config, + // otherwise add to the currently being parsed vap storage. + if (!parsing_vaps) { + m_hostapd_config_head.push_back(line); + } else { + m_hostapd_config_vaps[cur_vap].push_back(line); + } + } + + m_error_description = strerror(errno); + + // if we've got to parsing vaps and no read errors, assume all is good + m_ok = parsing_vaps && !ifs.bad(); + return m_ok; +} + +std::ostream &operator<<(std::ostream &o, const Configuration &conf) +{ + o << "== configuration details ==\n" + << "= ok: " << std::boolalpha << conf.m_ok << '\n' + << "= erorr text: " << conf.m_error_description << '\n' + << "= file: " << conf.m_configuration_file << '\n' + << "= head: " << '\n'; + + for (const auto &line : conf.m_hostapd_config_head) { + o << line << '\n'; + } + + o << "== vaps (" << conf.m_hostapd_config_vaps.size() << ") ==\n"; + + for (const auto &vap : conf.m_hostapd_config_vaps) { + o << " = vap (" << vap.first << " ) =\n"; + for (const auto &line : vap.second) { + o << line << '\n'; + } + } + + return o; +} + +} // namespace config +} // namespace hostapd +} // namespace prplmesh + +/* +//////////////// hostapd configuration format //////////////// + +hostapd has a special format that does NOT have an ini like format. +We expect the following format of the file: +(between /// BEGIN hostapd.conf /// and /// END hostapd.conf ///) + +/// BEGIN hostapd.conf /// + +## "head" part ## + +# everything until the first `bss=` in the file +# is considered "head" +# after the first `bss=` "vaps" are presented. +# so we expect no parametrs that does not belong to vaps +# after the first `bss=` +# take a look below for more details + +# note that we don't expect a space between the key and the equal sign +# the code in this class seeks for `key=` when a key is needed. +# therefore `key =` (with space) will fail +# also, everything immidiatly after the equal sign (=) is +# part of the value. the space before 11 in this is part of the value: +# bassid= 11:22:33:44:55:66 +key=value + +## "vaps part - we expect at least one vap to be configured ## + +# vap (bss and vap are interchangable) +bss=wlan0_0 + +# this key (ssid) belongs to the previous vap (bss value which is wlan0_0) +ssid=test2 + +# another vap +bss=wlan0_1 + +# this key (bssid) belongs to the previous vap wlan0_1 +bssid=00:13:10:95:fe:0b + +///// END hostapd.conf /// +*/ diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h new file mode 100644 index 0000000000..cb01fbedbc --- /dev/null +++ b/common/beerocks/hostapd/include/configuration.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2016-2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include +#include +#include + +namespace prplmesh { +namespace hostapd { +namespace config { + +class Configuration { + +public: + /** + * @brief construct a Configurationn object + * @param file_name the name of the configuration file. + */ + Configuration(const std::string& file_name); + + ~Configuration() = default; + Configuration(const Configuration &) = default; + Configuration(Configuration &&) = default; + + /** + * @brief for simple use in if statements + * @return true or false with the following meaning: + * true - configuration is non empty and valid + * false - otherwise + */ + operator bool() const; + + /** + * @brief load the configuration + * @details loads the file this object is constructed with + * @return *this (as bool, see above) + * e.g. + * Configuratio conf("hostapd.conf"); + * if (!conf.load()) { + * // handle error state + * } + * + */ + bool load(); + +private: + std::string m_configuration_file; + bool m_ok = false; + + // each string is a line in the original configuration file + // that belongs to the "head" part. read the explnation at + // the end of the cpp file for more details + std::vector m_hostapd_config_head; + + // a map between a vap (the key) to its key/value pairs. + // each string in the value part of the map is the line in the original + // configuration file with the original key=value + // e.g. the following lines in the configuration file: + // bss=wlan0.1 <------------------------------ the key in the map: "wlan0.1" + // ctrl_interface=/var/run/hostapd <---------- each line is an element in the array of the map's value + // ap_isolate=1 + // ap_max_inactivity=60 + // bss_transition=1 + // interworking=1 + // disassoc_low_ack=1 + // bss=wlan0.2 <------------------------------ the key in the map: "wlan0.2" + // ctrl_interface=/var/run/hostXXd + // ap_isolate=1 + // ap_max_inactivity=60 + // bss_transition=0 + // interworking=3 + // creates two entries in the map: + // { "wlan0.1" : ["ctrl_interface=/var/run/hostapd", "ap_isolate=1", "ap_max_inactivity=60", "bss_transition=1", "interworking=1", "disassoc_low_ack=1"] }, + // { "wlan0.2" : ["ctrl_interface=/var/run/hosXXpd", "ap_isolate=1", "ap_max_inactivity=60", "bss_transition=0", "interworking=3"] } + std::map> m_hostapd_config_vaps; + + std::string m_error_description = "no error"; + + // for logs + friend std::ostream &operator<<(std::ostream &, const Configuration &); +}; + +// for logs +std::ostream &operator<<(std::ostream &, const Configuration &); + +} // namespace config +} // namespace hostapd +} // namespace prplmesh From d93648985e4a3d608bce0db4bcec026909a26c49 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 8 Jul 2020 11:48:28 +0300 Subject: [PATCH 03/27] prplmesh-hostapd: added a unit test file unit tests to test hostapd configuration functionality https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/CMakeLists.txt | 85 ++++ .../hostapd/unit_tests/configuration_test.cpp | 455 ++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 common/beerocks/hostapd/CMakeLists.txt create mode 100644 common/beerocks/hostapd/unit_tests/configuration_test.cpp diff --git a/common/beerocks/hostapd/CMakeLists.txt b/common/beerocks/hostapd/CMakeLists.txt new file mode 100644 index 0000000000..8287220dec --- /dev/null +++ b/common/beerocks/hostapd/CMakeLists.txt @@ -0,0 +1,85 @@ +project(prplmesh_hostapd VERSION ${prplmesh_VERSION}) + +# Set the base path for the current module +set(MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +# Default defaults +set(PRPLMESH_HOSTAPD_TYPE "DUMMY" CACHE STRING "PRPLMESH_HOSTAPD type") +set_property(CACHE PRPLMESH_HOSTAPD_TYPE PROPERTY STRINGS "DUMMY" "NL80211" "DWPAL") +add_definitions(-DBEEROCKS_TMP_PATH="${TMP_PATH}") + +if (TARGET_PLATFORM STREQUAL "rdkb") + + set(PRPLMESH_HOSTAPD_TYPE "DWPAL") + # Extra libraries + list(APPEND PRPLMESH_HOSTAPD_LIBS rt dl) + +elseif(TARGET_PLATFORM STREQUAL "openwrt") + + if (TARGET_PLATFORM_TYPE STREQUAL "ugw") + set(PRPLMESH_HOSTAPD_TYPE "DWPAL") + else() + set(PRPLMESH_HOSTAPD_TYPE "NL80211") + endif() + + # hostapd directory + file(GLOB PRPLMESH_HOSTAPD_SEARCH_PATHS "${PLATFORM_BUILD_DIR}/hostapd*/hostapd-*") + find_path(PRPLMESH_HOSTAPD_INCLUDE_DIR NAMES "src/common/wpa_ctrl.h" PATHS ${PRPLMESH_HOSTAPD_SEARCH_PATHS} NO_CMAKE_FIND_ROOT_PATH) + set(PRPLMESH_HOSTAPD_DIR "${PRPLMESH_HOSTAPD_INCLUDE_DIR}") + +endif() + +set(PRPLMESH_HOSTAPD_TYPE ${PRPLMESH_HOSTAPD_TYPE} CACHE STRING "Which PRPLMESH_HOSTAPD backend to use") + +########################################################################## +########################################################################## +########################################################################## + +set( + hostapd_sources + configuration.cpp +) + + +# Build the library +add_library(${PROJECT_NAME} ${hostapd_sources}) +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${prplmesh_VERSION} SOVERSION ${prplmesh_VERSION_MAJOR}) +target_link_libraries(${PROJECT_NAME} elpp ${PRPLMESH_HOSTAPD_LIBS}) +target_include_directories(${PROJECT_NAME} + PRIVATE + ${PLATFORM_INCLUDE_DIR} + PUBLIC + $ +) + +install( + TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if (BUILD_TESTS) + string(TOLOWER ${PRPLMESH_HOSTAPD_TYPE} PRPLMESH_HOSTAPD_TYPE_LOWERCASE) + set(TEST_PROJECT_NAME ${PROJECT_NAME}_${PRPLMESH_HOSTAPD_TYPE_LOWERCASE}_unit_tests) + set(unit_tests_sources + ${MODULE_PATH}/unit_tests/configuration_test.cpp + ) + add_executable(${TEST_PROJECT_NAME} + ${unit_tests_sources} + ) + if (COVERAGE) + set_target_properties(${TEST_PROJECT_NAME} PROPERTIES COMPILE_FLAGS "--coverage -fPIC -O0") + set_target_properties(${TEST_PROJECT_NAME} PROPERTIES LINK_FLAGS "--coverage") + endif() + target_include_directories(${TEST_PROJECT_NAME} + PRIVATE + ${PLATFORM_INCLUDE_DIR} + PUBLIC + $ + ) + target_link_libraries(${TEST_PROJECT_NAME} ${PROJECT_NAME} ${PRPLMESH_HOSTAPD_LIBS}) + target_link_libraries(${TEST_PROJECT_NAME} gtest_main) + install(TARGETS ${TEST_PROJECT_NAME} DESTINATION bin/tests) + add_test(NAME ${TEST_PROJECT_NAME} COMMAND $) +endif() diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp new file mode 100644 index 0000000000..653040a60a --- /dev/null +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include "configuration.h" +#include + +#include + +namespace { + +const std::string configuration_path("/tmp/"); +const std::string configuration_file_name("hostapd.conf"); +const std::string configuration_content( + "driver=nl80211\n" + "logger_syslog=127\n" + "logger_syslog_level=2\n" + "logger_stdout=127\n" + "logger_stdout_level=2\n" + "sDisableMasterVap=1\n" + "atf_config_file=/var/run/hostapd-phy0-atf.conf\n" + "country_code=US\n" + "ieee80211d=1\n" + "hw_mode=g\n" + "beacon_int=100\n" + "channel=acs_smart\n" + "ieee80211n=1\n" + "ht_capab=[LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][MAX-AMSDU-7935][DSSS_CCK-40]\n" + "vht_capab=[RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-" + "BEAMFORMER][VHT-TXOP-PS][VHT160][MAX-MPDU-11454][MAX-A-MPDU-LEN-EXP7][BF-ANTENNA-4][SOUNDING-" + "DIMENSION-2]\n" + "ieee80211ax=1\n" + "acs_num_scans=1\n" + "acs_smart_info_file=/var/run/acs_smart_info_wlan0.txt\n" + "acs_history_file=/var/run/acs_history_wlan0.txt\n" + "interface=wlan0\n" + "ctrl_interface=/var/run/hostapd\n" + "ap_isolate=1\n" + "rrm_neighbor_report=1\n" + "bss_transition=1\n" + "interworking=1\n" + "disassoc_low_ack=1\n" + "preamble=1\n" + "wmm_enabled=1\n" + "ignore_broadcast_ssid=1\n" + "uapsd_advertisement_enabled=1\n" + "mbo=1\n" + "vendor_elements=dd050009860100\n" + "vendor_vht=1\n" + "auth_algs=1\n" + "wpa=0\n" + "ssid=dummy_ssid_0\n" + "bridge=br-lan\n" + "bssid=02:9A:96:FB:59:0F\n" + + "bss=wlan0.0\n" + "ctrl_interface=/var/run/hostapd\n" + "ap_isolate=1\n" + "ap_max_inactivity=60\n" + "rrm_neighbor_report=1\n" + "bss_transition=1\n" + "interworking=1\n" + "disassoc_low_ack=1\n" + "preamble=1\n" + "wmm_enabled=1\n" + "mode=ap\n" + "ignore_broadcast_ssid=0\n" + "uapsd_advertisement_enabled=1\n" + "mbo=1\n" + "vendor_elements=dd050009860100\n" + "vendor_vht=1\n" + "auth_algs=1\n" + "eap_server=1\n" + "device_type=6-0050F204-1\n" + "device_name=WLAN-ROUTER\n" + "manufacturer=Intel Corporation\n" + "config_methods=push_button\n" + "wps_independent=1\n" + "wps_cred_processing=1\n" + "os_version=01020300\n" + "manufacturer_url=http://www.intel.com\n" + "model_description=TR069 Gateway\n" + "wps_rf_bands=a\n" + "bridge=br-lan\n" + "bssid=02:9A:96:FB:59:11\n" + "ssid=Multi-AP-24G-1\n" + "wpa=2\n" + "okc=0\n" + "wpa_key_mgmt=WPA-PSK\n" + "wpa_pairwise=CCMP\n" + "ieee80211w=0\n" + "wpa_passphrase=maprocks1\n" + "disable_pmksa_caching=1\n" + "wpa_disable_eapol_key_retries=0\n" + "wps_state=2\n" + "mesh_mode=fAP\n" + "multi_ap_backhaul_ssid=\"Multi-AP-24G-2\"\n" + "multi_ap_backhaul_wpa_passphrase=maprocks2\n" + + "bss=wlan0.1\n" + "mode=non-ap\n" + "ctrl_interface=/var/run/hostapd\n" + "ap_isolate=1\n" + "ap_max_inactivity=60\n" + "rrm_neighbor_report=1\n" + "bss_transition=1\n" + "interworking=1\n" + "disassoc_low_ack=1\n" + "preamble=1\n" + "wmm_enabled=1\n" + "ignore_broadcast_ssid=0\n" + "uapsd_advertisement_enabled=1\n" + "mbo=1\n" + "vendor_elements=dd050009860100\n" + "vendor_vht=1\n" + "auth_algs=1\n" + "bridge=br-lan\n" + "bssid=02:9A:96:FB:59:12\n" + "ssid=Multi-AP-24G-2\n" + "wpa=2\n" + "okc=0\n" + "wpa_key_mgmt=WPA-PSK\n" + "wpa_pairwise=CCMP\n" + "ieee80211w=0\n" + "wpa_passphrase=maprocks2\n" + "disable_pmksa_caching=1\n" + "wpa_disable_eapol_key_retries=0\n" + "mesh_mode=bAP\n" + "sFourAddrMode=1\n" + "max_num_sta=1\n" + + "#bss=wlan0.2\n" + "#ctrl_interface=/var/run/hostapd\n" + "#ap_isolate=1\n" + "#ap_max_inactivity=60\n" + "#rrm_neighbor_report=1\n" + "#mode=ap\n" + "#bss_transition=1\n" + "#interworking=1\n" + "#disassoc_low_ack=1\n" + "#preamble=1\n" + "#wmm_enabled=1\n" + "#ignore_broadcast_ssid=0\n" + "#uapsd_advertisement_enabled=1\n" + "#mbo=1\n" + "#vendor_elements=dd050009860100\n" + "#vendor_vht=1\n" + "#auth_algs=1\n" + "#wpa=0\n" + "#bridge=br-lan\n" + "#bssid=02:9A:96:FB:59:13\n" + "#start_disabled=1\n" + + "bss=wlan0.3\n" + "ctrl_interface=/var/run/hostapd\n" + "ap_isolate=1\n" + "ap_max_inactivity=60\n" + "rrm_neighbor_report=1\n" + "bss_transition=1\n" + "interworking=1\n" + "disassoc_low_ack=1\n" + "preamble=1\n" + "wmm_enabled=1\n" + "ignore_broadcast_ssid=0\n" + "uapsd_advertisement_enabled=1\n" + "mbo=1\n" + "vendor_elements=dd050009860100\n" + "vendor_vht=1\n" + "auth_algs=1\n" + "wpa=0\n" + "bridge=br-lan\n" + "bssid=02:9A:96:FB:59:14\n" + "start_disabled=1\n" + "mode=ap\n"); + +void clean_start() +{ + // save the content of the string (start clean) + std::ofstream tmp(configuration_path + configuration_file_name); + tmp << configuration_content; + tmp.flush(); +} + +TEST(configuration_test, load) +{ + //// start prerequsite //// + + clean_start(); + + //// end prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + EXPECT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + EXPECT_TRUE(conf) << conf; +} + +TEST(configuration_test, store) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + // add a value to vap + conf.set_create_vap_value("wlan0.3", "was_i_stroed", "yes_you_were"); + EXPECT_TRUE(conf) << conf; + + // store + conf.store(); + EXPECT_TRUE(conf) << conf; +} + +TEST(configuration_test, set_string_vap_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + // replace existing value for existing key for existing vap + conf.set_create_vap_value("wlan0.2", "disassoc_low_ack", "734"); + EXPECT_TRUE(conf) << conf; + + // add a key/value to exising vap + conf.set_create_vap_value("wlan0.3", "unit_test_ok", "true"); + EXPECT_TRUE(conf) << conf; + + // remove key/value from existing vap + conf.set_create_vap_value("wlan0.0", "vendor_elements", ""); + EXPECT_TRUE(conf) << conf; + + // set key/value for NON existing vap + conf.set_create_vap_value("no_such", "how_do_you_do", "i_am_doing_fine"); + EXPECT_FALSE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end test //// +} + +TEST(configuration_test, set_int_vap_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + // replace existing value for existing key for existing vap + conf.set_create_vap_value("wlan0.1", "ignore_broadcast_ssid", 42); + EXPECT_TRUE(conf) << conf; + + // add a key/value to exising vap + conf.set_create_vap_value("wlan0.3", "i_am_negative", -24); + EXPECT_TRUE(conf) << conf; + + // try to replace existing value for existing key for existing vap + // with NON-int value. we expect the value to be trancated here + // at the caller site + conf.set_create_vap_value("wlan0.2", "wmm_enabled", 333.444); + EXPECT_TRUE(conf) << conf; + + //// end test //// +} + +TEST(configuration_test, get_vap_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + std::string value; + + // get existing value for existing vap + value = conf.get_vap_value("wlan0.1", "wpa_passphrase"); + EXPECT_EQ(value, "maprocks2") << conf; + + // another check - existing value for existing vap + value = conf.get_vap_value("wlan0.1", "bssid"); + EXPECT_EQ(value, "02:9A:96:FB:59:12"); + + // get NON existing value for existing vap + value = conf.get_vap_value("wlan0.1", "does_not_exist"); + EXPECT_EQ(value, "") << conf; + + // try to get for NON existing vap + value = conf.get_vap_value("no_vap", "key"); + EXPECT_EQ(value, "") << conf; + + //// end test //// +} + +TEST(configuration_test, disable_all_ap) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + // disable by adding a key/value + auto disable_func = [&conf](const std::string vap) { + conf.set_create_vap_value(vap, "start_disabled", 1); + }; + + conf.for_all_ap_vaps(disable_func); + EXPECT_TRUE(conf) << conf; + + auto comment_func = [&conf](const std::string vap) { + conf.comment_vap(vap); + }; + + conf.for_all_ap_vaps(comment_func); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end prerequsite //// +} + +TEST(configuration_test, comment_vap) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + conf.comment_vap("wlan0.1"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end test //// +} + +TEST(configuration_test, uncomment_vap) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + // comment twice! + conf.comment_vap("wlan0.1"); + conf.comment_vap("wlan0.1"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + conf.uncomment_vap("wlan0.1"); + EXPECT_TRUE(conf) << conf; + + conf.uncomment_vap("wlan0.2"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + std::cerr << conf; +} + +} // namespace From 328d2c9df5576e8a897e82a3f5ce7ea657eee7f3 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 8 Jul 2020 11:51:35 +0300 Subject: [PATCH 04/27] prplmesh-hostapd: added cmake for prplmesh hostapd created CMakeLists.txt to build prplmest_hostapd library and its unit tests https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/beerocks/CMakeLists.txt b/common/beerocks/CMakeLists.txt index 0479907511..6347f7a547 100644 --- a/common/beerocks/CMakeLists.txt +++ b/common/beerocks/CMakeLists.txt @@ -1,10 +1,11 @@ add_subdirectory(bcl) -add_subdirectory(bwl) -add_subdirectory(tlvf) add_subdirectory(btl) +add_subdirectory(bwl) +add_subdirectory(hostapd) add_subdirectory(scripts) +add_subdirectory(tlvf) # BWL depends on BCL, so make sure it's built first add_dependencies(bwl bcl) add_dependencies(btlvf bcl) -add_dependencies(btl btlvf) \ No newline at end of file +add_dependencies(btl btlvf) From 4c53b57efd46418cc0ab09fa19fc6f6eec0db3e0 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 9 Jul 2020 12:54:28 +0300 Subject: [PATCH 05/27] prplmesh-hostapd: added set_create_vap_value() introducing: set_create_vap_value functionality and unit test also: few minor general issues: * a variable for the test file * interface to get the last message (not implemented yet) * change the variable name from error to message and fix accordingly in the code https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 54 +++++++++++++++++-- .../beerocks/hostapd/include/configuration.h | 22 +++++++- .../hostapd/unit_tests/configuration_test.cpp | 23 ++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index adb50bb73e..896aedbe4c 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -59,18 +59,66 @@ bool Configuration::load() } } - m_error_description = strerror(errno); + m_last_message = strerror(errno); // if we've got to parsing vaps and no read errors, assume all is good m_ok = parsing_vaps && !ifs.bad(); - return m_ok; + + // return this as bool + return *this; +} + +bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, + const std::string &value) +{ + auto existing_vap = m_hostapd_config_vaps.find(vap); + + if (existing_vap == m_hostapd_config_vaps.end()) { + m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap; + m_ok = false; + return *this; + } + + // array of values for the requested vap + auto &vaps_values = existing_vap->second; + + // search for the key + std::string key_eq(key + "="); + auto it_str = + std::find_if(vaps_values.begin(), vaps_values.end(), [&key_eq](std::string str) -> bool { + return (str.compare(0, key_eq.length(), key_eq) == 0); + }); + + // we first delete the key, and if the requested value is non empty + // we push it to the end of the array + + // delete the key-value if found + if (it_str != vaps_values.end()) { + it_str = vaps_values.erase(it_str); + } else { + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was not found"; + } + + // when the new value is provided add the key back with that new value + if (value.length() != 0) { + vaps_values.push_back(key_eq + value); + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was (re)added"; + } else { + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was deleted"; + } + + m_ok = true; + return *this; } std::ostream &operator<<(std::ostream &o, const Configuration &conf) { o << "== configuration details ==\n" << "= ok: " << std::boolalpha << conf.m_ok << '\n' - << "= erorr text: " << conf.m_error_description << '\n' + << "= last message: " << conf.m_last_message << '\n' << "= file: " << conf.m_configuration_file << '\n' << "= head: " << '\n'; diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index cb01fbedbc..d7005ae39c 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -48,6 +48,24 @@ class Configuration { */ bool load(); + /** + * @brief set key/value for the given vap + * @details set the key/vale for the given vap, either replace or create. + * @return + * true - the vap exists, values were set. + * false - the given vap was not found + */ + bool set_create_vap_value(const std::string &vap, const std::string &key, + const std::string &value); + + /** + * @brief for debug: return the last internal message + * @details each action on this class changes its internal + * message (similar to errno) - for debug usage + * @return s string describing the last message + */ + const std::string &get_last_message() const; + private: std::string m_configuration_file; bool m_ok = false; @@ -62,7 +80,7 @@ class Configuration { // configuration file with the original key=value // e.g. the following lines in the configuration file: // bss=wlan0.1 <------------------------------ the key in the map: "wlan0.1" - // ctrl_interface=/var/run/hostapd <---------- each line is an element in the array of the map's value + // ctrl_interface=/var/run/hostapd <---------- each line is an element in the array of the map's value // ap_isolate=1 // ap_max_inactivity=60 // bss_transition=1 @@ -79,7 +97,7 @@ class Configuration { // { "wlan0.2" : ["ctrl_interface=/var/run/hosXXpd", "ap_isolate=1", "ap_max_inactivity=60", "bss_transition=0", "interworking=3"] } std::map> m_hostapd_config_vaps; - std::string m_error_description = "no error"; + std::string m_last_message = "all good"; // for logs friend std::ostream &operator<<(std::ostream &, const Configuration &); diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 653040a60a..46c774f20a 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -449,6 +449,29 @@ TEST(configuration_test, uncomment_vap) conf.store(); EXPECT_TRUE(conf) << conf; + //// end prerequsite //// + + //// start test //// + + // replace existing value for existing key for existing vap + conf.set_create_vap_value("wlan0.2", "disassoc_low_ack", "734"); + EXPECT_TRUE(conf) << conf; + + // add a key/value to exising vap + conf.set_create_vap_value("wlan0.3", "unit_test_ok", "true"); + EXPECT_TRUE(conf) << conf; + + // remove key/value from existing vap + conf.set_create_vap_value("wlan0.0", "vendor_elements", ""); + EXPECT_TRUE(conf) << conf; + + // set key/value for NON existing vap + conf.set_create_vap_value("no_such", "how_do_you_do", "i_am_doing_fine"); + EXPECT_FALSE(conf) << conf; + + //// end test //// + + // for humans std::cerr << conf; } From b4c53d86bea2696630d7a158c8a3265f844cd5d9 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 9 Jul 2020 13:24:15 +0300 Subject: [PATCH 06/27] prplmesh-hostapd: added set_create_vap_value() overload with int introducing: set_create_vap_value, int overload implemented the function using the string version added functionality and tests https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 13 ++++++-- .../beerocks/hostapd/include/configuration.h | 16 +++++++++ .../hostapd/unit_tests/configuration_test.cpp | 33 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 896aedbe4c..b66116817d 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -74,8 +74,9 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri auto existing_vap = m_hostapd_config_vaps.find(vap); if (existing_vap == m_hostapd_config_vaps.end()) { - m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap; - m_ok = false; + m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap + + " (requested to set: " + key + "/" + value + ")"; + m_ok = false; return *this; } @@ -114,6 +115,14 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri return *this; } +bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, + const int value) +{ + return set_create_vap_value(vap, key, std::to_string(value)); +} + +const std::string &Configuration::get_last_message() const { return m_last_message; } + std::ostream &operator<<(std::ostream &o, const Configuration &conf) { o << "== configuration details ==\n" diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index d7005ae39c..8cb73673c8 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -51,6 +51,10 @@ class Configuration { /** * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. + * @param + * - the vap to set values for (string) + * - the key to set its value (string) + * - the value (string) * @return * true - the vap exists, values were set. * false - the given vap was not found @@ -58,6 +62,18 @@ class Configuration { bool set_create_vap_value(const std::string &vap, const std::string &key, const std::string &value); + /** + * @brief set key/value for the given vap + * @details set the key/vale for the given vap, either replace or create. + * @param + * - the vap to set values for (string) + * - the key to set its value (string) + * - the value (int) + * @return + * true - the vap exists, values were set. + * false - the given vap was not found + */ + bool set_create_vap_value(const std::string &vap, const std::string &key, const int value); /** * @brief for debug: return the last internal message * @details each action on this class changes its internal diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 46c774f20a..25bd4a4488 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -470,6 +470,39 @@ TEST(configuration_test, uncomment_vap) EXPECT_FALSE(conf) << conf; //// end test //// +} + +TEST(configuration_test, set_int_vap_values) +{ + //// start prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_file); + EXPECT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + EXPECT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + // replace existing value for existing key for existing vap + conf.set_create_vap_value("wlan0.1", "ignore_broadcast_ssid", 42); + EXPECT_TRUE(conf) << conf; + + // add a key/value to exising vap + conf.set_create_vap_value("wlan0.3", "i_am_negative", -24); + EXPECT_TRUE(conf) << conf; + + // try to replace existing value for existing key for existing vap + // with NON-int value. we expect the value to be trancated here + // at the caller site + conf.set_create_vap_value("wlan0.2", "wmm_enabled", 333.444); + EXPECT_TRUE(conf) << conf; + + //// end test //// // for humans std::cerr << conf; From bdcd28d1b7662c15bf8dcca6cd6b44aa0d56cde1 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 9 Jul 2020 13:45:17 +0300 Subject: [PATCH 07/27] prplmesh-hostapd: added store() introducing: store stores internal configuration into the same file it was loaded from, effectively changing the configuration. added functionality and tests also: * chnaged prerequisite in tests from EXPECT to ASSERT - as we can't continue without it https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 30 +++++++++++++++++++ .../beerocks/hostapd/include/configuration.h | 14 +++++---- .../hostapd/unit_tests/configuration_test.cpp | 13 +++----- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index b66116817d..6a9319fdd8 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -68,6 +68,36 @@ bool Configuration::load() return *this; } +bool Configuration::store() +{ + std::ofstream out_file(m_configuration_file, std::ofstream::out | std::ofstream::trunc); + + // store the head + for (const auto &line : m_hostapd_config_head) { + out_file << line << "\n"; + } + + // store the vaps + for (auto &vap : m_hostapd_config_vaps) { + + // add empty line for readability + out_file << "\n"; + + for (auto &line : vap.second) { + out_file << line << "\n"; + } + } + + // close the file + out_file.close(); + if (out_file.fail()) { + m_last_message = strerror(errno); + m_ok = false; + } + + return *this; +} + bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, const std::string &value) { diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 8cb73673c8..d652ce0b5e 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -39,15 +39,17 @@ class Configuration { * @brief load the configuration * @details loads the file this object is constructed with * @return *this (as bool, see above) - * e.g. - * Configuratio conf("hostapd.conf"); - * if (!conf.load()) { - * // handle error state - * } - * */ bool load(); + /** + * @brief stores the configuration + * @details stores the internal representation of the configuration + * into the file it was loaded from, effectively changing the configuration + * @return *this (as bool, see above) + */ + bool store(); + /** * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 25bd4a4488..d654471018 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -210,15 +210,14 @@ TEST(configuration_test, store) // // clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file conf.load(); ASSERT_TRUE(conf) << conf; - + //// end prerequsite //// - + // add a value to vap conf.set_create_vap_value("wlan0.3", "was_i_stroed", "yes_you_were"); EXPECT_TRUE(conf) << conf; @@ -449,10 +448,6 @@ TEST(configuration_test, uncomment_vap) conf.store(); EXPECT_TRUE(conf) << conf; - //// end prerequsite //// - - //// start test //// - // replace existing value for existing key for existing vap conf.set_create_vap_value("wlan0.2", "disassoc_low_ack", "734"); EXPECT_TRUE(conf) << conf; @@ -478,11 +473,11 @@ TEST(configuration_test, set_int_vap_values) // construct a configuration prplmesh::hostapd::config::Configuration conf(configuration_file); - EXPECT_FALSE(conf) << conf; + ASSERT_FALSE(conf) << conf; // load the dummy configuration file conf.load(); - EXPECT_TRUE(conf) << conf; + ASSERT_TRUE(conf) << conf; //// end prerequsite //// From 9c4327ea66e31453bb79b36ae38869b19e105783 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 9 Jul 2020 17:52:45 +0300 Subject: [PATCH 08/27] prplmesh-hostapd: added get_vap_value() introducing: get_vap_value added the ability to get a value for a specific vap with a given key. added functionality and tests https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 31 +++++++++++++ .../beerocks/hostapd/include/configuration.h | 14 +++++- .../hostapd/unit_tests/configuration_test.cpp | 46 +++++++++++++++++-- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 6a9319fdd8..dda5addef4 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -151,6 +151,37 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri return set_create_vap_value(vap, key, std::to_string(value)); } +std::string Configuration::get_vap_value(const std::string &vap, const std::string &key) +{ + // search for the requested vap + auto existing_vap = m_hostapd_config_vaps.find(vap); + + if (existing_vap == m_hostapd_config_vaps.end()) { + m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap + + " (requested key: " + key + ")"; + m_ok = false; + return ""; + } + + // search for the key + std::string key_eq(key + "="); + auto it_str = std::find_if(existing_vap->second.begin(), existing_vap->second.end(), + [&key_eq](std::string str) -> bool { + return (str.compare(0, key_eq.length(), key_eq) == 0); + }); + + if (it_str == existing_vap->second.end()) { + m_last_message = std::string(__FUNCTION__) + + " couldn't find requested key for vap: " + vap + "; requested key: " + key; + m_ok = false; + return ""; + } + + m_ok = true; + // return from the just after the '=' sign to the end of the string + return it_str->substr(key_eq.length()); +} + const std::string &Configuration::get_last_message() const { return m_last_message; } std::ostream &operator<<(std::ostream &o, const Configuration &conf) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index d652ce0b5e..0ebb53b72e 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -54,7 +54,7 @@ class Configuration { * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. * @param - * - the vap to set values for (string) + * - the vap to set the value for (string) * - the key to set its value (string) * - the value (string) * @return @@ -68,7 +68,7 @@ class Configuration { * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. * @param - * - the vap to set values for (string) + * - the vap to set the value for (string) * - the key to set its value (string) * - the value (int) * @return @@ -76,6 +76,16 @@ class Configuration { * false - the given vap was not found */ bool set_create_vap_value(const std::string &vap, const std::string &key, const int value); + + /** + * @brief get the value of the given key for the given vap + * @param + * - the vap to get the value for (string) + * - the key to get its value (string) + * @return a string with the value or empty if not found + */ + std::string get_vap_value(const std::string &vap, const std::string &key); + /** * @brief for debug: return the last internal message * @details each action on this class changes its internal diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index d654471018..84b5c974c9 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -207,7 +207,7 @@ TEST(configuration_test, store) //// start prerequsite //// // save the content of the string (start clean) - // // clean_start(); + // clean_start(); // construct a configuration ASSERT_FALSE(conf) << conf; @@ -215,9 +215,9 @@ TEST(configuration_test, store) // load the dummy configuration file conf.load(); ASSERT_TRUE(conf) << conf; - + //// end prerequsite //// - + // add a value to vap conf.set_create_vap_value("wlan0.3", "was_i_stroed", "yes_you_were"); EXPECT_TRUE(conf) << conf; @@ -498,9 +498,45 @@ TEST(configuration_test, set_int_vap_values) EXPECT_TRUE(conf) << conf; //// end test //// +} + +TEST(configuration_test, get_vap_values) +{ + //// start prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_file); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + std::string value; + + // get existing value for existing vap + value = conf.get_vap_value("wlan0.1", "wpa_passphrase"); + EXPECT_EQ(value, "maprocks2") << conf; + + // another check - existing value for existing vap + value = conf.get_vap_value("wlan0.1", "bssid"); + EXPECT_EQ(value, "02:9A:96:FB:59:12"); + + // get NON existing value for existing vap + value = conf.get_vap_value("wlan0.1", "does_not_exist"); + EXPECT_EQ(value, "") << conf; + + // try to get for NON existing vap + value = conf.get_vap_value("no_vap", "key"); + EXPECT_EQ(value, "") << conf; + + //// end test //// // for humans - std::cerr << conf; + std::cerr << "received value: " << value << '\n'; } - } // namespace From 9beae66f454b8cfaf80b30cbff39e0a2bc23622d Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Sun, 12 Jul 2020 13:24:34 +0300 Subject: [PATCH 09/27] prplmesh-hostapd: added disable_all_ap_vaps() added functionality to disable all vaps that are access-points (as opposed to STA for example). the code mimics the existing functionality taken from ap_wlan_hal_dwpal.cpp. added funcvtionlity and unit test note: the example unit-test configuration file did not contain any vap with the mode= line, therefore it was added few of this. also: it was discovered that it is OK to look for non-exiting key within vap's configuration and the code was changed accordingly https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 16 ++++++++++- .../beerocks/hostapd/include/configuration.h | 7 +++++ .../hostapd/unit_tests/configuration_test.cpp | 27 +++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index dda5addef4..7117530fc0 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -159,6 +159,7 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri if (existing_vap == m_hostapd_config_vaps.end()) { m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap + " (requested key: " + key + ")"; + // it is an error to ask for non existing vap m_ok = false; return ""; } @@ -173,7 +174,9 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri if (it_str == existing_vap->second.end()) { m_last_message = std::string(__FUNCTION__) + " couldn't find requested key for vap: " + vap + "; requested key: " + key; - m_ok = false; + + // it ok not to find the requested key + m_ok = true; return ""; } @@ -182,6 +185,17 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri return it_str->substr(key_eq.length()); } +void Configuration::disable_all_ap_vaps() { + for_each( + m_hostapd_config_vaps.begin(), + m_hostapd_config_vaps.end(), + [this](const std::pair> &vap) { + if ( get_vap_value(vap.first, "mode") == "ap" ) { + set_create_vap_value(vap.first, "start_disabled", "1"); + } + }); +} + const std::string &Configuration::get_last_message() const { return m_last_message; } std::ostream &operator<<(std::ostream &o, const Configuration &conf) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 0ebb53b72e..d46cfd65e8 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -86,6 +86,13 @@ class Configuration { */ std::string get_vap_value(const std::string &vap, const std::string &key); + /** + * @brief disable all ap vaps + * @details disable all vaps that thier mode is "ap", + * (leaving STAs vaps untouched for example) + */ + void disable_all_ap_vaps(); + /** * @brief for debug: return the last internal message * @details each action on this class changes its internal diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 84b5c974c9..b7295b5d87 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -535,8 +535,31 @@ TEST(configuration_test, get_vap_values) EXPECT_EQ(value, "") << conf; //// end test //// +} + + +TEST(configuration_test, diable_all_ap) +{ + //// start prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_file); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// - // for humans - std::cerr << "received value: " << value << '\n'; + //// start test //// + + conf.disable_all_ap_vaps(); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end test //// } } // namespace From 955cfed7ef08790d30e1bc665eb3c04076ea00fe Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Sun, 12 Jul 2020 17:25:13 +0300 Subject: [PATCH 10/27] prplmesh-hostapd: added private help functions added private helper functions for functions changed all usages accordingly https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 108 ++++++++++++------ .../beerocks/hostapd/include/configuration.h | 27 ++++- 2 files changed, 97 insertions(+), 38 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 7117530fc0..21f55898c8 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -15,7 +15,7 @@ namespace prplmesh { namespace hostapd { namespace config { -Configuration::Configuration(const std::string& file_name) : m_configuration_file(file_name) {} +Configuration::Configuration(const std::string &file_name) : m_configuration_file(file_name) {} Configuration::operator bool() const { return m_ok; } @@ -26,6 +26,11 @@ bool Configuration::load() // loading the file relays on the expected format // otherwise the load fails + // for cases when load is called more than once, we + // first clear internal data + m_hostapd_config_head.clear(); + m_hostapd_config_vaps.clear(); + std::ifstream ifs(m_configuration_file); std::string line; @@ -95,37 +100,32 @@ bool Configuration::store() m_ok = false; } + m_ok = true; return *this; } bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, const std::string &value) { - auto existing_vap = m_hostapd_config_vaps.find(vap); - - if (existing_vap == m_hostapd_config_vaps.end()) { - m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap + - " (requested to set: " + key + "/" + value + ")"; - m_ok = false; - return *this; + // search for the requested vap + auto find_vap = get_vap(std::string(__FUNCTION__) + " key/value: " + key + '/' + value, vap); + if (!std::get<0>(find_vap)) { + return false; } + auto existing_vap = std::get<1>(find_vap); + bool existing_vap_commented = existing_vap->front()[0] == '#'; - // array of values for the requested vap - auto &vaps_values = existing_vap->second; - - // search for the key std::string key_eq(key + "="); - auto it_str = - std::find_if(vaps_values.begin(), vaps_values.end(), [&key_eq](std::string str) -> bool { - return (str.compare(0, key_eq.length(), key_eq) == 0); - }); + auto it_str = std::find_if( + existing_vap->begin(), existing_vap->end(), + [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); // we first delete the key, and if the requested value is non empty // we push it to the end of the array // delete the key-value if found - if (it_str != vaps_values.end()) { - it_str = vaps_values.erase(it_str); + if (it_str != existing_vap->end()) { + it_str = existing_vap->erase(it_str); } else { m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was not found"; @@ -133,7 +133,11 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri // when the new value is provided add the key back with that new value if (value.length() != 0) { - vaps_values.push_back(key_eq + value); + if (existing_vap_commented) { + existing_vap->push_back('#' + key_eq + value); + } else { + existing_vap->push_back(key_eq + value); + } m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was (re)added"; } else { @@ -154,35 +158,30 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri std::string Configuration::get_vap_value(const std::string &vap, const std::string &key) { // search for the requested vap - auto existing_vap = m_hostapd_config_vaps.find(vap); - - if (existing_vap == m_hostapd_config_vaps.end()) { - m_last_message = std::string(__FUNCTION__) + " couldn't find requested vap: " + vap + - " (requested key: " + key + ")"; - // it is an error to ask for non existing vap - m_ok = false; + auto find_vap = get_vap(std::string(__FUNCTION__), vap); + if (!std::get<0>(find_vap)) { return ""; } + const auto& existing_vap = std::get<1>(find_vap); + // + // from now on this function is ok with all situations + // (e.g. not finding the requested key) + m_ok = true; // search for the key std::string key_eq(key + "="); - auto it_str = std::find_if(existing_vap->second.begin(), existing_vap->second.end(), - [&key_eq](std::string str) -> bool { - return (str.compare(0, key_eq.length(), key_eq) == 0); - }); + auto it_str = std::find_if( + existing_vap->begin(), existing_vap->end(), + [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); - if (it_str == existing_vap->second.end()) { + if (it_str == existing_vap->end()) { m_last_message = std::string(__FUNCTION__) + " couldn't find requested key for vap: " + vap + "; requested key: " + key; - - // it ok not to find the requested key - m_ok = true; return ""; } - m_ok = true; // return from the just after the '=' sign to the end of the string - return it_str->substr(key_eq.length()); + return it_str->substr(it_str->find('=') + 1); } void Configuration::disable_all_ap_vaps() { @@ -198,6 +197,43 @@ void Configuration::disable_all_ap_vaps() { const std::string &Configuration::get_last_message() const { return m_last_message; } +std::tuple *> +Configuration::get_vap(const std::string &calling_function, const std::string &vap) +{ + // search for the requested vap + auto existing_vap = m_hostapd_config_vaps.find(vap); + + if (existing_vap == m_hostapd_config_vaps.end()) { + m_last_message = calling_function + " couldn't find requested vap: " + vap; + m_ok = false; + return std::make_tuple(false, nullptr); + } + + m_ok = true; + return std::make_tuple(true, &existing_vap->second); +} + +bool Configuration::is_key_in_line(const std::string &line, const std::string &key) const +{ + // we need to make sure when searching for example + // for "ssid", to ignore cases like: + // multi_ap_backhaul_ssid="Multi-AP-24G-2" + // ^^^^^ + // bssid=02:9A:96:FB:59:11 + // ^^^^^ + // and we need to take into consideration + // that the key might be or might not be commented. + // so the search algorithm is: + // - find the requested key and + // - make sure it is either on the first position + // or it has a comment sign just before it + auto found_pos = line.rfind(key); + bool ret = found_pos != std::string::npos && (found_pos == 0 || line.at(found_pos - 1) == '#'); + +// std::cerr << "line: " << line << "; key: " << key << "; ret: " << std::boolalpha << ret << '\n'; + return ret; +} + std::ostream &operator<<(std::ostream &o, const Configuration &conf) { o << "== configuration details ==\n" diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index d46cfd65e8..d210f47ffa 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -21,7 +21,7 @@ class Configuration { * @brief construct a Configurationn object * @param file_name the name of the configuration file. */ - Configuration(const std::string& file_name); + Configuration(const std::string &file_name); ~Configuration() = default; Configuration(const Configuration &) = default; @@ -97,10 +97,33 @@ class Configuration { * @brief for debug: return the last internal message * @details each action on this class changes its internal * message (similar to errno) - for debug usage - * @return s string describing the last message + * @return string describing the last message */ const std::string &get_last_message() const; +private: + /** + * @brief helper: get exiting vap + * @details any function that works on a specific vap does + * the same: search the vap, set a variable when found + * and set an error state when not found, this function + * does it instead of copy/paste the same code + * @param the calling function, string (to report error) + * @param the requested vap, string + * @return tuple*> (found/not found, a + * pointer to the vap's array) + */ + std::tuple *> get_vap(const std::string &calling_function, + const std::string &vap); + + /** + * @brief helper: check if the line contains the key + * @param line in the format key=value or commented ###key=value + * @param the requested key, string + * @return true/false if the line contains the key + */ + bool is_key_in_line(const std::string &line, const std::string &key) const; + private: std::string m_configuration_file; bool m_ok = false; From ead60219bf6b92b7af220af8ff5a5aa38253dc8f Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Sun, 12 Jul 2020 17:48:41 +0300 Subject: [PATCH 11/27] prplmesh-hostapd: added comment_vap() comment out (disable) the given vap by adding a comment (#) to it and to all keys related to this vap added functionality and unit test also: clang format chages https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 20 ++++++++++--------- .../beerocks/hostapd/include/configuration.h | 16 +++++++++++---- .../hostapd/unit_tests/configuration_test.cpp | 20 ++++++++++++++++--- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 21f55898c8..8ca1caee8e 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -184,15 +184,17 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri return it_str->substr(it_str->find('=') + 1); } -void Configuration::disable_all_ap_vaps() { - for_each( - m_hostapd_config_vaps.begin(), - m_hostapd_config_vaps.end(), - [this](const std::pair> &vap) { - if ( get_vap_value(vap.first, "mode") == "ap" ) { - set_create_vap_value(vap.first, "start_disabled", "1"); - } - }); +void Configuration::comment_vap(const std::string &vap) +{ + // search for the requested vap + auto find_vap = get_vap(std::string(__FUNCTION__), vap); + if (!std::get<0>(find_vap)) { + return; + } + const auto &existing_vap = std::get<1>(find_vap); + + std::for_each(existing_vap->begin(), existing_vap->end(), + [](std::string &line) { line.insert(0, 1, '#'); }); } const std::string &Configuration::get_last_message() const { return m_last_message; } diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index d210f47ffa..771c0e7708 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -87,11 +87,19 @@ class Configuration { std::string get_vap_value(const std::string &vap, const std::string &key); /** - * @brief disable all ap vaps - * @details disable all vaps that thier mode is "ap", - * (leaving STAs vaps untouched for example) + * @brief disables vap by adding a comment to it + * e.g: + * before: + * bss=wlan0_0 + * ssid=test2 + * bss=wlan0_1 + * after: + * #bss=wlan0_0 + * #ssid=test2 + * bss=wlan0_1 + * */ - void disable_all_ap_vaps(); + void comment_vap(const std::string &vap); /** * @brief for debug: return the last internal message diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index b7295b5d87..4b60826152 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -537,7 +537,6 @@ TEST(configuration_test, get_vap_values) //// end test //// } - TEST(configuration_test, diable_all_ap) { //// start prerequsite //// @@ -551,10 +550,25 @@ TEST(configuration_test, diable_all_ap) ASSERT_TRUE(conf) << conf; //// end prerequsite //// +} + +TEST(configuration_test, disable_vap) +{ + //// start prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_file); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// //// start test //// - - conf.disable_all_ap_vaps(); + + conf.comment_vap("wlan0.1"); EXPECT_TRUE(conf) << conf; conf.store(); From edd5bc21fe15ab9f0501ddf20813fb8a98e1f42d Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 13 Jul 2020 11:56:32 +0300 Subject: [PATCH 12/27] prplmesh-hostapd: added comment ability load, search and store comented lines in the file. this ability is needed for disabling vaps in the "up stream" version of hostapd. the intel version disables vaps by adding the line "start_disabled=1" https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 26 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 8ca1caee8e..9146f3ac46 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -25,18 +25,25 @@ bool Configuration::load() // the expected format of hostapd configuration file // loading the file relays on the expected format // otherwise the load fails + + // for cases when load is called more than once, we + // first clear internal data + m_hostapd_config_head.clear(); + m_hostapd_config_vaps.clear(); // for cases when load is called more than once, we // first clear internal data m_hostapd_config_head.clear(); m_hostapd_config_vaps.clear(); + // strat reading std::ifstream ifs(m_configuration_file); std::string line; bool parsing_vaps = false; std::string cur_vap; + // go over line by line in the file while (getline(ifs, line)) { // skip empty lines @@ -45,14 +52,15 @@ bool Configuration::load() } // check if the string belongs to a vap config part and capture which one. const std::string bss_eq("bss="); - if (line.compare(0, bss_eq.length(), bss_eq) == 0) { + auto end_comment = line.find_first_not_of('#'); + if (line.compare(end_comment, bss_eq.length(), bss_eq) == 0) { // from now on we are in the vaps area, all // key/value pairs belongs to vaps parsing_vaps = true; // copy the bss (vap) value - cur_vap.assign(line, bss_eq.length(), std::string::npos); + cur_vap.assign(line, end_comment + bss_eq.length(), std::string::npos); } // if not a vap line store it in the header part of the config, @@ -168,7 +176,10 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri // (e.g. not finding the requested key) m_ok = true; - // search for the key + // search for the key from the back of the string + // ignore comments this way + // e.g: + // ###bssid=11:22:ff:ee:aa std::string key_eq(key + "="); auto it_str = std::find_if( existing_vap->begin(), existing_vap->end(), @@ -202,8 +213,13 @@ const std::string &Configuration::get_last_message() const { return m_last_messa std::tuple *> Configuration::get_vap(const std::string &calling_function, const std::string &vap) { - // search for the requested vap - auto existing_vap = m_hostapd_config_vaps.find(vap); + // search for the requested vap - ignore comments + // by searching from the back of the saved vap (rfind) + auto existing_vap = + std::find_if(m_hostapd_config_vaps.begin(), m_hostapd_config_vaps.end(), + [&vap](const std::pair> ¤t_vap) { + return current_vap.first.rfind(vap) != std::string::npos; + }); if (existing_vap == m_hostapd_config_vaps.end()) { m_last_message = calling_function + " couldn't find requested vap: " + vap; From 03adfc7645a9145a8f3440df420123e14e4946e9 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 13 Jul 2020 12:02:55 +0300 Subject: [PATCH 13/27] prplmesh-hostapd: added uncomment_vap() uncomment (enable) the given vap by removing all comment signes (###) from it and from all keys related to this vap added functionality and unit test https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 17 ++++++++ .../beerocks/hostapd/include/configuration.h | 15 +++++++ .../hostapd/unit_tests/configuration_test.cpp | 43 ++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 9146f3ac46..bd39d6ffc4 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -208,6 +208,23 @@ void Configuration::comment_vap(const std::string &vap) [](std::string &line) { line.insert(0, 1, '#'); }); } +void Configuration::uncomment_vap(const std::string &vap) +{ + // search for the requested vap + auto find_vap = get_vap(std::string(__FUNCTION__), vap); + if (!std::get<0>(find_vap)) { + return; + } + const auto &existing_vap = std::get<1>(find_vap); + + std::for_each(existing_vap->begin(), existing_vap->end(), [](std::string &line) { + auto end_comment = line.find_first_not_of('#'); + if (std::string::npos != end_comment) { + line.erase(0, end_comment); + }; + }); +} + const std::string &Configuration::get_last_message() const { return m_last_message; } std::tuple *> diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 771c0e7708..1a5489e606 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -101,6 +101,21 @@ class Configuration { */ void comment_vap(const std::string &vap); + /** + * @brief enables vap by removing comments from it + * e.g: + * before: + * #bss=wlan0_0 + * ##ssid=test2 + * bss=wlan0_1 + * after: + * bss=wlan0_0 + * ssid=test2 + * bss=wlan0_1 + * + */ + void uncomment_vap(const std::string &vap); + /** * @brief for debug: return the last internal message * @details each action on this class changes its internal diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 4b60826152..b34ee792b2 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -552,7 +552,7 @@ TEST(configuration_test, diable_all_ap) //// end prerequsite //// } -TEST(configuration_test, disable_vap) +TEST(configuration_test, comment_vap) { //// start prerequsite //// @@ -576,4 +576,45 @@ TEST(configuration_test, disable_vap) //// end test //// } + +TEST(configuration_test, uncomment_vap) +{ + //// start prerequsite //// + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_file); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + // comment twice! + conf.comment_vap("wlan0.1"); + conf.comment_vap("wlan0.1"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + conf.uncomment_vap("wlan0.1"); + EXPECT_TRUE(conf) << conf; + + conf.uncomment_vap("wlan0.2"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end test //// +} + } // namespace From 39b1f33ee1f93c3d10931f875e0aee8b9e414710 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 13 Jul 2020 13:01:17 +0300 Subject: [PATCH 14/27] prplmesh-hostapd: added for_all_ap_vaps() introducing: a function that goes all over all vaps which their mode is "ap" and apply the input function to them. may be used for example to disable all vaps. added functionality and tested by: - adding a parameter "start_disabled=1" to all. - commenting out https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/include/configuration.h | 17 +++++++++++++++++ .../hostapd/unit_tests/configuration_test.cpp | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 1a5489e606..703c972b44 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -116,6 +116,13 @@ class Configuration { */ void uncomment_vap(const std::string &vap); + /** + * @brief apply func to all ap vaps + * @details apply func to all vaps that thier mode is "ap" + * (leaving STAs vaps untouched for example). + */ + template void for_all_ap_vaps(func); + /** * @brief for debug: return the last internal message * @details each action on this class changes its internal @@ -184,6 +191,16 @@ class Configuration { friend std::ostream &operator<<(std::ostream &, const Configuration &); }; +template void Configuration::for_all_ap_vaps(func f) +{ + for_each(m_hostapd_config_vaps.begin(), m_hostapd_config_vaps.end(), + [this, &f](const std::pair> &vap) mutable { + if (get_vap_value(vap.first, "mode") == "ap") { + f(vap.first); + } + }); +} + // for logs std::ostream &operator<<(std::ostream &, const Configuration &); diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index b34ee792b2..81d62b2420 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -549,6 +549,23 @@ TEST(configuration_test, diable_all_ap) conf.load(); ASSERT_TRUE(conf) << conf; + auto disable_func = [&conf](const std::string vap) { + conf.set_create_vap_value(vap, "start_disabled", 1); + }; + + conf.for_all_ap_vaps(disable_func); + EXPECT_TRUE(conf) << conf; + + auto comment_func = [&conf](const std::string vap) { + conf.comment_vap(vap); + }; + + conf.for_all_ap_vaps(comment_func); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + //// end prerequsite //// } From 15ee90f1ef21613ffa7c8f4ffd3f632858f3233e Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 13 Jul 2020 13:55:24 +0300 Subject: [PATCH 15/27] prplmesh-hostapd: added for_all_ap_vaps() with user iterator There is a need to go on both the ap-vaps configured in hostapd and in paralell go over the given configuration (non-hostapd structure) The added functionality gives hostapd configuration class the responsibility of iterating over the vaps _and_ increment the user-iterator. the result is that the user's function is called with the next vap _and_ the next element in its given list. it is the user responsibility however to make sure that its container has at least as the number of vaps. otherwise for_all_ap_vaps() would increment the iterator passed the end of the user's container tested by implementing the non iterator version of for_all_ap_vaps() in term of the iterator version, with a dummy iterator https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 11 +++---- .../beerocks/hostapd/include/configuration.h | 29 +++++++++++++++++-- .../hostapd/unit_tests/configuration_test.cpp | 13 +++++++-- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index bd39d6ffc4..31e3e0e7f8 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -123,6 +123,10 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri auto existing_vap = std::get<1>(find_vap); bool existing_vap_commented = existing_vap->front()[0] == '#'; + // search for the key from the back of the string + // ignore comments this way + // e.g: + // ###bssid=11:22:ff:ee:aa std::string key_eq(key + "="); auto it_str = std::find_if( existing_vap->begin(), existing_vap->end(), @@ -176,10 +180,8 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri // (e.g. not finding the requested key) m_ok = true; - // search for the key from the back of the string - // ignore comments this way - // e.g: - // ###bssid=11:22:ff:ee:aa + const auto &existing_vap = std::get<1>(find_vap); + std::string key_eq(key + "="); auto it_str = std::find_if( existing_vap->begin(), existing_vap->end(), @@ -265,7 +267,6 @@ bool Configuration::is_key_in_line(const std::string &line, const std::string &k auto found_pos = line.rfind(key); bool ret = found_pos != std::string::npos && (found_pos == 0 || line.at(found_pos - 1) == '#'); -// std::cerr << "line: " << line << "; key: " << key << "; ret: " << std::boolalpha << ret << '\n'; return ret; } diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 703c972b44..319c65690b 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -6,6 +6,7 @@ * See LICENSE file for more details. */ +#include #include #include #include @@ -123,6 +124,16 @@ class Configuration { */ template void for_all_ap_vaps(func); + /** + * @brief apply func to all ap vaps, increment the given iterator + * with each call to func: f(current_vap, ++user_itertor) + * func interface: template f(const std::string& vap, iter user_iterator) + * @details apply func to all vaps that thier mode is "ap" while incrementing + * the user iterator. this functionality enables iterating over two containers + * in paralel: the ap-vaps and the user container. + */ + template void for_all_ap_vaps(func, iter current, const iter end); + /** * @brief for debug: return the last internal message * @details each action on this class changes its internal @@ -192,11 +203,25 @@ class Configuration { }; template void Configuration::for_all_ap_vaps(func f) +{ + auto f_with_iter = [&f](const std::string &vap, int iter) { f(vap); }; + + int dummy(0); + for_all_ap_vaps(f_with_iter, dummy, 10); +} + +template +void Configuration::for_all_ap_vaps(func f, iter current_iter, const iter end) { for_each(m_hostapd_config_vaps.begin(), m_hostapd_config_vaps.end(), - [this, &f](const std::pair> &vap) mutable { + [this, &f, ¤t_iter, + &end](const std::pair> &vap) { if (get_vap_value(vap.first, "mode") == "ap") { - f(vap.first); + if (end == current_iter) { + f(vap.first, end); + } else { + f(vap.first, current_iter++); + } } }); } diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 81d62b2420..067ece960b 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -367,13 +367,20 @@ TEST(configuration_test, disable_all_ap) conf.for_all_ap_vaps(disable_func); EXPECT_TRUE(conf) << conf; - auto comment_func = [&conf](const std::string vap) { - conf.comment_vap(vap); - }; + + // disable by commenting + auto comment_func = [&conf](const std::string vap) { conf.comment_vap(vap); }; conf.for_all_ap_vaps(comment_func); EXPECT_TRUE(conf) << conf; + // store + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end prerequsite //// +} + conf.store(); EXPECT_TRUE(conf) << conf; From 10775b86ceaa7fea3e55b04c8da14264c0284f38 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 15 Jul 2020 12:33:42 +0300 Subject: [PATCH 16/27] prplmesh-hostapd: added test enable_all_vaps() a test that goes all over all vaps and enables them https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- .../hostapd/unit_tests/configuration_test.cpp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 067ece960b..f903ec65ec 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -381,6 +381,36 @@ TEST(configuration_test, disable_all_ap) //// end prerequsite //// } +TEST(configuration_test, enable_all_ap) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + // enable by removing key/value + auto enable_func = [&conf](const std::string vap) { + conf.set_create_vap_value(vap, "start_disabled", ""); + }; + + conf.for_all_ap_vaps(enable_func); + EXPECT_TRUE(conf) << conf; + + // enable by uncommenting + auto uncomment_func = [&conf](const std::string vap) { conf.uncomment_vap(vap); }; + + conf.for_all_ap_vaps(uncomment_func); + EXPECT_TRUE(conf) << conf; + + // store conf.store(); EXPECT_TRUE(conf) << conf; From 9fdd0c32af453a07c40409873929e528f7766cad Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 15 Jul 2020 12:35:15 +0300 Subject: [PATCH 17/27] prplmesh-hostapd: added test iterate-both-containers testing the ability to iterate over the user container and the hostapd's ap-vaps in paralel giving the user the ability to set parametrs based on its own container and the hostapd container https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- .../hostapd/unit_tests/configuration_test.cpp | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index f903ec65ec..bd7c234111 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -671,4 +671,46 @@ TEST(configuration_test, uncomment_vap) //// end test //// } +TEST(configuration_test, itererate_both_containers) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + //clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + //// start test //// + + std::vector ssid = {"00:11:22:33:44:55", "ab:cd:ef:01:02:03"}; + + auto set_ssid = [&conf, &ssid](const std::string vap, std::vector::iterator it) { + if (it != ssid.end()) { + // as long as we didn't finish our containr + // we set the given's vap ssid + conf.set_create_vap_value(vap, "ssid", *it); + } else { + // when we done with our container, we simply + // comment the rest of the vaps + conf.comment_vap(vap); + } + }; + + conf.for_all_ap_vaps(set_ssid, ssid.begin(), ssid.end()); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; + + //// end test //// +} + } // namespace From 6399e7b80cb84e8c22c2d8414a05fc91de67f595 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 15 Jul 2020 13:42:56 +0300 Subject: [PATCH 18/27] prplmesh-hostapd: few cleanups after rebase rebase --autosquash -i origin/master introduced issues that I couldn't figure out how to fix with rebasing again. gave up and added this fix commit. https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 4 +- .../hostapd/unit_tests/configuration_test.cpp | 171 +----------------- 2 files changed, 5 insertions(+), 170 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index 31e3e0e7f8..c06e693709 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -175,13 +175,11 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri return ""; } const auto& existing_vap = std::get<1>(find_vap); - // + // from now on this function is ok with all situations // (e.g. not finding the requested key) m_ok = true; - const auto &existing_vap = std::get<1>(find_vap); - std::string key_eq(key + "="); auto it_str = std::find_if( existing_vap->begin(), existing_vap->end(), diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index bd7c234111..615d0121bf 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -209,6 +209,10 @@ TEST(configuration_test, store) // save the content of the string (start clean) // clean_start(); + // load the dummy configuration file + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + EXPECT_FALSE(conf) << conf; + // construct a configuration ASSERT_FALSE(conf) << conf; @@ -504,173 +508,6 @@ TEST(configuration_test, uncomment_vap) //// end test //// } -TEST(configuration_test, set_int_vap_values) -{ - //// start prerequsite //// - - // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_file); - ASSERT_FALSE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - //// end prerequsite //// - - //// start test //// - - // replace existing value for existing key for existing vap - conf.set_create_vap_value("wlan0.1", "ignore_broadcast_ssid", 42); - EXPECT_TRUE(conf) << conf; - - // add a key/value to exising vap - conf.set_create_vap_value("wlan0.3", "i_am_negative", -24); - EXPECT_TRUE(conf) << conf; - - // try to replace existing value for existing key for existing vap - // with NON-int value. we expect the value to be trancated here - // at the caller site - conf.set_create_vap_value("wlan0.2", "wmm_enabled", 333.444); - EXPECT_TRUE(conf) << conf; - - //// end test //// -} - -TEST(configuration_test, get_vap_values) -{ - //// start prerequsite //// - - // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_file); - ASSERT_FALSE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - //// end prerequsite //// - - //// start test //// - - std::string value; - - // get existing value for existing vap - value = conf.get_vap_value("wlan0.1", "wpa_passphrase"); - EXPECT_EQ(value, "maprocks2") << conf; - - // another check - existing value for existing vap - value = conf.get_vap_value("wlan0.1", "bssid"); - EXPECT_EQ(value, "02:9A:96:FB:59:12"); - - // get NON existing value for existing vap - value = conf.get_vap_value("wlan0.1", "does_not_exist"); - EXPECT_EQ(value, "") << conf; - - // try to get for NON existing vap - value = conf.get_vap_value("no_vap", "key"); - EXPECT_EQ(value, "") << conf; - - //// end test //// -} - -TEST(configuration_test, diable_all_ap) -{ - //// start prerequsite //// - - // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_file); - ASSERT_FALSE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - auto disable_func = [&conf](const std::string vap) { - conf.set_create_vap_value(vap, "start_disabled", 1); - }; - - conf.for_all_ap_vaps(disable_func); - EXPECT_TRUE(conf) << conf; - - auto comment_func = [&conf](const std::string vap) { - conf.comment_vap(vap); - }; - - conf.for_all_ap_vaps(comment_func); - EXPECT_TRUE(conf) << conf; - - conf.store(); - EXPECT_TRUE(conf) << conf; - - //// end prerequsite //// -} - -TEST(configuration_test, comment_vap) -{ - //// start prerequsite //// - - // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_file); - ASSERT_FALSE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - //// end prerequsite //// - - //// start test //// - - conf.comment_vap("wlan0.1"); - EXPECT_TRUE(conf) << conf; - - conf.store(); - EXPECT_TRUE(conf) << conf; - - //// end test //// -} - -TEST(configuration_test, uncomment_vap) -{ - //// start prerequsite //// - - // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_file); - ASSERT_FALSE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - // comment twice! - conf.comment_vap("wlan0.1"); - conf.comment_vap("wlan0.1"); - EXPECT_TRUE(conf) << conf; - - conf.store(); - EXPECT_TRUE(conf) << conf; - - // load the dummy configuration file - conf.load(); - ASSERT_TRUE(conf) << conf; - - //// end prerequsite //// - - //// start test //// - - conf.uncomment_vap("wlan0.1"); - EXPECT_TRUE(conf) << conf; - - conf.uncomment_vap("wlan0.2"); - EXPECT_TRUE(conf) << conf; - - conf.store(); - EXPECT_TRUE(conf) << conf; - - //// end test //// -} - TEST(configuration_test, itererate_both_containers) { //// start prerequsite //// From 6bd96dda7d67d73684c94a83b6a7bb618e85ca3b Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Wed, 15 Jul 2020 16:08:00 +0300 Subject: [PATCH 19/27] prplmesh-hostapd: added set_create_xx_head_value added a functionality to set or create key/value in the header part: either string or int added functionality and tests https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 42 ++++++++++++-- .../beerocks/hostapd/include/configuration.h | 28 ++++++++- .../hostapd/unit_tests/configuration_test.cpp | 58 +++++++++++++++++++ 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index c06e693709..d1a81b959b 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -112,6 +112,44 @@ bool Configuration::store() return *this; } +bool Configuration::set_create_head_value(const std::string &key, const std::string &value) +{ + // search for the key + std::string key_eq(key + "="); + auto it_str = std::find_if( + m_hostapd_config_head.begin(), m_hostapd_config_head.end(), + [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); + + // we first delete the key, and if the requested value is non empty + // we push it to the end of the array + + // delete the key-value if found + if (it_str != m_hostapd_config_head.end()) { + it_str = m_hostapd_config_head.erase(it_str); + } else { + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' for head was not found"; + } + + // when the new value is provided add the key back with that new value + if (value.length() != 0) { + m_hostapd_config_head.push_back(key_eq + value); + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' was (re)added to head"; + } else { + m_last_message = + std::string(__FUNCTION__) + " the key '" + key + "' was deleted from head"; + } + + m_ok = true; + return *this; +} + +bool Configuration::set_create_head_value(const std::string &key, const int value) +{ + return set_create_head_value(key, std::to_string(value)); +} + bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, const std::string &value) { @@ -123,10 +161,6 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri auto existing_vap = std::get<1>(find_vap); bool existing_vap_commented = existing_vap->front()[0] == '#'; - // search for the key from the back of the string - // ignore comments this way - // e.g: - // ###bssid=11:22:ff:ee:aa std::string key_eq(key + "="); auto it_str = std::find_if( existing_vap->begin(), existing_vap->end(), diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 319c65690b..203fcddc8d 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -51,6 +51,32 @@ class Configuration { */ bool store(); + /** + * @brief set key/value in the head section + * @details set the key/vale in the head section, + * either replace or create. + * @note comments in the head section are not supported + * for example, if the key/value line was commented before + * the call to this function, it would be uncommented afterwards + * @param + * - the key to set its value (string) + * - the value (string) + */ + bool set_create_head_value(const std::string &key, const std::string &value); + + /** + * @brief set key/value in the head section + * @details set the key/vale in the head section, + * either replace or create. + * @note comments in the head section are not supported + * for example, if the key/value line was commented before + * the call to this function, it would be uncommented afterwards + * @param + * - the key to set its value (string) + * - the value (int) + */ + bool set_create_head_value(const std::string &key, const int value); + /** * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. @@ -170,7 +196,7 @@ class Configuration { bool m_ok = false; // each string is a line in the original configuration file - // that belongs to the "head" part. read the explnation at + // that belongs to the "head" part. read the explenation at // the end of the cpp file for more details std::vector m_hostapd_config_head; diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 615d0121bf..5fab64f5e2 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -231,6 +231,64 @@ TEST(configuration_test, store) EXPECT_TRUE(conf) << conf; } +TEST(configuration_test, set_string_head_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + // clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + // replace existing key + conf.set_create_head_value("vht_capab", "this is shorter"); + EXPECT_TRUE(conf) << conf; + + // remove exiting key + conf.set_create_head_value("ht_capab", ""); + EXPECT_TRUE(conf) << conf; + + // add a new key/value + conf.set_create_head_value("new_head", "{pnew->next=phead; phead=pnew;}"); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; +} + +TEST(configuration_test, set_int_head_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + // replace existing key + conf.set_create_head_value("bss_transition", 451); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; +} + TEST(configuration_test, set_string_vap_values) { //// start prerequsite //// From f2f33cdff2e8d04a4932e4a080f157208c9e12aa Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 16 Jul 2020 14:01:17 +0300 Subject: [PATCH 20/27] prplmesh-hostapd: added get_head_value() added a function to get a value from the head section added functionality and unit test also in this commit: the variable named it_str is now called line_iter (it is better describs its meaning) https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 35 ++++++++++++++----- .../beerocks/hostapd/include/configuration.h | 15 ++++++-- .../hostapd/unit_tests/configuration_test.cpp | 30 ++++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index d1a81b959b..f73eb90847 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -116,7 +116,7 @@ bool Configuration::set_create_head_value(const std::string &key, const std::str { // search for the key std::string key_eq(key + "="); - auto it_str = std::find_if( + auto line_iter = std::find_if( m_hostapd_config_head.begin(), m_hostapd_config_head.end(), [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); @@ -124,8 +124,8 @@ bool Configuration::set_create_head_value(const std::string &key, const std::str // we push it to the end of the array // delete the key-value if found - if (it_str != m_hostapd_config_head.end()) { - it_str = m_hostapd_config_head.erase(it_str); + if (line_iter != m_hostapd_config_head.end()) { + line_iter = m_hostapd_config_head.erase(line_iter); } else { m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' for head was not found"; @@ -150,6 +150,23 @@ bool Configuration::set_create_head_value(const std::string &key, const int valu return set_create_head_value(key, std::to_string(value)); } +std::string Configuration::get_head_value(const std::string &key) +{ + std::string key_eq(key + "="); + auto line_iter = std::find_if( + m_hostapd_config_head.begin(), m_hostapd_config_head.end(), + [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); + + if (line_iter == m_hostapd_config_head.end()) { + m_last_message = std::string(__FUNCTION__) + + " couldn't find requested key in head: " + key; + return ""; + } + + // return from just after the '=' sign to the end of the string + return line_iter->substr(line_iter->find('=') + 1); +} + bool Configuration::set_create_vap_value(const std::string &vap, const std::string &key, const std::string &value) { @@ -162,7 +179,7 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri bool existing_vap_commented = existing_vap->front()[0] == '#'; std::string key_eq(key + "="); - auto it_str = std::find_if( + auto line_iter = std::find_if( existing_vap->begin(), existing_vap->end(), [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); @@ -170,8 +187,8 @@ bool Configuration::set_create_vap_value(const std::string &vap, const std::stri // we push it to the end of the array // delete the key-value if found - if (it_str != existing_vap->end()) { - it_str = existing_vap->erase(it_str); + if (line_iter != existing_vap->end()) { + line_iter = existing_vap->erase(line_iter); } else { m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' for vap " + vap + " was not found"; @@ -215,18 +232,18 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri m_ok = true; std::string key_eq(key + "="); - auto it_str = std::find_if( + auto line_iter = std::find_if( existing_vap->begin(), existing_vap->end(), [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); - if (it_str == existing_vap->end()) { + if (line_iter == existing_vap->end()) { m_last_message = std::string(__FUNCTION__) + " couldn't find requested key for vap: " + vap + "; requested key: " + key; return ""; } // return from the just after the '=' sign to the end of the string - return it_str->substr(it_str->find('=') + 1); + return line_iter->substr(line_iter->find('=') + 1); } void Configuration::comment_vap(const std::string &vap) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 203fcddc8d..dfd25cbaeb 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -77,6 +77,14 @@ class Configuration { */ bool set_create_head_value(const std::string &key, const int value); + /** + * @brief get the value of the given key from the head + * @param + * - the key to get its value (string) + * @return a string with the value or empty if not found + */ + std::string get_head_value(const std::string &key); + /** * @brief set key/value for the given vap * @details set the key/vale for the given vap, either replace or create. @@ -193,7 +201,9 @@ class Configuration { private: std::string m_configuration_file; - bool m_ok = false; + + // may be changed because of const functions, therefore mutable + mutable bool m_ok = false; // each string is a line in the original configuration file // that belongs to the "head" part. read the explenation at @@ -222,7 +232,8 @@ class Configuration { // { "wlan0.2" : ["ctrl_interface=/var/run/hosXXpd", "ap_isolate=1", "ap_max_inactivity=60", "bss_transition=0", "interworking=3"] } std::map> m_hostapd_config_vaps; - std::string m_last_message = "all good"; + // see m_ok's comment + mutable std::string m_last_message = "all good"; // for logs friend std::ostream &operator<<(std::ostream &, const Configuration &); diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 5fab64f5e2..787e5a4120 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -289,6 +289,36 @@ TEST(configuration_test, set_int_head_values) EXPECT_TRUE(conf) << conf; } +TEST(configuration_test, get_head_values) +{ + //// start prerequsite //// + + // save the content of the string (start clean) + clean_start(); + + // construct a configuration + prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + ASSERT_FALSE(conf) << conf; + + // load the dummy configuration file + conf.load(); + ASSERT_TRUE(conf) << conf; + + //// end prerequsite //// + + // get existing key + auto val = conf.get_head_value("ctrl_interface"); + EXPECT_EQ(val, "/var/run/hostapd"); + + // get non existing key + val = conf.get_head_value("out_of_office"); + EXPECT_EQ(val, ""); + EXPECT_TRUE(conf) << conf; + + conf.store(); + EXPECT_TRUE(conf) << conf; +} + TEST(configuration_test, set_string_vap_values) { //// start prerequsite //// From 933fe636fc01fef54b05a652c5d14353003929de Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 20 Jul 2020 12:58:48 +0300 Subject: [PATCH 21/27] prplmesh-hostapd: added vap_indication to load() originally bss= was the only sign for vap configuration inside the hostapd file. this was based on intel's hostapd version. the hostapd upstream indicates vap configuration with interface= sign. load() now seeks for the vap sign as set by the user. also in this commit: - removed namespace config from prplmesh::hostapd. left with only the two and class Configuration. - clang format fixes https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/configuration.cpp | 39 ++++----- .../beerocks/hostapd/include/configuration.h | 14 +-- .../hostapd/unit_tests/configuration_test.cpp | 86 ++++++++++--------- 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/configuration.cpp index f73eb90847..38118af330 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/configuration.cpp @@ -13,19 +13,18 @@ namespace prplmesh { namespace hostapd { -namespace config { Configuration::Configuration(const std::string &file_name) : m_configuration_file(file_name) {} Configuration::operator bool() const { return m_ok; } -bool Configuration::load() +bool Configuration::load(const std::string &vap_indication) { // please take a look at the end of this file for // the expected format of hostapd configuration file // loading the file relays on the expected format // otherwise the load fails - + // for cases when load is called more than once, we // first clear internal data m_hostapd_config_head.clear(); @@ -51,16 +50,15 @@ bool Configuration::load() continue; } // check if the string belongs to a vap config part and capture which one. - const std::string bss_eq("bss="); auto end_comment = line.find_first_not_of('#'); - if (line.compare(end_comment, bss_eq.length(), bss_eq) == 0) { + if (line.compare(end_comment, vap_indication.length(), vap_indication) == 0) { // from now on we are in the vaps area, all // key/value pairs belongs to vaps parsing_vaps = true; - // copy the bss (vap) value - cur_vap.assign(line, end_comment + bss_eq.length(), std::string::npos); + // copy the vap value + cur_vap.assign(line, end_comment + vap_indication.length(), std::string::npos); } // if not a vap line store it in the header part of the config, @@ -72,7 +70,10 @@ bool Configuration::load() } } - m_last_message = strerror(errno); + std::stringstream load_message; + load_message << "load() final message: os - " << strerror(errno) << "; existing vaps - " + << std::boolalpha << parsing_vaps; + m_last_message = load_message.str(); // if we've got to parsing vaps and no read errors, assume all is good m_ok = parsing_vaps && !ifs.bad(); @@ -134,11 +135,9 @@ bool Configuration::set_create_head_value(const std::string &key, const std::str // when the new value is provided add the key back with that new value if (value.length() != 0) { m_hostapd_config_head.push_back(key_eq + value); - m_last_message = - std::string(__FUNCTION__) + " the key '" + key + "' was (re)added to head"; + m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' was (re)added to head"; } else { - m_last_message = - std::string(__FUNCTION__) + " the key '" + key + "' was deleted from head"; + m_last_message = std::string(__FUNCTION__) + " the key '" + key + "' was deleted from head"; } m_ok = true; @@ -158,8 +157,7 @@ std::string Configuration::get_head_value(const std::string &key) [&key_eq, this](const std::string &line) -> bool { return is_key_in_line(line, key_eq); }); if (line_iter == m_hostapd_config_head.end()) { - m_last_message = std::string(__FUNCTION__) + - " couldn't find requested key in head: " + key; + m_last_message = std::string(__FUNCTION__) + " couldn't find requested key in head: " + key; return ""; } @@ -225,7 +223,7 @@ std::string Configuration::get_vap_value(const std::string &vap, const std::stri if (!std::get<0>(find_vap)) { return ""; } - const auto& existing_vap = std::get<1>(find_vap); + const auto &existing_vap = std::get<1>(find_vap); // from now on this function is ok with all situations // (e.g. not finding the requested key) @@ -331,10 +329,10 @@ std::ostream &operator<<(std::ostream &o, const Configuration &conf) o << line << '\n'; } - o << "== vaps (" << conf.m_hostapd_config_vaps.size() << ") ==\n"; + o << "== vaps (total of: " << conf.m_hostapd_config_vaps.size() << " vaps) ==\n"; for (const auto &vap : conf.m_hostapd_config_vaps) { - o << " = vap (" << vap.first << " ) =\n"; + o << " vap: " << vap.first << "\n"; for (const auto &line : vap.second) { o << line << '\n'; } @@ -343,7 +341,6 @@ std::ostream &operator<<(std::ostream &o, const Configuration &conf) return o; } -} // namespace config } // namespace hostapd } // namespace prplmesh @@ -351,8 +348,10 @@ std::ostream &operator<<(std::ostream &o, const Configuration &conf) //////////////// hostapd configuration format //////////////// hostapd has a special format that does NOT have an ini like format. -We expect the following format of the file: -(between /// BEGIN hostapd.conf /// and /// END hostapd.conf ///) +between /// BEGIN hostapd.conf /// and /// END hostapd.conf /// is the +format of the file. +note: the string "bss=" may be replaced by the +user in the call to load() with another vap-indicator (e.g. "interface=") /// BEGIN hostapd.conf /// diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index dfd25cbaeb..3a26a151c0 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -13,7 +13,6 @@ namespace prplmesh { namespace hostapd { -namespace config { class Configuration { @@ -39,9 +38,13 @@ class Configuration { /** * @brief load the configuration * @details loads the file this object is constructed with + * @param vap_indication - set the indication that a vap + * configuration section begins. e.g. "bss=" or "interface=" + * nothe that the equal sign is expected to be part of vap_indication + * std::string vap_indication("interface="); * @return *this (as bool, see above) */ - bool load(); + bool load(const std::string &vap_indication); /** * @brief stores the configuration @@ -203,7 +206,7 @@ class Configuration { std::string m_configuration_file; // may be changed because of const functions, therefore mutable - mutable bool m_ok = false; + mutable bool m_ok = false; // each string is a line in the original configuration file // that belongs to the "head" part. read the explenation at @@ -232,8 +235,8 @@ class Configuration { // { "wlan0.2" : ["ctrl_interface=/var/run/hosXXpd", "ap_isolate=1", "ap_max_inactivity=60", "bss_transition=0", "interworking=3"] } std::map> m_hostapd_config_vaps; - // see m_ok's comment - mutable std::string m_last_message = "all good"; + // see m_ok's comment + mutable std::string m_last_message = "initial state, yet nothing was done"; // for logs friend std::ostream &operator<<(std::ostream &, const Configuration &); @@ -266,6 +269,5 @@ void Configuration::for_all_ap_vaps(func f, iter current_iter, const iter end) // for logs std::ostream &operator<<(std::ostream &, const Configuration &); -} // namespace config } // namespace hostapd } // namespace prplmesh diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 787e5a4120..66cad3bb95 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -13,8 +13,9 @@ namespace { +const std::string vap_indication("interface="); const std::string configuration_path("/tmp/"); -const std::string configuration_file_name("hostapd.conf"); +const std::string configuration_file_name("omnia.conf"); const std::string configuration_content( "driver=nl80211\n" "logger_syslog=127\n" @@ -177,6 +178,7 @@ const std::string configuration_content( "start_disabled=1\n" "mode=ap\n"); +/* void clean_start() { // save the content of the string (start clean) @@ -184,21 +186,22 @@ void clean_start() tmp << configuration_content; tmp.flush(); } +*/ TEST(configuration_test, load) { //// start prerequsite //// - clean_start(); + ////clean_start(); //// end prerequsite //// // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); EXPECT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication); EXPECT_TRUE(conf) << conf; } @@ -207,17 +210,17 @@ TEST(configuration_test, store) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // load the dummy configuration file - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); EXPECT_FALSE(conf) << conf; // construct a configuration ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -236,14 +239,14 @@ TEST(configuration_test, set_string_head_values) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -269,14 +272,14 @@ TEST(configuration_test, set_int_head_values) //// start prerequsite //// // save the content of the string (start clean) - clean_start(); + //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -294,14 +297,14 @@ TEST(configuration_test, get_head_values) //// start prerequsite //// // save the content of the string (start clean) - clean_start(); + //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -324,14 +327,14 @@ TEST(configuration_test, set_string_vap_values) //// start prerequsite //// // save the content of the string (start clean) - // // clean_start(); + // // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -365,14 +368,14 @@ TEST(configuration_test, set_int_vap_values) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -401,14 +404,14 @@ TEST(configuration_test, get_vap_values) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -441,14 +444,14 @@ TEST(configuration_test, disable_all_ap) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; // disable by adding a key/value @@ -459,7 +462,6 @@ TEST(configuration_test, disable_all_ap) conf.for_all_ap_vaps(disable_func); EXPECT_TRUE(conf) << conf; - // disable by commenting auto comment_func = [&conf](const std::string vap) { conf.comment_vap(vap); }; @@ -478,14 +480,14 @@ TEST(configuration_test, enable_all_ap) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; // enable by removing key/value @@ -514,14 +516,14 @@ TEST(configuration_test, comment_vap) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -542,14 +544,14 @@ TEST(configuration_test, uncomment_vap) //// start prerequsite //// // save the content of the string (start clean) - // clean_start(); + // //clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; // comment twice! @@ -561,7 +563,7 @@ TEST(configuration_test, uncomment_vap) EXPECT_TRUE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -601,14 +603,14 @@ TEST(configuration_test, itererate_both_containers) //// start prerequsite //// // save the content of the string (start clean) - //clean_start(); + ////clean_start(); // construct a configuration - prplmesh::hostapd::config::Configuration conf(configuration_path + configuration_file_name); + prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(); + conf.load(vap_indication);; ASSERT_TRUE(conf) << conf; //// end prerequsite //// From 72cc86f33811f519335937e0b40c9768ee06e10a Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 20 Jul 2020 14:56:09 +0300 Subject: [PATCH 22/27] prplmesh-hostapd: added ap predicate to for_all_ap_vaps there is a need to let the usr the ability to decide if a given vap is an ap vap or not. e.g. for intel the predicate is: mode=ap for ap stream hostapd: all vaps are ap vaps https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- .../beerocks/hostapd/include/configuration.h | 33 +++++---- .../hostapd/unit_tests/configuration_test.cpp | 74 ++++++++++++++----- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/configuration.h index 3a26a151c0..9cc87a44df 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/configuration.h @@ -156,20 +156,26 @@ class Configuration { /** * @brief apply func to all ap vaps - * @details apply func to all vaps that thier mode is "ap" - * (leaving STAs vaps untouched for example). + * @details apply func to all vaps that ap_predicate returns + * true for them. this enables leaving STAs vaps untouched for example. + * @param func has the following interface: void f(const std::string &vap); + * @param ap_predicate has the following interface: bool f(const std::string &vap); */ - template void for_all_ap_vaps(func); + template void for_all_ap_vaps(func, ap_predicate); /** * @brief apply func to all ap vaps, increment the given iterator * with each call to func: f(current_vap, ++user_itertor) - * func interface: template f(const std::string& vap, iter user_iterator) - * @details apply func to all vaps that thier mode is "ap" while incrementing - * the user iterator. this functionality enables iterating over two containers + * @details apply func to all vaps that ap_predicate returns true for + * while incrementing the user iterator. + * this functionality enables iterating over two containers * in paralel: the ap-vaps and the user container. + * @param func has the following interface: template f(const std::string& vap, iter user_iterator) + * @param iter - iterator to the begining of the user sequence + * @param ap_predicate has the following interface: bool f(const std::string &vap); */ - template void for_all_ap_vaps(func, iter current, const iter end); + template + void for_all_ap_vaps(func, iter current, const iter end, ap_predicate); /** * @brief for debug: return the last internal message @@ -242,21 +248,22 @@ class Configuration { friend std::ostream &operator<<(std::ostream &, const Configuration &); }; -template void Configuration::for_all_ap_vaps(func f) +template +void Configuration::for_all_ap_vaps(func f, ap_predicate pred) { auto f_with_iter = [&f](const std::string &vap, int iter) { f(vap); }; int dummy(0); - for_all_ap_vaps(f_with_iter, dummy, 10); + for_all_ap_vaps(f_with_iter, dummy, 10, pred); } -template -void Configuration::for_all_ap_vaps(func f, iter current_iter, const iter end) +template +void Configuration::for_all_ap_vaps(func f, iter current_iter, const iter end, ap_predicate pred) { for_each(m_hostapd_config_vaps.begin(), m_hostapd_config_vaps.end(), [this, &f, ¤t_iter, - &end](const std::pair> &vap) { - if (get_vap_value(vap.first, "mode") == "ap") { + &end, &pred](const std::pair> &vap) { + if (pred(vap.first)) { if (end == current_iter) { f(vap.first, end); } else { diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 66cad3bb95..4a746e617d 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -178,6 +178,7 @@ const std::string configuration_content( "start_disabled=1\n" "mode=ap\n"); + /* void clean_start() { @@ -220,13 +221,14 @@ TEST(configuration_test, store) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// // add a value to vap - conf.set_create_vap_value("wlan0.3", "was_i_stroed", "yes_you_were"); + conf.set_create_vap_value("wlan1", "was_i_stroed", "yes_you_were"); EXPECT_TRUE(conf) << conf; // store @@ -246,7 +248,8 @@ TEST(configuration_test, set_string_head_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -279,7 +282,8 @@ TEST(configuration_test, set_int_head_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -304,7 +308,8 @@ TEST(configuration_test, get_head_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -334,7 +339,8 @@ TEST(configuration_test, set_string_vap_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -375,7 +381,8 @@ TEST(configuration_test, set_int_vap_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -411,7 +418,8 @@ TEST(configuration_test, get_vap_values) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -451,21 +459,34 @@ TEST(configuration_test, disable_all_ap) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; + // identify vap by mode=ap + /* + auto mode_predicate = [&conf](const std::string &vap) { + return conf.get_vap_value(vap, "mode") == "ap"; + }; + */ + + // all vaps are ap vaps + auto all_predicate = [&conf](const std::string &vap) { + return true; + }; + // disable by adding a key/value auto disable_func = [&conf](const std::string vap) { conf.set_create_vap_value(vap, "start_disabled", 1); }; - conf.for_all_ap_vaps(disable_func); + conf.for_all_ap_vaps(disable_func, all_predicate); EXPECT_TRUE(conf) << conf; // disable by commenting auto comment_func = [&conf](const std::string vap) { conf.comment_vap(vap); }; - conf.for_all_ap_vaps(comment_func); + conf.for_all_ap_vaps(comment_func, all_predicate); EXPECT_TRUE(conf) << conf; // store @@ -487,7 +508,8 @@ TEST(configuration_test, enable_all_ap) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; // enable by removing key/value @@ -495,13 +517,17 @@ TEST(configuration_test, enable_all_ap) conf.set_create_vap_value(vap, "start_disabled", ""); }; - conf.for_all_ap_vaps(enable_func); + auto ap_predicate = [&conf](const std::string &vap) { + return conf.get_vap_value(vap, "mode") == "ap"; + }; + + conf.for_all_ap_vaps(enable_func, ap_predicate); EXPECT_TRUE(conf) << conf; // enable by uncommenting - auto uncomment_func = [&conf](const std::string vap) { conf.uncomment_vap(vap); }; + auto uncomment_func = [&conf](const std::string &vap) { conf.uncomment_vap(vap); }; - conf.for_all_ap_vaps(uncomment_func); + conf.for_all_ap_vaps(uncomment_func, ap_predicate); EXPECT_TRUE(conf) << conf; // store @@ -523,7 +549,8 @@ TEST(configuration_test, comment_vap) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -551,7 +578,8 @@ TEST(configuration_test, uncomment_vap) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; // comment twice! @@ -563,7 +591,8 @@ TEST(configuration_test, uncomment_vap) EXPECT_TRUE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -610,7 +639,8 @@ TEST(configuration_test, itererate_both_containers) ASSERT_FALSE(conf) << conf; // load the dummy configuration file - conf.load(vap_indication);; + conf.load(vap_indication); + ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// @@ -631,7 +661,11 @@ TEST(configuration_test, itererate_both_containers) } }; - conf.for_all_ap_vaps(set_ssid, ssid.begin(), ssid.end()); + auto ap_predicate = [&conf](const std::string &vap) { + return conf.get_vap_value(vap, "mode") == "ap"; + }; + + conf.for_all_ap_vaps(set_ssid, ssid.begin(), ssid.end(), ap_predicate); EXPECT_TRUE(conf) << conf; conf.store(); From c21d2bed7dbec19a5ffd667fe5ee25f7ec244f5b Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 30 Jul 2020 14:26:54 +0300 Subject: [PATCH 23/27] prplmesh-hostapd: PR 1514 review fixups this commnit fixes all relevant comments for https://github.com/prplfoundation/prplMesh/pull/1550 https://jira.prplfoundation.org/browse/PPM-249 Signed-off-by: Ran Regev --- common/beerocks/hostapd/CMakeLists.txt | 2 +- .../include/{ => hostapd}/configuration.h | 11 ++- .../hostapd/{ => source}/configuration.cpp | 75 ++++--------------- .../hostapd/unit_tests/configuration_test.cpp | 37 +++++---- 4 files changed, 45 insertions(+), 80 deletions(-) rename common/beerocks/hostapd/include/{ => hostapd}/configuration.h (95%) rename common/beerocks/hostapd/{ => source}/configuration.cpp (83%) diff --git a/common/beerocks/hostapd/CMakeLists.txt b/common/beerocks/hostapd/CMakeLists.txt index 8287220dec..f69c15a220 100644 --- a/common/beerocks/hostapd/CMakeLists.txt +++ b/common/beerocks/hostapd/CMakeLists.txt @@ -37,7 +37,7 @@ set(PRPLMESH_HOSTAPD_TYPE ${PRPLMESH_HOSTAPD_TYPE} CACHE STRING "Which PRPLMESH_ set( hostapd_sources - configuration.cpp + source/configuration.cpp ) diff --git a/common/beerocks/hostapd/include/configuration.h b/common/beerocks/hostapd/include/hostapd/configuration.h similarity index 95% rename from common/beerocks/hostapd/include/configuration.h rename to common/beerocks/hostapd/include/hostapd/configuration.h index 9cc87a44df..accbb458de 100644 --- a/common/beerocks/hostapd/include/configuration.h +++ b/common/beerocks/hostapd/include/hostapd/configuration.h @@ -211,7 +211,12 @@ class Configuration { private: std::string m_configuration_file; - // may be changed because of const functions, therefore mutable + // m_ok hoslds the internal state of the configuration + // the user may query the success/fail state of the last command + // simply by reading the value of this variable. + // the access to it is via operator bool() + // m_ok itslef may be changed because of a call to + // const function, therefore it is mutable mutable bool m_ok = false; // each string is a line in the original configuration file @@ -261,8 +266,8 @@ template void Configuration::for_all_ap_vaps(func f, iter current_iter, const iter end, ap_predicate pred) { for_each(m_hostapd_config_vaps.begin(), m_hostapd_config_vaps.end(), - [this, &f, ¤t_iter, - &end, &pred](const std::pair> &vap) { + [this, &f, ¤t_iter, &end, + &pred](const std::pair> &vap) { if (pred(vap.first)) { if (end == current_iter) { f(vap.first, end); diff --git a/common/beerocks/hostapd/configuration.cpp b/common/beerocks/hostapd/source/configuration.cpp similarity index 83% rename from common/beerocks/hostapd/configuration.cpp rename to common/beerocks/hostapd/source/configuration.cpp index 38118af330..891746675c 100644 --- a/common/beerocks/hostapd/configuration.cpp +++ b/common/beerocks/hostapd/source/configuration.cpp @@ -6,10 +6,10 @@ * See LICENSE file for more details. */ -#include "configuration.h" #include #include #include +#include namespace prplmesh { namespace hostapd { @@ -20,9 +20,9 @@ Configuration::operator bool() const { return m_ok; } bool Configuration::load(const std::string &vap_indication) { - // please take a look at the end of this file for - // the expected format of hostapd configuration file - // loading the file relays on the expected format + // please take a look at README.md (common/beerocks/hostapd/README.md) for + // the expected format of hostapd configuration file. + // loading the file relies on the expected format // otherwise the load fails // for cases when load is called more than once, we @@ -317,74 +317,29 @@ bool Configuration::is_key_in_line(const std::string &line, const std::string &k return ret; } -std::ostream &operator<<(std::ostream &o, const Configuration &conf) +std::ostream &operator<<(std::ostream &os, const Configuration &conf) { - o << "== configuration details ==\n" - << "= ok: " << std::boolalpha << conf.m_ok << '\n' - << "= last message: " << conf.m_last_message << '\n' - << "= file: " << conf.m_configuration_file << '\n' - << "= head: " << '\n'; + os << "== configuration details ==\n" + << "= ok: " << std::boolalpha << conf.m_ok << '\n' + << "= last message: " << conf.m_last_message << '\n' + << "= file: " << conf.m_configuration_file << '\n' + << "= head: " << '\n'; for (const auto &line : conf.m_hostapd_config_head) { - o << line << '\n'; + os << line << '\n'; } - o << "== vaps (total of: " << conf.m_hostapd_config_vaps.size() << " vaps) ==\n"; + os << "== vaps (total of: " << conf.m_hostapd_config_vaps.size() << " vaps) ==\n"; for (const auto &vap : conf.m_hostapd_config_vaps) { - o << " vap: " << vap.first << "\n"; + os << " vap: " << vap.first << "\n"; for (const auto &line : vap.second) { - o << line << '\n'; + os << line << '\n'; } } - return o; + return os; } } // namespace hostapd } // namespace prplmesh - -/* -//////////////// hostapd configuration format //////////////// - -hostapd has a special format that does NOT have an ini like format. -between /// BEGIN hostapd.conf /// and /// END hostapd.conf /// is the -format of the file. -note: the string "bss=" may be replaced by the -user in the call to load() with another vap-indicator (e.g. "interface=") - -/// BEGIN hostapd.conf /// - -## "head" part ## - -# everything until the first `bss=` in the file -# is considered "head" -# after the first `bss=` "vaps" are presented. -# so we expect no parametrs that does not belong to vaps -# after the first `bss=` -# take a look below for more details - -# note that we don't expect a space between the key and the equal sign -# the code in this class seeks for `key=` when a key is needed. -# therefore `key =` (with space) will fail -# also, everything immidiatly after the equal sign (=) is -# part of the value. the space before 11 in this is part of the value: -# bassid= 11:22:33:44:55:66 -key=value - -## "vaps part - we expect at least one vap to be configured ## - -# vap (bss and vap are interchangable) -bss=wlan0_0 - -# this key (ssid) belongs to the previous vap (bss value which is wlan0_0) -ssid=test2 - -# another vap -bss=wlan0_1 - -# this key (bssid) belongs to the previous vap wlan0_1 -bssid=00:13:10:95:fe:0b - -///// END hostapd.conf /// -*/ diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 4a746e617d..04774eea30 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -6,13 +6,14 @@ * See LICENSE file for more details. */ -#include "configuration.h" #include +#include #include namespace { +//const std::string vap_indication("bss="); const std::string vap_indication("interface="); const std::string configuration_path("/tmp/"); const std::string configuration_file_name("omnia.conf"); @@ -56,9 +57,10 @@ const std::string configuration_content( "wpa=0\n" "ssid=dummy_ssid_0\n" "bridge=br-lan\n" - "bssid=02:9A:96:FB:59:0F\n" + "bssid=02:9A:96:FB:59:0F\n" + - "bss=wlan0.0\n" + vap_indication + + "wlan0.0\n" "ctrl_interface=/var/run/hostapd\n" "ap_isolate=1\n" "ap_max_inactivity=60\n" @@ -100,9 +102,10 @@ const std::string configuration_content( "wps_state=2\n" "mesh_mode=fAP\n" "multi_ap_backhaul_ssid=\"Multi-AP-24G-2\"\n" - "multi_ap_backhaul_wpa_passphrase=maprocks2\n" + "multi_ap_backhaul_wpa_passphrase=maprocks2\n" + - "bss=wlan0.1\n" + vap_indication + + "wlan0.1\n" "mode=non-ap\n" "ctrl_interface=/var/run/hostapd\n" "ap_isolate=1\n" @@ -134,7 +137,9 @@ const std::string configuration_content( "sFourAddrMode=1\n" "max_num_sta=1\n" - "#bss=wlan0.2\n" + "#" + + vap_indication + + "wlan0.2\n" "#ctrl_interface=/var/run/hostapd\n" "#ap_isolate=1\n" "#ap_max_inactivity=60\n" @@ -154,9 +159,10 @@ const std::string configuration_content( "#wpa=0\n" "#bridge=br-lan\n" "#bssid=02:9A:96:FB:59:13\n" - "#start_disabled=1\n" + "#start_disabled=1\n" + - "bss=wlan0.3\n" + vap_indication + + "wlan0.3\n" "ctrl_interface=/var/run/hostapd\n" "ap_isolate=1\n" "ap_max_inactivity=60\n" @@ -178,8 +184,6 @@ const std::string configuration_content( "start_disabled=1\n" "mode=ap\n"); - -/* void clean_start() { // save the content of the string (start clean) @@ -187,7 +191,6 @@ void clean_start() tmp << configuration_content; tmp.flush(); } -*/ TEST(configuration_test, load) { @@ -228,7 +231,7 @@ TEST(configuration_test, store) //// end prerequsite //// // add a value to vap - conf.set_create_vap_value("wlan1", "was_i_stroed", "yes_you_were"); + conf.set_create_vap_value("wlan0.1", "was_i_stroed", "yes_you_were"); EXPECT_TRUE(conf) << conf; // store @@ -315,8 +318,8 @@ TEST(configuration_test, get_head_values) //// end prerequsite //// // get existing key - auto val = conf.get_head_value("ctrl_interface"); - EXPECT_EQ(val, "/var/run/hostapd"); + auto val = conf.get_head_value("acs_num_scans"); + EXPECT_EQ(val, "1"); // get non existing key val = conf.get_head_value("out_of_office"); @@ -332,7 +335,7 @@ TEST(configuration_test, set_string_vap_values) //// start prerequsite //// // save the content of the string (start clean) - // // //clean_start(); + clean_start(); // construct a configuration prplmesh::hostapd::Configuration conf(configuration_path + configuration_file_name); @@ -340,13 +343,15 @@ TEST(configuration_test, set_string_vap_values) // load the dummy configuration file conf.load(vap_indication); - ; ASSERT_TRUE(conf) << conf; //// end prerequsite //// //// start test //// + conf.set_create_vap_value("wlan0", "ssid", "ran_home"); + EXPECT_TRUE(conf) << conf; + // replace existing value for existing key for existing vap conf.set_create_vap_value("wlan0.2", "disassoc_low_ack", "734"); EXPECT_TRUE(conf) << conf; From 52b7859bebd92d11d1f3aab5ac9031b84075d9ed Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Mon, 20 Jul 2020 14:56:09 +0300 Subject: [PATCH 24/27] prplmesh-hostapd: added ap predicate to for_all_ap_vaps there is a need to let the usr the ability to decide if a given vap is an ap vap or not. e.g. for intel the predicate is: mode=ap for up stream's hostapd: all vaps are ap vaps Signed-off-by: Ran Regev --- common/beerocks/hostapd/unit_tests/configuration_test.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 04774eea30..890e7703a7 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -476,9 +476,7 @@ TEST(configuration_test, disable_all_ap) */ // all vaps are ap vaps - auto all_predicate = [&conf](const std::string &vap) { - return true; - }; + auto all_predicate = [&conf](const std::string &vap) { return true; }; // disable by adding a key/value auto disable_func = [&conf](const std::string vap) { From 629bf6608b20020725120b616808cbe28600e8b5 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 23 Jul 2020 13:51:45 +0300 Subject: [PATCH 25/27] nl80211: hostapd configuration: cmake added prplmesh_hostapd dependency to bwl and btl in cmake Signed-off-by: Ran Regev --- common/beerocks/bwl/CMakeLists.txt | 4 ++- common/beerocks/hostapd/CMakeLists.txt | 43 ++------------------------ 2 files changed, 6 insertions(+), 41 deletions(-) diff --git a/common/beerocks/bwl/CMakeLists.txt b/common/beerocks/bwl/CMakeLists.txt index 7ec702efad..39ef976aa2 100644 --- a/common/beerocks/bwl/CMakeLists.txt +++ b/common/beerocks/bwl/CMakeLists.txt @@ -109,6 +109,8 @@ elseif(BWL_TYPE STREQUAL "NL80211") ${PLATFORM_STAGING_DIR}/usr/lib ) + list(APPEND BWL_LIBS prplmesh_hostapd) + elseif(BWL_TYPE STREQUAL "DUMMY") set(bwl_platform_sources @@ -179,7 +181,7 @@ if (BUILD_TESTS) PUBLIC $ ) - target_link_libraries(${TEST_PROJECT_NAME} bcl elpp ${BWL_LIBS}) + target_link_libraries(${TEST_PROJECT_NAME} prplmesh_hostapd bcl elpp ${BWL_LIBS}) target_link_libraries(${TEST_PROJECT_NAME} gtest_main) install(TARGETS ${TEST_PROJECT_NAME} DESTINATION bin/tests) add_test(NAME ${TEST_PROJECT_NAME} COMMAND $) diff --git a/common/beerocks/hostapd/CMakeLists.txt b/common/beerocks/hostapd/CMakeLists.txt index f69c15a220..f7b3d5afc3 100644 --- a/common/beerocks/hostapd/CMakeLists.txt +++ b/common/beerocks/hostapd/CMakeLists.txt @@ -1,47 +1,11 @@ + project(prplmesh_hostapd VERSION ${prplmesh_VERSION}) # Set the base path for the current module set(MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) -# Default defaults -set(PRPLMESH_HOSTAPD_TYPE "DUMMY" CACHE STRING "PRPLMESH_HOSTAPD type") -set_property(CACHE PRPLMESH_HOSTAPD_TYPE PROPERTY STRINGS "DUMMY" "NL80211" "DWPAL") -add_definitions(-DBEEROCKS_TMP_PATH="${TMP_PATH}") - -if (TARGET_PLATFORM STREQUAL "rdkb") - - set(PRPLMESH_HOSTAPD_TYPE "DWPAL") - # Extra libraries - list(APPEND PRPLMESH_HOSTAPD_LIBS rt dl) - -elseif(TARGET_PLATFORM STREQUAL "openwrt") - - if (TARGET_PLATFORM_TYPE STREQUAL "ugw") - set(PRPLMESH_HOSTAPD_TYPE "DWPAL") - else() - set(PRPLMESH_HOSTAPD_TYPE "NL80211") - endif() - - # hostapd directory - file(GLOB PRPLMESH_HOSTAPD_SEARCH_PATHS "${PLATFORM_BUILD_DIR}/hostapd*/hostapd-*") - find_path(PRPLMESH_HOSTAPD_INCLUDE_DIR NAMES "src/common/wpa_ctrl.h" PATHS ${PRPLMESH_HOSTAPD_SEARCH_PATHS} NO_CMAKE_FIND_ROOT_PATH) - set(PRPLMESH_HOSTAPD_DIR "${PRPLMESH_HOSTAPD_INCLUDE_DIR}") - -endif() - -set(PRPLMESH_HOSTAPD_TYPE ${PRPLMESH_HOSTAPD_TYPE} CACHE STRING "Which PRPLMESH_HOSTAPD backend to use") - -########################################################################## -########################################################################## -########################################################################## - -set( - hostapd_sources - source/configuration.cpp -) - - # Build the library +set(hostapd_sources source/configuration.cpp) add_library(${PROJECT_NAME} ${hostapd_sources}) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${prplmesh_VERSION} SOVERSION ${prplmesh_VERSION_MAJOR}) target_link_libraries(${PROJECT_NAME} elpp ${PRPLMESH_HOSTAPD_LIBS}) @@ -60,8 +24,7 @@ install( ) if (BUILD_TESTS) - string(TOLOWER ${PRPLMESH_HOSTAPD_TYPE} PRPLMESH_HOSTAPD_TYPE_LOWERCASE) - set(TEST_PROJECT_NAME ${PROJECT_NAME}_${PRPLMESH_HOSTAPD_TYPE_LOWERCASE}_unit_tests) + set(TEST_PROJECT_NAME ${PROJECT_NAME}_unit_tests) set(unit_tests_sources ${MODULE_PATH}/unit_tests/configuration_test.cpp ) From 4c321ebee4028f877f3331ffecb2b011360503a0 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 23 Jul 2020 13:52:54 +0300 Subject: [PATCH 26/27] nl80211: hostapd configuration update_vap_credentials() added hostapd configuration to update_vap_credentials() the implementation was copied and then adopted from dwpal implementation (common/beerocks/bwl/dwpal/ap_wlan_hal_dwpal.cpp) tested on Turris Omnia Signed-off-by: Ran Regev --- .../bwl/nl80211/ap_wlan_hal_nl80211.cpp | 297 +++++++++++++++++- 1 file changed, 296 insertions(+), 1 deletion(-) diff --git a/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp b/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp index aa54592dc2..ff12ac0856 100644 --- a/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp +++ b/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -106,6 +107,46 @@ static uint8_t wpa_bw_to_beerocks_bw(const std::string &chan_width) return (chan_width == "80+80") ? 160 : beerocks::string_utils::stoi(chan_width); } +/// @brief figures out hostapd config file name by the +// interface name and loads its content +static prplmesh::hostapd::Configuration load_hostapd_config(const std::string &radio_iface_name) +{ + std::vector hostapd_cfg_names = { + "/tmp/run/hostapd-phy0.conf", "/tmp/run/hostapd-phy1.conf", "/var/run/hostapd-phy0.conf", + "/var/run/hostapd-phy1.conf", "/var/run/hostapd-phy2.conf", "/var/run/hostapd-phy3.conf"}; + + for (const auto &try_fname : hostapd_cfg_names) { + LOG(DEBUG) << "Trying to load " << try_fname << "..."; + + if (!beerocks::os_utils::file_exists(try_fname)) { + continue; + } + + prplmesh::hostapd::Configuration hostapd_conf(try_fname); + + // try loading + if (!hostapd_conf.load("interface=")) { + LOG(ERROR) << "Failed to load hostapd config file: " << hostapd_conf; + continue; + } + + // check if it is the right one: + // we are looking for the line in the vap that declares this vap: interface=radio_iface_name + // we could equaly ask hostapd_conf if it has this vap, but there is no such interface + // to do this. it should be something like: hostapd_conf.is_vap_exists(radio_iface_name); + if (hostapd_conf.get_vap_value(radio_iface_name, "interface") != radio_iface_name) { + LOG(DEBUG) << radio_iface_name << " does not exists in " << try_fname; + continue; + } + + // all is good, return the conf + return hostapd_conf; + } + + // return an empty one since we couldn't find the + return prplmesh::hostapd::Configuration("file not found"); +} + ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// Implementation /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// @@ -268,7 +309,261 @@ bool ap_wlan_hal_nl80211::update_vap_credentials( std::list &bss_info_conf_list, const std::string &backhaul_wps_ssid, const std::string &backhaul_wps_passphrase) { - //TODO Implement #346 + // The bss_info_conf_list contains list of all VAPs and STAs that have to be created + // on the radio. If the list is empty, there should be no VAPs left. + // At the moment we are taking a shortcut to make this work for certification setup. + // We are updating the hostapd.conf files with the info from the bss_info_conf_list + // and sending RELOAD command to hostapd making it update the VAPs. + // With that approach there are following limitations: + // - all the VAPs are expected to be always present in the config, but have + // start_disabled=1 option if not active + // - any configuration change causing the hostapd config files to be re-generated + // and the hostapd processes restarted will override the prplMesh configuration + + // The hostapd config is stored here. The items order in the header and VAPs sections + // is mostly preserved (with the exception of the items prplMesh modifies). This helps + // to keep the configuration the same and easily see what prplMesh is changing. + + // Load hostapd config for the radio + + LOG(DEBUG) << std::string(__FUNCTION__) << " was called with list size: " << bss_info_conf_list.size(); + + + prplmesh::hostapd::Configuration conf = load_hostapd_config(m_radio_info.iface_name); + if (!conf) { + LOG(ERROR) << "Autoconfiguration: no hostapd config to apply configuration!"; + return false; + } + + // If a Multi-AP Agent receives an AP-Autoconfiguration WSC message containing one or + // more M2, it shall validate each M2 (based on its 1905 AL MAC address) and configure + // a BSS on the corresponding radio for each of the M2. If the Multi-AP Agent is currently + // operating a BSS with operating parameters that do not completely match any of the M2 in + // the received AP-Autoconfiguration WSC message, it shall tear down that BSS. + + // decalre a function for iterating over bss-conf and ap-vaps + bool abort = false; + auto configure_func = [&abort, &conf, &bss_info_conf_list, &backhaul_wps_ssid, + &backhaul_wps_passphrase]( + const std::string vap, + std::list::iterator bss_it) { + if (abort) { + return; + } + + if (bss_it != bss_info_conf_list.end()) { + + // we still have data on the input bss-conf list + + // escape I + auto auth_type = + son::wireless_utils::wsc_to_bwl_authentication(bss_it->authentication_type); + if (auth_type == "INVALID") { + LOG(ERROR) << "Autoconfiguration: auth type is 'INVALID'; number: " + << (uint16_t)bss_it->authentication_type; + abort = true; + return; + } + + // escape II + auto enc_type = son::wireless_utils::wsc_to_bwl_encryption(bss_it->encryption_type); + if (enc_type == "INVALID") { + LOG(ERROR) << "Autoconfiguration: enc_type is 'INVALID'; number: " + << int(bss_it->encryption_type); + abort = true; + return; + } + + // escape III + if (conf.get_vap_value(vap, "bssid").empty()) { + LOG(ERROR) << "Failed to get BSSID for vap: " << vap; + abort = true; + return; + } + + // escape IV + // BSS type (backhaul, fronthaul or both) + // Use Intel Mesh-Mode (upstream Multi-AP functionality not supported by Intel): + // Not supporting hybrid mode for in mesh mode (TODO - move to hybrid mode after + // https://github.com/prplfoundation/prplMesh/issues/889) + if (bss_it->fronthaul && bss_it->backhaul) { + LOG(ERROR) << "Not supporting hybrid VAP"; + abort = true; + return; + } + + // settings + + // ssid + conf.set_create_vap_value(vap, "ssid", bss_it->ssid); + + // Hostapd "wpa" field. + // This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0) + // and/or WPA2 (full IEEE 802.11i/RSN): + // bit0 = WPA + // bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) + int wpa = 0; + + // Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The + // entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be + // added to enable SHA256-based stronger algorithms. + // WPA-PSK = WPA-Personal / WPA2-Personal + std::string wpa_key_mgmt; // default to empty -> delete from hostapd config + + // (dot11RSNAConfigPairwiseCiphersTable) + // Pairwise cipher for WPA (v1) (default: TKIP) + // wpa_pairwise=TKIP CCMP + // Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) + // rsn_pairwise=CCMP + std::string wpa_pairwise; // default to empty -> delete from hostapd config + + // WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit + // secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase + // (8..63 characters), wpa_passphrase. + std::string wpa_passphrase; + std::string wpa_psk; + + // ieee80211w: Whether management frame protection (MFP) is enabled + // 0 = disabled (default) + // 1 = optional + // 2 = required + std::string ieee80211w; + + // This parameter can be used to disable caching of PMKSA created through EAP + // authentication. RSN preauthentication may still end up using PMKSA caching if + // it is enabled (rsn_preauth=1). + // 0 = PMKSA caching enabled (default) + // 1 = PMKSA caching disabled + std::string disable_pmksa_caching; + + // Opportunistic Key Caching (aka Proactive Key Caching) + // Allow PMK cache to be shared opportunistically among configured interfaces + // and BSSes (i.e., all configurations within a single hostapd process). + // 0 = disabled (default) + // 1 = enabled + std::string okc; + + // This parameter can be used to disable retransmission of EAPOL-Key frames that + // are used to install keys (EAPOL-Key message 3/4 and group message 1/2). This + // is similar to setting wpa_group_update_count=1 and + std::string wpa_disable_eapol_key_retries; + + // EasyMesh R1 only allows Open and WPA2 PSK auth&encryption methods. + // Quote: A Multi-AP Controller shall set the Authentication Type attribute + // in M2 to indicate WPA2-Personal or Open System Authentication. + // bss_it->authentication_type is a bitfield, but we are not going + // to accept any combinations due to the above limitation. + if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_OPEN) { + wpa = 0x0; + if (bss_it->encryption_type != WSC::eWscEncr::WSC_ENCR_NONE) { + LOG(ERROR) << "Autoconfiguration: " << vap << " encryption set on open VAP"; + abort = true; + return; + } + if (bss_it->network_key.length() > 0) { + LOG(ERROR) << "Autoconfiguration: " << vap << " network key set for open VAP"; + abort = true; + return; + } + } else if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_WPA2PSK) { + wpa = 0x2; + wpa_key_mgmt.assign("WPA-PSK"); + // Cipher must include AES for WPA2, TKIP is optional + if ((static_cast(bss_it->encryption_type) & static_cast(WSC::eWscEncr::WSC_ENCR_AES)) == + 0) { + LOG(ERROR) << "Autoconfiguration: " << vap + << " CCMP(AES) is required for WPA2"; + abort = true; + return; + } + if ((uint16_t(bss_it->encryption_type) & uint16_t(WSC::eWscEncr::WSC_ENCR_TKIP)) != + 0) { + wpa_pairwise.assign("TKIP CCMP"); + } else { + wpa_pairwise.assign("CCMP"); + } + if (bss_it->network_key.length() < 8 || bss_it->network_key.length() > 64) { + LOG(ERROR) << "Autoconfiguration: " << vap << " invalid network key length " + << bss_it->network_key.length(); + abort = true; + return; + } + if (bss_it->network_key.length() < 64) { + wpa_passphrase.assign(bss_it->network_key); + } else { + wpa_psk.assign(bss_it->network_key); + } + ieee80211w.assign("0"); + disable_pmksa_caching.assign("1"); + okc.assign("0"); + wpa_disable_eapol_key_retries.assign("0"); + } else { + LOG(ERROR) << "Autoconfiguration: " << vap << " invalid authentication type"; + abort = true; + return; + } + + LOG(DEBUG) << "Autoconfiguration for ssid: " << bss_it->ssid + << " auth_type: " << auth_type << " encr_type: " << enc_type + << " network_key: " << bss_it->network_key + << " fronthaul=" << beerocks::string_utils::bool_str(bss_it->fronthaul) + << " backhaul=" << beerocks::string_utils::bool_str(bss_it->backhaul); + + conf.set_create_vap_value(vap, "wps_state", bss_it->fronthaul ? "2" : ""); + conf.set_create_vap_value(vap, "wps_independent", "0"); + conf.set_create_vap_value(vap, "sFourAddrMode", bss_it->backhaul ? "1" : ""); + conf.set_create_vap_value(vap, "max_num_sta", bss_it->backhaul ? "1" : ""); + + // oddly enough, multi_ap_backhaul_wpa_passphrase has to be + // quoted, while wpa_passphrase does not... + if (bss_it->fronthaul && !backhaul_wps_ssid.empty()) { + conf.set_create_vap_value(vap, "multi_ap_backhaul_ssid", + "\"" + backhaul_wps_ssid + "\""); + conf.set_create_vap_value(vap, "multi_ap_backhaul_wpa_passphrase", + backhaul_wps_passphrase); + } + + conf.set_create_vap_value(vap, "wpa", wpa); + conf.set_create_vap_value(vap, "okc", okc); + conf.set_create_vap_value(vap, "wpa_key_mgmt", wpa_key_mgmt); + conf.set_create_vap_value(vap, "wpa_pairwise", wpa_pairwise); + conf.set_create_vap_value(vap, "wpa_psk", wpa_psk); + conf.set_create_vap_value(vap, "ieee80211w", ieee80211w); + conf.set_create_vap_value(vap, "wpa_passphrase", wpa_passphrase); + conf.set_create_vap_value(vap, "disable_pmksa_caching", disable_pmksa_caching); + conf.set_create_vap_value(vap, "wpa_disable_eapol_key_retries", + wpa_disable_eapol_key_retries); + + // finally enable the vap (remove any previously set start_disabled) + conf.set_create_vap_value(vap, "start_disabled", ""); + + } else { + // no more data in the input bss-conf list + // disable the rest of the vaps + conf.set_create_vap_value(vap, "start_disabled", 1); + conf.set_create_vap_value(vap, "ssid", ""); + } + }; + + conf.for_all_ap_vaps(configure_func, bss_info_conf_list.begin(), bss_info_conf_list.end(), + [](const std::string &) { return true; }); + + if (abort) { + return false; + } + + if (!conf.store()) { + LOG(ERROR) << "Autoconfiguration: cannot save hostapd config!"; + return false; + } + + const std::string cmd("UPDATE "); + if (!wpa_ctrl_send_msg(cmd)) { + LOG(ERROR) << "Autoconfiguration: \"" << cmd << "\" command to hostapd has failed"; + return false; + } + + LOG(DEBUG) << "Autoconfiguration: done:\n" << conf; return true; } From b87c6f4f9749bca3986d5a05acdcf5dab9801f62 Mon Sep 17 00:00:00 2001 From: Ran Regev Date: Thu, 30 Jul 2020 15:04:10 +0300 Subject: [PATCH 27/27] nl80211: hostapd configuration: close issues found in PR 1550 this commit closes issues found in https://github.com/prplfoundation/prplMesh/pull/1550 https://jira.prplfoundation.org/browse/PPM-4 MAP-4.2.1:turris-omnia Signed-off-by: Ran Regev --- .../bwl/nl80211/ap_wlan_hal_nl80211.cpp | 387 ++++++++---------- .../hostapd/include/hostapd/configuration.h | 4 +- .../beerocks/hostapd/source/configuration.cpp | 4 +- .../hostapd/unit_tests/configuration_test.cpp | 2 + 4 files changed, 189 insertions(+), 208 deletions(-) diff --git a/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp b/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp index ff12ac0856..e5e780f6d7 100644 --- a/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp +++ b/common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp @@ -7,24 +7,21 @@ */ #include "ap_wlan_hal_nl80211.h" - #include #include #include #include #include -#include - -#include - #include - +#include +#include #include #include #include #include #include #include +#include ////////////////////////////////////////////////////////////////////////////// ////////////////////////// Local Module Definitions ////////////////////////// @@ -309,26 +306,13 @@ bool ap_wlan_hal_nl80211::update_vap_credentials( std::list &bss_info_conf_list, const std::string &backhaul_wps_ssid, const std::string &backhaul_wps_passphrase) { - // The bss_info_conf_list contains list of all VAPs and STAs that have to be created - // on the radio. If the list is empty, there should be no VAPs left. - // At the moment we are taking a shortcut to make this work for certification setup. - // We are updating the hostapd.conf files with the info from the bss_info_conf_list - // and sending RELOAD command to hostapd making it update the VAPs. - // With that approach there are following limitations: - // - all the VAPs are expected to be always present in the config, but have - // start_disabled=1 option if not active - // - any configuration change causing the hostapd config files to be re-generated - // and the hostapd processes restarted will override the prplMesh configuration - - // The hostapd config is stored here. The items order in the header and VAPs sections - // is mostly preserved (with the exception of the items prplMesh modifies). This helps - // to keep the configuration the same and easily see what prplMesh is changing. + if (0 == bss_info_conf_list.size()) { + LOG(DEBUG) << "given bss conf list size is zero, no changes to existing hostapd " + "configuration are applied"; + return true; + } // Load hostapd config for the radio - - LOG(DEBUG) << std::string(__FUNCTION__) << " was called with list size: " << bss_info_conf_list.size(); - - prplmesh::hostapd::Configuration conf = load_hostapd_config(m_radio_info.iface_name); if (!conf) { LOG(ERROR) << "Autoconfiguration: no hostapd config to apply configuration!"; @@ -342,208 +326,201 @@ bool ap_wlan_hal_nl80211::update_vap_credentials( // the received AP-Autoconfiguration WSC message, it shall tear down that BSS. // decalre a function for iterating over bss-conf and ap-vaps - bool abort = false; - auto configure_func = [&abort, &conf, &bss_info_conf_list, &backhaul_wps_ssid, - &backhaul_wps_passphrase]( - const std::string vap, - std::list::iterator bss_it) { - if (abort) { - return; - } - - if (bss_it != bss_info_conf_list.end()) { - - // we still have data on the input bss-conf list - - // escape I - auto auth_type = - son::wireless_utils::wsc_to_bwl_authentication(bss_it->authentication_type); - if (auth_type == "INVALID") { - LOG(ERROR) << "Autoconfiguration: auth type is 'INVALID'; number: " - << (uint16_t)bss_it->authentication_type; - abort = true; + bool abort = false; + auto configure_func = + [&abort, &conf, &bss_info_conf_list, &backhaul_wps_ssid, &backhaul_wps_passphrase]( + const std::string &vap, + std::remove_reference::type::iterator bss_it) { + if (abort) { return; } - // escape II - auto enc_type = son::wireless_utils::wsc_to_bwl_encryption(bss_it->encryption_type); - if (enc_type == "INVALID") { - LOG(ERROR) << "Autoconfiguration: enc_type is 'INVALID'; number: " - << int(bss_it->encryption_type); - abort = true; - return; - } + if (bss_it != bss_info_conf_list.end()) { - // escape III - if (conf.get_vap_value(vap, "bssid").empty()) { - LOG(ERROR) << "Failed to get BSSID for vap: " << vap; - abort = true; - return; - } + // we still have data on the input bss-conf list - // escape IV - // BSS type (backhaul, fronthaul or both) - // Use Intel Mesh-Mode (upstream Multi-AP functionality not supported by Intel): - // Not supporting hybrid mode for in mesh mode (TODO - move to hybrid mode after - // https://github.com/prplfoundation/prplMesh/issues/889) - if (bss_it->fronthaul && bss_it->backhaul) { - LOG(ERROR) << "Not supporting hybrid VAP"; - abort = true; - return; - } - - // settings - - // ssid - conf.set_create_vap_value(vap, "ssid", bss_it->ssid); - - // Hostapd "wpa" field. - // This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0) - // and/or WPA2 (full IEEE 802.11i/RSN): - // bit0 = WPA - // bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) - int wpa = 0; - - // Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The - // entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be - // added to enable SHA256-based stronger algorithms. - // WPA-PSK = WPA-Personal / WPA2-Personal - std::string wpa_key_mgmt; // default to empty -> delete from hostapd config - - // (dot11RSNAConfigPairwiseCiphersTable) - // Pairwise cipher for WPA (v1) (default: TKIP) - // wpa_pairwise=TKIP CCMP - // Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) - // rsn_pairwise=CCMP - std::string wpa_pairwise; // default to empty -> delete from hostapd config - - // WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit - // secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase - // (8..63 characters), wpa_passphrase. - std::string wpa_passphrase; - std::string wpa_psk; - - // ieee80211w: Whether management frame protection (MFP) is enabled - // 0 = disabled (default) - // 1 = optional - // 2 = required - std::string ieee80211w; - - // This parameter can be used to disable caching of PMKSA created through EAP - // authentication. RSN preauthentication may still end up using PMKSA caching if - // it is enabled (rsn_preauth=1). - // 0 = PMKSA caching enabled (default) - // 1 = PMKSA caching disabled - std::string disable_pmksa_caching; - - // Opportunistic Key Caching (aka Proactive Key Caching) - // Allow PMK cache to be shared opportunistically among configured interfaces - // and BSSes (i.e., all configurations within a single hostapd process). - // 0 = disabled (default) - // 1 = enabled - std::string okc; - - // This parameter can be used to disable retransmission of EAPOL-Key frames that - // are used to install keys (EAPOL-Key message 3/4 and group message 1/2). This - // is similar to setting wpa_group_update_count=1 and - std::string wpa_disable_eapol_key_retries; - - // EasyMesh R1 only allows Open and WPA2 PSK auth&encryption methods. - // Quote: A Multi-AP Controller shall set the Authentication Type attribute - // in M2 to indicate WPA2-Personal or Open System Authentication. - // bss_it->authentication_type is a bitfield, but we are not going - // to accept any combinations due to the above limitation. - if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_OPEN) { - wpa = 0x0; - if (bss_it->encryption_type != WSC::eWscEncr::WSC_ENCR_NONE) { - LOG(ERROR) << "Autoconfiguration: " << vap << " encryption set on open VAP"; + // escape I + auto auth_type = + son::wireless_utils::wsc_to_bwl_authentication(bss_it->authentication_type); + if (auth_type == "INVALID") { + LOG(ERROR) << "Autoconfiguration: auth type is 'INVALID'; number: " + << (uint16_t)bss_it->authentication_type; abort = true; return; } - if (bss_it->network_key.length() > 0) { - LOG(ERROR) << "Autoconfiguration: " << vap << " network key set for open VAP"; + + // escape II + auto enc_type = son::wireless_utils::wsc_to_bwl_encryption(bss_it->encryption_type); + if (enc_type == "INVALID") { + LOG(ERROR) << "Autoconfiguration: enc_type is 'INVALID'; number: " + << int(bss_it->encryption_type); abort = true; return; } - } else if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_WPA2PSK) { - wpa = 0x2; - wpa_key_mgmt.assign("WPA-PSK"); - // Cipher must include AES for WPA2, TKIP is optional - if ((static_cast(bss_it->encryption_type) & static_cast(WSC::eWscEncr::WSC_ENCR_AES)) == - 0) { - LOG(ERROR) << "Autoconfiguration: " << vap - << " CCMP(AES) is required for WPA2"; + + // escape III + if (conf.get_vap_value(vap, "bssid").empty()) { + LOG(ERROR) << "Failed to get BSSID for vap: " << vap; abort = true; return; } - if ((uint16_t(bss_it->encryption_type) & uint16_t(WSC::eWscEncr::WSC_ENCR_TKIP)) != - 0) { - wpa_pairwise.assign("TKIP CCMP"); + + // settings + + // Hostapd "wpa" field. + // This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0) + // and/or WPA2 (full IEEE 802.11i/RSN): + // bit0 = WPA + // bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) + int wpa = 0; + + // Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The + // entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be + // added to enable SHA256-based stronger algorithms. + // WPA-PSK = WPA-Personal / WPA2-Personal + std::string wpa_key_mgmt; // default to empty -> delete from hostapd config + + // (dot11RSNAConfigPairwiseCiphersTable) + // Pairwise cipher for WPA (v1) (default: TKIP) + // wpa_pairwise=TKIP CCMP + // Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) + // rsn_pairwise=CCMP + std::string wpa_pairwise; // default to empty -> delete from hostapd config + + // WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit + // secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase + // (8..63 characters), wpa_passphrase. + std::string wpa_passphrase; + std::string wpa_psk; + + // ieee80211w: Whether management frame protection (MFP) is enabled + // 0 = disabled (default) + // 1 = optional + // 2 = required + std::string ieee80211w; + + // This parameter can be used to disable caching of PMKSA created through EAP + // authentication. RSN preauthentication may still end up using PMKSA caching if + // it is enabled (rsn_preauth=1). + // 0 = PMKSA caching enabled (default) + // 1 = PMKSA caching disabled + std::string disable_pmksa_caching; + + // Opportunistic Key Caching (aka Proactive Key Caching) + // Allow PMK cache to be shared opportunistically among configured interfaces + // and BSSes (i.e., all configurations within a single hostapd process). + // 0 = disabled (default) + // 1 = enabled + std::string okc; + + // This parameter can be used to disable retransmission of EAPOL-Key frames that + // are used to install keys (EAPOL-Key message 3/4 and group message 1/2). This + // is similar to setting wpa_group_update_count=1 and + std::string wpa_disable_eapol_key_retries; + + // EasyMesh R1 only allows Open and WPA2 PSK auth&encryption methods. + // Quote: A Multi-AP Controller shall set the Authentication Type attribute + // in M2 to indicate WPA2-Personal or Open System Authentication. + // bss_it->authentication_type is a bitfield, but we are not going + // to accept any combinations due to the above limitation. + if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_OPEN) { + wpa = 0x0; + if (bss_it->encryption_type != WSC::eWscEncr::WSC_ENCR_NONE) { + LOG(ERROR) << "Autoconfiguration: " << vap << " encryption set on open VAP"; + abort = true; + return; + } + if (bss_it->network_key.length() > 0) { + LOG(ERROR) + << "Autoconfiguration: " << vap << " network key set for open VAP"; + abort = true; + return; + } + } else if (bss_it->authentication_type == WSC::eWscAuth::WSC_AUTH_WPA2PSK) { + wpa = 0x2; + wpa_key_mgmt.assign("WPA-PSK"); + // Cipher must include AES for WPA2, TKIP is optional + if ((static_cast(bss_it->encryption_type) & + static_cast(WSC::eWscEncr::WSC_ENCR_AES)) == 0) { + LOG(ERROR) + << "Autoconfiguration: " << vap << " CCMP(AES) is required for WPA2"; + abort = true; + return; + } + if ((uint16_t(bss_it->encryption_type) & + uint16_t(WSC::eWscEncr::WSC_ENCR_TKIP)) != 0) { + wpa_pairwise.assign("TKIP CCMP"); + } else { + wpa_pairwise.assign("CCMP"); + } + if (bss_it->network_key.length() < 8 || bss_it->network_key.length() > 64) { + LOG(ERROR) << "Autoconfiguration: " << vap << " invalid network key length " + << bss_it->network_key.length(); + abort = true; + return; + } + if (bss_it->network_key.length() < 64) { + wpa_passphrase.assign(bss_it->network_key); + } else { + wpa_psk.assign(bss_it->network_key); + } + ieee80211w.assign("0"); + disable_pmksa_caching.assign("1"); + okc.assign("0"); + wpa_disable_eapol_key_retries.assign("0"); } else { - wpa_pairwise.assign("CCMP"); - } - if (bss_it->network_key.length() < 8 || bss_it->network_key.length() > 64) { - LOG(ERROR) << "Autoconfiguration: " << vap << " invalid network key length " - << bss_it->network_key.length(); + LOG(ERROR) << "Autoconfiguration: " << vap << " invalid authentication type"; abort = true; return; } - if (bss_it->network_key.length() < 64) { - wpa_passphrase.assign(bss_it->network_key); - } else { - wpa_psk.assign(bss_it->network_key); - } - ieee80211w.assign("0"); - disable_pmksa_caching.assign("1"); - okc.assign("0"); - wpa_disable_eapol_key_retries.assign("0"); - } else { - LOG(ERROR) << "Autoconfiguration: " << vap << " invalid authentication type"; - abort = true; - return; - } - LOG(DEBUG) << "Autoconfiguration for ssid: " << bss_it->ssid - << " auth_type: " << auth_type << " encr_type: " << enc_type - << " network_key: " << bss_it->network_key - << " fronthaul=" << beerocks::string_utils::bool_str(bss_it->fronthaul) - << " backhaul=" << beerocks::string_utils::bool_str(bss_it->backhaul); - - conf.set_create_vap_value(vap, "wps_state", bss_it->fronthaul ? "2" : ""); - conf.set_create_vap_value(vap, "wps_independent", "0"); - conf.set_create_vap_value(vap, "sFourAddrMode", bss_it->backhaul ? "1" : ""); - conf.set_create_vap_value(vap, "max_num_sta", bss_it->backhaul ? "1" : ""); - - // oddly enough, multi_ap_backhaul_wpa_passphrase has to be - // quoted, while wpa_passphrase does not... - if (bss_it->fronthaul && !backhaul_wps_ssid.empty()) { - conf.set_create_vap_value(vap, "multi_ap_backhaul_ssid", - "\"" + backhaul_wps_ssid + "\""); - conf.set_create_vap_value(vap, "multi_ap_backhaul_wpa_passphrase", - backhaul_wps_passphrase); - } + LOG(DEBUG) << "Autoconfiguration for ssid: " << bss_it->ssid + << " auth_type: " << auth_type << " encr_type: " << enc_type + << " network_key: " << bss_it->network_key + << " fronthaul=" << beerocks::string_utils::bool_str(bss_it->fronthaul) + << " backhaul=" << beerocks::string_utils::bool_str(bss_it->backhaul); + + conf.set_create_vap_value(vap, "ssid", bss_it->ssid); + conf.set_create_vap_value(vap, "wps_state", bss_it->fronthaul ? "2" : ""); + conf.set_create_vap_value(vap, "wps_independent", "0"); + conf.set_create_vap_value(vap, "max_num_sta", bss_it->backhaul ? "1" : ""); + + // oddly enough, multi_ap_backhaul_wpa_passphrase has to be + // quoted, while wpa_passphrase does not... + if (bss_it->fronthaul && !backhaul_wps_ssid.empty()) { + conf.set_create_vap_value(vap, "multi_ap_backhaul_ssid", + "\"" + backhaul_wps_ssid + "\""); + conf.set_create_vap_value(vap, "multi_ap_backhaul_wpa_passphrase", + backhaul_wps_passphrase); + } - conf.set_create_vap_value(vap, "wpa", wpa); - conf.set_create_vap_value(vap, "okc", okc); - conf.set_create_vap_value(vap, "wpa_key_mgmt", wpa_key_mgmt); - conf.set_create_vap_value(vap, "wpa_pairwise", wpa_pairwise); - conf.set_create_vap_value(vap, "wpa_psk", wpa_psk); - conf.set_create_vap_value(vap, "ieee80211w", ieee80211w); - conf.set_create_vap_value(vap, "wpa_passphrase", wpa_passphrase); - conf.set_create_vap_value(vap, "disable_pmksa_caching", disable_pmksa_caching); - conf.set_create_vap_value(vap, "wpa_disable_eapol_key_retries", - wpa_disable_eapol_key_retries); + // remove when not needed + if (!bss_it->fronthaul && backhaul_wps_ssid.empty()) { + conf.set_create_vap_value(vap, "multi_ap_backhaul_ssid", ""); + conf.set_create_vap_value(vap, "multi_ap_backhaul_wpa_passphrase", ""); + } - // finally enable the vap (remove any previously set start_disabled) - conf.set_create_vap_value(vap, "start_disabled", ""); + conf.set_create_vap_value(vap, "wpa", wpa); + conf.set_create_vap_value(vap, "okc", okc); + conf.set_create_vap_value(vap, "wpa_key_mgmt", wpa_key_mgmt); + conf.set_create_vap_value(vap, "wpa_pairwise", wpa_pairwise); + conf.set_create_vap_value(vap, "wpa_psk", wpa_psk); + conf.set_create_vap_value(vap, "ieee80211w", ieee80211w); + conf.set_create_vap_value(vap, "wpa_passphrase", wpa_passphrase); + conf.set_create_vap_value(vap, "disable_pmksa_caching", disable_pmksa_caching); + conf.set_create_vap_value(vap, "wpa_disable_eapol_key_retries", + wpa_disable_eapol_key_retries); + + // finally enable the vap (remove any previously set start_disabled and uncomment) + conf.set_create_vap_value(vap, "start_disabled", ""); + conf.uncomment_vap(vap); - } else { - // no more data in the input bss-conf list - // disable the rest of the vaps - conf.set_create_vap_value(vap, "start_disabled", 1); - conf.set_create_vap_value(vap, "ssid", ""); - } - }; + } else { + // no more data in the input bss-conf list + // disable the rest of the vaps + conf.comment_vap(vap); + } + }; conf.for_all_ap_vaps(configure_func, bss_info_conf_list.begin(), bss_info_conf_list.end(), [](const std::string &) { return true; }); @@ -561,7 +538,7 @@ bool ap_wlan_hal_nl80211::update_vap_credentials( if (!wpa_ctrl_send_msg(cmd)) { LOG(ERROR) << "Autoconfiguration: \"" << cmd << "\" command to hostapd has failed"; return false; - } + } LOG(DEBUG) << "Autoconfiguration: done:\n" << conf; return true; diff --git a/common/beerocks/hostapd/include/hostapd/configuration.h b/common/beerocks/hostapd/include/hostapd/configuration.h index accbb458de..a9eff78bc4 100644 --- a/common/beerocks/hostapd/include/hostapd/configuration.h +++ b/common/beerocks/hostapd/include/hostapd/configuration.h @@ -21,7 +21,7 @@ class Configuration { * @brief construct a Configurationn object * @param file_name the name of the configuration file. */ - Configuration(const std::string &file_name); + explicit Configuration(const std::string &file_name); ~Configuration() = default; Configuration(const Configuration &) = default; @@ -216,7 +216,7 @@ class Configuration { // simply by reading the value of this variable. // the access to it is via operator bool() // m_ok itslef may be changed because of a call to - // const function, therefore it is mutable + // may be changed because of const functions, therefore mutable mutable bool m_ok = false; // each string is a line in the original configuration file diff --git a/common/beerocks/hostapd/source/configuration.cpp b/common/beerocks/hostapd/source/configuration.cpp index 891746675c..6482a447d0 100644 --- a/common/beerocks/hostapd/source/configuration.cpp +++ b/common/beerocks/hostapd/source/configuration.cpp @@ -102,6 +102,9 @@ bool Configuration::store() } } + m_ok = true; + m_last_message = m_configuration_file + " was stored"; + // close the file out_file.close(); if (out_file.fail()) { @@ -109,7 +112,6 @@ bool Configuration::store() m_ok = false; } - m_ok = true; return *this; } diff --git a/common/beerocks/hostapd/unit_tests/configuration_test.cpp b/common/beerocks/hostapd/unit_tests/configuration_test.cpp index 890e7703a7..ec97b4b296 100644 --- a/common/beerocks/hostapd/unit_tests/configuration_test.cpp +++ b/common/beerocks/hostapd/unit_tests/configuration_test.cpp @@ -192,6 +192,8 @@ void clean_start() tmp.flush(); } +// Suppress cppcheck syntax error for gtest TEST macro +// cppcheck-suppress syntaxError TEST(configuration_test, load) { //// start prerequsite ////