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 |
| 123 | NetlinkSocket::startAsyncReceive(MessageCallback cb) |
| 124 | { |
| 125 | BOOST_ASSERT(cb != nullptr); |
| 126 | BOOST_ASSERT(m_onMessage == nullptr); |
| 127 | |
| 128 | m_onMessage = std::move(cb); |
| 129 | asyncWait(); |
| 130 | } |
| 131 | |
| 132 | static const char* |
| 133 | nlmsgTypeToString(uint16_t type) |
| 134 | { |
| 135 | #define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">" |
| 136 | #define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">" |
| 137 | switch (type) { |
| 138 | NLMSG_STRINGIFY(NOOP); |
| 139 | NLMSG_STRINGIFY(ERROR); |
| 140 | NLMSG_STRINGIFY(DONE); |
| 141 | NLMSG_STRINGIFY(OVERRUN); |
| 142 | RTM_STRINGIFY(NEWLINK); |
| 143 | RTM_STRINGIFY(DELLINK); |
| 144 | RTM_STRINGIFY(GETLINK); |
| 145 | RTM_STRINGIFY(NEWADDR); |
| 146 | RTM_STRINGIFY(DELADDR); |
| 147 | RTM_STRINGIFY(GETADDR); |
| 148 | RTM_STRINGIFY(NEWROUTE); |
| 149 | RTM_STRINGIFY(DELROUTE); |
| 150 | RTM_STRINGIFY(GETROUTE); |
| 151 | default: |
| 152 | return ""; |
| 153 | } |
| 154 | #undef NLMSG_STRINGIFY |
| 155 | #undef RTM_STRINGIFY |
| 156 | } |
| 157 | |
| 158 | void |
| 159 | NetlinkSocket::asyncWait() |
| 160 | { |
| 161 | m_sock->async_read_some(boost::asio::null_buffers(), |
| 162 | // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending |
| 163 | [this, sock = m_sock] (const boost::system::error_code& ec, size_t) { |
| 164 | if (!sock->is_open() || ec == boost::asio::error::operation_aborted) { |
| 165 | // socket was closed, ignore the error |
| 166 | NDN_LOG_DEBUG("netlink socket closed or operation aborted"); |
| 167 | } |
| 168 | else if (ec) { |
| 169 | NDN_LOG_ERROR("read failed: " << ec.message()); |
| 170 | BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")")); |
| 171 | } |
| 172 | else { |
| 173 | receiveAndValidate(); |
| 174 | asyncWait(); |
| 175 | } |
| 176 | }); |
| 177 | } |
| 178 | |
| 179 | void |
| 180 | NetlinkSocket::receiveAndValidate() |
| 181 | { |
| 182 | sockaddr_nl sender{}; |
| 183 | iovec iov{}; |
| 184 | iov.iov_base = m_buffer.data(); |
| 185 | iov.iov_len = m_buffer.size(); |
| 186 | uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))]; |
| 187 | msghdr msg{}; |
| 188 | msg.msg_name = &sender; |
| 189 | msg.msg_namelen = sizeof(sender); |
| 190 | msg.msg_iov = &iov; |
| 191 | msg.msg_iovlen = 1; |
| 192 | msg.msg_control = cmsgBuffer; |
| 193 | msg.msg_controllen = sizeof(cmsgBuffer); |
| 194 | |
| 195 | ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0); |
| 196 | if (nBytesRead < 0) { |
| 197 | std::string errorString = std::strerror(errno); |
| 198 | if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) { |
| 199 | // not a fatal error |
| 200 | NDN_LOG_DEBUG("recvmsg failed: " << errorString); |
| 201 | return; |
| 202 | } |
| 203 | NDN_LOG_ERROR("recvmsg failed: " << errorString); |
| 204 | BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")")); |
| 205 | } |
| 206 | |
| 207 | NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket"); |
| 208 | |
| 209 | if (msg.msg_flags & MSG_TRUNC) { |
| 210 | NDN_LOG_ERROR("truncated message"); |
| 211 | BOOST_THROW_EXCEPTION(Error("Received truncated netlink message")); |
| 212 | // TODO: grow the buffer and start over |
| 213 | } |
| 214 | |
| 215 | if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) { |
| 216 | NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid); |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | uint32_t nlGroup = 0; |
| 221 | for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| 222 | if (cmsg->cmsg_level == SOL_NETLINK && |
| 223 | cmsg->cmsg_type == NETLINK_PKTINFO && |
| 224 | cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) { |
| 225 | const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg)); |
| 226 | nlGroup = pktinfo->group; |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead)); |
| 231 | for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) { |
| 232 | NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") << |
| 233 | "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) << |
| 234 | " len=" << nlmsg->nlmsg_len << |
| 235 | " seq=" << nlmsg->nlmsg_seq << |
| 236 | " pid=" << nlmsg->nlmsg_pid << |
| 237 | " group=" << nlGroup); |
| 238 | |
| 239 | if (nlGroup == 0 && // not a multicast notification |
| 240 | (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_seqNum)) { // not for us |
| 241 | NDN_LOG_TRACE("seq/pid mismatch, ignoring"); |
| 242 | continue; |
| 243 | } |
| 244 | |
| 245 | if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) { |
| 246 | NDN_LOG_ERROR("dump is inconsistent"); |
| 247 | BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump")); |
| 248 | // TODO: discard the rest of the message and retry the dump |
| 249 | } |
| 250 | |
| 251 | m_onMessage(nlmsg); |
| 252 | |
| 253 | if (nlmsg->nlmsg_type == NLMSG_DONE) { |
| 254 | break; |
| 255 | } |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | RtnlSocket::RtnlSocket(boost::asio::io_service& io) |
| 260 | : NetlinkSocket(io) |
| 261 | { |
| 262 | } |
| 263 | |
| 264 | void |
| 265 | RtnlSocket::open() |
| 266 | { |
| 267 | NDN_LOG_TRACE("opening rtnetlink socket"); |
| 268 | NetlinkSocket::open(NETLINK_ROUTE); |
| 269 | } |
| 270 | |
| 271 | void |
| 272 | RtnlSocket::sendDumpRequest(uint16_t nlmsgType) |
| 273 | { |
| 274 | struct RtnlRequest |
| 275 | { |
| 276 | nlmsghdr nlh; |
| 277 | alignas(NLMSG_ALIGNTO) ifinfomsg ifi; |
| 278 | alignas(NLMSG_ALIGNTO) rtattr rta; |
| 279 | alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK |
| 280 | }; |
| 281 | |
| 282 | auto request = make_shared<RtnlRequest>(); |
| 283 | request->nlh.nlmsg_type = nlmsgType; |
| 284 | request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
| 285 | request->nlh.nlmsg_seq = ++m_seqNum; |
| 286 | request->nlh.nlmsg_pid = m_pid; |
| 287 | request->ifi.ifi_family = AF_UNSPEC; |
| 288 | request->rta.rta_type = IFLA_EXT_MASK; |
| 289 | request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext)); |
| 290 | request->rtext = RTEXT_FILTER_SKIP_STATS; |
| 291 | request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len; |
| 292 | |
| 293 | boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len), |
| 294 | // capture 'request' to prevent its premature deallocation |
| 295 | [request] (const boost::system::error_code& ec, size_t) { |
| 296 | if (!ec) { |
| 297 | auto type = request->nlh.nlmsg_type; |
| 298 | NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type) |
| 299 | << " seq=" << request->nlh.nlmsg_seq); |
| 300 | } |
| 301 | else if (ec != boost::asio::error::operation_aborted) { |
| 302 | NDN_LOG_ERROR("write failed: " << ec.message()); |
| 303 | BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")")); |
| 304 | } |
| 305 | }); |
| 306 | } |
| 307 | |
| 308 | } // namespace net |
| 309 | } // namespace ndn |