743 lines
16 KiB
C++
743 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2007-2009 Strasbourg University
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Author: Sebastien Vincent <vincent@clarinet.u-strasbg.fr>
|
|
*/
|
|
|
|
#include "ndisc-cache.h"
|
|
|
|
#include "icmpv6-l4-protocol.h"
|
|
#include "ipv6-interface.h"
|
|
#include "ipv6-l3-protocol.h"
|
|
|
|
#include "ns3/log.h"
|
|
#include "ns3/names.h"
|
|
#include "ns3/node.h"
|
|
#include "ns3/uinteger.h"
|
|
|
|
namespace ns3
|
|
{
|
|
|
|
NS_LOG_COMPONENT_DEFINE("NdiscCache");
|
|
|
|
NS_OBJECT_ENSURE_REGISTERED(NdiscCache);
|
|
|
|
TypeId
|
|
NdiscCache::GetTypeId()
|
|
{
|
|
static TypeId tid = TypeId("ns3::NdiscCache")
|
|
.SetParent<Object>()
|
|
.SetGroupName("Internet")
|
|
.AddAttribute("UnresolvedQueueSize",
|
|
"Size of the queue for packets pending an NA reply.",
|
|
UintegerValue(DEFAULT_UNRES_QLEN),
|
|
MakeUintegerAccessor(&NdiscCache::m_unresQlen),
|
|
MakeUintegerChecker<uint32_t>());
|
|
return tid;
|
|
}
|
|
|
|
NdiscCache::NdiscCache()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
NdiscCache::~NdiscCache()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
Flush();
|
|
}
|
|
|
|
void
|
|
NdiscCache::DoDispose()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
for (auto& iter : m_ndCache)
|
|
{
|
|
delete iter.second; /* delete the pointer NdiscCache::Entry */
|
|
}
|
|
m_ndCache.clear();
|
|
m_device = nullptr;
|
|
m_interface = nullptr;
|
|
m_icmpv6 = nullptr;
|
|
Object::DoDispose();
|
|
}
|
|
|
|
void
|
|
NdiscCache::SetDevice(Ptr<NetDevice> device,
|
|
Ptr<Ipv6Interface> interface,
|
|
Ptr<Icmpv6L4Protocol> icmpv6)
|
|
{
|
|
NS_LOG_FUNCTION(this << device << interface);
|
|
m_device = device;
|
|
m_interface = interface;
|
|
m_icmpv6 = icmpv6;
|
|
}
|
|
|
|
Ptr<Ipv6Interface>
|
|
NdiscCache::GetInterface() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_interface;
|
|
}
|
|
|
|
Ptr<NetDevice>
|
|
NdiscCache::GetDevice() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_device;
|
|
}
|
|
|
|
NdiscCache::Entry*
|
|
NdiscCache::Lookup(Ipv6Address dst)
|
|
{
|
|
NS_LOG_FUNCTION(this << dst);
|
|
|
|
if (m_ndCache.find(dst) != m_ndCache.end())
|
|
{
|
|
NdiscCache::Entry* entry = m_ndCache[dst];
|
|
NS_LOG_LOGIC("Found an entry: " << *entry);
|
|
|
|
return entry;
|
|
}
|
|
NS_LOG_LOGIC("Nothing found");
|
|
return nullptr;
|
|
}
|
|
|
|
std::list<NdiscCache::Entry*>
|
|
NdiscCache::LookupInverse(Address dst)
|
|
{
|
|
NS_LOG_FUNCTION(this << dst);
|
|
|
|
std::list<NdiscCache::Entry*> entryList;
|
|
for (auto i = m_ndCache.begin(); i != m_ndCache.end(); i++)
|
|
{
|
|
NdiscCache::Entry* entry = (*i).second;
|
|
if (entry->GetMacAddress() == dst)
|
|
{
|
|
NS_LOG_LOGIC("Found an entry:" << (*entry));
|
|
entryList.push_back(entry);
|
|
}
|
|
}
|
|
return entryList;
|
|
}
|
|
|
|
NdiscCache::Entry*
|
|
NdiscCache::Add(Ipv6Address to)
|
|
{
|
|
NS_LOG_FUNCTION(this << to);
|
|
NS_ASSERT(m_ndCache.find(to) == m_ndCache.end());
|
|
|
|
auto entry = new NdiscCache::Entry(this);
|
|
entry->SetIpv6Address(to);
|
|
m_ndCache[to] = entry;
|
|
return entry;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Remove(NdiscCache::Entry* entry)
|
|
{
|
|
NS_LOG_FUNCTION(this << entry);
|
|
|
|
for (auto i = m_ndCache.begin(); i != m_ndCache.end(); i++)
|
|
{
|
|
if ((*i).second == entry)
|
|
{
|
|
m_ndCache.erase(i);
|
|
entry->ClearWaitingPacket();
|
|
delete entry;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::Flush()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
|
|
for (auto i = m_ndCache.begin(); i != m_ndCache.end();)
|
|
{
|
|
if (!i->second->IsAutoGenerated())
|
|
{
|
|
i->second->ClearWaitingPacket();
|
|
delete i->second;
|
|
i = m_ndCache.erase(i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::SetUnresQlen(uint32_t unresQlen)
|
|
{
|
|
NS_LOG_FUNCTION(this << unresQlen);
|
|
m_unresQlen = unresQlen;
|
|
}
|
|
|
|
uint32_t
|
|
NdiscCache::GetUnresQlen()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_unresQlen;
|
|
}
|
|
|
|
void
|
|
NdiscCache::PrintNdiscCache(Ptr<OutputStreamWrapper> stream)
|
|
{
|
|
NS_LOG_FUNCTION(this << stream);
|
|
std::ostream* os = stream->GetStream();
|
|
|
|
for (auto i = m_ndCache.begin(); i != m_ndCache.end(); i++)
|
|
{
|
|
*os << i->first << " dev ";
|
|
std::string found = Names::FindName(m_device);
|
|
if (!Names::FindName(m_device).empty())
|
|
{
|
|
*os << found;
|
|
}
|
|
else
|
|
{
|
|
*os << static_cast<int>(m_device->GetIfIndex());
|
|
}
|
|
|
|
*os << " lladdr " << i->second->GetMacAddress();
|
|
|
|
if (i->second->IsReachable())
|
|
{
|
|
*os << " REACHABLE\n";
|
|
}
|
|
else if (i->second->IsDelay())
|
|
{
|
|
*os << " DELAY\n";
|
|
}
|
|
else if (i->second->IsIncomplete())
|
|
{
|
|
*os << " INCOMPLETE\n";
|
|
}
|
|
else if (i->second->IsProbe())
|
|
{
|
|
*os << " PROBE\n";
|
|
}
|
|
else if (i->second->IsStale())
|
|
{
|
|
*os << " STALE\n";
|
|
}
|
|
else if (i->second->IsPermanent())
|
|
{
|
|
*os << " PERMANENT\n";
|
|
}
|
|
else if (i->second->IsAutoGenerated())
|
|
{
|
|
*os << " STATIC_AUTOGENERATED\n";
|
|
}
|
|
else
|
|
{
|
|
NS_FATAL_ERROR("Test for possibly unreachable code-- please file a bug report, with a "
|
|
"test case, if this is ever hit");
|
|
}
|
|
}
|
|
}
|
|
|
|
NdiscCache::Entry::Entry(NdiscCache* nd)
|
|
: m_ndCache(nd),
|
|
m_waiting(),
|
|
m_router(false),
|
|
m_nudTimer(Timer::CANCEL_ON_DESTROY),
|
|
m_lastReachabilityConfirmation(),
|
|
m_nsRetransmit(0)
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::SetRouter(bool router)
|
|
{
|
|
NS_LOG_FUNCTION(this << router);
|
|
m_router = router;
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsRouter() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_router;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::AddWaitingPacket(Ipv6PayloadHeaderPair p)
|
|
{
|
|
NS_LOG_FUNCTION(this << p.second << p.first);
|
|
|
|
if (m_waiting.size() >= m_ndCache->GetUnresQlen())
|
|
{
|
|
/* we store only m_unresQlen packet => first packet in first packet remove */
|
|
/** @todo report packet as 'dropped' */
|
|
m_waiting.pop_front();
|
|
}
|
|
m_waiting.push_back(p);
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::ClearWaitingPacket()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
/** @todo report packets as 'dropped' */
|
|
m_waiting.clear();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::FunctionReachableTimeout()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
this->MarkStale();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::FunctionRetransmitTimeout()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
Ipv6Address addr;
|
|
|
|
/* determine source address */
|
|
if (m_ipv6Address.IsLinkLocal())
|
|
{
|
|
addr = m_ndCache->GetInterface()->GetLinkLocalAddress().GetAddress();
|
|
}
|
|
else if (!m_ipv6Address.IsAny())
|
|
{
|
|
addr = m_ndCache->GetInterface()->GetAddressMatchingDestination(m_ipv6Address).GetAddress();
|
|
|
|
if (addr.IsAny()) /* maybe address has expired */
|
|
{
|
|
/* delete the entry */
|
|
m_ndCache->Remove(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_nsRetransmit < m_ndCache->m_icmpv6->GetMaxMulticastSolicit())
|
|
{
|
|
m_nsRetransmit++;
|
|
|
|
m_ndCache->m_icmpv6->SendNS(addr,
|
|
Ipv6Address::MakeSolicitedAddress(m_ipv6Address),
|
|
m_ipv6Address,
|
|
m_ndCache->GetDevice()->GetAddress());
|
|
/* arm the timer again */
|
|
StartRetransmitTimer();
|
|
}
|
|
else
|
|
{
|
|
Ipv6PayloadHeaderPair malformedPacket = m_waiting.front();
|
|
if (!malformedPacket.first)
|
|
{
|
|
malformedPacket.first = Create<Packet>();
|
|
}
|
|
else
|
|
{
|
|
malformedPacket.first->AddHeader(malformedPacket.second);
|
|
}
|
|
|
|
m_ndCache->m_icmpv6->SendErrorDestinationUnreachable(malformedPacket.first,
|
|
addr,
|
|
Icmpv6Header::ICMPV6_ADDR_UNREACHABLE);
|
|
|
|
/* delete the entry */
|
|
m_ndCache->Remove(this);
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::FunctionDelayTimeout()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
Ipv6Address addr;
|
|
|
|
this->MarkProbe();
|
|
|
|
if (m_ipv6Address.IsLinkLocal())
|
|
{
|
|
addr = m_ndCache->GetInterface()->GetLinkLocalAddress().GetAddress();
|
|
}
|
|
else if (!m_ipv6Address.IsAny())
|
|
{
|
|
addr = m_ndCache->GetInterface()->GetAddressMatchingDestination(m_ipv6Address).GetAddress();
|
|
if (addr.IsAny()) /* maybe address has expired */
|
|
{
|
|
/* delete the entry */
|
|
m_ndCache->Remove(this);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* should not happen */
|
|
return;
|
|
}
|
|
|
|
Ipv6PayloadHeaderPair p = m_ndCache->m_icmpv6->ForgeNS(addr,
|
|
m_ipv6Address,
|
|
m_ipv6Address,
|
|
m_ndCache->GetDevice()->GetAddress());
|
|
p.first->AddHeader(p.second);
|
|
m_ndCache->GetDevice()->Send(p.first, this->GetMacAddress(), Ipv6L3Protocol::PROT_NUMBER);
|
|
|
|
m_nsRetransmit = 1;
|
|
StartProbeTimer();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::FunctionProbeTimeout()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
|
|
if (m_nsRetransmit < m_ndCache->m_icmpv6->GetMaxUnicastSolicit())
|
|
{
|
|
m_nsRetransmit++;
|
|
|
|
Ipv6Address addr;
|
|
|
|
if (m_ipv6Address.IsLinkLocal())
|
|
{
|
|
addr = m_ndCache->GetInterface()->GetLinkLocalAddress().GetAddress();
|
|
}
|
|
else if (!m_ipv6Address.IsAny())
|
|
{
|
|
addr = m_ndCache->GetInterface()
|
|
->GetAddressMatchingDestination(m_ipv6Address)
|
|
.GetAddress();
|
|
if (addr.IsAny()) /* maybe address has expired */
|
|
{
|
|
/* delete the entry */
|
|
m_ndCache->Remove(this);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* should not happen */
|
|
return;
|
|
}
|
|
|
|
/* icmpv6->SendNS (m_ndCache->GetInterface ()->GetLinkLocalAddress (), m_ipv6Address,
|
|
* m_ipv6Address, m_ndCache->GetDevice ()->GetAddress ()); */
|
|
Ipv6PayloadHeaderPair p =
|
|
m_ndCache->m_icmpv6->ForgeNS(addr,
|
|
m_ipv6Address,
|
|
m_ipv6Address,
|
|
m_ndCache->GetDevice()->GetAddress());
|
|
p.first->AddHeader(p.second);
|
|
m_ndCache->GetDevice()->Send(p.first, this->GetMacAddress(), Ipv6L3Protocol::PROT_NUMBER);
|
|
|
|
/* arm the timer again */
|
|
StartProbeTimer();
|
|
}
|
|
else
|
|
{
|
|
/* delete the entry */
|
|
m_ndCache->Remove(this);
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::SetIpv6Address(Ipv6Address ipv6Address)
|
|
{
|
|
NS_LOG_FUNCTION(this << ipv6Address);
|
|
m_ipv6Address = ipv6Address;
|
|
}
|
|
|
|
Ipv6Address
|
|
NdiscCache::Entry::GetIpv6Address() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_ipv6Address;
|
|
}
|
|
|
|
Time
|
|
NdiscCache::Entry::GetLastReachabilityConfirmation() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_lastReachabilityConfirmation;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::StartReachableTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
if (m_nudTimer.IsRunning())
|
|
{
|
|
m_nudTimer.Cancel();
|
|
}
|
|
|
|
m_lastReachabilityConfirmation = Simulator::Now();
|
|
m_nudTimer.SetFunction(&NdiscCache::Entry::FunctionReachableTimeout, this);
|
|
m_nudTimer.SetDelay(m_ndCache->m_icmpv6->GetReachableTime());
|
|
m_nudTimer.Schedule();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::UpdateReachableTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
|
|
if (m_state == REACHABLE)
|
|
{
|
|
m_lastReachabilityConfirmation = Simulator::Now();
|
|
if (m_nudTimer.IsRunning())
|
|
{
|
|
m_nudTimer.Cancel();
|
|
}
|
|
m_nudTimer.Schedule();
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::StartProbeTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
if (m_nudTimer.IsRunning())
|
|
{
|
|
m_nudTimer.Cancel();
|
|
}
|
|
|
|
m_nudTimer.SetFunction(&NdiscCache::Entry::FunctionProbeTimeout, this);
|
|
m_nudTimer.SetDelay(m_ndCache->m_icmpv6->GetRetransmissionTime());
|
|
m_nudTimer.Schedule();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::StartDelayTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
if (m_nudTimer.IsRunning())
|
|
{
|
|
m_nudTimer.Cancel();
|
|
}
|
|
|
|
m_nudTimer.SetFunction(&NdiscCache::Entry::FunctionDelayTimeout, this);
|
|
m_nudTimer.SetDelay(m_ndCache->m_icmpv6->GetDelayFirstProbe());
|
|
m_nudTimer.Schedule();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::StartRetransmitTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
if (m_nudTimer.IsRunning())
|
|
{
|
|
m_nudTimer.Cancel();
|
|
}
|
|
|
|
m_nudTimer.SetFunction(&NdiscCache::Entry::FunctionRetransmitTimeout, this);
|
|
m_nudTimer.SetDelay(m_ndCache->m_icmpv6->GetRetransmissionTime());
|
|
m_nudTimer.Schedule();
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::StopNudTimer()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
m_nudTimer.Cancel();
|
|
m_nsRetransmit = 0;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkIncomplete(Ipv6PayloadHeaderPair p)
|
|
{
|
|
NS_LOG_FUNCTION(this << p.second << p.first);
|
|
m_state = INCOMPLETE;
|
|
|
|
if (p.first)
|
|
{
|
|
m_waiting.push_back(p);
|
|
}
|
|
}
|
|
|
|
std::list<NdiscCache::Ipv6PayloadHeaderPair>
|
|
NdiscCache::Entry::MarkReachable(Address mac)
|
|
{
|
|
NS_LOG_FUNCTION(this << mac);
|
|
m_state = REACHABLE;
|
|
m_macAddress = mac;
|
|
return m_waiting;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkProbe()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
m_state = PROBE;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkStale()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
m_state = STALE;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkReachable()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
m_state = REACHABLE;
|
|
}
|
|
|
|
std::list<NdiscCache::Ipv6PayloadHeaderPair>
|
|
NdiscCache::Entry::MarkStale(Address mac)
|
|
{
|
|
NS_LOG_FUNCTION(this << mac);
|
|
m_state = STALE;
|
|
m_macAddress = mac;
|
|
return m_waiting;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkDelay()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
m_state = DELAY;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkPermanent()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
StopNudTimer();
|
|
m_state = PERMANENT;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::MarkAutoGenerated()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
StopNudTimer();
|
|
m_state = STATIC_AUTOGENERATED;
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsStale() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == STALE);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsReachable() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == REACHABLE);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsDelay() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == DELAY);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsIncomplete() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == INCOMPLETE);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsProbe() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == PROBE);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsPermanent() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == PERMANENT);
|
|
}
|
|
|
|
bool
|
|
NdiscCache::Entry::IsAutoGenerated() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return (m_state == STATIC_AUTOGENERATED);
|
|
}
|
|
|
|
Address
|
|
NdiscCache::Entry::GetMacAddress() const
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
return m_macAddress;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::SetMacAddress(Address mac)
|
|
{
|
|
NS_LOG_FUNCTION(this << mac << int(m_state));
|
|
m_macAddress = mac;
|
|
}
|
|
|
|
void
|
|
NdiscCache::Entry::Print(std::ostream& os) const
|
|
{
|
|
os << m_ipv6Address << " lladdr " << m_macAddress << " state ";
|
|
switch (m_state)
|
|
{
|
|
case INCOMPLETE:
|
|
os << "INCOMPLETE";
|
|
break;
|
|
case REACHABLE:
|
|
os << "REACHABLE";
|
|
break;
|
|
case STALE:
|
|
os << "STALE";
|
|
break;
|
|
case DELAY:
|
|
os << "DELAY";
|
|
break;
|
|
case PROBE:
|
|
os << "PROBE";
|
|
break;
|
|
case PERMANENT:
|
|
os << "PERMANENT";
|
|
break;
|
|
case STATIC_AUTOGENERATED:
|
|
os << "STATIC_AUTOGENERATED";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
NdiscCache::RemoveAutoGeneratedEntries()
|
|
{
|
|
NS_LOG_FUNCTION(this);
|
|
for (auto i = m_ndCache.begin(); i != m_ndCache.end();)
|
|
{
|
|
if (i->second->IsAutoGenerated())
|
|
{
|
|
i->second->ClearWaitingPacket();
|
|
delete i->second;
|
|
i = m_ndCache.erase(i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::ostream&
|
|
operator<<(std::ostream& os, const NdiscCache::Entry& entry)
|
|
{
|
|
entry.Print(os);
|
|
return os;
|
|
}
|
|
|
|
} /* namespace ns3 */
|