librats 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,17 @@
16
16
  #else
17
17
  #include <ifaddrs.h>
18
18
  #endif
19
+
20
+ // macOS / BSD default-gateway lookup via the PF_ROUTE sysctl routing table.
21
+ #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
22
+ defined(__OpenBSD__) || defined(__DragonFly__)
23
+ #define RATS_HAVE_BSD_ROUTES 1
24
+ #include <sys/types.h>
25
+ #include <sys/socket.h>
26
+ #include <sys/sysctl.h>
27
+ #include <net/route.h>
28
+ #include <net/if.h>
29
+ #endif
19
30
  #endif
20
31
 
21
32
 
@@ -25,6 +36,12 @@
25
36
  #include <cstring>
26
37
  #include <iostream>
27
38
  #include <vector>
39
+ #include <algorithm>
40
+
41
+ #ifndef _WIN32
42
+ #include <cstdio>
43
+ #include <cstdlib>
44
+ #endif
28
45
 
29
46
  // Network utilities module logging macros
30
47
  #define LOG_NETUTILS_DEBUG(message) LOG_DEBUG("network_utils", message)
@@ -379,6 +396,41 @@ bool is_hostname(const std::string& str) {
379
396
  return true;
380
397
  }
381
398
 
399
+ bool is_public_ip(const std::string& ip) {
400
+ if (ip.empty()) return false;
401
+
402
+ if (is_valid_ipv6(ip)) {
403
+ struct in6_addr a;
404
+ if (inet_pton(AF_INET6, ip.c_str(), &a) != 1) return false;
405
+ const uint8_t* b = a.s6_addr;
406
+ bool all_zero = true;
407
+ for (int i = 0; i < 16; ++i) { if (b[i]) { all_zero = false; break; } }
408
+ if (all_zero) return false; // :: (unspecified)
409
+ bool loopback = (b[15] == 1);
410
+ for (int i = 0; i < 15; ++i) { if (b[i]) { loopback = false; break; } }
411
+ if (loopback) return false; // ::1
412
+ if ((b[0] & 0xfe) == 0xfc) return false; // fc00::/7 unique local
413
+ if (b[0] == 0xfe && (b[1] & 0xc0) == 0x80) return false; // fe80::/10 link-local
414
+ if (b[0] == 0xff) return false; // ff00::/8 multicast
415
+ return true;
416
+ }
417
+
418
+ struct in_addr a;
419
+ if (inet_pton(AF_INET, ip.c_str(), &a) != 1) return false;
420
+ uint32_t h = ntohl(a.s_addr);
421
+ uint8_t o1 = static_cast<uint8_t>((h >> 24) & 0xff);
422
+ uint8_t o2 = static_cast<uint8_t>((h >> 16) & 0xff);
423
+ if (o1 == 0) return false; // 0.0.0.0/8
424
+ if (o1 == 127) return false; // loopback
425
+ if (o1 == 10) return false; // 10.0.0.0/8
426
+ if (o1 == 172 && o2 >= 16 && o2 <= 31) return false; // 172.16.0.0/12
427
+ if (o1 == 192 && o2 == 168) return false; // 192.168.0.0/16
428
+ if (o1 == 169 && o2 == 254) return false; // 169.254.0.0/16 link-local
429
+ if (o1 == 100 && o2 >= 64 && o2 <= 127) return false; // 100.64.0.0/10 CGNAT
430
+ if (o1 >= 224) return false; // multicast / reserved
431
+ return true;
432
+ }
433
+
382
434
  std::vector<std::string> get_local_interface_addresses() {
383
435
  LOG_NETUTILS_DEBUG("Getting all local interface addresses (IPv4 and IPv6)");
384
436
 
@@ -390,11 +442,140 @@ std::vector<std::string> get_local_interface_addresses() {
390
442
  auto ipv6_addresses = get_local_interface_addresses_v6();
391
443
  addresses.insert(addresses.end(), ipv6_addresses.begin(), ipv6_addresses.end());
392
444
 
393
- LOG_NETUTILS_INFO("Found " << addresses.size() << " total local interface addresses ("
445
+ LOG_NETUTILS_INFO("Found " << addresses.size() << " total local interface addresses ("
394
446
  << ipv4_addresses.size() << " IPv4, " << ipv6_addresses.size() << " IPv6)");
395
-
447
+
396
448
  return addresses;
397
449
  }
398
450
 
451
+ namespace {
452
+
453
+ // Append unique, non-empty entries preserving order
454
+ void append_unique(std::vector<std::string>& out, const std::string& value) {
455
+ if (value.empty()) return;
456
+ if (std::find(out.begin(), out.end(), value) == out.end()) {
457
+ out.push_back(value);
458
+ }
459
+ }
460
+
461
+ // Best-effort guess: for each local IPv4 assume the gateway is the .1 host of a
462
+ // /24 network. Covers the overwhelming majority of home routers and serves as a
463
+ // fallback when the OS routing table is unavailable.
464
+ void append_gateway_heuristics(std::vector<std::string>& out) {
465
+ for (const auto& ip : get_local_interface_addresses_v4()) {
466
+ if (ip.empty() || ip == "127.0.0.1") continue;
467
+ auto last_dot = ip.find_last_of('.');
468
+ if (last_dot == std::string::npos) continue;
469
+ append_unique(out, ip.substr(0, last_dot) + ".1");
470
+ }
471
+ }
472
+
473
+ } // anonymous namespace
474
+
475
+ std::vector<std::string> get_default_gateways() {
476
+ std::vector<std::string> gateways;
477
+
478
+ #ifdef _WIN32
479
+ ULONG out_buf_len = sizeof(IP_ADAPTER_INFO);
480
+ std::vector<uint8_t> buffer(out_buf_len);
481
+ DWORD ret = GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(buffer.data()), &out_buf_len);
482
+ if (ret == ERROR_BUFFER_OVERFLOW) {
483
+ buffer.resize(out_buf_len);
484
+ ret = GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(buffer.data()), &out_buf_len);
485
+ }
486
+ if (ret == NO_ERROR) {
487
+ for (PIP_ADAPTER_INFO adapter = reinterpret_cast<PIP_ADAPTER_INFO>(buffer.data());
488
+ adapter != nullptr; adapter = adapter->Next) {
489
+ for (const IP_ADDR_STRING* gw = &adapter->GatewayList; gw != nullptr; gw = gw->Next) {
490
+ std::string gw_ip(gw->IpAddress.String);
491
+ if (gw_ip != "0.0.0.0") {
492
+ append_unique(gateways, gw_ip);
493
+ }
494
+ }
495
+ }
496
+ } else {
497
+ LOG_NETUTILS_DEBUG("GetAdaptersInfo failed with error: " << ret);
498
+ }
499
+ #elif defined(__linux__)
500
+ // /proc/net/route columns: Iface Destination Gateway Flags ... (hex, little-endian)
501
+ if (FILE* f = std::fopen("/proc/net/route", "r")) {
502
+ char line[256];
503
+ // Skip header line
504
+ if (std::fgets(line, sizeof(line), f)) {
505
+ char iface[64];
506
+ unsigned long dest = 0, gw = 0;
507
+ while (std::fgets(line, sizeof(line), f)) {
508
+ if (std::sscanf(line, "%63s %lx %lx", iface, &dest, &gw) == 3) {
509
+ if (dest == 0 && gw != 0) {
510
+ struct in_addr addr;
511
+ addr.s_addr = static_cast<in_addr_t>(gw);
512
+ char ip_str[INET_ADDRSTRLEN];
513
+ if (inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str))) {
514
+ append_unique(gateways, ip_str);
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ std::fclose(f);
521
+ }
522
+ #elif defined(RATS_HAVE_BSD_ROUTES)
523
+ // Dump the IPv4 routing table and pick the gateway of the default route(s).
524
+ int mib[6] = { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_DUMP, 0 };
525
+ size_t needed = 0;
526
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) == 0 && needed > 0) {
527
+ std::vector<char> buf(needed);
528
+ if (sysctl(mib, 6, buf.data(), &needed, nullptr, 0) == 0) {
529
+ // sockaddrs in a routing message are padded to a 4-byte boundary.
530
+ auto sa_roundup = [](socklen_t len) -> size_t {
531
+ return len ? (1 + ((static_cast<size_t>(len) - 1) | (sizeof(uint32_t) - 1)))
532
+ : sizeof(uint32_t);
533
+ };
534
+ char* lim = buf.data() + needed;
535
+ for (char* next = buf.data(); next + sizeof(struct rt_msghdr) <= lim; ) {
536
+ auto* rtm = reinterpret_cast<struct rt_msghdr*>(next);
537
+ if (rtm->rtm_msglen == 0) break;
538
+ char* msg_end = next + rtm->rtm_msglen;
539
+ next = msg_end;
540
+
541
+ if (!(rtm->rtm_flags & RTF_GATEWAY)) continue;
542
+ if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY)) continue;
543
+
544
+ // Address list follows the header, ordered by the RTA_* bit flags.
545
+ char* sa_ptr = reinterpret_cast<char*>(rtm + 1);
546
+ struct sockaddr* dst = nullptr;
547
+ struct sockaddr* gw = nullptr;
548
+ for (int bit = 1; bit && sa_ptr < msg_end; bit <<= 1) {
549
+ if (!(rtm->rtm_addrs & bit)) continue;
550
+ auto* sa = reinterpret_cast<struct sockaddr*>(sa_ptr);
551
+ if (bit == RTA_DST) dst = sa;
552
+ else if (bit == RTA_GATEWAY) gw = sa;
553
+ sa_ptr += sa_roundup(sa->sa_len);
554
+ }
555
+
556
+ if (!dst || !gw) continue;
557
+ if (dst->sa_family != AF_INET || gw->sa_family != AF_INET) continue;
558
+ // Default route: destination 0.0.0.0
559
+ if (reinterpret_cast<struct sockaddr_in*>(dst)->sin_addr.s_addr != 0) continue;
560
+
561
+ char ip_str[INET_ADDRSTRLEN];
562
+ auto* gw4 = reinterpret_cast<struct sockaddr_in*>(gw);
563
+ if (inet_ntop(AF_INET, &gw4->sin_addr, ip_str, sizeof(ip_str))) {
564
+ append_unique(gateways, ip_str);
565
+ }
566
+ }
567
+ }
568
+ } else {
569
+ LOG_NETUTILS_DEBUG("PF_ROUTE sysctl for default gateway failed");
570
+ }
571
+ #endif
572
+
573
+ // Always add heuristics as a fallback so callers have something to try
574
+ append_gateway_heuristics(gateways);
575
+
576
+ LOG_NETUTILS_INFO("Detected " << gateways.size() << " default gateway candidate(s)");
577
+ return gateways;
578
+ }
579
+
399
580
  } // namespace network_utils
