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.
- package/binding.gyp +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2342 -0
- package/native-src/src/dht.h +501 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2715 -0
- package/native-src/src/librats.h +1729 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1317 -0
- package/native-src/src/librats_c.h +237 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +2 -8
- package/scripts/build-librats.js +59 -12
- package/scripts/prepare-package.js +133 -37
|
@@ -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
|