ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
upnp.c File Reference

UPnP/NAT-PMP port mapping implementation. More...

Go to the source code of this file.

Functions

asciichat_error_t nat_upnp_open (uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
 Discover and open port via UPnP.
 
void nat_upnp_close (nat_upnp_context_t **ctx)
 Close port mapping and clean up.
 
bool nat_upnp_is_active (const nat_upnp_context_t *ctx)
 Check if port mapping is still active.
 
asciichat_error_t nat_upnp_refresh (nat_upnp_context_t *ctx)
 Refresh port mapping (e.g., for long-running servers)
 
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.
 

Detailed Description

UPnP/NAT-PMP port mapping implementation.

Strategy for enabling direct TCP without WebRTC:

  1. Try UPnP discovery (works on ~90% of consumer routers)
  2. Fall back to NAT-PMP if UPnP fails (Apple/Time Capsule)
  3. If both fail, client falls back to ACDS + WebRTC

This pragmatic approach provides direct connectivity for most home users while maintaining compatibility with stricter NATs via WebRTC fallback.

Definition in file upnp.c.

Function Documentation

◆ nat_upnp_close()

void nat_upnp_close ( nat_upnp_context_t **  ctx)

Close port mapping and clean up.

Removes the port mapping from the gateway and frees resources. Safe to call with NULL.

Parameters
ctxContext handle (will be set to NULL on return)

Definition at line 289 of file upnp.c.

289 {
290 if (!ctx || !(*ctx)) {
291 return;
292 }
293
294 if ((*ctx)->is_mapped) {
295 // Note: In a real implementation, we'd remove the port mapping from the gateway.
296 // For MVP, we just log and let the lease expire naturally (typically 1 hour).
297 log_debug("NAT: Port mapping will expire in ~1 hour (cleanup handled by router)");
298 }
299
300 SAFE_FREE(*ctx);
301 *ctx = NULL;
302}
#define SAFE_FREE(ptr)
Definition common.h:320
#define log_debug(...)
Log a DEBUG message.

References log_debug, and SAFE_FREE.

Referenced by main().

◆ nat_upnp_get_address()

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.

Useful for ACDS registration where we need to advertise the public endpoint for P2P connections.

Parameters
ctxContext handle
[out]addrBuffer to write "IP:port" format (must be at least 22 bytes)
addr_lenSize of addr buffer
Returns
ASCIICHAT_OK if successfully written
ERROR_INVALID_PARAM if addr is too small

Definition at line 323 of file upnp.c.

323 {
324 if (!ctx || !addr || addr_len < 22) {
325 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Invalid arguments for get_address");
326 }
327
328 if (!ctx->is_mapped || ctx->external_ip[0] == '\0') {
329 return SET_ERRNO(ERROR_NETWORK, "NAT: No active mapping to advertise");
330 }
331
332 // Format as "IP:port" (e.g., "203.0.113.42:27224")
333 int written = snprintf(addr, addr_len, "%s:%u", ctx->external_ip, ctx->mapped_port);
334
335 if (written < 0 || (size_t)written >= addr_len) {
336 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Address buffer too small");
337 }
338
339 return ASCIICHAT_OK;
340}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_NETWORK
Definition error_codes.h:69
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
uint16_t mapped_port
External port that was mapped (may differ from internal)
Definition upnp.h:27
bool is_mapped
true if port mapping is currently active
Definition upnp.h:31
char external_ip[16]
Detected external/public IP (e.g., "203.0.113.42")
Definition upnp.h:26

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_NETWORK, nat_upnp_context::external_ip, nat_upnp_context::is_mapped, nat_upnp_context::mapped_port, and SET_ERRNO.

Referenced by main(), and server_main().

◆ nat_upnp_is_active()

bool nat_upnp_is_active ( const nat_upnp_context_t ctx)

Check if port mapping is still active.

Useful for long-running servers to verify the mapping hasn't expired.

Parameters
ctxContext handle
Returns
true if port is still mapped on the gateway

Definition at line 304 of file upnp.c.

304 {
305 if (!ctx) {
306 return false;
307 }
308 return ctx->is_mapped && ctx->external_ip[0] != '\0';
309}

References nat_upnp_context::external_ip, and nat_upnp_context::is_mapped.

◆ nat_upnp_open()

asciichat_error_t nat_upnp_open ( uint16_t  internal_port,
const char *  description,
nat_upnp_context_t **  ctx 
)

Discover and open port via UPnP.

Attempts to find an UPnP-enabled gateway and request port mapping. On success, fills in external_ip and mapped_port.

Parameters
internal_portLocal TCP port to map (e.g., 27224 for ACDS)
descriptionDescription for port mapping (e.g., "ASCII-Chat Server")
[out]ctxContext handle (must be freed with nat_upnp_close())
Returns
ASCIICHAT_OK if port was successfully mapped
ERROR_NETWORK_* if discovery or mapping failed (not fatal, fallback to WebRTC)

Example:

nat_upnp_context_t *ctx = NULL;
asciichat_error_t result = nat_upnp_open(27224, "ASCII-Chat Server", &ctx);
if (result == ASCIICHAT_OK && ctx) {
printf("Public IP: %s\n", ctx->external_ip);
// Advertise ctx->external_ip:ctx->mapped_port to ACDS
}
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
Handle to UPnP context.
Definition upnp.h:25
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:249

Definition at line 249 of file upnp.c.

249 {
250 if (!ctx || !description) {
251 return SET_ERRNO(ERROR_INVALID_PARAM, "nat_upnp_open: Invalid arguments");
252 }
253
254 // Allocate context
256 if (!(*ctx)) {
257 return ERROR_MEMORY;
258 }
259
260 memset(*ctx, 0, sizeof(nat_upnp_context_t));
261
262 // Try UPnP first (works on ~90% of home routers)
263 log_info("NAT: Attempting UPnP port mapping for port %u...", internal_port);
264 asciichat_error_t result = upnp_try_map_port(internal_port, description, *ctx);
265
266 if (result == ASCIICHAT_OK) {
267 log_info("NAT: ✓ UPnP port mapping successful!");
268 return ASCIICHAT_OK;
269 }
270
271 log_info("NAT: UPnP failed, trying NAT-PMP fallback...");
272 result = natpmp_try_map_port(internal_port, description, *ctx);
273
274 if (result == ASCIICHAT_OK) {
275 log_info("NAT: ✓ NAT-PMP port mapping successful!");
276 return ASCIICHAT_OK;
277 }
278
279 // Both UPnP and NAT-PMP failed - this is OK, not fatal
280 log_warn("NAT: Both UPnP and NAT-PMP failed. Direct TCP won't work, will use ACDS + WebRTC.");
281 log_warn("NAT: This is normal for strict NATs. No action required.");
282
283 SAFE_FREE(*ctx);
284 *ctx = NULL;
285
286 return SET_ERRNO(ERROR_NETWORK, "NAT: No automatic port mapping available (will use WebRTC)");
287}
#define SAFE_MALLOC(size, cast)
Definition common.h:208
@ ERROR_MEMORY
Definition error_codes.h:53
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.

References ASCIICHAT_OK, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_NETWORK, log_info, log_warn, SAFE_FREE, SAFE_MALLOC, and SET_ERRNO.

Referenced by main(), and server_main().

◆ nat_upnp_refresh()

asciichat_error_t nat_upnp_refresh ( nat_upnp_context_t ctx)

Refresh port mapping (e.g., for long-running servers)

Some gateways may expire mappings. Call periodically (e.g., every hour) to ensure the mapping stays active.

Parameters
ctxContext handle
Returns
ASCIICHAT_OK if refresh succeeded

Definition at line 311 of file upnp.c.

311 {
312 if (!ctx || !ctx->is_mapped) {
313 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Cannot refresh - no active mapping");
314 }
315
316 log_debug("NAT: Refreshing port mapping (would extend lease in full implementation)");
317
318 // In a real implementation, we'd re-register the port mapping to extend the lease.
319 // For now, we just return success since the lease is 1 hour anyway.
320 return ASCIICHAT_OK;
321}

References ASCIICHAT_OK, ERROR_INVALID_PARAM, nat_upnp_context::is_mapped, log_debug, and SET_ERRNO.