rib: move entire subdir to daemon/rib

Refs: #4528
Change-Id: I7de03631ddef0f014f12f979373aa449f42486d1
diff --git a/daemon/rib/fib-update.cpp b/daemon/rib/fib-update.cpp
new file mode 100644
index 0000000..25e2a62
--- /dev/null
+++ b/daemon/rib/fib-update.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fib-update.hpp"
+
+namespace nfd {
+namespace rib {
+
+FibUpdate
+FibUpdate::createAddUpdate(const Name& name, const uint64_t faceId, const uint64_t cost)
+{
+  FibUpdate update;
+
+  update.name = name;
+  update.faceId = faceId;
+  update.cost = cost;
+  update.action = ADD_NEXTHOP;
+
+  return update;
+}
+
+FibUpdate
+FibUpdate::createRemoveUpdate(const Name& name, const uint64_t faceId)
+{
+  FibUpdate update;
+
+  update.name = name;
+  update.faceId = faceId;
+  update.action = REMOVE_NEXTHOP;
+
+  return update;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/fib-update.hpp b/daemon/rib/fib-update.hpp
new file mode 100644
index 0000000..5339a37
--- /dev/null
+++ b/daemon/rib/fib-update.hpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_FIB_UPDATE_HPP
+#define NFD_DAEMON_RIB_FIB_UPDATE_HPP
+
+#include "core/common.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** \class FibUpdate
+ *  \brief represents a FIB update
+ */
+class FibUpdate
+{
+public:
+  FibUpdate()
+    : faceId(0)
+    , cost(0)
+  {
+  }
+
+  bool
+  operator==(const FibUpdate& other) const
+  {
+    return (this->name == other.name &&
+            this->faceId == other.faceId &&
+            this->cost == other.cost &&
+            this->action == other.action);
+  }
+
+  static FibUpdate
+  createAddUpdate(const Name& name, const uint64_t faceId, const uint64_t cost);
+
+  static FibUpdate
+  createRemoveUpdate(const Name& name, const uint64_t faceId);
+
+  enum Action {
+    ADD_NEXTHOP    = 0,
+    REMOVE_NEXTHOP = 1
+  };
+
+public:
+  Name name;
+  uint64_t faceId;
+  uint64_t cost;
+  Action action;
+};
+
+inline std::ostream&
+operator<<(std::ostream& os, const FibUpdate& update)
+{
+  os << "FibUpdate("
+     << " Name: " << update.name << ", "
+     << "faceId: " << update.faceId << ", ";
+
+  if (update.action == FibUpdate::ADD_NEXTHOP) {
+    os << "cost: " << update.cost << ", "
+       << "action: ADD_NEXTHOP";
+  }
+  else {
+    os << "action: REMOVE_NEXTHOP";
+  }
+
+  os << ")";
+
+  return os;
+}
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_FIB_UPDATE_HPP
diff --git a/daemon/rib/fib-updater.cpp b/daemon/rib/fib-updater.cpp
new file mode 100644
index 0000000..574b5f9
--- /dev/null
+++ b/daemon/rib/fib-updater.cpp
@@ -0,0 +1,716 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "fib-updater.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(FibUpdater);
+
+using ndn::nfd::ControlParameters;
+
+const unsigned int FibUpdater::MAX_NUM_TIMEOUTS = 10;
+const uint32_t FibUpdater::ERROR_FACE_NOT_FOUND = 410;
+
+FibUpdater::FibUpdater(Rib& rib, ndn::nfd::Controller& controller)
+  : m_rib(rib)
+  , m_controller(controller)
+{
+  rib.setFibUpdater(this);
+}
+
+void
+FibUpdater::computeAndSendFibUpdates(const RibUpdateBatch& batch,
+                                     const FibUpdateSuccessCallback& onSuccess,
+                                     const FibUpdateFailureCallback& onFailure)
+{
+  m_batchFaceId = batch.getFaceId();
+
+  // Erase previously calculated inherited routes
+  m_inheritedRoutes.clear();
+
+  // Erase previously calculated FIB updates
+  m_updatesForBatchFaceId.clear();
+  m_updatesForNonBatchFaceId.clear();
+
+  computeUpdates(batch);
+
+  sendUpdatesForBatchFaceId(onSuccess, onFailure);
+}
+
+void
+FibUpdater::computeUpdates(const RibUpdateBatch& batch)
+{
+  NFD_LOG_DEBUG("Computing updates for batch with faceID: " << batch.getFaceId());
+
+  // Compute updates and add to m_fibUpdates
+  for (const RibUpdate& update : batch) {
+    switch (update.getAction()) {
+      case RibUpdate::REGISTER:
+        computeUpdatesForRegistration(update);
+        break;
+      case RibUpdate::UNREGISTER:
+        computeUpdatesForUnregistration(update);
+        break;
+      case RibUpdate::REMOVE_FACE:
+        computeUpdatesForUnregistration(update);
+
+        // Do not apply updates with the same face ID as the destroyed face
+        // since they will be rejected by the FIB
+        m_updatesForBatchFaceId.clear();
+        break;
+    }
+  }
+}
+
+void
+FibUpdater::computeUpdatesForRegistration(const RibUpdate& update)
+{
+  const Name& prefix = update.getName();
+  const Route& route = update.getRoute();
+
+  Rib::const_iterator it = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (it != m_rib.end()) {
+    shared_ptr<const RibEntry> entry(it->second);
+
+    RibEntry::const_iterator existingRoute = entry->findRoute(route);
+
+    // Route will be new
+    if (existingRoute == entry->end()) {
+      // Will the new route change the namespace's capture flag?
+      bool willCaptureBeTurnedOn = (entry->hasCapture() == false && route.isRibCapture());
+
+      createFibUpdatesForNewRoute(*entry, route, willCaptureBeTurnedOn);
+    }
+    else {
+      // Route already exists
+      RibEntry entryCopy = *entry;
+
+      Route& routeToUpdate = *(entryCopy.findRoute(route));
+
+      routeToUpdate.flags = route.flags;
+      routeToUpdate.cost = route.cost;
+      routeToUpdate.expires = route.expires;
+
+      createFibUpdatesForUpdatedRoute(entryCopy, route, *existingRoute);
+    }
+  }
+  else {
+    // New name in RIB
+    // Find prefix's parent
+    shared_ptr<RibEntry> parent = m_rib.findParent(prefix);
+
+    Rib::RibEntryList descendants = m_rib.findDescendantsForNonInsertedName(prefix);
+    Rib::RibEntryList children;
+
+    for (const auto& descendant : descendants) {
+      // If the child has the same parent as the new entry,
+      // the new entry must be the child's new parent
+      if (descendant->getParent() == parent) {
+        children.push_back(descendant);
+      }
+    }
+
+    createFibUpdatesForNewRibEntry(prefix, route, children);
+  }
+}
+
+void
+FibUpdater::computeUpdatesForUnregistration(const RibUpdate& update)
+{
+  const Name& prefix = update.getName();
+  const Route& route = update.getRoute();
+
+  Rib::const_iterator ribIt = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (ribIt != m_rib.end()) {
+    shared_ptr<const RibEntry> entry(ribIt->second);
+
+    const bool hadCapture = entry->hasCapture();
+
+    RibEntry::const_iterator existing = entry->findRoute(route);
+
+    if (existing != entry->end()) {
+      RibEntry temp = *entry;
+
+      // Erase route in temp entry
+      temp.eraseRoute(route);
+
+      const bool captureWasTurnedOff = (hadCapture && !temp.hasCapture());
+
+      createFibUpdatesForErasedRoute(temp, *existing, captureWasTurnedOff);
+
+      // The RibEntry still has the face ID; need to update FIB
+      // with lowest cost for the same face instead of removing the face from the FIB
+      const Route* next = entry->getRouteWithSecondLowestCostByFaceId(route.faceId);
+
+      if (next != nullptr) {
+        createFibUpdatesForNewRoute(temp, *next, false);
+      }
+
+      // The RibEntry will be empty after this removal
+      if (entry->getNRoutes() == 1) {
+        createFibUpdatesForErasedRibEntry(*entry);
+      }
+    }
+  }
+}
+
+void
+FibUpdater::sendUpdates(const FibUpdateList& updates,
+                        const FibUpdateSuccessCallback& onSuccess,
+                        const FibUpdateFailureCallback& onFailure)
+{
+  std::string updateString = (updates.size() == 1) ? " update" : " updates";
+  NFD_LOG_DEBUG("Applying " << updates.size() << updateString << " to FIB");
+
+  for (const FibUpdate& update : updates) {
+    NFD_LOG_DEBUG("Sending FIB update: " << update);
+
+    if (update.action == FibUpdate::ADD_NEXTHOP) {
+      sendAddNextHopUpdate(update, onSuccess, onFailure);
+    }
+    else if (update.action == FibUpdate::REMOVE_NEXTHOP) {
+      sendRemoveNextHopUpdate(update, onSuccess, onFailure);
+    }
+  }
+}
+
+void
+FibUpdater::sendUpdatesForBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                                      const FibUpdateFailureCallback& onFailure)
+{
+  if (m_updatesForBatchFaceId.size() > 0) {
+    sendUpdates(m_updatesForBatchFaceId, onSuccess, onFailure);
+  }
+  else {
+    sendUpdatesForNonBatchFaceId(onSuccess, onFailure);
+  }
+}
+
+void
+FibUpdater::sendUpdatesForNonBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                                         const FibUpdateFailureCallback& onFailure)
+{
+  if (m_updatesForNonBatchFaceId.size() > 0) {
+    sendUpdates(m_updatesForNonBatchFaceId, onSuccess, onFailure);
+  }
+  else {
+    onSuccess(m_inheritedRoutes);
+  }
+}
+
+void
+FibUpdater::sendAddNextHopUpdate(const FibUpdate& update,
+                                 const FibUpdateSuccessCallback& onSuccess,
+                                 const FibUpdateFailureCallback& onFailure,
+                                 uint32_t nTimeouts)
+{
+  m_controller.start<ndn::nfd::FibAddNextHopCommand>(
+    ControlParameters()
+      .setName(update.name)
+      .setFaceId(update.faceId)
+      .setCost(update.cost),
+    bind(&FibUpdater::onUpdateSuccess, this, update, onSuccess, onFailure),
+    bind(&FibUpdater::onUpdateError, this, update, onSuccess, onFailure, _1, nTimeouts));
+}
+
+void
+FibUpdater::sendRemoveNextHopUpdate(const FibUpdate& update,
+                                    const FibUpdateSuccessCallback& onSuccess,
+                                    const FibUpdateFailureCallback& onFailure,
+                                    uint32_t nTimeouts)
+{
+  m_controller.start<ndn::nfd::FibRemoveNextHopCommand>(
+    ControlParameters()
+      .setName(update.name)
+      .setFaceId(update.faceId),
+    bind(&FibUpdater::onUpdateSuccess, this, update, onSuccess, onFailure),
+    bind(&FibUpdater::onUpdateError, this, update, onSuccess, onFailure, _1, nTimeouts));
+}
+
+void
+FibUpdater::onUpdateSuccess(const FibUpdate update,
+                            const FibUpdateSuccessCallback& onSuccess,
+                            const FibUpdateFailureCallback& onFailure)
+{
+  if (update.faceId == m_batchFaceId) {
+    m_updatesForBatchFaceId.remove(update);
+
+    if (m_updatesForBatchFaceId.size() == 0) {
+      sendUpdatesForNonBatchFaceId(onSuccess, onFailure);
+    }
+  }
+  else {
+    m_updatesForNonBatchFaceId.remove(update);
+
+    if (m_updatesForNonBatchFaceId.size() == 0) {
+      onSuccess(m_inheritedRoutes);
+    }
+  }
+}
+
+void
+FibUpdater::onUpdateError(const FibUpdate update,
+                          const FibUpdateSuccessCallback& onSuccess,
+                          const FibUpdateFailureCallback& onFailure,
+                          const ndn::nfd::ControlResponse& response, uint32_t nTimeouts)
+{
+  uint32_t code = response.getCode();
+  NFD_LOG_DEBUG("Failed to apply " << update <<
+                " (code: " << code << ", error: " << response.getText() << ")");
+
+  if (code == ndn::nfd::Controller::ERROR_TIMEOUT && nTimeouts < MAX_NUM_TIMEOUTS) {
+    sendAddNextHopUpdate(update, onSuccess, onFailure, ++nTimeouts);
+  }
+  else if (code == ERROR_FACE_NOT_FOUND) {
+    if (update.faceId == m_batchFaceId) {
+      onFailure(code, response.getText());
+    }
+    else {
+      m_updatesForNonBatchFaceId.remove(update);
+
+      if (m_updatesForNonBatchFaceId.size() == 0) {
+        onSuccess(m_inheritedRoutes);
+      }
+    }
+  }
+  else {
+    NDN_THROW(Error("Non-recoverable error: " + response.getText() + " code: " + to_string(code)));
+  }
+}
+
+void
+FibUpdater::addFibUpdate(FibUpdate update)
+{
+  FibUpdateList& updates = (update.faceId == m_batchFaceId) ? m_updatesForBatchFaceId :
+                                                              m_updatesForNonBatchFaceId;
+
+  // If an update with the same name and route already exists,
+  // replace it
+  FibUpdateList::iterator it = std::find_if(updates.begin(), updates.end(),
+    [&update] (const FibUpdate& other) {
+      return update.name == other.name && update.faceId == other.faceId;
+    });
+
+  if (it != updates.end()) {
+    FibUpdate& existingUpdate = *it;
+    existingUpdate.action = update.action;
+    existingUpdate.cost = update.cost;
+  }
+  else {
+    updates.push_back(update);
+  }
+}
+
+void
+FibUpdater::addInheritedRoutes(const RibEntry& entry, const Rib::RouteSet& routesToAdd)
+{
+  for (const Route& route : routesToAdd) {
+    // Don't add an ancestor faceId if the namespace has an entry for that faceId
+    if (!entry.hasFaceId(route.faceId)) {
+      // Create a record of the inherited route so it can be added to the RIB later
+      addInheritedRoute(entry.getName(), route);
+
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+    }
+  }
+}
+
+void
+FibUpdater::addInheritedRoutes(const Name& name, const Rib::RouteSet& routesToAdd,
+                               const Route& ignore)
+{
+  for (const Route& route : routesToAdd) {
+    if (route.faceId != ignore.faceId) {
+      // Create a record of the inherited route so it can be added to the RIB later
+      addInheritedRoute(name, route);
+
+      addFibUpdate(FibUpdate::createAddUpdate(name, route.faceId, route.cost));
+    }
+  }
+}
+
+void
+FibUpdater::removeInheritedRoutes(const RibEntry& entry, const Rib::Rib::RouteSet& routesToRemove)
+{
+  for (const Route& route : routesToRemove) {
+    // Only remove if the route has been inherited
+    if (entry.hasInheritedRoute(route)) {
+      removeInheritedRoute(entry.getName(), route);
+      addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+    }
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForNewRibEntry(const Name& name, const Route& route,
+                                           const Rib::RibEntryList& children)
+{
+  // Create FIB update for new entry
+  addFibUpdate(FibUpdate::createAddUpdate(name, route.faceId, route.cost));
+
+  // No flags are set
+  if (!route.isChildInherit() && !route.isRibCapture()) {
+    // Add ancestor routes to self
+    addInheritedRoutes(name, m_rib.getAncestorRoutes(name), route);
+  }
+  else if (route.isChildInherit() && route.isRibCapture()) {
+    // Add route to children
+    Rib::RouteSet routesToAdd;
+    routesToAdd.insert(route);
+
+    // Remove routes blocked by capture and add self to children
+    modifyChildrensInheritedRoutes(children, routesToAdd, m_rib.getAncestorRoutes(name));
+  }
+  else if (route.isChildInherit()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(name);
+
+    // Add ancestor routes to self
+    addInheritedRoutes(name, ancestorRoutes, route);
+
+    // If there is an ancestor route which is the same as the new route, replace it
+    // with the new route
+    Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+    // There is a route that needs to be overwritten, erase and then replace
+    if (it != ancestorRoutes.end()) {
+      ancestorRoutes.erase(it);
+    }
+
+    // Add new route to ancestor list so it can be added to children
+    ancestorRoutes.insert(route);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(children, ancestorRoutes, Rib::RouteSet());
+  }
+  else if (route.isRibCapture()) {
+    // Remove routes blocked by capture
+    modifyChildrensInheritedRoutes(children, Rib::RouteSet(), m_rib.getAncestorRoutes(name));
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForNewRoute(const RibEntry& entry, const Route& route,
+                                        bool captureWasTurnedOn)
+{
+  // Only update if the new route has a lower cost than a previously installed route
+  const Route* prevRoute = entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
+
+  Rib::RouteSet routesToAdd;
+  if (route.isChildInherit()) {
+    // Add to children if this new route doesn't override a previous lower cost, or
+    // add to children if this new route is lower cost than a previous route.
+    // Less than equal, since entry may find this route
+    if (prevRoute == nullptr || route.cost <= prevRoute->cost) {
+      // Add self to children
+      routesToAdd.insert(route);
+    }
+  }
+
+  Rib::RouteSet routesToRemove;
+  if (captureWasTurnedOn) {
+    // Capture flag on
+    routesToRemove = m_rib.getAncestorRoutes(entry);
+
+    // Remove ancestor routes from self
+    removeInheritedRoutes(entry, routesToRemove);
+  }
+
+  modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+
+  // If another route with same faceId and lower cost exists, don't update.
+  // Must be done last so that add updates replace removal updates
+  // Create FIB update for new entry
+  const Route* other = entry.getRouteWithLowestCostByFaceId(route.faceId);
+
+  if (other == nullptr || route.cost <= other->cost) {
+    addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForUpdatedRoute(const RibEntry& entry, const Route& route,
+                                            const Route& existingRoute)
+{
+  const bool costDidChange = (route.cost != existingRoute.cost);
+
+  // Look for an installed route with the lowest cost and child inherit set
+  const Route* prevRoute = entry.getRouteWithLowestCostAndChildInheritByFaceId(route.faceId);
+
+  // No flags changed and cost didn't change, no change in FIB
+  if (route.flags == existingRoute.flags && !costDidChange) {
+    return;
+  }
+
+  // Cost changed so create update for the entry itself
+  if (costDidChange) {
+    // Create update if this route's cost is lower than other routes
+    if (route.cost <= entry.getRouteWithLowestCostByFaceId(route.faceId)->cost) {
+      // Create FIB update for the updated entry
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), route.faceId, route.cost));
+    }
+    else if (existingRoute.cost < entry.getRouteWithLowestCostByFaceId(route.faceId)->cost) {
+      // Create update if this route used to be the lowest route but is no longer
+      // the lowest cost route.
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), prevRoute->faceId, prevRoute->cost));
+    }
+
+    // If another route with same faceId and lower cost and ChildInherit exists,
+    // don't update children.
+    if (prevRoute == nullptr || route.cost <= prevRoute->cost) {
+      // If no flags changed but child inheritance is set, need to update children
+      // with new cost
+      if ((route.flags == existingRoute.flags) && route.isChildInherit()) {
+        // Add self to children
+        Rib::RouteSet routesToAdd;
+        routesToAdd.insert(route);
+        modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+
+        return;
+      }
+    }
+  }
+
+  // Child inherit was turned on
+  if (!existingRoute.isChildInherit() && route.isChildInherit()) {
+    // If another route with same faceId and lower cost and ChildInherit exists,
+    // don't update children.
+    if (prevRoute == nullptr || route.cost <= prevRoute->cost) {
+      // Add self to children
+      Rib::RouteSet routesToAdd;
+      routesToAdd.insert(route);
+      modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+    }
+  } // Child inherit was turned off
+  else if (existingRoute.isChildInherit() && !route.isChildInherit()) {
+    // Remove self from children
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    Rib::RouteSet routesToAdd;
+    // If another route with same faceId and ChildInherit exists, update children with this route.
+    if (prevRoute != nullptr) {
+      routesToAdd.insert(*prevRoute);
+    }
+    else {
+      // Look for an ancestor that was blocked previously
+      const Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+      Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+      // If an ancestor is found, add it to children
+      if (it != ancestorRoutes.end()) {
+        routesToAdd.insert(*it);
+      }
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+
+  // Capture was turned on
+  if (!existingRoute.isRibCapture() && route.isRibCapture()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+    // Remove ancestor routes from self
+    removeInheritedRoutes(entry, ancestorRoutes);
+
+    // Remove ancestor routes from children
+    modifyChildrensInheritedRoutes(entry.getChildren(), Rib::RouteSet(), ancestorRoutes);
+  }  // Capture was turned off
+  else if (existingRoute.isRibCapture() && !route.isRibCapture()) {
+    Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+    // Add ancestor routes to self
+    addInheritedRoutes(entry, ancestorRoutes);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(entry.getChildren(), ancestorRoutes, Rib::RouteSet());
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForErasedRoute(const RibEntry& entry, const Route& route,
+                                               const bool captureWasTurnedOff)
+{
+  addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+
+  if (route.isChildInherit() && route.isRibCapture()) {
+    // Remove self from children
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    // If capture is turned off for the route and another route is installed in the RibEntry,
+    // add ancestors to self
+    Rib::RouteSet routesToAdd;
+    if (captureWasTurnedOff && entry.getNRoutes() != 0) {
+      // Look for an ancestors that were blocked previously
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+
+      // Add ancestor routes to self
+      addInheritedRoutes(entry, routesToAdd);
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+  else if (route.isChildInherit()) {
+    // If not blocked by capture, add inherited routes to children
+    Rib::RouteSet routesToAdd;
+    if (!entry.hasCapture()) {
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+    }
+
+    Rib::RouteSet routesToRemove;
+    routesToRemove.insert(route);
+
+    // Add ancestor routes to children
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+  }
+  else if (route.isRibCapture()) {
+    // If capture is turned off for the route and another route is installed in the RibEntry,
+    // add ancestors to self
+    Rib::RouteSet routesToAdd;
+    if (captureWasTurnedOff && entry.getNRoutes() != 0) {
+      // Look for an ancestors that were blocked previously
+      routesToAdd = m_rib.getAncestorRoutes(entry);
+
+      // Add ancestor routes to self
+      addInheritedRoutes(entry, routesToAdd);
+    }
+
+    modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, Rib::RouteSet());
+  }
+
+  // Need to check if the removed route was blocking an inherited route
+  Rib::RouteSet ancestorRoutes = m_rib.getAncestorRoutes(entry);
+
+  // If the current entry has capture set or is pending removal, don't add inherited route
+  if (!entry.hasCapture() && entry.getNRoutes() != 0) {
+    // If there is an ancestor route which is the same as the erased route, add that route
+    // to the current entry
+    Rib::RouteSet::iterator it = ancestorRoutes.find(route);
+
+    if (it != ancestorRoutes.end()) {
+      addInheritedRoute(entry.getName(), *it);
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), it->faceId, it->cost));
+    }
+  }
+}
+
+void
+FibUpdater::createFibUpdatesForErasedRibEntry(const RibEntry& entry)
+{
+  for (const Route& route : entry.getInheritedRoutes()) {
+    addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), route.faceId));
+  }
+}
+
+void
+FibUpdater::modifyChildrensInheritedRoutes(const Rib::RibEntryList& children,
+                                           const Rib::RouteSet& routesToAdd,
+                                           const Rib::RouteSet& routesToRemove)
+{
+  for (const auto& child : children) {
+    traverseSubTree(*child, routesToAdd, routesToRemove);
+  }
+}
+
+void
+FibUpdater::traverseSubTree(const RibEntry& entry, Rib::Rib::RouteSet routesToAdd,
+                            Rib::Rib::RouteSet routesToRemove)
+{
+  // If a route on the namespace has the capture flag set, ignore self and children
+  if (entry.hasCapture()) {
+    return;
+  }
+
+  // Remove inherited routes from current namespace
+  for (auto removeIt = routesToRemove.begin(); removeIt != routesToRemove.end(); ) {
+    // If a route on the namespace has the same face ID and child inheritance set,
+    // ignore this route
+    if (entry.hasChildInheritOnFaceId(removeIt->faceId)) {
+      removeIt = routesToRemove.erase(removeIt);
+      continue;
+    }
+
+    // Only remove route if it removes an existing inherited route
+    if (entry.hasInheritedRoute(*removeIt)) {
+      removeInheritedRoute(entry.getName(), *removeIt);
+      addFibUpdate(FibUpdate::createRemoveUpdate(entry.getName(), removeIt->faceId));
+    }
+
+    ++removeIt;
+  }
+
+  // Add inherited routes to current namespace
+  for (auto addIt = routesToAdd.begin(); addIt != routesToAdd.end(); ) {
+    // If a route on the namespace has the same face ID and child inherit set, ignore this face
+    if (entry.hasChildInheritOnFaceId(addIt->faceId)) {
+      addIt = routesToAdd.erase(addIt);
+      continue;
+    }
+
+    // Only add route if it does not override an existing route
+    if (!entry.hasFaceId(addIt->faceId)) {
+      addInheritedRoute(entry.getName(), *addIt);
+      addFibUpdate(FibUpdate::createAddUpdate(entry.getName(), addIt->faceId, addIt->cost));
+    }
+
+    ++addIt;
+  }
+
+  modifyChildrensInheritedRoutes(entry.getChildren(), routesToAdd, routesToRemove);
+}
+
+void
+FibUpdater::addInheritedRoute(const Name& name, const Route& route)
+{
+  RibUpdate update;
+  update.setAction(RibUpdate::REGISTER)
+        .setName(name)
+        .setRoute(route);
+
+  m_inheritedRoutes.push_back(update);
+}
+
+void
+FibUpdater::removeInheritedRoute(const Name& name, const Route& route)
+{
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(name)
+        .setRoute(route);
+
+  m_inheritedRoutes.push_back(update);
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/fib-updater.hpp b/daemon/rib/fib-updater.hpp
new file mode 100644
index 0000000..a404853
--- /dev/null
+++ b/daemon/rib/fib-updater.hpp
@@ -0,0 +1,275 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_FIB_UPDATER_HPP
+#define NFD_DAEMON_RIB_FIB_UPDATER_HPP
+
+#include "core/common.hpp"
+#include "fib-update.hpp"
+#include "rib.hpp"
+#include "rib-update-batch.hpp"
+
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief computes FibUpdates based on updates to the RIB and sends them to NFD
+ */
+class FibUpdater : noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    using std::runtime_error::runtime_error;
+  };
+
+public:
+  typedef std::list<FibUpdate> FibUpdateList;
+
+  typedef std::function<void(RibUpdateList inheritedRoutes)> FibUpdateSuccessCallback;
+  typedef std::function<void(uint32_t code, const std::string& error)> FibUpdateFailureCallback;
+
+  FibUpdater(Rib& rib, ndn::nfd::Controller& controller);
+
+  /** \brief computes FibUpdates using the provided RibUpdateBatch and then sends the
+   *         updates to NFD's FIB
+   *
+   *  \note  Caller must guarantee that the previous batch has either succeeded or failed
+   *         before calling this method
+   */
+  void
+  computeAndSendFibUpdates(const RibUpdateBatch& batch,
+                           const FibUpdateSuccessCallback& onSuccess,
+                           const FibUpdateFailureCallback& onFailure);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief determines the type of action that will be performed on the RIB and calls the
+  *          corresponding computation method
+  */
+  void
+  computeUpdates(const RibUpdateBatch& batch);
+
+  /** \brief sends the passed updates to NFD
+  *
+  *   onSuccess or onFailure will be called based on the results in
+  *   onUpdateSuccess or onUpdateFailure
+  *
+  *   \see FibUpdater::onUpdateSuccess
+  *   \see FibUpdater::onUpdateFailure
+  */
+  void
+  sendUpdates(const FibUpdateList& updates,
+              const FibUpdateSuccessCallback& onSuccess,
+              const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends the updates in m_updatesForBatchFaceId to NFD if any exist,
+  *          otherwise calls FibUpdater::sendUpdatesForNonBatchFaceId.
+  */
+  void
+  sendUpdatesForBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                            const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends the updates in m_updatesForNonBatchFaceId to NFD if any exist,
+  *          otherwise calls onSuccess.
+  */
+  void
+  sendUpdatesForNonBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
+                               const FibUpdateFailureCallback& onFailure);
+
+  /** \brief sends a FibAddNextHopCommand to NFD using the parameters supplied by
+  *          the passed update
+  *
+  *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
+  */
+  void
+  sendAddNextHopUpdate(const FibUpdate& update,
+                       const FibUpdateSuccessCallback& onSuccess,
+                       const FibUpdateFailureCallback& onFailure,
+                       uint32_t nTimeouts = 0);
+
+  /** \brief sends a FibRemoveNextHopCommand to NFD using the parameters supplied by
+  *          the passed update
+  *
+  *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
+  */
+  void
+  sendRemoveNextHopUpdate(const FibUpdate& update,
+                          const FibUpdateSuccessCallback& onSuccess,
+                          const FibUpdateFailureCallback& onFailure,
+                          uint32_t nTimeouts = 0);
+
+private:
+  /** \brief calculates the FibUpdates generated by a RIB registration
+  */
+  void
+  computeUpdatesForRegistration(const RibUpdate& update);
+
+  /** \brief calculates the FibUpdates generated by a RIB unregistration
+  */
+  void
+  computeUpdatesForUnregistration(const RibUpdate& update);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \brief callback used by NfdController when a FibAddNextHopCommand or FibRemoveNextHopCommand
+  *          is successful.
+  *
+  *   If the update has the same Face ID as the batch being processed, the update is
+  *   removed from m_updatesForBatchFaceId. If m_updatesForBatchFaceId becomes empty,
+  *   the updates with a different Face ID than the batch are sent to NFD.
+  *
+  *   If the update has a different Face ID than the batch being processed, the update is
+  *   removed from m_updatesForBatchNonFaceId. If m_updatesForBatchNonFaceId becomes empty,
+  *   the FIB update process is considered a success.
+  */
+  void
+  onUpdateSuccess(const FibUpdate update,
+                  const FibUpdateSuccessCallback& onSuccess,
+                  const FibUpdateFailureCallback& onFailure);
+
+  /** \brief callback used by NfdController when a FibAddNextHopCommand or FibRemoveNextHopCommand
+  *          is successful.
+  *
+  *   If the update has not reached the max number of timeouts allowed, the update
+  *   is retried.
+  *
+  *   If the update failed due to a non-existent face and the update has the same Face ID
+  *   as the update batch, the FIB update process fails.
+  *
+  *   If the update failed due to a non-existent face and the update has a different
+  *   face than the update batch, the update is not retried and the error is
+  *   ignored.
+  *
+  *   Otherwise, a non-recoverable error has occurred and an exception is thrown.
+  */
+  void
+  onUpdateError(const FibUpdate update,
+                const FibUpdateSuccessCallback& onSuccess,
+                const FibUpdateFailureCallback& onFailure,
+                const ndn::nfd::ControlResponse& response, uint32_t nTimeouts);
+
+private:
+  /** \brief adds the update to an update list based on its Face ID
+  *
+  *   If the update has the same Face ID as the update batch, the update is added
+  *   to m_updatesForBatchFaceId.
+  *
+  *   Otherwise, the update is added to m_updatesForBatchNonFaceId.
+  */
+  void
+  addFibUpdate(const FibUpdate update);
+
+  /** \brief creates records of the passed routes added to the entry and creates FIB updates
+  */
+  void
+  addInheritedRoutes(const RibEntry& entry, const Rib::RouteSet& routesToAdd);
+
+  /** \brief creates records of the passed routes added to the name and creates FIB updates.
+  *          Routes in routesToAdd with the same Face ID as ignore will be not be considered.
+  */
+  void
+  addInheritedRoutes(const Name& name, const Rib::RouteSet& routesToAdd, const Route& ignore);
+
+  /** \brief creates records of the passed routes removed from the entry and creates FIB updates
+  */
+  void
+  removeInheritedRoutes(const RibEntry& entry, const Rib::RouteSet& routesToRemove);
+
+  /** \brief calculates updates for a name that will create a new RIB entry
+  */
+  void
+  createFibUpdatesForNewRibEntry(const Name& name, const Route& route,
+                                 const Rib::RibEntryList& children);
+
+  /** \brief calculates updates for a new route added to a RIB entry
+  */
+  void
+  createFibUpdatesForNewRoute(const RibEntry& entry, const Route& route,
+                              const bool captureWasTurnedOn);
+
+  /** \brief calculates updates for changes to an existing route for a RIB entry
+  */
+  void
+  createFibUpdatesForUpdatedRoute(const RibEntry& entry, const Route& route,
+                                  const Route& existingRoute);
+
+  /** \brief calculates updates for a an existing route removed from a RIB entry
+  */
+  void
+  createFibUpdatesForErasedRoute(const RibEntry& entry, const Route& route,
+                                 const bool captureWasTurnedOff);
+
+  /** \brief calculates updates for an entry that will be removed from the RIB
+  */
+  void
+  createFibUpdatesForErasedRibEntry(const RibEntry& entry);
+
+  /** \brief adds and removes passed routes to children's inherited routes
+  */
+  void
+  modifyChildrensInheritedRoutes(const Rib::RibEntryList& children,
+                                 const Rib::RouteSet& routesToAdd,
+                                 const Rib::RouteSet& routesToRemove);
+
+  /** \brief traverses the entry's children adding and removing the passed routes
+  */
+  void
+  traverseSubTree(const RibEntry& entry, Rib::RouteSet routesToAdd, Rib::RouteSet routesToRemove);
+
+private:
+  /** \brief creates a record of a calculated inherited route that should be added to the entry
+  */
+  void
+  addInheritedRoute(const Name& name, const Route& route);
+
+  /** \brief creates a record of an existing inherited route that should be removed from the entry
+  */
+  void
+  removeInheritedRoute(const Name& name, const Route& route);
+
+private:
+  const Rib& m_rib;
+  ndn::nfd::Controller& m_controller;
+  uint64_t m_batchFaceId;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  FibUpdateList m_updatesForBatchFaceId;
+  FibUpdateList m_updatesForNonBatchFaceId;
+
+  /** \brief list of inherited routes generated during FIB update calculation;
+   *         passed to the RIB when updates are completed successfully
+   */
+  RibUpdateList m_inheritedRoutes;
+
+private:
+  static const unsigned int MAX_NUM_TIMEOUTS;
+  static const uint32_t ERROR_FACE_NOT_FOUND;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_FIB_UPDATER_HPP
diff --git a/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.cpp b/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.cpp
new file mode 100644
index 0000000..ca54132
--- /dev/null
+++ b/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "client-to-nlsr-readvertise-policy.hpp"
+
+namespace nfd {
+namespace rib {
+
+optional<ReadvertiseAction>
+ClientToNlsrReadvertisePolicy::handleNewRoute(const RibRouteRef& ribRoute) const
+{
+  if (ribRoute.route->origin == ndn::nfd::ROUTE_ORIGIN_CLIENT) {
+    return ReadvertiseAction{ribRoute.entry->getName(), ndn::security::SigningInfo()};
+  }
+  else {
+    return nullopt;
+  }
+}
+
+time::milliseconds
+ClientToNlsrReadvertisePolicy::getRefreshInterval() const
+{
+  return 1_h;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.hpp b/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.hpp
new file mode 100644
index 0000000..b1c5e29
--- /dev/null
+++ b/daemon/rib/readvertise/client-to-nlsr-readvertise-policy.hpp
@@ -0,0 +1,54 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_CLIENT_TO_NLSR_READVERTISE_POLICY_HPP
+#define NFD_DAEMON_RIB_READVERTISE_CLIENT_TO_NLSR_READVERTISE_POLICY_HPP
+
+#include "readvertise-policy.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** \brief a policy to readvertise routes registered by end hosts into NLSR
+ */
+class ClientToNlsrReadvertisePolicy : public ReadvertisePolicy
+{
+public:
+  /** \brief advertise if the route's origin is client
+   *
+   *  If the route origin is "client" (typically from auto prefix propagation), readvertise it
+   *  using the default signing identity.
+   */
+  optional<ReadvertiseAction>
+  handleNewRoute(const RibRouteRef& ribRoute) const override;
+
+  time::milliseconds
+  getRefreshInterval() const override;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_CLIENT_TO_NLSR_READVERTISE_POLICY_HPP
diff --git a/daemon/rib/readvertise/host-to-gateway-readvertise-policy.cpp b/daemon/rib/readvertise/host-to-gateway-readvertise-policy.cpp
new file mode 100644
index 0000000..2d508f8
--- /dev/null
+++ b/daemon/rib/readvertise/host-to-gateway-readvertise-policy.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "host-to-gateway-readvertise-policy.hpp"
+#include "core/scope-prefix.hpp"
+#include "rib/rib-manager.hpp"
+
+#include <ndn-cxx/security/pib/identity.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+namespace nfd {
+namespace rib {
+
+static const name::Component IGNORE_COMPONENT("nrd");
+static const time::seconds DEFAULT_REFRESH_INTERVAL = 25_s;
+
+HostToGatewayReadvertisePolicy::HostToGatewayReadvertisePolicy(const ndn::KeyChain& keyChain,
+                                                               const ConfigSection& section)
+  : m_keyChain(keyChain)
+{
+  auto interval = section.get_optional<uint64_t>("refresh_interval");
+  m_refreshInterval = interval ? time::seconds(*interval) : DEFAULT_REFRESH_INTERVAL;
+}
+
+optional<ReadvertiseAction>
+HostToGatewayReadvertisePolicy::handleNewRoute(const RibRouteRef& ribRoute) const
+{
+  auto ribEntryName = ribRoute.entry->getName();
+  if (scope_prefix::LOCALHOST.isPrefixOf(ribEntryName) ||
+      ribEntryName == RibManager::LOCALHOP_TOP_PREFIX) {
+    return nullopt;
+  }
+
+  // find out the shortest identity whose name is a prefix of the RIB entry name
+  auto prefixToAdvertise = ribEntryName;
+  ndn::security::pib::Identity signingIdentity;
+  bool isFound = false;
+
+  for (const auto& identity : m_keyChain.getPib().getIdentities()) {
+    auto prefix = identity.getName();
+
+    // ignore the identity name's last component if it is "nrd"
+    if (!prefix.empty() && IGNORE_COMPONENT == prefix.at(-1)) {
+      prefix = prefix.getPrefix(-1);
+    }
+
+    if (prefix.isPrefixOf(prefixToAdvertise)) {
+      isFound = true;
+      prefixToAdvertise = prefix;
+      signingIdentity = identity;
+    }
+  }
+
+  if (isFound) {
+    return ReadvertiseAction{prefixToAdvertise, ndn::security::signingByIdentity(signingIdentity)};
+  }
+  else {
+    return nullopt;
+  }
+}
+
+time::milliseconds
+HostToGatewayReadvertisePolicy::getRefreshInterval() const
+{
+  return m_refreshInterval;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/readvertise/host-to-gateway-readvertise-policy.hpp b/daemon/rib/readvertise/host-to-gateway-readvertise-policy.hpp
new file mode 100644
index 0000000..69e914a
--- /dev/null
+++ b/daemon/rib/readvertise/host-to-gateway-readvertise-policy.hpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_HOST_TO_GATEWAY_READVERTISE_POLICY_HPP
+#define NFD_DAEMON_RIB_READVERTISE_HOST_TO_GATEWAY_READVERTISE_POLICY_HPP
+
+#include "readvertise-policy.hpp"
+#include "core/config-file.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief a policy to readvertise routes registered by local applications into remote gateway
+ */
+class HostToGatewayReadvertisePolicy : public ReadvertisePolicy
+{
+public:
+  HostToGatewayReadvertisePolicy(const ndn::KeyChain& keyChain,
+                                 const ConfigSection& section);
+
+public:
+  optional<ReadvertiseAction>
+  handleNewRoute(const RibRouteRef& ribRoute) const override;
+
+  time::milliseconds
+  getRefreshInterval() const override;
+
+private:
+  const ndn::KeyChain& m_keyChain;
+  time::seconds m_refreshInterval;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_HOST_TO_GATEWAY_READVERTISE_POLICY_HPP
diff --git a/daemon/rib/readvertise/nfd-rib-readvertise-destination.cpp b/daemon/rib/readvertise/nfd-rib-readvertise-destination.cpp
new file mode 100644
index 0000000..f0a0482
--- /dev/null
+++ b/daemon/rib/readvertise/nfd-rib-readvertise-destination.cpp
@@ -0,0 +1,110 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfd-rib-readvertise-destination.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/mgmt/nfd/control-command.hpp>
+#include <ndn-cxx/mgmt/nfd/control-response.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(NfdRibReadvertiseDestination);
+
+using ndn::nfd::ControlResponse;
+
+NfdRibReadvertiseDestination::NfdRibReadvertiseDestination(ndn::nfd::Controller& controller,
+                                                           Rib& rib,
+                                                           const ndn::nfd::CommandOptions& options,
+                                                           const ndn::nfd::ControlParameters& parameters)
+  : m_controller(controller)
+  , m_commandOptions(options)
+  , m_controlParameters(parameters)
+{
+  m_ribInsertConn = rib.afterInsertEntry.connect(
+    std::bind(&NfdRibReadvertiseDestination::handleRibInsert, this, _1));
+  m_ribEraseConn = rib.afterEraseEntry.connect(
+    std::bind(&NfdRibReadvertiseDestination::handleRibErase, this, _1));
+}
+
+void
+NfdRibReadvertiseDestination::advertise(const nfd::rib::ReadvertisedRoute& rr,
+                                        std::function<void()> successCb,
+                                        std::function<void(const std::string&)> failureCb)
+{
+  NFD_LOG_DEBUG("advertise " << rr.prefix << " on " << m_commandOptions.getPrefix());
+
+  m_controller.start<ndn::nfd::RibRegisterCommand>(
+    getControlParameters().setName(rr.prefix),
+    [=] (const ControlParameters& cp) { successCb(); },
+    [=] (const ControlResponse& cr) { failureCb(cr.getText()); },
+    getCommandOptions().setSigningInfo(rr.signer));
+}
+
+void
+NfdRibReadvertiseDestination::withdraw(const nfd::rib::ReadvertisedRoute& rr,
+                                       std::function<void()> successCb,
+                                       std::function<void(const std::string&)> failureCb)
+{
+  NFD_LOG_DEBUG("withdraw " << rr.prefix << " on " << m_commandOptions.getPrefix());
+
+  m_controller.start<ndn::nfd::RibUnregisterCommand>(
+    getControlParameters().setName(rr.prefix),
+    [=] (const ControlParameters& cp) { successCb(); },
+    [=] (const ControlResponse& cr) { failureCb(cr.getText()); },
+    getCommandOptions().setSigningInfo(rr.signer));
+}
+
+ndn::nfd::ControlParameters
+NfdRibReadvertiseDestination::getControlParameters()
+{
+  return m_controlParameters;
+}
+
+ndn::nfd::CommandOptions
+NfdRibReadvertiseDestination::getCommandOptions()
+{
+  return m_commandOptions;
+}
+
+void
+NfdRibReadvertiseDestination::handleRibInsert(const ndn::Name& name)
+{
+  if (name.isPrefixOf(m_commandOptions.getPrefix())) {
+    setAvailability(true);
+  }
+}
+
+void
+NfdRibReadvertiseDestination::handleRibErase(const ndn::Name& name)
+{
+  if (name.isPrefixOf(m_commandOptions.getPrefix())) {
+    setAvailability(false);
+  }
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/readvertise/nfd-rib-readvertise-destination.hpp b/daemon/rib/readvertise/nfd-rib-readvertise-destination.hpp
new file mode 100644
index 0000000..9c5bce6
--- /dev/null
+++ b/daemon/rib/readvertise/nfd-rib-readvertise-destination.hpp
@@ -0,0 +1,92 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_NFD_RIB_READVERTISE_DESTINATION_HPP
+#define NFD_DAEMON_RIB_READVERTISE_NFD_RIB_READVERTISE_DESTINATION_HPP
+
+#include "readvertise-destination.hpp"
+#include "rib/rib.hpp"
+
+#include <ndn-cxx/mgmt/nfd/command-options.hpp>
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief a readvertise destination using NFD RIB management protocol
+ */
+class NfdRibReadvertiseDestination : public ReadvertiseDestination
+{
+public:
+  NfdRibReadvertiseDestination(ndn::nfd::Controller& controller,
+                               Rib& rib,
+                               const ndn::nfd::CommandOptions& options = ndn::nfd::CommandOptions(),
+                               const ndn::nfd::ControlParameters& parameters =
+                                 ndn::nfd::ControlParameters()
+                                   .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT));
+
+  /** \brief add a name prefix into NFD RIB
+   */
+  void
+  advertise(const ReadvertisedRoute& rr,
+            std::function<void()> successCb,
+            std::function<void(const std::string&)> failureCb) override;
+
+  /** \brief remove a name prefix from NFD RIB
+   */
+  void
+  withdraw(const ReadvertisedRoute& rr,
+           std::function<void()> successCb,
+           std::function<void(const std::string&)> failureCb) override;
+
+protected:
+  ndn::nfd::ControlParameters
+  getControlParameters();
+
+  ndn::nfd::CommandOptions
+  getCommandOptions();
+
+private:
+  void
+  handleRibInsert(const Name& name);
+
+  void
+  handleRibErase(const Name& name);
+
+private:
+  ndn::nfd::Controller& m_controller;
+
+  signal::ScopedConnection m_ribInsertConn;
+  signal::ScopedConnection m_ribEraseConn;
+
+  ndn::nfd::CommandOptions m_commandOptions;
+  ndn::nfd::ControlParameters m_controlParameters;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_NFD_RIB_READVERTISE_DESTINATION_HPP
diff --git a/daemon/rib/readvertise/readvertise-destination.cpp b/daemon/rib/readvertise/readvertise-destination.cpp
new file mode 100644
index 0000000..a687a8a
--- /dev/null
+++ b/daemon/rib/readvertise/readvertise-destination.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "readvertise-destination.hpp"
+#include "core/logger.hpp"
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(ReadvertiseDestination);
+
+void
+ReadvertiseDestination::setAvailability(bool isAvailable)
+{
+  if (m_isAvailable != isAvailable) {
+    if (isAvailable) {
+      NFD_LOG_DEBUG("Destination has become available.");
+    }
+    else {
+      NFD_LOG_DEBUG("Destinatino has become unavailable.");
+    }
+    m_isAvailable = isAvailable;
+    afterAvailabilityChange(isAvailable);
+  }
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/readvertise/readvertise-destination.hpp b/daemon/rib/readvertise/readvertise-destination.hpp
new file mode 100644
index 0000000..1c4fb76
--- /dev/null
+++ b/daemon/rib/readvertise/readvertise-destination.hpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_READVERTISE_DESTINATION_HPP
+#define NFD_DAEMON_RIB_READVERTISE_READVERTISE_DESTINATION_HPP
+
+#include "readvertised-route.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** \brief a destination to readvertise into
+ */
+class ReadvertiseDestination : noncopyable
+{
+public:
+  virtual
+  ~ReadvertiseDestination() = default;
+
+  virtual void
+  advertise(const ReadvertisedRoute& rr,
+            std::function<void()> successCb,
+            std::function<void(const std::string&)> failureCb) = 0;
+
+  virtual void
+  withdraw(const ReadvertisedRoute& rr,
+           std::function<void()> successCb,
+           std::function<void(const std::string&)> failureCb) = 0;
+
+  bool
+  isAvailable() const
+  {
+    return m_isAvailable;
+  }
+
+protected:
+  void
+  setAvailability(bool isAvailable);
+
+public:
+  /** \brief signals when the destination becomes available or unavailable
+   */
+  signal::Signal<ReadvertiseDestination, bool> afterAvailabilityChange;
+
+private:
+  bool m_isAvailable = false;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_READVERTISE_DESTINATION_HPP
diff --git a/daemon/rib/readvertise/readvertise-policy.hpp b/daemon/rib/readvertise/readvertise-policy.hpp
new file mode 100644
index 0000000..99deccb
--- /dev/null
+++ b/daemon/rib/readvertise/readvertise-policy.hpp
@@ -0,0 +1,66 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_READVERTISE_POLICY_HPP
+#define NFD_DAEMON_RIB_READVERTISE_READVERTISE_POLICY_HPP
+
+#include "rib/rib.hpp"
+
+#include <ndn-cxx/security/signing-info.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief a decision made by readvertise policy
+ */
+struct ReadvertiseAction
+{
+  Name prefix; ///< the prefix that should be readvertised
+  ndn::security::SigningInfo signer; ///< credentials for command signing
+};
+
+/** \brief a policy to decide whether to readvertise a route, and what prefix to readvertise
+ */
+class ReadvertisePolicy : noncopyable
+{
+public:
+  virtual
+  ~ReadvertisePolicy() = default;
+
+  /** \brief decide whether to readvertise a route, and what prefix to readvertise
+   */
+  virtual optional<ReadvertiseAction>
+  handleNewRoute(const RibRouteRef& ribRoute) const = 0;
+
+  /** \return how often readvertisements made by this policy should be refreshed.
+   */
+  virtual time::milliseconds
+  getRefreshInterval() const = 0;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_READVERTISE_POLICY_HPP
diff --git a/daemon/rib/readvertise/readvertise.cpp b/daemon/rib/readvertise/readvertise.cpp
new file mode 100644
index 0000000..454ab20
--- /dev/null
+++ b/daemon/rib/readvertise/readvertise.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "readvertise.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/util/random.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(Readvertise);
+
+const time::milliseconds Readvertise::RETRY_DELAY_MIN = 50_s;
+const time::milliseconds Readvertise::RETRY_DELAY_MAX = 3600_s;
+
+static time::milliseconds
+randomizeTimer(time::milliseconds baseTimer)
+{
+  std::uniform_int_distribution<> dist(-5, 5);
+  auto newTime = baseTimer + time::milliseconds(dist(ndn::random::getRandomNumberEngine()));
+  return std::max(newTime, 0_ms);
+}
+
+Readvertise::Readvertise(Rib& rib, ndn::util::Scheduler& scheduler,
+                         unique_ptr<ReadvertisePolicy> policy,
+                         unique_ptr<ReadvertiseDestination> destination)
+  : m_scheduler(scheduler)
+  , m_policy(std::move(policy))
+  , m_destination(std::move(destination))
+{
+  m_addRouteConn = rib.afterAddRoute.connect([this] (const auto& r) { this->afterAddRoute(r); });
+  m_removeRouteConn = rib.beforeRemoveRoute.connect([this] (const auto& r) { this->beforeRemoveRoute(r); });
+
+  m_destination->afterAvailabilityChange.connect([this] (bool isAvailable) {
+    if (isAvailable) {
+      this->afterDestinationAvailable();
+    }
+    else {
+      this->afterDestinationUnavailable();
+    }
+  });
+}
+
+void
+Readvertise::afterAddRoute(const RibRouteRef& ribRoute)
+{
+  optional<ReadvertiseAction> action = m_policy->handleNewRoute(ribRoute);
+  if (!action) {
+    NFD_LOG_DEBUG("add-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                  ',' << ribRoute.route->origin << ") not-readvertising");
+    return;
+  }
+
+  ReadvertisedRouteContainer::iterator rrIt;
+  bool isNew = false;
+  std::tie(rrIt, isNew) = m_rrs.emplace(action->prefix);
+
+  if (!isNew && rrIt->signer != action->signer) {
+    NFD_LOG_WARN("add-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                  ',' << ribRoute.route->origin << ") readvertising-as " << action->prefix <<
+                 " old-signer " << rrIt->signer << " new-signer " << action->signer);
+  }
+  rrIt->signer = action->signer;
+
+  RouteRrIndex::iterator indexIt;
+  std::tie(indexIt, isNew) = m_routeToRr.emplace(ribRoute, rrIt);
+  BOOST_ASSERT(isNew);
+
+  if (rrIt->nRibRoutes++ > 0) {
+    NFD_LOG_DEBUG("add-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                  ',' << ribRoute.route->origin << ") already-readvertised-as " << action->prefix);
+    return;
+  }
+
+  NFD_LOG_DEBUG("add-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                ',' << ribRoute.route->origin << ") readvertising-as " << action->prefix <<
+                " signer " << action->signer);
+  rrIt->retryDelay = RETRY_DELAY_MIN;
+  this->advertise(rrIt);
+}
+
+void
+Readvertise::beforeRemoveRoute(const RibRouteRef& ribRoute)
+{
+  auto indexIt = m_routeToRr.find(ribRoute);
+  if (indexIt == m_routeToRr.end()) {
+    NFD_LOG_DEBUG("remove-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                  ',' << ribRoute.route->origin << ") not-readvertised");
+    return;
+  }
+
+  auto rrIt = indexIt->second;
+  m_routeToRr.erase(indexIt);
+
+  if (--rrIt->nRibRoutes > 0) {
+    NFD_LOG_DEBUG("remove-route " << ribRoute.entry->getName() << '(' << ribRoute.route->faceId <<
+                  ',' << ribRoute.route->origin << ") needed-by " << rrIt->nRibRoutes);
+    return;
+  }
+
+  rrIt->retryDelay = RETRY_DELAY_MIN;
+  this->withdraw(rrIt);
+}
+
+void
+Readvertise::afterDestinationAvailable()
+{
+  for (auto rrIt = m_rrs.begin(); rrIt != m_rrs.end(); ++rrIt) {
+    rrIt->retryDelay = RETRY_DELAY_MIN;
+    this->advertise(rrIt);
+  }
+}
+
+void
+Readvertise::afterDestinationUnavailable()
+{
+  for (auto rrIt = m_rrs.begin(); rrIt != m_rrs.end();) {
+    if (rrIt->nRibRoutes > 0) {
+      rrIt->retryEvt.cancel(); // stop retrying or refreshing
+      ++rrIt;
+    }
+    else {
+      rrIt = m_rrs.erase(rrIt); // assume withdraw has completed
+    }
+  }
+}
+
+void
+Readvertise::advertise(ReadvertisedRouteContainer::iterator rrIt)
+{
+  BOOST_ASSERT(rrIt->nRibRoutes > 0);
+
+  if (!m_destination->isAvailable()) {
+    NFD_LOG_DEBUG("advertise " << rrIt->prefix << " destination-unavailable");
+    return;
+  }
+
+  m_destination->advertise(*rrIt,
+    [=] {
+      NFD_LOG_DEBUG("advertise " << rrIt->prefix << " success");
+      rrIt->retryDelay = RETRY_DELAY_MIN;
+      rrIt->retryEvt = m_scheduler.scheduleEvent(randomizeTimer(m_policy->getRefreshInterval()),
+                                                 [=] { advertise(rrIt); });
+    },
+    [=] (const std::string& msg) {
+      NFD_LOG_DEBUG("advertise " << rrIt->prefix << " failure " << msg);
+      rrIt->retryDelay = std::min(RETRY_DELAY_MAX, rrIt->retryDelay * 2);
+      rrIt->retryEvt = m_scheduler.scheduleEvent(randomizeTimer(rrIt->retryDelay),
+                                                 [=] { advertise(rrIt); });
+    });
+}
+
+void
+Readvertise::withdraw(ReadvertisedRouteContainer::iterator rrIt)
+{
+  BOOST_ASSERT(rrIt->nRibRoutes == 0);
+
+  if (!m_destination->isAvailable()) {
+    NFD_LOG_DEBUG("withdraw " << rrIt->prefix << " destination-unavailable");
+    m_rrs.erase(rrIt);
+    return;
+  }
+
+  m_destination->withdraw(*rrIt,
+    [=] {
+      NFD_LOG_DEBUG("withdraw " << rrIt->prefix << " success");
+      m_rrs.erase(rrIt);
+    },
+    [=] (const std::string& msg) {
+      NFD_LOG_DEBUG("withdraw " << rrIt->prefix << " failure " << msg);
+      rrIt->retryDelay = std::min(RETRY_DELAY_MAX, rrIt->retryDelay * 2);
+      rrIt->retryEvt = m_scheduler.scheduleEvent(randomizeTimer(rrIt->retryDelay),
+                                                 [=] { withdraw(rrIt); });
+    });
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/readvertise/readvertise.hpp b/daemon/rib/readvertise/readvertise.hpp
new file mode 100644
index 0000000..80dbbc7
--- /dev/null
+++ b/daemon/rib/readvertise/readvertise.hpp
@@ -0,0 +1,94 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_READVERTISE_HPP
+#define NFD_DAEMON_RIB_READVERTISE_READVERTISE_HPP
+
+#include "readvertise-destination.hpp"
+#include "readvertise-policy.hpp"
+#include "readvertised-route.hpp"
+#include "rib/rib.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** \brief readvertise a subset of routes to a destination according to a policy
+ *
+ *  The Readvertise class allows RIB routes to be readvertised to a destination such as a routing
+ *  protocol daemon or another NFD-RIB. It monitors the RIB for route additions and removals,
+ *  asks the ReadvertisePolicy to make decision on whether to readvertise each new route and what
+ *  prefix to readvertise as, and invokes a ReadvertiseDestination to send the commands.
+ */
+class Readvertise : noncopyable
+{
+
+public:
+  Readvertise(Rib& rib,
+              ndn::util::Scheduler& scheduler,
+              unique_ptr<ReadvertisePolicy> policy,
+              unique_ptr<ReadvertiseDestination> destination);
+
+private:
+  void
+  afterAddRoute(const RibRouteRef& ribRoute);
+
+  void
+  beforeRemoveRoute(const RibRouteRef& ribRoute);
+
+  void
+  afterDestinationAvailable();
+
+  void
+  afterDestinationUnavailable();
+
+  void
+  advertise(ReadvertisedRouteContainer::iterator rrIt);
+
+  void
+  withdraw(ReadvertisedRouteContainer::iterator rrIt);
+
+private:
+  /** \brief maps from RIB route to readvertised route derived from RIB route(s)
+   */
+  using RouteRrIndex = std::map<RibRouteRef, ReadvertisedRouteContainer::iterator>;
+
+  static const time::milliseconds RETRY_DELAY_MIN;
+  static const time::milliseconds RETRY_DELAY_MAX;
+
+  ndn::util::Scheduler& m_scheduler;
+  unique_ptr<ReadvertisePolicy> m_policy;
+  unique_ptr<ReadvertiseDestination> m_destination;
+
+  ReadvertisedRouteContainer m_rrs;
+  RouteRrIndex m_routeToRr;
+
+  signal::ScopedConnection m_addRouteConn;
+  signal::ScopedConnection m_removeRouteConn;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_READVERTISE_HPP
diff --git a/daemon/rib/readvertise/readvertised-route.hpp b/daemon/rib/readvertise/readvertised-route.hpp
new file mode 100644
index 0000000..7dc02ae
--- /dev/null
+++ b/daemon/rib/readvertise/readvertised-route.hpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_READVERTISE_READVERTISED_ROUTE_HPP
+#define NFD_DAEMON_RIB_READVERTISE_READVERTISED_ROUTE_HPP
+
+#include "core/common.hpp"
+
+#include <ndn-cxx/security/signing-info.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+namespace nfd {
+namespace rib {
+
+/** \brief state of a readvertised route
+ */
+class ReadvertisedRoute : noncopyable
+{
+public:
+  explicit
+  ReadvertisedRoute(const Name& prefix)
+    : prefix(prefix)
+    , nRibRoutes(0)
+    , retryDelay(0)
+  {
+  }
+
+public:
+  Name prefix; ///< readvertised prefix
+  mutable ndn::security::SigningInfo signer; ///< signer for commands
+  mutable size_t nRibRoutes; ///< number of RIB routes that cause the readvertisement
+  mutable time::milliseconds retryDelay; ///< retry interval (not used for refresh)
+  mutable ndn::util::scheduler::ScopedEventId retryEvt; ///< retry or refresh event
+};
+
+inline bool
+operator<(const ReadvertisedRoute& lhs, const ReadvertisedRoute& rhs)
+{
+  return lhs.prefix < rhs.prefix;
+}
+
+using ReadvertisedRouteContainer = std::set<ReadvertisedRoute>;
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_READVERTISE_READVERTISED_ROUTE_HPP
diff --git a/daemon/rib/rib-entry.cpp b/daemon/rib/rib-entry.cpp
new file mode 100644
index 0000000..008ac4f
--- /dev/null
+++ b/daemon/rib/rib-entry.cpp
@@ -0,0 +1,290 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-entry.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/mgmt/nfd/control-command.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(RibEntry);
+
+RibEntry::RouteList::iterator
+RibEntry::findRoute(const Route& route)
+{
+  return std::find_if(begin(), end(), bind(&compareFaceIdAndOrigin, _1, route));
+}
+
+RibEntry::RouteList::const_iterator
+RibEntry::findRoute(const Route& route) const
+{
+  return std::find_if(begin(), end(), bind(&compareFaceIdAndOrigin, _1, route));
+}
+
+std::pair<RibEntry::iterator, bool>
+RibEntry::insertRoute(const Route& route)
+{
+  iterator it = findRoute(route);
+
+  if (it == end()) {
+    if (route.flags & ndn::nfd::ROUTE_FLAG_CAPTURE) {
+      m_nRoutesWithCaptureSet++;
+    }
+
+    m_routes.push_back(route);
+    return {std::prev(m_routes.end()), true};
+  }
+
+  return {it, false};
+}
+
+void
+RibEntry::eraseRoute(const Route& route)
+{
+  RibEntry::iterator it = findRoute(route);
+  eraseRoute(it);
+}
+
+bool
+RibEntry::hasRoute(const Route& route)
+{
+  RibEntry::const_iterator it = findRoute(route);
+
+  return it != end();
+}
+
+bool
+RibEntry::hasFaceId(const uint64_t faceId) const
+{
+  RibEntry::const_iterator it = std::find_if(begin(), end(), bind(&compareFaceId, _1, faceId));
+
+  return it != end();
+}
+
+size_t
+RibEntry::getNRoutes() const
+{
+  return m_routes.size();
+}
+
+void
+RibEntry::addChild(shared_ptr<RibEntry> child)
+{
+  BOOST_ASSERT(!child->getParent());
+  child->setParent(this->shared_from_this());
+  m_children.push_back(std::move(child));
+}
+
+void
+RibEntry::removeChild(shared_ptr<RibEntry> child)
+{
+  BOOST_ASSERT(child->getParent().get() == this);
+  child->setParent(nullptr);
+  m_children.remove(child);
+}
+
+RibEntry::RouteList::iterator
+RibEntry::eraseRoute(RouteList::iterator route)
+{
+  if (route != m_routes.end()) {
+    if (route->flags & ndn::nfd::ROUTE_FLAG_CAPTURE) {
+      m_nRoutesWithCaptureSet--;
+    }
+
+    // Cancel any scheduled event
+    NFD_LOG_TRACE("Cancelling expiration event: " << route->getExpirationEvent());
+    route->cancelExpirationEvent();
+
+    return m_routes.erase(route);
+  }
+
+  return m_routes.end();
+}
+
+void
+RibEntry::addInheritedRoute(const Route& route)
+{
+  m_inheritedRoutes.push_back(route);
+}
+
+void
+RibEntry::removeInheritedRoute(const Route& route)
+{
+  m_inheritedRoutes.remove_if(bind(&compareFaceId, _1, route.faceId));
+}
+
+RibEntry::RouteList::const_iterator
+RibEntry::findInheritedRoute(const Route& route) const
+{
+  return std::find_if(m_inheritedRoutes.begin(), m_inheritedRoutes.end(),
+                      bind(&compareFaceId, _1, route.faceId));
+}
+
+bool
+RibEntry::hasInheritedRoute(const Route& route) const
+{
+  return findInheritedRoute(route) != m_inheritedRoutes.end();
+}
+
+bool
+RibEntry::hasCapture() const
+{
+  return m_nRoutesWithCaptureSet > 0;
+}
+
+bool
+RibEntry::hasChildInheritOnFaceId(uint64_t faceId) const
+{
+  for (const Route& route : m_routes) {
+    if (route.faceId == faceId && (route.flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+const Route*
+RibEntry::getRouteWithLowestCostByFaceId(uint64_t faceId) const
+{
+  const Route* candidate = nullptr;
+
+  for (const Route& route : m_routes) {
+    // Matching face ID
+    if (route.faceId == faceId) {
+      // If this is the first route with this Face ID found
+      if (candidate == nullptr) {
+        candidate = &route;
+      }
+      else if (route.cost < candidate->cost) {
+        // Found a route with a lower cost
+        candidate = &route;
+      }
+    }
+  }
+
+  return candidate;
+}
+
+const Route*
+RibEntry::getRouteWithSecondLowestCostByFaceId(uint64_t faceId) const
+{
+  std::vector<const Route*> matches;
+
+  // Copy routes which have faceId
+  for (const Route& route : m_routes) {
+    if (route.faceId == faceId) {
+      matches.push_back(&route);
+    }
+  }
+
+  // If there are less than 2 routes, there is no second lowest
+  if (matches.size() < 2) {
+    return nullptr;
+  }
+
+  // Get second lowest cost
+  std::nth_element(matches.begin(), matches.begin() + 1, matches.end(),
+    [] (const Route* lhs, const Route* rhs) { return lhs->cost < rhs->cost; });
+
+  return matches.at(1);
+}
+
+const Route*
+RibEntry::getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId) const
+{
+  const Route* candidate = nullptr;
+
+  for (const Route& route : m_routes) {
+    // Correct face ID and Child Inherit flag set
+    if (route.faceId == faceId &&
+        (route.flags & ndn::nfd::ROUTE_FLAG_CHILD_INHERIT) == ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+    {
+      // If this is the first route with this Face ID found
+      if (candidate == nullptr) {
+        candidate = &route;
+      }
+      else if (route.cost < candidate->cost) {
+        // Found a route with a lower cost
+        candidate = &route;
+      }
+    }
+  }
+
+  return candidate;
+}
+
+ndn::PrefixAnnouncement
+RibEntry::getPrefixAnnouncement(time::milliseconds minExpiration,
+                                time::milliseconds maxExpiration) const
+{
+  const Route* bestAnnRoute = nullptr;
+  auto entryExpiry = time::steady_clock::TimePoint::min();
+
+  for (const Route& route : *this) {
+    if (route.expires) {
+      entryExpiry = std::max(entryExpiry, *route.expires);
+      if (route.announcement) {
+        if (bestAnnRoute == nullptr || *route.expires > *bestAnnRoute->expires) {
+          bestAnnRoute = &route;
+        }
+      }
+    }
+    else {
+      entryExpiry = time::steady_clock::TimePoint::max();
+    }
+  }
+
+  if (bestAnnRoute != nullptr) {
+    return *bestAnnRoute->announcement;
+  }
+
+  ndn::PrefixAnnouncement ann;
+  ann.setAnnouncedName(m_name);
+  ann.setExpiration(ndn::clamp(
+    time::duration_cast<time::milliseconds>(entryExpiry - time::steady_clock::now()),
+    minExpiration, maxExpiration));
+  return ann;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibEntry& entry)
+{
+  os << "RibEntry {\n";
+  os << "  Name: " << entry.getName() << "\n";
+
+  for (const Route& route : entry) {
+    os << "  " << route << "\n";
+  }
+
+  os << "}";
+
+  return os;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/rib-entry.hpp b/daemon/rib/rib-entry.hpp
new file mode 100644
index 0000000..45d5a87
--- /dev/null
+++ b/daemon/rib/rib-entry.hpp
@@ -0,0 +1,288 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_RIB_ENTRY_HPP
+#define NFD_DAEMON_RIB_RIB_ENTRY_HPP
+
+#include "route.hpp"
+
+#include <list>
+
+namespace nfd {
+namespace rib {
+
+/** \brief Represents a RIB entry, which contains one or more Routes with the same prefix.
+ */
+class RibEntry : public std::enable_shared_from_this<RibEntry>
+{
+public:
+  typedef std::list<Route> RouteList;
+  typedef RouteList::iterator iterator;
+  typedef RouteList::const_iterator const_iterator;
+
+  RibEntry()
+    : m_nRoutesWithCaptureSet(0)
+  {
+  }
+
+  void
+  setName(const Name& prefix);
+
+  const Name&
+  getName() const;
+
+  shared_ptr<RibEntry>
+  getParent() const;
+
+  bool
+  hasParent() const;
+
+  void
+  addChild(shared_ptr<RibEntry> child);
+
+  void
+  removeChild(shared_ptr<RibEntry> child);
+
+  const std::list<shared_ptr<RibEntry>>&
+  getChildren() const;
+
+  bool
+  hasChildren() const;
+
+  /** \brief inserts a new route into the entry's route list
+   *  If another route already exists with the same faceId and origin,
+   *  the new route is not inserted.
+   *  \return a pair, whose first element is the iterator to the newly
+   *  inserted element if the insert succeeds and to the
+   *  previously-existing element otherwise, and whose second element
+   *  is true if the insert succeeds and false otherwise.
+   */
+  std::pair<RibEntry::iterator, bool>
+  insertRoute(const Route& route);
+
+  /** \brief erases a Route with the same faceId and origin
+   */
+  void
+  eraseRoute(const Route& route);
+
+  /** \brief erases a Route with the passed iterator
+   *  \return{ an iterator to the element that followed the erased iterator }
+   */
+  iterator
+  eraseRoute(RouteList::iterator route);
+
+  bool
+  hasFaceId(const uint64_t faceId) const;
+
+  const RouteList&
+  getRoutes() const;
+
+  size_t
+  getNRoutes() const;
+
+  iterator
+  findRoute(const Route& route);
+
+  const_iterator
+  findRoute(const Route& route) const;
+
+  bool
+  hasRoute(const Route& route);
+
+  void
+  addInheritedRoute(const Route& route);
+
+  void
+  removeInheritedRoute(const Route& route);
+
+  /** \brief Returns the routes this namespace has inherited.
+   *  The inherited routes returned represent inherited routes this namespace has in the FIB.
+   *  \return{ routes inherited by this namespace }
+   */
+  const RouteList&
+  getInheritedRoutes() const;
+
+  /** \brief Finds an inherited route with a matching face ID.
+   *  \return{ An iterator to the matching route if one is found;
+   *           otherwise, an iterator to the end of the entry's
+   *           inherited route list }
+   */
+  RouteList::const_iterator
+  findInheritedRoute(const Route& route) const;
+
+  /** \brief Determines if the entry has an inherited route with a matching face ID.
+   *  \return{ True, if a matching inherited route is found; otherwise, false. }
+   */
+  bool
+  hasInheritedRoute(const Route& route) const;
+
+  bool
+  hasCapture() const;
+
+  /** \brief Determines if the entry has an inherited route with the passed
+   *         face ID and its child inherit flag set.
+   *  \return{ True, if a matching inherited route is found; otherwise, false. }
+   */
+  bool
+  hasChildInheritOnFaceId(uint64_t faceId) const;
+
+  /** \brief Returns the route with the lowest cost that has the passed face ID.
+   *  \return{ The route with the lowest cost that has the passed face ID}
+   */
+  const Route*
+  getRouteWithLowestCostByFaceId(uint64_t faceId) const;
+
+  const Route*
+  getRouteWithSecondLowestCostByFaceId(uint64_t faceId) const;
+
+  /** \brief Returns the route with the lowest cost that has the passed face ID
+   *         and its child inherit flag set.
+   *  \return{ The route with the lowest cost that has the passed face ID
+   *           and its child inherit flag set }
+   */
+  const Route*
+  getRouteWithLowestCostAndChildInheritByFaceId(uint64_t faceId) const;
+
+  /** \brief Retrieve a prefix announcement suitable for readvertising this route.
+   *
+   *  If one or more routes in this RIB entry contains a prefix announcement, this method returns
+   *  the announcement from the route that expires last.
+   *
+   *  If this RIB entry does not have a route containing a prefix announcement, this method creates
+   *  a new announcement. Its expiration period reflects the remaining lifetime of this RIB entry,
+   *  confined within [\p minExpiration, \p maxExpiration] range. The caller is expected to sign
+   *  this announcement.
+   *
+   *  \warning (minExpiration > maxExpiration) triggers undefined behavior.
+   */
+  ndn::PrefixAnnouncement
+  getPrefixAnnouncement(time::milliseconds minExpiration = 15_s,
+                        time::milliseconds maxExpiration = 1_h) const;
+
+  const_iterator
+  begin() const;
+
+  const_iterator
+  end() const;
+
+  iterator
+  begin();
+
+  iterator
+  end();
+
+private:
+  void
+  setParent(shared_ptr<RibEntry> parent);
+
+private:
+  Name m_name;
+  std::list<shared_ptr<RibEntry>> m_children;
+  shared_ptr<RibEntry> m_parent;
+  RouteList m_routes;
+  RouteList m_inheritedRoutes;
+
+  /** \brief The number of routes on this namespace with the capture flag set.
+   *
+   *  This count is used to check if the namespace will block inherited routes.
+   *  If the number is greater than zero, a route on the namespace has its capture
+   *  flag set which means the namespace should not inherit any routes.
+   */
+  uint64_t m_nRoutesWithCaptureSet;
+};
+
+inline void
+RibEntry::setName(const Name& prefix)
+{
+  m_name = prefix;
+}
+
+inline const Name&
+RibEntry::getName() const
+{
+  return m_name;
+}
+
+inline void
+RibEntry::setParent(shared_ptr<RibEntry> parent)
+{
+  m_parent = parent;
+}
+
+inline shared_ptr<RibEntry>
+RibEntry::getParent() const
+{
+  return m_parent;
+}
+
+inline const std::list<shared_ptr<RibEntry>>&
+RibEntry::getChildren() const
+{
+  return m_children;
+}
+
+inline const RibEntry::RouteList&
+RibEntry::getRoutes() const
+{
+  return m_routes;
+}
+
+inline const RibEntry::RouteList&
+RibEntry::getInheritedRoutes() const
+{
+  return m_inheritedRoutes;
+}
+
+inline RibEntry::const_iterator
+RibEntry::begin() const
+{
+  return m_routes.begin();
+}
+
+inline RibEntry::const_iterator
+RibEntry::end() const
+{
+  return m_routes.end();
+}
+
+inline RibEntry::iterator
+RibEntry::begin()
+{
+  return m_routes.begin();
+}
+
+inline RibEntry::iterator
+RibEntry::end()
+{
+  return m_routes.end();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibEntry& entry);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_RIB_ENTRY_HPP
diff --git a/daemon/rib/rib-manager.cpp b/daemon/rib/rib-manager.cpp
new file mode 100644
index 0000000..4477ebc
--- /dev/null
+++ b/daemon/rib/rib-manager.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-manager.hpp"
+
+#include "core/fib-max-depth.hpp"
+#include "core/logger.hpp"
+
+#include <ndn-cxx/lp/tags.hpp>
+#include <ndn-cxx/mgmt/nfd/control-command.hpp>
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+#include <ndn-cxx/mgmt/nfd/control-response.hpp>
+#include <ndn-cxx/mgmt/nfd/face-status.hpp>
+#include <ndn-cxx/mgmt/nfd/rib-entry.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(RibManager);
+
+static const std::string MGMT_MODULE_NAME = "rib";
+static const Name LOCALHOST_TOP_PREFIX = "/localhost/nfd";
+static const time::seconds ACTIVE_FACE_FETCH_INTERVAL = 5_min;
+
+const Name RibManager::LOCALHOP_TOP_PREFIX = "/localhop/nfd";
+
+RibManager::RibManager(Rib& rib, ndn::Face& face, ndn::KeyChain& keyChain,
+                       ndn::nfd::Controller& nfdController, Dispatcher& dispatcher,
+                       ndn::util::Scheduler& scheduler)
+  : ManagerBase(dispatcher, MGMT_MODULE_NAME)
+  , m_rib(rib)
+  , m_keyChain(keyChain)
+  , m_nfdController(nfdController)
+  , m_dispatcher(dispatcher)
+  , m_scheduler(scheduler)
+  , m_faceMonitor(face)
+  , m_localhostValidator(face)
+  , m_localhopValidator(face)
+  , m_isLocalhopEnabled(false)
+{
+  registerCommandHandler<ndn::nfd::RibRegisterCommand>("register",
+    bind(&RibManager::registerEntry, this, _2, _3, _4, _5));
+  registerCommandHandler<ndn::nfd::RibUnregisterCommand>("unregister",
+    bind(&RibManager::unregisterEntry, this, _2, _3, _4, _5));
+
+  registerStatusDatasetHandler("list", bind(&RibManager::listEntries, this, _1, _2, _3));
+}
+
+void
+RibManager::applyLocalhostConfig(const ConfigSection& section, const std::string& filename)
+{
+  m_localhostValidator.load(section, filename);
+}
+
+void
+RibManager::enableLocalhop(const ConfigSection& section, const std::string& filename)
+{
+  m_localhopValidator.load(section, filename);
+  m_isLocalhopEnabled = true;
+}
+
+void
+RibManager::disableLocalhop()
+{
+  m_isLocalhopEnabled = false;
+}
+
+void
+RibManager::registerWithNfd()
+{
+  registerTopPrefix(LOCALHOST_TOP_PREFIX);
+
+  if (m_isLocalhopEnabled) {
+    registerTopPrefix(LOCALHOP_TOP_PREFIX);
+  }
+
+  NFD_LOG_INFO("Start monitoring face create/destroy events");
+  m_faceMonitor.onNotification.connect(bind(&RibManager::onNotification, this, _1));
+  m_faceMonitor.start();
+
+  scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
+}
+
+void
+RibManager::enableLocalFields()
+{
+  m_nfdController.start<ndn::nfd::FaceUpdateCommand>(
+    ControlParameters().setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, true),
+    [] (const ControlParameters&) {
+      NFD_LOG_DEBUG("Local fields enabled");
+    },
+    [] (const ControlResponse& res) {
+      NDN_THROW(Error("Couldn't enable local fields (" + to_string(res.getCode()) +
+                      " " + res.getText() + ")"));
+    });
+}
+
+void
+RibManager::beginAddRoute(const Name& name, Route route, optional<time::nanoseconds> expires,
+                          const std::function<void(RibUpdateResult)>& done)
+{
+  if (expires) {
+    route.expires = time::steady_clock::now() + *expires;
+  }
+  else if (route.expires) {
+    expires = *route.expires - time::steady_clock::now();
+  }
+
+  if (expires && *expires <= 0_s) {
+    m_rib.onRouteExpiration(name, route);
+    return done(RibUpdateResult::EXPIRED);
+  }
+
+  NFD_LOG_INFO("Adding route " << name << " nexthop=" << route.faceId <<
+               " origin=" << route.origin << " cost=" << route.cost);
+
+  if (expires) {
+    auto event = m_scheduler.scheduleEvent(*expires, [=] { m_rib.onRouteExpiration(name, route); });
+    route.setExpirationEvent(event);
+    NFD_LOG_TRACE("Scheduled unregistration at: " << *route.expires);
+  }
+
+  m_registeredFaces.insert(route.faceId);
+
+  RibUpdate update;
+  update.setAction(RibUpdate::REGISTER)
+        .setName(name)
+        .setRoute(route);
+  beginRibUpdate(update, done);
+}
+
+void
+RibManager::beginRemoveRoute(const Name& name, const Route& route,
+                             const std::function<void(RibUpdateResult)>& done)
+{
+  NFD_LOG_INFO("Removing route " << name << " nexthop=" << route.faceId <<
+               " origin=" << route.origin);
+
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(name)
+        .setRoute(route);
+  beginRibUpdate(update, done);
+}
+
+void
+RibManager::beginRibUpdate(const RibUpdate& update,
+                           const std::function<void(RibUpdateResult)>& done)
+{
+  m_rib.beginApplyUpdate(update,
+    [=] {
+      NFD_LOG_DEBUG("RIB update succeeded for " << update);
+      done(RibUpdateResult::OK);
+    },
+    [=] (uint32_t code, const std::string& error) {
+      NFD_LOG_DEBUG("RIB update failed for " << update << " (" << code << " " << error << ")");
+
+      // Since the FIB rejected the update, clean up invalid routes
+      scheduleActiveFaceFetch(1_s);
+
+      done(RibUpdateResult::ERROR);
+    });
+}
+
+void
+RibManager::registerTopPrefix(const Name& topPrefix)
+{
+  // add FIB nexthop
+  m_nfdController.start<ndn::nfd::FibAddNextHopCommand>(
+    ControlParameters().setName(Name(topPrefix).append(MGMT_MODULE_NAME))
+                       .setFaceId(0),
+    [=] (const ControlParameters& res) {
+      NFD_LOG_DEBUG("Successfully registered " << topPrefix << " with NFD");
+
+      // Routes must be inserted into the RIB so route flags can be applied
+      Route route;
+      route.faceId = res.getFaceId();
+      route.origin = ndn::nfd::ROUTE_ORIGIN_APP;
+      route.flags = ndn::nfd::ROUTE_FLAG_CHILD_INHERIT;
+
+      m_rib.insert(topPrefix, route);
+
+      m_registeredFaces.insert(route.faceId);
+    },
+    [=] (const ControlResponse& res) {
+      NDN_THROW(Error("Cannot add FIB entry " + topPrefix.toUri() + " (" +
+                      to_string(res.getCode()) + " " + res.getText() + ")"));
+    });
+
+  // add top prefix to the dispatcher without prefix registration
+  m_dispatcher.addTopPrefix(topPrefix, false);
+}
+
+void
+RibManager::registerEntry(const Name& topPrefix, const Interest& interest,
+                          ControlParameters parameters,
+                          const ndn::mgmt::CommandContinuation& done)
+{
+  if (parameters.getName().size() > FIB_MAX_DEPTH) {
+    done(ControlResponse(414, "Route prefix cannot exceed " + ndn::to_string(FIB_MAX_DEPTH) +
+                              " components"));
+    return;
+  }
+
+  setFaceForSelfRegistration(interest, parameters);
+
+  // Respond since command is valid and authorized
+  done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
+
+  Route route;
+  route.faceId = parameters.getFaceId();
+  route.origin = parameters.getOrigin();
+  route.cost = parameters.getCost();
+  route.flags = parameters.getFlags();
+
+  optional<time::nanoseconds> expires;
+  if (parameters.hasExpirationPeriod() &&
+      parameters.getExpirationPeriod() != time::milliseconds::max()) {
+    expires = time::duration_cast<time::nanoseconds>(parameters.getExpirationPeriod());
+  }
+
+  beginAddRoute(parameters.getName(), std::move(route), expires, [] (RibUpdateResult) {});
+}
+
+void
+RibManager::unregisterEntry(const Name& topPrefix, const Interest& interest,
+                            ControlParameters parameters,
+                            const ndn::mgmt::CommandContinuation& done)
+{
+  setFaceForSelfRegistration(interest, parameters);
+
+  // Respond since command is valid and authorized
+  done(ControlResponse(200, "Success").setBody(parameters.wireEncode()));
+
+  Route route;
+  route.faceId = parameters.getFaceId();
+  route.origin = parameters.getOrigin();
+
+  beginRemoveRoute(parameters.getName(), route, [] (RibUpdateResult) {});
+}
+
+void
+RibManager::listEntries(const Name& topPrefix, const Interest& interest,
+                        ndn::mgmt::StatusDatasetContext& context)
+{
+  auto now = time::steady_clock::now();
+  for (const auto& kv : m_rib) {
+    const RibEntry& entry = *kv.second;
+    ndn::nfd::RibEntry item;
+    item.setName(entry.getName());
+    for (const Route& route : entry.getRoutes()) {
+      ndn::nfd::Route r;
+      r.setFaceId(route.faceId);
+      r.setOrigin(route.origin);
+      r.setCost(route.cost);
+      r.setFlags(route.flags);
+      if (route.expires) {
+        r.setExpirationPeriod(time::duration_cast<time::milliseconds>(*route.expires - now));
+      }
+      item.addRoute(r);
+    }
+    context.append(item.wireEncode());
+  }
+  context.end();
+}
+
+void
+RibManager::setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters)
+{
+  bool isSelfRegistration = (parameters.getFaceId() == 0);
+  if (isSelfRegistration) {
+    shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = request.getTag<lp::IncomingFaceIdTag>();
+    // NDNLPv2 says "application MUST be prepared to receive a packet without IncomingFaceId field",
+    // but it's fine to assert IncomingFaceId is available, because InternalFace lives inside NFD
+    // and is initialized synchronously with IncomingFaceId field enabled.
+    BOOST_ASSERT(incomingFaceIdTag != nullptr);
+    parameters.setFaceId(*incomingFaceIdTag);
+  }
+}
+
+ndn::mgmt::Authorization
+RibManager::makeAuthorization(const std::string& verb)
+{
+  return [this] (const Name& prefix, const Interest& interest,
+                 const ndn::mgmt::ControlParameters* params,
+                 const ndn::mgmt::AcceptContinuation& accept,
+                 const ndn::mgmt::RejectContinuation& reject) {
+    BOOST_ASSERT(params != nullptr);
+    BOOST_ASSERT(typeid(*params) == typeid(ndn::nfd::ControlParameters));
+    BOOST_ASSERT(prefix == LOCALHOST_TOP_PREFIX || prefix == LOCALHOP_TOP_PREFIX);
+
+    ndn::ValidatorConfig& validator = prefix == LOCALHOST_TOP_PREFIX ?
+                                      m_localhostValidator : m_localhopValidator;
+    validator.validate(interest,
+                       bind([&interest, this, accept] { extractRequester(interest, accept); }),
+                       bind([reject] { reject(ndn::mgmt::RejectReply::STATUS403); }));
+  };
+}
+
+std::ostream&
+operator<<(std::ostream& os, RibManager::SlAnnounceResult res)
+{
+  switch (res) {
+    case RibManager::SlAnnounceResult::OK:
+      return os << "OK";
+    case RibManager::SlAnnounceResult::ERROR:
+      return os << "ERROR";
+    case RibManager::SlAnnounceResult::VALIDATION_FAILURE:
+      return os << "VALIDATION_FAILURE";
+    case RibManager::SlAnnounceResult::EXPIRED:
+      return os << "EXPIRED";
+    case RibManager::SlAnnounceResult::NOT_FOUND:
+      return os << "NOT_FOUND";
+  }
+  BOOST_ASSERT_MSG(false, "bad SlAnnounceResult");
+  return os;
+}
+
+RibManager::SlAnnounceResult
+RibManager::getSlAnnounceResultFromRibUpdateResult(RibUpdateResult r)
+{
+  switch (r) {
+  case RibUpdateResult::OK:
+    return SlAnnounceResult::OK;
+  case RibUpdateResult::ERROR:
+    return SlAnnounceResult::ERROR;
+  case RibUpdateResult::EXPIRED:
+    return SlAnnounceResult::EXPIRED;
+  default:
+    BOOST_ASSERT(false);
+    return SlAnnounceResult::ERROR;
+  }
+}
+
+void
+RibManager::slAnnounce(const ndn::PrefixAnnouncement& pa, uint64_t faceId,
+                       time::milliseconds maxLifetime, const SlAnnounceCallback& cb)
+{
+  BOOST_ASSERT(pa.getData());
+
+  if (!m_isLocalhopEnabled) {
+    NFD_LOG_INFO("slAnnounce " << pa.getAnnouncedName() << " " << faceId <<
+                 ": localhop_security unconfigured");
+    cb(SlAnnounceResult::VALIDATION_FAILURE);
+    return;
+  }
+
+  m_localhopValidator.validate(*pa.getData(),
+    [=] (const Data&) {
+      Route route(pa, faceId);
+      route.expires = std::min(route.annExpires, time::steady_clock::now() + maxLifetime);
+      beginAddRoute(pa.getAnnouncedName(), route, nullopt,
+        [=] (RibUpdateResult ribRes) {
+          auto res = getSlAnnounceResultFromRibUpdateResult(ribRes);
+          NFD_LOG_INFO("slAnnounce " << pa.getAnnouncedName() << " " << faceId << ": " << res);
+          cb(res);
+        });
+    },
+    [=] (const Data&, ndn::security::v2::ValidationError err) {
+      NFD_LOG_INFO("slAnnounce " << pa.getAnnouncedName() << " " << faceId <<
+                   " validation error: " << err);
+      cb(SlAnnounceResult::VALIDATION_FAILURE);
+    }
+  );
+}
+
+void
+RibManager::slRenew(const Name& name, uint64_t faceId, time::milliseconds maxLifetime,
+                    const SlAnnounceCallback& cb)
+{
+  Route routeQuery;
+  routeQuery.faceId = faceId;
+  routeQuery.origin = ndn::nfd::ROUTE_ORIGIN_PREFIXANN;
+  Route* oldRoute = m_rib.findLongestPrefix(name, routeQuery);
+
+  if (oldRoute == nullptr || !oldRoute->announcement) {
+    NFD_LOG_DEBUG("slRenew " << name << " " << faceId << ": not found");
+    return cb(SlAnnounceResult::NOT_FOUND);
+  }
+  Name routeName = oldRoute->announcement->getAnnouncedName();
+
+  Route route = *oldRoute;
+  route.expires = std::min(route.annExpires, time::steady_clock::now() + maxLifetime);
+  beginAddRoute(routeName, route, nullopt,
+    [=] (RibUpdateResult ribRes) {
+      auto res = getSlAnnounceResultFromRibUpdateResult(ribRes);
+      NFD_LOG_INFO("slRenew " << name << " " << faceId << ": " << res << " " << routeName);
+      cb(res);
+    });
+}
+
+void
+RibManager::slFindAnn(const Name& name, const SlFindAnnCallback& cb) const
+{
+  shared_ptr<RibEntry> entry;
+  auto exactMatch = m_rib.find(name);
+  if (exactMatch != m_rib.end()) {
+    entry = exactMatch->second;
+  }
+  else {
+    entry = m_rib.findParent(name);
+  }
+  if (entry == nullptr) {
+    return cb(nullopt);
+  }
+
+  auto pa = entry->getPrefixAnnouncement();
+  pa.toData(m_keyChain);
+  cb(pa);
+}
+
+void
+RibManager::fetchActiveFaces()
+{
+  NFD_LOG_DEBUG("Fetching active faces");
+
+  m_nfdController.fetch<ndn::nfd::FaceDataset>(
+    bind(&RibManager::removeInvalidFaces, this, _1),
+    bind(&RibManager::onFetchActiveFacesFailure, this, _1, _2),
+    ndn::nfd::CommandOptions());
+}
+
+void
+RibManager::onFetchActiveFacesFailure(uint32_t code, const std::string& reason)
+{
+  NFD_LOG_DEBUG("Face Status Dataset request failure " << code << " " << reason);
+  scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
+}
+
+void
+RibManager::onFaceDestroyedEvent(uint64_t faceId)
+{
+  m_rib.beginRemoveFace(faceId);
+  m_registeredFaces.erase(faceId);
+}
+
+void
+RibManager::scheduleActiveFaceFetch(const time::seconds& timeToWait)
+{
+  m_activeFaceFetchEvent = m_scheduler.scheduleEvent(timeToWait, [this] { fetchActiveFaces(); });
+}
+
+void
+RibManager::removeInvalidFaces(const std::vector<ndn::nfd::FaceStatus>& activeFaces)
+{
+  NFD_LOG_DEBUG("Checking for invalid face registrations");
+
+  FaceIdSet activeFaceIds;
+  for (const auto& faceStatus : activeFaces) {
+    activeFaceIds.insert(faceStatus.getFaceId());
+  }
+
+  // Look for face IDs that were registered but not active to find missed
+  // face destroyed events
+  for (auto faceId : m_registeredFaces) {
+    if (activeFaceIds.count(faceId) == 0) {
+      NFD_LOG_DEBUG("Removing invalid face ID: " << faceId);
+      m_scheduler.scheduleEvent(0_ns, [this, faceId] { this->onFaceDestroyedEvent(faceId); });
+    }
+  }
+
+  // Reschedule the check for future clean up
+  scheduleActiveFaceFetch(ACTIVE_FACE_FETCH_INTERVAL);
+}
+
+void
+RibManager::onNotification(const ndn::nfd::FaceEventNotification& notification)
+{
+  NFD_LOG_TRACE("onNotification: " << notification);
+
+  if (notification.getKind() == ndn::nfd::FACE_EVENT_DESTROYED) {
+    NFD_LOG_DEBUG("Received notification for destroyed faceId: " << notification.getFaceId());
+    m_scheduler.scheduleEvent(0_ns, [this, id = notification.getFaceId()] { onFaceDestroyedEvent(id); });
+  }
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/rib-manager.hpp b/daemon/rib/rib-manager.hpp
new file mode 100644
index 0000000..503dc2f
--- /dev/null
+++ b/daemon/rib/rib-manager.hpp
@@ -0,0 +1,268 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_RIB_MANAGER_HPP
+#define NFD_DAEMON_RIB_RIB_MANAGER_HPP
+
+#include "fib-updater.hpp"
+#include "rib.hpp"
+
+#include "core/config-file.hpp"
+#include "core/manager-base.hpp"
+
+#include <ndn-cxx/security/validator-config.hpp>
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+#include <ndn-cxx/mgmt/nfd/face-event-notification.hpp>
+#include <ndn-cxx/mgmt/nfd/face-monitor.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+namespace nfd {
+namespace rib {
+
+/**
+ * @brief Serve commands and datasets in NFD RIB management protocol.
+ */
+class RibManager : public nfd::ManagerBase
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    using std::runtime_error::runtime_error;
+  };
+
+  RibManager(Rib& rib, ndn::Face& face, ndn::KeyChain& keyChain, ndn::nfd::Controller& nfdController,
+             Dispatcher& dispatcher, ndn::util::Scheduler& scheduler);
+
+  /**
+   * @brief Apply localhost_security configuration.
+   */
+  void
+  applyLocalhostConfig(const ConfigSection& section, const std::string& filename);
+
+  /**
+   * @brief Apply localhop_security configuration and allow accepting commands on
+   *        /localhop/nfd/rib prefix.
+   */
+  void
+  enableLocalhop(const ConfigSection& section, const std::string& filename);
+
+  /**
+   * @brief Disallow accepting commands on /localhop/nfd/rib prefix.
+   */
+  void
+  disableLocalhop();
+
+  /**
+   * @brief Start accepting commands and dataset requests.
+   */
+  void
+  registerWithNfd();
+
+  /**
+   * @brief Enable NDNLP IncomingFaceId field in order to support self-registration commands.
+   */
+  void
+  enableLocalFields();
+
+public: // self-learning support
+  enum class SlAnnounceResult {
+    OK,                 ///< RIB and FIB have been updated
+    ERROR,              ///< unspecified error
+    VALIDATION_FAILURE, ///< the announcement cannot be verified against the trust schema
+    EXPIRED,            ///< the announcement has expired
+    NOT_FOUND,          ///< route does not exist (slRenew only)
+  };
+
+  using SlAnnounceCallback = std::function<void(SlAnnounceResult res)>;
+  using SlFindAnnCallback = std::function<void(optional<ndn::PrefixAnnouncement>)>;
+
+  /** \brief Insert a route by prefix announcement from self-learning strategy.
+   *  \param pa A prefix announcement. It must contain the Data.
+   *  \param faceId Face on which the announcement arrives.
+   *  \param maxLifetime Maximum route lifetime as imposed by self-learning strategy.
+   *  \param cb Callback to receive the operation result.
+   *
+   *  If \p pa passes validation and is unexpired, inserts or replaces a route for the announced
+   *  name and faceId whose lifetime is set to the earlier of now+maxLifetime or prefix
+   *  announcement expiration time, updates FIB, and invokes \p cb with SlAnnounceResult::OK.
+   *  In case \p pa expires when validation completes, invokes \p cb with SlAnnounceResult::EXPIRED.
+   *  If \p pa cannot be verified by the trust schema given in rib.localhop_security config key,
+   *  or the relevant config has not been loaded via \c enableLocalHop, invokes \p cb with
+   *  SlAnnounceResult::VALIDATION_FAILURE.
+   *
+   *  Self-learning strategy invokes this method after receiving a Data carrying a prefix
+   *  announcement.
+   */
+  void
+  slAnnounce(const ndn::PrefixAnnouncement& pa, uint64_t faceId, time::milliseconds maxLifetime,
+             const SlAnnounceCallback& cb);
+
+  /** \brief Renew a route created by prefix announcement from self-learning strategy.
+   *  \param name Data name, for finding RIB entry by longest-prefix-match.
+   *  \param faceId Nexthop face.
+   *  \param maxLifetime Maximum route lifetime as imposed by self-learning strategy.
+   *  \param cb Callback to receive the operation result.
+   *
+   *  If the specified route exists, prolongs its lifetime to the earlier of now+maxLifetime or
+   *  prefix announcement expiration time, and invokes \p cb with SlAnnounceResult::OK.
+   *  If the prefix announcement has expired, invokes \p cb with SlAnnounceResult::EXPIRED.
+   *  If the route is not found, invokes \p cb with SlAnnounceResult::NOT_FOUND.
+   *
+   *  Self-learning strategy invokes this method after an Interest forwarded via a learned route
+   *  is satisfied.
+   *
+   *  \bug In current implementation, if an slAnnounce operation is in progress to create a Route
+   *       or replace a prefix announcement, slRenew could fail because Route does not exist in
+   *       existing RIB, or overwrite the new prefix announcement with an old one.
+   */
+  void
+  slRenew(const Name& name, uint64_t faceId, time::milliseconds maxLifetime,
+          const SlAnnounceCallback& cb);
+
+  /** \brief Retrieve an outgoing prefix announcement for self-learning strategy.
+   *  \param name Data name.
+   *  \param cb Callback to receive a prefix announcement that announces a prefix of \p name, or
+   *            nullopt if no RIB entry is found by longest-prefix-match of \p name.
+   *
+   *  Self-learning strategy invokes this method before sending a Data in reply to a discovery
+   *  Interest, so as to attach a prefix announcement onto that Data.
+   *
+   *  \bug In current implementation, if an slAnnounce operation is in progress, slFindAnn does not
+   *       wait for that operation to complete and its result reflects the prior RIB state.
+   */
+  void
+  slFindAnn(const Name& name, const SlFindAnnCallback& cb) const;
+
+private: // RIB and FibUpdater actions
+  enum class RibUpdateResult
+  {
+    OK,
+    ERROR,
+    EXPIRED,
+  };
+
+  static SlAnnounceResult
+  getSlAnnounceResultFromRibUpdateResult(RibUpdateResult r);
+
+  /** \brief Start adding a route to RIB and FIB.
+   *  \param name route name
+   *  \param route route parameters; may contain absolute expiration time
+   *  \param expires relative expiration time; if specified, overwrites \c route.expires
+   *  \param done completion callback
+   */
+  void
+  beginAddRoute(const Name& name, Route route, optional<time::nanoseconds> expires,
+                const std::function<void(RibUpdateResult)>& done);
+
+  /** \brief Start removing a route from RIB and FIB.
+   *  \param name route name
+   *  \param route route parameters
+   *  \param done completion callback
+   */
+  void
+  beginRemoveRoute(const Name& name, const Route& route,
+                   const std::function<void(RibUpdateResult)>& done);
+
+  void
+  beginRibUpdate(const RibUpdate& update, const std::function<void(RibUpdateResult)>& done);
+
+private: // management Dispatcher related
+  void
+  registerTopPrefix(const Name& topPrefix);
+
+  /** \brief Serve rib/register command.
+   */
+  void
+  registerEntry(const Name& topPrefix, const Interest& interest,
+                ControlParameters parameters,
+                const ndn::mgmt::CommandContinuation& done);
+
+  /** \brief Serve rib/unregister command.
+   */
+  void
+  unregisterEntry(const Name& topPrefix, const Interest& interest,
+                  ControlParameters parameters,
+                  const ndn::mgmt::CommandContinuation& done);
+
+  /** \brief Serve rib/list dataset.
+   */
+  void
+  listEntries(const Name& topPrefix, const Interest& interest,
+              ndn::mgmt::StatusDatasetContext& context);
+
+  void
+  setFaceForSelfRegistration(const Interest& request, ControlParameters& parameters);
+
+  ndn::mgmt::Authorization
+  makeAuthorization(const std::string& verb) override;
+
+private: // Face monitor
+  void
+  fetchActiveFaces();
+
+  void
+  onFetchActiveFacesFailure(uint32_t code, const std::string& reason);
+
+  void
+  onFaceDestroyedEvent(uint64_t faceId);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  void
+  scheduleActiveFaceFetch(const time::seconds& timeToWait);
+
+  void
+  removeInvalidFaces(const std::vector<ndn::nfd::FaceStatus>& activeFaces);
+
+  void
+  onNotification(const ndn::nfd::FaceEventNotification& notification);
+
+public:
+  static const Name LOCALHOP_TOP_PREFIX;
+
+private:
+  Rib& m_rib;
+  ndn::KeyChain& m_keyChain;
+  ndn::nfd::Controller& m_nfdController;
+  Dispatcher& m_dispatcher;
+  ndn::util::Scheduler& m_scheduler;
+
+  ndn::nfd::FaceMonitor m_faceMonitor;
+  ndn::ValidatorConfig m_localhostValidator;
+  ndn::ValidatorConfig m_localhopValidator;
+  bool m_isLocalhopEnabled;
+
+  ndn::util::scheduler::ScopedEventId m_activeFaceFetchEvent;
+  using FaceIdSet = std::set<uint64_t>;
+  FaceIdSet m_registeredFaces; ///< contains FaceIds with one or more Routes in the RIB
+};
+
+std::ostream&
+operator<<(std::ostream& os, RibManager::SlAnnounceResult res);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_RIB_MANAGER_HPP
diff --git a/daemon/rib/rib-update-batch.cpp b/daemon/rib/rib-update-batch.cpp
new file mode 100644
index 0000000..e639ae3
--- /dev/null
+++ b/daemon/rib/rib-update-batch.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-update-batch.hpp"
+
+namespace nfd {
+namespace rib {
+
+RibUpdateBatch::RibUpdateBatch(uint64_t faceId)
+  : m_faceId(faceId)
+{
+}
+
+void
+RibUpdateBatch::add(const RibUpdate& update)
+{
+  BOOST_ASSERT(m_faceId == update.getRoute().faceId);
+
+  m_updates.push_back(update);
+}
+
+RibUpdateBatch::const_iterator
+RibUpdateBatch::begin() const
+{
+  return m_updates.begin();
+}
+
+RibUpdateBatch::const_iterator
+RibUpdateBatch::end() const
+{
+  return m_updates.end();
+}
+
+size_t
+RibUpdateBatch::size() const
+{
+  return m_updates.size();
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/rib-update-batch.hpp b/daemon/rib/rib-update-batch.hpp
new file mode 100644
index 0000000..f72a9e8
--- /dev/null
+++ b/daemon/rib/rib-update-batch.hpp
@@ -0,0 +1,77 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_RIB_UPDATE_BATCH_HPP
+#define NFD_DAEMON_RIB_RIB_UPDATE_BATCH_HPP
+
+#include "rib-update.hpp"
+
+#include <list>
+
+namespace nfd {
+namespace rib {
+
+typedef std::list<RibUpdate> RibUpdateList;
+
+/** \brief Represents a collection of RibUpdates to be applied to a single FaceId.
+ */
+class RibUpdateBatch
+{
+public:
+  typedef RibUpdateList::const_iterator const_iterator;
+
+  explicit
+  RibUpdateBatch(uint64_t faceId);
+
+  uint64_t
+  getFaceId() const;
+
+  void
+  add(const RibUpdate& update);
+
+  const_iterator
+  begin() const;
+
+  const_iterator
+  end() const;
+
+  size_t
+  size() const;
+
+private:
+  uint64_t m_faceId;
+  RibUpdateList m_updates;
+};
+
+inline uint64_t
+RibUpdateBatch::getFaceId() const
+{
+  return m_faceId;
+}
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_RIB_UPDATE_BATCH_HPP
diff --git a/daemon/rib/rib-update.cpp b/daemon/rib/rib-update.cpp
new file mode 100644
index 0000000..8efe327
--- /dev/null
+++ b/daemon/rib/rib-update.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib-update.hpp"
+
+namespace nfd {
+namespace rib {
+
+RibUpdate::RibUpdate()
+{
+
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate::Action action)
+{
+  switch (action) {
+  case RibUpdate::REGISTER:
+    os << "REGISTER";
+    break;
+  case RibUpdate::UNREGISTER:
+    os << "UNREGISTER";
+    break;
+  case RibUpdate::REMOVE_FACE:
+    os << "REMOVE_FACE";
+    break;
+  }
+
+  return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate& update)
+{
+  os << "RibUpdate {\n";
+  os << "  Name: " << update.getName() << "\n";
+  os << "  Action: " << update.getAction() << "\n";
+  os << "  " << update.getRoute() << "\n";
+  os << "}";
+
+  return os;
+}
+
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/rib-update.hpp b/daemon/rib/rib-update.hpp
new file mode 100644
index 0000000..ad5a390
--- /dev/null
+++ b/daemon/rib/rib-update.hpp
@@ -0,0 +1,128 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_RIB_UPDATE_HPP
+#define NFD_DAEMON_RIB_RIB_UPDATE_HPP
+
+#include "core/common.hpp"
+#include "route.hpp"
+
+namespace nfd {
+namespace rib {
+
+/** RibUpdate
+ *  \brief represents a route that will be added to or removed from a namespace
+ *
+ *  \note This type is copyable so that it can be stored in STL containers.
+ */
+class RibUpdate
+{
+public:
+  enum Action {
+    REGISTER     = 0,
+    UNREGISTER   = 1,
+
+    /** \brief An update triggered by a face destruction notification
+     *
+     *  \note indicates a Route needs to be removed after a face is destroyed
+     */
+    REMOVE_FACE = 2
+  };
+
+  RibUpdate();
+
+  RibUpdate&
+  setAction(Action action);
+
+  Action
+  getAction() const;
+
+  RibUpdate&
+  setName(const Name& name);
+
+  const Name&
+  getName() const;
+
+  RibUpdate&
+  setRoute(const Route& route);
+
+  const Route&
+  getRoute() const;
+
+private:
+  Action m_action;
+  Name m_name;
+  Route m_route;
+};
+
+inline RibUpdate&
+RibUpdate::setAction(Action action)
+{
+  m_action = action;
+  return *this;
+}
+
+inline RibUpdate::Action
+RibUpdate::getAction() const
+{
+  return m_action;
+}
+
+inline RibUpdate&
+RibUpdate::setName(const Name& name)
+{
+  m_name = name;
+  return *this;
+}
+
+inline const Name&
+RibUpdate::getName() const
+{
+  return m_name;
+}
+
+inline RibUpdate&
+RibUpdate::setRoute(const Route& route)
+{
+  m_route = route;
+  return *this;
+}
+
+inline const Route&
+RibUpdate::getRoute() const
+{
+  return m_route;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate::Action action);
+
+std::ostream&
+operator<<(std::ostream& os, const RibUpdate& update);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_RIB_UPDATE_HPP
diff --git a/daemon/rib/rib.cpp b/daemon/rib/rib.cpp
new file mode 100644
index 0000000..bbd72f1
--- /dev/null
+++ b/daemon/rib/rib.cpp
@@ -0,0 +1,528 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "rib.hpp"
+#include "fib-updater.hpp"
+#include "core/logger.hpp"
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(Rib);
+
+bool
+operator<(const RibRouteRef& lhs, const RibRouteRef& rhs)
+{
+  return std::tie(lhs.entry->getName(), lhs.route->faceId, lhs.route->origin) <
+         std::tie(rhs.entry->getName(), rhs.route->faceId, rhs.route->origin);
+}
+
+static inline bool
+sortRoutes(const Route& lhs, const Route& rhs)
+{
+  return lhs.faceId < rhs.faceId;
+}
+
+Rib::Rib()
+  : m_nItems(0)
+  , m_isUpdateInProgress(false)
+{
+}
+
+void
+Rib::setFibUpdater(FibUpdater* updater)
+{
+  m_fibUpdater = updater;
+}
+
+Rib::const_iterator
+Rib::find(const Name& prefix) const
+{
+  return m_rib.find(prefix);
+}
+
+Route*
+Rib::find(const Name& prefix, const Route& route) const
+{
+  auto ribIt = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry = ribIt->second;
+    auto routeIt = entry->findRoute(route);
+    if (routeIt != entry->end()) {
+      return &*routeIt;
+    }
+  }
+
+  return nullptr;
+}
+
+Route*
+Rib::findLongestPrefix(const Name& prefix, const Route& route) const
+{
+  Route* existingRoute = find(prefix, route);
+  if (existingRoute == nullptr) {
+    auto parent = findParent(prefix);
+    if (parent) {
+      existingRoute = find(parent->getName(), route);
+    }
+  }
+
+  return existingRoute;
+}
+
+void
+Rib::insert(const Name& prefix, const Route& route)
+{
+  auto ribIt = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry(ribIt->second);
+
+    RibEntry::iterator entryIt;
+    bool didInsert = false;
+    std::tie(entryIt, didInsert) = entry->insertRoute(route);
+
+    if (didInsert) {
+      // The route was new and we successfully inserted it.
+      m_nItems++;
+
+      afterAddRoute(RibRouteRef{entry, entryIt});
+
+      // Register with face lookup table
+      m_faceMap[route.faceId].push_back(entry);
+    }
+    else {
+      // Route exists, update fields
+      // First cancel old scheduled event, if any, then set the EventId to new one
+      if (entryIt->getExpirationEvent()) {
+        NFD_LOG_TRACE("Cancelling expiration event for " << entry->getName() << " " << *entryIt);
+        entryIt->cancelExpirationEvent();
+      }
+
+      *entryIt = route;
+    }
+  }
+  else {
+    // New name prefix
+    auto entry = make_shared<RibEntry>();
+
+    m_rib[prefix] = entry;
+    m_nItems++;
+
+    entry->setName(prefix);
+    auto routeIt = entry->insertRoute(route).first;
+
+    // Find prefix's parent
+    shared_ptr<RibEntry> parent = findParent(prefix);
+
+    // Add self to parent's children
+    if (parent != nullptr) {
+      parent->addChild(entry);
+    }
+
+    RibEntryList children = findDescendants(prefix);
+
+    for (const auto& child : children) {
+      if (child->getParent() == parent) {
+        // Remove child from parent and inherit parent's child
+        if (parent != nullptr) {
+          parent->removeChild(child);
+        }
+
+        entry->addChild(child);
+      }
+    }
+
+    // Register with face lookup table
+    m_faceMap[route.faceId].push_back(entry);
+
+    // do something after inserting an entry
+    afterInsertEntry(prefix);
+    afterAddRoute(RibRouteRef{entry, routeIt});
+  }
+}
+
+void
+Rib::erase(const Name& prefix, const Route& route)
+{
+  auto ribIt = m_rib.find(prefix);
+
+  // Name prefix exists
+  if (ribIt != m_rib.end()) {
+    shared_ptr<RibEntry> entry = ribIt->second;
+    auto routeIt = entry->findRoute(route);
+
+    if (routeIt != entry->end()) {
+      beforeRemoveRoute(RibRouteRef{entry, routeIt});
+
+      auto faceId = route.faceId;
+      entry->eraseRoute(routeIt);
+      m_nItems--;
+
+      // If this RibEntry no longer has this faceId, unregister from face lookup table
+      if (!entry->hasFaceId(faceId)) {
+        m_faceMap[faceId].remove(entry);
+      }
+
+      // If a RibEntry's route list is empty, remove it from the tree
+      if (entry->getRoutes().empty()) {
+        eraseEntry(ribIt);
+      }
+    }
+  }
+}
+
+void
+Rib::onRouteExpiration(const Name& prefix, const Route& route)
+{
+  NFD_LOG_DEBUG(route << " for " << prefix << " has expired");
+
+  RibUpdate update;
+  update.setAction(RibUpdate::UNREGISTER)
+        .setName(prefix)
+        .setRoute(route);
+
+  beginApplyUpdate(update, nullptr, nullptr);
+}
+
+shared_ptr<RibEntry>
+Rib::findParent(const Name& prefix) const
+{
+  for (int i = prefix.size() - 1; i >= 0; i--) {
+    auto it = m_rib.find(prefix.getPrefix(i));
+    if (it != m_rib.end()) {
+      return it->second;
+    }
+  }
+
+  return nullptr;
+}
+
+std::list<shared_ptr<RibEntry>>
+Rib::findDescendants(const Name& prefix) const
+{
+  std::list<shared_ptr<RibEntry>> children;
+
+  RibTable::const_iterator it = m_rib.find(prefix);
+  if (it != m_rib.end()) {
+    ++it;
+    for (; it != m_rib.end(); ++it) {
+      if (prefix.isPrefixOf(it->first)) {
+        children.push_back((it->second));
+      }
+      else {
+        break;
+      }
+    }
+  }
+
+  return children;
+}
+
+std::list<shared_ptr<RibEntry>>
+Rib::findDescendantsForNonInsertedName(const Name& prefix) const
+{
+  std::list<shared_ptr<RibEntry>> children;
+
+  for (const auto& pair : m_rib) {
+    if (prefix.isPrefixOf(pair.first)) {
+      children.push_back(pair.second);
+    }
+  }
+
+  return children;
+}
+
+Rib::RibTable::iterator
+Rib::eraseEntry(RibTable::iterator it)
+{
+  // Entry does not exist
+  if (it == m_rib.end()) {
+    return m_rib.end();
+  }
+
+  shared_ptr<RibEntry> entry(it->second);
+
+  shared_ptr<RibEntry> parent = entry->getParent();
+
+  // Remove self from parent's children
+  if (parent != nullptr) {
+    parent->removeChild(entry);
+  }
+
+  for (auto childIt = entry->getChildren().begin(); childIt != entry->getChildren().end(); ) {
+    shared_ptr<RibEntry> child = *childIt;
+
+    // Advance iterator so it is not invalidated by removal
+    ++childIt;
+
+    // Remove children from self
+    entry->removeChild(child);
+
+    // Update parent's children
+    if (parent != nullptr) {
+      parent->addChild(child);
+    }
+  }
+
+  auto nextIt = m_rib.erase(it);
+
+  // do something after erasing an entry.
+  afterEraseEntry(entry->getName());
+
+  return nextIt;
+}
+
+Rib::RouteSet
+Rib::getAncestorRoutes(const RibEntry& entry) const
+{
+  RouteSet ancestorRoutes(&sortRoutes);
+
+  shared_ptr<RibEntry> parent = entry.getParent();
+
+  while (parent != nullptr) {
+    for (const Route& route : parent->getRoutes()) {
+      if (route.isChildInherit()) {
+        ancestorRoutes.insert(route);
+      }
+    }
+
+    if (parent->hasCapture()) {
+      break;
+    }
+
+    parent = parent->getParent();
+  }
+
+  return ancestorRoutes;
+}
+
+Rib::RouteSet
+Rib::getAncestorRoutes(const Name& name) const
+{
+  RouteSet ancestorRoutes(&sortRoutes);
+
+  shared_ptr<RibEntry> parent = findParent(name);
+
+  while (parent != nullptr) {
+    for (const Route& route : parent->getRoutes()) {
+      if (route.isChildInherit()) {
+        ancestorRoutes.insert(route);
+      }
+    }
+
+    if (parent->hasCapture()) {
+      break;
+    }
+
+    parent = parent->getParent();
+  }
+
+  return ancestorRoutes;
+}
+
+void
+Rib::beginApplyUpdate(const RibUpdate& update,
+                      const Rib::UpdateSuccessCallback& onSuccess,
+                      const Rib::UpdateFailureCallback& onFailure)
+{
+  BOOST_ASSERT(m_fibUpdater != nullptr);
+
+  addUpdateToQueue(update, onSuccess, onFailure);
+
+  sendBatchFromQueue();
+}
+
+void
+Rib::beginRemoveFace(uint64_t faceId)
+{
+  for (const auto& nameAndRoute : findRoutesWithFaceId(faceId)) {
+    RibUpdate update;
+    update.setAction(RibUpdate::REMOVE_FACE)
+          .setName(nameAndRoute.first)
+          .setRoute(nameAndRoute.second);
+
+    addUpdateToQueue(update, nullptr, nullptr);
+  }
+
+  sendBatchFromQueue();
+}
+
+void
+Rib::addUpdateToQueue(const RibUpdate& update,
+                      const Rib::UpdateSuccessCallback& onSuccess,
+                      const Rib::UpdateFailureCallback& onFailure)
+{
+  RibUpdateBatch batch(update.getRoute().faceId);
+  batch.add(update);
+
+  UpdateQueueItem item{batch, onSuccess, onFailure};
+  m_updateBatches.push_back(std::move(item));
+}
+
+void
+Rib::sendBatchFromQueue()
+{
+  if (m_updateBatches.empty() || m_isUpdateInProgress) {
+    return;
+  }
+
+  m_isUpdateInProgress = true;
+
+  UpdateQueueItem item = std::move(m_updateBatches.front());
+  m_updateBatches.pop_front();
+
+  RibUpdateBatch& batch = item.batch;
+
+  // Until task #1698, each RibUpdateBatch contains exactly one RIB update
+  BOOST_ASSERT(batch.size() == 1);
+
+  auto fibSuccessCb = bind(&Rib::onFibUpdateSuccess, this, batch, _1, item.managerSuccessCallback);
+  auto fibFailureCb = bind(&Rib::onFibUpdateFailure, this, item.managerFailureCallback, _1, _2);
+
+#ifdef WITH_TESTS
+  if (mockFibResponse != nullptr) {
+    m_fibUpdater->computeAndSendFibUpdates(batch, bind([]{}), bind([]{}));
+    bool shouldFibSucceed = mockFibResponse(batch);
+    if (wantMockFibResponseOnce) {
+      mockFibResponse = nullptr;
+    }
+    if (shouldFibSucceed) {
+      fibSuccessCb(m_fibUpdater->m_inheritedRoutes);
+    }
+    else {
+      fibFailureCb(504, "mocked failure");
+    }
+    return;
+  }
+#endif
+
+  m_fibUpdater->computeAndSendFibUpdates(batch, fibSuccessCb, fibFailureCb);
+}
+
+void
+Rib::onFibUpdateSuccess(const RibUpdateBatch& batch,
+                        const RibUpdateList& inheritedRoutes,
+                        const Rib::UpdateSuccessCallback& onSuccess)
+{
+  for (const RibUpdate& update : batch) {
+    switch (update.getAction()) {
+    case RibUpdate::REGISTER:
+      insert(update.getName(), update.getRoute());
+      break;
+    case RibUpdate::UNREGISTER:
+    case RibUpdate::REMOVE_FACE:
+      erase(update.getName(), update.getRoute());
+      break;
+    }
+  }
+
+  // Add and remove precalculated inherited routes to RibEntries
+  modifyInheritedRoutes(inheritedRoutes);
+
+  m_isUpdateInProgress = false;
+
+  if (onSuccess != nullptr) {
+    onSuccess();
+  }
+
+  // Try to advance the batch queue
+  sendBatchFromQueue();
+}
+
+void
+Rib::onFibUpdateFailure(const Rib::UpdateFailureCallback& onFailure,
+                        uint32_t code, const std::string& error)
+{
+  m_isUpdateInProgress = false;
+
+  if (onFailure != nullptr) {
+    onFailure(code, error);
+  }
+
+  // Try to advance the batch queue
+  sendBatchFromQueue();
+}
+
+void
+Rib::modifyInheritedRoutes(const RibUpdateList& inheritedRoutes)
+{
+  for (const RibUpdate& update : inheritedRoutes) {
+    auto ribIt = m_rib.find(update.getName());
+    BOOST_ASSERT(ribIt != m_rib.end());
+    shared_ptr<RibEntry> entry(ribIt->second);
+
+    switch (update.getAction()) {
+    case RibUpdate::REGISTER:
+      entry->addInheritedRoute(update.getRoute());
+      break;
+    case RibUpdate::UNREGISTER:
+      entry->removeInheritedRoute(update.getRoute());
+      break;
+    case RibUpdate::REMOVE_FACE:
+      break;
+    }
+  }
+}
+
+std::list<Rib::NameAndRoute>
+Rib::findRoutesWithFaceId(uint64_t faceId)
+{
+  std::list<NameAndRoute> routes;
+
+  auto lookupIt = m_faceMap.find(faceId);
+  if (lookupIt == m_faceMap.end()) {
+    // No RIB entries have this face
+    return routes;
+  }
+
+  // For each RIB entry that has faceId
+  for (const auto& entry : lookupIt->second) {
+    // Find the routes in the entry
+    for (const Route& route : *entry) {
+      if (route.faceId == faceId) {
+        routes.emplace_back(entry->getName(), route);
+      }
+    }
+  }
+
+  return routes;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Rib& rib)
+{
+  for (const auto& item : rib) {
+    os << *item.second << "\n";
+  }
+
+  return os;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/rib.hpp b/daemon/rib/rib.hpp
new file mode 100644
index 0000000..ea848f8
--- /dev/null
+++ b/daemon/rib/rib.hpp
@@ -0,0 +1,307 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_RIB_HPP
+#define NFD_DAEMON_RIB_RIB_HPP
+
+#include "rib-entry.hpp"
+#include "rib-update-batch.hpp"
+
+#include <ndn-cxx/mgmt/nfd/control-parameters.hpp>
+
+namespace nfd {
+namespace rib {
+
+using ndn::nfd::ControlParameters;
+
+class FibUpdater;
+
+/** \brief references a route
+ */
+struct RibRouteRef
+{
+  shared_ptr<RibEntry> entry;
+  RibEntry::const_iterator route;
+};
+
+bool
+operator<(const RibRouteRef& lhs, const RibRouteRef& rhs);
+
+/** \brief represents the Routing Information Base
+
+    The Routing Information Base contains a collection of Routes, each
+    represents a piece of static or dynamic routing information
+    registered by applications, operators, or NFD itself. Routes
+    associated with the same namespace are collected into a RIB entry.
+ */
+class Rib : noncopyable
+{
+public:
+  typedef std::list<shared_ptr<RibEntry>> RibEntryList;
+  typedef std::map<Name, shared_ptr<RibEntry>> RibTable;
+  typedef RibTable::const_iterator const_iterator;
+  typedef std::map<uint64_t, std::list<shared_ptr<RibEntry>>> FaceLookupTable;
+  typedef bool (*RouteComparePredicate)(const Route&, const Route&);
+  typedef std::set<Route, RouteComparePredicate> RouteSet;
+
+  Rib();
+
+  void
+  setFibUpdater(FibUpdater* updater);
+
+  const_iterator
+  find(const Name& prefix) const;
+
+  Route*
+  find(const Name& prefix, const Route& route) const;
+
+  Route*
+  findLongestPrefix(const Name& prefix, const Route& route) const;
+
+  const_iterator
+  begin() const;
+
+  const_iterator
+  end() const;
+
+  size_t
+  size() const;
+
+  bool
+  empty() const;
+
+  shared_ptr<RibEntry>
+  findParent(const Name& prefix) const;
+
+  /** \brief finds namespaces under the passed prefix
+   *  \return{ a list of entries which are under the passed prefix }
+   */
+  std::list<shared_ptr<RibEntry>>
+  findDescendants(const Name& prefix) const;
+
+  /** \brief finds namespaces under the passed prefix
+   *
+   *  \note Unlike findDescendants, needs to find where prefix would fit in tree
+   *  before collecting list of descendant prefixes
+   *
+   *  \return{ a list of entries which would be under the passed prefix if the prefix
+   *  existed in the RIB }
+   */
+  std::list<shared_ptr<RibEntry>>
+  findDescendantsForNonInsertedName(const Name& prefix) const;
+
+public:
+  using UpdateSuccessCallback = std::function<void()>;
+  using UpdateFailureCallback = std::function<void(uint32_t code, const std::string& error)>;
+
+  /** \brief passes the provided RibUpdateBatch to FibUpdater to calculate and send FibUpdates.
+   *
+   *  If the FIB is updated successfully, onFibUpdateSuccess() will be called, and the
+   *  RIB will be updated
+   *
+   *  If the FIB update fails, onFibUpdateFailure() will be called, and the RIB will not
+   *  be updated.
+   */
+  void
+  beginApplyUpdate(const RibUpdate& update,
+                   const UpdateSuccessCallback& onSuccess,
+                   const UpdateFailureCallback& onFailure);
+
+  /** \brief starts the FIB update process when a face has been destroyed
+   */
+  void
+  beginRemoveFace(uint64_t faceId);
+
+  void
+  onFibUpdateSuccess(const RibUpdateBatch& batch,
+                     const RibUpdateList& inheritedRoutes,
+                     const Rib::UpdateSuccessCallback& onSuccess);
+
+  void
+  onFibUpdateFailure(const Rib::UpdateFailureCallback& onFailure,
+                     uint32_t code, const std::string& error);
+
+  void
+  onRouteExpiration(const Name& prefix, const Route& route);
+
+  void
+  insert(const Name& prefix, const Route& route);
+
+private:
+  /** \brief adds the passed update to a RibUpdateBatch and adds the batch to
+  *          the end of the update queue.
+  *
+  *   If an update is not in progress, the front update batch in the queue will be
+  *   processed by the RIB.
+  *
+  *   If an update is in progress, the added update will eventually be processed
+  *   when it reaches the front of the queue; after other update batches are
+  *   processed, the queue is advanced.
+  */
+  void
+  addUpdateToQueue(const RibUpdate& update,
+                   const Rib::UpdateSuccessCallback& onSuccess,
+                   const Rib::UpdateFailureCallback& onFailure);
+
+  /** \brief Attempts to send the front update batch in the queue.
+  *
+  *   If an update is not in progress, the front update batch in the queue will be
+  *   sent to the RIB for processing.
+  *
+  *   If an update is in progress, nothing will be done.
+  */
+  void
+  sendBatchFromQueue();
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+#ifdef WITH_TESTS
+  /** \brief In unit tests, mock FIB update result.
+   *
+   *  If the callback is not nullptr, sendBatchFromQueue() immediately succeeds or fails according
+   *  to the return value of callback function.
+   */
+  std::function<bool(const RibUpdateBatch&)> mockFibResponse;
+
+  bool wantMockFibResponseOnce = false; ///< if true, mockFibResponse is cleared after every use.
+#endif
+
+  void
+  erase(const Name& prefix, const Route& route);
+
+private:
+  RibTable::iterator
+  eraseEntry(RibTable::iterator it);
+
+  void
+  updateRib(const RibUpdateBatch& batch);
+
+  /** \brief returns routes inherited from the entry's ancestors
+   *
+   *  \return{ a list of inherited routes }
+   */
+  RouteSet
+  getAncestorRoutes(const RibEntry& entry) const;
+
+  /** \brief returns routes inherited from the parent of the name and the parent's ancestors
+   *
+   *  \note A parent is first found for the passed name before inherited routes are collected
+   *
+   *  \return{ a list of inherited routes }
+   */
+  RouteSet
+  getAncestorRoutes(const Name& name) const;
+
+  /** \brief applies the passed inheritedRoutes and their actions to the corresponding RibEntries'
+   *  inheritedRoutes lists
+   */
+  void
+  modifyInheritedRoutes(const RibUpdateList& inheritedRoutes);
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  using NameAndRoute = std::pair<const Name&, const Route&>;
+
+  std::list<NameAndRoute>
+  findRoutesWithFaceId(uint64_t faceId);
+
+public:
+  /** \brief signals after a RIB entry is inserted
+   *
+   *  A RIB entry is inserted when the first route associated with a
+   *  certain namespace is added.
+   */
+  ndn::util::signal::Signal<Rib, Name> afterInsertEntry;
+
+  /** \brief signals after a RIB entry is erased
+   *
+   *  A RIB entry is erased when the last route associated with a
+   *  certain namespace is removed.
+   */
+
+  ndn::util::signal::Signal<Rib, Name> afterEraseEntry;
+
+  /** \brief signals after a Route is added
+   */
+  ndn::util::signal::Signal<Rib, RibRouteRef> afterAddRoute;
+
+  /** \brief signals before a route is removed
+   */
+  ndn::util::signal::Signal<Rib, RibRouteRef> beforeRemoveRoute;
+
+private:
+  RibTable m_rib;
+  FaceLookupTable m_faceMap;
+  FibUpdater* m_fibUpdater;
+
+  size_t m_nItems;
+
+  friend class FibUpdater;
+
+private:
+  struct UpdateQueueItem
+  {
+    RibUpdateBatch batch;
+    const Rib::UpdateSuccessCallback managerSuccessCallback;
+    const Rib::UpdateFailureCallback managerFailureCallback;
+  };
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  typedef std::list<UpdateQueueItem> UpdateQueue;
+  UpdateQueue m_updateBatches;
+
+private:
+  bool m_isUpdateInProgress;
+};
+
+inline Rib::const_iterator
+Rib::begin() const
+{
+  return m_rib.begin();
+}
+
+inline Rib::const_iterator
+Rib::end() const
+{
+  return m_rib.end();
+}
+
+inline size_t
+Rib::size() const
+{
+  return m_nItems;
+}
+
+inline bool
+Rib::empty() const
+{
+  return m_rib.empty();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Rib& rib);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_RIB_HPP
diff --git a/daemon/rib/route.cpp b/daemon/rib/route.cpp
new file mode 100644
index 0000000..04999bb
--- /dev/null
+++ b/daemon/rib/route.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2018,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "route.hpp"
+#include <ndn-cxx/util/string-helper.hpp>
+
+namespace nfd {
+namespace rib {
+
+const uint64_t PA_ROUTE_COST = 2048; ///< cost of route created by prefix announcement
+
+static time::steady_clock::TimePoint
+computeExpiration(const ndn::PrefixAnnouncement& ann)
+{
+  time::steady_clock::Duration validityEnd = time::steady_clock::Duration::max();
+  if (ann.getValidityPeriod()) {
+    auto now = time::system_clock::now();
+    if (!ann.getValidityPeriod()->isValid(now)) {
+      validityEnd = time::steady_clock::Duration::zero();
+    }
+    else {
+      validityEnd = ann.getValidityPeriod()->getPeriod().second - now;
+    }
+  }
+  return time::steady_clock::now() +
+    std::min(validityEnd, time::duration_cast<time::steady_clock::Duration>(ann.getExpiration()));
+}
+
+Route::Route(const ndn::PrefixAnnouncement& ann, uint64_t faceId)
+  : faceId(faceId)
+  , origin(ndn::nfd::ROUTE_ORIGIN_PREFIXANN)
+  , cost(PA_ROUTE_COST)
+  , flags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT)
+  , expires(computeExpiration(ann))
+  , announcement(ann)
+  , annExpires(*expires)
+{
+}
+
+bool
+operator==(const Route& lhs, const Route& rhs)
+{
+  return lhs.faceId == rhs.faceId &&
+         lhs.origin == rhs.origin &&
+         lhs.flags == rhs.flags &&
+         lhs.cost == rhs.cost &&
+         lhs.expires == rhs.expires &&
+         lhs.announcement == rhs.announcement;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Route& route)
+{
+  os << "Route("
+     << "faceid: " << route.faceId
+     << ", origin: " << route.origin
+     << ", cost: " << route.cost
+     << ", flags: " << ndn::AsHex{route.flags};
+  if (route.expires) {
+    os << ", expires in: " << time::duration_cast<time::milliseconds>(*route.expires - time::steady_clock::now());
+  }
+  else {
+    os << ", never expires";
+  }
+  if (route.announcement) {
+    os << ", announcement: (" << *route.announcement << ')';
+  }
+  os << ')';
+
+  return os;
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/route.hpp b/daemon/rib/route.hpp
new file mode 100644
index 0000000..f38fbab
--- /dev/null
+++ b/daemon/rib/route.hpp
@@ -0,0 +1,134 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_ROUTE_HPP
+#define NFD_DAEMON_RIB_ROUTE_HPP
+
+#include "core/common.hpp"
+
+#include <ndn-cxx/encoding/nfd-constants.hpp>
+#include <ndn-cxx/mgmt/nfd/route-flags-traits.hpp>
+#include <ndn-cxx/prefix-announcement.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+#include <type_traits>
+
+namespace nfd {
+namespace rib {
+
+/** \brief represents a route for a name prefix
+ */
+class Route : public ndn::nfd::RouteFlagsTraits<Route>
+{
+public:
+  /** \brief default constructor
+   */
+  Route() = default;
+
+  /** \brief construct from a prefix announcement
+   *  \param ann a prefix announcement that has passed verification
+   *  \param faceId the face on which \p ann arrived
+   */
+  Route(const ndn::PrefixAnnouncement& ann, uint64_t faceId);
+
+  const ndn::util::scheduler::EventId&
+  getExpirationEvent() const
+  {
+    return m_expirationEvent;
+  }
+
+  void
+  setExpirationEvent(const ndn::util::scheduler::EventId& eid)
+  {
+    m_expirationEvent = eid;
+  }
+
+  void
+  cancelExpirationEvent()
+  {
+    m_expirationEvent.cancel();
+  }
+
+  std::underlying_type_t<ndn::nfd::RouteFlags>
+  getFlags() const
+  {
+    return flags;
+  }
+
+public:
+  uint64_t faceId = 0;
+  ndn::nfd::RouteOrigin origin = ndn::nfd::ROUTE_ORIGIN_APP;
+  uint64_t cost = 0;
+  std::underlying_type_t<ndn::nfd::RouteFlags> flags = ndn::nfd::ROUTE_FLAGS_NONE;
+  optional<time::steady_clock::TimePoint> expires;
+
+  /** \brief The prefix announcement that caused the creation of this route.
+   *
+   *  This is nullopt if this route is not created by a prefix announcement.
+   */
+  optional<ndn::PrefixAnnouncement> announcement;
+
+  /** \brief Expiration time of the prefix announcement.
+   *
+   *  Valid only if announcement is not nullopt.
+   *
+   *  If this field is before or equal the current time, it indicates the prefix announcement is
+   *  not yet valid or has expired. In this case, the exact value of this field does not matter.
+   *  If this field is after the current time, it indicates when the prefix announcement expires.
+   */
+  time::steady_clock::TimePoint annExpires;
+
+private:
+  ndn::util::scheduler::EventId m_expirationEvent;
+};
+
+bool
+operator==(const Route& lhs, const Route& rhs);
+
+inline bool
+operator!=(const Route& lhs, const Route& rhs)
+{
+  return !(lhs == rhs);
+}
+
+inline bool
+compareFaceIdAndOrigin(const Route& lhs, const Route& rhs)
+{
+  return (lhs.faceId == rhs.faceId && lhs.origin == rhs.origin);
+}
+
+inline bool
+compareFaceId(const Route& route, const uint64_t faceId)
+{
+  return (route.faceId == faceId);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Route& route);
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_ROUTE_HPP
diff --git a/daemon/rib/service.cpp b/daemon/rib/service.cpp
new file mode 100644
index 0000000..35c092d
--- /dev/null
+++ b/daemon/rib/service.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "service.hpp"
+
+#include "fib-updater.hpp"
+#include "readvertise/client-to-nlsr-readvertise-policy.hpp"
+#include "readvertise/host-to-gateway-readvertise-policy.hpp"
+#include "readvertise/nfd-rib-readvertise-destination.hpp"
+#include "readvertise/readvertise.hpp"
+
+#include "core/global-io.hpp"
+#include "core/logger.hpp"
+
+#include <boost/property_tree/info_parser.hpp>
+#include <ndn-cxx/transport/tcp-transport.hpp>
+#include <ndn-cxx/transport/unix-transport.hpp>
+
+namespace nfd {
+namespace rib {
+
+NFD_LOG_INIT(RibService);
+
+Service* Service::s_instance = nullptr;
+
+static const std::string CFG_SECTION = "rib";
+static const std::string CFG_LOCALHOST_SECURITY = "localhost_security";
+static const std::string CFG_LOCALHOP_SECURITY = "localhop_security";
+static const std::string CFG_PREFIX_PROPAGATE = "auto_prefix_propagate";
+static const std::string CFG_READVERTISE_NLSR = "readvertise_nlsr";
+static const Name READVERTISE_NLSR_PREFIX = "/localhost/nlsr";
+static const uint64_t PROPAGATE_DEFAULT_COST = 15;
+static const time::milliseconds PROPAGATE_DEFAULT_TIMEOUT = 10_s;
+
+static ConfigSection
+loadConfigSectionFromFile(const std::string& filename)
+{
+  ConfigSection config;
+  // Any format errors should have been caught already
+  boost::property_tree::read_info(filename, config);
+  return config;
+}
+
+/**
+ * \brief Look into the config file and construct appropriate transport to communicate with NFD
+ * If NFD-RIB instance was initialized with config file, INFO format is assumed
+ */
+static shared_ptr<ndn::Transport>
+makeLocalNfdTransport(const ConfigSection& config)
+{
+  if (config.get_child_optional("face_system.unix")) {
+    // default socket path should be the same as in UnixStreamFactory::processConfig
+    auto path = config.get<std::string>("face_system.unix.path", "/var/run/nfd.sock");
+    return make_shared<ndn::UnixTransport>(path);
+  }
+  else if (config.get_child_optional("face_system.tcp") &&
+           config.get<std::string>("face_system.tcp.listen", "yes") == "yes") {
+    // default port should be the same as in TcpFactory::processConfig
+    auto port = config.get<std::string>("face_system.tcp.port", "6363");
+    return make_shared<ndn::TcpTransport>("localhost", port);
+  }
+  else {
+    NDN_THROW(ConfigFile::Error("No transport is available to communicate with NFD"));
+  }
+}
+
+Service::Service(const std::string& configFile, ndn::KeyChain& keyChain)
+  : Service(keyChain, makeLocalNfdTransport(loadConfigSectionFromFile(configFile)),
+            [&configFile] (ConfigFile& config, bool isDryRun) {
+              config.parse(configFile, isDryRun);
+            })
+{
+}
+
+Service::Service(const ConfigSection& configSection, ndn::KeyChain& keyChain)
+  : Service(keyChain, makeLocalNfdTransport(configSection),
+            [&configSection] (ConfigFile& config, bool isDryRun) {
+              config.parse(configSection, isDryRun, "internal://nfd.conf");
+            })
+{
+}
+
+template<typename ConfigParseFunc>
+Service::Service(ndn::KeyChain& keyChain, shared_ptr<ndn::Transport> localNfdTransport,
+                 const ConfigParseFunc& configParse)
+  : m_keyChain(keyChain)
+  , m_face(std::move(localNfdTransport), getGlobalIoService(), m_keyChain)
+  , m_scheduler(m_face.getIoService())
+  , m_nfdController(m_face, m_keyChain)
+  , m_fibUpdater(m_rib, m_nfdController)
+  , m_dispatcher(m_face, m_keyChain)
+  , m_ribManager(m_rib, m_face, m_keyChain, m_nfdController, m_dispatcher, m_scheduler)
+{
+  if (s_instance != nullptr) {
+    NDN_THROW(std::logic_error("RIB service cannot be instantiated more than once"));
+  }
+  if (&getGlobalIoService() != &getRibIoService()) {
+    NDN_THROW(std::logic_error("RIB service must run on RIB thread"));
+  }
+  s_instance = this;
+
+  ConfigFile config(ConfigFile::ignoreUnknownSection);
+  config.addSectionHandler(CFG_SECTION, bind(&Service::processConfig, this, _1, _2, _3));
+  configParse(config, true);
+  configParse(config, false);
+
+  m_ribManager.registerWithNfd();
+  m_ribManager.enableLocalFields();
+}
+
+Service::~Service()
+{
+  s_instance = nullptr;
+}
+
+Service&
+Service::get()
+{
+  if (s_instance == nullptr) {
+    NDN_THROW(std::logic_error("RIB service is not instantiated"));
+  }
+  if (&getGlobalIoService() != &getRibIoService()) {
+    NDN_THROW(std::logic_error("Must get RIB service on RIB thread"));
+  }
+  return *s_instance;
+}
+
+void
+Service::processConfig(const ConfigSection& section, bool isDryRun, const std::string& filename)
+{
+  if (isDryRun) {
+    checkConfig(section, filename);
+  }
+  else {
+    applyConfig(section, filename);
+  }
+}
+
+void
+Service::checkConfig(const ConfigSection& section, const std::string& filename)
+{
+  for (const auto& item : section) {
+    const std::string& key = item.first;
+    const ConfigSection& value = item.second;
+    if (key == CFG_LOCALHOST_SECURITY || key == CFG_LOCALHOP_SECURITY) {
+      ndn::ValidatorConfig testValidator(m_face);
+      testValidator.load(value, filename);
+    }
+    else if (key == CFG_PREFIX_PROPAGATE) {
+      // AutoPrefixPropagator does not support config dry-run
+    }
+    else if (key == CFG_READVERTISE_NLSR) {
+      ConfigFile::parseYesNo(item, CFG_SECTION + "." + CFG_READVERTISE_NLSR);
+    }
+    else {
+      NDN_THROW(ConfigFile::Error("Unrecognized option " + CFG_SECTION + "." + key));
+    }
+  }
+}
+
+void
+Service::applyConfig(const ConfigSection& section, const std::string& filename)
+{
+  bool wantPrefixPropagate = false;
+  bool wantReadvertiseNlsr = false;
+
+  for (const auto& item : section) {
+    const std::string& key = item.first;
+    const ConfigSection& value = item.second;
+    if (key == CFG_LOCALHOST_SECURITY) {
+      m_ribManager.applyLocalhostConfig(value, filename);
+    }
+    else if (key == CFG_LOCALHOP_SECURITY) {
+      m_ribManager.enableLocalhop(value, filename);
+    }
+    else if (key == CFG_PREFIX_PROPAGATE) {
+      wantPrefixPropagate = true;
+
+      if (!m_readvertisePropagation) {
+        NFD_LOG_DEBUG("Enabling automatic prefix propagation");
+
+        auto parameters = ndn::nfd::ControlParameters()
+          .setCost(PROPAGATE_DEFAULT_COST)
+          .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT);
+        auto cost = item.second.get_optional<uint64_t>("cost");
+        if (cost) {
+          parameters.setCost(*cost);
+        }
+
+        auto options = ndn::nfd::CommandOptions()
+          .setPrefix(RibManager::LOCALHOP_TOP_PREFIX)
+          .setTimeout(PROPAGATE_DEFAULT_TIMEOUT);
+        auto timeout = item.second.get_optional<uint64_t>("timeout");
+        if (timeout) {
+          options.setTimeout(time::milliseconds(*timeout));
+        }
+
+        m_readvertisePropagation = make_unique<Readvertise>(
+          m_rib,
+          m_scheduler,
+          make_unique<HostToGatewayReadvertisePolicy>(m_keyChain, item.second),
+          make_unique<NfdRibReadvertiseDestination>(m_nfdController, m_rib, options, parameters));
+      }
+    }
+    else if (key == CFG_READVERTISE_NLSR) {
+      wantReadvertiseNlsr = ConfigFile::parseYesNo(item, CFG_SECTION + "." + CFG_READVERTISE_NLSR);
+    }
+    else {
+      NDN_THROW(ConfigFile::Error("Unrecognized option " + CFG_SECTION + "." + key));
+    }
+  }
+
+  if (!wantPrefixPropagate && m_readvertisePropagation != nullptr) {
+    NFD_LOG_DEBUG("Disabling automatic prefix propagation");
+    m_readvertisePropagation.reset();
+  }
+
+  if (wantReadvertiseNlsr && m_readvertiseNlsr == nullptr) {
+    NFD_LOG_DEBUG("Enabling readvertise-to-nlsr");
+    auto options = ndn::nfd::CommandOptions().setPrefix(READVERTISE_NLSR_PREFIX);
+    m_readvertiseNlsr = make_unique<Readvertise>(
+      m_rib,
+      m_scheduler,
+      make_unique<ClientToNlsrReadvertisePolicy>(),
+      make_unique<NfdRibReadvertiseDestination>(m_nfdController, m_rib, options));
+  }
+  else if (!wantReadvertiseNlsr && m_readvertiseNlsr != nullptr) {
+    NFD_LOG_DEBUG("Disabling readvertise-to-nlsr");
+    m_readvertiseNlsr.reset();
+  }
+}
+
+} // namespace rib
+} // namespace nfd
diff --git a/daemon/rib/service.hpp b/daemon/rib/service.hpp
new file mode 100644
index 0000000..0d6a71a
--- /dev/null
+++ b/daemon/rib/service.hpp
@@ -0,0 +1,127 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2019,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_RIB_SERVICE_HPP
+#define NFD_DAEMON_RIB_SERVICE_HPP
+
+#include "rib-manager.hpp"
+
+#include "core/config-file.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/mgmt/dispatcher.hpp>
+#include <ndn-cxx/mgmt/nfd/controller.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/transport/transport.hpp>
+#include <ndn-cxx/util/scheduler.hpp>
+
+namespace nfd {
+namespace rib {
+
+class Readvertise;
+
+/**
+ * \brief initializes and executes NFD-RIB service thread
+ *
+ * Only one instance of this class can be created at any time.
+ * After initialization, NFD-RIB instance can be started by running the global io_service.
+ */
+class Service : noncopyable
+{
+public:
+  /**
+   * \brief create NFD-RIB service
+   * \param configFile absolute or relative path of configuration file
+   * \param keyChain the KeyChain
+   * \throw std::logic_error Instance of rib::Service has been already constructed
+   * \throw std::logic_error Instance of rib::Service is not constructed on RIB thread
+   */
+  Service(const std::string& configFile, ndn::KeyChain& keyChain);
+
+  /**
+   * \brief create NFD-RIB service
+   * \param configSection parsed configuration section
+   * \param keyChain the KeyChain
+   * \note This constructor overload is more appropriate for integrated environments,
+   *       such as NS-3 or android. Error messages related to configuration file
+   *       will use "internal://nfd.conf" as configuration filename.
+   * \throw std::logic_error Instance of rib::Service has been already constructed
+   * \throw std::logic_error Instance of rib::Service is not constructed on RIB thread
+   */
+  Service(const ConfigSection& configSection, ndn::KeyChain& keyChain);
+
+  /**
+   * \brief Destructor
+   */
+  ~Service();
+
+  /**
+   * \brief Get a reference to the only instance of this class
+   * \throw std::logic_error No instance has been constructed
+   * \throw std::logic_error This function is invoked on a thread other than the RIB thread
+   */
+  static Service&
+  get();
+
+  RibManager&
+  getRibManager()
+  {
+    return m_ribManager;
+  }
+
+private:
+  template<typename ConfigParseFunc>
+  Service(ndn::KeyChain& keyChain, shared_ptr<ndn::Transport> localNfdTransport,
+          const ConfigParseFunc& configParse);
+
+  void
+  processConfig(const ConfigSection& section, bool isDryRun, const std::string& filename);
+
+  void
+  checkConfig(const ConfigSection& section, const std::string& filename);
+
+  void
+  applyConfig(const ConfigSection& section, const std::string& filename);
+
+private:
+  static Service* s_instance;
+
+  ndn::KeyChain& m_keyChain;
+  ndn::Face m_face;
+  ndn::util::Scheduler m_scheduler;
+  ndn::nfd::Controller m_nfdController;
+
+  Rib m_rib;
+  FibUpdater m_fibUpdater;
+  unique_ptr<Readvertise> m_readvertiseNlsr;
+  unique_ptr<Readvertise> m_readvertisePropagation;
+  ndn::mgmt::Dispatcher m_dispatcher;
+  RibManager m_ribManager;
+};
+
+} // namespace rib
+} // namespace nfd
+
+#endif // NFD_DAEMON_RIB_SERVICE_HPP