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/backhaul_manager/backhaul_manager_thread.cpp b/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.cpp index f5720d6740..abaf33f7c0 100644 --- a/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.cpp +++ b/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.cpp @@ -8,6 +8,7 @@ #include "backhaul_manager_thread.h" +#include "../link_metrics/ieee802_3_link_metrics_collector.h" #include "../tlvf_utils.h" #include @@ -30,6 +31,8 @@ #include #include #include +#include +#include #include #include #include @@ -78,6 +81,227 @@ namespace beerocks { const char *backhaul_manager::s_arrStates[] = {FOREACH_STATE(GENERATE_STRING)}; +/** + * @brief Gets media type group for given interface. + * + * @param[in] interface_name Name of the local interface. + * @param[in, out] media_type_group The media type group of the connecting interface. + * + * @return True on success and false otherwise. + */ +static bool get_media_type_group(const std::string &interface_name, + ieee1905_1::eMediaTypeGroup &media_type_group) +{ + // TODO: Currently, only Ethernet is supported + // When dealing with WiFi interfaces in task #792, use NL80211_CMD_GET_INTERFACE command to + // find out if media type group is WiFi + media_type_group = ieee1905_1::eMediaTypeGroup::IEEE_802_3; + + return true; +} + +/** + * @brief Gets media type for given interface. + * + * The mechanism to use to obtain media type depends on the media type group: + * Ethernet, WiFi, MoCA, etc. + * + * @param[in] interface_name Name of the local interface. + * @param[in] media_type_group The media type group of the connecting interface. + * @param[in, out] media_type The underlying network technology of the connecting interface. + * + * @return True on success and false otherwise. + */ +static bool get_media_type(const std::string &interface_name, + ieee1905_1::eMediaTypeGroup media_type_group, + ieee1905_1::eMediaType &media_type) +{ + bool result = false; + + if (ieee1905_1::eMediaTypeGroup::IEEE_802_3 == media_type_group) { + uint32_t speed; + if (network_utils::linux_iface_get_speed(interface_name, speed)) { + if (SPEED_100 == speed) { + media_type = ieee1905_1::eMediaType::IEEE_802_3U_FAST_ETHERNET; + } else if (SPEED_1000 <= speed) { + media_type = ieee1905_1::eMediaType::IEEE_802_3AB_GIGABIT_ETHERNET; + } else { + media_type = ieee1905_1::eMediaType::UNKNONWN_MEDIA; + } + + result = true; + } + } else if (ieee1905_1::eMediaTypeGroup::IEEE_802_11 == media_type_group) { + // TODO: Not supported yet + LOG(ERROR) << "IEEE_802_11 media is not supported yet"; + } else if (ieee1905_1::eMediaTypeGroup::IEEE_1901 == media_type_group) { + // TODO: Not supported yet + LOG(ERROR) << "IEEE_1901 media is not supported yet"; + } else if (ieee1905_1::eMediaTypeGroup::MoCA == media_type_group) { + // TODO: Not supported yet + LOG(ERROR) << "MoCA media is not supported yet"; + } else { + media_type = ieee1905_1::eMediaType::UNKNONWN_MEDIA; + result = true; + } + + return result; +} + +/** + * @brief Adds link metrics to response message. + * + * Gets link metrics of the interface between the reporter AL MAC and the neighbor AL MAC. + * Creates a Transmitter Link Metric TLV or a Receiver Link Metric TLV or both and adds them to + * the response message. + * + * @param[in] local_interface_name Name of the local interface. + * @param[in] media_type The underlying network technology of the connecting interface. + * @param[in] reporter_al_mac 1905.1 AL MAC address of the device that transmits the response message. + * @param[in] neighbor_al_mac 1905.1 AL MAC address of a neighbor of the receiving device. + * @param[in] link_metrics_type The link metrics type requested: TX, RX or both. + * @param[in, out] cmdu_tx CMDU response message where TLVs must be added to. + * + * @return True on success and false otherwise. + */ +static bool add_link_metrics(const std::string &local_interface_name, + ieee1905_1::eMediaType media_type, const sMacAddr &reporter_al_mac, + const sMacAddr &neighbor_al_mac, + ieee1905_1::eLinkMetricsType link_metrics_type, + ieee1905_1::CmduMessageTx &cmdu_tx) +{ + /** + * Metrics information associated to the link between the local interface and the neighbor's interface. + */ + sLinkMetrics link_metrics; + + /** + * Link metrics collector to use to get link metrics in the connecting interface. + */ + std::unique_ptr collector; + + /** + * Get a link metrics collector suitable for the underlying network technology of the + * connecting interface. + * Collector choice depends on bits 15 to 8 of media type. + */ + uint8_t media_type_msb = media_type >> 8; + if (0 == media_type_msb) { + collector = std::make_unique(); + } else if (1 == media_type_msb) { + // TODO: Create a collector for wireless interfaces + //collector = std::make_unique(); + LOG(ERROR) << "Unsupported media type: " << std::hex << (int)media_type; + } else { + LOG(ERROR) << "Unsupported media type: " << std::hex << (int)media_type; + return false; + } + + /** + * Get link metrics information + */ + if (collector && + collector->get_link_metrics(local_interface_name, neighbor_al_mac, link_metrics)) { + + /** + * Get MAC address for local interface. + */ + std::string mac; + if (!network_utils::linux_iface_get_mac(local_interface_name, mac)) { + LOG(ERROR) << "Failed getting MAC address for interface: " << local_interface_name; + return false; + } + sMacAddr local_interface_mac = network_utils::mac_from_string(mac); + + /** + * Add Transmitter Link Metric TLV if specifically requested or both requested + */ + if ((ieee1905_1::eLinkMetricsType::TX_LINK_METRICS_ONLY == link_metrics_type) || + (ieee1905_1::eLinkMetricsType::BOTH_TX_AND_RX_LINK_METRICS == link_metrics_type)) { + auto tlvTransmitterLinkMetric = + cmdu_tx.addClass(); + if (!tlvTransmitterLinkMetric) { + LOG(ERROR) << "addClass ieee1905_1::tlvTransmitterLinkMetric failed"; + return false; + } + + tlvTransmitterLinkMetric->reporter_al_mac() = reporter_al_mac; + tlvTransmitterLinkMetric->neighbor_al_mac() = neighbor_al_mac; + + if (!tlvTransmitterLinkMetric->alloc_interface_pair_info()) { + LOG(ERROR) << "alloc_interface_pair_info failed"; + return false; + } + auto interface_pair_info = tlvTransmitterLinkMetric->interface_pair_info(0); + if (!std::get<0>(interface_pair_info)) { + LOG(ERROR) << "Failed accessing interface_pair_info"; + return false; + } + auto interfacePairInfo = std::get<1>(interface_pair_info); + interfacePairInfo.rc_interface_mac = local_interface_mac; + // TODO: This is not correct... We actually have to get this from the topology + // discovery message, which will give us the neighbor interface MAC. + interfacePairInfo.neighbor_interface_mac = neighbor_al_mac; + interfacePairInfo.link_metric_info.intfType = media_type; + // TODO + //Indicates whether or not the 1905.1 link includes one or more IEEE 802.1 bridges + //eIEEE802_1BridgeFlag IEEE802_1BridgeFlag; + interfacePairInfo.link_metric_info.packet_errors = + link_metrics.transmitter.packet_errors; + interfacePairInfo.link_metric_info.transmitted_packets = + link_metrics.transmitter.transmitted_packets; + interfacePairInfo.link_metric_info.mac_throughput_capacity = + std::min(link_metrics.transmitter.mac_throughput_capacity_mbps, + static_cast(UINT16_MAX)); + interfacePairInfo.link_metric_info.link_availability = + link_metrics.transmitter.link_availability; + interfacePairInfo.link_metric_info.phy_rate = + std::min(link_metrics.transmitter.phy_rate_mbps, static_cast(UINT16_MAX)); + } + + /** + * Add Receiver Link Metric TLV if specifically requested or both requested + */ + if ((ieee1905_1::eLinkMetricsType::RX_LINK_METRICS_ONLY == link_metrics_type) || + (ieee1905_1::eLinkMetricsType::BOTH_TX_AND_RX_LINK_METRICS == link_metrics_type)) { + auto tlvReceiverLinkMetric = cmdu_tx.addClass(); + if (!tlvReceiverLinkMetric) { + LOG(ERROR) << "addClass ieee1905_1::tlvReceiverLinkMetric failed"; + return false; + } + + tlvReceiverLinkMetric->reporter_al_mac() = reporter_al_mac; + tlvReceiverLinkMetric->neighbor_al_mac() = neighbor_al_mac; + + if (!tlvReceiverLinkMetric->alloc_interface_pair_info()) { + LOG(ERROR) << "alloc_interface_pair_info failed"; + return false; + } + auto interface_pair_info = tlvReceiverLinkMetric->interface_pair_info(0); + if (!std::get<0>(interface_pair_info)) { + LOG(ERROR) << "Failed accessing interface_pair_info"; + return false; + } + auto interfacePairInfo = std::get<1>(interface_pair_info); + interfacePairInfo.rc_interface_mac = local_interface_mac; + // TODO: This is not correct... We actually have to get this from the topology + // discovery message, which will give us the neighbor interface MAC. + interfacePairInfo.neighbor_interface_mac = neighbor_al_mac; + interfacePairInfo.link_metric_info.intfType = media_type; + interfacePairInfo.link_metric_info.packet_errors = link_metrics.receiver.packet_errors; + interfacePairInfo.link_metric_info.packets_received = + link_metrics.receiver.packets_received; + interfacePairInfo.link_metric_info.rssi_db = link_metrics.receiver.rssi; + } + } else { + LOG(ERROR) << "Unable to get link link_metrics for interface " << local_interface_name + << " and neighbor " << network_utils::mac_to_string(neighbor_al_mac); + return false; + } + + return true; +} + ////////////////////////////////////////////////////////////////////////////// /////////////////////////////// Implementation /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// @@ -407,7 +631,7 @@ bool backhaul_manager::finalize_slaves_connect_state(bool fConnected, // Read the IP addresses of the bridge interface if (network_utils::get_iface_info(iface_info, strIface) != 0) { LOG(ERROR) << "Failed reading addresses for: " << strIface; - return (false); + return false; } notification->params().gw_ipv4 = network_utils::ipv4_from_string(bridge_info.ip_gw); @@ -880,7 +1104,7 @@ bool backhaul_manager::backhaul_fsm_main(bool &skip_select) } default: { LOG(ERROR) << "Undefined state: " << int(m_eFSMState); - return (false); + return false; } } @@ -1358,7 +1582,7 @@ bool backhaul_manager::backhaul_fsm_wireless(bool &skip_select) } default: { LOG(ERROR) << "backhaul_fsm_wireless() Invalid state: " << int(m_eFSMState); - return (false); + return false; } } return (true); @@ -1837,6 +2061,9 @@ bool backhaul_manager::handle_1905_1_message(ieee1905_1::CmduMessageRx &cmdu_rx, case ieee1905_1::eMessageType::HIGHER_LAYER_DATA_MESSAGE: { return handle_1905_higher_layer_data_message(cmdu_rx, src_mac); } + case ieee1905_1::eMessageType::LINK_METRIC_QUERY_MESSAGE: { + return handle_1905_link_metric_query(cmdu_rx, src_mac); + } case ieee1905_1::eMessageType::COMBINED_INFRASTRUCTURE_METRICS_MESSAGE: { return handle_1905_combined_infrastructure_metrics(cmdu_rx, src_mac); } @@ -1988,21 +2215,26 @@ bool backhaul_manager::handle_1905_topology_query(ieee1905_1::CmduMessageRx &cmd * Set the number of local interfaces and fill info of each of the local interfaces, according * to IEEE_1905 section 6.4.5 */ - uint32_t speed; - if (network_utils::linux_iface_get_speed(m_sConfig.wire_iface, speed)) { - std::shared_ptr localInterfaceInfo = - tlvDeviceInformation->create_local_interface_list(); + /** + * Add a LocalInterfaceInfo field for the wired interface, if any. + */ + std::string local_interface_name = m_sConfig.wire_iface; + if (!local_interface_name.empty() && + network_utils::linux_iface_is_up_and_running(local_interface_name)) { ieee1905_1::eMediaType media_type = ieee1905_1::eMediaType::UNKNONWN_MEDIA; - if (SPEED_100 == speed) { - media_type = ieee1905_1::eMediaType::IEEE_802_3U_FAST_ETHERNET; - } else if (SPEED_1000 <= speed) { - media_type = ieee1905_1::eMediaType::IEEE_802_3AB_GIGABIT_ETHERNET; + if (!get_media_type(local_interface_name, ieee1905_1::eMediaTypeGroup::IEEE_802_3, + media_type)) { + LOG(ERROR) << "Unable to compute media type for interface " << local_interface_name; + return false; } + std::shared_ptr localInterfaceInfo = + tlvDeviceInformation->create_local_interface_list(); + // default to zero mac if get_mac fails. std::string wire_iface_mac = network_utils::ZERO_MAC_STRING; - network_utils::linux_iface_get_mac(m_sConfig.wire_iface, wire_iface_mac); + network_utils::linux_iface_get_mac(local_interface_name, wire_iface_mac); localInterfaceInfo->mac() = network_utils::mac_from_string(wire_iface_mac); localInterfaceInfo->media_type() = media_type; localInterfaceInfo->media_info_length() = 0; @@ -2265,6 +2497,154 @@ bool backhaul_manager::handle_1905_higher_layer_data_message(ieee1905_1::CmduMes return send_cmdu_to_bus(cmdu_tx, src_mac, bridge_info.mac); } +bool backhaul_manager::handle_1905_link_metric_query(ieee1905_1::CmduMessageRx &cmdu_rx, + const std::string &src_mac) +{ + const auto mid = cmdu_rx.getMessageId(); + LOG(DEBUG) << "Received LINK_METRIC_QUERY_MESSAGE, mid=" << std::hex << int(mid); + + /** + * The IEEE 1905.1 standard says about the Link Metric Query TLV and the neighbor type octet + * that "If the value is 0, then the EUI48 field is not present; if the value is 1, then the + * EUI-48 field shall be present." + * + * However, optional fields are not currently supported by TLVF. + * + * As a workaround, instead of defining a tlvLinkMetricQuery TLV with an optional field, we + * have defined two different TLVs: tlvLinkMetricQuery with the optional field and + * tlvLinkMetricQueryAllNeighbors without it. Application must check which of both TLVs has + * been received inside the message. + */ + std::shared_ptr tlvLinkMetricQueryAllNeighbors; + std::shared_ptr tlvLinkMetricQuery; + + tlvLinkMetricQueryAllNeighbors = cmdu_rx.getClass(); + if (!tlvLinkMetricQueryAllNeighbors) { + tlvLinkMetricQuery = cmdu_rx.getClass(); + if (!tlvLinkMetricQuery) { + LOG(ERROR) << "getClass ieee1905_1::tlvLinkMetricQueryAllNeighbors and " + "ieee1905_1::tlvLinkMetricQuery failed"; + return false; + } + } + + /** + * 1905.1 AL MAC address of the device that transmits the response message. + */ + sMacAddr reporter_al_mac = network_utils::mac_from_string(bridge_info.mac); + + /** + * 1905.1 AL MAC address of a neighbor of the receiving device. + * Query can specify a particular neighbor device or all neighbor devices. + */ + sMacAddr neighbor_al_mac = network_utils::ZERO_MAC; + + /** + * Obtain link metrics for either all neighbors or a specific neighbor + */ + ieee1905_1::eLinkMetricNeighborType neighbor_type; + + /** + * The link metrics type requested: TX, RX or both + */ + ieee1905_1::eLinkMetricsType link_metrics_type; + + if (tlvLinkMetricQuery) { + /** + * If tlvLinkMetricQuery has been included in message, we will be permissive enough to + * allow it specify ALL_NEIGHBORS and if so, then we will just ignore the field + * containing the MAC address of neighbor. + */ + neighbor_type = tlvLinkMetricQuery->neighbor_type(); + neighbor_al_mac = tlvLinkMetricQuery->mac_al_1905_device(); + link_metrics_type = tlvLinkMetricQuery->link_metrics_type(); + } else { + neighbor_type = tlvLinkMetricQueryAllNeighbors->neighbor_type(); + if (ieee1905_1::eLinkMetricNeighborType::ALL_NEIGHBORS != neighbor_type) { + LOG(ERROR) << "Unexpected neighbor type: " << std::hex << int(neighbor_type); + return false; + } + link_metrics_type = tlvLinkMetricQueryAllNeighbors->link_metrics_type(); + } + + /** + * Set alias flag to true if link metrics for a specific neighbor have been requested + */ + bool specific_neighbor = + ieee1905_1::eLinkMetricNeighborType::SPECIFIC_NEIGHBOR == neighbor_type; + + /** + * Create response message + */ + auto cmdu_tx_header = + cmdu_tx.create(mid, ieee1905_1::eMessageType::LINK_METRIC_RESPONSE_MESSAGE); + if (!cmdu_tx_header) { + LOG(ERROR) << "Failed creating LINK_METRIC_RESPONSE_MESSAGE header! mid=" << std::hex + << (int)mid; + return false; + } + + /** + * Get the list of neighbor links from the topology database. + * Neighbor links are grouped by the interface that connects to them. + */ + std::map> neighbor_links_map; + if (!get_neighbor_links(neighbor_al_mac, neighbor_links_map)) { + LOG(ERROR) << "Failed to get the list of neighbor links"; + return false; + } + + /** + * If the specified neighbor 1905.1 AL ID does not identify a neighbor of the receiving 1905.1 + * AL, then a link metric ResultCode TLV (see Table 6-21) with a value set to “invalid + * neighbor” shall be included in this message. + */ + bool invalid_neighbor = specific_neighbor && neighbor_links_map.empty(); + if (invalid_neighbor) { + auto tlvLinkMetricResultCode = cmdu_tx.addClass(); + if (!tlvLinkMetricResultCode) { + LOG(ERROR) << "addClass ieee1905_1::tlvLinkMetricResultCode failed, mid=" << std::hex + << (int)mid; + return false; + } + + tlvLinkMetricResultCode->value() = ieee1905_1::tlvLinkMetricResultCode::INVALID_NEIGHBOR; + } else { + + /** + * Report link metrics for the link with specific neighbor or for all neighbors, as + * obtained from topology database + */ + for (const auto &entry : neighbor_links_map) { + std::string local_interface_name = entry.first; + std::vector neighbors = entry.second; + + ieee1905_1::eMediaTypeGroup media_type_group = ieee1905_1::eMediaTypeGroup::UNKNOWN; + if (!get_media_type_group(local_interface_name, media_type_group)) { + LOG(ERROR) << "Unable to compute media type group for interface " + << local_interface_name; + return false; + } + + ieee1905_1::eMediaType media_type = ieee1905_1::eMediaType::UNKNONWN_MEDIA; + if (!get_media_type(local_interface_name, media_type_group, media_type)) { + LOG(ERROR) << "Unable to compute media type for interface " << local_interface_name; + return false; + } + + for (const auto &neighbor : neighbors) { + if (!add_link_metrics(local_interface_name, media_type, reporter_al_mac, neighbor, + link_metrics_type, cmdu_tx)) { + return false; + } + } + } + } + + LOG(DEBUG) << "Sending LINK_METRIC_RESPONSE_MESSAGE, mid: " << std::hex << (int)mid; + return send_cmdu_to_bus(cmdu_tx, src_mac, bridge_info.mac); +} + bool backhaul_manager::handle_1905_combined_infrastructure_metrics( ieee1905_1::CmduMessageRx &cmdu_rx, const std::string &src_mac) { @@ -2934,4 +3314,21 @@ std::shared_ptr backhaul_manager::get_wireless_hal(std::strin return slave_sk->second->sta_wlan_hal; } +bool backhaul_manager::get_neighbor_links( + const sMacAddr &neighbor_mac_filter, + std::map> &neighbor_links_map) +{ + + // TODO: Topology Database is required to implement this method. + // Since we don't have it yet, method will return a single neighbor, the controller (which is + // BTW incorrect for the second repeater if we have a chain topology, because then the + // controller is not a neighbor). + sMacAddr neighbor = network_utils::mac_from_string(controller_bridge_mac); + if ((neighbor_mac_filter == network_utils::ZERO_MAC) || (neighbor_mac_filter == neighbor)) { + neighbor_links_map[m_sConfig.wire_iface].push_back(neighbor); + } + + return true; +} + } // namespace beerocks diff --git a/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.h b/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.h index 4707b6b47c..dfd7df47a1 100644 --- a/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.h +++ b/agent/src/beerocks/slave/backhaul_manager/backhaul_manager_thread.h @@ -80,6 +80,8 @@ class backhaul_manager : public btl::transport_socket_thread { bool handle_1905_topology_query(ieee1905_1::CmduMessageRx &cmdu_rx, const std::string &src_mac); bool handle_1905_higher_layer_data_message(ieee1905_1::CmduMessageRx &cmdu_rx, const std::string &src_mac); + bool handle_1905_link_metric_query(ieee1905_1::CmduMessageRx &cmdu_rx, + const std::string &src_mac); bool handle_1905_combined_infrastructure_metrics(ieee1905_1::CmduMessageRx &cmdu_rx, const std::string &src_mac); bool handle_ap_capability_query(ieee1905_1::CmduMessageRx &cmdu_rx, const std::string &src_mac); @@ -268,6 +270,23 @@ class backhaul_manager : public btl::transport_socket_thread { */ std::unordered_map m_radio_info_map; + /** + * @brief Gets the list of neighbor links from topology database. + * + * Neighbor links are pairs (interface, neighbor) where 'interface' is the name of the interface + * that connects to the neighbor device and 'neighbor' is the MAC address of the neighbor device. + * + * @param[in] neighbor_mac_filter Optional MAC address to filter the neighbor links to be + * returned. A value of network_utils::ZERO_MAC means no filter has to be applied. A specific + * MAC address means that only links to that device must be included. + * @param[in, out] neighbor_links_map Map containing lists of neighbors grouped by the interface + * that connects to them. + * + * @return True on success and false otherwise. + */ + bool get_neighbor_links(const sMacAddr &neighbor_mac_filter, + std::map> &neighbor_links_map); + /* * State Machines */ 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..a60f8b85e7 --- /dev/null +++ b/agent/src/beerocks/slave/link_metrics/ieee802_3_link_metrics_collector.cpp @@ -0,0 +1,277 @@ +/* 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 float layer2_payload_size = 1500; + const float layer1_total_size = 1538; + link_metrics.transmitter.mac_throughput_capacity_mbps = + phy_rate_mbps * (layer2_payload_size / 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 socket; /* 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 + */ + socket.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 = &socket; + rtnl_msg.msg_namelen = sizeof(socket); + + 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 = &socket; + rtnl_reply.msg_namelen = sizeof(socket); + + /** + * 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, + [[gnu::unused]] const sMacAddr &neighbor_interface_address, sLinkMetrics &link_metrics) +{ + 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 diff --git a/agent/src/beerocks/slave/son_slave_thread.cpp b/agent/src/beerocks/slave/son_slave_thread.cpp index 6d1cfdda28..35eb15c984 100644 --- a/agent/src/beerocks/slave/son_slave_thread.cpp +++ b/agent/src/beerocks/slave/son_slave_thread.cpp @@ -376,8 +376,6 @@ bool slave_thread::handle_cmdu_control_ieee1905_1_message(Socket *sd, return handle_client_association_request(sd, cmdu_rx); case ieee1905_1::eMessageType::AP_METRICS_QUERY_MESSAGE: return handle_ap_metrics_query(sd, cmdu_rx); - case ieee1905_1::eMessageType::LINK_METRIC_QUERY_MESSAGE: - return handle_link_metrics_query(sd, cmdu_rx); case ieee1905_1::eMessageType::CHANNEL_PREFERENCE_QUERY_MESSAGE: return handle_channel_preference_query(sd, cmdu_rx); case ieee1905_1::eMessageType::CHANNEL_SELECTION_REQUEST_MESSAGE: @@ -4408,14 +4406,6 @@ bool slave_thread::handle_ap_metrics_query(Socket *sd, ieee1905_1::CmduMessageRx return true; } -bool slave_thread::handle_link_metrics_query(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx) -{ - const auto mid = cmdu_rx.getMessageId(); - LOG(DEBUG) << "Received LINK_METRIC_QUERY_MESSAGE, mid=" << std::hex << int(mid); - // TODO add handling for Link metric query response} - return true; -} - bool slave_thread::handle_channel_preference_query(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx) { const auto mid = cmdu_rx.getMessageId(); diff --git a/agent/src/beerocks/slave/son_slave_thread.h b/agent/src/beerocks/slave/son_slave_thread.h index 8b9334b16b..98c23f304d 100644 --- a/agent/src/beerocks/slave/son_slave_thread.h +++ b/agent/src/beerocks/slave/son_slave_thread.h @@ -238,7 +238,6 @@ class slave_thread : public beerocks::socket_thread { bool autoconfig_wsc_add_m1(); bool send_operating_channel_report(); bool handle_ap_metrics_query(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx); - bool handle_link_metrics_query(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx); bool handle_channel_preference_query(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx); bool handle_channel_selection_request(Socket *sd, ieee1905_1::CmduMessageRx &cmdu_rx); bool channel_selection_get_channel_preference(ieee1905_1::CmduMessageRx &cmdu_rx); diff --git a/controller/src/beerocks/master/son_master_thread.cpp b/controller/src/beerocks/master/son_master_thread.cpp index 8252b174a4..aec759f568 100644 --- a/controller/src/beerocks/master/son_master_thread.cpp +++ b/controller/src/beerocks/master/son_master_thread.cpp @@ -1209,7 +1209,7 @@ bool master_thread::handle_cmdu_1905_link_metric_response(const std::string &src auto TxLinkMetricData = cmdu_rx.getClass(); if (!TxLinkMetricData) { - LOG(ERROR) << "addClass ieee1905_1::tx_Link_metric_data has failed"; + LOG(ERROR) << "getClass ieee1905_1::tlvTransmitterLinkMetric has failed"; return false; } @@ -1222,7 +1222,7 @@ bool master_thread::handle_cmdu_1905_link_metric_response(const std::string &src old_link_metrics_removed = true; } - LOG(DEBUG) << "recieved tlvTransmitterLinkMetric from al_mac =" << reporting_agent_al_mac + LOG(DEBUG) << "Received TLV_TRANSMITTER_LINK_METRIC from al_mac =" << reporting_agent_al_mac << std::endl << "reported al_mac =" << TxLinkMetricData->neighbor_al_mac() << std::endl; @@ -1235,7 +1235,7 @@ bool master_thread::handle_cmdu_1905_link_metric_response(const std::string &src auto RxLinkMetricData = cmdu_rx.getClass(); if (!RxLinkMetricData) { - LOG(ERROR) << "addClass ieee1905_1::tlvReceiverLinkMetric has failed"; + LOG(ERROR) << "getClass ieee1905_1::tlvReceiverLinkMetric has failed"; return false; } @@ -1250,7 +1250,7 @@ bool master_thread::handle_cmdu_1905_link_metric_response(const std::string &src } } - LOG(DEBUG) << "recieved tlvReceiverLinkMetric from al_mac=" << reporting_agent_al_mac + LOG(DEBUG) << "Received TLV_RECEIVER_LINK_METRIC from al_mac=" << reporting_agent_al_mac << std::endl << "reported al_mac =" << RxLinkMetricData->neighbor_al_mac() << std::endl; diff --git a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricNeighborType.h b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricNeighborType.h new file mode 100644 index 0000000000..3b0925e6d7 --- /dev/null +++ b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricNeighborType.h @@ -0,0 +1,30 @@ +/////////////////////////////////////// +// AUTO GENERATED FILE - DO NOT EDIT // +/////////////////////////////////////// + +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * Copyright (c) 2016-2019 Intel Corporation + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef _TLVF_IEEE_1905_1_ELINKMETRICNEIGHBORTYPE_H_ +#define _TLVF_IEEE_1905_1_ELINKMETRICNEIGHBORTYPE_H_ + +#include +#include +#include + +namespace ieee1905_1 { + +enum eLinkMetricNeighborType: uint8_t { + ALL_NEIGHBORS = 0x0, + SPECIFIC_NEIGHBOR = 0x1, +}; + + +}; // close namespace: ieee1905_1 + +#endif //_TLVF/IEEE_1905_1_ELINKMETRICNEIGHBORTYPE_H_ diff --git a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricsType.h b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricsType.h new file mode 100644 index 0000000000..4ecede1a10 --- /dev/null +++ b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eLinkMetricsType.h @@ -0,0 +1,31 @@ +/////////////////////////////////////// +// AUTO GENERATED FILE - DO NOT EDIT // +/////////////////////////////////////// + +/* SPDX-License-Identifier: BSD-2-Clause-Patent + * + * Copyright (c) 2016-2019 Intel Corporation + * + * This code is subject to the terms of the BSD+Patent license. + * See LICENSE file for more details. + */ + +#ifndef _TLVF_IEEE_1905_1_ELINKMETRICSTYPE_H_ +#define _TLVF_IEEE_1905_1_ELINKMETRICSTYPE_H_ + +#include +#include +#include + +namespace ieee1905_1 { + +enum eLinkMetricsType: uint8_t { + TX_LINK_METRICS_ONLY = 0x0, + RX_LINK_METRICS_ONLY = 0x1, + BOTH_TX_AND_RX_LINK_METRICS = 0x2, +}; + + +}; // close namespace: ieee1905_1 + +#endif //_TLVF/IEEE_1905_1_ELINKMETRICSTYPE_H_ diff --git a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eMediaType.h b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eMediaType.h index f0489b87bf..f26fd9273f 100644 --- a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eMediaType.h +++ b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/eMediaType.h @@ -36,6 +36,14 @@ enum eMediaType: uint16_t { UNKNONWN_MEDIA = 0xffff, }; +enum eMediaTypeGroup: uint8_t { + IEEE_802_3 = 0x0, + IEEE_802_11 = 0x1, + IEEE_1901 = 0x2, + MoCA = 0x3, + UNKNOWN = 0xff, +}; + }; // close namespace: ieee1905_1 diff --git a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvLinkMetricQuery.h b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvLinkMetricQuery.h index 6b8cf1aced..04ff752d5b 100644 --- a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvLinkMetricQuery.h +++ b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvLinkMetricQuery.h @@ -21,10 +21,36 @@ #include #include #include "tlvf/ieee_1905_1/eTlvType.h" +#include "tlvf/ieee_1905_1/eLinkMetricNeighborType.h" +#include "tlvf/ieee_1905_1/eLinkMetricsType.h" #include "tlvf/common/sMacAddr.h" namespace ieee1905_1 { +class tlvLinkMetricQuery; + +class tlvLinkMetricQueryAllNeighbors : public BaseClass +{ + public: + tlvLinkMetricQueryAllNeighbors(uint8_t* buff, size_t buff_len, bool parse = false); + explicit tlvLinkMetricQueryAllNeighbors(std::shared_ptr base, bool parse = false); + ~tlvLinkMetricQueryAllNeighbors(); + + const eTlvType& type(); + const uint16_t& length(); + const eLinkMetricNeighborType& neighbor_type(); + eLinkMetricsType& link_metrics_type(); + void class_swap() override; + bool finalize() override; + static size_t get_initial_size(); + + private: + bool init(); + eTlvType* m_type = nullptr; + uint16_t* m_length = nullptr; + eLinkMetricNeighborType* m_neighbor_type = nullptr; + eLinkMetricsType* m_link_metrics_type = nullptr; +}; class tlvLinkMetricQuery : public BaseClass { @@ -33,22 +59,11 @@ class tlvLinkMetricQuery : public BaseClass explicit tlvLinkMetricQuery(std::shared_ptr base, bool parse = false); ~tlvLinkMetricQuery(); - enum eNeighborType: uint8_t { - ALL_NEIGHBORS = 0x0, - SPECIFIC_NEIGHBOR = 0x1, - }; - - enum eLinkMetricsType: uint8_t { - TX_LINK_METRICS_ONLY = 0x0, - RX_LINK_METRICS_ONLY = 0x1, - BOTH_TX_AND_RX_LINK_METRICS = 0x2, - }; - const eTlvType& type(); const uint16_t& length(); - eNeighborType& neighbor_type(); + eLinkMetricNeighborType& neighbor_type(); sMacAddr& mac_al_1905_device(); - eLinkMetricsType& link_metrics(); + eLinkMetricsType& link_metrics_type(); void class_swap() override; bool finalize() override; static size_t get_initial_size(); @@ -57,9 +72,9 @@ class tlvLinkMetricQuery : public BaseClass bool init(); eTlvType* m_type = nullptr; uint16_t* m_length = nullptr; - eNeighborType* m_neighbor_type = nullptr; + eLinkMetricNeighborType* m_neighbor_type = nullptr; sMacAddr* m_mac_al_1905_device = nullptr; - eLinkMetricsType* m_link_metrics = nullptr; + eLinkMetricsType* m_link_metrics_type = nullptr; }; }; // close namespace: ieee1905_1 diff --git a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvReceiverLinkMetric.h b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvReceiverLinkMetric.h index 6003b6a77c..b78a53ab57 100644 --- a/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvReceiverLinkMetric.h +++ b/framework/tlvf/AutoGenerated/include/tlvf/ieee_1905_1/tlvReceiverLinkMetric.h @@ -38,19 +38,19 @@ class tlvReceiverLinkMetric : public BaseClass typedef struct sLinkMetricInfo { //The underlying network technology eMediaType intfType; - uint32_t packet_errors_received; + uint32_t packet_errors; //Number of packets received at the //interface during the same measurement //period used to count packetErrors. - uint32_t packets_receiveds; + uint32_t packets_received; //If the media type of the link is IEEE 802.11 (8 MSB value of media //type as defined in Table 6-12, 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_db; void struct_swap(){ tlvf_swap(16, reinterpret_cast(&intfType)); - tlvf_swap(32, reinterpret_cast(&packet_errors_received)); - tlvf_swap(32, reinterpret_cast(&packets_receiveds)); + tlvf_swap(32, reinterpret_cast(&packet_errors)); + tlvf_swap(32, reinterpret_cast(&packets_received)); } void struct_init(){ rssi_db = 0xff; diff --git a/framework/tlvf/AutoGenerated/src/tlvf/ieee_1905_1/tlvLinkMetricQuery.cpp b/framework/tlvf/AutoGenerated/src/tlvf/ieee_1905_1/tlvLinkMetricQuery.cpp index a21d566eb6..be425da061 100644 --- a/framework/tlvf/AutoGenerated/src/tlvf/ieee_1905_1/tlvLinkMetricQuery.cpp +++ b/framework/tlvf/AutoGenerated/src/tlvf/ieee_1905_1/tlvLinkMetricQuery.cpp @@ -15,6 +15,117 @@ using namespace ieee1905_1; +tlvLinkMetricQueryAllNeighbors::tlvLinkMetricQueryAllNeighbors(uint8_t* buff, size_t buff_len, bool parse) : + BaseClass(buff, buff_len, parse) { + m_init_succeeded = init(); +} +tlvLinkMetricQueryAllNeighbors::tlvLinkMetricQueryAllNeighbors(std::shared_ptr base, bool parse) : +BaseClass(base->getBuffPtr(), base->getBuffRemainingBytes(), parse){ + m_init_succeeded = init(); +} +tlvLinkMetricQueryAllNeighbors::~tlvLinkMetricQueryAllNeighbors() { +} +const eTlvType& tlvLinkMetricQueryAllNeighbors::type() { + return (const eTlvType&)(*m_type); +} + +const uint16_t& tlvLinkMetricQueryAllNeighbors::length() { + return (const uint16_t&)(*m_length); +} + +const eLinkMetricNeighborType& tlvLinkMetricQueryAllNeighbors::neighbor_type() { + return (const eLinkMetricNeighborType&)(*m_neighbor_type); +} + +eLinkMetricsType& tlvLinkMetricQueryAllNeighbors::link_metrics_type() { + return (eLinkMetricsType&)(*m_link_metrics_type); +} + +void tlvLinkMetricQueryAllNeighbors::class_swap() +{ + tlvf_swap(16, reinterpret_cast(m_length)); + tlvf_swap(8*sizeof(eLinkMetricsType), reinterpret_cast(m_link_metrics_type)); +} + +bool tlvLinkMetricQueryAllNeighbors::finalize() +{ + if (m_parse__) { + TLVF_LOG(DEBUG) << "finalize() called but m_parse__ is set"; + return true; + } + if (m_finalized__) { + TLVF_LOG(DEBUG) << "finalize() called for already finalized class"; + return true; + } + if (!isPostInitSucceeded()) { + TLVF_LOG(ERROR) << "post init check failed"; + return false; + } + if (m_inner__) { + if (!m_inner__->finalize()) { + TLVF_LOG(ERROR) << "m_inner__->finalize() failed"; + return false; + } + auto tailroom = m_inner__->getMessageBuffLength() - m_inner__->getMessageLength(); + m_buff_ptr__ -= tailroom; + *m_length -= tailroom; + } + class_swap(); + m_finalized__ = true; + return true; +} + +size_t tlvLinkMetricQueryAllNeighbors::get_initial_size() +{ + size_t class_size = 0; + class_size += sizeof(eTlvType); // type + class_size += sizeof(uint16_t); // length + class_size += sizeof(eLinkMetricNeighborType); // neighbor_type + class_size += sizeof(eLinkMetricsType); // link_metrics_type + return class_size; +} + +bool tlvLinkMetricQueryAllNeighbors::init() +{ + if (getBuffRemainingBytes() < get_initial_size()) { + TLVF_LOG(ERROR) << "Not enough available space on buffer. Class init failed"; + return false; + } + m_type = (eTlvType*)m_buff_ptr__; + if (!m_parse__) *m_type = eTlvType::TLV_LINK_METRIC_QUERY; + if (!buffPtrIncrementSafe(sizeof(eTlvType))) { + LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eTlvType) << ") Failed!"; + return false; + } + m_length = (uint16_t*)m_buff_ptr__; + if (!m_parse__) *m_length = 0; + if (!buffPtrIncrementSafe(sizeof(uint16_t))) { + LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(uint16_t) << ") Failed!"; + return false; + } + m_neighbor_type = (eLinkMetricNeighborType*)m_buff_ptr__; + if (!m_parse__) *m_neighbor_type = eLinkMetricNeighborType::ALL_NEIGHBORS; + if (!buffPtrIncrementSafe(sizeof(eLinkMetricNeighborType))) { + LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eLinkMetricNeighborType) << ") Failed!"; + return false; + } + if(m_length && !m_parse__){ (*m_length) += sizeof(eLinkMetricNeighborType); } + m_link_metrics_type = (eLinkMetricsType*)m_buff_ptr__; + if (!buffPtrIncrementSafe(sizeof(eLinkMetricsType))) { + LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eLinkMetricsType) << ") Failed!"; + return false; + } + if(m_length && !m_parse__){ (*m_length) += sizeof(eLinkMetricsType); } + if (m_parse__) { class_swap(); } + if (m_parse__) { + if (*m_type != eTlvType::TLV_LINK_METRIC_QUERY) { + TLVF_LOG(ERROR) << "TLV type mismatch. Expected value: " << int(eTlvType::TLV_LINK_METRIC_QUERY) << ", received value: " << int(*m_type); + return false; + } + } + return true; +} + tlvLinkMetricQuery::tlvLinkMetricQuery(uint8_t* buff, size_t buff_len, bool parse) : BaseClass(buff, buff_len, parse) { m_init_succeeded = init(); @@ -33,24 +144,24 @@ const uint16_t& tlvLinkMetricQuery::length() { return (const uint16_t&)(*m_length); } -tlvLinkMetricQuery::eNeighborType& tlvLinkMetricQuery::neighbor_type() { - return (eNeighborType&)(*m_neighbor_type); +eLinkMetricNeighborType& tlvLinkMetricQuery::neighbor_type() { + return (eLinkMetricNeighborType&)(*m_neighbor_type); } sMacAddr& tlvLinkMetricQuery::mac_al_1905_device() { return (sMacAddr&)(*m_mac_al_1905_device); } -tlvLinkMetricQuery::eLinkMetricsType& tlvLinkMetricQuery::link_metrics() { - return (eLinkMetricsType&)(*m_link_metrics); +eLinkMetricsType& tlvLinkMetricQuery::link_metrics_type() { + return (eLinkMetricsType&)(*m_link_metrics_type); } void tlvLinkMetricQuery::class_swap() { tlvf_swap(16, reinterpret_cast(m_length)); - tlvf_swap(8*sizeof(eNeighborType), reinterpret_cast(m_neighbor_type)); + tlvf_swap(8*sizeof(eLinkMetricNeighborType), reinterpret_cast(m_neighbor_type)); m_mac_al_1905_device->struct_swap(); - tlvf_swap(8*sizeof(eLinkMetricsType), reinterpret_cast(m_link_metrics)); + tlvf_swap(8*sizeof(eLinkMetricsType), reinterpret_cast(m_link_metrics_type)); } bool tlvLinkMetricQuery::finalize() @@ -86,9 +197,9 @@ size_t tlvLinkMetricQuery::get_initial_size() size_t class_size = 0; class_size += sizeof(eTlvType); // type class_size += sizeof(uint16_t); // length - class_size += sizeof(eNeighborType); // neighbor_type + class_size += sizeof(eLinkMetricNeighborType); // neighbor_type class_size += sizeof(sMacAddr); // mac_al_1905_device - class_size += sizeof(eLinkMetricsType); // link_metrics + class_size += sizeof(eLinkMetricsType); // link_metrics_type return class_size; } @@ -110,12 +221,12 @@ bool tlvLinkMetricQuery::init() LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(uint16_t) << ") Failed!"; return false; } - m_neighbor_type = (eNeighborType*)m_buff_ptr__; - if (!buffPtrIncrementSafe(sizeof(eNeighborType))) { - LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eNeighborType) << ") Failed!"; + m_neighbor_type = (eLinkMetricNeighborType*)m_buff_ptr__; + if (!buffPtrIncrementSafe(sizeof(eLinkMetricNeighborType))) { + LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eLinkMetricNeighborType) << ") Failed!"; return false; } - if(m_length && !m_parse__){ (*m_length) += sizeof(eNeighborType); } + if(m_length && !m_parse__){ (*m_length) += sizeof(eLinkMetricNeighborType); } m_mac_al_1905_device = (sMacAddr*)m_buff_ptr__; if (!buffPtrIncrementSafe(sizeof(sMacAddr))) { LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(sMacAddr) << ") Failed!"; @@ -123,7 +234,7 @@ bool tlvLinkMetricQuery::init() } if(m_length && !m_parse__){ (*m_length) += sizeof(sMacAddr); } if (!m_parse__) { m_mac_al_1905_device->struct_init(); } - m_link_metrics = (eLinkMetricsType*)m_buff_ptr__; + m_link_metrics_type = (eLinkMetricsType*)m_buff_ptr__; if (!buffPtrIncrementSafe(sizeof(eLinkMetricsType))) { LOG(ERROR) << "buffPtrIncrementSafe(" << std::dec << sizeof(eLinkMetricsType) << ") Failed!"; return false; diff --git a/framework/tlvf/src/include/tlvf/CmduMessage.h b/framework/tlvf/src/include/tlvf/CmduMessage.h index cd941e577f..32a1892322 100644 --- a/framework/tlvf/src/include/tlvf/CmduMessage.h +++ b/framework/tlvf/src/include/tlvf/CmduMessage.h @@ -35,10 +35,6 @@ class CmduMessage { eMessageType getMessageType(); uint16_t getMessageId(); void setMessageId(uint16_t mid); - bool getNextTlvType(eTlvType &tlvType) const; - int getNextTlvType() const; - uint16_t getNextTlvLength() const; - uint8_t *getNextTlvData() const; // Forward wrapper functions // TODO check which of them can be removed diff --git a/framework/tlvf/src/include/tlvf/CmduMessageRx.h b/framework/tlvf/src/include/tlvf/CmduMessageRx.h index 515f0574e9..effb4b69ca 100644 --- a/framework/tlvf/src/include/tlvf/CmduMessageRx.h +++ b/framework/tlvf/src/include/tlvf/CmduMessageRx.h @@ -23,6 +23,8 @@ class CmduMessageRx : public CmduMessage { CmduMessageRx &operator=(const CmduMessageRx &) = delete; private: + int getNextTlvType() const; + uint16_t getNextTlvLength() const; std::shared_ptr parseNextTlv(); }; diff --git a/framework/tlvf/src/src/CmduMessage.cpp b/framework/tlvf/src/src/CmduMessage.cpp index 10c63959df..c802f2528a 100644 --- a/framework/tlvf/src/src/CmduMessage.cpp +++ b/framework/tlvf/src/src/CmduMessage.cpp @@ -10,40 +10,6 @@ using namespace ieee1905_1; -int CmduMessage::getNextTlvType() const -{ - if (!getCmduHeader()) - return -1; - sTlvHeader *tlv = reinterpret_cast(msg.prevClass()->getBuffPtr()); - return tlv->type; -} - -bool CmduMessage::getNextTlvType(eTlvType &tlvType) const -{ - int tlvValue = getNextTlvType(); - if (tlvValue < 0) - return false; - tlvType = static_cast(tlvValue); - return eTlvTypeValidate::check(tlvValue); -} - -uint16_t CmduMessage::getNextTlvLength() const -{ - if (!getCmduHeader()) - return -1; - sTlvHeader *tlv = reinterpret_cast(msg.prevClass()->getBuffPtr()); - return tlv->length; -} - -uint8_t *CmduMessage::getNextTlvData() const -{ - if (!getCmduHeader()) - return nullptr; - - sTlvHeader *tlv = reinterpret_cast(msg.prevClass()->getBuffPtr()); - return reinterpret_cast(tlv) + sizeof(*tlv); -} - eMessageType CmduMessage::getMessageType() { uint16_t msgValue = 0; diff --git a/framework/tlvf/src/src/CmduMessageRx.cpp b/framework/tlvf/src/src/CmduMessageRx.cpp index 72a0d82109..3c25514d8b 100644 --- a/framework/tlvf/src/src/CmduMessageRx.cpp +++ b/framework/tlvf/src/src/CmduMessageRx.cpp @@ -51,6 +51,27 @@ using namespace ieee1905_1; +int CmduMessageRx::getNextTlvType() const +{ + if (!getCmduHeader()) + return -1; + sTlvHeader *tlv = reinterpret_cast(msg.prevClass()->getBuffPtr()); + return tlv->type; +} + +uint16_t CmduMessageRx::getNextTlvLength() const +{ + if (!getCmduHeader()) { + return UINT16_MAX; + } + sTlvHeader *tlv = reinterpret_cast(msg.prevClass()->getBuffPtr()); + + uint16_t tlv_length = tlv->length; + swap_16(tlv_length); + + return tlv_length; +} + std::shared_ptr CmduMessageRx::parseNextTlv() { switch (getNextTlvType()) { @@ -76,7 +97,27 @@ std::shared_ptr CmduMessageRx::parseNextTlv() return msg.addClass(); } case (8): { - return msg.addClass(); + /** + * The IEEE 1905.1 standard says about the Link Metric Query TLV and the neighbor type + * octet that "If the value is 0, then the EUI48 field is not present; if the value is 1, + * then the EUI-48 field shall be present." + * + * However, optional fields are not currently supported by TLVF. + * + * As a workaround, instead of defining a tlvLinkMetricQuery TLV with an optional field, + * we have defined two different TLVs, one with the optional field and the other one + * without it. Application must then check the length of received TLV to know if optional + * field (MAC address of neighbor device) is present or not and then create an instance of + * either tlvLinkMetricQuery or tlvLinkMetricQueryAllNeighbors respectively. + */ + const uint16_t all_neighbors_tlv_length = 2; + uint16_t tlv_length = getNextTlvLength(); + + if (all_neighbors_tlv_length == tlv_length) { + return msg.addClass(); + } else { + return msg.addClass(); + } } case (9): { return msg.addClass(); diff --git a/framework/tlvf/test/tlvf_test.cpp b/framework/tlvf/test/tlvf_test.cpp index 3166daaf19..c78538e574 100644 --- a/framework/tlvf/test/tlvf_test.cpp +++ b/framework/tlvf/test/tlvf_test.cpp @@ -447,8 +447,8 @@ int test_all() } MAPF_DBG("TLV LENGTH START: " << firstTlv->length()); - auto secondTlv = msg.addClass(); // another tlv for the example - secondTlv->link_metrics() = tlvLinkMetricQuery::eLinkMetricsType::RX_LINK_METRICS_ONLY; + auto secondTlv = msg.addClass(); // another tlv for the example + secondTlv->link_metrics_type() = eLinkMetricsType::RX_LINK_METRICS_ONLY; LOG(DEBUG) << "Start WSC M2"; auto thirdTlv = msg.addClass(); @@ -611,8 +611,6 @@ int test_all() errors++; } - MAPF_DBG("size: " << received_message.getNextTlvLength()); - auto tlv2 = received_message.getClass(); if (tlv2 != nullptr) { MAPF_DBG("TLV2 LENGTH AFTER INIT: " << tlv2->length()); diff --git a/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricNeighborType.yaml b/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricNeighborType.yaml new file mode 100644 index 0000000000..59078b9bb3 --- /dev/null +++ b/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricNeighborType.yaml @@ -0,0 +1,10 @@ +# +--- +_namespace: ieee1905_1 + +eLinkMetricNeighborType: + _type: enum + _enum_storage: uint8_t + ALL_NEIGHBORS: 0x00 + SPECIFIC_NEIGHBOR: 0x01 + diff --git a/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricsType.yaml b/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricsType.yaml new file mode 100644 index 0000000000..d4990eee38 --- /dev/null +++ b/framework/tlvf/yaml/tlvf/ieee_1905_1/eLinkMetricsType.yaml @@ -0,0 +1,11 @@ +# +--- +_namespace: ieee1905_1 + +eLinkMetricsType: + _type: enum + _enum_storage: uint8_t + TX_LINK_METRICS_ONLY: 0x00 + RX_LINK_METRICS_ONLY: 0x01 + BOTH_TX_AND_RX_LINK_METRICS: 0x02 + diff --git a/framework/tlvf/yaml/tlvf/ieee_1905_1/eMediaType.yaml b/framework/tlvf/yaml/tlvf/ieee_1905_1/eMediaType.yaml index e60d172c2c..0de5ffa8f2 100755 --- a/framework/tlvf/yaml/tlvf/ieee_1905_1/eMediaType.yaml +++ b/framework/tlvf/yaml/tlvf/ieee_1905_1/eMediaType.yaml @@ -20,3 +20,12 @@ eMediaType: MOCA_V1_1: 0x0300 UNKNONWN_MEDIA: 0xFFFF +eMediaTypeGroup: + _type: enum + _enum_storage: uint8_t + IEEE_802_3: 0x00 + IEEE_802_11: 0x01 + IEEE_1901: 0x02 + MoCA: 0x03 + UNKNOWN: 0xFF + diff --git a/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvLinkMetricQuery.yaml b/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvLinkMetricQuery.yaml index 3b4152f643..5cc89d8e92 100644 --- a/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvLinkMetricQuery.yaml +++ b/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvLinkMetricQuery.yaml @@ -2,6 +2,23 @@ --- _namespace: ieee1905_1 +# TLVF does not currently support optional fields and this TLV contains one. +# As a workaround, we define two different TLVs instead of a single tlvLinkMetricQuery with an +# optional field. Application must then check the length of received message to know if optional +# field is present or not and then create an instance of either of these classes. + +tlvLinkMetricQueryAllNeighbors: + _type: class + _is_tlv_class : True + type: + _type: eTlvType + _value_const: TLV_LINK_METRIC_QUERY + length: uint16_t + neighbor_type: + _type: eLinkMetricNeighborType + _value_const: ALL_NEIGHBORS + link_metrics_type: eLinkMetricsType + tlvLinkMetricQuery: _type: class _is_tlv_class : True @@ -9,22 +26,8 @@ tlvLinkMetricQuery: _type: eTlvType _value_const: TLV_LINK_METRIC_QUERY length: uint16_t - neighbor_type: eNeighborType + neighbor_type: eLinkMetricNeighborType mac_al_1905_device: _type: sMacAddr - _optional: true - link_metrics: eLinkMetricsType - -eNeighborType: - _type: enum - _enum_storage: uint8_t - ALL_NEIGHBORS: 0x00 - SPECIFIC_NEIGHBOR: 0x01 + link_metrics_type: eLinkMetricsType -eLinkMetricsType: - _type: enum - _enum_storage: uint8_t - TX_LINK_METRICS_ONLY: 0x00 - RX_LINK_METRICS_ONLY: 0x01 - BOTH_TX_AND_RX_LINK_METRICS: 0x02 - \ No newline at end of file diff --git a/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvReceiverLinkMetric.yaml b/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvReceiverLinkMetric.yaml index 2b07c3fc1f..b59a2c6790 100644 --- a/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvReceiverLinkMetric.yaml +++ b/framework/tlvf/yaml/tlvf/ieee_1905_1/tlvReceiverLinkMetric.yaml @@ -23,8 +23,8 @@ sLinkMetricInfo: intfType: _type: eMediaType _comment: The underlying network technology - packet_errors_received: uint32_t - packets_receiveds: + packet_errors: uint32_t + packets_received: _type: uint32_t _comment: | Number of packets received at the diff --git a/tests/test_flows.py b/tests/test_flows.py index 62464cffdd..3c97b7366f 100755 --- a/tests/test_flows.py +++ b/tests/test_flows.py @@ -424,6 +424,19 @@ def test_ap_capability_query(self): self.debug("Confirming ap capability report has been received on controller") self.check_log(self.gateway, "controller", "AP_CAPABILITY_REPORT_MESSAGE") + def test_link_metric_query(self): + self.gateway_ucc.dev_send_1905(self.mac_repeater1, 0x0005, + tlv(0x08,0x0002,"0x00 0x02")) + time.sleep(1) + + self.debug("Confirming link metric query has been received on agent") + self.check_log(self.repeater1, "agent", "Received LINK_METRIC_QUERY_MESSAGE") + + self.debug("Confirming link metric response has been received on controller") + self.check_log(self.gateway, "controller", "Received LINK_METRIC_RESPONSE_MESSAGE") + self.check_log(self.gateway, "controller", "Received TLV_TRANSMITTER_LINK_METRIC") + self.check_log(self.gateway, "controller", "Received TLV_RECEIVER_LINK_METRIC") + def test_combined_infra_metrics(self): self.debug("Send AP Metrics query message to agent 1") self.gateway_ucc.dev_send_1905(self.mac_repeater1, 0x800B, @@ -456,17 +469,13 @@ def test_combined_infra_metrics(self): "0xa4243444 0xa5253545 0xa6263646")) self.check_log(self.gateway, "controller", "Received AP_METRICS_RESPONSE_MESSAGE") - self.debug("Send 1905 Link metric query to agent 1 (neighbor agent 2)") + self.debug("Send 1905 Link metric query to agent 1 (neighbor gateway)") self.gateway_ucc.dev_send_1905(self.mac_repeater1, 0x0005, - tlv(0x08,0x0008,"0x01 {%s} 0x02" % self.mac_repeater2)) - self.check_log(self.repeater1, "agent_wlan0", "Received LINK_METRIC_QUERY_MESSAGE") - # TODO agent should send response autonomously - self.repeater1_ucc.dev_send_1905(self.mac_gateway, 0x6, - tlv(0x09,0x0029,"{%s} {%s} {%s} {%s} 0x0100 0x01 0x00000000 0x0000e300 0x4230 0x0064 0x0300" % - (self.mac_repeater1, self.mac_repeater2, self.mac_repeater1_wlan0, self.mac_repeater2_wlan2)), - tlv(0x0a,0x0023,"{%s} {%s} {%s} {%s} 0x0100 0x00000007 0x00020000 0x31" % - (self.mac_repeater1, self.mac_repeater2, self.mac_repeater1, self.mac_repeater2))) + tlv(0x08,0x0008,"0x01 {%s} 0x02" % self.mac_gateway)) + self.check_log(self.repeater1, "agent", "Received LINK_METRIC_QUERY_MESSAGE") self.check_log(self.gateway, "controller", "Received LINK_METRIC_RESPONSE_MESSAGE") + self.check_log(self.gateway, "controller", "Received TLV_TRANSMITTER_LINK_METRIC") + self.check_log(self.gateway, "controller", "Received TLV_RECEIVER_LINK_METRIC") # Trigger combined infra metrics self.debug("Send Combined infrastructure metrics message to agent 1")