librats 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/binding.gyp +1 -0
  3. package/lib/index.d.ts +2 -1
  4. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  5. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  6. package/native-src/CMakeLists.txt +360 -0
  7. package/native-src/LICENSE +21 -0
  8. package/native-src/src/bencode.cpp +485 -0
  9. package/native-src/src/bencode.h +145 -0
  10. package/native-src/src/bittorrent.cpp +3682 -0
  11. package/native-src/src/bittorrent.h +731 -0
  12. package/native-src/src/dht.cpp +2460 -0
  13. package/native-src/src/dht.h +508 -0
  14. package/native-src/src/encrypted_socket.cpp +817 -0
  15. package/native-src/src/encrypted_socket.h +239 -0
  16. package/native-src/src/file_transfer.cpp +1808 -0
  17. package/native-src/src/file_transfer.h +567 -0
  18. package/native-src/src/fs.cpp +639 -0
  19. package/native-src/src/fs.h +108 -0
  20. package/native-src/src/gossipsub.cpp +1137 -0
  21. package/native-src/src/gossipsub.h +403 -0
  22. package/native-src/src/ice.cpp +1386 -0
  23. package/native-src/src/ice.h +328 -0
  24. package/native-src/src/json.hpp +25526 -0
  25. package/native-src/src/krpc.cpp +558 -0
  26. package/native-src/src/krpc.h +145 -0
  27. package/native-src/src/librats.cpp +2735 -0
  28. package/native-src/src/librats.h +1732 -0
  29. package/native-src/src/librats_bittorrent.cpp +167 -0
  30. package/native-src/src/librats_c.cpp +1333 -0
  31. package/native-src/src/librats_c.h +239 -0
  32. package/native-src/src/librats_encryption.cpp +123 -0
  33. package/native-src/src/librats_file_transfer.cpp +226 -0
  34. package/native-src/src/librats_gossipsub.cpp +293 -0
  35. package/native-src/src/librats_ice.cpp +515 -0
  36. package/native-src/src/librats_logging.cpp +158 -0
  37. package/native-src/src/librats_mdns.cpp +171 -0
  38. package/native-src/src/librats_nat.cpp +571 -0
  39. package/native-src/src/librats_persistence.cpp +815 -0
  40. package/native-src/src/logger.h +412 -0
  41. package/native-src/src/mdns.cpp +1178 -0
  42. package/native-src/src/mdns.h +253 -0
  43. package/native-src/src/network_utils.cpp +598 -0
  44. package/native-src/src/network_utils.h +162 -0
  45. package/native-src/src/noise.cpp +981 -0
  46. package/native-src/src/noise.h +227 -0
  47. package/native-src/src/os.cpp +371 -0
  48. package/native-src/src/os.h +40 -0
  49. package/native-src/src/rats_export.h +17 -0
  50. package/native-src/src/sha1.cpp +163 -0
  51. package/native-src/src/sha1.h +42 -0
  52. package/native-src/src/socket.cpp +1376 -0
  53. package/native-src/src/socket.h +309 -0
  54. package/native-src/src/stun.cpp +484 -0
  55. package/native-src/src/stun.h +349 -0
  56. package/native-src/src/threadmanager.cpp +105 -0
  57. package/native-src/src/threadmanager.h +53 -0
  58. package/native-src/src/tracker.cpp +1110 -0
  59. package/native-src/src/tracker.h +268 -0
  60. package/native-src/src/version.cpp +24 -0
  61. package/native-src/src/version.h.in +45 -0
  62. package/native-src/version.rc.in +31 -0
  63. package/package.json +2 -8
  64. package/scripts/build-librats.js +59 -12
  65. package/scripts/prepare-package.js +133 -37
  66. package/src/librats_node.cpp +46 -1
