util: NetworkMonitor: fine-grained signals on interface/address changes
Change-Id: I60d4cc6d673b920ba81d57502977f0340be0da48
Refs: #3353
diff --git a/src/util/detail/network-monitor-impl-rtnl.cpp b/src/util/detail/network-monitor-impl-rtnl.cpp
index 36c9190..587bc90 100644
--- a/src/util/detail/network-monitor-impl-rtnl.cpp
+++ b/src/util/detail/network-monitor-impl-rtnl.cpp
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
@@ -17,74 +17,494 @@
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
-#include "ndn-cxx-config.hpp"
-
-#ifdef NDN_CXX_HAVE_RTNETLINK
-
#include "network-monitor-impl-rtnl.hpp"
+#include "linux-if-constants.hpp"
+#include "../logger.hpp"
+#include "../network-address.hpp"
+#include "../network-interface.hpp"
+#include "../time.hpp"
-#include <netinet/in.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <net/if.h>
+#include <boost/asio/write.hpp>
#include <cerrno>
-#include <cstring>
+#include <cstdlib>
+#include <net/if_arp.h>
+#include <sys/socket.h>
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
namespace ndn {
namespace util {
NetworkMonitor::Impl::Impl(NetworkMonitor& nm, boost::asio::io_service& io)
: m_nm(nm)
- , m_socket(io)
+ , m_socket(make_shared<boost::asio::posix::stream_descriptor>(io))
+ , m_pid(0)
+ , m_sequenceNo(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
+ , m_isEnumeratingLinks(false)
+ , m_isEnumeratingAddresses(false)
{
- int fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
- if (fd < 0)
- BOOST_THROW_EXCEPTION(Error(std::string("Cannot create netlink socket (") +
- std::strerror(errno) + ")"));
+ initSocket();
+ asyncRead();
- sockaddr_nl addr{};
- addr.nl_family = AF_NETLINK;
- addr.nl_groups = RTMGRP_LINK |
- RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
- RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+ NDN_LOG_TRACE("enumerating links");
+ sendDumpRequest(RTM_GETLINK);
+ m_isEnumeratingLinks = true;
+}
- if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
- BOOST_THROW_EXCEPTION(Error(std::string("Cannot bind on netlink socket (") +
- std::strerror(errno) + ")"));
+NetworkMonitor::Impl::~Impl()
+{
+ boost::system::error_code error;
+ m_socket->close(error);
+}
+
+shared_ptr<NetworkInterface>
+NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+{
+ for (const auto& e : m_interfaces) {
+ if (e.second->getName() == ifname)
+ return e.second;
}
+ return nullptr;
+}
- m_socket.assign(fd);
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+ std::vector<shared_ptr<NetworkInterface>> v;
+ v.reserve(m_interfaces.size());
- m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
- bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+ for (const auto& e : m_interfaces) {
+ v.push_back(e.second);
+ }
+ return v;
+}
+
+bool
+NetworkMonitor::Impl::isEnumerating() const
+{
+ return m_isEnumeratingLinks || m_isEnumeratingAddresses;
}
void
-NetworkMonitor::Impl::onReceiveRtNetlink(const boost::system::error_code& error, size_t nBytesReceived)
+NetworkMonitor::Impl::initSocket()
{
+ NDN_LOG_TRACE("creating netlink socket");
+
+ int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0) {
+ BOOST_THROW_EXCEPTION(Error(std::string("Cannot create netlink socket (") +
+ std::strerror(errno) + ")"));
+ }
+ m_socket->assign(fd);
+
+ sockaddr_nl addr{};
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_LINK | RTMGRP_NOTIFY |
+ RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
+ RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+ if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
+ BOOST_THROW_EXCEPTION(Error(std::string("Cannot bind netlink socket (") +
+ std::strerror(errno) + ")"));
+ }
+
+ // find out what pid has been assigned to us
+ socklen_t len = sizeof(addr);
+ if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
+ BOOST_THROW_EXCEPTION(Error(std::string("Cannot obtain netlink socket address (") +
+ std::strerror(errno) + ")"));
+ }
+ if (len != sizeof(addr)) {
+ BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
+ }
+ if (addr.nl_family != AF_NETLINK) {
+ BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
+ }
+ m_pid = addr.nl_pid;
+ NDN_LOG_TRACE("our pid is " << m_pid);
+}
+
+void
+NetworkMonitor::Impl::sendDumpRequest(uint16_t nlmsgType)
+{
+ auto request = make_shared<RtnlRequest>();
+ request->nlh.nlmsg_len = sizeof(RtnlRequest);
+ request->nlh.nlmsg_type = nlmsgType;
+ request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ request->nlh.nlmsg_seq = ++m_sequenceNo;
+ request->nlh.nlmsg_pid = m_pid;
+ request->ifi.ifi_family = AF_UNSPEC;
+ request->rta.rta_type = IFLA_EXT_MASK;
+ request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
+ request->rtext = 1 << 3; // RTEXT_FILTER_SKIP_STATS
+
+ boost::asio::async_write(*m_socket, boost::asio::buffer(request.get(), sizeof(RtnlRequest)),
+ // capture 'request' to prevent its premature deallocation
+ [request] (const boost::system::error_code& error, size_t) {
+ if (error && error != boost::asio::error::operation_aborted) {
+ NDN_LOG_ERROR("write failed: " << error.message());
+ BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + error.message() + ")"));
+ }
+ });
+}
+
+static const char*
+nlmsgTypeToString(uint16_t type)
+{
+#define NDN_NLMSG_STRING(x) case NLMSG_##x: return "<" #x ">"
+#define NDN_RTM_STRING(x) case RTM_##x: return "<" #x ">"
+ switch (type) {
+ NDN_NLMSG_STRING(NOOP);
+ NDN_NLMSG_STRING(ERROR);
+ NDN_NLMSG_STRING(DONE);
+ NDN_NLMSG_STRING(OVERRUN);
+ NDN_RTM_STRING(NEWLINK);
+ NDN_RTM_STRING(DELLINK);
+ NDN_RTM_STRING(NEWADDR);
+ NDN_RTM_STRING(DELADDR);
+ NDN_RTM_STRING(NEWROUTE);
+ NDN_RTM_STRING(DELROUTE);
+ default:
+ return "";
+ }
+#undef NDN_NLMSG_STRING
+#undef NDN_RTM_STRING
+}
+
+static InterfaceType
+ifiTypeToInterfaceType(uint16_t type)
+{
+ switch (type) {
+ case ARPHRD_ETHER:
+ return InterfaceType::ETHERNET;
+ case ARPHRD_LOOPBACK:
+ return InterfaceType::LOOPBACK;
+ default:
+ return InterfaceType::UNKNOWN;
+ }
+}
+
+static AddressFamily
+ifaFamilyToAddressFamily(uint8_t family)
+{
+ switch (family) {
+ case AF_INET:
+ return AddressFamily::V4;
+ case AF_INET6:
+ return AddressFamily::V6;
+ default:
+ return AddressFamily::UNSPECIFIED;
+ }
+}
+
+static AddressScope
+ifaScopeToAddressScope(uint8_t scope)
+{
+ switch (scope) {
+ case RT_SCOPE_NOWHERE:
+ return AddressScope::NOWHERE;
+ case RT_SCOPE_HOST:
+ return AddressScope::HOST;
+ case RT_SCOPE_LINK:
+ return AddressScope::LINK;
+ default:
+ return AddressScope::GLOBAL;
+ }
+}
+
+void
+NetworkMonitor::Impl::asyncRead()
+{
+ m_socket->async_read_some(boost::asio::buffer(m_buffer),
+ bind(&Impl::handleRead, this, _1, _2, m_socket));
+}
+
+void
+NetworkMonitor::Impl::handleRead(const boost::system::error_code& error, size_t nBytesRead,
+ const shared_ptr<boost::asio::posix::stream_descriptor>& socket)
+{
+ if (!socket->is_open() ||
+ error == boost::asio::error::operation_aborted) {
+ // socket was closed, ignore the error
+ NDN_LOG_TRACE("socket closed or operation aborted");
+ return;
+ }
if (error) {
+ NDN_LOG_ERROR("read failed: " << error.message());
+ BOOST_THROW_EXCEPTION(Error("Netlink socket read failed (" + error.message() + ")"));
+ }
+
+ NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
+
+ const nlmsghdr* nlh = reinterpret_cast<const nlmsghdr*>(m_buffer.data());
+ if (!isEnumerating() || (nlh->nlmsg_seq == m_sequenceNo && nlh->nlmsg_pid == m_pid)) {
+ parseNetlinkMessage(nlh, nBytesRead);
+ }
+ else {
+ NDN_LOG_TRACE("seq/pid mismatch, ignoring");
+ }
+
+ asyncRead();
+}
+
+void
+NetworkMonitor::Impl::parseNetlinkMessage(const nlmsghdr* nlh, size_t len)
+{
+ while (NLMSG_OK(nlh, len)) {
+ NDN_LOG_TRACE("parsing " << (nlh->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
+ "message type=" << nlh->nlmsg_type << nlmsgTypeToString(nlh->nlmsg_type) <<
+ " len=" << nlh->nlmsg_len <<
+ " seq=" << nlh->nlmsg_seq <<
+ " pid=" << nlh->nlmsg_pid);
+
+ if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
+ NDN_LOG_ERROR("netlink dump was interrupted");
+ // TODO: technically we should retry the dump...
+ break;
+ }
+
+ if (nlh->nlmsg_type == NLMSG_DONE)
+ break;
+
+ switch (nlh->nlmsg_type) {
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ parseLinkMessage(nlh, reinterpret_cast<const ifinfomsg*>(NLMSG_DATA(nlh)));
+ if (!isEnumerating())
+ m_nm.onNetworkStateChanged(); // backward compat
+ break;
+
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ parseAddressMessage(nlh, reinterpret_cast<const ifaddrmsg*>(NLMSG_DATA(nlh)));
+ if (!isEnumerating())
+ m_nm.onNetworkStateChanged(); // backward compat
+ break;
+
+ case RTM_NEWROUTE:
+ case RTM_DELROUTE:
+ parseRouteMessage(nlh, reinterpret_cast<const rtmsg*>(NLMSG_DATA(nlh)));
+ if (!isEnumerating())
+ m_nm.onNetworkStateChanged(); // backward compat
+ break;
+
+ case NLMSG_ERROR: {
+ const nlmsgerr* err = reinterpret_cast<const nlmsgerr*>(NLMSG_DATA(nlh));
+ if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(nlmsgerr)))
+ NDN_LOG_ERROR("truncated NLMSG_ERROR");
+ else if (err->error == 0)
+ // an error code of zero indicates an ACK message, not an error
+ NDN_LOG_TRACE("ACK");
+ else
+ NDN_LOG_ERROR("NLMSG_ERROR: " << std::strerror(std::abs(err->error)));
+ break;
+ }
+ }
+
+ nlh = NLMSG_NEXT(nlh, len);
+ }
+
+ if (nlh->nlmsg_type == NLMSG_DONE && m_isEnumeratingLinks) {
+ // links enumeration complete, now request all the addresses
+ m_isEnumeratingLinks = false;
+ NDN_LOG_TRACE("enumerating addresses");
+ sendDumpRequest(RTM_GETADDR);
+ m_isEnumeratingAddresses = true;
+ }
+ else if (nlh->nlmsg_type == NLMSG_DONE && m_isEnumeratingAddresses) {
+ // links and addresses enumeration complete
+ m_isEnumeratingAddresses = false;
+ // TODO: enumerate routes
+ NDN_LOG_DEBUG("enumeration complete");
+ m_nm.onEnumerationCompleted();
+ }
+}
+
+void
+NetworkMonitor::Impl::parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi)
+{
+ if (ifiTypeToInterfaceType(ifi->ifi_type) == InterfaceType::UNKNOWN) {
+ NDN_LOG_DEBUG("unhandled interface type " << ifi->ifi_type);
return;
}
- const nlmsghdr* nlh = reinterpret_cast<const nlmsghdr*>(m_buffer);
- while ((NLMSG_OK(nlh, nBytesReceived)) && (nlh->nlmsg_type != NLMSG_DONE)) {
- if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR ||
- nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK ||
- nlh->nlmsg_type == RTM_NEWROUTE || nlh->nlmsg_type == RTM_DELROUTE) {
- m_nm.onNetworkStateChanged();
- break;
- }
- nlh = NLMSG_NEXT(nlh, nBytesReceived);
+ shared_ptr<NetworkInterface> interface;
+ auto it = m_interfaces.find(ifi->ifi_index);
+ if (it != m_interfaces.end()) {
+ interface = it->second;
+ BOOST_ASSERT(interface != nullptr);
+ BOOST_ASSERT(interface->getIndex() == ifi->ifi_index);
}
- m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
- bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+ if (nlh->nlmsg_type == RTM_DELLINK) {
+ if (interface != nullptr) {
+ NDN_LOG_DEBUG("removing interface " << interface->getName());
+ m_interfaces.erase(it);
+ m_nm.onInterfaceRemoved(interface);
+ }
+ return;
+ }
+
+ if (interface == nullptr) {
+ // cannot use make_shared because NetworkInterface constructor is private
+ interface.reset(new NetworkInterface);
+ interface->setIndex(ifi->ifi_index);
+ }
+ interface->setType(ifiTypeToInterfaceType(ifi->ifi_type));
+ interface->setFlags(ifi->ifi_flags);
+
+ const rtattr* rta = reinterpret_cast<const rtattr*>(IFLA_RTA(ifi));
+ size_t rtaTotalLen = IFLA_PAYLOAD(nlh);
+ uint8_t operState = linux_if::OPER_STATE_UNKNOWN;
+
+ while (RTA_OK(rta, rtaTotalLen)) {
+ size_t attrLen = RTA_PAYLOAD(rta);
+
+ switch (rta->rta_type) {
+ case IFLA_ADDRESS:
+ if (attrLen == ethernet::ADDR_LEN) {
+ ethernet::Address addr(reinterpret_cast<const uint8_t*>(RTA_DATA(rta)));
+ interface->setEthernetAddress(addr);
+ }
+ break;
+
+ case IFLA_BROADCAST:
+ if (attrLen == ethernet::ADDR_LEN) {
+ ethernet::Address addr(reinterpret_cast<const uint8_t*>(RTA_DATA(rta)));
+ interface->setEthernetBroadcastAddress(addr);
+ }
+ break;
+
+ case IFLA_IFNAME: {
+ auto attrData = reinterpret_cast<const char*>(RTA_DATA(rta));
+ if (::strnlen(attrData, attrLen) <= attrLen)
+ interface->setName(attrData);
+ break;
+ }
+
+ case IFLA_MTU:
+ if (attrLen == sizeof(uint32_t))
+ interface->setMtu(*(reinterpret_cast<const uint32_t*>(RTA_DATA(rta))));
+ break;
+
+ case IFLA_OPERSTATE:
+ if (attrLen == sizeof(uint8_t))
+ operState = *(reinterpret_cast<const uint8_t*>RTA_DATA(rta));
+ break;
+ }
+
+ rta = RTA_NEXT(rta, rtaTotalLen);
+ }
+
+ updateInterfaceState(*interface, operState);
+
+ if (it == m_interfaces.end()) {
+ NDN_LOG_DEBUG("adding interface " << interface->getName());
+ m_interfaces[interface->getIndex()] = interface;
+ m_nm.onInterfaceAdded(interface);
+ }
+}
+
+void
+NetworkMonitor::Impl::parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa)
+{
+ auto it = m_interfaces.find(ifa->ifa_index);
+ if (it == m_interfaces.end()) {
+ // unknown interface, ignore message
+ NDN_LOG_TRACE("unknown interface index " << ifa->ifa_index);
+ return;
+ }
+ auto interface = it->second;
+ BOOST_ASSERT(interface != nullptr);
+
+ namespace ip = boost::asio::ip;
+
+ NetworkAddress address;
+ address.m_family = ifaFamilyToAddressFamily(ifa->ifa_family);
+ BOOST_ASSERT(address.m_family != AddressFamily::UNSPECIFIED);
+ address.m_prefixLength = ifa->ifa_prefixlen;
+ address.m_flags = ifa->ifa_flags; // will be overridden by IFA_FLAGS below, if the attribute is present
+ address.m_scope = ifaScopeToAddressScope(ifa->ifa_scope);
+
+ const rtattr* rta = reinterpret_cast<const rtattr*>(IFA_RTA(ifa));
+ size_t rtaTotalLen = IFA_PAYLOAD(nlh);
+
+ while (RTA_OK(rta, rtaTotalLen)) {
+ auto attrData = reinterpret_cast<const unsigned char*>(RTA_DATA(rta));
+ size_t attrLen = RTA_PAYLOAD(rta);
+
+ switch (rta->rta_type) {
+ case IFA_LOCAL:
+ if (ifa->ifa_family == AF_INET && attrLen == sizeof(ip::address_v4::bytes_type)) {
+ ip::address_v4::bytes_type bytes;
+ std::copy_n(attrData, bytes.size(), bytes.begin());
+ address.m_ip = ip::address_v4(bytes);
+ }
+ break;
+
+ case IFA_ADDRESS:
+ if (ifa->ifa_family == AF_INET6 && attrLen == sizeof(ip::address_v6::bytes_type)) {
+ ip::address_v6::bytes_type bytes;
+ std::copy_n(attrData, bytes.size(), bytes.begin());
+ address.m_ip = ip::address_v6(bytes);
+ }
+ break;
+
+ case IFA_BROADCAST:
+ if (ifa->ifa_family == AF_INET && attrLen == sizeof(ip::address_v4::bytes_type)) {
+ ip::address_v4::bytes_type bytes;
+ std::copy_n(attrData, bytes.size(), bytes.begin());
+ address.m_broadcast = ip::address_v4(bytes);
+ }
+ break;
+
+#ifdef NDN_CXX_HAVE_IFA_FLAGS
+ case IFA_FLAGS:
+ if (attrLen == sizeof(uint32_t))
+ address.m_flags = *(reinterpret_cast<const uint32_t*>(attrData));
+ break;
+#endif // NDN_CXX_HAVE_IFA_FLAGS
+ }
+
+ rta = RTA_NEXT(rta, rtaTotalLen);
+ }
+
+ if (nlh->nlmsg_type == RTM_NEWADDR)
+ interface->addNetworkAddress(address);
+ else if (nlh->nlmsg_type == RTM_DELADDR)
+ interface->removeNetworkAddress(address);
+}
+
+void
+NetworkMonitor::Impl::parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm)
+{
+ // TODO
+}
+
+void
+NetworkMonitor::Impl::updateInterfaceState(NetworkInterface& interface, uint8_t operState)
+{
+ if (operState == linux_if::OPER_STATE_UP) {
+ interface.setState(InterfaceState::RUNNING);
+ }
+ else if (operState == linux_if::OPER_STATE_DORMANT) {
+ interface.setState(InterfaceState::DORMANT);
+ }
+ else {
+ // fallback to flags
+ auto flags = interface.getFlags();
+ if ((flags & linux_if::FLAG_LOWER_UP) && !(flags & linux_if::FLAG_DORMANT))
+ interface.setState(InterfaceState::RUNNING);
+ else if (flags & IFF_UP)
+ interface.setState(InterfaceState::NO_CARRIER);
+ else
+ interface.setState(InterfaceState::DOWN);
+ }
}
} // namespace util
} // namespace ndn
-
-#endif // NDN_CXX_HAVE_RTNETLINK