Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Ping.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
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"
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 if (fMaxHops) {
48 sb << "Max-Hops: "sv << *fMaxHops;
49 }
50 if (fTimeout) {
51 sb << ", Timeout: "sv << *fTimeout;
52 }
53 if (fPacketPayloadSize) {
54 sb << ", Packet-Payload-Size: "sv << *fPacketPayloadSize;
55 }
56 sb << "}"sv;
57 return sb;
58}
59
60/*
61 ********************************************************************************
62 ********************** NetworkMonitor::Ping::Pinger::ResultType ****************
63 ********************************************************************************
64 */
65String Pinger::ResultType::ToString () const
66{
68 sb << "{"sv;
69 sb << "Ping-Time: "sv << fPingTime;
70 sb << ", Hop-Count: "sv << fHopCount;
71 sb << "}"sv;
72 return sb;
73}
74
75/*
76 ********************************************************************************
77 *************************** NetworkMonitor::Ping::Pinger ***********************
78 ********************************************************************************
79 */
80Pinger::Pinger (const InternetAddress& addr, const Options& options)
81 : fDestination_{addr}
82 , fOptions_{options}
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)}
89{
90 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Pinger::CTOR", "addr={}, options={}"_f, fDestination_, fOptions_};
91 // use random data as a payload
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_));
95 }
96}
97
98Pinger::ResultType Pinger::RunOnce (const optional<unsigned int>& ttl)
99{
100 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Pinger::RunOnce", "ttl={}"_f, ttl};
101 return RunOnce_ICMP_ (ttl.value_or (fOptions_.fMaxHops.value_or (Options::kDefaultMaxHops)));
102}
103
104Pinger::ResultType Pinger::RunOnce_ICMP_ (unsigned int ttl)
105{
107 Stroika_Foundation_Debug_OptionalizeTraceArgs ("Frameworks::NetworkMonitor::Ping::Pinger::RunOnce_ICMP_", "ttl={}"_f, ttl)};
108 fSocket_.setsockopt (IPPROTO_IP, IP_TTL, ttl); // max # of hops
109
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);
116 return tmp;
117 }();
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_);
121 fSocket_.SendTo (fSendPacket_, SocketAddress{fDestination_, 0});
122
123 // Find first packet responding (some packets could be bogus/ignored)
124 Time::TimePointSeconds pingTimeoutAfter = Time::GetTickCount () + fPingTimeout_;
125 while (true) {
126 ThrowTimeoutExceptionAfter (pingTimeoutAfter);
127 SocketAddress fromAddress;
128 constexpr size_t kExtraSluff_{100}; // Leave a little extra room in case some packets return extra
129 StackBuffer<byte> recv_buf{Memory::eUninitialized, fICMPPacketSize_ + sizeof (ICMP::V4::PacketHeader) + 2 * sizeof (IP::V4::PacketHeader) +
130 kExtraSluff_}; // icmpPacketSize includes ONE ICMP header and payload, but we get 2 IP and 2 ICMP headers in TTL Exceeded response
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);
134#endif
135 const IP::V4::PacketHeader* replyIPHeader = reinterpret_cast<const IP::V4::PacketHeader*> (recv_buf.begin ());
136
137 // Skip ahead to the ICMP header within the IP packet
138 unsigned short header_len = replyIPHeader->ihl * 4;
139 const ICMP::V4::PacketHeader* replyICMPHeader = (const ICMP::V4::PacketHeader*)((const byte*)replyIPHeader + header_len);
140
141 // Make sure the reply is sane
142 if (n < header_len + ICMP::V4::ICMP_MIN) {
143 Execution::Throw (Execution::Exception{"too few bytes from "sv + Characters::ToString (fromAddress)}); // draft @todo fix
144 }
145
146 /*
147 * 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
148 */
149 optional<uint16_t> echoedID;
150 switch (replyICMPHeader->type) {
151 case ICMP::V4::ICMP_ECHO_REPLY: {
152 echoedID = replyICMPHeader->id;
153 } break;
154 case ICMP::V4::ICMP_TTL_EXPIRE: {
155 // 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
156 const ICMP::V4::PacketHeader* echoedICMPHeader =
157 (const ICMP::V4::PacketHeader*)((const byte*)replyICMPHeader + 8 + sizeof (IP::V4::PacketHeader));
158 echoedID = echoedICMPHeader->id;
159 } break;
160 case ICMP::V4::ICMP_DEST_UNREACH: {
161 // 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
162 const ICMP::V4::PacketHeader* echoedICMPHeader =
163 (const ICMP::V4::PacketHeader*)((const byte*)replyICMPHeader + 8 + sizeof (IP::V4::PacketHeader));
164 echoedID = echoedICMPHeader->id;
165 } break;
166 }
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));
169 // Must be a reply for another pinger running locally, so just
170 // ignore it.
171 continue;
172 }
173
174 /*
175 * Handle different response messages differently - but looking for ICMP_ECHO_REPLY, or ICMP_TTL_EXPIRE (for traceroute)
176 */
177 switch (replyICMPHeader->type) {
178 case ICMP::V4::ICMP_ECHO_REPLY: {
179 // Different operating systems use different starting values for TTL. TTL here is the original number used,
180 // less the number of hops. So we are left with making an educated guess. Need refrence and would be nice to find better
181 // way, but this seems to work pretty often.
182 unsigned int nHops{};
183 if (replyIPHeader->ttl > 128) {
184 nHops = 257 - replyIPHeader->ttl;
185 }
186 else if (replyIPHeader->ttl > 64) {
187 nHops = 129 - replyIPHeader->ttl;
188 }
189 else {
190 nHops = 65 - replyIPHeader->ttl;
191 }
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
193 DbgTrace (L"reply->ttl = {}, nHops = {}"_f, replyIPHeader->ttl, nHops);
194#endif
195 return ResultType{Duration{Time::GetTickCount ().time_since_epoch ().count () - replyICMPHeader->timestamp / 1000.0}, nHops};
196 }
197 case ICMP::V4::ICMP_TTL_EXPIRE: {
199 }
200 case ICMP::V4::ICMP_DEST_UNREACH: {
201 Execution::Throw (ICMP::V4::DestinationUnreachableException{replyICMPHeader->code, InternetAddress{replyIPHeader->saddr}});
202 };
203 default: {
204 Execution::Throw (ICMP::V4::UnknownICMPPacket{replyICMPHeader->type});
205 }
206 }
207 }
209 return ResultType{};
210}
211
212/*
213 ********************************************************************************
214 ********************** NetworkMonitor::Ping::SampleOptions *********************
215 ********************************************************************************
216 */
217Characters::String SampleOptions::ToString () const
218{
219 StringBuilder sb;
220 sb << "{"sv;
221 sb << "Interval: "sv << fInterval;
222 sb << ", Count: "sv << fSampleCount;
223 sb << "}"sv;
224 return sb;
225}
226
227/*
228 ********************************************************************************
229 ******************* NetworkMonitor::Ping::SampleResults ************************
230 ********************************************************************************
231 */
232String SampleResults::ToString () const
233{
234 StringBuilder sb;
235 sb << "{"sv;
236 if (fMedianPingTime) {
237 sb << "Median-Ping-Time: "sv << *fMedianPingTime;
238 }
239 if (fMedianHopCount) {
240 sb << ", Median-Hop-Count: "sv << fMedianHopCount;
241 }
242 if (fExceptionCount != 0) {
243 sb << ", Exception-Count: "sv << fExceptionCount; // to see exceptions - run with sample-count = 1
244 }
245 sb << "}"sv;
246 return sb;
247}
248
249/*
250 ********************************************************************************
251 ************************* NetworkMonitor::Ping::Sample *************************
252 ********************************************************************************
253 */
254SampleResults NetworkMonitor::Ping::Sample (const InternetAddress& addr, const SampleOptions& sampleOptions, const Options& options)
255{
256 Debug::TraceContextBumper ctx{"Frameworks::NetworkMonitor::Ping::Sample", "addr={}, sampleOptions={}, options={}"_f, addr, sampleOptions, options};
257 Pinger pinger{addr, options};
259 Collection<unsigned int> sampleHopCounts;
260 unsigned int samplesTaken{};
261 while (samplesTaken < sampleOptions.fSampleCount) {
262 if (samplesTaken != 0) {
263 Execution::Sleep (sampleOptions.fInterval);
264 }
265 try {
266 Pinger::ResultType tmp = pinger.RunOnce ();
267 sampleTimes += tmp.fPingTime;
268 sampleHopCounts += tmp.fHopCount;
269 ++samplesTaken;
270 }
271 catch (...) {
272 ++samplesTaken;
273 }
274 }
275 return sampleTimes.empty () ? SampleResults{{}, {}, samplesTaken}
276 : SampleResults{Duration{*sampleTimes.Median ()}, *sampleHopCounts.Median (),
277 static_cast<unsigned int> (samplesTaken - sampleTimes.size ())};
278}
#define AssertNotReached()
Definition Assertions.h:355
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:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
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 ...
Definition Collection.h:102
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
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:300
nonvirtual optional< RESULT_TYPE > Median(const INORDER_COMPARE_FUNCTION &compare={}) const
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:306
nonvirtual ResultType RunOnce(const optional< unsigned int > &ttl={})
run the Ping () operation one time, and return the (timing) results.
Definition Ping.cpp:98
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:465
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
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...
Definition Throw.inl:43