librats 0.5.0 → 0.5.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.
Files changed (63) hide show
  1. package/binding.gyp +1 -0
  2. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  3. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  4. package/native-src/CMakeLists.txt +360 -0
  5. package/native-src/LICENSE +21 -0
  6. package/native-src/src/bencode.cpp +485 -0
  7. package/native-src/src/bencode.h +145 -0
  8. package/native-src/src/bittorrent.cpp +3682 -0
  9. package/native-src/src/bittorrent.h +731 -0
  10. package/native-src/src/dht.cpp +2342 -0
  11. package/native-src/src/dht.h +501 -0
  12. package/native-src/src/encrypted_socket.cpp +817 -0
  13. package/native-src/src/encrypted_socket.h +239 -0
  14. package/native-src/src/file_transfer.cpp +1808 -0
  15. package/native-src/src/file_transfer.h +567 -0
  16. package/native-src/src/fs.cpp +639 -0
  17. package/native-src/src/fs.h +108 -0
  18. package/native-src/src/gossipsub.cpp +1137 -0
  19. package/native-src/src/gossipsub.h +403 -0
  20. package/native-src/src/ice.cpp +1386 -0
  21. package/native-src/src/ice.h +328 -0
  22. package/native-src/src/json.hpp +25526 -0
  23. package/native-src/src/krpc.cpp +558 -0
  24. package/native-src/src/krpc.h +145 -0
  25. package/native-src/src/librats.cpp +2715 -0
  26. package/native-src/src/librats.h +1729 -0
  27. package/native-src/src/librats_bittorrent.cpp +167 -0
  28. package/native-src/src/librats_c.cpp +1317 -0
  29. package/native-src/src/librats_c.h +237 -0
  30. package/native-src/src/librats_encryption.cpp +123 -0
  31. package/native-src/src/librats_file_transfer.cpp +226 -0
  32. package/native-src/src/librats_gossipsub.cpp +293 -0
  33. package/native-src/src/librats_ice.cpp +515 -0
  34. package/native-src/src/librats_logging.cpp +158 -0
  35. package/native-src/src/librats_mdns.cpp +171 -0
  36. package/native-src/src/librats_nat.cpp +571 -0
  37. package/native-src/src/librats_persistence.cpp +815 -0
  38. package/native-src/src/logger.h +412 -0
  39. package/native-src/src/mdns.cpp +1178 -0
  40. package/native-src/src/mdns.h +253 -0
  41. package/native-src/src/network_utils.cpp +598 -0
  42. package/native-src/src/network_utils.h +162 -0
  43. package/native-src/src/noise.cpp +981 -0
  44. package/native-src/src/noise.h +227 -0
  45. package/native-src/src/os.cpp +371 -0
  46. package/native-src/src/os.h +40 -0
  47. package/native-src/src/rats_export.h +17 -0
  48. package/native-src/src/sha1.cpp +163 -0
  49. package/native-src/src/sha1.h +42 -0
  50. package/native-src/src/socket.cpp +1376 -0
  51. package/native-src/src/socket.h +309 -0
  52. package/native-src/src/stun.cpp +484 -0
  53. package/native-src/src/stun.h +349 -0
  54. package/native-src/src/threadmanager.cpp +105 -0
  55. package/native-src/src/threadmanager.h +53 -0
  56. package/native-src/src/tracker.cpp +1110 -0
  57. package/native-src/src/tracker.h +268 -0
  58. package/native-src/src/version.cpp +24 -0
  59. package/native-src/src/version.h.in +45 -0
  60. package/native-src/version.rc.in +31 -0
  61. package/package.json +2 -8
  62. package/scripts/build-librats.js +59 -12
  63. package/scripts/prepare-package.js +133 -37
