net: NetworkMonitorImplOsx: minimize number of calls to getifaddrs()

Change-Id: I952d68bea780d2a237bffee29adea00dbf8d128a
Refs: #3817
diff --git a/src/net/detail/network-monitor-impl-osx.cpp b/src/net/detail/network-monitor-impl-osx.cpp
index 16b32bb..34d53ed 100644
--- a/src/net/detail/network-monitor-impl-osx.cpp
+++ b/src/net/detail/network-monitor-impl-osx.cpp
@@ -74,6 +74,34 @@
 
 NDN_LOG_INIT(ndn.NetworkMonitor);
 
+class IfAddrs : noncopyable
+{
+public:
+  IfAddrs()
+  {
+    if (::getifaddrs(&m_ifaList) < 0) {
+      BOOST_THROW_EXCEPTION(NetworkMonitorImplOsx::Error(std::string("getifaddrs() failed: ") +
+                                                         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)
@@ -97,8 +125,6 @@
                                   nullptr, // object to observe
                                   CFNotificationSuspensionBehaviorDeliverImmediately);
 
-  io.post([this] { enumerateInterfaces(); });
-
   CFRunLoopAddSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
 
   // Notifications from SystemConfiguration:
@@ -117,6 +143,8 @@
   // CFArrayAppendValue(patterns, CFSTR("State:/Network/Global/IPv4"));
 
   SCDynamicStoreSetNotificationKeys(m_scStore.get(), nullptr, patterns);
+
+  io.post([this] { enumerateInterfaces(); });
 }
 
 NetworkMonitorImplOsx::~NetworkMonitorImplOsx()
@@ -160,40 +188,19 @@
 NetworkMonitorImplOsx::scheduleCfLoop()
 {
   // poll each second for new events
-  m_cfLoopEvent = m_scheduler.scheduleEvent(time::seconds(1), [this] { pollCfLoop(); });
-}
-
-void
-NetworkMonitorImplOsx::pollCfLoop()
-{
-  // this should dispatch ready events and exit
-  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
-
-  scheduleCfLoop();
-}
-
-void
-NetworkMonitorImplOsx::addNewInterface(const std::string& ifName)
-{
-  shared_ptr<NetworkInterface> interface = makeNetworkInterface();
-  interface->setName(ifName);
-  interface->setState(getInterfaceState(interface->getName()));
-  updateInterfaceInfo(*interface);
-  if (interface->getType() == InterfaceType::UNKNOWN) {
-    NDN_LOG_DEBUG("ignoring " << ifName << " because it has unhandled interface type");
-    return;
-  }
-
-  NDN_LOG_DEBUG("adding interface " << interface->getName());
-  m_interfaces.insert(make_pair(interface->getName(), interface));
-  this->emitSignal(onInterfaceAdded, interface);
+  m_cfLoopEvent = m_scheduler.scheduleEvent(time::seconds(1), [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);
+    addNewInterface(ifName, ifaList);
   }
   this->emitSignal(onEnumerationCompleted);
 }
@@ -219,7 +226,7 @@
 }
 
 std::set<std::string>
-NetworkMonitorImplOsx::getInterfaceNames()
+NetworkMonitorImplOsx::getInterfaceNames() const
 {
   CFReleaser<CFDictionaryRef> dict =
     (CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), CFSTR("State:/Network/Interface"));
@@ -234,11 +241,29 @@
   return ifNames;
 }
 
+void
+NetworkMonitorImplOsx::addNewInterface(const std::string& ifName, const IfAddrs& ifaList)
+{
+  shared_ptr<NetworkInterface> interface = makeNetworkInterface();
+  interface->setName(ifName);
+  interface->setState(getInterfaceState(interface->getName()));
+  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 std::string& ifName)
+NetworkMonitorImplOsx::getInterfaceState(const std::string& ifName) const
 {
   CFReleaser<CFStringRef> linkName =
-    CFStringCreateWithCString(nullptr, ("State:/Network/Interface/" + ifName + "/Link").c_str(),
+    CFStringCreateWithCString(nullptr, ("State:/Network/Interface/" + ifName + "/Link").data(),
                               kCFStringEncodingASCII);
 
   CFReleaser<CFDictionaryRef> dict = (CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), linkName.get());
@@ -254,6 +279,20 @@
   return CFBooleanGetValue(isActive) ? InterfaceState::RUNNING : InterfaceState::DOWN;
 }
 
+size_t
+NetworkMonitorImplOsx::getInterfaceMtu(const std::string& ifName)
+{
+  ifreq ifr{};
+  std::strncpy(ifr.ifr_name, ifName.data(), sizeof(ifr.ifr_name) - 1);
+
+  if (::ioctl(m_nullUdpSocket.native_handle(), SIOCGIFMTU, &ifr) == 0) {
+    return static_cast<size_t>(ifr.ifr_mtu);
+  }
+
+  NDN_LOG_WARN("failed to get MTU of " << ifName << ": " << std::strerror(errno));
+  return ethernet::MAX_DATA_LEN;
+}
+
 template<typename AddressBytes>
 static uint8_t
 computePrefixLength(const AddressBytes& mask)
@@ -269,14 +308,9 @@
 }
 
 void
