ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
nat.h File Reference

NAT quality detection for discovery mode host selection. More...

Go to the source code of this file.

Data Structures

struct  nat_quality_t
 NAT quality assessment result. More...
 

Functions

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.
 
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_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.
 
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.
 
int nat_compute_tier (const nat_quality_t *quality)
 Compute NAT tier for host selection (0=best, 4=worst)
 

Detailed Description

NAT quality detection for discovery mode host selection.

Detects NAT characteristics to determine the best host candidate. Uses STUN, UPnP/NAT-PMP, and bandwidth measurements.

Definition in file nat.h.

Function Documentation

◆ nat_compare_quality()

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.

Algorithm is deterministic - both sides get the same result. Uses NAT tier priority with bandwidth as tiebreaker.

Parameters
oursOur NAT quality
theirsPeer's NAT quality
we_are_initiatorTrue if we started the session
Returns
-1 = we host, 0 = equal (initiator wins), 1 = they host

Definition at line 52 of file nat.c.

52 {
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}
int nat_compute_tier(const nat_quality_t *quality)
Compute NAT tier for host selection (0=best, 4=worst)
Definition nat.c:37
#define BANDWIDTH_OVERRIDE_RATIO
Definition nat.c:28
uint32_t upload_kbps
Upload bandwidth in Kbps.
Definition nat.h:39
uint64_t rtt_to_acds_ns
Latency to ACDS in nanoseconds.
Definition nat.h:41

References BANDWIDTH_OVERRIDE_RATIO, nat_compute_tier(), nat_quality_t::rtt_to_acds_ns, and nat_quality_t::upload_kbps.

Referenced by negotiate_determine_result(), and negotiate_elect_future_host().

◆ nat_compute_tier()

int nat_compute_tier ( const nat_quality_t quality)

Compute NAT tier for host selection (0=best, 4=worst)

Parameters
qualityNAT quality
Returns
Tier value

Definition at line 37 of file nat.c.

37 {
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}
bool lan_reachable
Same subnet as peer.
Definition nat.h:33
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

References nat_quality_t::has_public_ip, nat_quality_t::lan_reachable, nat_quality_t::nat_type, and nat_quality_t::upnp_available.

Referenced by discovery_session_process(), nat_compare_quality(), nat_detect_quality(), and negotiate_receive_peer_quality().

◆ nat_detect_quality()

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.

Runs STUN probe, UPnP check, and gathers ICE candidates in parallel. Results are stored in the provided nat_quality_t structure.

Parameters
qualityOutput structure for results
stun_serverSTUN server URL (e.g., "stun:stun.l.google.com:19302")
local_portLocal port to use for detection
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 283 of file nat.c.

283 {
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}
void nat_quality_init(nat_quality_t *quality)
Initialize NAT quality structure with defaults.
Definition nat.c:30
const char * nat_type_to_string(acip_nat_type_t type)
Get human-readable description of NAT type.
Definition nat.c:516
const options_t * options_get(void)
Definition rcu.c:347
bool detection_complete
All probes finished.
Definition nat.h:51
bool has_host_candidates
Local IP reachable.
Definition nat.h:46
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
uint16_t upnp_mapped_port
Mapped external port (if upnp_available)
Definition nat.h:31
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

References nat_quality_t::detection_complete, nat_quality_t::has_host_candidates, nat_quality_t::has_public_ip, nat_quality_t::has_relay_candidates, nat_quality_t::has_srflx_candidates, nat_compute_tier(), nat_quality_init(), nat_quality_t::nat_type, nat_type_to_string(), nat_upnp_get_address(), nat_upnp_is_active(), nat_upnp_open(), options_get(), nat_quality_t::public_address, nat_quality_t::upnp_available, and nat_quality_t::upnp_mapped_port.

Referenced by negotiate_start_detection().

◆ nat_measure_bandwidth()

asciichat_error_t nat_measure_bandwidth ( nat_quality_t quality,
socket_t  acds_socket 
)

