fw: add strategy parameters to configure exponential retx suppression

Supported by ASF, BestRoute, and Multicast strategies

Refs: #4924
Change-Id: I215d9212d90b93fa622cc65278703dc5198d0c9d
This commit is contained in:
Ashlesh Gawande authored and Davide Pesavento committed 2022-04-08 00:27:27 -04:00
1 parent deb5427bb4
commit 1ef93d0316
21 files changed
+384 -188

No files matched your search

+4 -4
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -171,10 +171,10 @@ ProbingModule::getProbingProbability(uint64_t rank, uint64_t rankSum, uint64_t n
}
void
ProbingModule::setProbingInterval(size_t probingInterval)
ProbingModule::setProbingInterval(time::milliseconds probingInterval)
{
if (time::milliseconds(probingInterval) >= MIN_PROBING_INTERVAL) {
m_probingInterval = time::milliseconds(probingInterval);
if (probingInterval >= MIN_PROBING_INTERVAL) {
m_probingInterval = probingInterval;
}
else {
NDN_THROW(std::invalid_argument("Probing interval must be >= " +
+2 -2
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -54,7 +54,7 @@ public:
afterForwardingProbe(const fib::Entry& fibEntry, const Name& interestName);
void
setProbingInterval(size_t probingInterval);
setProbingInterval(time::milliseconds probingInterval);
time::milliseconds
getProbingInterval() const
+14 -55
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -35,28 +35,27 @@ namespace asf {
NFD_LOG_INIT(AsfStrategy);
NFD_REGISTER_STRATEGY(AsfStrategy);
const time::milliseconds AsfStrategy::RETX_SUPPRESSION_INITIAL(10);
const time::milliseconds AsfStrategy::RETX_SUPPRESSION_MAX(250);
AsfStrategy::AsfStrategy(Forwarder& forwarder, const Name& name)
: Strategy(forwarder)
, m_measurements(getMeasurements())
, m_probing(m_measurements)
, m_retxSuppression(RETX_SUPPRESSION_INITIAL,
RetxSuppressionExponential::DEFAULT_MULTIPLIER,
RETX_SUPPRESSION_MAX)
{
ParsedInstanceName parsed = parseInstanceName(name);
if (!parsed.parameters.empty()) {
processParams(parsed.parameters);
}
if (parsed.version && *parsed.version != getStrategyName()[-1].toVersion()) {
NDN_THROW(std::invalid_argument(
"AsfStrategy does not support version " + to_string(*parsed.version)));
}
StrategyParameters params = parseParameters(parsed.parameters);
m_retxSuppression = RetxSuppressionExponential::construct(params);
auto probingInterval = params.getOrDefault<time::milliseconds::rep>("probing-interval",
m_probing.getProbingInterval().count());
m_probing.setProbingInterval(time::milliseconds(probingInterval));
m_nMaxTimeouts = params.getOrDefault<size_t>("max-timeouts", m_nMaxTimeouts);
this->setInstanceName(makeInstanceName(name, getStrategyName()));
NDN_LOG_DEBUG(*m_retxSuppression);
NFD_LOG_DEBUG("probing-interval=" << m_probing.getProbingInterval()
<< " max-timeouts=" << m_nMaxTimeouts);
}
@@ -68,46 +67,6 @@ AsfStrategy::getStrategyName()
return strategyName;
}
static uint64_t
getParamValue(const std::string& param, const std::string& value)
{
try {
if (!value.empty() && value[0] == '-')
NDN_THROW(boost::bad_lexical_cast());
return boost::lexical_cast<uint64_t>(value);
}
catch (const boost::bad_lexical_cast&) {
NDN_THROW(std::invalid_argument("Value of " + param + " must be a non-negative integer"));
}
}
void
AsfStrategy::processParams(const PartialName& parsed)
{
for (const auto& component : parsed) {
std::string parsedStr(reinterpret_cast<const char*>(component.value()), component.value_size());
auto n = parsedStr.find("~");
if (n == std::string::npos) {
NDN_THROW(std::invalid_argument("Format is <parameter>~<value>"));
}
auto f = parsedStr.substr(0, n);
auto s = parsedStr.substr(n + 1);
if (f == "probing-interval") {
m_probing.setProbingInterval(getParamValue(f, s));
}
else if (f == "max-timeouts") {
m_nMaxTimeouts = getParamValue(f, s);
if (m_nMaxTimeouts <= 0)
NDN_THROW(std::invalid_argument("max-timeouts should be greater than 0"));
}
else {
NDN_THROW(std::invalid_argument("Parameter should be probing-interval or max-timeouts"));
}
}
}
void
AsfStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint& ingress,
const shared_ptr<pit::Entry>& pitEntry)
@@ -131,7 +90,7 @@ AsfStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint&
auto* faceToUse = getBestFaceForForwarding(interest, ingress.face, fibEntry, pitEntry, false);
if (faceToUse != nullptr) {
auto suppressResult = m_retxSuppression.decidePerUpstream(*pitEntry, *faceToUse);
auto suppressResult = m_retxSuppression->decidePerUpstream(*pitEntry, *faceToUse);
if (suppressResult == RetxSuppressionResult::SUPPRESS) {
// Cannot be sent on this face, interest was received within the suppression window
NFD_LOG_DEBUG(interest << " retx-interest from=" << ingress
@@ -143,7 +102,7 @@ AsfStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint&
NFD_LOG_DEBUG(interest << " retx-interest from=" << ingress << " forward-to=" << faceToUse->getId());
auto* outRecord = forwardInterest(interest, *faceToUse, fibEntry, pitEntry);
if (outRecord && suppressResult == RetxSuppressionResult::FORWARD) {
m_retxSuppression.incrementIntervalForOutRecord(*outRecord);
m_retxSuppression->incrementIntervalForOutRecord(*outRecord);
}
}
return;
@@ -158,7 +117,7 @@ AsfStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint&
return;
}
auto& outFace = it->getFace();
auto suppressResult = m_retxSuppression.decidePerUpstream(*pitEntry, outFace);
auto suppressResult = m_retxSuppression->decidePerUpstream(*pitEntry, outFace);
if (suppressResult == RetxSuppressionResult::SUPPRESS) {
NFD_LOG_DEBUG(interest << " retx-interest from=" << ingress
<< " retry-to=" << outFace.getId() << " suppressed");
@@ -169,7 +128,7 @@ AsfStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint&
// were already attached to this face in the previous forwarding
auto* outRecord = sendInterest(interest, outFace, pitEntry);
if (outRecord && suppressResult == RetxSuppressionResult::FORWARD) {
m_retxSuppression.incrementIntervalForOutRecord(*outRecord);
m_retxSuppression->incrementIntervalForOutRecord(*outRecord);
}
}
}
+7 -11
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -26,10 +26,10 @@
#ifndef NFD_DAEMON_FW_ASF_STRATEGY_HPP
#define NFD_DAEMON_FW_ASF_STRATEGY_HPP
#include "strategy.hpp"
#include "asf-measurements.hpp"
#include "asf-probing-module.hpp"
#include "fw/retx-suppression-exponential.hpp"
#include "fw/strategy.hpp"
#include "retx-suppression-exponential.hpp"
namespace nfd {
namespace fw {
@@ -65,9 +65,6 @@ public: // triggers
const shared_ptr<pit::Entry>& pitEntry) override;
private:
void
processParams(const PartialName& parsed);
pit::OutRecord*
forwardInterest(const Interest& interest, Face& outFace, const fib::Entry& fibEntry,
const shared_ptr<pit::Entry>& pitEntry);
@@ -89,12 +86,11 @@ private:
private:
AsfMeasurements m_measurements;
ProbingModule m_probing;
RetxSuppressionExponential m_retxSuppression;
size_t m_nMaxTimeouts = 3;
static const time::milliseconds RETX_SUPPRESSION_INITIAL;
static const time::milliseconds RETX_SUPPRESSION_MAX;
NFD_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
std::unique_ptr<RetxSuppressionExponential> m_retxSuppression;
ProbingModule m_probing;
size_t m_nMaxTimeouts = 3;
};
} // namespace asf
+8 -11
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -33,25 +33,22 @@ namespace fw {
NFD_LOG_INIT(BestRouteStrategy);
NFD_REGISTER_STRATEGY(BestRouteStrategy);
const time::milliseconds BestRouteStrategy::RETX_SUPPRESSION_INITIAL(10);
const time::milliseconds BestRouteStrategy::RETX_SUPPRESSION_MAX(250);
BestRouteStrategy::BestRouteStrategy(Forwarder& forwarder, const Name& name)
: Strategy(forwarder)
, ProcessNackTraits(this)
, m_retxSuppression(RETX_SUPPRESSION_INITIAL,
RetxSuppressionExponential::DEFAULT_MULTIPLIER,
RETX_SUPPRESSION_MAX)
{
ParsedInstanceName parsed = parseInstanceName(name);
if (!parsed.parameters.empty()) {
NDN_THROW(std::invalid_argument("BestRouteStrategy does not accept parameters"));
}
if (parsed.version && *parsed.version != getStrategyName()[-1].toVersion()) {
NDN_THROW(std::invalid_argument(
"BestRouteStrategy does not support version " + to_string(*parsed.version)));
}
StrategyParameters params = parseParameters(parsed.parameters);
m_retxSuppression = RetxSuppressionExponential::construct(params);
this->setInstanceName(makeInstanceName(name, getStrategyName()));
NDN_LOG_DEBUG(*m_retxSuppression);
}
const Name&
@@ -65,7 +62,7 @@ void
BestRouteStrategy::afterReceiveInterest(const Interest& interest, const FaceEndpoint& ingress,
const shared_ptr<pit::Entry>& pitEntry)
{
RetxSuppressionResult suppression = m_retxSuppression.decidePerPitEntry(*pitEntry);
RetxSuppressionResult suppression = m_retxSuppression->decidePerPitEntry(*pitEntry);
if (suppression == RetxSuppressionResult::SUPPRESS) {
NFD_LOG_DEBUG(interest << " from=" << ingress << " suppressed");
return;
+2 -4
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -70,9 +70,7 @@ public: // triggers
const shared_ptr<pit::Entry>& pitEntry) override;
NFD_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
static const time::milliseconds RETX_SUPPRESSION_INITIAL;
static const time::milliseconds RETX_SUPPRESSION_MAX;
RetxSuppressionExponential m_retxSuppression;
std::unique_ptr<RetxSuppressionExponential> m_retxSuppression;
friend ProcessNackTraits<BestRouteStrategy>;
};
+9 -12
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -34,24 +34,21 @@ NFD_REGISTER_STRATEGY(MulticastStrategy);
NFD_LOG_INIT(MulticastStrategy);
const time::milliseconds MulticastStrategy::RETX_SUPPRESSION_INITIAL(10);
const time::milliseconds MulticastStrategy::RETX_SUPPRESSION_MAX(250);
MulticastStrategy::MulticastStrategy(Forwarder& forwarder, const Name& name)
: Strategy(forwarder)
, m_retxSuppression(RETX_SUPPRESSION_INITIAL,
RetxSuppressionExponential::DEFAULT_MULTIPLIER,
RETX_SUPPRESSION_MAX)
{
ParsedInstanceName parsed = parseInstanceName(name);
if (!parsed.parameters.empty()) {
NDN_THROW(std::invalid_argument("MulticastStrategy does not accept parameters"));
}
if (parsed.version && *parsed.version != getStrategyName()[-1].toVersion()) {
NDN_THROW(std::invalid_argument(
"MulticastStrategy does not support version " + to_string(*parsed.version)));
}
StrategyParameters params = parseParameters(parsed.parameters);
m_retxSuppression = RetxSuppressionExponential::construct(params);
this->setInstanceName(makeInstanceName(name, getStrategyName()));
NDN_LOG_DEBUG(*m_retxSuppression);
}
const Name&
@@ -71,7 +68,7 @@ MulticastStrategy::afterReceiveInterest(const Interest& interest, const FaceEndp
for (const auto& nexthop : nexthops) {
Face& outFace = nexthop.getFace();
RetxSuppressionResult suppressResult = m_retxSuppression.decidePerUpstream(*pitEntry, outFace);
RetxSuppressionResult suppressResult = m_retxSuppression->decidePerUpstream(*pitEntry, outFace);
if (suppressResult == RetxSuppressionResult::SUPPRESS) {
NFD_LOG_DEBUG(interest << " from=" << ingress << " to=" << outFace.getId() << " suppressed");
@@ -85,7 +82,7 @@ MulticastStrategy::afterReceiveInterest(const Interest& interest, const FaceEndp
NFD_LOG_DEBUG(interest << " from=" << ingress << " pitEntry-to=" << outFace.getId());
auto* sentOutRecord = this->sendInterest(interest, outFace, pitEntry);
if (sentOutRecord && suppressResult == RetxSuppressionResult::FORWARD) {
m_retxSuppression.incrementIntervalForOutRecord(*sentOutRecord);
m_retxSuppression->incrementIntervalForOutRecord(*sentOutRecord);
}
}
}
+2 -6
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -51,12 +51,8 @@ public: // triggers
void
afterNewNextHop(const fib::NextHop& nextHop, const shared_ptr<pit::Entry>& pitEntry) override;
private:
RetxSuppressionExponential m_retxSuppression;
NFD_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
static const time::milliseconds RETX_SUPPRESSION_INITIAL;
static const time::milliseconds RETX_SUPPRESSION_MAX;
std::unique_ptr<RetxSuppressionExponential> m_retxSuppression;
};
} // namespace fw
+35 -12
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -28,11 +28,13 @@
namespace nfd {
namespace fw {
const RetxSuppressionExponential::Duration RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL = 1_ms;
const RetxSuppressionExponential::Duration RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL = 10_ms;
const RetxSuppressionExponential::Duration RetxSuppressionExponential::DEFAULT_MAX_INTERVAL = 250_ms;
const float RetxSuppressionExponential::DEFAULT_MULTIPLIER = 2.0f;
class RetxSuppressionExponential::PitInfo final : public StrategyInfo
namespace {
class PitInfo final : public StrategyInfo
{
public:
static constexpr int
@@ -42,7 +44,7 @@ public:
}
explicit
PitInfo(const Duration& initialInterval)
PitInfo(const RetxSuppressionExponential::Duration& initialInterval)
: suppressionInterval(initialInterval)
{
}
@@ -51,19 +53,27 @@ public:
/** \brief if last transmission occurred within suppressionInterval,
* retransmission will be suppressed
*/
Duration suppressionInterval;
RetxSuppressionExponential::Duration suppressionInterval;
};
RetxSuppressionExponential::RetxSuppressionExponential(const Duration& initialInterval,
float multiplier,
const Duration& maxInterval)
} // namespace
RetxSuppressionExponential::RetxSuppressionExponential(Duration initialInterval,
Duration maxInterval,
float multiplier)
: m_initialInterval(initialInterval)
, m_multiplier(multiplier)
, m_maxInterval(maxInterval)
, m_multiplier(multiplier)
{
BOOST_ASSERT(initialInterval > 0_us);
BOOST_ASSERT(multiplier >= 1.0f);
BOOST_ASSERT(maxInterval >= initialInterval);
if (m_initialInterval <= 0_ns) {
NDN_THROW(std::invalid_argument("Retx suppression initial interval must be > 0"));
}
if (m_maxInterval < m_initialInterval) {
NDN_THROW(std::invalid_argument("Retx suppression max interval must be >= initial interval"));
}
if (m_multiplier < 1.0f) {
NDN_THROW(std::invalid_argument("Retx suppression multiplier must be >= 1"));
}
}
RetxSuppressionResult
@@ -123,5 +133,18 @@ RetxSuppressionExponential::incrementIntervalForOutRecord(pit::OutRecord& outRec
time::duration_cast<Duration>(pi->suppressionInterval * m_multiplier));
}
std::unique_ptr<RetxSuppressionExponential>
RetxSuppressionExponential::construct(const StrategyParameters& params)
{
auto init = params.getOrDefault<Duration::rep>("retx-suppression-initial",
RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL.count());
auto max = params.getOrDefault<Duration::rep>("retx-suppression-max",
RetxSuppressionExponential::DEFAULT_MAX_INTERVAL.count());
auto mult = params.getOrDefault<float>("retx-suppression-multiplier",
RetxSuppressionExponential::DEFAULT_MULTIPLIER);
return make_unique<RetxSuppressionExponential>(Duration(init), Duration(max), mult);
}
} // namespace fw
} // namespace nfd
+27 -17
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2017, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -28,27 +28,30 @@
#include "algorithm.hpp"
#include "retx-suppression.hpp"
#include "strategy.hpp"
namespace nfd {
namespace fw {
/** \brief a retransmission suppression decision algorithm that
* suppresses retransmissions using exponential backoff
/**
* \brief A retransmission suppression decision algorithm that suppresses
* retransmissions using exponential backoff.
*
* The i-th retransmission will be suppressed if the last transmission (out-record)
* occurred within MIN(initialInterval * multiplier^(i-1), maxInterval)
* The i-th retransmission will be suppressed if the last transmission (out-record)
* occurred within `MIN(initialInterval * multiplier^(i-1), maxInterval)`.
*/
class RetxSuppressionExponential
{
public:
/** \brief time granularity
/**
* \brief Time granularity.
*/
typedef time::microseconds Duration;
using Duration = time::milliseconds;
explicit
RetxSuppressionExponential(const Duration& initialInterval = DEFAULT_INITIAL_INTERVAL,
float multiplier = DEFAULT_MULTIPLIER,
const Duration& maxInterval = DEFAULT_MAX_INTERVAL);
RetxSuppressionExponential(Duration initialInterval = DEFAULT_INITIAL_INTERVAL,
Duration maxInterval = DEFAULT_MAX_INTERVAL,
float multiplier = DEFAULT_MULTIPLIER);
/** \brief determines whether Interest is a retransmission per pit entry
* and if so, whether it shall be forwarded or suppressed
@@ -67,20 +70,27 @@ public:
void
incrementIntervalForOutRecord(pit::OutRecord& outRecord);
public:
/** \brief StrategyInfo on pit::Entry
*/
class PitInfo;
static std::unique_ptr<RetxSuppressionExponential>
construct(const StrategyParameters& params);
private: // non-member operators (hidden friends)
friend std::ostream&
operator<<(std::ostream& os, const RetxSuppressionExponential& retxSupp)
{
return os << "RetxSuppressionExponential initial-interval=" << retxSupp.m_initialInterval
<< " max-interval=" << retxSupp.m_maxInterval
<< " multiplier=" << retxSupp.m_multiplier;
}
public:
static const Duration DEFAULT_INITIAL_INTERVAL;
static const float DEFAULT_MULTIPLIER;
static const Duration DEFAULT_MAX_INTERVAL;
static const float DEFAULT_MULTIPLIER;
private:
NFD_PUBLIC_WITH_TESTS_ELSE_PRIVATE:
const Duration m_initialInterval;
const float m_multiplier;
const Duration m_maxInterval;
const float m_multiplier;
};
} // namespace fw
+26 -3
View File
@@ -140,6 +140,29 @@ Strategy::makeInstanceName(const Name& input, const Name& strategyName)
return hasVersion ? input : Name(input).append(strategyName.at(-1));
}
StrategyParameters
Strategy::parseParameters(const PartialName& params)
{
StrategyParameters parsed;
for (const auto& component : params) {
auto sep = std::find(component.value_begin(), component.value_end(), '~');
if (sep == component.value_end()) {
NDN_THROW(std::invalid_argument("Strategy parameters format is (<parameter>~<value>)*"));
}
std::string p(component.value_begin(), sep);
std::advance(sep, 1);
std::string v(sep, component.value_end());
if (p.empty() || v.empty()) {
NDN_THROW(std::invalid_argument("Strategy parameter name and value cannot be empty"));
}
parsed[std::move(p)] = std::move(v);
}
return parsed;
}
Strategy::Strategy(Forwarder& forwarder)
: afterAddFace(forwarder.m_faceTable.afterAdd)
, beforeRemoveFace(forwarder.m_faceTable.beforeRemove)
@@ -299,7 +322,7 @@ Strategy::lookupFib(const pit::Entry& pitEntry) const
for (const auto& delegation : fh) {
fibEntry = &fib.findLongestPrefixMatch(delegation);
if (fibEntry->hasNextHops()) {
if (fibEntry->getPrefix().size() == 0) {
if (fibEntry->getPrefix().empty()) {
// in consumer region, return the default route
NFD_LOG_TRACE("lookupFib inConsumerRegion found=" << fibEntry->getPrefix());
}
@@ -309,9 +332,9 @@ Strategy::lookupFib(const pit::Entry& pitEntry) const
}
return *fibEntry;
}
BOOST_ASSERT(fibEntry->getPrefix().size() == 0); // only ndn:/ FIB entry can have zero nexthop
BOOST_ASSERT(fibEntry->getPrefix().empty()); // only ndn:/ FIB entry can have zero nexthop
}
BOOST_ASSERT(fibEntry != nullptr && fibEntry->getPrefix().size() == 0);
BOOST_ASSERT(fibEntry != nullptr && fibEntry->getPrefix().empty());
return *fibEntry; // only occurs if no delegation finds a FIB nexthop
}
+58 -5
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -32,6 +32,8 @@
namespace nfd {
namespace fw {
class StrategyParameters;
/**
* \brief Represents a forwarding strategy
*/
@@ -66,7 +68,7 @@ public: // registry
canCreate(const Name& instanceName);
/** \return A strategy instance created from \p instanceName
* \retval nullptr if !canCreate(instanceName)
* \retval nullptr if `canCreate(instanceName) == false`
* \throw std::invalid_argument strategy type constructor does not accept
* specified version or parameters
*/
@@ -374,9 +376,9 @@ protected: // accessors
protected: // instance name
struct ParsedInstanceName
{
Name strategyName; ///< strategy name without parameters
optional<uint64_t> version; ///< whether strategyName contains a version component
PartialName parameters; ///< parameter components
Name strategyName; ///< Strategy name without parameters
optional<uint64_t> version; ///< The strategy version number, if present
PartialName parameters; ///< Parameter components, may be empty
};
/** \brief Parse a strategy instance name
@@ -408,6 +410,15 @@ protected: // instance name
m_name = name;
}
NFD_PUBLIC_WITH_TESTS_ELSE_PROTECTED:
/**
* \brief Parse strategy parameters encoded in a strategy instance name
* \param params encoded parameters, typically obtained from a call to parseInstanceName()
* \throw std::invalid_argument the encoding format is invalid or unsupported by this implementation
*/
static StrategyParameters
parseParameters(const PartialName& params);
private: // registry
using CreateFunc = std::function<unique_ptr<Strategy>(Forwarder&, const Name& /*strategyName*/)>;
using Registry = std::map<Name, CreateFunc>; // indexed by strategy name
@@ -428,6 +439,48 @@ private: // instance fields
MeasurementsAccessor m_measurements;
};
class StrategyParameters : public std::map<std::string, std::string>
{
public:
// Note: only arithmetic types are supported by getOrDefault() for now
template<typename T>
std::enable_if_t<std::is_signed<T>::value, T>
getOrDefault(const key_type& key, const T& defaultVal) const
{
auto it = find(key);
if (it == end()) {
return defaultVal;
}
T val{};
if (!boost::conversion::try_lexical_convert(it->second, val)) {
NDN_THROW(std::invalid_argument(key + " value is malformed"));
}
return val;
}
template<typename T>
std::enable_if_t<std::is_unsigned<T>::value, T>
getOrDefault(const key_type& key, const T& defaultVal) const
{
auto it = find(key);
if (it == end()) {
return defaultVal;
}
if (it->second.find('-') != std::string::npos) {
NDN_THROW(std::invalid_argument(key + " cannot be negative"));
}
T val{};
if (!boost::conversion::try_lexical_convert(it->second, val)) {
NDN_THROW(std::invalid_argument(key + " value is malformed"));
}
return val;
}
};
} // namespace fw
} // namespace nfd
+8 -3
View File
@@ -40,13 +40,18 @@ nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/asf
Use the default values for all parameters.
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/asf/v=4/probing-interval~30000
Set probing interval to 30 seconds.
Set the probing interval to 30 seconds.
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/asf/v=4/max-timeouts~5
Set max timeouts to 5.
Set the maximum number of timeouts to 5.
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/asf/v=4/probing-interval~30000/max-timeouts~2
Set probing interval to 30 seconds and max timeouts to 2.
Set the probing interval to 30 seconds and the maximum number of timeouts to 2.
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/asf/v=4/retx-suppression-multiplier~2.5/probing-interval~45000
Set the retransmission suppression multiplier to 2.5 and the probing interval
to 45 seconds. See :manpage:`nfdc-strategy(1)` for more information on the
retransmission suppression parameters.
See also
--------
+27
View File
@@ -42,6 +42,29 @@ OPTIONS
A name that identifies the forwarding strategy.
Consult NFD Developer's Guide for a complete list of all implemented strategies.
For the ASF, BestRoute, and Multicast strategies, the following options may be supplied
to configure exponential retransmission suppression by appending them after the strategy
name and version number.
**retx-suppression-initial**
Starting duration of the suppression interval within which any retransmitted
Interests are considered for suppression. The default value is 10 ms.
Format: ``retx-suppression-initial~<milliseconds>``
**retx-suppression-max**
Maximum duration of the suppression interval. Must not be smaller than
**retx-suppression-initial**. The default value is 250 ms.
Format: ``retx-suppression-max~<milliseconds>``
**retx-suppression-multiplier**
The suppression interval is increased by this multiplier. The default value is 2.
Format: ``retx-suppression-multiplier~<float>``
See :manpage:`nfd-asf-strategy(7)` for details on additional parameters for ASF strategy.
EXIT CODES
----------
0: Success
@@ -66,6 +89,10 @@ nfdc strategy set prefix / strategy /localhost/nfd/strategy/best-route
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/multicast/v=4
Set the strategy for the "/ndn" prefix to multicast, version 4.
nfdc strategy set prefix /ndn strategy /localhost/nfd/strategy/multicast/v=4/retx-suppression-initial~20/retx-suppression-max~500
Set the strategy for the "/ndn" prefix to multicast, version 4, with retransmission
suppression initial interval set to 20 ms and maximum interval set to 500 ms.
nfdc strategy unset prefix /ndn
Clear the strategy choice for the "/ndn" prefix.
+37 -23
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -541,39 +541,53 @@ BOOST_FIXTURE_TEST_CASE(ProbingInterval, AsfStrategyParametersGridFixture)
BOOST_CHECK_EQUAL(linkAC->getFace(nodeA).getCounters().nOutInterests, 1);
}
BOOST_AUTO_TEST_CASE(InstantiationWithParameters)
BOOST_AUTO_TEST_CASE(Parameters)
{
FaceTable faceTable;
Forwarder forwarder{faceTable};
auto checkValidity = [&] (std::string parameters, bool isCorrect) {
Name strategyName(Name(AsfStrategy::getStrategyName()).append(std::move(parameters)));
if (isCorrect) {
BOOST_CHECK_NO_THROW(make_unique<AsfStrategy>(forwarder, strategyName));
}
else {
BOOST_CHECK_THROW(make_unique<AsfStrategy>(forwarder, strategyName), std::invalid_argument);
auto checkValidity = [&] (const std::string& parameters, bool isCorrect) {
Name strategyName(Name(AsfStrategy::getStrategyName()).append(parameters));
std::unique_ptr<AsfStrategy> strategy;
BOOST_TEST_CONTEXT(parameters) {
if (isCorrect) {
strategy = make_unique<AsfStrategy>(forwarder, strategyName);
BOOST_CHECK(strategy->m_retxSuppression != nullptr);
}
else {
BOOST_CHECK_THROW(make_unique<AsfStrategy>(forwarder, strategyName), std::invalid_argument);
}
}
return strategy;
};
checkValidity("/probing-interval~30000/max-timeouts~5", true);
checkValidity("/max-timeouts~5/probing-interval~30000", true);
checkValidity("/probing-interval~30000", true);
checkValidity("/max-timeouts~5", true);
checkValidity("", true);
auto strategy = checkValidity("", true);
BOOST_TEST(strategy->m_probing.getProbingInterval() == 60_s);
BOOST_TEST(strategy->m_nMaxTimeouts == 3);
strategy = checkValidity("/probing-interval~30000/max-timeouts~5", true);
BOOST_TEST(strategy->m_probing.getProbingInterval() == 30_s);
BOOST_TEST(strategy->m_nMaxTimeouts == 5);
strategy = checkValidity("/max-timeouts~5/probing-interval~30000", true);
BOOST_TEST(strategy->m_probing.getProbingInterval() == 30_s);
BOOST_TEST(strategy->m_nMaxTimeouts == 5);
strategy = checkValidity("/probing-interval~1000", true);
BOOST_TEST(strategy->m_probing.getProbingInterval() == 1_s);
BOOST_TEST(strategy->m_nMaxTimeouts == 3);
strategy = checkValidity("/max-timeouts~0", true);
BOOST_TEST(strategy->m_probing.getProbingInterval() == 60_s);
BOOST_TEST(strategy->m_nMaxTimeouts == 0);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
checkValidity("/probing-interval~500", false); // At least 1 seconds
checkValidity("/probing-interval~500", false); // minimum is 1 second
checkValidity("/probing-interval~-5000", false);
checkValidity("/max-timeouts~0", false);
checkValidity("/max-timeouts~-5", false);
checkValidity("/max-timeouts~-5/probing-interval~-30000", false);
checkValidity("/max-timeouts", false);
checkValidity("/probing-interval~", false);
checkValidity("/~1000", false);
checkValidity("/max-timeouts~-1", false);
checkValidity("/max-timeouts~ -1", false);
checkValidity("/max-timeouts~1-0", false);
checkValidity("/max-timeouts~1/probing-interval~-30000", false);
checkValidity("/probing-interval~foo", false);
checkValidity("/max-timeouts~1~2", false);
checkValidity("/foo", false);
checkValidity("/foo~42", false);
}
BOOST_AUTO_TEST_SUITE_END() // TestAsfStrategy
+4 -4
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(Forward)
shared_ptr<pit::Entry> pitEntry = pit.insert(*interest).first;
const auto TICK = time::duration_cast<time::nanoseconds>(
BestRouteStrategy::RETX_SUPPRESSION_INITIAL * 0.1);
RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL * 0.1);
// first Interest goes to nexthop with lowest FIB cost,
// however face1 is downstream so it cannot be used
@@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(Forward)
retxFrom4Evt = getScheduler().schedule(TICK * 5, periodicalRetxFrom4);
};
periodicalRetxFrom4();
this->advanceClocks(TICK, BestRouteStrategy::RETX_SUPPRESSION_MAX * 16);
this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 16);
retxFrom4Evt.cancel();
// nexthops for accepted retransmissions: follow FIB cost,
@@ -130,7 +130,7 @@ BOOST_AUTO_TEST_CASE(Forward)
strategy.sendInterestHistory.clear();
for (int i = 0; i < 3; ++i) {
this->advanceClocks(TICK, BestRouteStrategy::RETX_SUPPRESSION_MAX * 2);
this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 2);
pitEntry->insertOrUpdateInRecord(*face5, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face5, 0), pitEntry);
}
+5 -4
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -170,7 +170,8 @@ BOOST_AUTO_TEST_CASE(Forward2)
BOOST_TEST(didSendInterestTo(*face1));
BOOST_TEST(didSendInterestTo(*face2));
const auto TICK = time::duration_cast<time::nanoseconds>(MulticastStrategy::RETX_SUPPRESSION_INITIAL) / 10;
const auto TICK = time::duration_cast<time::nanoseconds>(
RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL) / 10;
// downstream retransmits frequently, but the strategy should not send Interests
// more often than DEFAULT_MIN_RETX_INTERVAL
@@ -195,7 +196,7 @@ BOOST_AUTO_TEST_CASE(Forward2)
retxFrom4Evt = getScheduler().schedule(TICK * 5, periodicalRetxFrom4);
};
periodicalRetxFrom4();
this->advanceClocks(TICK, MulticastStrategy::RETX_SUPPRESSION_MAX * 16);
this->advanceClocks(TICK, RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 16);
retxFrom4Evt.cancel();
}
@@ -215,7 +216,7 @@ BOOST_AUTO_TEST_CASE(LoopingInterest)
BOOST_AUTO_TEST_CASE(RetxSuppression)
{
const auto suppressPeriod = MulticastStrategy::RETX_SUPPRESSION_INITIAL;
const auto suppressPeriod = RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL;
BOOST_ASSERT(suppressPeriod >= 8_ms);
// Set up the FIB
+3 -3
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2019, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(Exponential)
FaceTable faceTable;
Forwarder forwarder(faceTable);
Pit& pit = forwarder.getPit();
RetxSuppressionExponential rs(10_ms, 3.0, 100_ms);
RetxSuppressionExponential rs(10_ms, 100_ms, 3.0);
shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
@@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE(ExponentialPerUpstream)
FaceTable faceTable;
Forwarder forwarder(faceTable);
Pit& pit = forwarder.getPit();
RetxSuppressionExponential rs(10_ms, 3.0, 100_ms);
RetxSuppressionExponential rs(10_ms, 100_ms, 3.0);
shared_ptr<DummyFace> face1 = make_shared<DummyFace>();
shared_ptr<DummyFace> face2 = make_shared<DummyFace>();
+82 -3
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2021, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -74,8 +74,8 @@ public:
using Tests = boost::mpl::vector<
Test<AccessStrategy, false, 1>,
Test<AsfStrategy, true, 4>,
Test<BestRouteStrategy, false, 5>,
Test<MulticastStrategy, false, 4>,
Test<BestRouteStrategy, true, 5>,
Test<MulticastStrategy, true, 4>,
Test<SelfLearningStrategy, false, 1>,
Test<RandomStrategy, false, 1>
>;
@@ -120,6 +120,85 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(InstanceName, T, Tests)
}
}
template<typename S>
class SuppressionParametersFixture
{
public:
std::unique_ptr<S>
checkValidity(const std::string& parameters, bool isCorrect)
{
Name strategyName(Name(S::getStrategyName()).append(parameters));
std::unique_ptr<S> strategy;
BOOST_TEST_CONTEXT(parameters) {
if (isCorrect) {
strategy = make_unique<S>(m_forwarder, strategyName);
BOOST_CHECK(strategy->m_retxSuppression != nullptr);
}
else {
BOOST_CHECK_THROW(make_unique<S>(m_forwarder, strategyName), std::invalid_argument);
}
}
return strategy;
}
private:
FaceTable m_faceTable;
Forwarder m_forwarder{m_faceTable};
};
using StrategiesWithRetxSuppressionExponential = boost::mpl::vector<
AsfStrategy,
BestRouteStrategy,
MulticastStrategy
>;
BOOST_FIXTURE_TEST_CASE_TEMPLATE(SuppressionParameters, S, StrategiesWithRetxSuppressionExponential,
SuppressionParametersFixture<S>)
{
auto strategy = this->checkValidity("", true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
strategy = this->checkValidity("/retx-suppression-initial~20", true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 20_ms);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
this->checkValidity("/retx-suppression-initial~0", false);
this->checkValidity("/retx-suppression-initial~20.5", false);
this->checkValidity("/retx-suppression-initial~-10", false);
this->checkValidity("/retx-suppression-initial~ -5", false);
this->checkValidity("/retx-suppression-initial~NaN", false);
strategy = this->checkValidity("/retx-suppression-max~1000", true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 1_s);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == RetxSuppressionExponential::DEFAULT_MULTIPLIER);
strategy = this->checkValidity("/retx-suppression-initial~40/retx-suppression-max~500", true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 40_ms);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 500_ms);
this->checkValidity("/retx-suppression-initial~20/retx-suppression-max~10", false);
this->checkValidity("/retx-suppression-max~ 500", false);
this->checkValidity("/retx-suppression-max~521.5", false);
strategy = this->checkValidity("/retx-suppression-multiplier~2.25", true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == RetxSuppressionExponential::DEFAULT_MAX_INTERVAL);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == 2.25);
this->checkValidity("/retx-suppression-multiplier~0", false);
this->checkValidity("/retx-suppression-multiplier~0.9", false);
this->checkValidity("/retx-suppression-multiplier~-2.1", false);
this->checkValidity("/retx-suppression-multiplier~foo", false);
strategy = this->checkValidity("/retx-suppression-initial~20/retx-suppression-max~500/retx-suppression-multiplier~3",
true);
BOOST_TEST(strategy->m_retxSuppression->m_initialInterval == 20_ms);
BOOST_TEST(strategy->m_retxSuppression->m_maxInterval == 500_ms);
BOOST_TEST(strategy->m_retxSuppression->m_multiplier == 3);
this->checkValidity("/foo~42", true); // unknown parameters are ignored
}
BOOST_AUTO_TEST_SUITE_END() // TestStrategyInstantiation
BOOST_AUTO_TEST_SUITE_END() // Fw
+24 -4
View File
@@ -1,6 +1,6 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2020, Regents of the University of California,
* Copyright (c) 2014-2022, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
@@ -94,13 +94,33 @@ BOOST_AUTO_TEST_CASE(FaceTableAccess)
FaceId id1 = face1->getId();
FaceId id2 = face2->getId();
BOOST_CHECK(strategy.getLocalFaces() == std::vector<FaceId>{id2});
BOOST_TEST(strategy.getLocalFaces() == std::vector<FaceId>{id2}, boost::test_tools::per_element());
face2->close();
face1->close();
BOOST_CHECK((strategy.addedFaces == std::vector<FaceId>{id1, id2}));
BOOST_CHECK((strategy.removedFaces == std::vector<FaceId>{id2, id1}));
BOOST_TEST((strategy.addedFaces == std::vector<FaceId>{id1, id2}), boost::test_tools::per_element());
BOOST_TEST((strategy.removedFaces == std::vector<FaceId>{id2, id1}), boost::test_tools::per_element());
}
BOOST_AUTO_TEST_CASE(ParseParameters)
{
BOOST_TEST(Strategy::parseParameters("").empty());
BOOST_TEST(Strategy::parseParameters("/").empty());
BOOST_CHECK_THROW(Strategy::parseParameters("/foo"), std::invalid_argument);
BOOST_CHECK_THROW(Strategy::parseParameters("/foo~"), std::invalid_argument);
BOOST_CHECK_THROW(Strategy::parseParameters("/~bar"), std::invalid_argument);
BOOST_CHECK_THROW(Strategy::parseParameters("/~"), std::invalid_argument);
BOOST_CHECK_THROW(Strategy::parseParameters("/~~"), std::invalid_argument);
StrategyParameters expected;
expected["foo"] = "bar";
BOOST_TEST(Strategy::parseParameters("/foo~bar") == expected);
BOOST_CHECK_THROW(Strategy::parseParameters("/foo~bar/42"), std::invalid_argument);
expected["the-answer"] = "42";
BOOST_TEST(Strategy::parseParameters("/foo~bar/the-answer~42") == expected);
expected["foo"] = "foo2";
BOOST_TEST(Strategy::parseParameters("/foo~bar/the-answer~42/foo~foo2") == expected);
}
BOOST_AUTO_TEST_SUITE_END() // TestStrategy
-2
View File
@@ -28,8 +28,6 @@ import os, subprocess
VERSION = '22.02'
APPNAME = 'nfd'
BUGREPORT = 'https://redmine.named-data.net/projects/nfd'
URL = 'https://named-data.net/doc/NFD/'
GIT_TAG_PREFIX = 'NFD-'
def options(opt):