400
581
  } // namespace librats
@@ -41,11 +41,38 @@ bool is_valid_ipv6(const std::string& ip_str);
41
41
  */
42
42
  bool is_hostname(const std::string& str);
43
43
 
44
+ /**
45
+ * Check whether an IP address is publicly routable (not a private/reserved range).
46
+ *
47
+ * Returns false for RFC1918 (10/8, 172.16/12, 192.168/16), CGNAT (100.64/10),
48
+ * loopback, link-local (169.254/16, fe80::/10), unspecified, multicast/reserved,
49
+ * and IPv6 unique-local (fc00::/7). A non-IP / unparseable string yields false.
50
+ *
51
+ * Used both by the DHT (BEP 42 external-IP voting) and by automatic port
52
+ * forwarding to detect a double-NAT gateway whose reported "external" IP is itself
53
+ * private and therefore not a usable public endpoint.
54
+ */
55
+ bool is_public_ip(const std::string& ip);
56
+
44
57
  /**
45
58
  * Get all local network interface addresses (IPv4 and IPv6)
46
59
  * @return Vector of local IP addresses from all network interfaces
47
60
  */
48
61
  std::vector<std::string> get_local_interface_addresses();
49
62
 
63
+ /**
64
+ * Get the default IPv4 gateway address(es) of the host.
65
+ *
66
+ * Used for NAT port forwarding (NAT-PMP talks to the gateway directly, and UPnP
67
+ * can use it to restrict discovery to the local router). The OS routing table is
68
+ * consulted where available (Windows iphlpapi, Linux /proc/net/route). When the
69
+ * platform routing table cannot be read, a best-effort heuristic derived from the
70
+ * local IPv4 addresses (network .1) is appended so callers still have a candidate
71
+ * to try.
72
+ *
73
+ * @return Vector of gateway IPv4 addresses, most specific first. May be empty.
74
+ */
75
+ std::vector<std::string> get_default_gateways();
76
+
50
77
  } // namespace network_utils
