diff options
Diffstat (limited to 'src/nat/nat_auto.c')
-rw-r--r-- | src/nat/nat_auto.c | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/src/nat/nat_auto.c b/src/nat/nat_auto.c new file mode 100644 index 0000000..baa1cc7 --- /dev/null +++ b/src/nat/nat_auto.c @@ -0,0 +1,584 @@ +/* + This file is part of GNUnet. + (C) 2012 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. +*/ + +/** + * @file nat/nat_auto.c + * @brief functions for auto-configuration of the network + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_resolver_service.h" +#include "gnunet_nat_lib.h" +#include "nat.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__) + + +/** + * How long do we wait for the NAT test to report success? + */ +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15) + +/** + * Phases of the auto configuration. + */ +enum AutoPhase +{ + /** + * Initial start value. + */ + AUTO_INIT = 0, + + /** + * Test if we are online. + */ + AUTO_ONLINE, + + /** + * Test our external IP. + */ + AUTO_EXTERNAL_IP, + + /** + * Test our internal IP. + */ + AUTO_LOCAL_IP, + + /** + * Test if NAT was punched. + */ + AUTO_NAT_PUNCHED, + + /** + * Test if UPnP is working. + */ + AUTO_UPNPC, + + /** + * Test if ICMP server works. + */ + AUTO_ICMP_SERVER, + + /** + * Test if ICMP client works. + */ + AUTO_ICMP_CLIENT, + + /** + * Last phase, we're done. + */ + AUTO_DONE + +}; + + +/** + * Handle to auto-configuration in progress. + */ +struct GNUNET_NAT_AutoHandle +{ + + /** + * Handle to the active NAT test. + */ + struct GNUNET_NAT_Test *tst; + + /** + * Function to call when done. + */ + GNUNET_NAT_AutoResultCallback fin_cb; + + /** + * Closure for 'fin_cb'. + */ + void *fin_cb_cls; + + /** + * Handle for active 'GNUNET_NAT_mini_get_external_ipv4'-operation. + */ + struct GNUNET_NAT_ExternalHandle *eh; + + /** + * Current configuration (with updates from previous phases) + */ + struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Original configuration (used to calculate differences) + */ + struct GNUNET_CONFIGURATION_Handle *initial_cfg; + + /** + * Task identifier for the timeout. + */ + GNUNET_SCHEDULER_TaskIdentifier task; + + /** + * Where are we in the test? + */ + enum AutoPhase phase; + + /** + * Do we have IPv6? + */ + int have_v6; + +}; + + +/** + * Run the next phase of the auto test. + * + * @param ah auto test handle + */ +static void +next_phase (struct GNUNET_NAT_AutoHandle *ah); + + +/** + * Function called if NAT failed to confirm success. + * Clean up and update GUI (with failure). + * + * @param cls closure with setup context + * @param tc scheduler callback + */ +static void +fail_timeout (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("NAT traversal with ICMP Server timed out.\n")); + GNUNET_assert (NULL != ah->tst); + ah->task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_NAT_test_stop (ah->tst); + ah->tst = NULL; + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", + "ENABLE_ICMP_SERVER", + "NO"); + next_phase (ah); +} + + +/** + * Function called by NAT on success. + * Clean up and update GUI (with success). + * + * @param cls the auto handle + * @param success currently always GNUNET_OK + */ +static void +result_callback (void *cls, int success) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + + GNUNET_SCHEDULER_cancel (ah->task); + ah->task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_NAT_test_stop (ah->tst); + ah->tst = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + success + ? _("NAT traversal with ICMP Server succeeded.\n") + : _("NAT traversal with ICMP Server failed.\n")); + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_ICMP_SERVER", + success ? "YES": "NO"); + next_phase (ah); +} + +/** + * Main function for the connection reversal test. + * + * @param cls the 'int*' for the result + * @param tc scheduler context + */ +static void +reversal_test (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + + ah->task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Testing connection reversal with ICMP server.\n")); + GNUNET_RESOLVER_connect (ah->cfg); + ah->tst = GNUNET_NAT_test_start (ah->cfg, GNUNET_YES, 0, 0, + &result_callback, ah); + if (NULL == ah->tst) + { + next_phase (ah); + return; + } + ah->task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &fail_timeout, ah); +} + + +/** + * Test if we are online at all. + * + * @param ah auto setup context + */ +static void +test_online (struct GNUNET_NAT_AutoHandle *ah) +{ + // FIXME: not implemented + next_phase (ah); +} + + +/** + * Set our external IPv4 address. + * + * @param cls closure with our setup context + * @param addr the address, NULL on errors + */ +static void +set_external_ipv4 (void *cls, const struct in_addr *addr) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + char buf[INET_ADDRSTRLEN]; + + ah->eh = NULL; + if (NULL == addr) + { + next_phase (ah); + return; + } + /* enable 'behind nat' */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Detected external IP `%s'\n"), + inet_ntop (AF_INET, + addr, + buf, + sizeof (buf))); + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "BEHIND_NAT", "YES"); + + /* set external IP address */ + if (NULL == inet_ntop (AF_INET, addr, buf, sizeof (buf))) + { + GNUNET_break (0); + next_phase (ah); + return; + } + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS", + buf); + next_phase (ah); +} + + +/** + * Determine our external IPv4 address. + * + * @param ah auto setup context + */ +static void +test_external_ip (struct GNUNET_NAT_AutoHandle *ah) +{ + // FIXME: CPS? + /* try to detect external IP */ + ah->eh = GNUNET_NAT_mini_get_external_ipv4 (TIMEOUT, + &set_external_ipv4, ah); +} + + +/** + * Process list of local IP addresses. Find and set the + * one of the default interface. + * + * @param cls our NAT auto handle + * @param name name of the interface (can be NULL for unknown) + * @param isDefault is this presumably the default interface + * @param addr address of this interface (can be NULL for unknown or unassigned) + * @param broadcast_addr the broadcast address (can be NULL for unknown or unassigned) + * @param netmask the network mask (can be NULL for unknown or unassigned)) + * @param addrlen length of the address + * @return GNUNET_OK to continue iteration, GNUNET_SYSERR to abort + */ +static int +nipo (void *cls, const char *name, int isDefault, const struct sockaddr *addr, + const struct sockaddr *broadcast_addr, const struct sockaddr *netmask, + socklen_t addrlen) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + const struct sockaddr_in *in; + char buf[INET_ADDRSTRLEN]; + + if (!isDefault) + return GNUNET_OK; + if ( (sizeof (struct sockaddr_in6) == addrlen) && + (0 != memcmp (&in6addr_loopback, &((const struct sockaddr_in6 *) addr)->sin6_addr, + sizeof (struct in6_addr))) && + (! IN6_IS_ADDR_LINKLOCAL(&((const struct sockaddr_in6 *) addr)->sin6_addr)) ) + { + ah->have_v6 = GNUNET_YES; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("This system has a global IPv6 address, setting IPv6 to supported.\n")); + return GNUNET_OK; + } + if (addrlen != sizeof (struct sockaddr_in)) + return GNUNET_OK; + in = (const struct sockaddr_in *) addr; + + /* set internal IP address */ + if (NULL == inet_ntop (AF_INET, &in->sin_addr, buf, sizeof (buf))) + { + GNUNET_break (0); + return GNUNET_OK; + } + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS", + buf); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Detected internal network address `%s'.\n"), + buf); + /* no need to continue iteration */ + return GNUNET_SYSERR; +} + + +/** + * Determine our local IP addresses; detect internal IP & IPv6-support + * + * @param ah auto setup context + */ +static void +test_local_ip (struct GNUNET_NAT_AutoHandle *ah) +{ + ah->have_v6 = GNUNET_NO; + GNUNET_OS_network_interfaces_list (&nipo, ah); + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "DISABLEV6", + (GNUNET_YES == ah->have_v6) ? "NO" : "YES"); + next_phase (ah); +} + + +/** + * Test if NAT has been punched + * + * @param ah auto setup context + */ +static void +test_nat_punched (struct GNUNET_NAT_AutoHandle *ah) +{ + // FIXME: not implemented + next_phase (ah); +} + + +/** + * Test if UPnPC works. + * + * @param ah auto setup context + */ +static void +test_upnpc (struct GNUNET_NAT_AutoHandle *ah) +{ + int have_upnpc; + + /* test if upnpc is available */ + have_upnpc = (GNUNET_SYSERR != + GNUNET_OS_check_helper_binary ("upnpc")); + /* FIXME: test if upnpc is actually working, that is, if transports + start to work once we use UPnP */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + (have_upnpc) + ? _("upnpc found, enabling its use\n") + : _("upnpc not found\n")); + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_UPNP", + (GNUNET_YES == have_upnpc) ? "YES" : "NO"); + next_phase (ah); +} + + +/** + * Test if ICMP server is working + * + * @param ah auto setup context + */ +static void +test_icmp_server (struct GNUNET_NAT_AutoHandle *ah) +{ + int hns; + char *tmp; + char *binary; + + tmp = NULL; + binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-server"); + hns = + ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS", + &tmp)) && (0 < strlen (tmp)) && + (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")) && + (GNUNET_YES == + GNUNET_OS_check_helper_binary (binary))); + GNUNET_free_non_null (tmp); + GNUNET_free (binary); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + (hns) + ? _("gnunet-helper-nat-server found, testing it\n") + : _("No working gnunet-helper-nat-server found\n")); + if (hns) + ah->task = GNUNET_SCHEDULER_add_now (&reversal_test, ah); + else + next_phase (ah); +} + + +/** + * Test if ICMP client is working + * + * @param ah auto setup context + */ +static void +test_icmp_client (struct GNUNET_NAT_AutoHandle *ah) +{ + int hnc; + char *tmp; + char *binary; + + tmp = NULL; + binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-client"); + hnc = + ((GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS", + &tmp)) && (0 < strlen (tmp)) && + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")) && + (GNUNET_YES == + GNUNET_OS_check_helper_binary (binary))); + GNUNET_free_non_null (tmp); + GNUNET_free (binary); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + (hnc) + ? _("gnunet-helper-nat-client found, enabling it\n") + : _("gnunet-helper-nat-client not found or behind NAT, disabling it\n")); + next_phase (ah); +} + + +/** + * Run the next phase of the auto test. + */ +static void +next_phase (struct GNUNET_NAT_AutoHandle *ah) +{ + struct GNUNET_CONFIGURATION_Handle *diff; + + ah->phase++; + switch (ah->phase) + { + case AUTO_INIT: + GNUNET_assert (0); + break; + case AUTO_ONLINE: + test_online (ah); + break; + case AUTO_EXTERNAL_IP: + test_external_ip (ah); + break; + case AUTO_LOCAL_IP: + test_local_ip (ah); + break; + case AUTO_NAT_PUNCHED: + test_nat_punched (ah); + break; + case AUTO_UPNPC: + test_upnpc (ah); + break; + case AUTO_ICMP_SERVER: + test_icmp_server (ah); + break; + case AUTO_ICMP_CLIENT: + test_icmp_client (ah); + break; + case AUTO_DONE: + diff = GNUNET_CONFIGURATION_get_diff (ah->initial_cfg, + ah->cfg); + ah->fin_cb (ah->fin_cb_cls, + diff); + GNUNET_CONFIGURATION_destroy (diff); + GNUNET_NAT_autoconfig_cancel (ah); + return; + } +} + + + +/** + * Start auto-configuration routine. The resolver service should + * be available when this function is called. + * + * @param cfg initial configuration + * @param cb function to call with autoconfiguration result + * @param cb_cls closure for cb + * @return handle to cancel operation + */ +struct GNUNET_NAT_AutoHandle * +GNUNET_NAT_autoconfig_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + GNUNET_NAT_AutoResultCallback cb, + void *cb_cls) +{ + struct GNUNET_NAT_AutoHandle *ah; + + ah = GNUNET_malloc (sizeof (struct GNUNET_NAT_AutoHandle)); + ah->fin_cb = cb; + ah->fin_cb_cls = cb_cls; + ah->cfg = GNUNET_CONFIGURATION_dup (cfg); + ah->initial_cfg = GNUNET_CONFIGURATION_dup (cfg); + + /* never use loopback addresses if user wanted autoconfiguration */ + GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", + "USE_LOCALADDR", + "NO"); + next_phase (ah); + return ah; +} + + +/** + * Abort autoconfiguration. + * + * @param ah handle for operation to abort + */ +void +GNUNET_NAT_autoconfig_cancel (struct GNUNET_NAT_AutoHandle *ah) +{ + if (NULL != ah->tst) + { + GNUNET_NAT_test_stop (ah->tst); + ah->tst = NULL; + } + if (NULL != ah->eh) + { + GNUNET_NAT_mini_get_external_ipv4_cancel (ah->eh); + ah->eh = NULL; + } + if (GNUNET_SCHEDULER_NO_TASK != ah->task) + { + GNUNET_SCHEDULER_cancel (ah->task); + ah->task = GNUNET_SCHEDULER_NO_TASK; + } + GNUNET_CONFIGURATION_destroy (ah->cfg); + GNUNET_CONFIGURATION_destroy (ah->initial_cfg); + GNUNET_free (ah); +} + + + +/* end of nat_auto.c */ |