blob: 3bcf38cfb159bd70678e852f6b35e92d559b3be0 [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#include "ip_utils.h"
Adrien Béraud80c14e12023-07-18 16:13:15 -040018#include "sip_utils.h"
19#include "string_utils.h"
Adrien Béraud612b55b2023-05-29 10:42:04 -040020
Adrien Béraud80c14e12023-07-18 16:13:15 -040021#include <fmt/format.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040022
Adrien Béraud4822fa02023-07-30 00:21:05 -040023extern "C" {
Adrien Béraud612b55b2023-05-29 10:42:04 -040024#include <sys/types.h>
25#include <unistd.h>
26#include <limits.h>
27
28#ifdef _WIN32
29#define InetPtonA inet_pton
30WINSOCK_API_LINKAGE INT WSAAPI InetPtonA(INT Family, LPCSTR pStringBuf, PVOID pAddr);
31#else
32#include <arpa/inet.h>
33#include <arpa/nameser.h>
34#include <resolv.h>
35#include <netdb.h>
36#include <netinet/ip.h>
37#include <net/if.h>
38#include <ifaddrs.h>
39#include <sys/ioctl.h>
40#endif
Adrien Béraud4822fa02023-07-30 00:21:05 -040041}
Adrien Béraud612b55b2023-05-29 10:42:04 -040042
43#ifndef HOST_NAME_MAX
44#ifdef MAX_COMPUTERNAME_LENGTH
45#define HOST_NAME_MAX MAX_COMPUTERNAME_LENGTH
46#else
47// Max 255 chars as per RFC 1035
48#define HOST_NAME_MAX 255
49#endif
50#endif
51
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040052namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040053
Adrien Béraud9ed4dd92023-07-19 00:13:05 -040054namespace sip_utils {
Adrien Béraud612b55b2023-05-29 10:42:04 -040055std::string_view
56sip_strerror(pj_status_t code)
57{
58 thread_local char err_msg[PJ_ERR_MSG_SIZE];
Adrien Béraud80c14e12023-07-18 16:13:15 -040059 return sip_utils::as_view(pj_strerror(code, err_msg, sizeof err_msg));
Adrien Béraud612b55b2023-05-29 10:42:04 -040060}
Adrien Béraud9ed4dd92023-07-19 00:13:05 -040061}
Adrien Béraud612b55b2023-05-29 10:42:04 -040062
63std::string
64ip_utils::getHostname()
65{
66 char hostname[HOST_NAME_MAX];
67 if (gethostname(hostname, HOST_NAME_MAX))
68 return {};
69 return hostname;
70}
71
Adrien Béraud63a1fac2023-08-23 09:31:17 -040072
73ip_utils::IpInterfaceAddress
74ip_utils::getHostName()
Adrien Béraud612b55b2023-05-29 10:42:04 -040075{
Adrien Béraud63a1fac2023-08-23 09:31:17 -040076 IpInterfaceAddress ret;
Adrien Béraud612b55b2023-05-29 10:42:04 -040077 char tempstr[INET_ADDRSTRLEN];
78 const char* p = NULL;
79#ifdef _WIN32
80 struct hostent* h = NULL;
81 struct sockaddr_in localAddr;
82 memset(&localAddr, 0, sizeof(localAddr));
83 gethostname(out, out_len);
84 h = gethostbyname(out);
85 if (h != NULL) {
86 memcpy(&localAddr.sin_addr, h->h_addr_list[0], 4);
87 p = inet_ntop(AF_INET, &localAddr.sin_addr, tempstr, sizeof(tempstr));
88 if (p)
Adrien Béraud63a1fac2023-08-23 09:31:17 -040089 ret.address = p;
Adrien Béraud612b55b2023-05-29 10:42:04 -040090 else
Adrien Béraud63a1fac2023-08-23 09:31:17 -040091 return {};
Adrien Béraud612b55b2023-05-29 10:42:04 -040092 } else {
Adrien Béraud63a1fac2023-08-23 09:31:17 -040093 return {};
Adrien Béraud612b55b2023-05-29 10:42:04 -040094 }
95#elif (defined(BSD) && BSD >= 199306) || defined(__FreeBSD_kernel__)
Adrien Béraud612b55b2023-05-29 10:42:04 -040096 struct ifaddrs* ifap;
97 struct ifaddrs* ifa;
98 if (getifaddrs(&ifap) != 0)
Adrien Béraud63a1fac2023-08-23 09:31:17 -040099 return {};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400100 // Cycle through available interfaces.
101 for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
102 // Skip loopback, point-to-point and down interfaces.
103 // except don't skip down interfaces if we're trying to get
104 // a list of configurable interfaces.
105 if ((ifa->ifa_flags & IFF_LOOPBACK) || (!(ifa->ifa_flags & IFF_UP)))
106 continue;
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400107 auto family = ifa->ifa_addr->sa_family;
108 if (family == AF_INET) {
109 void* addr;
110 if (family == AF_INET) {
111 if (((struct sockaddr_in*) (ifa->ifa_addr))->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
112 continue;
113 addr = &((struct sockaddr_in*) (ifa->ifa_addr))->sin_addr;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400114 }
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400115
116 ret.interface = ifa->ifa_name;
117 p = inet_ntop(family,
118 addr,
Adrien Béraud612b55b2023-05-29 10:42:04 -0400119 tempstr,
120 sizeof(tempstr));
121 if (p)
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400122 ret.address = p;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400123 break;
124 }
125 }
126 freeifaddrs(ifap);
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400127 return ret;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400128#else
129 struct ifconf ifConf;
130 struct ifreq ifReq;
131 struct sockaddr_in localAddr;
132 char szBuffer[MAX_INTERFACE * sizeof(struct ifreq)];
133 int nResult;
134 int localSock;
135 memset(&ifConf, 0, sizeof(ifConf));
136 memset(&ifReq, 0, sizeof(ifReq));
137 memset(szBuffer, 0, sizeof(szBuffer));
138 memset(&localAddr, 0, sizeof(localAddr));
139 // Create an unbound datagram socket to do the SIOCGIFADDR ioctl on.
140 localSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
141 if (localSock == INVALID_SOCKET)
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400142 return ret;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400143 /* Get the interface configuration information... */
144 ifConf.ifc_len = (int) sizeof szBuffer;
145 ifConf.ifc_ifcu.ifcu_buf = (caddr_t) szBuffer;
146 nResult = ioctl(localSock, SIOCGIFCONF, &ifConf);
147 if (nResult < 0) {
148 close(localSock);
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400149 return ret;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400150 }
151 unsigned int i;
152 unsigned int j = 0;
153 // Cycle through the list of interfaces looking for IP addresses.
154 for (i = 0u; i < (unsigned int) ifConf.ifc_len && j < MIN_INTERFACE;) {
155 struct ifreq* pifReq = (struct ifreq*) ((caddr_t) ifConf.ifc_req + i);
156 i += sizeof *pifReq;
157 // See if this is the sort of interface we want to deal with.
158 memset(ifReq.ifr_name, 0, sizeof(ifReq.ifr_name));
159 strncpy(ifReq.ifr_name, pifReq->ifr_name, sizeof(ifReq.ifr_name));
160 ioctl(localSock, SIOCGIFFLAGS, &ifReq);
161 // Skip loopback, point-to-point and down interfaces.
162 // except don't skip down interfaces if we're trying to get
163 // a list of configurable interfaces.
164 if ((ifReq.ifr_flags & IFF_LOOPBACK) || (!(ifReq.ifr_flags & IFF_UP)))
165 continue;
166 if (pifReq->ifr_addr.sa_family == AF_INET) {
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400167 if (((sockaddr_in*)&pifReq->ifr_addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400168 // We don't want the loopback interface. Go to the next one.
169 continue;
170 }
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400171 ret.interface = pifReq->ifr_name;
172 p = inet_ntop(pifReq->ifr_addr.sa_family,
173 (sockaddr_in*)&pifReq->ifr_addr,
174 tempstr,
175 sizeof(tempstr));
176 if (p)
177 ret.address = p;
178
Adrien Béraud612b55b2023-05-29 10:42:04 -0400179 }
180 j++; // Increment j if we found an address which is not loopback and is up.
181 }
182 close(localSock);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400183#endif
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400184 return {};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400185}
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400186
Adrien Béraud612b55b2023-05-29 10:42:04 -0400187std::string
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400188ip_utils::getGateway(std::string_view localHost, ip_utils::subnet_mask prefix)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400189{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400190 if (prefix == ip_utils::subnet_mask::prefix_32bit)
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400191 return std::string(localHost);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400192 std::string defaultGw {};
193 // Make a vector of each individual number in the ip address.
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400194 std::vector<std::string_view> tokens = split_string(localHost, '.');
Adrien Béraud612b55b2023-05-29 10:42:04 -0400195 // Build a gateway address from the individual ip components.
196 for (unsigned i = 0; i <= (unsigned) prefix; i++)
Adrien Béraud80c14e12023-07-18 16:13:15 -0400197 defaultGw = fmt::format("{:s}{:s}.", defaultGw, tokens[i]);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400198 for (unsigned i = (unsigned) ip_utils::subnet_mask::prefix_32bit;
199 i > (unsigned) prefix + 1;
200 i--)
201 defaultGw += "0.";
202 defaultGw += "1";
203 return defaultGw;
204}
205
206IpAddr
207ip_utils::getLocalGateway()
208{
209 char localHostBuf[INET_ADDRSTRLEN];
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400210 auto hostInfo = ip_utils::getHostName();
211 if (hostInfo.address.empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400212 // JAMI_WARN("Couldn't find local host");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400213 return {};
214 } else {
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400215 return IpAddr(ip_utils::getGateway(hostInfo.address, ip_utils::subnet_mask::prefix_24bit));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400216 }
217}
218
219std::vector<IpAddr>
220ip_utils::getAddrList(std::string_view name, pj_uint16_t family)
221{
222 std::vector<IpAddr> ipList;
223 if (name.empty())
224 return ipList;
225 if (IpAddr::isValid(name, family)) {
226 ipList.emplace_back(name);
227 return ipList;
228 }
229
230 static constexpr unsigned MAX_ADDR_NUM = 128;
231 pj_addrinfo res[MAX_ADDR_NUM];
232 unsigned addr_num = MAX_ADDR_NUM;
233 const pj_str_t pjname(sip_utils::CONST_PJ_STR(name));
234 auto status = pj_getaddrinfo(family, &pjname, &addr_num, res);
235 if (status != PJ_SUCCESS) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400236 // JAMI_ERR("Error resolving %.*s : %s",
237 // (int) name.size(),
238 // name.data(),
239 // sip_utils::sip_strerror(status).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400240 return ipList;
241 }
242
243 for (unsigned i = 0; i < addr_num; i++) {
244 bool found = false;
245 for (const auto& ip : ipList)
246 if (!pj_sockaddr_cmp(&ip, &res[i].ai_addr)) {
247 found = true;
248 break;
249 }
250 if (!found)
251 ipList.emplace_back(res[i].ai_addr);
252 }
253
254 return ipList;
255}
256
257bool
258ip_utils::haveCommonAddr(const std::vector<IpAddr>& a, const std::vector<IpAddr>& b)
259{
260 for (const auto& i : a) {
261 for (const auto& j : b) {
262 if (i == j)
263 return true;
264 }
265 }
266 return false;
267}
268
269IpAddr
270ip_utils::getLocalAddr(pj_uint16_t family)
271{
272 IpAddr ip_addr {};
273 pj_status_t status = pj_gethostip(family, ip_addr.pjPtr());
274 if (status == PJ_SUCCESS) {
275 return ip_addr;
276 }
Morteza Namvar5f639522023-07-04 17:08:58 -0400277 // JAMI_WARN("Could not get preferred address familly (%s)",
278 // (family == pj_AF_INET6()) ? "IPv6" : "IPv4");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400279 family = (family == pj_AF_INET()) ? pj_AF_INET6() : pj_AF_INET();
280 status = pj_gethostip(family, ip_addr.pjPtr());
281 if (status == PJ_SUCCESS) {
282 return ip_addr;
283 }
Morteza Namvar5f639522023-07-04 17:08:58 -0400284 // JAMI_ERR("Could not get local IP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400285 return ip_addr;
286}
287
288IpAddr
289ip_utils::getInterfaceAddr(const std::string& interface, pj_uint16_t family)
290{
291 if (interface == DEFAULT_INTERFACE)
292 return getLocalAddr(family);
293
294 IpAddr addr {};
295
296#ifndef _WIN32
297 const auto unix_family = family == pj_AF_INET() ? AF_INET : AF_INET6;
298
299 int fd = socket(unix_family, SOCK_DGRAM, 0);
300 if (fd < 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400301 // JAMI_ERR("Could not open socket: %m");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400302 return addr;
303 }
304
305 if (unix_family == AF_INET6) {
306 int val = family != pj_AF_UNSPEC();
307 if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*) &val, sizeof(val)) < 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400308 // JAMI_ERR("Could not setsockopt: %m");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400309 close(fd);
310 return addr;
311 }
312 }
313
314 ifreq ifr;
315 strncpy(ifr.ifr_name, interface.c_str(), sizeof ifr.ifr_name);
316 // guarantee that ifr_name is NULL-terminated
317 ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
318
319 memset(&ifr.ifr_addr, 0, sizeof(ifr.ifr_addr));
320 ifr.ifr_addr.sa_family = unix_family;
321
322 ioctl(fd, SIOCGIFADDR, &ifr);
323 close(fd);
324
325 addr = ifr.ifr_addr;
326 if (addr.isUnspecified())
327 return getLocalAddr(addr.getFamily());
328#else // _WIN32
329 struct addrinfo hints;
330 struct addrinfo* result = NULL;
331 struct sockaddr_in* sockaddr_ipv4;
332 struct sockaddr_in6* sockaddr_ipv6;
333
334 ZeroMemory(&hints, sizeof(hints));
335
336 DWORD dwRetval = getaddrinfo(interface.c_str(), "0", &hints, &result);
337 if (dwRetval != 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400338 // JAMI_ERR("getaddrinfo failed with error: %lu", dwRetval);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400339 return addr;
340 }
341
342 switch (result->ai_family) {
343 sockaddr_ipv4 = (struct sockaddr_in*) result->ai_addr;
344 addr = sockaddr_ipv4->sin_addr;
345 break;
346 case AF_INET6:
347 sockaddr_ipv6 = (struct sockaddr_in6*) result->ai_addr;
348 addr = sockaddr_ipv6->sin6_addr;
349 break;
350 default:
351 break;
352 }
353
354 if (addr.isUnspecified())
355 return getLocalAddr(addr.getFamily());
356#endif // !_WIN32
357
358 return addr;
359}
360
361std::vector<std::string>
362ip_utils::getAllIpInterfaceByName()
363{
364 std::vector<std::string> ifaceList;
365 ifaceList.push_back("default");
366#ifndef _WIN32
367 static ifreq ifreqs[20];
368 ifconf ifconf;
369
370 ifconf.ifc_buf = (char*) (ifreqs);
371 ifconf.ifc_len = sizeof(ifreqs);
372
373 int sock = socket(AF_INET6, SOCK_STREAM, 0);
374
375 if (sock >= 0) {
376 if (ioctl(sock, SIOCGIFCONF, &ifconf) >= 0)
377 for (unsigned i = 0; i < ifconf.ifc_len / sizeof(ifreq); ++i)
378 ifaceList.push_back(std::string(ifreqs[i].ifr_name));
379
380 close(sock);
381 }
382
383#else
Morteza Namvar5f639522023-07-04 17:08:58 -0400384 // JAMI_ERR("Not implemented yet. (iphlpapi.h problem)");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400385#endif
386 return ifaceList;
387}
388
389std::vector<std::string>
390ip_utils::getAllIpInterface()
391{
392 pj_sockaddr addrList[16];
393 unsigned addrCnt = PJ_ARRAY_SIZE(addrList);
394
395 std::vector<std::string> ifaceList;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400396 if (pj_enum_ip_interface(pj_AF_UNSPEC(), &addrCnt, addrList) == PJ_SUCCESS) {
397 for (unsigned i = 0; i < addrCnt; i++) {
398 char addr[PJ_INET6_ADDRSTRLEN];
399 pj_sockaddr_print(&addrList[i], addr, sizeof(addr), 0);
Adrien Béraud63a1fac2023-08-23 09:31:17 -0400400 ifaceList.emplace_back(addr);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400401 }
402 }
403
404 return ifaceList;
405}
406
407std::vector<IpAddr>
408ip_utils::getLocalNameservers()
409{
410 std::vector<IpAddr> res;
411#if defined __ANDROID__ || defined _WIN32 || TARGET_OS_IPHONE
412#ifdef _MSC_VER
413#pragma message(__FILE__ "(" STR2(__LINE__) ") : -NOTE- " \
414 "Not implemented")
415#else
416#warning "Not implemented"
417#endif
418#else
419 if (not(_res.options & RES_INIT))
420 res_init();
421 res.insert(res.end(), _res.nsaddr_list, _res.nsaddr_list + _res.nscount);
422#endif
423 return res;
424}
425
426bool
427IpAddr::isValid(std::string_view address, pj_uint16_t family)
428{
429 const pj_str_t pjstring(sip_utils::CONST_PJ_STR(address));
430 pj_str_t ret_str;
431 pj_uint16_t ret_port;
432 int ret_family;
433 auto status = pj_sockaddr_parse2(pj_AF_UNSPEC(), 0, &pjstring, &ret_str, &ret_port, &ret_family);
434 if (status != PJ_SUCCESS || (family != pj_AF_UNSPEC() && ret_family != family))
435 return false;
436
437 char buf[PJ_INET6_ADDRSTRLEN];
438 pj_str_t addr_with_null = {buf, 0};
439 pj_strncpy_with_null(&addr_with_null, &ret_str, sizeof(buf));
440 struct sockaddr sa;
441 return inet_pton(ret_family == pj_AF_INET6() ? AF_INET6 : AF_INET, buf, &(sa.sa_data)) == 1;
442}
443
444bool
445IpAddr::isUnspecified() const
446{
447 switch (addr.addr.sa_family) {
448 case AF_INET:
449 return IN_IS_ADDR_UNSPECIFIED(&addr.ipv4.sin_addr);
450 case AF_INET6:
451 return IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr));
452 default:
453 return true;
454 }
455}
456
457bool
458IpAddr::isLoopback() const
459{
460 switch (addr.addr.sa_family) {
461 case AF_INET: {
462 auto addr_host = ntohl(addr.ipv4.sin_addr.s_addr);
463 uint8_t b1 = (uint8_t)(addr_host >> 24);
464 return b1 == 127;
465 }
466 case AF_INET6:
467 return IN6_IS_ADDR_LOOPBACK(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr));
468 default:
469 return false;
470 }
471}
472
473bool
474IpAddr::isPrivate() const
475{
476 if (isLoopback()) {
477 return true;
478 }
479 switch (addr.addr.sa_family) {
480 case AF_INET: {
481 auto addr_host = ntohl(addr.ipv4.sin_addr.s_addr);
482 uint8_t b1, b2;
483 b1 = (uint8_t)(addr_host >> 24);
484 b2 = (uint8_t)((addr_host >> 16) & 0x0ff);
485 // 10.x.y.z
486 if (b1 == 10)
487 return true;
488 // 172.16.0.0 - 172.31.255.255
489 if ((b1 == 172) && (b2 >= 16) && (b2 <= 31))
490 return true;
491 // 192.168.0.0 - 192.168.255.255
492 if ((b1 == 192) && (b2 == 168))
493 return true;
494 return false;
495 }
496 case AF_INET6: {
497 const pj_uint8_t* addr6 = reinterpret_cast<const pj_uint8_t*>(&addr.ipv6.sin6_addr);
498 if (addr6[0] == 0xfc)
499 return true;
500 return false;
501 }
502 default:
503 return false;
504 }
505}
Sébastien Blin464bdff2023-07-19 08:02:53 -0400506} // namespace dhtnet