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