internet-apps: Add new Ping (co-authors Chandrakant Jena and Tom Henderson)
This commit is contained in:
committed by
Tommaso Pecorella
parent
83dcacadc5
commit
da107e04ee
@@ -21,12 +21,14 @@ Changes from ns-3.37 to ns-3.38
|
||||
* (network) Add class `TimestampTag` for associating a timestamp with a packet.
|
||||
* (wifi) Added a new attribute **NMaxInflights** to QosTxop to set the maximum number of links on which an MPDU can be simultaneously in-flight.
|
||||
* (core) Added several macros in **warnings.h** to silence compiler warnings in specific sections of code. Their use is discouraged, uness really necessary.
|
||||
* (internet-apps) Add class `Ping` for a ping model that works for both IPv4 and IPv6.
|
||||
|
||||
### Changes to existing API
|
||||
|
||||
* (network) **Ipv4Address** and **Ipv6Address** now do not raise an exception if built from an invalid string. Instead the address is marked as not initialized.
|
||||
* (internet) TCP Westwood model has been removed due to a bug in BW estimation documented in <https://gitlab.com/nsnam/ns-3-dev/-/issues/579>. The TCP Westwood+ model is now named **TcpWestwoodPlus** and can be instantiated like all the other TCP flavors.
|
||||
* (internet) `TcpCubic` attribute `HyStartDetect` changed from `int` to `enum HybridSSDetectionMode`.
|
||||
* (internet-apps) Classes `v4Ping` and `Ping6` will be deprecated and removed in the future, replaced by the new `Ping` class.
|
||||
|
||||
### Changes to build system
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ Release 3-dev
|
||||
- (internet) !1186 - `TcpWestwood` model has been removed, and the class has been renamed `TcpWestwoodPlus`.
|
||||
- (internet) !1229 - You can now ping broadcast addresses.
|
||||
- (core) !1236 - Added some macros to silence compiler warnings. The new macros are in **warnings.h**, and their use is not suggested unless for very specific cases.
|
||||
- (internet-apps) - A new Ping model that works for both IPv4 and IPv6 has been added, to replace the address family specific v4Ping and Ping6.
|
||||
|
||||
### Bugs fixed
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ build_lib(
|
||||
model/dhcp-client.cc
|
||||
model/dhcp-header.cc
|
||||
model/dhcp-server.cc
|
||||
model/ping.cc
|
||||
model/ping6.cc
|
||||
model/radvd-interface.cc
|
||||
model/radvd-prefix.cc
|
||||
@@ -24,6 +25,7 @@ build_lib(
|
||||
model/dhcp-client.h
|
||||
model/dhcp-header.h
|
||||
model/dhcp-server.h
|
||||
model/ping.h
|
||||
model/ping6.h
|
||||
model/radvd-interface.h
|
||||
model/radvd-prefix.h
|
||||
|
||||
697
src/internet-apps/model/ping.cc
Normal file
697
src/internet-apps/model/ping.cc
Normal file
@@ -0,0 +1,697 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Chandrakant Jena
|
||||
* Copyright (c) 2007-2009 Strasbourg University (original Ping6 code)
|
||||
* Copyright (c) 2008 INRIA (original Ping code)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation;
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Author: Chandrakant Jena <chandrakant.barcelona@gmail.com>
|
||||
* Tommaso Pecorella <tommaso.pecorella@unifi.it>
|
||||
*
|
||||
* Derived from original v4Ping application (author: Mathieu Lacage)
|
||||
* Derived from original ping6 application (author: Sebastien Vincent)
|
||||
*/
|
||||
|
||||
#include "ping.h"
|
||||
|
||||
#include "ns3/abort.h"
|
||||
#include "ns3/assert.h"
|
||||
#include "ns3/boolean.h"
|
||||
#include "ns3/enum.h"
|
||||
#include "ns3/icmpv4.h"
|
||||
#include "ns3/icmpv6-header.h"
|
||||
#include "ns3/inet-socket-address.h"
|
||||
#include "ns3/ipv4-address.h"
|
||||
#include "ns3/ipv6-extension-header.h"
|
||||
#include "ns3/ipv6-header.h"
|
||||
#include "ns3/ipv6-l3-protocol.h"
|
||||
#include "ns3/ipv6-packet-info-tag.h"
|
||||
#include "ns3/log.h"
|
||||
#include "ns3/packet.h"
|
||||
#include "ns3/socket.h"
|
||||
#include "ns3/trace-source-accessor.h"
|
||||
#include "ns3/uinteger.h"
|
||||
|
||||
namespace ns3
|
||||
{
|
||||
|
||||
NS_LOG_COMPONENT_DEFINE("Ping");
|
||||
|
||||
NS_OBJECT_ENSURE_REGISTERED(Ping);
|
||||
|
||||
/// This value is used to quickly identify ECHO packets generated by this app.
|
||||
constexpr uint16_t PING_ID{0xbeef};
|
||||
|
||||
TypeId
|
||||
Ping::GetTypeId()
|
||||
{
|
||||
static TypeId tid =
|
||||
TypeId("ns3::Ping")
|
||||
.SetParent<Application>()
|
||||
.SetGroupName("Internet-Apps")
|
||||
.AddConstructor<Ping>()
|
||||
.AddAttribute("Destination",
|
||||
"The unicast IPv4 or IPv6 address of the machine we want to ping",
|
||||
AddressValue(),
|
||||
MakeAddressAccessor(&Ping::m_destination),
|
||||
MakeAddressChecker())
|
||||
.AddAttribute("VerboseMode",
|
||||
"Configure verbose, quiet, or silent output",
|
||||
EnumValue(VerboseMode::VERBOSE),
|
||||
MakeEnumAccessor(&Ping::m_verbose),
|
||||
MakeEnumChecker(VerboseMode::VERBOSE,
|
||||
"Verbose",
|
||||
VerboseMode::QUIET,
|
||||
"Quiet",
|
||||
VerboseMode::SILENT,
|
||||
"Silent"))
|
||||
.AddAttribute("Interval",
|
||||
"Time interval between sending each packet",
|
||||
TimeValue(Seconds(1)),
|
||||
MakeTimeAccessor(&Ping::m_interval),
|
||||
MakeTimeChecker())
|
||||
.AddAttribute(
|
||||
"Size",
|
||||
"The number of data bytes to be sent, before ICMP and IP headers are added",
|
||||
UintegerValue(56),
|
||||
MakeUintegerAccessor(&Ping::m_size),
|
||||
MakeUintegerChecker<uint32_t>(16))
|
||||
.AddAttribute(
|
||||
"Count",
|
||||
"The maximum number of packets the application will send (zero means no limits)",
|
||||
UintegerValue(0),
|
||||
MakeUintegerAccessor(&Ping::m_count),
|
||||
MakeUintegerChecker<uint32_t>())
|
||||
.AddAttribute("InterfaceAddress",
|
||||
"Local address of the sender",
|
||||
AddressValue(),
|
||||
MakeAddressAccessor(&Ping::m_interfaceAddress),
|
||||
MakeAddressChecker())
|
||||
.AddAttribute("Timeout",
|
||||
"Time to wait for a response if no RTT samples are available",
|
||||
TimeValue(Seconds(1)),
|
||||
MakeTimeAccessor(&Ping::m_timeout),
|
||||
MakeTimeChecker())
|
||||
.AddTraceSource("Tx",
|
||||
"The sequence number and ICMP echo response packet.",
|
||||
MakeTraceSourceAccessor(&Ping::m_txTrace),
|
||||
"ns3::Ping::TxTrace")
|
||||
.AddTraceSource("Rtt",
|
||||
"The sequence number and RTT sample.",
|
||||
MakeTraceSourceAccessor(&Ping::m_rttTrace),
|
||||
"ns3::Ping::RttTrace")
|
||||
.AddTraceSource("Drop",
|
||||
"Drop events due to destination unreachable or other errors.",
|
||||
MakeTraceSourceAccessor(&Ping::m_dropTrace),
|
||||
"ns3::Ping::DropTrace")
|
||||
.AddTraceSource("Report",
|
||||
"Summary report at close of application.",
|
||||
MakeTraceSourceAccessor(&Ping::m_reportTrace),
|
||||
"ns3::Ping::ReportTrace");
|
||||
return tid;
|
||||
}
|
||||
|
||||
Ping::Ping()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
}
|
||||
|
||||
Ping::~Ping()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
}
|
||||
|
||||
void
|
||||
Ping::DoDispose()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
StopApplication();
|
||||
m_socket = nullptr;
|
||||
Application::DoDispose();
|
||||
}
|
||||
|
||||
uint64_t
|
||||
Ping::GetApplicationSignature() const
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
|
||||
uint64_t appSignature = GetNode()->GetId();
|
||||
appSignature <<= 32;
|
||||
|
||||
Ptr<Node> node = GetNode();
|
||||
for (uint32_t i = 0; i < node->GetNApplications(); ++i)
|
||||
{
|
||||
if (node->GetApplication(i) == this)
|
||||
{
|
||||
appSignature += i;
|
||||
return appSignature;
|
||||
}
|
||||
}
|
||||
NS_ASSERT_MSG(false, "forgot to add application to node");
|
||||
return 0; // quiet compiler
|
||||
}
|
||||
|
||||
void
|
||||
Ping::Receive(Ptr<Socket> socket)
|
||||
{
|
||||
NS_LOG_FUNCTION(this << socket);
|
||||
|
||||
while (m_socket->GetRxAvailable() > 0)
|
||||
{
|
||||
Address from;
|
||||
Ptr<Packet> packet = m_socket->RecvFrom(from);
|
||||
uint32_t recvSize = packet->GetSize();
|
||||
NS_LOG_DEBUG("recv " << recvSize << " bytes " << *packet);
|
||||
|
||||
if (InetSocketAddress::IsMatchingType(from))
|
||||
{
|
||||
InetSocketAddress realFrom = InetSocketAddress::ConvertFrom(from);
|
||||
Ipv4Header ipv4Hdr;
|
||||
packet->RemoveHeader(ipv4Hdr);
|
||||
recvSize -= ipv4Hdr.GetSerializedSize();
|
||||
Icmpv4Header icmp;
|
||||
packet->RemoveHeader(icmp);
|
||||
|
||||
switch (icmp.GetType())
|
||||
{
|
||||
case Icmpv4Header::ICMPV4_ECHO_REPLY: {
|
||||
Icmpv4Echo echo;
|
||||
packet->RemoveHeader(echo);
|
||||
|
||||
if (echo.GetIdentifier() != PING_ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NS_LOG_INFO("Received Echo Reply size = "
|
||||
<< std::dec << recvSize << " bytes from " << realFrom.GetIpv4()
|
||||
<< " id = " << echo.GetIdentifier()
|
||||
<< " seq = " << echo.GetSequenceNumber()
|
||||
<< " TTL = " << static_cast<uint16_t>(ipv4Hdr.GetTtl()));
|
||||
|
||||
uint32_t dataSize = echo.GetDataSize();
|
||||
if (dataSize < 8)
|
||||
{
|
||||
NS_LOG_INFO("Packet too short, discarding");
|
||||
return;
|
||||
}
|
||||
uint8_t* buf = new uint8_t[dataSize];
|
||||
echo.GetData(buf);
|
||||
uint64_t appSignature = Read64(buf);
|
||||
delete[] buf;
|
||||
|
||||
if (appSignature == m_appSignature)
|
||||
{
|
||||
Time sendTime = m_sent.at(echo.GetSequenceNumber()).txTime;
|
||||
NS_ASSERT(Simulator::Now() >= sendTime);
|
||||
Time delta = Simulator::Now() - sendTime;
|
||||
|
||||
bool dupReply = false;
|
||||
if (m_sent.at(echo.GetSequenceNumber()).acked)
|
||||
{
|
||||
m_duplicate++;
|
||||
dupReply = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recv++;
|
||||
m_sent.at(echo.GetSequenceNumber()).acked = true;
|
||||
}
|
||||
|
||||
m_avgRtt.Update(delta.GetMilliSeconds());
|
||||
m_rttTrace(echo.GetSequenceNumber(), delta);
|
||||
|
||||
if (m_verbose == VerboseMode::VERBOSE)
|
||||
{
|
||||
std::cout << recvSize << " bytes from " << realFrom.GetIpv4() << ":"
|
||||
<< " icmp_seq=" << echo.GetSequenceNumber()
|
||||
<< " ttl=" << static_cast<uint16_t>(ipv4Hdr.GetTtl())
|
||||
<< " time=" << delta.GetMicroSeconds() / 1000.0 << " ms";
|
||||
if (dupReply && !m_multipleDestinations)
|
||||
{
|
||||
std::cout << " (DUP!)";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Icmpv4Header::ICMPV4_DEST_UNREACH: {
|
||||
Icmpv4DestinationUnreachable destUnreach;
|
||||
packet->RemoveHeader(destUnreach);
|
||||
|
||||
NS_LOG_INFO("Received Destination Unreachable from " << realFrom.GetIpv4());
|
||||
break;
|
||||
}
|
||||
case Icmpv4Header::ICMPV4_TIME_EXCEEDED: {
|
||||
Icmpv4TimeExceeded timeExceeded;
|
||||
packet->RemoveHeader(timeExceeded);
|
||||
|
||||
NS_LOG_INFO("Received Time Exceeded from " << realFrom.GetIpv4());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Inet6SocketAddress::IsMatchingType(from))
|
||||
{
|
||||
Inet6SocketAddress realFrom = Inet6SocketAddress::ConvertFrom(from);
|
||||
Ipv6Header ipv6Hdr;
|
||||
|
||||
// We need the IP interface index.
|
||||
Ipv6PacketInfoTag infoTag;
|
||||
packet->RemovePacketTag(infoTag);
|
||||
Ptr<Ipv6L3Protocol> ipv6 = m_node->GetObject<Ipv6L3Protocol>();
|
||||
Ipv6Address myAddr = infoTag.GetAddress();
|
||||
int32_t ipIfIndex = ipv6->GetInterfaceForAddress(myAddr);
|
||||
|
||||
packet->RemoveHeader(ipv6Hdr);
|
||||
recvSize -= ipv6Hdr.GetSerializedSize();
|
||||
uint8_t type;
|
||||
packet->CopyData(&type, sizeof(type));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Icmpv6Header::ICMPV6_ECHO_REPLY: {
|
||||
Icmpv6Echo echo(0);
|
||||
packet->RemoveHeader(echo);
|
||||
|
||||
if (echo.GetId() != PING_ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NS_LOG_INFO("Received Echo Reply size = "
|
||||
<< std::dec << recvSize << " bytes from " << realFrom.GetIpv6()
|
||||
<< " id = " << echo.GetId() << " seq = " << echo.GetSeq()
|
||||
<< " Hop Count = " << static_cast<uint16_t>(ipv6Hdr.GetHopLimit()));
|
||||
|
||||
uint32_t dataSize = packet->GetSize();
|
||||
if (dataSize < 8)
|
||||
{
|
||||
NS_LOG_INFO("Packet too short, discarding");
|
||||
return;
|
||||
}
|
||||
uint8_t* buf = new uint8_t[dataSize];
|
||||
packet->CopyData(buf, dataSize);
|
||||
uint64_t appSignature = Read64(buf);
|
||||
delete[] buf;
|
||||
|
||||
if (appSignature == m_appSignature)
|
||||
{
|
||||
Time sendTime = m_sent.at(echo.GetSeq()).txTime;
|
||||
NS_ASSERT(Simulator::Now() >= sendTime);
|
||||
Time delta = Simulator::Now() - sendTime;
|
||||
|
||||
bool dupReply = false;
|
||||
if (m_sent.at(echo.GetSeq()).acked)
|
||||
{
|
||||
m_duplicate++;
|
||||
dupReply = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recv++;
|
||||
m_sent.at(echo.GetSeq()).acked = true;
|
||||
}
|
||||
|
||||
m_avgRtt.Update(delta.GetMilliSeconds());
|
||||
m_rttTrace(echo.GetSeq(), delta);
|
||||
|
||||
if (m_verbose == VerboseMode::VERBOSE)
|
||||
{
|
||||
std::cout << recvSize << " bytes from (" << realFrom.GetIpv6() << "):"
|
||||
<< " icmp_seq=" << echo.GetSeq()
|
||||
<< " ttl=" << static_cast<uint16_t>(ipv6Hdr.GetHopLimit())
|
||||
<< " time=" << delta.GetMicroSeconds() / 1000.0 << " ms";
|
||||
if (dupReply)
|
||||
{
|
||||
std::cout << " (DUP!)";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
ipv6->ReachabilityHint(ipIfIndex, realFrom.GetIpv6());
|
||||
break;
|
||||
}
|
||||
case Icmpv6Header::ICMPV6_ERROR_DESTINATION_UNREACHABLE: {
|
||||
Icmpv6DestinationUnreachable destUnreach;
|
||||
packet->RemoveHeader(destUnreach);
|
||||
|
||||
NS_LOG_INFO("Received Destination Unreachable from " << realFrom.GetIpv6());
|
||||
break;
|
||||
}
|
||||
case Icmpv6Header::ICMPV6_ERROR_TIME_EXCEEDED: {
|
||||
Icmpv6TimeExceeded timeExceeded;
|
||||
packet->RemoveHeader(timeExceeded);
|
||||
|
||||
NS_LOG_INFO("Received Time Exceeded from " << realFrom.GetIpv6());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_multipleDestinations && m_count > 0 && m_recv == m_count)
|
||||
{
|
||||
Simulator::ScheduleNow(&Ping::StopApplication, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes data to buffer in little-endian format; least significant byte
|
||||
// of data is at lowest buffer address
|
||||
void
|
||||
Ping::Write64(uint8_t* buffer, const uint64_t data)
|
||||
{
|
||||
NS_LOG_FUNCTION(this << (void*)buffer << data);
|
||||
buffer[0] = (data >> 0) & 0xff;
|
||||
buffer[1] = (data >> 8) & 0xff;
|
||||
buffer[2] = (data >> 16) & 0xff;
|
||||
buffer[3] = (data >> 24) & 0xff;
|
||||
buffer[4] = (data >> 32) & 0xff;
|
||||
buffer[5] = (data >> 40) & 0xff;
|
||||
buffer[6] = (data >> 48) & 0xff;
|
||||
buffer[7] = (data >> 56) & 0xff;
|
||||
}
|
||||
|
||||
// Read data from a little-endian formatted buffer to data
|
||||
uint64_t
|
||||
Ping::Read64(const uint8_t* buffer)
|
||||
{
|
||||
NS_LOG_FUNCTION(this << (void*)buffer);
|
||||
uint64_t data = buffer[7];
|
||||
data <<= 8;
|
||||
data |= buffer[6];
|
||||
data <<= 8;
|
||||
data |= buffer[5];
|
||||
data <<= 8;
|
||||
data |= buffer[4];
|
||||
data <<= 8;
|
||||
data |= buffer[3];
|
||||
data <<= 8;
|
||||
data |= buffer[2];
|
||||
data <<= 8;
|
||||
data |= buffer[1];
|
||||
data <<= 8;
|
||||
data |= buffer[0];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void
|
||||
Ping::Send()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
|
||||
NS_LOG_INFO("m_seq=" << m_seq);
|
||||
|
||||
// Prepare the payload.
|
||||
// We must write quantities out in some form of network order. Since there
|
||||
// isn't an htonl to work with we just follow the convention in pcap traces
|
||||
// (where any difference would show up anyway) and borrow that code. Don't
|
||||
// be too surprised when you see that this is a little endian convention.
|
||||
//
|
||||
uint8_t* data = new uint8_t[m_size];
|
||||
memset(data, 0, m_size);
|
||||
NS_ASSERT_MSG(m_size >= 16, "ECHO Payload size must be at least 16 bytes");
|
||||
|
||||
Write64(data, m_appSignature);
|
||||
Ptr<Packet> dataPacket = Create<Packet>(data, m_size);
|
||||
|
||||
Ptr<Packet> p = Create<Packet>();
|
||||
int returnValue = 0;
|
||||
|
||||
if (!m_useIpv6)
|
||||
{
|
||||
// Using IPv4
|
||||
Icmpv4Echo echo;
|
||||
echo.SetSequenceNumber(m_seq);
|
||||
echo.SetIdentifier(PING_ID);
|
||||
|
||||
// In the Icmpv4Echo the payload is part of the header.
|
||||
echo.SetData(dataPacket);
|
||||
|
||||
p->AddHeader(echo);
|
||||
Icmpv4Header header;
|
||||
header.SetType(Icmpv4Header::ICMPV4_ECHO);
|
||||
header.SetCode(0);
|
||||
if (Node::ChecksumEnabled())
|
||||
{
|
||||
header.EnableChecksum();
|
||||
}
|
||||
p->AddHeader(header);
|
||||
returnValue =
|
||||
m_socket->SendTo(p, 0, InetSocketAddress(Ipv4Address::ConvertFrom(m_destination), 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Using IPv6
|
||||
Icmpv6Echo echo(true);
|
||||
|
||||
echo.SetSeq(m_seq);
|
||||
echo.SetId(PING_ID);
|
||||
|
||||
// In the Icmpv6Echo the payload is just the content of the packet.
|
||||
p = dataPacket->Copy();
|
||||
|
||||
p->AddHeader(echo);
|
||||
|
||||
/* use Loose Routing (routing type 0) */
|
||||
if (!m_routers.empty())
|
||||
{
|
||||
Ipv6ExtensionLooseRoutingHeader routingHeader;
|
||||
routingHeader.SetNextHeader(Ipv6Header::IPV6_ICMPV6);
|
||||
routingHeader.SetTypeRouting(0);
|
||||
routingHeader.SetSegmentsLeft(m_routers.size());
|
||||
routingHeader.SetRoutersAddress(m_routers);
|
||||
p->AddHeader(routingHeader);
|
||||
m_socket->SetAttribute("Protocol", UintegerValue(Ipv6Header::IPV6_EXT_ROUTING));
|
||||
}
|
||||
|
||||
returnValue =
|
||||
m_socket->SendTo(p, 0, Inet6SocketAddress(Ipv6Address::ConvertFrom(m_destination), 0));
|
||||
|
||||
// Loose routing could have changed (temporarily) the protocol. Set it again to receive the
|
||||
// replies.
|
||||
m_socket->SetAttribute("Protocol", UintegerValue(Ipv6Header::IPV6_ICMPV6));
|
||||
}
|
||||
if (returnValue > 0)
|
||||
{
|
||||
m_sent.emplace_back(Simulator::Now(), false);
|
||||
m_txTrace(m_seq, p);
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_LOG_INFO("Send failure; socket return value: " << returnValue);
|
||||
}
|
||||
m_seq++;
|
||||
delete[] data;
|
||||
|
||||
if (m_count == 0 || m_seq < m_count)
|
||||
{
|
||||
m_next = Simulator::Schedule(m_interval, &Ping::Send, this);
|
||||
}
|
||||
|
||||
// We have sent all the requests. Schedule a shutdown after the linger time
|
||||
if (m_count > 0 && m_seq == m_count)
|
||||
{
|
||||
Time lingerTime = m_avgRtt.Count() > 0 ? MilliSeconds(2 * m_avgRtt.Max()) : m_timeout;
|
||||
Simulator::Schedule(lingerTime, &Ping::StopApplication, this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Ping::StartApplication()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
|
||||
if (m_destination.IsInvalid())
|
||||
{
|
||||
NS_ABORT_MSG("Destination Address value must be set when starting application");
|
||||
}
|
||||
|
||||
m_appSignature = GetApplicationSignature();
|
||||
|
||||
m_started = Simulator::Now();
|
||||
m_reportPrinted = false;
|
||||
if (m_verbose == VerboseMode::VERBOSE || m_verbose == VerboseMode::QUIET)
|
||||
{
|
||||
if (Ipv4Address::IsMatchingType(m_destination))
|
||||
{
|
||||
InetSocketAddress realFrom = Ipv4Address::ConvertFrom(m_destination);
|
||||
std::cout << "PING " << realFrom.GetIpv4() << " - " << m_size << " bytes of data; "
|
||||
<< m_size + 28 << " bytes including ICMP and IPv4 headers.\n";
|
||||
}
|
||||
else if (Ipv6Address::IsMatchingType(m_destination))
|
||||
{
|
||||
Inet6SocketAddress realFrom = Ipv6Address::ConvertFrom(m_destination);
|
||||
std::cout << "PING " << realFrom.GetIpv6() << " - " << m_size << " bytes of data; "
|
||||
<< m_size + 48 << " bytes including ICMP and IPv6 headers.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_ABORT_MSG("Invalid Address");
|
||||
}
|
||||
}
|
||||
|
||||
if (Ipv4Address::IsMatchingType(m_destination))
|
||||
{
|
||||
m_socket =
|
||||
Socket::CreateSocket(GetNode(), TypeId::LookupByName("ns3::Ipv4RawSocketFactory"));
|
||||
NS_ASSERT_MSG(m_socket, "Ping::StartApplication: can not create socket.");
|
||||
m_socket->SetAttribute("Protocol", UintegerValue(1)); // icmp
|
||||
m_socket->SetRecvCallback(MakeCallback(&Ping::Receive, this));
|
||||
m_useIpv6 = false;
|
||||
|
||||
Ipv4Address dst = Ipv4Address::ConvertFrom(m_destination);
|
||||
m_multipleDestinations = dst.IsMulticast() || dst.IsBroadcast();
|
||||
}
|
||||
else if (Ipv6Address::IsMatchingType(m_destination))
|
||||
{
|
||||
m_socket =
|
||||
Socket::CreateSocket(GetNode(), TypeId::LookupByName("ns3::Ipv6RawSocketFactory"));
|
||||
NS_ASSERT_MSG(m_socket, "Ping::StartApplication: can not create socket.");
|
||||
m_socket->SetAttribute("Protocol", UintegerValue(Ipv6Header::IPV6_ICMPV6));
|
||||
m_socket->SetRecvCallback(MakeCallback(&Ping::Receive, this));
|
||||
m_socket->SetRecvPktInfo(true);
|
||||
m_useIpv6 = true;
|
||||
|
||||
Ipv6Address dst = Ipv6Address::ConvertFrom(m_destination);
|
||||
m_multipleDestinations = dst.IsMulticast();
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_ABORT_MSG("Destination Address value must be of type Ipv4 or Ipv6");
|
||||
}
|
||||
|
||||
if (!m_interfaceAddress.IsInvalid())
|
||||
{
|
||||
if (Ipv4Address::IsMatchingType(m_interfaceAddress))
|
||||
{
|
||||
InetSocketAddress senderInet =
|
||||
InetSocketAddress(Ipv4Address::ConvertFrom(m_interfaceAddress));
|
||||
int status = m_socket->Bind(senderInet);
|
||||
NS_ASSERT_MSG(status == 0, "Failed to bind IPv4 socket");
|
||||
}
|
||||
else if (Ipv6Address::IsMatchingType(m_interfaceAddress))
|
||||
{
|
||||
Inet6SocketAddress senderInet =
|
||||
Inet6SocketAddress(Ipv6Address::ConvertFrom(m_interfaceAddress));
|
||||
int status = m_socket->Bind(senderInet);
|
||||
NS_ASSERT_MSG(status == 0, "Failed to bind IPv6 socket");
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_ABORT_MSG("Sender Address value must be of type Ipv4 or Ipv6");
|
||||
}
|
||||
}
|
||||
|
||||
// Guess how large should be the data storage and pre-book it.
|
||||
if (m_count == 0)
|
||||
{
|
||||
Time delta = m_stopTime - Now();
|
||||
int64_t guessedTx = Div(delta, m_interval) + 1;
|
||||
m_sent.reserve(guessedTx);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sent.reserve(m_count);
|
||||
}
|
||||
|
||||
Send();
|
||||
}
|
||||
|
||||
void
|
||||
Ping::StopApplication()
|
||||
{
|
||||
NS_LOG_FUNCTION(this);
|
||||
if (m_stopEvent.IsRunning())
|
||||
{
|
||||
m_stopEvent.Cancel();
|
||||
}
|
||||
if (m_next.IsRunning())
|
||||
{
|
||||
m_next.Cancel();
|
||||
}
|
||||
if (m_socket)
|
||||
{
|
||||
m_socket->Close();
|
||||
}
|
||||
PrintReport();
|
||||
}
|
||||
|
||||
void
|
||||
Ping::PrintReport()
|
||||
{
|
||||
if (m_reportPrinted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_reportPrinted = true;
|
||||
|
||||
if (m_verbose == VerboseMode::VERBOSE || m_verbose == VerboseMode::QUIET)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os.precision(4);
|
||||
if (Ipv4Address::IsMatchingType(m_destination))
|
||||
{
|
||||
InetSocketAddress realFrom = Ipv4Address::ConvertFrom(m_destination);
|
||||
os << "\n--- " << realFrom.GetIpv4() << " ping statistics ---\n";
|
||||
}
|
||||
else if (Ipv6Address::IsMatchingType(m_destination))
|
||||
{
|
||||
Inet6SocketAddress realFrom = Ipv6Address::ConvertFrom(m_destination);
|
||||
os << "\n--- " << realFrom.GetIpv6() << " ping statistics ---\n";
|
||||
}
|
||||
os << m_seq << " packets transmitted, " << m_recv << " received, ";
|
||||
if (m_duplicate)
|
||||
{
|
||||
os << m_duplicate << " duplicates, ";
|
||||
}
|
||||
|
||||
// note: integer math to match Linux implementation and avoid turning a 99.9% into a 100%.
|
||||
os << ((m_seq - m_recv) * 100 / m_seq) << "% packet loss, "
|
||||
<< "time " << (Simulator::Now() - m_started).GetMilliSeconds() << "ms\n";
|
||||
|
||||
if (m_avgRtt.Count() > 0)
|
||||
{
|
||||
os << "rtt min/avg/max/mdev = " << m_avgRtt.Min() << "/" << m_avgRtt.Avg() << "/"
|
||||
<< m_avgRtt.Max() << "/" << m_avgRtt.Stddev() << " ms\n";
|
||||
}
|
||||
std::cout << os.str();
|
||||
}
|
||||
PingReport report;
|
||||
report.m_transmitted = m_seq;
|
||||
report.m_received = m_recv;
|
||||
// note: integer math to match Linux implementation and avoid turning a 99.9% into a 100%.
|
||||
report.m_loss = (m_seq - m_recv) * 100 / m_seq;
|
||||
report.m_duration = (Simulator::Now() - m_started);
|
||||
report.m_rttMin = m_avgRtt.Min();
|
||||
report.m_rttAvg = m_avgRtt.Avg();
|
||||
report.m_rttMax = m_avgRtt.Max();
|
||||
report.m_rttMdev = m_avgRtt.Stddev();
|
||||
m_reportTrace(report);
|
||||
}
|
||||
|
||||
void
|
||||
Ping::SetRouters(const std::vector<Ipv6Address>& routers)
|
||||
{
|
||||
m_routers = routers;
|
||||
}
|
||||
|
||||
} // namespace ns3
|
||||
283
src/internet-apps/model/ping.h
Normal file
283
src/internet-apps/model/ping.h
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Chandrakant Jena
|
||||
* Copyright (c) 2007-2009 Strasbourg University (original Ping6 code)
|
||||
* Copyright (c) 2008 INRIA (original v4Ping code)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation;
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* Author: Chandrakant Jena <chandrakant.barcelona@gmail.com>
|
||||
*
|
||||
* Derived from original v4Ping application (author: Mathieu Lacage)
|
||||
* Derived from original ping6 application (author: Sebastien Vincent)
|
||||
*/
|
||||
|
||||
#ifndef PING_H
|
||||
#define PING_H
|
||||
|
||||
#include "ns3/application.h"
|
||||
#include "ns3/average.h"
|
||||
#include "ns3/traced-callback.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ns3
|
||||
{
|
||||
|
||||
class Socket;
|
||||
|
||||
/**
|
||||
* \ingroup internet-apps
|
||||
* \defgroup ping Ping
|
||||
*/
|
||||
|
||||
/**
|
||||
* \ingroup ping
|
||||
*
|
||||
* This application behaves similarly to the Unix ping application, although
|
||||
* with fewer options supported. The application can be used to send
|
||||
* ICMP echo requests to unicast IPv4 and IPv6 addresses.
|
||||
* The application can produce a verbose output similar to the real
|
||||
* application, and can also export statistics via a trace source.
|
||||
* The ping packet count, packet size, and interval between pings can
|
||||
* be controlled via attributes of this class.
|
||||
*/
|
||||
class Ping : public Application
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* \brief Get the type ID.
|
||||
* \return the object TypeId
|
||||
*/
|
||||
static TypeId GetTypeId();
|
||||
|
||||
/**
|
||||
* \enum VerboseMode
|
||||
* \brief Encode three possible levels of verbose output
|
||||
*/
|
||||
enum VerboseMode
|
||||
{
|
||||
VERBOSE = 0, //!< Verbose output (similar to real ping output)
|
||||
QUIET = 1, //!< Quiet output (similar to real 'ping -q' output)
|
||||
SILENT = 2, //!< Silent output (no terminal output at all)
|
||||
};
|
||||
|
||||
/**
|
||||
* \enum DropReason
|
||||
* \brief Reason why a ping was dropped
|
||||
*/
|
||||
enum DropReason
|
||||
{
|
||||
DROP_TIMEOUT = 0, //!< Response timed out
|
||||
DROP_HOST_UNREACHABLE, //!< Received ICMP Destination Host Unreachable
|
||||
DROP_NET_UNREACHABLE, //!< Received ICMP Destination Network Unreachable
|
||||
};
|
||||
|
||||
/**
|
||||
* A ping report provides all of the data that is typically output to the
|
||||
* terminal when the application stops, including number sent and received
|
||||
* and the RTT statistics.
|
||||
*/
|
||||
struct PingReport
|
||||
{
|
||||
uint32_t m_transmitted{0}; //!< Number of echo requests sent
|
||||
uint32_t m_received{0}; //!< Number of echo replies received
|
||||
uint16_t m_loss{0}; //!< Percentage of lost packets (decimal value 0-100)
|
||||
Time m_duration{0}; //!< Duration of the application
|
||||
double m_rttMin{0}; //!< rtt min value
|
||||
double m_rttAvg{0}; //!< rtt avg value
|
||||
double m_rttMax{0}; //!< rtt max value
|
||||
double m_rttMdev{0}; //!< rtt mdev value
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
Ping();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~Ping() override;
|
||||
|
||||
/**
|
||||
* \brief Set routers for IPv6 routing type 0 (loose routing).
|
||||
* \param routers routers addresses
|
||||
*/
|
||||
void SetRouters(const std::vector<Ipv6Address>& routers);
|
||||
|
||||
/**
|
||||
* TracedCallback signature for Rtt trace
|
||||
*
|
||||
* \param [in] seq The ICMP sequence number
|
||||
* \param [in] p The ICMP echo request packet (including ICMP header)
|
||||
*/
|
||||
typedef void (*TxTrace)(uint16_t seq, Ptr<const Packet> p);
|
||||
|
||||
/**
|
||||
* TracedCallback signature for Rtt trace
|
||||
*
|
||||
* \param [in] seq The ICMP sequence number
|
||||
* \param [in] rtt The reported RTT
|
||||
*/
|
||||
typedef void (*RttTrace)(uint16_t seq, Time rtt);
|
||||
|
||||
/**
|
||||
* TracedCallback signature for Drop trace
|
||||
*
|
||||
* \param [in] seq The ICMP sequence number
|
||||
* \param [in] reason The reason for the reported drop
|
||||
*/
|
||||
typedef void (*DropTrace)(uint16_t seq, DropReason reason);
|
||||
|
||||
/**
|
||||
* TracedCallback signature for Report trace
|
||||
*
|
||||
* \param [in] report The report information
|
||||
*/
|
||||
typedef void (*ReportTrace)(const struct PingReport& report);
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Writes data to buffer in little-endian format.
|
||||
*
|
||||
* Least significant byte of data is at lowest buffer address
|
||||
*
|
||||
* \param[out] buffer the buffer to write to
|
||||
* \param[in] data the data to write
|
||||
*/
|
||||
void Write64(uint8_t* buffer, const uint64_t data);
|
||||
|
||||
/**
|
||||
* \brief Writes data from a little-endian formatted buffer to data.
|
||||
*
|
||||
* \param buffer the buffer to read from
|
||||
* \return the read data
|
||||
*/
|
||||
uint64_t Read64(const uint8_t* buffer);
|
||||
|
||||
// inherited from Application base class.
|
||||
void StartApplication() override;
|
||||
void StopApplication() override;
|
||||
void DoDispose() override;
|
||||
|
||||
/**
|
||||
* \brief Return the application signatiure.
|
||||
* \returns the application signature.
|
||||
*
|
||||
* The application signature is the NodeId concatenated with the
|
||||
* application index inthe node.
|
||||
*/
|
||||
uint64_t GetApplicationSignature() const;
|
||||
|
||||
/**
|
||||
* \brief Receive an ICMPv4 or an ICMPv6 Echo reply
|
||||
* \param socket the receiving socket
|
||||
*
|
||||
* This function is called by lower layers through a callback.
|
||||
*/
|
||||
void Receive(Ptr<Socket> socket);
|
||||
|
||||
/**
|
||||
* \brief Send one Ping (ICMPv4 ECHO or ICMPv6 ECHO) to the destination
|
||||
*/
|
||||
void Send();
|
||||
|
||||
/**
|
||||
* Print the report
|
||||
*/
|
||||
void PrintReport();
|
||||
|
||||
/// Sender Local Address
|
||||
Address m_interfaceAddress;
|
||||
/// Remote address
|
||||
Address m_destination;
|
||||
/// Wait interval between ECHO requests
|
||||
Time m_interval{Seconds(1)};
|
||||
|
||||
/**
|
||||
* Specifies the number of data bytes to be sent.
|
||||
* The default is 56, which translates into 64 ICMP data bytes when combined with the 8 bytes of
|
||||
* ICMP header data.
|
||||
*/
|
||||
uint32_t m_size{56};
|
||||
/// The socket we send packets from
|
||||
Ptr<Socket> m_socket;
|
||||
/// ICMP ECHO sequence number
|
||||
uint16_t m_seq{0};
|
||||
/// Callbacks for tracing the packet Tx events
|
||||
TracedCallback<uint16_t, Ptr<Packet>> m_txTrace;
|
||||
/// TracedCallback for RTT samples
|
||||
TracedCallback<uint16_t, Time> m_rttTrace;
|
||||
/// TracedCallback for drop events
|
||||
TracedCallback<uint16_t, DropReason> m_dropTrace;
|
||||
/// TracedCallback for final ping report
|
||||
TracedCallback<const struct PingReport&> m_reportTrace;
|
||||
/// Variable to stor verbose mode
|
||||
VerboseMode m_verbose{VerboseMode::VERBOSE};
|
||||
/// Received packets counter
|
||||
uint32_t m_recv{0};
|
||||
/// Duplicate packets counter
|
||||
uint32_t m_duplicate{0};
|
||||
/// Start time to report total ping time
|
||||
Time m_started;
|
||||
/// Average rtt is ms
|
||||
Average<double> m_avgRtt;
|
||||
/// Next packet will be sent
|
||||
EventId m_next;
|
||||
|
||||
/**
|
||||
* \brief Sent echo request data.
|
||||
*/
|
||||
class EchoRequestData
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* \param txTimePar Echo request Tx time.
|
||||
* \param ackedPar True if the Echo request has been acknoledged at leats once.
|
||||
*/
|
||||
EchoRequestData(Time txTimePar, bool ackedPar)
|
||||
: txTime(txTimePar),
|
||||
acked(ackedPar)
|
||||
{
|
||||
}
|
||||
|
||||
Time txTime; //!< Tx time
|
||||
bool acked{false}; //!< True if packet has been acknowledged
|
||||
};
|
||||
|
||||
/// All sent but not answered packets. Map icmp seqno -> when sent, acked at least once.
|
||||
std::vector<EchoRequestData> m_sent;
|
||||
/// Number of packets to be sent.
|
||||
uint32_t m_count{0};
|
||||
/// Time to wait for a response, in seconds. The option affects only timeout in absence of any
|
||||
/// responses, otherwise ping waits for two RTTs
|
||||
Time m_timeout{Seconds(1)};
|
||||
/// True if the report has been printed already.
|
||||
bool m_reportPrinted{false};
|
||||
/// Use IPv4 (false) or IPv6 (true)
|
||||
bool m_useIpv6{false};
|
||||
/// Destination is Broadcast or Multicast
|
||||
bool m_multipleDestinations{false};
|
||||
|
||||
/// Routers addresses for IPv6 routing type 0.
|
||||
std::vector<Ipv6Address> m_routers;
|
||||
|
||||
/// App signature: ID of the node where the app is installed || ID of the Application.
|
||||
uint64_t m_appSignature{0};
|
||||
};
|
||||
|
||||
} // namespace ns3
|
||||
|
||||
#endif /* PING_H */
|
||||
Reference in New Issue
Block a user