Skip to content
This repository has been archived by the owner on Sep 7, 2020. It is now read-only.

Commit

Permalink
agent: add link_metrics_collector interface and ethernet implementation
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
mariomaz committed Mar 6, 2020
1 parent 93a49af commit 9823ecf
Show file tree
Hide file tree
Showing 4 changed files with 452 additions and 0 deletions.
3 changes: 3 additions & 0 deletions agent/src/beerocks/slave/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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*
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <bcl/network/network_utils.h>

#include <easylogging++.h>

#include <errno.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

// SPEED values
#include <linux/ethtool.h>

#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<ifinfomsg *>(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
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 9823ecf

Please sign in to comment.