net: initial support for generic netlink (genl)
Change-Id: Iaea67d08c835b856d2928eb49003ad214de824ca
Refs: #4020
diff --git a/src/net/detail/netlink-socket.cpp b/src/net/detail/netlink-socket.cpp
index ea5a421..c325737 100644
--- a/src/net/detail/netlink-socket.cpp
+++ b/src/net/detail/netlink-socket.cpp
@@ -27,7 +27,7 @@
#include "../../util/time.hpp"
#include <cerrno>
-#include <cstring>
+#include <linux/genetlink.h>
#include <sys/socket.h>
#include <boost/asio/write.hpp>
@@ -139,30 +139,19 @@
}
}
-static const char*
-nlmsgTypeToString(uint16_t type)
+std::string
+NetlinkSocket::nlmsgTypeToString(uint16_t type) const
{
-#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
-#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
+#define NLMSG_STRINGIFY(x) case NLMSG_##x: return to_string(type) + "<" #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 "";
+ return to_string(type);
}
#undef NLMSG_STRINGIFY
-#undef RTM_STRINGIFY
}
void
@@ -241,7 +230,7 @@
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) <<
+ "message type=" << nlmsgTypeToString(nlmsg->nlmsg_type) <<
" len=" << nlmsg->nlmsg_len <<
" seq=" << nlmsg->nlmsg_seq <<
" pid=" << nlmsg->nlmsg_pid <<
@@ -326,10 +315,9 @@
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) {
+ [this, 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)
+ NDN_LOG_TRACE("sent dump request type=" << nlmsgTypeToString(request->nlh.nlmsg_type)
<< " seq=" << request->nlh.nlmsg_seq);
}
else if (ec != boost::asio::error::operation_aborted) {
@@ -339,5 +327,218 @@
});
}
+std::string
+RtnlSocket::nlmsgTypeToString(uint16_t type) const
+{
+#define RTM_STRINGIFY(x) case RTM_##x: return to_string(type) + "<" #x ">"
+ switch (type) {
+ 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 NetlinkSocket::nlmsgTypeToString(type);
+ }
+#undef RTM_STRINGIFY
+}
+
+GenlSocket::GenlSocket(boost::asio::io_service& io)
+ : NetlinkSocket(io)
+{
+ m_cachedFamilyIds["nlctrl"] = GENL_ID_CTRL;
+}
+
+void
+GenlSocket::open()
+{
+ NDN_LOG_TRACE("opening genetlink socket");
+ NetlinkSocket::open(NETLINK_GENERIC);
+}
+
+void
+GenlSocket::sendRequest(const std::string& familyName, uint8_t command,
+ const void* payload, size_t payloadLen,
+ MessageCallback messageCb, std::function<void()> errorCb)
+{
+ auto it = m_cachedFamilyIds.find(familyName);
+ if (it != m_cachedFamilyIds.end()) {
+ if (it->second >= GENL_MIN_ID) {
+ sendRequest(it->second, command, payload, payloadLen, std::move(messageCb));
+ }
+ else if (errorCb) {
+ errorCb();
+ }
+ return;
+ }
+
+ auto ret = m_familyResolvers.emplace(std::piecewise_construct,
+ std::forward_as_tuple(familyName),
+ std::forward_as_tuple(familyName, *this));
+ auto& resolver = ret.first->second;
+ if (ret.second) {
+ // cache the result
+ resolver.onResolved.connectSingleShot([=] (uint16_t familyId) {
+ m_cachedFamilyIds[familyName] = familyId;
+ });
+ resolver.onError.connectSingleShot([=] {
+ m_cachedFamilyIds[familyName] = 0;
+ });
+ }
+ resolver.onResolved.connectSingleShot([=, cb = std::move(messageCb)] (uint16_t familyId) {
+ sendRequest(familyId, command, payload, payloadLen, std::move(cb));
+ });
+ if (errorCb) {
+ resolver.onError.connectSingleShot(std::move(errorCb));
+ }
+}
+
+void
+GenlSocket::sendRequest(uint16_t familyId, uint8_t command,
+ const void* payload, size_t payloadLen, MessageCallback cb)
+{
+ struct GenlRequestHeader
+ {
+ alignas(NLMSG_ALIGNTO) nlmsghdr nlh;
+ alignas(NLMSG_ALIGNTO) genlmsghdr genlh;
+ };
+ static_assert(sizeof(GenlRequestHeader) == NLMSG_SPACE(GENL_HDRLEN), "");
+
+ auto hdr = make_shared<GenlRequestHeader>();
+ hdr->nlh.nlmsg_len = sizeof(GenlRequestHeader) + payloadLen;
+ hdr->nlh.nlmsg_type = familyId;
+ hdr->nlh.nlmsg_flags = NLM_F_REQUEST;
+ hdr->nlh.nlmsg_seq = ++m_seqNum;
+ hdr->nlh.nlmsg_pid = m_pid;
+ hdr->genlh.cmd = command;
+ hdr->genlh.version = 1;
+
+ registerRequestCallback(hdr->nlh.nlmsg_seq, std::move(cb));
+
+ std::array<boost::asio::const_buffer, 2> bufs = {
+ boost::asio::buffer(hdr.get(), sizeof(GenlRequestHeader)),
+ boost::asio::buffer(payload, payloadLen)
+ };
+ boost::asio::async_write(*m_sock, bufs,
+ // capture 'hdr' to prevent its premature deallocation
+ [this, hdr] (const boost::system::error_code& ec, size_t) {
+ if (!ec) {
+ NDN_LOG_TRACE("sent genl request type=" << nlmsgTypeToString(hdr->nlh.nlmsg_type) <<
+ " cmd=" << static_cast<unsigned>(hdr->genlh.cmd) <<
+ " seq=" << hdr->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() + ")"));
+ }
+ });
+}
+
+GenlFamilyResolver::GenlFamilyResolver(std::string familyName, GenlSocket& socket)
+ : m_sock(socket)
+ , m_family(std::move(familyName))
+{
+ if (m_family.size() >= GENL_NAMSIZ) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("netlink family name '" + m_family + "' too long"));
+ }
+
+ NDN_LOG_TRACE("resolving netlink family " << m_family);
+ asyncResolve();
+}
+
+void
+GenlFamilyResolver::asyncResolve()
+{
+ struct FamilyNameAttribute
+ {
+ alignas(NLMSG_ALIGNTO) nlattr nla;
+ alignas(NLMSG_ALIGNTO) char name[GENL_NAMSIZ];
+ };
+
+ auto attr = make_shared<FamilyNameAttribute>();
+ attr->nla.nla_type = CTRL_ATTR_FAMILY_NAME;
+ attr->nla.nla_len = NLA_HDRLEN + m_family.size() + 1;
+ ::strncpy(attr->name, m_family.data(), GENL_NAMSIZ);
+
+ m_sock.sendRequest(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, attr.get(), attr->nla.nla_len,
+ // capture 'attr' to prevent its premature deallocation
+ [this, attr] (const auto& msg) { this->handleResolve(msg); });
+}
+
+void
+GenlFamilyResolver::handleResolve(const NetlinkMessage& nlmsg)
+{
+ switch (nlmsg->nlmsg_type) {
+ case NLMSG_ERROR: {
+ const nlmsgerr* err = nlmsg.getPayload<nlmsgerr>();
+ if (err == nullptr) {
+ NDN_LOG_WARN("malformed nlmsgerr");
+ }
+ else if (err->error != 0) {
+ NDN_LOG_DEBUG("failed to resolve netlink family " << m_family << ": "
+ << std::strerror(std::abs(err->error)));
+ }
+ onError();
+ break;
+ }
+
+ case GENL_ID_CTRL: {
+ const genlmsghdr* genlh = nlmsg.getPayload<genlmsghdr>();
+ if (genlh == nullptr) {
+ NDN_LOG_WARN("malformed genlmsghdr");
+ return onError();
+ }
+ if (genlh->cmd != CTRL_CMD_NEWFAMILY) {
+ NDN_LOG_WARN("unexpected genl cmd=" << static_cast<unsigned>(genlh->cmd));
+ return onError();
+ }
+
+ auto attrs = nlmsg.getAttributes<nlattr>(genlh);
+ auto familyName = attrs.getAttributeByType<std::string>(CTRL_ATTR_FAMILY_NAME);
+ if (familyName && *familyName != m_family) {
+ NDN_LOG_WARN("CTRL_ATTR_FAMILY_NAME mismatch: " << *familyName << " != " << m_family);
+ return onError();
+ }
+ auto familyId = attrs.getAttributeByType<uint16_t>(CTRL_ATTR_FAMILY_ID);
+ if (!familyId) {
+ NDN_LOG_WARN("missing CTRL_ATTR_FAMILY_ID");
+ return onError();
+ }
+ if (*familyId < GENL_MIN_ID) {
+ NDN_LOG_WARN("invalid CTRL_ATTR_FAMILY_ID=" << *familyId);
+ return onError();
+ }
+
+ NDN_LOG_TRACE("resolved netlink family name=" << m_family << " id=" << *familyId);
+ onResolved(*familyId);
+ break;
+ }
+
+ default: {
+ NDN_LOG_WARN("unexpected message type");
+ onError();
+ break;
+ }
+ }
+}
+
+std::string
+GenlSocket::nlmsgTypeToString(uint16_t type) const
+{
+ if (type >= GENL_MIN_ID) {
+ for (const auto& p : m_cachedFamilyIds) {
+ if (p.second == type) {
+ return to_string(type) + "<" + p.first + ">";
+ }
+ }
+ }
+
+ return NetlinkSocket::nlmsgTypeToString(type);
+}
+
} // namespace net
} // namespace ndn
diff --git a/src/net/detail/netlink-socket.hpp b/src/net/detail/netlink-socket.hpp
index 15036ec..4a6e4f1 100644
--- a/src/net/detail/netlink-socket.hpp
+++ b/src/net/detail/netlink-socket.hpp
@@ -24,8 +24,8 @@
#ifndef NDN_NET_NETLINK_SOCKET_HPP
#define NDN_NET_NETLINK_SOCKET_HPP
-#include "../../common.hpp"
#include "../network-monitor.hpp"
+#include "../../util/signal/signal.hpp"
#include <boost/asio/posix/stream_descriptor.hpp>
#include <map>
@@ -64,6 +64,9 @@
void
registerRequestCallback(uint32_t seq, MessageCallback cb);
+ virtual std::string
+ nlmsgTypeToString(uint16_t type) const;
+
private:
void
asyncWait();
@@ -81,7 +84,7 @@
std::map<uint32_t, MessageCallback> m_pendingRequests; ///< request sequence number => callback
};
-class RtnlSocket : public NetlinkSocket
+class RtnlSocket final : public NetlinkSocket
{
public:
explicit
@@ -92,6 +95,60 @@
void
sendDumpRequest(uint16_t nlmsgType, MessageCallback cb);
+
+protected:
+ std::string
+ nlmsgTypeToString(uint16_t type) const final;
+};
+
+class GenlSocket;
+
+class GenlFamilyResolver : noncopyable
+{
+public:
+ GenlFamilyResolver(std::string familyName, GenlSocket& socket);
+
+ util::Signal<GenlFamilyResolver, uint16_t> onResolved;
+ util::Signal<GenlFamilyResolver> onError;
+
+private:
+ void
+ asyncResolve();
+
+ void
+ handleResolve(const NetlinkMessage& nlmsg);
+
+private:
+ GenlSocket& m_sock;
+ std::string m_family;
+};
+
+class GenlSocket final : public NetlinkSocket
+{
+public:
+ explicit
+ GenlSocket(boost::asio::io_service& io);
+
+ void
+ open();
+
+ void
+ sendRequest(const std::string& familyName, uint8_t command,
+ const void* payload, size_t payloadLen,
+ MessageCallback messageCb, std::function<void()> errorCb);
+
+ void
+ sendRequest(uint16_t familyId, uint8_t command,
+ const void* payload, size_t payloadLen,
+ MessageCallback messageCb);
+
+protected:
+ std::string
+ nlmsgTypeToString(uint16_t type) const final;
+
+private:
+ std::map<std::string, uint16_t> m_cachedFamilyIds; ///< family name => family id
+ std::map<std::string, GenlFamilyResolver> m_familyResolvers; ///< family name => resolver instance
};
} // namespace net
diff --git a/src/net/detail/network-monitor-impl-netlink.cpp b/src/net/detail/network-monitor-impl-netlink.cpp
index 80385c1..e404322 100644
--- a/src/net/detail/network-monitor-impl-netlink.cpp
+++ b/src/net/detail/network-monitor-impl-netlink.cpp
@@ -39,16 +39,17 @@
NetworkMonitorImplNetlink::NetworkMonitorImplNetlink(boost::asio::io_service& io)
: m_rtnlSocket(io)
+ , m_genlSocket(io)
, m_isEnumeratingLinks(false)
, m_isEnumeratingAddresses(false)
{
m_rtnlSocket.open();
+
for (auto group : {RTNLGRP_LINK,
RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE,
RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE}) {
m_rtnlSocket.joinGroup(group);
}
-
m_rtnlSocket.registerNotificationCallback([this] (const auto& msg) { this->parseRtnlMessage(msg); });
NDN_LOG_TRACE("enumerating links");
diff --git a/src/net/detail/network-monitor-impl-netlink.hpp b/src/net/detail/network-monitor-impl-netlink.hpp
index 0b461fb..cbb0166 100644
--- a/src/net/detail/network-monitor-impl-netlink.hpp
+++ b/src/net/detail/network-monitor-impl-netlink.hpp
@@ -84,6 +84,7 @@
private:
std::map<int, shared_ptr<NetworkInterface>> m_interfaces; ///< ifindex => interface
RtnlSocket m_rtnlSocket; ///< rtnetlink socket
+ GenlSocket m_genlSocket; ///< generic netlink socket to communicate with nl80211
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
};