Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make wpa-controller tests deterministic #308

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/linux/external/hostap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ set(HOSTAP_TMP_DIR ${HOSTAP_PREFIX}/tmp)
set(HOSTAP_STAMP_DIR ${HOSTAP_PREFIX}/src/${HOSTAP_EP_NAME}-stamp)
set(HOSTAP_DOWNLOAD_DIR ${HOSTAP_PREFIX}/src)
set(HOSTAP_SOURCE_DIR ${HOSTAP_PREFIX}/src/${HOSTAP_EP_NAME})
set(HOSTAP_BINARY_DIR ${HOSTAP_PREFIX}/src/${HOSTAP_EP_NAME}-build)
set(HOSTAP_BINARY_DIR ${HOSTAP_SOURCE_DIR}/hostapd CACHE FILEPATH "hostapd binary directory" FORCE)
set(HOSTAP_INSTALL_DIR ${HOSTAP_PREFIX})
set(HOSTAP_LOG_DIR ${HOSTAP_STAMP_DIR})

Expand Down
23 changes: 23 additions & 0 deletions src/linux/wpa-controller/include/Wpa/ProtocolWpaConfig.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#error "CONFIG_WPA_CONTROL_SOCKET_PATH_BASE must be defined."
#endif

#include <Wpa/WpaCore.hxx>

namespace Wpa
{
/**
Expand All @@ -29,6 +31,27 @@ struct ProtocolWpaConfig
* @brief The path to the control sockets used by hostapd.
*/
static constexpr auto ControlSocketPathHostapd{ CONFIG_WPA_CONTROL_SOCKET_PATH_BASE "/hostapd" };

/**
* @brief Get the control socket path for the specified WPA type.
*
* @param wpaType The type of WPA daemon to get the control socket path for.
* @return constexpr auto The control socket path for the specified WPA type.
*/
static constexpr auto
GetControlSocketPath(WpaType wpaType)
{
switch (wpaType) {
case WpaType::Hostapd:
return ControlSocketPathHostapd;
case WpaType::WpaSupplicant:
return ControlSocketPathWpaSupplicant;
case WpaType::Unknown:
[[fallthrough]];
default:
return ControlSocketPathBase;
}
}
};
} // namespace Wpa

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/linux/wpa-controller/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ target_link_libraries(wpa-controller-test-unit
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/detail/config/hostapd.conf.format.hxx.in
${CMAKE_CURRENT_BINARY_DIR}/hostapd.conf.format.hxx
${CMAKE_CURRENT_SOURCE_DIR}/detail/config/HostapdBinaryInfo.hxx.in
${CMAKE_CURRENT_BINARY_DIR}/HostapdBinaryInfo.hxx
@ONLY
)

Expand Down
96 changes: 58 additions & 38 deletions tests/unit/linux/wpa-controller/detail/WpaDaemonManager.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -10,72 +10,89 @@
#include <stdexcept>
#include <string_view>

#include "WpaDaemonManager.hxx"
#include <Wpa/ProtocolWpaConfig.hxx>
#include <Wpa/WpaCore.hxx>
#include <magic_enum.hpp>
#include <plog/Log.h>

#include "hostapd.conf.format.hxx"
#include "HostapdBinaryInfo.hxx"
#include "WpaDaemonManager.hxx"

namespace detail
{
/**
* @brief Format string for the default wpa_supplicant configuration file contents.
*
* There are 1 arguments expected to be substituted into the format string:
*
* 1. The control interface path.
*/
static constexpr auto WpaDaemonWpaSupplicantConfigurationFileContentsFormat = R"CONFIG(
ctrl_interface={}
)CONFIG";

/**
* @brief Format string for the default hostapd configuration file contents.
*
* There are 2 argumentd expected to be substituted into the format string:
*
* 1. The wlan interface name.
* 2. The control interface path.
*/
static constexpr auto WpaDaemonHostapdConfigurationFileContentsFormat = R"CONFIG(
interface={}
driver=nl80211
ctrl_interface={}
ssid=wificontrollertest
hw_mode=g
channel=1
auth_algs=3
wpa=2
wpa_passphrase=password
wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
)CONFIG";

/**
* @brief Write the default configuration file contents for the specified wpa
* daemon type to the specified file stream.
*
* @param wpaType The type of wpa daemon to write the configuration file for.
* @param interfaceName The wlan interface the daemon will be managing.
* @param controlSocketPath The path to the control socket for the daemon.
* @param configurationFile The file stream to write the configuration file to.
*/
void
WriteDefaultConfigurationFileContents(Wpa::WpaType wpaType, std::string_view interfaceName, std::ofstream& configurationFile)
WriteDefaultConfigurationFileContents(Wpa::WpaType wpaType, std::string_view interfaceName, std::string_view controlSocketPath, std::ofstream& configurationFile)
{
switch (wpaType) {
case Wpa::WpaType::Hostapd: {
configurationFile << std::format(WpaDaemonHostapdConfigurationFileContentsFormat, interfaceName);
case Wpa::WpaType::Hostapd:
configurationFile << std::format(WpaDaemonHostapdConfigurationFileContentsFormat, interfaceName, controlSocketPath);
break;
}
default: {
case Wpa::WpaType::WpaSupplicant:
configurationFile << std::format(WpaDaemonWpaSupplicantConfigurationFileContentsFormat, controlSocketPath);
break;
case Wpa::WpaType::Unknown:
[[fallthrough]];
default:
throw std::runtime_error(std::format("Unsupported wpa daemon type '{}'", magic_enum::enum_name(wpaType)));
}
}
}
} // namespace detail

