librats 0.9.1 → 1.0.0
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.
- package/native-src/CMakeLists.txt +14 -0
- package/native-src/src/dht.cpp +3 -32
- package/native-src/src/dht.h +9 -1
- package/native-src/src/librats.cpp +42 -12
- package/native-src/src/librats.h +146 -2
- package/native-src/src/librats_discovery.cpp +22 -3
- package/native-src/src/librats_persistence.cpp +12 -2
- package/native-src/src/librats_portmap.cpp +419 -0
- package/native-src/src/natpmp.cpp +416 -0
- package/native-src/src/natpmp.h +140 -0
- package/native-src/src/network_monitor.cpp +367 -0
- package/native-src/src/network_monitor.h +104 -0
- package/native-src/src/network_utils.cpp +183 -2
- package/native-src/src/network_utils.h +27 -0
- package/native-src/src/port_mapping.h +78 -0
- package/native-src/src/socket.cpp +26 -6
- package/native-src/src/socket.h +6 -2
- package/native-src/src/upnp.cpp +635 -0
- package/native-src/src/upnp.h +163 -0
- package/native-src/src/wakeup_pipe.h +60 -0
- package/package.json +1 -1
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file network_monitor.cpp
|
|
3
|
+
* @brief Platform backends for NetworkMonitor (see network_monitor.h).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// socket.h pulls in winsock2.h/ws2tcpip.h first on Windows (must precede the
|
|
7
|
+
// iphlpapi / windows headers below to avoid the classic winsock2/windows.h clash).
|
|
8
|
+
#include "socket.h"
|
|
9
|
+
#include "network_monitor.h"
|
|
10
|
+
#include "network_utils.h"
|
|
11
|
+
#include "logger.h"
|
|
12
|
+
|
|
13
|
+
#include <algorithm>
|
|
14
|
+
|
|
15
|
+
#ifdef _WIN32
|
|
16
|
+
#include <iphlpapi.h>
|
|
17
|
+
#include <netioapi.h>
|
|
18
|
+
#elif defined(__linux__)
|
|
19
|
+
#include <sys/socket.h>
|
|
20
|
+
#include <linux/netlink.h>
|
|
21
|
+
#include <linux/rtnetlink.h>
|
|
22
|
+
#include <unistd.h>
|
|
23
|
+
#include <poll.h>
|
|
24
|
+
#include <cstring>
|
|
25
|
+
#include <cerrno>
|
|
26
|
+
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
|
|
27
|
+
defined(__OpenBSD__) || defined(__DragonFly__)
|
|
28
|
+
#define RATS_MONITOR_BSD_ROUTES 1
|
|
29
|
+
#include <sys/types.h>
|
|
30
|
+
#include <sys/socket.h>
|
|
31
|
+
#include <net/route.h>
|
|
32
|
+
#include <net/if.h>
|
|
33
|
+
#include <unistd.h>
|
|
34
|
+
#include <poll.h>
|
|
35
|
+
#include <cerrno>
|
|
36
|
+
#endif
|
|
37
|
+
|
|
38
|
+
#define LOG_NETMON_DEBUG(message) LOG_DEBUG("netmon", message)
|
|
39
|
+
#define LOG_NETMON_INFO(message) LOG_INFO("netmon", message)
|
|
40
|
+
#define LOG_NETMON_WARN(message) LOG_WARN("netmon", message)
|
|
41
|
+
#define LOG_NETMON_ERROR(message) LOG_ERROR("netmon", message)
|
|
42
|
+
|
|
43
|
+
namespace librats {
|
|
44
|
+
|
|
45
|
+
namespace {
|
|
46
|
+
|
|
47
|
+
std::vector<std::string> snapshot_addresses() {
|
|
48
|
+
auto addrs = network_utils::get_local_interface_addresses();
|
|
49
|
+
std::sort(addrs.begin(), addrs.end());
|
|
50
|
+
addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end());
|
|
51
|
+
return addrs;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Platform backend state
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
struct NetworkMonitor::Impl {
|
|
61
|
+
#ifdef _WIN32
|
|
62
|
+
HANDLE handle = nullptr;
|
|
63
|
+
#elif defined(__linux__) || defined(RATS_MONITOR_BSD_ROUTES)
|
|
64
|
+
int fd = -1; // netlink (Linux) or PF_ROUTE (BSD) socket
|
|
65
|
+
int stop_pipe[2] = {-1, -1};
|
|
66
|
+
std::thread reader;
|
|
67
|
+
#endif
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
#ifdef _WIN32
|
|
71
|
+
// NotifyUnicastIpAddressChange invokes this from an OS worker thread on any
|
|
72
|
+
// unicast address add/remove/change. Windows gives no usable detail here, so we
|
|
73
|
+
// just trigger a re-enumeration (matches the cross-platform "something changed"
|
|
74
|
+
// contract). Must be __stdcall (WINAPI) to match PUNICAST_IPADDRESS_CHANGE_CALLBACK.
|
|
75
|
+
static void WINAPI rats_ip_change_cb(void* ctx, MIB_UNICASTIPADDRESS_ROW* /*row*/,
|
|
76
|
+
MIB_NOTIFICATION_TYPE /*type*/) {
|
|
77
|
+
auto* self = static_cast<NetworkMonitor*>(ctx);
|
|
78
|
+
if (self) self->check_now();
|
|
79
|
+
}
|
|
80
|
+
#endif
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Lifecycle
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
NetworkMonitor::NetworkMonitor() = default;
|
|
87
|
+
|
|
88
|
+
NetworkMonitor::~NetworkMonitor() {
|
|
89
|
+
stop();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
bool NetworkMonitor::start(ChangeCallback on_change) {
|
|
93
|
+
if (running_.exchange(true)) {
|
|
94
|
+
return event_backend_active_;
|
|
95
|
+
}
|
|
96
|
+
on_change_ = std::move(on_change);
|
|
97
|
+
impl_ = std::make_unique<Impl>();
|
|
98
|
+
last_addresses_ = snapshot_addresses();
|
|
99
|
+
|
|
100
|
+
event_backend_active_ = backend_start();
|
|
101
|
+
LOG_NETMON_INFO("Network monitor started ("
|
|
102
|
+
<< (event_backend_active_ ? "event-driven" : "polling")
|
|
103
|
+
<< ", " << last_addresses_.size() << " local address(es))");
|
|
104
|
+
|
|
105
|
+
worker_ = std::thread([this]() { worker_loop(); });
|
|
106
|
+
return event_backend_active_;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void NetworkMonitor::stop() {
|
|
110
|
+
if (!running_.exchange(false)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
cv_.notify_all(); // wake the worker out of its wait
|
|
114
|
+
backend_stop(); // stop OS notifications / join the reader thread
|
|
115
|
+
if (worker_.joinable()) {
|
|
116
|
+
worker_.join();
|
|
117
|
+
}
|
|
118
|
+
impl_.reset();
|
|
119
|
+
event_backend_active_ = false;
|
|
120
|
+
LOG_NETMON_INFO("Network monitor stopped");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void NetworkMonitor::check_now() {
|
|
124
|
+
if (!running_.load()) return;
|
|
125
|
+
{
|
|
126
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
127
|
+
change_pending_ = true;
|
|
128
|
+
}
|
|
129
|
+
cv_.notify_all();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// Worker: debounce + diff + dispatch
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
void NetworkMonitor::worker_loop() {
|
|
137
|
+
// When push notifications are active, the long interval is just a safety net
|
|
138
|
+
// for events the OS might drop (e.g. across suspend/resume). Without them,
|
|
139
|
+
// this interval is the actual detection latency.
|
|
140
|
+
const auto poll_interval = event_backend_active_
|
|
141
|
+
? std::chrono::milliseconds(30000)
|
|
142
|
+
: std::chrono::milliseconds(5000);
|
|
143
|
+
|
|
144
|
+
while (running_.load()) {
|
|
145
|
+
bool was_event = false;
|
|
146
|
+
{
|
|
147
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
148
|
+
cv_.wait_for(lock, poll_interval,
|
|
149
|
+
[this]() { return !running_.load() || change_pending_; });
|
|
150
|
+
if (!running_.load()) break;
|
|
151
|
+
|
|
152
|
+
was_event = change_pending_;
|
|
153
|
+
change_pending_ = false;
|
|
154
|
+
|
|
155
|
+
if (was_event) {
|
|
156
|
+
// Coalesce the burst: wait out a quiet debounce window (only a
|
|
157
|
+
// stop interrupts it), then drop any events that arrived during it.
|
|
158
|
+
cv_.wait_for(lock, debounce_, [this]() { return !running_.load(); });
|
|
159
|
+
if (!running_.load()) break;
|
|
160
|
+
change_pending_ = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
auto current = snapshot_addresses();
|
|
165
|
+
if (current != last_addresses_) {
|
|
166
|
+
LOG_NETMON_INFO("Local address set changed (" << last_addresses_.size()
|
|
167
|
+
<< " -> " << current.size() << ")");
|
|
168
|
+
last_addresses_ = current;
|
|
169
|
+
if (on_change_) {
|
|
170
|
+
on_change_(current);
|
|
171
|
+
}
|
|
172
|
+
} else if (was_event) {
|
|
173
|
+
LOG_NETMON_DEBUG("Network event with no effective address change; ignored");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Windows backend: NotifyUnicastIpAddressChange
|
|
180
|
+
// ============================================================================
|
|
181
|
+
#ifdef _WIN32
|
|
182
|
+
|
|
183
|
+
bool NetworkMonitor::backend_start() {
|
|
184
|
+
DWORD rv = NotifyUnicastIpAddressChange(AF_UNSPEC, &rats_ip_change_cb, this,
|
|
185
|
+
FALSE, &impl_->handle);
|
|
186
|
+
if (rv != NO_ERROR) {
|
|
187
|
+
LOG_NETMON_WARN("NotifyUnicastIpAddressChange failed (" << rv
|
|
188
|
+
<< "); falling back to polling");
|
|
189
|
+
impl_->handle = nullptr;
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
void NetworkMonitor::backend_stop() {
|
|
196
|
+
if (impl_ && impl_->handle != nullptr) {
|
|
197
|
+
// Cancels and waits for any in-flight callback to return, so no callback
|
|
198
|
+
// can run against a half-destroyed monitor afterwards.
|
|
199
|
+
CancelMibChangeNotify2(impl_->handle);
|
|
200
|
+
impl_->handle = nullptr;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Linux backend: NETLINK_ROUTE socket
|
|
206
|
+
// ============================================================================
|
|
207
|
+
#elif defined(__linux__)
|
|
208
|
+
|
|
209
|
+
bool NetworkMonitor::backend_start() {
|
|
210
|
+
int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
|
|
211
|
+
if (fd < 0) {
|
|
212
|
+
LOG_NETMON_WARN("netlink socket() failed (" << errno << "); falling back to polling");
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
sockaddr_nl addr{};
|
|
217
|
+
addr.nl_family = AF_NETLINK;
|
|
218
|
+
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR
|
|
219
|
+
| RTMGRP_LINK | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE;
|
|
220
|
+
if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
|
|
221
|
+
LOG_NETMON_WARN("netlink bind() failed (" << errno << "); falling back to polling");
|
|
222
|
+
::close(fd);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (::pipe(impl_->stop_pipe) < 0) {
|
|
227
|
+
::close(fd);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
impl_->fd = fd;
|
|
231
|
+
|
|
232
|
+
impl_->reader = std::thread([this]() {
|
|
233
|
+
char buf[4096];
|
|
234
|
+
struct pollfd fds[2];
|
|
235
|
+
fds[0].fd = impl_->fd; fds[0].events = POLLIN;
|
|
236
|
+
fds[1].fd = impl_->stop_pipe[0]; fds[1].events = POLLIN;
|
|
237
|
+
|
|
238
|
+
while (running_.load()) {
|
|
239
|
+
int pr = ::poll(fds, 2, -1);
|
|
240
|
+
if (pr < 0) {
|
|
241
|
+
if (errno == EINTR) continue;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (fds[1].revents & POLLIN) break; // stop requested
|
|
245
|
+
if (!(fds[0].revents & POLLIN)) continue;
|
|
246
|
+
|
|
247
|
+
ssize_t len = ::recv(impl_->fd, buf, sizeof(buf), 0);
|
|
248
|
+
if (len <= 0) {
|
|
249
|
+
// ENOBUFS just means we missed messages under load — treat as a change.
|
|
250
|
+
if (len < 0 && errno == ENOBUFS) { check_now(); continue; }
|
|
251
|
+
if (len < 0 && errno == EINTR) continue;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
bool pertinent = false;
|
|
256
|
+
for (auto* nh = reinterpret_cast<nlmsghdr*>(buf);
|
|
257
|
+
NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
|
|
258
|
+
switch (nh->nlmsg_type) {
|
|
259
|
+
case RTM_NEWADDR: case RTM_DELADDR:
|
|
260
|
+
case RTM_NEWLINK: case RTM_DELLINK:
|
|
261
|
+
case RTM_NEWROUTE: case RTM_DELROUTE:
|
|
262
|
+
pertinent = true;
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (pertinent) check_now();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
void NetworkMonitor::backend_stop() {
|
|
275
|
+
if (!impl_) return;
|
|
276
|
+
if (impl_->stop_pipe[1] >= 0) {
|
|
277
|
+
char b = 1;
|
|
278
|
+
ssize_t n = ::write(impl_->stop_pipe[1], &b, 1);
|
|
279
|
+
(void)n;
|
|
280
|
+
}
|
|
281
|
+
if (impl_->reader.joinable()) impl_->reader.join();
|
|
282
|
+
if (impl_->fd >= 0) { ::close(impl_->fd); impl_->fd = -1; }
|
|
283
|
+
if (impl_->stop_pipe[0] >= 0) { ::close(impl_->stop_pipe[0]); impl_->stop_pipe[0] = -1; }
|
|
284
|
+
if (impl_->stop_pipe[1] >= 0) { ::close(impl_->stop_pipe[1]); impl_->stop_pipe[1] = -1; }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// macOS / BSD backend: PF_ROUTE routing socket
|
|
289
|
+
// ============================================================================
|
|
290
|
+
#elif defined(RATS_MONITOR_BSD_ROUTES)
|
|
291
|
+
|
|
292
|
+
bool NetworkMonitor::backend_start() {
|
|
293
|
+
int fd = ::socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
|
|
294
|
+
if (fd < 0) {
|
|
295
|
+
LOG_NETMON_WARN("PF_ROUTE socket() failed (" << errno << "); falling back to polling");
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
if (::pipe(impl_->stop_pipe) < 0) {
|
|
299
|
+
::close(fd);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
impl_->fd = fd;
|
|
303
|
+
|
|
304
|
+
impl_->reader = std::thread([this]() {
|
|
305
|
+
char buf[2048];
|
|
306
|
+
struct pollfd fds[2];
|
|
307
|
+
fds[0].fd = impl_->fd; fds[0].events = POLLIN;
|
|
308
|
+
fds[1].fd = impl_->stop_pipe[0]; fds[1].events = POLLIN;
|
|
309
|
+
|
|
310
|
+
while (running_.load()) {
|
|
311
|
+
int pr = ::poll(fds, 2, -1);
|
|
312
|
+
if (pr < 0) {
|
|
313
|
+
if (errno == EINTR) continue;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
if (fds[1].revents & POLLIN) break; // stop requested
|
|
317
|
+
if (!(fds[0].revents & POLLIN)) continue;
|
|
318
|
+
|
|
319
|
+
ssize_t len = ::read(impl_->fd, buf, sizeof(buf));
|
|
320
|
+
if (len <= 0) {
|
|
321
|
+
if (len < 0 && errno == EINTR) continue;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
if (static_cast<size_t>(len) < sizeof(rt_msghdr)) continue;
|
|
325
|
+
|
|
326
|
+
auto* rtm = reinterpret_cast<rt_msghdr*>(buf);
|
|
327
|
+
switch (rtm->rtm_type) {
|
|
328
|
+
case RTM_NEWADDR: case RTM_DELADDR:
|
|
329
|
+
case RTM_IFINFO:
|
|
330
|
+
#ifdef RTM_IFANNOUNCE
|
|
331
|
+
case RTM_IFANNOUNCE:
|
|
332
|
+
#endif
|
|
333
|
+
case RTM_ADD: case RTM_DELETE: case RTM_CHANGE:
|
|
334
|
+
check_now();
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
void NetworkMonitor::backend_stop() {
|
|
345
|
+
if (!impl_) return;
|
|
346
|
+
if (impl_->stop_pipe[1] >= 0) {
|
|
347
|
+
char b = 1;
|
|
348
|
+
ssize_t n = ::write(impl_->stop_pipe[1], &b, 1);
|
|
349
|
+
(void)n;
|
|
350
|
+
}
|
|
351
|
+
if (impl_->reader.joinable()) impl_->reader.join();
|
|
352
|
+
if (impl_->fd >= 0) { ::close(impl_->fd); impl_->fd = -1; }
|
|
353
|
+
if (impl_->stop_pipe[0] >= 0) { ::close(impl_->stop_pipe[0]); impl_->stop_pipe[0] = -1; }
|
|
354
|
+
if (impl_->stop_pipe[1] >= 0) { ::close(impl_->stop_pipe[1]); impl_->stop_pipe[1] = -1; }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// Fallback backend: polling only (worker_loop diffs on its poll interval)
|
|
359
|
+
// ============================================================================
|
|
360
|
+
#else
|
|
361
|
+
|
|
362
|
+
bool NetworkMonitor::backend_start() { return false; }
|
|
363
|
+
void NetworkMonitor::backend_stop() {}
|
|
364
|
+
|
|
365
|
+
#endif
|
|
366
|
+
|
|
367
|
+
} // namespace librats
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file network_monitor.h
|
|
5
|
+
* @brief Detects host network configuration changes (IP / interface / route).
|
|
6
|
+
*
|
|
7
|
+
* When the machine's connectivity changes — a new interface comes up, an IP
|
|
8
|
+
* address is added or removed, the default route flips (Wi-Fi <-> cellular,
|
|
9
|
+
* dock/undock, VPN up/down, wake-from-sleep) — a long-lived P2P node must react:
|
|
10
|
+
* re-create router port mappings, re-discover its public address via STUN, and
|
|
11
|
+
* re-announce to the DHT. Otherwise it keeps advertising a stale, unreachable
|
|
12
|
+
* endpoint until the next periodic refresh.
|
|
13
|
+
*
|
|
14
|
+
* NetworkMonitor provides an event-driven signal for exactly that. The design
|
|
15
|
+
* mirrors libtorrent's aux::ip_notifier but is adapted to librats' own threading
|
|
16
|
+
* model (no boost::asio): each platform backend only signals "something changed",
|
|
17
|
+
* and the monitor itself re-enumerates the local interface addresses and invokes
|
|
18
|
+
* the callback ONLY when the address set actually differs. Bursts of OS events
|
|
19
|
+
* (a single interface transition typically emits several) are coalesced with a
|
|
20
|
+
* short debounce window.
|
|
21
|
+
*
|
|
22
|
+
* Platform backends:
|
|
23
|
+
* - Windows: NotifyUnicastIpAddressChange() (iphlpapi)
|
|
24
|
+
* - Linux: NETLINK_ROUTE socket, RTMGRP_*_IFADDR / *_LINK groups
|
|
25
|
+
* - macOS/BSD: PF_ROUTE routing socket
|
|
26
|
+
* - otherwise: periodic polling of the interface address list
|
|
27
|
+
*
|
|
28
|
+
* The callback runs on the monitor's worker thread, so it must not block for
|
|
29
|
+
* long; offload slow recovery work (STUN, port mapping) to another thread.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
#include <atomic>
|
|
33
|
+
#include <chrono>
|
|
34
|
+
#include <condition_variable>
|
|
35
|
+
#include <functional>
|
|
36
|
+
#include <memory>
|
|
37
|
+
#include <mutex>
|
|
38
|
+
#include <string>
|
|
39
|
+
#include <thread>
|
|
40
|
+
#include <vector>
|
|
41
|
+
|
|
42
|
+
namespace librats {
|
|
43
|
+
|
|
44
|
+
class NetworkMonitor {
|
|
45
|
+
public:
|
|
46
|
+
/// Invoked (debounced) whenever the set of local interface addresses changes.
|
|
47
|
+
/// @param current_addresses the new, full list of local interface addresses.
|
|
48
|
+
using ChangeCallback = std::function<void(const std::vector<std::string>& current_addresses)>;
|
|
49
|
+
|
|
50
|
+
NetworkMonitor();
|
|
51
|
+
~NetworkMonitor();
|
|
52
|
+
|
|
53
|
+
NetworkMonitor(const NetworkMonitor&) = delete;
|
|
54
|
+
NetworkMonitor& operator=(const NetworkMonitor&) = delete;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start monitoring. The callback fires on each detected change (after the
|
|
58
|
+
* debounce window) on the monitor's worker thread.
|
|
59
|
+
*
|
|
60
|
+
* @return true if an OS push-notification backend is active; false if the
|
|
61
|
+
* monitor fell back to periodic polling. The monitor works either
|
|
62
|
+
* way, so the return value is informational only.
|
|
63
|
+
*/
|
|
64
|
+
bool start(ChangeCallback on_change);
|
|
65
|
+
|
|
66
|
+
/// Stop monitoring and join the worker thread. Idempotent.
|
|
67
|
+
void stop();
|
|
68
|
+
|
|
69
|
+
bool is_running() const { return running_.load(); }
|
|
70
|
+
|
|
71
|
+
/// Whether OS push notifications (vs. polling) are in use. Valid after start().
|
|
72
|
+
bool is_event_driven() const { return event_backend_active_; }
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Request an immediate re-check, coalesced/debounced exactly like a real OS
|
|
76
|
+
* event. Thread-safe. Useful to call after the device wakes from sleep, or
|
|
77
|
+
* from the platform backends themselves. A no-op if not running.
|
|
78
|
+
*/
|
|
79
|
+
void check_now();
|
|
80
|
+
|
|
81
|
+
/// Override the debounce window used to coalesce event bursts (default 2s).
|
|
82
|
+
void set_debounce(std::chrono::milliseconds d) { debounce_ = d; }
|
|
83
|
+
|
|
84
|
+
private:
|
|
85
|
+
void worker_loop();
|
|
86
|
+
bool backend_start(); // set up the OS notifier; returns true if event-driven
|
|
87
|
+
void backend_stop(); // tear the OS notifier / reader thread down
|
|
88
|
+
|
|
89
|
+
ChangeCallback on_change_;
|
|
90
|
+
std::atomic<bool> running_{false};
|
|
91
|
+
bool change_pending_ = false; // guarded by mutex_
|
|
92
|
+
bool event_backend_active_ = false; // true once an OS backend is confirmed up
|
|
93
|
+
std::thread worker_;
|
|
94
|
+
std::mutex mutex_;
|
|
95
|
+
std::condition_variable cv_;
|
|
96
|
+
std::chrono::milliseconds debounce_{2000};
|
|
97
|
+
std::vector<std::string> last_addresses_;
|
|
98
|
+
|
|
99
|
+
// Platform-specific backend state (fds, OS handles, reader thread).
|
|
100
|
+
struct Impl;
|
|
101
|
+
std::unique_ptr<Impl> impl_;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
} // namespace librats
|
|
@@ -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
|