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