UPnPsdk 0.1
Universal Plug and Play +, Software Development Kit
 
Loading...
Searching...
No Matches
sockaddr.cpp
Go to the documentation of this file.
1// Copyright (C) 2022+ GPL 3 and higher by Ingo Höft, <Ingo@Hoeft-online.de>
2// Redistribution only with this Copyright remark. Last modified: 2026-03-10
9#include <UPnPsdk/synclog.hpp>
10#include <umock/netdb.hpp>
12#include <algorithm>
13#include <regex>
15
16namespace UPnPsdk {
17
18namespace {
19
20// Free function to logical compare two sockaddr structures
21// --------------------------------------------------------
32bool sockaddrcmp(const ::sockaddr_storage* a_ss1,
33 const ::sockaddr_storage* a_ss2) noexcept {
34 // Throws no exception.
35 if (a_ss1 == nullptr && a_ss2 == nullptr)
36 return true;
37 if (a_ss1 == nullptr || a_ss2 == nullptr)
38 return false;
39
40 switch (a_ss1->ss_family) {
41 case AF_UNSPEC:
42 if (a_ss2->ss_family != AF_UNSPEC)
43 return false;
44 break;
45
46 case AF_INET6: {
47 // I compare ipv6 addresses which are stored in a 16 byte array
48 // (unsigned char s6_addr[16]). So we have to use memcmp() for
49 // comparison.
50 const ::sockaddr_in6* const s6_addr1 =
51 reinterpret_cast<const ::sockaddr_in6*>(a_ss1);
52 const ::sockaddr_in6* const s6_addr2 =
53 reinterpret_cast<const ::sockaddr_in6*>(a_ss2);
54
55 if (a_ss2->ss_family != AF_INET6 ||
56 ::memcmp(&s6_addr1->sin6_addr, &s6_addr2->sin6_addr,
57 sizeof(in6_addr)) != 0 ||
58 s6_addr1->sin6_port != s6_addr2->sin6_port ||
59 s6_addr1->sin6_scope_id != s6_addr2->sin6_scope_id)
60 return false;
61 } break;
62
63 case AF_INET: {
64 const ::sockaddr_in* const s_addr1 =
65 reinterpret_cast<const ::sockaddr_in*>(a_ss1);
66 const ::sockaddr_in* const s_addr2 =
67 reinterpret_cast<const ::sockaddr_in*>(a_ss2);
68
69 if (a_ss2->ss_family != AF_INET ||
70 s_addr1->sin_addr.s_addr != s_addr2->sin_addr.s_addr ||
71 s_addr1->sin_port != s_addr2->sin_port)
72 return false;
73 } break;
74
75 default:
76 return false;
77 }
78
79 return true;
80}
81
82} // anonymous namespace
83
84
85// Free function to check if a string represents a valid port number
86// -----------------------------------------------------------------
89int to_port(std::string_view a_port_str, in_port_t* const a_port_num) noexcept {
90 TRACE("Executing to_port() with port=\"a_port_str\"")
91
92 // // Trim input string.
93 // std::string port_str;
94 // auto start = a_port_str.find_first_not_of(" \t");
95 // // Avoid exception with program terminate if all spaces/tabs.
96 // if (start != a_port_str.npos) {
97 // auto end = a_port_str.find_last_not_of(" \t");
98 // port_str = a_port_str.substr(start, (end - start) + 1);
99 // }
100
101 // Only non empty strings. I have to check this to avoid stoi() exception
102 // below.
103 if (a_port_str.empty()) {
104 if (a_port_num != nullptr)
105 *a_port_num = 0;
106 return 0;
107 }
108
109 // Now I check if the string are all digit characters
110 bool nonzero{false};
111 for (char ch : a_port_str) {
112 if (!std::isdigit(static_cast<unsigned char>(ch))) {
113 return -1;
114 } else if (ch != '0') {
115 nonzero = true;
116 }
117 }
118
119 // Only strings with max. 5 char may be valid (uint16_t has max. 65535).
120 if (a_port_str.length() > 5) {
121 if (nonzero)
122 return 1; // value valid but more than 5 char.
123 else
124 return -1; // string is all zero with more than 5 char.
125 }
126
127 // Valid positive number but is it within the port range (uint16_t)?
128 // Error conditions of the function is not checked because there is always a
129 // pre-checked valid number string given.
130 // TODO: Update on MacOS to Clang compiler that supports std::from_chars().
131#ifdef __clang__
132 std::string port_str(a_port_str);
133 int port = atoi(port_str.c_str());
134#else
135 int port{};
136 std::from_chars(a_port_str.data(), a_port_str.data() + a_port_str.size(),
137 port);
138#endif
139 if (port > 65535) {
140 return 1;
141 } else if (a_port_num != nullptr) {
142 // Type cast is no problem because the port value is checked to be
143 // 0..65635 so it always fit into in_port_t(uint16_t).
144 *a_port_num = static_cast<in_port_t>(port);
145 }
146
147 return 0;
148}
149
150
151// Free function to split inet address and port(service)
152// -----------------------------------------------------
153// For port conversion:
154// Don't use '::htons' (with colons) instead of 'htons', MacOS don't like it.
155// 'sin6_port' is also 'sin_port' due to union.
156//
157// Unique pattern recognition, port delimiter is always ':'
158// Starting with '['
159// Pattern e.g.: [2001:db8::1%1]:50001
160// [2001:db8::2]:
161// [2001:db8::3]
162// [::ffff:142.250.185.99]:50001
163// Starting with "::"
164// ::
165// ::1
166// ::1] // invalid
167// ::ffff:142.250.185.99
168// ::101.45.75.219 // deprecated
169// Starting with ':' and is port
170// :50002
171// :https
172// Containing '.'
173// 127.0.0.4:50003
174// 127.0.0.5:
175// 127.0.0.6
176// Containing one ':'
177// example.com:
178// example.com:50004
179// localhost:
180// localhost:50005
181// Is port
182// 50006
183// Remaining
184// 2001:db8::7
185void split_addr_port(std::string_view a_addr_str, std::string& a_addr,
186 std::string& a_serv) {
187 TRACE("Executing split_addr_port()")
188
189 // Special cases
190 if (a_addr_str.empty()) {
191 // An empty address string clears address/port.
192 a_addr.clear();
193 a_serv.clear();
194 return;
195 }
196
197 std::string_view addr_str;
198 std::string serv_str;
199
200 size_t pos{};
201 if (a_addr_str.length() < 2) {
202 // The shortest possible ip address is "::". This helps to avoid string
203 // exceptions 'out_of_range'.
204 addr_str = a_addr_str; // Give it back as (possible) address.
205
206 } else if (a_addr_str.front() == '[') {
207 // Starting with '[', split address if required
208 if ((pos = a_addr_str.find("]:")) != std::string::npos) {
209 addr_str = a_addr_str.substr(0, pos + 1); // Get IP address
210 serv_str = a_addr_str.substr(pos + 2); // Get port string
211 if (serv_str.empty())
212 serv_str = '0';
213 } else {
214 addr_str = a_addr_str; // Get IP address
215 }
216
217 } else if (a_addr_str.front() == ':' && a_addr_str[1] == ':') {
218 // Starting with "::", this cannot have a port.
219 addr_str = a_addr_str;
220
221 } else if (a_addr_str.front() == ':') {
222 // Starting with ':' and is port
223 in_port_t port{};
224 switch (to_port(a_addr_str.substr(1), &port)) {
225 case -1:
226 // No numeric port, check for alphanum port.
227 serv_str = a_addr_str.substr(1);
228 break;
229 case 0:
230 // Only port given, set only port.
231 serv_str = std::to_string(port);
232 break;
233 case 1:
234 // Value not in range 0..65535.
235 goto exit_overrun;
236 }
237 } else if (a_addr_str.find_first_of('.') != std::string::npos) {
238 // Containing '.'
239 if ((pos = a_addr_str.find_last_of(':')) != std::string::npos) {
240 addr_str = a_addr_str.substr(0, pos); // Get IP address
241 serv_str = a_addr_str.substr(pos + 1); // Get port string
242 if (serv_str.empty())
243 serv_str = '0';
244 } else {
245 // No port, set only address.
246 addr_str = a_addr_str;
247 }
248 } else if (std::ranges::count(a_addr_str, ':') == 1) {
249 // Containing one ':'
250 pos = a_addr_str.find_last_of(':');
251 addr_str = a_addr_str.substr(0, pos); // Get IP address
252 serv_str = a_addr_str.substr(pos + 1); // Get port string
253 if (serv_str.empty())
254 serv_str = '0';
255 } else {
256 // Is port
257 in_port_t port{};
258 switch (to_port(a_addr_str, &port)) {
259 case -1:
260 // Remaining
261 // is either only numeric address, or any alphanumeric identifier.
262 addr_str = a_addr_str;
263 break;
264 case 0:
265 // Only port given, set only port.
266 serv_str = std::to_string(port);
267 break;
268 case 1:
269 // Value not in range 0..65535
270 goto exit_overrun;
271 }
272 }
273
274 // Return result for a_addr.
275 // Remove surounding brackets if any, shortest possible netaddress is
276 // "[::]".
277 if (addr_str.length() >= 4 && addr_str.front() == '[' &&
278 addr_str.back() == ']' && std::ranges::count(addr_str, ':') >= 2) {
279 // Here it can be an IPv6 address without '.', or an IPv4 mapped IPv6
280 // address with '.' and prefix "::ffff:".
281 // Remove surounding brackets.
282
283 a_addr = addr_str.substr(1, addr_str.length() - 2);
284
285 } else {
286 // Haven't found a valid numeric ip address, no need to remove
287 // brackets, should be interpreted as alphanumeric address.
288
289 a_addr = addr_str;
290 }
291
292 // Return result for a_serv.
293 // Check for valid port. ::getaddrinfo accepts invalid ports > 65535.
294 switch (to_port(serv_str)) {
295 case -1:
296 case 0:
297 break;
298 default:
299 goto exit_overrun;
300 }
301
302 a_serv = serv_str;
303
304 return;
305
306exit_overrun:
307 throw std::range_error(
308 UPnPsdk_LOGEXCEPT("MSG1127") "Number string from \"" +
309 std::string(a_addr_str) + "\" for port is out of range 0..65535.");
310}
311
312
313// Specialized sockaddr_structure
314// ==============================
315// Constructor
316SSockaddr::SSockaddr(){
317 TRACE2(this, " Construct SSockaddr()") //
318}
319
320// Destructor
321SSockaddr::~SSockaddr() {
322 TRACE2(this, " Destruct SSockaddr()")
323 // Destroy structure
324 ::memset(&m_sa_union, 0xAA, sizeof(m_sa_union));
325}
326
327// Get reference to the sockaddr_storage structure.
328// Only as example, we don't use it.
329// SSockaddr::operator const ::sockaddr_storage&() const {
330// return this->ss;
331// }
332
333// Copy constructor
334// ----------------
335SSockaddr::SSockaddr(const SSockaddr& that) {
336 TRACE2(this, " Construct copy SSockaddr()")
337 m_sa_union = that.m_sa_union;
338}
339
340// Copy assignment operator
341// ------------------------
343 TRACE2(this,
344 " Executing SSockaddr::operator=(SSockaddr) (struct assign op).")
345 std::swap(m_sa_union, that.m_sa_union);
346
347 return *this;
348}
349
350// Assignment operator= to set socket address from string.
351// -------------------------------------------------------
352void SSockaddr::operator=(std::string_view a_addr_str) {
353 TRACE2(this, " Executing SSockaddr::operator=(a_addr_str)")
354
355 if (a_addr_str.empty()) {
356 // This clears the complete socket address.
357 ::memset(&m_sa_union, 0, sizeof(m_sa_union));
358 m_sa_union.ss.ss_family = AF_UNSPEC;
359 return;
360 }
361 std::string ai_addr_str;
362 std::string ai_port_str;
363
364 // Throws exception std::out_of_range().
365 split_addr_port(a_addr_str, ai_addr_str, ai_port_str);
366
367 // With an empty address part (e.g. ":50001") only set the port and leave
368 // the (old) address unmodified.
369 if (ai_addr_str.empty()) {
370 in_port_t port;
371 if (to_port(ai_port_str, &port) != 0)
372 throw std::invalid_argument(
373 UPnPsdk_LOGEXCEPT("MSG1043") "Invalid netaddress \"" +
374 std::string(a_addr_str) + "\".\n");
375 m_sa_union.sin6.sin6_port = htons(port);
376 return;
377 }
378
379 // Provide resources for ::getaddrinfo()
380 // ai_flags ensure that only numeric values are accepted.
381 ::addrinfo hints{};
382 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
383 hints.ai_family = AF_UNSPEC;
384 ::addrinfo* res{nullptr}; // Result from getaddrinfo().
385
386 // Call ::getaddrinfo() to check the ip address string.
387 int ret = umock::netdb_h.getaddrinfo(ai_addr_str.c_str(),
388 ai_port_str.c_str(), &hints, &res);
389 if (ret != 0) {
390 umock::netdb_h.freeaddrinfo(res);
391 throw std::invalid_argument(
392 UPnPsdk_LOGEXCEPT("MSG1156") "Invalid netaddress \"" +
393 std::string(a_addr_str) + "\".\n");
394 } else {
395 if (ai_port_str.empty()) {
396 // Preserve old port
397 in_port_t port = m_sa_union.sin6.sin6_port;
398 ::memcpy(&m_sa_union, res->ai_addr, sizeof(m_sa_union));
399 m_sa_union.sin6.sin6_port = port;
400 } else {
401 ::memcpy(&m_sa_union, res->ai_addr, sizeof(m_sa_union));
402 }
403 umock::netdb_h.freeaddrinfo(res);
404 }
405
406 return;
407}
408
409// Assignment operator= to set socket port from an integer
410// -------------------------------------------------------
411void SSockaddr::operator=(const in_port_t a_port) {
412 // Don't use ::htons, MacOS don't like it.
413 // sin6_port is also sin_port due to union.
414 m_sa_union.sin6.sin6_port = htons(a_port);
415}
416
417// Assignment operator= to set socket address from a trivial socket address
418// structure
419// ------------------------------------------------------------------------
420void SSockaddr::operator=(const ::sockaddr_storage& a_ss) {
421 ::memcpy(&m_sa_union, &a_ss, sizeof(m_sa_union));
422}
423
424// Compare operator== to test if another trivial socket address is equal to this
425// -----------------------------------------------------------------------------
426bool SSockaddr::operator==(const SSockaddr& a_saddr) const {
427 return sockaddrcmp(&a_saddr.ss, &ss);
428}
429
430// Getter for the assosiated ip address without port
431// -------------------------------------------------
432// e.g. "[2001:db8::2]" or "192.168.254.253".
433const std::string SSockaddr::netaddr() noexcept {
434 // TRACE not usable with chained output.
435 // TRACE2(this, " Executing SSockaddr::netaddr()")
436
437 // Accept nameinfo only for supported address families.
438 switch (m_sa_union.ss.ss_family) {
439 case AF_INET6:
440 case AF_INET:
441 break;
442 case AF_UNSPEC:
443 return "";
444 default:
445 UPnPsdk_LOGERR("MSG1129") "Unsupported address family "
446 << std::to_string(m_sa_union.ss.ss_family)
447 << ". Continue with unspecified netaddress \"\".\n";
448 return "";
449 }
450
451 // The buffer fit to an IPv6 address with mapped IPv4 address (max. 46) and
452 // also fit to an IPv6 address with scope id (max. 51).
453 char addrStr[39 /*sizeof(IPv6_addr)*/ + 1 /*'%'*/ +
454 10 /*sin6_scope_id_max(4294967295)*/ + 1 /*'\0'*/]{};
455 // ::getnameinfo() appends scope id with '%' if sin6_scope_id is > 0.
456 int ret = ::getnameinfo(&m_sa_union.sa, sizeof(m_sa_union.ss), addrStr,
457 sizeof(addrStr), nullptr, 0, NI_NUMERICHOST);
458 if (ret != 0) {
459 // 'std::to_string()' may throw 'std::bad_alloc' from the std::string
460 // constructor. It is a fatal error that violates the promise to
461 // noexcept and immediately terminates the propgram. This is
462 // intentional because the error cannot be handled except improving the
463 // hardware.
464 UPnPsdk_LOGERR(
465 "MSG1036") "Failed to get netaddress with address family "
466 << std::to_string(m_sa_union.ss.ss_family) << ": "
467 << ::gai_strerror(ret)
468 << ". Continue with unspecified netaddress \"\".\n";
469 return "";
470 }
471
472 // Next may throw 'std::length_error' if the length of the constructed
473 // std::string would exceed max_size(). This should never happen with given
474 // lengths of addrStr (promise noexcept).
475 if (m_sa_union.ss.ss_family == AF_INET6)
476 return '[' + std::string(addrStr) + ']';
477 else
478 return std::string(addrStr);
479}
480
481// Getter for the assosiated ip address with port
482// ----------------------------------------------
483// e.g. "[2001:db8::2]:50001" or "192.168.254.253:50001".
484const std::string SSockaddr::netaddrp() noexcept {
485 // TRACE not usable with chained output.
486 // TRACE2(this, " Executing SSockaddr::netaddrp()")
487 //
488 // sin_port and sin6_port are on the same memory location (union of the
489 // structures) so I can use it for AF_INET and AF_INET6. 'std::to_string()'
490 // may throw 'std::bad_alloc' from the std::string constructor. It is a
491 // fatal error that violates the promise to noexcept and immediately
492 // terminates the propgram. This is intentional because the error cannot be
493 // handled except improving the hardware.
494 switch (m_sa_union.ss.ss_family) {
495 case AF_INET6:
496 case AF_INET:
497 case AF_UNSPEC:
498 return this->netaddr() + ":" +
499 std::to_string(ntohs(m_sa_union.sin6.sin6_port));
500 break;
501 case AF_UNIX:
502 return this->netaddr() + ":0";
503 }
504
505 return "";
506}
507
508// Getter for the assosiated port number
509// -------------------------------------
510in_port_t SSockaddr::port() const {
511 TRACE2(this, " Executing SSockaddr::port()")
512 // sin_port and sin6_port are on the same memory location (union of the
513 // structures) so we can use it for AF_INET and AF_INET6.
514 // Don't use ::ntohs, MacOS don't like it.
515 return ntohs(m_sa_union.sin6.sin6_port);
516}
517
518// Getter for sizeof the current (sin6 or sin) Sockaddr Structure.
519// ---------------------------------------------------------------
520socklen_t SSockaddr::sizeof_saddr() const {
521 TRACE2(this, " Executing SSockaddr::sizeof_saddr()")
522 switch (m_sa_union.ss.ss_family) {
523 case AF_INET6:
524 return sizeof(m_sa_union.sin6);
525 case AF_INET:
526 return sizeof(m_sa_union.sin);
527 case AF_UNSPEC:
528 return sizeof(m_sa_union.ss);
529 default:
530 return 0;
531 }
532}
533
534// Get if the socket address is a loopback address
535// ----------------------------------------------
537 // I handle only IPv6 addresses and check if I have either the IPv6
538 // loopback address or any IPv4 mapped IPv6 address between
539 // "[::ffff:127.0.0.0]" and "[::ffff:127.255.255.255]".
540 return (
541 m_sa_union.ss.ss_family == AF_INET6 &&
542 (IN6_IS_ADDR_LOOPBACK(&m_sa_union.sin6.sin6_addr) ||
543 (ntohl(static_cast<uint32_t>(m_sa_union.sin6.sin6_addr.s6_addr[12])) >=
544 2130706432 &&
545 ntohl(static_cast<uint32_t>(m_sa_union.sin6.sin6_addr.s6_addr[12])) <=
546 2147483647)));
547#if 0
548 (m_sa_union.ss.ss_family == AF_INET &&
549 // address between "127.0.0.0" and "127.255.255.255"
550 ntohl(m_sa_union.sin.sin_addr.s_addr) >= 2130706432 &&
551 ntohl(m_sa_union.sin.sin_addr.s_addr) <= 2147483647));
552#endif
553}
554
556// Getter of the netaddress to output stream
557// -----------------------------------------
558std::ostream& operator<<(std::ostream& os, SSockaddr& saddr) {
559 os << saddr.netaddrp();
560 return os;
561}
563
564} // namespace UPnPsdk
bool sockaddrcmp(const ::sockaddr_storage *a_ss1, const ::sockaddr_storage *a_ss2) noexcept
logical compare two sockaddr structures
Definition sockaddr.cpp:32
int to_port(std::string_view a_port_str, in_port_t *const a_port_num=nullptr) noexcept
Free function to check if a string represents a valid port number.
Definition sockaddr.cpp:89
UPnPsdk_API::std::ostream & operator<<(::std::ostream &os, SSockaddr &saddr)
output the netaddress
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
Reengineered Object Oriented UPnP+ program code.
Declaration of the Sockaddr class and some free helper functions.
Trivial ::sockaddr structures enhanced with methods.
Definition sockaddr.hpp:133
const std::string netaddrp() noexcept
Get the assosiated netaddress with port.
Definition sockaddr.cpp:484
bool operator==(const SSockaddr &) const
Test if another socket address is logical equal to this.
Definition sockaddr.cpp:426
bool is_loopback() const
Get if the socket address is a loopback address.
Definition sockaddr.cpp:536
SSockaddr & operator=(SSockaddr)
Copy assignment operator, needs user defined copy contructor.
Definition sockaddr.cpp:342
in_port_t port() const
Get the numeric port.
Definition sockaddr.cpp:510
const std::string netaddr() noexcept
Get the assosiated netaddress without port.
Definition sockaddr.cpp:433
sockaddr_storage & ss
Reference to sockaddr_storage struct.
Definition sockaddr.hpp:135
socklen_t sizeof_saddr() const
Get sizeof the current filled (sin6 or sin) Sockaddr Structure.
Definition sockaddr.cpp:520
Define macro for synced logging to the console for detailed info and debug.