Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
ConnectionlessSocket.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. 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"
12
13#include "Socket-Private_.h"
14
15#include "ConnectionlessSocket.h"
16
17// Comment this in to turn on aggressive noisy DbgTrace in this module
18//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
19using std::byte;
20
21using namespace Stroika::Foundation;
23using namespace Stroika::Foundation::Execution;
24using namespace Stroika::Foundation::Memory;
25using namespace Stroika::Foundation::IO;
27
28using namespace Stroika::Foundation::IO::Network::PRIVATE_;
29
31
32namespace {
33 struct Rep_ : BackSocketImpl_<ConnectionlessSocket::_IRep> {
34 using inherited = BackSocketImpl_<ConnectionlessSocket::_IRep>;
36 : inherited{sd}
37 {
38 }
39 virtual void SendTo (const byte* start, const byte* end, const SocketAddress& sockAddr) override
40 {
41#if USE_NOISY_TRACE_IN_THIS_MODULE_
42 Debug::TraceContextBumper ctx{"IO::Network::Socket...rep...::SendTo", "end-start={}, sockAddr={}"_f,
43 static_cast<long long> (end - start), sockAddr};
44#endif
45 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
46 sockaddr_storage sa = sockAddr.As<sockaddr_storage> ();
47#if qStroika_Foundation_Common_Platform_POSIX
48 Handle_ErrNoResultInterruption ([this, &start, &end, &sa, &sockAddr] () -> int {
49 return ::sendto (fSD_, reinterpret_cast<const char*> (start), end - start, 0, reinterpret_cast<sockaddr*> (&sa),
50 sockAddr.GetRequiredSize ());
51 });
52#elif qStroika_Foundation_Common_Platform_Windows
53 Require (end - start < numeric_limits<int>::max ());
54 ThrowWSASystemErrorIfSOCKET_ERROR (::sendto (fSD_, reinterpret_cast<const char*> (start), static_cast<int> (end - start), 0,
55 reinterpret_cast<sockaddr*> (&sa), static_cast<int> (sockAddr.GetRequiredSize ())));
56#else
58#endif
59 }
60 virtual size_t ReceiveFrom (byte* intoStart, byte* intoEnd, int flag, SocketAddress* fromAddress, Time::DurationSeconds timeout) override
61 {
62 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
63
64 if constexpr (qStroika_Foundation_Common_Platform_Windows) {
65 // TMPHACK for - http://stroika-bugs.sophists.com/browse/STK-964
66 auto s = Execution::WaitForIOReady{fSD_}.WaitQuietly (timeout);
68 if (s.empty ()) {
69 Execution::Throw (Execution::TimeOutException::kThe);
70 }
71 }
72
73 // 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
74 // cleaner...
75 constexpr Time::DurationSeconds kMaxPolltime_{numeric_limits<int>::max () / 1000.0};
76 if (timeout < kMaxPolltime_) {
77 int timeout_millisecs = Math::Round<int> (timeout.count () * 1000);
78 pollfd pollData{};
79 pollData.fd = fSD_;
80 pollData.events = POLLIN;
81#if qStroika_Foundation_Common_Platform_Windows
82 int nresults;
83 if ((nresults = ::WSAPoll (&pollData, 1, timeout_millisecs)) == SOCKET_ERROR) {
84 Execution::ThrowSystemErrNo (::WSAGetLastError ());
85 }
86#else
87 int nresults = Handle_ErrNoResultInterruption ([&] () { return ::poll (&pollData, 1, timeout_millisecs); });
88#endif
89 if (nresults == 0) [[unlikely]] {
90 Execution::Throw (Execution::TimeOutException::kThe);
91 }
92 }
93
94 struct sockaddr_storage sa;
95 socklen_t salen = sizeof (sa);
96#if qStroika_Foundation_Common_Platform_POSIX
97 size_t result = static_cast<size_t> (Handle_ErrNoResultInterruption ([&] () -> int {
98 return ::recvfrom (fSD_, reinterpret_cast<char*> (intoStart), intoEnd - intoStart, flag,
99 fromAddress == nullptr ? nullptr : reinterpret_cast<sockaddr*> (&sa), fromAddress == nullptr ? nullptr : &salen);
100 }));
101 if (fromAddress != nullptr) {
102 *fromAddress = sa;
103 }
104 return result;
105#elif qStroika_Foundation_Common_Platform_Windows
106 Require (intoEnd - intoStart < numeric_limits<int>::max ());
107 size_t result = static_cast<size_t> (ThrowWSASystemErrorIfSOCKET_ERROR (
108 ::recvfrom (fSD_, reinterpret_cast<char*> (intoStart), static_cast<int> (intoEnd - intoStart), flag,
109 fromAddress == nullptr ? nullptr : reinterpret_cast<sockaddr*> (&sa), fromAddress == nullptr ? nullptr : &salen)));
110 if (fromAddress != nullptr) {
111 *fromAddress = sa;
112 }
113 return result;
114#else
116#endif
117 }
118 virtual void JoinMulticastGroup (const InternetAddress& iaddr, const InternetAddress& onInterface) override
119 {
120 Debug::TraceContextBumper ctx{"IO::Network::Socket::JoinMulticastGroup",
121 Stroika_Foundation_Debug_OptionalizeTraceArgs ("iaddr={} onInterface={}"_f, iaddr, onInterface)};
122 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
123 Assert (iaddr.GetAddressFamily () == InternetAddress::AddressFamily::V4 or iaddr.GetAddressFamily () == InternetAddress::AddressFamily::V6);
124 auto activity = Execution::LazyEvalActivity{[&] () -> Characters::String {
125 return "joining multicast group "sv + Characters::ToString (iaddr) + " on interface "sv + Characters::ToString (onInterface);
126 }};
127 Execution::DeclareActivity activityDeclare{&activity};
128 switch (iaddr.GetAddressFamily ()) {
129 case InternetAddress::AddressFamily::V4: {
130 ::ip_mreq m{};
131 m.imr_multiaddr = iaddr.As<in_addr> ();
132 m.imr_interface = onInterface.As<in_addr> ();
133 setsockopt (IPPROTO_IP, IP_ADD_MEMBERSHIP, m);
134 } break;
135 case InternetAddress::AddressFamily::V6: {
136 ::ipv6_mreq m{};
137 m.ipv6mr_multiaddr = iaddr.As<in6_addr> ();
138 m.ipv6mr_interface = 0; //??? seems to mean any
139 setsockopt (IPPROTO_IPV6, IPV6_JOIN_GROUP, m);
140 } break;
141 default:
143 }
144 }
145 virtual void LeaveMulticastGroup (const InternetAddress& iaddr, const InternetAddress& onInterface) override
146 {
147 Debug::TraceContextBumper ctx{"IO::Network::Socket::LeaveMulticastGroup", "iaddr={} onInterface={}"_f, iaddr, onInterface};
148 AssertExternallySynchronizedMutex::WriteContext declareContext{fThisAssertExternallySynchronized};
149 switch (iaddr.GetAddressFamily ()) {
150 case InternetAddress::AddressFamily::V4: {
151 ::ip_mreq m{};
152 m.imr_multiaddr = iaddr.As<in_addr> ();
153 m.imr_interface = onInterface.As<in_addr> ();
154 setsockopt (IPPROTO_IP, IP_DROP_MEMBERSHIP, m);
155 } break;
156 case InternetAddress::AddressFamily::V6: {
157 ::ipv6_mreq m{};
158 m.ipv6mr_multiaddr = iaddr.As<in6_addr> ();
159 m.ipv6mr_interface = 0; ///??? seems to mean any
160 setsockopt (IPPROTO_IPV6, IPV6_LEAVE_GROUP, m);
161 } break;
162 default:
164 }
165 }
166 virtual uint8_t GetMulticastTTL () const override
167 {
168 AssertExternallySynchronizedMutex::ReadContext declareContext{this->fThisAssertExternallySynchronized};
169 switch (GetAddressFamily ()) {
170 case SocketAddress::INET: {
171 return getsockopt<uint8_t> (IPPROTO_IP, IP_MULTICAST_TTL);
172 }
173 case SocketAddress::INET6: {
174 return getsockopt<uint8_t> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
175 }
176 default:
177 RequireNotReached (); // only legal for IP sockets
178 return 0;
179 }
180 }
181 virtual void SetMulticastTTL (uint8_t ttl) override
182 {
183 static constexpr Execution::Activity kSettingMulticastTTL{"setting multicast TTL"sv};
184 Execution::DeclareActivity activityDeclare{&kSettingMulticastTTL};
185 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
186 switch (GetAddressFamily ()) {
187 case SocketAddress::INET: {
188 setsockopt<uint8_t> (IPPROTO_IP, IP_MULTICAST_TTL, ttl);
189 break;
190 }
191 case SocketAddress::INET6: {
192 constexpr bool kIPV6LoophackMulticastTTLLinuxBug_{qStroika_Foundation_Common_Platform_Linux}; // http://stroika-bugs.sophists.com/browse/STK-578
193 if (kIPV6LoophackMulticastTTLLinuxBug_) {
194 try {
195 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl);
196 }
197 catch (const std::system_error& e) {
198 // I've dug into this, and have no idea why its failing - with EINVAL
199 if (e.code () == errc::invalid_argument) {
200 DbgTrace ("IPV6_MULTICAST_HOPS: For now ignoring what is probably a very small, minor bug, but one "
201 "where I have no idea why this is happening - but I saw reliably on Ubuntu/Linux"_f);
202 }
203 // @todo - fix this code - almost certainly wrong...
204 }
205 }
206 else {
207 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_HOPS, ttl);
208 }
209 break;
210 }
211 default:
212 RequireNotReached (); // only legal for IP sockets
213 }
214 }
215 virtual bool GetMulticastLoopMode () const override
216 {
217 AssertExternallySynchronizedMutex::ReadContext declareContext{this->fThisAssertExternallySynchronized};
218 switch (GetAddressFamily ()) {
219 case SocketAddress::INET: {
220 return !!getsockopt<char> (IPPROTO_IP, IP_MULTICAST_LOOP);
221 }
222 case SocketAddress::INET6: {
223 return !!getsockopt<char> (IPPROTO_IPV6, IP_MULTICAST_LOOP);
224 }
225 default:
226 RequireNotReached (); // only legal for IP sockets
227 return false;
228 }
229 }
230 virtual void SetMulticastLoopMode (bool loopMode) override
231 {
232 static constexpr Execution::Activity kSettingMulticastLoopMode{"setting multicast loop mode"sv};
233 Execution::DeclareActivity activityDeclare{&kSettingMulticastLoopMode};
234 AssertExternallySynchronizedMutex::WriteContext declareContext{this->fThisAssertExternallySynchronized};
235 switch (GetAddressFamily ()) {
236 case SocketAddress::INET: {
237 setsockopt<char> (IPPROTO_IP, IP_MULTICAST_LOOP, loopMode);
238 break;
239 }
240 case SocketAddress::INET6: {
241 constexpr bool kIPV6LoophackMulticastModeLinuxBug_{qStroika_Foundation_Common_Platform_Linux}; // http://stroika-bugs.sophists.com/browse/STK-578
242 if (kIPV6LoophackMulticastModeLinuxBug_) {
243 try {
244 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_LOOP, loopMode);
245 }
246 catch (const std::system_error& e) {
247 // I've dug into this, and have no idea why its failing - with EINVAL
248 if (e.code () == errc::invalid_argument) {
249 DbgTrace ("IPV6_MULTICAST_LOOP: For now ignoring what is probably a very small, minor bug, but one "
250 "where I have no idea why this is happening - but I saw reliably on Ubuntu/Linux"_f);
251 }
252 // @todo - fix this code - almost certainly wrong...
253 }
254 }
255 else {
256 setsockopt<char> (IPPROTO_IPV6, IPV6_MULTICAST_LOOP, loopMode);
257 }
258 break;
259 }
260 default:
261 RequireNotReached (); // only legal for IP sockets
262 }
263 }
264 };
265}
266
267/*
268 ********************************************************************************
269 ************************** ConnectionlessSocket ********************************
270 ********************************************************************************
271 */
272ConnectionlessSocket::Ptr ConnectionlessSocket::New (SocketAddress::FamilyType family, Type socketKind, const optional<IPPROTO>& protocol)
273{
274 Require (socketKind != Type::STREAM); // use ConnectionOrientedStreamSocket or ConnectionOrientedMasterSocket
275 return Ptr{Memory::MakeSharedPtr<Rep_> (_Protected::mkLowLevelSocket_ (family, socketKind, protocol))};
276}
277
279{
280 return Ptr{Memory::MakeSharedPtr<Rep_> (sd)};
281}
#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)
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)