ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
nat.c
Go to the documentation of this file.
1
7#include "nat.h"
8
9#include <ascii-chat/buffer_pool.h>
10#include <ascii-chat/common.h>
11#include <ascii-chat/common/buffer_sizes.h>
12#include <ascii-chat/log/logging.h>
13#include <ascii-chat/network/nat/upnp.h>
14#include <ascii-chat/network/packet.h>
15#include <ascii-chat/network/webrtc/stun.h>
16#include <ascii-chat/platform/abstraction.h>
17#include <ascii-chat/platform/socket.h>
18#include <ascii-chat/util/ip.h>
19#include <ascii-chat/util/time.h>
20
21#include <string.h>
22#ifndef _WIN32
23#include <netdb.h>
24#endif
25#include <stdio.h>
26
27// Bandwidth override threshold: 10x difference can override NAT priority
28#define BANDWIDTH_OVERRIDE_RATIO 10
29
31 if (!quality)
32 return;
33 memset(quality, 0, sizeof(nat_quality_t));
34 quality->nat_type = ACIP_NAT_TYPE_SYMMETRIC; // Assume worst case
35}
36
37int nat_compute_tier(const nat_quality_t *quality) {
38 if (!quality)
39 return 4;
40
41 if (quality->lan_reachable)
42 return 0; // LAN - best
43 if (quality->has_public_ip)
44 return 1; // Public IP
45 if (quality->upnp_available)
46 return 2; // UPnP mapping
47 if (quality->nat_type <= ACIP_NAT_TYPE_RESTRICTED)
48 return 3; // STUN hole-punchable
49 return 4; // TURN relay required
50}
51
52int nat_compare_quality(const nat_quality_t *ours, const nat_quality_t *theirs, bool we_are_initiator) {
53 if (!ours || !theirs) {
54 return we_are_initiator ? -1 : 1;
55 }
56
57 int our_tier = nat_compute_tier(ours);
58 int their_tier = nat_compute_tier(theirs);
59
60 // Check for bandwidth override: massive bandwidth advantage can override NAT tier
61 if (ours->upload_kbps > 0 && theirs->upload_kbps > 0) {
62 if (ours->upload_kbps >= theirs->upload_kbps * BANDWIDTH_OVERRIDE_RATIO) {
63 log_debug("NAT compare: we win by bandwidth override (%u vs %u kbps)", ours->upload_kbps, theirs->upload_kbps);
64 return -1;
65 }
66 if (theirs->upload_kbps >= ours->upload_kbps * BANDWIDTH_OVERRIDE_RATIO) {
67 log_debug("NAT compare: they win by bandwidth override (%u vs %u kbps)", theirs->upload_kbps, ours->upload_kbps);
68 return 1;
69 }
70 }
71
72 // Compare NAT tiers (lower = better)
73 if (our_tier < their_tier) {
74 log_debug("NAT compare: we win by tier (%d vs %d)", our_tier, their_tier);
75 return -1;
76 }
77 if (our_tier > their_tier) {
78 log_debug("NAT compare: they win by tier (%d vs %d)", our_tier, their_tier);
79 return 1;
80 }
81
82 // Same tier - bandwidth is tiebreaker
83 if (ours->upload_kbps > theirs->upload_kbps) {
84 log_debug("NAT compare: we win by bandwidth (%u vs %u kbps)", ours->upload_kbps, theirs->upload_kbps);
85 return -1;
86 }
87 if (ours->upload_kbps < theirs->upload_kbps) {
88 log_debug("NAT compare: they win by bandwidth (%u vs %u kbps)", theirs->upload_kbps, ours->upload_kbps);
89 return 1;
90 }
91
92 // Same bandwidth - latency is tiebreaker
93 if (ours->rtt_to_acds_ns < theirs->rtt_to_acds_ns) {
94 log_debug("NAT compare: we win by latency (%lu ns vs %lu ns)", ours->rtt_to_acds_ns, theirs->rtt_to_acds_ns);
95 return -1;
96 }
97 if (ours->rtt_to_acds_ns > theirs->rtt_to_acds_ns) {
98 log_debug("NAT compare: they win by latency (%lu ns vs %lu ns)", theirs->rtt_to_acds_ns, ours->rtt_to_acds_ns);
99 return 1;
100 }
101
102 // Everything equal - initiator hosts
103 log_debug("NAT compare: equal quality, initiator wins (we_are_initiator=%d)", we_are_initiator);
104 return we_are_initiator ? -1 : 1;
105}
106
115static asciichat_error_t nat_parse_stun_response(const uint8_t *response, size_t response_len, char *reflexive_addr,
116 uint16_t *reflexive_port) {
117 if (!response || response_len < 20 || !reflexive_addr || !reflexive_port) {
118 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid STUN response parameters");
119 }
120
121 // STUN packets have a 20-byte header followed by attributes
122 // Attributes are TLV (Type-Length-Value) format
123 // Look for XOR_MAPPED_ADDRESS (0x0020) or MAPPED_ADDRESS (0x0001)
124
125 const uint8_t *current = response + 20;
126 const uint8_t *end = response + response_len;
127
128 while (current + 4 <= end) {
129 uint16_t attr_type = (current[0] << 8) | current[1];
130 uint16_t attr_len = (current[2] << 8) | current[3];
131
132 if (current + 4 + attr_len > end) {
133 break;
134 }
135
136 // Look for MAPPED_ADDRESS (0x0001)
137 if (attr_type == 0x0001 && attr_len >= 8) {
138 // Format: reserved byte, family (1=IPv4, 2=IPv6), port (2 bytes), address
139 uint8_t family = current[5];
140 if (family == 1) { // IPv4
141 uint16_t port = ((uint16_t)current[6] << 8) | current[7];
142 uint32_t addr =
143 ((uint32_t)current[8] << 24) | ((uint32_t)current[9] << 16) | ((uint32_t)current[10] << 8) | current[11];
144
145 // Convert to dotted quad notation
146 safe_snprintf(reflexive_addr, 64, "%u.%u.%u.%u", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF,
147 addr & 0xFF);
148 *reflexive_port = port;
149 return ASCIICHAT_OK;
150 }
151 }
152
153 // Move to next attribute (with 4-byte alignment padding)
154 uint16_t padded_len = (attr_len + 3) & ~3;
155 current += 4 + padded_len;
156 }
157
158 return SET_ERRNO(ERROR_FORMAT, "STUN response missing MAPPED_ADDRESS");
159}
160
168static asciichat_error_t nat_stun_probe(nat_quality_t *quality, const char *stun_server, uint16_t local_port) {
169 if (!quality || !stun_server || !stun_server[0]) {
170 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for STUN probe");
171 }
172
173 log_debug("Starting STUN probe to %s (local_port=%u)", stun_server, local_port);
174
175 // Parse stun_server for hostname and port
176 char host_buf[BUFFER_SIZE_SMALL] = {0};
177 uint16_t stun_port = STUN_DEFAULT_PORT;
178
179 SAFE_STRNCPY(host_buf, stun_server, sizeof(host_buf));
180
181 char *colon = strchr(host_buf, ':');
182 if (colon) {
183 *colon = '\0';
184 stun_port = (uint16_t)atoi(colon + 1);
185 }
186
187 // Detect address family from STUN server hostname (IPv4 vs IPv6)
188 int stun_family = is_valid_ipv6(host_buf) ? AF_INET6 : AF_INET;
189
190 // Create UDP socket for STUN probe
191 socket_t sock = socket_create(stun_family, SOCK_DGRAM, 0);
192 if (sock == INVALID_SOCKET_VALUE) {
193 log_warn("Failed to create UDP socket for STUN probe");
194 return SET_ERRNO(ERROR_NETWORK, "Cannot create UDP socket");
195 }
196
197 // Set timeout on socket
198 socket_set_timeout(sock, 5000 * NS_PER_MS_INT); // 5 second timeout
199
200 // Resolve STUN server hostname
201 struct addrinfo hints = {0};
202 hints.ai_family = stun_family;
203 hints.ai_socktype = SOCK_DGRAM;
204
205 struct addrinfo *result = NULL;
206 char port_str[16];
207 safe_snprintf(port_str, sizeof(port_str), "%u", stun_port);
208
209 int ret = getaddrinfo(host_buf, port_str, &hints, &result);
210 if (ret != 0) {
211 log_warn("Failed to resolve STUN server %s: %s", host_buf, gai_strerror(ret));
212 socket_close(sock);
213 return SET_ERRNO(ERROR_NETWORK_CONNECT, "Cannot resolve STUN server hostname");
214 }
215
216 // Build minimal STUN binding request (RFC 5389)
217 // Header: Magic (0x2112A442) + Transaction ID (96 bits)
218 uint8_t stun_request[20];
219 stun_request[0] = 0x00; // Message type: Binding Request (0x0001), bits split
220 stun_request[1] = 0x01;
221 stun_request[2] = 0x00; // Message length: 0 (no attributes for simple probe)
222 stun_request[3] = 0x00;
223
224 // Magic cookie (STUN magic number)
225 stun_request[4] = 0x21;
226 stun_request[5] = 0x12;
227 stun_request[6] = 0xa4;
228 stun_request[7] = 0x42;
229
230 // Transaction ID (96 bits) - use simple pattern for MVP
231 memset(stun_request + 8, 0xAA, 12);
232
233 // Send STUN binding request
234 uint64_t start_time = time_get_ns();
235 ssize_t sent = socket_sendto(sock, stun_request, sizeof(stun_request), 0, result->ai_addr, result->ai_addrlen);
236
237 if (sent < 0) {
238 log_warn("Failed to send STUN request");
239 freeaddrinfo(result);
240 socket_close(sock);
241 return SET_ERRNO(ERROR_NETWORK, "Cannot send STUN request");
242 }
243
244 // Receive STUN response
245 uint8_t stun_response[512];
246 struct sockaddr_in peer_addr;
247 socklen_t peer_addr_len = sizeof(peer_addr);
248
249 ssize_t recv_len =
250 socket_recvfrom(sock, stun_response, sizeof(stun_response), 0, (struct sockaddr *)&peer_addr, &peer_addr_len);
251
252 uint64_t end_time = time_get_ns();
253 uint32_t rtt_ms = (uint32_t)(time_ns_to_ms(end_time - start_time));
254
255 freeaddrinfo(result);
256 socket_close(sock);
257
258 if (recv_len < 20) {
259 log_warn("STUN response too short or timeout");
260 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "STUN server did not respond");
261 }
262
263 // Parse STUN response to extract reflexive address
264 char reflexive_addr[64] = {0};
265 uint16_t reflexive_port = 0;
266 asciichat_error_t parse_result =
267 nat_parse_stun_response(stun_response, (size_t)recv_len, reflexive_addr, &reflexive_port);
268
269 if (parse_result != ASCIICHAT_OK) {
270 log_warn("Failed to parse STUN response");
271 return parse_result;
272 }
273
274 SAFE_STRNCPY(quality->public_address, reflexive_addr, sizeof(quality->public_address));
275 quality->public_port = reflexive_port;
276 quality->stun_latency_ns = (uint64_t)rtt_ms * NS_PER_MS_INT;
277
278 log_info("STUN probe successful: reflexive_address=%s:%u, rtt=%u ms", reflexive_addr, reflexive_port, rtt_ms);
279
280 return ASCIICHAT_OK;
281}
282
283asciichat_error_t nat_detect_quality(nat_quality_t *quality, const char *stun_server, uint16_t local_port) {
284 if (!quality) {
285 return SET_ERRNO(ERROR_INVALID_PARAM, "quality is NULL");
286 }
287
288 nat_quality_init(quality);
289 log_info("Starting NAT quality detection (local_port=%u)", local_port);
290
291 // Try UPnP/NAT-PMP first
292 nat_upnp_context_t *upnp = NULL;
293 asciichat_error_t upnp_result = nat_upnp_open(local_port, "ascii-chat", &upnp);
294 if (upnp_result == ASCIICHAT_OK && upnp && nat_upnp_is_active(upnp)) {
295 quality->upnp_available = true;
296 quality->upnp_mapped_port = upnp->mapped_port;
297
298 char addr_buf[64];
299 if (nat_upnp_get_address(upnp, addr_buf, sizeof(addr_buf)) == ASCIICHAT_OK) {
300 // Parse IP:port
301 char *colon = strchr(addr_buf, ':');
302 if (colon) {
303 size_t ip_len = (size_t)(colon - addr_buf);
304 if (ip_len < sizeof(quality->public_address)) {
305 memcpy(quality->public_address, addr_buf, ip_len);
306 quality->public_address[ip_len] = '\0';
307 }
308 } else {
309 SAFE_STRNCPY(quality->public_address, addr_buf, sizeof(quality->public_address));
310 }
311 }
312
313 log_info("UPnP: mapped port %u, external IP %s", quality->upnp_mapped_port, quality->public_address);
314 quality->nat_type = ACIP_NAT_TYPE_FULL_CONE; // UPnP implies at least full-cone equivalent
315 } else {
316 log_debug("UPnP: not available or mapping failed");
317 }
318
319 // Try STUN probe if provided and UPnP didn't succeed
320 if (!quality->upnp_available && stun_server && stun_server[0]) {
321 asciichat_error_t stun_result = nat_stun_probe(quality, stun_server, local_port);
322 if (stun_result == ASCIICHAT_OK) {
323 // Successfully got reflexive address via STUN
324 quality->has_srflx_candidates = true;
325
326 // If reflexive address is not private range, we have public IP
327 // Private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
328 if (quality->public_address[0] && strncmp(quality->public_address, "10.", 3) != 0 &&
329 strncmp(quality->public_address, "172.16.", 7) != 0 && strncmp(quality->public_address, "192.168.", 8) != 0) {
330 quality->has_public_ip = true;
331 quality->nat_type = ACIP_NAT_TYPE_OPEN;
332 } else {
333 // Reflexive address is still private - behind symmetric NAT
334 quality->nat_type = ACIP_NAT_TYPE_SYMMETRIC;
335 }
336 } else {
337 log_debug("STUN probe failed, falling back to symmetric NAT assumption");
338 }
339 }
340
341 // Set ICE candidate flags based on what we found
342 quality->has_host_candidates = true; // We always have local IP
343 quality->has_srflx_candidates = quality->upnp_available || quality->has_public_ip || quality->has_srflx_candidates;
344
345 // Check if TURN relay servers are available (configured via options or ACDS)
346 const options_t *opts = options_get();
347 const char *turn_servers = opts ? opts->turn_servers : NULL;
348 quality->has_relay_candidates = (turn_servers && turn_servers[0] != '\0');
349 if (quality->has_relay_candidates) {
350 log_debug("TURN relay servers configured: %s", turn_servers);
351 }
352
353 quality->detection_complete = true;
354 log_info("NAT detection complete: tier=%d, upnp=%d, has_public_ip=%d, nat_type=%s", nat_compute_tier(quality),
355 quality->upnp_available, quality->has_public_ip, nat_type_to_string(quality->nat_type));
356
357 if (upnp) {
358 // Keep UPnP mapping active for the session
359 // Don't close it here - caller is responsible for cleanup
360 }
361
362 return ASCIICHAT_OK;
363}
364
365asciichat_error_t nat_measure_bandwidth(nat_quality_t *quality, socket_t acds_socket) {
366 if (!quality) {
367 return SET_ERRNO(ERROR_INVALID_PARAM, "quality is NULL");
368 }
369 if (acds_socket == INVALID_SOCKET_VALUE) {
370 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid socket");
371 }
372
373 // Prepare bandwidth test packet
374 acip_bandwidth_test_t test_msg = {0};
375 memset(test_msg.session_id, 0, sizeof(test_msg.session_id)); // Anonymous test
376 memset(test_msg.participant_id, 0, sizeof(test_msg.participant_id));
377 test_msg.test_size_bytes = 65536; // 64KB test payload
378 test_msg.client_send_time_ns = time_get_ns();
379
380 // Send BANDWIDTH_TEST packet
381 asciichat_error_t result = packet_send(acds_socket, PACKET_TYPE_ACIP_BANDWIDTH_TEST, &test_msg, sizeof(test_msg));
382 if (result != ASCIICHAT_OK) {
383 log_warn("Failed to send bandwidth test packet");
384 return result;
385 }
386
387 // Send test payload (64KB of data)
388 uint8_t *test_data = SAFE_MALLOC(test_msg.test_size_bytes, uint8_t *);
389 if (!test_data) {
390 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate bandwidth test payload");
391 }
392 memset(test_data, 0xAA, test_msg.test_size_bytes); // Fill with pattern
393
394 ssize_t sent = send(acds_socket, (const char *)test_data, test_msg.test_size_bytes, 0);
395 SAFE_FREE(test_data);
396
397 if (sent != (ssize_t)test_msg.test_size_bytes) {
398 log_warn("Failed to send bandwidth test payload: sent=%zd, expected=%u", sent, test_msg.test_size_bytes);
399 return SET_ERRNO(ERROR_NETWORK, "Bandwidth test payload send failed");
400 }
401
402 log_debug("Sent bandwidth test with %u bytes", test_msg.test_size_bytes);
403
404 // Set socket timeout for response (5 seconds)
405 if (socket_set_timeout(acds_socket, 5 * NS_PER_SEC_INT) != 0) {
406 log_warn("Failed to set socket timeout for bandwidth test");
407 return SET_ERRNO(ERROR_NETWORK, "Failed to set socket timeout");
408 }
409
410 // Wait for BANDWIDTH_RESULT response
411 packet_type_t response_type;
412 void *response_data = NULL;
413 size_t response_len = 0;
414
415 result = packet_receive(acds_socket, &response_type, &response_data, &response_len);
416
417 if (result != ASCIICHAT_OK) {
418 log_warn("Bandwidth test timeout or receive failed");
419 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "No bandwidth test response from ACDS");
420 }
421
422 if (response_type != PACKET_TYPE_ACIP_BANDWIDTH_RESULT) {
423 POOL_FREE(response_data, response_len);
424 log_warn("Unexpected packet type %d (expected BANDWIDTH_RESULT)", response_type);
425 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Wrong packet type in bandwidth test response");
426 }
427
428 if (response_len != sizeof(acip_bandwidth_result_t)) {
429 POOL_FREE(response_data, response_len);
430 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid bandwidth result size");
431 }
432
433 // Parse results
434 acip_bandwidth_result_t *results = (acip_bandwidth_result_t *)response_data;
435 quality->upload_kbps = results->measured_upload_kbps;
436 quality->download_kbps = results->measured_download_kbps;
437 quality->rtt_to_acds_ns = results->rtt_ns;
438 quality->jitter_ns = results->jitter_ns;
439 quality->packet_loss_pct = results->packet_loss_pct;
440
441 POOL_FREE(response_data, response_len);
442
443 log_debug("Bandwidth measurement: upload=%u kbps, download=%u kbps, rtt=%lu ns", quality->upload_kbps,
444 quality->download_kbps, quality->rtt_to_acds_ns);
445
446 return ASCIICHAT_OK;
447}
448
449void nat_quality_to_acip(const nat_quality_t *quality, const uint8_t session_id[16], const uint8_t participant_id[16],
450 acip_nat_quality_t *out) {
451 if (!quality || !out)
452 return;
453
454 memset(out, 0, sizeof(acip_nat_quality_t));
455
456 if (session_id)
457 memcpy(out->session_id, session_id, 16);
458 if (participant_id)
459 memcpy(out->participant_id, participant_id, 16);
460
461 out->has_public_ip = quality->has_public_ip ? 1 : 0;
462 out->upnp_available = quality->upnp_available ? 1 : 0;
463 out->upnp_mapped_port[0] = (uint8_t)(quality->upnp_mapped_port >> 8);
464 out->upnp_mapped_port[1] = (uint8_t)(quality->upnp_mapped_port & 0xFF);
465 out->stun_nat_type = (uint8_t)quality->nat_type;
466 out->lan_reachable = quality->lan_reachable ? 1 : 0;
467 out->stun_latency_ns = quality->stun_latency_ns;
468
469 out->upload_kbps = quality->upload_kbps;
470 out->download_kbps = quality->download_kbps;
471 out->rtt_to_acds_ns = quality->rtt_to_acds_ns;
472 out->jitter_ns = quality->jitter_ns;
473 out->packet_loss_pct = quality->packet_loss_pct;
474
475 SAFE_STRNCPY(out->public_address, quality->public_address, sizeof(out->public_address));
476 out->public_port = quality->public_port;
477
478 out->ice_candidate_types = 0;
479 if (quality->has_host_candidates)
480 out->ice_candidate_types |= 1;
481 if (quality->has_srflx_candidates)
482 out->ice_candidate_types |= 2;
483 if (quality->has_relay_candidates)
484 out->ice_candidate_types |= 4;
485}
486
487void nat_quality_from_acip(const acip_nat_quality_t *acip, nat_quality_t *out) {
488 if (!acip || !out)
489 return;
490
491 nat_quality_init(out);
492
493 out->has_public_ip = acip->has_public_ip != 0;
494 out->upnp_available = acip->upnp_available != 0;
495 out->upnp_mapped_port = ((uint16_t)acip->upnp_mapped_port[0] << 8) | acip->upnp_mapped_port[1];
496 out->nat_type = (acip_nat_type_t)acip->stun_nat_type;
497 out->lan_reachable = acip->lan_reachable != 0;
498 out->stun_latency_ns = acip->stun_latency_ns;
499
500 out->upload_kbps = acip->upload_kbps;
501 out->download_kbps = acip->download_kbps;
502 out->rtt_to_acds_ns = acip->rtt_to_acds_ns;
503 out->jitter_ns = acip->jitter_ns;
504 out->packet_loss_pct = acip->packet_loss_pct;
505
506 SAFE_STRNCPY(out->public_address, acip->public_address, sizeof(out->public_address));
507 out->public_port = acip->public_port;
508
509 out->has_host_candidates = (acip->ice_candidate_types & 1) != 0;
510 out->has_srflx_candidates = (acip->ice_candidate_types & 2) != 0;
511 out->has_relay_candidates = (acip->ice_candidate_types & 4) != 0;
512
513 out->detection_complete = true;
514}
515
516const char *nat_type_to_string(acip_nat_type_t type) {
517 switch (type) {
518 case ACIP_NAT_TYPE_OPEN:
519 return "Open (Public IP)";
520 case ACIP_NAT_TYPE_FULL_CONE:
521 return "Full Cone";
522 case ACIP_NAT_TYPE_RESTRICTED:
523 return "Restricted Cone";
524 case ACIP_NAT_TYPE_PORT_RESTRICTED:
525 return "Port Restricted";
526 case ACIP_NAT_TYPE_SYMMETRIC:
527 return "Symmetric";
528 default:
529 return "Unknown";
530 }
531}
int socket_t
int is_valid_ipv6(const char *ip)
Definition ip.c:105
void nat_quality_to_acip(const nat_quality_t *quality, const uint8_t session_id[16], const uint8_t participant_id[16], acip_nat_quality_t *out)
Convert nat_quality_t to acip_nat_quality_t for network transmission.
Definition nat.c:449
asciichat_error_t nat_measure_bandwidth(nat_quality_t *quality, socket_t acds_socket)
Measure upload bandwidth to ACDS server.
Definition nat.c:365
int nat_compare_quality(const nat_quality_t *ours, const nat_quality_t *theirs, bool we_are_initiator)
Compare two NAT qualities and determine who should host.
Definition nat.c:52
void nat_quality_init(nat_quality_t *quality)
Initialize NAT quality structure with defaults.
Definition nat.c:30
asciichat_error_t nat_detect_quality(nat_quality_t *quality, const char *stun_server, uint16_t local_port)
Detect NAT quality using all available methods.
Definition nat.c:283
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
Definition nat.c:37
void nat_quality_from_acip(const acip_nat_quality_t *acip, nat_quality_t *out)
Convert acip_nat_quality_t to nat_quality_t.
Definition nat.c:487
const char * nat_type_to_string(acip_nat_type_t type)
Get human-readable description of NAT type.
Definition nat.c:516
#define BANDWIDTH_OVERRIDE_RATIO
Definition nat.c:28
NAT quality detection for discovery mode host selection.
asciichat_error_t packet_receive(socket_t sockfd, packet_type_t *type, void **data, size_t *len)
Receive a packet with proper header validation and CRC32 checking.
Definition packet.c:348
asciichat_error_t packet_send(socket_t sockfd, packet_type_t type, const void *data, size_t len)
Send a packet with proper header and CRC32.
Definition packet.c:288
const options_t * options_get(void)
Definition rcu.c:347
int socket_set_timeout(socket_t sock, uint64_t timeout_ns)
Set socket receive and send timeouts.
Definition socket.c:82
uint8_t session_id[16]
uint8_t participant_id[16]
NAT quality assessment result.
Definition nat.h:27
bool detection_complete
All probes finished.
Definition nat.h:51
bool has_host_candidates
Local IP reachable.
Definition nat.h:46
uint32_t upload_kbps
Upload bandwidth in Kbps.
Definition nat.h:39
uint32_t download_kbps
Download bandwidth in Kbps.
Definition nat.h:40
bool lan_reachable
Same subnet as peer.
Definition nat.h:33
uint64_t stun_latency_ns
RTT to STUN server in nanoseconds.
Definition nat.h:34
bool has_srflx_candidates
STUN worked.
Definition nat.h:47
char public_address[64]
Public IP address.
Definition nat.h:35
bool has_relay_candidates
TURN available.
Definition nat.h:48
uint8_t packet_loss_pct
Packet loss percentage.
Definition nat.h:43
acip_nat_type_t nat_type
NAT classification.
Definition nat.h:32
bool has_public_ip
STUN reflexive == local IP.
Definition nat.h:29
bool upnp_available
UPnP/NAT-PMP mapping succeeded.
Definition nat.h:30
uint16_t upnp_mapped_port
Mapped external port (if upnp_available)
Definition nat.h:31
uint64_t rtt_to_acds_ns
Latency to ACDS in nanoseconds.
Definition nat.h:41
uint16_t public_port
Public port.
Definition nat.h:36
uint64_t jitter_ns
Packet timing variance in nanoseconds.
Definition nat.h:42
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
bool nat_upnp_is_active(const nat_upnp_context_t *ctx)
Definition upnp.c:291
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
Definition upnp.c:236
asciichat_error_t nat_upnp_get_address(const nat_upnp_context_t *ctx, char *addr, size_t addr_len)
Definition upnp.c:310
uint64_t time_get_ns(void)
Definition util/time.c:48