From 9aee8fba34402877327649847665c8949984ed20 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Mon, 1 Jul 2024 21:08:31 +0000 Subject: [PATCH 01/13] Add interface name to control socket connection. --- .../wpa-controller/WpaControlSocketConnection.cxx | 9 ++++++++- .../include/Wpa/WpaControlSocketConnection.hxx | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/linux/wpa-controller/WpaControlSocketConnection.cxx b/src/linux/wpa-controller/WpaControlSocketConnection.cxx index b059dc9b..53259392 100644 --- a/src/linux/wpa-controller/WpaControlSocketConnection.cxx +++ b/src/linux/wpa-controller/WpaControlSocketConnection.cxx @@ -16,7 +16,8 @@ WpaControlSocketConnection::~WpaControlSocketConnection() } WpaControlSocketConnection::WpaControlSocketConnection(std::string_view interfaceName, std::filesystem::path controlSocketPathDir) : - m_controlSocketPath(std::move(controlSocketPathDir) / interfaceName) + m_controlSocketPath(std::move(controlSocketPathDir) / interfaceName), + m_interfaceName(interfaceName) { } @@ -63,3 +64,9 @@ WpaControlSocketConnection::Disconnect() noexcept m_controlSocket = nullptr; } } + +std::string_view +WpaControlSocketConnection::GetInterfaceName() const noexcept +{ + return m_interfaceName; +} diff --git a/src/linux/wpa-controller/include/Wpa/WpaControlSocketConnection.hxx b/src/linux/wpa-controller/include/Wpa/WpaControlSocketConnection.hxx index 62ca4243..2ea54c0f 100644 --- a/src/linux/wpa-controller/include/Wpa/WpaControlSocketConnection.hxx +++ b/src/linux/wpa-controller/include/Wpa/WpaControlSocketConnection.hxx @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -56,6 +57,14 @@ public: void Disconnect() noexcept; + /** + * @brief Get the interface name for this connection. + * + * @return std::string_view + */ + std::string_view + GetInterfaceName() const noexcept; + protected: /** * @brief Construct a new WpaControlSocketConnection object. @@ -67,6 +76,7 @@ protected: private: std::filesystem::path m_controlSocketPath; + std::string m_interfaceName; struct wpa_ctrl* m_controlSocket{ nullptr }; }; } // namespace Wpa From 741858128ad4ce5e4ed86e777dffbe7d3206674f Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Mon, 1 Jul 2024 23:02:46 +0000 Subject: [PATCH 02/13] Add max event size to protocol definitions. --- src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx index 6ee7544a..3eb182d4 100644 --- a/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx +++ b/src/linux/wpa-controller/include/Wpa/ProtocolWpa.hxx @@ -60,6 +60,12 @@ struct ProtocolWpa static constexpr auto ResponsePayloadFail = "FAIL"; static constexpr auto ResponsePayloadPing = "PONG"; + /** + * @brief Maximum size of a WPA event. This value is used by the wpa_cli and hostapd_cli tools as an upper bound, so + * is used similarly here. + */ + static constexpr auto EventSizeMax{ 4096 }; + /** * @brief Determines if a response payload indicates success. * From 8545d3119ea499ed3edb24c9cf201f4d8d1767da Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Mon, 1 Jul 2024 23:08:22 +0000 Subject: [PATCH 03/13] Add WPA event listener interface and initial implementation. --- src/linux/wpa-controller/CMakeLists.txt | 5 + src/linux/wpa-controller/WpaEventHandler.cxx | 273 ++++++++++++++++++ .../include/Wpa/IWpaEventListener.hxx | 33 +++ .../wpa-controller/include/Wpa/WpaEvent.hxx | 22 ++ .../include/Wpa/WpaEventArgs.hxx | 21 ++ .../include/Wpa/WpaEventHandler.hxx | 102 +++++++ 6 files changed, 456 insertions(+) create mode 100644 src/linux/wpa-controller/WpaEventHandler.cxx create mode 100644 src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx create mode 100644 src/linux/wpa-controller/include/Wpa/WpaEvent.hxx create mode 100644 src/linux/wpa-controller/include/Wpa/WpaEventArgs.hxx create mode 100644 src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx diff --git a/src/linux/wpa-controller/CMakeLists.txt b/src/linux/wpa-controller/CMakeLists.txt index 4f24bedf..f2072d4e 100644 --- a/src/linux/wpa-controller/CMakeLists.txt +++ b/src/linux/wpa-controller/CMakeLists.txt @@ -18,6 +18,7 @@ target_sources(wpa-controller WpaController.cxx WpaControlSocket.cxx WpaControlSocketConnection.cxx + WpaEventHandler.cxx WpaKeyValuePair.cxx WpaParsingUtilities.cxx WpaParsingUtilities.hxx @@ -29,6 +30,8 @@ target_sources(wpa-controller FILES ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IWpaEventListener.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IWpaEventProducer.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpa.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpaConfig.hxx @@ -40,6 +43,8 @@ target_sources(wpa-controller ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaControlSocket.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaControlSocketConnection.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaEvent.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaEventHandler.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaKeyValuePair.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponseParser.hxx diff --git a/src/linux/wpa-controller/WpaEventHandler.cxx b/src/linux/wpa-controller/WpaEventHandler.cxx new file mode 100644 index 00000000..fbf0c1dc --- /dev/null +++ b/src/linux/wpa-controller/WpaEventHandler.cxx @@ -0,0 +1,273 @@ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Wpa; + +WpaEventHandler::WpaEventHandler(std::unique_ptr wpaControlSocketConnection) : + m_wpaControlSocketConnection(std::move(wpaControlSocketConnection)) +{ +} + +WpaEventHandler::~WpaEventHandler() +{ + StopListening(); +} + +WpaEventListenerRegistrationToken +WpaEventHandler::RegisterEventListener(std::weak_ptr wpaEventListener) +{ + std::unique_lock eventListenerWriteLock{ m_eventListenerStateGate }; + + auto wpaEventListenerRegistrationToken = m_eventListenerRegistrationTokenNext++; + m_eventListeners[wpaEventListenerRegistrationToken] = wpaEventListener; + LOGD << std::format("Registered WPA event listener with eventListenerRegistrationToken '{}' on interface '{}'", wpaEventListenerRegistrationToken, m_wpaControlSocketConnection->GetInterfaceName()); + + return wpaEventListenerRegistrationToken; +} + +void +WpaEventHandler::UnregisterEventListener(WpaEventListenerRegistrationToken wpaEventListenerRegistrationToken) +{ + std::unique_lock eventListenerWriteLock{ m_eventListenerStateGate }; + + const auto numRemoved = m_eventListeners.erase(wpaEventListenerRegistrationToken); + if (numRemoved == 0) { + LOGW << std::format("Attempted to unregister a WPA event listener that was not registered for interface '{}'.", m_wpaControlSocketConnection->GetInterfaceName()); + } else { + LOGD << std::format("Unregistered WPA event listener with eventListenerRegistrationToken '{}' on interface '{}'", wpaEventListenerRegistrationToken, m_wpaControlSocketConnection->GetInterfaceName()); + } +} + +void +WpaEventHandler::StartListening() +{ + std::unique_lock eventListenerWriteLock{ m_eventListenerStateGate }; + + m_eventListenerThread = std::jthread([this](std::stop_token stopToken) mutable { + ProcessEvents(*m_wpaControlSocketConnection, std::move(stopToken)); + }); +} + +void +WpaEventHandler::StopListening() +{ + // Check if the eventfd file descriptor for signaling thread stop has been created. If so, signal the thread to stop. + if (m_fdEventFdStop != -1) { + uint64_t value{ EventFdStopRequestValue }; + ssize_t numWritten = write(m_fdEventFdStop, &value, sizeof value); + if (numWritten != sizeof value) { + LOGE << std::format("Failed to write to eventfd for interface '{}'.", m_wpaControlSocketConnection->GetInterfaceName()); + } + + close(m_fdEventFdStop); + m_fdEventFdStop = -1; + } + + m_eventListenerThread.request_stop(); + m_eventListenerThread.join(); +} + +void +WpaEventHandler::ProcessNextEvent(WpaControlSocketConnection& wpaControlSocketConnection) +{ + const auto InterfaceName{ wpaControlSocketConnection.GetInterfaceName() }; + LOGD << std::format("Processing pending WPA event on interface '{}'.", InterfaceName); + + // Allocate a buffer for the event data, and subtract one to leave room for the null terminator. + std::array wpaEventBuffer{}; + std::size_t wpaEventBufferSize = std::size(wpaEventBuffer) - 1; + + // Retrieve the next WPA event from the control socket. + int ret = wpa_ctrl_recv(*m_wpaControlSocketConnection, std::data(wpaEventBuffer), &wpaEventBufferSize); + if (ret < 0) { + LOGE << std::format("Failed to receive WPA event on interface '{}'.", InterfaceName); + return; + } + + // Explicitly null-terminate the buffer in case bad data was provided. + wpaEventBuffer[wpaEventBufferSize] = '\0'; + + // Record the time this event was received. + const auto timestampNow{ std::chrono::system_clock::now() }; + std::string wpaEventPayload{ std::data(wpaEventBuffer), wpaEventBufferSize }; + + // Create a WPA event args object to pass to the listeners. + WpaEventArgs wpaEventArgs{ + .Timestamp = timestampNow, + .Event = { + .Source = WpaType::Hostapd, // TODO: determine true source of the event. + .Payload = { std::data(wpaEventBuffer), wpaEventBufferSize }, + }, + }; + + // Make a copy of the event listeners to minimuze the time we hold the state lock. This is safe since the event + // listeners are weak_ptrs which we later attempt to promote to a full shared_ptr before de-referencing them. + decltype(m_eventListeners) eventListeners{}; + std::vector eventListenerRegistrationTokensExpired{}; + { + std::shared_lock eventListenerReadLock{ m_eventListenerStateGate }; + eventListeners = m_eventListeners; + } + + // Invoke each registered event listener with the event args. + for (const auto& [eventListenerRegistrationToken, eventListenerWeak] : eventListeners) { + std::shared_ptr eventListener = eventListenerWeak.lock(); + if (!eventListener) { + LOGW << std::format("WPA event listener with registration token '{}' on interface '{}' expired; removing it", eventListenerRegistrationToken, InterfaceName); + eventListenerRegistrationTokensExpired.push_back(eventListenerRegistrationToken); + continue; + } + + // Fire the event on the listener. + eventListener->OnWpaEvent(this, &wpaEventArgs); + } + + // Remove any expired listeners. + if (!std::empty(eventListenerRegistrationTokensExpired)) { + std::unique_lock eventListenerWriteLock{ m_eventListenerStateGate }; + for (const auto& eventListenerRegistrationToken : eventListenerRegistrationTokensExpired) { + m_eventListeners.erase(eventListenerRegistrationToken); + } + } +} + +void +WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConnection, std::stop_token stopToken) +{ + // One event for eventfd stop signaling, and one for WPA control socket event signaling. + static constexpr int EpollEventsMax{ 2 }; + + const auto InterfaceName{ wpaControlSocketConnection.GetInterfaceName() }; + + std::array epollEvents = {}; + auto* eventEventFd = &epollEvents[0]; + auto* eventWpa = &epollEvents[1]; + + // Create an epoll instance to listen for events on the eventfd file descriptor and WPA control socket. + int ret{ -1 }; + int fdEpoll = epoll_create1(0); + if (fdEpoll < 0) { + ret = errno; + LOGE << std::format("Failed to create epoll instance for interface '{}': {}", InterfaceName, ret); + return; + } + + auto closeEpollFileDescriptorOnExit = notstd::ScopeExit([&] { + close(fdEpoll); + }); + + // Configure eventfd descriptor for thread stop signaling via epoll. + int fdEventFdStop = eventfd(0, 0); + if (fdEventFdStop < 0) { + ret = errno; + LOGE << std::format("Failed to create eventfd for interface '{}': {}", InterfaceName, ret); + return; + } + + auto closeEventFdStopFileDescriptorOnExit = notstd::ScopeExit([&] { + close(fdEventFdStop); + }); + + eventEventFd->events = EPOLLIN; + eventEventFd->data.fd = fdEventFdStop; + + // Add the eventfd descriptor to the epoll instance. + ret = epoll_ctl(fdEpoll, EPOLL_CTL_ADD, fdEventFdStop, eventEventFd); + if (ret < 0) { + ret = errno; + LOGE << std::format("Failed to add eventfd to epoll instance for interface '{}': {}", InterfaceName, ret); + return; + } + + struct wpa_ctrl* wpaControlSocket = wpaControlSocketConnection; + + // Configure WPA control socket for event signaling via epoll. + int fdWpa = wpa_ctrl_get_fd(wpaControlSocket); + if (fdWpa < 0) { + ret = errno; + LOGE << std::format("Failed to get WPA control socket file descriptor for interface '{}': {}", InterfaceName, ret); + return; + } + + eventWpa->events = EPOLLIN; + eventWpa->data.fd = fdWpa; + + ret = epoll_ctl(fdEpoll, EPOLL_CTL_ADD, fdWpa, eventWpa); + if (ret < 0) { + ret = errno; + LOGE << std::format("Failed to add WPA control socket to epoll instance for interface '{}': {}", InterfaceName, ret); + return; + } + + // Attach to WAP eventstream. + ret = wpa_ctrl_attach(wpaControlSocket); + if (ret < 0) { + ret = errno; + LOGE << std::format("Failed to attach to WPA control socket for interface '{}': {}", InterfaceName, ret); + return; + } + + auto detachFromWpaControlSocketOnExit = notstd::ScopeExit([&] { + wpa_ctrl_detach(wpaControlSocket); + }); + + bool stopRequested{ false }; + for (;;) { + if (stopToken.stop_requested() || stopRequested) { + LOGD << std::format("Stopping WPA event listener for interface '{}'.", InterfaceName); + break; + } + + // Wait for at least one event file descriptor to be signaled. + const auto numEvents = epoll_wait(fdEpoll, std::data(epollEvents), std::size(epollEvents), -1); + if (numEvents < 0) { + ret = errno; + LOGE << std::format("Failed to wait for events on interface '{}': {}", InterfaceName, ret); + continue; + } + if (numEvents == 0) { + LOGW << std::format("No events received on interface '{}'.", InterfaceName); + continue; + } + + // Determine which file descriptor was signaled, and handle it. + for (std::size_t i = 0; i < std::size(epollEvents); ++i) { + if (epollEvents[i].data.fd == fdEventFdStop) { + LOGD << "Eventfd was signaled; checking for pending event(s)."; + uint64_t value{ ~EventFdStopRequestValue }; + ssize_t numRead = read(fdEventFdStop, &value, sizeof value); + if (numRead != sizeof value) { + ret = errno; + LOGE << std::format("Failed to read from eventfd for interface '{}': {}", InterfaceName, ret); + continue; + } + if (value == EventFdStopRequestValue) { + LOGD << std::format("Received stop signal on interface '{}'.", InterfaceName); + stopRequested = true; + break; + } + } + if (epollEvents[i].data.fd == fdWpa) { + LOGD << "WPA control socket was signaled; checking for pending event(s)."; + while (wpa_ctrl_pending(wpaControlSocket) > 0) { + ProcessNextEvent(wpaControlSocketConnection); + } + } + } + } +} diff --git a/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx b/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx new file mode 100644 index 00000000..1e74ba88 --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx @@ -0,0 +1,33 @@ + +#ifndef I_WPA_EVENT_HANDLER_HXX +#define I_WPA_EVENT_HANDLER_HXX + +#include + +namespace Wpa +{ +/** + * @brief Represents the sender or source of a WPA event. + */ +struct WpaEventSender +{ + virtual ~WpaEventSender() = default; +}; + +/** + * @brief Interface for WPA event consumers. + */ +struct IWpaEventListener +{ + /** + * @brief Invoked when a WPA event is received. + * + * @param sender The sender or source of the event. + * @param eventArgs The event arguments. + */ + virtual void + OnWpaEvent(WpaEventSender *sender, const WpaEventArgs *eventArgs) = 0; +}; +} // namespace Wpa + +#endif // I_WPA_EVENT_HANDLER_HXX diff --git a/src/linux/wpa-controller/include/Wpa/WpaEvent.hxx b/src/linux/wpa-controller/include/Wpa/WpaEvent.hxx new file mode 100644 index 00000000..2d0bc3eb --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaEvent.hxx @@ -0,0 +1,22 @@ + +#ifndef WPA_EVENT_HXX +#define WPA_EVENT_HXX + +#include + +#include + +namespace Wpa +{ +/** + * @brief Represents an event from a WPA daemon/service. + */ +struct WpaEvent +{ + WpaType Source; + std::string Payload; +}; + +} // namespace Wpa + +#endif // WPA_EVENT_HXX diff --git a/src/linux/wpa-controller/include/Wpa/WpaEventArgs.hxx b/src/linux/wpa-controller/include/Wpa/WpaEventArgs.hxx new file mode 100644 index 00000000..39be4760 --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaEventArgs.hxx @@ -0,0 +1,21 @@ + +#ifndef WPA_EVENT_ARGS_HXX +#define WPA_EVENT_ARGS_HXX + +#include + +#include + +namespace Wpa +{ +/** + * @brief The event arguments for the WPA event. + */ +struct WpaEventArgs +{ + std::chrono::time_point Timestamp; + WpaEvent Event; +}; +} // namespace Wpa + +#endif // WPA_EVENT_ARGS_HXX diff --git a/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx new file mode 100644 index 00000000..c30792ed --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx @@ -0,0 +1,102 @@ + +#ifndef WPA_EVENT_LISTENER_HXX +#define WPA_EVENT_LISTENER_HXX + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Wpa +{ +using WpaEventListenerRegistrationToken = uint32_t; + +/** + * @brief Class that processes WPA events and distributes them to registered listeners. + */ +struct WpaEventHandler : + public WpaEventSender +{ + /** + * @brief Create a new WpaEventHandler. + * + * @param wpaControlSocketConnection + */ + WpaEventHandler(std::unique_ptr wpaControlSocketConnection); + + /** + * @brief Destroy the WpaEventHandler object. + */ + ~WpaEventHandler(); + + /** + * @brief Register a listener for WPA events. + * + * @param wpaEventListener The listener to register. + * @return WpaEventListenerRegistrationToken A token that can be used to unregister the listener. + */ + WpaEventListenerRegistrationToken + RegisterEventListener(std::weak_ptr wpaEventListener); + + /** + * @brief Unregister a listener for WPA events. + * + * @param wpaEventListenerRegistrationToken The token returned by RegisterEventListener. + */ + void + UnregisterEventListener(WpaEventListenerRegistrationToken wpaEventListenerRegistrationToken); + + /** + * @brief Start listening for WPA events. + */ + void + StartListening(); + + /** + * @brief Stop listening for WPA events. + */ + void + StopListening(); + +private: + /** + * @brief Process the next WPA event. + * + * @param WpaControlSocketConnection The WPA control socket connection to use. + */ + void + ProcessNextEvent(WpaControlSocketConnection& wpaControlSocketConnection); + + /** + * @brief Listen for and process WPA events. + * + * @param wpaControlSocketConnection The WPA control socket connection to listen on. + * @param stopToken The stop token to use to stop listening. + */ + void + ProcessEvents(WpaControlSocketConnection& wpaControlSocketConnection, std::stop_token stopToken); + +private: + std::unique_ptr m_wpaControlSocketConnection{ nullptr }; + + // The below m_eventListenerStateGate mutex protects m_eventListeners, m_eventListenerRegistrationTokenNext, and + // m_eventListenerThread. + std::shared_mutex m_eventListenerStateGate; + std::unordered_map> m_eventListeners; + WpaEventListenerRegistrationToken m_eventListenerRegistrationTokenNext{ 0 }; + std::jthread m_eventListenerThread; + + // Signal value to stop the event listener thread. + static constexpr std::uint64_t EventFdStopRequestValue{ 1 }; + int m_fdEventFdStop{ -1 }; +}; +} // namespace Wpa + +#endif // WPA_EVENT_LISTENER_HXX From ef7f23574746b06882a9f3c507c5decda9d7f60f Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 02:45:08 +0000 Subject: [PATCH 04/13] Allow polymorphic deletion of IWpaEventListener. --- src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx b/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx index 1e74ba88..07f80ec9 100644 --- a/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx +++ b/src/linux/wpa-controller/include/Wpa/IWpaEventListener.hxx @@ -19,6 +19,8 @@ struct WpaEventSender */ struct IWpaEventListener { + virtual ~IWpaEventListener() = default; + /** * @brief Invoked when a WPA event is received. * From 88f0c3d7c2ceb1e7b88b92190030f35924ba3295 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 02:54:11 +0000 Subject: [PATCH 05/13] Add proxy for IWpaEventListener implementations. --- src/linux/wpa-controller/CMakeLists.txt | 3 +- .../wpa-controller/WpaEventListenerProxy.cxx | 26 +++++++++ .../include/Wpa/WpaEventListenerProxy.hxx | 58 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/linux/wpa-controller/WpaEventListenerProxy.cxx create mode 100644 src/linux/wpa-controller/include/Wpa/WpaEventListenerProxy.hxx diff --git a/src/linux/wpa-controller/CMakeLists.txt b/src/linux/wpa-controller/CMakeLists.txt index f2072d4e..5aa49382 100644 --- a/src/linux/wpa-controller/CMakeLists.txt +++ b/src/linux/wpa-controller/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(wpa-controller WpaControlSocket.cxx WpaControlSocketConnection.cxx WpaEventHandler.cxx + WpaEventListenerProxy.cxx WpaKeyValuePair.cxx WpaParsingUtilities.cxx WpaParsingUtilities.hxx @@ -31,7 +32,6 @@ target_sources(wpa-controller ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/Hostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IWpaEventListener.hxx - ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/IWpaEventProducer.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolHostapd.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpa.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/ProtocolWpaConfig.hxx @@ -45,6 +45,7 @@ target_sources(wpa-controller ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaCore.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaEvent.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaEventHandler.hxx + ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaEventListenerProxy.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaKeyValuePair.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponse.hxx ${WPA_CONTROLLER_PUBLIC_INCLUDE_PREFIX}/WpaResponseParser.hxx diff --git a/src/linux/wpa-controller/WpaEventListenerProxy.cxx b/src/linux/wpa-controller/WpaEventListenerProxy.cxx new file mode 100644 index 00000000..46ba6c8f --- /dev/null +++ b/src/linux/wpa-controller/WpaEventListenerProxy.cxx @@ -0,0 +1,26 @@ + +#include + +#include +#include +#include + +using namespace Wpa; + +WpaEventListenerProxy::WpaEventListenerProxy(IWpaEventListener &wpaEventListenerProxy) : + m_wpaEventListenerProxy(wpaEventListenerProxy) +{ +} + +/* static */ +std::shared_ptr +WpaEventListenerProxy::Create(IWpaEventListener &wpaEventListenerProxy) +{ + return std::make_shared>(wpaEventListenerProxy); +} + +void +WpaEventListenerProxy::OnWpaEvent(WpaEventSender *sender, const WpaEventArgs *eventArgs) +{ + m_wpaEventListenerProxy.OnWpaEvent(sender, eventArgs); +} diff --git a/src/linux/wpa-controller/include/Wpa/WpaEventListenerProxy.hxx b/src/linux/wpa-controller/include/Wpa/WpaEventListenerProxy.hxx new file mode 100644 index 00000000..b6236f23 --- /dev/null +++ b/src/linux/wpa-controller/include/Wpa/WpaEventListenerProxy.hxx @@ -0,0 +1,58 @@ + +#ifndef WPA_EVENT_LISTENER_PROXY_HXX +#define WPA_EVENT_LISTENER_PROXY_HXX + +#include + +#include + +namespace Wpa +{ +/** + * @brief Helper to allow use of classes that implement IWpaEventListener without providing a std::weak_ptr<> of itself + * to WpaEventHandler. + * + * The implemenation simply forwards the OnWpaEvent() call to the IWpaEventListener instance provided in the + * constructor. The caller is responsible for ensuring that the IWpaEventListener instance outlives the + * WpaEventListenerProxy instance. + */ +struct WpaEventListenerProxy : + public IWpaEventListener, + public std::enable_shared_from_this +{ + virtual ~WpaEventListenerProxy() = default; + + /** + * @brief Create a new WpaEventListenerProxy instance that forwards OnWpaEvent() calls to the provided + * IWpaEventListener. + * + * @param wpaEventListenerProxy The IWpaEventListener instance to forward OnWpaEvent() calls to. + * @return std::shared_ptr + */ + static std::shared_ptr + Create(IWpaEventListener &wpaEventListenerProxy); + + /** + * @brief Invoked when a WPA event is received. + * + * @param sender The sender or source of the event. + * @param eventArgs The event arguments. + */ + void + OnWpaEvent(WpaEventSender *sender, const WpaEventArgs *eventArgs) override; + +protected: + /** + * @brief Construct a new WpaEventListenerProxy instance that forwards OnWpaEvent() calls to the provided + * IWpaEventListener. + * + * @param wpaEventListenerProxy The IWpaEventListener instance to forward OnWpaEvent() calls to. + */ + WpaEventListenerProxy(IWpaEventListener &wpaEventListenerProxy); + +private: + IWpaEventListener &m_wpaEventListenerProxy; +}; +} // namespace Wpa + +#endif // WPA_EVENT_LISTENER_PROXY_HXX From 8691e5f72bbb52e9a5bf457bfa9e87441e2754fd Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 02:55:22 +0000 Subject: [PATCH 06/13] Implement IWpaEventListener in Hostapd class. --- src/linux/wpa-controller/Hostapd.cxx | 17 +++++++++- .../wpa-controller/include/Wpa/Hostapd.hxx | 32 ++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/linux/wpa-controller/Hostapd.cxx b/src/linux/wpa-controller/Hostapd.cxx index 37f3c1c6..71b8a413 100644 --- a/src/linux/wpa-controller/Hostapd.cxx +++ b/src/linux/wpa-controller/Hostapd.cxx @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -34,10 +35,18 @@ Hostapd::IsManagingInterface(std::string_view interfaceName) noexcept Hostapd::Hostapd(std::string_view interfaceName) : m_interface(interfaceName), - m_controller(interfaceName, WpaType::Hostapd) + m_controller(interfaceName, WpaType::Hostapd), + m_eventListenerProxy(WpaEventListenerProxy::Create(*this)), + m_eventHandler(std::make_unique(WpaControlSocketConnection::TryCreate(interfaceName, m_controller.ControlSocketPath()))), + m_eventHandlerRegistrationToken(m_eventHandler->RegisterEventListener(m_eventListenerProxy->weak_from_this())) { } +Hostapd::~Hostapd() +{ + m_eventHandler->UnregisterEventListener(m_eventHandlerRegistrationToken); +} + std::string_view Hostapd::GetInterface() { @@ -506,3 +515,9 @@ Hostapd::GetIpAddress() const noexcept { return m_ownIpAddress; } + +void +Hostapd::OnWpaEvent(WpaEventSender* sender, const WpaEventArgs* eventArgs) +{ + LOGD << std::format("Hostapd event @ {}: {:#08x} {}", eventArgs->Timestamp, reinterpret_cast(sender), eventArgs->Event.Payload); +} diff --git a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx index 8dffc630..6c2e542d 100644 --- a/src/linux/wpa-controller/include/Wpa/Hostapd.hxx +++ b/src/linux/wpa-controller/include/Wpa/Hostapd.hxx @@ -2,13 +2,17 @@ #ifndef HOSTAPD_HXX #define HOSTAPD_HXX +#include #include #include #include +#include #include #include #include +#include +#include #include namespace Wpa @@ -17,7 +21,8 @@ namespace Wpa * @brief Concrete implementation of the IHostapd interface. */ struct Hostapd : - public IHostapd + public IHostapd, + public IWpaEventListener { /** * @brief Construct a new Hostapd object. @@ -26,6 +31,11 @@ struct Hostapd : */ explicit Hostapd(std::string_view interfaceName); + /** + * @brief Destroy the Hostapd object. + */ + ~Hostapd(); + /** * @brief Determines if the specified interface is being managed by hostapd. * @@ -236,19 +246,33 @@ struct Hostapd : SetNetworkAccessServerId(std::string_view networkAccessServiceId = GenerateNetworkAccessServerId()); /** - * @brief Get the IP address of this access point. - * + * @brief Get the IP address of this access point. + * * Currently, this is non-configurable and is always set to the loopback address (127.0.0.1). - * + * * @return std::string_view */ std::string_view GetIpAddress() const noexcept; +private: + /** + * @brief Invoked when a WPA event is received. + * + * @param sender The sender or source of the event. + * @param eventArgs The event arguments. + */ + void + OnWpaEvent(WpaEventSender *sender, const WpaEventArgs *eventArgs) override; + private: const std::string m_interface; std::string m_ownIpAddress{ "127.0.0.1" }; WpaController m_controller; + std::unique_ptr m_eventHandlerControlSocketConnection{ nullptr }; + std::shared_ptr m_eventListenerProxy; + std::unique_ptr m_eventHandler{ nullptr }; + WpaEventListenerRegistrationToken m_eventHandlerRegistrationToken{}; }; } // namespace Wpa From 47c38f87c872d973af1becffc2cc16a02df09a5f Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 06:37:59 +0000 Subject: [PATCH 07/13] Avoid joining the event thread if not started. --- src/linux/wpa-controller/WpaEventHandler.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/linux/wpa-controller/WpaEventHandler.cxx b/src/linux/wpa-controller/WpaEventHandler.cxx index fbf0c1dc..5085d372 100644 --- a/src/linux/wpa-controller/WpaEventHandler.cxx +++ b/src/linux/wpa-controller/WpaEventHandler.cxx @@ -78,8 +78,10 @@ WpaEventHandler::StopListening() m_fdEventFdStop = -1; } - m_eventListenerThread.request_stop(); - m_eventListenerThread.join(); + if (m_eventListenerThread.joinable()) { + m_eventListenerThread.request_stop(); + m_eventListenerThread.join(); + } } void From fd73b3be2eb331392cd1116157b73b4a2b071f4a Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 06:38:10 +0000 Subject: [PATCH 08/13] Avoid use of invalid socket connection. --- src/linux/wpa-controller/Hostapd.cxx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/linux/wpa-controller/Hostapd.cxx b/src/linux/wpa-controller/Hostapd.cxx index 71b8a413..5532aa8d 100644 --- a/src/linux/wpa-controller/Hostapd.cxx +++ b/src/linux/wpa-controller/Hostapd.cxx @@ -36,10 +36,20 @@ Hostapd::IsManagingInterface(std::string_view interfaceName) noexcept Hostapd::Hostapd(std::string_view interfaceName) : m_interface(interfaceName), m_controller(interfaceName, WpaType::Hostapd), - m_eventListenerProxy(WpaEventListenerProxy::Create(*this)), - m_eventHandler(std::make_unique(WpaControlSocketConnection::TryCreate(interfaceName, m_controller.ControlSocketPath()))), - m_eventHandlerRegistrationToken(m_eventHandler->RegisterEventListener(m_eventListenerProxy->weak_from_this())) + m_eventListenerProxy(WpaEventListenerProxy::Create(*this)) { + auto controlSocketConnection{ WpaControlSocketConnection::TryCreate(interfaceName, m_controller.ControlSocketPath()) }; + if (!controlSocketConnection) { + throw HostapdException("Failed to create hostapd event handler control socket connection"); + } + + auto eventHandler{ std::make_unique(std::move(controlSocketConnection)) }; + if (!eventHandler) { + throw HostapdException("Failed to create hostapd event handler"); + } + + m_eventHandlerRegistrationToken = eventHandler->RegisterEventListener(m_eventListenerProxy->weak_from_this()); + m_eventHandler = std::move(eventHandler); } Hostapd::~Hostapd() From 17f4ffa6174c423ace65fbcc56427a368b393724 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 06:38:26 +0000 Subject: [PATCH 09/13] Update test for earlier failure in creation of Hostapd with invalid interface. --- tests/unit/linux/wpa-controller/TestHostapd.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/linux/wpa-controller/TestHostapd.cxx b/tests/unit/linux/wpa-controller/TestHostapd.cxx index 9f23894f..b76a1488 100644 --- a/tests/unit/linux/wpa-controller/TestHostapd.cxx +++ b/tests/unit/linux/wpa-controller/TestHostapd.cxx @@ -49,10 +49,10 @@ TEST_CASE("Create a Hostapd instance (root)", "[wpa][hostapd][client][remote]") REQUIRE_NOTHROW(hostapd2.emplace(WpaDaemonManager::InterfaceNameDefault)); } - SECTION("Sending command on invalid interface fails") + SECTION("Create with invalid interface name fails") { - Hostapd hostapd("invalid"); - REQUIRE_THROWS_AS(hostapd.Ping(), HostapdException); + std::optional hostapd; + REQUIRE_THROWS_AS(hostapd.emplace("invalid"), HostapdException); } SECTION("Create reflects correct interface name") From b6da61e50cacf851f32e009daaf8b2b2093c6ea2 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 07:10:18 +0000 Subject: [PATCH 10/13] Start listening for events on create. --- src/linux/wpa-controller/Hostapd.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/linux/wpa-controller/Hostapd.cxx b/src/linux/wpa-controller/Hostapd.cxx index 5532aa8d..747a044f 100644 --- a/src/linux/wpa-controller/Hostapd.cxx +++ b/src/linux/wpa-controller/Hostapd.cxx @@ -50,6 +50,7 @@ Hostapd::Hostapd(std::string_view interfaceName) : m_eventHandlerRegistrationToken = eventHandler->RegisterEventListener(m_eventListenerProxy->weak_from_this()); m_eventHandler = std::move(eventHandler); + m_eventHandler->StartListening(); } Hostapd::~Hostapd() From 0ea21154b8051b446ba07bd7dea934874c41a3b1 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 07:10:41 +0000 Subject: [PATCH 11/13] Sequencing fixes. --- src/linux/wpa-controller/WpaEventHandler.cxx | 25 +++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/linux/wpa-controller/WpaEventHandler.cxx b/src/linux/wpa-controller/WpaEventHandler.cxx index 5085d372..e6d36c72 100644 --- a/src/linux/wpa-controller/WpaEventHandler.cxx +++ b/src/linux/wpa-controller/WpaEventHandler.cxx @@ -66,21 +66,21 @@ WpaEventHandler::StartListening() void WpaEventHandler::StopListening() { + std::shared_lock eventListenerReadLock{ m_eventListenerStateGate }; + // Check if the eventfd file descriptor for signaling thread stop has been created. If so, signal the thread to stop. if (m_fdEventFdStop != -1) { uint64_t value{ EventFdStopRequestValue }; ssize_t numWritten = write(m_fdEventFdStop, &value, sizeof value); if (numWritten != sizeof value) { - LOGE << std::format("Failed to write to eventfd for interface '{}'.", m_wpaControlSocketConnection->GetInterfaceName()); + LOGE << std::format("Failed to write eventfd stop signal value for interface '{}'.", m_wpaControlSocketConnection->GetInterfaceName()); } - - close(m_fdEventFdStop); - m_fdEventFdStop = -1; } if (m_eventListenerThread.joinable()) { m_eventListenerThread.request_stop(); m_eventListenerThread.join(); + m_eventListenerThread = {}; } } @@ -151,6 +151,12 @@ WpaEventHandler::ProcessNextEvent(WpaControlSocketConnection& wpaControlSocketCo void WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConnection, std::stop_token stopToken) { + const auto interfaceName{ wpaControlSocketConnection.GetInterfaceName() }; + LOGD << std::format("WPA event listener thread for interface '{}' started", interfaceName); + auto logOnExit = notstd::ScopeExit([&]{ + LOGD << std::format("WPA event listener thread for interface '{}' stopped", interfaceName); + }); + // One event for eventfd stop signaling, and one for WPA control socket event signaling. static constexpr int EpollEventsMax{ 2 }; @@ -181,8 +187,11 @@ WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConne return; } - auto closeEventFdStopFileDescriptorOnExit = notstd::ScopeExit([&] { - close(fdEventFdStop); + m_fdEventFdStop = fdEventFdStop; + + auto closeEventFdStopFileDescriptorOnExit = notstd::ScopeExit([this] { + close(m_fdEventFdStop); + m_fdEventFdStop = -1; }); eventEventFd->events = EPOLLIN; @@ -216,7 +225,7 @@ WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConne return; } - // Attach to WAP eventstream. + // Attach to WPA event stream. ret = wpa_ctrl_attach(wpaControlSocket); if (ret < 0) { ret = errno; @@ -255,7 +264,7 @@ WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConne ssize_t numRead = read(fdEventFdStop, &value, sizeof value); if (numRead != sizeof value) { ret = errno; - LOGE << std::format("Failed to read from eventfd for interface '{}': {}", InterfaceName, ret); + LOGE << std::format("Failed to read from eventfd for interface '{}': numRead {} ({} {})", InterfaceName, numRead, ret, strerror(ret)); continue; } if (value == EventFdStopRequestValue) { From 874265da42335a63109e81fe8e3df271a0c1225d Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 07:17:16 +0000 Subject: [PATCH 12/13] Fix typo. --- src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx index c30792ed..2c61cb09 100644 --- a/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx +++ b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx @@ -1,6 +1,6 @@ -#ifndef WPA_EVENT_LISTENER_HXX -#define WPA_EVENT_LISTENER_HXX +#ifndef WPA_EVENT_HANDLER_HXX +#define WPA_EVENT_HANDLER_HXX #include #include @@ -99,4 +99,4 @@ private: }; } // namespace Wpa -#endif // WPA_EVENT_LISTENER_HXX +#endif // WPA_EVENT_HANDLER_HXX From becbfa0a80090d5590857cd68c6610d2573882c0 Mon Sep 17 00:00:00 2001 From: Andrew Beltrano Date: Tue, 2 Jul 2024 07:46:49 +0000 Subject: [PATCH 13/13] Accept wpa type with event listener. --- src/linux/wpa-controller/Hostapd.cxx | 4 ++-- src/linux/wpa-controller/WpaEventHandler.cxx | 9 +++++---- src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/linux/wpa-controller/Hostapd.cxx b/src/linux/wpa-controller/Hostapd.cxx index 747a044f..c3cb6062 100644 --- a/src/linux/wpa-controller/Hostapd.cxx +++ b/src/linux/wpa-controller/Hostapd.cxx @@ -43,11 +43,11 @@ Hostapd::Hostapd(std::string_view interfaceName) : throw HostapdException("Failed to create hostapd event handler control socket connection"); } - auto eventHandler{ std::make_unique(std::move(controlSocketConnection)) }; + auto eventHandler{ std::make_unique(std::move(controlSocketConnection), WpaType::Hostapd) }; if (!eventHandler) { throw HostapdException("Failed to create hostapd event handler"); } - + m_eventHandlerRegistrationToken = eventHandler->RegisterEventListener(m_eventListenerProxy->weak_from_this()); m_eventHandler = std::move(eventHandler); m_eventHandler->StartListening(); diff --git a/src/linux/wpa-controller/WpaEventHandler.cxx b/src/linux/wpa-controller/WpaEventHandler.cxx index e6d36c72..9a63e250 100644 --- a/src/linux/wpa-controller/WpaEventHandler.cxx +++ b/src/linux/wpa-controller/WpaEventHandler.cxx @@ -18,8 +18,9 @@ using namespace Wpa; -WpaEventHandler::WpaEventHandler(std::unique_ptr wpaControlSocketConnection) : - m_wpaControlSocketConnection(std::move(wpaControlSocketConnection)) +WpaEventHandler::WpaEventHandler(std::unique_ptr wpaControlSocketConnection, WpaType wpaType) : + m_wpaControlSocketConnection(std::move(wpaControlSocketConnection)), + m_wpaType(wpaType) { } @@ -112,7 +113,7 @@ WpaEventHandler::ProcessNextEvent(WpaControlSocketConnection& wpaControlSocketCo WpaEventArgs wpaEventArgs{ .Timestamp = timestampNow, .Event = { - .Source = WpaType::Hostapd, // TODO: determine true source of the event. + .Source = m_wpaType, .Payload = { std::data(wpaEventBuffer), wpaEventBufferSize }, }, }; @@ -153,7 +154,7 @@ WpaEventHandler::ProcessEvents(WpaControlSocketConnection& wpaControlSocketConne { const auto interfaceName{ wpaControlSocketConnection.GetInterfaceName() }; LOGD << std::format("WPA event listener thread for interface '{}' started", interfaceName); - auto logOnExit = notstd::ScopeExit([&]{ + auto logOnExit = notstd::ScopeExit([&] { LOGD << std::format("WPA event listener thread for interface '{}' stopped", interfaceName); }); diff --git a/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx index 2c61cb09..08f64e91 100644 --- a/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx +++ b/src/linux/wpa-controller/include/Wpa/WpaEventHandler.hxx @@ -13,6 +13,7 @@ #include #include +#include namespace Wpa { @@ -29,7 +30,7 @@ struct WpaEventHandler : * * @param wpaControlSocketConnection */ - WpaEventHandler(std::unique_ptr wpaControlSocketConnection); + WpaEventHandler(std::unique_ptr wpaControlSocketConnection, WpaType wpaType); /** * @brief Destroy the WpaEventHandler object. @@ -85,6 +86,7 @@ private: private: std::unique_ptr m_wpaControlSocketConnection{ nullptr }; + WpaType m_wpaType; // The below m_eventListenerStateGate mutex protects m_eventListeners, m_eventListenerRegistrationTokenNext, and // m_eventListenerThread.