net: refactor netlink socket handling
In preparation for generic netlink support
Change-Id: I69d93b69c08d4c3f7fd0dabbf3e51dbd49f9ef06
Refs: #4020
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