blob: e404322489e46b0734edb3297be554ad7515fe60 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2018 Regents of the University of California.
*
* This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*
* @author Davide Pesavento <davide.pesavento@lip6.fr>
*/
#include "network-monitor-impl-netlink.hpp"
#include "linux-if-constants.hpp"
#include "netlink-message.hpp"
#include "../network-address.hpp"
#include "../network-interface.hpp"
#include "../../util/logger.hpp"
#include <linux/if_addr.h>
#include <linux/if_link.h>
#include <net/if_arp.h>
NDN_LOG_INIT(ndn.NetworkMonitor);
namespace ndn {
namespace net {
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");
m_rtnlSocket.sendDumpRequest(RTM_GETLINK,
[this] (const auto& msg) { this->parseRtnlMessage(msg); });
m_isEnumeratingLinks = true;
}
shared_ptr<const NetworkInterface>
NetworkMonitorImplNetlink::getNetworkInterface(const std::string& ifname) const
{
for (const auto& e : m_interfaces) {
if (e.second->getName() == ifname)
return e.second;
}
return nullptr;
}
std::vector<shared_ptr<const NetworkInterface>>
NetworkMonitorImplNetlink::listNetworkInterfaces() const
{
std::vector<shared_ptr<const NetworkInterface>> v;
v.reserve(m_interfaces.size());
for (const auto& e : m_interfaces) {
v.push_back(e.second);
}
return v;
}
bool
NetworkMonitorImplNetlink::isEnumerating() const
{
return m_isEnumeratingLinks || m_isEnumeratingAddresses;
}
void
NetworkMonitorImplNetlink::parseRtnlMessage(const NetlinkMessage& nlmsg)
{
switch (nlmsg->nlmsg_type) {
case RTM_NEWLINK:
case RTM_DELLINK:
parseLinkMessage(nlmsg);
if (!isEnumerating())
this->emitSignal(onNetworkStateChanged); // backward compat
break;
case RTM_NEWADDR:
case RTM_DELADDR:
parseAddressMessage(nlmsg);
if (!isEnumerating())
this->emitSignal(onNetworkStateChanged); // backward compat
break;
case RTM_NEWROUTE:
case RTM_DELROUTE:
parseRouteMessage(nlmsg);
if (!isEnumerating())
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,
[this] (const auto& msg) { this->parseRtnlMessage(msg); });
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;
}
}
static InterfaceType
ifiTypeToInterfaceType(uint16_t type)
{
switch (type) {
case ARPHRD_ETHER:
return InterfaceType::ETHERNET;
case ARPHRD_LOOPBACK:
return InterfaceType::LOOPBACK;
default:
return InterfaceType::UNKNOWN;
}
}
static AddressFamily
ifaFamilyToAddressFamily(uint8_t family)
{
switch (family) {
case AF_INET:
return AddressFamily::V4;
case AF_INET6:
return AddressFamily::V6;
default:
return AddressFamily::UNSPECIFIED;
}
}
static AddressScope
ifaScopeToAddressScope(uint8_t scope)
{
switch (scope) {
case RT_SCOPE_NOWHERE:
return AddressScope::NOWHERE;
case RT_SCOPE_HOST:
return AddressScope::HOST;
case RT_SCOPE_LINK:
return AddressScope::LINK;
default:
return AddressScope::GLOBAL;
}
}
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)
{
const ifinfomsg* ifi = nlmsg.getPayload<ifinfomsg>();
if (ifi == nullptr) {
NDN_LOG_WARN("malformed ifinfomsg");
return;
}
if (ifiTypeToInterfaceType(ifi->ifi_type) == InterfaceType::UNKNOWN) {
NDN_LOG_DEBUG("unhandled interface type " << ifi->ifi_type);
return;
}
shared_ptr<NetworkInterface> interface;
auto it = m_interfaces.find(ifi->ifi_index);
if (it != m_interfaces.end()) {
interface = it->second;
BOOST_ASSERT(interface != nullptr);
BOOST_ASSERT(interface->getIndex() == ifi->ifi_index);
}
if (nlmsg->nlmsg_type == RTM_DELLINK) {
if (interface != nullptr) {
NDN_LOG_DEBUG("removing interface " << interface->getName());
m_interfaces.erase(it);
this->emitSignal(onInterfaceRemoved, interface);
}
return;
}
if (interface == nullptr) {
interface = makeNetworkInterface();
interface->setIndex(ifi->ifi_index);
}
interface->setType(ifiTypeToInterfaceType(ifi->ifi_type));
interface->setFlags(ifi->ifi_flags);
auto attrs = nlmsg.getAttributes<rtattr>(ifi);
NDN_LOG_TRACE("message contains " << attrs.size() << " attributes");
auto address = attrs.getAttributeByType<ethernet::Address>(IFLA_ADDRESS);
if (address)
interface->setEthernetAddress(*address);
auto broadcast = attrs.getAttributeByType<ethernet::Address>(IFLA_BROADCAST);
if (broadcast)
interface->setEthernetBroadcastAddress(*broadcast);
auto name = attrs.getAttributeByType<std::string>(IFLA_IFNAME);
if (name)
interface->setName(*name);
auto mtu = attrs.getAttributeByType<uint32_t>(IFLA_MTU);
if (mtu)
interface->setMtu(*mtu);
auto state = attrs.getAttributeByType<uint8_t>(IFLA_OPERSTATE);
updateInterfaceState(*interface, state ? *state : linux_if::OPER_STATE_UNKNOWN);
if (it == m_interfaces.end()) {
NDN_LOG_DEBUG("adding interface " << interface->getName());
m_interfaces[interface->getIndex()] = interface;
this->emitSignal(onInterfaceAdded, interface);
}
}
void
NetworkMonitorImplNetlink::parseAddressMessage(const NetlinkMessage& nlmsg)
{
const ifaddrmsg* ifa = nlmsg.getPayload<ifaddrmsg>();
if (ifa == nullptr) {
NDN_LOG_WARN("malformed ifaddrmsg");
return;
}
auto it = m_interfaces.find(ifa->ifa_index);
if (it == m_interfaces.end()) {
// unknown interface, ignore message
NDN_LOG_TRACE("unknown interface index " << ifa->ifa_index);
return;
}
auto interface = it->second;
BOOST_ASSERT(interface != nullptr);
auto attrs = nlmsg.getAttributes<rtattr>(ifa);
NDN_LOG_TRACE("message contains " << attrs.size() << " attributes");
namespace ip = boost::asio::ip;
ip::address ipAddr, broadcastAddr;
if (ifa->ifa_family == AF_INET) {
auto v4 = attrs.getAttributeByType<ip::address_v4>(IFA_LOCAL);
if (v4)
ipAddr = *v4;
v4 = attrs.getAttributeByType<ip::address_v4>(IFA_BROADCAST);
if (v4)
broadcastAddr = *v4;
}
else if (ifa->ifa_family == AF_INET6) {
auto v6 = attrs.getAttributeByType<ip::address_v6>(IFA_ADDRESS);
if (v6) {
if (v6->is_link_local())
v6->scope_id(ifa->ifa_index);
ipAddr = *v6;
}
}
uint32_t flags = ifa->ifa_flags; // overwritten by IFA_FLAGS if supported and present
#ifdef NDN_CXX_HAVE_IFA_FLAGS
auto extFlags = attrs.getAttributeByType<uint32_t>(IFA_FLAGS);
if (extFlags)
flags = *extFlags;
#endif // NDN_CXX_HAVE_IFA_FLAGS
NetworkAddress address(ifaFamilyToAddressFamily(ifa->ifa_family),
ipAddr,
broadcastAddr,
ifa->ifa_prefixlen,
ifaScopeToAddressScope(ifa->ifa_scope),
flags);
BOOST_ASSERT(address.getFamily() != AddressFamily::UNSPECIFIED);
if (nlmsg->nlmsg_type == RTM_NEWADDR)
interface->addNetworkAddress(address);
else if (nlmsg->nlmsg_type == RTM_DELADDR)
interface->removeNetworkAddress(address);
}
void
NetworkMonitorImplNetlink::parseRouteMessage(const NetlinkMessage& nlmsg)
{
// TODO
}
void
NetworkMonitorImplNetlink::parseErrorMessage(const NetlinkMessage& nlmsg)
{
const nlmsgerr* err = nlmsg.getPayload<nlmsgerr>();
if (err == nullptr) {
NDN_LOG_WARN("malformed nlmsgerr");
return;
}
if (err->error == 0) {
// an error code of zero indicates an ACK message, not an error
NDN_LOG_TRACE("ACK");
return;
}
NDN_LOG_ERROR("NLMSG_ERROR: " << std::strerror(std::abs(err->error)));
#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
if (!(nlmsg->nlmsg_flags & NLM_F_ACK_TLVS))
return;
size_t errLen = NLMSG_LENGTH(sizeof(nlmsgerr));
if (!(nlmsg->nlmsg_flags & NLM_F_CAPPED))
errLen += err->msg.nlmsg_len - NLMSG_HDRLEN; // don't count the inner nlmsghdr twice
if (nlmsg->nlmsg_len <= errLen)
return;
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 && !msg->empty())
NDN_LOG_ERROR("kernel message: " << *msg);
#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
}
} // namespace net
} // namespace ndn