/* This file is part of GNUnet. Copyright (C) 2010, 2012, 2013 GNUnet e.V. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/setup/gnunet-setup-transport.c * @brief support for transport (NAT) configuration * @author Christian Grothoff * * TODO: * - cummulate collected information in the context * - implement and refine existing network setup tests * - if NAT detected and all traversal methods fail and no IPv6, * set transport plugin ports to 0 */ #include "gnunet-setup.h" #include "gnunet-setup-transport.h" #include #include #include /** * 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 = 1, /** * 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 }; /** * Context for the autoconfig test. */ struct GNUNET_SetupAutoContext { /** * Handle to the active NAT test. */ struct GNUNET_NAT_Test *tst; /** * Function to call when done. */ GNUNET_SetupAutoConfigFinished 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; /** * Task identifier for the timeout. */ struct GNUNET_SCHEDULER_Task * tsk; /** * Where are we in the test? */ enum AutoPhase phase; }; /** * Run the next phase of the auto test. */ static void next_phase (struct GNUNET_SetupAutoContext *ac); /** * Update the ICMP server button based on the result. * * @param on #GNUNET_YES to enable, #GNUNET_NO to disable */ static void update_icmp_server_enable_button (int on) { GtkToggleButton *button; button = GTK_TOGGLE_BUTTON (GNUNET_SETUP_get_object ("GNUNET_setup_transport_icmp_server_enable_checkbutton")); if (button == NULL) { GNUNET_break (0); return; } gtk_toggle_button_set_active (button, on ? TRUE : FALSE); } /** * Function called by NAT on success. * Clean up and update GUI (with success). * * @param cls closure (unused) * @param result error status */ static void result_callback (void *cls, enum GNUNET_NAT_StatusCode result) { struct GNUNET_SetupAutoContext *ac = cls; GNUNET_SCHEDULER_cancel (ac->tsk); ac->tsk = NULL; GNUNET_NAT_test_stop (ac->tst); ac->tst = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, (GNUNET_NAT_ERROR_SUCCESS == result) ? _("NAT traversal with ICMP Server succeeded.\n") : _("NAT traversal with ICMP Server failed.\n")); update_icmp_server_enable_button ((GNUNET_NAT_ERROR_SUCCESS == result)); if (NULL != cfg) GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "ENABLE_ICMP_SERVER", (GNUNET_NAT_ERROR_SUCCESS == result) ? "YES": "NO"); next_phase (ac); } /** * Main function for the connection reversal test. * * @param cls the 'int*' for the result */ static void reversal_test (void *cls) { struct GNUNET_SetupAutoContext *ac = cls; GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Testing connection reversal with ICMP server.\n")); GNUNET_assert (NULL != cfg); GNUNET_RESOLVER_connect (cfg); ac->tst = GNUNET_NAT_test_start (cfg, GNUNET_YES, 0, 0, TIMEOUT, &result_callback, ac); if (NULL == ac->tst) { next_phase (ac); return; } } /** * Test if we are online at all. * * @param ac auto setup context */ static void test_online (struct GNUNET_SetupAutoContext *ac) { // FIXME: not implemented next_phase (ac); } /** * Set our external IPv4 address. * * @param cls closure with our setup context * @param addr the address, NULL on errors * @param result error code */ static void set_external_ipv4 (void *cls, const struct in_addr *addr, enum GNUNET_NAT_StatusCode result) { struct GNUNET_SetupAutoContext *ac = cls; char buf[INET_ADDRSTRLEN]; GObject *o; ac->eh = NULL; if (NULL == addr) { next_phase (ac); return; } /* enable 'behind nat' */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Detected external IP `%s'\n"), inet_ntop (AF_INET, addr, buf, sizeof (buf))); if (NULL != cfg) GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "BEHIND_NAT", "YES"); o = GNUNET_SETUP_get_object ("GNUNET_setup_transport_nat_checkbutton"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (o), TRUE); /* set external IP address */ if (NULL == inet_ntop (AF_INET, addr, buf, sizeof (buf))) { GNUNET_break (0); next_phase (ac); return; } if (NULL != cfg) GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "EXTERNAL_ADDRESS", buf); o = GNUNET_SETUP_get_object ("GNUNET_setup_transport_external_ip_address_entry"); gtk_entry_set_text (GTK_ENTRY (o), buf); next_phase (ac); } /** * Determine our external IPv4 address. * * @param ac auto setup context */ static void test_external_ip (struct GNUNET_SetupAutoContext *ac) { // FIXME: CPS? /* try to detect external IP */ ac->eh = GNUNET_NAT_mini_get_external_ipv4 (TIMEOUT, &set_external_ipv4, ac); } /** * Process list of local IP addresses. Find and set the * one of the default interface. * * @param cls pointer to int to store if we have a non-local IPv6 address * @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) { int *have_v6 = cls; const struct sockaddr_in *in; char buf[INET_ADDRSTRLEN]; GtkEntry *entry; if (!isDefault) return GNUNET_OK; if ( (sizeof (struct sockaddr_in6) == addrlen) && (0 != memcmp (&in6addr_loopback, &((struct sockaddr_in6 *)addr)->sin6_addr, sizeof (struct in6_addr))) && (! IN6_IS_ADDR_LINKLOCAL (&((const struct sockaddr_in6*) addr)->sin6_addr))) { *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 (cfg, "nat", "INTERNAL_ADDRESS", buf); GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Detected internal network address `%s'.\n"), buf); entry = GTK_ENTRY (GNUNET_SETUP_get_object ("GNUNET_setup_transport_internal_ip_entry")); gtk_entry_set_text (entry, buf); /* no need to continue iteration */ return GNUNET_SYSERR; } /** * Determine our local IP addresses; detect internal IP & IPv6-support * * @param ac auto setup context */ static void test_local_ip (struct GNUNET_SetupAutoContext *ac) { GtkToggleButton *button; int have_v6; have_v6 = GNUNET_NO; GNUNET_OS_network_interfaces_list (&nipo, &have_v6); button = GTK_TOGGLE_BUTTON (GNUNET_SETUP_get_object ("GNUNET_setup_transport_disable_ipv6_checkbutton")); gtk_toggle_button_set_active (button, (GNUNET_YES == have_v6) ? FALSE : TRUE); if (NULL != cfg) GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "DISABLEV6", (GNUNET_YES == have_v6) ? "NO" : "YES"); next_phase (ac); } /** * Test if NAT has been punched * * @param ac auto setup context */ static void test_nat_punched (struct GNUNET_SetupAutoContext *ac) { // FIXME: not implemented next_phase (ac); } /** * Test if UPnPC works. * * @param ac auto setup context */ static void test_upnpc (struct GNUNET_SetupAutoContext *ac) { int have_upnpc; GtkToggleButton *button; /* test if upnpc is available */ button = GTK_TOGGLE_BUTTON (GNUNET_SETUP_get_object ("GNUNET_setup_transport_upnp_enable_checkbutton")); have_upnpc = (GNUNET_SYSERR != GNUNET_OS_check_helper_binary ("upnpc",GNUNET_NO, NULL)); /* 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")); gtk_toggle_button_set_active (button, have_upnpc ? TRUE : FALSE); if (NULL != cfg) GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "ENABLE_UPNP", (GNUNET_YES == have_upnpc) ? "YES" : "NO"); next_phase (ac); } /** * Test if ICMP server is working * * @param ac auto setup context */ static void test_icmp_server (struct GNUNET_SetupAutoContext *ac) { 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 (cfg, "nat", "EXTERNAL_ADDRESS", &tmp)) && (0 < strlen (tmp)) && (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, "nat", "BEHIND_NAT")) && (GNUNET_YES == GNUNET_OS_check_helper_binary (binary,GNUNET_NO, NULL))); 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) GNUNET_SCHEDULER_add_now (&reversal_test, ac); else next_phase (ac); } /** * Test if ICMP client is working * * @param ac auto setup context */ static void test_icmp_client (struct GNUNET_SetupAutoContext *ac) { GtkToggleButton *button; 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 (cfg, "nat", "INTERNAL_ADDRESS", &tmp)) && (0 < strlen (tmp)) && (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (cfg, "nat", "BEHIND_NAT")) && (GNUNET_YES == GNUNET_OS_check_helper_binary (binary,GNUNET_NO, NULL))); 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")); button = GTK_TOGGLE_BUTTON (GNUNET_SETUP_get_object ("GNUNET_setup_transport_icmp_client_enable_checkbutton")); gtk_toggle_button_set_active (button, hnc ? TRUE : FALSE); next_phase (ac); } /** * User asked for autoconfiguration. Try the full program. * * @param fin_cb function to call when done * @param fin_cb_cls closure for 'fin_cb' * @return handle for the operation */ struct GNUNET_SetupAutoContext * GNUNET_setup_transport_autoconfig_start (GNUNET_SetupAutoConfigFinished fin_cb, void *fin_cb_cls) { struct GNUNET_SetupAutoContext *ac; ac = GNUNET_new (struct GNUNET_SetupAutoContext); ac->fin_cb = fin_cb; ac->fin_cb_cls = fin_cb_cls; /* never use loopback addresses if user wanted autoconfiguration */ GNUNET_CONFIGURATION_set_value_string (cfg, "nat", "USE_LOCALADDR", "NO"); next_phase (ac); return ac; } /** * Run the next phase of the auto test. */ static void next_phase (struct GNUNET_SetupAutoContext *ac) { ac->phase++; switch (ac->phase) { case AUTO_INIT: GNUNET_assert (0); break; case AUTO_ONLINE: test_online (ac); break; case AUTO_EXTERNAL_IP: test_external_ip (ac); break; case AUTO_LOCAL_IP: test_local_ip (ac); break; case AUTO_NAT_PUNCHED: test_nat_punched (ac); break; case AUTO_UPNPC: test_upnpc (ac); break; case AUTO_ICMP_SERVER: test_icmp_server (ac); break; case AUTO_ICMP_CLIENT: test_icmp_client (ac); break; case AUTO_DONE: ac->fin_cb (ac->fin_cb_cls); GNUNET_free (ac); return; } } /** * Autoconfiguration test is finished, clear the block so * that it can be run again. * * @param cls pointer to the location that needs to be NULLed */ static void clear_ac (void *cls) { struct GNUNET_SetupAutoContext **acp = cls; *acp = NULL; gtk_widget_set_sensitive (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_transport_autoconfig_button")), TRUE); } /** * User asked for autoconfiguration. Try the full program */ void GNUNET_setup_transport_autoconfig_button_clicked_cb () { static struct GNUNET_SetupAutoContext *ac; /* make sure only one test is running at a time */ if (NULL != ac) { GNUNET_break (0); return; } gtk_widget_set_sensitive (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_transport_autoconfig_button")), FALSE); ac = GNUNET_setup_transport_autoconfig_start (&clear_ac, &ac); } /* end of gnunet-setup-transport.c */