Skip to content
This repository was archived by the owner on Sep 7, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b8ff8d6
prplmesh-hostapd: info: added README.md file
RanRegev Jul 7, 2020
0ed7fba
prplmesh-hostapd: added configuration class
RanRegev Jul 8, 2020
d936489
prplmesh-hostapd: added a unit test file
RanRegev Jul 8, 2020
328d2c9
prplmesh-hostapd: added cmake for prplmesh hostapd
RanRegev Jul 8, 2020
4c53b57
prplmesh-hostapd: added set_create_vap_value()
RanRegev Jul 9, 2020
b4c53d8
prplmesh-hostapd: added set_create_vap_value() overload with int
RanRegev Jul 9, 2020
bdcd28d
prplmesh-hostapd: added store()
RanRegev Jul 9, 2020
9c4327e
prplmesh-hostapd: added get_vap_value()
RanRegev Jul 9, 2020
9beae66
prplmesh-hostapd: added disable_all_ap_vaps()
RanRegev Jul 12, 2020
955cfed
prplmesh-hostapd: added private help functions
RanRegev Jul 12, 2020
ead6021
prplmesh-hostapd: added comment_vap()
RanRegev Jul 12, 2020
edd5bc2
prplmesh-hostapd: added comment ability
RanRegev Jul 13, 2020
03adfc7
prplmesh-hostapd: added uncomment_vap()
RanRegev Jul 13, 2020
39b1f33
prplmesh-hostapd: added for_all_ap_vaps()
RanRegev Jul 13, 2020
15ee90f
prplmesh-hostapd: added for_all_ap_vaps() with user iterator
RanRegev Jul 13, 2020
10775b8
prplmesh-hostapd: added test enable_all_vaps()
RanRegev Jul 15, 2020
9fdd0c3
prplmesh-hostapd: added test iterate-both-containers
RanRegev Jul 15, 2020
6399e7b
prplmesh-hostapd: few cleanups after rebase
RanRegev Jul 15, 2020
6bd96dd
prplmesh-hostapd: added set_create_xx_head_value
RanRegev Jul 15, 2020
f2f33cd
prplmesh-hostapd: added get_head_value()
RanRegev Jul 16, 2020
933fe63
prplmesh-hostapd: added vap_indication to load()
RanRegev Jul 20, 2020
72cc86f
prplmesh-hostapd: added ap predicate to for_all_ap_vaps
RanRegev Jul 20, 2020
c21d2be
prplmesh-hostapd: PR 1514 review fixups
RanRegev Jul 30, 2020
52b7859
prplmesh-hostapd: added ap predicate to for_all_ap_vaps
RanRegev Jul 20, 2020
629bf66
nl80211: hostapd configuration: cmake
RanRegev Jul 23, 2020
4c321eb
nl80211: hostapd configuration update_vap_credentials()
RanRegev Jul 23, 2020
b87c6f4
nl80211: hostapd configuration: close issues found in PR 1550
RanRegev Jul 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions common/beerocks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
add_subdirectory(bcl)
add_subdirectory(bwl)
add_subdirectory(tlvf)
add_subdirectory(btl)
add_subdirectory(bwl)
add_subdirectory(hostapd)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit message says "created CMakeLists.txt" but the commit actually adds the CMakeLists.txt created in the previous commit to the parent, and sorts the add_subdirectory calls alphabetically. The latter should be mentioned in the commit message as well.

But there's really no need to split off the creation of CMakeLists.txt or adding it to the parent in a separate commit.

Similarly, when a unit test is added, it's better to add it to CI in the same commit.

By the way, you may want to re-read https://github.com/prplfoundation/prplMesh/blob/master/CONTRIBUTING.md#commits. About the subject line:

  • Write it in the imperative: "add support for X".
  • Avoid verbs that don't say anything: "fix", "improve", "update", ...

And about the body:

  • the (core) change itself - in the imperative (i.e. "add foo()", not ("added foo()");

And it's not written explicilty there, but the examples show that the JIRA URL should be a separate paragraph.

All of this is not very important, i.e. not worth spending your time on fixing all the commit messages are commits. Just something to take into account for the future.

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)
add_dependencies(btl btlvf)
4 changes: 3 additions & 1 deletion common/beerocks/bwl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -179,7 +181,7 @@ if (BUILD_TESTS)
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
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 $<TARGET_FILE:${TEST_PROJECT_NAME}>)
Expand Down
284 changes: 278 additions & 6 deletions common/beerocks/bwl/nl80211/ap_wlan_hal_nl80211.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,21 @@
*/

#include "ap_wlan_hal_nl80211.h"

#include <bcl/beerocks_os_utils.h>
#include <bcl/beerocks_utils.h>
#include <bcl/beerocks_version.h>
#include <bcl/network/network_utils.h>
#include <bcl/son/son_wireless_utils.h>

#include <easylogging++.h>

#include <cmath>

#include <easylogging++.h>
#include <hostapd/configuration.h>
#include <linux/nl80211.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/genl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <type_traits>

//////////////////////////////////////////////////////////////////////////////
////////////////////////// Local Module Definitions //////////////////////////
Expand Down Expand Up @@ -106,6 +104,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<std::string> 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 ///////////////////////////////
//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -268,7 +306,241 @@ bool ap_wlan_hal_nl80211::update_vap_credentials(
std::list<son::wireless_utils::sBssInfoConf> &bss_info_conf_list,
const std::string &backhaul_wps_ssid, const std::string &backhaul_wps_passphrase)
{
//TODO Implement #346
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
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::remove_reference<decltype(bss_info_conf_list)>::type::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;
}

// 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<uint16_t>(bss_it->encryption_type) &
static_cast<uint16_t>(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, "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);
}

// 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", "");
}

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.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; });

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;
}

Expand Down
48 changes: 48 additions & 0 deletions common/beerocks/hostapd/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

project(prplmesh_hostapd VERSION ${prplmesh_VERSION})

# Set the base path for the current module
set(MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})

# 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})
target_include_directories(${PROJECT_NAME}
PRIVATE
${PLATFORM_INCLUDE_DIR}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

install(
TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if (BUILD_TESTS)
set(TEST_PROJECT_NAME ${PROJECT_NAME}_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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
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 $<TARGET_FILE:${TEST_PROJECT_NAME}>)
endif()
Loading