librats 0.9.2 → 1.0.1
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 +4 -0
- package/native-src/src/dht.cpp +24 -1
- package/native-src/src/librats.cpp +32 -10
- package/native-src/src/librats.h +73 -1
- package/native-src/src/librats_discovery.cpp +21 -15
- package/native-src/src/librats_portmap.cpp +175 -0
- package/native-src/src/network_monitor.cpp +367 -0
- package/native-src/src/network_monitor.h +104 -0
- package/package.json +1 -1
|
@@ -132,6 +132,8 @@ set(LIBRARY_SOURCES
|
|
|
132
132
|
src/socket.h
|
|
133
133
|
src/network_utils.cpp
|
|
134
134
|
src/network_utils.h
|
|
135
|
+
src/network_monitor.cpp
|
|
136
|
+
src/network_monitor.h
|
|
135
137
|
src/dht.cpp
|
|
136
138
|
src/dht.h
|
|
137
139
|
src/bencode.cpp
|
|
@@ -468,6 +470,8 @@ if(RATS_BUILD_TESTS)
|
|
|
468
470
|
tests/test_ice.cpp
|
|
469
471
|
# Automatic port forwarding tests (UPnP/NAT-PMP)
|
|
470
472
|
tests/test_portmap.cpp
|
|
473
|
+
# Network change detection tests
|
|
474
|
+
tests/test_network_monitor.cpp
|
|
471
475
|
# I/O poller abstraction (epoll/kqueue/IOCP)
|
|
472
476
|
tests/test_io_poller.cpp
|
|
473
477
|
# Buffer utilities for file transfer and bittorrent
|
package/native-src/src/dht.cpp
CHANGED
|
@@ -2894,7 +2894,30 @@ bool DhtClient::load_routing_table() {
|
|
|
2894
2894
|
LOG_DHT_WARN("Unsupported routing table version: " << version);
|
|
2895
2895
|
return false;
|
|
2896
2896
|
}
|
|
2897
|
-
|
|
2897
|
+
|
|
2898
|
+
// Restore our own node ID so it stays stable across restarts (matches libtorrent's
|
|
2899
|
+
// calculate_node_id). The external address is not known yet at load time, which is
|
|
2900
|
+
// libtorrent's "external_address.is_unspecified()" case -> reuse the saved ID as long
|
|
2901
|
+
// as it is valid (non-zero). If the external IP is later discovered and the restored
|
|
2902
|
+
// ID is no longer valid for it (BEP 42), set_external_ip() regenerates it then.
|
|
2903
|
+
// Restored before bucketing the loaded nodes below, since get_bucket_index() is
|
|
2904
|
+
// computed relative to node_id_.
|
|
2905
|
+
if (routing_data.contains("node_id")) {
|
|
2906
|
+
try {
|
|
2907
|
+
NodeId saved_id = hex_to_node_id(routing_data["node_id"].get<std::string>());
|
|
2908
|
+
bool all_zeros = std::all_of(saved_id.begin(), saved_id.end(),
|
|
2909
|
+
[](uint8_t b) { return b == 0; });
|
|
2910
|
+
if (!all_zeros) {
|
|
2911
|
+
node_id_ = saved_id;
|
|
2912
|
+
LOG_DHT_INFO("Restored DHT node ID from disk: " << node_id_to_hex(node_id_));
|
|
2913
|
+
} else {
|
|
2914
|
+
LOG_DHT_WARN("Saved node ID is invalid (zero/malformed), keeping generated one");
|
|
2915
|
+
}
|
|
2916
|
+
} catch (const std::exception& e) {
|
|
2917
|
+
LOG_DHT_WARN("Failed to restore saved node ID, keeping generated one: " << e.what());
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2898
2921
|
// Load nodes
|
|
2899
2922
|
const auto& nodes_array = routing_data["nodes"];
|
|
2900
2923
|
size_t loaded_count = 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "librats.h"
|
|
2
2
|
#include "os.h"
|
|
3
3
|
#include "network_utils.h"
|
|
4
|
+
#include "network_monitor.h" // complete type for unique_ptr<NetworkMonitor> member
|
|
4
5
|
#include "version.h"
|
|
5
6
|
#include <algorithm>
|
|
6
7
|
#include <array>
|
|
@@ -171,6 +172,10 @@ bool RatsClient::start() {
|
|
|
171
172
|
// No-op when disabled; runs discovery/mapping on its own background threads.
|
|
172
173
|
start_port_mapping();
|
|
173
174
|
|
|
175
|
+
// React to host network changes (IP/interface/route): renew port mappings,
|
|
176
|
+
// re-discover the public address and re-announce. No-op when disabled.
|
|
177
|
+
start_network_monitor();
|
|
178
|
+
|
|
174
179
|
LOG_CLIENT_INFO("RatsClient started successfully on port " << listen_port_);
|
|
175
180
|
|
|
176
181
|
// Attempt to reconnect to saved peers
|
|
@@ -202,6 +207,11 @@ void RatsClient::stop() {
|
|
|
202
207
|
|
|
203
208
|
LOG_CLIENT_INFO("Stopping RatsClient");
|
|
204
209
|
|
|
210
|
+
// Stop network-change detection FIRST and join its recovery worker: that
|
|
211
|
+
// worker touches the port-mapping backends and the DHT clients, both torn
|
|
212
|
+
// down below, so it must not be running past this point.
|
|
213
|
+
stop_network_monitor();
|
|
214
|
+
|
|
205
215
|
// Remove port mappings and stop UPnP/NAT-PMP backends before tearing down
|
|
206
216
|
// sockets (best-effort cleanup so we don't leave stale router mappings).
|
|
207
217
|
stop_port_mapping();
|
|
@@ -1387,22 +1397,34 @@ static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::
|
|
|
1387
1397
|
|
|
1388
1398
|
// Local interface address blocking methods
|
|
1389
1399
|
void RatsClient::initialize_local_addresses() {
|
|
1390
|
-
LOG_CLIENT_INFO("Initializing local interface addresses for connection blocking");
|
|
1391
|
-
|
|
1392
1400
|
std::lock_guard<std::mutex> lock(local_addresses_mutex_);
|
|
1393
|
-
|
|
1394
|
-
//
|
|
1401
|
+
|
|
1402
|
+
// (Re)enumerate the host's interface addresses. This is also called on every
|
|
1403
|
+
// network change, so it must be a diff, not a blind insert: drop auto-detected
|
|
1404
|
+
// addresses that have disappeared (a removed IP must stop being treated as
|
|
1405
|
+
// "ourselves"), while preserving localhost entries and any externally-
|
|
1406
|
+
// discovered / manually-ignored addresses (STUN reflexive, mapped external IP,
|
|
1407
|
+
// user add_ignored_address()), which live in the same set.
|
|
1395
1408
|
auto addrs = network_utils::get_local_interface_addresses();
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1409
|
+
std::unordered_set<std::string> new_auto(addrs.begin(), addrs.end());
|
|
1410
|
+
|
|
1411
|
+
for (const auto& old : auto_interface_addresses_) {
|
|
1412
|
+
if (new_auto.find(old) == new_auto.end()) {
|
|
1413
|
+
local_interface_addresses_.erase(old);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
for (const auto& addr : addrs) {
|
|
1417
|
+
local_interface_addresses_.insert(addr);
|
|
1418
|
+
}
|
|
1399
1419
|
for (const auto& addr : localhost_addrs) {
|
|
1400
1420
|
local_interface_addresses_.emplace(std::string(addr));
|
|
1401
1421
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1422
|
+
auto_interface_addresses_ = std::move(new_auto);
|
|
1423
|
+
|
|
1424
|
+
LOG_CLIENT_INFO("Local interface addresses: " << local_interface_addresses_.size()
|
|
1425
|
+
<< " blocked (" << addrs.size() << " from interfaces)");
|
|
1404
1426
|
for (const auto& addr : local_interface_addresses_) {
|
|
1405
|
-
|
|
1427
|
+
LOG_CLIENT_DEBUG(" - " << addr);
|
|
1406
1428
|
}
|
|
1407
1429
|
}
|
|
1408
1430
|
|
package/native-src/src/librats.h
CHANGED
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
|
|
39
39
|
namespace librats {
|
|
40
40
|
|
|
41
|
+
// Detects host network configuration changes (defined in network_monitor.h).
|
|
42
|
+
// Forward-declared here; only used via unique_ptr, so RatsClient's destructor
|
|
43
|
+
// (defined in librats.cpp, which includes network_monitor.h) sees the full type.
|
|
44
|
+
class NetworkMonitor;
|
|
45
|
+
|
|
41
46
|
/**
|
|
42
47
|
* PeerIOContext - Per-peer async I/O buffers and framing state
|
|
43
48
|
*
|
|
@@ -282,6 +287,10 @@ public:
|
|
|
282
287
|
using DisconnectCallback = std::function<void(socket_t, const std::string&)>;
|
|
283
288
|
using MessageCallback = std::function<void(const std::string&, const nlohmann::json&)>;
|
|
284
289
|
using SendCallback = std::function<void(bool, const std::string&)>;
|
|
290
|
+
// Fired when the host's network configuration changes (interface up/down, IP
|
|
291
|
+
// added/removed, default route change). The argument is the new full list of
|
|
292
|
+
// local interface addresses. See on_network_changed().
|
|
293
|
+
using NetworkChangeCallback = std::function<void(const std::vector<std::string>& local_addresses)>;
|
|
285
294
|
|
|
286
295
|
// =========================================================================
|
|
287
296
|
// Constructor and Destructor
|
|
@@ -1478,6 +1487,35 @@ public:
|
|
|
1478
1487
|
*/
|
|
1479
1488
|
void on_port_mapping(PortMapCallback callback);
|
|
1480
1489
|
|
|
1490
|
+
// =========================================================================
|
|
1491
|
+
// Network change detection
|
|
1492
|
+
// =========================================================================
|
|
1493
|
+
//
|
|
1494
|
+
// A long-lived node must notice when the host's connectivity changes (a new
|
|
1495
|
+
// interface, an IP added/removed, the default route flipping between Wi-Fi
|
|
1496
|
+
// and cellular, dock/undock, VPN up/down, wake-from-sleep) and recover:
|
|
1497
|
+
// renew router port mappings, re-discover its public address via STUN, and
|
|
1498
|
+
// re-announce to the DHT. Otherwise it keeps advertising a stale endpoint
|
|
1499
|
+
// until the next periodic refresh. Enabled by default. Implemented in
|
|
1500
|
+
// librats_portmap.cpp on top of the platform-specific NetworkMonitor.
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Enable or disable automatic reaction to host network changes. Enabled by
|
|
1504
|
+
* default. Can be called before or after start(): toggling while running
|
|
1505
|
+
* starts/stops the monitor immediately. Not persisted.
|
|
1506
|
+
*/
|
|
1507
|
+
void set_network_change_detection_enabled(bool enabled);
|
|
1508
|
+
bool is_network_change_detection_enabled() const;
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Register a callback fired (debounced) whenever the set of local interface
|
|
1512
|
+
* addresses changes. The argument is the new full list of local addresses.
|
|
1513
|
+
* Invoked from the monitor's worker thread, so keep the handler quick. This
|
|
1514
|
+
* is in addition to — not a replacement for — the built-in recovery
|
|
1515
|
+
* (port re-mapping, STUN re-discovery, DHT re-announce).
|
|
1516
|
+
*/
|
|
1517
|
+
void on_network_changed(NetworkChangeCallback callback);
|
|
1518
|
+
|
|
1481
1519
|
#ifdef RATS_STORAGE
|
|
1482
1520
|
// =========================================================================
|
|
1483
1521
|
// Distributed Storage API (requires RATS_STORAGE)
|
|
@@ -1964,6 +2002,11 @@ private:
|
|
|
1964
2002
|
// 6. io_mutex_ (I/O poller and send buffer access)
|
|
1965
2003
|
// 7. message_handlers_mutex_ (Message handler registration)
|
|
1966
2004
|
// 8. reconnect_mutex_ (Reconnection queue management)
|
|
2005
|
+
// 9. port_mapping_mutex_ (UPnP/NAT-PMP backends and mapped address)
|
|
2006
|
+
// 10. network_monitor_mutex_ / network_recovery_mutex_ (network-change state)
|
|
2007
|
+
//
|
|
2008
|
+
// (9) and (10) are leaf locks: they are never held while acquiring any lock
|
|
2009
|
+
// above, so they impose no additional ordering constraints.
|
|
1967
2010
|
// =========================================================================
|
|
1968
2011
|
|
|
1969
2012
|
// [1] Configuration persistence (protected by config_mutex_)
|
|
@@ -1988,7 +2031,12 @@ private:
|
|
|
1988
2031
|
// [4] Local interface address blocking (protected by local_addresses_mutex_)
|
|
1989
2032
|
mutable std::mutex local_addresses_mutex_; // [4] Protects local interface addresses
|
|
1990
2033
|
std::unordered_set<std::string> local_interface_addresses_;
|
|
1991
|
-
|
|
2034
|
+
// Subset of local_interface_addresses_ that came from interface enumeration.
|
|
2035
|
+
// Tracked separately so a network-change refresh can drop only stale auto
|
|
2036
|
+
// entries without evicting localhost or externally-discovered addresses
|
|
2037
|
+
// (STUN reflexive, mapped external IP, user add_ignored_address()).
|
|
2038
|
+
std::unordered_set<std::string> auto_interface_addresses_;
|
|
2039
|
+
|
|
1992
2040
|
// [5] Organized peer management using RatsPeer struct (protected by peers_mutex_)
|
|
1993
2041
|
mutable std::mutex peers_mutex_; // [5] Protects peer data (most frequently locked)
|
|
1994
2042
|
std::unordered_map<std::string, RatsPeer> peers_; // keyed by peer_id
|
|
@@ -2054,6 +2102,30 @@ private:
|
|
|
2054
2102
|
// TCP mapping is established, otherwise the local listen port.
|
|
2055
2103
|
uint16_t get_advertised_port() const;
|
|
2056
2104
|
|
|
2105
|
+
// Network change detection (implemented in librats_portmap.cpp). Monitor runs
|
|
2106
|
+
// a platform watcher; on a real address-set change it refreshes the self-
|
|
2107
|
+
// address set inline and wakes the recovery worker, which re-maps ports,
|
|
2108
|
+
// re-discovers the public IP and re-announces. These mutexes are last in the
|
|
2109
|
+
// lock order (after [8]) and are never held while calling into other
|
|
2110
|
+
// subsystems, so they introduce no new ordering constraints.
|
|
2111
|
+
std::unique_ptr<NetworkMonitor> network_monitor_;
|
|
2112
|
+
bool network_change_detection_enabled_ = true; // start the monitor on start()
|
|
2113
|
+
mutable std::mutex network_monitor_mutex_; // guards the user callback below
|
|
2114
|
+
NetworkChangeCallback network_change_callback_;
|
|
2115
|
+
// Dedicated recovery worker: serialises the slow recovery work (STUN can
|
|
2116
|
+
// block for seconds) off the monitor thread and coalesces rapid changes.
|
|
2117
|
+
std::thread network_recovery_thread_;
|
|
2118
|
+
std::mutex network_recovery_mutex_;
|
|
2119
|
+
std::condition_variable network_recovery_cv_;
|
|
2120
|
+
bool network_recovery_pending_ = false;
|
|
2121
|
+
bool network_recovery_stop_ = false;
|
|
2122
|
+
|
|
2123
|
+
void start_network_monitor();
|
|
2124
|
+
void stop_network_monitor();
|
|
2125
|
+
void network_recovery_loop();
|
|
2126
|
+
void handle_network_change(const std::vector<std::string>& current_addresses);
|
|
2127
|
+
void recover_after_network_change();
|
|
2128
|
+
|
|
2057
2129
|
#ifdef RATS_STORAGE
|
|
2058
2130
|
// Distributed storage manager (optional, requires RATS_STORAGE)
|
|
2059
2131
|
std::unique_ptr<StorageManager> storage_manager_;
|
|
@@ -265,22 +265,19 @@ std::chrono::seconds RatsClient::calculate_discovery_interval() const {
|
|
|
265
265
|
|
|
266
266
|
void RatsClient::automatic_discovery_loop() {
|
|
267
267
|
LOG_CLIENT_INFO("Automatic peer discovery loop started");
|
|
268
|
-
|
|
269
|
-
// Initial delay to let DHT bootstrap
|
|
270
|
-
{
|
|
271
|
-
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
272
|
-
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(INITIAL_DISCOVERY_DELAY_SECONDS), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
273
|
-
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
268
|
|
|
278
|
-
// Best-effort: discover our public IP via STUN and derive a BEP 42 node ID from it
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
269
|
+
// Best-effort: discover our public IP via STUN and derive a BEP 42 node ID from it BEFORE
|
|
270
|
+
// the initial bootstrap delay below. The DHT starts receiving KRPC responses (each carrying
|
|
271
|
+
// our address in the "ip" field) immediately on bootstrap, and that in-DHT vote can reach
|
|
272
|
+
// EXTERNAL_IP_VOTE_THRESHOLD within a few seconds. Probing STUN first lets set_external_ip()
|
|
273
|
+
// adopt the address and regenerate the node ID up front, so the DHT bootstraps under the
|
|
274
|
+
// correct ID and the ip-field voting silently confirms the already-adopted address instead
|
|
275
|
+
// of racing it to consensus. The voting remains the fallback when STUN is unavailable or the
|
|
276
|
+
// address later changes.
|
|
277
|
+
//
|
|
278
|
+
// This runs on the discovery thread (not a standalone one) on purpose: stop_dht_discovery()
|
|
279
|
+
// joins this thread via stop_automatic_peer_discovery() BEFORE resetting the DHT clients, so
|
|
280
|
+
// set_external_ip() can never touch a destroyed DhtClient.
|
|
284
281
|
if (auto_discovery_running_.load() && running_.load()) {
|
|
285
282
|
auto mapped = discover_public_address("stun.l.google.com", 19302, 4000);
|
|
286
283
|
if (mapped) {
|
|
@@ -296,6 +293,15 @@ void RatsClient::automatic_discovery_loop() {
|
|
|
296
293
|
}
|
|
297
294
|
}
|
|
298
295
|
|
|
296
|
+
// Initial delay to let DHT bootstrap
|
|
297
|
+
{
|
|
298
|
+
std::unique_lock<std::mutex> lock(shutdown_mutex_);
|
|
299
|
+
if (shutdown_cv_.wait_for(lock, std::chrono::seconds(INITIAL_DISCOVERY_DELAY_SECONDS), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
|
|
300
|
+
LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
299
305
|
// Announce immediately - this also discovers peers during traversal
|
|
300
306
|
announce_rats_peer();
|
|
301
307
|
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
#include "librats.h"
|
|
12
|
+
#include "librats_log_macros.h"
|
|
12
13
|
#include "network_utils.h"
|
|
14
|
+
#include "network_monitor.h"
|
|
13
15
|
#include "logger.h"
|
|
14
16
|
|
|
15
17
|
namespace librats {
|
|
@@ -241,4 +243,177 @@ void RatsClient::stop_port_mapping() {
|
|
|
241
243
|
if (natpmp) natpmp->stop();
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// Network change detection
|
|
248
|
+
// ============================================================================
|
|
249
|
+
|
|
250
|
+
void RatsClient::set_network_change_detection_enabled(bool enabled) {
|
|
251
|
+
bool was_enabled;
|
|
252
|
+
{
|
|
253
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
254
|
+
was_enabled = network_change_detection_enabled_;
|
|
255
|
+
network_change_detection_enabled_ = enabled;
|
|
256
|
+
}
|
|
257
|
+
if (was_enabled == enabled) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
LOG_INFO("netmon", "Network change detection " << (enabled ? "enabled" : "disabled"));
|
|
261
|
+
if (running_.load()) {
|
|
262
|
+
if (enabled) {
|
|
263
|
+
start_network_monitor();
|
|
264
|
+
} else {
|
|
265
|
+
stop_network_monitor();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
bool RatsClient::is_network_change_detection_enabled() const {
|
|
271
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
272
|
+
return network_change_detection_enabled_;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void RatsClient::on_network_changed(NetworkChangeCallback callback) {
|
|
276
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
277
|
+
network_change_callback_ = std::move(callback);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
void RatsClient::start_network_monitor() {
|
|
281
|
+
{
|
|
282
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
283
|
+
if (!network_change_detection_enabled_ || network_monitor_) {
|
|
284
|
+
return; // disabled or already running
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Spin up the recovery worker before the monitor so it can never miss the
|
|
289
|
+
// first wake-up.
|
|
290
|
+
{
|
|
291
|
+
std::lock_guard<std::mutex> lock(network_recovery_mutex_);
|
|
292
|
+
network_recovery_stop_ = false;
|
|
293
|
+
network_recovery_pending_ = false;
|
|
294
|
+
}
|
|
295
|
+
network_recovery_thread_ = std::thread([this]() { network_recovery_loop(); });
|
|
296
|
+
|
|
297
|
+
auto monitor = std::make_unique<NetworkMonitor>();
|
|
298
|
+
monitor->start([this](const std::vector<std::string>& addrs) {
|
|
299
|
+
handle_network_change(addrs);
|
|
300
|
+
});
|
|
301
|
+
{
|
|
302
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
303
|
+
network_monitor_ = std::move(monitor);
|
|
304
|
+
}
|
|
305
|
+
LOG_INFO("netmon", "Network change detection active");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
void RatsClient::stop_network_monitor() {
|
|
309
|
+
// Stop the OS watcher first so no further change events are queued.
|
|
310
|
+
std::unique_ptr<NetworkMonitor> monitor;
|
|
311
|
+
{
|
|
312
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
313
|
+
monitor = std::move(network_monitor_);
|
|
314
|
+
}
|
|
315
|
+
if (monitor) {
|
|
316
|
+
monitor->stop(); // joins the monitor's worker thread
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Then wake and join the recovery worker. Done after the monitor is down so
|
|
320
|
+
// no new recovery can be scheduled while we shut it down.
|
|
321
|
+
{
|
|
322
|
+
std::lock_guard<std::mutex> lock(network_recovery_mutex_);
|
|
323
|
+
network_recovery_stop_ = true;
|
|
324
|
+
}
|
|
325
|
+
network_recovery_cv_.notify_all();
|
|
326
|
+
if (network_recovery_thread_.joinable()) {
|
|
327
|
+
network_recovery_thread_.join();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
void RatsClient::network_recovery_loop() {
|
|
332
|
+
while (true) {
|
|
333
|
+
{
|
|
334
|
+
std::unique_lock<std::mutex> lock(network_recovery_mutex_);
|
|
335
|
+
network_recovery_cv_.wait(lock, [this]() {
|
|
336
|
+
return network_recovery_pending_ || network_recovery_stop_;
|
|
337
|
+
});
|
|
338
|
+
if (network_recovery_stop_) {
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
network_recovery_pending_ = false;
|
|
342
|
+
}
|
|
343
|
+
recover_after_network_change();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
void RatsClient::handle_network_change(const std::vector<std::string>& current_addresses) {
|
|
348
|
+
if (!running_.load()) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
LOG_CLIENT_INFO("Network change detected (" << current_addresses.size()
|
|
352
|
+
<< " local address(es)); refreshing network state");
|
|
353
|
+
|
|
354
|
+
// Cheap and synchronous: refresh the self-address set so a freshly added
|
|
355
|
+
// local address isn't misjudged as a remote peer, and a removed one stops
|
|
356
|
+
// being blocked. (Diff-based; preserves STUN/mapped/ignored entries.)
|
|
357
|
+
initialize_local_addresses();
|
|
358
|
+
|
|
359
|
+
// Notify the application.
|
|
360
|
+
NetworkChangeCallback cb;
|
|
361
|
+
{
|
|
362
|
+
std::lock_guard<std::mutex> lock(network_monitor_mutex_);
|
|
363
|
+
cb = network_change_callback_;
|
|
364
|
+
}
|
|
365
|
+
if (cb) {
|
|
366
|
+
cb(current_addresses);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Hand the slow recovery (port re-mapping + STUN + re-announce) to the
|
|
370
|
+
// dedicated worker so we don't block the monitor thread. Coalesced: if a
|
|
371
|
+
// recovery is already running, this just marks another pass is needed.
|
|
372
|
+
{
|
|
373
|
+
std::lock_guard<std::mutex> lock(network_recovery_mutex_);
|
|
374
|
+
network_recovery_pending_ = true;
|
|
375
|
+
}
|
|
376
|
+
network_recovery_cv_.notify_all();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
void RatsClient::recover_after_network_change() {
|
|
380
|
+
if (!running_.load()) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
LOG_CLIENT_INFO("Recovering after network change: renewing port mappings and re-announcing");
|
|
384
|
+
|
|
385
|
+
// 1. Renew router port mappings. Our LAN IP and/or the gateway likely
|
|
386
|
+
// changed, so existing UPnP/NAT-PMP leases are stale or aimed at the wrong
|
|
387
|
+
// internal address. Tear down and re-run discovery from scratch.
|
|
388
|
+
if (is_port_mapping_enabled()) {
|
|
389
|
+
stop_port_mapping();
|
|
390
|
+
if (running_.load()) {
|
|
391
|
+
start_port_mapping();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!running_.load()) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 2. Re-discover our public address via STUN, update the BEP 42 node IDs and
|
|
400
|
+
// re-announce so the DHT advertises our current reachable endpoint rather
|
|
401
|
+
// than the one from the previous network. This runs on the recovery
|
|
402
|
+
// thread, which stop() joins before the DHT clients are destroyed, so the
|
|
403
|
+
// set_external_ip()/announce calls can never touch a freed DhtClient.
|
|
404
|
+
if (is_dht_running()) {
|
|
405
|
+
auto mapped = discover_public_address("stun.l.google.com", 19302, 4000);
|
|
406
|
+
if (mapped) {
|
|
407
|
+
if (dht_client_) dht_client_->set_external_ip(mapped->address);
|
|
408
|
+
if (dht_client_v6_) dht_client_v6_->set_external_ip(mapped->address);
|
|
409
|
+
LOG_CLIENT_INFO("Public address after network change: " << mapped->address);
|
|
410
|
+
} else {
|
|
411
|
+
LOG_CLIENT_DEBUG("STUN public address discovery failed after network change");
|
|
412
|
+
}
|
|
413
|
+
if (running_.load()) {
|
|
414
|
+
announce_rats_peer();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
244
419
|
} // namespace librats
|
|
@@ -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
|