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>
28#define BANDWIDTH_OVERRIDE_RATIO 10
34 quality->
nat_type = ACIP_NAT_TYPE_SYMMETRIC;
47 if (quality->
nat_type <= ACIP_NAT_TYPE_RESTRICTED)
53 if (!ours || !theirs) {
54 return we_are_initiator ? -1 : 1;
63 log_debug(
"NAT compare: we win by bandwidth override (%u vs %u kbps)", ours->
upload_kbps, theirs->
upload_kbps);
67 log_debug(
"NAT compare: they win by bandwidth override (%u vs %u kbps)", theirs->
upload_kbps, ours->
upload_kbps);
73 if (our_tier < their_tier) {
74 log_debug(
"NAT compare: we win by tier (%d vs %d)", our_tier, their_tier);
77 if (our_tier > their_tier) {
78 log_debug(
"NAT compare: they win by tier (%d vs %d)", our_tier, their_tier);
103 log_debug(
"NAT compare: equal quality, initiator wins (we_are_initiator=%d)", we_are_initiator);
104 return we_are_initiator ? -1 : 1;
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");
125 const uint8_t *current = response + 20;
126 const uint8_t *end = response + response_len;
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];
132 if (current + 4 + attr_len > end) {
137 if (attr_type == 0x0001 && attr_len >= 8) {
139 uint8_t family = current[5];
141 uint16_t port = ((uint16_t)current[6] << 8) | current[7];
143 ((uint32_t)current[8] << 24) | ((uint32_t)current[9] << 16) | ((uint32_t)current[10] << 8) | current[11];
146 safe_snprintf(reflexive_addr, 64,
"%u.%u.%u.%u", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF,
148 *reflexive_port = port;
154 uint16_t padded_len = (attr_len + 3) & ~3;
155 current += 4 + padded_len;
158 return SET_ERRNO(ERROR_FORMAT,
"STUN response missing MAPPED_ADDRESS");
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");
173 log_debug(
"Starting STUN probe to %s (local_port=%u)", stun_server, local_port);
176 char host_buf[BUFFER_SIZE_SMALL] = {0};
177 uint16_t stun_port = STUN_DEFAULT_PORT;
179 SAFE_STRNCPY(host_buf, stun_server,
sizeof(host_buf));
181 char *colon = strchr(host_buf,
':');
184 stun_port = (uint16_t)atoi(colon + 1);
188 int stun_family =
is_valid_ipv6(host_buf) ? AF_INET6 : AF_INET;
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");
201 struct addrinfo hints = {0};
202 hints.ai_family = stun_family;
203 hints.ai_socktype = SOCK_DGRAM;
205 struct addrinfo *result = NULL;
209 int ret = getaddrinfo(host_buf, port_str, &hints, &result);
211 log_warn(
"Failed to resolve STUN server %s: %s", host_buf, gai_strerror(ret));
213 return SET_ERRNO(ERROR_NETWORK_CONNECT,
"Cannot resolve STUN server hostname");
218 uint8_t stun_request[20];
219 stun_request[0] = 0x00;
220 stun_request[1] = 0x01;
221 stun_request[2] = 0x00;
222 stun_request[3] = 0x00;
225 stun_request[4] = 0x21;
226 stun_request[5] = 0x12;
227 stun_request[6] = 0xa4;
228 stun_request[7] = 0x42;
231 memset(stun_request + 8, 0xAA, 12);
235 ssize_t sent = socket_sendto(sock, stun_request,
sizeof(stun_request), 0, result->ai_addr, result->ai_addrlen);
238 log_warn(
"Failed to send STUN request");
239 freeaddrinfo(result);
241 return SET_ERRNO(ERROR_NETWORK,
"Cannot send STUN request");
245 uint8_t stun_response[512];
246 struct sockaddr_in peer_addr;
247 socklen_t peer_addr_len =
sizeof(peer_addr);
250 socket_recvfrom(sock, stun_response,
sizeof(stun_response), 0, (
struct sockaddr *)&peer_addr, &peer_addr_len);
253 uint32_t rtt_ms = (uint32_t)(time_ns_to_ms(end_time - start_time));
255 freeaddrinfo(result);
259 log_warn(
"STUN response too short or timeout");
260 return SET_ERRNO(ERROR_NETWORK_TIMEOUT,
"STUN server did not respond");
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);
269 if (parse_result != ASCIICHAT_OK) {
270 log_warn(
"Failed to parse STUN response");
278 log_info(
"STUN probe successful: reflexive_address=%s:%u, rtt=%u ms", reflexive_addr, reflexive_port, rtt_ms);
285 return SET_ERRNO(ERROR_INVALID_PARAM,
"quality is NULL");
289 log_info(
"Starting NAT quality detection (local_port=%u)", local_port);
292 nat_upnp_context_t *upnp = NULL;
293 asciichat_error_t upnp_result =
nat_upnp_open(local_port,
"ascii-chat", &upnp);
301 char *colon = strchr(addr_buf,
':');
303 size_t ip_len = (size_t)(colon - addr_buf);
314 quality->
nat_type = ACIP_NAT_TYPE_FULL_CONE;
316 log_debug(
"UPnP: not available or mapping failed");
321 asciichat_error_t stun_result = nat_stun_probe(quality, stun_server, local_port);
322 if (stun_result == ASCIICHAT_OK) {
331 quality->
nat_type = ACIP_NAT_TYPE_OPEN;
334 quality->
nat_type = ACIP_NAT_TYPE_SYMMETRIC;
337 log_debug(
"STUN probe failed, falling back to symmetric NAT assumption");
347 const char *turn_servers = opts ? opts->turn_servers : NULL;
350 log_debug(
"TURN relay servers configured: %s", turn_servers);
354 log_info(
"NAT detection complete: tier=%d, upnp=%d, has_public_ip=%d, nat_type=%s",
nat_compute_tier(quality),
367 return SET_ERRNO(ERROR_INVALID_PARAM,
"quality is NULL");
369 if (acds_socket == INVALID_SOCKET_VALUE) {
370 return SET_ERRNO(ERROR_INVALID_PARAM,
"invalid socket");
374 acip_bandwidth_test_t test_msg = {0};
375 memset(test_msg.session_id, 0,
sizeof(test_msg.session_id));
376 memset(test_msg.participant_id, 0,
sizeof(test_msg.participant_id));
377 test_msg.test_size_bytes = 65536;
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");
388 uint8_t *test_data = SAFE_MALLOC(test_msg.test_size_bytes, uint8_t *);
390 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate bandwidth test payload");
392 memset(test_data, 0xAA, test_msg.test_size_bytes);
394 ssize_t sent = send(acds_socket, (
const char *)test_data, test_msg.test_size_bytes, 0);
395 SAFE_FREE(test_data);
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");
402 log_debug(
"Sent bandwidth test with %u bytes", test_msg.test_size_bytes);
406 log_warn(
"Failed to set socket timeout for bandwidth test");
407 return SET_ERRNO(ERROR_NETWORK,
"Failed to set socket timeout");
411 packet_type_t response_type;
412 void *response_data = NULL;
413 size_t response_len = 0;
415 result =
packet_receive(acds_socket, &response_type, &response_data, &response_len);
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");
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");
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");
434 acip_bandwidth_result_t *results = (acip_bandwidth_result_t *)response_data;
435 quality->
upload_kbps = results->measured_upload_kbps;
441 POOL_FREE(response_data, response_len);
443 log_debug(
"Bandwidth measurement: upload=%u kbps, download=%u kbps, rtt=%lu ns", quality->
upload_kbps,
450 acip_nat_quality_t *out) {
451 if (!quality || !out)
454 memset(out, 0,
sizeof(acip_nat_quality_t));
465 out->stun_nat_type = (uint8_t)quality->
nat_type;
475 SAFE_STRNCPY(out->public_address, quality->
public_address,
sizeof(out->public_address));
478 out->ice_candidate_types = 0;
480 out->ice_candidate_types |= 1;
482 out->ice_candidate_types |= 2;
484 out->ice_candidate_types |= 4;
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;
518 case ACIP_NAT_TYPE_OPEN:
519 return "Open (Public IP)";
520 case ACIP_NAT_TYPE_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:
int is_valid_ipv6(const char *ip)
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.
asciichat_error_t nat_measure_bandwidth(nat_quality_t *quality, socket_t acds_socket)
Measure upload bandwidth to ACDS server.
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.
void nat_quality_init(nat_quality_t *quality)
Initialize NAT quality structure with defaults.
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.
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
void nat_quality_from_acip(const acip_nat_quality_t *acip, nat_quality_t *out)
Convert acip_nat_quality_t to nat_quality_t.
const char * nat_type_to_string(acip_nat_type_t type)
Get human-readable description of NAT type.
#define BANDWIDTH_OVERRIDE_RATIO
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.
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.
const options_t * options_get(void)
int socket_set_timeout(socket_t sock, uint64_t timeout_ns)
Set socket receive and send timeouts.
uint8_t participant_id[16]
NAT quality assessment result.
bool detection_complete
All probes finished.
bool has_host_candidates
Local IP reachable.
uint32_t upload_kbps
Upload bandwidth in Kbps.
uint32_t download_kbps
Download bandwidth in Kbps.
bool lan_reachable
Same subnet as peer.
uint64_t stun_latency_ns
RTT to STUN server in nanoseconds.
bool has_srflx_candidates
STUN worked.
char public_address[64]
Public IP address.
bool has_relay_candidates
TURN available.
uint8_t packet_loss_pct
Packet loss percentage.
acip_nat_type_t nat_type
NAT classification.
bool has_public_ip
STUN reflexive == local IP.
bool upnp_available
UPnP/NAT-PMP mapping succeeded.
uint16_t upnp_mapped_port
Mapped external port (if upnp_available)
uint64_t rtt_to_acds_ns
Latency to ACDS in nanoseconds.
uint16_t public_port
Public port.
uint64_t jitter_ns
Packet timing variance in nanoseconds.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
bool nat_upnp_is_active(const nat_upnp_context_t *ctx)
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
asciichat_error_t nat_upnp_get_address(const nat_upnp_context_t *ctx, char *addr, size_t addr_len)
uint64_t time_get_ns(void)