From 80e1315b56f559db60499f5373e90c293c5ab065 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 6 Jan 2017 13:26:38 +0100 Subject: separate service for autoconfiguration from NAT traversal --- src/nat-auto/.gitignore | 3 + src/nat-auto/Makefile.am | 58 +++ src/nat-auto/gnunet-nat-auto.c | 428 ++++++++++++++++++++++ src/nat-auto/gnunet-nat-server.c | 384 ++++++++++++++++++++ src/nat-auto/gnunet-service-nat-auto.c | 481 ++++++++++++++++++++++++ src/nat-auto/nat-auto.conf.in | 15 + src/nat-auto/nat-auto.h | 110 ++++++ src/nat-auto/nat_auto_api.c | 274 ++++++++++++++ src/nat-auto/nat_auto_api_test.c | 644 +++++++++++++++++++++++++++++++++ 9 files changed, 2397 insertions(+) create mode 100644 src/nat-auto/.gitignore create mode 100644 src/nat-auto/Makefile.am create mode 100644 src/nat-auto/gnunet-nat-auto.c create mode 100644 src/nat-auto/gnunet-nat-server.c create mode 100644 src/nat-auto/gnunet-service-nat-auto.c create mode 100644 src/nat-auto/nat-auto.conf.in create mode 100644 src/nat-auto/nat-auto.h create mode 100644 src/nat-auto/nat_auto_api.c create mode 100644 src/nat-auto/nat_auto_api_test.c (limited to 'src/nat-auto') diff --git a/src/nat-auto/.gitignore b/src/nat-auto/.gitignore new file mode 100644 index 000000000..6ba53d72f --- /dev/null +++ b/src/nat-auto/.gitignore @@ -0,0 +1,3 @@ +gnunet-service-nat-auto +gnunet-nat-auto +gnunet-nat-server diff --git a/src/nat-auto/Makefile.am b/src/nat-auto/Makefile.am new file mode 100644 index 000000000..dbe910306 --- /dev/null +++ b/src/nat-auto/Makefile.am @@ -0,0 +1,58 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfgdir= $(pkgdatadir)/config.d/ + +pkgcfg_DATA = \ + nat-auto.conf + +bin_PROGRAMS = \ + gnunet-nat-auto \ + gnunet-nat-server + +libexec_PROGRAMS = \ + gnunet-service-nat-auto + +gnunet_nat_server_SOURCES = \ + gnunet-nat-server.c nat-auto.h +gnunet_nat_server_LDADD = \ + $(top_builddir)/src/nat/libgnunetnat.la \ + $(top_builddir)/src/util/libgnunetutil.la + +gnunet_nat_auto_SOURCES = \ + gnunet-nat-auto.c nat-auto.h +gnunet_nat_auto_LDADD = \ + libgnunetnatauto.la \ + $(top_builddir)/src/util/libgnunetutil.la + + +if USE_COVERAGE + AM_CFLAGS = -fprofile-arcs -ftest-coverage +endif + +lib_LTLIBRARIES = \ + libgnunetnatauto.la + +libgnunetnatauto_la_SOURCES = \ + nat_auto_api.c \ + nat_auto_api_test.c +libgnunetnatauto_la_LIBADD = \ + $(top_builddir)/src/nat/libgnunetnatnew.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) @EXT_LIBS@ +libgnunetnatauto_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +gnunet_service_nat_auto_SOURCES = \ + gnunet-service-nat-auto.c +gnunet_service_nat_auto_LDADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/nat/libgnunetnatnew.la \ + $(LIBGCRYPT_LIBS) \ + -lgcrypt \ + $(GN_LIBINTL) + diff --git a/src/nat-auto/gnunet-nat-auto.c b/src/nat-auto/gnunet-nat-auto.c new file mode 100644 index 000000000..3b9a5fa94 --- /dev/null +++ b/src/nat-auto/gnunet-nat-auto.c @@ -0,0 +1,428 @@ +/* + This file is part of GNUnet. + Copyright (C) 2015, 2016, 2017 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/nat/gnunet-nat-auto.c + * @brief Command-line tool for testing and autoconfiguration of NAT traversal + * @author Christian Grothoff + * @author Bruno Cabral + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_service.h" +#include "gnunet_nat_auto_service.h" + +/** + * Value to return from #main(). + */ +static int global_ret; + +/** + * Handle to ongoing autoconfiguration. + */ +static struct GNUNET_NAT_AutoHandle *ah; + +/** + * If we do auto-configuration, should we write the result + * to a file? + */ +static int write_cfg; + +/** + * Configuration filename. + */ +static const char *cfg_file; + +/** + * Original configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Address we are bound to (in test), or should bind to + * (if #do_stun is set). + */ +static char *bind_addr; + +/** + * External IP address and port to use for the test. + * If not set, use #bind_addr. + */ +static char *extern_addr; + +/** + * Should we run autoconfiguration? + */ +static unsigned int do_auto; + +/** + * Handle to a NAT test operation. + */ +static struct GNUNET_NAT_Test *nt; + +/** + * Flag set to 1 if we use IPPROTO_UDP. + */ +static int use_udp; + +/** + * Flag set to 1 if we use IPPROTO_TCP. + */ +static int use_tcp; + +/** + * Protocol to use. + */ +static uint8_t proto; + +/** + * Test if all activities have finished, and if so, + * terminate. + */ +static void +test_finished () +{ + if (NULL != ah) + return; + if (NULL != nt) + return; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Function to iterate over sugested changes options + * + * @param cls closure + * @param section name of the section + * @param option name of the option + * @param value value of the option + */ +static void +auto_conf_iter (void *cls, + const char *section, + const char *option, + const char *value) +{ + struct GNUNET_CONFIGURATION_Handle *new_cfg = cls; + + PRINTF ("%s: %s\n", + option, + value); + if (NULL != new_cfg) + GNUNET_CONFIGURATION_set_value_string (new_cfg, + section, + option, + value); +} + + +/** + * Function called with the result from the autoconfiguration. + * + * @param cls closure + * @param diff minimal suggested changes to the original configuration + * to make it work (as best as we can) + * @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code + * @param type what the situation of the NAT + */ +static void +auto_config_cb (void *cls, + const struct GNUNET_CONFIGURATION_Handle *diff, + enum GNUNET_NAT_StatusCode result, + enum GNUNET_NAT_Type type) +{ + const char *nat_type; + char unknown_type[64]; + struct GNUNET_CONFIGURATION_Handle *new_cfg; + + ah = NULL; + switch (type) + { + case GNUNET_NAT_TYPE_NO_NAT: + nat_type = "NO NAT"; + break; + case GNUNET_NAT_TYPE_UNREACHABLE_NAT: + nat_type = "NAT but we can traverse"; + break; + case GNUNET_NAT_TYPE_STUN_PUNCHED_NAT: + nat_type = "NAT but STUN is able to identify the correct information"; + break; + case GNUNET_NAT_TYPE_UPNP_NAT: + nat_type = "NAT but UPNP opened the ports"; + break; + default: + SPRINTF (unknown_type, + "NAT unknown, type %u", + type); + nat_type = unknown_type; + break; + } + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "NAT status: %s/%s\n", + GNUNET_NAT_status2string (result), + nat_type); + + /* Shortcut: if there are no changes suggested, bail out early. */ + if (GNUNET_NO == + GNUNET_CONFIGURATION_is_dirty (diff)) + { + test_finished (); + return; + } + + /* Apply diff to original configuration and show changes + to the user */ + new_cfg = write_cfg ? GNUNET_CONFIGURATION_dup (cfg) : NULL; + + if (NULL != diff) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + _("Suggested configuration changes:\n")); + GNUNET_CONFIGURATION_iterate_section_values (diff, + "nat", + &auto_conf_iter, + new_cfg); + } + + /* If desired, write configuration to file; we write only the + changes to the defaults to keep things compact. */ + if ( (write_cfg) && + (NULL != diff) ) + { + struct GNUNET_CONFIGURATION_Handle *def_cfg; + + GNUNET_CONFIGURATION_set_value_string (new_cfg, + "ARM", + "CONFIG", + NULL); + def_cfg = GNUNET_CONFIGURATION_create (); + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_load (def_cfg, + NULL)); + if (GNUNET_OK != + GNUNET_CONFIGURATION_write_diffs (def_cfg, + new_cfg, + cfg_file)) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + _("Failed to write configuration to `%s'\n"), + cfg_file); + global_ret = 1; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + _("Wrote updated configuration to `%s'\n"), + cfg_file); + } + GNUNET_CONFIGURATION_destroy (def_cfg); + } + + if (NULL != new_cfg) + GNUNET_CONFIGURATION_destroy (new_cfg); + test_finished (); +} + + +/** + * Function called to report success or failure for + * NAT configuration test. + * + * @param cls closure + * @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code + */ +static void +test_report_cb (void *cls, + enum GNUNET_NAT_StatusCode result) +{ + nt = NULL; + PRINTF ("NAT test result: %s\n", + GNUNET_NAT_status2string (result)); + test_finished (); +} + + +/** + * Task run on shutdown. + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + if (NULL != ah) + { + GNUNET_NAT_autoconfig_cancel (ah); + ah = NULL; + } + if (NULL != nt) + { + GNUNET_NAT_test_stop (nt); + nt = NULL; + } +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + struct sockaddr_in bind_sa; + struct sockaddr_in extern_sa; + + cfg_file = cfgfile; + cfg = c; + + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + + if (do_auto) + { + ah = GNUNET_NAT_autoconfig_start (c, + &auto_config_cb, + NULL); + } + + if (use_tcp && use_udp) + { + if (do_auto) + return; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Cannot use TCP and UDP\n"); + global_ret = 1; + return; + } + proto = 0; + if (use_tcp) + proto = IPPROTO_TCP; + if (use_udp) + proto = IPPROTO_UDP; + + if (NULL != bind_addr) + { + if (GNUNET_OK != + GNUNET_STRINGS_to_address_ipv4 (bind_addr, + strlen (bind_addr), + &bind_sa)) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Invalid socket address `%s'\n", + bind_addr); + global_ret = 1; + return; + } + } + if (NULL != extern_addr) + { + if (GNUNET_OK != + GNUNET_STRINGS_to_address_ipv4 (extern_addr, + strlen (extern_addr), + &extern_sa)) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Invalid socket address `%s'\n", + extern_addr); + global_ret = 1; + return; + } + } + + if (NULL != bind_addr) + { + if (NULL == extern_addr) + extern_sa = bind_sa; + nt = GNUNET_NAT_test_start (c, + proto, + bind_sa.sin_addr, + ntohs (bind_sa.sin_port), + extern_sa.sin_addr, + ntohs (extern_sa.sin_port), + &test_report_cb, + NULL); + } + test_finished (); +} + + +/** + * Main function of gnunet-nat + * + * @param argc number of command-line arguments + * @param argv command line + * @return 0 on success, -1 on error + */ +int +main (int argc, + char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'a', "auto", NULL, + gettext_noop ("run autoconfiguration"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &do_auto }, + {'b', "bind", "ADDRESS", + gettext_noop ("which IP and port are we bound to"), + GNUNET_YES, &GNUNET_GETOPT_set_string, &bind_addr }, + {'e', "external", "ADDRESS", + gettext_noop ("which external IP and port should be used to test"), + GNUNET_YES, &GNUNET_GETOPT_set_string, &extern_addr }, + {'t', "tcp", NULL, + gettext_noop ("use TCP"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &use_tcp }, + {'u', "udp", NULL, + gettext_noop ("use UDP"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &use_udp }, + {'w', "write", NULL, + gettext_noop ("write configuration file (for autoconfiguration)"), + GNUNET_NO, &GNUNET_GETOPT_set_one, &write_cfg }, + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return 2; + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "gnunet-nat-auto [options]", + _("GNUnet NAT traversal autoconfiguration"), + options, + &run, + NULL)) + { + global_ret = 1; + } + GNUNET_free ((void*) argv); + return global_ret; +} + + +/* end of gnunet-nat-auto.c */ diff --git a/src/nat-auto/gnunet-nat-server.c b/src/nat-auto/gnunet-nat-server.c new file mode 100644 index 000000000..93352f5f0 --- /dev/null +++ b/src/nat-auto/gnunet-nat-server.c @@ -0,0 +1,384 @@ +/* + This file is part of GNUnet. + Copyright (C) 2011 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/nat/gnunet-nat-server.c + * @brief Daemon to run on 'gnunet.org' to help test NAT traversal code + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_lib.h" +#include "gnunet_protocols.h" +#include "nat-auto.h" + + +/** + * Our server. + */ +static struct GNUNET_SERVER_Handle *server; + +/** + * Our configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + + +/** + * Try contacting the peer using autonomous NAT traveral method. + * + * @param dst_ipv4 IPv4 address to send the fake ICMP message + * @param dport destination port to include in ICMP message + * @param is_tcp mark for TCP (#GNUNET_YES) or UDP (#GNUNET_NO) + */ +static void +try_anat (uint32_t dst_ipv4, + uint16_t dport, + int is_tcp) +{ + struct GNUNET_NAT_Handle *h; + struct sockaddr_in sa; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asking for connection reversal with %x and code %u\n", + (unsigned int) dst_ipv4, + (unsigned int) dport); + h = GNUNET_NAT_register (cfg, + is_tcp, + dport, + 0, + NULL, NULL, NULL, NULL, NULL, NULL); + memset (&sa, 0, sizeof (sa)); + sa.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + sa.sin_len = sizeof (sa); +#endif + sa.sin_addr.s_addr = dst_ipv4; + GNUNET_NAT_run_client (h, &sa); + GNUNET_NAT_unregister (h); +} + + +/** + * Closure for #tcp_send. + */ +struct TcpContext +{ + /** + * TCP socket. + */ + struct GNUNET_NETWORK_Handle *s; + + /** + * Data to transmit. + */ + uint16_t data; +}; + + +/** + * Task called by the scheduler once we can do the TCP send + * (or once we failed to connect...). + * + * @param cls the `struct TcpContext` + */ +static void +tcp_send (void *cls) +{ + struct TcpContext *ctx = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + tc = GNUNET_SCHEDULER_get_task_context (); + if ((NULL != tc->write_ready) && + (GNUNET_NETWORK_fdset_isset (tc->write_ready, ctx->s))) + { + if (-1 == + GNUNET_NETWORK_socket_send (ctx->s, &ctx->data, sizeof (ctx->data))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, "send"); + } + GNUNET_NETWORK_socket_shutdown (ctx->s, SHUT_RDWR); + } + GNUNET_NETWORK_socket_close (ctx->s); + GNUNET_free (ctx); +} + + +/** + * Try to send @a data to the + * IP @a dst_ipv4' at port @a dport via TCP. + * + * @param dst_ipv4 target IP + * @param dport target port + * @param data data to send + */ +static void +try_send_tcp (uint32_t dst_ipv4, + uint16_t dport, + uint16_t data) +{ + struct GNUNET_NETWORK_Handle *s; + struct sockaddr_in sa; + struct TcpContext *ctx; + + s = GNUNET_NETWORK_socket_create (AF_INET, + SOCK_STREAM, + 0); + if (NULL == s) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "socket"); + return; + } + memset (&sa, 0, sizeof (sa)); + sa.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + sa.sin_len = sizeof (sa); +#endif + sa.sin_addr.s_addr = dst_ipv4; + sa.sin_port = htons (dport); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending TCP message to `%s'\n", + GNUNET_a2s ((struct sockaddr *) &sa, + sizeof (sa))); + if ( (GNUNET_OK != + GNUNET_NETWORK_socket_connect (s, + (const struct sockaddr *) &sa, + sizeof (sa))) && + (errno != EINPROGRESS) ) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "connect"); + GNUNET_NETWORK_socket_close (s); + return; + } + ctx = GNUNET_new (struct TcpContext); + ctx->s = s; + ctx->data = data; + GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_SECONDS, + s, + &tcp_send, + ctx); +} + + +/** + * Try to send @a data to the + * IP @a dst_ipv4 at port @a dport via UDP. + * + * @param dst_ipv4 target IP + * @param dport target port + * @param data data to send + */ +static void +try_send_udp (uint32_t dst_ipv4, + uint16_t dport, + uint16_t data) +{ + struct GNUNET_NETWORK_Handle *s; + struct sockaddr_in sa; + + s = GNUNET_NETWORK_socket_create (AF_INET, + SOCK_DGRAM, + 0); + if (NULL == s) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "socket"); + return; + } + memset (&sa, 0, sizeof (sa)); + sa.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + sa.sin_len = sizeof (sa); +#endif + sa.sin_addr.s_addr = dst_ipv4; + sa.sin_port = htons (dport); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending UDP packet to `%s'\n", + GNUNET_a2s ((struct sockaddr *) &sa, + sizeof (sa))); + if (-1 == + GNUNET_NETWORK_socket_sendto (s, + &data, + sizeof (data), + (const struct sockaddr *) &sa, + sizeof (sa))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "sendto"); + GNUNET_NETWORK_socket_close (s); +} + + +/** + * We've received a request to probe a NAT + * traversal. Do it. + * + * @param cls unused + * @param client handle to client (we always close) + * @param msg message with details about what to test + */ +static void +test (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *msg) +{ + const struct GNUNET_NAT_TestMessage *tm; + uint16_t dport; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received test request\n"); + tm = (const struct GNUNET_NAT_TestMessage *) msg; + dport = ntohs (tm->dport); + if (0 == dport) + try_anat (tm->dst_ipv4, + ntohs (tm->data), + (int) ntohl (tm->is_tcp)); + else if (GNUNET_YES == ntohl (tm->is_tcp)) + try_send_tcp (tm->dst_ipv4, + dport, + tm->data); + else + try_send_udp (tm->dst_ipv4, + dport, + tm->data); + GNUNET_SERVER_receive_done (client, + GNUNET_NO); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_SERVER_destroy (server); + server = NULL; +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + static const struct GNUNET_SERVER_MessageHandler handlers[] = { + {&test, NULL, GNUNET_MESSAGE_TYPE_NAT_TEST, + sizeof (struct GNUNET_NAT_TestMessage)}, + {NULL, NULL, 0, 0} + }; + unsigned int port; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + + socklen_t slen[] = { + sizeof (in4), + sizeof (in6), + 0 + }; + struct sockaddr *sa[] = { + (struct sockaddr *) &in4, + (struct sockaddr *) &in6, + NULL + }; + + cfg = c; + if ( (NULL == args[0]) || + (1 != SSCANF (args[0], "%u", &port)) || + (0 == port) || + (65536 <= port) ) + { + FPRINTF (stderr, + _("Please pass valid port number as the first argument! (got `%s')\n"), + args[0]); + return; + } + memset (&in4, 0, sizeof (in4)); + memset (&in6, 0, sizeof (in6)); + in4.sin_family = AF_INET; + in4.sin_port = htons ((uint16_t) port); + in6.sin6_family = AF_INET6; + in6.sin6_port = htons ((uint16_t) port); +#if HAVE_SOCKADDR_IN_SIN_LEN + in4.sin_len = sizeof (in4); + in6.sin6_len = sizeof (in6); +#endif + server = GNUNET_SERVER_create (NULL, + NULL, + (struct sockaddr * const *) sa, + slen, + GNUNET_TIME_UNIT_SECONDS, + GNUNET_YES); + GNUNET_SERVER_add_handlers (server, + handlers); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); +} + + +/** + * Main function of gnunet-nat-server. + * + * @param argc number of command-line arguments + * @param argv command line + * @return 0 on success, -1 on error + */ +int +main (int argc, char *const argv[]) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return 2; + + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, + argv, + "gnunet-nat-server [options] PORT", + _("GNUnet NAT traversal test helper daemon"), + options, + &run, + NULL)) + { + GNUNET_free ((void*) argv); + return 1; + } + GNUNET_free ((void*) argv); + return 0; +} + + +/* end of gnunet-nat-server.c */ diff --git a/src/nat-auto/gnunet-service-nat-auto.c b/src/nat-auto/gnunet-service-nat-auto.c new file mode 100644 index 000000000..897d6feb2 --- /dev/null +++ b/src/nat-auto/gnunet-service-nat-auto.c @@ -0,0 +1,481 @@ +/* + This file is part of GNUnet. + Copyright (C) 2016, 2017 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 nat-auto/gnunet-service-nat-auto.c + * @brief NAT autoconfiguration service + * @author Christian Grothoff + * + * TODO: + * - merge client handle and autoconfig context + * - implement "more" autoconfig: + * + re-work gnunet-nat-server & integrate! + * + test manually punched NAT (how?) + */ +#include "platform.h" +#include +#include "gnunet_util_lib.h" +#include "gnunet_protocols.h" +#include "gnunet_signatures.h" +#include "gnunet_nat_service.h" +#include "gnunet_statistics_service.h" +#include "gnunet_resolver_service.h" +#include "nat-auto.h" +#include + + +/** + * How long do we wait until we forcefully terminate autoconfiguration? + */ +#define AUTOCONFIG_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) + + +/** + * Internal data structure we track for each of our clients. + */ +struct ClientHandle +{ + + /** + * Kept in a DLL. + */ + struct ClientHandle *next; + + /** + * Kept in a DLL. + */ + struct ClientHandle *prev; + + /** + * Underlying handle for this client with the service. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * Message queue for communicating with the client. + */ + struct GNUNET_MQ_Handle *mq; +}; + + +/** + * Context for autoconfiguration operations. + */ +struct AutoconfigContext +{ + /** + * Kept in a DLL. + */ + struct AutoconfigContext *prev; + + /** + * Kept in a DLL. + */ + struct AutoconfigContext *next; + + /** + * Which client asked the question. + */ + struct ClientHandle *ch; + + /** + * Configuration we are creating. + */ + struct GNUNET_CONFIGURATION_Handle *c; + + /** + * Original configuration (for diffing). + */ + struct GNUNET_CONFIGURATION_Handle *orig; + + /** + * Timeout task to force termination. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * #GNUNET_YES if upnpc should be used, + * #GNUNET_NO if upnpc should not be used, + * #GNUNET_SYSERR if we should simply not change the option. + */ + int enable_upnpc; + + /** + * Status code to return to the client. + */ + enum GNUNET_NAT_StatusCode status_code; + + /** + * NAT type to return to the client. + */ + enum GNUNET_NAT_Type type; +}; + + +/** + * Head of client DLL. + */ +static struct ClientHandle *ch_head; + +/** + * Tail of client DLL. + */ +static struct ClientHandle *ch_tail; + +/** + * DLL of our autoconfiguration operations. + */ +static struct AutoconfigContext *ac_head; + +/** + * DLL of our autoconfiguration operations. + */ +static struct AutoconfigContext *ac_tail; + +/** + * Handle to our current configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Handle to the statistics service. + */ +static struct GNUNET_STATISTICS_Handle *stats; + + +/** + * Check validity of #GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG message + * from client. + * + * @param cls client who sent the message + * @param message the message received + * @return #GNUNET_OK if message is well-formed + */ +static int +check_autoconfig_request (void *cls, + const struct GNUNET_NAT_AutoconfigRequestMessage *message) +{ + return GNUNET_OK; /* checked later */ +} + + +/** + * Stop all pending activities with respect to the @a ac + * + * @param ac autoconfiguration to terminate activities for + */ +static void +terminate_ac_activities (struct AutoconfigContext *ac) +{ + if (NULL != ac->timeout_task) + { + GNUNET_SCHEDULER_cancel (ac->timeout_task); + ac->timeout_task = NULL; + } +} + + +/** + * Finish handling the autoconfiguration request and send + * the response to the client. + * + * @param cls the `struct AutoconfigContext` to conclude + */ +static void +conclude_autoconfig_request (void *cls) +{ + struct AutoconfigContext *ac = cls; + struct ClientHandle *ch = ac->ch; + struct GNUNET_NAT_AutoconfigResultMessage *arm; + struct GNUNET_MQ_Envelope *env; + size_t c_size; + char *buf; + struct GNUNET_CONFIGURATION_Handle *diff; + + ac->timeout_task = NULL; + terminate_ac_activities (ac); + + /* Send back response */ + diff = GNUNET_CONFIGURATION_get_diff (ac->orig, + ac->c); + buf = GNUNET_CONFIGURATION_serialize (diff, + &c_size); + GNUNET_CONFIGURATION_destroy (diff); + env = GNUNET_MQ_msg_extra (arm, + c_size, + GNUNET_MESSAGE_TYPE_NAT_AUTO_CFG_RESULT); + arm->status_code = htonl ((uint32_t) ac->status_code); + arm->type = htonl ((uint32_t) ac->type); + GNUNET_memcpy (&arm[1], + buf, + c_size); + GNUNET_free (buf); + GNUNET_MQ_send (ch->mq, + env); + + /* clean up */ + GNUNET_CONFIGURATION_destroy (ac->orig); + GNUNET_CONFIGURATION_destroy (ac->c); + GNUNET_CONTAINER_DLL_remove (ac_head, + ac_tail, + ac); + GNUNET_free (ac); + GNUNET_SERVICE_client_continue (ch->client); +} + + +/** + * Check if all autoconfiguration operations have concluded, + * and if they have, send the result back to the client. + * + * @param ac autoconfiguation context to check + */ +static void +check_autoconfig_finished (struct AutoconfigContext *ac) +{ + GNUNET_SCHEDULER_cancel (ac->timeout_task); + ac->timeout_task + = GNUNET_SCHEDULER_add_now (&conclude_autoconfig_request, + ac); +} + + +/** + * Update ENABLE_UPNPC configuration option. + * + * @param ac autoconfiguration to update + */ +static void +update_enable_upnpc_option (struct AutoconfigContext *ac) +{ + switch (ac->enable_upnpc) + { + case GNUNET_YES: + GNUNET_CONFIGURATION_set_value_string (ac->c, + "NAT", + "ENABLE_UPNP", + "YES"); + break; + case GNUNET_NO: + GNUNET_CONFIGURATION_set_value_string (ac->c, + "NAT", + "ENABLE_UPNP", + "NO"); + break; + case GNUNET_SYSERR: + /* We are unsure, do not change option */ + break; + } +} + + +/** + * Handler for #GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG message from + * client. + * + * @param cls client who sent the message + * @param message the message received + */ +static void +handle_autoconfig_request (void *cls, + const struct GNUNET_NAT_AutoconfigRequestMessage *message) +{ + struct ClientHandle *ch = cls; + size_t left = ntohs (message->header.size) - sizeof (*message); + struct AutoconfigContext *ac; + + ac = GNUNET_new (struct AutoconfigContext); + ac->status_code = GNUNET_NAT_ERROR_SUCCESS; + ac->ch = ch; + ac->c = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_deserialize (ac->c, + (const char *) &message[1], + left, + GNUNET_NO)) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (ch->client); + GNUNET_CONFIGURATION_destroy (ac->c); + GNUNET_free (ac); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received REQUEST_AUTO_CONFIG message from client\n"); + + GNUNET_CONTAINER_DLL_insert (ac_head, + ac_tail, + ac); + ac->orig + = GNUNET_CONFIGURATION_dup (ac->c); + ac->timeout_task + = GNUNET_SCHEDULER_add_delayed (AUTOCONFIG_TIMEOUT, + &conclude_autoconfig_request, + ac); + ac->enable_upnpc = GNUNET_SYSERR; /* undecided */ + + /* Probe for upnpc */ + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("upnpc", + GNUNET_NO, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("UPnP client `upnpc` command not found, disabling UPnP\n")); + ac->enable_upnpc = GNUNET_NO; + } + else + { + /* We might at some point be behind NAT, try upnpc */ + ac->enable_upnpc = GNUNET_YES; + } + update_enable_upnpc_option (ac); + + /* Finally, check if we are already done */ + check_autoconfig_finished (ac); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + struct AutoconfigContext *ac; + + while (NULL != (ac = ac_head)) + { + GNUNET_CONTAINER_DLL_remove (ac_head, + ac_tail, + ac); + terminate_ac_activities (ac); + GNUNET_free (ac); + } + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, + GNUNET_NO); + stats = NULL; + } +} + + +/** + * Setup NAT service. + * + * @param cls closure + * @param c configuration to use + * @param service the initialized service + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + cfg = c; + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); + stats = GNUNET_STATISTICS_create ("nat-auto", + cfg); +} + + +/** + * Callback called when a client connects to the service. + * + * @param cls closure for the service + * @param c the new client that connected to the service + * @param mq the message queue used to send messages to the client + * @return a `struct ClientHandle` + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *c, + struct GNUNET_MQ_Handle *mq) +{ + struct ClientHandle *ch; + + ch = GNUNET_new (struct ClientHandle); + ch->mq = mq; + ch->client = c; + GNUNET_CONTAINER_DLL_insert (ch_head, + ch_tail, + ch); + return ch; +} + + +/** + * Callback called when a client disconnected from the service + * + * @param cls closure for the service + * @param c the client that disconnected + * @param internal_cls a `struct ClientHandle *` + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *c, + void *internal_cls) +{ + struct ClientHandle *ch = internal_cls; + + GNUNET_CONTAINER_DLL_remove (ch_head, + ch_tail, + ch); + GNUNET_free (ch); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("nat", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_var_size (autoconfig_request, + GNUNET_MESSAGE_TYPE_NAT_AUTO_REQUEST_CFG, + struct GNUNET_NAT_AutoconfigRequestMessage, + NULL), + GNUNET_MQ_handler_end ()); + + +#if defined(LINUX) && defined(__GLIBC__) +#include + +/** + * MINIMIZE heap size (way below 128k) since this process doesn't need much. + */ +void __attribute__ ((constructor)) +GNUNET_ARM_memory_init () +{ + mallopt (M_TRIM_THRESHOLD, 4 * 1024); + mallopt (M_TOP_PAD, 1 * 1024); + malloc_trim (0); +} +#endif + +/* end of gnunet-service-nat.c */ diff --git a/src/nat-auto/nat-auto.conf.in b/src/nat-auto/nat-auto.conf.in new file mode 100644 index 000000000..daa3e389d --- /dev/null +++ b/src/nat-auto/nat-auto.conf.in @@ -0,0 +1,15 @@ +[nat] +AUTOSTART = @AUTOSTART@ +@UNIXONLY@ PORT = 2124 +HOSTNAME = localhost +BINARY = gnunet-service-nat-auto +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-nat-auto.sock +UNIX_MATCH_UID = YES +UNIX_MATCH_GID = YES + +[gnunet-nat-server] +HOSTNAME = gnunet.org +PORT = 5724 +NOARMBIND = YES diff --git a/src/nat-auto/nat-auto.h b/src/nat-auto/nat-auto.h new file mode 100644 index 000000000..150dc32c2 --- /dev/null +++ b/src/nat-auto/nat-auto.h @@ -0,0 +1,110 @@ +/* + This file is part of GNUnet. + Copyright (C) 2011, 2016, 2017 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/nat-auto/nat-auto.h + * @brief Messages for interaction with gnunet-nat-auto-service + * @author Christian Grothoff + * + */ +#ifndef NAT_AUTO_H +#define NAT_AUTO_H +#include "gnunet_util_lib.h" + + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Request to test NAT traversal, sent to the gnunet-nat-server + * (not the service!). + */ +struct GNUNET_NAT_TestMessage +{ + /** + * Header with type #GNUNET_MESSAGE_TYPE_NAT_TEST + */ + struct GNUNET_MessageHeader header; + + /** + * IPv4 target IP address + */ + uint32_t dst_ipv4; + + /** + * Port to use, 0 to send dummy ICMP response. + */ + uint16_t dport; + + /** + * Data to send OR advertised-port (in NBO) to use for dummy ICMP. + */ + uint16_t data; + + /** + * #GNUNET_YES for TCP, #GNUNET_NO for UDP. + */ + int32_t is_tcp; + +}; + + +/** + * Client requesting automatic configuration. + */ +struct GNUNET_NAT_AutoconfigRequestMessage +{ + /** + * Header with type #GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG + */ + struct GNUNET_MessageHeader header; + + /* Followed by configuration (diff, serialized, compressed) */ + +}; + + +/** + * Service responding with proposed configuration. + */ +struct GNUNET_NAT_AutoconfigResultMessage +{ + /** + * Header with type #GNUNET_MESSAGE_TYPE_NAT_AUTO_CFG_RESULT + */ + struct GNUNET_MessageHeader header; + + /** + * An `enum GNUNET_NAT_StatusCode` in NBO. + */ + int32_t status_code GNUNET_PACKED; + + /** + * An `enum GNUNET_NAT_Type` in NBO. + */ + int32_t type GNUNET_PACKED; + + /* Followed by configuration (diff, serialized, compressed) */ +}; + + +GNUNET_NETWORK_STRUCT_END + +#endif diff --git a/src/nat-auto/nat_auto_api.c b/src/nat-auto/nat_auto_api.c new file mode 100644 index 000000000..e6b0512c6 --- /dev/null +++ b/src/nat-auto/nat_auto_api.c @@ -0,0 +1,274 @@ + +/* + This file is part of GNUnet. + Copyright (C) 2007-2016 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. +*/ + +/** + * @author Christian Grothoff + * @author Milan Bouchet-Valat + * + * @file nat/nat_auto_api.c + * Routines for NAT auto configuration. + */ +#include "platform.h" +#include "gnunet_nat_service.h" +#include "gnunet_nat_auto_service.h" +#include "nat-auto.h" + + + +/** + * Handle to auto-configuration in progress. + */ +struct GNUNET_NAT_AutoHandle +{ + + /** + * Configuration we use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Message queue for communicating with the NAT service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Function called with the result from the autoconfiguration. + */ + GNUNET_NAT_AutoResultCallback arc; + + /** + * Closure for @e arc. + */ + void *arc_cls; + +}; + + +/** + * Converts `enum GNUNET_NAT_StatusCode` to string + * + * @param err error code to resolve to a string + * @return point to a static string containing the error code + */ +const char * +GNUNET_NAT_status2string (enum GNUNET_NAT_StatusCode err) +{ + switch (err) + { + case GNUNET_NAT_ERROR_SUCCESS: + return _ ("Operation Successful"); + case GNUNET_NAT_ERROR_IPC_FAILURE: + return _ ("IPC failure"); + case GNUNET_NAT_ERROR_INTERNAL_NETWORK_ERROR: + return _ ("Failure in network subsystem, check permissions."); + case GNUNET_NAT_ERROR_TIMEOUT: + return _ ("Encountered timeout while performing operation"); + case GNUNET_NAT_ERROR_NOT_ONLINE: + return _ ("detected that we are offline"); + case GNUNET_NAT_ERROR_UPNPC_NOT_FOUND: + return _ ("`upnpc` command not found"); + case GNUNET_NAT_ERROR_UPNPC_FAILED: + return _ ("Failed to run `upnpc` command"); + case GNUNET_NAT_ERROR_UPNPC_TIMEOUT: + return _ ("`upnpc' command took too long, process killed"); + case GNUNET_NAT_ERROR_UPNPC_PORTMAP_FAILED: + return _ ("`upnpc' command failed to establish port mapping"); + case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_NOT_FOUND: + return _ ("`external-ip' command not found"); + case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_FAILED: + return _ ("Failed to run `external-ip` command"); + case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID: + return _ ("`external-ip' command output invalid"); + case GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID: + return _ ("no valid address was returned by `external-ip'"); + case GNUNET_NAT_ERROR_NO_VALID_IF_IP_COMBO: + return _ ("Could not determine interface with internal/local network address"); + case GNUNET_NAT_ERROR_HELPER_NAT_SERVER_NOT_FOUND: + return _ ("No functioning gnunet-helper-nat-server installation found"); + case GNUNET_NAT_ERROR_NAT_TEST_START_FAILED: + return _ ("NAT test could not be initialized"); + case GNUNET_NAT_ERROR_NAT_TEST_TIMEOUT: + return _ ("NAT test timeout reached"); + case GNUNET_NAT_ERROR_NAT_REGISTER_FAILED: + return _ ("could not register NAT"); + case GNUNET_NAT_ERROR_HELPER_NAT_CLIENT_NOT_FOUND: + return _ ("No working gnunet-helper-nat-client installation found"); + default: + return "unknown status code"; + } +} + + +/** + * Check result from autoconfiguration attempt. + * + * @param cls the `struct GNUNET_NAT_AutoHandle` + * @param res the result + * @return #GNUNET_OK if @a res is well-formed (always for now) + */ +static int +check_auto_result (void *cls, + const struct GNUNET_NAT_AutoconfigResultMessage *res) +{ + return GNUNET_OK; +} + + +/** + * Handle result from autoconfiguration attempt. + * + * @param cls the `struct GNUNET_NAT_AutoHandle` + * @param res the result + */ +static void +handle_auto_result (void *cls, + const struct GNUNET_NAT_AutoconfigResultMessage *res) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + size_t left; + struct GNUNET_CONFIGURATION_Handle *cfg; + enum GNUNET_NAT_Type type + = (enum GNUNET_NAT_Type) ntohl (res->type); + enum GNUNET_NAT_StatusCode status + = (enum GNUNET_NAT_StatusCode) ntohl (res->status_code); + + left = ntohs (res->header.size) - sizeof (*res); + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_deserialize (cfg, + (const char *) &res[1], + left, + GNUNET_NO)) + { + GNUNET_break (0); + ah->arc (ah->arc_cls, + NULL, + GNUNET_NAT_ERROR_IPC_FAILURE, + type); + } + else + { + ah->arc (ah->arc_cls, + cfg, + status, + type); + } + GNUNET_CONFIGURATION_destroy (cfg); + GNUNET_NAT_autoconfig_cancel (ah); +} + + +/** + * Handle queue errors by reporting autoconfiguration failure. + * + * @param cls the `struct GNUNET_NAT_AutoHandle *` + * @param error details about the error + */ +static void +ah_error_handler (void *cls, + enum GNUNET_MQ_Error error) +{ + struct GNUNET_NAT_AutoHandle *ah = cls; + + ah->arc (ah->arc_cls, + NULL, + GNUNET_NAT_ERROR_IPC_FAILURE, + GNUNET_NAT_TYPE_UNKNOWN); + GNUNET_NAT_autoconfig_cancel (ah); +} + + +/** + * Start auto-configuration routine. The transport adapters should + * be stopped while this function is called. + * + * @param cfg initial configuration + * @param cb function to call with autoconfiguration result + * @param cb_cls closure for @a 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 = GNUNET_new (struct GNUNET_NAT_AutoHandle); + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (auto_result, + GNUNET_MESSAGE_TYPE_NAT_AUTO_CFG_RESULT, + struct GNUNET_NAT_AutoconfigResultMessage, + ah), + GNUNET_MQ_handler_end () + }; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_NAT_AutoconfigRequestMessage *req; + char *buf; + size_t size; + + buf = GNUNET_CONFIGURATION_serialize (cfg, + &size); + if (size > GNUNET_SERVER_MAX_MESSAGE_SIZE - sizeof (*req)) + { + GNUNET_break (0); + GNUNET_free (buf); + GNUNET_free (ah); + return NULL; + } + ah->arc = cb; + ah->arc_cls = cb_cls; + ah->mq = GNUNET_CLIENT_connecT (cfg, + "nat", + handlers, + &ah_error_handler, + ah); + if (NULL == ah->mq) + { + GNUNET_break (0); + GNUNET_free (buf); + GNUNET_free (ah); + return NULL; + } + env = GNUNET_MQ_msg_extra (req, + size, + GNUNET_MESSAGE_TYPE_NAT_AUTO_REQUEST_CFG); + GNUNET_memcpy (&req[1], + buf, + size); + GNUNET_free (buf); + GNUNET_MQ_send (ah->mq, + env); + return ah; +} + + +/** + * Abort autoconfiguration. + * + * @param ah handle for operation to abort + */ +void +GNUNET_NAT_autoconfig_cancel (struct GNUNET_NAT_AutoHandle *ah) +{ + GNUNET_MQ_destroy (ah->mq); + GNUNET_free (ah); +} + +/* end of nat_api_auto.c */ diff --git a/src/nat-auto/nat_auto_api_test.c b/src/nat-auto/nat_auto_api_test.c new file mode 100644 index 000000000..056d2a2bf --- /dev/null +++ b/src/nat-auto/nat_auto_api_test.c @@ -0,0 +1,644 @@ +/* + This file is part of GNUnet. + Copyright (C) 2011, 2016 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 nat/nat_auto_api_test.c + * @brief functions to test if the NAT configuration is successful at achieving NAT traversal (with the help of a gnunet-nat-server) + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_lib.h" +#include "nat-auto.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__) + +#define NAT_SERVER_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * Entry we keep for each incoming connection. + */ +struct NatActivity +{ + /** + * This is a doubly-linked list. + */ + struct NatActivity *next; + + /** + * This is a doubly-linked list. + */ + struct NatActivity *prev; + + /** + * Socket of the incoming connection. + */ + struct GNUNET_NETWORK_Handle *sock; + + /** + * Handle of the master context. + */ + struct GNUNET_NAT_Test *h; + + /** + * Task reading from the incoming connection. + */ + struct GNUNET_SCHEDULER_Task *rtask; +}; + + +/** + * Entry we keep for each connection to the gnunet-nat-service. + */ +struct ClientActivity +{ + /** + * This is a doubly-linked list. + */ + struct ClientActivity *next; + + /** + * This is a doubly-linked list. + */ + struct ClientActivity *prev; + + /** + * Socket of the incoming connection. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Handle to overall NAT test. + */ + struct GNUNET_NAT_Test *h; + +}; + + +/** + * Handle to a NAT test. + */ +struct GNUNET_NAT_Test +{ + + /** + * Configuration used + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Function to call with success report + */ + GNUNET_NAT_TestCallback report; + + /** + * Closure for @e report. + */ + void *report_cls; + + /** + * Handle to NAT traversal in use + */ + struct GNUNET_NAT_Handle *nat; + + /** + * Handle to listen socket, or NULL + */ + struct GNUNET_NETWORK_Handle *lsock; + + /** + * Head of list of nat activities. + */ + struct NatActivity *na_head; + + /** + * Tail of list of nat activities. + */ + struct NatActivity *na_tail; + + /** + * Head of list of client activities. + */ + struct ClientActivity *ca_head; + + /** + * Tail of list of client activities. + */ + struct ClientActivity *ca_tail; + + /** + * Identity of task for the listen socket (if any) + */ + struct GNUNET_SCHEDULER_Task *ltask; + + /** + * Task identifier for the timeout (if any) + */ + struct GNUNET_SCHEDULER_Task *ttask; + + /** + * #GNUNET_YES if we're testing TCP + */ + int is_tcp; + + /** + * Data that should be transmitted or source-port. + */ + uint16_t data; + + /** + * Advertised port to the other peer. + */ + uint16_t adv_port; + + /** + * Status code to be reported to the timeout/status call + */ + enum GNUNET_NAT_StatusCode status; +}; + + +/** + * Function called from #GNUNET_NAT_register whenever someone asks us + * to do connection reversal. + * + * @param cls closure, our `struct GNUNET_NAT_Handle` + * @param addr public IP address of the other peer + * @param addrlen actual lenght of the @a addr + */ +static void +reversal_cb (void *cls, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct GNUNET_NAT_Test *h = cls; + const struct sockaddr_in *sa; + + if (sizeof (struct sockaddr_in) != addrlen) + return; + sa = (const struct sockaddr_in *) addr; + if (h->data != sa->sin_port) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received connection reversal request for wrong port\n"); + return; /* wrong port */ + } + /* report success */ + h->report (h->report_cls, + GNUNET_NAT_ERROR_SUCCESS); +} + + +/** + * Activity on our incoming socket. Read data from the + * incoming connection. + * + * @param cls the `struct GNUNET_NAT_Test` + */ +static void +do_udp_read (void *cls) +{ + struct GNUNET_NAT_Test *tst = cls; + uint16_t data; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + tc = GNUNET_SCHEDULER_get_task_context (); + tst->ltask = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + tst->lsock, + &do_udp_read, + tst); + if ((NULL != tc->write_ready) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + tst->lsock)) && + (sizeof (data) == + GNUNET_NETWORK_socket_recv (tst->lsock, + &data, + sizeof (data)))) + { + if (data == tst->data) + tst->report (tst->report_cls, + GNUNET_NAT_ERROR_SUCCESS); + else + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received data mismatches expected value\n"); + } + else + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to receive data from inbound connection\n"); +} + + +/** + * Activity on our incoming socket. Read data from the + * incoming connection. + * + * @param cls the `struct NatActivity` + */ +static void +do_read (void *cls) +{ + struct NatActivity *na = cls; + struct GNUNET_NAT_Test *tst; + uint16_t data; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + tc = GNUNET_SCHEDULER_get_task_context (); + na->rtask = NULL; + tst = na->h; + GNUNET_CONTAINER_DLL_remove (tst->na_head, + tst->na_tail, + na); + if ((NULL != tc->write_ready) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + na->sock)) && + (sizeof (data) == + GNUNET_NETWORK_socket_recv (na->sock, + &data, + sizeof (data)))) + { + if (data == tst->data) + tst->report (tst->report_cls, + GNUNET_NAT_ERROR_SUCCESS); + else + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received data does not match expected value\n"); + } + else + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failed to receive data from inbound connection\n"); + GNUNET_NETWORK_socket_close (na->sock); + GNUNET_free (na); +} + + +/** + * Activity on our listen socket. Accept the + * incoming connection. + * + * @param cls the `struct GNUNET_NAT_Test` + */ +static void +do_accept (void *cls) +{ + struct GNUNET_NAT_Test *tst = cls; + struct GNUNET_NETWORK_Handle *s; + struct NatActivity *wl; + + tst->ltask = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + tst->lsock, + &do_accept, + tst); + s = GNUNET_NETWORK_socket_accept (tst->lsock, + NULL, + NULL); + if (NULL == s) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, + "accept"); + return; /* odd error */ + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Got an inbound connection, waiting for data\n"); + wl = GNUNET_new (struct NatActivity); + wl->sock = s; + wl->h = tst; + wl->rtask = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + wl->sock, + &do_read, + wl); + GNUNET_CONTAINER_DLL_insert (tst->na_head, + tst->na_tail, + wl); +} + + +/** + * We got disconnected from the NAT server. Stop + * waiting for a reply. + * + * @param cls the `struct ClientActivity` + * @param error error code + */ +static void +mq_error_handler (void *cls, + enum GNUNET_MQ_Error error) +{ + struct ClientActivity *ca = cls; + struct GNUNET_NAT_Test *tst = ca->h; + + GNUNET_CONTAINER_DLL_remove (tst->ca_head, + tst->ca_tail, + ca); + GNUNET_MQ_destroy (ca->mq); + GNUNET_free (ca); +} + + +/** + * Address-callback, used to send message to gnunet-nat-server. + * + * @param cls closure + * @param add_remove #GNUNET_YES to mean the new public IP address, #GNUNET_NO to mean + * the previous (now invalid) one + * @param addr either the previous or the new public IP address + * @param addrlen actual length of the @a addr + */ +static void +addr_cb (void *cls, + int add_remove, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct GNUNET_NAT_Test *h = cls; + struct ClientActivity *ca; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_NAT_TestMessage *msg; + const struct sockaddr_in *sa; + + if (GNUNET_YES != add_remove) + return; + if (addrlen != sizeof (struct sockaddr_in)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "NAT test ignores IPv6 address `%s' returned from NAT library\n", + GNUNET_a2s (addr, + addrlen)); + return; /* ignore IPv6 here */ + } + LOG (GNUNET_ERROR_TYPE_INFO, + "Asking gnunet-nat-server to connect to `%s'\n", + GNUNET_a2s (addr, + addrlen)); + + ca = GNUNET_new (struct ClientActivity); + ca->h = h; + ca->mq = GNUNET_CLIENT_connecT (h->cfg, + "gnunet-nat-server", + NULL, + &mq_error_handler, + ca); + if (NULL == ca->mq) + { + GNUNET_free (ca); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to connect to `gnunet-nat-server'\n")); + return; + } + GNUNET_CONTAINER_DLL_insert (h->ca_head, + h->ca_tail, + ca); + sa = (const struct sockaddr_in *) addr; + env = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_NAT_TEST); + msg->dst_ipv4 = sa->sin_addr.s_addr; + msg->dport = sa->sin_port; + msg->data = h->data; + msg->is_tcp = htonl ((uint32_t) h->is_tcp); + GNUNET_MQ_send (ca->mq, + env); +} + + +/** + * Timeout task for a nat test. + * Calls the report-callback with a timeout return value + * + * Destroys the nat handle after the callback has been processed. + * + * @param cls handle to the timed out NAT test + */ +static void +do_timeout (void *cls) +{ + struct GNUNET_NAT_Test *nh = cls; + + nh->ttask = NULL; + nh->report (nh->report_cls, + (GNUNET_NAT_ERROR_SUCCESS == nh->status) + ? GNUNET_NAT_ERROR_TIMEOUT + : nh->status); +} + + +/** + * Start testing if NAT traversal works using the + * given configuration (IPv4-only). + * + * ALL failures are reported directly to the report callback + * + * @param cfg configuration for the NAT traversal + * @param is_tcp #GNUNET_YES to test TCP, #GNUNET_NO to test UDP + * @param bnd_port port to bind to, 0 for connection reversal + * @param adv_port externally advertised port to use + * @param timeout delay after which the test should be aborted + * @param report function to call with the result of the test + * @param report_cls closure for @a report + * @return handle to cancel NAT test or NULL. The error is always indicated via the report callback + */ +struct GNUNET_NAT_Test * +GNUNET_NAT_test_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + int is_tcp, + uint16_t bnd_port, + uint16_t adv_port, + struct GNUNET_TIME_Relative timeout, + GNUNET_NAT_TestCallback report, + void *report_cls) +{ + struct GNUNET_NAT_Test *nh; + struct sockaddr_in sa; + const struct sockaddr *addrs[] = { + (const struct sockaddr *) &sa + }; + const socklen_t addrlens[] = { + sizeof (sa) + }; + + memset (&sa, 0, sizeof (sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons (bnd_port); +#if HAVE_SOCKADDR_IN_SIN_LEN + sa.sin_len = sizeof (sa); +#endif + + nh = GNUNET_new (struct GNUNET_NAT_Test); + nh->cfg = cfg; + nh->is_tcp = is_tcp; + nh->data = bnd_port; + nh->adv_port = adv_port; + nh->report = report; + nh->report_cls = report_cls; + nh->status = GNUNET_NAT_ERROR_SUCCESS; + if (0 == bnd_port) + { + nh->nat + = GNUNET_NAT_register (cfg, + is_tcp, + 0, + 0, + NULL, + NULL, + &addr_cb, + &reversal_cb, + nh, + NULL); + } + else + { + nh->lsock = + GNUNET_NETWORK_socket_create (AF_INET, + (is_tcp == + GNUNET_YES) ? SOCK_STREAM : SOCK_DGRAM, + 0); + if ((nh->lsock == NULL) || + (GNUNET_OK != + GNUNET_NETWORK_socket_bind (nh->lsock, + (const struct sockaddr *) &sa, + sizeof (sa)))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to create listen socket bound to `%s' for NAT test: %s\n"), + GNUNET_a2s ((const struct sockaddr *) &sa, + sizeof (sa)), + STRERROR (errno)); + if (NULL != nh->lsock) + { + GNUNET_NETWORK_socket_close (nh->lsock); + nh->lsock = NULL; + } + nh->status = GNUNET_NAT_ERROR_INTERNAL_NETWORK_ERROR; + nh->ttask = GNUNET_SCHEDULER_add_now (&do_timeout, + nh); + return nh; + } + if (GNUNET_YES == is_tcp) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_listen (nh->lsock, + 5)); + nh->ltask = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + nh->lsock, + &do_accept, + nh); + } + else + { + nh->ltask = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + nh->lsock, + &do_udp_read, + nh); + } + LOG (GNUNET_ERROR_TYPE_INFO, + "NAT test listens on port %u (%s)\n", + bnd_port, + (GNUNET_YES == is_tcp) ? "tcp" : "udp"); + nh->nat = GNUNET_NAT_register (cfg, + is_tcp, + adv_port, + 1, + addrs, + addrlens, + &addr_cb, + NULL, + nh, + NULL); + if (NULL == nh->nat) + { + LOG (GNUNET_ERROR_TYPE_INFO, + _("NAT test failed to start NAT library\n")); + if (NULL != nh->ltask) + { + GNUNET_SCHEDULER_cancel (nh->ltask); + nh->ltask = NULL; + } + if (NULL != nh->lsock) + { + GNUNET_NETWORK_socket_close (nh->lsock); + nh->lsock = NULL; + } + nh->status = GNUNET_NAT_ERROR_NAT_REGISTER_FAILED; + nh->ttask = GNUNET_SCHEDULER_add_now (&do_timeout, + nh); + return nh; + } + } + nh->ttask = GNUNET_SCHEDULER_add_delayed (timeout, + &do_timeout, + nh); + return nh; +} + + +/** + * Stop an active NAT test. + * + * @param tst test to stop. + */ +void +GNUNET_NAT_test_stop (struct GNUNET_NAT_Test *tst) +{ + struct NatActivity *pos; + struct ClientActivity *cpos; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Stopping NAT test\n"); + while (NULL != (cpos = tst->ca_head)) + { + GNUNET_CONTAINER_DLL_remove (tst->ca_head, + tst->ca_tail, + cpos); + GNUNET_MQ_destroy (cpos->mq); + GNUNET_free (cpos); + } + while (NULL != (pos = tst->na_head)) + { + GNUNET_CONTAINER_DLL_remove (tst->na_head, + tst->na_tail, + pos); + GNUNET_SCHEDULER_cancel (pos->rtask); + GNUNET_NETWORK_socket_close (pos->sock); + GNUNET_free (pos); + } + if (NULL != tst->ttask) + { + GNUNET_SCHEDULER_cancel (tst->ttask); + tst->ttask = NULL; + } + if (NULL != tst->ltask) + { + GNUNET_SCHEDULER_cancel (tst->ltask); + tst->ltask = NULL; + } + if (NULL != tst->lsock) + { + GNUNET_NETWORK_socket_close (tst->lsock); + tst->lsock = NULL; + } + if (NULL != tst->nat) + { + GNUNET_NAT_unregister (tst->nat); + tst->nat = NULL; + } + GNUNET_free (tst); +} + +/* end of nat_test.c */ -- cgit v1.2.3