Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Ping.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
8#include "Stroika/Foundation/Containers/Collection.h"
10#include "Stroika/Foundation/Execution/Timeout.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"
16
17#include "Ping.h"
18
19using std::byte;
20
21using namespace Stroika::Foundation;
24using namespace Stroika::Foundation::Debug;
25using namespace Stroika::Foundation::Execution;
26using namespace Stroika::Foundation::Memory;
27using namespace Stroika::Foundation::IO;
29using namespace Stroika::Foundation::IO::Network::InternetProtocol;
30
31using namespace Stroika::Frameworks;
32using namespace Stroika::Frameworks::NetworkMonitor;
33using namespace Stroika::Frameworks::NetworkMonitor::Ping;
34
35// Comment this in to turn on aggressive noisy DbgTrace in this module
36//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
37
38/*
39 ********************************************************************************
40 ********************** NetworkMonitor::Ping::Options ***************************
41 ********************************************************************************
42 */
43String Ping::Options::ToString () const
44{
46 sb << "{"sv;
47 sb << "maxHops: "sv << fMaxHops;
48 sb << ", timeout: "sv << fTimeout;
49 sb << ", packetPayloadSize: "sv << fPacketPayloadSize;
50 sb << "}"sv;
51 return sb;
52}
53
54/*
55 ********************************************************************************
56 ********************** NetworkMonitor::Ping::Pinger::ResultType ****************
57 ********************************************************************************
58 */
59String Pinger::ResultType::ToString () const
60{
62 sb << "{"sv;
63 sb << "pingTime: "sv << fPingTime;
64 sb << ", hopCount: "sv << fHopCount;
65 sb << "}"sv;
66 return sb;
67}
68
69/*
70 ********************************************************************************
71 *************************** NetworkMonitor::Ping::Pinger ***********************
72 ********************************************************************************
73 */
74Pinger::Pinger (const InternetAddress& addr, const Options& options)
75 : fDestination_{addr}
76 , fOptions_{options}
77 , fICMPPacketSize_{Options::kAllowedICMPPayloadSizeRange.Pin (options.fPacketPayloadSize) + sizeof (ICMP::V4::PacketHeader)}
78 , fSendPacket_{fICMPPacketSize_}
79 , fSocket_{IO::Network::ConnectionlessSocket::New (SocketAddress::INET, Socket::RAW, IPPROTO_ICMP)}
80 , fNextSequenceNumber_{static_cast<uint16_t> (fAllUInt16Distribution_ (fRng_))}
81 , fPingTimeout_{options.fTimeout}
82{
83 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Pinger::CTOR", "addr={}, options={}"_f, fDestination_, fOptions_};
84 // use random data as a payload
85 for (byte* p = (byte*)fSendPacket_.begin () + sizeof (ICMP::V4::PacketHeader); p < fSendPacket_.end (); ++p) {
86 uniform_int_distribution<mt19937::result_type> anyByteDistribution (0, numeric_limits<uint8_t>::max ());
87 *p = static_cast<byte> (anyByteDistribution (fRng_));
88 }
89}
90
91Pinger::ResultType Pinger::RunOnce (const optional<unsigned int>& ttl)
92{
93 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Pinger::RunOnce", "ttl={}"_f, ttl};
94 return RunOnce_ICMP_ (ttl.value_or (fOptions_.fMaxHops));
95}
96
97Pinger::ResultType Pinger::RunOnce_ICMP_ (unsigned int ttl)
98{
100 Stroika_Foundation_Debug_OptionalizeTraceArgs ("Frameworks::NetworkMonitor::Ping::Pinger::RunOnce_ICMP_", "ttl={}"_f, ttl)};
101 fSocket_.setsockopt (IPPROTO_IP, IP_TTL, ttl); // max # of hops
102
103 ICMP::V4::PacketHeader pingRequest = [&] () {
104 ICMP::V4::PacketHeader tmp{};
105 tmp.type = ICMP::V4::ICMP_ECHO_REQUEST;
106 tmp.id = static_cast<uint16_t> (fAllUInt16Distribution_ (fRng_));
107 tmp.seq = ++fNextSequenceNumber_;
108 tmp.timestamp = static_cast<uint32_t> (Time::GetTickCount ().time_since_epoch ().count () * 1000);
109 return tmp;
110 }();
111 (void)::memcpy (fSendPacket_.begin (), &pingRequest, sizeof (pingRequest));
112 reinterpret_cast<ICMP::V4::PacketHeader*> (fSendPacket_.begin ())->checksum =
113 IP::ip_checksum (fSendPacket_.begin (), fSendPacket_.begin () + fICMPPacketSize_);
114 fSocket_.SendTo (fSendPacket_, SocketAddress{fDestination_, 0});
115
116 // Find first packet responding (some packets could be bogus/ignored)
117 Time::TimePointSeconds pingTimeoutAfter = Time::GetTickCount () + fPingTimeout_;
118 while (true) {
119 ThrowTimeoutExceptionAfter (pingTimeoutAfter);
120 SocketAddress fromAddress;
121 constexpr size_t kExtraSluff_{100}; // Leave a little extra room in case some packets return extra
122 StackBuffer<byte> recv_buf{Memory::eUninitialized, fICMPPacketSize_ + sizeof (ICMP::V4::PacketHeader) + 2 * sizeof (IP::V4::PacketHeader) +
123 kExtraSluff_}; // icmpPacketSize includes ONE ICMP header and payload, but we get 2 IP and 2 ICMP headers in TTL Exceeded response
124 size_t n = fSocket_.ReceiveFrom (recv_buf, 0, &fromAddress, fPingTimeout_).size ();
125#if USE_NOISY_TRACE_IN_THIS_MODULE_
126 DbgTrace ("got back packet from {}"_f, fromAddress);
127#endif
128 const IP::V4::PacketHeader* replyIPHeader = reinterpret_cast<const IP::V4::PacketHeader*> (recv_buf.begin ());
129
130 // Skip ahead to the ICMP header within the IP packet
131 unsigned short header_len = replyIPHeader->ihl * 4;
132 const ICMP::V4::PacketHeader* replyICMPHeader = (const ICMP::V4::PacketHeader*)((const byte*)replyIPHeader + header_len);
133
134 // Make sure the reply is sane
135 if (n < header_len + ICMP::V4::ICMP_MIN) {
136 Execution::Throw (Execution::Exception{"too few bytes from "sv + Characters::ToString (fromAddress)}); // draft @todo fix
137 }
138
139 /*
140 * If we got a response id, compare it with the request we sent to make sure we're reading a resposne to the request we sent
141 */
142 optional<uint16_t> echoedID;
143 switch (replyICMPHeader->type) {
144 case ICMP::V4::ICMP_ECHO_REPLY: {
145 echoedID = replyICMPHeader->id;
146 } break;
147 case ICMP::V4::ICMP_TTL_EXPIRE: {
148 // According to https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Time_exceeded - we also can find the first 8 bytes of original datagram's data
149 const ICMP::V4::PacketHeader* echoedICMPHeader =
150 (const ICMP::V4::PacketHeader*)((const byte*)replyICMPHeader + 8 + sizeof (IP::V4::PacketHeader));
151 echoedID = echoedICMPHeader->id;
152 } break;
153 case ICMP::V4::ICMP_DEST_UNREACH: {
154 // According to https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Destination_unreachable - we also can find the first 8 bytes of original datagram's data
155 const ICMP::V4::PacketHeader* echoedICMPHeader =
156 (const ICMP::V4::PacketHeader*)((const byte*)replyICMPHeader + 8 + sizeof (IP::V4::PacketHeader));
157 echoedID = echoedICMPHeader->id;
158 } break;
159 }
160 if (echoedID and echoedID != pingRequest.id) {
161 DbgTrace ("echoedID ({} != pingRequest.id (0x{:x})) so ignoring this reply"_f, echoedID, static_cast<int> (pingRequest.id));
162 // Must be a reply for another pinger running locally, so just ignore it (this will likely be quite rare).
163 continue;
164 }
165
166 /*
167 * Handle different response messages differently - but looking for ICMP_ECHO_REPLY, or ICMP_TTL_EXPIRE (for traceroute)
168 */
169 switch (replyICMPHeader->type) {
170 case ICMP::V4::ICMP_ECHO_REPLY: {
171 // Different operating systems use different starting values for TTL. TTL here is the original number used,
172 // less the number of hops. So we are left with making an educated guess. Need refrence and would be nice to find better
173 // way, but this seems to work pretty often.
174 unsigned int nHops{};
175 if (replyIPHeader->ttl > 128) {
176 nHops = 257 - replyIPHeader->ttl;
177 }
178 else if (replyIPHeader->ttl > 64) {
179 nHops = 129 - replyIPHeader->ttl;
180 }
181 else {
182 nHops = 65 - replyIPHeader->ttl;
183 }
184#if USE_NOISY_TRACE_IN_THIS_MODULE_
185 DbgTrace ("reply->ttl = {}, nHops = {}"_f, replyIPHeader->ttl, nHops);
186#endif
187 return ResultType{Duration{Time::GetTickCount ().time_since_epoch ().count () - replyICMPHeader->timestamp / 1000.0}, nHops};
188 }
189 case ICMP::V4::ICMP_TTL_EXPIRE: {
191 }
192 case ICMP::V4::ICMP_DEST_UNREACH: {
193 Execution::Throw (ICMP::V4::DestinationUnreachableException{replyICMPHeader->code, InternetAddress{replyIPHeader->saddr}});
194 };
195 default: {
196 Execution::Throw (ICMP::V4::UnknownICMPPacket{replyICMPHeader->type});
197 }
198 }
199 }
201 return ResultType{};
202}
203
204/*
205 ********************************************************************************
206 ********************** NetworkMonitor::Ping::SampleOptions *********************
207 ********************************************************************************
208 */
209Characters::String SampleOptions::ToString () const
210{
211 StringBuilder sb;
212 sb << "{"sv;
213 sb << "interval: "sv << fInterval;
214 sb << ", count: "sv << fSampleCount;
215 sb << "}"sv;
216 return sb;
217}
218
219/*
220 ********************************************************************************
221 ******************* NetworkMonitor::Ping::SampleResults ************************
222 ********************************************************************************
223 */
224String SampleResults::ToString () const
225{
226 StringBuilder sb;
227 sb << "{"sv;
228 if (fMedianPingTime) {
229 sb << "medianPingTime: "sv << *fMedianPingTime;
230 }
231 if (fMedianHopCount) {
232 sb << ", medianHopCount: "sv << fMedianHopCount;
233 }
234 if (fExceptionCount != 0) {
235 sb << ", exceptionCount: "sv << fExceptionCount; // to see exceptions - run with sample-count = 1
236 }
237 sb << "}"sv;
238 return sb;
239}
240
241/*
242 ********************************************************************************
243 ************************* NetworkMonitor::Ping::Sample *************************
244 ********************************************************************************
245 */
246SampleResults NetworkMonitor::Ping::Sample (const InternetAddress& addr, const SampleOptions& sampleOptions, const Options& options)
247{
248 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Sample", "addr={}, sampleOptions={}, options={}"_f, addr, sampleOptions, options};
249 Pinger pinger{addr, options};
251 Collection<unsigned int> sampleHopCounts;
252 unsigned int samplesTaken{};
253 while (samplesTaken < sampleOptions.fSampleCount) {
254 if (samplesTaken != 0) {
255 Execution::Sleep (sampleOptions.fInterval);
256 }
257 try {
258 Pinger::ResultType tmp = pinger.RunOnce ();
259 sampleTimes += tmp.fPingTime;
260 sampleHopCounts += tmp.fHopCount;
261 ++samplesTaken;
262 }
263 catch (...) {
264 ++samplesTaken;
265 }
266 }
267 return sampleTimes.empty () ? SampleResults{{}, {}, samplesTaken}
268 : SampleResults{Duration{*sampleTimes.Median ()}, *sampleHopCounts.Median (),
269 static_cast<unsigned int> (samplesTaken - sampleTimes.size ())};
270}
#define AssertNotReached()
Definition Assertions.h:358
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
#define DbgTrace
Definition Trace.h:327
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:288
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,...
Definition String.h:201
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...
Definition Exceptions.h:219
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> (=.
Definition Duration.h:96
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual optional< RESULT_TYPE > Median(const INORDER_COMPARE_FUNCTION &compare={}) const
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:317
nonvirtual ResultType RunOnce(const optional< unsigned int > &ttl={})
run the Ping () operation one time, and return the (timing) results.
Definition Ping.cpp:91
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:424
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.inl:101
void ThrowTimeoutExceptionAfter(Time::TimePointSeconds afterTickCount, EXCEPTION &&exception2Throw)
How Stroika reports a timeout.
Definition Timeout.inl:21
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54