Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
Neighbors.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <filesystem>
7
9#include "Stroika/Foundation/Characters/String2Int.h"
11#include "Stroika/Foundation/Execution/Exceptions.h"
12#include "Stroika/Foundation/Execution/ProcessRunner.h"
16#include "Stroika/Foundation/Streams/MemoryStream.h"
17
18#include "Neighbors.h"
19
20// Comment this in to turn on aggressive noisy DbgTrace in this module
21//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
22
23using namespace Stroika::Foundation;
26using namespace Stroika::Foundation::Execution;
27using namespace Stroika::Foundation::Memory;
28using namespace Stroika::Foundation::IO;
30using namespace Stroika::Foundation::Streams;
31
33
34// @todo Consider supporting ip neigh show and/or ip -6 neigh show
35//- https://www.midnightfreddie.com/how-to-arp-a-in-ipv6.html
36//-http ://man7.org/linux/man-pages/man8/ip-neighbour.8.html
37//
38// @todo perhaps add ability - background thread - to monitor, and always report up to date list?
39//
40namespace {
41 Collection<Neighbor> ArpDashA_ (bool includePurgedEntries, bool omitAllFFHardwareAddresses)
42 {
43#if USE_NOISY_TRACE_IN_THIS_MODULE_
44 Debug::TraceContextBumper ctx{"{}ArpDashA_", "includePurgedEntries={}"_f, includePurgedEntries};
45#endif
46#if qStroika_Foundation_Common_Platform_Windows
47 SystemInterfacesMgr sysInterfacesMgr;
48#endif
50 using std::byte;
51#if qStroika_Foundation_Common_Platform_POSIX
52 ProcessRunner pr{"arp -an"sv}; // -a means 'BSD-style output' and -n means numeric (dont do reverse dns)
53#elif qStroika_Foundation_Common_Platform_Windows
54 ProcessRunner pr{includePurgedEntries ? "arp -av"sv : "arp -a"sv}; // -a means 'BSD-style output', -v verbose(show invalid items)
55#endif
56 Streams::MemoryStream::Ptr<byte> useStdOut = Streams::MemoryStream::New<byte> ();
57 pr.Run (nullptr, useStdOut);
58 String out;
59#if qStroika_Foundation_Common_Platform_Windows
60 String curInterface;
61#endif
63 for (String i = stdOut.ReadLine (); not i.empty (); i = stdOut.ReadLine ()) {
64#if qStroika_Foundation_Common_Platform_POSIX
65 Sequence<String> s = i.Tokenize ();
66 if (s.length () >= 4) {
67 // raspberrypi.34ChurchStreet.sophists.com (192.168.244.32) at b8:27:eb:cc:c7:80 [ether] on enp0s31f6
68 // ? (192.168.244.173) at b8:3e:59:88:71:06 [ether] on enp0s31f6
69 //
70 // ? (192.168.244.70) at 50:dc:e7:7c:76:a0 [ether] on enp0s31f6
71 // ? (192.168.244.152) at <incomplete> on enp0s31f6
72 // ? (192.168.244.210) at <incomplete> on enp0s31f6
73 // ? (192.168.244.137) at <incomplete> on enp0s31f6
74 // ? (192.168.244.126) at 00:1a:62:04:0a:b1 [ether] on enp0s31f6
75 //
76 // According to https://unix.stackexchange.com/questions/192313/how-do-you-clear-the-arp-cache-on-linux
77 // 'incomplete' entries mean about to be removed from the ARP table (or mostly removed).
78 //
79 if (s[1].StartsWith ("("sv) and s[1].EndsWith (")"sv)) {
80 if (not includePurgedEntries and s[3].Contains ("incomplete"sv)) {
81 continue;
82 }
83 String interfaceID;
84 size_t l = s.length ();
85 if (l >= 6 and s[l - 2] == "on"sv) {
86 interfaceID = s[l - 1];
87 }
88 result += Neighbor{InternetAddress{s[1].SubString (1, -1)}, s[3], interfaceID};
89 }
90 }
91#elif qStroika_Foundation_Common_Platform_Windows
92 if (i.StartsWith ("Interface:"sv)) {
93 Sequence<String> s = i.Tokenize ();
94 if (s.length () >= 2) {
95 curInterface = s[1];
96 if (auto iface = sysInterfacesMgr.GetContainingAddress (InternetAddress{curInterface})) {
97 curInterface = iface->fInternalInterfaceID;
98 }
99 else {
100 WeakAssert (false); // bad -
101 curInterface = String{};
102 }
103 }
104 }
105 if (i.StartsWith (" "sv) and not curInterface.empty ()) {
106 Sequence<String> s = i.Tokenize ();
107 if (s.length () >= 3 and (s[2] == "static"sv or s[2] == "dynamic"sv)) {
108 // Windows arp produces address of the form xy-ab-cd, while most other platforms produce xy:ab:cd, so standardize the
109 // format we produce; helpful for WTF for example, sharing data among servers from different platforms --LGP 2022-06-14
110 static const String kDash_ = "-"sv;
111 static const String kColon_ = ":"sv;
112 if (omitAllFFHardwareAddresses) {
113 static const String kFFFF_ = "ff-ff-ff-ff-ff-ff"sv;
114 if (s[1] == kFFFF_) {
115 //DbgTrace (L"ignoring arped fake(broadcast) address %s", s[1].c_str ());
116 continue;
117 }
118 }
119 result += Neighbor{InternetAddress{s[0]}, s[1].ReplaceAll (kDash_, kColon_), curInterface};
120 }
121 }
122#endif
123 }
124#if USE_NOISY_TRACE_IN_THIS_MODULE_
125 DbgTrace ("returning: {}"_f, result);
126#endif
127 return result;
128 }
129}
130
131#if qStroika_Foundation_Common_Platform_Linux
132namespace {
133 Collection<Neighbor> ProcNetArp_ (bool includePurgedEntries)
134 {
135#if USE_NOISY_TRACE_IN_THIS_MODULE_
136 Debug::TraceContextBumper ctx{"{}ProcNetArp_", "includePurgedEntries={}"_f, includePurgedEntries};
137#endif
139 using Characters::String2Int;
141 static const filesystem::path kProcFileName_{"/proc/net/arp"sv};
142 /*
143 IP address HW type Flags HW address Mask Device
144 192.168.244.194 0x1 0x0 00:00:00:00:00:00 * enp0s31f6
145 192.168.244.160 0x1 0x0 00:00:00:00:00:00 * enp0s31f6
146 192.168.244.5 0x1 0x2 04:a1:51:cd:fc:4c * enp0s31f6
147 192.168.244.235 0x1 0x2 64:1c:ae:2a:95:7d * enp0s31f6
148 */
149 bool readFirstLine = false;
150 // Note - /procfs files always unseekable
151 for (const Sequence<String>& line :
152 reader.ReadMatrix (IO::FileSystem::FileInputStream::New (kProcFileName_, IO::FileSystem::FileInputStream::eNotSeekable))) {
153#if USE_NOISY_TRACE_IN_THIS_MODULE_
154 DbgTrace ("in ProcNetArp_ capture_ line={}"_f, line);
155#endif
156 if (not readFirstLine) {
157 readFirstLine = true;
158 continue;
159 }
160 if (line[2] == "0x0"sv) {
161 continue; // I think this means disabled item
162 }
163 result += Neighbor{InternetAddress{line[0]}, line[3], line[5]};
164 }
165#if USE_NOISY_TRACE_IN_THIS_MODULE_
166 DbgTrace ("returning: {}"_f, result);
167#endif
168 return result;
169 }
170}
171#endif
172
173/*
174 ********************************************************************************
175 ********************** NeighborsMonitor::Neighbor ******************************
176 ********************************************************************************
177 */
179{
180 StringBuilder sb;
181 sb << "{"sv;
182 sb << "InternetAddress:"sv << fInternetAddress << ","sv;
183 sb << "HardwareAddress:"sv << fHardwareAddress << ","sv;
184 sb << "InterfaceID:"sv << fInterfaceID;
185 sb << "}"sv;
186 return sb;
187}
188
189/*
190 ********************************************************************************
191 ******************************** NeighborsMonitor ******************************
192 ********************************************************************************
193 */
194class NeighborsMonitor::Rep_ {
195public:
196 Rep_ (const Options& o)
197 : fOptions_{o}
198 {
199 Require (not o.fMonitor.has_value ()); // cuz NYI
200 }
201 Collection<NeighborsMonitor::Neighbor> GetNeighbors () const
202 {
203 std::exception_ptr e = nullptr; // try best approach, and then try other fallbacks, but rethrow exception from best
204 auto tryStrategy = [&] (Options::Strategy s) -> optional<Collection<NeighborsMonitor::Neighbor>> {
205 // if strategies not specified, try any we are handed
206 if (not fOptions_.fStategies or fOptions_.fStategies->Contains (s)) {
207 try {
208 return GetNeighbors_ (s);
209 }
210 catch (...) {
211 if (e == nullptr) {
212 e = current_exception ();
213 }
214 }
215 }
216 return nullopt;
217 };
218 // try strategies in best to worst order (filtered by caller restriction on which we can try)
219#if qStroika_Foundation_Common_Platform_Linux
220 if (auto o = tryStrategy (Options::Strategy::eProcNetArp)) {
221 return *o;
222 }
223#endif
224 if (auto o = tryStrategy (Options::Strategy::eArpProgram)) {
225 return *o;
226 }
227 if (e != nullptr) {
228 rethrow_exception (e);
229 }
230 Throw (Exception{"No matching / available neighbors strategy"sv});
231 }
232 Collection<NeighborsMonitor::Neighbor> GetNeighbors_ (Options::Strategy s) const
233 {
234#if USE_NOISY_TRACE_IN_THIS_MODULE_
235 Debug::TraceContextBumper ctx{"NeighborsMonitor::Rep_::GetNeighbors_", "s={}"_f, s};
236#endif
237 switch (s) {
238 case Options::Strategy::eArpProgram:
239 return ArpDashA_ (fOptions_.fIncludePurgedEntries.value_or (false), fOptions_.fOmitAllFFHardwareAddresses.value_or (true));
240#if qStroika_Foundation_Common_Platform_Linux
241 case Options::Strategy::eProcNetArp:
242 // fOmitAllFFHardwareAddresses not needed here apparently
243 return ProcNetArp_ (fOptions_.fIncludePurgedEntries.value_or (false));
244#endif
245 default:
246 Assert (false);
248 }
249 }
250 Options fOptions_;
251};
252
253NeighborsMonitor::NeighborsMonitor (const Options& options)
254 : fRep_{Memory::MakeSharedPtr<Rep_> (options)}
255{
256}
257
258Collection<NeighborsMonitor::Neighbor> NeighborsMonitor::GetNeighbors () const
259{
260 return fRep_->GetNeighbors ();
261}
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:438
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
#define DbgTrace
Definition Trace.h:309
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 ...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
This COULD be easily used to read CSV files, or tab-delimited files, for example.
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
Run the given command, and optionally support stdin/stdout/stderr as streams (either sync with Run,...
nonvirtual optional< Interface > GetContainingAddress(const InternetAddress &ia)
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual size_t length() const
STL-ish alias for size() - really in STL only used in string, I think, but still makes sense as an al...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...