/* This file is part of GNUnet. (C) 2009 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * This file has been adapted from the Transmission project: * Originally licensed by the GPL version 2. * Copyright (C) 2007-2009 Charles Kerr */ /** * @file nat/natpmp.c * @brief NAT-PMP support for the NAT library * * @author Milan Bouchet-Valat */ #ifdef __cplusplus extern "C" { #if 0 /* keep Emacsens' auto-indent happy */ } #endif #endif #include #include #include #include #define ENABLE_STRNATPMPERR #include #include "platform.h" #include "gnunet_common.h" #include "gnunet_nat_lib.h" #include "natpmp.h" #define LIFETIME_SECS 3600 #define COMMAND_WAIT_SECS 8 /* Component name for logging */ #define COMP_NAT_NATPMP _("NAT (NAT-PMP))") typedef enum { NATPMP_IDLE, NATPMP_ERR, NATPMP_DISCOVER, NATPMP_RECV_PUB, NATPMP_SEND_MAP, NATPMP_RECV_MAP, NATPMP_SEND_UNMAP, NATPMP_RECV_UNMAP } NATPMP_state; struct GNUNET_NAT_NATPMP_Handle { const struct sockaddr *addr; socklen_t addrlen; int is_mapped; int has_discovered; int port; time_t renew_time; time_t command_time; NATPMP_state state; natpmp_t natpmp; }; /** *** **/ static void log_val (const char *func, int ret) { #ifdef DEBUG if (ret == NATPMP_TRYAGAIN) GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, _("%s retry (%d)\n"), func, ret); // return; if (ret >= 0) GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, _("%s succeeded (%d)\n"), func, ret); else GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, "%s failed. natpmp returned %d (%s); errno is %d (%s)\n", func, ret, strnatpmperr (ret), errno, strerror (errno)); #else return; #endif } struct GNUNET_NAT_NATPMP_Handle * GNUNET_NAT_NATPMP_init (const struct sockaddr *addr, socklen_t addrlen, u_short port) { struct GNUNET_NAT_NATPMP_Handle *nat; nat = GNUNET_malloc (sizeof (struct GNUNET_NAT_NATPMP_Handle)); nat->state = NATPMP_DISCOVER; nat->port = port; nat->addr = addr; nat->addrlen = addrlen; return nat; } void GNUNET_NAT_NATPMP_close (GNUNET_NAT_NATPMP_Handle * nat) { if (nat) { closenatpmp (&nat->natpmp); GNUNET_free (nat); } } static int can_send_command (const struct GNUNET_NAT_NATPMP_Handle *nat) { return time (NULL) >= nat->command_time; } static void set_command_time (struct GNUNET_NAT_NATPMP_Handle *nat) { nat->command_time = time (NULL) + COMMAND_WAIT_SECS; } int GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, struct sockaddr **ext_addr) { int ret; /* Keep to NULL if address could not be found */ *ext_addr = NULL; if (is_enabled && (nat->state == NATPMP_DISCOVER)) { int val = initnatpmp (&nat->natpmp); log_val ("initnatpmp", val); val = sendpublicaddressrequest (&nat->natpmp); log_val ("sendpublicaddressrequest", val); nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_PUB; nat->has_discovered = 1; set_command_time (nat); } if ((nat->state == NATPMP_RECV_PUB) && can_send_command (nat)) { natpmpresp_t response; const int val = readnatpmpresponseorretry (&nat->natpmp, &response); log_val ("readnatpmpresponseorretry", val); if (val >= 0) { *ext_addr = GNUNET_malloc (sizeof (response.pnu.publicaddress.addr)); memcpy (*ext_addr, &response.pnu.publicaddress.addr, (sizeof (response.pnu.publicaddress.addr))); #ifdef DEBUG GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, _("Found public IP address %s\n"), GNUNET_a2s (*ext_addr, sizeof (*ext_addr))); #endif nat->state = NATPMP_IDLE; } else if (val != NATPMP_TRYAGAIN) { nat->state = NATPMP_ERR; } } if ((nat->state == NATPMP_IDLE) || (nat->state == NATPMP_ERR)) { if (nat->is_mapped && !is_enabled) nat->state = NATPMP_SEND_UNMAP; } if ((nat->state == NATPMP_SEND_UNMAP) && can_send_command (nat)) { const int val = sendnewportmappingrequest (&nat->natpmp, NATPMP_PROTOCOL_TCP, nat->port, nat->port, 0); log_val ("sendnewportmappingrequest", val); nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_UNMAP; set_command_time (nat); } if (nat->state == NATPMP_RECV_UNMAP) { natpmpresp_t resp; const int val = readnatpmpresponseorretry (&nat->natpmp, &resp); log_val ("readnatpmpresponseorretry", val); if (val >= 0) { const int p = resp.pnu.newportmapping.privateport; GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, _("No longer forwarding port %d\n"), p); if (nat->port == p) { nat->port = -1; nat->state = NATPMP_IDLE; nat->is_mapped = 0; } } else if (val != NATPMP_TRYAGAIN) { nat->state = NATPMP_ERR; } } if (nat->state == NATPMP_IDLE) { if (is_enabled && !nat->is_mapped && nat->has_discovered) nat->state = NATPMP_SEND_MAP; else if (nat->is_mapped && time (NULL) >= nat->renew_time) nat->state = NATPMP_SEND_MAP; } if ((nat->state == NATPMP_SEND_MAP) && can_send_command (nat)) { const int val = sendnewportmappingrequest (&nat->natpmp, NATPMP_PROTOCOL_TCP, nat->port, nat->port, LIFETIME_SECS); log_val ("sendnewportmappingrequest", val); nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_MAP; set_command_time (nat); } if (nat->state == NATPMP_RECV_MAP) { natpmpresp_t resp; const int val = readnatpmpresponseorretry (&nat->natpmp, &resp); log_val ("readnatpmpresponseorretry", val); if (val >= 0) { nat->state = NATPMP_IDLE; nat->is_mapped = 1; nat->renew_time = time (NULL) + LIFETIME_SECS; nat->port = resp.pnu.newportmapping.privateport; GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, _("Port %d forwarded successfully\n"), nat->port); } else if (val != NATPMP_TRYAGAIN) { nat->state = NATPMP_ERR; } } switch (nat->state) { case NATPMP_IDLE: ret = nat->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED; break; case NATPMP_DISCOVER: ret = GNUNET_NAT_PORT_UNMAPPED; break; case NATPMP_RECV_PUB: case NATPMP_SEND_MAP: case NATPMP_RECV_MAP: ret = GNUNET_NAT_PORT_MAPPING; break; case NATPMP_SEND_UNMAP: case NATPMP_RECV_UNMAP: ret = GNUNET_NAT_PORT_UNMAPPING; break; default: ret = GNUNET_NAT_PORT_ERROR; break; } return ret; }