UPnPsdk 0.1
Universal Plug and Play +, Software Development Kit
 
Loading...
Searching...
No Matches
addrinfo.cpp
Go to the documentation of this file.
1// Copyright (C) 2023+ GPL 3 and higher by Ingo Höft, <Ingo@Hoeft-online.de>
2// Redistribution only with this Copyright remark. Last modified: 2026-03-20
9#include <UPnPsdk/synclog.hpp>
10#include <UPnPsdk/sockaddr.hpp>
11
12#include <umock/netdb.hpp>
14#include <cstring>
16
17
18namespace UPnPsdk {
19namespace {
20
25int getaddrinfo(const std::string& a_node, const std::string& a_service,
26 addrinfo a_hints, addrinfo*& a_res) {
27 TRACE("Executing UPnPsdk::getaddrinfo()")
28#ifdef _MSC_VER
29 // Correct result for V4MAPPED unspecified address "0.0.0.0". On win32
30 // getaddrinfo() fails with this conversion. To be compatible with other
31 // platforms the conversion is emulated. Only little quirk is that the IPv4
32 // mapped IPv6 is shown as "[::ffff:0:0] (hex format) instead of usual
33 // "[::ffff:0.0.0.0]" (num base 10 format). But it shouldn't harm because
34 // it is only a different view of the same binary address value.
35 // getnameinfo() fails to show it with num base 10 values.
36 if ((a_hints.ai_flags & AI_V4MAPPED) && a_hints.ai_family == AF_INET6) {
37 if (a_node == "0.0.0.0") {
38 addrinfo hints{}, *res{nullptr};
39 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
40 hints.ai_family = AF_INET6;
41 hints.ai_socktype = a_hints.ai_socktype;
42 hints.ai_protocol = a_hints.ai_protocol;
43 int ret = umock::netdb_h.getaddrinfo(nullptr, a_service.c_str(),
44 &hints, &res);
45 if (ret != 0)
46 return ret;
47 if (res->ai_next != nullptr)
48 return EAI_NONAME; // "No such host is known. "
49
50 in6_addr& sa6 =
51 reinterpret_cast<sockaddr_in6*>(res->ai_addr)->sin6_addr;
52 sa6.s6_addr[10] = 0xff; // prefix id for ipv4 mapping.
53 sa6.s6_addr[11] = 0xff; // ./.
54
55 // Reset flag to have same result. See note below.
56 a_hints.ai_flags = a_hints.ai_flags & ~AI_NUMERICHOST;
57
58 a_res = res;
59 return 0;
60 }
61
62 // In contrast to other platforms ::getaddrinfo() on win32 does not
63 // create AI_V4MAPPED addresses with AI_NUMERICHOST set. This is to
64 // workaround it. The SDK only uses IPv6 addresses. All IPv4 addresses
65 // are mapped to IPv6. There is only one combination with AF_INET6 and
66 // no AI_NUMERICHOST where win32 do AI_V4MAPPED. All others fail. See
67 // Unit Test 'GetaddrinfoWin32Test how_it_works'.
68 if (!a_node.empty() &&
69 !std::isalpha(a_node.front())) // No alpha-num name
70 a_hints.ai_flags =
71 a_hints.ai_flags & ~AI_NUMERICHOST; // Reset flag.
72 }
73#endif
74
75 return umock::netdb_h.getaddrinfo(
76 (a_node.empty() ? nullptr : a_node.c_str()),
77 (a_service.empty() ? nullptr : a_service.c_str()), &a_hints, &a_res);
78}
79
80} // anonymous namespace
81
82
83// CAddrinfo class to encapsulate ::getaddrinfo() system calls
84// ===========================================================
85// On Microsoft Windows this needs to have Windows Sockets initialized with
86// WSAStartup().
87
88// Constructor for getting an address information with service name that can
89// also be a port number string.
90// -------------------------------------------------------------------------
91CAddrinfo::CAddrinfo(std::string_view a_node, //
92 std::string_view a_service, //
93 const int a_flags, //
94 const int a_socktype)
95 : m_node(a_node), m_service(a_service) {
96 TRACE2(this, " Construct CAddrinfo() with extra service")
97 // I cannot use the initialization list of the constructor because the
98 // member order in the structure addrinfo is different on Linux, MacOS and
99 // win32. I have to use the member names to initialize them, what's not
100 // possible for structures in the constructors initialization list.
101 m_hints.ai_socktype = a_socktype;
102 // Due to specification IPv4 addresses are always mapped to IPv6.
103 m_hints.ai_family = AF_INET6;
104 m_hints.ai_flags = AI_V4MAPPED | a_flags;
105}
106
107// Constructor for getting an address information from only a netaddress.
108// ----------------------------------------------------------------------
109CAddrinfo::CAddrinfo(std::string_view a_node, //
110 const int a_flags, //
111 const int a_socktype)
112 : m_node(a_node), m_service(a_node == "" ? "0" : "") {
113 TRACE2(this, " Construct CAddrinfo() with netaddress")
114 // I cannot use the initialization list of the constructor because the
115 // member order in the structure addrinfo is different on Linux, MacOS and
116 // win32. I have to use the member names to initialize them, what's not
117 // possible for structures in the constructors initialization list.
118 m_hints.ai_socktype = a_socktype;
119 // Due to specification IPv4 addresses are always mapped to IPv6.
120 m_hints.ai_family = AF_INET6;
121 m_hints.ai_flags = AI_V4MAPPED | a_flags;
122}
123
124
125// Destructor
126// ----------
128CAddrinfo::~CAddrinfo() {
129 TRACE2(this, " Destruct CAddrinfo()")
130 this->free_addrinfo();
131}
133
134// Private method to free allocated memory for address information
135// ---------------------------------------------------------------
136void CAddrinfo::free_addrinfo() noexcept {
137 if (m_res != &m_hints) {
138 TRACE2("syscall ::freeaddrinfo() with m_res = ", m_res)
139 umock::netdb_h.freeaddrinfo(m_res);
140 m_res = &m_hints;
141 m_res_current = &m_hints;
142 }
143}
144
145
146// Member access operator ->
147// -------------------------
148const ::addrinfo* CAddrinfo::operator->() const noexcept {
149 return m_res_current;
150}
151
152
153// Getter for the first entry of an address info from the operating system
154// -----------------------------------------------------------------------
155// Get address information with cached hints.
157 TRACE2(this, " Executing CAddrinfo::get_first()")
158
159 // Prepare input for ::getaddrinfo()
160 std::string node, service;
161 try {
162 if (m_service.empty())
163 split_addr_port(m_node, node, service);
164 else
165 split_addr_port(m_node + ":" + m_service, node, service);
166 } catch (const std::range_error& ex) {
167 m_error_msg =
168 UPnPsdk_LOGWHAT "MSG1128: catched next line ...\n" + ex.what();
169
170 return false;
171 }
172
173 // syscall ::getaddrinfo() with prepared arguments
174 // -----------------------------------------------
175 ::addrinfo* new_res{nullptr}; // Result from ::getaddrinfo()
176 const int ret = UPnPsdk::getaddrinfo(node, service, m_hints, new_res);
177#ifndef __APPLE__
178 // std::format() since c++20 isn't supported on AppleClang 15, even on c++23
179 TRACE2(this, " syscall ::getaddrinfo(" + node + ", " + service +
180 ") with new_res = " +
181 std::format("{:#x}", reinterpret_cast<uintptr_t>(new_res)))
182#endif
183 if (g_dbug) {
184 // Very helpful for debugging to see what is given to ::getaddrinfo()
185 char addrStr[INET6_ADDRSTRLEN]{};
186 char servStr[NI_MAXSERV]{};
187 if (ret == 0)
188 ::getnameinfo(new_res->ai_addr,
189 static_cast<socklen_t>(new_res->ai_addrlen), addrStr,
190 sizeof(addrStr), servStr, sizeof(servStr),
191 NI_NUMERICHOST | NI_NUMERICSERV);
192 // clang-format off
193 UPnPsdk_LOGINFO("MSG1111") "syscall ::getaddrinfo("
194 << (node.empty() ? "nullptr, " : "\"" + node + "\", ")
195 << (service.empty() ? "nullptr, " : "\"" + service + "\", ")
196 << &m_hints << ", " << &new_res
197 << ") node=\"" << m_node << "\", "
198 << (m_hints.ai_flags & AI_V4MAPPED ? "AI_V4MAPPED, " : "")
199 << (m_hints.ai_flags & AI_NUMERICHOST ? "AI_NUMERICHOST, " : "")
200 << (m_hints.ai_flags & AI_NUMERICSERV ? "AI_NUMERICSERV, " : "")
201 << (m_hints.ai_flags & AI_PASSIVE ? "AI_PASSIVE, " : "")
202 << (m_hints.ai_family == AF_INET6 ? "AF_INET6" :
203 (m_hints.ai_family == AF_INET ? "AF_INET" :
204 (m_hints.ai_family == AF_UNSPEC ? "AF_UNSPEC" :
205 "m_hints.ai_family=" + std::to_string(m_hints.ai_family))))
206 << ", "
207 << (m_hints.ai_socktype == SOCK_STREAM ? "SOCK_STREAM" :
208 (m_hints.ai_socktype == SOCK_DGRAM ? "SOCK_DGRAM" :
209 (m_hints.ai_socktype == SOCK_RAW ? "SOCK_RAW" :
210 "socktype=" + std::to_string(m_hints.ai_socktype))))
211 << (ret != 0
212 ? ". Get EAI_ERROR(" + std::to_string(ret) + ")"
213 : ". Get first \"" + std::string(addrStr) + "\" port "
214 + std::string(servStr)
215 + (new_res->ai_next == nullptr ? ", no more entries." : ", more entries..."))
216 << '\n';
217 }
218
219 if (ret != 0) {
221 // Error numbers definded in netdb.h.
222 // Maybe an alphanumeric node name that cannot be resolved (e.g. by
223 // DNS)? Anyway, the user has to decide what to do. Because this
224 // depends on extern available DNS server the error can occur
225 // unexpectedly at any time. We have no influence on it but I will give
226 // an extended error message.
227 m_error_msg = UPnPsdk_LOGWHAT + "MSG1112: errid(" +
228 std::to_string(ret) + ")=\"" + ::gai_strerror(ret) + "\", " +
229 ((m_hints.ai_family == AF_UNSPEC) ? "IPv?_" :
230 ((m_hints.ai_family == AF_INET6) ? "IPv6_" : "IPv4_")) +
231 ((m_hints.ai_flags & AI_NUMERICHOST) ? "numeric_host=\"" : "alphanum_name=\"") +
232 m_node + "\", service=\"" +
233 m_service + "\"" +
234 ((m_hints.ai_flags & AI_PASSIVE) ? ", passive_listen" : "") +
235 ((m_hints.ai_flags & AI_NUMERICHOST) ? "" : ", (maybe DNS query temporary failed?)") +
236 ", ai_socktype=" + std::to_string(m_hints.ai_socktype) + "\n";
237 UPnPsdk_LOGERR("MSG1041") << m_error_msg << '\n';
238
239 return false;
240 }
241 // clang-format on
242
243 for (::addrinfo* res{new_res}; res != nullptr; res = res->ai_next) {
244 // Different on platforms: Ubuntu & MacOS return protocol number, win32
245 // returns 0. I just return what was used to call ::getaddrinfo().
246 res->ai_protocol = m_hints.ai_protocol;
247 //
248 // Different on platforms: Ubuntu returns set flags, MacOS & win32
249 // return 0. I just return what was used to call ::getaddrinfo().
250 res->ai_flags = m_hints.ai_flags;
251 }
252
253 // If get_first() is called the second time then m_res still points to the
254 // previous allocated memory. To avoid a memory leak it must be freed before
255 // pointing to the new allocated memory.
256 this->free_addrinfo();
257 // finaly point to the new address information from the operating
258 // system.
259 m_res = new_res;
260 m_res_current = new_res;
261
262 return true;
263}
264
265
266// Getter for the next available address information
267// -------------------------------------------------
268bool CAddrinfo::get_next() noexcept {
269 if (m_res_current->ai_next == nullptr) {
270 // It doesn't matter if already pointing to m_hints. m_hints->ai_next is
271 // also nullptr.
272 m_res_current = &m_hints;
273 return false;
274 }
275 m_res_current = m_res_current->ai_next;
276 return true;
277}
278
279
280// Get the socket address from current selcted address information
281// ---------------------------------------------------------------
283 if (m_res == &m_hints)
284 a_saddr = "";
285 else
286 a_saddr = reinterpret_cast<sockaddr_storage&>(*m_res_current->ai_addr);
287}
288
289
290// Get cached error message
291// ------------------------
292const std::string& CAddrinfo::what() const { return m_error_msg; }
293
294} // namespace UPnPsdk
Declaration of the Addrinfo class.
Get information from the operating system about an internet address.
Definition addrinfo.hpp:55
const ::addrinfo * operator->() const noexcept
Read access to members of the addrinfo structure
Definition addrinfo.cpp:148
bool get_next() noexcept
Get next available address information.
Definition addrinfo.cpp:268
bool get_first()
Get the first entry of an address info from the operating system.
Definition addrinfo.cpp:156
const std::string & what() const
Get cached error message.
Definition addrinfo.cpp:292
CAddrinfo(std::string_view a_node, std::string_view a_service, const int a_flags=0, const int a_socktype=SOCK_STREAM)
Constructor for getting an address information with service name.
Definition addrinfo.cpp:91
void sockaddr(SSockaddr &a_saddr)
Get the socket address from current selcted address information.
Definition addrinfo.cpp:282
void split_addr_port(std::string_view a_addr_str, std::string &a_addr, std::string &a_serv)
Free function to split inet address and port(service)
Definition sockaddr.cpp:185
int getaddrinfo(const std::string &a_node, const std::string &a_service, addrinfo a_hints, addrinfo *&a_res)
Wrapper to emulate missing functionality to be compatible between different platforms.
Definition addrinfo.cpp:25
Reengineered Object Oriented UPnP+ program code.
UPnPsdk_EXTERN bool g_dbug
Switch to enable verbose (debug) output.
Definition global.hpp:26
Declaration of the Sockaddr class and some free helper functions.
Trivial ::sockaddr structures enhanced with methods.
Definition sockaddr.hpp:133
Define macro for synced logging to the console for detailed info and debug.