19#include <ascii-chat/network/nat/upnp.h>
20#include <ascii-chat/common.h>
21#include <ascii-chat/log/logging.h>
34#include <miniupnpc/miniupnpc.h>
35#include <miniupnpc/upnpcommands.h>
36#include <miniupnpc/upnperrors.h>
45static asciichat_error_t upnp_try_map_port(uint16_t internal_port,
const char *description, nat_upnp_context_t *ctx) {
46 struct UPNPDev *device_list = NULL;
51 char external_addr[40];
53 memset(&urls, 0,
sizeof(urls));
54 memset(&data, 0,
sizeof(data));
57 log_debug(
"UPnP: Starting discovery (2 second timeout)...");
58 device_list = upnpDiscover(2000,
67 SET_ERRNO(ERROR_NETWORK,
"UPnP: No devices found (router may not support UPnP)");
71 log_debug(
"UPnP: Found %d device(s)", 1);
78#ifdef MINIUPNPC_GETVALIDIGD_7ARG
79 upnp_result = UPNP_GetValidIGD(device_list, &urls, &data, external_addr,
sizeof(external_addr), NULL, 0);
81 upnp_result = UPNP_GetValidIGD(device_list, &urls, &data, external_addr,
sizeof(external_addr));
84 if (upnp_result != 1) {
85 SET_ERRNO(ERROR_NETWORK,
"UPnP: No valid Internet Gateway found");
86 freeUPNPDevlist(device_list);
91 log_debug(
"UPnP: Found valid IGD, external address: %s", external_addr);
94 upnp_result = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, external_addr);
96 if (upnp_result != UPNPCOMMAND_SUCCESS) {
97 SET_ERRNO(ERROR_NETWORK,
"UPnP: Failed to get external IP: %s", strupnperror(upnp_result));
98 freeUPNPDevlist(device_list);
100 return ERROR_NETWORK;
103 SAFE_STRNCPY(ctx->external_ip, external_addr,
sizeof(ctx->external_ip));
104 log_info(
"UPnP: External IP detected: %s", ctx->external_ip);
107 safe_snprintf(port_str,
sizeof(port_str),
"%u", internal_port);
109 log_debug(
"UPnP: Requesting port mapping for port %u (%s)...", internal_port, description);
111 upnp_result = UPNP_AddPortMapping(urls.controlURL,
112 data.first.servicetype,
121 if (upnp_result != UPNPCOMMAND_SUCCESS) {
122 SET_ERRNO(ERROR_NETWORK,
"UPnP: Failed to add port mapping: %s", strupnperror(upnp_result));
123 freeUPNPDevlist(device_list);
125 return ERROR_NETWORK;
128 log_info(
"UPnP: ✓ Port %u successfully mapped on %s", internal_port, urls.controlURL);
131 SAFE_STRNCPY(ctx->device_description, urls.controlURL,
sizeof(ctx->device_description));
132 ctx->internal_port = internal_port;
133 ctx->mapped_port = internal_port;
134 ctx->is_natpmp =
false;
135 ctx->is_mapped =
true;
138 freeUPNPDevlist(device_list);
145static asciichat_error_t upnp_try_map_port(uint16_t internal_port,
const char *description, nat_upnp_context_t *ctx) {
149 SET_ERRNO(ERROR_NETWORK,
"miniupnpc not installed (UPnP disabled)");
150 return ERROR_NETWORK;
159static asciichat_error_t natpmp_try_map_port(uint16_t internal_port, nat_upnp_context_t *ctx) {
160#if !defined(__APPLE__) || !defined(HAVE_MINIUPNPC)
164 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: Not available on this platform (Apple only)");
166 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: libnatpmp not available (install miniupnpc)");
168 return ERROR_NETWORK;
171 natpmpresp_t response;
173 char external_ip_str[16];
175 log_debug(
"NAT-PMP: Initializing (fallback)...");
178 result = initnatpmp(&natpmp, 0, 0);
180 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: Failed to initialize (%d)", result);
181 return ERROR_NETWORK;
185 result = sendpublicaddressrequest(&natpmp);
187 closenatpmp(&natpmp);
188 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: Failed to request public address");
189 return ERROR_NETWORK;
193 memset(&response, 0,
sizeof(response));
194 result = readnatpmpresponseorretry(&natpmp, &response);
195 if (result != NATPMP_TRYAGAIN && response.type == NATPMP_RESPTYPE_PUBLICADDRESS) {
196 unsigned char *ipv4 = (
unsigned char *)&response.pnu.publicaddress.addr;
197 safe_snprintf(external_ip_str,
sizeof(external_ip_str),
"%u.%u.%u.%u", ipv4[0], ipv4[1], ipv4[2], ipv4[3]);
198 SAFE_STRNCPY(ctx->external_ip, external_ip_str,
sizeof(ctx->external_ip));
199 log_info(
"NAT-PMP: External IP detected: %s", ctx->external_ip);
203 result = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_TCP, internal_port, internal_port,
206 closenatpmp(&natpmp);
207 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: Failed to send port mapping request");
208 return ERROR_NETWORK;
212 memset(&response, 0,
sizeof(response));
213 result = readnatpmpresponseorretry(&natpmp, &response);
214 if (result != NATPMP_TRYAGAIN && response.type == NATPMP_RESPTYPE_TCPPORTMAPPING) {
215 log_info(
"NAT-PMP: ✓ Port %u successfully mapped", internal_port);
216 ctx->internal_port = internal_port;
217 ctx->mapped_port = response.pnu.newportmapping.mappedpublicport;
218 ctx->is_natpmp =
true;
219 ctx->is_mapped =
true;
220 SAFE_STRNCPY(ctx->device_description,
"Time Capsule/Apple AirPort",
sizeof(ctx->device_description));
222 closenatpmp(&natpmp);
223 SET_ERRNO(ERROR_NETWORK,
"NAT-PMP: Failed to map port");
224 return ERROR_NETWORK;
227 closenatpmp(&natpmp);
236asciichat_error_t
nat_upnp_open(uint16_t internal_port,
const char *description, nat_upnp_context_t **ctx) {
237 if (!ctx || !description) {
238 return SET_ERRNO(ERROR_INVALID_PARAM,
"nat_upnp_open: Invalid arguments");
242 *ctx = SAFE_MALLOC(
sizeof(nat_upnp_context_t), nat_upnp_context_t *);
247 memset(*ctx, 0,
sizeof(nat_upnp_context_t));
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);
253 if (result == ASCIICHAT_OK) {
254 log_info(
"NAT: ✓ UPnP port mapping successful!");
258 log_info(
"NAT: UPnP failed, trying NAT-PMP fallback...");
259 result = natpmp_try_map_port(internal_port, *ctx);
261 if (result == ASCIICHAT_OK) {
262 log_info(
"NAT: ✓ NAT-PMP port mapping successful!");
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.");
273 return SET_ERRNO(ERROR_NETWORK,
"NAT: No automatic port mapping available (will use WebRTC)");
277 if (!ctx || !(*ctx)) {
281 if ((*ctx)->is_mapped) {
284 log_debug(
"NAT: Port mapping will expire in ~1 hour (cleanup handled by router)");
295 return ctx->is_mapped && ctx->external_ip[0] !=
'\0';
299 if (!ctx || !ctx->is_mapped) {
300 return SET_ERRNO(ERROR_INVALID_PARAM,
"NAT: Cannot refresh - no active mapping");
303 log_debug(
"NAT: Refreshing port mapping (would extend lease in full implementation)");
311 if (!ctx || !addr || addr_len < 22) {
312 return SET_ERRNO(ERROR_INVALID_PARAM,
"NAT: Invalid arguments for get_address");
315 if (!ctx->is_mapped || ctx->external_ip[0] ==
'\0') {
316 return SET_ERRNO(ERROR_NETWORK,
"NAT: No active mapping to advertise");
320 int written =
safe_snprintf(addr, addr_len,
"%s:%u", ctx->external_ip, ctx->mapped_port);
322 if (written < 0 || (
size_t)written >= addr_len) {
323 return SET_ERRNO(ERROR_INVALID_PARAM,
"NAT: Address buffer too small");
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
asciichat_error_t nat_upnp_refresh(nat_upnp_context_t *ctx)
bool nat_upnp_is_active(const nat_upnp_context_t *ctx)
void nat_upnp_close(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)