blob: d637b39dc296f7e11d5bd6c08aaac8edd6396f1a [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.
*
*
* Parts of this implementation is based on daemondo command of MacPorts
* (https://www.macports.org/):
*
* Copyright (c) 2005-2007 James Berry <jberry@macports.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of The MacPorts Project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "ndn-cxx/net/detail/network-monitor-impl-osx.hpp"
#include "ndn-cxx/net/network-address.hpp"
#include "ndn-cxx/name.hpp"
#include "ndn-cxx/util/cf-string-osx.hpp"
#include "ndn-cxx/util/logger.hpp"
#include <ifaddrs.h> // for getifaddrs()
#include <net/if.h> // for if_nametoindex()
#include <net/if_dl.h> // for struct sockaddr_dl
#include <net/if_types.h> // for IFT_* constants
#include <netinet/in.h> // for struct sockaddr_in{,6}
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/udp.hpp>
NDN_LOG_INIT(ndn.NetworkMonitor);
namespace ndn {
namespace net {
using util::CFReleaser;
class IfAddrs : noncopyable
{
public:
IfAddrs()
{
if (::getifaddrs(&m_ifaList) < 0) {
BOOST_THROW_EXCEPTION(NetworkMonitorImplOsx::Error("getifaddrs() failed: "s + strerror(errno)));
}
}
~IfAddrs()
{
if (m_ifaList != nullptr) {
::freeifaddrs(m_ifaList);
}
}
ifaddrs*
get() const noexcept
{
return m_ifaList;
}
private:
ifaddrs* m_ifaList = nullptr;
};
NetworkMonitorImplOsx::NetworkMonitorImplOsx(boost::asio::io_service& io)
: m_scheduler(io)
, m_cfLoopEvent(m_scheduler)
, m_context{0, this, nullptr, nullptr, nullptr}
, m_scStore(SCDynamicStoreCreate(nullptr, CFSTR("net.named-data.ndn-cxx.NetworkMonitor"),
&NetworkMonitorImplOsx::onConfigChanged, &m_context))
, m_loopSource(SCDynamicStoreCreateRunLoopSource(nullptr, m_scStore.get(), 0))
, m_ioctlSocket(io, boost::asio::ip::udp::v4())
{
scheduleCfLoop();
// Notifications from Darwin Notify Center:
//
// com.apple.system.config.network_change
//
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
static_cast<void*>(this),
&NetworkMonitorImplOsx::afterNotificationCenterEvent,
CFSTR("com.apple.system.config.network_change"),
nullptr, // object to observe
CFNotificationSuspensionBehaviorDeliverImmediately);
CFRunLoopAddSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
// Notifications from SystemConfiguration:
//
// State:/Network/Interface/.*/Link
// State:/Network/Interface/.*/IPv4
// State:/Network/Interface/.*/IPv6
// State:/Network/Interface/.*/AirPort (not used)
//
// https://developer.apple.com/library/content/documentation/Networking/Conceptual/SystemConfigFrameworks/SC_UnderstandSchema/SC_UnderstandSchema.html
//
auto patterns = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/Link"));
CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/IPv4"));
CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/IPv6"));
if (!SCDynamicStoreSetNotificationKeys(m_scStore.get(), nullptr, patterns)) {
BOOST_THROW_EXCEPTION(Error("SCDynamicStoreSetNotificationKeys failed"));
}
io.post([this] { enumerateInterfaces(); });
}
NetworkMonitorImplOsx::~NetworkMonitorImplOsx()
{
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDarwinNotifyCenter(),
static_cast<void*>(this));
}
shared_ptr<const NetworkInterface>
NetworkMonitorImplOsx::getNetworkInterface(const std::string& ifname) const
{
auto it = m_interfaces.find(ifname);
return it == m_interfaces.end() ? nullptr : it->second;
}
std::vector<shared_ptr<const NetworkInterface>>
NetworkMonitorImplOsx::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;
}
void
NetworkMonitorImplOsx::afterNotificationCenterEvent(CFNotificationCenterRef center,
void* observer,
CFStringRef name,
const void* object,
CFDictionaryRef userInfo)
{
static_cast<NetworkMonitorImplOsx*>(observer)->emitSignal(onNetworkStateChanged);
}
void
NetworkMonitorImplOsx::scheduleCfLoop()
{
// poll each second for new events
m_cfLoopEvent = m_scheduler.scheduleEvent(1_s, [this] {
// this should dispatch ready events and exit
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
scheduleCfLoop();
});
}
void
NetworkMonitorImplOsx::enumerateInterfaces()
{
IfAddrs ifaList;
for (const auto& ifName : getInterfaceNames()) {
addNewInterface(ifName, ifaList);
}
this->emitSignal(onEnumerationCompleted);
}
std::set<std::string>
NetworkMonitorImplOsx::getInterfaceNames() const
{
CFReleaser<CFDictionaryRef> dict =
(CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), CFSTR("State:/Network/Interface"));
if (dict.get() == nullptr) {
return {};
}
CFArrayRef interfaces = (CFArrayRef)CFDictionaryGetValue(dict.get(), CFSTR("Interfaces"));
if (interfaces == nullptr) {
return {};
}
std::set<std::string> ifNames;
size_t count = CFArrayGetCount(interfaces);
for (size_t i = 0; i != count; ++i) {
auto ifName = (CFStringRef)CFArrayGetValueAtIndex(interfaces, i);
ifNames.insert(util::cfstring::toStdString(ifName));
}
return ifNames;
}
void
NetworkMonitorImplOsx::addNewInterface(const std::string& ifName, const IfAddrs& ifaList)
{
shared_ptr<NetworkInterface> interface = makeNetworkInterface();
interface->setName(ifName);
interface->setState(getInterfaceState(*interface));
updateInterfaceInfo(*interface, ifaList);
if (interface->getType() == InterfaceType::UNKNOWN) {
NDN_LOG_DEBUG("ignoring " << ifName << " due to unhandled interface type");
return;
}
NDN_LOG_DEBUG("adding interface " << interface->getName());
m_interfaces[interface->getName()] = interface;
this->emitSignal(onInterfaceAdded, interface);
}
InterfaceState
NetworkMonitorImplOsx::getInterfaceState(const NetworkInterface& netif) const
{
CFReleaser<CFStringRef> linkName =
util::cfstring::fromStdString("State:/Network/Interface/" + netif.getName() + "/Link");
CFReleaser<CFDictionaryRef> dict =
(CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), linkName.get());
if (dict.get() == nullptr) {
return InterfaceState::UNKNOWN;
}
CFBooleanRef isActive = (CFBooleanRef)CFDictionaryGetValue(dict.get(), CFSTR("Active"));
if (isActive == nullptr) {
return InterfaceState::UNKNOWN;
}
return CFBooleanGetValue(isActive) ? InterfaceState::RUNNING : InterfaceState::DOWN;
}
size_t
NetworkMonitorImplOsx::getInterfaceMtu(const NetworkInterface& netif)
{
ifreq ifr{};
std::strncpy(ifr.ifr_name, netif.getName().data(), sizeof(ifr.ifr_name) - 1);
if (::ioctl(m_ioctlSocket.native_handle(), SIOCGIFMTU, &ifr) == 0) {
return static_cast<size_t>(ifr.ifr_mtu);
}
NDN_LOG_WARN("failed to get MTU of " << netif.getName() << ": " << std::strerror(errno));
return ethernet::MAX_DATA_LEN;
}
template<typename AddressBytes>
static uint8_t
computePrefixLength(const AddressBytes& mask)
{
uint8_t prefixLength = 0;
for (auto byte : mask) {
while (byte != 0) {
++prefixLength;
byte <<= 1;
}
}
return prefixLength;
}
void
NetworkMonitorImplOsx::updateInterfaceInfo(NetworkInterface& netif, const IfAddrs& ifaList)
{
BOOST_ASSERT(!netif.getName().empty());
netif.setMtu(getInterfaceMtu(netif));
for (ifaddrs* ifa = ifaList.get(); ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_name != netif.getName())
continue;
netif.setFlags(ifa->ifa_flags);
if (ifa->ifa_addr == nullptr)
continue;
namespace ip = boost::asio::ip;
AddressFamily addrFamily = AddressFamily::UNSPECIFIED;
ip::address ipAddr, broadcastAddr;
uint8_t prefixLength = 0;
switch (ifa->ifa_addr->sa_family) {
case AF_INET: {
addrFamily = AddressFamily::V4;
const sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ifa->ifa_addr);
ip::address_v4::bytes_type bytes;
std::copy_n(reinterpret_cast<const unsigned char*>(&sin->sin_addr), bytes.size(), bytes.begin());
ipAddr = ip::address_v4(bytes);
const sockaddr_in* sinMask = reinterpret_cast<sockaddr_in*>(ifa->ifa_netmask);
std::copy_n(reinterpret_cast<const unsigned char*>(&sinMask->sin_addr), bytes.size(), bytes.begin());
prefixLength = computePrefixLength(bytes);
break;
}
case AF_INET6: {
addrFamily = AddressFamily::V6;
const sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ifa->ifa_addr);
ip::address_v6::bytes_type bytes;
std::copy_n(reinterpret_cast<const unsigned char*>(&sin6->sin6_addr), bytes.size(), bytes.begin());
ip::address_v6 v6Addr(bytes);
if (v6Addr.is_link_local())
v6Addr.scope_id(if_nametoindex(netif.getName().data()));
ipAddr = v6Addr;
const sockaddr_in6* sinMask = reinterpret_cast<sockaddr_in6*>(ifa->ifa_netmask);
std::copy_n(reinterpret_cast<const unsigned char*>(&sinMask->sin6_addr), bytes.size(), bytes.begin());
prefixLength = computePrefixLength(bytes);
break;
}
case AF_LINK: {
const sockaddr_dl* sdl = reinterpret_cast<sockaddr_dl*>(ifa->ifa_addr);
netif.setIndex(sdl->sdl_index);
if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == ethernet::ADDR_LEN) {
netif.setType(InterfaceType::ETHERNET);
netif.setEthernetAddress(ethernet::Address(reinterpret_cast<uint8_t*>(LLADDR(sdl))));
NDN_LOG_TRACE(netif.getName() << " has Ethernet address " << netif.getEthernetAddress());
}
else if (sdl->sdl_type == IFT_LOOP) {
netif.setType(InterfaceType::LOOPBACK);
}
else {
netif.setType(InterfaceType::UNKNOWN);
}
break;
}
}
if (netif.canBroadcast()) {
netif.setEthernetBroadcastAddress(ethernet::getBroadcastAddress());
if (addrFamily == AddressFamily::V4 && ifa->ifa_broadaddr != nullptr) {
const sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ifa->ifa_broadaddr);
ip::address_v4::bytes_type bytes;
std::copy_n(reinterpret_cast<const unsigned char*>(&sin->sin_addr), bytes.size(), bytes.begin());
broadcastAddr = ip::address_v4(bytes);
}
}
if (addrFamily == AddressFamily::UNSPECIFIED)
continue;
AddressScope scope = AddressScope::GLOBAL;
if (ipAddr.is_loopback()) {
scope = AddressScope::HOST;
}
else if ((ipAddr.is_v4() && (ipAddr.to_v4().to_ulong() & 0xFFFF0000) == 0xA9FE0000) ||
(ipAddr.is_v6() && ipAddr.to_v6().is_link_local())) {
scope = AddressScope::LINK;
}
netif.addNetworkAddress(NetworkAddress(addrFamily, ipAddr, broadcastAddr, prefixLength, scope, 0));
}
}
void
NetworkMonitorImplOsx::onConfigChanged(SCDynamicStoreRef m_scStore, CFArrayRef changedKeys, void* context)
{
static_cast<NetworkMonitorImplOsx*>(context)->onConfigChanged(changedKeys);
}
void
NetworkMonitorImplOsx::onConfigChanged(CFArrayRef changedKeys)
{
IfAddrs ifaList;
size_t count = CFArrayGetCount(changedKeys);
for (size_t i = 0; i != count; ++i) {
Name key(util::cfstring::toStdString((CFStringRef)CFArrayGetValueAtIndex(changedKeys, i)));
std::string ifName = key.at(-2).toUri();
auto ifIt = m_interfaces.find(ifName);
if (ifIt == m_interfaces.end()) {
addNewInterface(ifName, ifaList);
return;
}
auto removeInterface = [&] {
NDN_LOG_DEBUG("removing interface " << ifName);
shared_ptr<NetworkInterface> removedNetif = ifIt->second;
m_interfaces.erase(ifIt);
this->emitSignal(onInterfaceRemoved, removedNetif);
};
NetworkInterface& netif = *ifIt->second;
std::string changedItem = key.at(-1).toUri();
if (changedItem == "Link") {
auto newState = getInterfaceState(netif);
if (newState == InterfaceState::UNKNOWN) {
// check if it is really unknown or interface removed
if (getInterfaceNames().count(ifName) == 0) {
removeInterface();
return;
}
}
NDN_LOG_TRACE(ifName << " status changed from " << netif.getState() << " to " << newState);
netif.setState(newState);
}
else if (changedItem == "IPv4" || changedItem == "IPv6") {
auto updatedNetif = makeNetworkInterface();
updatedNetif->setName(ifName);
updateInterfaceInfo(*updatedNetif, ifaList);
if (updatedNetif->getType() == InterfaceType::UNKNOWN) {
NDN_LOG_DEBUG(ifName << " type changed to unknown");
removeInterface();
return;
}
const auto& newAddrs = updatedNetif->getNetworkAddresses();
const auto& oldAddrs = netif.getNetworkAddresses();
std::set<NetworkAddress> added;
std::set<NetworkAddress> removed;
std::set_difference(newAddrs.begin(), newAddrs.end(),
oldAddrs.begin(), oldAddrs.end(), std::inserter(added, added.end()));
std::set_difference(oldAddrs.begin(), oldAddrs.end(),
newAddrs.begin(), newAddrs.end(), std::inserter(removed, removed.end()));
for (const auto& addr : removed) {
netif.removeNetworkAddress(addr);
}
for (const auto& addr : added) {
netif.addNetworkAddress(addr);
}
}
}
}
} // namespace net
} // namespace ndn