51
78
  } // namespace librats
@@ -0,0 +1,78 @@
1
+ #pragma once
2
+
3
+ /**
4
+ * @file port_mapping.h
5
+ * @brief Shared types for automatic NAT port forwarding (UPnP IGD + NAT-PMP)
6
+ *
7
+ * Both the UPnP (@ref UpnpClient) and NAT-PMP (@ref NatPmpClient) backends ask a
8
+ * home router to forward an external (WAN) port to a local (LAN) port so that
9
+ * inbound peer connections can reach a host behind NAT. They share the small set
10
+ * of vocabulary types defined here: the transport protocol of the mapping, which
11
+ * backend produced a result, and the result/callback shape RatsClient consumes.
12
+ */
13
+
14
+ #include <cstdint>
15
+ #include <string>
16
+ #include <functional>
17
+
18
+ namespace librats {
19
+
20
+ /// Transport protocol of a port mapping.
21
+ enum class PortMapProtocol : uint8_t {
22
+ TCP,
23
+ UDP
24
+ };
25
+
26
+ /// Which NAT traversal backend produced a result.
27
+ enum class PortMapTransport : uint8_t {
28
+ UPnP,
29
+ NatPMP
30
+ };
31
+
32
+ /// Human readable protocol name ("TCP"/"UDP").
33
+ inline const char* to_string(PortMapProtocol p) {
34
+ return p == PortMapProtocol::TCP ? "TCP" : "UDP";
35
+ }
36
+
37
+ /// Human readable transport name ("UPnP"/"NAT-PMP").
38
+ inline const char* to_string(PortMapTransport t) {
39
+ return t == PortMapTransport::UPnP ? "UPnP" : "NAT-PMP";
40
+ }
41
+
42
+ /**
43
+ * Result of a port mapping attempt.
44
+ *
45
+ * On success @ref external_port holds the public port the router assigned (which
46
+ * may differ from the requested one) and, when the backend can report it,
47
+ * @ref external_ip holds the discovered public IP address.
48
+ */
49
+ struct PortMapResult {
50
+ PortMapTransport transport; ///< Backend that produced this result
51
+ PortMapProtocol protocol; ///< Protocol of the mapping
52
+ bool success = false; ///< Whether the mapping is currently active
53
+ uint16_t internal_port = 0; ///< Local (LAN) port that was mapped
54
+ uint16_t external_port = 0; ///< Public (WAN) port assigned by the router
55
+ std::string external_ip; ///< Discovered public IP (may be empty)
56
+ std::string error; ///< Human readable error when !success
57
+ };
58
+
59
+ /**
60
+ * Callback invoked whenever a mapping is established, refreshed, removed or fails.
61
+ * Always called from the backend's own worker thread.
62
+ */
63
+ using PortMapCallback = std::function<void(const PortMapResult&)>;
64
+
65
+ /**
66
+ * Configuration for RatsClient's automatic port forwarding.
67
+ *
68
+ * Both backends run in parallel by default; whichever the router supports
69
+ * succeeds. Disabling one (or all) of them is a matter of flipping a flag.
70
+ */
71
+ struct PortMappingConfig {
72
+ bool enabled = true; ///< Master switch for automatic port forwarding
73
+ bool enable_upnp = true; ///< Use the UPnP IGD backend
74
+ bool enable_natpmp = true; ///< Use the NAT-PMP backend
75
+ uint32_t lease_duration_seconds = 3600; ///< Requested lease duration
76
+ };
77
+
78
+ } // namespace librats
@@ -844,18 +844,29 @@ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data,
844
844
  }
