tools: nfdc route list and nfd route show commands
refs #3866 Change-Id: Ic8feab0ce9e5707c1cf382cdea7264e28e3edb30
This commit is contained in:
@@ -3,11 +3,12 @@ nfdc-route
|
||||
|
||||
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 @@ Each *route* in the RIB indicates that contents under a name prefix may be avail
|
||||
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 @@ This command returns when the request has been accepted, but does not wait for R
|
||||
|
||||
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 @@ EXIT CODES
|
||||
|
||||
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)
|
||||
|
||||
@@ -36,6 +36,158 @@ namespace tests {
|
||||
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_XML = stripXmlSpaces(R"XML(
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -148,6 +148,16 @@ FindFace::query()
|
||||
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
|
||||
{
|
||||
|
||||
@@ -92,6 +92,11 @@ public:
|
||||
return m_results;
|
||||
}
|
||||
|
||||
/** \return FaceId for all results
|
||||
*/
|
||||
std::set<uint64_t>
|
||||
getFaceIds() const;
|
||||
|
||||
/** \return a single face status
|
||||
* \pre getResults().size() == 1
|
||||
*/
|
||||
|
||||
+120
-21
@@ -34,6 +34,19 @@ namespace nfdc {
|
||||
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")
|
||||
@@ -55,6 +68,77 @@ RibModule::registerCommands(CommandParser& parser)
|
||||
parser.addCommand(defRouteRemove, &RibModule::remove);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -147,11 +231,11 @@ RibModule::remove(ExecuteContext& ctx)
|
||||
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 @@ RibModule::formatStatusText(std::ostream& os) const
|
||||
{
|
||||
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
|
||||
|
||||
@@ -47,6 +47,16 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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;
|
||||
|
||||
@@ -145,11 +145,6 @@ registerStatusCommands(CommandParser& parser)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user