X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fnat%2Fgnunet-nat-server.c;h=6722deefba0ab4716e66b71c37e017ab8df9f615;hb=2105059516320800eaa8fff1196b58f29a50ba7c;hp=ab99b5f280d7f059ee4362e11b675d0b8983e413;hpb=436f9a3345ae1f9276593ad34d8606a5de8fceeb;p=oweals%2Fgnunet.git diff --git a/src/nat/gnunet-nat-server.c b/src/nat/gnunet-nat-server.c index ab99b5f28..6722deefb 100644 --- a/src/nat/gnunet-nat-server.c +++ b/src/nat/gnunet-nat-server.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - (C) 2010 Christian Grothoff (and other contributing authors) + 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 @@ -14,637 +14,360 @@ 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. + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** - * @file src/transport/gnunet-nat-server.c - * @brief Tool to help bypass NATs using ICMP method; must run as root (SUID will do) - * This code will work under GNU/Linux only (or maybe BSDs, but never W32) + * @file src/nat/gnunet-nat-server.c + * @brief Daemon to run on 'gnunet.org' to help test NAT traversal code * @author Christian Grothoff - * - * This program will send ONE ICMP message every 500 ms RAW sockets - * to a DUMMY IP address and also listens for ICMP replies. Since - * it uses RAW sockets, it must be installed SUID or run as 'root'. - * In order to keep the security risk of the resulting SUID binary - * minimal, the program ONLY opens the two RAW sockets with root - * privileges, then drops them and only then starts to process - * command line arguments. The code also does not link against - * any shared libraries (except libc) and is strictly minimal - * (except for checking for errors). The following list of people - * have reviewed this code and considered it safe since the last - * modification (if you reviewed it, please have your name added - * to the list): - * - * - Christian Grothoff - * - Nathan Evans - * - Benjamin Kuperman (22 Aug 2010) */ -#if HAVE_CONFIG_H -/* Just needed for HAVE_SOCKADDR_IN_SIN_LEN test macro! */ -#include "gnunet_config.h" -#else -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_lib.h" +#include "gnunet_protocols.h" +#include "nat.h" -/** - * Should we print some debug output? - */ -#define VERBOSE 0 /** - * Must match IP given in the client. + * Our server. */ -#define DUMMY_IP "192.0.2.86" +static struct GNUNET_SERVER_Handle *server; /** - * Port for UDP + * Our configuration. */ -#define NAT_TRAV_PORT 22225 +static const struct GNUNET_CONFIGURATION_Handle *cfg; -/** - * How often do we send our ICMP messages to receive replies? - */ -#define ICMP_SEND_FREQUENCY_MS 500 /** - * IPv4 header. + * 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) */ -struct ip_header +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); +} - /** - * Version (4 bits) + Internet header length (4 bits) - */ - uint8_t vers_ihl; - - /** - * Type of service - */ - uint8_t tos; - - /** - * Total length - */ - uint16_t pkt_len; - - /** - * Identification - */ - uint16_t id; - - /** - * Flags (3 bits) + Fragment offset (13 bits) - */ - uint16_t flags_frag_offset; - - /** - * Time to live - */ - uint8_t ttl; - - /** - * Protocol - */ - uint8_t proto; - - /** - * Header checksum - */ - uint16_t checksum; +/** + * Closure for 'tcp_send'. + */ +struct TcpContext +{ /** - * Source address + * TCP socket. */ - uint32_t src_ip; + struct GNUNET_NETWORK_Handle *s; /** - * Destination address + * Data to transmit. */ - uint32_t dst_ip; -}; - -/** - * Format of ICMP packet. - */ -struct icmp_ttl_exceeded_header -{ - uint8_t type; - - uint8_t code; - - uint16_t checksum; - - uint32_t unused; - - /* followed by original payload */ -}; - -struct icmp_echo_header -{ - uint8_t type; - - uint8_t code; - - uint16_t checksum; - - uint32_t reserved; -}; - - -/** - * Beginning of UDP packet. - */ -struct udp_header -{ - uint16_t src_port; - - uint16_t dst_port; - - uint16_t length; - - uint16_t crc; + uint16_t data; }; -/** - * Socket we use to receive "fake" ICMP replies. - */ -static int icmpsock; - -/** - * Socket we use to send our ICMP requests. - */ -static int rawsock; - -/** - * Socket we use to send our UDP requests. - */ -static int udpsock; - -/** - * Target "dummy" address. - */ -static struct in_addr dummy; - /** - * CRC-16 for IP/ICMP headers. + * Task called by the scheduler once we can do the TCP send + * (or once we failed to connect...). * - * @param data what to calculate the CRC over - * @param bytes number of bytes in data (must be multiple of 2) - * @return the CRC 16. + * @param cls the 'struct TcpContext' */ -static uint16_t -calc_checksum(const uint16_t *data, - unsigned int bytes) +static void +tcp_send (void *cls) { - uint32_t sum; - unsigned int i; - - sum = 0; - for (i=0;i> 16); - sum = htons(0xffff - sum); - return sum; + 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); } /** - * Send an ICMP message to the dummy IP. + * Try to send @a data to the + * IP @a dst_ipv4' at port @a dport via TCP. * - * @param my_ip source address (our ip address) + * @param dst_ipv4 target IP + * @param dport target port + * @param data data to send */ static void -send_icmp_echo (const struct in_addr *my_ip) +try_send_tcp (uint32_t dst_ipv4, + uint16_t dport, + uint16_t data) { - char packet[sizeof (struct ip_header) + sizeof (struct icmp_echo_header)]; - struct icmp_echo_header icmp_echo; - struct ip_header ip_pkt; - struct sockaddr_in dst; - size_t off; - int err; - - off = 0; - ip_pkt.vers_ihl = 0x45; - ip_pkt.tos = 0; - ip_pkt.pkt_len = htons (sizeof (packet)); - ip_pkt.id = htons (256); - ip_pkt.flags_frag_offset = 0; - ip_pkt.ttl = IPDEFTTL; - ip_pkt.proto = IPPROTO_ICMP; - ip_pkt.checksum = 0; - ip_pkt.src_ip = my_ip->s_addr; - ip_pkt.dst_ip = dummy.s_addr; - ip_pkt.checksum = htons(calc_checksum((uint16_t*)&ip_pkt, - sizeof (struct ip_header))); - memcpy (&packet[off], - &ip_pkt, - sizeof (struct ip_header)); - off += sizeof (struct ip_header); - - icmp_echo.type = ICMP_ECHO; - icmp_echo.code = 0; - icmp_echo.checksum = 0; - icmp_echo.reserved = 0; - icmp_echo.checksum = htons(calc_checksum((uint16_t*)&icmp_echo, - sizeof (struct icmp_echo_header))); - memcpy (&packet[off], - &icmp_echo, - sizeof (struct icmp_echo_header)); - off += sizeof (struct icmp_echo_header); - - memset (&dst, 0, sizeof (dst)); - dst.sin_family = AF_INET; + 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 - dst.sin_len = sizeof (struct sockaddr_in); -#endif - dst.sin_addr = dummy; - err = sendto(rawsock, - packet, off, 0, - (struct sockaddr*)&dst, - sizeof(dst)); - if (err < 0) - { -#if VERBOSE - fprintf(stderr, - "sendto failed: %s\n", strerror(errno)); + sa.sin_len = sizeof (sa); #endif - } - else if (sizeof (packet) != err) - { - fprintf(stderr, - "Error: partial send of ICMP message\n"); - } + 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); } /** - * Send a UDP message to the dummy IP. + * 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 -send_udp () +try_send_udp (uint32_t dst_ipv4, + uint16_t dport, + uint16_t data) { - struct sockaddr_in dst; - ssize_t err; - - memset (&dst, 0, sizeof (dst)); - dst.sin_family = AF_INET; + 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 - dst.sin_len = sizeof (struct sockaddr_in); -#endif - dst.sin_addr = dummy; - dst.sin_port = htons (NAT_TRAV_PORT); - err = sendto(udpsock, - NULL, 0, 0, - (struct sockaddr*)&dst, - sizeof(dst)); - if (err < 0) - { -#if VERBOSE - fprintf(stderr, - "sendto failed: %s\n", strerror(errno)); + sa.sin_len = sizeof (sa); #endif - } - else if (0 != err) - { - fprintf(stderr, - "Error: partial send of ICMP message\n"); - } + 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 an ICMP response. Process it. + * 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 -process_icmp_response () +test (void *cls, + struct GNUNET_SERVER_Client *client, + const struct GNUNET_MessageHeader *msg) { - char buf[65536]; - ssize_t have; - struct in_addr source_ip; - struct ip_header ip_pkt; - struct icmp_ttl_exceeded_header icmp_ttl; - struct icmp_echo_header icmp_echo; - struct udp_header udp_pkt; - size_t off; - uint16_t port; - - have = read (icmpsock, buf, sizeof (buf)); - if (-1 == have) - { - fprintf (stderr, - "Error reading raw socket: %s\n", - strerror (errno)); - return; - } -#if VERBOSE - fprintf (stderr, - "Received message of %u bytes\n", - (unsigned int) have); -#endif - if (have < (ssize_t) (sizeof (struct ip_header) + sizeof (struct icmp_ttl_exceeded_header) + sizeof (struct ip_header))) - { - /* malformed */ - return; - } - off = 0; - memcpy (&ip_pkt, - &buf[off], - sizeof (struct ip_header)); - off += sizeof (struct ip_header); - memcpy(&source_ip, - &ip_pkt.src_ip, - sizeof (source_ip)); - memcpy (&icmp_ttl, - &buf[off], - sizeof (struct icmp_ttl_exceeded_header)); - off += sizeof (struct icmp_ttl_exceeded_header); - if ( (ICMP_TIME_EXCEEDED != icmp_ttl.type) || - (0 != icmp_ttl.code) ) - { - /* different type than what we want */ - return; - } - /* skip 2nd IP header */ - memcpy (&ip_pkt, - &buf[off], - sizeof (struct ip_header)); - off += sizeof (struct ip_header); - - switch (ip_pkt.proto) - { - case IPPROTO_ICMP: - if (have != (sizeof (struct ip_header) * 2 + - sizeof (struct icmp_ttl_exceeded_header) + - sizeof (struct icmp_echo_header)) ) - { - /* malformed */ - return; - } - /* grab ICMP ECHO content */ - memcpy (&icmp_echo, - &buf[off], - sizeof (struct icmp_echo_header)); - port = (uint16_t) ntohl (icmp_echo.reserved); - break; - case IPPROTO_UDP: - if (have != (sizeof (struct ip_header) * 2 + - sizeof (struct icmp_ttl_exceeded_header) + - sizeof (struct udp_header)) ) - { - /* malformed */ - return; - } - /* grab UDP content */ - memcpy (&udp_pkt, - &buf[off], - sizeof (struct udp_header)); - port = ntohs (udp_pkt.length); - break; - default: - /* different type than what we want */ - return; - } - - if (port == 0) - fprintf (stdout, - "%s\n", - inet_ntop (AF_INET, - &source_ip, - buf, - sizeof (buf))); + 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 - fprintf (stdout, - "%s:%u\n", - inet_ntop (AF_INET, - &source_ip, - buf, - sizeof (buf)), - (unsigned int) port); - fflush (stdout); + try_send_udp (tm->dst_ipv4, + dport, + tm->data); + GNUNET_SERVER_receive_done (client, + GNUNET_NO); } /** - * Create an ICMP raw socket for reading. + * Task run during shutdown. * - * @return -1 on error + * @param cls unused */ -static int -make_icmp_socket () +static void +shutdown_task (void *cls) { - int ret; - - ret = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (-1 == ret) - { - fprintf (stderr, - "Error opening RAW socket: %s\n", - strerror (errno)); - return -1; - } - if (ret >= FD_SETSIZE) - { - fprintf (stderr, - "Socket number too large (%d > %u)\n", - ret, - (unsigned int) FD_SETSIZE); - close (ret); - return -1; - } - return ret; + GNUNET_SERVER_destroy (server); + server = NULL; } /** - * Create an ICMP raw socket for writing. + * Main function that will be run. * - * @return -1 on error + * @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 int -make_raw_socket () +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) { - const int one = 1; - int ret; - - ret = socket (AF_INET, SOCK_RAW, IPPROTO_RAW); - if (-1 == ret) - { - fprintf (stderr, - "Error opening RAW socket: %s\n", - strerror (errno)); - return -1; - } - if (-1 == setsockopt(ret, - SOL_SOCKET, - SO_BROADCAST, - (char *)&one, sizeof(one))) - { - fprintf(stderr, - "setsockopt failed: %s\n", - strerror (errno)); - close (ret); - return -1; - } - if (-1 == setsockopt(ret, - IPPROTO_IP, - IP_HDRINCL, - (char *)&one, sizeof(one))) - { - fprintf(stderr, - "setsockopt failed: %s\n", - strerror (errno)); - close (ret); - return -1; - } - return ret; + 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 ((args[0] == NULL) || (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); } /** - * Create a UDP socket for writinging. + * Main function of gnunet-nat-server. * - * @param my_ip source address (our ip address) - * @return -1 on error + * @param argc number of command-line arguments + * @param argv command line + * @return 0 on success, -1 on error */ -static int -make_udp_socket (const struct in_addr *my_ip) -{ - int ret; - struct sockaddr_in addr; - - ret = socket (AF_INET, SOCK_DGRAM, 0); - if (-1 == ret) - { - fprintf (stderr, - "Error opening UDP socket: %s\n", - strerror (errno)); - return -1; - } - memset (&addr, - 0, - sizeof (addr)); - addr.sin_family = AF_INET; -#if HAVE_SOCKADDR_IN_SIN_LEN - addr.sin_len = sizeof (struct sockaddr_in); -#endif - addr.sin_addr = *my_ip; - addr.sin_port = htons (NAT_TRAV_PORT); - - if (0 != bind (ret, - &addr, - sizeof(addr))) - { - fprintf (stderr, - "Error binding UDP socket to port %u: %s\n", - NAT_TRAV_PORT, - strerror (errno)); - /* likely problematic, but not certain, try to continue */ - } - return ret; -} - - int -main (int argc, - char *const *argv) +main (int argc, char *const argv[]) { - struct in_addr external; - fd_set rs; - struct timeval tv; - uid_t uid; - unsigned int alt; - - if (2 != argc) - { - fprintf (stderr, - "This program must be started with our (internal NAT) IP as the only argument.\n"); - return 1; - } - if (1 != inet_pton (AF_INET, argv[1], &external)) - { - fprintf (stderr, - "Error parsing IPv4 address: %s\n", - strerror (errno)); - return 1; - } - if (1 != inet_pton (AF_INET, DUMMY_IP, &dummy)) - { - fprintf (stderr, - "Internal error converting dummy IP to binary.\n"); - return 2; - } - if (-1 == (icmpsock = make_icmp_socket())) - { - return 3; - } - if (-1 == (rawsock = make_raw_socket())) - { - close (icmpsock); - return 3; - } - uid = getuid (); - if (0 != setresuid (uid, uid, uid)) - { - fprintf (stderr, - "Failed to setresuid: %s\n", - strerror (errno)); - /* not critical, continue anyway */ - } - if (-1 == (udpsock = make_udp_socket(&external))) - { - close (icmpsock); - close (rawsock); - return 3; - } - alt = 0; - while (1) - { - FD_ZERO (&rs); - FD_SET (icmpsock, &rs); - tv.tv_sec = 0; - tv.tv_usec = ICMP_SEND_FREQUENCY_MS * 1000; - if (-1 == select (icmpsock + 1, &rs, NULL, NULL, &tv)) - { - if (errno == EINTR) - continue; - fprintf (stderr, - "select failed: %s\n", - strerror (errno)); - break; - } - if (1 == getppid()) /* Check the parent process id, if 1 the parent has died, so we should die too */ - break; - if (FD_ISSET (icmpsock, &rs)) - process_icmp_response (); - if (0 == (++alt % 2)) - send_icmp_echo (&external); - else - send_udp (); - } - /* select failed (internal error or OS out of resources) */ - close (icmpsock); - close (rawsock); - close (udpsock); - return 4; + 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; }