net: refactor netlink socket handling
In preparation for generic netlink support
Change-Id: I69d93b69c08d4c3f7fd0dabbf3e51dbd49f9ef06
Refs: #4020
diff --git a/src/net/detail/netlink-util.hpp b/src/net/detail/netlink-message.hpp
similarity index 90%
rename from src/net/detail/netlink-util.hpp
rename to src/net/detail/netlink-message.hpp
index 15f54f3..3908d4e 100644
--- a/src/net/detail/netlink-util.hpp
+++ b/src/net/detail/netlink-message.hpp
@@ -21,8 +21,8 @@
* @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
-#ifndef NDN_NET_NETLINK_UTIL_HPP
-#define NDN_NET_NETLINK_UTIL_HPP
+#ifndef NDN_NET_NETLINK_MESSAGE_HPP
+#define NDN_NET_NETLINK_MESSAGE_HPP
#include "../../common.hpp"
#include "../ethernet.hpp"
@@ -307,33 +307,7 @@
std::map<uint16_t, const T*> m_attrs;
};
-inline const char*
-nlmsgTypeToString(uint16_t type) noexcept
-{
-#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
-#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
- switch (type) {
- NLMSG_STRINGIFY(NOOP);
- NLMSG_STRINGIFY(ERROR);
- NLMSG_STRINGIFY(DONE);
- NLMSG_STRINGIFY(OVERRUN);
- RTM_STRINGIFY(NEWLINK);
- RTM_STRINGIFY(DELLINK);
- RTM_STRINGIFY(GETLINK);
- RTM_STRINGIFY(NEWADDR);
- RTM_STRINGIFY(DELADDR);
- RTM_STRINGIFY(GETADDR);
- RTM_STRINGIFY(NEWROUTE);
- RTM_STRINGIFY(DELROUTE);
- RTM_STRINGIFY(GETROUTE);
- default:
- return "";
- }
-#undef NLMSG_STRINGIFY
-#undef RTM_STRINGIFY
-}
-
} // namespace net
} // namespace ndn
-#endif // NDN_NET_NETLINK_UTIL_HPP
+#endif // NDN_NET_NETLINK_MESSAGE_HPP
diff --git a/src/net/detail/netlink-socket.cpp b/src/net/detail/netlink-socket.cpp
new file mode 100644
index 0000000..b564afe
--- /dev/null
+++ b/src/net/detail/netlink-socket.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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 "netlink-socket.hpp"
+#include "netlink-message.hpp"
+#include "../../util/logger.hpp"
+#include "../../util/time.hpp"
+
+#include <cerrno>
+#include <cstring>
+#include <sys/socket.h>
+
+#include <boost/asio/write.hpp>
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+#ifndef RTEXT_FILTER_SKIP_STATS
+#define RTEXT_FILTER_SKIP_STATS (1 << 3)
+#endif
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+namespace ndn {
+namespace net {
+
+NetlinkSocket::NetlinkSocket(boost::asio::io_service& io)
+ : m_sock(make_shared<boost::asio::posix::stream_descriptor>(io))
+ , m_pid(0)
+ , m_seqNum(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
+ , m_buffer(16 * 1024) // 16 KiB
+{
+}
+
+NetlinkSocket::~NetlinkSocket()
+{
+ boost::system::error_code ec;
+ m_sock->close(ec);
+}
+
+void
+NetlinkSocket::open(int protocol)
+{
+ int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+ if (fd < 0) {
+ BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
+ }
+ m_sock->assign(fd);
+
+ // increase socket receive buffer to 1MB to avoid losing messages
+ const int bufsize = 1 * 1024 * 1024;
+ if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
+ // not a fatal error
+ NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
+ }
+
+ // enable control messages for received packets to get the destination group
+ const int one = 1;
+ if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
+ BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
+ }
+
+ sockaddr_nl addr{};
+ addr.nl_family = AF_NETLINK;
+ if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
+ BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + 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("Cannot obtain netlink socket address ("s + 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);
+
+#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
+ // enable extended ACK reporting
+ if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
+ // not a fatal error
+ NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
+ }
+#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
+}
+
+void
+NetlinkSocket::joinGroup(int group)
+{
+ if (::setsockopt(m_sock->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+ &group, sizeof(group)) < 0) {
+ BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
+ " (" + std::strerror(errno) + ")"));
+ }
+}
+
+void
+NetlinkSocket::startAsyncReceive(MessageCallback cb)
+{
+ BOOST_ASSERT(cb != nullptr);
+ BOOST_ASSERT(m_onMessage == nullptr);
+
+ m_onMessage = std::move(cb);
+ asyncWait();
+}
+
+static const char*
+nlmsgTypeToString(uint16_t type)
+{
+#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
+#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
+ switch (type) {
+ NLMSG_STRINGIFY(NOOP);
+ NLMSG_STRINGIFY(ERROR);
+ NLMSG_STRINGIFY(DONE);
+ NLMSG_STRINGIFY(OVERRUN);
+ RTM_STRINGIFY(NEWLINK);
+ RTM_STRINGIFY(DELLINK);
+ RTM_STRINGIFY(GETLINK);
+ RTM_STRINGIFY(NEWADDR);
+ RTM_STRINGIFY(DELADDR);
+ RTM_STRINGIFY(GETADDR);
+ RTM_STRINGIFY(NEWROUTE);
+ RTM_STRINGIFY(DELROUTE);
+ RTM_STRINGIFY(GETROUTE);
+ default:
+ return "";
+ }
+#undef NLMSG_STRINGIFY
+#undef RTM_STRINGIFY
+}
+
+void
+NetlinkSocket::asyncWait()
+{
+ m_sock->async_read_some(boost::asio::null_buffers(),
+ // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending
+ [this, sock = m_sock] (const boost::system::error_code& ec, size_t) {
+ if (!sock->is_open() || ec == boost::asio::error::operation_aborted) {
+ // socket was closed, ignore the error
+ NDN_LOG_DEBUG("netlink socket closed or operation aborted");
+ }
+ else if (ec) {
+ NDN_LOG_ERROR("read failed: " << ec.message());
+ BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")"));
+ }
+ else {
+ receiveAndValidate();
+ asyncWait();
+ }
+ });
+}
+
+void
+NetlinkSocket::receiveAndValidate()
+{
+ sockaddr_nl sender{};
+ iovec iov{};
+ iov.iov_base = m_buffer.data();
+ iov.iov_len = m_buffer.size();
+ uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))];
+ msghdr msg{};
+ msg.msg_name = &sender;
+ msg.msg_namelen = sizeof(sender);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsgBuffer;
+ msg.msg_controllen = sizeof(cmsgBuffer);
+
+ ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0);
+ if (nBytesRead < 0) {
+ std::string errorString = std::strerror(errno);
+ if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+ // not a fatal error
+ NDN_LOG_DEBUG("recvmsg failed: " << errorString);
+ return;
+ }
+ NDN_LOG_ERROR("recvmsg failed: " << errorString);
+ BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
+ }
+
+ NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
+
+ if (msg.msg_flags & MSG_TRUNC) {
+ NDN_LOG_ERROR("truncated message");
+ BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
+ // TODO: grow the buffer and start over
+ }
+
+ if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) {
+ NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
+ return;
+ }
+
+ uint32_t nlGroup = 0;
+ for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_NETLINK &&
+ cmsg->cmsg_type == NETLINK_PKTINFO &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
+ const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
+ nlGroup = pktinfo->group;
+ }
+ }
+
+ NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
+ for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
+ NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
+ "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
+ " len=" << nlmsg->nlmsg_len <<
+ " seq=" << nlmsg->nlmsg_seq <<
+ " pid=" << nlmsg->nlmsg_pid <<
+ " group=" << nlGroup);
+
+ if (nlGroup == 0 && // not a multicast notification
+ (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_seqNum)) { // not for us
+ NDN_LOG_TRACE("seq/pid mismatch, ignoring");
+ continue;
+ }
+
+ if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
+ NDN_LOG_ERROR("dump is inconsistent");
+ BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
+ // TODO: discard the rest of the message and retry the dump
+ }
+
+ m_onMessage(nlmsg);
+
+ if (nlmsg->nlmsg_type == NLMSG_DONE) {
+ break;
+ }
+ }
+}
+
+RtnlSocket::RtnlSocket(boost::asio::io_service& io)
+ : NetlinkSocket(io)
+{
+}
+
+void
+RtnlSocket::open()
+{
+ NDN_LOG_TRACE("opening rtnetlink socket");
+ NetlinkSocket::open(NETLINK_ROUTE);
+}
+
+void
+RtnlSocket::sendDumpRequest(uint16_t nlmsgType)
+{
+ struct RtnlRequest
+ {
+ nlmsghdr nlh;
+ alignas(NLMSG_ALIGNTO) ifinfomsg ifi;
+ alignas(NLMSG_ALIGNTO) rtattr rta;
+ alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK
+ };
+
+ auto request = make_shared<RtnlRequest>();
+ request->nlh.nlmsg_type = nlmsgType;
+ request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ request->nlh.nlmsg_seq = ++m_seqNum;
+ 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 = RTEXT_FILTER_SKIP_STATS;
+ request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len;
+
+ boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len),
+ // capture 'request' to prevent its premature deallocation
+ [request] (const boost::system::error_code& ec, size_t) {
+ if (!ec) {
+ auto type = request->nlh.nlmsg_type;
+ NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
+ << " seq=" << request->nlh.nlmsg_seq);
+ }
+ else if (ec != boost::asio::error::operation_aborted) {
+ NDN_LOG_ERROR("write failed: " << ec.message());
+ BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
+ }
+ });
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/detail/netlink-socket.hpp b/src/net/detail/netlink-socket.hpp
new file mode 100644
index 0000000..e7043fc
--- /dev/null
+++ b/src/net/detail/netlink-socket.hpp
@@ -0,0 +1,98 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 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_NET_NETLINK_SOCKET_HPP
+#define NDN_NET_NETLINK_SOCKET_HPP
+
+#include "../../common.hpp"
+#include "../network-monitor.hpp"
+
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <vector>
+
+#ifndef NDN_CXX_HAVE_RTNETLINK
+#error "This file should not be included ..."
+#endif
+
+namespace ndn {
+namespace net {
+
+class NetlinkMessage;
+
+class NetlinkSocket : noncopyable
+{
+public:
+ using Error = NetworkMonitor::Error;
+ using MessageCallback = std::function<void(const NetlinkMessage&)>;
+
+ void
+ joinGroup(int group);
+
+protected:
+ explicit
+ NetlinkSocket(boost::asio::io_service& io);
+
+ ~NetlinkSocket();
+
+ void
+ open(int protocol);
+
+ void
+ startAsyncReceive(MessageCallback cb);
+
+private:
+ void
+ asyncWait();
+
+ void
+ receiveAndValidate();
+
+protected:
+ shared_ptr<boost::asio::posix::stream_descriptor> m_sock; ///< netlink socket descriptor
+ uint32_t m_pid; ///< port ID of this socket
+ uint32_t m_seqNum; ///< sequence number of the last netlink request sent to the kernel
+
+private:
+ std::vector<uint8_t> m_buffer; ///< buffer for netlink messages from the kernel
+ MessageCallback m_onMessage; ///< callback invoked when a valid netlink message is received
+};
+
+class RtnlSocket : public NetlinkSocket
+{
+public:
+ explicit
+ RtnlSocket(boost::asio::io_service& io);
+
+ void
+ open();
+
+ void
+ sendDumpRequest(uint16_t nlmsgType);
+
+ using NetlinkSocket::startAsyncReceive;
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETLINK_SOCKET_HPP
diff --git a/src/net/detail/network-monitor-impl-netlink.cpp b/src/net/detail/network-monitor-impl-netlink.cpp
index 3c651d6..71a1ace 100644
--- a/src/net/detail/network-monitor-impl-netlink.cpp
+++ b/src/net/detail/network-monitor-impl-netlink.cpp
@@ -23,71 +23,39 @@
#include "network-monitor-impl-netlink.hpp"
#include "linux-if-constants.hpp"
-#include "netlink-util.hpp"
+#include "netlink-message.hpp"
#include "../network-address.hpp"
#include "../network-interface.hpp"
#include "../../util/logger.hpp"
-#include "../../util/time.hpp"
-
-#include <cerrno>
-#include <cstdlib>
#include <linux/if_addr.h>
#include <linux/if_link.h>
#include <net/if_arp.h>
-#include <sys/socket.h>
-
-#include <boost/asio/write.hpp>
-
-#ifndef SOL_NETLINK
-#define SOL_NETLINK 270
-#endif
-
-#ifndef RTEXT_FILTER_SKIP_STATS
-#define RTEXT_FILTER_SKIP_STATS (1 << 3)
-#endif
NDN_LOG_INIT(ndn.NetworkMonitor);
namespace ndn {
namespace net {
-struct RtnlRequest
-{
- nlmsghdr nlh;
- ifinfomsg ifi;
- rtattr rta alignas(NLMSG_ALIGNTO); // rtattr has to be aligned
- uint32_t rtext; // space for IFLA_EXT_MASK
-};
-
NetworkMonitorImplNetlink::NetworkMonitorImplNetlink(boost::asio::io_service& 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_rtnlSocket(io)
, m_isEnumeratingLinks(false)
, m_isEnumeratingAddresses(false)
{
- NDN_LOG_TRACE("creating NETLINK_ROUTE socket");
- initSocket(NETLINK_ROUTE);
+ m_rtnlSocket.open();
for (auto group : {RTNLGRP_LINK,
RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE,
RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE}) {
- joinGroup(group);
+ m_rtnlSocket.joinGroup(group);
}
- asyncRead();
+ m_rtnlSocket.startAsyncReceive([this] (const auto& msg) { this->parseRtnlMessage(msg); });
NDN_LOG_TRACE("enumerating links");
- sendDumpRequest(RTM_GETLINK);
+ m_rtnlSocket.sendDumpRequest(RTM_GETLINK);
m_isEnumeratingLinks = true;
}
-NetworkMonitorImplNetlink::~NetworkMonitorImplNetlink()
-{
- boost::system::error_code error;
- m_socket->close(error);
-}
-
shared_ptr<const NetworkInterface>
NetworkMonitorImplNetlink::getNetworkInterface(const std::string& ifname) const
{
@@ -117,220 +85,7 @@
}
void
-NetworkMonitorImplNetlink::initSocket(int family)
-{
- int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, family);
- if (fd < 0) {
- BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
- }
- m_socket->assign(fd);
-
- // increase socket receive buffer to 1MB to avoid losing messages
- const int bufsize = 1 * 1024 * 1024;
- if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
- // not a fatal error
- NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
- }
-
- // enable control messages for received packets to get the destination group
- const int one = 1;
- if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
- BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
- }
-
- sockaddr_nl addr{};
- addr.nl_family = AF_NETLINK;
- if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
- BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + 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("Cannot obtain netlink socket address ("s + 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);
-
-#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
- // enable extended ACK reporting
- if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
- // not a fatal error
- NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
- }
-#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
-}
-
-void
-NetworkMonitorImplNetlink::joinGroup(int group)
-{
- if (::setsockopt(m_socket->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
- &group, sizeof(group)) < 0) {
- BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
- " (" + std::strerror(errno) + ")"));
- }
-}
-
-void
-NetworkMonitorImplNetlink::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 = 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) {
- auto type = request->nlh.nlmsg_type;
- NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
- << " seq=" << request->nlh.nlmsg_seq);
- }
- else if (error != boost::asio::error::operation_aborted) {
- NDN_LOG_ERROR("write failed: " << error.message());
- BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + error.message() + ")"));
- }
- });
-}
-
-void
-NetworkMonitorImplNetlink::asyncRead()
-{
- m_socket->async_read_some(boost::asio::null_buffers(),
- // capture a copy of 'm_socket' to prevent its deallocation while the handler is still pending
- [this, socket = m_socket] (const auto& error, auto&&...) {
- if (!socket->is_open() || error == boost::asio::error::operation_aborted) {
- // socket was closed, ignore the error
- NDN_LOG_DEBUG("socket closed or operation aborted");
- }
- else if (error) {
- NDN_LOG_ERROR("read failed: " << error.message());
- BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + error.message() + ")"));
- }
- else {
- this->receiveMessage();
- this->asyncRead();
- }
- });
-}
-
-void
-NetworkMonitorImplNetlink::receiveMessage()
-{
- msghdr msg{};
- sockaddr_nl sender{};
- msg.msg_name = &sender;
- msg.msg_namelen = sizeof(sender);
- iovec iov{};
- iov.iov_base = m_buffer.data();
- iov.iov_len = m_buffer.size();
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- std::array<uint8_t, CMSG_SPACE(sizeof(nl_pktinfo))> cmsgBuffer;
- msg.msg_control = cmsgBuffer.data();
- msg.msg_controllen = cmsgBuffer.size();
-
- ssize_t nBytesRead = ::recvmsg(m_socket->native_handle(), &msg, 0);
- if (nBytesRead < 0) {
- std::string errorString = std::strerror(errno);
- if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
- NDN_LOG_DEBUG("recvmsg failed: " << errorString);
- return;
- }
- else {
- NDN_LOG_ERROR("recvmsg failed: " << errorString);
- BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
- }
- }
-
- NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
-
- if (msg.msg_flags & MSG_TRUNC) {
- NDN_LOG_ERROR("truncated message");
- BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
- // TODO: grow the buffer and start over
- }
-
- if (msg.msg_namelen >= sizeof(sockaddr_nl) && sender.nl_pid != 0) {
- NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
- return;
- }
-
- if (nBytesRead == 0) {
- return;
- }
-
- uint32_t nlGroup = 0;
- for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
- if (cmsg->cmsg_level == SOL_NETLINK &&
- cmsg->cmsg_type == NETLINK_PKTINFO &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
- const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
- nlGroup = pktinfo->group;
- }
- }
-
- NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
- for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
- NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
- "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
- " len=" << nlmsg->nlmsg_len <<
- " seq=" << nlmsg->nlmsg_seq <<
- " pid=" << nlmsg->nlmsg_pid <<
- " group=" << nlGroup);
-
- if (nlGroup == 0 && // not a multicast notification
- (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_sequenceNo)) { // not for us
- NDN_LOG_TRACE("seq/pid mismatch, ignoring");
- continue;
- }
-
- if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
- NDN_LOG_ERROR("dump is inconsistent");
- BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
- // TODO: discard the rest of the message and retry the dump
- }
-
- if (nlmsg->nlmsg_type == NLMSG_DONE) {
- break;
- }
-
- parseNetlinkMessage(nlmsg);
- }
-
- if (nlmsg->nlmsg_type == NLMSG_DONE) {
- if (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 (m_isEnumeratingAddresses) {
- // links and addresses enumeration complete
- m_isEnumeratingAddresses = false;
- // TODO: enumerate routes
- NDN_LOG_DEBUG("enumeration complete");
- this->emitSignal(onEnumerationCompleted);
- }
- }
-}
-
-void
-NetworkMonitorImplNetlink::parseNetlinkMessage(const NetlinkMessage& nlmsg)
+NetworkMonitorImplNetlink::parseRtnlMessage(const NetlinkMessage& nlmsg)
{
switch (nlmsg->nlmsg_type) {
case RTM_NEWLINK:
@@ -354,6 +109,23 @@
this->emitSignal(onNetworkStateChanged); // backward compat
break;
+ case NLMSG_DONE:
+ if (m_isEnumeratingLinks) {
+ // links enumeration complete, now request all the addresses
+ m_isEnumeratingLinks = false;
+ NDN_LOG_TRACE("enumerating addresses");
+ m_rtnlSocket.sendDumpRequest(RTM_GETADDR);
+ m_isEnumeratingAddresses = true;
+ }
+ else if (m_isEnumeratingAddresses) {
+ // links and addresses enumeration complete
+ m_isEnumeratingAddresses = false;
+ // TODO: enumerate routes
+ NDN_LOG_DEBUG("enumeration complete");
+ this->emitSignal(onEnumerationCompleted);
+ }
+ break;
+
case NLMSG_ERROR:
parseErrorMessage(nlmsg);
break;
@@ -401,6 +173,27 @@
}
}
+static void
+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);
+ }
+}
+
void
NetworkMonitorImplNetlink::parseLinkMessage(const NetlinkMessage& nlmsg)
{
@@ -568,31 +361,10 @@
auto nla = reinterpret_cast<const nlattr*>(reinterpret_cast<const uint8_t*>(&*nlmsg) + errLen);
auto attrs = NetlinkMessageAttributes<nlattr>(nla, nlmsg->nlmsg_len - errLen);
auto msg = attrs.getAttributeByType<std::string>(NLMSGERR_ATTR_MSG);
- if (msg)
+ if (msg && !msg->empty())
NDN_LOG_ERROR("kernel message: " << *msg);
#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
}
-void
-NetworkMonitorImplNetlink::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 net
} // namespace ndn
diff --git a/src/net/detail/network-monitor-impl-netlink.hpp b/src/net/detail/network-monitor-impl-netlink.hpp
index 4075437..0b461fb 100644
--- a/src/net/detail/network-monitor-impl-netlink.hpp
+++ b/src/net/detail/network-monitor-impl-netlink.hpp
@@ -31,28 +31,21 @@
#error "This file should not be included ..."
#endif
-#include <boost/asio/posix/stream_descriptor.hpp>
+#include "netlink-socket.hpp"
-#include <array>
#include <map>
namespace ndn {
namespace net {
-class NetlinkMessage;
-
class NetworkMonitorImplNetlink : public NetworkMonitorImpl
{
public:
- using Error = NetworkMonitor::Error;
-
/** \brief initialize netlink socket and start enumerating interfaces
*/
explicit
NetworkMonitorImplNetlink(boost::asio::io_service& io);
- ~NetworkMonitorImplNetlink();
-
uint32_t
getCapabilities() const final
{
@@ -74,22 +67,7 @@
isEnumerating() const;
void
- initSocket(int family);
-
- void
- joinGroup(int group);
-
- void
- sendDumpRequest(uint16_t nlmsgType);
-
- void
- asyncRead();
-
- void
- receiveMessage();
-
- void
- parseNetlinkMessage(const NetlinkMessage& nlmsg);
+ parseRtnlMessage(const NetlinkMessage& nlmsg);
void
parseLinkMessage(const NetlinkMessage& nlmsg);
@@ -103,15 +81,9 @@
void
parseErrorMessage(const NetlinkMessage& nlmsg);
- static void
- updateInterfaceState(NetworkInterface& interface, uint8_t operState);
-
private:
std::map<int, shared_ptr<NetworkInterface>> m_interfaces; ///< ifindex => interface
- std::array<uint8_t, 16384> m_buffer; ///< 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
+ RtnlSocket m_rtnlSocket; ///< rtnetlink socket
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
};
diff --git a/src/net/detail/network-monitor-impl-osx.hpp b/src/net/detail/network-monitor-impl-osx.hpp
index f536b5d..874f01a 100644
--- a/src/net/detail/network-monitor-impl-osx.hpp
+++ b/src/net/detail/network-monitor-impl-osx.hpp
@@ -47,8 +47,6 @@
class NetworkMonitorImplOsx : public NetworkMonitorImpl
{
public:
- using Error = NetworkMonitor::Error;
-
NetworkMonitorImplOsx(boost::asio::io_service& io);
~NetworkMonitorImplOsx();
diff --git a/src/net/network-monitor.hpp b/src/net/network-monitor.hpp
index afafd58..9b2d08d 100644
--- a/src/net/network-monitor.hpp
+++ b/src/net/network-monitor.hpp
@@ -133,6 +133,8 @@
class NetworkMonitorImpl : noncopyable
{
public:
+ using Error = NetworkMonitor::Error;
+
virtual
~NetworkMonitorImpl() = default;