Coverage Report

Created: 2025-07-12 06:38

/src/pistache/src/common/net.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * SPDX-FileCopyrightText: 2015 Mathieu Stefani
3
 * SPDX-FileCopyrightText: 2023 Andrea Pappacoda
4
 * SPDX-License-Identifier: Apache-2.0
5
 */
6
7
/*
8
 * net.cc
9
 * Mathieu Stefani, 12 August 2015
10
 */
11
12
#include <pistache/winornix.h>
13
14
#include <pistache/common.h>
15
#include <pistache/config.h>
16
#include <pistache/net.h>
17
18
#include <limits>
19
#include <stdexcept>
20
#include <string>
21
#include <vector>
22
23
#include <cassert>
24
#include <cstdlib>
25
#include <cstring>
26
27
#include <pistache/ps_strl.h>
28
29
#include PST_ARPA_INET_HDR
30
31
#include PST_IFADDRS_HDR
32
33
#include PST_NETDB_HDR
34
#include PST_SOCKET_HDR
35
36
#include <sys/types.h>
37
38
namespace Pistache
39
{
40
    namespace helpers
41
    {
42
        Address httpAddr(const std::string_view& view)
43
0
        {
44
0
            return(httpAddr(view, 0/*default port*/,
45
0
                            Address::Scheme::Unspecified,
46
0
                            nullptr)); // nullptr: page_cptr
47
0
        }
48
    } // namespace helpers
49
50
    Port::Port(uint16_t port)
51
19.4k
        : port(port)
52
19.4k
    { }
53
54
    Port::Port(const std::string& data)
55
2.35k
    {
56
2.35k
        if (data.empty())
57
0
            throw std::invalid_argument("Invalid port: empty port");
58
2.35k
        char* end     = nullptr;
59
2.35k
        long port_num = strtol(data.c_str(), &end, 10);
60
2.35k
        if (*end != 0 || port_num < Port::min() || port_num > Port::max())
61
954
            throw std::invalid_argument("Invalid port: " + data);
62
1.40k
        port = static_cast<uint16_t>(port_num);
63
1.40k
    }
64
65
0
    bool Port::isReserved() const { return port < 1024; }
66
67
    bool Port::isUsed() const
68
0
    {
69
0
        throw std::runtime_error("Unimplemented");
70
0
    }
71
72
0
    std::string Port::toString() const { return std::to_string(port); }
73
74
    IP::IP()
75
2.27k
    {
76
2.27k
        addr_.ss_family = AF_UNSPEC;
77
2.27k
    }
78
79
    IP::IP(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
80
0
    {
81
0
        addr_.ss_family          = AF_INET;
82
0
        const uint8_t buff[]     = { a, b, c, d };
83
        // Note that in Windows in_addr is a union - effectively a "u_long"
84
        // that can be accessed as 4 u_char, 2 u_short, or 1 u_long.
85
        // Meanwhile, s_addr on the right-hand side is type ULONG. So both
86
        // sides are effectively "ulong *" and the cast is valid
87
0
        PST_IN_ADDR_T* in_addr   = reinterpret_cast<PST_IN_ADDR_T*>
88
0
            (&reinterpret_cast<struct sockaddr_in*>(&addr_)->sin_addr.s_addr);
89
90
0
        static_assert(sizeof(buff) == sizeof(*in_addr));
91
0
        std::memcpy(in_addr, buff, sizeof(*in_addr));
92
0
    }
93
94
    IP::IP(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f,
95
           uint16_t g, uint16_t h)
96
0
    {
97
0
        addr_.ss_family        = AF_INET6;
98
0
        const uint16_t buff[8] = { a, b, c, d, e, f, g, h };
99
0
        uint16_t remap[8]      = { 0, 0, 0, 0, 0, 0, 0, 0 };
100
0
        if (htonl(1) != 1)
101
0
        {
102
0
            for (int i = 0; i < 8; i++)
103
0
            {
104
0
                const uint16_t swapped = htons(buff[i]);
105
0
                remap[i]               = swapped;
106
0
            }
107
0
        }
108
0
        else
109
0
        {
110
0
            std::memcpy(remap, buff, sizeof(remap));
111
0
        }
112
0
        auto& in6_addr = reinterpret_cast<struct sockaddr_in6*>(&addr_)->sin6_addr.s6_addr;
113
114
0
        static_assert(sizeof(in6_addr) == sizeof(remap));
115
0
        std::memcpy(in6_addr, remap, sizeof(in6_addr));
116
0
    }
117
118
    IP::IP(const struct sockaddr* addr)
119
0
    {
120
0
        if (addr->sa_family == AF_INET)
121
0
        {
122
0
            const struct sockaddr_in* in_addr = reinterpret_cast<const struct sockaddr_in*>(addr);
123
0
            struct sockaddr_in* ss_in_addr    = reinterpret_cast<struct sockaddr_in*>(&addr_);
124
125
            /* Should this simply be `*ss_in_addr = *in_addr`? */
126
0
            ss_in_addr->sin_family      = in_addr->sin_family;
127
0
            ss_in_addr->sin_addr.s_addr = in_addr->sin_addr.s_addr;
128
0
            ss_in_addr->sin_port        = in_addr->sin_port;
129
0
        }
130
0
        else if (addr->sa_family == AF_INET6)
131
0
        {
132
0
            const struct sockaddr_in6* in_addr = reinterpret_cast<const struct sockaddr_in6*>(addr);
133
0
            struct sockaddr_in6* ss_in_addr    = reinterpret_cast<struct sockaddr_in6*>(&addr_);
134
135
            /* Should this simply be `*ss_in_addr = *in_addr`? */
136
0
            ss_in_addr->sin6_family   = in_addr->sin6_family;
137
0
            ss_in_addr->sin6_port     = in_addr->sin6_port;
138
0
            ss_in_addr->sin6_flowinfo = in_addr->sin6_flowinfo; /* Should be 0 per RFC 3493 */
139
0
            std::memcpy(ss_in_addr->sin6_addr.s6_addr, in_addr->sin6_addr.s6_addr, sizeof(ss_in_addr->sin6_addr.s6_addr));
140
0
        }
141
0
        else if (addr->sa_family == AF_UNIX)
142
0
        {
143
0
            const struct sockaddr_un* un_addr = reinterpret_cast<const struct sockaddr_un*>(addr);
144
0
            struct sockaddr_un* ss_un_addr    = reinterpret_cast<struct sockaddr_un*>(&addr_);
145
146
0
            ss_un_addr->sun_family = un_addr->sun_family;
147
0
            std::memcpy(ss_un_addr->sun_path, un_addr->sun_path, sizeof(ss_un_addr->sun_path));
148
0
        }
149
0
        else
150
0
        {
151
0
            PS_LOG_WARNING_ARGS("Invalid socket family %d", addr->sa_family);
152
0
            throw std::invalid_argument("Invalid socket family");
153
0
        }
154
0
    }
155
156
0
    IP IP::any() { return IP(0, 0, 0, 0); }
157
158
    IP IP::any(bool is_ipv6)
159
0
    {
160
0
        if (is_ipv6)
161
0
        {
162
0
            return IP(0, 0, 0, 0, 0, 0, 0, 0);
163
0
        }
164
0
        else
165
0
        {
166
0
            return IP(0, 0, 0, 0);
167
0
        }
168
0
    }
169
170
0
    IP IP::loopback() { return IP(127, 0, 0, 1); }
171
172
    IP IP::loopback(bool is_ipv6)
173
0
    {
174
0
        if (is_ipv6)
175
0
        {
176
0
            return IP(0, 0, 0, 0, 0, 0, 0, 1);
177
0
        }
178
0
        else
179
0
        {
180
0
            return IP(127, 0, 0, 1);
181
0
        }
182
0
    }
183
184
0
    int IP::getFamily() const { return addr_.ss_family; }
185
186
    uint16_t IP::getPort() const
187
0
    {
188
0
        if (addr_.ss_family == AF_INET)
189
0
        {
190
0
            return ntohs(reinterpret_cast<const struct sockaddr_in*>(&addr_)->sin_port);
191
0
        }
192
0
        else if (addr_.ss_family == AF_INET6)
193
0
        {
194
0
            return ntohs(reinterpret_cast<const struct sockaddr_in6*>(&addr_)->sin6_port);
195
0
        }
196
0
        else if (addr_.ss_family == AF_UNIX)
197
0
        {
198
            // Ports are a meaningless concept for unix domain sockets.  Return
199
            // an arbitrary value.
200
0
            return 0;
201
0
        }
202
0
        else
203
0
        {
204
0
            Pistache::details::unreachable();
205
0
        }
206
0
    }
207
208
    std::string IP::toString() const
209
0
    {
210
0
        if (addr_.ss_family == AF_UNIX)
211
0
        {
212
0
            auto& unAddr = reinterpret_cast<const struct sockaddr_un&>(addr_);
213
0
            if (unAddr.sun_path[0] == '\0')
214
0
            {
215
                // The socket is abstract (not present in the file system name
216
                // space).  Its name starts with the byte following the initial
217
                // NUL.  As the name may contain embedded NUL bytes and its
218
                // length is not available here, simply note that it's an
219
                // abstract address.
220
0
                return std::string("[Abstract]");
221
0
            }
222
0
            else
223
0
            {
224
0
                return std::string(unAddr.sun_path);
225
0
            }
226
0
        }
227
228
0
        char buff[INET6_ADDRSTRLEN];
229
0
        const auto* addr_sa = reinterpret_cast<const struct sockaddr*>(&addr_);
230
0
        int err             = getnameinfo(
231
0
                        addr_sa, sizeof(addr_), buff, sizeof(buff), nullptr, 0, NI_NUMERICHOST);
232
0
        if (err) /* [[unlikely]] */
233
0
        {
234
0
            throw std::runtime_error(gai_strerror(err));
235
0
        }
236
0
        return std::string(buff);
237
0
    }
238
239
    void IP::toNetwork(PST_IN_ADDR_T* out) const
240
0
    {
241
0
        if (addr_.ss_family != AF_INET)
242
0
        {
243
0
            throw std::invalid_argument("Inapplicable or invalid address family");
244
0
        }
245
        // Note that in Windows in_addr is a union - effectively a "u_long"
246
        // that can be accessed as 4 u_char, 2 u_short, or 1 u_long.
247
        // Meanwhile, s_addr on the right-hand side is type ULONG. So both
248
        // sides are effectively "ulong *" and the cast is valid
249
0
        const PST_IN_ADDR_T* out_ptr   = reinterpret_cast<const PST_IN_ADDR_T*>
250
0
            (&reinterpret_cast<const struct sockaddr_in*>(&addr_)->sin_addr.s_addr);
251
0
        *out = *out_ptr;
252
0
    }
253
254
    void IP::toNetwork(struct in6_addr* out) const
255
0
    {
256
0
        if (addr_.ss_family != AF_INET)
257
0
        {
258
0
            throw std::invalid_argument("Inapplicable or invalid address family");
259
0
        }
260
0
        *out = reinterpret_cast<const struct sockaddr_in6*>(&addr_)->sin6_addr;
261
0
    }
262
263
    bool IP::supported()
264
0
    {
265
0
        struct PST_IFADDRS* ifaddr = nullptr;
266
0
        struct PST_IFADDRS* ifa    = nullptr;
267
0
        int addr_family, n;
268
0
        bool supportsIpv6 = false;
269
270
0
        if (PST_GETIFADDRS(&ifaddr) == -1)
271
0
        {
272
0
            throw std::runtime_error("Call to getifaddrs() failed");
273
0
        }
274
275
0
        for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++)
276
0
        {
277
0
            if (ifa->ifa_addr == nullptr)
278
0
            {
279
0
                continue;
280
0
            }
281
282
0
            addr_family = ifa->ifa_addr->sa_family;
283
0
            if (addr_family == AF_INET6)
284
0
            {
285
0
                supportsIpv6 = true;
286
0
                continue;
287
0
            }
288
0
        }
289
290
0
        PST_FREEIFADDRS(ifaddr);
291
0
        return supportsIpv6;
292
0
    }
293
294
    AddressParser::AddressParser(const std::string& data)
295
9.80k
    {
296
        /* If the passed value is a simple IPv6 address as defined by RFC 2373
297
         * (i.e without port nor '[' and ']'), no custom parsing is required. */
298
9.80k
        struct in6_addr tmp;
299
9.80k
        if (inet_pton(AF_INET6, data.c_str(), &tmp) == 1)
300
87
        {
301
87
            char normalized_addr[INET6_ADDRSTRLEN];
302
87
            inet_ntop(AF_INET6, &tmp, normalized_addr, sizeof(normalized_addr));
303
87
            host_   = normalized_addr;
304
87
            family_ = AF_INET6;
305
87
            return;
306
87
        }
307
308
9.71k
        std::size_t end_pos   = data.find(']');
309
9.71k
        std::size_t start_pos = data.find('[');
310
9.71k
        if (start_pos != std::string::npos && end_pos != std::string::npos && start_pos < end_pos)
311
1.77k
        {
312
1.77k
            std::size_t colon_pos = data.find_first_of(':', end_pos);
313
1.77k
            if (colon_pos != std::string::npos)
314
443
            {
315
443
                hasColon_ = true;
316
443
            }
317
            // Strip '[' and ']' in IPv6 addresses, as it is not part of the
318
            // address itself according to RFC 4291 and RFC 5952, but just a way
319
            // to represent address + port in an unambiguous way.
320
1.77k
            host_   = data.substr(start_pos + 1, end_pos - 1);
321
1.77k
            family_ = AF_INET6;
322
1.77k
            ++end_pos;
323
1.77k
        }
324
7.94k
        else
325
7.94k
        {
326
7.94k
            std::size_t colon_pos = data.find(':');
327
7.94k
            if (colon_pos != std::string::npos)
328
2.03k
            {
329
2.03k
                hasColon_ = true;
330
2.03k
            }
331
7.94k
            end_pos = colon_pos;
332
7.94k
            host_   = data.substr(0, end_pos);
333
7.94k
            family_ = AF_INET;
334
7.94k
        }
335
336
9.71k
        if (end_pos != std::string::npos && hasColon_)
337
2.47k
        {
338
2.47k
            port_ = data.substr(end_pos + 1);
339
2.47k
            if (port_.empty())
340
45
            {
341
45
                PS_LOG_DEBUG_ARGS("port_ empty, data (addr string) %s, "
342
45
                                  "throwing \"Invalid port\"",
343
45
                                  data.c_str());
344
345
45
                throw std::invalid_argument("Invalid port");
346
45
            }
347
348
            // Check if port_ is a valid number
349
2.42k
            char* tmp2 = nullptr;
350
2.42k
            long strtol_res = std::strtol(port_.c_str(), &tmp2, 10);
351
2.42k
            if ((strtol_res < 0) || (!tmp2))
352
74
            {
353
74
                PS_LOG_DEBUG_ARGS("strtol failed for port_ %s, "
354
74
                                  "throwing \"Invalid port\"",
355
74
                                  port_.c_str());
356
74
                throw std::invalid_argument("Invalid port");
357
74
            }
358
2.35k
            hasNumericPort_ = (*tmp2 == '\0');
359
2.35k
        }
360
9.71k
    }
361
362
9.68k
    const std::string& AddressParser::rawHost() const { return host_; }
363
364
9.68k
    const std::string& AddressParser::rawPort() const { return port_; }
365
366
0
    bool AddressParser::hasColon() const { return hasColon_; }
367
368
0
    bool AddressParser::hasNumericPort() const { return hasNumericPort_; }
369
370
9.68k
    int AddressParser::family() const { return family_; }
371
372
    Address::Address()
373
2.27k
        : ip_ {}
374
2.27k
        , port_ { 0 }
375
2.27k
        , addrLen_(sizeof(struct sockaddr_in6))
376
2.27k
        , scheme_(Scheme::Unspecified)
377
2.27k
    { }
378
379
    Address::Address(std::string host, Port port)
380
0
    {
381
0
        std::string addr = std::move(host);
382
0
        addr.append(":");
383
0
        addr.append(port.toString());
384
0
        init(std::move(addr), 0 /* no default port, set explicitly */);
385
0
    }
386
387
    Address::Address(std::string addr)
388
0
    {
389
0
        init(std::move(addr), 0 /* default port*/);
390
0
    }
391
392
    Address::Address(const char* addr)
393
0
    {
394
0
        init(std::string(addr), 0 /* default port*/);
395
0
    }
396
397
    Address Address::makeWithDefaultPort(std::string addr,
398
                                         Port default_port, // defaults to zero
399
                                         Scheme scheme,
400
                                         const std::string * page_cptr)
401
0
    { // static
402
0
        Address res;
403
0
        res.init(std::move(addr), default_port, scheme, page_cptr);
404
405
0
        return (res);
406
0
    }
407
408
    Address::Address(IP ip, Port port)
409
0
        : ip_(ip)
410
0
        , port_(port)
411
0
    {
412
0
        addrLen_ = ip.getFamily() == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
413
0
    }
414
415
    Address Address::fromUnix(struct sockaddr* addr)
416
0
    {
417
0
        const auto family = addr->sa_family;
418
0
        if (family == AF_INET || family == AF_INET6 || family == AF_UNIX)
419
0
        {
420
0
            IP ip     = IP(addr);
421
0
            Port port = Port(ip.getPort());
422
0
            return Address(ip, port);
423
0
        }
424
0
        throw Error("Not an IP or unix domain socket");
425
0
    }
426
427
0
    std::string Address::host() const { return ip_.toString(); }
428
429
0
    Port Address::port() const { return port_; }
430
431
0
    int Address::family() const { return ip_.getFamily(); }
432
433
    void Address::init(const std::string& addr)
434
0
    {
435
0
        init(addr, 0 /*default port*/);
436
0
    }
437
438
    void Address::init(const std::string& addr, Port default_port)
439
0
    {
440
0
        scheme_ = Scheme::Unspecified;
441
442
        // Handle unix domain addresses separately.
443
0
        if (isUnixDomain(addr))
444
0
        {
445
0
            struct sockaddr_un unAddr = {};
446
0
            unAddr.sun_family         = AF_UNIX;
447
448
            // See unix(7) manual page; distinguish among unnamed, abstract,
449
            // and pathname socket addresses.
450
0
            const auto size = std::min(addr.size(), sizeof unAddr.sun_path);
451
0
            if (size == 0)
452
0
            {
453
0
                addrLen_ = sizeof unAddr.sun_family;
454
0
            }
455
0
            else if (addr[0] == '\0')
456
0
            {
457
0
                addrLen_ = static_cast<socklen_t>(
458
0
                    sizeof unAddr.sun_family + size);
459
0
                std::memcpy(unAddr.sun_path, addr.data(), size);
460
0
            }
461
0
            else
462
0
            {
463
0
                addrLen_ = static_cast<socklen_t>(
464
0
                    offsetof(struct sockaddr_un, sun_path) + size);
465
0
                PS_STRLCPY(unAddr.sun_path, addr.c_str(), size);
466
0
                if (size == sizeof unAddr.sun_path)
467
0
                {
468
0
                    unAddr.sun_path[size - 1] = '\0';
469
0
                }
470
0
            }
471
472
0
            ip_   = IP(reinterpret_cast<struct sockaddr*>(&unAddr));
473
0
            port_ = Port(ip_.getPort());
474
0
            return;
475
0
        }
476
477
0
        if (!default_port)
478
0
            default_port = 80;
479
0
        std::string default_port_str(std::to_string(default_port));
480
481
0
        addrLen_ = family() == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
482
483
0
        const AddressParser parser(addr);
484
0
        const std::string& host = parser.rawHost();
485
0
        const std::string& port = parser.rawPort();
486
487
0
        const bool wildcard = host == "*";
488
489
0
        struct addrinfo hints = {};
490
0
        hints.ai_family       = AF_UNSPEC;
491
0
        hints.ai_socktype     = SOCK_STREAM;
492
0
        hints.ai_protocol     = IPPROTO_TCP;
493
494
0
        if (wildcard)
495
0
        {
496
0
            hints.ai_flags = AI_PASSIVE;
497
0
        }
498
499
        // The host is set to nullptr if empty because getaddrinfo() requires
500
        // it, and also when it is set to "*" because, when combined with the
501
        // AI_PASSIVE flag, it yields the proper wildcard address. The port, if
502
        // empty, is set to 80 (http) by default.
503
0
        const char* const addrinfo_host = host.empty() || wildcard ? nullptr : host.c_str();
504
0
        const char* const addrinfo_port = port.empty() ? default_port_str.c_str() : port.c_str();
505
506
0
        AddrInfo addrinfo;
507
0
        const int err = addrinfo.invoke(addrinfo_host, addrinfo_port, &hints);
508
0
        if (err)
509
0
        {
510
0
            throw std::invalid_argument(gai_strerror(err));
511
0
        }
512
513
0
        const struct addrinfo* result = addrinfo.get_info_ptr();
514
515
0
        ip_   = IP(result->ai_addr);
516
0
        port_ = Port(ip_.getPort());
517
518
        // Check that the port has not overflowed while calling getaddrinfo()
519
0
        if (parser.hasNumericPort() && port_ != std::strtol(addrinfo_port, nullptr, 10))
520
0
        {
521
0
            throw std::invalid_argument("Invalid numeric port");
522
0
        }
523
0
    }
524
525
    void Address::init(const std::string& addr, Port default_port,
526
                       Scheme scheme, const std::string * page_cptr)
527
0
    {
528
0
        init(addr, default_port);
529
0
        scheme_ = scheme;
530
531
0
        if (page_cptr)
532
0
            page_ = (*page_cptr);
533
0
    }
534
535
    // Applies heuristics to deterimine whether or not addr names a unix
536
    // domain address.  If it is zero-length, begins with a NUL byte, or
537
    // contains a '/' character (none of which are possible for legitimate
538
    // IP-based addresses), it's deemed to be a unix domain address.
539
    //
540
    // This heuristic rejects pathname unix domain addresses that contain no
541
    // '/' characters; such addresses tend not to occur in practice.  See the
542
    // unix(7) manual page for more infomation.
543
    bool Address::isUnixDomain(const std::string& addr)
544
0
    {
545
0
        return addr.size() == 0 || addr[0] == '\0' || addr.find('/') != std::string::npos;
546
0
    }
547
548
    std::ostream& operator<<(std::ostream& os, const Address& address)
549
0
    {
550
        /* As recommended by section 6 of RFC 5952,
551
         * Notes on Combining IPv6 Addresses with Port Numbers */
552
0
        if (address.family() == AF_INET6)
553
0
        {
554
0
            os << '[';
555
0
        }
556
0
        os << address.host();
557
0
        if (address.family() == AF_INET6)
558
0
        {
559
0
            os << ']';
560
0
        }
561
0
        os << ":" << address.port();
562
0
        return os;
563
0
    }
564
565
    Error::Error(const char* message)
566
0
        : std::runtime_error(message)
567
0
    { }
568
569
    Error::Error(std::string message)
570
0
        : std::runtime_error(std::move(message))
571
0
    { }
572
573
    Error Error::system(const char* message)
574
0
    {
575
0
        PST_DECL_SE_ERR_P_EXTRA;
576
577
0
        std::string str(message);
578
0
        str += ": ";
579
0
        str += PST_STRERROR_R_ERRNO;
580
581
0
        return Error(std::move(str));
582
0
    }
583
584
} // namespace Pistache