Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ConnectionlessSocket.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
7#include "Stroika/Foundation/Execution/Activity.h"
9#include "Stroika/Foundation/Execution/TimeOutException.h"
11
12#include "Socket-Private_.h"
13
14#include "ConnectionlessSocket.h"
15
16// Comment this in to turn on aggressive noisy DbgTrace in this module
17//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
18using std::byte;
19
20using namespace Stroika::Foundation;
22using namespace Stroika::Foundation::Execution;
23using namespace Stroika::Foundation::Memory;
24using namespace Stroika::Foundation::IO;
26
27using namespace Stroika::Foundation::IO::Network::PRIVATE_;
28
30
31namespace {
32 struct Rep_ : BackSocketImpl_<ConnectionlessSocket::_IRep> {
33 using inherited = BackSocketImpl_<ConnectionlessSocket::_IRep>;
35 : inherited{sd}
36 {
37 }
38 virtual void SendTo (const byte* start, const byte* end, const SocketAddress& sockAddr) override
39 {
40#if USE_NOISY_TRACE_IN_THIS_MODULE_
41 Debug::TraceContextBumper ctx{"IO::Network::Socket...rep...::SendTo", "end-start={}, sockAddr={}"_f,
42 static_cast<long long> (end - start), sockAddr};
43#endif
44 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
45 sockaddr_storage sa = sockAddr.As<sockaddr_storage> ();
46#if qStroika_Foundation_Common_Platform_POSIX
47 Handle_ErrNoResultInterruption ([this, &start, &end, &sa, &sockAddr] () -> int {
48 return ::sendto (fSD_, reinterpret_cast<const char*> (start), end - start, 0, reinterpret_cast<sockaddr*> (&sa),
49 sockAddr.GetRequiredSize ());
50 });
51#elif qStroika_Foundation_Common_Platform_Windows
52 Require (end - start < numeric_limits<int>::max ());
53 ThrowWSASystemErrorIfSOCKET_ERROR (::sendto (fSD_, reinterpret_cast<const char*> (start), static_cast<int> (end - start), 0,
54 reinterpret_cast<sockaddr*> (&sa), static_cast<int> (sockAddr.GetRequiredSize ())));
55#else
57#endif
58 }
59 virtual size_t ReceiveFrom (byte* intoStart, byte* intoEnd, int flag, SocketAddress* fromAddress, Time::DurationSeconds timeout) override
60 {
61 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
62
63 if constexpr (qStroika_Foundation_Common_Platform_Windows) {
64 // TMPHACK for - http://stroika-bugs.sophists.com/browse/STK-964
65 auto s = Execution::WaitForIOReady{fSD_}.WaitQuietly (timeout);
67 if (s.empty ()) {
69 }
70 }
71
72 // Note - COULD have implemented timeout with SO_RCVTIMEO, but that would risk statefulness, and confusion setting/resetting the parameter. Could be done, but this seems
73 // cleaner...
74 constexpr Time::DurationSeconds kMaxPolltime_{numeric_limits<int>::max () / 1000.0};
75 if (timeout < kMaxPolltime_) {
76 int timeout_millisecs = Math::Round<int> (timeout.count () * 1000);
77 pollfd pollData{};
78 pollData.fd = fSD_;
79 pollData.events = POLLIN;
80#if qStroika_Foundation_Common_Platform_Windows
81 int nresults;
82 if ((nresults = ::WSAPoll (&pollData, 1, timeout_millisecs)) == SOCKET_ERROR) {
83 Execution::ThrowSystemErrNo (::WSAGetLastError ());
84 }
85#else
86 int nresults = Handle_ErrNoResultInterruption ([&] () { return ::poll (&pollData, 1, timeout_millisecs); });
87#endif
88 if (nresults == 0) [[unlikely]] {
90 }
91 }
92
93 struct sockaddr_storage sa;
94 socklen_t salen = sizeof (sa);
95#if qStroika_Foundation_Common_Platform_POSIX
96 size_t result = static_cast<size_t> (Handle_ErrNoResultInterruption ([&] () -> int {
97 return ::recvfrom (fSD_, reinterpret_cast<char*> (intoStart), intoEnd - intoStart, flag,
98 fromAddress == nullptr ? nullptr : reinterpret_cast<sockaddr*> (&sa), fromAddress == nullptr ? nullptr : &salen);
99 }));
100 if (fromAddress != nullptr) {
101 *fromAddress = sa;
102 }
103 return result;
104#elif qStroika_Foundation_Common_Platform_Windows
105 Require (intoEnd - intoStart < numeric_limits<int>::max ());
106 size_t result = static_cast<size_t> (ThrowWSASystemErrorIfSOCKET_ERROR (
107 ::recvfrom (fSD_, reinterpret_cast<char*> (intoStart), static_cast<int> (intoEnd - intoStart), flag,
108 fromAddress == nullptr ? nullptr : reinterpret_cast<sockaddr*> (&sa), fromAddress == nullptr ? nullptr : &salen)));
109 if (fromAddress != nullptr) {
110 *fromAddress = sa;
111 }
112 return result;
113#else
115#endif
116 }
117 virtual void JoinMulticastGroup (const InternetAddress& iaddr, const InternetAddress& onInterface) override
118 {
119 Debug::TraceContextBumper ctx{"IO::Network::Socket::JoinMulticastGroup",
120 Stroika_Foundation_Debug_OptionalizeTraceArgs ("iaddr={} onInterface={}"_f, iaddr, onInterface)};
121 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
122 Assert (iaddr.GetAddressFamily () == InternetAddress::AddressFamily::V4 or iaddr.GetAddressFamily () == InternetAddress::AddressFamily::V6);
123 auto activity = Execution::LazyEvalActivity{[&] () -> Characters::String {
124 return "joining multicast group "sv + Characters::ToString (iaddr) + " on interface "sv + Characters::ToString (onInterface);
125 }};
126 Execution::DeclareActivity activityDeclare{&activity};
127 switch (iaddr.GetAddressFamily ()) {
128 case InternetAddress::AddressFamily::V4: {
129 ::ip_mreq m{};
130 m.imr_multiaddr = iaddr.As<in_addr> ();
131 m.imr_interface = onInterface.As<in_addr> ();
132 setsockopt (IPPROTO_IP, IP_ADD_MEMBERSHIP, m);
133 } break;
134 case InternetAddress::AddressFamily::V6: {
135 ::ipv6_mreq m{};
136 m.ipv6mr_multiaddr = iaddr.As<in6_addr> ();
137 m.ipv6mr_interface = 0; //??? seems to mean any
138 setsockopt (IPPROTO_IPV6, IPV6_JOIN_GROUP, m);
139 } break;
140 default:
142 }
143 }
144 virtual void LeaveMulticastGroup (const InternetAddress& iaddr, const InternetAddress& onInterface) override
145 {
146 Debug::TraceContextBumper ctx{"IO::Network::Socket::LeaveMulticastGroup", "iaddr={} onInterface={}"_f, iaddr, onInterface};
147 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
148 switch (iaddr.GetAddressFamily ()) {
149 case InternetAddress::AddressFamily::V4: {
150 ::ip_mreq m{};
151 m.imr_multiaddr = iaddr.As<in_addr> ();
152 m.imr_interface = onInterface.As<in_addr> ();
153 setsockopt (IPPROTO_IP, IP_DROP_MEMBERSHIP, m);
154 } break;
155 case InternetAddress::AddressFamily::V6: {
156 ::ipv6_mreq m{};
157 m.ipv6mr_multiaddr = iaddr.As<in6_addr> ();
158 m.ipv6mr_interface = 0; ///??? seems to mean any
159 setsockopt (IPPROTO_IPV6, IPV6_LEAVE_GROUP, m);
160 } break;
161 default:
163 }
164 }
165 virtual uint8_t GetMulticastTTL () const override
166 {
167 AssertExternallySynchronizedMutex::ReadContext declareContext{this->fThisAssertExternallySynchronized};
168 switch (GetAddressFamily ()) {
169 case SocketAddress::INET: {
170 return getsockopt<uint8_t> (IPPROTO_IP, IP_MULTICAST_TTL);
171 }
172 case SocketAddress::INET6: {
173 return getsockopt<uint8_t> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
174 }
175 default:
176 RequireNotReached (); // only legal for IP sockets
177 return 0;
178 }
179 }
180 virtual void SetMulticastTTL (uint8_t ttl) override
181 {
182 static constexpr Execution::Activity kSettingMulticastTTL{"setting multicast TTL"sv};
183 Execution::DeclareActivity activityDeclare{&kSettingMulticastTTL};
184 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
185 switch (GetAddressFamily ()) {
186 case SocketAddress::INET: {
187 setsockopt<uint8_t> (IPPROTO_IP, IP_MULTICAST_TTL, ttl);
188 break;
189 }
190 case SocketAddress::INET6: {
191 constexpr bool kIPV6LoophackMulticastTTLLinuxBug_{qStroika_Foundation_Common_Platform_Linux}; // http://stroika-bugs.sophists.com/browse/STK-578
192 if (kIPV6LoophackMulticastTTLLinuxBug_) {
193 try {
194 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl);
195 }
196 catch (const std::system_error& e) {
197 // I've dug into this, and have no idea why its failing - with EINVAL
198 if (e.code () == errc::invalid_argument) {
199 DbgTrace ("IPV6_MULTICAST_HOPS: For now ignoring what is probably a very small, minor bug, but one "
200 "where I have no idea why this is happening - but I saw reliably on Ubuntu/Linux"_f);
201 }
202 // @todo - fix this code - almost certainly wrong...
203 }
204 }
205 else {
206 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl);
207 }
208 break;
209 }
210 default:
211 RequireNotReached (); // only legal for IP sockets
212 }
213 }
214 virtual bool GetMulticastLoopMode () const override
215 {
216 AssertExternallySynchronizedMutex::ReadContext declareContext{this->fThisAssertExternallySynchronized};
217 switch (GetAddressFamily ()) {
218 case SocketAddress::INET: {
219 return !!getsockopt<char> (IPPROTO_IP, IP_MULTICAST_LOOP);
220 }
221 case SocketAddress::INET6: {
222 return !!getsockopt<char> (IPPROTO_IPV6, IP_MULTICAST_LOOP);
223 }
224 default:
225 RequireNotReached (); // only legal for IP sockets
226 return false;
227 }
228 }
229 virtual void SetMulticastLoopMode (bool loopMode) override
230 {
231 static constexpr Execution::Activity kSettingMulticastLoopMode{"setting multicast loop mode"sv};
232 Execution::DeclareActivity activityDeclare{&kSettingMulticastLoopMode};
233 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
234 switch (GetAddressFamily ()) {
235 case SocketAddress::INET: {
236 setsockopt<char> (IPPROTO_IP, IP_MULTICAST_LOOP, loopMode);
237 break;
238 }
239 case SocketAddress::INET6: {
240 constexpr bool kIPV6LoophackMulticastModeLinuxBug_{qStroika_Foundation_Common_Platform_Linux}; // http://stroika-bugs.sophists.com/browse/STK-578
241 if (kIPV6LoophackMulticastModeLinuxBug_) {
242 try {
243 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_LOOP, loopMode);
244 }
245 catch (const std::system_error& e) {
246 // I've dug into this, and have no idea why its failing - with EINVAL
247 if (e.code () == errc::invalid_argument) {
248 DbgTrace ("IPV6_MULTICAST_LOOP: For now ignoring what is probably a very small, minor bug, but one "
249 "where I have no idea why this is happening - but I saw reliably on Ubuntu/Linux"_f);
250 }
251 // @todo - fix this code - almost certainly wrong...
252 }
253 }
254 else {
255 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_LOOP, loopMode);
256 }
257 break;
258 }
259 default:
260 RequireNotReached (); // only legal for IP sockets
261 }
262 }
263 };
264}
265
266/*
267 ********************************************************************************
268 ************************** ConnectionlessSocket ********************************
269 ********************************************************************************
270 */
271ConnectionlessSocket::Ptr ConnectionlessSocket::New (SocketAddress::FamilyType family, Type socketKind, const optional<IPPROTO>& protocol)
272{
273 Require (socketKind != Type::STREAM); // use ConnectionOrientedStreamSocket or ConnectionOrientedMasterSocket
274 return Ptr{make_shared<Rep_> (_Protected::mkLowLevelSocket_ (family, socketKind, protocol))};
275}
276
278{
279 return Ptr{make_shared<Rep_> (sd)};
280}
#define AssertNotImplemented()
Definition Assertions.h:401
#define RequireNotReached()
Definition Assertions.h:385
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
nonvirtual constexpr AddressFamily GetAddressFamily() const
FamilyType
Socket address family - also sometimes referred to as domain (argument to ::socket calls it domain)
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Handle_ErrNoResultInterruption(CALL call) -> decltype(call())
Handle UNIX EINTR system call behavior - fairly transparently - just effectively removes them from th...
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)