4#include "Stroika/Frameworks/StroikaPreComp.h"
8#include "Stroika/Foundation/Containers/Collection.h"
10#include "Stroika/Foundation/Execution/TimeOutException.h"
11#include "Stroika/Foundation/IO/Network/InternetProtocol/ICMP.h"
12#include "Stroika/Foundation/IO/Network/InternetProtocol/IP.h"
14#include "Stroika/Foundation/IO/Network/SocketAddress.h"
24using namespace Stroika::Foundation::Debug;
26using namespace Stroika::Foundation::Memory;
27using namespace Stroika::Foundation::IO;
29using namespace Stroika::Foundation::IO::Network::InternetProtocol;
31using namespace Stroika::Frameworks;
32using namespace Stroika::Frameworks::NetworkMonitor;
33using namespace Stroika::Frameworks::NetworkMonitor::Ping;
43String Ping::Options::ToString ()
const
48 sb <<
"Max-Hops: "sv << *fMaxHops;
51 sb <<
", Timeout: "sv << *fTimeout;
53 if (fPacketPayloadSize) {
54 sb <<
", Packet-Payload-Size: "sv << *fPacketPayloadSize;
65String Pinger::ResultType::ToString ()
const
69 sb <<
"Ping-Time: "sv << fPingTime;
70 sb <<
", Hop-Count: "sv << fHopCount;
83 , fICMPPacketSize_{Options::kAllowedICMPPayloadSizeRange.Pin (options.fPacketPayloadSize.value_or (Options::kDefaultPayloadSize)) +
84 sizeof (ICMP::V4::PacketHeader)}
85 , fSendPacket_{fICMPPacketSize_}
86 , fSocket_{IO::Network::ConnectionlessSocket::New (
SocketAddress::INET, Socket::RAW, IPPROTO_ICMP)}
87 , fNextSequenceNumber_{static_cast<uint16_t> (fAllUInt16Distribution_ (fRng_))}
88 , fPingTimeout_{options.fTimeout.value_or (Options::kDefaultTimeout)}
90 Debug::TraceContextBumper ctx{
"Frameworks::NetworkMonitor::Ping::Pinger::CTOR",
"addr={}, options={}"_f, fDestination_, fOptions_};
92 for (
byte* p = (
byte*)fSendPacket_.begin () + sizeof (ICMP::V4::PacketHeader); p < fSendPacket_.end (); ++p) {
93 uniform_int_distribution<mt19937::result_type> anyByteDistribution (0, numeric_limits<uint8_t>::max ());
94 *p =
static_cast<byte> (anyByteDistribution (fRng_));
101 return RunOnce_ICMP_ (ttl.value_or (fOptions_.fMaxHops.value_or (Options::kDefaultMaxHops)));
104Pinger::ResultType Pinger::RunOnce_ICMP_ (
unsigned int ttl)
108 fSocket_.
setsockopt (IPPROTO_IP, IP_TTL, ttl);
110 ICMP::V4::PacketHeader pingRequest = [&] () {
111 ICMP::V4::PacketHeader tmp{};
112 tmp.type = ICMP::V4::ICMP_ECHO_REQUEST;
113 tmp.id =
static_cast<uint16_t
> (fAllUInt16Distribution_ (fRng_));
114 tmp.seq = ++fNextSequenceNumber_;
115 tmp.timestamp =
static_cast<uint32_t
> (Time::GetTickCount ().time_since_epoch ().count () * 1000);
118 (void)::memcpy (fSendPacket_.begin (), &pingRequest,
sizeof (pingRequest));
119 reinterpret_cast<ICMP::V4::PacketHeader*
> (fSendPacket_.begin ())->checksum =
120 IP::ip_checksum (fSendPacket_.begin (), fSendPacket_.begin () + fICMPPacketSize_);
128 constexpr size_t kExtraSluff_{100};
129 StackBuffer<byte> recv_buf{Memory::eUninitialized, fICMPPacketSize_ +
sizeof (ICMP::V4::PacketHeader) + 2 *
sizeof (IP::V4::PacketHeader) +
131 size_t n = fSocket_.
ReceiveFrom (recv_buf, 0, &fromAddress, fPingTimeout_).size ();
132#if USE_NOISY_TRACE_IN_THIS_MODULE_
133 DbgTrace (
"got back packet from {}"_f, fromAddress);
135 const IP::V4::PacketHeader* replyIPHeader =
reinterpret_cast<const IP::V4::PacketHeader*
> (recv_buf.begin ());
138 unsigned short header_len = replyIPHeader->ihl * 4;
139 const ICMP::V4::PacketHeader* replyICMPHeader = (
const ICMP::V4::PacketHeader*)((
const byte*)replyIPHeader + header_len);
142 if (n < header_len + ICMP::V4::ICMP_MIN) {
149 optional<uint16_t> echoedID;
150 switch (replyICMPHeader->type) {
151 case ICMP::V4::ICMP_ECHO_REPLY: {
152 echoedID = replyICMPHeader->id;
154 case ICMP::V4::ICMP_TTL_EXPIRE: {
156 const ICMP::V4::PacketHeader* echoedICMPHeader =
157 (
const ICMP::V4::PacketHeader*)((
const byte*)replyICMPHeader + 8 +
sizeof (IP::V4::PacketHeader));
158 echoedID = echoedICMPHeader->id;
160 case ICMP::V4::ICMP_DEST_UNREACH: {
162 const ICMP::V4::PacketHeader* echoedICMPHeader =
163 (
const ICMP::V4::PacketHeader*)((
const byte*)replyICMPHeader + 8 +
sizeof (IP::V4::PacketHeader));
164 echoedID = echoedICMPHeader->id;
167 if (echoedID and echoedID != pingRequest.id) {
168 DbgTrace (
"echoedID ({} != pingRequest.id (0x{:x}) so ignoring this reply"_f, echoedID,
static_cast<int> (pingRequest.id));
177 switch (replyICMPHeader->type) {
178 case ICMP::V4::ICMP_ECHO_REPLY: {
182 unsigned int nHops{};
183 if (replyIPHeader->ttl > 128) {
184 nHops = 257 - replyIPHeader->ttl;
186 else if (replyIPHeader->ttl > 64) {
187 nHops = 129 - replyIPHeader->ttl;
190 nHops = 65 - replyIPHeader->ttl;
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
193 DbgTrace (L
"reply->ttl = {}, nHops = {}"_f, replyIPHeader->ttl, nHops);
195 return ResultType{
Duration{Time::GetTickCount ().time_since_epoch ().count () - replyICMPHeader->timestamp / 1000.0}, nHops};
197 case ICMP::V4::ICMP_TTL_EXPIRE: {
200 case ICMP::V4::ICMP_DEST_UNREACH: {
221 sb <<
"Interval: "sv << fInterval;
222 sb <<
", Count: "sv << fSampleCount;
232String SampleResults::ToString ()
const
236 if (fMedianPingTime) {
237 sb <<
"Median-Ping-Time: "sv << *fMedianPingTime;
239 if (fMedianHopCount) {
240 sb <<
", Median-Hop-Count: "sv << fMedianHopCount;
242 if (fExceptionCount != 0) {
243 sb <<
", Exception-Count: "sv << fExceptionCount;
254SampleResults NetworkMonitor::Ping::Sample (
const InternetAddress& addr,
const SampleOptions& sampleOptions,
const Options& options)
256 Debug::TraceContextBumper ctx{
"Frameworks::NetworkMonitor::Ping::Sample",
"addr={}, sampleOptions={}, options={}"_f, addr, sampleOptions, options};
257 Pinger pinger{addr, options};
260 unsigned int samplesTaken{};
261 while (samplesTaken < sampleOptions.fSampleCount) {
262 if (samplesTaken != 0) {
266 Pinger::ResultType tmp = pinger.RunOnce ();
267 sampleTimes += tmp.fPingTime;
268 sampleHopCounts += tmp.fHopCount;
275 return sampleTimes.
empty () ? SampleResults{{}, {}, samplesTaken}
277 static_cast<unsigned int> (samplesTaken - sampleTimes.
size ())};
#define AssertNotReached()
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
nonvirtual void SendTo(span< const byte > data, const SocketAddress &sockAddr) const
nonvirtual span< byte > ReceiveFrom(span< byte > into, int flag, SocketAddress *fromAddress, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void setsockopt(int level, int optname, ARG_TYPE arg) const
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
Duration is a chrono::duration<double> (=.
nonvirtual size_t size() const
Returns the number of items contained.
nonvirtual optional< RESULT_TYPE > Median(const INORDER_COMPARE_FUNCTION &compare={}) const
nonvirtual bool empty() const
Returns true iff size() == 0.
nonvirtual ResultType RunOnce(const optional< unsigned int > &ttl={})
run the Ping () operation one time, and return the (timing) results.
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
void Sleep(Time::Duration seconds2Wait)
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
Throw TimeOutException if the @Time::GetTickCount () is >= the given value.
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...