From de2a05214a87f7893d94b7e9e6f03de7c7eb44c6 Mon Sep 17 00:00:00 2001 From: Mario Maz Date: Fri, 31 Jul 2020 19:14:33 +0200 Subject: [PATCH] bcl: add InterfaceStateManager and its dependencies https://jira.prplfoundation.org/browse/PPM-18 Create InterfaceStateManager class and all its dependencies. Follow-up commits will use this class in the ieee1905_transport process as a new mechanism to detect changes in the state of network interfaces, based on the new sockets API. Signed-off-by: Mario Maz --- common/beerocks/bcl/CMakeLists.txt | 1 + .../bcl/include/bcl/network/file_descriptor.h | 37 ++ .../bcl/network/file_descriptor_impl.h | 139 +++++++ .../bcl/network/interface_flags_reader.h | 38 ++ .../bcl/network/interface_flags_reader_impl.h | 32 ++ .../bcl/network/interface_state_manager.h | 28 ++ .../network/interface_state_manager_impl.h | 120 ++++++ .../bcl/network/interface_state_monitor.h | 86 +++++ .../network/interface_state_monitor_impl.h | 90 +++++ .../network/interface_state_monitor_mock.h | 35 ++ .../bcl/network/interface_state_reader.h | 37 ++ .../bcl/network/interface_state_reader_impl.h | 49 +++ .../bcl/network/interface_state_reader_mock.h | 27 ++ .../bcl/include/bcl/network/sockets.h | 184 +++++++++ .../bcl/include/bcl/network/sockets_impl.h | 348 ++++++++++++++++++ .../network/interface_flags_reader_impl.cpp | 37 ++ .../network/interface_state_manager_impl.cpp | 79 ++++ .../network/interface_state_monitor_impl.cpp | 75 ++++ .../network/interface_state_reader_impl.cpp | 36 ++ .../interface_state_manager_impl_test.cpp | 150 ++++++++ 20 files changed, 1628 insertions(+) create mode 100644 common/beerocks/bcl/include/bcl/network/file_descriptor.h create mode 100644 common/beerocks/bcl/include/bcl/network/file_descriptor_impl.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_flags_reader.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_flags_reader_impl.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_manager.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_manager_impl.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_monitor.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_monitor_impl.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_monitor_mock.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_reader.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_reader_impl.h create mode 100644 common/beerocks/bcl/include/bcl/network/interface_state_reader_mock.h create mode 100644 common/beerocks/bcl/include/bcl/network/sockets.h create mode 100644 common/beerocks/bcl/include/bcl/network/sockets_impl.h create mode 100644 common/beerocks/bcl/source/network/interface_flags_reader_impl.cpp create mode 100644 common/beerocks/bcl/source/network/interface_state_manager_impl.cpp create mode 100644 common/beerocks/bcl/source/network/interface_state_monitor_impl.cpp create mode 100644 common/beerocks/bcl/source/network/interface_state_reader_impl.cpp create mode 100644 common/beerocks/bcl/unit_tests/interface_state_manager_impl_test.cpp diff --git a/common/beerocks/bcl/CMakeLists.txt b/common/beerocks/bcl/CMakeLists.txt index 9048f458a8..5859173d51 100644 --- a/common/beerocks/bcl/CMakeLists.txt +++ b/common/beerocks/bcl/CMakeLists.txt @@ -47,6 +47,7 @@ if (BUILD_TESTS) ${bcl_sources} ${MODULE_PATH}/unit_tests/network_utils_test.cpp ${MODULE_PATH}/unit_tests/event_loop_impl_test.cpp + ${MODULE_PATH}/unit_tests/interface_state_manager_impl_test.cpp ${MODULE_PATH}/unit_tests/wireless_utils_test.cpp ) add_executable(${TEST_PROJECT_NAME} diff --git a/common/beerocks/bcl/include/bcl/network/file_descriptor.h b/common/beerocks/bcl/include/bcl/network/file_descriptor.h new file mode 100644 index 0000000000..25b16241bd --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/file_descriptor.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_FILE_DESCRIPTOR_H_ +#define BCL_NETWORK_FILE_DESCRIPTOR_H_ + +namespace beerocks { +namespace net { + +/** + * This interface models OS resources implementing the file descriptor interface. + */ +class FileDescriptor { +public: + static constexpr int invalid_descriptor = -1; + + virtual ~FileDescriptor() = default; + + /** + * @brief Returns the file descriptor. + * + * A file descriptor is a number that uniquely identifies an open file in the OS. + * + * @return File descriptor. + */ + virtual int fd() = 0; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_FILE_DESCRIPTOR_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/file_descriptor_impl.h b/common/beerocks/bcl/include/bcl/network/file_descriptor_impl.h new file mode 100644 index 0000000000..c7f3c3c303 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/file_descriptor_impl.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_FILE_DESCRIPTOR_IMPL_H_ +#define BCL_NETWORK_FILE_DESCRIPTOR_IMPL_H_ + +#include "file_descriptor.h" + +#include + +#include + +namespace beerocks { +namespace net { + +/** + * File descriptor implementation. + * This class is basically a wrapper around an `int fd` that automatically closes the descriptor + * on destructor and prevents from having multiple copies of the file descriptor to avoid + * programming errors. + * + * This class will be aggregated by all classes modeling OS resources implementing the file + * descriptor interface. + */ +class FileDescriptorImpl : public FileDescriptor { +public: + /** + * @brief Class constructor. + * + * @param fd File descriptor value + */ + explicit FileDescriptorImpl(int fd) : m_fd(fd) + { + if (invalid_descriptor == fd) { + LOG(ERROR) << "Invalid file descriptor: " << strerror(errno); + } + } + + /** + * @brief Copy constructor + * + * Delete copy constructor to avoid having multiple copies of the file descriptor + */ + FileDescriptorImpl(const FileDescriptorImpl &) = delete; + + /** + * @brief Move constructor. + * + * A move constructor allows the resources owned by an rvalue object to be moved into an + * lvalue without creating its copy. + */ + FileDescriptorImpl(FileDescriptorImpl &&obj) + { + this->m_fd = obj.m_fd; + obj.m_fd = FileDescriptor::invalid_descriptor; + } + + /** + * @brief Assignment operator + * + * Delete assignment operator to avoid having multiple copies of the file descriptor + */ + FileDescriptorImpl &operator=(const FileDescriptorImpl &) = delete; + + /** + * @brief Move assignment operator + * + * The move assignment operator is used to transfer ownership of a file descriptor + */ + FileDescriptorImpl &operator=(FileDescriptorImpl &&obj) + { + // Self-assignment detection + if (&obj == this) { + return *this; + } + + // Release any resource we're holding + close(); + + // Transfer ownership of obj.m_fd to m_fd + this->m_fd = obj.m_fd; + + return *this; + } + + /** + * @brief Class destructor. + * + * Close socket on destructor (if it is still open) + */ + ~FileDescriptorImpl() override { close(); } + + /** + * @brief Gets file descriptor. + * + * File descriptor can be used for example in: + * - select(), poll() or epoll() to wait for events on this descriptor. + * - send() family of functions to send data through the socket connection. + * - recv() family of functions to receive data from the socket connection. + * + * @return File descriptor value. + */ + int fd() override { return m_fd; } + +private: + /** + * File descriptor value + */ + int m_fd; + + /** + * @brief Closes file descriptor. + * + * If valid, closes file descriptor and then invalidates it. + * + * @return True on success and false otherwise (for example, if it was already closed) + */ + bool close() + { + if (FileDescriptor::invalid_descriptor == m_fd) { + return false; + } + + int rc = ::close(m_fd); + m_fd = FileDescriptor::invalid_descriptor; + + return (0 == rc); + } +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_FILE_DESCRIPTOR_IMPL_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_flags_reader.h b/common/beerocks/bcl/include/bcl/network/interface_flags_reader.h new file mode 100644 index 0000000000..fcb2a9a747 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_flags_reader.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_FLAGS_READER_H_ +#define BCL_NETWORK_INTERFACE_FLAGS_READER_H_ + +#include + +#include + +namespace beerocks { +namespace net { + +class InterfaceFlagsReader { +public: + virtual ~InterfaceFlagsReader() = default; + + /** + * @brief Reads interface flags. + * + * Reads the active flag word of the network interface with given index. + * + * @param[in] iface_name Interface name. + * @param[out] iface_flags Device flags (bitmask) + * @return True on success and false otherwise. + */ + virtual bool read_flags(const std::string &iface_name, uint16_t &iface_flags) = 0; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_FLAGS_READER_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_flags_reader_impl.h b/common/beerocks/bcl/include/bcl/network/interface_flags_reader_impl.h new file mode 100644 index 0000000000..71a7842039 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_flags_reader_impl.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_FLAGS_READER_IMPL_H_ +#define BCL_NETWORK_INTERFACE_FLAGS_READER_IMPL_H_ + +#include "interface_flags_reader.h" + +namespace beerocks { +namespace net { + +class InterfaceFlagsReaderImpl : public InterfaceFlagsReader { +public: + /** + * @brief Reads interface flags. + * + * @see InterfaceFlagsReader::read_flags + * + * This implementation uses ioctl() with SIOCGIFFLAGS to read device flags. + */ + bool read_flags(const std::string &iface_name, uint16_t &iface_flags) override; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_FLAGS_READER_IMPL_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_manager.h b/common/beerocks/bcl/include/bcl/network/interface_state_manager.h new file mode 100644 index 0000000000..4340fa059a --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_manager.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_MANAGER_H_ +#define BCL_NETWORK_INTERFACE_STATE_MANAGER_H_ + +#include "interface_state_monitor.h" +#include "interface_state_reader.h" + +namespace beerocks { +namespace net { + +/** + * The InterfaceStateManager is a facade interface for both the InterfaceStateMonitor and + * InterfaceStateReader interfaces together. + */ +class InterfaceStateManager : public InterfaceStateMonitor, public InterfaceStateReader { +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_MANAGER_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_manager_impl.h b/common/beerocks/bcl/include/bcl/network/interface_state_manager_impl.h new file mode 100644 index 0000000000..34c541782b --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_manager_impl.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_MANAGER_IMPL_H_ +#define BCL_NETWORK_INTERFACE_STATE_MANAGER_IMPL_H_ + +#include "interface_state_manager.h" + +#include +#include + +namespace beerocks { +namespace net { + +/** + * This class implements the InterfaceStateManager facade interface in terms of (by delegating to) + * the InterfaceStateMonitor and InterfaceStateReader interfaces and performs additional + * functionality before/after forwarding requests. + */ +class InterfaceStateManagerImpl : public InterfaceStateManager { +public: + /** + * @brief Class constructor + * + * This implementation delegates InterfaceStateMonitor and InterfaceStateReader requests to + * given reader and monitor instances respectively. + * + * The interface state monitor is used to monitor changes in the state of the network + * interfaces in an event-driven way, that is, without polling (which is very CPU expensive). + * + * The interface state reader is used to read the state of an interface when it is not already + * known (i.e. the first time it is queried and no state-changed event has occurred yet). + * + * @param interface_state_monitor Interface state monitor. + * @param interface_state_reader Interface state reader. + */ + InterfaceStateManagerImpl(std::unique_ptr interface_state_monitor, + std::unique_ptr interface_state_reader); + + /** + * @brief Starts the interface state manager. + * + * Installs a state-changed event handler on the monitor and then delegates to + * InterfaceStateMonitor::start. + * + * The handler function stores the interface state into the list of current states for each + * known interface. This way, when read_state() is called, the cached state can be quickly + * returned instead of having to query the network interface with the reader. + * + * @see InterfaceStateMonitor::start + */ + bool start() override; + + /** + * @brief Stops the interface state manager. + * + * Removes the state-changed event handler on the monitor and then delegates to + * InterfaceStateMonitor::stop. + * + * @see InterfaceStateMonitor::stop + */ + bool stop() override; + + /** + * @brief Reads interface up-and-running state. + * + * If the interface state is known (either because a state-changed event has occurred or + * because it has been explicitly read), the cached state is returned. Otherwise delegates to + * InterfaceStateReader::read_state and stores obtained state. + * + * @see InterfaceStateReader::read_state + */ + bool read_state(const std::string &iface_name, bool &iface_state) override; + +private: + /** + * Interface state monitor used to monitor changes in the state of the network interfaces. + */ + std::unique_ptr m_interface_state_monitor; + + /** + * Interface state reader used to read the state of an interface when it is not already known + * (i.e. the first time it is queried and no state-changed event has been occurred yet). + */ + std::unique_ptr m_interface_state_reader; + + /** + * Map containing the current state of each known interface. + * The map key is the interface name and the map value is the interface state (true if + * up-and-running and false otherwise). + */ + std::unordered_map m_interface_states; + + /** + * @brief Gets last known interface state. + * + * @param[in] iface_name Interface name. + * @param[out] iface_state Interface state (true if it is up-and-running). + * @return True on success and false otherwise (i.e.: interface state is not known yet). + */ + bool get_state(const std::string &iface_name, bool &iface_state); + + /** + * @brief Sets last known interface state. + * + * @param[in] iface_name Interface name. + * @param[in] iface_state Interface state (true if it is up and running). + */ + void set_state(const std::string &iface_name, bool iface_state); +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_MANAGER_IMPL_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_monitor.h b/common/beerocks/bcl/include/bcl/network/interface_state_monitor.h new file mode 100644 index 0000000000..ba04a68ece --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_monitor.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_MONITOR_H_ +#define BCL_NETWORK_INTERFACE_STATE_MONITOR_H_ + +#include +#include + +namespace beerocks { +namespace net { + +class InterfaceStateMonitor { +public: + /** + * Network interface state-change handler function. + * + * @param iface_name Interface name. + * @param iface_state Interface state (true if it is up and running). + */ + using StateChangeHandler = std::function; + + /** + * @brief Class destructor + */ + virtual ~InterfaceStateMonitor() = default; + + /** + * @brief Starts the interface state monitor. + * + * Starts monitoring the state of all the network interfaces and calls back the installed + * handler (if any) whenever any of the interfaces changes its state to or from the + * up-and-running value. + * + * @return True on success and false otherwise. + */ + virtual bool start() = 0; + + /** + * @brief Stops the interface state monitor. + * + * @return True on success and false otherwise. + */ + virtual bool stop() = 0; + + /** + * @brief Sets the state-changed event handler function. + * + * Sets the callback function to handle network interface state changes. + * Use nullptr to remove previously installed callback function. + * + * @param handler State change handler function (or nullptr). + */ + void set_handler(const StateChangeHandler &handler) { m_handler = handler; } + +protected: + /** + * @brief Notifies a network interface state-changed event. + * + * @param iface_name Name of the network interface that changed state. + * @param iface_state New state of the network interface (true means up-and-running). + */ + void notify_state_changed(const std::string &iface_name, bool iface_state) const + { + if (m_handler) { + m_handler(iface_name, iface_state); + } + } + +private: + /** + * Network interface state-change handler function that is called back whenever any network + * interface changes its state. + */ + StateChangeHandler m_handler; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_MONITOR_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_monitor_impl.h b/common/beerocks/bcl/include/bcl/network/interface_state_monitor_impl.h new file mode 100644 index 0000000000..427efe409d --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_monitor_impl.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_MONITOR_IMPL_H_ +#define BCL_NETWORK_INTERFACE_STATE_MONITOR_IMPL_H_ + +#include "interface_state_monitor.h" +#include "sockets_impl.h" + +#include + +namespace beerocks { + +class EventLoop; + +namespace net { + +class InterfaceStateMonitorImpl : public InterfaceStateMonitor { + static constexpr size_t netlink_buffer_size = 8192; + +public: + /** + * @brief Class constructor + * + * @param connection Netlink socket connection for kernel/user-space communication. + * @param event_loop Event loop to wait for I/O events. + */ + InterfaceStateMonitorImpl(const std::shared_ptr &connection, + const std::shared_ptr &event_loop); + + /** + * @brief Starts the interface state monitor. + * + * @see InterfaceStateMonitor::start + */ + bool start() override; + + /** + * @brief Stops the interface state monitor. + * + * @see InterfaceStateMonitor::stop + */ + bool stop() override; + +private: + /** + * Buffer to hold data received through socket connection + */ + BufferImpl m_buffer; + + /** + * Socket connection through which interface state information is received. + */ + std::shared_ptr m_connection; + + /** + * Application event loop used by the monitor to wait for I/O events. + */ + std::shared_ptr m_event_loop; + + /** + * @brief Parses data received through the Netlink socket connection. + * + * The array of bytes contains a list of Netlink messages. + * + * @param data Pointer to array of bytes to parse. + * @param length Number of bytes to parse. + */ + void parse(const uint8_t *data, size_t length) const; + + /** + * @brief Parses message received through the Netlink socket connection. + * + * If the type of the Netlink message is RTM_NEWLINK or RTM_DELLINK then reads the interface + * index and state and notifies a change in the interface state. + * + * @param msg_hdr Netlink message to parse. + */ + void parse(const nlmsghdr *msg_hdr) const; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_MONITOR_IMPL_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_monitor_mock.h b/common/beerocks/bcl/include/bcl/network/interface_state_monitor_mock.h new file mode 100644 index 0000000000..63198905d7 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_monitor_mock.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_MONITOR_MOCK_H_ +#define BCL_NETWORK_INTERFACE_STATE_MONITOR_MOCK_H_ + +#include "interface_state_monitor.h" + +#include + +namespace beerocks { +namespace net { + +class InterfaceStateMonitorMock : public InterfaceStateMonitor { +public: + MOCK_METHOD(bool, start, (), (override)); + MOCK_METHOD(bool, stop, (), (override)); + + /** + * This method was inherited as protected but we're changing it to public via a using + * declaration so we can invoke it from unit tests to emulate that a state-changed event has + * occurred. + */ + using InterfaceStateMonitor::notify_state_changed; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_MONITOR_MOCK_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_reader.h b/common/beerocks/bcl/include/bcl/network/interface_state_reader.h new file mode 100644 index 0000000000..2e9a876a22 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_reader.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_READER_H_ +#define BCL_NETWORK_INTERFACE_STATE_READER_H_ + +#include + +namespace beerocks { +namespace net { + +class InterfaceStateReader { +public: + virtual ~InterfaceStateReader() = default; + + /** + * @brief Reads interface up-and-running state. + * + * Reads the state of the network interface with given name. State is true if the interface is + * up and running and false otherwise. + * + * @param[in] iface_name Interface name. + * @param[out] iface_state Interface state (true if it is up and running). + * @return True on success and false otherwise. + */ + virtual bool read_state(const std::string &iface_name, bool &iface_state) = 0; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_READER_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_reader_impl.h b/common/beerocks/bcl/include/bcl/network/interface_state_reader_impl.h new file mode 100644 index 0000000000..aca49c043f --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_reader_impl.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_READER_IMPL_H_ +#define BCL_NETWORK_INTERFACE_STATE_READER_IMPL_H_ + +#include "interface_state_reader.h" + +#include + +namespace beerocks { +namespace net { + +class InterfaceFlagsReader; + +class InterfaceStateReaderImpl : public InterfaceStateReader { +public: + /** + * @brief Class constructor + */ + explicit InterfaceStateReaderImpl( + const std::shared_ptr &interface_flags_reader); + + /** + * @brief Reads interface up-and-running state. + * + * @see InterfaceStateReader::read_state + * + * This implementation uses an interface flags reader to read device flags and compares the + * bitmask against bits IFF_UP & IFF_RUNNING. + */ + bool read_state(const std::string &iface_name, bool &iface_state) override; + +private: + /** + * Interface flags reader used to read device flags. + */ + std::shared_ptr m_interface_flags_reader; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_READER_IMPL_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/interface_state_reader_mock.h b/common/beerocks/bcl/include/bcl/network/interface_state_reader_mock.h new file mode 100644 index 0000000000..2098585590 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/interface_state_reader_mock.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_INTERFACE_STATE_READER_MOCK_H_ +#define BCL_NETWORK_INTERFACE_STATE_READER_MOCK_H_ + +#include "interface_state_reader.h" + +#include + +namespace beerocks { +namespace net { + +class InterfaceStateReaderMock : public InterfaceStateReader { +public: + MOCK_METHOD(bool, read_state, (const std::string &iface_name, bool &iface_state), (override)); +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_INTERFACE_STATE_READER_MOCK_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/sockets.h b/common/beerocks/bcl/include/bcl/network/sockets.h new file mode 100644 index 0000000000..1669bf3599 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/sockets.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_SOCKETS_H_ +#define BCL_NETWORK_SOCKETS_H_ + +#include "file_descriptor_impl.h" + +#include + +#include + +namespace beerocks { +namespace net { + +/** + * Array of bytes used to hold data received through a socket. + * Code is programmed to interfaces so it does not care about which implementation is used. + * Unit tests can use a mock and set different expectations per test (pretend that different data + * has been received through the socket). + */ +class Buffer { +public: + virtual ~Buffer() = default; + virtual const uint8_t *data() const = 0; + virtual size_t size() const = 0; + virtual void clear() = 0; + + uint8_t *data() { return const_cast(const_cast(this)->data()); } +}; + +/** + * Sockets are OS resources implementing the file descriptor interface. The way this fact is + * modeled is by extending the FileDescriptor interface. + */ +class Socket : public FileDescriptor { +public: + /** + * Wrapper class around sockaddr (structure describing a generic socket address) + */ + class Address { + public: + /** + * @brief Class destructor + */ + virtual ~Address() = default; + + /** + * @brief Returns address of sockaddr structure. + * + * @return address of sockaddr. + */ + virtual const struct sockaddr *sockaddr() const = 0; + + /** + * @brief Returns the length of the sockaddr structure. + * + * @return length of sockaddr + */ + virtual socklen_t length() const = 0; + + /** + * @brief Returns address of sockaddr structure. + * + * This is the non-const version of the method with the same name. + * + * @return address of sockaddr. + */ + struct sockaddr *sockaddr() + { + /** + * This is a way to "Avoid Duplication in const and Non-const Member Function" as + * described in "Effective C++, 3rd ed" by Scott Meyers. + * The two casts and function call may be ugly but they're correct and the method is + * implemented in the interface class, so available to all implementation classes for free. + */ + return const_cast(const_cast(this)->sockaddr()); + } + }; + + /** + * Classes implementing this interface model either the socket connection established at the + * server side when accept() system call is called or at the client side when connect() is called. + * + * The interface defines the methods to send data over a socket and to receive data from a socket. + */ + class Connection { + public: + /** + * @brief Class destructor + */ + virtual ~Connection() = default; + + /** + * @brief Returns the underlying socket used by this connection. + * + * Access to the underlying socket is required to obtain the socket file descriptor with which + * wait for read or write events using select() or epoll() functions. + * + * @return Socket used by the connection + */ + virtual std::shared_ptr socket() = 0; + + /** + * @brief Receives data through the socket connection. + * + * @param[out] buffer Buffer to hold received data. + * @param[in] offset Position into the buffer to start receiving data. + * @return Number of bytes received, -1 on failure. + */ + virtual int receive(Buffer &buffer, size_t offset = 0) = 0; + + /** + * @brief Receives data through the socket connection. + * + * @param[out] buffer Buffer to hold received data. + * @param[out] address Address where the data came from. + * @return Number of bytes received, -1 on failure. + */ + virtual int receive_from(Buffer &buffer, Address &address) = 0; + + /** + * @brief Sends data through the socket connection. + * + * @param[in] buffer Buffer holding data to send. + * @param[in] length Number of bytes to send. + * @return Number of bytes transmitted, -1 on failure. + */ + virtual int send(const Buffer &buffer, size_t length) = 0; + + /** + * @brief Sends data through the socket connection. + * + * @param[in] buffer Buffer holding data to send. + * @param[in] length Number of bytes to be transmitted. + * @param[in] address Destination address. + * @return Number of bytes transmitted, -1 on failure. + */ + virtual int send_to(const Buffer &buffer, size_t length, const Address &address) = 0; + }; +}; + +class ServerSocket { +public: + /** + * @brief Class destructor + */ + virtual ~ServerSocket() = default; + + /** + * @brief Accepts a connection request. + * + * @param address Address of the peer socket. + * @return First connection request on the queue of pending connections for the listening + * socket. + */ + virtual std::unique_ptr accept(Socket::Address &address) = 0; +}; + +class ClientSocket { +public: + /** + * @brief Class destructor + */ + virtual ~ClientSocket() = default; + + /** + * @brief Connects the socket to the address specified. + * + * @param address Destination address. + * @return Connection established with peer socket. + */ + virtual std::unique_ptr connect(const Socket::Address &address) = 0; +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_SOCKETS_H_ */ diff --git a/common/beerocks/bcl/include/bcl/network/sockets_impl.h b/common/beerocks/bcl/include/bcl/network/sockets_impl.h new file mode 100644 index 0000000000..8760a4dee5 --- /dev/null +++ b/common/beerocks/bcl/include/bcl/network/sockets_impl.h @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef BCL_NETWORK_SOCKETS_IMPL_H_ +#define BCL_NETWORK_SOCKETS_IMPL_H_ + +#include "sockets.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace beerocks { +namespace net { + +/** + * One possible Buffer implementation + */ +template class BufferImpl : public Buffer { +public: + const uint8_t *data() const override { return m_data; } + size_t size() const override { return sizeof(m_data); } + void clear() override { memset(m_data, 0, size()); } + +private: + uint8_t m_data[Size]{}; +}; + +/** + * Abstract base class for all types of sockets: Raw, UDP, TCP, UDS, ... + * This implementation class aggregates a FileDescriptor implementation so it has a file + * descriptor. Methods overridden from FileDescriptor interface delegate on the aggregated + * implementation. + * Derived classes provide the file descriptor obtained with a call to socket(), using different + * family, type and protocol parameters. + * This class aggregates a FileDescriptor instead of inheriting from one of its implementations to + * follow the principle of "Favor Aggregation over Inheritance". + * See https://wiki.c2.com/?UseCompositionAndInterfacesWithoutClassInheritance + */ +class SocketAbstractImpl : public Socket { +public: + /** + * @brief Returns the socket file descriptor. + * + * @return Socket file descriptor. + */ + int fd() override { return m_descriptor.fd(); } + +protected: + /** + * @brief Class constructor. + * + * Constructor is protected so only derived classes can call it. + */ + explicit SocketAbstractImpl(int fd) : m_descriptor(fd) {} + +private: + /** + * File descriptor (i.e.: wrapper to `int fd` that closes descriptor on destructor) + */ + FileDescriptorImpl m_descriptor; +}; + +class UdsAddress : public Socket::Address { +public: + UdsAddress(const std::string &path = "") + { + m_address.sun_family = AF_UNIX; + string_utils::copy_string(m_address.sun_path, path.c_str(), sizeof(m_address.sun_path)); + } + + std::string path() const { return m_address.sun_path; } + + const struct sockaddr *sockaddr() const override + { + return reinterpret_cast(&m_address); + } + socklen_t length() const override { return m_length; } + +private: + sockaddr_un m_address = {}; + socklen_t m_length = sizeof(m_address); +}; + +class InternetAddress : public Socket::Address { +public: + explicit InternetAddress(uint16_t port, uint32_t address = INADDR_ANY) + { + m_address.sin_family = AF_INET; + m_address.sin_addr.s_addr = address; + m_address.sin_port = htons(port); + } + + const struct sockaddr *sockaddr() const override + { + return reinterpret_cast(&m_address); + } + socklen_t length() const override { return m_length; } + +private: + sockaddr_in m_address = {}; + socklen_t m_length = sizeof(m_address); +}; + +class LinkLevelAddress : public Socket::Address { +public: + LinkLevelAddress() {} + + LinkLevelAddress(uint32_t iface_index, const sMacAddr &mac) + { + m_address.sll_family = AF_PACKET; + m_address.sll_ifindex = iface_index; + m_address.sll_halen = sizeof(sMacAddr); + std::copy_n(mac.oct, sizeof(sMacAddr), m_address.sll_addr); + } + + const struct sockaddr *sockaddr() const override + { + return reinterpret_cast(&m_address); + } + socklen_t length() const override { return m_length; } + +private: + sockaddr_ll m_address = {}; + socklen_t m_length = sizeof(m_address); +}; + +class NetlinkAddress : public Socket::Address { +public: + explicit NetlinkAddress(uint32_t groups = 0) + { + m_address.nl_family = AF_NETLINK; + m_address.nl_groups = groups; + } + + const struct sockaddr *sockaddr() const override + { + return reinterpret_cast(&m_address); + } + socklen_t length() const override { return m_length; } + +private: + sockaddr_nl m_address = {}; + socklen_t m_length = sizeof(m_address); +}; + +/** + * This class is a wrapper for the socket file descriptor obtained with the accept() system call. + */ +class ConnectedSocket : public SocketAbstractImpl { +public: + explicit ConnectedSocket(int fd) : SocketAbstractImpl(fd) {} +}; + +class RawSocket : public SocketAbstractImpl { +public: + explicit RawSocket(uint16_t protocol = ETH_P_ALL) + : SocketAbstractImpl(socket(AF_PACKET, SOCK_RAW, htons(protocol))) + { + } +}; + +class UdpSocket : public SocketAbstractImpl { +public: + UdpSocket() : SocketAbstractImpl(socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) {} +}; + +class TcpSocket : public SocketAbstractImpl { +public: + TcpSocket() : SocketAbstractImpl(socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) {} +}; + +class UdsSocket : public SocketAbstractImpl { +public: + UdsSocket() : SocketAbstractImpl(socket(AF_UNIX, SOCK_STREAM, 0)) {} +}; + +class AbstractNetlinkSocket : public SocketAbstractImpl { +protected: + explicit AbstractNetlinkSocket(uint16_t protocol) + : SocketAbstractImpl(socket(AF_NETLINK, SOCK_RAW, protocol)) + { + } +}; + +class NetlinkRouteSocket : public AbstractNetlinkSocket { +public: + NetlinkRouteSocket() : AbstractNetlinkSocket(NETLINK_ROUTE) {} +}; + +/** + * This class implements the Socket::Connection interface with methods that wrap the system calls + * to send and receive both bytes and packets in stream-oriented and packet-oriented sockets + * respectively. + */ +class SocketConnectionImpl : public Socket::Connection { +public: + explicit SocketConnectionImpl(const std::shared_ptr &socket) : m_socket(socket) {} + + /** + * @brief Returns the underlying socket used by this connection. + * + * @see Connection::socket + */ + std::shared_ptr socket() override { return m_socket; } + + /** + * @brief Receives data through the socket connection. + * + * @see Connection::receive + * + * This implementation uses the recv() system calll. + */ + int receive(Buffer &buffer, size_t offset = 0) override + { + if (offset >= buffer.size()) { + return -1; + } + return ::recv(m_socket->fd(), buffer.data() + offset, buffer.size() - offset, MSG_DONTWAIT); + } + + /** + * @brief Receives data through the socket connection. + * + * @see Connection::receive_from + * + * This implementation uses the recvfrom() system calll. + */ + int receive_from(Buffer &buffer, Socket::Address &address) override + { + socklen_t address_length = address.length(); + return ::recvfrom(m_socket->fd(), buffer.data(), buffer.size(), MSG_DONTWAIT, + address.sockaddr(), &address_length); + } + + /** + * @brief Sends data through the socket connection. + * + * @see Connection::send + * + * This implementation uses the send() system calll. + */ + int send(const Buffer &buffer, size_t length) override + { + if (length > buffer.size()) { + return -1; + } + return ::send(m_socket->fd(), buffer.data(), length, MSG_NOSIGNAL); + } + + /** + * @brief Sends data through the socket connection. + * + * @see Connection::send_to + * + * This implementation uses the sendto() system calll. + */ + int send_to(const Buffer &buffer, size_t length, const Socket::Address &address) override + { + return ::sendto(m_socket->fd(), buffer.data(), buffer.size(), 0, address.sockaddr(), + address.length()); + } + +private: + /** + * Connected socket used by this connection object. + */ + std::shared_ptr m_socket; +}; + +class ServerSocketAbstractImpl : public ServerSocket { +public: + int bind(const Socket::Address &address) + { + return ::bind(m_socket->fd(), address.sockaddr(), address.length()); + } + + int listen(int backlog) { return ::listen(m_socket->fd(), backlog); } + + std::unique_ptr accept(Socket::Address &address) override + { + socklen_t address_length = address.length(); + int s = ::accept(m_socket->fd(), address.sockaddr(), &address_length); + if (FileDescriptor::invalid_descriptor == s) { + return nullptr; + } + + return std::make_unique(std::make_shared(s)); + } + +protected: + explicit ServerSocketAbstractImpl(const std::shared_ptr &socket) : m_socket(socket) {} + std::shared_ptr m_socket; +}; + +class ClientSocketAbstractImpl : public ClientSocket { +public: + int bind(const Socket::Address &address) + { + return ::bind(m_socket->fd(), address.sockaddr(), address.length()); + } + + std::unique_ptr connect(const Socket::Address &address) override + { + if (0 != ::connect(m_socket->fd(), address.sockaddr(), address.length())) { + return nullptr; + } + return std::make_unique(m_socket); + } + +protected: + explicit ClientSocketAbstractImpl(const std::shared_ptr &socket) : m_socket(socket) {} + std::shared_ptr m_socket; +}; + +template class ServerSocketImpl : public ServerSocketAbstractImpl { +public: + explicit ServerSocketImpl(const std::shared_ptr &socket) + : ServerSocketAbstractImpl(socket) + { + } +}; + +template class ClientSocketImpl : public ClientSocketAbstractImpl { +public: + explicit ClientSocketImpl(const std::shared_ptr &socket) + : ClientSocketAbstractImpl(socket) + { + } +}; + +} // namespace net +} // namespace beerocks + +#endif /* BCL_NETWORK_SOCKETS_IMPL_H_ */ diff --git a/common/beerocks/bcl/source/network/interface_flags_reader_impl.cpp b/common/beerocks/bcl/source/network/interface_flags_reader_impl.cpp new file mode 100644 index 0000000000..e1e293ef4a --- /dev/null +++ b/common/beerocks/bcl/source/network/interface_flags_reader_impl.cpp @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include +#include +#include + +#include +#include + +namespace beerocks { +namespace net { + +bool InterfaceFlagsReaderImpl::read_flags(const std::string &iface_name, uint16_t &iface_flags) +{ + UdpSocket socket; + + ifreq if_req; + string_utils::copy_string(if_req.ifr_name, iface_name.c_str(), IFNAMSIZ); + + int rc = ioctl(socket.fd(), SIOCGIFFLAGS, &if_req); + if (rc == -1) { + return false; + } + + iface_flags = if_req.ifr_flags; + + return true; +} + +} // namespace net +} // namespace beerocks diff --git a/common/beerocks/bcl/source/network/interface_state_manager_impl.cpp b/common/beerocks/bcl/source/network/interface_state_manager_impl.cpp new file mode 100644 index 0000000000..273fc7fcee --- /dev/null +++ b/common/beerocks/bcl/source/network/interface_state_manager_impl.cpp @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include + +namespace beerocks { +namespace net { + +InterfaceStateManagerImpl::InterfaceStateManagerImpl( + std::unique_ptr interface_state_monitor, + std::unique_ptr interface_state_reader) + : m_interface_state_monitor(std::move(interface_state_monitor)), + m_interface_state_reader(std::move(interface_state_reader)) +{ +} + +bool InterfaceStateManagerImpl::start() +{ + m_interface_state_monitor->set_handler([&](const std::string &iface_name, bool iface_state) { + bool last_iface_state; + if (get_state(iface_name, last_iface_state)) { + if (last_iface_state == iface_state) { + return; + } + } + + set_state(iface_name, iface_state); + notify_state_changed(iface_name, iface_state); + }); + + return m_interface_state_monitor->start(); +} + +bool InterfaceStateManagerImpl::stop() +{ + m_interface_state_monitor->set_handler(nullptr); + + return m_interface_state_monitor->stop(); +} + +bool InterfaceStateManagerImpl::read_state(const std::string &iface_name, bool &iface_state) +{ + if (get_state(iface_name, iface_state)) { + return true; + } + + if (!m_interface_state_reader->read_state(iface_name, iface_state)) { + return false; + } + + set_state(iface_name, iface_state); + + return true; +} + +bool InterfaceStateManagerImpl::get_state(const std::string &iface_name, bool &iface_state) +{ + const auto &it = m_interface_states.find(iface_name); + if (m_interface_states.end() == it) { + return false; + } + + iface_state = it->second; + + return true; +} + +void InterfaceStateManagerImpl::set_state(const std::string &iface_name, bool iface_state) +{ + m_interface_states[iface_name] = iface_state; +} + +} // namespace net +} // namespace beerocks diff --git a/common/beerocks/bcl/source/network/interface_state_monitor_impl.cpp b/common/beerocks/bcl/source/network/interface_state_monitor_impl.cpp new file mode 100644 index 0000000000..1172bee9d9 --- /dev/null +++ b/common/beerocks/bcl/source/network/interface_state_monitor_impl.cpp @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include +#include +#include + +#include + +using namespace beerocks; + +namespace beerocks { +namespace net { + +InterfaceStateMonitorImpl::InterfaceStateMonitorImpl( + const std::shared_ptr &connection, + const std::shared_ptr &event_loop) + : m_connection(connection), m_event_loop(event_loop) +{ +} + +bool InterfaceStateMonitorImpl::start() +{ + EventLoop::EventHandlers handlers; + handlers.on_read = [&](int fd, EventLoop &loop) -> bool { + int length = m_connection->receive(m_buffer); + if (length > 0) { + parse(m_buffer.data(), length); + } + + return true; + }; + + return m_event_loop->register_handlers(m_connection->socket()->fd(), handlers); +} + +bool InterfaceStateMonitorImpl::stop() +{ + return m_event_loop->remove_handlers(m_connection->socket()->fd()); +} + +void InterfaceStateMonitorImpl::parse(const uint8_t *data, size_t length) const +{ + for (const nlmsghdr *msg_hdr = reinterpret_cast(data); + NLMSG_OK(msg_hdr, length); msg_hdr = NLMSG_NEXT(msg_hdr, length)) { + parse(msg_hdr); + } +} + +void InterfaceStateMonitorImpl::parse(const nlmsghdr *msg_hdr) const +{ + switch (msg_hdr->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + const ifinfomsg *ifi = static_cast(NLMSG_DATA(msg_hdr)); + + uint32_t iface_index = ifi->ifi_index; + bool iface_state = (ifi->ifi_flags & IFF_UP) && (ifi->ifi_flags & IFF_RUNNING); + + char iface_name[IFNAMSIZ]{}; + if (0 != if_indextoname(iface_index, iface_name)) { + notify_state_changed(iface_name, iface_state); + } + + break; + } +} + +} // namespace net +} // namespace beerocks diff --git a/common/beerocks/bcl/source/network/interface_state_reader_impl.cpp b/common/beerocks/bcl/source/network/interface_state_reader_impl.cpp new file mode 100644 index 0000000000..e899cdcdf8 --- /dev/null +++ b/common/beerocks/bcl/source/network/interface_state_reader_impl.cpp @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include +#include + +#include + +namespace beerocks { +namespace net { + +InterfaceStateReaderImpl::InterfaceStateReaderImpl( + const std::shared_ptr &interface_flags_reader) + : m_interface_flags_reader(interface_flags_reader) +{ +} + +bool InterfaceStateReaderImpl::read_state(const std::string &iface_name, bool &iface_state) +{ + uint16_t flags; + if (!m_interface_flags_reader->read_flags(iface_name, flags)) { + return false; + } + + iface_state = (flags & IFF_UP) && (flags & IFF_RUNNING); + + return true; +} + +} // namespace net +} // namespace beerocks diff --git a/common/beerocks/bcl/unit_tests/interface_state_manager_impl_test.cpp b/common/beerocks/bcl/unit_tests/interface_state_manager_impl_test.cpp new file mode 100644 index 0000000000..5b60677ba7 --- /dev/null +++ b/common/beerocks/bcl/unit_tests/interface_state_manager_impl_test.cpp @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md) + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include +#include +#include + +#include + +#include +#include + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; + +TEST(interface_state_manager_impl, start_stop_should_succeed) +{ + auto monitor = std::make_unique(); + auto reader = std::make_unique(); + + { + InSequence sequence; + + EXPECT_CALL(*monitor, start()).WillOnce(Return(true)); + EXPECT_CALL(*monitor, stop()).WillOnce(Return(true)); + }; + + beerocks::net::InterfaceStateManagerImpl interface_state_manager(std::move(monitor), + std::move(reader)); + + ASSERT_TRUE(interface_state_manager.start()); + ASSERT_TRUE(interface_state_manager.stop()); +} + +/** + * In this test, no state-changed event has occurred yet so interface state is obtained with an + * explicit read through the interface state reader. + */ +TEST(interface_state_manager_impl, read_state_should_succeed_before_event) +{ + auto monitor = std::make_unique(); + auto reader = std::make_unique(); + + const char *iface_name = "eth0"; + const bool expected_iface_state = true; + bool actual_iface_state = false; + + { + InSequence sequence; + + EXPECT_CALL(*monitor, start()).WillOnce(Return(true)); + EXPECT_CALL(*reader, read_state(iface_name, _)) + .WillOnce(Invoke([&](const std::string &iface_name, bool &iface_state) -> bool { + iface_state = expected_iface_state; + return true; + })); + EXPECT_CALL(*monitor, stop()).WillOnce(Return(true)); + }; + + beerocks::net::InterfaceStateManagerImpl interface_state_manager(std::move(monitor), + std::move(reader)); + + ASSERT_TRUE(interface_state_manager.start()); + ASSERT_TRUE(interface_state_manager.read_state(iface_name, actual_iface_state)); + ASSERT_EQ(actual_iface_state, expected_iface_state); + ASSERT_TRUE(interface_state_manager.stop()); +} + +/** + * In this test, the interface state is obtained after a state-changed event (no explicit read + * operation is required nor performed) + */ +TEST(interface_state_manager_impl, read_state_should_succeed_after_event) +{ + auto monitor = std::make_unique(); + auto reader = std::make_unique(); + + const char *iface_name = "eth0"; + const bool expected_iface_state = true; + bool actual_iface_state = false; + + // The monitor mock is used in the expectation to emulate that a state-changed event has + // occurred. + // Since the unique_ptr to the monitor mock is moved into the interface state manager, it + // is not available inside the expectation. To overcome this problem, we use the raw pointer + // instead. + auto monitor_raw_ptr = monitor.get(); + + { + InSequence sequence; + + EXPECT_CALL(*monitor, start()).WillOnce(Invoke([&]() -> bool { + monitor_raw_ptr->notify_state_changed(iface_name, expected_iface_state); + return true; + })); + EXPECT_CALL(*monitor, stop()).WillOnce(Return(true)); + }; + + beerocks::net::InterfaceStateManagerImpl interface_state_manager(std::move(monitor), + std::move(reader)); + + ASSERT_TRUE(interface_state_manager.start()); + ASSERT_TRUE(interface_state_manager.read_state(iface_name, actual_iface_state)); + ASSERT_EQ(actual_iface_state, expected_iface_state); + ASSERT_TRUE(interface_state_manager.stop()); +} + +TEST(interface_state_manager_impl, notify_state_changed_should_succeed) +{ + auto monitor = std::make_unique(); + auto reader = std::make_unique(); + + const char *iface_name = "eth0"; + const bool expected_iface_state = true; + bool actual_iface_state = false; + + // The monitor mock is used in the expectation to emulate that a state-changed event has + // occurred. + // Since the unique_ptr to the monitor mock is moved into the interface state manager, it + // is not available inside the expectation. To overcome this problem, we use the raw pointer + // instead. + auto monitor_raw_ptr = monitor.get(); + + { + InSequence sequence; + + EXPECT_CALL(*monitor, start()).WillOnce(Invoke([&]() -> bool { + monitor_raw_ptr->notify_state_changed(iface_name, expected_iface_state); + return true; + })); + EXPECT_CALL(*monitor, stop()).WillOnce(Return(true)); + }; + + beerocks::net::InterfaceStateManagerImpl interface_state_manager(std::move(monitor), + std::move(reader)); + + interface_state_manager.set_handler( + [&](const std::string &iface_name, bool iface_state) { actual_iface_state = iface_state; }); + + ASSERT_TRUE(interface_state_manager.start()); + ASSERT_EQ(actual_iface_state, expected_iface_state); + ASSERT_TRUE(interface_state_manager.stop()); +}