From: Christian Grothoff Date: Fri, 8 Jul 2011 10:23:45 +0000 (+0000) Subject: towards UPnP support X-Git-Tag: initial-import-from-subversion-38251~17940 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=95d933d6e52f1c8d8dda1f6e1d8e8f8dc03d3c17;p=oweals%2Fgnunet.git towards UPnP support --- diff --git a/src/include/gnunet_nat_lib.h b/src/include/gnunet_nat_lib.h index 357758677..57eaac606 100644 --- a/src/include/gnunet_nat_lib.h +++ b/src/include/gnunet_nat_lib.h @@ -201,6 +201,44 @@ int GNUNET_NAT_mini_get_external_ipv4 (struct in_addr *addr); +/** + * Handle to a mapping created with upnpc. + */ +struct GNUNET_NAT_MiniHandle; + + +/** + * Start mapping the given port using (mini)upnpc. This function + * should typically not be used directly (it is used within the + * general-purpose 'GNUNET_NAT_register' code). However, it can be + * used if specifically UPnP-based NAT traversal is to be used or + * tested. + * + * @param port port to map + * @param is_tcp GNUNET_YES to map TCP, GNUNET_NO for UDP + * @param ac function to call with mapping result + * @param ac_cls closure for 'ac' + * @return NULL on error + */ +struct GNUNET_NAT_MiniHandle * +GNUNET_NAT_mini_map_start (uint16_t port, + int is_tcp, + GNUNET_NAT_AddressCallback ac, + void *ac_cls); + + +/** + * Remove a mapping created with (mini)upnpc. Calling + * this function will give 'upnpc' 1s to remove tha mapping, + * so while this function is non-blocking, a task will be + * left with the scheduler for up to 1s past this call. + * + * @param mini the handle + */ +void +GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini); + + #endif /* end of gnunet_nat_lib.h */ diff --git a/src/nat/Makefile.am b/src/nat/Makefile.am index 769b68bd1..6076e3294 100644 --- a/src/nat/Makefile.am +++ b/src/nat/Makefile.am @@ -57,6 +57,7 @@ libgnunetnat_la_LDFLAGS = \ check_PROGRAMS = \ test_nat \ + test_nat_mini \ test_nat_test if ENABLE_TEST_RUN @@ -69,6 +70,12 @@ test_nat_LDADD = \ $(top_builddir)/src/nat/libgnunetnat.la \ $(top_builddir)/src/util/libgnunetutil.la +test_nat_mini_SOURCES = \ + test_nat_mini.c +test_nat_mini_LDADD = \ + $(top_builddir)/src/nat/libgnunetnat.la \ + $(top_builddir)/src/util/libgnunetutil.la + test_nat_test_SOURCES = \ test_nat_test.c diff --git a/src/nat/nat_mini.c b/src/nat/nat_mini.c index 2ecdf3575..8f13d298f 100644 --- a/src/nat/nat_mini.c +++ b/src/nat/nat_mini.c @@ -28,6 +28,22 @@ #include "gnunet_nat_lib.h" #include "nat.h" +/** + * How long do we give upnpc to create a mapping? + */ +#define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15) + + +/** + * How long do we give upnpc to remove a mapping? + */ +#define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1) + +/** + * How often do we check for changes in the mapping? + */ +#define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + /** * Try to get the external IPv4 address of this peer. @@ -89,5 +105,322 @@ GNUNET_NAT_mini_get_external_ipv4 (struct in_addr *addr) } +/** + * Handle to a mapping created with upnpc. + */ +struct GNUNET_NAT_MiniHandle +{ + + /** + * Function to call on mapping changes. + */ + GNUNET_NAT_AddressCallback ac; + + /** + * Closure for 'ac'. + */ + void *ac_cls; + + /** + * Command used to install the map. + */ + struct GNUNET_OS_CommandHandle *map_cmd; + + /** + * Command used to refresh our map information. + */ + struct GNUNET_OS_CommandHandle *refresh_cmd; + + /** + * Command used to remove the mapping. + */ + struct GNUNET_OS_CommandHandle *unmap_cmd; + + /** + * Our current external mapping (if we have one). + */ + struct sockaddr_in current_addr; + + /** + * We check the mapping periodically to see if it + * still works. This task triggers the check. + */ + GNUNET_SCHEDULER_TaskIdentifier refresh_task; + + /** + * Are we mapping TCP or UDP? + */ + int is_tcp; + + /** + * Did we succeed with creating a mapping? + */ + int did_map; + + /** + * Which port are we mapping? + */ + uint16_t port; + +}; + + +/** + * Run upnpc -l to find out if our mapping changed. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + * @param tc scheduler context + */ +static void +do_refresh (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Process the output from 'upnpc -l' to see if our + * external mapping changed. If so, do the notifications. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + * @param line line of output, NULL at the end + */ +static void +process_refresh_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + if (NULL == line) + { + GNUNET_OS_command_stop (mini->refresh_cmd, + &type, &code); + mini->refresh_cmd = NULL; + mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, + &do_refresh, + mini); + return; + } + /* FIXME: parse 'line' */ + fprintf (stderr, + "Refresh output: `%s'\n", + line); +} + + +/** + * Run upnpc -l to find out if our mapping changed. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + * @param tc scheduler context + */ +static void +do_refresh (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + + mini->refresh_task = GNUNET_SCHEDULER_NO_TASK; + mini->refresh_cmd = GNUNET_OS_command_run (&process_refresh_output, + mini, + MAP_TIMEOUT, + "upnpc", + "upnpc", + "-l", + NULL); +} + + +/** + * Process the output from the 'upnpc -r' command. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + * @param line line of output, NULL at the end + */ +static void +process_map_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + const char *ipaddr; + char *ipa; + const char *pstr; + unsigned int port; + + if (NULL == line) + { + GNUNET_OS_command_stop (mini->map_cmd, + &type, &code); + mini->map_cmd = NULL; + if (mini->did_map == GNUNET_YES) + mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, + &do_refresh, + mini); + return; + } + /* + The upnpc output we're after looks like this: + + "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000" + */ + if ( (NULL == (ipaddr = strstr (line, " "))) || + (NULL == (pstr = strstr (ipaddr, ":"))) || + (1 != sscanf (pstr + 1, "%u", &port)) ) + { + fprintf (stderr, + "Skipping output `%s'\n", + line); + return; /* skip line */ + } + ipa = GNUNET_strdup (ipaddr + 1); + strstr (ipa, ":")[0] = '\0'; + if (1 != inet_pton (AF_INET, + ipa, + &mini->current_addr.sin_addr)) + { + GNUNET_free (ipa); + fprintf (stderr, + "Skipping output `%s'\n", + line); + return; /* skip line */ + } + GNUNET_free (ipa); + + mini->current_addr.sin_port = htons (port); + mini->current_addr.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + mini->current_addr.sin_len = sizeof (struct sockaddr_in); +#endif + mini->did_map = GNUNET_YES; + mini->ac (mini->ac_cls, GNUNET_YES, + (const struct sockaddr*) &mini->current_addr, + sizeof (mini->current_addr)); +} + + +/** + * Start mapping the given port using (mini)upnpc. This function + * should typically not be used directly (it is used within the + * general-purpose 'GNUNET_NAT_register' code). However, it can be + * used if specifically UPnP-based NAT traversal is to be used or + * tested. + * + * @param port port to map + * @param is_tcp GNUNET_YES to map TCP, GNUNET_NO for UDP + * @param ac function to call with mapping result + * @param ac_cls closure for 'ac' + * @return NULL on error + */ +struct GNUNET_NAT_MiniHandle * +GNUNET_NAT_mini_map_start (uint16_t port, + int is_tcp, + GNUNET_NAT_AddressCallback ac, + void *ac_cls) +{ + struct GNUNET_NAT_MiniHandle *ret; + char pstr[6]; + + ret = GNUNET_malloc (sizeof (struct GNUNET_NAT_MiniHandle)); + ret->ac = ac; + ret->ac_cls = ac_cls; + ret->is_tcp = is_tcp; + ret->port = port; + GNUNET_snprintf (pstr, sizeof (pstr), + "%u", + (unsigned int) port); + ret->map_cmd = GNUNET_OS_command_run (&process_map_output, + ret, + MAP_TIMEOUT, + "upnpc", + "upnpc", + "-r", pstr, + is_tcp ? "tcp" : "udp", + NULL); + + return ret; +} + + +/** + * Process output from our 'unmap' command. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + * @param line line of output, NULL at the end + */ +static void +process_unmap_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + if (NULL == line) + { + GNUNET_OS_command_stop (mini->unmap_cmd, + &type, &code); + mini->unmap_cmd = NULL; + GNUNET_free (mini); + return; + } + /* we don't really care about the output... */ +} + + +/** + * Remove a mapping created with (mini)upnpc. Calling + * this function will give 'upnpc' 1s to remove tha mapping, + * so while this function is non-blocking, a task will be + * left with the scheduler for up to 1s past this call. + * + * @param mini the handle + */ +void +GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini) +{ + char pstr[6]; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + + if (! mini->did_map) + { + if (mini->map_cmd != NULL) + { + GNUNET_OS_command_stop (mini->map_cmd, + &type, &code); + mini->map_cmd = NULL; + } + GNUNET_free (mini); + return; + } + if (GNUNET_SCHEDULER_NO_TASK != mini->refresh_task) + { + GNUNET_SCHEDULER_cancel (mini->refresh_task); + mini->refresh_task = GNUNET_SCHEDULER_NO_TASK; + } + if (mini->refresh_cmd != NULL) + { + GNUNET_OS_command_stop (mini->refresh_cmd, + &type, &code); + mini->refresh_cmd = NULL; + } + mini->ac (mini->ac_cls, GNUNET_NO, + (const struct sockaddr*) &mini->current_addr, + sizeof (mini->current_addr)); + GNUNET_snprintf (pstr, sizeof (pstr), + "%u", + (unsigned int) mini->port); + mini->unmap_cmd = GNUNET_OS_command_run (&process_unmap_output, + mini, + UNMAP_TIMEOUT, + "upnpc", + "upnpc", + "-d", pstr, + mini->is_tcp ? "tcp" : "udp", + NULL); +} + /* end of nat_mini.c */ diff --git a/src/nat/test_nat_mini.c b/src/nat/test_nat_mini.c new file mode 100644 index 000000000..57abca3d0 --- /dev/null +++ b/src/nat/test_nat_mini.c @@ -0,0 +1,142 @@ +/* + This file is part of GNUnet. + (C) 2009, 2011 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 3, 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. +*/ + +/** + * Testcase for port redirection and public IP address retrieval. + * This test never fails, because there need to be a NAT box set up for tha * + * @file nat/test_nat_mini.c + * @brief Testcase for NAT library - mini + * @author Christian Grothoff + * + * TODO: actually use ARM to start resolver service to make DNS work! + */ + +#include "platform.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_program_lib.h" +#include "gnunet_scheduler_lib.h" +#include "gnunet_nat_lib.h" + + +#define VERBOSE GNUNET_YES + + +/* Time to wait before stopping NAT, in seconds */ +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) + + + + + +/** + * Function called on each address that the NAT service + * believes to be valid for the transport. + */ +static void +addr_callback (void *cls, int add_remove, + const struct sockaddr *addr, socklen_t addrlen) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Address changed: %s `%s' (%u bytes)\n", + add_remove == GNUNET_YES ? "added" : "removed", + GNUNET_a2s (addr, addrlen), + (unsigned int) addrlen); +} + + +/** + * Function that terminates the test. + */ +static void +stop (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Stopping NAT and quitting...\n"); + GNUNET_NAT_mini_map_stop (mini); +} + +#define PORT 10000 + +/** + * Main function run with scheduler. + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_NAT_MiniHandle *mini; + + GNUNET_log_setup ("test-nat-mini", "DEBUG", NULL); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting NAT redirection for port %u...\n", + PORT); + mini = GNUNET_NAT_mini_map_start (PORT, + GNUNET_YES /* tcp */, + &addr_callback, NULL); + if (NULL == mini) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Could not start UPnP interaction\n"); + return; + } + GNUNET_SCHEDULER_add_delayed (TIMEOUT, &stop, mini); +} + + +int +main (int argc, char *const argv[]) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + char *const argv_prog[] = { + "test-nat-mini", + "-c", + "test_nat_data.conf", + "-L", +#if VERBOSE + "DEBUG", +#else + "WARNING", +#endif + NULL + }; + + GNUNET_log_setup ("test-nat-mini", +#if VERBOSE + "DEBUG", +#else + "WARNING", +#endif + NULL); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "UPnP test for NAT library, timeout set to %d seconds\n", TIMEOUT); + GNUNET_PROGRAM_run (5, argv_prog, "test-nat-mini", + "nohelp", options, &run, NULL); + return 0; +} + +/* end of test_nat_mini.c */ diff --git a/src/util/os_priority.c b/src/util/os_priority.c index a5e63635d..1cff5d140 100644 --- a/src/util/os_priority.c +++ b/src/util/os_priority.c @@ -1075,6 +1075,7 @@ GNUNET_OS_start_process_v (const int *lsocks, #endif } + /** * Retrieve the status of a process * @param proc process ID