/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /** * Copyright (c) 2014-2015, 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 . */ #include "forwarder.hpp" #include "core/logger.hpp" #include "core/random.hpp" #include "strategy.hpp" #include namespace nfd { NFD_LOG_INIT("Forwarder"); using fw::Strategy; const Name Forwarder::LOCALHOST_NAME("ndn:/localhost"); Forwarder::Forwarder() : m_faceTable(*this) , m_fib(m_nameTree) , m_pit(m_nameTree) , m_measurements(m_nameTree) , m_strategyChoice(m_nameTree, fw::makeDefaultStrategy(*this)) { fw::installStrategies(*this); } Forwarder::~Forwarder() { } void Forwarder::startProcessInterest(Face& face, const Interest& interest) { // check fields used by forwarding are well-formed try { if (interest.hasLink()) { interest.getLink(); } } catch (const tlv::Error&) { NFD_LOG_DEBUG("startProcessInterest face=" << face.getId() << " interest=" << interest.getName() << " malformed"); // It's safe to call interest.getName() because Name has been fully parsed return; } this->onIncomingInterest(face, interest); } void Forwarder::startProcessData(Face& face, const Data& data) { // check fields used by forwarding are well-formed // (none needed) this->onIncomingData(face, data); } void Forwarder::startProcessNack(Face& face, const lp::Nack& nack) { // check fields used by forwarding are well-formed try { if (nack.getInterest().hasLink()) { nack.getInterest().getLink(); } } catch (const tlv::Error&) { NFD_LOG_DEBUG("startProcessNack face=" << face.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " malformed"); return; } this->onIncomingNack(face, nack); } void Forwarder::onIncomingInterest(Face& inFace, const Interest& interest) { // receive Interest NFD_LOG_DEBUG("onIncomingInterest face=" << inFace.getId() << " interest=" << interest.getName()); const_cast(interest).setIncomingFaceId(inFace.getId()); ++m_counters.nInInterests; // /localhost scope control bool isViolatingLocalhost = !inFace.isLocal() && LOCALHOST_NAME.isPrefixOf(interest.getName()); if (isViolatingLocalhost) { NFD_LOG_DEBUG("onIncomingInterest face=" << inFace.getId() << " interest=" << interest.getName() << " violates /localhost"); // (drop) return; } // PIT insert shared_ptr pitEntry = m_pit.insert(interest).first; // detect duplicate Nonce int dnw = pitEntry->findNonce(interest.getNonce(), inFace); bool hasDuplicateNonce = (dnw != pit::DUPLICATE_NONCE_NONE) || m_deadNonceList.has(interest.getName(), interest.getNonce()); if (hasDuplicateNonce) { // goto Interest loop pipeline this->onInterestLoop(inFace, interest, pitEntry); return; } // cancel unsatisfy & straggler timer this->cancelUnsatisfyAndStragglerTimer(pitEntry); // is pending? const pit::InRecordCollection& inRecords = pitEntry->getInRecords(); bool isPending = inRecords.begin() != inRecords.end(); if (!isPending) { m_cs.find(interest, bind(&Forwarder::onContentStoreHit, this, ref(inFace), pitEntry, _1, _2), bind(&Forwarder::onContentStoreMiss, this, ref(inFace), pitEntry, _1)); } else { this->onContentStoreMiss(inFace, pitEntry, interest); } } void Forwarder::onInterestLoop(Face& inFace, const Interest& interest, shared_ptr pitEntry) { // if multi-access face, drop if (inFace.isMultiAccess()) { NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() << " interest=" << interest.getName() << " drop"); return; } NFD_LOG_DEBUG("onInterestLoop face=" << inFace.getId() << " interest=" << interest.getName() << " send-Nack-duplicate"); // send Nack with reason=DUPLICATE // note: Don't enter outgoing Nack pipeline because it needs an in-record. lp::Nack nack(interest); nack.setReason(lp::NackReason::DUPLICATE); inFace.sendNack(nack); } void Forwarder::onContentStoreMiss(const Face& inFace, shared_ptr pitEntry, const Interest& interest) { NFD_LOG_DEBUG("onContentStoreMiss interest=" << interest.getName()); shared_ptr face = const_pointer_cast(inFace.shared_from_this()); // insert InRecord pitEntry->insertOrUpdateInRecord(face, interest); // set PIT unsatisfy timer this->setUnsatisfyTimer(pitEntry); shared_ptr fibEntry; // has Link object? if (!interest.hasLink()) { // FIB lookup with Interest name fibEntry = m_fib.findLongestPrefixMatch(*pitEntry); NFD_LOG_TRACE("onContentStoreMiss noLinkObject"); } else { const Link& link = interest.getLink(); // in producer region? if (m_networkRegionTable.isInProducerRegion(link)) { // FIB lookup with Interest name fibEntry = m_fib.findLongestPrefixMatch(*pitEntry); NFD_LOG_TRACE("onContentStoreMiss inProducerRegion"); } // has SelectedDelegation? else if (interest.hasSelectedDelegation()) { // FIB lookup with SelectedDelegation fibEntry = m_fib.findLongestPrefixMatch(interest.getSelectedDelegation()); NFD_LOG_TRACE("onContentStoreMiss hasSelectedDelegation=" << interest.getSelectedDelegation()); } else { // FIB lookup with first delegation Name fibEntry = m_fib.findLongestPrefixMatch(link.getDelegations().begin()->second); // in default-free zone? bool isDefaultFreeZone = !(fibEntry->getPrefix().size() == 0 && fibEntry->hasNextHops()); if (isDefaultFreeZone) { // choose and set SelectedDelegation for (const std::pair& delegation : link.getDelegations()) { const Name& delegationName = delegation.second; fibEntry = m_fib.findLongestPrefixMatch(delegationName); if (fibEntry->hasNextHops()) { const_cast(interest).setSelectedDelegation(delegationName); NFD_LOG_TRACE("onContentStoreMiss enterDefaultFreeZone" << " setSelectedDelegation=" << delegationName); break; } } } else { NFD_LOG_TRACE("onContentStoreMiss inConsumerRegion"); } } } // dispatch to strategy BOOST_ASSERT(fibEntry != nullptr); this->dispatchToStrategy(pitEntry, bind(&Strategy::afterReceiveInterest, _1, cref(inFace), cref(interest), fibEntry, pitEntry)); } void Forwarder::onContentStoreHit(const Face& inFace, shared_ptr pitEntry, const Interest& interest, const Data& data) { NFD_LOG_DEBUG("onContentStoreHit interest=" << interest.getName()); const_pointer_cast(data.shared_from_this())->setIncomingFaceId(FACEID_CONTENT_STORE); // XXX should we lookup PIT for other Interests that also match csMatch? // set PIT straggler timer this->setStragglerTimer(pitEntry, true, data.getFreshnessPeriod()); // goto outgoing Data pipeline this->onOutgoingData(data, *const_pointer_cast(inFace.shared_from_this())); } /** \brief compare two InRecords for picking outgoing Interest * \return true if b is preferred over a * * This function should be passed to std::max_element over InRecordCollection. * The outgoing Interest picked is the last incoming Interest * that does not come from outFace. * If all InRecords come from outFace, it's fine to pick that. This happens when * there's only one InRecord that comes from outFace. The legit use is for * vehicular network; otherwise, strategy shouldn't send to the sole inFace. */ static inline bool compare_pickInterest(const pit::InRecord& a, const pit::InRecord& b, const Face* outFace) { bool isOutFaceA = a.getFace().get() == outFace; bool isOutFaceB = b.getFace().get() == outFace; if (!isOutFaceA && isOutFaceB) { return false; } if (isOutFaceA && !isOutFaceB) { return true; } return a.getLastRenewed() > b.getLastRenewed(); } void Forwarder::onOutgoingInterest(shared_ptr pitEntry, Face& outFace, bool wantNewNonce) { if (outFace.getId() == INVALID_FACEID) { NFD_LOG_WARN("onOutgoingInterest face=invalid interest=" << pitEntry->getName()); return; } NFD_LOG_DEBUG("onOutgoingInterest face=" << outFace.getId() << " interest=" << pitEntry->getName()); // scope control if (pitEntry->violatesScope(outFace)) { NFD_LOG_DEBUG("onOutgoingInterest face=" << outFace.getId() << " interest=" << pitEntry->getName() << " violates scope"); return; } // pick Interest const pit::InRecordCollection& inRecords = pitEntry->getInRecords(); pit::InRecordCollection::const_iterator pickedInRecord = std::max_element( inRecords.begin(), inRecords.end(), bind(&compare_pickInterest, _1, _2, &outFace)); BOOST_ASSERT(pickedInRecord != inRecords.end()); shared_ptr interest = const_pointer_cast( pickedInRecord->getInterest().shared_from_this()); if (wantNewNonce) { interest = make_shared(*interest); static boost::random::uniform_int_distribution dist; interest->setNonce(dist(getGlobalRng())); } // insert OutRecord pitEntry->insertOrUpdateOutRecord(outFace.shared_from_this(), *interest); // send Interest outFace.sendInterest(*interest); ++m_counters.nOutInterests; } void Forwarder::onInterestReject(shared_ptr pitEntry) { if (pitEntry->hasUnexpiredOutRecords()) { NFD_LOG_ERROR("onInterestReject interest=" << pitEntry->getName() << " cannot reject forwarded Interest"); return; } NFD_LOG_DEBUG("onInterestReject interest=" << pitEntry->getName()); // cancel unsatisfy & straggler timer this->cancelUnsatisfyAndStragglerTimer(pitEntry); // set PIT straggler timer this->setStragglerTimer(pitEntry, false); } void Forwarder::onInterestUnsatisfied(shared_ptr pitEntry) { NFD_LOG_DEBUG("onInterestUnsatisfied interest=" << pitEntry->getName()); // invoke PIT unsatisfied callback this->dispatchToStrategy(pitEntry, bind(&Strategy::beforeExpirePendingInterest, _1, pitEntry)); // goto Interest Finalize pipeline this->onInterestFinalize(pitEntry, false); } void Forwarder::onInterestFinalize(shared_ptr pitEntry, bool isSatisfied, const time::milliseconds& dataFreshnessPeriod) { NFD_LOG_DEBUG("onInterestFinalize interest=" << pitEntry->getName() << (isSatisfied ? " satisfied" : " unsatisfied")); // Dead Nonce List insert if necessary this->insertDeadNonceList(*pitEntry, isSatisfied, dataFreshnessPeriod, 0); // PIT delete this->cancelUnsatisfyAndStragglerTimer(pitEntry); m_pit.erase(pitEntry); } void Forwarder::onIncomingData(Face& inFace, const Data& data) { // receive Data NFD_LOG_DEBUG("onIncomingData face=" << inFace.getId() << " data=" << data.getName()); const_cast(data).setIncomingFaceId(inFace.getId()); ++m_counters.nInData; // /localhost scope control bool isViolatingLocalhost = !inFace.isLocal() && LOCALHOST_NAME.isPrefixOf(data.getName()); if (isViolatingLocalhost) { NFD_LOG_DEBUG("onIncomingData face=" << inFace.getId() << " data=" << data.getName() << " violates /localhost"); // (drop) return; } // PIT match pit::DataMatchResult pitMatches = m_pit.findAllDataMatches(data); if (pitMatches.begin() == pitMatches.end()) { // goto Data unsolicited pipeline this->onDataUnsolicited(inFace, data); return; } // CS insert m_cs.insert(data); std::set pendingDownstreams; // foreach PitEntry for (const shared_ptr& pitEntry : pitMatches) { NFD_LOG_DEBUG("onIncomingData matching=" << pitEntry->getName()); // cancel unsatisfy & straggler timer this->cancelUnsatisfyAndStragglerTimer(pitEntry); // remember pending downstreams const pit::InRecordCollection& inRecords = pitEntry->getInRecords(); for (const pit::InRecord& inRecord : inRecords) { if (inRecord.getExpiry() > time::steady_clock::now()) { pendingDownstreams.insert(inRecord.getFace().get()); } } // invoke PIT satisfy callback this->dispatchToStrategy(pitEntry, bind(&Strategy::beforeSatisfyInterest, _1, pitEntry, cref(inFace), cref(data))); // Dead Nonce List insert if necessary (for OutRecord of inFace) this->insertDeadNonceList(*pitEntry, true, data.getFreshnessPeriod(), &inFace); // mark PIT satisfied pitEntry->deleteInRecords(); pitEntry->deleteOutRecord(inFace); // set PIT straggler timer this->setStragglerTimer(pitEntry, true, data.getFreshnessPeriod()); } // foreach pending downstream for (Face* pendingDownstream : pendingDownstreams) { if (pendingDownstream == &inFace) { continue; } // goto outgoing Data pipeline this->onOutgoingData(data, *pendingDownstream); } } void Forwarder::onDataUnsolicited(Face& inFace, const Data& data) { // accept to cache? bool acceptToCache = inFace.isLocal(); if (acceptToCache) { // CS insert m_cs.insert(data, true); } NFD_LOG_DEBUG("onDataUnsolicited face=" << inFace.getId() << " data=" << data.getName() << (acceptToCache ? " cached" : " not cached")); } void Forwarder::onOutgoingData(const Data& data, Face& outFace) { if (outFace.getId() == INVALID_FACEID) { NFD_LOG_WARN("onOutgoingData face=invalid data=" << data.getName()); return; } NFD_LOG_DEBUG("onOutgoingData face=" << outFace.getId() << " data=" << data.getName()); // /localhost scope control bool isViolatingLocalhost = !outFace.isLocal() && LOCALHOST_NAME.isPrefixOf(data.getName()); if (isViolatingLocalhost) { NFD_LOG_DEBUG("onOutgoingData face=" << outFace.getId() << " data=" << data.getName() << " violates /localhost"); // (drop) return; } // TODO traffic manager // send Data outFace.sendData(data); ++m_counters.nOutData; } void Forwarder::onIncomingNack(Face& inFace, const lp::Nack& nack) { ++m_counters.nInNacks; // if multi-access face, drop if (inFace.isMultiAccess()) { NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " face-is-multi-access"); return; } // PIT match shared_ptr pitEntry = m_pit.find(nack.getInterest()); // if no PIT entry found, drop if (pitEntry == nullptr) { NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " no-PIT-entry"); return; } // has out-record? pit::OutRecordCollection::iterator outRecord = pitEntry->getOutRecord(inFace); // if no out-record found, drop if (outRecord == pitEntry->getOutRecords().end()) { NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " no-out-record"); return; } // if out-record has different Nonce, drop if (nack.getInterest().getNonce() != outRecord->getLastNonce()) { NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " wrong-Nonce " << nack.getInterest().getNonce() << "!=" << outRecord->getLastNonce()); return; } NFD_LOG_DEBUG("onIncomingNack face=" << inFace.getId() << " nack=" << nack.getInterest().getName() << "~" << nack.getReason() << " OK"); // record Nack on out-record outRecord->setIncomingNack(nack); // trigger strategy: after receive NACK shared_ptr fibEntry = m_fib.findLongestPrefixMatch(*pitEntry); this->dispatchToStrategy(pitEntry, bind(&Strategy::afterReceiveNack, _1, cref(inFace), cref(nack), fibEntry, pitEntry)); } void Forwarder::onOutgoingNack(shared_ptr pitEntry, const Face& outFace, const lp::NackHeader& nack) { if (outFace.getId() == INVALID_FACEID) { NFD_LOG_WARN("onOutgoingNack face=invalid" << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason() << " no-in-record"); return; } // has in-record? pit::InRecordCollection::const_iterator inRecord = pitEntry->getInRecord(outFace); // if no in-record found, drop if (inRecord == pitEntry->getInRecords().end()) { NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason() << " no-in-record"); return; } // if multi-access face, drop if (outFace.isMultiAccess()) { NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason() << " face-is-multi-access"); return; } NFD_LOG_DEBUG("onOutgoingNack face=" << outFace.getId() << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason() << " OK"); // create Nack packet with the Interest from in-record lp::Nack nackPkt(inRecord->getInterest()); nackPkt.setHeader(nack); // erase in-record pitEntry->deleteInRecord(outFace); // send Nack on face const_cast(outFace).sendNack(nackPkt); ++m_counters.nOutNacks; } static inline bool compare_InRecord_expiry(const pit::InRecord& a, const pit::InRecord& b) { return a.getExpiry() < b.getExpiry(); } void Forwarder::setUnsatisfyTimer(shared_ptr pitEntry) { const pit::InRecordCollection& inRecords = pitEntry->getInRecords(); pit::InRecordCollection::const_iterator lastExpiring = std::max_element(inRecords.begin(), inRecords.end(), &compare_InRecord_expiry); time::steady_clock::TimePoint lastExpiry = lastExpiring->getExpiry(); time::nanoseconds lastExpiryFromNow = lastExpiry - time::steady_clock::now(); if (lastExpiryFromNow <= time::seconds(0)) { // TODO all InRecords are already expired; will this happen? } scheduler::cancel(pitEntry->m_unsatisfyTimer); pitEntry->m_unsatisfyTimer = scheduler::schedule(lastExpiryFromNow, bind(&Forwarder::onInterestUnsatisfied, this, pitEntry)); } void Forwarder::setStragglerTimer(shared_ptr pitEntry, bool isSatisfied, const time::milliseconds& dataFreshnessPeriod) { time::nanoseconds stragglerTime = time::milliseconds(100); scheduler::cancel(pitEntry->m_stragglerTimer); pitEntry->m_stragglerTimer = scheduler::schedule(stragglerTime, bind(&Forwarder::onInterestFinalize, this, pitEntry, isSatisfied, dataFreshnessPeriod)); } void Forwarder::cancelUnsatisfyAndStragglerTimer(shared_ptr pitEntry) { scheduler::cancel(pitEntry->m_unsatisfyTimer); scheduler::cancel(pitEntry->m_stragglerTimer); } static inline void insertNonceToDnl(DeadNonceList& dnl, const pit::Entry& pitEntry, const pit::OutRecord& outRecord) { dnl.add(pitEntry.getName(), outRecord.getLastNonce()); } void Forwarder::insertDeadNonceList(pit::Entry& pitEntry, bool isSatisfied, const time::milliseconds& dataFreshnessPeriod, Face* upstream) { // need Dead Nonce List insert? bool needDnl = false; if (isSatisfied) { bool hasFreshnessPeriod = dataFreshnessPeriod >= time::milliseconds::zero(); // Data never becomes stale if it doesn't have FreshnessPeriod field needDnl = static_cast(pitEntry.getInterest().getMustBeFresh()) && (hasFreshnessPeriod && dataFreshnessPeriod < m_deadNonceList.getLifetime()); } else { needDnl = true; } if (!needDnl) { return; } // Dead Nonce List insert if (upstream == 0) { // insert all outgoing Nonces const pit::OutRecordCollection& outRecords = pitEntry.getOutRecords(); std::for_each(outRecords.begin(), outRecords.end(), bind(&insertNonceToDnl, ref(m_deadNonceList), cref(pitEntry), _1)); } else { // insert outgoing Nonce of a specific face pit::OutRecordCollection::const_iterator outRecord = pitEntry.getOutRecord(*upstream); if (outRecord != pitEntry.getOutRecords().end()) { m_deadNonceList.add(pitEntry.getName(), outRecord->getLastNonce()); } } } } // namespace nfd