blob: b51b001e56e2eb7587d48f8440650f63572becea [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2024, 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 "fw/multicast-strategy.hpp"
#include "common/global.hpp"
#include "tests/test-common.hpp"
#include "tests/daemon/face/dummy-face.hpp"
#include "choose-strategy.hpp"
#include "strategy-tester.hpp"
#include "topology-tester.hpp"
#include <boost/mp11/list.hpp>
namespace nfd::tests {
using MulticastStrategyTester = StrategyTester<fw::MulticastStrategy>;
NFD_REGISTER_STRATEGY(MulticastStrategyTester);
class MulticastStrategyFixture : public GlobalIoTimeFixture
{
protected:
MulticastStrategyFixture()
: strategy(choose<MulticastStrategyTester>(forwarder))
, face1(make_shared<DummyFace>())
, face2(make_shared<DummyFace>())
, face3(make_shared<DummyFace>())
{
faceTable.add(face1);
faceTable.add(face2);
faceTable.add(face3);
}
bool
didSendInterestTo(const Face& face) const
{
auto it = std::find_if(strategy.sendInterestHistory.begin(),
strategy.sendInterestHistory.end(),
[&] (const auto& elem) { return elem.outFaceId == face.getId(); });
return it != strategy.sendInterestHistory.end();
}
protected:
FaceTable faceTable;
Forwarder forwarder{faceTable};
MulticastStrategyTester& strategy;
Fib& fib{forwarder.getFib()};
Pit& pit{forwarder.getPit()};
shared_ptr<DummyFace> face1;
shared_ptr<DummyFace> face2;
shared_ptr<DummyFace> face3;
};
BOOST_AUTO_TEST_SUITE(Fw)
BOOST_FIXTURE_TEST_SUITE(TestMulticastStrategy, MulticastStrategyFixture)
BOOST_AUTO_TEST_CASE(Bug5123)
{
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face2, 0);
// Send an Interest from face 1 to face 2
auto interest = makeInterest("ndn:/H0D6i5fc");
auto pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face1, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 1);
// Advance more than default suppression
this->advanceClocks(15_ms);
// Get same interest from face 2 which does not have anywhere to go
pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face2, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face2), pitEntry);
// Since the interest is the same as the one sent out earlier, the PIT entry should not be
// rejected, as any data coming back must be able to satisfy the original interest from face 1
BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
/*
* +---------+ +---------+ +---------+
* | nodeA |------------| nodeB |----------| nodeC |
* +---------+ 10ms +---------+ 100ms +---------+
*/
const Name PRODUCER_PREFIX = "/ndn/edu/nodeC/ping";
TopologyTester topo;
TopologyNode nodeA = topo.addForwarder("A"),
nodeB = topo.addForwarder("B"),
nodeC = topo.addForwarder("C");
for (TopologyNode node : {nodeA, nodeB, nodeC}) {
topo.setStrategy<fw::MulticastStrategy>(node);
}
shared_ptr<TopologyLink> linkAB = topo.addLink("AB", 10_ms, {nodeA, nodeB}),
linkBC = topo.addLink("BC", 100_ms, {nodeB, nodeC});
shared_ptr<TopologyAppLink> appA = topo.addAppFace("cA", nodeA),
appB = topo.addAppFace("cB", nodeB),
pingServer = topo.addAppFace("p", nodeC, PRODUCER_PREFIX);
topo.addEchoProducer(pingServer->getClientFace());
topo.registerPrefix(nodeA, linkAB->getFace(nodeA), PRODUCER_PREFIX, 10);
topo.registerPrefix(nodeB, linkAB->getFace(nodeB), PRODUCER_PREFIX, 10);
topo.registerPrefix(nodeB, linkBC->getFace(nodeB), PRODUCER_PREFIX, 100);
Name name(PRODUCER_PREFIX);
name.appendTimestamp();
interest = makeInterest(name);
appA->getClientFace().expressInterest(*interest, nullptr, nullptr, nullptr);
this->advanceClocks(10_ms, 20_ms);
// AppB expresses the same interest
interest->refreshNonce();
appB->getClientFace().expressInterest(*interest, nullptr, nullptr, nullptr);
this->advanceClocks(10_ms, 200_ms);
// Data should have made to appB
BOOST_CHECK_EQUAL(linkBC->getFace(nodeB).getCounters().nInData, 1);
BOOST_CHECK_EQUAL(linkAB->getFace(nodeA).getCounters().nInData, 0);
this->advanceClocks(10_ms, 10_ms);
// nodeA should have gotten the data successfully
BOOST_CHECK_EQUAL(linkAB->getFace(nodeA).getCounters().nInData, 1);
BOOST_CHECK_EQUAL(topo.getForwarder(nodeA).getCounters().nUnsolicitedData, 0);
}
BOOST_AUTO_TEST_CASE(Forward2)
{
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face1, 0);
fib.addOrUpdateNextHop(fibEntry, *face2, 0);
fib.addOrUpdateNextHop(fibEntry, *face3, 0);
auto interest = makeInterest("ndn:/H0D6i5fc");
auto pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face3, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face3), pitEntry);
BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 2);
BOOST_TEST(didSendInterestTo(*face1));
BOOST_TEST(didSendInterestTo(*face2));
const auto TICK = time::duration_cast<time::nanoseconds>(
fw::RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL) / 10;
// downstream retransmits frequently, but the strategy should not send Interests
// more often than DEFAULT_MIN_RETX_INTERVAL
ndn::scheduler::EventId retxFrom4Evt;
size_t nSentLast = strategy.sendInterestHistory.size();
auto timeSentLast = time::steady_clock::now();
std::function<void()> periodicalRetxFrom4; // let periodicalRetxFrom4 lambda capture itself
periodicalRetxFrom4 = [&] {
pitEntry->insertOrUpdateInRecord(*face3, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face3), pitEntry);
size_t nSent = strategy.sendInterestHistory.size();
if (nSent > nSentLast) {
// Multicast strategy should multicast the interest to other two faces
BOOST_CHECK_EQUAL(nSent - nSentLast, 2);
auto timeSent = time::steady_clock::now();
BOOST_CHECK_GE(timeSent - timeSentLast, TICK * 8);
nSentLast = nSent;
timeSentLast = timeSent;
}
retxFrom4Evt = getScheduler().schedule(TICK * 5, periodicalRetxFrom4);
};
periodicalRetxFrom4();
this->advanceClocks(TICK, fw::RetxSuppressionExponential::DEFAULT_MAX_INTERVAL * 16);
retxFrom4Evt.cancel();
}
BOOST_AUTO_TEST_CASE(LoopingInterest)
{
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face1, 0);
auto interest = makeInterest("ndn:/H0D6i5fc");
auto pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face1, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
BOOST_TEST(strategy.rejectPendingInterestHistory.size() == 0);
BOOST_TEST(strategy.sendInterestHistory.size() == 0);
}
BOOST_AUTO_TEST_CASE(DuplicateInterest)
{
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face3, 0);
auto interest = makeInterest("ndn:/H0D6i5fc");
// first interest
forwarder.onIncomingInterest(*interest, FaceEndpoint(*face1));
BOOST_TEST(forwarder.getCounters().nInInterests == 1);
BOOST_TEST(strategy.sendInterestHistory.size() == 1);
// second interest (duplicate, should enter onInterestLoop)
forwarder.onIncomingInterest(*interest, FaceEndpoint(*face2));
BOOST_TEST(forwarder.getCounters().nInInterests == 2);
BOOST_TEST(strategy.sendInterestHistory.size() == 1);
BOOST_TEST(strategy.rejectPendingInterestHistory.size() == 0);
BOOST_TEST(strategy.sendNackHistory.size() == 0);
}
BOOST_AUTO_TEST_CASE(RetxSuppression)
{
const auto suppressPeriod = fw::RetxSuppressionExponential::DEFAULT_INITIAL_INTERVAL;
BOOST_ASSERT(suppressPeriod >= 8_ms);
// Set up the FIB
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face1, 0);
fib.addOrUpdateNextHop(fibEntry, *face2, 0);
fib.addOrUpdateNextHop(fibEntry, *face3, 0);
// Interest arrives from face 1
auto interest = makeInterest("/t8ZiSOi3");
auto pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face1, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
// forwarded to faces 2 and 3
BOOST_TEST(strategy.sendInterestHistory.size() == 2);
BOOST_TEST(didSendInterestTo(*face2));
BOOST_TEST(didSendInterestTo(*face3));
strategy.sendInterestHistory.clear();
// still within the initial suppression period for face 2 and 3
this->advanceClocks(suppressPeriod - 5_ms);
// Interest arrives from face 2
interest->refreshNonce();
pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face2, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face2), pitEntry);
// forwarded only to face 1, suppressed on face 3
BOOST_TEST(strategy.sendInterestHistory.size() == 1);
BOOST_TEST(didSendInterestTo(*face1));
strategy.sendInterestHistory.clear();
// faces 2 and 3 no longer inside the suppression window
this->advanceClocks(7_ms);
// Interest arrives from face 3
interest->refreshNonce();
pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face3, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face3), pitEntry);
// suppressed on face 1, forwarded on face 2 (and suppression window doubles)
BOOST_TEST(strategy.sendInterestHistory.size() == 1);
BOOST_TEST(didSendInterestTo(*face2));
strategy.sendInterestHistory.clear();
// face 1 exits the suppression period, face 2 still inside
this->advanceClocks(2 * suppressPeriod - 2_ms);
// Interest arrives from face 3
interest->refreshNonce();
pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face3, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face3), pitEntry);
// forwarded only to face 1, suppressed on face 2
BOOST_TEST(strategy.sendInterestHistory.size() == 1);
BOOST_TEST(didSendInterestTo(*face1));
strategy.sendInterestHistory.clear();
// face 2 exits the suppression period
this->advanceClocks(3_ms);
// Interest arrives from face 1
interest->refreshNonce();
pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face1, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
// forwarded to faces 2 and 3
BOOST_TEST(strategy.sendInterestHistory.size() == 2);
BOOST_TEST(didSendInterestTo(*face2));
BOOST_TEST(didSendInterestTo(*face3));
strategy.sendInterestHistory.clear();
}
BOOST_AUTO_TEST_CASE(NewNextHop)
{
fib::Entry& fibEntry = *fib.insert(Name()).first;
fib.addOrUpdateNextHop(fibEntry, *face1, 0);
fib.addOrUpdateNextHop(fibEntry, *face2, 0);
auto interest = makeInterest("ndn:/H0D6i5fc");
auto pitEntry = pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*face1, *interest);
strategy.afterReceiveInterest(*interest, FaceEndpoint(*face1), pitEntry);
BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 1);
fib.addOrUpdateNextHop(fibEntry, *face3, 0);
BOOST_CHECK_EQUAL(strategy.rejectPendingInterestHistory.size(), 0);
BOOST_CHECK_EQUAL(strategy.sendInterestHistory.size(), 2);
}
BOOST_AUTO_TEST_SUITE(LocalhopScope)
class ForwardAsyncFixture : public MulticastStrategyFixture
{
protected:
shared_ptr<Face> inFace1;
shared_ptr<Face> inFace2;
shared_ptr<Face> fibFace1;
shared_ptr<Face> fibFace2;
shared_ptr<Face> newFibFace;
size_t expectedInterests = 0;
};
class BasicNonLocal : public ForwardAsyncFixture
{
protected:
BasicNonLocal()
{
inFace1 = face1;
// inFace2 = nullptr;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = face3;
expectedInterests = 0; // anything received on non-local face can only be sent to local face
}
};
class NewFibLocal : public ForwardAsyncFixture
{
protected:
NewFibLocal()
{
inFace1 = face1;
// inFace2 = nullptr;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
expectedInterests = 1;
faceTable.add(newFibFace);
}
};
class InFaceLocal : public ForwardAsyncFixture
{
protected:
InFaceLocal()
{
inFace1 = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
// inFace2 = nullptr;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = face3;
expectedInterests = 1;
faceTable.add(inFace1);
}
};
class InFaceLocalSameNewFace : public ForwardAsyncFixture
{
protected:
InFaceLocalSameNewFace()
{
inFace1 = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
// inFace2 = nullptr;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = inFace1;
expectedInterests = 0;
faceTable.add(inFace1);
}
};
class InFaceLocalAdHocSameNewFace : public ForwardAsyncFixture
{
protected:
InFaceLocalAdHocSameNewFace()
{
inFace1 = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL,
ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
ndn::nfd::LINK_TYPE_AD_HOC);
// inFace2 = nullptr;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = inFace1;
expectedInterests = 1;
faceTable.add(inFace1);
}
};
class InFaceLocalAndNonLocal1 : public ForwardAsyncFixture
{
protected:
InFaceLocalAndNonLocal1()
{
inFace1 = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
inFace2 = face1;
fibFace1 = face1;
fibFace2 = face2;
newFibFace = face3;
expectedInterests = 1;
faceTable.add(inFace1);
}
};
class InFaceLocalAndNonLocal2 : public ForwardAsyncFixture
{
protected:
InFaceLocalAndNonLocal2()
{
inFace1 = face1;
inFace2 = make_shared<DummyFace>("dummy://", "dummy://", ndn::nfd::FACE_SCOPE_LOCAL);
fibFace1 = face1;
fibFace2 = face2;
newFibFace = face3;
expectedInterests = 1;
faceTable.add(inFace2);
}
};
class InFaceSelection1 : public ForwardAsyncFixture
{
protected:
InFaceSelection1()
{
inFace1 = face1;
// inFace2 = nullptr;
fibFace1 = face3;
fibFace2 = face2;
newFibFace = face1;
expectedInterests = 0;
}
};
class InFaceSelection2 : public ForwardAsyncFixture
{
protected:
InFaceSelection2()
{
inFace1 = face2;
inFace2 = face1;
fibFace1 = face2;
fibFace2 = face3;
newFibFace = face1;
// this test will trigger the check for additional branch, but it
// still is not going to pass the localhop check
expectedInterests = 0;
}
};
using Tests = boost::mp11::mp_list<
BasicNonLocal,
NewFibLocal,
InFaceLocal,
InFaceLocalSameNewFace,
InFaceLocalAdHocSameNewFace,
InFaceLocalAndNonLocal1,
InFaceLocalAndNonLocal2,
InFaceSelection1,
InFaceSelection2
>;
BOOST_FIXTURE_TEST_CASE_TEMPLATE(ForwardAsync, T, Tests, T)
{
fib::Entry& fibEntry = *this->fib.insert(Name("/localhop")).first;
this->fib.addOrUpdateNextHop(fibEntry, *this->fibFace1, 0);
this->fib.addOrUpdateNextHop(fibEntry, *this->fibFace2, 0);
auto interest = makeInterest("ndn:/localhop/H0D6i5fc");
auto pitEntry = this->pit.insert(*interest).first;
pitEntry->insertOrUpdateInRecord(*this->inFace1, *interest);
this->strategy.afterReceiveInterest(*interest, FaceEndpoint(*this->inFace1), pitEntry);
if (this->inFace2 != nullptr) {
auto interest2 = makeInterest("ndn:/localhop/H0D6i5fc");
pitEntry->insertOrUpdateInRecord(*this->inFace2, *interest2);
this->strategy.afterReceiveInterest(*interest2, FaceEndpoint(*this->inFace2), pitEntry);
}
this->strategy.sendInterestHistory.clear();
this->fib.addOrUpdateNextHop(fibEntry, *this->newFibFace, 0);
BOOST_CHECK_EQUAL(this->strategy.sendInterestHistory.size(), this->expectedInterests);
}
BOOST_AUTO_TEST_SUITE_END() // LocalhopScope
BOOST_AUTO_TEST_SUITE_END() // TestMulticastStrategy
BOOST_AUTO_TEST_SUITE_END() // Fw
} // namespace nfd::tests