845
845
 
846
846
  std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer& sender_peer,
847
- int timeout_ms) {
848
- // Handle timeout using select
849
- if (timeout_ms >= 0) {
847
+ int timeout_ms, socket_t interrupt_fd) {
848
+ // Handle timeout (and optional interrupt socket) using select. When no interrupt
849
+ // fd is supplied this path is identical to the plain timeout behavior.
850
+ const bool have_interrupt = is_valid_socket(interrupt_fd);
851
+ if (timeout_ms >= 0 || have_interrupt) {
850
852
  fd_set read_fds;
851
853
  FD_ZERO(&read_fds);
852
854
  FD_SET(socket, &read_fds);
855
+ socket_t maxfd = socket;
856
+ if (have_interrupt) {
857
+ FD_SET(interrupt_fd, &read_fds);
858
+ if (interrupt_fd > maxfd) maxfd = interrupt_fd;
859
+ }
853
860
 
854
861
  struct timeval timeout;
855
- timeout.tv_sec = timeout_ms / 1000;
856
- timeout.tv_usec = (timeout_ms % 1000) * 1000;
862
+ struct timeval* ptimeout = nullptr; // timeout_ms < 0 => block until readable
863
+ if (timeout_ms >= 0) {
864
+ timeout.tv_sec = timeout_ms / 1000;
865
+ timeout.tv_usec = (timeout_ms % 1000) * 1000;
866
+ ptimeout = &timeout;
867
+ }
857
868
 
858
- int result = select(socket + 1, &read_fds, nullptr, nullptr, &timeout);
869
+ int result = select(static_cast<int>(maxfd) + 1, &read_fds, nullptr, nullptr, ptimeout);
859
870
  if (result == 0) {
860
871
  LOG_SOCKET_DEBUG("UDP receive timeout (" << timeout_ms << "ms)");
861
872
  return {};
@@ -863,6 +874,15 @@ std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer&
863
874
  LOG_SOCKET_ERROR("Select error while waiting for UDP data");
864
875
  return {};
865
876
  }
877
+ // Woken by the interrupt socket (e.g. stop requested): leave the data socket
878
+ // untouched and report no data so the caller can re-check its stop flag.
879
+ if (have_interrupt && FD_ISSET(interrupt_fd, &read_fds)) {
880
+ return {};
881
+ }
882
+ // Guard against calling recvfrom on a data socket that isn't actually ready.
883
+ if (!FD_ISSET(socket, &read_fds)) {
884
+ return {};
885
+ }
866
886
  }
867
887
 
868
888
  std::vector<uint8_t> buffer(buffer_size);
@@ -170,10 +170,14 @@ int send_udp_data(socket_t socket, const std::vector<uint8_t>& data, const std::
170
170
  * @param buffer_size Maximum number of bytes to receive
171
171
  * @param sender_peer Output parameter for the sender's peer info
172
172
  * @param timeout_ms Timeout in milliseconds (-1 for blocking, 0 for non-blocking, >0 for timeout)
173
- * @return Received data, empty vector on timeout or error
173
+ * @param interrupt_fd Optional second socket to watch; when it becomes readable the
174
+ * call returns immediately with an empty vector (used to wake a
175
+ * blocking receive on shutdown). INVALID_SOCKET_VALUE disables it.
176
+ * @return Received data, empty vector on timeout, error or interrupt
174
177
  */
175
178
  std::vector<uint8_t> receive_udp_data(socket_t socket, size_t buffer_size, Peer& sender_peer,
176
- int timeout_ms = -1);
179
+ int timeout_ms = -1,
180
+ socket_t interrupt_fd = INVALID_SOCKET_VALUE);
177
181
 
178
182
  // Common Socket Functions
179
183
  /**