diff --git a/CHANGES.md b/CHANGES.md index 37108cafb..585c802d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 . 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 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1311602c0..aadc6eefc 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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 diff --git a/src/internet-apps/CMakeLists.txt b/src/internet-apps/CMakeLists.txt index b2416e6e9..2164bec2d 100644 --- a/src/internet-apps/CMakeLists.txt +++ b/src/internet-apps/CMakeLists.txt @@ -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 diff --git a/src/internet-apps/model/ping.cc b/src/internet-apps/model/ping.cc new file mode 100644 index 000000000..194afb24d --- /dev/null +++ b/src/internet-apps/model/ping.cc @@ -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 + * Tommaso Pecorella + * + * 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() + .SetGroupName("Internet-Apps") + .AddConstructor() + .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(16)) + .AddAttribute( + "Count", + "The maximum number of packets the application will send (zero means no limits)", + UintegerValue(0), + MakeUintegerAccessor(&Ping::m_count), + MakeUintegerChecker()) + .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 = 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) +{ + NS_LOG_FUNCTION(this << socket); + + while (m_socket->GetRxAvailable() > 0) + { + Address from; + Ptr 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(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(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 ipv6 = m_node->GetObject(); + 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(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(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 dataPacket = Create(data, m_size); + + Ptr p = Create(); + 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& routers) +{ + m_routers = routers; +} + +} // namespace ns3 diff --git a/src/internet-apps/model/ping.h b/src/internet-apps/model/ping.h new file mode 100644 index 000000000..36219efc6 --- /dev/null +++ b/src/internet-apps/model/ping.h @@ -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 + * + * 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 + +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& 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 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); + + /** + * \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 m_socket; + /// ICMP ECHO sequence number + uint16_t m_seq{0}; + /// Callbacks for tracing the packet Tx events + TracedCallback> m_txTrace; + /// TracedCallback for RTT samples + TracedCallback m_rttTrace; + /// TracedCallback for drop events + TracedCallback m_dropTrace; + /// TracedCallback for final ping report + TracedCallback 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 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 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 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 */