34#include <miniupnpc/miniupnpc.h>
35#include <miniupnpc/upnpcommands.h>
36#include <miniupnpc/upnperrors.h>
45 switch (upnp_result) {
46 case UPNPCOMMAND_SUCCESS:
48 case UPNPCOMMAND_UNKNOWN_ERROR:
49 case UPNPCOMMAND_INVALID_ARGS:
50 case UPNPCOMMAND_HTTP_ERROR:
67 struct UPNPDev *device_list = NULL;
72 char external_addr[40];
74 memset(&urls, 0,
sizeof(urls));
75 memset(&data, 0,
sizeof(data));
78 log_debug(
"UPnP: Starting discovery (2 second timeout)...");
79 device_list = upnpDiscover(2000,
98 upnp_result = UPNP_GetValidIGD(device_list, &urls, &data, external_addr,
sizeof(external_addr), NULL, 0);
100 if (upnp_result != 1) {
102 freeUPNPDevlist(device_list);
107 log_debug(
"UPnP: Found valid IGD, external address: %s", external_addr);
110 upnp_result = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, external_addr);
112 if (upnp_result != UPNPCOMMAND_SUCCESS) {
114 freeUPNPDevlist(device_list);
123 snprintf(port_str,
sizeof(port_str),
"%u", internal_port);
125 log_debug(
"UPnP: Requesting port mapping for port %u (%s)...", internal_port, description);
127 upnp_result = UPNP_AddPortMapping(urls.controlURL,
128 data.first.servicetype,
137 if (upnp_result != UPNPCOMMAND_SUCCESS) {
139 freeUPNPDevlist(device_list);
144 log_info(
"UPnP: ✓ Port %u successfully mapped on %s", internal_port, urls.controlURL);
154 freeUPNPDevlist(device_list);
184 natpmp_response_t response;
186 char external_ip_str[16];
188 log_debug(
"NAT-PMP: Initializing (fallback)...");
191 result = initnatpmp(&natpmp, 0, 0);
198 result = sendpublicaddressrequest(&natpmp);
200 closenatpmp(&natpmp);
206 memset(&response, 0,
sizeof(response));
207 result = getnatpmpresponseorretry(&natpmp, &response);
208 if (result != NATPMP_TRYAGAIN && response.type == NATPMP_RESPTYPE_PUBLICADDRESS) {
209 unsigned char *ipv4 = (
unsigned char *)&response.pnu.publicaddress.publicaddress;
210 snprintf(external_ip_str,
sizeof(external_ip_str),
"%u.%u.%u.%u", ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
216 result = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, internal_port, internal_port,
219 closenatpmp(&natpmp);
225 memset(&response, 0,
sizeof(response));
226 result = getnatpmpresponseorretry(&natpmp, &response);
227 if (result != NATPMP_TRYAGAIN && response.type == NATPMP_RESPTYPE_TCPPORTMAPPING) {
228 log_info(
"NAT-PMP: ✓ Port %u successfully mapped", internal_port);
230 ctx->
mapped_port = response.pnu.newportmapping.mappedexternalport;
235 closenatpmp(&natpmp);
240 closenatpmp(&natpmp);
250 if (!ctx || !description) {
263 log_info(
"NAT: Attempting UPnP port mapping for port %u...", internal_port);
267 log_info(
"NAT: ✓ UPnP port mapping successful!");
271 log_info(
"NAT: UPnP failed, trying NAT-PMP fallback...");
272 result = natpmp_try_map_port(internal_port, description, *ctx);
275 log_info(
"NAT: ✓ NAT-PMP port mapping successful!");
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.");
290 if (!ctx || !(*ctx)) {
294 if ((*ctx)->is_mapped) {
297 log_debug(
"NAT: Port mapping will expire in ~1 hour (cleanup handled by router)");
316 log_debug(
"NAT: Refreshing port mapping (would extend lease in full implementation)");
324 if (!ctx || !addr || addr_len < 22) {
335 if (written < 0 || (
size_t)written >= addr_len) {
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
📝 Logging API with multiple log levels and terminal output control
uint16_t internal_port
Internal port we're binding to.
uint16_t mapped_port
External port that was mapped (may differ from internal)
bool is_natpmp
true if NAT-PMP was used, false if UPnP
bool is_mapped
true if port mapping is currently active
char device_description[256]
Device name for logging (e.g., "TP-Link Archer C7")
char external_ip[16]
Detected external/public IP (e.g., "203.0.113.42")
⏱️ High-precision timing utilities using sokol_time.h and uthash
asciichat_error_t nat_upnp_refresh(nat_upnp_context_t *ctx)
Refresh port mapping (e.g., for long-running servers)
bool nat_upnp_is_active(const nat_upnp_context_t *ctx)
Check if port mapping is still active.
void nat_upnp_close(nat_upnp_context_t **ctx)
Close port mapping and clean up.
asciichat_error_t nat_upnp_open(uint16_t internal_port, const char *description, nat_upnp_context_t **ctx)
Discover and open port via UPnP.
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.
UPnP/NAT-PMP port mapping for direct TCP connectivity.