Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
DNS.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cstdio>
7
8#if qStroika_Foundation_Common_Platform_POSIX
9#include <netdb.h>
10#include <sys/socket.h>
11#include <unistd.h>
12#elif qStroika_Foundation_Common_Platform_Windows
13#include <WinSock2.h>
14
15#include <WS2tcpip.h>
16#endif
17
20#include "Stroika/Foundation/Containers/Collection.h"
21#include "Stroika/Foundation/Execution/Exceptions.h"
23#if qStroika_Foundation_Common_Platform_Windows
24#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
25#include "Stroika/Foundation/IO/Network/Platform/Windows/WinSock.h"
26#endif
27#include "Stroika/Foundation/Execution/Exceptions.h"
28#include "Stroika/Foundation/IO/Network/SocketAddress.h"
29
30#include "DNS.h"
31
32using namespace Stroika::Foundation;
35using namespace Stroika::Foundation::Execution;
36using namespace Stroika::Foundation::Memory;
37using namespace Stroika::Foundation::IO;
39
40#if qStroika_Foundation_Common_Platform_Windows
41// API should return char* but MSFT returns WIDECHARS sometimes - undo that
42#undef gai_strerror
43#define gai_strerror gai_strerrorA
44#endif // qW
45
46// Comment this in to turn on aggressive noisy DbgTrace in this module
47//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
48
49namespace {
50 // @todo - somewhat rough draft - not sure if we need better default_error_condition, or equivilant() overrides
51 class getaddrinfo_error_category_ : public error_category { // categorize an error
52 public:
53 virtual const char* name () const noexcept override
54 {
55 return "DNS error"; // used to return return "getaddrinfo"; - but the name DNS is more widely recognized, and even though this could be from another source, this name is more clear
56 }
57 virtual error_condition default_error_condition (int ev) const noexcept override
58 {
59 switch (ev) {
60#if EAI_ADDRFAMILY
61 case EAI_ADDRFAMILY:
62 return std::error_condition{errc::address_family_not_supported}; // best approximartion I can find
63#endif
64#if EAI_NONAME
65 case EAI_NONAME:
66 return error_condition{errc::no_such_device}; // best approximartion I can find
67#endif
68#if EAI_MEMORY
69 case EAI_MEMORY:
70 return error_condition{errc::not_enough_memory};
71#endif
72 }
73 return error_condition{errc::bad_message}; // no idea what to return here
74 }
75 virtual string message (int _Errval) const override
76 {
77 // On visual studio - vs2k17 - we get a spurrious space at the end of the message. This
78 // isn't called for by the spec - http://pubs.opengroup.org/onlinepubs/007904875/functions/gai_strerror.html
79 // but isn't prohibited either. Strip it.
80 const char* result = ::gai_strerror (_Errval);
81 while (isspace (*result)) {
82 ++result;
83 }
84 const char* e = result + ::strlen (result);
85 while (result < e and isspace (*(e - 1))) {
86 e--;
87 }
88 return string{result, e};
89 }
90 };
91 const error_category& DNS_error_category () noexcept
92 {
93 return Common::Immortalize<getaddrinfo_error_category_> ();
94 }
95}
96
97/*
98 ********************************************************************************
99 ************************** Network::GetInterfaces ******************************
100 ********************************************************************************
101 */
103{
104 static const DNS kDefaultDNS_;
105 return kDefaultDNS_;
106}
107
108DNS::DNS ()
109{
110#if qStroika_Foundation_Common_Platform_Windows
111 IO::Network::Platform::Windows::WinSock::AssureStarted ();
112#endif
113}
114
115DNS::HostEntry DNS::GetHostEntry (const String& hostNameOrAddress) const
116{
117#if USE_NOISY_TRACE_IN_THIS_MODULE_
118 Debug::TraceContextBumper ctx{"DNS::HostEntry DNS::GetHostEntry", "hostNameOrAddress={}"_f, hostNameOrAddress};
119#endif
120 HostEntry result;
121
122 addrinfo hints{};
123 hints.ai_family = AF_UNSPEC;
124 hints.ai_socktype = SOCK_STREAM;
125 hints.ai_flags = AI_CANONNAME;
126#if defined(AI_IDN)
127 hints.ai_flags |= AI_IDN;
128#endif
129#if defined(AI_CANONIDN)
130 hints.ai_flags |= AI_CANONIDN;
131#endif
132 string tmp = hostNameOrAddress.AsUTF8<string> (); // BAD - SB tstring - or??? not sure what... - I think need to map to Punycode
133 if (not tmp.empty () and tmp[0] == '[' and tmp[tmp.size () - 1] == ']' and isdigit (tmp[1])) {
134 // only allowed [] around numeric ip addresses
135 tmp = tmp.substr (1, tmp.size () - 2);
136 }
137 addrinfo* res = nullptr;
138 int errCode = ::getaddrinfo (tmp.c_str (), nullptr, &hints, &res);
139 [[maybe_unused]] auto&& cleanup = Execution::Finally ([res] () noexcept { ::freeaddrinfo (res); });
140 if (errCode != 0) {
141 // @todo - I think we need to capture erron as well if errCode == EAI_SYSTEM (see http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)
142 Throw (SystemErrorException (errCode, DNS_error_category ()));
143 }
144 AssertNotNull (res); // else would have thrown
145
146 // @todo proplerly support http://www.ietf.org/rfc/rfc3987.txt and UTF8 etc.
147 // See http://linux.die.net/man/3/getaddrinfo for info on glibc support for AI_IDN etc..
148 // and how todo on windows (or do myself portably?)
149 // MAYBER done OK?
150 //
151 // NI_IDN -- If this flag is used, then the name found in the lookup process is converted from IDN format
152 // to the locale's encoding if necessary. ASCII-only names are not affected by the conversion, which makes
153 // this flag usable in existing programs and environments.
154 //
155
156 if (res->ai_canonname != nullptr) {
157 // utf8 part a WAG
158 result.fCanonicalName = String::FromUTF8 (res->ai_canonname);
159 }
160
161 for (addrinfo* i = res; i != nullptr; i = i->ai_next) {
162 if (i != res and i->ai_canonname != nullptr and i->ai_canonname[0] != '\0') {
163 result.fAliases += String::FromUTF8 (i->ai_canonname);
164 }
165 SocketAddress sa{*i->ai_addr};
166 if (sa.IsInternetAddress ()) {
167 result.fAddressList += sa.GetInternetAddress ();
168 }
169 }
170
171#if USE_NOISY_TRACE_IN_THIS_MODULE_
172 DbgTrace (L"Lookup(%s)", hostNameOrAddress.c_str ());
173 DbgTrace (L"CANONNAME: %s", result.fCanonicalName.c_str ());
174 for (const String& i : result.fAliases) {
175 DbgTrace (L" ALIAS: %s", i.c_str ());
176 }
177 for (const InternetAddress& i : result.fAddressList) {
178 DbgTrace (L" ADDR: %s", i.As<String> ().c_str ());
179 }
180#endif
181 return result;
182}
183
184optional<String> DNS::ReverseLookup (const InternetAddress& address) const
185{
186#if USE_NOISY_TRACE_IN_THIS_MODULE_
187 Debug::TraceContextBumper ctx{"DNS::HostEntry DNS::ReverseLookup", "address={}"_f, address};
188#endif
189 char hbuf[NI_MAXHOST];
190 SocketAddress sa{address, 0};
191 sockaddr_storage sadata = sa.As<sockaddr_storage> ();
192 int flags = NI_NAMEREQD;
193#if defined(NI_IDN)
194 flags |= NI_IDN;
195#endif
196 int errCode = ::getnameinfo (reinterpret_cast<const sockaddr*> (&sadata), static_cast<socklen_t> (sa.GetRequiredSize ()), hbuf,
197 sizeof (hbuf), NULL, 0, flags);
198 switch (errCode) {
199 case 0:
200 //@todo handle I18N more carefully
201 // NI_IDN -- If this flag is used, then the name found in the lookup process is converted from IDN format
202 // to the locale's encoding if necessary. ASCII-only names are not affected by the conversion, which makes
203 // this flag usable in existing programs and environments.
204 return String::FromUTF8 (hbuf);
205 case EAI_NONAME:
206 return {};
207 default:
208 Throw (SystemErrorException{errCode, DNS_error_category ()});
209 }
210}
211
212optional<String> DNS::QuietReverseLookup (const InternetAddress& address) const
213{
214#if USE_NOISY_TRACE_IN_THIS_MODULE_
215 Debug::TraceContextBumper ctx{"DNS::HostEntry DNS::ReverseLookup", "address={}"_f, address};
216#endif
217 char hbuf[NI_MAXHOST];
218 SocketAddress sa{address, 0};
219 sockaddr_storage sadata = sa.As<sockaddr_storage> ();
220 int flags = NI_NAMEREQD;
221#if defined(NI_IDN)
222 flags |= NI_IDN;
223#endif
224 int errCode = ::getnameinfo (reinterpret_cast<const sockaddr*> (&sadata), static_cast<socklen_t> (sa.GetRequiredSize ()), hbuf,
225 sizeof (hbuf), nullptr, 0, flags);
226 switch (errCode) {
227 case 0:
228 //@todo handle I18N more carefully
229 // NI_IDN -- If this flag is used, then the name found in the lookup process is converted from IDN format
230 // to the locale's encoding if necessary. ASCII-only names are not affected by the conversion, which makes
231 // this flag usable in existing programs and environments.
232 return String::FromUTF8 (hbuf);
233 default:
234 return {};
235 }
236}
237
239{
240#if USE_NOISY_TRACE_IN_THIS_MODULE_
242 {"DNS::HostEntry DNS::GetHostAddresses", "address={}"_f, address);
243#endif
244 return GetHostEntry (hostNameOrAddress).fAddressList;
245 }
246
248 {
249#if USE_NOISY_TRACE_IN_THIS_MODULE_
250 Debug::TraceContextBumper ctx{"DNS::HostEntry DNS::GetHostAddresses", "address={}, family={}"_f, address, family};
251#endif
252 auto h = GetHostEntry (hostNameOrAddress).fAddressList;
253 for (auto i = h.begin (); i != h.end (); ++i) {
254 if (i->GetAddressFamily () != family) {
255 h.Remove (i);
256 }
257 }
258 return h;
259 }
260
261 InternetAddress DNS::GetHostAddress (const String& hostNameOrAddress) const
262 {
263#if USE_NOISY_TRACE_IN_THIS_MODULE_
264 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs (L"DNS::HostEntry DNS::GetHostAddresses", L"address=%s",
265 Characters::ToString (address).c_str ())};
266#endif
267 auto h = GetHostEntry (hostNameOrAddress).fAddressList;
268 if (h.empty ()) {
269 Execution::Throw (RuntimeErrorException{"No associated addresses"sv});
270 }
271 return h[0];
272 }
273
274 InternetAddress DNS::GetHostAddress (const String& hostNameOrAddress, InternetAddress::AddressFamily family) const
275 {
276#if USE_NOISY_TRACE_IN_THIS_MODULE_
277 Debug::TraceContextBumper ctx{"DNS::HostEntry DNS::GetHostAddresses", "address={}, family={}"_f, address family};
278#endif
279 auto h = GetHostEntry (hostNameOrAddress).fAddressList;
280 for (auto i = h.begin (); i != h.end (); ++i) {
281 if (i->GetAddressFamily () != family) {
282 h.Remove (i);
283 }
284 }
285 if (h.empty ()) {
286 Execution::Throw (RuntimeErrorException{"No associated addresses"sv});
287 }
288 return h[0];
289 }
#define AssertNotNull(p)
Definition Assertions.h:333
#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
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1055
static String FromUTF8(span< CHAR_T > from)
Definition String.inl:420
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< String > ReverseLookup(const InternetAddress &address) const
Definition DNS.cpp:184
nonvirtual optional< String > QuietReverseLookup(const InternetAddress &address) const
Definition DNS.cpp:212
nonvirtual InternetAddress GetHostAddress(const String &hostNameOrAddress) const
simple wrapper on GetHostEntry.
Definition DNS.cpp:261
nonvirtual HostEntry GetHostEntry(const String &hostNameOrAddress) const
Definition DNS.cpp:115
nonvirtual Sequence< InternetAddress > GetHostAddresses(const String &hostNameOrAddress) const
simple wrapper on GetHostEntry - looking up the hostname/ip address and returning the list of associa...
Definition DNS.cpp:238
nonvirtual InternetAddress GetInternetAddress() const
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 Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31