tools: nfdc route list and nfd route show commands
refs #3866
Change-Id: Ic8feab0ce9e5707c1cf382cdea7264e28e3edb30
diff --git a/docs/manpages/nfdc-route.rst b/docs/manpages/nfdc-route.rst
index 0c2f0e0..8ae5bc6 100644
--- a/docs/manpages/nfdc-route.rst
+++ b/docs/manpages/nfdc-route.rst
@@ -3,11 +3,12 @@
SYNOPSIS
--------
-| nfdc route [list]
-| nfdc fib [list]
+| nfdc route [list [[nexthop] <FACEID|FACEURI>] [origin <ORIGIN>]]
+| nfdc route show [prefix] <PREFIX>
| nfdc route add [prefix] <PREFIX> [nexthop] <FACEID|FACEURI> [origin <ORIGIN>] [cost <COST>]
| [no-inherit] [capture] [expires <EXPIRATION-MILLIS>]
-| nfdc unregister [-o <ORIGIN>] <PREFIX> <FACEID>
+| nfdc route remove [prefix] <PREFIX> [nexthop] <FACEID|FACEURI> [origin <ORIGIN>]
+| nfdc fib [list]
DESCRIPTION
-----------
@@ -17,10 +18,9 @@
A route contains a name prefix, a nexthop face, the origin, a cost, and a set of route inheritance flags;
refer to NFD Management protocol for more information.
-The **nfdc route list** command shows a list of routes in the RIB.
+The **nfdc route list** command lists RIB routes, optionally filtered by nexthop and origin.
-The **nfdc fib list** command shows the forwarding information base (FIB),
-which is calculated from RIB routes and used directly by NFD forwarding.
+The **nfdc route show** command shows RIB routes at a specified name prefix.
The **nfdc route add** command requests to add a route.
If a route with the same prefix, nexthop, and origin already exists,
@@ -29,6 +29,9 @@
The **nfdc route remove** command removes a route with matching prefix, nexthop, and origin.
+The **nfdc fib list** command shows the forwarding information base (FIB),
+which is calculated from RIB routes and used directly by NFD forwarding.
+
OPTIONS
-------
<PREFIX>
@@ -77,6 +80,8 @@
5: Ambiguous: multiple matching faces are found (**nfdc route add** only)
+6: Route not found (**nfdc route list** and **nfdc route show** only)
+
SEE ALSO
--------
nfd(1), nfdc(1)
diff --git a/tests/tools/nfdc/rib-module.t.cpp b/tests/tools/nfdc/rib-module.t.cpp
index 2cdc21d..0c5bc31 100644
--- a/tests/tools/nfdc/rib-module.t.cpp
+++ b/tests/tools/nfdc/rib-module.t.cpp
@@ -36,6 +36,158 @@
BOOST_AUTO_TEST_SUITE(Nfdc)
BOOST_FIXTURE_TEST_SUITE(TestRibModule, StatusFixture<RibModule>)
+class RouteListFixture : public ExecuteCommandFixture
+{
+protected:
+ bool
+ respondRibDataset(const Interest& interest)
+ {
+ if (!Name("/localhost/nfd/rib/list").isPrefixOf(interest.getName())) {
+ return false;
+ }
+
+ RibEntry entry1;
+ entry1.setName("/5BBmTevRJ");
+ entry1.addRoute(Route()
+ .setFaceId(6720)
+ .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT)
+ .setCost(2956)
+ .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT | ndn::nfd::ROUTE_FLAG_CAPTURE)
+ .setExpirationPeriod(time::milliseconds(29950035)));
+ entry1.addRoute(Route()
+ .setFaceId(6720)
+ .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+ .setCost(425)
+ .setFlags(ndn::nfd::ROUTE_FLAGS_NONE));
+ entry1.addRoute(Route()
+ .setFaceId(8599)
+ .setOrigin(ndn::nfd::ROUTE_ORIGIN_STATIC)
+ .setCost(9140)
+ .setFlags(ndn::nfd::ROUTE_FLAG_CHILD_INHERIT));
+
+ RibEntry entry2;
+ entry2.setName("/aDPTKCio");
+ entry2.addRoute(Route()
+ .setFaceId(31066)
+ .setOrigin(ndn::nfd::ROUTE_ORIGIN_CLIENT)
+ .setCost(4617)
+ .setFlags(ndn::nfd::ROUTE_FLAG_CAPTURE));
+
+ this->sendDataset(interest.getName(), entry1, entry2);
+ return true;
+ }
+};
+
+BOOST_FIXTURE_TEST_SUITE(ListShowCommand, RouteListFixture)
+
+const std::string NOFILTER_OUTPUT = std::string(R"TEXT(
+prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s
+prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never
+prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never
+prefix=/aDPTKCio nexthop=31066 origin=65 cost=4617 flags=capture expires=never
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(ListNoFilter)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondRibDataset(interest));
+ };
+
+ this->execute("route");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal(NOFILTER_OUTPUT));
+ BOOST_CHECK(err.is_empty());
+}
+
+const std::string NEXTHOP_OUTPUT = std::string(R"TEXT(
+prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s
+prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never
+prefix=/aDPTKCio nexthop=31066 origin=65 cost=4617 flags=capture expires=never
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(ListByNexthop)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondFaceQuery(interest) || this->respondRibDataset(interest));
+ };
+
+ this->execute("route list udp4://225.131.75.231:56363");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal(NEXTHOP_OUTPUT));
+ BOOST_CHECK(err.is_empty());
+}
+
+const std::string ORIGIN_OUTPUT = std::string(R"TEXT(
+prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never
+prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(ListByOrigin)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondRibDataset(interest));
+ };
+
+ this->execute("route list origin 255");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal(ORIGIN_OUTPUT));
+ BOOST_CHECK(err.is_empty());
+}
+
+const std::string PREFIX_OUTPUT = std::string(R"TEXT(
+prefix=/5BBmTevRJ nexthop=6720 origin=65 cost=2956 flags=child-inherit|capture expires=29950s
+prefix=/5BBmTevRJ nexthop=6720 origin=255 cost=425 flags=none expires=never
+prefix=/5BBmTevRJ nexthop=8599 origin=255 cost=9140 flags=child-inherit expires=never
+)TEXT").substr(1);
+
+BOOST_AUTO_TEST_CASE(ShowByPrefix)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondRibDataset(interest));
+ };
+
+ this->execute("route show 5BBmTevRJ");
+ BOOST_CHECK_EQUAL(exitCode, 0);
+ BOOST_CHECK(out.is_equal(PREFIX_OUTPUT));
+ BOOST_CHECK(err.is_empty());
+}
+
+BOOST_AUTO_TEST_CASE(FaceNotExist)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondFaceQuery(interest));
+ };
+
+ this->execute("route list 23728");
+ BOOST_CHECK_EQUAL(exitCode, 3);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Face not found\n"));
+}
+
+BOOST_AUTO_TEST_CASE(RouteNotExist)
+{
+ this->processInterest = [this] (const Interest& interest) {
+ BOOST_CHECK(this->respondFaceQuery(interest) || this->respondRibDataset(interest));
+ };
+
+ this->execute("route list 10156");
+ BOOST_CHECK_EQUAL(exitCode, 6);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Route not found\n"));
+}
+
+BOOST_AUTO_TEST_CASE(ErrorDataset)
+{
+ this->processInterest = nullptr; // no response to dataset
+
+ this->execute("route list");
+ BOOST_CHECK_EQUAL(exitCode, 1);
+ BOOST_CHECK(out.is_empty());
+ BOOST_CHECK(err.is_equal("Error 10060 when fetching RIB dataset: Timeout\n"));
+}
+
+BOOST_AUTO_TEST_SUITE_END() // ListShowCommand
+
BOOST_FIXTURE_TEST_SUITE(AddCommand, ExecuteCommandFixture)
BOOST_AUTO_TEST_CASE(NormalByFaceId)
@@ -339,10 +491,11 @@
const std::string STATUS_TEXT =
"RIB:\n"
- " / route={faceid=262 (origin=255 cost=9 RibCapture), faceid=272 (origin=255 cost=50), "
- "faceid=274 (origin=255 cost=78 ChildInherit RibCapture), "
- "faceid=276 (origin=255 cost=79 expires=47s ChildInherit)}\n"
- " /localhost/nfd route={faceid=258 (origin=0 cost=0 ChildInherit)}\n";
+ " / routes={nexthop=262 origin=255 cost=9 flags=capture expires=never, "
+ "nexthop=272 origin=255 cost=50 flags=none expires=never, "
+ "nexthop=274 origin=255 cost=78 flags=child-inherit|capture expires=never, "
+ "nexthop=276 origin=255 cost=79 flags=child-inherit expires=47s}\n"
+ " /localhost/nfd routes={nexthop=258 origin=0 cost=0 flags=child-inherit expires=never}\n";
BOOST_AUTO_TEST_CASE(Status)
{
diff --git a/tools/nfdc/find-face.cpp b/tools/nfdc/find-face.cpp
index 5645e26..5c23c7a 100644
--- a/tools/nfdc/find-face.cpp
+++ b/tools/nfdc/find-face.cpp
@@ -148,6 +148,16 @@
m_ctx.face.processEvents();
}
+std::set<uint64_t>
+FindFace::getFaceIds() const
+{
+ std::set<uint64_t> faceIds;
+ for (const FaceStatus& faceStatus : m_results) {
+ faceIds.insert(faceStatus.getFaceId());
+ }
+ return faceIds;
+}
+
const FaceStatus&
FindFace::getFaceStatus() const
{
diff --git a/tools/nfdc/find-face.hpp b/tools/nfdc/find-face.hpp
index 2661aa6..120718b 100644
--- a/tools/nfdc/find-face.hpp
+++ b/tools/nfdc/find-face.hpp
@@ -92,6 +92,11 @@
return m_results;
}
+ /** \return FaceId for all results
+ */
+ std::set<uint64_t>
+ getFaceIds() const;
+
/** \return a single face status
* \pre getResults().size() == 1
*/
diff --git a/tools/nfdc/rib-module.cpp b/tools/nfdc/rib-module.cpp
index 4342894..d7f04cd 100644
--- a/tools/nfdc/rib-module.cpp
+++ b/tools/nfdc/rib-module.cpp
@@ -34,6 +34,19 @@
void
RibModule::registerCommands(CommandParser& parser)
{
+ CommandDefinition defRouteList("route", "list");
+ defRouteList
+ .setTitle("print RIB routes")
+ .addArg("nexthop", ArgValueType::FACE_ID_OR_URI, Required::NO, Positional::YES)
+ .addArg("origin", ArgValueType::UNSIGNED, Required::NO, Positional::NO);
+ parser.addCommand(defRouteList, &RibModule::list);
+
+ CommandDefinition defRouteShow("route", "show");
+ defRouteShow
+ .setTitle("show routes toward a prefix")
+ .addArg("prefix", ArgValueType::NAME, Required::YES, Positional::YES);
+ parser.addCommand(defRouteShow, &RibModule::show);
+
CommandDefinition defRouteAdd("route", "add");
defRouteAdd
.setTitle("add a route")
@@ -56,6 +69,77 @@
}
void
+RibModule::list(ExecuteContext& ctx)
+{
+ auto nexthopIt = ctx.args.find("nexthop");
+ std::set<uint64_t> nexthops;
+ auto origin = ctx.args.getOptional<uint64_t>("origin");
+
+ if (nexthopIt != ctx.args.end()) {
+ FindFace findFace(ctx);
+ FindFace::Code res = findFace.execute(nexthopIt->second, true);
+
+ ctx.exitCode = static_cast<int>(res);
+ switch (res) {
+ case FindFace::Code::OK:
+ break;
+ case FindFace::Code::ERROR:
+ case FindFace::Code::CANONIZE_ERROR:
+ case FindFace::Code::NOT_FOUND:
+ ctx.err << findFace.getErrorReason() << '\n';
+ return;
+ default:
+ BOOST_ASSERT_MSG(false, "unexpected FindFace result");
+ return;
+ }
+
+ nexthops = findFace.getFaceIds();
+ }
+
+ listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) {
+ return (nexthops.empty() || nexthops.count(route.getFaceId()) > 0) &&
+ (!origin || route.getOrigin() == *origin);
+ });
+}
+
+void
+RibModule::show(ExecuteContext& ctx)
+{
+ auto prefix = ctx.args.get<Name>("prefix");
+
+ listRoutesImpl(ctx, [&] (const RibEntry& entry, const Route& route) {
+ return entry.getName() == prefix;
+ });
+}
+
+void
+RibModule::listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter)
+{
+ ctx.controller.fetch<ndn::nfd::RibDataset>(
+ [&] (const std::vector<RibEntry>& dataset) {
+ bool hasRoute = false;
+ for (const RibEntry& entry : dataset) {
+ for (const Route& route : entry.getRoutes()) {
+ if (filter(entry, route)) {
+ hasRoute = true;
+ formatRouteText(ctx.out, entry, route, true);
+ ctx.out << '\n';
+ }
+ }
+ }
+
+ if (!hasRoute) {
+ ctx.exitCode = 6;
+ ctx.err << "Route not found\n";
+ }
+ },
+ ctx.makeDatasetFailureHandler("RIB dataset"),
+ ctx.makeCommandOptions());
+
+ ctx.face.processEvents();
+}
+
+void
RibModule::add(ExecuteContext& ctx)
{
auto prefix = ctx.args.get<Name>("prefix");
@@ -147,11 +231,11 @@
return;
}
- for (const FaceStatus& faceStatus : findFace.getResults()) {
+ for (uint64_t faceId : findFace.getFaceIds()) {
ControlParameters unregisterParams;
unregisterParams
.setName(prefix)
- .setFaceId(faceStatus.getFaceId())
+ .setFaceId(faceId)
.setOrigin(origin);
ctx.controller.start<ndn::nfd::RibUnregisterCommand>(
@@ -238,35 +322,50 @@
{
os << "RIB:\n";
for (const RibEntry& item : m_status) {
- this->formatItemText(os, item);
+ os << " ";
+ formatEntryText(os, item);
+ os << '\n';
}
}
void
-RibModule::formatItemText(std::ostream& os, const RibEntry& item) const
+RibModule::formatEntryText(std::ostream& os, const RibEntry& entry)
{
- os << " " << item.getName() << " route={";
+ os << entry.getName() << " routes={";
text::Separator sep(", ");
- for (const Route& route : item.getRoutes()) {
- os << sep
- << "faceid=" << route.getFaceId()
- << " (origin=" << route.getOrigin()
- << " cost=" << route.getCost();
- if (route.hasExpirationPeriod()) {
- os << " expires=" << text::formatDuration(route.getExpirationPeriod());
- }
- if (route.isChildInherit()) {
- os << " ChildInherit";
- }
- if (route.isRibCapture()) {
- os << " RibCapture";
- }
- os << ")";
+ for (const Route& route : entry.getRoutes()) {
+ os << sep;
+ formatRouteText(os, entry, route, false);
}
os << "}";
- os << "\n";
+}
+
+void
+RibModule::formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route,
+ bool includePrefix)
+{
+ text::ItemAttributes ia;
+
+ if (includePrefix) {
+ os << ia("prefix") << entry.getName();
+ }
+ os << ia("nexthop") << route.getFaceId();
+ os << ia("origin") << static_cast<uint64_t>(route.getOrigin());
+ os << ia("cost") << route.getCost();
+ os << ia("flags") << static_cast<ndn::nfd::RouteFlags>(route.getFlags());
+
+ // 'origin' field is printed as a number, because printing 'origin' as string may mislead user
+ // into passing strings to 'origin' command line argument which currently only accepts numbers.
+ ///\todo #3987 print 'origin' with RouteOrigin stream insertion operator
+
+ if (route.hasExpirationPeriod()) {
+ os << ia("expires") << text::formatDuration(route.getExpirationPeriod());
+ }
+ else {
+ os << ia("expires") << "never";
+ }
}
} // namespace nfdc
diff --git a/tools/nfdc/rib-module.hpp b/tools/nfdc/rib-module.hpp
index aee2c8b..ae53928 100644
--- a/tools/nfdc/rib-module.hpp
+++ b/tools/nfdc/rib-module.hpp
@@ -47,6 +47,16 @@
static void
registerCommands(CommandParser& parser);
+ /** \brief the 'route list' command
+ */
+ static void
+ list(ExecuteContext& ctx);
+
+ /** \brief the 'route show' command
+ */
+ static void
+ show(ExecuteContext& ctx);
+
/** \brief the 'route add' command
*/
static void
@@ -66,6 +76,15 @@
void
formatStatusXml(std::ostream& os) const override;
+ void
+ formatStatusText(std::ostream& os) const override;
+
+private:
+ using RoutePredicate = function<bool(const RibEntry&, const Route&)>;
+
+ static void
+ listRoutesImpl(ExecuteContext& ctx, const RoutePredicate& filter);
+
/** \brief format a single status item as XML
* \param os output stream
* \param item status item
@@ -73,15 +92,22 @@
void
formatItemXml(std::ostream& os, const RibEntry& item) const;
- void
- formatStatusText(std::ostream& os) const override;
-
- /** \brief format a single status item as text
+ /** \brief format a RibEntry as text
* \param os output stream
- * \param item status item
+ * \param entry RIB entry
*/
- void
- formatItemText(std::ostream& os, const RibEntry& item) const;
+ static void
+ formatEntryText(std::ostream& os, const RibEntry& entry);
+
+ /** \brief format a Route as text
+ * \param os output stream
+ * \param entry RIB entry
+ * \param route RIB route within \p entry
+ * \param includePrefix whether to print the name prefix
+ */
+ static void
+ formatRouteText(std::ostream& os, const RibEntry& entry, const Route& route,
+ bool includePrefix);
private:
std::vector<RibEntry> m_status;
diff --git a/tools/nfdc/status.cpp b/tools/nfdc/status.cpp
index 6165565..a09fd7e 100644
--- a/tools/nfdc/status.cpp
+++ b/tools/nfdc/status.cpp
@@ -145,11 +145,6 @@
defFibList
.setTitle("print FIB entries");
parser.addCommand(defFibList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantFib));
-
- CommandDefinition defRouteList("route", "list");
- defRouteList
- .setTitle("print RIB entries");
- parser.addCommand(defRouteList, bind(&reportStatusSingleSection, _1, &StatusReportOptions::wantRib));
}
} // namespace nfdc