diff --git a/src/linux/external/hostap/hostapd/.config b/src/linux/external/hostap/hostapd/.config index c4352c95..8a0f94a7 100644 --- a/src/linux/external/hostap/hostapd/.config +++ b/src/linux/external/hostap/hostapd/.config @@ -149,6 +149,7 @@ CONFIG_IPV6=y # IEEE Std 802.11r-2008 (Fast BSS Transition) # [Manually enabled] CONFIG_IEEE80211R=y +CONFIG_IEEE80211R_AP=y # Use the hostapd's IEEE 802.11 authentication (ACL), but without # the IEEE 802.11 Management capability (e.g., FreeBSD/net80211) diff --git a/src/linux/wifi/core/AccessPointControllerLinux.cxx b/src/linux/wifi/core/AccessPointControllerLinux.cxx index 11075853..a21670ce 100644 --- a/src/linux/wifi/core/AccessPointControllerLinux.cxx +++ b/src/linux/wifi/core/AccessPointControllerLinux.cxx @@ -30,6 +30,7 @@ using namespace Microsoft::Net::Wifi; using Microsoft::Net::Netlink::Nl80211::Nl80211Wiphy; +using Wpa::EnforceConfigurationChange; AccessPointControllerLinux::AccessPointControllerLinux(std::string_view interfaceName) : AccessPointController(interfaceName), @@ -174,7 +175,7 @@ AccessPointControllerLinux::SetProtocol(Ieee80211Protocol ieeeProtocol) noexcept try { for (auto& propertyToSet : propertiesToSet) { std::tie(propertyKeyToSet, propertyValueToSet) = std::move(propertyToSet); - hostapdOperationSucceeded = m_hostapd.SetProperty(propertyKeyToSet, propertyValueToSet); + hostapdOperationSucceeded = m_hostapd.SetProperty(propertyKeyToSet, propertyValueToSet, EnforceConfigurationChange::Defer); if (!hostapdOperationSucceeded) { break; } @@ -233,7 +234,7 @@ AccessPointControllerLinux::SetFrequencyBands(std::vector #include +#include #include #include +#include +#include #include #include @@ -12,6 +16,7 @@ #include #include #include +#include #include using namespace Wpa; @@ -41,38 +46,30 @@ Hostapd::Ping() return response->Payload().starts_with(ProtocolHostapd::ResponsePayloadPing); } -HostapdStatus -Hostapd::GetStatus() +bool +Hostapd::Reload() { - static constexpr WpaCommandStatus StatusCommand; + static constexpr WpaCommand ReloadCommand(ProtocolHostapd::CommandPayloadReload); - auto response = m_controller.SendCommand(StatusCommand); + const auto response = m_controller.SendCommand(ReloadCommand); if (!response) { - throw HostapdException("Failed to send hostapd 'status' command"); + throw HostapdException("Failed to send hostapd 'reload' command"); } - return response->Status; + return response->IsOk(); } -bool -Hostapd::SetSsid(std::string_view ssid, bool reload) +HostapdStatus +Hostapd::GetStatus() { - const bool ssidWasSet = SetProperty(ProtocolHostapd::PropertyNameSsid, ssid); - if (!ssidWasSet) { - throw HostapdException("Failed to set hostapd 'ssid' property"); - } - - if (!reload) { - LOGW << "Skipping hostapd reload after setting 'ssid' (requested)"; - return true; - } + static constexpr WpaCommandStatus StatusCommand; - const bool configurationReloadSucceeded = Reload(); - if (!configurationReloadSucceeded) { - throw HostapdException("Failed to reload hostapd configuration"); + auto response = m_controller.SendCommand(StatusCommand); + if (!response) { + throw HostapdException("Failed to send hostapd 'status' command"); } - return true; + return response->Status; } bool @@ -95,9 +92,9 @@ Hostapd::GetProperty(std::string_view propertyName, std::string& propertyValue) } bool -Hostapd::SetProperty(std::string_view propertyName, std::string_view propertyValue) +Hostapd::SetProperty(std::string_view propertyName, std::string_view propertyValue, EnforceConfigurationChange enforceConfigurationChange) { - LOGD << std::format("Attempting to set hostapd property '{}'({}) to '{}'({})", propertyName, std::size(propertyName), propertyValue, std::size(propertyValue)); + LOGD << std::format("Attempting to set hostapd property '{}' (size={}) to '{}' (size={})", propertyName, std::size(propertyName), propertyValue, std::size(propertyValue)); const WpaCommandSet setCommand(propertyName, propertyValue); const auto response = m_controller.SendCommand(setCommand); @@ -105,7 +102,22 @@ Hostapd::SetProperty(std::string_view propertyName, std::string_view propertyVal throw HostapdException("Failed to send hostapd 'set' command"); } - return response->IsOk(); + if (!response->IsOk()) { + LOGV << std::format("Invalid response received when setting hostapd property '{}' to '{}' (payload={})", propertyName, propertyValue, response->Payload()); + throw HostapdException(std::format("Failed to set hostapd property '{}' to '{}'", propertyName, propertyValue)); + } + + if (enforceConfigurationChange == EnforceConfigurationChange::Defer) { + LOGD << std::format("Skipping enforcement of '{}' configuration change (requested)", propertyName); + return true; + } + + const bool configurationReloadSucceeded = Reload(); + if (!configurationReloadSucceeded) { + throw HostapdException(std::format("Failed to reload hostapd configuration after '{}' property change", propertyName)); + } + + return true; } bool @@ -166,14 +178,65 @@ Hostapd::Terminate() } bool -Hostapd::Reload() +Hostapd::SetSsid(std::string_view ssid, EnforceConfigurationChange enforceConfigurationChange) { - static constexpr WpaCommand ReloadCommand(ProtocolHostapd::CommandPayloadReload); + const bool ssidWasSet = SetProperty(ProtocolHostapd::PropertyNameSsid, ssid, enforceConfigurationChange); + return ssidWasSet; +} - const auto response = m_controller.SendCommand(ReloadCommand); - if (!response) { - throw HostapdException("Failed to send hostapd 'reload' command"); +bool +Hostapd::SetWpaProtocols(std::vector protocols, EnforceConfigurationChange enforceConfigurationChange) +{ + if (std::empty(protocols)) { + throw HostapdException("No WPA protocols were provided"); } - return response->IsOk(); + uint32_t protocolsToSet = 0; + for (const auto protocol : protocols) { + const auto protocolValue = std::to_underlying(protocol); + if ((protocolValue & ~WpaProtocolMask) != 0) { + throw HostapdException(std::format("Invalid WPA protocol value '{}'", protocolValue)); + } + protocolsToSet |= protocolValue; + } + + protocolsToSet &= WpaProtocolMask; + + const auto protocolsValue = std::format("{}", protocolsToSet); + const bool protocolsWereSet = SetProperty(ProtocolHostapd::PropertyNameWpaProtocol, protocolsValue, enforceConfigurationChange); + if (!protocolsWereSet) { + throw HostapdException(std::format("Failed to set hostapd 'wpa' property to '{}'", protocolsValue)); + } + + return true; +} + +bool +Hostapd::SetKeyManagement(std::vector keyManagements, EnforceConfigurationChange enforceConfigurationChange) +{ + if (std::empty(keyManagements)) { + throw HostapdException("No WPA key management values were provided"); + } + + // Convert the key management values to a space-delimited string of individual key management values expected by hostapd. + std::string keyManagementPropertyValue; + { + std::ostringstream keyManagementPropertyValueBuilder{}; + for (const auto keyManagement : keyManagements) { + const auto keyManagementValue = WpaKeyManagementPropertyValue(keyManagement); + if (keyManagementValue == WpaKeyManagementInvalidValue) { + throw HostapdException(std::format("Invalid WPA key management value '{}'", magic_enum::enum_name(keyManagement))); + } + keyManagementPropertyValueBuilder << keyManagementValue << ' '; + } + + keyManagementPropertyValue = keyManagementPropertyValueBuilder.str(); + } + + const auto keyManagementWasSet = SetProperty(ProtocolHostapd::PropertyNameWpaKeyManagement, keyManagementPropertyValue, enforceConfigurationChange); + if (!keyManagementWasSet) { + throw HostapdException(std::format("Failed to set hostapd 'wpa_key_mgmt' property to '{}'", keyManagementPropertyValue)); + } + + return true; } diff --git a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx index ebe3b2b0..16984a10 100644 --- a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -7,6 +7,7 @@ #include #include +#include #include namespace Wpa @@ -57,6 +58,15 @@ struct Hostapd : bool Ping() override; + /** + * @brief Reloads the interface. This will cause any settings that have been changed to take effect. + * + * @return true If the configuration was reloaded successfully. + * @return false If the configuration was not reloaded successfully. + */ + bool + Reload() override; + /** * @brief Get the name of the interface hostapd is managing. * @@ -73,16 +83,6 @@ struct Hostapd : HostapdStatus GetStatus() override; - /** - * @brief Set the ssid of the access point. - * - * @param ssid The ssid to set. - * @return true - * @return false - */ - bool - SetSsid(std::string_view ssid, bool reload = true); - /** * @brief Get a property value for the interface. * @@ -99,20 +99,45 @@ struct Hostapd : * * @param propertyName The name of the property to set. * @param propertyValue The value of the property to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. * @return true The property was set successfully. * @return false The property was not set successfully. */ bool - SetProperty(std::string_view propertyName, std::string_view propertyValue) override; + SetProperty(std::string_view propertyName, std::string_view propertyValue, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Now) override; /** - * @brief Reloads the interface. + * @brief Set the ssid for the interface. * - * @return true - * @return false + * @param ssid The ssid to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. + * @return true If the ssid was set successfully. + * @return false If the ssid was not set successfully. */ bool - Reload() override; + SetSsid(std::string_view ssid, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Now) override; + + /** + * @brief Set the WPA protocol(s) for the interface. + * + * @param protocols The protocols to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. + * @return true If the protocols were set successfully. + * @return false If the protocols were not set successfully. + */ + bool + SetWpaProtocols(std::vector protocols, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Now) override; + + /** + * @brief Set the Key Management object + * + * @param keyManagements The key management value(s) to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. + * @return true The key management value(s) were set successfully. + * @return false The key management value(s) were not set successfully. + */ + bool + SetKeyManagement(std::vector keyManagements, EnforceConfigurationChange enforceConfigurationChange = EnforceConfigurationChange::Now) override; private: const std::string m_interface; diff --git a/src/linux/wpa-controller/include/Wpa/IHostapd.hxx b/src/linux/wpa-controller/include/Wpa/IHostapd.hxx index 710c4100..295fc67f 100644 --- a/src/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -5,11 +5,21 @@ #include #include #include +#include #include +#include namespace Wpa { +/** + * @brief Syntax sugar for specifying when configuration change should be enforced. + */ +enum class EnforceConfigurationChange { + Now, + Defer, +}; + /** * @brief Interface for interacting with the hostapd daemon. * @@ -30,9 +40,14 @@ struct IHostapd * Prevent copying and moving of IHostapd objects. */ IHostapd(const IHostapd&) = delete; - IHostapd& operator=(const IHostapd&) = delete; + IHostapd(IHostapd&&) = delete; - IHostapd& operator=(IHostapd&&) = delete; + + IHostapd& + operator=(const IHostapd&) = delete; + + IHostapd& + operator=(IHostapd&&) = delete; /** * @brief Enables the interface for use. @@ -67,6 +82,15 @@ struct IHostapd virtual bool Ping() = 0; + /** + * @brief Reloads the interface. This will cause any settings that have been changed to take effect. + * + * @return true If the configuration was reloaded successfully. + * @return false If the configuration was not reloaded successfully. + */ + virtual bool + Reload() = 0; + /** * @brief Get the name of the interface hostapd is managing. * @@ -99,20 +123,44 @@ struct IHostapd * * @param propertyName The name of the property to set. * @param propertyValue The value of the property to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. * @return true The property was set successfully. * @return false The property was not set successfully. */ virtual bool - SetProperty(std::string_view propertyName, std::string_view propertyValue) = 0; + SetProperty(std::string_view propertyName, std::string_view propertyValue, EnforceConfigurationChange enforceConfigurationChange) = 0; /** - * @brief Reloads the interface. + * @brief Set the ssid for the interface. * - * @return true - * @return false + * @param ssid The ssid to set. + * @return true If the ssid was set successfully. + * @return false If the ssid was not set successfully. */ virtual bool - Reload() = 0; + SetSsid(std::string_view ssid, EnforceConfigurationChange enforceConfigurationChange) = 0; + + /** + * @brief Set the WPA protocol(s) for the interface. + * + * @param protocols The protocols to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. + * @return true If the protocols were set successfully. + * @return false If the protocols were not set successfully. + */ + virtual bool + SetWpaProtocols(std::vector protocols, EnforceConfigurationChange enforceConfigurationChange) = 0; + + /** + * @brief Set the Key Management object + * + * @param keyManagements The key management value(s) to set. + * @param enforceConfigurationChange When the enforce the configuration change. A value of 'Now' will trigger a configuration reload. + * @return true The key management value(s) were set successfully. + * @return false The key management value(s) were not set successfully. + */ + virtual bool + SetKeyManagement(std::vector keyManagements, EnforceConfigurationChange enforceConfigurationChange) = 0; }; /** diff --git a/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index 6d6d426b..5cc2acfa 100644 --- a/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,121 @@ enum class HostapdHwMode { Ieee80211any, }; +/** + * @brief WPA encoding of IEEE 802.11i-2004 protocol types. + * + * Note that hostapd configuration doesn't directly encode WPA3 in this enumeration. Instead, a combination of the + * 'Wpa2' value below with the 'wpa_key_mgmt' property set to 'SAE' or 'FT-SAE'. + * + * Values obtained from hostap/src/common/defs.h. + */ +enum class WpaProtocol : uint32_t { + Wpa = (1U << 0U), + Wpa2 = (1U << 1U), + Wapi = (1U << 2U), + Osen = (1U << 3U), + // Aliases + Rsn = Wpa2, +}; + +/** + * @brief Numerical bitmask of valid WpaProtocol values. + */ +static constexpr auto WpaProtocolMask = + std::to_underlying(WpaProtocol::Wpa) | + std::to_underlying(WpaProtocol::Wpa2) | + std::to_underlying(WpaProtocol::Wapi) | + std::to_underlying(WpaProtocol::Osen); + +/** + * @brief WPA encoding of IEEE 802.11 cipher types. + * + * Values obtained from hostap/src/common/defs.h. + */ +enum class WpaCipher : uint32_t { + None = (1U << 0U), + Wep40 = (1U << 1U), + Wep104 = (1U << 2U), + Tkip = (1U << 3U), + Ccmp = (1U << 4U), + Aes128Cmac = (1U << 5U), + Gcmp = (1U << 6U), + Sms4 = (1U << 7U), + Gcmp256 = (1U << 8U), + Ccmp256 = (1U << 9U), + // (1U << 10U) intentionally skipped + BipGmac128 = (1U << 11U), + BipGmac256 = (1U << 12U), + BipCmac256 = (1U << 13U), + GtkNotUsed = (1U << 14U), +}; + +/** + * @brief WPA encoding of IEEE 802.11 algorithm types. + * + * Values obtained from hostap/src/common/defs.h. + */ +enum class WpaAlgorithm : uint32_t { + None = 0, + Wep, + Tkip, + Ccmp, + BipCmac128, + Gcmp, + Sms4, + Krk, + Gcmp256, + Ccmp256, + BipGmac128, + BipGmac256, + BipCmac256, +}; + +/** + * @brief WPA encoding of IEEE 802.11 key management types. + * + * Values obtained from hostap/src/common/defs.h. + */ +enum class WpaKeyManagement : uint32_t { + Ieee80211x = (1U << 0U), + Psk = (1U << 1U), + None = (1U << 2U), + Ieee80211xNoWpa = (1U << 3U), + WpaNone = (1U << 4U), + FtIeee8021x = (1U << 5U), + FtPsk = (1U << 6U), + Ieee8021xSha256 = (1U << 7U), + PskSha256 = (1U << 8U), + Wps = (1U << 9U), + Sae = (1U << 10U), + FtSae = (1U << 11U), + WapiPsk = (1U << 12U), + WapiCert = (1U << 13U), + Cckm = (1U << 14U), + Osen = (1U << 15U), + Ieee80211xSuiteB = (1U << 16U), + Ieee80211xSuiteB192 = (1U << 17U), + FilsSha256 = (1U << 18U), + FilsSha384 = (1U << 19U), + FtFilsSha256 = (1U << 20U), + FtFilsSha384 = (1U << 21U), + Owe = (1U << 22U), + Dpp = (1U << 23U), + FtIeee8021xSha384 = (1U << 24U), + Pasn = (1U << 25U), +}; + +/** + * @brief All valid WpaKeyManagement values supporting fast-transition (FT). + */ +static constexpr auto WpaKeyManagementFt = + std::to_underlying(WpaKeyManagement::Ieee80211x) | + std::to_underlying(WpaKeyManagement::FtIeee8021x) | + std::to_underlying(WpaKeyManagement::FtIeee8021xSha384) | + std::to_underlying(WpaKeyManagement::FtSae) | + std::to_underlying(WpaKeyManagement::FtFilsSha256) | + std::to_underlying(WpaKeyManagement::FtFilsSha384); + struct MulticastListenerDiscoveryProtocolInfo { int Id; @@ -220,6 +336,12 @@ struct ProtocolHostapd : static constexpr auto PropertyNameBssSsid = "ssid"; static constexpr auto PropertyNameBssNumStations = "num_sta"; + static constexpr auto PropertyNameWpaKeyManagement = "wpa_key_mgmt"; + static constexpr auto PropertyNameWpaPairwise = "wpa_pairwise"; + static constexpr auto PropertyNameWpaPassphrase = "wpa_passphrase"; + static constexpr auto PropertyNameWpaPsk = "wpa_psk"; + static constexpr auto PropertyNameRsnPairwise = "rsn_pairwise"; + // Response properties for the "STATUS" command. // Note: all properties must be terminated with the key-value delimeter (=). static constexpr auto ResponseStatusPropertyKeyState = PropertyNameState; @@ -251,6 +373,79 @@ HostapdInterfaceStateFromString(std::string_view state) noexcept; */ bool IsHostapdStateOperational(HostapdInterfaceState state) noexcept; + +/** + * @brief WpaKeyManagement sentinel for an invalid value. + */ +constexpr std::string_view WpaKeyManagementInvalidValue = "UNKNOWN"; + +/** + * @brief Convert a WpaKeyManagement value to the corresponding property value string expected by hostapd. + * + * @param wpaKeyManagement The WpaKeyManagement value to convert. + * @return constexpr std::string_view The corresponding hostapd property value string. + */ +constexpr std::string_view +WpaKeyManagementPropertyValue(WpaKeyManagement wpaKeyManagement) noexcept +{ + switch (wpaKeyManagement) { + case WpaKeyManagement::Ieee80211x: + return "WPA-EAP"; + case WpaKeyManagement::Psk: + return "WPA-PSK"; + case WpaKeyManagement::FtIeee8021x: + return "FT-EAP"; + case WpaKeyManagement::FtPsk: + return "FT-PSK"; + case WpaKeyManagement::Ieee8021xSha256: + return "WPA-EAP-SHA256"; + case WpaKeyManagement::PskSha256: + return "WPA-PSK-SHA256"; + case WpaKeyManagement::Sae: + return "SAE"; + case WpaKeyManagement::FtSae: + return "FT-SAE"; + case WpaKeyManagement::Osen: + return "OSEN"; + case WpaKeyManagement::Ieee80211xSuiteB: + return "WPA-EAP-SUITE-B"; + case WpaKeyManagement::Ieee80211xSuiteB192: + return "WPA-EAP-SUITE-B-192"; + case WpaKeyManagement::FilsSha256: + return "FILS-SHA256"; + case WpaKeyManagement::FilsSha384: + return "FILS-SHA384"; + case WpaKeyManagement::FtFilsSha256: + return "FT-FILS-SHA256"; + case WpaKeyManagement::FtFilsSha384: + return "FT-FILS-SHA384"; + case WpaKeyManagement::Owe: + return "OWE"; + case WpaKeyManagement::Dpp: + return "DPP"; + case WpaKeyManagement::FtIeee8021xSha384: + return "FT-EAP-SHA384"; + case WpaKeyManagement::Pasn: + return "PASN"; + // Below values are not accepted for nor parsed from the configuration file. + case WpaKeyManagement::None: + [[fallthrough]]; + case WpaKeyManagement::Ieee80211xNoWpa: + [[fallthrough]]; + case WpaKeyManagement::WpaNone: + [[fallthrough]]; + case WpaKeyManagement::Wps: + [[fallthrough]]; + case WpaKeyManagement::WapiPsk: + [[fallthrough]]; + case WpaKeyManagement::WapiCert: + [[fallthrough]]; + case WpaKeyManagement::Cckm: + [[fallthrough]]; + default: + return WpaKeyManagementInvalidValue; + } +} } // namespace Wpa #endif // HOSTAPD_PROTOCOL_HXX diff --git a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx index 2f4ef926..75c8ce39 100644 --- a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx +++ b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx @@ -2,7 +2,9 @@ #ifndef PROTOCOL_WPA_HXX #define PROTOCOL_WPA_HXX +#include #include +#include namespace Wpa { @@ -50,6 +52,9 @@ struct ProtocolWpa static constexpr auto CommandPayloadLogLevel = "LOG_LEVEL"; static constexpr auto CommandPayloadReload = "RELOAD"; + // Common property names. + static constexpr auto PropertyNameWpaProtocol = "wpa"; + // Response payloads. static constexpr auto ResponsePayloadOk = "OK"; static constexpr auto ResponsePayloadFail = "FAIL"; diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index 77c11ded..e3e85f76 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -1,9 +1,12 @@ -#include +#include // NOLINT +#include +#include #include #include #include #include +#include #include #include @@ -131,7 +134,7 @@ TEST_CASE("Send command: GetStatus() (root)", "[wpa][hostapd][client][remote]") const auto ieee80211acInitial = hostapd.GetStatus().Ieee80211ac; - auto ieee80211acValueExpected = static_cast(ieee80211acInitial); + auto ieee80211acValueExpected = static_cast(ieee80211acInitial); REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameIeee80211AC, GetPropertyEnablementValue(ieee80211acValueExpected))); auto ieee80211acValueUpdated = hostapd.GetStatus().Ieee80211ac; REQUIRE(ieee80211acValueUpdated == ieee80211acValueExpected); @@ -203,21 +206,53 @@ TEST_CASE("Send SetProperty() command (root)", "[wpa][hostapd][client][remote]") { using namespace Wpa; + static constexpr auto PropertyNameInvalid{ "whatever" }; + static constexpr auto PropertyValueInvalid{ "whatever" }; + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); SECTION("SetProperty() doesn't throw") { - REQUIRE_NOTHROW(hostapd.SetProperty("whatever", "whatever")); + REQUIRE_NOTHROW(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + REQUIRE_NOTHROW(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Defer)); } SECTION("SetProperty() returns false for invalid property") { - REQUIRE_FALSE(hostapd.SetProperty("whatever", "whatever")); + REQUIRE_THROWS_AS(hostapd.SetProperty(PropertyNameInvalid, PropertyValueInvalid, EnforceConfigurationChange::Now), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetProperty(PropertyNameInvalid, PropertyValueInvalid, EnforceConfigurationChange::Defer), HostapdException); } SECTION("SetProperty() returns true for valid property") { - REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Defer)); + } + + SECTION("SetProperty() allows setting a property to the same value") + { + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Defer)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Defer)); + } + + SECTION("SetProperty allows setting a property to a different value") + { + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue2G, EnforceConfigurationChange::Now)); + + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue5G, EnforceConfigurationChange::Defer)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue6G, EnforceConfigurationChange::Defer)); + } + + SECTION("SetProperty allows interleaving enforcement of configuration changes") + { + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValueAuto, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue2G, EnforceConfigurationChange::Defer)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue5G, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetProperty(ProtocolHostapd::PropertyNameSetBand, ProtocolHostapd::PropertySetBandValue6G, EnforceConfigurationChange::Defer)); } // TODO: validate that the property was actually set. Need to find a property whose value is retrievable. @@ -288,7 +323,7 @@ TEST_CASE("Send command: Terminate() ping failure (root)", "[wpa][hostapd][clien using namespace Wpa; using namespace std::chrono_literals; - static constexpr auto TerminationWaitTime{ 2s }; + static constexpr auto TerminationWaitTime{ 2s }; // NOLINT Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); REQUIRE(hostapd.Ping()); @@ -356,3 +391,147 @@ TEST_CASE("Send SetSsid() command (root)", "[wpa][hostapd][client][remote]") REQUIRE(statusAfterFail.Bss[0].Ssid.starts_with(statusInitial.Bss[0].Ssid)); } } + +TEST_CASE("Send SetWpaProtocols() command (root)", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + static constexpr auto WpaProtocolInvalid = static_cast(std::numeric_limits>::max()); + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + + SECTION("Doesn't throw") + { + REQUIRE_NOTHROW(hostapd.SetWpaProtocols({ WpaProtocol::Wpa }, EnforceConfigurationChange::Now)); + REQUIRE_NOTHROW(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Now)); + REQUIRE_NOTHROW(hostapd.SetWpaProtocols({ WpaProtocol::Wpa }, EnforceConfigurationChange::Defer)); + REQUIRE_NOTHROW(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Defer)); + } + + SECTION("Fails with empty input") + { + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({}, EnforceConfigurationChange::Now), HostapdException); + } + + SECTION("Fails with invalid input") + { + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocolInvalid }, EnforceConfigurationChange::Now), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocolInvalid }, EnforceConfigurationChange::Now), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocolInvalid, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Now), HostapdException); + + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocolInvalid }, EnforceConfigurationChange::Defer), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocolInvalid }, EnforceConfigurationChange::Defer), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocolInvalid, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Defer), HostapdException); + } + + SECTION("Succeeds with valid, single input") + { + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa }, EnforceConfigurationChange::Defer)); + } + + SECTION("Succeeds with valid, multiple inputs") + { + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Defer)); + } + + SECTION("Succeeds with valid, duplicate inputs") + { + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa, WpaProtocol::Wpa }, EnforceConfigurationChange::Defer)); + + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa2, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetWpaProtocols({ WpaProtocol::Wpa2, WpaProtocol::Wpa2 }, EnforceConfigurationChange::Defer)); + } +} + +TEST_CASE("Send SetKeyManagement() command (root)", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + static constexpr std::initializer_list KeyManagementInvalidValues = { + WpaKeyManagement::None, + WpaKeyManagement::Ieee80211xNoWpa, + WpaKeyManagement::WpaNone, + WpaKeyManagement::Wps, + WpaKeyManagement::WapiPsk, + WpaKeyManagement::WapiCert, + WpaKeyManagement::Cckm, + }; + + static constexpr std::initializer_list KeyManagementValidValues = { + WpaKeyManagement::Ieee80211x, + WpaKeyManagement::Psk, + // WpaKeyManagement::FtIeee8021x, // feature work not yet completed + // WpaKeyManagement::FtPsk, // feature work not yet completed + WpaKeyManagement::Ieee8021xSha256, + WpaKeyManagement::PskSha256, + WpaKeyManagement::Sae, + // WpaKeyManagement::FtSae, // feature work not yet completed + // WpaKeyManagement::Osen, // feature work not yet completed + // WpaKeyManagement::Ieee80211xSuiteB, // feature work not yet completed + // WpaKeyManagement::Ieee80211xSuiteB192, // feature work not yet completed + // WpaKeyManagement::FilsSha256, // feature work not yet completed + // WpaKeyManagement::FilsSha384, // feature work not yet completed + // WpaKeyManagement::FtFilsSha256, // feature work not yet completed + // WpaKeyManagement::FtFilsSha384, // feature work not yet completed + WpaKeyManagement::Owe, + WpaKeyManagement::Dpp, + // WpaKeyManagement::FtIeee8021xSha384, // feature work not yet completed + // WpaKeyManagement::Pasn, // feature work not yet completed + }; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + + SECTION("Doesn't throw") + { + REQUIRE_NOTHROW(hostapd.SetKeyManagement({ WpaKeyManagement::Psk }, EnforceConfigurationChange::Now)); + REQUIRE_NOTHROW(hostapd.SetKeyManagement({ WpaKeyManagement::Psk }, EnforceConfigurationChange::Defer)); + REQUIRE_NOTHROW(hostapd.SetKeyManagement({ WpaKeyManagement::Psk, WpaKeyManagement::Sae }, EnforceConfigurationChange::Now)); + REQUIRE_NOTHROW(hostapd.SetKeyManagement({ WpaKeyManagement::Psk, WpaKeyManagement::Sae }, EnforceConfigurationChange::Defer)); + } + + SECTION("Fails with empty input") + { + REQUIRE_THROWS_AS(hostapd.SetKeyManagement({}, EnforceConfigurationChange::Now), HostapdException); + } + + SECTION("Fails with every invalid input") + { + std::vector keyManagementInvalidValues{}; + for (const auto keyManagementInvalidValue : KeyManagementInvalidValues) { + keyManagementInvalidValues.push_back(keyManagementInvalidValue); + REQUIRE_THROWS_AS(hostapd.SetKeyManagement({ keyManagementInvalidValue }, EnforceConfigurationChange::Now), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetKeyManagement({ keyManagementInvalidValue }, EnforceConfigurationChange::Defer), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetKeyManagement(keyManagementInvalidValues, EnforceConfigurationChange::Now), HostapdException); + REQUIRE_THROWS_AS(hostapd.SetKeyManagement(keyManagementInvalidValues, EnforceConfigurationChange::Defer), HostapdException); + } + } + + SECTION("Succeeds with all valid, single inputs") + { + for (const auto keyManagementValidValue : KeyManagementValidValues) { + REQUIRE(hostapd.SetKeyManagement({ keyManagementValidValue }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetKeyManagement({ keyManagementValidValue }, EnforceConfigurationChange::Defer)); + } + } + + SECTION("Succeeds with valid, multiple inputs") + { + std::vector keyManagementValidValues{}; + for (const auto keyManagementValidValue : KeyManagementValidValues) { + keyManagementValidValues.push_back(keyManagementValidValue); + REQUIRE(hostapd.SetKeyManagement(keyManagementValidValues, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetKeyManagement(keyManagementValidValues, EnforceConfigurationChange::Defer)); + } + } + + SECTION("Succeeds with valid, duplicate inputs") + { + for (const auto keyManagementValidValue : KeyManagementValidValues) { + REQUIRE(hostapd.SetKeyManagement({ keyManagementValidValue, keyManagementValidValue }, EnforceConfigurationChange::Now)); + REQUIRE(hostapd.SetKeyManagement({ keyManagementValidValue, keyManagementValidValue }, EnforceConfigurationChange::Defer)); + } + } +} diff --git a/tests/unit/linux/wpa-controller/detail/WpaDaemonManager.hxx b/tests/unit/linux/wpa-controller/detail/WpaDaemonManager.hxx index 6383e00b..03240b31 100644 --- a/tests/unit/linux/wpa-controller/detail/WpaDaemonManager.hxx +++ b/tests/unit/linux/wpa-controller/detail/WpaDaemonManager.hxx @@ -9,7 +9,6 @@ #include #include -#include "detail/WpaDaemonManager.hxx" #include /**