Measure upload bandwidth to ACDS server.

Uploads a test payload and measures throughput.

Parameters
qualityStructure to update with bandwidth results
acds_socketConnected socket to ACDS
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 365 of file nat.c.

365 {
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}
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
int socket_set_timeout(socket_t sock, uint64_t timeout_ns)
Set socket receive and send timeouts.
Definition socket.c:82
uint32_t download_kbps
Download bandwidth in Kbps.
Definition nat.h:40
uint8_t packet_loss_pct
Packet loss percentage.
Definition nat.h:43
uint64_t jitter_ns
Packet timing variance in nanoseconds.
Definition nat.h:42
uint64_t time_get_ns(void)
Definition util/time.c:48

References nat_quality_t::download_kbps, nat_quality_t::jitter_ns, nat_quality_t::packet_loss_pct, packet_receive(), packet_send(), nat_quality_t::rtt_to_acds_ns, socket_set_timeout(), time_get_ns(), and nat_quality_t::upload_kbps.

◆ nat_quality_from_acip()

void nat_quality_from_acip ( const acip_nat_quality_t *  acip,
nat_quality_t out 
)

Convert acip_nat_quality_t to nat_quality_t.

Parameters
acipACIP message structure
outOutput local NAT quality structure

Definition at line 487 of file nat.c.

487 {
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}
uint64_t stun_latency_ns
RTT to STUN server in nanoseconds.
Definition nat.h:34
uint16_t public_port
Public port.
Definition nat.h:36

References nat_quality_t::detection_complete, nat_quality_t::download_kbps, nat_quality_t::has_host_candidates, nat_quality_t::has_public_ip, nat_quality_t::has_relay_candidates, nat_quality_t::has_srflx_candidates, nat_quality_t::jitter_ns, nat_quality_t::lan_reachable, nat_quality_init(), nat_quality_t::nat_type, nat_quality_t::packet_loss_pct, nat_quality_t::public_address, nat_quality_t::public_port, nat_quality_t::rtt_to_acds_ns, nat_quality_t::stun_latency_ns, nat_quality_t::upload_kbps, nat_quality_t::upnp_available, and nat_quality_t::upnp_mapped_port.

Referenced by negotiate_elect_future_host(), and negotiate_receive_peer_quality().

◆ nat_quality_init()

void nat_quality_init ( nat_quality_t quality)

Initialize NAT quality structure with defaults.

Parameters
qualityStructure to initialize

Definition at line 30 of file nat.c.

30 {
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}
NAT quality assessment result.
Definition nat.h:27

References nat_quality_t::nat_type.

Referenced by nat_detect_quality(), nat_quality_from_acip(), and negotiate_init().

◆ nat_quality_to_acip()

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.

Parameters
qualityLocal NAT quality
session_idSession UUID
participant_idParticipant UUID
outOutput ACIP message structure

Definition at line 449 of file nat.c.

450 {
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}
uint8_t session_id[16]
uint8_t participant_id[16]

References nat_quality_t::download_kbps, nat_quality_t::has_host_candidates, nat_quality_t::has_public_ip, nat_quality_t::has_relay_candidates, nat_quality_t::has_srflx_candidates, nat_quality_t::jitter_ns, nat_quality_t::lan_reachable, nat_quality_t::nat_type, nat_quality_t::packet_loss_pct, participant_id, nat_quality_t::public_address, nat_quality_t::public_port, nat_quality_t::rtt_to_acds_ns, session_id, nat_quality_t::stun_latency_ns, nat_quality_t::upload_kbps, nat_quality_t::upnp_available, and nat_quality_t::upnp_mapped_port.

◆ nat_type_to_string()

const char * nat_type_to_string ( acip_nat_type_t  type)

Get human-readable description of NAT type.

Parameters
typeNAT type
Returns
String description

Definition at line 516 of file nat.c.

516 {
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}

Referenced by nat_detect_quality().