blob: 13b8c0e2617944275e73559ca2a551cbc6c0a863 [file] [log] [blame]
Davide Pesavento50b92262018-07-11 12:28:31 -04001/* -*- 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 "network-monitor-impl-netlink.hpp"
25#include "linux-if-constants.hpp"
26#include "netlink-util.hpp"
27#include "../network-address.hpp"
28#include "../network-interface.hpp"
29#include "../../util/logger.hpp"
30#include "../../util/time.hpp"
31
32#include <cerrno>
33#include <cstdlib>
34
35#include <linux/if_addr.h>
36#include <linux/if_link.h>
37#include <net/if_arp.h>
38#include <sys/socket.h>
39
40#include <boost/asio/write.hpp>
41
42#ifndef SOL_NETLINK
43#define SOL_NETLINK 270
44#endif
45
46#ifndef RTEXT_FILTER_SKIP_STATS
47#define RTEXT_FILTER_SKIP_STATS (1 << 3)
48#endif
49
50NDN_LOG_INIT(ndn.NetworkMonitor);
51
52namespace ndn {
53namespace net {
54
55struct RtnlRequest
56{
57 nlmsghdr nlh;
58 ifinfomsg ifi;
59 rtattr rta alignas(NLMSG_ALIGNTO); // rtattr has to be aligned
60 uint32_t rtext; // space for IFLA_EXT_MASK
61};
62
63NetworkMonitorImplNetlink::NetworkMonitorImplNetlink(boost::asio::io_service& io)
64 : m_socket(make_shared<boost::asio::posix::stream_descriptor>(io))
65 , m_pid(0)
66 , m_sequenceNo(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
67 , m_isEnumeratingLinks(false)
68 , m_isEnumeratingAddresses(false)
69{
70 NDN_LOG_TRACE("creating NETLINK_ROUTE socket");
71 initSocket(NETLINK_ROUTE);
72 for (auto group : {RTNLGRP_LINK,
73 RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE,
74 RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE}) {
75 joinGroup(group);
76 }
77
78 asyncRead();
79
80 NDN_LOG_TRACE("enumerating links");
81 sendDumpRequest(RTM_GETLINK);
82 m_isEnumeratingLinks = true;
83}
84
85NetworkMonitorImplNetlink::~NetworkMonitorImplNetlink()
86{
87 boost::system::error_code error;
88 m_socket->close(error);
89}
90
91shared_ptr<const NetworkInterface>
92NetworkMonitorImplNetlink::getNetworkInterface(const std::string& ifname) const
93{
94 for (const auto& e : m_interfaces) {
95 if (e.second->getName() == ifname)
96 return e.second;
97 }
98 return nullptr;
99}
100
101std::vector<shared_ptr<const NetworkInterface>>
102NetworkMonitorImplNetlink::listNetworkInterfaces() const
103{
104 std::vector<shared_ptr<const NetworkInterface>> v;
105 v.reserve(m_interfaces.size());
106
107 for (const auto& e : m_interfaces) {
108 v.push_back(e.second);
109 }
110 return v;
111}
112
113bool
114NetworkMonitorImplNetlink::isEnumerating() const
115{
116 return m_isEnumeratingLinks || m_isEnumeratingAddresses;
117}
118
119void
120NetworkMonitorImplNetlink::initSocket(int family)
121{
122 int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, family);
123 if (fd < 0) {
124 BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
125 }
126 m_socket->assign(fd);
127
128 // increase socket receive buffer to 1MB to avoid losing messages
129 const int bufsize = 1 * 1024 * 1024;
130 if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
131 // not a fatal error
132 NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
133 }
134
135 sockaddr_nl addr{};
136 addr.nl_family = AF_NETLINK;
137 if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
138 BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")"));
139 }
140
141 // find out what pid has been assigned to us
142 socklen_t len = sizeof(addr);
143 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
144 BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")"));
145 }
146 if (len != sizeof(addr)) {
147 BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
148 }
149 if (addr.nl_family != AF_NETLINK) {
150 BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
151 }
152 m_pid = addr.nl_pid;
153 NDN_LOG_TRACE("our pid is " << m_pid);
154
155#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
156 // enable extended ACK reporting
157 const int one = 1;
158 if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
159 // not a fatal error
160 NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
161 }
162#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
163}
164
165void
166NetworkMonitorImplNetlink::joinGroup(int group)
167{
168 if (::setsockopt(m_socket->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
169 &group, sizeof(group)) < 0) {
170 BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
171 " (" + std::strerror(errno) + ")"));
172 }
173}
174
175void
176NetworkMonitorImplNetlink::sendDumpRequest(uint16_t nlmsgType)
177{
178 auto request = make_shared<RtnlRequest>();
179 request->nlh.nlmsg_len = sizeof(RtnlRequest);
180 request->nlh.nlmsg_type = nlmsgType;
181 request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
182 request->nlh.nlmsg_seq = ++m_sequenceNo;
183 request->nlh.nlmsg_pid = m_pid;
184 request->ifi.ifi_family = AF_UNSPEC;
185 request->rta.rta_type = IFLA_EXT_MASK;
186 request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
187 request->rtext = RTEXT_FILTER_SKIP_STATS;
188
189 boost::asio::async_write(*m_socket, boost::asio::buffer(request.get(), sizeof(RtnlRequest)),
190 // capture 'request' to prevent its premature deallocation
191 [request] (const boost::system::error_code& error, size_t) {
192 if (!error) {
193 auto type = request->nlh.nlmsg_type;
194 NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
195 << " seq=" << request->nlh.nlmsg_seq);
196 }
197 else if (error != boost::asio::error::operation_aborted) {
198 NDN_LOG_ERROR("write failed: " << error.message());
199 BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + error.message() + ")"));
200 }
201 });
202}
203
204void
205NetworkMonitorImplNetlink::asyncRead()
206{
207 m_socket->async_read_some(boost::asio::buffer(m_buffer),
208 // capture a copy of 'm_socket' to prevent its deallocation while the handler is still pending
209 [this, socket = m_socket] (auto&&... args) {
210 this->handleRead(std::forward<decltype(args)>(args)..., socket);
211 });
212}
213
214void
215NetworkMonitorImplNetlink::handleRead(const boost::system::error_code& error, size_t nBytesRead,
216 const shared_ptr<boost::asio::posix::stream_descriptor>& socket)
217{
218 if (!socket->is_open() ||
219 error == boost::asio::error::operation_aborted) {
220 // socket was closed, ignore the error
221 NDN_LOG_TRACE("socket closed or operation aborted");
222 return;
223 }
224 if (error) {
225 NDN_LOG_ERROR("read failed: " << error.message());
226 BOOST_THROW_EXCEPTION(Error("Netlink socket read failed (" + error.message() + ")"));
227 }
228
229 NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
230
231 NetlinkMessage nlmsg(m_buffer.data(), nBytesRead);
232 for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
233 NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
234 "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
235 " len=" << nlmsg->nlmsg_len <<
236 " seq=" << nlmsg->nlmsg_seq <<
237 " pid=" << nlmsg->nlmsg_pid);
238
239 if (isEnumerating() && (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_sequenceNo)) {
240 NDN_LOG_TRACE("seq/pid mismatch, ignoring");
241 continue;
242 }
243
244 if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
245 NDN_LOG_ERROR("netlink dump is inconsistent");
246 // TODO: discard the rest of the message and retry the dump
247 break;
248 }
249
250 if (nlmsg->nlmsg_type == NLMSG_DONE) {
251 break;
252 }
253
254 parseNetlinkMessage(nlmsg);
255 }
256
257 if (nlmsg->nlmsg_type == NLMSG_DONE) {
258 if (m_isEnumeratingLinks) {
259 // links enumeration complete, now request all the addresses
260 m_isEnumeratingLinks = false;
261 NDN_LOG_TRACE("enumerating addresses");
262 sendDumpRequest(RTM_GETADDR);
263 m_isEnumeratingAddresses = true;
264 }
265 else if (m_isEnumeratingAddresses) {
266 // links and addresses enumeration complete
267 m_isEnumeratingAddresses = false;
268 // TODO: enumerate routes
269 NDN_LOG_DEBUG("enumeration complete");
270 this->emitSignal(onEnumerationCompleted);
271 }
272 }
273
274 asyncRead();
275}
276
277void
278NetworkMonitorImplNetlink::parseNetlinkMessage(const NetlinkMessage& nlmsg)
279{
280 switch (nlmsg->nlmsg_type) {
281 case RTM_NEWLINK:
282 case RTM_DELLINK:
283 parseLinkMessage(nlmsg);
284 if (!isEnumerating())
285 this->emitSignal(onNetworkStateChanged); // backward compat
286 break;
287
288 case RTM_NEWADDR:
289 case RTM_DELADDR:
290 parseAddressMessage(nlmsg);
291 if (!isEnumerating())
292 this->emitSignal(onNetworkStateChanged); // backward compat
293 break;
294
295 case RTM_NEWROUTE:
296 case RTM_DELROUTE:
297 parseRouteMessage(nlmsg);
298 if (!isEnumerating())
299 this->emitSignal(onNetworkStateChanged); // backward compat
300 break;
301
302 case NLMSG_ERROR:
303 parseErrorMessage(nlmsg);
304 break;
305 }
306}
307
308static InterfaceType
309ifiTypeToInterfaceType(uint16_t type)
310{
311 switch (type) {
312 case ARPHRD_ETHER:
313 return InterfaceType::ETHERNET;
314 case ARPHRD_LOOPBACK:
315 return InterfaceType::LOOPBACK;
316 default:
317 return InterfaceType::UNKNOWN;
318 }
319}
320
321static AddressFamily
322ifaFamilyToAddressFamily(uint8_t family)
323{
324 switch (family) {
325 case AF_INET:
326 return AddressFamily::V4;
327 case AF_INET6:
328 return AddressFamily::V6;
329 default:
330 return AddressFamily::UNSPECIFIED;
331 }
332}
333
334static AddressScope
335ifaScopeToAddressScope(uint8_t scope)
336{
337 switch (scope) {
338 case RT_SCOPE_NOWHERE:
339 return AddressScope::NOWHERE;
340 case RT_SCOPE_HOST:
341 return AddressScope::HOST;
342 case RT_SCOPE_LINK:
343 return AddressScope::LINK;
344 default:
345 return AddressScope::GLOBAL;
346 }
347}
348
349void
350NetworkMonitorImplNetlink::parseLinkMessage(const NetlinkMessage& nlmsg)
351{
352 const ifinfomsg* ifi = nlmsg.getPayload<ifinfomsg>();
353 if (ifi == nullptr) {
354 NDN_LOG_WARN("malformed ifinfomsg");
355 return;
356 }
357
358 if (ifiTypeToInterfaceType(ifi->ifi_type) == InterfaceType::UNKNOWN) {
359 NDN_LOG_DEBUG("unhandled interface type " << ifi->ifi_type);
360 return;
361 }
362
363 shared_ptr<NetworkInterface> interface;
364 auto it = m_interfaces.find(ifi->ifi_index);
365 if (it != m_interfaces.end()) {
366 interface = it->second;
367 BOOST_ASSERT(interface != nullptr);
368 BOOST_ASSERT(interface->getIndex() == ifi->ifi_index);
369 }
370
371 if (nlmsg->nlmsg_type == RTM_DELLINK) {
372 if (interface != nullptr) {
373 NDN_LOG_DEBUG("removing interface " << interface->getName());
374 m_interfaces.erase(it);
375 this->emitSignal(onInterfaceRemoved, interface);
376 }
377 return;
378 }
379
380 if (interface == nullptr) {
381 interface = makeNetworkInterface();
382 interface->setIndex(ifi->ifi_index);
383 }
384 interface->setType(ifiTypeToInterfaceType(ifi->ifi_type));
385 interface->setFlags(ifi->ifi_flags);
386
387 auto attrs = nlmsg.getAttributes<rtattr>(ifi);
388 NDN_LOG_TRACE("message contains " << attrs.size() << " attributes");
389
390 auto address = attrs.getAttributeByType<ethernet::Address>(IFLA_ADDRESS);
391 if (address)
392 interface->setEthernetAddress(*address);
393
394 auto broadcast = attrs.getAttributeByType<ethernet::Address>(IFLA_BROADCAST);
395 if (broadcast)
396 interface->setEthernetBroadcastAddress(*broadcast);
397
398 auto name = attrs.getAttributeByType<std::string>(IFLA_IFNAME);
399 if (name)
400 interface->setName(*name);
401
402 auto mtu = attrs.getAttributeByType<uint32_t>(IFLA_MTU);
403 if (mtu)
404 interface->setMtu(*mtu);
405
406 auto state = attrs.getAttributeByType<uint8_t>(IFLA_OPERSTATE);
407 updateInterfaceState(*interface, state ? *state : linux_if::OPER_STATE_UNKNOWN);
408
409 if (it == m_interfaces.end()) {
410 NDN_LOG_DEBUG("adding interface " << interface->getName());
411 m_interfaces[interface->getIndex()] = interface;
412 this->emitSignal(onInterfaceAdded, interface);
413 }
414}
415
416void
417NetworkMonitorImplNetlink::parseAddressMessage(const NetlinkMessage& nlmsg)
418{
419 const ifaddrmsg* ifa = nlmsg.getPayload<ifaddrmsg>();
420 if (ifa == nullptr) {
421 NDN_LOG_WARN("malformed ifaddrmsg");
422 return;
423 }
424
425 auto it = m_interfaces.find(ifa->ifa_index);
426 if (it == m_interfaces.end()) {
427 // unknown interface, ignore message
428 NDN_LOG_TRACE("unknown interface index " << ifa->ifa_index);
429 return;
430 }
431 auto interface = it->second;
432 BOOST_ASSERT(interface != nullptr);
433
434 auto attrs = nlmsg.getAttributes<rtattr>(ifa);
435 NDN_LOG_TRACE("message contains " << attrs.size() << " attributes");
436
437 namespace ip = boost::asio::ip;
438 ip::address ipAddr, broadcastAddr;
439 if (ifa->ifa_family == AF_INET) {
440 auto v4 = attrs.getAttributeByType<ip::address_v4>(IFA_LOCAL);
441 if (v4)
442 ipAddr = *v4;
443
444 v4 = attrs.getAttributeByType<ip::address_v4>(IFA_BROADCAST);
445 if (v4)
446 broadcastAddr = *v4;
447 }
448 else if (ifa->ifa_family == AF_INET6) {
449 auto v6 = attrs.getAttributeByType<ip::address_v6>(IFA_ADDRESS);
450 if (v6) {
451 if (v6->is_link_local())
452 v6->scope_id(ifa->ifa_index);
453
454 ipAddr = *v6;
455 }
456 }
457
458 uint32_t flags = ifa->ifa_flags; // overwritten by IFA_FLAGS if supported and present
459#ifdef NDN_CXX_HAVE_IFA_FLAGS
460 auto extFlags = attrs.getAttributeByType<uint32_t>(IFA_FLAGS);
461 if (extFlags)
462 flags = *extFlags;
463#endif // NDN_CXX_HAVE_IFA_FLAGS
464
465 NetworkAddress address(ifaFamilyToAddressFamily(ifa->ifa_family),
466 ipAddr,
467 broadcastAddr,
468 ifa->ifa_prefixlen,
469 ifaScopeToAddressScope(ifa->ifa_scope),
470 flags);
471 BOOST_ASSERT(address.getFamily() != AddressFamily::UNSPECIFIED);
472
473 if (nlmsg->nlmsg_type == RTM_NEWADDR)
474 interface->addNetworkAddress(address);
475 else if (nlmsg->nlmsg_type == RTM_DELADDR)
476 interface->removeNetworkAddress(address);
477}
478
479void
480NetworkMonitorImplNetlink::parseRouteMessage(const NetlinkMessage& nlmsg)
481{
482 // TODO
483}
484
485void
486NetworkMonitorImplNetlink::parseErrorMessage(const NetlinkMessage& nlmsg)
487{
488 const nlmsgerr* err = nlmsg.getPayload<nlmsgerr>();
489 if (err == nullptr) {
490 NDN_LOG_WARN("malformed nlmsgerr");
491 return;
492 }
493
494 if (err->error == 0) {
495 // an error code of zero indicates an ACK message, not an error
496 NDN_LOG_TRACE("ACK");
497 return;
498 }
499
500 NDN_LOG_ERROR("NLMSG_ERROR: " << std::strerror(std::abs(err->error)));
501
502#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
503 if (!(nlmsg->nlmsg_flags & NLM_F_ACK_TLVS))
504 return;
505
506 size_t errLen = NLMSG_LENGTH(sizeof(nlmsgerr));
507 if (!(nlmsg->nlmsg_flags & NLM_F_CAPPED))
508 errLen += err->msg.nlmsg_len - NLMSG_HDRLEN; // don't count the inner nlmsghdr twice
509
510 if (nlmsg->nlmsg_len <= errLen)
511 return;
512
513 auto nla = reinterpret_cast<const nlattr*>(reinterpret_cast<const uint8_t*>(&*nlmsg) + errLen);
514 auto attrs = NetlinkMessageAttributes<nlattr>(nla, nlmsg->nlmsg_len - errLen);
515 auto msg = attrs.getAttributeByType<std::string>(NLMSGERR_ATTR_MSG);
516 if (msg)
517 NDN_LOG_ERROR("kernel message: " << *msg);
518#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
519}
520
521void
522NetworkMonitorImplNetlink::updateInterfaceState(NetworkInterface& interface, uint8_t operState)
523{
524 if (operState == linux_if::OPER_STATE_UP) {
525 interface.setState(InterfaceState::RUNNING);
526 }
527 else if (operState == linux_if::OPER_STATE_DORMANT) {
528 interface.setState(InterfaceState::DORMANT);
529 }
530 else {
531 // fallback to flags
532 auto flags = interface.getFlags();
533 if ((flags & linux_if::FLAG_LOWER_UP) && !(flags & linux_if::FLAG_DORMANT))
534 interface.setState(InterfaceState::RUNNING);
535 else if (flags & IFF_UP)
536 interface.setState(InterfaceState::NO_CARRIER);
537 else
538 interface.setState(InterfaceState::DOWN);
539 }
540}
541
542} // namespace net
543} // namespace ndn