-NetworkMonitorImplOsx::updateInterfaceInfo(NetworkInterface& netif)
+NetworkMonitorImplOsx::updateInterfaceInfo(NetworkInterface& netif, const IfAddrs& ifaList)
 {
-  ifaddrs* ifa_list = nullptr;
-  if (::getifaddrs(&ifa_list) < 0) {
-    BOOST_THROW_EXCEPTION(Error(std::string("getifaddrs() failed: ") + strerror(errno)));
-  }
-
-  for (ifaddrs* ifa = ifa_list; ifa != nullptr; ifa = ifa->ifa_next) {
+  for (ifaddrs* ifa = ifaList.get(); ifa != nullptr; ifa = ifa->ifa_next) {
     if (ifa->ifa_name != netif.getName())
       continue;
 
@@ -327,7 +361,7 @@
         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() << ": set Ethernet address " << netif.getEthernetAddress());
+          NDN_LOG_TRACE(netif.getName() << " has Ethernet address " << netif.getEthernetAddress());
         }
         else if (sdl->sdl_type == IFT_LOOP) {
           netif.setType(InterfaceType::LOOPBACK);
@@ -347,7 +381,6 @@
         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);
-        NDN_LOG_TRACE(netif.getName() << ": set IPv4 broadcast address " << broadcastAddr);
       }
     }
 
@@ -362,22 +395,6 @@
 
     netif.addNetworkAddress(NetworkAddress(addrFamily, ipAddr, broadcastAddr, prefixLength, scope, 0));
   }
-
-  ::freeifaddrs(ifa_list);
-}
-
-size_t
-NetworkMonitorImplOsx::getInterfaceMtu(const std::string& ifName)
-{
-  ifreq ifr{};
-  std::strncpy(ifr.ifr_name, ifName.c_str(), sizeof(ifr.ifr_name) - 1);
-
-  if (::ioctl(m_nullUdpSocket.native_handle(), SIOCGIFMTU, &ifr) == 0) {
-    return static_cast<size_t>(ifr.ifr_mtu);
-  }
-
-  NDN_LOG_WARN("Failed to get interface MTU: " << std::strerror(errno));
-  return ethernet::MAX_DATA_LEN;
 }
 
 void
@@ -389,6 +406,8 @@
 void
 NetworkMonitorImplOsx::onConfigChanged(CFArrayRef changedKeys)
 {
+  IfAddrs ifaList;
+
   size_t count = CFArrayGetCount(changedKeys);
   for (size_t i = 0; i != count; ++i) {
     std::string keyName = convertToStdString((CFStringRef)CFArrayGetValueAtIndex(changedKeys, i));
@@ -397,7 +416,7 @@
 
     auto ifIt = m_interfaces.find(ifName);
     if (ifIt == m_interfaces.end()) {
-      addNewInterface(ifName);
+      addNewInterface(ifName, ifaList);
       return;
     }
 
@@ -412,27 +431,23 @@
 
     if (key.at(-1).toUri() == "Link") {
       auto newState = getInterfaceState(ifName);
-
       if (newState == InterfaceState::UNKNOWN) {
         // check if it is really unknown or interface removed
         if (getInterfaceNames().count(ifName) == 0) {
-          // newState = InterfaceState::DOWN;
           removeInterface();
           return;
         }
       }
-
-      NDN_LOG_TRACE("Status of " << ifName << " changed from " << netif.getState() << " to " << newState);
+      NDN_LOG_TRACE(ifName << " status changed from " << netif.getState() << " to " << newState);
       netif.setState(newState);
     }
 
     if (key.at(-1).toUri() == "IPv4" || key.at(-1).toUri() == "IPv6") {
       shared_ptr<NetworkInterface> updatedInterface = makeNetworkInterface();
       updatedInterface->setName(ifName);
-      updateInterfaceInfo(*updatedInterface);
+      updateInterfaceInfo(*updatedInterface, ifaList);
       if (updatedInterface->getType() == InterfaceType::UNKNOWN) {
-        // somehow, type of interface changed to unknown
-        NDN_LOG_DEBUG("Removing " << ifName << " because it changed to unhandled interface type");
+        NDN_LOG_DEBUG(ifName << " type changed to unknown");
         removeInterface();
         return;
       }
@@ -442,17 +457,14 @@
 
       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);
       }
diff --git a/src/net/detail/network-monitor-impl-osx.hpp b/src/net/detail/network-monitor-impl-osx.hpp
index 8de354c..7339153 100644
--- a/src/net/detail/network-monitor-impl-osx.hpp
+++ b/src/net/detail/network-monitor-impl-osx.hpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
@@ -41,6 +41,8 @@
 namespace ndn {
 namespace net {
 
+class IfAddrs;
+
 class NetworkMonitorImplOsx : public NetworkMonitorImpl
 {
 public:
@@ -65,38 +67,33 @@
   std::vector<shared_ptr<const NetworkInterface>>
   listNetworkInterfaces() const final;
 
+private:
   static void
-  afterNotificationCenterEvent(CFNotificationCenterRef center,
-                               void* observer,
-                               CFStringRef name,
-                               const void* object,
+  afterNotificationCenterEvent(CFNotificationCenterRef center, void* observer,
+                               CFStringRef name, const void* object,
                                CFDictionaryRef userInfo);
 
-private:
   void
   scheduleCfLoop();
 
   void
-  pollCfLoop();
-
-  void
-  addNewInterface(const std::string& ifName);
-
-  void
   enumerateInterfaces();
 
   std::set<std::string>
-  getInterfaceNames();
-
-  InterfaceState
-  getInterfaceState(const std::string& ifName);
+  getInterfaceNames() const;
 
   void
-  updateInterfaceInfo(NetworkInterface& netif);
+  addNewInterface(const std::string& ifName, const IfAddrs& ifaList);
+
+  InterfaceState
+  getInterfaceState(const std::string& ifName) const;
 
   size_t
   getInterfaceMtu(const std::string& ifName);
 
+  void
+  updateInterfaceInfo(NetworkInterface& netif, const IfAddrs& ifaList);
+
   static void
   onConfigChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void* context);