util: NetworkMonitor: fine-grained signals on interface/address changes
Change-Id: I60d4cc6d673b920ba81d57502977f0340be0da48
Refs: #3353
diff --git a/src/util/detail/linux-if-constants.cpp b/src/util/detail/linux-if-constants.cpp
new file mode 100644
index 0000000..c50fa50
--- /dev/null
+++ b/src/util/detail/linux-if-constants.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifdef __linux__
+
+#include "linux-if-constants.hpp"
+
+#include <sys/socket.h>
+#include <linux/if.h>
+
+namespace ndn {
+namespace util {
+namespace linux_if {
+
+const uint32_t FLAG_LOWER_UP = IFF_LOWER_UP;
+const uint32_t FLAG_DORMANT = IFF_DORMANT;
+const uint32_t FLAG_ECHO = IFF_ECHO;
+
+const uint8_t OPER_STATE_UNKNOWN = IF_OPER_UNKNOWN;
+const uint8_t OPER_STATE_NOTPRESENT = IF_OPER_NOTPRESENT;
+const uint8_t OPER_STATE_DOWN = IF_OPER_DOWN;
+const uint8_t OPER_STATE_LOWERLAYERDOWN = IF_OPER_LOWERLAYERDOWN;
+const uint8_t OPER_STATE_TESTING = IF_OPER_TESTING;
+const uint8_t OPER_STATE_DORMANT = IF_OPER_DORMANT;
+const uint8_t OPER_STATE_UP = IF_OPER_UP;
+
+} // namespace linux_if
+} // namespace util
+} // namespace ndn
+
+#endif // __linux__
diff --git a/src/util/detail/linux-if-constants.hpp b/src/util/detail/linux-if-constants.hpp
new file mode 100644
index 0000000..29e7115
--- /dev/null
+++ b/src/util/detail/linux-if-constants.hpp
@@ -0,0 +1,58 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_UTIL_LINUX_IF_CONSTANTS_HPP
+#define NDN_UTIL_LINUX_IF_CONSTANTS_HPP
+#ifdef __linux__
+
+#include <cstdint>
+
+namespace ndn {
+namespace util {
+namespace linux_if {
+
+// linux/if.h and net/if.h cannot be (directly or indirectly) included in the
+// same translation unit because they contain duplicate declarations, therefore
+// we have to resort to this workaround when we need to include both linux/if.h
+// and any other headers that pull in net/if.h (e.g. boost/asio.hpp)
+
+// net_device_flags missing from <net/if.h>
+extern const uint32_t FLAG_LOWER_UP;
+extern const uint32_t FLAG_DORMANT;
+extern const uint32_t FLAG_ECHO;
+
+// RFC 2863 operational status
+extern const uint8_t OPER_STATE_UNKNOWN;
+extern const uint8_t OPER_STATE_NOTPRESENT;
+extern const uint8_t OPER_STATE_DOWN;
+extern const uint8_t OPER_STATE_LOWERLAYERDOWN;
+extern const uint8_t OPER_STATE_TESTING;
+extern const uint8_t OPER_STATE_DORMANT;
+extern const uint8_t OPER_STATE_UP;
+
+} // namespace linux_if
+} // namespace util
+} // namespace ndn
+
+#endif // __linux__
+#endif // NDN_UTIL_LINUX_IF_CONSTANTS_HPP
diff --git a/src/util/detail/network-monitor-impl-osx.cpp b/src/util/detail/network-monitor-impl-osx.cpp
index bfa3877..9f28f27 100644
--- a/src/util/detail/network-monitor-impl-osx.cpp
+++ b/src/util/detail/network-monitor-impl-osx.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).
*
@@ -55,6 +55,7 @@
#ifdef NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H
#include "network-monitor-impl-osx.hpp"
+#include "../network-interface.hpp"
namespace ndn {
namespace util {
@@ -93,11 +94,23 @@
static_cast<void*>(this));
}
+shared_ptr<NetworkInterface>
+NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+{
+ return nullptr;
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+ return {};
+}
+
void
NetworkMonitor::Impl::afterNotificationCenterEvent(CFNotificationCenterRef center,
- void *observer,
+ void* observer,
CFStringRef name,
- const void *object,
+ const void* object,
CFDictionaryRef userInfo)
{
static_cast<Impl*>(observer)->m_nm.onNetworkStateChanged();
diff --git a/src/util/detail/network-monitor-impl-osx.hpp b/src/util/detail/network-monitor-impl-osx.hpp
index 4015a78..c36866a 100644
--- a/src/util/detail/network-monitor-impl-osx.hpp
+++ b/src/util/detail/network-monitor-impl-osx.hpp
@@ -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).
*
@@ -22,8 +22,13 @@
#ifndef NDN_UTIL_NETWORK_MONITOR_IMPL_OSX_HPP
#define NDN_UTIL_NETWORK_MONITOR_IMPL_OSX_HPP
+#include "ndn-cxx-config.hpp"
#include "../network-monitor.hpp"
+#ifndef NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H
+#error "This file should not be compiled ..."
+#endif
+
#include "../scheduler.hpp"
#include "../scheduler-scoped-event-id.hpp"
@@ -40,11 +45,17 @@
~Impl();
+ shared_ptr<NetworkInterface>
+ getNetworkInterface(const std::string& ifname) const;
+
+ std::vector<shared_ptr<NetworkInterface>>
+ listNetworkInterfaces() const;
+
static void
afterNotificationCenterEvent(CFNotificationCenterRef center,
- void *observer,
+ void* observer,
CFStringRef name,
- const void *object,
+ const void* object,
CFDictionaryRef userInfo);
private:
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
diff --git a/src/util/detail/network-monitor-impl-rtnl.hpp b/src/util/detail/network-monitor-impl-rtnl.hpp
index 141554d..18996f9 100644
--- a/src/util/detail/network-monitor-impl-rtnl.hpp
+++ b/src/util/detail/network-monitor-impl-rtnl.hpp
@@ -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,34 +17,97 @@
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
#ifndef NDN_UTIL_NETWORK_MONITOR_IMPL_RTNL_HPP
#define NDN_UTIL_NETWORK_MONITOR_IMPL_RTNL_HPP
+#include "ndn-cxx-config.hpp"
#include "../network-monitor.hpp"
+#ifndef NDN_CXX_HAVE_RTNETLINK
+#error "This file should not be compiled ..."
+#endif
+
#include <boost/asio/posix/stream_descriptor.hpp>
+#include <array>
+#include <map>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_addr.h>
+#include <linux/if_link.h>
+
namespace ndn {
namespace util {
-const size_t NETLINK_BUFFER_SIZE = 4096;
-
class NetworkMonitor::Impl
{
public:
+ /** \brief initialize netlink socket and start enumerating interfaces
+ */
Impl(NetworkMonitor& nm, boost::asio::io_service& io);
+ ~Impl();
+
+ shared_ptr<NetworkInterface>
+ getNetworkInterface(const std::string& ifname) const;
+
+ std::vector<shared_ptr<NetworkInterface>>
+ listNetworkInterfaces() const;
+
private:
+ struct RtnlRequest
+ {
+ nlmsghdr nlh;
+ ifinfomsg ifi;
+ rtattr rta __attribute__((aligned(NLMSG_ALIGNTO))); // rtattr has to be aligned
+ uint32_t rtext; // space for IFLA_EXT_MASK
+ };
+
+ bool
+ isEnumerating() const;
+
void
- onReceiveRtNetlink(const boost::system::error_code& error, size_t nBytesReceived);
+ initSocket();
+
+ void
+ sendDumpRequest(uint16_t nlmsgType);
+
+ void
+ asyncRead();
+
+ void
+ handleRead(const boost::system::error_code& error, size_t nBytesReceived,
+ const shared_ptr<boost::asio::posix::stream_descriptor>& socket);
+
+ void
+ parseNetlinkMessage(const nlmsghdr* nlh, size_t len);
+
+ void
+ parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi);
+
+ void
+ parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa);
+
+ void
+ parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm);
+
+ static void
+ updateInterfaceState(NetworkInterface& interface, uint8_t operState);
private:
NetworkMonitor& m_nm;
-
- uint8_t m_buffer[NETLINK_BUFFER_SIZE];
- boost::asio::posix::stream_descriptor m_socket;
+ std::map<int /*ifindex*/, shared_ptr<NetworkInterface>> m_interfaces; ///< interface map
+ std::array<uint8_t, 16384> m_buffer; ///< holds netlink messages received from the kernel
+ shared_ptr<boost::asio::posix::stream_descriptor> m_socket; ///< the netlink socket
+ uint32_t m_pid; ///< our port ID (unicast address for netlink sockets)
+ uint32_t m_sequenceNo; ///< sequence number of the last netlink request sent to the kernel
+ bool m_isEnumeratingLinks; ///< true if a dump of all links is in progress
+ bool m_isEnumeratingAddresses; ///< true if a dump of all addresses is in progress
};
} // namespace util
diff --git a/src/util/network-address.cpp b/src/util/network-address.cpp
new file mode 100644
index 0000000..5744aba
--- /dev/null
+++ b/src/util/network-address.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <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 "network-address.hpp"
+
+namespace ndn {
+namespace util {
+
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope)
+{
+ switch (scope) {
+ case AddressScope::NOWHERE:
+ return os << "nowhere";
+ case AddressScope::HOST:
+ return os << "host";
+ case AddressScope::LINK:
+ return os << "link";
+ case AddressScope::GLOBAL:
+ return os << "global";
+ }
+ return os;
+}
+
+NetworkAddress::NetworkAddress()
+ : m_family(AddressFamily::UNSPECIFIED)
+ , m_flags(0)
+ , m_scope(AddressScope::NOWHERE)
+ , m_prefixLength(0)
+{
+}
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkAddress& addr)
+{
+ return os << addr.getIp() << '/' << static_cast<unsigned int>(addr.getPrefixLength());
+}
+
+} // namespace util
+} // namespace ndn
diff --git a/src/util/network-address.hpp b/src/util/network-address.hpp
new file mode 100644
index 0000000..fd39782
--- /dev/null
+++ b/src/util/network-address.hpp
@@ -0,0 +1,130 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_UTIL_NETWORK_ADDRESS_HPP
+#define NDN_UTIL_NETWORK_ADDRESS_HPP
+
+#include "network-monitor.hpp"
+
+#include <boost/asio/ip/address.hpp>
+
+namespace ndn {
+namespace util {
+
+enum class AddressFamily {
+ UNSPECIFIED,
+ V4,
+ V6,
+};
+
+enum class AddressScope {
+ NOWHERE,
+ HOST,
+ LINK,
+ GLOBAL,
+};
+
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope);
+
+/**
+ * @brief Stores one IP address supported by a network interface.
+ */
+class NetworkAddress
+{
+public: // getters
+ /** @brief Returns the address family
+ */
+ AddressFamily
+ getFamily() const
+ {
+ return m_family;
+ }
+
+ /** @brief Returns the IP address (v4 or v6)
+ */
+ boost::asio::ip::address
+ getIp() const
+ {
+ return m_ip;
+ }
+
+ /** @brief Returns the IP broadcast address
+ */
+ boost::asio::ip::address
+ getBroadcast() const
+ {
+ return m_broadcast;
+ }
+
+ /** @brief Returns a bitset of platform-specific flags enabled on the address
+ */
+ uint32_t
+ getFlags() const
+ {
+ return m_flags;
+ }
+
+ /** @brief Returns the address scope
+ */
+ AddressScope
+ getScope() const
+ {
+ return m_scope;
+ }
+
+ /** @brief Returns the prefix length
+ */
+ uint8_t
+ getPrefixLength() const
+ {
+ return m_prefixLength;
+ }
+
+ friend bool
+ operator<(const NetworkAddress& a, const NetworkAddress& b)
+ {
+ return a.m_ip < b.m_ip;
+ }
+
+private: // constructor
+ NetworkAddress();
+
+private:
+ friend class NetworkMonitor::Impl;
+
+ AddressFamily m_family;
+ boost::asio::ip::address m_ip;
+ boost::asio::ip::address m_broadcast;
+ uint32_t m_flags; // IFA_F_* in if_addr.h
+ AddressScope m_scope;
+ uint8_t m_prefixLength;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkAddress& address);
+
+} // namespace util
+} // namespace ndn
+
+#endif // NDN_UTIL_NETWORK_ADDRESS_HPP
diff --git a/src/util/network-interface.cpp b/src/util/network-interface.cpp
new file mode 100644
index 0000000..1b4dfc5
--- /dev/null
+++ b/src/util/network-interface.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <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 "network-interface.hpp"
+#include "detail/linux-if-constants.hpp"
+#include "logger.hpp"
+#include "string-helper.hpp"
+
+#include <net/if.h>
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+namespace ndn {
+namespace util {
+
+NetworkInterface::NetworkInterface()
+ : m_index(0)
+ , m_type(InterfaceType::UNKNOWN)
+ , m_flags(0)
+ , m_state(InterfaceState::UNKNOWN)
+ , m_mtu(0)
+{
+}
+
+bool
+NetworkInterface::addNetworkAddress(const NetworkAddress& address)
+{
+ if (!address.getIp().is_unspecified()) {
+ // need to erase the existing address before inserting
+ // because the address flags may have changed
+ bool isNew = m_netAddresses.erase(address) == 0;
+ m_netAddresses.insert(address);
+ if (isNew) {
+ NDN_LOG_DEBUG("added address " << address << " to " << m_name);
+ onAddressAdded(address);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+NetworkInterface::removeNetworkAddress(const NetworkAddress& address)
+{
+ if (m_netAddresses.erase(address) > 0) {
+ NDN_LOG_DEBUG("removed address " << address << " from " << m_name);
+ onAddressRemoved(address);
+ return true;
+ }
+ return false;
+}
+
+void
+NetworkInterface::setIndex(int index)
+{
+ m_index = index;
+}
+
+void
+NetworkInterface::setName(const std::string& name)
+{
+ BOOST_ASSERT(!name.empty());
+ m_name = name;
+}
+
+void
+NetworkInterface::setType(InterfaceType type)
+{
+ m_type = type;
+}
+
+void
+NetworkInterface::setFlags(uint32_t flags)
+{
+ m_flags = flags;
+}
+
+void
+NetworkInterface::setState(InterfaceState state)
+{
+ if (m_state != state) {
+ std::swap(m_state, state);
+ onStateChanged(state, m_state);
+ }
+}
+
+void
+NetworkInterface::setMtu(uint32_t mtu)
+{
+ if (m_mtu != mtu) {
+ std::swap(m_mtu, mtu);
+ onMtuChanged(mtu, m_mtu);
+ }
+}
+
+void
+NetworkInterface::setEthernetAddress(const ethernet::Address& address)
+{
+ m_etherAddress = address;
+}
+
+void
+NetworkInterface::setEthernetBroadcastAddress(const ethernet::Address& address)
+{
+ m_etherBrdAddress = address;
+}
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceType type)
+{
+ switch (type) {
+ case InterfaceType::UNKNOWN:
+ return os << "unknown";
+ case InterfaceType::LOOPBACK:
+ return os << "loopback";
+ case InterfaceType::ETHERNET:
+ return os << "ether";
+ }
+ return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceState state)
+{
+ switch (state) {
+ case InterfaceState::UNKNOWN:
+ return os << "unknown";
+ case InterfaceState::DOWN:
+ return os << "down";
+ case InterfaceState::NO_CARRIER:
+ return os << "no-carrier";
+ case InterfaceState::DORMANT:
+ return os << "dormant";
+ case InterfaceState::RUNNING:
+ return os << "running";
+ }
+ return os;
+}
+
+static void
+printFlag(std::ostream& os, uint32_t& flags, uint32_t flagVal, const char* flagStr)
+{
+ if (flags & flagVal) {
+ flags &= ~flagVal;
+ os << flagStr << (flags ? "," : "");
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkInterface& netif)
+{
+ os << netif.getIndex() << ": " << netif.getName() << ": ";
+
+ auto flags = netif.getFlags();
+ os << "<";
+#define PRINT_IFF(flag) printFlag(os, flags, IFF_##flag, #flag)
+ PRINT_IFF(UP);
+ PRINT_IFF(DEBUG);
+ PRINT_IFF(LOOPBACK);
+ PRINT_IFF(POINTOPOINT);
+ PRINT_IFF(BROADCAST);
+ PRINT_IFF(MULTICAST);
+ PRINT_IFF(NOTRAILERS);
+ PRINT_IFF(RUNNING);
+ PRINT_IFF(NOARP);
+ PRINT_IFF(PROMISC);
+ PRINT_IFF(ALLMULTI);
+#if defined(__linux__)
+ PRINT_IFF(MASTER);
+ PRINT_IFF(SLAVE);
+ PRINT_IFF(PORTSEL);
+ PRINT_IFF(AUTOMEDIA);
+ PRINT_IFF(DYNAMIC);
+#elif defined(__APPLE__) || defined(__FreeBSD__)
+ PRINT_IFF(OACTIVE);
+ PRINT_IFF(SIMPLEX);
+ PRINT_IFF(ALTPHYS);
+#endif
+#undef PRINT_IFF
+#if defined(__linux__)
+#define PRINT_IF_FLAG(flag) printFlag(os, flags, linux_if::FLAG_##flag, #flag)
+ PRINT_IF_FLAG(LOWER_UP);
+ PRINT_IF_FLAG(DORMANT);
+ PRINT_IF_FLAG(ECHO);
+#undef PRINT_IF_FLAG
+#endif
+ if (flags) {
+ // print unknown flags in hex
+ os << AsHex{flags};
+ }
+ os << ">";
+
+ os << " state " << netif.getState() << " mtu " << netif.getMtu() << "\n"
+ << " link/" << netif.getType() << " " << netif.getEthernetAddress()
+ << " brd " << netif.getEthernetBroadcastAddress() << "\n";
+
+ for (const auto& addr : netif.getNetworkAddresses()) {
+ os << " " << (addr.getFamily() == AddressFamily::V4 ? "inet " : "inet6 ") << addr;
+ if (netif.canBroadcast() && !addr.getBroadcast().is_unspecified())
+ os << " brd " << addr.getBroadcast();
+ os << " scope " << addr.getScope() << "\n";
+ }
+
+ return os;
+}
+
+} // namespace util
+} // namespace ndn
diff --git a/src/util/network-interface.hpp b/src/util/network-interface.hpp
new file mode 100644
index 0000000..4b53a1d
--- /dev/null
+++ b/src/util/network-interface.hpp
@@ -0,0 +1,256 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_UTIL_NETWORK_INTERFACE_HPP
+#define NDN_UTIL_NETWORK_INTERFACE_HPP
+
+#include "ethernet.hpp"
+#include "network-address.hpp"
+#include "network-monitor.hpp"
+#include "signal.hpp"
+
+#include <set>
+
+namespace ndn {
+namespace util {
+
+/** @brief Indicates the hardware type of a network interface
+ */
+enum class InterfaceType {
+ UNKNOWN,
+ LOOPBACK,
+ ETHERNET,
+ // we do not support anything else for now
+};
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceType type);
+
+/** @brief Indicates the state of a network interface
+ */
+enum class InterfaceState {
+ UNKNOWN, ///< interface is in an unknown state
+ DOWN, ///< interface is administratively down
+ NO_CARRIER, ///< interface is administratively up but has no carrier
+ DORMANT, ///< interface has a carrier but it cannot send or receive normal user traffic yet
+ RUNNING, ///< interface can be used to send and receive packets
+};
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceState state);
+
+/**
+ * @brief Represents one network interface attached to the host.
+ *
+ * Each network interface has a unique index, a name, and a set of flags indicating its
+ * capabilities and current state. It may contain one hardware (Ethernet) address, and
+ * zero or more network-layer (IP) addresses. Specific signals are emitted when the
+ * interface data change.
+ */
+class NetworkInterface
+{
+public: // signals
+ /** @brief Fires when interface state changes
+ */
+ Signal<NetworkInterface, InterfaceState /*old*/, InterfaceState /*new*/> onStateChanged;
+
+ /** @brief Fires when interface mtu changes
+ */
+ Signal<NetworkInterface, uint32_t /*old*/, uint32_t /*new*/> onMtuChanged;
+
+ /** @brief Fires when a network-layer address is added to the interface
+ */
+ Signal<NetworkInterface, NetworkAddress> onAddressAdded;
+
+ /** @brief Fires when a network-layer address is removed from the interface
+ */
+ Signal<NetworkInterface, NetworkAddress> onAddressRemoved;
+
+public: // getters
+ /** @brief Returns an opaque ID that uniquely identifies the interface on the system
+ */
+ int
+ getIndex() const
+ {
+ return m_index;
+ }
+
+ /** @brief Returns the name of the interface, unique on the system
+ */
+ std::string
+ getName() const
+ {
+ return m_name;
+ }
+
+ /** @brief Returns the hardware type of the interface
+ */
+ InterfaceType
+ getType() const
+ {
+ return m_type;
+ }
+
+ /** @brief Returns a bitset of platform-specific flags enabled on the interface
+ */
+ uint32_t
+ getFlags() const
+ {
+ return m_flags;
+ }
+
+ /** @brief Returns the current state of the interface
+ */
+ InterfaceState
+ getState() const
+ {
+ return m_state;
+ }
+
+ /** @brief Returns the MTU (maximum transmission unit) of the interface
+ */
+ uint32_t
+ getMtu() const
+ {
+ return m_mtu;
+ }
+
+ /** @brief Returns the link-layer (Ethernet) address of the interface
+ */
+ ethernet::Address
+ getEthernetAddress() const
+ {
+ return m_etherAddress;
+ }
+
+ /** @brief Returns the link-layer (Ethernet) broadcast address of the interface
+ */
+ ethernet::Address
+ getEthernetBroadcastAddress() const
+ {
+ return m_etherBrdAddress;
+ }
+
+ /** @brief Returns a list of all network-layer addresses present on the interface
+ */
+ const std::set<NetworkAddress>&
+ getNetworkAddresses() const
+ {
+ return m_netAddresses;
+ }
+
+ /** @brief Returns true if the interface is a loopback interface
+ */
+ bool
+ isLoopback() const
+ {
+ return (m_flags & IFF_LOOPBACK) != 0;
+ }
+
+ /** @brief Returns true if the interface is a point-to-point interface
+ */
+ bool
+ isPointToPoint() const
+ {
+ return (m_flags & IFF_POINTOPOINT) != 0;
+ }
+
+ /** @brief Returns true if the interface supports broadcast communication
+ */
+ bool
+ canBroadcast() const
+ {
+ return (m_flags & IFF_BROADCAST) != 0;
+ }
+
+ /** @brief Returns true if the interface supports multicast communication
+ */
+ bool
+ canMulticast() const
+ {
+ return (m_flags & IFF_MULTICAST) != 0;
+ }
+
+ /** @brief Returns true if the interface is administratively up
+ */
+ bool
+ isUp() const
+ {
+ return (m_flags & IFF_UP) != 0;
+ }
+
+private: // constructor
+ NetworkInterface();
+
+private: // modifiers
+ bool
+ addNetworkAddress(const NetworkAddress& address);
+
+ bool
+ removeNetworkAddress(const NetworkAddress& address);
+
+ void
+ setIndex(int index);
+
+ void
+ setName(const std::string& name);
+
+ void
+ setType(InterfaceType type);
+
+ void
+ setFlags(uint32_t flags);
+
+ void
+ setState(InterfaceState state);
+
+ void
+ setMtu(uint32_t mtu);
+
+ void
+ setEthernetAddress(const ethernet::Address& address);
+
+ void
+ setEthernetBroadcastAddress(const ethernet::Address& address);
+
+private:
+ friend class NetworkMonitor::Impl;
+
+ int m_index;
+ std::string m_name;
+ InterfaceType m_type;
+ uint32_t m_flags; // IFF_* in <net/if.h>
+ InterfaceState m_state;
+ uint32_t m_mtu;
+ ethernet::Address m_etherAddress;
+ ethernet::Address m_etherBrdAddress;
+ std::set<NetworkAddress> m_netAddresses;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkInterface& interface);
+
+} // namespace util
+} // namespace ndn
+
+#endif // NDN_UTIL_NETWORK_INTERFACE_HPP
diff --git a/src/util/network-monitor.cpp b/src/util/network-monitor.cpp
index a12e373..ccb5418 100644
--- a/src/util/network-monitor.cpp
+++ b/src/util/network-monitor.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,10 +17,12 @@
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
#include "network-monitor.hpp"
-
#include "ndn-cxx-config.hpp"
#if defined(NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H)
@@ -50,11 +52,23 @@
namespace util {
NetworkMonitor::NetworkMonitor(boost::asio::io_service& io)
- : m_impl(new Impl(*this, io))
+ : m_impl(make_unique<Impl>(*this, io))
{
}
NetworkMonitor::~NetworkMonitor() = default;
+shared_ptr<NetworkInterface>
+NetworkMonitor::getNetworkInterface(const std::string& ifname) const
+{
+ return m_impl->getNetworkInterface(ifname);
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::listNetworkInterfaces() const
+{
+ return m_impl->listNetworkInterfaces();
+}
+
} // namespace util
} // namespace ndn
diff --git a/src/util/network-monitor.hpp b/src/util/network-monitor.hpp
index dae4941..b48d5e9 100644
--- a/src/util/network-monitor.hpp
+++ b/src/util/network-monitor.hpp
@@ -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,6 +17,9 @@
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
#ifndef NDN_UTIL_NETWORK_MONITOR_HPP
@@ -24,6 +27,8 @@
#include "signal.hpp"
+#include <vector>
+
// forward declaration
namespace boost {
namespace asio {
@@ -34,23 +39,23 @@
namespace ndn {
namespace util {
+class NetworkInterface;
+
/**
- * @brief Network state change monitor
+ * @brief Network interfaces monitor
*
- * When network change is detected, onNetworkStateChanged signal will be fired.
- * Monitoring is run for the lifetime of the NetworkMonitor instance.
+ * Maintains an up-to-date view of every system network interface and notifies when an interface
+ * is added or removed.
*
* @note Implementation of this class is platform dependent and not all supported platforms
* are supported:
- * - OS X: CFNotificationCenterAddObserver
+ * - OS X: CFNotificationCenterAddObserver (incomplete)
* - Linux: rtnetlink notifications
*
- * Network state change detection is not guaranteed to be precise and (zero or more)
- * notifications are expected to be fired for the following events:
- * - any network interface going up or down
- * - IPv4 or IPv6 address changes on any of the interfaces
+ * @todo macOS implementation needs to be updated to emit the new signals and keep track of
+ * interfaces (links) and addresses
*/
-class NetworkMonitor : boost::noncopyable
+class NetworkMonitor : noncopyable
{
public:
class Error : public std::runtime_error
@@ -63,27 +68,50 @@
}
};
+ class Impl;
+
/**
- * @brief Construct instance and start monitoring for network state changes
+ * @brief Construct instance, request enumeration of all network interfaces, and start
+ * monitoring for network state changes
+ *
* @param io io_service thread that will dispatch events
* @throw Error when network monitoring is not supported or there is an error starting monitoring
*/
explicit
NetworkMonitor(boost::asio::io_service& io);
- /**
- * @brief Terminate network state monitoring
- */
~NetworkMonitor();
+ shared_ptr<NetworkInterface>
+ getNetworkInterface(const std::string& ifname) const;
+
+ std::vector<shared_ptr<NetworkInterface>>
+ listNetworkInterfaces() const;
+
+public: // signals
+ /** @brief Fires when network interfaces enumeration is complete
+ */
+ Signal<NetworkMonitor> onEnumerationCompleted;
+
+ /** @brief Fires when a new interface is added
+ */
+ Signal<NetworkMonitor, shared_ptr<NetworkInterface>> onInterfaceAdded;
+
+ /**
+ * @brief Fires when an interface is removed
+ * @note The NetworkInterface object is no longer present in the network
+ * interfaces map when the signal is emitted
+ */
+ Signal<NetworkMonitor, shared_ptr<NetworkInterface>> onInterfaceRemoved;
+
+ // only for backward compatibility
Signal<NetworkMonitor> onNetworkStateChanged;
private:
- class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace util
-} // namespace autoconfig
+} // namespace ndn
#endif // NDN_UTIL_NETWORK_MONITOR_HPP
diff --git a/tests/integrated/network-monitor.README.md b/tests/integrated/network-monitor.README.md
new file mode 100644
index 0000000..b99f26c
--- /dev/null
+++ b/tests/integrated/network-monitor.README.md
@@ -0,0 +1,42 @@
+## NetworkMonitor test
+
+These instructions are only for Linux.
+
+Run the network-monitor integrated test binary, e.g.:
+```
+./build/tests/integrated/network-monitor
+```
+Note: sudo is not required.
+
+You should see an `onInterfaceAdded` message for each ethernet and loopback
+network interface present on the system, followed by an `onAddressAdded`
+message for each IPv4/IPv6 address on each interface. Finally,
+`onEnumerationCompleted` is printed, along with a summary of all interfaces
+discovered thus far.
+
+[The following commands assume eth0 is the name of an ethernet interface
+on the machine. If your interfaces are named differently, replace eth0
+with the name of any ethernet interface that you have available.]
+
+Command | Expected output
+--------|----------------
+sudo ip link add link eth0 name nmtest0 type vlan id 42 | `nmtest0: onInterfaceAdded`
+sudo ip link set dev nmtest0 mtu 1342 | `nmtest0: onMtuChanged <old_mtu> -> 1342` (`old_mtu` is most likely 1500)
+sudo ip link set dev nmtest0 up | `nmtest0: onStateChanged down -> <new_state>` (`new_state` is one of: running, dormant, no-carrier)
+sudo ip address add 198.51.100.100/24 dev nmtest0 | `nmtest0: onAddressAdded 198.51.100.100/24`
+sudo ip address del 198.51.100.100/24 dev nmtest0 | `nmtest0: onAddressRemoved 198.51.100.100/24`
+sudo ip address add 2001:db8::1/80 dev nmtest0 | `nmtest0: onAddressAdded 2001:db8::1/80`
+sudo ip address del 2001:db8::1/80 dev nmtest0 | `nmtest0: onAddressRemoved 2001:db8::1/80`
+sudo ip link delete dev nmtest0 | `nmtest0: onInterfaceRemoved`
+
+If you unplug the ethernet cable from your network card, you should see:
+```
+eth0: onStateChanged running -> no-carrier
+nmtest0: onStateChanged running -> no-carrier
+```
+
+Plugging the cable back in should produce the following messages:
+```
+eth0: onStateChanged no-carrier -> running
+nmtest0: onStateChanged no-carrier -> running
+```
diff --git a/tests/integrated/network-monitor.cpp b/tests/integrated/network-monitor.cpp
index cbc0e8a..3b4a516 100644
--- a/tests/integrated/network-monitor.cpp
+++ b/tests/integrated/network-monitor.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).
*
@@ -25,6 +25,8 @@
#include "util/network-monitor.hpp"
+#include "util/network-address.hpp"
+#include "util/network-interface.hpp"
#include "util/time.hpp"
#include "boost-test.hpp"
@@ -34,25 +36,66 @@
namespace ndn {
namespace util {
+namespace tests {
-BOOST_AUTO_TEST_SUITE(UtilNetworkMonitor)
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestNetworkMonitor)
-BOOST_AUTO_TEST_CASE(Basic)
+static std::ostream&
+logEvent(const shared_ptr<NetworkInterface>& ni = nullptr, std::ostream& os = std::cout)
+{
+ os << '[' << time::toIsoString(time::system_clock::now()) << "] ";
+ if (ni != nullptr)
+ os << ni->getName() << ": ";
+ return os;
+}
+
+BOOST_AUTO_TEST_CASE(Signals)
{
boost::asio::io_service io;
- BOOST_REQUIRE_NO_THROW((NetworkMonitor(io)));
-
NetworkMonitor monitor(io);
monitor.onNetworkStateChanged.connect([] {
- std::cout << time::toString(time::system_clock::now())
- << "\tReceived network state change event" << std::endl;
+ logEvent() << "onNetworkStateChanged" << std::endl;
+ });
+
+ monitor.onEnumerationCompleted.connect([&monitor] {
+ logEvent() << "onEnumerationCompleted" << std::endl;
+ for (const auto& ni : monitor.listNetworkInterfaces()) {
+ std::cout << *ni;
+ }
+ });
+
+ monitor.onInterfaceAdded.connect([] (const shared_ptr<NetworkInterface>& ni) {
+ logEvent(ni) << "onInterfaceAdded\n" << *ni;
+
+ ni->onAddressAdded.connect([ni] (const NetworkAddress& address) {
+ logEvent(ni) << "onAddressAdded " << address << std::endl;
});
+ ni->onAddressRemoved.connect([ni] (const NetworkAddress& address) {
+ logEvent(ni) << "onAddressRemoved " << address << std::endl;
+ });
+
+ ni->onStateChanged.connect([ni] (InterfaceState oldState, InterfaceState newState) {
+ logEvent(ni) << "onStateChanged " << oldState << " -> " << newState << std::endl;
+ });
+
+ ni->onMtuChanged.connect([ni] (uint32_t oldMtu, uint32_t newMtu) {
+ logEvent(ni) << "onMtuChanged " << oldMtu << " -> " << newMtu << std::endl;
+ });
+ }); // monitor.onInterfaceAdded.connect
+
+ monitor.onInterfaceRemoved.connect([] (const shared_ptr<NetworkInterface>& ni) {
+ logEvent(ni) << "onInterfaceRemoved" << std::endl;
+ });
+
io.run();
}
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END() // TestNetworkMonitor
+BOOST_AUTO_TEST_SUITE_END() // Util
+} // namespace tests
} // namespace util
} // namespace ndn
diff --git a/tests/unit-tests/util/network-monitor.t.cpp b/tests/unit-tests/util/network-monitor.t.cpp
new file mode 100644
index 0000000..bac8750
--- /dev/null
+++ b/tests/unit-tests/util/network-monitor.t.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/network-monitor.hpp"
+
+#include "boost-test.hpp"
+#include <boost/asio/io_service.hpp>
+
+namespace ndn {
+namespace util {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestNetworkMonitor)
+
+BOOST_AUTO_TEST_CASE(DestructWithoutRun)
+{
+ boost::asio::io_service io;
+ auto nm = make_unique<NetworkMonitor>(io);
+ nm.reset();
+ BOOST_CHECK(true); // if we got this far, the test passed
+}
+
+BOOST_AUTO_TEST_CASE(DestructWhileEnumerating)
+{
+#ifdef __linux__
+ boost::asio::io_service io;
+ auto nm = make_unique<NetworkMonitor>(io);
+
+ nm->onInterfaceAdded.connect([&] (const shared_ptr<NetworkInterface>&) {
+ io.post([&] { nm.reset(); });
+ });
+ nm->onEnumerationCompleted.connect([&] {
+ BOOST_CHECK_EQUAL(nm->listNetworkInterfaces().size(), 0);
+ // make sure the test case terminates even if we have zero interfaces
+ io.post([&] { nm.reset(); });
+ });
+
+ io.run();
+#endif
+ BOOST_CHECK(true); // if we got this far, the test passed
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNetworkMonitor
+BOOST_AUTO_TEST_SUITE_END() // Util
+
+} // namespace tests
+} // namespace util
+} // namespace ndn
diff --git a/wscript b/wscript
index d410b4d..1f8d497 100644
--- a/wscript
+++ b/wscript
@@ -77,24 +77,19 @@
conf.find_program('sh', var='SH', mandatory=True)
- conf.check_cxx(lib='pthread', uselib_store='PTHREAD', define_name='HAVE_PTHREAD',
- mandatory=False)
+ conf.check_cxx(lib='pthread', uselib_store='PTHREAD', define_name='HAVE_PTHREAD', mandatory=False)
conf.check_cxx(lib='rt', uselib_store='RT', define_name='HAVE_RT', mandatory=False)
- conf.check_cxx(msg='Checking for function getpass', mandatory=False,
- define_name='HAVE_GETPASS', fragment='''
-#include <unistd.h>
-int
-main(int, char**)
-{
- char* pass = getpass("test prompt");
- (void)(pass);
- return 0;
-}
-''')
+ conf.check_cxx(function_name='getpass', header_name='unistd.h', mandatory=False)
- conf.check_cxx(msg='Checking for rtnetlink', mandatory=False,
- define_name='HAVE_RTNETLINK',
- header_name=['netinet/in.h', 'linux/netlink.h', 'linux/rtnetlink.h', 'net/if.h'])
+ if conf.check_cxx(msg='Checking for rtnetlink', define_name='HAVE_RTNETLINK', mandatory=False,
+ header_name=['linux/if_addr.h', 'linux/if_link.h',
+ 'linux/netlink.h', 'linux/rtnetlink.h']):
+ conf.env['HAVE_RTNETLINK'] = True
+ conf.check_cxx(msg='Checking for IFA_FLAGS', define_name='HAVE_IFA_FLAGS', mandatory=False,
+ fragment='''
+ #include <linux/if_addr.h>
+ int main() { return IFA_FLAGS; }
+ ''')
conf.check_osx_security(mandatory=False)
@@ -167,6 +162,7 @@
name="ndn-cxx",
source=bld.path.ant_glob('src/**/*.cpp',
excl=['src/security/**/*-osx.cpp',
+ 'src/**/*-rtnl.cpp',
'src/**/*-sqlite3.cpp']),
headers='src/common-pch.hpp',
use='version BOOST CRYPTOPP OPENSSL SQLITE3 RT PTHREAD',
@@ -174,6 +170,9 @@
export_includes="src",
install_path='${LIBDIR}')
+ if bld.env['HAVE_RTNETLINK']:
+ libndn_cxx['source'] += bld.path.ant_glob('src/**/*-rtnl.cpp')
+
if bld.env['HAVE_OSX_SECURITY']:
libndn_cxx['source'] += bld.path.ant_glob('src/security/**/*-osx.cpp')
libndn_cxx['mac_app'] = True