@@ -0,0 +1,1386 @@
1
+ #include "ice.h"
2
+ #include "socket.h"
3
+ #include "stun.h"
4
+ #include "network_utils.h"
5
+ #include "logger.h"
6
+ #include <random>
7
+ #include <sstream>
8
+ #include <iomanip>
9
+ #include <algorithm>
10
+ #include <cstring>
11
+
12
+ #ifdef _WIN32
13
+ #include <winsock2.h>
14
+ #include <ws2tcpip.h>
15
+ #else
16
+ #include <arpa/inet.h>
17
+ #include <netinet/in.h>
18
+ #include <sys/socket.h>
19
+ #endif
20
+
21
+ // ICE module logging macros
22
+ #define LOG_ICE_DEBUG(message) LOG_DEBUG("ice", message)
23
+ #define LOG_ICE_INFO(message) LOG_INFO("ice", message)
24
+ #define LOG_ICE_WARN(message) LOG_WARN("ice", message)
25
+ #define LOG_ICE_ERROR(message) LOG_ERROR("ice", message)
26
+
27
+ namespace librats {
28
+
29
+ //=============================================================================
30
+ // IceCandidate Implementation
31
+ //=============================================================================
32
+
33
+ IceCandidate::IceCandidate()
34
+ : component_id(1), transport(IceTransport::UDP), priority(0), port(0),
35
+ type(IceCandidateType::HOST), related_port(0), turn_port(0) {
36
+ }
37
+
38
+ std::string IceCandidate::to_sdp() const {
39
+ std::ostringstream sdp;
40
+ sdp << "candidate:" << foundation << " " << component_id << " "
41
+ << ice_transport_to_string(transport) << " " << priority << " "
42
+ << ip << " " << port << " typ " << ice_candidate_type_to_string(type);
43
+
44
+ if (!related_ip.empty() && related_port > 0) {
45
+ sdp << " raddr " << related_ip << " rport " << related_port;
46
+ }
47
+
48
+ return sdp.str();
49
+ }
50
+
51
+ IceCandidate IceCandidate::from_sdp(const std::string& sdp_line) {
52
+ IceCandidate candidate;
53
+ std::istringstream iss(sdp_line);
54
+ std::string token;
55
+
56
+ // Parse: candidate:foundation component transport priority ip port typ type [raddr related_ip rport related_port]
57
+ if (iss >> token && token.substr(0, 10) == "candidate:") {
58
+ candidate.foundation = token.substr(10);
59
+ iss >> candidate.component_id;
60
+
61
+ iss >> token;
62
+ candidate.transport = string_to_ice_transport(token);
63
+
64
+ iss >> candidate.priority;
65
+ iss >> candidate.ip;
66
+ iss >> candidate.port;
67
+
68
+ iss >> token; // "typ"
69
+ iss >> token;
70
+ candidate.type = string_to_ice_candidate_type(token);
71
+
72
+ // Parse optional related address
73
+ while (iss >> token) {
74
+ if (token == "raddr") {
75
+ iss >> candidate.related_ip;
76
+ } else if (token == "rport") {
77
+ iss >> candidate.related_port;
78
+ }
79
+ }
80
+ }
81
+
82
+ return candidate;
83
+ }
84
+
85
+ nlohmann::json IceCandidate::to_json() const {
86
+ nlohmann::json json;
87
+ json["foundation"] = foundation;
88
+ json["component_id"] = component_id;
89
+ json["transport"] = ice_transport_to_string(transport);
90
+ json["priority"] = priority;
91
+ json["ip"] = ip;
92
+ json["port"] = port;
93
+ json["type"] = ice_candidate_type_to_string(type);
94
+ json["related_ip"] = related_ip;
95
+ json["related_port"] = related_port;
96
+ json["ufrag"] = ufrag;
97
+ json["pwd"] = pwd;
98
+ json["turn_server"] = turn_server;
99
+ json["turn_port"] = turn_port;
100
+ return json;
101
+ }
102
+
103
+ IceCandidate IceCandidate::from_json(const nlohmann::json& json) {
104
+ IceCandidate candidate;
105
+ candidate.foundation = json.value("foundation", "");
106
+ candidate.component_id = json.value("component_id", 1);
107
+ candidate.transport = string_to_ice_transport(json.value("transport", "udp"));
108
+ candidate.priority = json.value("priority", 0);
109
+ candidate.ip = json.value("ip", "");
110
+ candidate.port = json.value("port", 0);
111
+ candidate.type = string_to_ice_candidate_type(json.value("type", "host"));
112
+ candidate.related_ip = json.value("related_ip", "");
113
+ candidate.related_port = json.value("related_port", 0);
114
+ candidate.ufrag = json.value("ufrag", "");
115
+ candidate.pwd = json.value("pwd", "");
116
+ candidate.turn_server = json.value("turn_server", "");
117
+ candidate.turn_port = json.value("turn_port", 0);
118
+ return candidate;
119
+ }
120
+
121
+ //=============================================================================
122
+ // IceCandidatePair Implementation
123
+ //=============================================================================
124
+
125
+ IceCandidatePair::IceCandidatePair(const IceCandidate& local, const IceCandidate& remote)
126
+ : local(local), remote(remote), nominated(false), check_count(0), succeeded(false) {
127
+ priority = calculate_priority();
128
+ last_check_time = std::chrono::steady_clock::now();
129
+ }
130
+
131
+ uint64_t IceCandidatePair::calculate_priority() const {
132
+ // RFC 8445 - Use the higher priority as the controlling agent priority
133
+ uint32_t controlling = (std::max)(local.priority, remote.priority);
134
+ uint32_t controlled = (std::min)(local.priority, remote.priority);
135
+
136
+ return (static_cast<uint64_t>(controlling) << 32) | controlled;
137
+ }
138
+
139
+ //=============================================================================
140
+ // IceConfig Implementation
141
+ //=============================================================================
142
+
143
+ IceConfig::IceConfig()
144
+ : enable_host_candidates(true),
145
+ enable_server_reflexive_candidates(true),
146
+ enable_relay_candidates(true),
147
+ enable_tcp_candidates(false),
148
+ stun_timeout_ms(5000),
149
+ turn_timeout_ms(10000),
150
+ connectivity_check_timeout_ms(30000),
151
+ max_connectivity_checks(100) {
152
+
153
+ // Default STUN servers
154
+ stun_servers.push_back("stun.l.google.com:19302");
155
+ stun_servers.push_back("stun1.l.google.com:19302");
156
+ stun_servers.push_back("stun.stunprotocol.org:3478");
157
+ }
158
+
159
+ //=============================================================================
160
+ // TurnClient Implementation
161
+ //=============================================================================
162
+
163
+ TurnClient::TurnClient(const std::string& server, uint16_t port,
164
+ const std::string& username, const std::string& password)
165
+ : server_(server), port_(port), username_(username), password_(password),
166
+ socket_(INVALID_SOCKET_VALUE), allocated_(false), allocated_port_(0) {
167
+
168
+ last_refresh_ = std::chrono::steady_clock::now();
169
+ }
170
+
171
+ TurnClient::~TurnClient() {
172
+ deallocate();
173
+ }
174
+
175
+ bool TurnClient::allocate_relay(std::string& allocated_ip, uint16_t& allocated_port) {
176
+ LOG_ICE_INFO("Allocating TURN relay on " << server_ << ":" << port_);
177
+
178
+ // Initialize socket library (safe to call multiple times)
179
+ if (!init_socket_library()) {
180
+ LOG_ICE_ERROR("Failed to initialize socket library");
181
+ return false;
182
+ }
183
+
184
+ // Create UDP socket for TURN
185
+ socket_ = create_udp_socket_v4(0);
186
+ if (!is_valid_socket(socket_)) {
187
+ LOG_ICE_ERROR("Failed to create socket for TURN client");
188
+ return false;
189
+ }
190
+
191
+ if (!send_allocate_request()) {
192
+ LOG_ICE_ERROR("Failed to send TURN allocate request");
193
+ close_socket(socket_);
194
+ socket_ = INVALID_SOCKET_VALUE;
195
+ return false;
196
+ }
197
+
198
+ // Wait for response
199
+ std::vector<uint8_t> response;
200
+ Peer sender;
201
+ response = receive_udp_data(socket_, 1500, sender);
202
+
203
+ if (response.empty()) {
204
+ LOG_ICE_ERROR("No response from TURN server");
205
+ close_socket(socket_);
206
+ socket_ = INVALID_SOCKET_VALUE;
207
+ return false;
208
+ }
209
+
210
+ if (!handle_allocate_response(response)) {
211
+ LOG_ICE_ERROR("Failed to handle TURN allocate response");
212
+ close_socket(socket_);
213
+ socket_ = INVALID_SOCKET_VALUE;
214
+ return false;
215
+ }
216
+
217
+ allocated_ = true;
218
+ allocated_ip = allocated_ip_;
219
+ allocated_port = allocated_port_;
220
+
221
+ LOG_ICE_INFO("TURN relay allocated: " << allocated_ip_ << ":" << allocated_port_);
222
+ return true;
223
+ }
224
+
225
+ bool TurnClient::create_permission(const std::string& peer_ip) {
226
+ if (!allocated_) {
227
+ LOG_ICE_ERROR("TURN relay not allocated");
228
+ return false;
229
+ }
230
+
231
+ LOG_ICE_DEBUG("Creating TURN permission for " << peer_ip);
232
+
233
+ // Create permission request - simplified implementation
234
+ // In a full implementation, this would send a proper TURN CreatePermission request
235
+ return true;
236
+ }
237
+
238
+ bool TurnClient::send_data(const std::vector<uint8_t>& data, const std::string& peer_ip, uint16_t peer_port) {
239
+ if (!allocated_) {
240
+ LOG_ICE_ERROR("TURN relay not allocated");
241
+ return false;
242
+ }
243
+
244
+ // Send data through TURN relay - simplified implementation
245
+ // In a full implementation, this would use TURN Send indication or ChannelData
246
+ return send_udp_data_to(socket_, data, peer_ip, peer_port) > 0;
247
+ }
248
+
249
+ std::vector<uint8_t> TurnClient::receive_data(std::string& from_ip, uint16_t& from_port, int timeout_ms) {
250
+ if (!allocated_) {
251
+ return {};
252
+ }
253
+
254
+ // Receive data through TURN relay - simplified implementation
255
+ std::vector<uint8_t> response = receive_udp_data_with_timeout(socket_, 1500, timeout_ms, &from_ip, reinterpret_cast<int*>(&from_port));
256
+ return response;
257
+ }
258
+
259
+ void TurnClient::refresh_allocation() {
260
+ if (!allocated_) {
261
+ return;
262
+ }
263
+
264
+ auto now = std::chrono::steady_clock::now();
265
+ auto duration = std::chrono::duration_cast<std::chrono::minutes>(now - last_refresh_);
266
+
267
+ if (duration.count() >= 5) { // Refresh every 5 minutes
268
+ send_refresh_request();
269
+ last_refresh_ = now;
270
+ }
271
+ }
272
+
273
+ void TurnClient::deallocate() {
274
+ if (allocated_) {
275
+ LOG_ICE_DEBUG("Deallocating TURN relay");
276
+ // Send deallocate request - simplified implementation
277
+ allocated_ = false;
278
+ }
279
+
280
+ if (is_valid_socket(socket_)) {
281
+ close_socket(socket_);
282
+ socket_ = INVALID_SOCKET_VALUE;
283
+ }
284
+ }
285
+
286
+ bool TurnClient::send_allocate_request() {
287
+ // Simplified TURN allocate request
288
+ // In a full implementation, this would follow RFC 5766
289
+ std::vector<uint8_t> request(20); // Basic STUN header
290
+
291
+ // STUN message type: Allocate Request (0x003)
292
+ request[0] = 0x00;
293
+ request[1] = 0x03;
294
+
295
+ // Message length (will be filled)
296
+ request[2] = 0x00;
297
+ request[3] = 0x00;
298
+
299
+ // Magic cookie
300
+ request[4] = 0x21;
301
+ request[5] = 0x12;
302
+ request[6] = 0xA4;
303
+ request[7] = 0x42;
304
+
305
+ // Transaction ID (random)
306
+ std::random_device rd;
307
+ std::mt19937 gen(rd());
308
+ for (int i = 8; i < 20; ++i) {
309
+ request[i] = static_cast<uint8_t>(gen() % 256);
310
+ }
311
+
312
+ return send_udp_data_to(socket_, request, server_, port_) > 0;
313
+ }
314
+
315
+ bool TurnClient::send_refresh_request() {
316
+ // Simplified refresh request
317
+ return true;
318
+ }
319
+
320
+ bool TurnClient::handle_allocate_response(const std::vector<uint8_t>& response) {
321
+ if (response.size() < 20) {
322
+ return false;
323
+ }
324
+
325
+ // Check if it's a success response (0x0103)
326
+ if (response[0] == 0x01 && response[1] == 0x03) {
327
+ // Parse XOR-RELAYED-ADDRESS attribute (simplified)
328
+ allocated_ip_ = "127.0.0.1"; // Placeholder
329
+ allocated_port_ = 12345; // Placeholder
330
+ return true;
331
+ }
332
+
333
+ return false;
334
+ }
335
+
336
+ bool TurnClient::authenticate_with_server() {
337
+ // Simplified authentication
338
+ return true;
339
+ }
340
+
341
+ //=============================================================================
342
+ // NatTypeDetector Implementation
343
+ //=============================================================================
344
+
345
+ NatTypeDetector::NatTypeDetector() {
346
+ LOG_ICE_DEBUG("NAT type detector created");
347
+ }
348
+
349
+ NatTypeDetector::~NatTypeDetector() {
350
+ LOG_ICE_DEBUG("NAT type detector destroyed");
351
+ }
352
+
353
+ NatType NatTypeDetector::detect_nat_type(const std::vector<std::string>& stun_servers, int timeout_ms) {
354
+ LOG_ICE_INFO("Starting NAT type detection");
355
+
356
+ if (stun_servers.empty()) {
357
+ LOG_ICE_ERROR("No STUN servers provided for NAT detection");
358
+ return NatType::UNKNOWN;
359
+ }
360
+
361
+ std::string server1 = stun_servers[0];
362
+ std::string server2 = stun_servers.size() > 1 ? stun_servers[1] : server1;
363
+
364
+ // Test 1: Check if UDP is blocked
365
+ if (test_udp_blocked(server1, timeout_ms)) {
366
+ LOG_ICE_INFO("NAT type detected: UDP BLOCKED");
367
+ return NatType::BLOCKED;
368
+ }
369
+
370
+ // Test 2: Check for open internet (no NAT)
371
+ if (test_open_internet(server1, timeout_ms)) {
372
+ LOG_ICE_INFO("NAT type detected: OPEN INTERNET");
373
+ return NatType::OPEN_INTERNET;
374
+ }
375
+
376
+ // Test 3: Check for symmetric NAT
377
+ if (test_symmetric_nat(server1, server2, timeout_ms)) {
378
+ LOG_ICE_INFO("NAT type detected: SYMMETRIC NAT");
379
+ return NatType::SYMMETRIC;
380
+ }
381
+
382
+ // Test 4: Check for full cone NAT
383
+ if (test_full_cone(server1, server2, timeout_ms)) {
384
+ LOG_ICE_INFO("NAT type detected: FULL CONE NAT");
385
+ return NatType::FULL_CONE;
386
+ }
387
+
388
+ // Default to port restricted cone NAT
389
+ LOG_ICE_INFO("NAT type detected: PORT RESTRICTED NAT");
390
+ return NatType::PORT_RESTRICTED;
391
+ }
392
+
393
+ std::string NatTypeDetector::nat_type_to_string(NatType type) const {
394
+ switch (type) {
395
+ case NatType::UNKNOWN: return "Unknown";
396
+ case NatType::OPEN_INTERNET: return "Open Internet";
397
+ case NatType::FULL_CONE: return "Full Cone NAT";
398
+ case NatType::RESTRICTED_CONE: return "Restricted Cone NAT";
399
+ case NatType::PORT_RESTRICTED: return "Port Restricted Cone NAT";
400
+ case NatType::SYMMETRIC: return "Symmetric NAT";
401
+ case NatType::BLOCKED: return "UDP Blocked";
402
+ default: return "Unknown";
403
+ }
404
+ }
405
+
406
+ bool NatTypeDetector::test_udp_blocked(const std::string& stun_server, int timeout_ms) {
407
+ size_t colon_pos = stun_server.find(':');
408
+ std::string host = stun_server.substr(0, colon_pos);
409
+ int port = colon_pos != std::string::npos ? std::stoi(stun_server.substr(colon_pos + 1)) : 3478;
410
+
411
+ StunClient stun_client;
412
+ StunAddress public_address;
413
+
414
+ return !stun_client.get_public_address(host, port, public_address, timeout_ms);
415
+ }
416
+
417
+ bool NatTypeDetector::test_open_internet(const std::string& stun_server, int timeout_ms) {
418
+ StunAddress mapped_addr = get_mapped_address(stun_server, timeout_ms);
419
+ if (mapped_addr.ip.empty()) {
420
+ return false;
421
+ }
422
+
423
+ // Get local addresses
424
+ auto local_addrs = network_utils::get_local_interface_addresses();
425
+
426
+ // Check if mapped address matches any local address
427
+ return std::find(local_addrs.begin(), local_addrs.end(), mapped_addr.ip) != local_addrs.end();
428
+ }
429
+
430
+ bool NatTypeDetector::test_full_cone(const std::string& stun_server1, const std::string& stun_server2, int timeout_ms) {
431
+ StunAddress addr1 = get_mapped_address(stun_server1, timeout_ms);
432
+ StunAddress addr2 = get_mapped_address(stun_server2, timeout_ms);
433
+
434
+ if (addr1.ip.empty() || addr2.ip.empty()) {
435
+ return false;
436
+ }
437
+
438
+ // In full cone NAT, the mapped address should be the same from different servers
439
+ return addr1.ip == addr2.ip && addr1.port == addr2.port;
440
+ }
441
+
442
+ bool NatTypeDetector::test_symmetric_nat(const std::string& stun_server1, const std::string& stun_server2, int timeout_ms) {
443
+ StunAddress addr1 = get_mapped_address(stun_server1, timeout_ms);
444
+ StunAddress addr2 = get_mapped_address(stun_server2, timeout_ms);
445
+
446
+ if (addr1.ip.empty() || addr2.ip.empty()) {
447
+ return false;
448
+ }
449
+
450
+ // In symmetric NAT, the mapped address changes for different destinations
451
+ return addr1.ip != addr2.ip || addr1.port != addr2.port;
452
+ }
453
+
454
+ StunAddress NatTypeDetector::get_mapped_address(const std::string& stun_server, int timeout_ms) {
455
+ size_t colon_pos = stun_server.find(':');
456
+ std::string host = stun_server.substr(0, colon_pos);
457
+ int port = colon_pos != std::string::npos ? std::stoi(stun_server.substr(colon_pos + 1)) : 3478;
458
+
459
+ StunClient stun_client;
460
+ StunAddress public_address;
461
+
462
+ if (stun_client.get_public_address(host, port, public_address, timeout_ms)) {
463
+ return public_address;
464
+ }
465
+
466
+ return StunAddress{}; // Empty address on failure
467
+ }
468
+
469
+ StunAddress NatTypeDetector::get_mapped_address_different_port(const std::string& stun_server, int timeout_ms) {
470
+ // This would use a different port on the same server for testing
471
+ return get_mapped_address(stun_server, timeout_ms);
472
+ }
473
+
474
+ //=============================================================================
475
+ // IceAgent Implementation
476
+ //=============================================================================
477
+
478
+ IceAgent::IceAgent(IceRole role, const IceConfig& config)
479
+ : role_(role), config_(config), running_(false), state_(IceConnectionState::NEW),
480
+ udp_socket_(INVALID_SOCKET_VALUE), tcp_socket_(INVALID_SOCKET_VALUE),
481
+ selected_pair_(IceCandidate(), IceCandidate()) {
482
+
483
+ // Initialize NAT detector
484
+ nat_detector_ = std::make_unique<NatTypeDetector>();
485
+
486
+ // Generate local credentials
487
+ local_ufrag_ = generate_ufrag();
488
+ local_pwd_ = generate_password();
489
+
490
+ LOG_ICE_INFO("ICE Agent created with role: " << (role_ == IceRole::CONTROLLING ? "CONTROLLING" : "CONTROLLED"));
491
+ }
492
+
493
+ IceAgent::~IceAgent() {
494
+ stop();
495
+ }
496
+
497
+ bool IceAgent::start() {
498
+ if (running_.load()) {
499
+ LOG_ICE_WARN("ICE Agent is already running");
500
+ return false;
501
+ }
502
+
503
+ LOG_ICE_INFO("Starting ICE Agent");
504
+
505
+ // Initialize socket library (safe to call multiple times)
506
+ if (!init_socket_library()) {
507
+ LOG_ICE_ERROR("Failed to initialize socket library");
508
+ return false;
509
+ }
510
+
511
+ // Create UDP socket for ICE
512
+ udp_socket_ = create_udp_socket_v4(0);
513
+ if (!is_valid_socket(udp_socket_)) {
514
+ LOG_ICE_ERROR("Failed to create UDP socket for ICE");
515
+ return false;
516
+ }
517
+
518
+ // Set socket to non-blocking mode for receive loop
519
+ if (!set_socket_nonblocking(udp_socket_)) {
520
+ LOG_ICE_WARN("Failed to set ICE UDP socket to non-blocking mode");
521
+ }
522
+
523
+ running_.store(true);
524
+ set_state(IceConnectionState::NEW);
525
+
526
+ // Start receive thread
527
+ receive_thread_ = std::thread(&IceAgent::receive_loop, this);
528
+
529
+ LOG_ICE_INFO("ICE Agent started successfully");
530
+ return true;
531
+ }
532
+
533
+ void IceAgent::stop() {
534
+ if (!running_.load()) {
535
+ return;
536
+ }
537
+
538
+ LOG_ICE_INFO("Stopping ICE Agent");
539
+
540
+ // Trigger immediate shutdown of all background threads
541
+ shutdown_immediate();
542
+
543
+ // Close sockets
544
+ if (is_valid_socket(udp_socket_)) {
545
+ close_socket(udp_socket_);
546
+ udp_socket_ = INVALID_SOCKET_VALUE;
547
+ }
548
+
549
+ if (is_valid_socket(tcp_socket_)) {
550
+ close_socket(tcp_socket_);
551
+ tcp_socket_ = INVALID_SOCKET_VALUE;
552
+ }
553
+
554
+ // Wait for threads to finish
555
+ if (gather_thread_.joinable()) {
556
+ gather_thread_.join();
557
+ }
558
+
559
+ if (check_thread_.joinable()) {
560
+ check_thread_.join();
561
+ }
562
+
563
+ if (receive_thread_.joinable()) {
564
+ receive_thread_.join();
565
+ }
566
+
567
+ // Clean up TURN client
568
+ turn_client_.reset();
569
+
570
+ LOG_ICE_INFO("ICE Agent stopped");
571
+ }
572
+
573
+ void IceAgent::shutdown_immediate() {
574
+ LOG_ICE_INFO("Triggering immediate shutdown of ICE background threads");
575
+
576
+ running_.store(false);
577
+ set_state(IceConnectionState::CLOSED);
578
+
579
+ // Notify all waiting threads to wake up immediately
580
+ shutdown_cv_.notify_all();
581
+ }
582
+
583
+ void IceAgent::set_local_credentials(const std::string& ufrag, const std::string& pwd) {
584
+ local_ufrag_ = ufrag;
585
+ local_pwd_ = pwd;
586
+ LOG_ICE_DEBUG("Set local credentials: ufrag=" << ufrag);
587
+ }
588
+
589
+ void IceAgent::set_remote_credentials(const std::string& ufrag, const std::string& pwd) {
590
+ remote_ufrag_ = ufrag;
591
+ remote_pwd_ = pwd;
592
+ LOG_ICE_DEBUG("Set remote credentials: ufrag=" << ufrag);
593
+ }
594
+
595
+ std::pair<std::string, std::string> IceAgent::get_local_credentials() const {
596
+ return {local_ufrag_, local_pwd_};
597
+ }
598
+
599
+ void IceAgent::gather_candidates() {
600
+ if (!running_.load()) {
601
+ LOG_ICE_ERROR("ICE Agent not running");
602
+ return;
603
+ }
604
+
605
+ LOG_ICE_INFO("Starting candidate gathering");
606
+ set_state(IceConnectionState::GATHERING);
607
+
608
+ // Start gathering in a separate thread
609
+ gather_thread_ = std::thread([this]() {
610
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
611
+ local_candidates_.clear();
612
+
613
+ if (config_.enable_host_candidates) {
614
+ gather_host_candidates();
615
+ }
616
+
617
+ if (config_.enable_server_reflexive_candidates) {
618
+ gather_server_reflexive_candidates();
619
+ }
620
+
621
+ if (config_.enable_relay_candidates) {
622
+ gather_relay_candidates();
623
+ }
624
+
625
+ if (config_.enable_tcp_candidates) {
626
+ gather_tcp_candidates();
627
+ }
628
+
629
+ LOG_ICE_INFO("Candidate gathering completed. Found " << local_candidates_.size() << " candidates");
630
+
631
+ // Notify about gathered candidates
632
+ if (candidate_callback_) {
633
+ for (const auto& candidate : local_candidates_) {
634
+ candidate_callback_(candidate);
635
+ }
636
+ }
637
+ });
638
+ }
639
+
640
+ std::vector<IceCandidate> IceAgent::get_local_candidates() const {
641
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
642
+ return local_candidates_;
643
+ }
644
+
645
+ void IceAgent::add_remote_candidate(const IceCandidate& candidate) {
646
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
647
+ remote_candidates_.push_back(candidate);
648
+ LOG_ICE_DEBUG("Added remote candidate: " << candidate.ip << ":" << candidate.port);
649
+
650
+ // Form new candidate pairs
651
+ form_candidate_pairs();
652
+ }
653
+
654
+ void IceAgent::add_remote_candidates(const std::vector<IceCandidate>& candidates) {
655
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
656
+ for (const auto& candidate : candidates) {
657
+ remote_candidates_.push_back(candidate);
658
+ LOG_ICE_DEBUG("Added remote candidate: " << candidate.ip << ":" << candidate.port);
659
+ }
660
+
661
+ // Form new candidate pairs
662
+ form_candidate_pairs();
663
+ }
664
+
665
+ void IceAgent::start_connectivity_checks() {
666
+ if (!running_.load()) {
667
+ LOG_ICE_ERROR("ICE Agent not running");
668
+ return;
669
+ }
670
+
671
+ // Check if connectivity checks are already running
672
+ if (check_thread_.joinable()) {
673
+ LOG_ICE_WARN("Connectivity checks already running, skipping duplicate start");
674
+ return;
675
+ }
676
+
677
+ LOG_ICE_INFO("Starting connectivity checks");
678
+ set_state(IceConnectionState::CHECKING);
679
+
680
+ // Start connectivity checks in a separate thread
681
+ check_thread_ = std::thread(&IceAgent::connectivity_check_loop, this);
682
+ }
683
+
684
+ void IceAgent::restart_ice() {
685
+ LOG_ICE_INFO("Restarting ICE");
686
+
687
+ // Clear previous state
688
+ {
689
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
690
+ local_candidates_.clear();
691
+ remote_candidates_.clear();
692
+ }
693
+
694
+ {
695
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
696
+ candidate_pairs_.clear();
697
+ }
698
+
699
+ // Generate new credentials
700
+ local_ufrag_ = generate_ufrag();
701
+ local_pwd_ = generate_password();
702
+
703
+ // Restart gathering
704
+ set_state(IceConnectionState::NEW);
705
+ gather_candidates();
706
+ }
707
+
708
+ bool IceAgent::send_data(const std::vector<uint8_t>& data) {
709
+ if (!is_connected()) {
710
+ LOG_ICE_ERROR("ICE not connected, cannot send data");
711
+ return false;
712
+ }
713
+
714
+ // Send using selected pair
715
+ IceCandidatePair selected = [this]() {
716
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
717
+ return selected_pair_;
718
+ }();
719
+
720
+ if (selected.local.type == IceCandidateType::RELAY && turn_client_) {
721
+ return turn_client_->send_data(data, selected.remote.ip, selected.remote.port);
722
+ } else {
723
+ return send_udp_data_to(udp_socket_, data, selected.remote.ip, selected.remote.port) > 0;
724
+ }
725
+ }
726
+
727
+ bool IceAgent::send_data_to(const std::vector<uint8_t>& data, const std::string& addr) {
728
+ // Parse address
729
+ size_t colon_pos = addr.find(':');
730
+ if (colon_pos == std::string::npos) {
731
+ return false;
732
+ }
733
+
734
+ std::string ip = addr.substr(0, colon_pos);
735
+ uint16_t port = static_cast<uint16_t>(std::stoi(addr.substr(colon_pos + 1)));
736
+
737
+ return send_udp_data_to(udp_socket_, data, ip, port) > 0;
738
+ }
739
+
740
+ NatType IceAgent::detect_nat_type() {
741
+ if (!nat_detector_) {
742
+ return NatType::UNKNOWN;
743
+ }
744
+
745
+ return nat_detector_->detect_nat_type(config_.stun_servers, config_.stun_timeout_ms);
746
+ }
747
+
748
+ bool IceAgent::perform_hole_punching(const std::string& peer_ip, uint16_t peer_port) {
749
+ LOG_ICE_INFO("Performing hole punching to " << peer_ip << ":" << peer_port);
750
+
751
+ // Try UDP hole punching first
752
+ if (udp_hole_punch(peer_ip, peer_port)) {
753
+ return true;
754
+ }
755
+
756
+ // Try TCP hole punching if enabled
757
+ if (config_.enable_tcp_candidates) {
758
+ return tcp_hole_punch(peer_ip, peer_port);
759
+ }
760
+
761
+ return false;
762
+ }
763
+
764
+ bool IceAgent::coordinate_connection(const nlohmann::json& signaling_data) {
765
+ LOG_ICE_INFO("Coordinating connection with signaling data");
766
+
767
+ try {
768
+ // Extract remote candidates from signaling data
769
+ if (signaling_data.contains("candidates")) {
770
+ std::vector<IceCandidate> remote_candidates;
771
+ for (const auto& candidate_json : signaling_data["candidates"]) {
772
+ remote_candidates.push_back(IceCandidate::from_json(candidate_json));
773
+ }
774
+ add_remote_candidates(remote_candidates);
775
+ }
776
+
777
+ // Extract remote credentials
778
+ if (signaling_data.contains("ufrag") && signaling_data.contains("pwd")) {
779
+ set_remote_credentials(signaling_data["ufrag"], signaling_data["pwd"]);
780
+ }
781
+
782
+ // Start connectivity checks
783
+ start_connectivity_checks();
784
+
785
+ return true;
786
+ } catch (const std::exception& e) {
787
+ LOG_ICE_ERROR("Failed to coordinate connection: " << e.what());
788
+ return false;
789
+ }
790
+ }
791
+
792
+ nlohmann::json IceAgent::get_local_description() const {
793
+ nlohmann::json desc;
794
+ desc["ufrag"] = local_ufrag_;
795
+ desc["pwd"] = local_pwd_;
796
+
797
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
798
+ nlohmann::json candidates = nlohmann::json::array();
799
+ for (const auto& candidate : local_candidates_) {
800
+ candidates.push_back(candidate.to_json());
801
+ }
802
+ desc["candidates"] = candidates;
803
+
804
+ return desc;
805
+ }
806
+
807
+ bool IceAgent::set_remote_description(const nlohmann::json& remote_desc) {
808
+ try {
809
+ if (remote_desc.contains("ufrag") && remote_desc.contains("pwd")) {
810
+ set_remote_credentials(remote_desc["ufrag"], remote_desc["pwd"]);
811
+ }
812
+
813
+ if (remote_desc.contains("candidates")) {
814
+ std::vector<IceCandidate> candidates;
815
+ for (const auto& candidate_json : remote_desc["candidates"]) {
816
+ candidates.push_back(IceCandidate::from_json(candidate_json));
817
+ }
818
+ add_remote_candidates(candidates);
819
+ }
820
+
821
+ return true;
822
+ } catch (const std::exception& e) {
823
+ LOG_ICE_ERROR("Failed to set remote description: " << e.what());
824
+ return false;
825
+ }
826
+ }
827
+
828
+ nlohmann::json IceAgent::create_connection_offer() const {
829
+ nlohmann::json offer;
830
+ offer["type"] = "offer";
831
+ offer["ice"] = get_local_description();
832
+ offer["role"] = (role_ == IceRole::CONTROLLING) ? "controlling" : "controlled";
833
+ return offer;
834
+ }
835
+
836
+ nlohmann::json IceAgent::create_connection_answer(const nlohmann::json& offer) const {
837
+ nlohmann::json answer;
838
+ answer["type"] = "answer";
839
+ answer["ice"] = get_local_description();
840
+ answer["role"] = (role_ == IceRole::CONTROLLING) ? "controlling" : "controlled";
841
+ return answer;
842
+ }
843
+
844
+ std::vector<IceCandidatePair> IceAgent::get_candidate_pairs() const {
845
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
846
+ return candidate_pairs_;
847
+ }
848
+
849
+ IceCandidatePair IceAgent::get_selected_pair() const {
850
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
851
+ return selected_pair_;
852
+ }
853
+
854
+ nlohmann::json IceAgent::get_statistics() const {
855
+ nlohmann::json stats;
856
+ stats["state"] = ice_connection_state_to_string(state_.load());
857
+ stats["role"] = (role_ == IceRole::CONTROLLING) ? "controlling" : "controlled";
858
+
859
+ {
860
+ std::lock_guard<std::mutex> lock(candidates_mutex_);
861
+ stats["local_candidates"] = local_candidates_.size();
862
+ stats["remote_candidates"] = remote_candidates_.size();
863
+ }
864
+
865
+ {
866
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
867
+ stats["candidate_pairs"] = candidate_pairs_.size();
868
+ if (selected_pair_.succeeded) {
869
+ stats["selected_pair"] = {
870
+ {"local", selected_pair_.local.ip + ":" + std::to_string(selected_pair_.local.port)},
871
+ {"remote", selected_pair_.remote.ip + ":" + std::to_string(selected_pair_.remote.port)},
872
+ {"priority", selected_pair_.priority}
873
+ };
874
+ }
875
+ }
876
+
877
+ return stats;
878
+ }
879
+
880
+ // Private methods implementation
881
+
882
+ void IceAgent::set_state(IceConnectionState new_state) {
883
+ IceConnectionState old_state = state_.exchange(new_state);
884
+ if (old_state != new_state) {
885
+ LOG_ICE_INFO("ICE state changed: " << ice_connection_state_to_string(old_state)
886
+ << " -> " << ice_connection_state_to_string(new_state));
887
+
888
+ if (state_change_callback_) {
889
+ state_change_callback_(new_state);
890
+ }
891
+ }
892
+ }
893
+
894
+ void IceAgent::gather_host_candidates() {
895
+ LOG_ICE_DEBUG("Gathering host candidates");
896
+
897
+ auto local_addresses = network_utils::get_local_interface_addresses();
898
+
899
+ // Get actual port from socket
900
+ uint16_t actual_port = 0;
901
+ if (is_valid_socket(udp_socket_)) {
902
+ sockaddr_storage addr;
903
+ socklen_t addr_len = sizeof(addr);
904
+ if (getsockname(udp_socket_, reinterpret_cast<sockaddr*>(&addr), &addr_len) == 0) {
905
+ if (addr.ss_family == AF_INET) {
906
+ sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(&addr);
907
+ actual_port = ntohs(addr_in->sin_port);
908
+ } else if (addr.ss_family == AF_INET6) {
909
+ sockaddr_in6* addr_in6 = reinterpret_cast<sockaddr_in6*>(&addr);
910
+ actual_port = ntohs(addr_in6->sin6_port);
911
+ }
912
+ }
913
+ }
914
+
915
+ for (const auto& ip : local_addresses) {
916
+ if (ip == "127.0.0.1" || ip == "::1") {
917
+ continue; // Skip loopback
918
+ }
919
+
920
+ IceCandidate candidate;
921
+ candidate.foundation = generate_foundation(candidate);
922
+ candidate.component_id = 1;
923
+ candidate.transport = IceTransport::UDP;
924
+ candidate.priority = calculate_candidate_priority(IceCandidateType::HOST, 65535, 1);
925
+ candidate.ip = ip;
926
+ candidate.port = actual_port;
927
+ candidate.type = IceCandidateType::HOST;
928
+ candidate.ufrag = local_ufrag_;
929
+ candidate.pwd = local_pwd_;
930
+
931
+ local_candidates_.push_back(candidate);
932
+ LOG_ICE_DEBUG("Added host candidate: " << candidate.ip << ":" << candidate.port);
933
+ }
934
+ }
935
+
936
+ void IceAgent::gather_server_reflexive_candidates() {
937
+ LOG_ICE_DEBUG("Gathering server reflexive candidates");
938
+
939
+ for (const auto& stun_server : config_.stun_servers) {
940
+ size_t colon_pos = stun_server.find(':');
941
+ std::string host = stun_server.substr(0, colon_pos);
942
+ int port = colon_pos != std::string::npos ? std::stoi(stun_server.substr(colon_pos + 1)) : 3478;
943
+
944
+ StunClient stun_client;
945
+ StunAddress public_address;
946
+
947
+ if (stun_client.get_public_address(host, port, public_address, config_.stun_timeout_ms)) {
948
+ IceCandidate candidate;
949
+ candidate.foundation = generate_foundation(candidate);
950
+ candidate.component_id = 1;
951
+ candidate.transport = IceTransport::UDP;
952
+ candidate.priority = calculate_candidate_priority(IceCandidateType::SERVER_REFLEXIVE, 65534, 1);
953
+ candidate.ip = public_address.ip;
954
+ candidate.port = public_address.port;
955
+ candidate.type = IceCandidateType::SERVER_REFLEXIVE;
956
+ candidate.ufrag = local_ufrag_;
957
+ candidate.pwd = local_pwd_;
958
+
959
+ // Set related address (base)
960
+ if (!local_candidates_.empty()) {
961
+ candidate.related_ip = local_candidates_[0].ip;
962
+ candidate.related_port = local_candidates_[0].port;
963
+ }
964
+
965
+ local_candidates_.push_back(candidate);
966
+ LOG_ICE_DEBUG("Added server reflexive candidate: " << candidate.ip << ":" << candidate.port);
967
+ break; // Only need one reflexive candidate
968
+ }
969
+ }
970
+ }
971
+
972
+ void IceAgent::gather_relay_candidates() {
973
+ LOG_ICE_DEBUG("Gathering relay candidates");
974
+
975
+ if (config_.turn_servers.empty()) {
976
+ LOG_ICE_DEBUG("No TURN servers configured");
977
+ return;
978
+ }
979
+
980
+ // Use first TURN server
981
+ std::string turn_server = config_.turn_servers[0];
982
+ size_t colon_pos = turn_server.find(':');
983
+ std::string host = turn_server.substr(0, colon_pos);
984
+ uint16_t port = colon_pos != std::string::npos ? static_cast<uint16_t>(std::stoi(turn_server.substr(colon_pos + 1))) : 3478;
985
+
986
+ std::string username = config_.turn_usernames.empty() ? "" : config_.turn_usernames[0];
987
+ std::string password = config_.turn_passwords.empty() ? "" : config_.turn_passwords[0];
988
+
989
+ turn_client_ = std::make_unique<TurnClient>(host, port, username, password);
990
+
991
+ std::string allocated_ip;
992
+ uint16_t allocated_port;
993
+
994
+ if (turn_client_->allocate_relay(allocated_ip, allocated_port)) {
995
+ IceCandidate candidate;
996
+ candidate.foundation = generate_foundation(candidate);
997
+ candidate.component_id = 1;
998
+ candidate.transport = IceTransport::UDP;
999
+ candidate.priority = calculate_candidate_priority(IceCandidateType::RELAY, 65533, 1);
1000
+ candidate.ip = allocated_ip;
1001
+ candidate.port = allocated_port;
1002
+ candidate.type = IceCandidateType::RELAY;
1003
+ candidate.ufrag = local_ufrag_;
1004
+ candidate.pwd = local_pwd_;
1005
+ candidate.turn_server = host;
1006
+ candidate.turn_port = port;
1007
+
1008
+ // Set related address (TURN server)
1009
+ candidate.related_ip = host;
1010
+ candidate.related_port = port;
1011
+
1012
+ local_candidates_.push_back(candidate);
1013
+ LOG_ICE_DEBUG("Added relay candidate: " << candidate.ip << ":" << candidate.port);
1014
+ }
1015
+ }
1016
+
1017
+ void IceAgent::gather_tcp_candidates() {
1018
+ LOG_ICE_DEBUG("Gathering TCP candidates");
1019
+
1020
+ // TCP candidate gathering would be implemented here
1021
+ // This is more complex and less commonly used
1022
+ }
1023
+
1024
+ void IceAgent::connectivity_check_loop() {
1025
+ LOG_ICE_INFO("Starting connectivity check loop");
1026
+
1027
+ auto start_time = std::chrono::steady_clock::now();
1028
+ auto timeout = std::chrono::milliseconds(config_.connectivity_check_timeout_ms);
1029
+
1030
+ while (running_.load() && state_.load() == IceConnectionState::CHECKING) {
1031
+ auto now = std::chrono::steady_clock::now();
1032
+ if (now - start_time > timeout) {
1033
+ LOG_ICE_WARN("Connectivity checks timed out");
1034
+ set_state(IceConnectionState::FAILED);
1035
+ break;
1036
+ }
1037
+
1038
+ std::vector<IceCandidatePair> pairs_to_check;
1039
+ {
1040
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
1041
+ pairs_to_check = candidate_pairs_;
1042
+ }
1043
+
1044
+ bool any_succeeded = false;
1045
+ for (auto& pair : pairs_to_check) {
1046
+ if (pair.check_count >= config_.max_connectivity_checks) {
1047
+ continue;
1048
+ }
1049
+
1050
+ if (perform_connectivity_check(pair)) {
1051
+ pair.succeeded = true;
1052
+ any_succeeded = true;
1053
+
1054
+ // Update the pair in the main list
1055
+ {
1056
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
1057
+ for (auto& stored_pair : candidate_pairs_) {
1058
+ if (stored_pair.local.ip == pair.local.ip &&
1059
+ stored_pair.local.port == pair.local.port &&
1060
+ stored_pair.remote.ip == pair.remote.ip &&
1061
+ stored_pair.remote.port == pair.remote.port) {
1062
+ stored_pair = pair;
1063
+ break;
1064
+ }
1065
+ }
1066
+ }
1067
+
1068
+ // Select this pair if it's the first successful one
1069
+ {
1070
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
1071
+ if (!selected_pair_.succeeded) {
1072
+ selected_pair_ = pair;
1073
+ nominate_pair(pair);
1074
+
1075
+ set_state(IceConnectionState::CONNECTED);
1076
+
1077
+ std::string local_addr = pair.local.ip + ":" + std::to_string(pair.local.port);
1078
+ std::string remote_addr = pair.remote.ip + ":" + std::to_string(pair.remote.port);
1079
+
1080
+ if (connected_callback_) {
1081
+ connected_callback_(local_addr, remote_addr);
1082
+ }
1083
+
1084
+ LOG_ICE_INFO("ICE connection established using pair: " << local_addr << " <-> " << remote_addr);
1085
+ return;
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ pair.check_count++;
1091
+ pair.last_check_time = now;
1092
+ }
1093
+
1094
+ if (!any_succeeded && now - start_time > std::chrono::seconds(10)) {
1095
+ // Try hole punching for remaining pairs
1096
+ for (const auto& pair : pairs_to_check) {
1097
+ if (!pair.succeeded) {
1098
+ perform_hole_punching(pair.remote.ip, pair.remote.port);
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ // Use conditional variable for responsive shutdown
1104
+ {
1105
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
1106
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(100), [this] { return !running_.load(); })) {
1107
+ break;
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ if (state_.load() == IceConnectionState::CHECKING) {
1113
+ set_state(IceConnectionState::FAILED);
1114
+ }
1115
+ }
1116
+
1117
+ bool IceAgent::perform_connectivity_check(IceCandidatePair& pair) {
1118
+ LOG_ICE_DEBUG("Performing connectivity check: "
1119
+ << pair.local.ip << ":" << pair.local.port << " -> "
1120
+ << pair.remote.ip << ":" << pair.remote.port);
1121
+
1122
+ // Create STUN binding request for connectivity check
1123
+ std::vector<uint8_t> request = StunClient::create_binding_request();
1124
+
1125
+ // Send request
1126
+ int sent = 0;
1127
+ if (pair.local.type == IceCandidateType::RELAY && turn_client_) {
1128
+ sent = turn_client_->send_data(request, pair.remote.ip, pair.remote.port) ? request.size() : 0;
1129
+ } else {
1130
+ sent = send_udp_data_to(udp_socket_, request, pair.remote.ip, pair.remote.port);
1131
+ }
1132
+
1133
+ if (sent <= 0) {
1134
+ LOG_ICE_DEBUG("Failed to send connectivity check");
1135
+ return false;
1136
+ }
1137
+
1138
+ // Wait for response (simplified)
1139
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
1140
+
1141
+ // In a real implementation, we would wait for and validate the STUN response
1142
+ // For now, we'll consider the check successful if we could send the request
1143
+ return true;
1144
+ }
1145
+
1146
+ void IceAgent::form_candidate_pairs() {
1147
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
1148
+
1149
+ candidate_pairs_.clear();
1150
+
1151
+ for (const auto& local : local_candidates_) {
1152
+ for (const auto& remote : remote_candidates_) {
1153
+ // Only pair candidates of the same component and transport
1154
+ if (local.component_id == remote.component_id &&
1155
+ local.transport == remote.transport) {
1156
+ candidate_pairs_.emplace_back(local, remote);
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ // Sort pairs by priority (highest first)
1162
+ std::sort(candidate_pairs_.begin(), candidate_pairs_.end(),
1163
+ [](const IceCandidatePair& a, const IceCandidatePair& b) {
1164
+ return a.priority > b.priority;
1165
+ });
1166
+
1167
+ LOG_ICE_DEBUG("Formed " << candidate_pairs_.size() << " candidate pairs");
1168
+ }
1169
+
1170
+ void IceAgent::prioritize_candidate_pairs() {
1171
+ std::lock_guard<std::mutex> lock(pairs_mutex_);
1172
+
1173
+ // Recalculate priorities and sort
1174
+ for (auto& pair : candidate_pairs_) {
1175
+ pair.priority = pair.calculate_priority();
1176
+ }
1177
+
1178
+ std::sort(candidate_pairs_.begin(), candidate_pairs_.end(),
1179
+ [](const IceCandidatePair& a, const IceCandidatePair& b) {
1180
+ return a.priority > b.priority;
1181
+ });
1182
+ }
1183
+
1184
+ void IceAgent::nominate_pair(IceCandidatePair& pair) {
1185
+ pair.nominated = true;
1186
+ LOG_ICE_INFO("Nominated candidate pair: "
1187
+ << pair.local.ip << ":" << pair.local.port << " <-> "
1188
+ << pair.remote.ip << ":" << pair.remote.port);
1189
+ }
1190
+
1191
+ void IceAgent::receive_loop() {
1192
+ LOG_ICE_DEBUG("Starting ICE receive loop");
1193
+
1194
+ while (running_.load()) {
1195
+ Peer sender;
1196
+ std::vector<uint8_t> data = receive_udp_data(udp_socket_, 1500, sender);
1197
+
1198
+ if (!data.empty()) {
1199
+ std::string from_addr = sender.ip + ":" + std::to_string(sender.port);
1200
+ handle_incoming_data(data, from_addr);
1201
+ }
1202
+
1203
+ // Refresh TURN allocation if needed
1204
+ if (turn_client_) {
1205
+ turn_client_->refresh_allocation();
1206
+ }
1207
+
1208
+ // Use conditional variable for responsive shutdown
1209
+ {
1210
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
1211
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(50), [this] { return !running_.load(); })) {
1212
+ break;
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ LOG_ICE_DEBUG("ICE receive loop ended");
1218
+ }
1219
+
1220
+ void IceAgent::handle_incoming_data(const std::vector<uint8_t>& data, const std::string& from_addr) {
1221
+ if (is_stun_message(data)) {
1222
+ handle_stun_message(data, from_addr);
1223
+ } else if (data_callback_) {
1224
+ data_callback_(data, from_addr);
1225
+ }
1226
+ }
1227
+
1228
+ bool IceAgent::is_stun_message(const std::vector<uint8_t>& data) {
1229
+ if (data.size() < 20) {
1230
+ return false;
1231
+ }
1232
+
1233
+ // Check STUN magic cookie
1234
+ uint32_t magic_cookie = (static_cast<uint32_t>(data[4]) << 24) |
1235
+ (static_cast<uint32_t>(data[5]) << 16) |
1236
+ (static_cast<uint32_t>(data[6]) << 8) |
1237
+ static_cast<uint32_t>(data[7]);
1238
+
1239
+ return magic_cookie == 0x2112A442;
1240
+ }
1241
+
1242
+ void IceAgent::handle_stun_message(const std::vector<uint8_t>& data, const std::string& from_addr) {
1243
+ LOG_ICE_DEBUG("Received STUN message from " << from_addr);
1244
+
1245
+ // Parse and handle STUN message
1246
+ // This would include handling binding requests, responses, and ICE-specific attributes
1247
+
1248
+ // For connectivity checks, we need to handle binding requests and send responses
1249
+ // For now, this is a simplified implementation
1250
+ }
1251
+
1252
+ uint32_t IceAgent::calculate_candidate_priority(IceCandidateType type, uint16_t local_pref, uint16_t component_id) {
1253
+ uint8_t type_pref = 0;
1254
+ switch (type) {
1255
+ case IceCandidateType::HOST: type_pref = 126; break;
1256
+ case IceCandidateType::PEER_REFLEXIVE: type_pref = 110; break;
1257
+ case IceCandidateType::SERVER_REFLEXIVE: type_pref = 100; break;
1258
+ case IceCandidateType::RELAY: type_pref = 0; break;
1259
+ }
1260
+
1261
+ return (static_cast<uint32_t>(type_pref) << 24) |
1262
+ (static_cast<uint32_t>(local_pref) << 8) |
1263
+ static_cast<uint32_t>(256 - component_id);
1264
+ }
1265
+
1266
+ std::string IceAgent::generate_foundation(const IceCandidate& candidate) {
1267
+ // Simple foundation generation based on type and base address
1268
+ std::string base = ice_candidate_type_to_string(candidate.type) + "_" + candidate.ip;
1269
+ std::hash<std::string> hasher;
1270
+ return std::to_string(hasher(base) % 1000000);
1271
+ }
1272
+
1273
+ std::string IceAgent::generate_ufrag() {
1274
+ const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1275
+ std::random_device rd;
1276
+ std::mt19937 gen(rd());
1277
+ std::uniform_int_distribution<> dis(0, sizeof(charset) - 2);
1278
+
1279
+ std::string ufrag;
1280
+ for (int i = 0; i < 8; ++i) {
1281
+ ufrag += charset[dis(gen)];
1282
+ }
1283
+ return ufrag;
1284
+ }
1285
+
1286
+ std::string IceAgent::generate_password() {
1287
+ const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
1288
+ std::random_device rd;
1289
+ std::mt19937 gen(rd());
1290
+ std::uniform_int_distribution<> dis(0, sizeof(charset) - 2);
1291
+
1292
+ std::string password;
1293
+ for (int i = 0; i < 24; ++i) {
1294
+ password += charset[dis(gen)];
1295
+ }
1296
+ return password;
1297
+ }
1298
+
1299
+ bool IceAgent::udp_hole_punch(const std::string& peer_ip, uint16_t peer_port) {
1300
+ LOG_ICE_DEBUG("Performing UDP hole punch to " << peer_ip << ":" << peer_port);
1301
+
1302
+ // Send multiple packets to open the NAT hole
1303
+ std::vector<uint8_t> punch_data = {'P', 'U', 'N', 'C', 'H'};
1304
+
1305
+ for (int i = 0; i < 5; ++i) {
1306
+ send_udp_data_to(udp_socket_, punch_data, peer_ip, peer_port);
1307
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
1308
+ }
1309
+
1310
+ return true;
1311
+ }
1312
+
1313
+ bool IceAgent::tcp_hole_punch(const std::string& peer_ip, uint16_t peer_port) {
1314
+ LOG_ICE_DEBUG("Performing TCP hole punch to " << peer_ip << ":" << peer_port);
1315
+
1316
+ // TCP hole punching is more complex and requires coordination
1317
+ // This is a simplified implementation
1318
+
1319
+ return false;
1320
+ }
1321
+
1322
+ bool IceAgent::coordinate_hole_punch(const nlohmann::json& punch_data) {
1323
+ try {
1324
+ std::string peer_ip = punch_data.value("ip", "");
1325
+ uint16_t peer_port = punch_data.value("port", 0);
1326
+
1327
+ if (!peer_ip.empty() && peer_port > 0) {
1328
+ return perform_hole_punching(peer_ip, peer_port);
1329
+ }
1330
+ } catch (const std::exception& e) {
1331
+ LOG_ICE_ERROR("Failed to coordinate hole punch: " << e.what());
1332
+ }
1333
+
1334
+ return false;
1335
+ }
1336
+
1337
+ //=============================================================================
1338
+ // Utility Functions
1339
+ //=============================================================================
1340
+
1341
+ std::string ice_candidate_type_to_string(IceCandidateType type) {
1342
+ switch (type) {
1343
+ case IceCandidateType::HOST: return "host";
1344
+ case IceCandidateType::SERVER_REFLEXIVE: return "srflx";
1345
+ case IceCandidateType::PEER_REFLEXIVE: return "prflx";
1346
+ case IceCandidateType::RELAY: return "relay";
1347
+ default: return "unknown";
1348
+ }
1349
+ }
1350
+
1351
+ IceCandidateType string_to_ice_candidate_type(const std::string& type_str) {
1352
+ if (type_str == "host") return IceCandidateType::HOST;
1353
+ if (type_str == "srflx") return IceCandidateType::SERVER_REFLEXIVE;
1354
+ if (type_str == "prflx") return IceCandidateType::PEER_REFLEXIVE;
1355
+ if (type_str == "relay") return IceCandidateType::RELAY;
1356
+ return IceCandidateType::HOST;
1357
+ }
1358
+
1359
+ std::string ice_transport_to_string(IceTransport transport) {
1360
+ switch (transport) {
1361
+ case IceTransport::UDP: return "udp";
1362
+ case IceTransport::TCP: return "tcp";
1363
+ default: return "udp";
1364
+ }
1365
+ }
1366
+
1367
+ IceTransport string_to_ice_transport(const std::string& transport_str) {
1368
+ if (transport_str == "tcp") return IceTransport::TCP;
1369
+ return IceTransport::UDP;
1370
+ }
1371
+
1372
+ std::string ice_connection_state_to_string(IceConnectionState state) {
1373
+ switch (state) {
1374
+ case IceConnectionState::NEW: return "new";
1375
+ case IceConnectionState::GATHERING: return "gathering";
1376
+ case IceConnectionState::CHECKING: return "checking";
1377
+ case IceConnectionState::CONNECTED: return "connected";
1378
+ case IceConnectionState::COMPLETED: return "completed";
1379
+ case IceConnectionState::FAILED: return "failed";
1380
+ case IceConnectionState::DISCONNECTED: return "disconnected";
1381
+ case IceConnectionState::CLOSED: return "closed";
1382
+ default: return "unknown";
1383
+ }
1384
+ }
1385
+
1386
+ } // namespace librats