Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 1 | /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ |
| 2 | /* |
| 3 | * Copyright (c) 2013-2018 Regents of the University of California. |
| 4 | * |
| 5 | * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions). |
| 6 | * |
| 7 | * ndn-cxx library is free software: you can redistribute it and/or modify it under the |
| 8 | * terms of the GNU Lesser General Public License as published by the Free Software |
| 9 | * Foundation, either version 3 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY |
| 12 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
| 13 | * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. |
| 14 | * |
| 15 | * You should have received copies of the GNU General Public License and GNU Lesser |
| 16 | * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see |
| 17 | * <http://www.gnu.org/licenses/>. |
| 18 | * |
| 19 | * See AUTHORS.md for complete list of ndn-cxx authors and contributors. |
| 20 | * |
| 21 | * @author Davide Pesavento <davide.pesavento@lip6.fr> |
| 22 | */ |
| 23 | |
| 24 | #include "netlink-socket.hpp" |
| 25 | #include "netlink-message.hpp" |
| 26 | #include "../../util/logger.hpp" |
| 27 | #include "../../util/time.hpp" |
| 28 | |
| 29 | #include <cerrno> |
| 30 | #include <cstring> |
| 31 | #include <sys/socket.h> |
| 32 | |
| 33 | #include <boost/asio/write.hpp> |
| 34 | |
| 35 | #ifndef SOL_NETLINK |
| 36 | #define SOL_NETLINK 270 |
| 37 | #endif |
| 38 | #ifndef RTEXT_FILTER_SKIP_STATS |
| 39 | #define RTEXT_FILTER_SKIP_STATS (1 << 3) |
| 40 | #endif |
| 41 | |
| 42 | NDN_LOG_INIT(ndn.NetworkMonitor); |
| 43 | |
| 44 | namespace ndn { |
| 45 | namespace net { |
| 46 | |
| 47 | NetlinkSocket::NetlinkSocket(boost::asio::io_service& io) |
| 48 | : m_sock(make_shared<boost::asio::posix::stream_descriptor>(io)) |
| 49 | , m_pid(0) |
| 50 | , m_seqNum(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count())) |
| 51 | , m_buffer(16 * 1024) // 16 KiB |
| 52 | { |
| 53 | } |
| 54 | |
| 55 | NetlinkSocket::~NetlinkSocket() |
| 56 | { |
| 57 | boost::system::error_code ec; |
| 58 | m_sock->close(ec); |
| 59 | } |
| 60 | |
| 61 | void |
| 62 | NetlinkSocket::open(int protocol) |
| 63 | { |
| 64 | int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); |
| 65 | if (fd < 0) { |
| 66 | BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")")); |
| 67 | } |
| 68 | m_sock->assign(fd); |
| 69 | |
| 70 | // increase socket receive buffer to 1MB to avoid losing messages |
| 71 | const int bufsize = 1 * 1024 * 1024; |
| 72 | if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) { |
| 73 | // not a fatal error |
| 74 | NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno)); |
| 75 | } |
| 76 | |
| 77 | // enable control messages for received packets to get the destination group |
| 78 | const int one = 1; |
| 79 | if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) { |
| 80 | BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")")); |
| 81 | } |
| 82 | |
| 83 | sockaddr_nl addr{}; |
| 84 | addr.nl_family = AF_NETLINK; |
| 85 | if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) { |
| 86 | BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")")); |
| 87 | } |
| 88 | |
| 89 | // find out what pid has been assigned to us |
| 90 | socklen_t len = sizeof(addr); |
| 91 | if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) { |
| 92 | BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")")); |
| 93 | } |
| 94 | if (len != sizeof(addr)) { |
| 95 | BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")")); |
| 96 | } |
| 97 | if (addr.nl_family != AF_NETLINK) { |
| 98 | BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")")); |
| 99 | } |
| 100 | m_pid = addr.nl_pid; |
| 101 | NDN_LOG_TRACE("our pid is " << m_pid); |
| 102 | |
| 103 | #ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK |
| 104 | // enable extended ACK reporting |
| 105 | if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) { |
| 106 | // not a fatal error |
| 107 | NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno)); |
| 108 | } |
| 109 | #endif // NDN_CXX_HAVE_NETLINK_EXT_ACK |
| 110 | } |
| 111 | |
| 112 | void |
| 113 | NetlinkSocket::joinGroup(int group) |
| 114 | { |
| 115 | if (::setsockopt(m_sock->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, |
| 116 | &group, sizeof(group)) < 0) { |
| 117 | BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) + |
| 118 | " (" + std::strerror(errno) + ")")); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | void |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 123 | NetlinkSocket::registerNotificationCallback(MessageCallback cb) |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 124 | { |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 125 | registerRequestCallback(0, std::move(cb)); |
| 126 | } |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 127 | |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 128 | void |
| 129 | NetlinkSocket::registerRequestCallback(uint32_t seq, MessageCallback cb) |
| 130 | { |
| 131 | if (cb == nullptr) { |
| 132 | m_pendingRequests.erase(seq); |
| 133 | } |
| 134 | else { |
| 135 | bool wasEmpty = m_pendingRequests.empty(); |
| 136 | m_pendingRequests.emplace(seq, std::move(cb)); |
| 137 | if (wasEmpty) |
| 138 | asyncWait(); |
| 139 | } |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | static const char* |
| 143 | nlmsgTypeToString(uint16_t type) |
| 144 | { |
| 145 | #define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">" |
| 146 | #define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">" |
| 147 | switch (type) { |
| 148 | NLMSG_STRINGIFY(NOOP); |
| 149 | NLMSG_STRINGIFY(ERROR); |
| 150 | NLMSG_STRINGIFY(DONE); |
| 151 | NLMSG_STRINGIFY(OVERRUN); |
| 152 | RTM_STRINGIFY(NEWLINK); |
| 153 | RTM_STRINGIFY(DELLINK); |
| 154 | RTM_STRINGIFY(GETLINK); |
| 155 | RTM_STRINGIFY(NEWADDR); |
| 156 | RTM_STRINGIFY(DELADDR); |
| 157 | RTM_STRINGIFY(GETADDR); |
| 158 | RTM_STRINGIFY(NEWROUTE); |
| 159 | RTM_STRINGIFY(DELROUTE); |
| 160 | RTM_STRINGIFY(GETROUTE); |
| 161 | default: |
| 162 | return ""; |
| 163 | } |
| 164 | #undef NLMSG_STRINGIFY |
| 165 | #undef RTM_STRINGIFY |
| 166 | } |
| 167 | |
| 168 | void |
| 169 | NetlinkSocket::asyncWait() |
| 170 | { |
| 171 | m_sock->async_read_some(boost::asio::null_buffers(), |
| 172 | // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending |
| 173 | [this, sock = m_sock] (const boost::system::error_code& ec, size_t) { |
| 174 | if (!sock->is_open() || ec == boost::asio::error::operation_aborted) { |
| 175 | // socket was closed, ignore the error |
| 176 | NDN_LOG_DEBUG("netlink socket closed or operation aborted"); |
| 177 | } |
| 178 | else if (ec) { |
| 179 | NDN_LOG_ERROR("read failed: " << ec.message()); |
| 180 | BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")")); |
| 181 | } |
| 182 | else { |
| 183 | receiveAndValidate(); |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 184 | if (!m_pendingRequests.empty()) |
| 185 | asyncWait(); |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 186 | } |
| 187 | }); |
| 188 | } |
| 189 | |
| 190 | void |
| 191 | NetlinkSocket::receiveAndValidate() |
| 192 | { |
| 193 | sockaddr_nl sender{}; |
| 194 | iovec iov{}; |
| 195 | iov.iov_base = m_buffer.data(); |
| 196 | iov.iov_len = m_buffer.size(); |
| 197 | uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))]; |
| 198 | msghdr msg{}; |
| 199 | msg.msg_name = &sender; |
| 200 | msg.msg_namelen = sizeof(sender); |
| 201 | msg.msg_iov = &iov; |
| 202 | msg.msg_iovlen = 1; |
| 203 | msg.msg_control = cmsgBuffer; |
| 204 | msg.msg_controllen = sizeof(cmsgBuffer); |
| 205 | |
| 206 | ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0); |
| 207 | if (nBytesRead < 0) { |
| 208 | std::string errorString = std::strerror(errno); |
| 209 | if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) { |
| 210 | // not a fatal error |
| 211 | NDN_LOG_DEBUG("recvmsg failed: " << errorString); |
| 212 | return; |
| 213 | } |
| 214 | NDN_LOG_ERROR("recvmsg failed: " << errorString); |
| 215 | BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")")); |
| 216 | } |
| 217 | |
| 218 | NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket"); |
| 219 | |
| 220 | if (msg.msg_flags & MSG_TRUNC) { |
| 221 | NDN_LOG_ERROR("truncated message"); |
| 222 | BOOST_THROW_EXCEPTION(Error("Received truncated netlink message")); |
| 223 | // TODO: grow the buffer and start over |
| 224 | } |
| 225 | |
| 226 | if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) { |
| 227 | NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid); |
| 228 | return; |
| 229 | } |
| 230 | |
| 231 | uint32_t nlGroup = 0; |
| 232 | for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| 233 | if (cmsg->cmsg_level == SOL_NETLINK && |
| 234 | cmsg->cmsg_type == NETLINK_PKTINFO && |
| 235 | cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) { |
| 236 | const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg)); |
| 237 | nlGroup = pktinfo->group; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead)); |
| 242 | for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) { |
| 243 | NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") << |
| 244 | "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) << |
| 245 | " len=" << nlmsg->nlmsg_len << |
| 246 | " seq=" << nlmsg->nlmsg_seq << |
| 247 | " pid=" << nlmsg->nlmsg_pid << |
| 248 | " group=" << nlGroup); |
| 249 | |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 250 | auto cbIt = m_pendingRequests.end(); |
| 251 | if (nlGroup != 0) { |
| 252 | // it's a multicast notification |
| 253 | cbIt = m_pendingRequests.find(0); |
| 254 | } |
| 255 | else if (nlmsg->nlmsg_pid == m_pid) { |
| 256 | // it's for us |
| 257 | cbIt = m_pendingRequests.find(nlmsg->nlmsg_seq); |
| 258 | } |
| 259 | else { |
| 260 | NDN_LOG_TRACE("pid mismatch, ignoring"); |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 261 | continue; |
| 262 | } |
| 263 | |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 264 | if (cbIt == m_pendingRequests.end()) { |
| 265 | NDN_LOG_TRACE("no handler registered, ignoring"); |
| 266 | continue; |
| 267 | } |
| 268 | else if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) { |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 269 | NDN_LOG_ERROR("dump is inconsistent"); |
| 270 | BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump")); |
| 271 | // TODO: discard the rest of the message and retry the dump |
| 272 | } |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 273 | else { |
| 274 | // invoke the callback |
| 275 | BOOST_ASSERT(cbIt->second); |
| 276 | cbIt->second(nlmsg); |
| 277 | } |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 278 | |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 279 | // garbage collect the handler if we don't need it anymore: |
| 280 | // do it only if this is a reply message (i.e. not a notification) and either |
| 281 | // (1) it's not a multi-part message, in which case this is the only fragment, or |
| 282 | // (2) it's the last fragment of a multi-part message |
| 283 | if (nlGroup == 0 && (!(nlmsg->nlmsg_flags & NLM_F_MULTI) || nlmsg->nlmsg_type == NLMSG_DONE)) { |
| 284 | NDN_LOG_TRACE("removing handler for seq=" << nlmsg->nlmsg_seq); |
| 285 | BOOST_ASSERT(cbIt != m_pendingRequests.end()); |
| 286 | m_pendingRequests.erase(cbIt); |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 287 | } |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | RtnlSocket::RtnlSocket(boost::asio::io_service& io) |
| 292 | : NetlinkSocket(io) |
| 293 | { |
| 294 | } |
| 295 | |
| 296 | void |
| 297 | RtnlSocket::open() |
| 298 | { |
| 299 | NDN_LOG_TRACE("opening rtnetlink socket"); |
| 300 | NetlinkSocket::open(NETLINK_ROUTE); |
| 301 | } |
| 302 | |
| 303 | void |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 304 | RtnlSocket::sendDumpRequest(uint16_t nlmsgType, MessageCallback cb) |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 305 | { |
| 306 | struct RtnlRequest |
| 307 | { |
| 308 | nlmsghdr nlh; |
| 309 | alignas(NLMSG_ALIGNTO) ifinfomsg ifi; |
| 310 | alignas(NLMSG_ALIGNTO) rtattr rta; |
| 311 | alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK |
| 312 | }; |
| 313 | |
| 314 | auto request = make_shared<RtnlRequest>(); |
| 315 | request->nlh.nlmsg_type = nlmsgType; |
| 316 | request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
| 317 | request->nlh.nlmsg_seq = ++m_seqNum; |
| 318 | request->nlh.nlmsg_pid = m_pid; |
| 319 | request->ifi.ifi_family = AF_UNSPEC; |
| 320 | request->rta.rta_type = IFLA_EXT_MASK; |
| 321 | request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext)); |
| 322 | request->rtext = RTEXT_FILTER_SKIP_STATS; |
| 323 | request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len; |
| 324 | |
Davide Pesavento | dc5bb96 | 2018-08-12 23:40:38 -0400 | [diff] [blame^] | 325 | registerRequestCallback(request->nlh.nlmsg_seq, std::move(cb)); |
| 326 | |
Davide Pesavento | a0b2a2c | 2018-07-19 01:01:06 -0400 | [diff] [blame] | 327 | boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len), |
| 328 | // capture 'request' to prevent its premature deallocation |
| 329 | [request] (const boost::system::error_code& ec, size_t) { |
| 330 | if (!ec) { |
| 331 | auto type = request->nlh.nlmsg_type; |
| 332 | NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type) |
| 333 | << " seq=" << request->nlh.nlmsg_seq); |
| 334 | } |
| 335 | else if (ec != boost::asio::error::operation_aborted) { |
| 336 | NDN_LOG_ERROR("write failed: " << ec.message()); |
| 337 | BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")")); |
| 338 | } |
| 339 | }); |
| 340 | } |
| 341 | |
| 342 | } // namespace net |
| 343 | } // namespace ndn |