fw: delegate sending Nack-Duplicate to forwarding strategy

This commit introduces a new strategy trigger (onInterestLoop) that
is invoked when a duplicate Interest is received. The default behavior
of the new trigger is to send a Nack as before. We also modify the
MulticastStrategy to override onInterestLoop and skip sending the Nack.

Refs: #5278
Co-authored-by: Davide Pesavento <davidepesa@gmail.com>
Change-Id: I07bb520bd31cbfc8d8f8581e05c49927f8ea35f7
diff --git a/daemon/fw/strategy.hpp b/daemon/fw/strategy.hpp
index 460fbdc..c42a968 100644
--- a/daemon/fw/strategy.hpp
+++ b/daemon/fw/strategy.hpp
@@ -135,7 +135,7 @@
    * The Interest:
    *  - has not exceeded HopLimit
    *  - does not violate Scope
-   *  - is not looped
+   *  - has not looped
    *  - cannot be satisfied by ContentStore
    *  - is under a namespace managed by this strategy
    *
@@ -161,6 +161,20 @@
                        const shared_ptr<pit::Entry>& pitEntry) = 0;
 
   /**
+   * \brief Trigger after an Interest loop is detected.
+   *
+   * The Interest:
+   *  - has not exceeded HopLimit
+   *  - does not violate Scope
+   *  - has looped
+   *  - is under a namespace managed by this strategy
+   *
+   * In the base class, this method sends a Nack with reason DUPLICATE to \p ingress.
+   */
+  virtual void
+  onInterestLoop(const Interest& interest, const FaceEndpoint& ingress);
+
+  /**
    * \brief Trigger after a matching Data is found in the Content Store.
    *
    * In the base class, this method sends \p data to \p ingress.
@@ -340,6 +354,21 @@
   }
 
   /**
+   * \brief Send a Nack packet without going through the outgoing Nack pipeline.
+   *
+   * \param nack the Nack packet
+   * \param egress face through which to send out the Nack
+   * \return Whether the Nack was sent (true) or dropped (false)
+   */
+  NFD_VIRTUAL_WITH_TESTS bool
+  sendNack(const lp::Nack& nack, Face& egress)
+  {
+    egress.sendNack(nack);
+    ++m_forwarder.m_counters.nOutNacks;
+    return true;
+  }
+
+  /**
    * \brief Send Nack to every face that has an in-record, except those in \p exceptFaces
    * \param header the Nack header
    * \param pitEntry the PIT entry