diff --git a/src/linux/wpa-controller/CMakeLists.txt b/src/linux/wpa-controller/CMakeLists.txt index 4c4f5c75..59056833 100644 --- a/src/linux/wpa-controller/CMakeLists.txt +++ b/src/linux/wpa-controller/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(wpa-controller ProtocolWpa.cxx WpaCommand.cxx WpaCommandGet.cxx + WpaCommandGetConfig.cxx WpaCommandSet.cxx WpaCommandStatus.cxx WpaController.cxx @@ -38,6 +39,7 @@ target_sources(wpa-controller ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpaConfig.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommand.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandGet.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandGetConfig.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandSet.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCommandStatus.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaController.hxx diff --git a/src/linux/wpa-controller/Hostapd.cxx b/src/linux/wpa-controller/Hostapd.cxx index c8bbad8b..b99d1542 100644 --- a/src/linux/wpa-controller/Hostapd.cxx +++ b/src/linux/wpa-controller/Hostapd.cxx @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -135,6 +137,19 @@ Hostapd::GetProperty(std::string_view propertyName) return propertyValue; } +HostapdBssConfiguration +Hostapd::GetConfiguration() +{ + static constexpr WpaCommandGetConfig GetConfigCommand{}; + + auto response = m_controller.SendCommand(GetConfigCommand); + if (!response) { + throw HostapdException("Failed to send hostapd 'get_config' command"); + } + + return response->Configuration; +} + void Hostapd::SetProperty(std::string_view propertyName, std::string_view propertyValue, EnforceConfigurationChange enforceConfigurationChange) { diff --git a/src/linux/wpa-controller/ProtocolHostapd.cxx b/src/linux/wpa-controller/ProtocolHostapd.cxx index 64376fa4..d9c252c8 100644 --- a/src/linux/wpa-controller/ProtocolHostapd.cxx +++ b/src/linux/wpa-controller/ProtocolHostapd.cxx @@ -1,4 +1,5 @@ +#include #include #include #include @@ -85,3 +86,55 @@ Wpa::WpaPreSharedKeyPropertyKeyAndValue(const WpaPreSharedKey& wpaPreSharedKey) auto propertyValue = WpaPreSharedKeyPropertyValue(wpaPreSharedKey); return std::make_pair(propertyName, std::move(propertyValue)); } + +std::vector +Wpa::WpaKeyManagementFromPropertyValue(std::string_view wpaKeyManagementProperty) noexcept +{ + std::string wpaKeyManagementString(wpaKeyManagementProperty); + std::istringstream wpaKeyManagementStream(wpaKeyManagementString); + std::vector wpaKeyManagements{}; + + for (std::string wpaKeyManagement; wpaKeyManagementStream >> wpaKeyManagement;) { + if (wpaKeyManagement == "WPA-EAP") { + wpaKeyManagements.push_back(WpaKeyManagement::Ieee8021x); + } else if (wpaKeyManagement == "WPA-PSK") { + wpaKeyManagements.push_back(WpaKeyManagement::Psk); + } else if (wpaKeyManagement == "FT-EAP") { + wpaKeyManagements.push_back(WpaKeyManagement::FtIeee8021x); + } else if (wpaKeyManagement == "FT-PSK") { + wpaKeyManagements.push_back(WpaKeyManagement::FtPsk); + } else if (wpaKeyManagement == "WPA-EAP-SHA256") { + wpaKeyManagements.push_back(WpaKeyManagement::Ieee8021xSha256); + } else if (wpaKeyManagement == "WPA-PSK-SHA256") { + wpaKeyManagements.push_back(WpaKeyManagement::PskSha256); + } else if (wpaKeyManagement == "SAE") { + wpaKeyManagements.push_back(WpaKeyManagement::Sae); + } else if (wpaKeyManagement == "FT-SAE") { + wpaKeyManagements.push_back(WpaKeyManagement::FtSae); + } else if (wpaKeyManagement == "OSEN") { + wpaKeyManagements.push_back(WpaKeyManagement::Osen); + } else if (wpaKeyManagement == "WPA-EAP-SUITE-B") { + wpaKeyManagements.push_back(WpaKeyManagement::Ieee8021xSuiteB); + } else if (wpaKeyManagement == "WPA-EAP-SUITE-B-192") { + wpaKeyManagements.push_back(WpaKeyManagement::Ieee8021xSuiteB192); + } else if (wpaKeyManagement == "FILS-SHA256") { + wpaKeyManagements.push_back(WpaKeyManagement::FilsSha256); + } else if (wpaKeyManagement == "FILS-SHA384") { + wpaKeyManagements.push_back(WpaKeyManagement::FilsSha384); + } else if (wpaKeyManagement == "FT-FILS-SHA256") { + wpaKeyManagements.push_back(WpaKeyManagement::FtFilsSha256); + } else if (wpaKeyManagement == "FT-FILS-SHA384") { + wpaKeyManagements.push_back(WpaKeyManagement::FtFilsSha384); + } else if (wpaKeyManagement == "OWE") { + wpaKeyManagements.push_back(WpaKeyManagement::Owe); + } else if (wpaKeyManagement == "DPP") { + wpaKeyManagements.push_back(WpaKeyManagement::Dpp); + } else if (wpaKeyManagement == "FT-EAP-SHA384") { + wpaKeyManagements.push_back(WpaKeyManagement::FtIeee8021xSha384); + } else if (wpaKeyManagement == "PASN") { + wpaKeyManagements.push_back(WpaKeyManagement::Pasn); + } + } + + return wpaKeyManagements; +} diff --git a/src/linux/wpa-controller/WpaCommandGetConfig.cxx b/src/linux/wpa-controller/WpaCommandGetConfig.cxx new file mode 100644 index 00000000..d4b8844f --- /dev/null +++ b/src/linux/wpa-controller/WpaCommandGetConfig.cxx @@ -0,0 +1,71 @@ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "WpaParsingUtilities.hxx" + +using namespace Wpa; + +std::unique_ptr +WpaCommandGetConfig::CreateResponseParser(const WpaCommand* command, std::string_view responsePayload) const +{ + return std::make_unique(command, responsePayload); +} + +// clang-format off +WpaGetConfigResponseParser::WpaGetConfigResponseParser(const WpaCommand* command, std::string_view responsePayload) : + WpaResponseParser(command, responsePayload, { + { ProtocolHostapd::ResponseGetConfigPropertyKeyBssid, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeySsid, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeyWpa, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeyWpaKeyMgmt, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeyGroupCipher, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeyRsnPairwiseCipher, WpaValuePresence::Required }, + { ProtocolHostapd::ResponseGetConfigPropertyKeyWpaPairwiseCipher, WpaValuePresence::Required }, + }) +// clang-format on +{ +} + +std::shared_ptr +WpaGetConfigResponseParser::ParsePayload() +{ + using namespace Wpa::Parsing; + + const auto& properties = GetProperties(); + const auto response = std::make_shared(); + auto& configuration = response->Configuration; + + for (const auto& [key, mapped] : properties) { + const auto& [value, index] = mapped; + if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyBssid) { + configuration.Bssid = value; + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeySsid) { + configuration.Ssid = value; + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyWpa) { + int wpa; + ParseInt(value, wpa); + configuration.Wpa = static_cast(wpa); + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyWpaKeyMgmt) { + configuration.WpaKeyMgmt = WpaKeyManagementFromPropertyValue(value); + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyGroupCipher) { + configuration.GroupCipher = WpaCipherFromPropertyValue(value); + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyRsnPairwiseCipher) { + configuration.RsnPairwiseCipher = WpaCipherFromPropertyValue(value); + } else if (key == ProtocolHostapd::ResponseGetConfigPropertyKeyWpaPairwiseCipher) { + configuration.WpaPairwiseCipher = WpaCipherFromPropertyValue(value); + } + } + + return response; +} diff --git a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx index 5d1c3794..ce1a20c5 100644 --- a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -118,6 +118,14 @@ struct Hostapd : std::string GetProperty(std::string_view propertyName) override; + /** + * @brief Get the configuration for the interface. + * + * @return HostapdBssConfiguration The configuration string value. + */ + HostapdBssConfiguration + GetConfiguration() override; + /** * @brief Set a property on the interface. * diff --git a/src/linux/wpa-controller/include/Wpa/IHostapd.hxx b/src/linux/wpa-controller/include/Wpa/IHostapd.hxx index ea8b143a..b76f6fc4 100644 --- a/src/linux/wpa-controller/include/Wpa/IHostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/IHostapd.hxx @@ -116,6 +116,14 @@ struct IHostapd virtual std::string GetProperty(std::string_view propertyName) = 0; + /** + * @brief Get the configuration for the interface. + * + * @return HostapdBssConfiguration The configuration string value. + */ + virtual HostapdBssConfiguration + GetConfiguration() = 0; + /** * @brief Set a property on the interface. * diff --git a/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx b/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx index bbe2760d..6250a26e 100644 --- a/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/ProtocolHostapd.hxx @@ -512,6 +512,54 @@ struct HostapdStatus // unsigned ChannelUtilitzationAverage; }; +struct HostapdBssConfiguration +{ + // TODO: All types used below are direct translations of the types used in + // the hostapd code; there may be better representations of them. + + std::string Bssid; + std::string Ssid; + + // Note: The following fields do not yet have parsing code so they are + // commented out for the time being. Many of them may not be kept if they + // have no use in the implementation, but for now, this is a complete list + // of all fields that are returned from the 'GET_CONFIG' message. + + // // Present with: + // // - CONFIG_WPS compilation option + // std::optional WpsState; + + // // Present with: + // // - Value for current 'wps_state" is set. + // // - Value for current 'wpa' is set. + // std::optional WpaPassphrase; + // std::optional WpaPsk; + + // // Present with: + // // - 'multi_ap=1' OR 'multi_ap=3' configuration file option + // std::optional MultiApMode; + // std::optional MultiApBackhaulSsid; + + // // Present with: + // // - 'multi_ap=1' OR 'multi_ap=3' configuration file option + // // - Value for current 'wps_state' is set. + // // - Value for current 'wpa' is set. + // std::optional MultiApBackhaulWpaPassphrase; + // std::optional MultiApBackhaulWpaPsk; + + WpaSecurityProtocol Wpa; + std::vector WpaKeyMgmt; + WpaCipher GroupCipher; + WpaCipher RsnPairwiseCipher; + WpaCipher WpaPairwiseCipher; + + // // Present only if 'wpa' and a current 'wpa_deny_ptk0_rekey' value is set. + // std::optional WpaDenyPtk0Rekey; + + // // Present only if driver supports it and if RSN/WPA2 is used with a CCMP/GCMP pairwise cipher. + // std::optional ExtendedKeyId; +}; + struct ProtocolHostapd : public ProtocolWpa { @@ -608,6 +656,12 @@ struct ProtocolHostapd : static constexpr auto PropertyValueSaePasswordClearAll = ""; static constexpr auto PropertyValueSaeKeyValueSeparator = "|"; + // Property names for "GET_CONFIG" command. + static constexpr auto PropertyNameKeyManagement = "key_mgmt"; + static constexpr auto PropertyNameGroupCipher = "group_cipher"; + static constexpr auto PropertyNameRsnPairwiseCipher = "rsn_pairwise_cipher"; + static constexpr auto PropertyNameWpaPairwiseCipher = "wpa_pairwise_cipher"; + // Response properties for the "STATUS" command. // Note: all properties must be terminated with the key-value delimeter (=). static constexpr auto ResponseStatusPropertyKeyState = PropertyNameState; @@ -617,6 +671,16 @@ struct ProtocolHostapd : static constexpr auto ResponseStatusPropertyKeyDisableAC = PropertyNameDisable11AC; static constexpr auto ResponseStatusPropertyKeyIeee80211AX = PropertyNameIeee80211AX; static constexpr auto ResponseStatusPropertyKeyDisableAX = PropertyNameDisable11AX; + + // Response properties for the "GET_CONFIG" command. + // Note: all properties must be terminated with the key-value delimeter (=). + static constexpr auto ResponseGetConfigPropertyKeyBssid = PropertyNameBssBssid; + static constexpr auto ResponseGetConfigPropertyKeySsid = PropertyNameBssSsid; + static constexpr auto ResponseGetConfigPropertyKeyWpa = PropertyNameWpaSecurityProtocol; + static constexpr auto ResponseGetConfigPropertyKeyWpaKeyMgmt = PropertyNameKeyManagement; + static constexpr auto ResponseGetConfigPropertyKeyGroupCipher = PropertyNameGroupCipher; + static constexpr auto ResponseGetConfigPropertyKeyRsnPairwiseCipher = PropertyNameRsnPairwiseCipher; + static constexpr auto ResponseGetConfigPropertyKeyWpaPairwiseCipher = PropertyNameWpaPairwiseCipher; }; /** @@ -766,6 +830,16 @@ WpaKeyManagementPropertyValue(WpaKeyManagement wpaKeyManagement) noexcept } } +/** + * @brief Convert a hostapd 'wpa_key_mgmt' property value string to the corresponding WpaKeyManagement value. + * This string may have several whitespace-separated values, such as "WPA-PSK SAE". + * + * @param wpaKeyManagementProperty The hostapd property value string to convert. + * @return std::vector The corresponding WpaKeyManagement values. + */ +std::vector +WpaKeyManagementFromPropertyValue(std::string_view wpaKeyManagementProperty) noexcept; + /** * @brief WpaCipher sentinel for an invalid value. */ @@ -815,6 +889,46 @@ WpaCipherPropertyValue(WpaCipher wpaCipher) noexcept } } +/** + * @brief Convert a hostapd cipher property string such as 'wpa_pairwise' and 'rsn_pairwise' to the corresponding WpaCipher value. + * + * @param wpaCipher The hostapd property value string to convert. + * @return constexpr WpaCipher The corresponding WpaCipher value. + */ +constexpr WpaCipher +WpaCipherFromPropertyValue(std::string_view wpaCipherProperty) noexcept +{ + if (wpaCipherProperty == "NONE") { + return WpaCipher::None; + } else if (wpaCipherProperty == "WEP40") { + return WpaCipher::Wep40; + } else if (wpaCipherProperty == "WEP104") { + return WpaCipher::Wep104; + } else if (wpaCipherProperty == "TKIP") { + return WpaCipher::Tkip; + } else if (wpaCipherProperty == "CCMP") { + return WpaCipher::Ccmp; + } else if (wpaCipherProperty == "AES-128-CMAC") { + return WpaCipher::Aes128Cmac; + } else if (wpaCipherProperty == "GCMP") { + return WpaCipher::Gcmp; + } else if (wpaCipherProperty == "GCMP-256") { + return WpaCipher::Gcmp256; + } else if (wpaCipherProperty == "CCMP-256") { + return WpaCipher::Ccmp256; + } else if (wpaCipherProperty == "BIP-GMAC-128") { + return WpaCipher::BipGmac128; + } else if (wpaCipherProperty == "BIP-GMAC-256") { + return WpaCipher::BipGmac256; + } else if (wpaCipherProperty == "BIP-CMAC-256") { + return WpaCipher::BipCmac256; + } else if (wpaCipherProperty == "GTK_NOT_USED") { + return WpaCipher::GtkNotUsed; + } else { + return WpaCipher::Unknown; + } +} + /** * @brief Get the hostapd property name to use to set the cipher for the specified WPA protocol. * diff --git a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx index d9ceff02..5a75ce2c 100644 --- a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx +++ b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx @@ -27,6 +27,7 @@ struct ProtocolWpa static constexpr auto CommandPayloadTerminate = "TERMINATE"; static constexpr auto CommandPayloadRelog = "RELOG"; static constexpr auto CommandPayloadGet = "GET"; + static constexpr auto CommandPayloadGetConfig = "GET_CONFIG"; static constexpr auto CommandPayloadSet = "SET"; static constexpr auto CommandPayloadNote = "NOTE"; static constexpr auto CommandPayloadMib = "MIB"; diff --git a/src/linux/wpa-controller/include/Wpa/WpaCommandGetConfig.hxx b/src/linux/wpa-controller/include/Wpa/WpaCommandGetConfig.hxx new file mode 100644 index 00000000..a0fbc6be --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaCommandGetConfig.hxx @@ -0,0 +1,65 @@ + +#ifndef WPA_COMMAND_GET_CONFIG_HXX +#define WPA_COMMAND_GET_CONFIG_HXX + +#include +#include + +#include +#include + +namespace Wpa +{ +/** + * @brief Representation of the "GET_CONFIG" command. + */ +struct WpaCommandGetConfig : + public WpaCommand +{ + /** + * @brief Construct a new WpaCommandGetConfig object. + */ + constexpr WpaCommandGetConfig() : + WpaCommand(ProtocolWpa::CommandPayloadGetConfig) + { + } + +private: + /** + * @brief Create a WpaResponseParser object that is specific to the "GET_CONFIG" command. + * + * @param command + * @param responsePayload + * @return std::unique_ptr + */ + std::unique_ptr + CreateResponseParser(const WpaCommand* command, std::string_view responsePayload) const override; +}; + +/** + * @brief Parser for the "GET_CONFIG" command response. + */ +struct WpaGetConfigResponseParser : + public WpaResponseParser +{ + /** + * @brief Construct a new WpaGetConfigResponseParser object. + * + * @param command The command associated with the response. + * @param responsePayload The response payload to parse. + */ + WpaGetConfigResponseParser(const WpaCommand* command, std::string_view responsePayload); + + /** + * @brief Parses the response payload, returning a WpaResponseStatus object + * if successful. + * + * @return std::shared_ptr + */ + std::shared_ptr + ParsePayload() override; +}; + +} // namespace Wpa + +#endif // WPA_COMMAND_GET_CONFIG_HXX diff --git a/src/linux/wpa-controller/include/Wpa/WpaResponseGetConfig.hxx b/src/linux/wpa-controller/include/Wpa/WpaResponseGetConfig.hxx new file mode 100644 index 00000000..8e589857 --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaResponseGetConfig.hxx @@ -0,0 +1,22 @@ + +#ifndef WPA_RESPONSE_GET_CONFIG_HXX +#define WPA_RESPONSE_GET_CONFIG_HXX + +#include +#include + +namespace Wpa +{ +/** + * @brief Representation of the response to the "GET_CONFIG" command. + */ +struct WpaResponseGetConfig : + public WpaResponse +{ + WpaResponseGetConfig() = default; + + HostapdBssConfiguration Configuration; +}; +} // namespace Wpa + +#endif // WPA_RESPONSE_COMMAND_GET_CONFIG_HXX diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index b76a1488..94908212 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -200,6 +200,18 @@ TEST_CASE("Send GetProperty() command (root)", "[wpa][hostapd][client][remote]") } } +TEST_CASE("Send command: GetConfiguration() (root)", "[wpa][hostapd][client][remote]") +{ + using namespace Wpa; + + Hostapd hostapd(WpaDaemonManager::InterfaceNameDefault); + + SECTION("GetConfiguration() doesn't throw") + { + REQUIRE_NOTHROW(hostapd.GetConfiguration()); + } +} + TEST_CASE("Send SetProperty() command (root)", "[wpa][hostapd][client][remote]") { using namespace Wpa;