/* static */
std::filesystem::path
WpaDaemonManager::FindDaemonBinary(Wpa::WpaType wpaType, const std::filesystem::path& searchPath)
{
using std::filesystem::perms;

const auto daemon = Wpa::GetWpaTypeDaemonBinaryName(wpaType);

LOGI << std::format("Searching for hostapd daemon binary '{}' in '{}'\n", daemon, searchPath.c_str());

for (const auto& directoryEntry : std::filesystem::recursive_directory_iterator(searchPath)) {
if (directoryEntry.is_regular_file() && directoryEntry.path().filename() == daemon) {
const auto permissions = directoryEntry.status().permissions();
if ((permissions & (perms::owner_exec | perms::group_exec | perms::others_exec)) != perms::none) {
return directoryEntry.path();
}
}
}

return {};
}

/* static */
std::filesystem::path
WpaDaemonManager::CreateAndWriteDefaultConfigurationFile(Wpa::WpaType wpaType, std::string_view interfaceName)
{
// Determine which daemon to create the configuration file for.
const auto daemon = Wpa::GetWpaTypeDaemonBinaryName(wpaType);
const auto daemonConfigurationFilePath = std::filesystem::temp_directory_path() / std::format("{}.conf", daemon);
const auto daemonControlSocketPath{ Wpa::ProtocolWpaConfig::GetControlSocketPath(wpaType) };

// Create and write default configuration file contents.
std::ofstream daemonConfigurationFile{ daemonConfigurationFilePath, std::ios::out | std::ios::trunc };
detail::WriteDefaultConfigurationFileContents(wpaType, interfaceName, daemonConfigurationFile);
detail::WriteDefaultConfigurationFileContents(wpaType, interfaceName, daemonControlSocketPath, daemonConfigurationFile);
daemonConfigurationFile.flush();
daemonConfigurationFile.close();

Expand All @@ -92,6 +109,15 @@ WpaDaemonManager::Start(Wpa::WpaType wpaType, std::string_view interfaceName, co
interfaceName = WpaDaemonManager::InterfaceNameDefault;
}

// Create the control socket path if it doesn't exist.
const std::filesystem::path controlSocketPath{ Wpa::ProtocolWpaConfig::GetControlSocketPath(wpaType) };
if (!std::filesystem::exists(controlSocketPath)) {
if (!std::filesystem::create_directories(controlSocketPath)) {
LOGE << std::format("Failed to create control socket path '{}'\n", controlSocketPath.c_str());
return std::nullopt;
}
}

// Determine which daemon to start and formulate daemon binary arguments.
const auto daemon = Wpa::GetWpaTypeDaemonBinaryName(wpaType);
const auto* configurationFileArgumentPrefix = (wpaType == Wpa::WpaType::WpaSupplicant) ? "-c" : "";
Expand Down Expand Up @@ -147,13 +173,7 @@ WpaDaemonManager::StartDefault(Wpa::WpaType wpaType, std::string_view interfaceN
return std::nullopt;
}

auto daemonFilePath = FindDaemonBinary(wpaType);
if (daemonFilePath.empty()) {
LOGE << std::format("Failed to find wpa '{}' daemon binary\n", magic_enum::enum_name(wpaType));
return std::nullopt;
}

return Start(wpaType, interfaceName, daemonFilePath, configurationFilePath);
return Start(wpaType, interfaceName, detail::HostapdBinaryPath, configurationFilePath);
}

/* static */
Expand Down
9 changes: 0 additions & 9 deletions tests/unit/linux/wpa-controller/detail/WpaDaemonManager.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,6 @@ struct WpaDaemonManager
*/
static constexpr auto ControlSocketPathBase{ "/run/" };

/**
* @brief Attempts to find the binary for the specified wpa daemon type.
*
* @param wpaType The type of wpa daemon to find the binary for.
* @return std::filesystem::path The path to the daemon binary, if found. Otherwise, an empty path.
*/
static std::filesystem::path
FindDaemonBinary(Wpa::WpaType wpaType, const std::filesystem::path& searchPath = std::filesystem::current_path());

/**
* @brief Create and write a default configuration file to disk for the
* specified wpa daemon type. The configuration file will be written to the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#ifndef HOSTAPD_BINARY_INFO_HXX
#define HOSTAPD_BINARY_INFO_HXX

/**
* Note: this is a generated file. Do not attempt to change it as any changes will be lost.
*/
#include <filesystem>

namespace detail
{
/**
* @brief The path to hostapd binaries.
*/
static const std::filesystem::path HostapdBinaryDirectory{ "@HOSTAP_BINARY_DIR@" };

/**
* @brief The path to the hostapd daemon binary.
*/
static const std::filesystem::path HostapdBinaryPath{ HostapdBinaryDirectory / "hostapd" };
} // namespace detail

#endif // HOSTAPD_BINARY_INFO_HXX

This file was deleted.