PageRenderTime 64ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/Library/Sources/Stroika/Foundation/IO/Network/DNS.cpp

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