ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
upnp.h File Reference

UPnP/NAT-PMP port mapping for direct TCP connectivity. More...

Go to the source code of this file.

Data Structures

struct  nat_upnp_context
 Handle to UPnP context. More...
 

Typedefs

typedef struct nat_upnp_context nat_upnp_context_t
 Handle to UPnP context.
 

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 for direct TCP connectivity.

Enables automatic port forwarding on home routers using UPnP/NAT-PMP, making direct TCP connections work for ~70% of home users without WebRTC.

Quick Win Strategy:

  • Try UPnP first (works on ~90% of home routers)
  • Fall back to NAT-PMP (Apple/Time Capsule)
  • If both fail, client connects via ACDS discovery + WebRTC
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file upnp.h.

Typedef Documentation

◆ nat_upnp_context_t

Handle to UPnP context.

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 276 of file upnp.c.

276 {
277 if (!ctx || !(*ctx)) {
278 return;
279 }
280
281 if ((*ctx)->is_mapped) {
282 // Note: In a real implementation, we'd remove the port mapping from the gateway.
283 // For MVP, we just log and let the lease expire naturally (typically 1 hour).
284 log_debug("NAT: Port mapping will expire in ~1 hour (cleanup handled by router)");
285 }
286
287 SAFE_FREE(*ctx);
288 *ctx = NULL;
289}
#define SAFE_FREE(ptr)
Definition common.h:376
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

References log_debug, and SAFE_FREE.

Referenced by session_server_like_run().

◆ 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 310 of file upnp.c.

310 {
311 if (!ctx || !addr || addr_len < 22) {
312 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Invalid arguments for get_address");
313 }
314
315 if (!ctx->is_mapped || ctx->external_ip[0] == '\0') {
316 return SET_ERRNO(ERROR_NETWORK, "NAT: No active mapping to advertise");
317 }
318
319 // Format as "IP:port" (e.g., "203.0.113.42:27224")
320 int written = safe_snprintf(addr, addr_len, "%s:%u", ctx->external_ip, ctx->mapped_port);
321
322 if (written < 0 || (size_t)written >= addr_len) {
323 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Address buffer too small");
324 }
325
326 return ASCIICHAT_OK;
327}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_NETWORK
Definition error_codes.h:77
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
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, safe_snprintf(), and SET_ERRNO.

Referenced by nat_detect_quality(), and session_server_like_run().

◆ 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 291 of file upnp.c.

291 {
292 if (!ctx) {
293 return false;
294 }
295 return ctx->is_mapped && ctx->external_ip[0] != '\0';
296}

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

Referenced by nat_detect_quality().

◆ 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:49
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:236

Definition at line 236 of file upnp.c.

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

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

Referenced by nat_detect_quality(), and session_server_like_run().

◆ 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 298 of file upnp.c.

298 {
299 if (!ctx || !ctx->is_mapped) {
300 return SET_ERRNO(ERROR_INVALID_PARAM, "NAT: Cannot refresh - no active mapping");
301 }
302
303 log_debug("NAT: Refreshing port mapping (would extend lease in full implementation)");
304
305 // In a real implementation, we'd re-register the port mapping to extend the lease.
306 // For now, we just return success since the lease is 1 hour anyway.
307 return ASCIICHAT_OK;
308}

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