@@ -0,0 +1,484 @@
1
+ #include "stun.h"
2
+ #include "ice.h"
3
+ #include "network_utils.h"
4
+ #include "logger.h"
5
+ #include <random>
6
+ #include <cstring>
7
+ #include <chrono>
8
+
9
+ #ifdef _WIN32
10
+ #include <winsock2.h>
11
+ #include <ws2tcpip.h>
12
+ #else
13
+ #include <arpa/inet.h>
14
+ #include <netinet/in.h>
15
+ #endif
16
+
17
+ // STUN module logging macros
18
+ #define LOG_STUN_DEBUG(message) LOG_DEBUG("stun", message)
19
+ #define LOG_STUN_INFO(message) LOG_INFO("stun", message)
20
+ #define LOG_STUN_WARN(message) LOG_WARN("stun", message)
21
+ #define LOG_STUN_ERROR(message) LOG_ERROR("stun", message)
22
+
23
+ namespace librats {
24
+
25
+ StunClient::StunClient() {
26
+ LOG_STUN_DEBUG("STUN client created");
27
+ }
28
+
29
+ StunClient::~StunClient() {
30
+ LOG_STUN_DEBUG("STUN client destroyed");
31
+ }
32
+
33
+ bool StunClient::get_public_address_from_google(StunAddress& public_address, int timeout_ms) {
34
+ return get_public_address("stun.l.google.com", 19302, public_address, timeout_ms);
35
+ }
36
+
37
+ bool StunClient::get_public_address(const std::string& stun_server,
38
+ int stun_port,
39
+ StunAddress& public_address,
40
+ int timeout_ms) {
41
+ LOG_STUN_INFO("Getting public address from STUN server: " << stun_server << ":" << stun_port);
42
+
43
+ // Initialize socket library (safe to call multiple times)
44
+ if (!init_socket_library()) {
45
+ LOG_STUN_ERROR("Failed to initialize socket library");
46
+ return false;
47
+ }
48
+
49
+ // Create UDP socket for STUN
50
+ socket_t stun_socket = create_udp_socket_v4(0); // Use ephemeral port
51
+ if (!is_valid_socket(stun_socket)) {
52
+ LOG_STUN_ERROR("Failed to create UDP socket for STUN");
53
+ return false;
54
+ }
55
+
56
+ // Set socket to non-blocking for timeout support
57
+ if (!set_socket_nonblocking(stun_socket)) {
58
+ LOG_STUN_WARN("Failed to set STUN socket to non-blocking mode");
59
+ }
60
+
61
+ // Create STUN binding request
62
+ std::vector<uint8_t> request = create_binding_request();
63
+
64
+ // Send request to STUN server
65
+ if (!send_stun_request(stun_socket, stun_server, stun_port, request)) {
66
+ LOG_STUN_ERROR("Failed to send STUN request to " << stun_server << ":" << stun_port);
67
+ close_socket(stun_socket);
68
+ return false;
69
+ }
70
+
71
+ // Receive response
72
+ std::vector<uint8_t> response;
73
+ if (!receive_stun_response(stun_socket, response, timeout_ms)) {
74
+ LOG_STUN_ERROR("Failed to receive STUN response from " << stun_server << ":" << stun_port);
75
+ close_socket(stun_socket);
76
+ return false;
77
+ }
78
+
79
+ // Parse response to get mapped address
80
+ if (!parse_binding_response(response, public_address)) {
81
+ LOG_STUN_ERROR("Failed to parse STUN response from " << stun_server << ":" << stun_port);
82
+ close_socket(stun_socket);
83
+ return false;
84
+ }
85
+
86
+ close_socket(stun_socket);
87
+
88
+ LOG_STUN_INFO("Successfully got public address: " << public_address.ip << ":" << public_address.port);
89
+ return true;
90
+ }
91
+
92
+ std::vector<uint8_t> StunClient::create_binding_request() {
93
+ std::vector<uint8_t> request(stun::HEADER_SIZE);
94
+
95
+ // Message Type: Binding Request
96
+ write_uint16(request.data(), stun::BINDING_REQUEST);
97
+
98
+ // Message Length: 0 (no attributes for basic binding request)
99
+ write_uint16(request.data() + 2, 0);
100
+
101
+ // Magic Cookie
102
+ write_uint32(request.data() + 4, stun::MAGIC_COOKIE);
103
+
104
+ // Transaction ID (96 bits = 12 bytes)
105
+ std::random_device rd;
106
+ std::mt19937 gen(rd());
107
+ std::uniform_int_distribution<> dis(0, 255);
108
+
109
+ for (int i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
110
+ request[8 + i] = static_cast<uint8_t>(dis(gen));
111
+ }
112
+
113
+ LOG_STUN_DEBUG("Created STUN binding request (" << request.size() << " bytes)");
114
+ return request;
115
+ }
116
+
117
+ bool StunClient::parse_binding_response(const std::vector<uint8_t>& response,
118
+ StunAddress& mapped_address) {
119
+ if (response.size() < stun::HEADER_SIZE) {
120
+ LOG_STUN_ERROR("STUN response too short: " << response.size() << " bytes");
121
+ return false;
122
+ }
123
+
124
+ // Parse header
125
+ uint16_t message_type = parse_uint16(response.data());
126
+ uint16_t message_length = parse_uint16(response.data() + 2);
127
+ uint32_t magic_cookie = parse_uint32(response.data() + 4);
128
+
129
+ // Verify this is a binding response
130
+ if (message_type != stun::BINDING_RESPONSE) {
131
+ LOG_STUN_ERROR("Invalid STUN message type: 0x" << std::hex << message_type);
132
+ return false;
133
+ }
134
+
135
+ // Verify magic cookie
136
+ if (magic_cookie != stun::MAGIC_COOKIE) {
137
+ LOG_STUN_ERROR("Invalid STUN magic cookie: 0x" << std::hex << magic_cookie);
138
+ return false;
139
+ }
140
+
141
+ // Check if response has the expected length
142
+ if (response.size() != stun::HEADER_SIZE + message_length) {
143
+ LOG_STUN_ERROR("STUN response length mismatch: expected " <<
144
+ (stun::HEADER_SIZE + message_length) << ", got " << response.size());
145
+ return false;
146
+ }
147
+
148
+ // Parse attributes
149
+ size_t offset = stun::HEADER_SIZE;
150
+ bool found_mapped_address = false;
151
+
152
+ while (offset < response.size()) {
153
+ if (offset + 4 > response.size()) {
154
+ LOG_STUN_ERROR("Truncated STUN attribute header");
155
+ break;
156
+ }
157
+
158
+ uint16_t attr_type = parse_uint16(response.data() + offset);
159
+ uint16_t attr_length = parse_uint16(response.data() + offset + 2);
160
+ offset += 4;
161
+
162
+ if (offset + attr_length > response.size()) {
163
+ LOG_STUN_ERROR("Truncated STUN attribute value");
164
+ break;
165
+ }
166
+
167
+ // Handle XOR-MAPPED-ADDRESS (preferred) or MAPPED-ADDRESS
168
+ if (attr_type == stun::ATTR_XOR_MAPPED_ADDRESS || attr_type == stun::ATTR_MAPPED_ADDRESS) {
169
+ if (attr_length < 8) {
170
+ LOG_STUN_ERROR("Invalid STUN address attribute length: " << attr_length);
171
+ offset += attr_length;
172
+ continue;
173
+ }
174
+
175
+ const uint8_t* attr_data = response.data() + offset;
176
+
177
+ // Skip reserved byte
178
+ uint8_t family = attr_data[1];
179
+ uint16_t port = parse_uint16(attr_data + 2);
180
+
181
+ if (family == stun::FAMILY_IPV4 && attr_length >= 8) {
182
+ // IPv4 address
183
+ uint32_t ip_addr = parse_uint32(attr_data + 4);
184
+
185
+ // Convert to string
186
+ struct in_addr addr;
187
+ addr.s_addr = htonl(ip_addr);
188
+ char ip_str[INET_ADDRSTRLEN];
189
+ if (inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN)) {
190
+ mapped_address.family = family;
191
+ mapped_address.port = port;
192
+ mapped_address.ip = ip_str;
193
+
194
+ // If this is XOR-MAPPED-ADDRESS, we need to XOR with magic cookie and transaction ID
195
+ if (attr_type == stun::ATTR_XOR_MAPPED_ADDRESS) {
196
+ xor_address(mapped_address, response.data() + 8); // transaction ID starts at offset 8
197
+ }
198
+
199
+ found_mapped_address = true;
200
+ LOG_STUN_DEBUG("Found " << (attr_type == stun::ATTR_XOR_MAPPED_ADDRESS ? "XOR-" : "")
201
+ << "MAPPED-ADDRESS: " << mapped_address.ip << ":" << mapped_address.port);
202
+ break;
203
+ }
204
+ } else if (family == stun::FAMILY_IPV6 && attr_length >= 20) {
205
+ // IPv6 address
206
+ LOG_STUN_DEBUG("IPv6 address found but not fully implemented");
207
+ // TODO: Implement IPv6 support if needed
208
+ }
209
+ }
210
+
211
+ // Move to next attribute (attributes are padded to 4-byte boundaries)
212
+ size_t padded_length = (attr_length + 3) & ~3;
213
+ offset += padded_length;
214
+ }
215
+
216
+ if (!found_mapped_address) {
217
+ LOG_STUN_ERROR("No mapped address found in STUN response");
218
+ return false;
219
+ }
220
+
221
+ return true;
222
+ }
223
+
224
+ void StunClient::generate_transaction_id(uint8_t* transaction_id) {
225
+ std::random_device rd;
226
+ std::mt19937 gen(rd());
227
+ std::uniform_int_distribution<> dis(0, 255);
228
+
229
+ for (int i = 0; i < stun::TRANSACTION_ID_SIZE; ++i) {
230
+ transaction_id[i] = static_cast<uint8_t>(dis(gen));
231
+ }
232
+ }
233
+
234
+ bool StunClient::send_stun_request(socket_t sock,
235
+ const std::string& server,
236
+ int port,
237
+ const std::vector<uint8_t>& request) {
238
+ LOG_STUN_DEBUG("Sending STUN request to " << server << ":" << port << " (" << request.size() << " bytes)");
239
+
240
+ // Use the socket library function to send UDP data
241
+ int bytes_sent = send_udp_data_to(sock, request, server, port);
242
+
243
+ if (bytes_sent < 0) {
244
+ LOG_STUN_ERROR("Failed to send STUN request to " << server << ":" << port);
245
+ return false;
246
+ }
247
+
248
+ if (bytes_sent != static_cast<int>(request.size())) {
249
+ LOG_STUN_ERROR("Partial STUN request sent: " << bytes_sent << "/" << request.size() << " bytes");
250
+ return false;
251
+ }
252
+
253
+ LOG_STUN_DEBUG("Successfully sent STUN request to " << server << ":" << port << " (" << bytes_sent << " bytes)");
254
+ return true;
255
+ }
256
+
257
+ bool StunClient::receive_stun_response(socket_t sock,
258
+ std::vector<uint8_t>& response,
259
+ int timeout_ms) {
260
+ LOG_STUN_DEBUG("Waiting for STUN response with timeout " << timeout_ms << "ms");
261
+
262
+ std::string sender_ip;
263
+ int sender_port;
264
+
265
+ // Use the socket library function to receive UDP data with timeout
266
+ response = receive_udp_data_with_timeout(sock, 1024, timeout_ms, &sender_ip, &sender_port);
267
+
268
+ if (response.empty()) {
269
+ LOG_STUN_ERROR("Failed to receive STUN response or timeout occurred");
270
+ return false;
271
+ }
272
+
273
+ LOG_STUN_DEBUG("Received STUN response from " << sender_ip << ":" << sender_port
274
+ << " (" << response.size() << " bytes)");
275
+
276
+ return true;
277
+ }
278
+
279
+ // Helper functions for parsing binary data
280
+ uint16_t StunClient::parse_uint16(const uint8_t* data) {
281
+ return (static_cast<uint16_t>(data[0]) << 8) | static_cast<uint16_t>(data[1]);
282
+ }
283
+
284
+ uint32_t StunClient::parse_uint32(const uint8_t* data) {
285
+ return (static_cast<uint32_t>(data[0]) << 24) |
286
+ (static_cast<uint32_t>(data[1]) << 16) |
287
+ (static_cast<uint32_t>(data[2]) << 8) |
288
+ static_cast<uint32_t>(data[3]);
289
+ }
290
+
291
+ void StunClient::write_uint16(uint8_t* data, uint16_t value) {
292
+ data[0] = static_cast<uint8_t>((value >> 8) & 0xFF);
293
+ data[1] = static_cast<uint8_t>(value & 0xFF);
294
+ }
295
+
296
+ void StunClient::write_uint32(uint8_t* data, uint32_t value) {
297
+ data[0] = static_cast<uint8_t>((value >> 24) & 0xFF);
298
+ data[1] = static_cast<uint8_t>((value >> 16) & 0xFF);
299
+ data[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
300
+ data[3] = static_cast<uint8_t>(value & 0xFF);
301
+ }
302
+
303
+ void StunClient::xor_address(StunAddress& address, const uint8_t* transaction_id) {
304
+ // For XOR-MAPPED-ADDRESS, we need to XOR the port with the first 16 bits of magic cookie
305
+ // and the IP address with magic cookie
306
+
307
+ // XOR port with first 16 bits of magic cookie
308
+ uint16_t magic_cookie_high = (stun::MAGIC_COOKIE >> 16) & 0xFFFF;
309
+ address.port ^= magic_cookie_high;
310
+
311
+ // XOR IPv4 address with magic cookie
312
+ if (address.family == stun::FAMILY_IPV4) {
313
+ struct in_addr addr;
314
+ if (inet_pton(AF_INET, address.ip.c_str(), &addr) == 1) {
315
+ uint32_t ip_addr = ntohl(addr.s_addr);
316
+ ip_addr ^= stun::MAGIC_COOKIE;
317
+ addr.s_addr = htonl(ip_addr);
318
+
319
+ char ip_str[INET_ADDRSTRLEN];
320
+ if (inet_ntop(AF_INET, &addr, ip_str, INET_ADDRSTRLEN)) {
321
+ address.ip = ip_str;
322
+ }
323
+ }
324
+ }
325
+ // TODO: Implement IPv6 XOR if needed (requires XOR with magic cookie + transaction ID)
326
+ }
327
+
328
+ // ICE-specific static helper function implementation
329
+ std::vector<uint8_t> StunClient::create_binding_request_ice(const std::string& username,
330
+ const std::string& password,
331
+ uint32_t priority,
332
+ bool controlling,
333
+ uint64_t tie_breaker,
334
+ bool use_candidate) {
335
+ // Start with basic binding request
336
+ std::vector<uint8_t> message = create_binding_request();
337
+
338
+ // Add ICE-specific attributes
339
+ size_t original_size = message.size();
340
+
341
+ // USERNAME attribute (0x0006)
342
+ if (!username.empty()) {
343
+ uint16_t attr_type = 0x0006;
344
+ uint16_t attr_length = username.length();
345
+
346
+ message.push_back((attr_type >> 8) & 0xFF);
347
+ message.push_back(attr_type & 0xFF);
348
+ message.push_back((attr_length >> 8) & 0xFF);
349
+ message.push_back(attr_length & 0xFF);
350
+
351
+ for (char c : username) {
352
+ message.push_back(static_cast<uint8_t>(c));
353
+ }
354
+
355
+ // Pad to 4-byte boundary
356
+ while ((message.size() - original_size) % 4 != 0) {
357
+ message.push_back(0);
358
+ }
359
+ }
360
+
361
+ // PRIORITY attribute (0x0024)
362
+ {
363
+ uint16_t attr_type = 0x0024;
364
+ uint16_t attr_length = 4;
365
+
366
+ message.push_back((attr_type >> 8) & 0xFF);
367
+ message.push_back(attr_type & 0xFF);
368
+ message.push_back((attr_length >> 8) & 0xFF);
369
+ message.push_back(attr_length & 0xFF);
370
+
371
+ message.push_back((priority >> 24) & 0xFF);
372
+ message.push_back((priority >> 16) & 0xFF);
373
+ message.push_back((priority >> 8) & 0xFF);
374
+ message.push_back(priority & 0xFF);
375
+ }
376
+
377
+ // ICE-CONTROLLING or ICE-CONTROLLED attribute
378
+ {
379
+ uint16_t attr_type = controlling ? 0x802A : 0x8029; // ICE-CONTROLLING : ICE-CONTROLLED
380
+ uint16_t attr_length = 8;
381
+
382
+ message.push_back((attr_type >> 8) & 0xFF);
383
+ message.push_back(attr_type & 0xFF);
384
+ message.push_back((attr_length >> 8) & 0xFF);
385
+ message.push_back(attr_length & 0xFF);
386
+
387
+ // Tie breaker value
388
+ for (int i = 7; i >= 0; i--) {
389
+ message.push_back((tie_breaker >> (i * 8)) & 0xFF);
390
+ }
391
+ }
392
+
393
+ // USE-CANDIDATE attribute (0x0025) - only for controlling agent
394
+ if (use_candidate && controlling) {
395
+ uint16_t attr_type = 0x0025;
396
+ uint16_t attr_length = 0; // No value for this attribute
397
+
398
+ message.push_back((attr_type >> 8) & 0xFF);
399
+ message.push_back(attr_type & 0xFF);
400
+ message.push_back((attr_length >> 8) & 0xFF);
401
+ message.push_back(attr_length & 0xFF);
402
+ }
403
+
404
+ // Update message length
405
+ uint16_t total_length = message.size() - 20; // Exclude header
406
+ message[2] = (total_length >> 8) & 0xFF;
407
+ message[3] = total_length & 0xFF;
408
+
409
+ return message;
410
+ }
411
+
412
+ // AdvancedNatDetector implementation
413
+ AdvancedNatDetector::AdvancedNatDetector() {
414
+ stun_client_ = std::make_unique<StunClient>();
415
+ LOG_STUN_DEBUG("AdvancedNatDetector created");
416
+ }
417
+
418
+ AdvancedNatDetector::~AdvancedNatDetector() {
419
+ LOG_STUN_DEBUG("AdvancedNatDetector destroyed");
420
+ }
421
+
422
+ NatTypeInfo AdvancedNatDetector::detect_nat_characteristics(const std::vector<std::string>& stun_servers,
423
+ int timeout_ms) {
424
+ NatTypeInfo info;
425
+
426
+ if (stun_servers.empty()) {
427
+ LOG_STUN_WARN("No STUN servers provided for NAT detection");
428
+ info.has_nat = true; // Assume NAT if we can't test
429
+ info.filtering_behavior = NatBehavior::UNKNOWN;
430
+ info.mapping_behavior = NatBehavior::UNKNOWN;
431
+ info.preserves_port = false;
432
+ info.hairpin_support = false;
433
+ info.description = "No STUN servers available";
434
+ return info;
435
+ }
436
+
437
+ // Basic implementation - test if we can reach STUN server
438
+ StunAddress public_address;
439
+
440
+ if (stun_client_->get_public_address(stun_servers[0], 3478, public_address, timeout_ms)) {
441
+ // Simple NAT type detection - this is a minimal implementation
442
+ info.has_nat = true; // Assume NAT for now
443
+ info.filtering_behavior = NatBehavior::ENDPOINT_INDEPENDENT;
444
+ info.mapping_behavior = NatBehavior::ENDPOINT_INDEPENDENT;
445
+ info.preserves_port = false;
446
+ info.hairpin_support = false;
447
+ info.description = "NAT detected via STUN";
448
+ } else {
449
+ info.has_nat = false;
450
+ info.filtering_behavior = NatBehavior::ENDPOINT_INDEPENDENT;
451
+ info.mapping_behavior = NatBehavior::ENDPOINT_INDEPENDENT;
452
+ info.preserves_port = true;
453
+ info.hairpin_support = true;
454
+ info.description = "Open internet connection or STUN failure";
455
+ }
456
+
457
+ return info;
458
+ }
459
+
460
+ bool AdvancedNatDetector::test_hairpin_support(const std::string& stun_server, int timeout_ms) {
461
+ // Simplified implementation - return false for now
462
+ LOG_STUN_DEBUG("Testing hairpin support (not fully implemented)");
463
+ return false;
464
+ }
465
+
466
+ bool AdvancedNatDetector::test_port_preservation(const std::vector<std::string>& stun_servers, int timeout_ms) {
467
+ // Simplified implementation - return false for now
468
+ LOG_STUN_DEBUG("Testing port preservation (not fully implemented)");
469
+ return false;
470
+ }
471
+
472
+ NatBehavior AdvancedNatDetector::test_filtering_behavior(const std::vector<std::string>& stun_servers, int timeout_ms) {
473
+ // Simplified implementation - return endpoint independent for now
474
+ LOG_STUN_DEBUG("Testing filtering behavior (not fully implemented)");
475
+ return NatBehavior::ENDPOINT_INDEPENDENT;
476
+ }
477
+
478
+ NatBehavior AdvancedNatDetector::test_mapping_behavior(const std::vector<std::string>& stun_servers, int timeout_ms) {
479
+ // Simplified implementation - return endpoint independent for now
480
+ LOG_STUN_DEBUG("Testing mapping behavior (not fully implemented)");
481
+ return NatBehavior::ENDPOINT_INDEPENDENT;
482
+ }
483
+
484
+ } // namespace librats