From 9823ecf34ce6f090cff1e5b83afae5facc686840 Mon Sep 17 00:00:00 2001 From: Mario Maz Date: Thu, 6 Feb 2020 14:18:47 +0100 Subject: [PATCH] agent: add link_metrics_collector interface and ethernet implementation Define a structure to hold collected link metrics. This structure will later be used to fill the TLVs of the Link Metric response message. Use uint32_t instead of uint16_t for max throughput capacity and bit rate fields to allow for bigger values. Spec document uses uint16_t so a saturation to the maximum value is required when copying values to the TLV. Link metrics have to be collected differently for wired and wireless links. Define an interface class for a link metrics collector. Derived implementation classes will override pure virtual methods. Create an implementation class for Ethernet links. This implementation reads network interface stats using a Netlink socket and the NETLINK_ROUTE protocol. TODO in future tasks: - Implement link metrics collector for WiFi links in ([TASK] Link metric collection for WiFi links #792) - Move all link metrics related classes to BPL ([TASK] Move link metric related classes to BPL #910) - Implement Ethernet link metrics collector using and extending Netlink classes defined in "Feature/add support for nl80211 standard commands to BWL #900" Signed-off-by: Mario Maz --- agent/src/beerocks/slave/CMakeLists.txt | 3 + .../ieee802_3_link_metrics_collector.cpp | 279 ++++++++++++++++++ .../ieee802_3_link_metrics_collector.h | 47 +++ .../slave/link_metrics/link_metrics.h | 123 ++++++++ 4 files changed, 452 insertions(+) create mode 100644 agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.cpp create mode 100644 agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.h create mode 100644 agent/src/beerocks/slave/link_metrics/link_metrics.h diff --git a/agent/src/beerocks/slave/CMakeLists.txt b/agent/src/beerocks/slave/CMakeLists.txt index 8775ae77d0..7f22cc39b2 100644 --- a/agent/src/beerocks/slave/CMakeLists.txt +++ b/agent/src/beerocks/slave/CMakeLists.txt @@ -6,6 +6,9 @@ set(MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) file(GLOB beerocks_agent_sources ${MODULE_PATH}/backhaul_manager/*.c* + # This code should be moved to BPL + # [TASK] Move link metric related classes to BPL #910 + ${MODULE_PATH}/link_metrics/*.c* ${MODULE_PATH}/platform_manager/*.c* ${MODULE_PATH}/*.c* ) diff --git a/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.cpp b/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.cpp new file mode 100644 index 0000000000..1196eaab0a --- /dev/null +++ b/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.cpp @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * Copyright (c) 2020 MaxLinear + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#include "ieee802_3_link_metrics_collector.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SPEED values +#include + +#define IFLIST_REPLY_BUFFER 8192 + +namespace beerocks { + +/** + * @brief Netlink request type. + */ +struct nl_req_t { + /** + * Netlink message + */ + struct nlmsghdr hdr; + + /** + * "general form of address family dependent" message, i.e. how to tell which AF we are + * interested in. */ + struct rtgenmsg gen; +}; + +/** + * @brief Gets link metrics of an Ethernet network interface. + * + * Gets link metrics for given Ethernet network interface from the specified Netlink message of + * type RTM_NEWLINK. + * + * @param[in] h Pointer to the Netlink message containing the data. + * @param[in] local_interface_name Name of the Ethernet network interface. + * @param[in, out] link_metris Link metrics structure with read values. + * + * @return True on success and false otherwise. + */ +static bool get_link_metrics(const struct nlmsghdr *h, const std::string &local_interface_name, + sLinkMetrics &link_metrics) +{ + bool result = false; + + struct ifinfomsg *iface = static_cast(NLMSG_DATA(h)); + + size_t length = 0; + if (h->nlmsg_len > NLMSG_LENGTH(sizeof(*iface))) { + length = h->nlmsg_len - NLMSG_LENGTH(sizeof(*iface)); + } + + /** + * Loop over all attributes of the RTM_NEWLINK message + */ + for (struct rtattr *attribute = IFLA_RTA(iface); RTA_OK(attribute, length); + attribute = RTA_NEXT(attribute, length)) { + switch (attribute->rta_type) { + case IFLA_IFNAME: + /** + * This message contains the stats for the interface we are interested in + */ + if (0 == std::strncmp(local_interface_name.c_str(), (char *)RTA_DATA(attribute), + local_interface_name.length() + 1)) { + result = true; + } + break; + case IFLA_STATS: + if (result) { + struct rtnl_link_stats *stats = (struct rtnl_link_stats *)RTA_DATA(attribute); + + /** + * Get interface speed into PHY rate. + */ + uint32_t phy_rate_mbps = UINT32_MAX; + beerocks::net::network_utils::linux_iface_get_speed(local_interface_name, + phy_rate_mbps); + + link_metrics.transmitter.packet_errors = stats->tx_errors; + link_metrics.transmitter.transmitted_packets = stats->tx_packets; + /** + * Note: The MAC throughput capacity is a function of the physical data rate and + * of the MAC overhead. We could somehow compute such overhead or, for simplicity, + * set the MAC throughput as a percentage of the physical data rate. + * For Ethernet, we can estimate the overhead: 7 bytes preamble, 1 byte SFD, 14 + * bytes header, 4 bytes CRC and 12 bytes of interpacket gap on a 1500 byte + * payload. So 1500/1538. + * (see https://en.wikipedia.org/wiki/Ethernet_frame) + */ + const uint32_t layer2_payload_size = 1500; + const uint32_t layer1_total_size = 1538; + link_metrics.transmitter.mac_throughput_capacity_mbps = + phy_rate_mbps * ((float)layer2_payload_size / (float)layer1_total_size); + // Note: For simplicity, link availability is set to "100% of the time" + link_metrics.transmitter.link_availability = 100; + link_metrics.transmitter.phy_rate_mbps = phy_rate_mbps; + + link_metrics.receiver.packet_errors = stats->rx_errors; + link_metrics.receiver.packets_received = stats->rx_packets; + link_metrics.receiver.rssi = UINT8_MAX; + } + break; + } + } + + return result; +} + +/** + * @brief Gets link metrics of an Ethernet network interface. + * + * Gets link metrics for given Ethernet network interface by sending a RTM_GETLINK Netlink request + * through the specified Netlink socket and parsing received response. + * + * @param[in] fd File descriptor of a connected Netlink socket. + * @param[in] local_interface_name Name of the Ethernet network interface. + * @param[in, out] link_metris Link metrics structure with read values. + * + * @return True on success and false otherwise. + */ +static bool get_link_metrics(int fd, const std::string &local_interface_name, + sLinkMetrics &link_metrics) +{ + bool result = false; + + struct sockaddr_nl kernel; /* the remote (kernel space) side of the communication */ + + struct msghdr rtnl_msg { + }; /* generic msghdr struct for use with sendmsg */ + struct iovec io { + }; /* IO vector for sendmsg */ + struct nl_req_t req { + }; /* structure that describes the Netlink packet itself */ + + /** + * Netlink socket is ready for use, prepare and send request + */ + kernel.nl_family = AF_NETLINK; /* fill-in kernel address (destination of our message) */ + + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + req.hdr.nlmsg_type = RTM_GETLINK; + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.hdr.nlmsg_seq = 1; + req.hdr.nlmsg_pid = 0; + req.gen.rtgen_family = AF_PACKET; /* no preferred AF, we will get *all* interfaces */ + + io.iov_base = &req; + io.iov_len = req.hdr.nlmsg_len; + rtnl_msg.msg_iov = &io; + rtnl_msg.msg_iovlen = 1; + rtnl_msg.msg_name = &kernel; + rtnl_msg.msg_namelen = sizeof(kernel); + + if (sendmsg(fd, (struct msghdr *)&rtnl_msg, 0) < 0) { + LOG(ERROR) << "Unable to send message through Netlink socket: " << strerror(errno); + } else { + int msg_done = 0; /* flag to end loop parsing */ + + /** + * Parse reply until message is done + */ + while (!msg_done) { + int length; + struct nlmsghdr *msg_ptr; /* pointer to current message part */ + + struct msghdr rtnl_reply { + }; /* generic msghdr structure for use with recvmsg */ + + /* a large buffer to receive lots of link information */ + char reply[IFLIST_REPLY_BUFFER]; + + io.iov_base = reply; + io.iov_len = IFLIST_REPLY_BUFFER; + rtnl_reply.msg_iov = &io; + rtnl_reply.msg_iovlen = 1; + rtnl_reply.msg_name = &kernel; + rtnl_reply.msg_namelen = sizeof(kernel); + + /** + * Read as much data as fits in the receive buffer + */ + if ((length = recvmsg(fd, &rtnl_reply, 0)) != 0) { + for (msg_ptr = (struct nlmsghdr *)reply; NLMSG_OK(msg_ptr, length); + msg_ptr = NLMSG_NEXT(msg_ptr, length)) { + switch (msg_ptr->nlmsg_type) { + case NLMSG_DONE: + /** + * This is the special meaning NLMSG_DONE message we asked for by using NLM_F_DUMP flag + */ + msg_done = 1; + break; + case RTM_NEWLINK: + /** + * This is a RTM_NEWLINK message, which contains lots of information about a link + */ + if (get_link_metrics(msg_ptr, local_interface_name, link_metrics)) { + msg_done = 1; + result = true; + } + break; + } + } + } + } + } + + return result; +} + +/** + * @brief Gets link metrics of an Ethernet network interface. + * + * Gets link metrics for given Ethernet network by means of a Netlink socket using NETLINK_ROUTE + * protocol. + * + * @param[in] local_interface_name Name of the Ethernet network interface. + * @param[in, out] link_metris Link metrics structure with read values. + * + * @return True on success and false otherwise. + */ +static bool get_link_metrics(const std::string &local_interface_name, sLinkMetrics &link_metrics) +{ + bool result = false; + + /** + * Create Netlink socket for kernel/user-space communication. + * No need to call bind() as packets are sent only between the kernel and the originating + * process (no multicasting). + */ + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + LOG(ERROR) << "Failed creating Netlink socket: " << strerror(errno); + } else { + /** + * Get link metrics using Netlink socket + */ + result = get_link_metrics(fd, local_interface_name, link_metrics); + + /** + * Clean up and finish properly + */ + close(fd); + } + + return result; +} + +ieee802_3_link_metrics_collector::~ieee802_3_link_metrics_collector() {} + +bool ieee802_3_link_metrics_collector::get_link_metrics(const std::string &local_interface_name, + const sMacAddr &neighbor_interface_address, + sLinkMetrics &link_metrics) +{ + (void)neighbor_interface_address; + + return beerocks::get_link_metrics(local_interface_name, link_metrics); +} + +} // namespace beerocks diff --git a/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.h b/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.h new file mode 100644 index 0000000000..d3b6ef1907 --- /dev/null +++ b/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * Copyright (c) 2020 MaxLinear + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ +#ifndef __IEEE802_3_LINK_METRICS_COLLECTOR_H__ +#define __IEEE802_3_LINK_METRICS_COLLECTOR_H__ + +#include "link_metrics.h" + +namespace beerocks { + +class ieee802_3_link_metrics_collector : public link_metrics_collector { + +public: + virtual ~ieee802_3_link_metrics_collector(); + + /** + * @brief Gets link metrics information. + * + * Gets link metrics associated to the link between given local interface and a neighbor's + * interface whose MAC address is 'neighbor_interface_address'. + * + * Note that metrics are associated to a link and not to an interface. For Ethernet interfaces + * and in Linux though, it is not possible to obtain per link stats: in Linux is easy to check + * how many packets were received by "eth0" *in total*, but it is not trivial to find out how + * many packets were received by "eth0" *from each neighbor*. For the sake of simplicity this + * implementation just reports the overall per-interface stats (thus ignoring the + * 'neighbor_interface_address' parameter). + * + * @param[in] local_interface_name Name of the local interface. + * @param[in] neighbor_interface_address MAC address at the other end of the link (this MAC + * address belongs to a neighbor's interface. + * @param[out] link_metrics Link metrics information. + * + * @return True on success and false otherwise. + */ + virtual bool get_link_metrics(const std::string &local_interface_name, + const sMacAddr &neighbor_interface_address, + sLinkMetrics &link_metrics) override; +}; + +} // namespace beerocks + +#endif diff --git a/agent/src/beerocks/slave/link_metrics/link_metrics.h b/agent/src/beerocks/slave/link_metrics/link_metrics.h new file mode 100644 index 0000000000..1dfa4e7cf6 --- /dev/null +++ b/agent/src/beerocks/slave/link_metrics/link_metrics.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * Copyright (c) 2020 MaxLinear + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ +#ifndef __LINK_METRICS_H__ +#define __LINK_METRICS_H__ + +#include + +#include + +namespace beerocks { + +/** + * @brief Transmitter metrics information associated to the link between a local interface and a + * neighbor's interface. + * + * Information in this structure is used to fill Transmitter Link Metric TLV. + */ +struct sTransmitterLinkMetrics { + /** + * Estimated number of lost packets on the transmit side of the link during the measurement + * period. + */ + uint32_t packet_errors = 0; + + /** + * Estimated number of packets transmitted by the Transmitter of the link on the same + * measurement period used to estimate tx_packet_errors. + */ + uint32_t transmitted_packets = 0; + + /** + * The maximum MAC throughput of the link estimated at the transmitter and expressed in Mb/s. + */ + uint32_t mac_throughput_capacity_mbps = 0; + + /** + * The estimated average percentage of time that the link is available for data transmissions. + */ + uint16_t link_availability = 0; + + /** + * If the media type of the link is IEEE 802.3, IEEE 1901, MoCA 1.1 or Generic Phy, then this + * value is the PHY rate estimated at the transmitter of the link expressed in Mb/s; + * otherwise, it is set to 0xFFFF. + */ + uint32_t phy_rate_mbps = 0; +}; + +/** + * @brief Receiver metrics information associated to the link between a local interface and a + * neighbor's interface. + * + * Information in this structure is used to fill Receiver Link Metric TLV. + */ +struct sReceiverLinkMetrics { + /** + * Estimated number of lost packets during the measurement period. + */ + uint32_t packet_errors = 0; + + /** + * Number of packets received at the interface during the same measurement period used to count + * packet_errors + */ + uint32_t packets_received = 0; + + /** + * If the media type of the link is IEEE 802.11, then this value is the estimated RSSI in dB at + * the receive side of the link expressed in dB; otherwise, it is set to 0xFF. + */ + uint8_t rssi = 0; +}; + +/** + * @brief Metrics information associated to the link between a local interface and a + * neighbor's interface. + * + * Information in this structure is used to build the Link Metric response message. + */ +struct sLinkMetrics { + struct sTransmitterLinkMetrics transmitter; /**< Transmitter link metrics. */ + struct sReceiverLinkMetrics receiver; /**< Receiver link metrics. */ +}; + +/** + * @brief Link metrics collector interface. + * + * This is a C++ interface: an abstract class that is designed to be specifically used as a base + * class and which derived classes (implementations) will override each pure virtual function. + * + * Known implementations: ieee802_3_link_metrics_collector and ieee802_11_link_metrics_collector. + */ +class link_metrics_collector { + +public: + virtual ~link_metrics_collector() = default; + + /** + * @brief Gets link metrics information. + * + * Gets link metrics associated to the link between given local interface and a neighbor's + * interface whose MAC address is 'neighbor_interface_address'. + * + * @param[in] local_interface_name Name of the local interface. + * @param[in] neighbor_interface_address MAC address at the other end of the link (this MAC + * address belongs to a neighbor's interface. + * @param[out] link_metrics Link metrics information. + * + * @return True on success and false otherwise. + */ + virtual bool get_link_metrics(const std::string &local_interface_name, + const sMacAddr &neighbor_interface_address, + sLinkMetrics &link_metrics) = 0; +}; + +} // namespace beerocks + +#endif