From 545faeda8f7fb7e03bb39602df6142f630157050 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 17 Dec 2016 08:00:29 +0100 Subject: misc. improvements to new NAT service, starting with autoconfiguration logic --- src/include/gnunet_nat_service.h | 2 +- src/nat/Makefile.am | 1 + src/nat/gnunet-nat.c | 2 +- src/nat/gnunet-service-nat.c | 1133 ++++++++++++++++++++++++------------- src/nat/gnunet-service-nat_mini.c | 759 +++++++++++++++++++++++++ src/nat/gnunet-service-nat_mini.h | 127 +++++ src/nat/nat.h | 2 +- src/nat/nat_api.c | 7 + src/util/util.conf | 10 + 9 files changed, 1649 insertions(+), 394 deletions(-) create mode 100644 src/nat/gnunet-service-nat_mini.c create mode 100644 src/nat/gnunet-service-nat_mini.h (limited to 'src') diff --git a/src/include/gnunet_nat_service.h b/src/include/gnunet_nat_service.h index b66b44240..a42d1d7e6 100644 --- a/src/include/gnunet_nat_service.h +++ b/src/include/gnunet_nat_service.h @@ -83,7 +83,7 @@ enum GNUNET_NAT_AddressClass /** * Addresses useful in the local wired network, * i.e. a MAC. Sensitive, but obvious to people nearby. - + * * Useful for broadcasts. */ GNUNET_NAT_AC_LAN = 8, diff --git a/src/nat/Makefile.am b/src/nat/Makefile.am index c62a8d2cf..1dd8e44b9 100644 --- a/src/nat/Makefile.am +++ b/src/nat/Makefile.am @@ -98,6 +98,7 @@ libgnunetnatnew_la_LDFLAGS = \ gnunet_service_nat_SOURCES = \ gnunet-service-nat.c \ gnunet-service-nat_stun.c gnunet-service-nat_stun.h \ + gnunet-service-nat_mini.c gnunet-service-nat_mini.h \ gnunet-service-nat_helper.c gnunet-service-nat_helper.h gnunet_service_nat_LDADD = \ $(top_builddir)/src/util/libgnunetutil.la \ diff --git a/src/nat/gnunet-nat.c b/src/nat/gnunet-nat.c index 10921150d..7d10167ce 100644 --- a/src/nat/gnunet-nat.c +++ b/src/nat/gnunet-nat.c @@ -611,7 +611,7 @@ main (int argc, gettext_noop ("which external IP and port should be used to test"), GNUNET_YES, &GNUNET_GETOPT_set_string, &extern_addr }, {'i', "in", "ADDRESS", - gettext_noop ("which IP and port are we locally using to listen to for connection reversals"), + gettext_noop ("which IP and port are we locally using to bind/listen to"), GNUNET_YES, &GNUNET_GETOPT_set_string, &local_addr }, {'r', "remote", "ADDRESS", gettext_noop ("which remote IP and port should be asked for connection reversal"), diff --git a/src/nat/gnunet-service-nat.c b/src/nat/gnunet-service-nat.c index 4ad6c8d2c..af1219dc0 100644 --- a/src/nat/gnunet-service-nat.c +++ b/src/nat/gnunet-service-nat.c @@ -40,6 +40,7 @@ #include "gnunet_statistics_service.h" #include "gnunet_nat_service.h" #include "gnunet-service-nat_stun.h" +#include "gnunet-service-nat_mini.h" #include "gnunet-service-nat_helper.h" #include "nat.h" #include @@ -51,6 +52,11 @@ */ #define SCAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15) +/** + * 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. @@ -186,6 +192,75 @@ struct StunExternalIP }; +/** + * 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; + + /** + * Timeout task to force termination. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; + + /** + * What type of system are we on? + */ + char *system_type; + + /** + * Handle to activity to probe for our external IP. + */ + struct GNUNET_NAT_ExternalHandle *probe_external; + + /** + * #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; +}; + + +/** + * DLL of our autoconfiguration operations. + */ +static struct AutoconfigContext *ac_head; + +/** + * DLL of our autoconfiguration operations. + */ +static struct AutoconfigContext *ac_tail; + /** * Timeout to use when STUN data is considered stale. */ @@ -236,6 +311,12 @@ static struct StunExternalIP *se_head; */ static struct StunExternalIP *se_tail; +/** + * Is UPnP enabled? #GNUNET_YES if enabled, #GNUNET_NO if disabled, + * #GNUNET_SYSERR if configuration enabled but binary is unavailable. + */ +static int enable_upnp; + /** * Free the DLL starting at #lal_head. @@ -322,7 +403,9 @@ match_ipv4 (const char *network, uint8_t bits) { struct in_addr net; - + + if (0 == ip->s_addr) + return GNUNET_YES; if (0 == bits) return GNUNET_YES; GNUNET_assert (1 == inet_pton (AF_INET, @@ -355,6 +438,10 @@ match_ipv6 (const char *network, network, &net)); memset (&mask, 0, sizeof (mask)); + if (0 == memcmp (&mask, + ip, + sizeof (mask))) + return GNUNET_YES; off = 0; while (bits > 8) { @@ -411,145 +498,444 @@ is_nat_v6 (const struct in6_addr *ip) /** - * Handler for #GNUNET_MESSAGE_TYPE_NAT_REGISTER message from client. - * We remember the client for updates upon future NAT events. - * - * @param cls client who sent the message - * @param message the message received + * Closure for #ifc_proc. */ -static void -handle_register (void *cls, - const struct GNUNET_NAT_RegisterMessage *message) +struct IfcProcContext { - struct ClientHandle *ch = cls; - const char *off; - size_t left; - if ( (0 != ch->proto) || - (NULL != ch->addrs) ) - { - /* double registration not allowed */ - GNUNET_break (0); - GNUNET_SERVICE_client_drop (ch->client); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Received REGISTER message from client\n"); - ch->flags = message->flags; - ch->proto = message->proto; - ch->adv_port = ntohs (message->adv_port); - ch->num_addrs = ntohs (message->adv_port); - ch->addrs = GNUNET_new_array (ch->num_addrs, - struct sockaddr *); - left = ntohs (message->header.size) - sizeof (*message); - off = (const char *) &message[1]; - for (unsigned int i=0;inum_addrs;i++) - { - size_t alen; - const struct sockaddr *sa = (const struct sockaddr *) off; + /** + * Head of DLL of local addresses. + */ + struct LocalAddressList *lal_head; - if (sizeof (sa_family_t) > left) - { - GNUNET_break (0); - GNUNET_SERVICE_client_drop (ch->client); - return; - } - switch (sa->sa_family) - { - case AF_INET: - { - const struct sockaddr_in *s4 = (const struct sockaddr_in *) sa; - - alen = sizeof (struct sockaddr_in); - if (is_nat_v4 (&s4->sin_addr)) - ch->natted_address = GNUNET_YES; - } - break; - case AF_INET6: - { - const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *) sa; - - alen = sizeof (struct sockaddr_in6); - if (is_nat_v6 (&s6->sin6_addr)) - ch->natted_address = GNUNET_YES; - } - break; -#if AF_UNIX - case AF_UNIX: - alen = sizeof (struct sockaddr_un); - break; -#endif - default: - GNUNET_break (0); - GNUNET_SERVICE_client_drop (ch->client); - return; - } - GNUNET_assert (alen <= left); - ch->addrs[i] = GNUNET_malloc (alen); - GNUNET_memcpy (ch->addrs[i], - sa, - alen); - off += alen; - } - GNUNET_SERVICE_client_continue (ch->client); -} + /** + * Tail of DLL of local addresses. + */ + struct LocalAddressList *lal_tail; + +}; /** - * Check validity of #GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN message from - * client. + * Callback function invoked for each interface found. Adds them + * to our new address list. * - * @param cls client who sent the message - * @param message the message received - * @return #GNUNET_OK if message is well-formed + * @param cls a `struct IfcProcContext *` + * @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 -check_stun (void *cls, - const struct GNUNET_NAT_HandleStunMessage *message) +ifc_proc (void *cls, + const char *name, + int isDefault, + const struct sockaddr *addr, + const struct sockaddr *broadcast_addr, + const struct sockaddr *netmask, + socklen_t addrlen) { - size_t sa_len = ntohs (message->sender_addr_size); - size_t expect = sa_len + ntohs (message->payload_size); - - if (ntohs (message->header.size) - sizeof (*message) != expect) + struct IfcProcContext *ifc_ctx = cls; + struct LocalAddressList *lal; + size_t alen; + const struct in_addr *ip4; + const struct in6_addr *ip6; + enum GNUNET_NAT_AddressClass ac; + + switch (addr->sa_family) { + case AF_INET: + alen = sizeof (struct sockaddr_in); + ip4 = &((const struct sockaddr_in *) addr)->sin_addr; + if (match_ipv4 ("127.0.0.0", ip4, 8)) + ac = GNUNET_NAT_AC_LOOPBACK; + else if (is_nat_v4 (ip4)) + ac = GNUNET_NAT_AC_LAN; + else + ac = GNUNET_NAT_AC_GLOBAL; + break; + case AF_INET6: + alen = sizeof (struct sockaddr_in6); + ip6 = &((const struct sockaddr_in6 *) addr)->sin6_addr; + if (match_ipv6 ("::1", ip6, 128)) + ac = GNUNET_NAT_AC_LOOPBACK; + else if (is_nat_v6 (ip6)) + ac = GNUNET_NAT_AC_LAN; + else + ac = GNUNET_NAT_AC_GLOBAL; + if ( (ip6->s6_addr[11] == 0xFF) && + (ip6->s6_addr[12] == 0xFE) ) + { + /* contains a MAC, be extra careful! */ + ac |= GNUNET_NAT_AC_PRIVATE; + } + break; +#if AF_UNIX + case AF_UNIX: GNUNET_break (0); - return GNUNET_SYSERR; - } - if (sa_len < sizeof (sa_family_t)) - { + return GNUNET_OK; +#endif + default: GNUNET_break (0); - return GNUNET_SYSERR; + return GNUNET_OK; } + lal = GNUNET_malloc (sizeof (*lal)); + lal->af = addr->sa_family; + lal->ac = ac; + GNUNET_memcpy (&lal->addr, + addr, + alen); + GNUNET_CONTAINER_DLL_insert (ifc_ctx->lal_head, + ifc_ctx->lal_tail, + lal); return GNUNET_OK; } /** - * Notify all clients about our external IP address - * as reported by the STUN server. + * Notify client about a change in the list of addresses this peer + * has. * - * @param ip the external IP + * @param delta the entry in the list that changed + * @param ch client to contact * @param add #GNUNET_YES to add, #GNUNET_NO to remove + * @param addr the address that changed + * @param addr_len number of bytes in @a addr */ static void -notify_clients_stun_change (const struct sockaddr_in *ip, - int add) +notify_client (struct LocalAddressList *delta, + struct ClientHandle *ch, + int add, + const void *addr, + size_t addr_len) { - for (struct ClientHandle *ch = ch_head; - NULL != ch; - ch = ch->next) - { - struct sockaddr_in v4; - struct GNUNET_NAT_AddressChangeNotificationMessage *msg; - struct GNUNET_MQ_Envelope *env; - - if (! ch->natted_address) - continue; - v4 = *ip; - v4.sin_port = htons (ch->adv_port); - env = GNUNET_MQ_msg_extra (msg, - sizeof (v4), - GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE); + struct GNUNET_MQ_Envelope *env; + struct GNUNET_NAT_AddressChangeNotificationMessage *msg; + + env = GNUNET_MQ_msg_extra (msg, + addr_len, + GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE); + msg->add_remove = htonl (add); + msg->addr_class = htonl (delta->ac); + GNUNET_memcpy (&msg[1], + addr, + addr_len); + GNUNET_MQ_send (ch->mq, + env); +} + + +/** + * Check if we should bother to notify this client about this + * address change, and if so, do it. + * + * @param delta the entry in the list that changed + * @param ch client to check + * @param add #GNUNET_YES to add, #GNUNET_NO to remove + */ +static void +check_notify_client (struct LocalAddressList *delta, + struct ClientHandle *ch, + int add) +{ + size_t alen; + struct sockaddr_in v4; + struct sockaddr_in6 v6; + + if (0 == (ch->flags & GNUNET_NAT_RF_ADDRESSES)) + return; + switch (delta->af) + { + case AF_INET: + alen = sizeof (struct sockaddr_in); + GNUNET_memcpy (&v4, + &delta->addr, + alen); + for (unsigned int i=0;inum_addrs;i++) + { + const struct sockaddr_in *c4; + + if (AF_INET != ch->addrs[i]->sa_family) + return; /* IPv4 not relevant */ + c4 = (const struct sockaddr_in *) ch->addrs[i]; + v4.sin_port = c4->sin_port; + notify_client (delta, + ch, + add, + &v4, + alen); + } + break; + case AF_INET6: + alen = sizeof (struct sockaddr_in6); + GNUNET_memcpy (&v6, + &delta->addr, + alen); + for (unsigned int i=0;inum_addrs;i++) + { + const struct sockaddr_in6 *c6; + + if (AF_INET6 != ch->addrs[i]->sa_family) + return; /* IPv4 not relevant */ + c6 = (const struct sockaddr_in6 *) ch->addrs[i]; + v6.sin6_port = c6->sin6_port; + notify_client (delta, + ch, + add, + &v6, + alen); + } + break; + default: + GNUNET_break (0); + return; + } +} + + +/** + * Notify all clients about a change in the list + * of addresses this peer has. + * + * @param delta the entry in the list that changed + * @param add #GNUNET_YES to add, #GNUNET_NO to remove + */ +static void +notify_clients (struct LocalAddressList *delta, + int add) +{ + for (struct ClientHandle *ch = ch_head; + NULL != ch; + ch = ch->next) + check_notify_client (delta, + ch, + add); +} + + +/** + * Task we run periodically to scan for network interfaces. + * + * @param cls NULL + */ +static void +run_scan (void *cls) +{ + struct IfcProcContext ifc_ctx; + int found; + + scan_task = GNUNET_SCHEDULER_add_delayed (SCAN_FREQ, + &run_scan, + NULL); + memset (&ifc_ctx, + 0, + sizeof (ifc_ctx)); + GNUNET_OS_network_interfaces_list (&ifc_proc, + &ifc_ctx); + /* remove addresses that disappeared */ + for (struct LocalAddressList *lal = lal_head; + NULL != lal; + lal = lal->next) + { + found = GNUNET_NO; + for (struct LocalAddressList *pos = ifc_ctx.lal_head; + NULL != pos; + pos = pos->next) + { + if ( (pos->af == lal->af) && + (0 == memcmp (&lal->addr, + &pos->addr, + (AF_INET == lal->af) + ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6))) ) + found = GNUNET_YES; + } + if (GNUNET_NO == found) + notify_clients (lal, + GNUNET_NO); + } + + /* add addresses that appeared */ + for (struct LocalAddressList *pos = ifc_ctx.lal_head; + NULL != pos; + pos = pos->next) + { + found = GNUNET_NO; + for (struct LocalAddressList *lal = lal_head; + NULL != lal; + lal = lal->next) + { + if ( (pos->af == lal->af) && + (0 == memcmp (&lal->addr, + &pos->addr, + (AF_INET == lal->af) + ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6))) ) + found = GNUNET_YES; + } + if (GNUNET_NO == found) + notify_clients (pos, + GNUNET_YES); + } + + destroy_lal (); + lal_head = ifc_ctx.lal_head; + lal_tail = ifc_ctx.lal_tail; +} + + +/** + * Handler for #GNUNET_MESSAGE_TYPE_NAT_REGISTER message from client. + * We remember the client for updates upon future NAT events. + * + * @param cls client who sent the message + * @param message the message received + */ +static void +handle_register (void *cls, + const struct GNUNET_NAT_RegisterMessage *message) +{ + struct ClientHandle *ch = cls; + const char *off; + size_t left; + + if ( (0 != ch->proto) || + (NULL != ch->addrs) ) + { + /* double registration not allowed */ + GNUNET_break (0); + GNUNET_SERVICE_client_drop (ch->client); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received REGISTER message from client\n"); + ch->flags = message->flags; + ch->proto = message->proto; + ch->adv_port = ntohs (message->adv_port); + ch->num_addrs = ntohs (message->num_addrs); + ch->addrs = GNUNET_new_array (ch->num_addrs, + struct sockaddr *); + left = ntohs (message->header.size) - sizeof (*message); + off = (const char *) &message[1]; + for (unsigned int i=0;inum_addrs;i++) + { + size_t alen; + const struct sockaddr *sa = (const struct sockaddr *) off; + + if (sizeof (sa_family_t) > left) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (ch->client); + return; + } + switch (sa->sa_family) + { + case AF_INET: + { + const struct sockaddr_in *s4 = (const struct sockaddr_in *) sa; + + alen = sizeof (struct sockaddr_in); + if (is_nat_v4 (&s4->sin_addr)) + ch->natted_address = GNUNET_YES; + } + break; + case AF_INET6: + { + const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *) sa; + + alen = sizeof (struct sockaddr_in6); + if (is_nat_v6 (&s6->sin6_addr)) + ch->natted_address = GNUNET_YES; + } + break; +#if AF_UNIX + case AF_UNIX: + alen = sizeof (struct sockaddr_un); + break; +#endif + default: + GNUNET_break (0); + GNUNET_SERVICE_client_drop (ch->client); + return; + } + GNUNET_assert (alen <= left); + ch->addrs[i] = GNUNET_malloc (alen); + GNUNET_memcpy (ch->addrs[i], + sa, + alen); + off += alen; + } + /* Actually send IP address list to client */ + for (struct LocalAddressList *lal = lal_head; + NULL != lal; + lal = lal->next) + { + check_notify_client (lal, + ch, + GNUNET_YES); + } + GNUNET_SERVICE_client_continue (ch->client); +} + + +/** + * Check validity of #GNUNET_MESSAGE_TYPE_NAT_HANDLE_STUN 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_stun (void *cls, + const struct GNUNET_NAT_HandleStunMessage *message) +{ + size_t sa_len = ntohs (message->sender_addr_size); + size_t expect = sa_len + ntohs (message->payload_size); + + if (ntohs (message->header.size) - sizeof (*message) != expect) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (sa_len < sizeof (sa_family_t)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Notify all clients about our external IP address + * as reported by the STUN server. + * + * @param ip the external IP + * @param add #GNUNET_YES to add, #GNUNET_NO to remove + */ +static void +notify_clients_stun_change (const struct sockaddr_in *ip, + int add) +{ + for (struct ClientHandle *ch = ch_head; + NULL != ch; + ch = ch->next) + { + struct sockaddr_in v4; + struct GNUNET_NAT_AddressChangeNotificationMessage *msg; + struct GNUNET_MQ_Envelope *env; + + if (! ch->natted_address) + continue; + v4 = *ip; + v4.sin_port = htons (ch->adv_port); + env = GNUNET_MQ_msg_extra (msg, + sizeof (v4), + GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE); msg->add_remove = htonl ((int32_t) add); msg->addr_class = htonl (GNUNET_NAT_AC_GLOBAL_EXTERN | GNUNET_NAT_AC_GLOBAL); @@ -826,344 +1212,289 @@ check_autoconfig_request (void *cls, /** - * Handler for #GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG message from - * client. + * Stop all pending activities with respect to the @a ac * - * @param cls client who sent the message - * @param message the message received + * @param ac autoconfiguration to terminate activities for */ static void -handle_autoconfig_request (void *cls, - const struct GNUNET_NAT_AutoconfigRequestMessage *message) +terminate_ac_activities (struct AutoconfigContext *ac) { - struct ClientHandle *ch = cls; - size_t left = ntohs (message->header.size) - sizeof (*message); - struct GNUNET_CONFIGURATION_Handle *c; - - c = GNUNET_CONFIGURATION_create (); - if (GNUNET_OK != - GNUNET_CONFIGURATION_deserialize (c, - (const char *) &message[1], - left, - GNUNET_NO)) + if (NULL != ac->probe_external) { - GNUNET_break (0); - GNUNET_SERVICE_client_drop (ch->client); - return; + GNUNET_NAT_mini_get_external_ipv4_cancel_ (ac->probe_external); + ac->probe_external = NULL; } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Received REQUEST_AUTO_CONFIG message from client\n"); - // FIXME: actually handle request... - GNUNET_CONFIGURATION_destroy (c); - GNUNET_SERVICE_client_continue (ch->client); -} - - -/** - * Task run during shutdown. - * - * @param cls unused - */ -static void -shutdown_task (void *cls) -{ - struct StunExternalIP *se; - - while (NULL != (se = se_head)) - { - GNUNET_CONTAINER_DLL_remove (se_head, - se_tail, - se); - GNUNET_SCHEDULER_cancel (se->timeout_task); - GNUNET_free (se); - } - if (NULL != scan_task) - { - GNUNET_SCHEDULER_cancel (scan_task); - scan_task = NULL; - } - if (NULL != stats) + if (NULL != ac->timeout_task) { - GNUNET_STATISTICS_destroy (stats, GNUNET_NO); - stats = NULL; + GNUNET_SCHEDULER_cancel (ac->timeout_task); + ac->timeout_task = NULL; } - destroy_lal (); } /** - * Closure for #ifc_proc. + * Finish handling the autoconfiguration request and send + * the response to the client. + * + * @param cls the `struct AutoconfigContext` to conclude */ -struct IfcProcContext +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; + + ac->timeout_task = NULL; + terminate_ac_activities (ac); + + /* Send back response */ + buf = GNUNET_CONFIGURATION_serialize (ac->c, + &c_size); + 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); - /** - * Head of DLL of local addresses. - */ - struct LocalAddressList *lal_head; - - /** - * Tail of DLL of local addresses. - */ - struct LocalAddressList *lal_tail; - -}; + /* clean up */ + GNUNET_free (ac->system_type); + GNUNET_CONFIGURATION_destroy (ac->c); + GNUNET_CONTAINER_DLL_remove (ac_head, + ac_tail, + ac); + GNUNET_free (ac); + GNUNET_SERVICE_client_continue (ch->client); +} /** - * Callback function invoked for each interface found. Adds them - * to our new address list. + * Check if all autoconfiguration operations have concluded, + * and if they have, send the result back to the client. * - * @param cls a `struct IfcProcContext *` - * @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 + * @param ac autoconfiguation context to check */ -static int -ifc_proc (void *cls, - const char *name, - int isDefault, - const struct sockaddr *addr, - const struct sockaddr *broadcast_addr, - const struct sockaddr *netmask, - socklen_t addrlen) +static void +check_autoconfig_finished (struct AutoconfigContext *ac) { - struct IfcProcContext *ifc_ctx = cls; - struct LocalAddressList *lal; - size_t alen; - const struct in_addr *ip4; - const struct in6_addr *ip6; - enum GNUNET_NAT_AddressClass ac; + if (NULL != ac->probe_external) + return; + GNUNET_SCHEDULER_cancel (ac->timeout_task); + ac->timeout_task + = GNUNET_SCHEDULER_add_now (&conclude_autoconfig_request, + ac); +} - switch (addr->sa_family) + +/** + * Update ENABLE_UPNPC configuration option. + * + * @param ac autoconfiguration to update + */ +static void +update_enable_upnpc_option (struct AutoconfigContext *ac) +{ + switch (ac->enable_upnpc) { - case AF_INET: - alen = sizeof (struct sockaddr_in); - ip4 = &((const struct sockaddr_in *) addr)->sin_addr; - if (match_ipv4 ("127.0.0.0", ip4, 8)) - ac = GNUNET_NAT_AC_LOOPBACK; - else if (is_nat_v4 (ip4)) - ac = GNUNET_NAT_AC_LAN; - else - ac = GNUNET_NAT_AC_GLOBAL; + case GNUNET_YES: + GNUNET_CONFIGURATION_set_value_string (ac->c, + "NAT", + "ENABLE_UPNPC", + "YES"); break; - case AF_INET6: - alen = sizeof (struct sockaddr_in6); - ip6 = &((const struct sockaddr_in6 *) addr)->sin6_addr; - if (match_ipv6 ("::1", ip6, 128)) - ac = GNUNET_NAT_AC_LOOPBACK; - else if (is_nat_v6 (ip6)) - ac = GNUNET_NAT_AC_LAN; - else - ac = GNUNET_NAT_AC_GLOBAL; - if ( (ip6->s6_addr[11] == 0xFF) && - (ip6->s6_addr[12] == 0xFE) ) - { - /* contains a MAC, be extra careful! */ - ac |= GNUNET_NAT_AC_PRIVATE; - } + case GNUNET_NO: + GNUNET_CONFIGURATION_set_value_string (ac->c, + "NAT", + "ENABLE_UPNPC", + "NO"); + break; + case GNUNET_SYSERR: + /* We are unsure, do not change option */ break; -#if AF_UNIX - case AF_UNIX: - GNUNET_break (0); - return GNUNET_OK; -#endif - default: - GNUNET_break (0); - return GNUNET_OK; } - lal = GNUNET_malloc (sizeof (*lal)); - lal->af = addr->sa_family; - lal->ac = ac; - GNUNET_memcpy (&lal->addr, - addr, - alen); - GNUNET_CONTAINER_DLL_insert (ifc_ctx->lal_head, - ifc_ctx->lal_tail, - lal); - return GNUNET_OK; } /** - * Notify client about a change in the list - * of addresses this peer has. + * Handle result from external IP address probe during + * autoconfiguration. * - * @param delta the entry in the list that changed - * @param ch client to contact - * @param add #GNUNET_YES to add, #GNUNET_NO to remove - * @param addr the address that changed - * @param addr_len number of bytes in @a addr + * @param cls our `struct AutoconfigContext` + * @param addr the address, NULL on errors + * @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code */ static void -notify_client (struct LocalAddressList *delta, - struct ClientHandle *ch, - int add, - const void *addr, - size_t addr_len) +auto_external_result_cb (void *cls, + const struct in_addr *addr, + enum GNUNET_NAT_StatusCode result) { - struct GNUNET_MQ_Envelope *env; - struct GNUNET_NAT_AddressChangeNotificationMessage *msg; + struct AutoconfigContext *ac = cls; - env = GNUNET_MQ_msg_extra (msg, - addr_len, - GNUNET_MESSAGE_TYPE_NAT_ADDRESS_CHANGE); - msg->add_remove = htonl (add); - msg->addr_class = htonl (delta->ac); - GNUNET_memcpy (&msg[1], - addr, - addr_len); - GNUNET_MQ_send (ch->mq, - env); -} + ac->probe_external = NULL; + switch (result) + { + case GNUNET_NAT_ERROR_SUCCESS: + ac->enable_upnpc = GNUNET_YES; + break; + case GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID: + case GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID: + case GNUNET_NAT_ERROR_IPC_FAILURE: + ac->enable_upnpc = GNUNET_NO; /* did not work */ + break; + default: + GNUNET_break (0); /* unexpected */ + ac->enable_upnpc = GNUNET_SYSERR; + break; + } + update_enable_upnpc_option (ac); + check_autoconfig_finished (ac); +} /** - * Notify all clients about a change in the list - * of addresses this peer has. + * Handler for #GNUNET_MESSAGE_TYPE_NAT_REQUEST_AUTO_CFG message from + * client. * - * @param delta the entry in the list that changed - * @param add #GNUNET_YES to add, #GNUNET_NO to remove + * @param cls client who sent the message + * @param message the message received */ static void -notify_clients (struct LocalAddressList *delta, - int add) +handle_autoconfig_request (void *cls, + const struct GNUNET_NAT_AutoconfigRequestMessage *message) { - for (struct ClientHandle *ch = ch_head; - NULL != ch; - ch = ch->next) + struct ClientHandle *ch = cls; + size_t left = ntohs (message->header.size) - sizeof (*message); + struct LocalAddressList *lal; + struct AutoconfigContext *ac; + + ac = GNUNET_new (struct AutoconfigContext); + ac->c = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_deserialize (ac->c, + (const char *) &message[1], + left, + GNUNET_NO)) { - size_t alen; - struct sockaddr_in v4; - struct sockaddr_in6 v6; - - if (0 == (ch->flags & GNUNET_NAT_RF_ADDRESSES)) - continue; - switch (delta->af) + 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"); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ac->c, + "PEER", + "SYSTEM_TYPE", + &ac->system_type)) + ac->system_type = "UNKNOWN"; + + GNUNET_CONTAINER_DLL_insert (ac_head, + ac_tail, + ac); + 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 + { + for (lal = lal_head; NULL != lal; lal = lal->next) + if (GNUNET_NAT_AC_LAN == (lal->ac & GNUNET_NAT_AC_LAN)) + /* we are behind NAT, useful to try upnpc */ + ac->enable_upnpc = GNUNET_YES; + } + if (GNUNET_YES == ac->enable_upnpc) + { + /* If we are a mobile device, always leave it on as the network + may change to one that supports UPnP anytime. If we are + stationary, check if our network actually supports UPnP, and if + not, disable it. */ + if ( (0 == strcasecmp (ac->system_type, + "INFRASTRUCTURE")) || + (0 == strcasecmp (ac->system_type, + "DESKTOP")) ) { - case AF_INET: - alen = sizeof (struct sockaddr_in); - GNUNET_memcpy (&v4, - &delta->addr, - alen); - for (unsigned int i=0;inum_addrs;i++) - { - const struct sockaddr_in *c4; - - if (AF_INET != ch->addrs[i]->sa_family) - continue; /* IPv4 not relevant */ - c4 = (const struct sockaddr_in *) ch->addrs[i]; - v4.sin_port = c4->sin_port; - notify_client (delta, - ch, - add, - &v4, - alen); - } - break; - case AF_INET6: - alen = sizeof (struct sockaddr_in6); - GNUNET_memcpy (&v6, - &delta->addr, - alen); - for (unsigned int i=0;inum_addrs;i++) - { - const struct sockaddr_in6 *c6; - - if (AF_INET6 != ch->addrs[i]->sa_family) - continue; /* IPv4 not relevant */ - c6 = (const struct sockaddr_in6 *) ch->addrs[i]; - v6.sin6_port = c6->sin6_port; - notify_client (delta, - ch, - add, - &v6, - alen); - } - break; - default: - GNUNET_break (0); - continue; + /* Check if upnpc gives us an external IP */ + ac->probe_external + = GNUNET_NAT_mini_get_external_ipv4_ (&auto_external_result_cb, + ac); } } + if (NULL == ac->probe_external) + update_enable_upnpc_option (ac); + + /* Finally, check if we are already done */ + check_autoconfig_finished (ac); } /** - * Task we run periodically to scan for network interfaces. + * Task run during shutdown. * - * @param cls NULL - */ + * @param cls unused + */ static void -run_scan (void *cls) +shutdown_task (void *cls) { - struct IfcProcContext ifc_ctx; - int found; - - scan_task = GNUNET_SCHEDULER_add_delayed (SCAN_FREQ, - &run_scan, - NULL); - memset (&ifc_ctx, - 0, - sizeof (ifc_ctx)); - GNUNET_OS_network_interfaces_list (&ifc_proc, - &ifc_ctx); - for (struct LocalAddressList *lal = lal_head; - NULL != lal; - lal = lal->next) + struct StunExternalIP *se; + struct AutoconfigContext *ac; + + while (NULL != (ac = ac_head)) { - found = GNUNET_NO; - for (struct LocalAddressList *pos = ifc_ctx.lal_head; - NULL != pos; - pos = pos->next) - { - if ( (pos->af == lal->af) && - (0 == memcmp (&lal->addr, - &pos->addr, - (AF_INET == lal->af) - ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6))) ) - found = GNUNET_YES; - } - if (GNUNET_NO == found) - notify_clients (lal, - GNUNET_NO); + GNUNET_CONTAINER_DLL_remove (ac_head, + ac_tail, + ac); + terminate_ac_activities (ac); + GNUNET_free (ac); } - - for (struct LocalAddressList *pos = ifc_ctx.lal_head; - NULL != pos; - pos = pos->next) + while (NULL != (se = se_head)) { - found = GNUNET_NO; - for (struct LocalAddressList *lal = lal_head; - NULL != lal; - lal = lal->next) - { - if ( (pos->af == lal->af) && - (0 == memcmp (&lal->addr, - &pos->addr, - (AF_INET == lal->af) - ? sizeof (struct sockaddr_in) - : sizeof (struct sockaddr_in6))) ) - found = GNUNET_YES; - } - if (GNUNET_NO == found) - notify_clients (pos, - GNUNET_YES); + GNUNET_CONTAINER_DLL_remove (se_head, + se_tail, + se); + GNUNET_SCHEDULER_cancel (se->timeout_task); + GNUNET_free (se); + } + if (NULL != scan_task) + { + GNUNET_SCHEDULER_cancel (scan_task); + scan_task = NULL; + } + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, + GNUNET_NO); + stats = NULL; } - destroy_lal (); - lal_head = ifc_ctx.lal_head; - lal_tail = ifc_ctx.lal_tail; } /** - * Handle network size estimate clients. + * Setup NAT service. * * @param cls closure * @param c configuration to use @@ -1181,6 +1512,26 @@ run (void *cls, "STUN_STALE", &stun_stale_timeout)) stun_stale_timeout = GNUNET_TIME_UNIT_HOURS; + + /* Check for UPnP */ + enable_upnp + = GNUNET_CONFIGURATION_get_value_yesno (cfg, + "NAT", + "ENABLE_UPNP"); + if (GNUNET_YES == enable_upnp) + { + /* check if it works */ + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("upnpc", + GNUNET_NO, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("UPnP enabled in configuration, but UPnP client `upnpc` command not found, disabling UPnP\n")); + enable_upnp = GNUNET_SYSERR; + } + } + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); stats = GNUNET_STATISTICS_create ("nat", diff --git a/src/nat/gnunet-service-nat_mini.c b/src/nat/gnunet-service-nat_mini.c new file mode 100644 index 000000000..658ec72b7 --- /dev/null +++ b/src/nat/gnunet-service-nat_mini.c @@ -0,0 +1,759 @@ +/* + This file is part of GNUnet. + Copyright (C) 2011-2014, 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/gnunet-service-nat_mini.c + * @brief functions for interaction with miniupnp; tested with miniupnpc 1.5 + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_nat_service.h" +#include "gnunet-service-nat_mini.h" +#include "nat.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__) + +/** + * How long do we give upnpc to create a mapping? + */ +#define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15) + +/** + * How long do we give upnpc to remove a mapping? + */ +#define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1) + +/** + * How often do we check for changes in the mapping? + */ +#define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5) + + +/* ************************* external-ip calling ************************ */ + +/** + * Opaque handle to cancel "GNUNET_NAT_mini_get_external_ipv4" operation. + */ +struct GNUNET_NAT_ExternalHandle +{ + + /** + * Function to call with the result. + */ + GNUNET_NAT_IPCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Read task. + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Handle to `external-ip` process. + */ + struct GNUNET_OS_Process *eip; + + /** + * Handle to stdout pipe of `external-ip`. + */ + struct GNUNET_DISK_PipeHandle *opipe; + + /** + * Read handle of @e opipe. + */ + const struct GNUNET_DISK_FileHandle *r; + + /** + * Number of bytes in @e buf that are valid. + */ + size_t off; + + /** + * Destination of our read operation (output of 'external-ip'). + */ + char buf[17]; + + /** + * Error code for better debugging and user feedback + */ + enum GNUNET_NAT_StatusCode ret; +}; + + +/** + * Read the output of `external-ip` into `buf`. When complete, parse + * the address and call our callback. + * + * @param cls the `struct GNUNET_NAT_ExternalHandle` + */ +static void +read_external_ipv4 (void *cls) +{ + struct GNUNET_NAT_ExternalHandle *eh = cls; + ssize_t ret; + struct in_addr addr; + + eh->task = NULL; + ret = GNUNET_DISK_file_read (eh->r, + &eh->buf[eh->off], + sizeof (eh->buf) - eh->off); + if (ret > 0) + { + /* try to read more */ + eh->off += ret; + eh->task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + eh->r, + &read_external_ipv4, + eh); + return; + } + eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID; + if ( (eh->off > 7) && + (eh->buf[eh->off - 1] == '\n') ) + { + eh->buf[eh->off - 1] = '\0'; + if (1 == inet_pton (AF_INET, + eh->buf, + &addr)) + { + if (0 != addr.s_addr) + eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID; /* got 0.0.0.0 */ + else + eh->ret = GNUNET_NAT_ERROR_SUCCESS; + } + } + eh->cb (eh->cb_cls, + (GNUNET_NAT_ERROR_SUCCESS == eh->ret) ? &addr : NULL, + eh->ret); + GNUNET_NAT_mini_get_external_ipv4_cancel_ (eh); +} + + +/** + * (Asynchronously) signal error invoking `external-ip` to client. + * + * @param cls the `struct GNUNET_NAT_ExternalHandle` (freed) + */ +static void +signal_external_ip_error (void *cls) +{ + struct GNUNET_NAT_ExternalHandle *eh = cls; + + eh->task = NULL; + eh->cb (eh->cb_cls, + NULL, + eh->ret); + GNUNET_free (eh); +} + + +/** + * Try to get the external IPv4 address of this peer. + * + * @param cb function to call with result + * @param cb_cls closure for @a cb + * @return handle for cancellation (can only be used until @a cb is called), never NULL + */ +struct GNUNET_NAT_ExternalHandle * +GNUNET_NAT_mini_get_external_ipv4_ (GNUNET_NAT_IPCallback cb, + void *cb_cls) +{ + struct GNUNET_NAT_ExternalHandle *eh; + + eh = GNUNET_new (struct GNUNET_NAT_ExternalHandle); + eh->cb = cb; + eh->cb_cls = cb_cls; + eh->ret = GNUNET_NAT_ERROR_SUCCESS; + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("external-ip", + GNUNET_NO, + NULL)) + { + LOG (GNUNET_ERROR_TYPE_INFO, + _("`external-ip' command not found\n")); + eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_NOT_FOUND; + eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error, + eh); + return eh; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Running `external-ip' to determine our external IP\n"); + eh->opipe = GNUNET_DISK_pipe (GNUNET_YES, + GNUNET_YES, + GNUNET_NO, + GNUNET_YES); + if (NULL == eh->opipe) + { + eh->ret = GNUNET_NAT_ERROR_IPC_FAILURE; + eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error, + eh); + return eh; + } + eh->eip = + GNUNET_OS_start_process (GNUNET_NO, + 0, + NULL, + eh->opipe, + NULL, + "external-ip", + "external-ip", + NULL); + if (NULL == eh->eip) + { + GNUNET_DISK_pipe_close (eh->opipe); + eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_FAILED; + eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error, + eh); + return eh; + } + GNUNET_DISK_pipe_close_end (eh->opipe, + GNUNET_DISK_PIPE_END_WRITE); + eh->r = GNUNET_DISK_pipe_handle (eh->opipe, + GNUNET_DISK_PIPE_END_READ); + eh->task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + eh->r, + &read_external_ipv4, + eh); + return eh; +} + + +/** + * Cancel operation. + * + * @param eh operation to cancel + */ +void +GNUNET_NAT_mini_get_external_ipv4_cancel_ (struct GNUNET_NAT_ExternalHandle *eh) +{ + if (NULL != eh->eip) + { + (void) GNUNET_OS_process_kill (eh->eip, + SIGKILL); + GNUNET_OS_process_destroy (eh->eip); + } + if (NULL != eh->opipe) + { + GNUNET_DISK_pipe_close (eh->opipe); + eh->opipe = NULL; + } + if (NULL != eh->task) + { + GNUNET_SCHEDULER_cancel (eh->task); + eh->task = NULL; + } + GNUNET_free (eh); +} + + +/* ************************* upnpc calling ************************ */ + + +/** + * Handle to a mapping created with upnpc. + */ +struct GNUNET_NAT_MiniHandle +{ + + /** + * Function to call on mapping changes. + */ + GNUNET_NAT_MiniAddressCallback ac; + + /** + * Closure for @e ac. + */ + void *ac_cls; + + /** + * Command used to install the map. + */ + struct GNUNET_OS_CommandHandle *map_cmd; + + /** + * Command used to refresh our map information. + */ + struct GNUNET_OS_CommandHandle *refresh_cmd; + + /** + * Command used to remove the mapping. + */ + struct GNUNET_OS_CommandHandle *unmap_cmd; + + /** + * Our current external mapping (if we have one). + */ + struct sockaddr_in current_addr; + + /** + * We check the mapping periodically to see if it + * still works. This task triggers the check. + */ + struct GNUNET_SCHEDULER_Task *refresh_task; + + /** + * Are we mapping TCP or UDP? + */ + int is_tcp; + + /** + * Did we succeed with creating a mapping? + */ + int did_map; + + /** + * Did we find our mapping during refresh scan? + */ + int found; + + /** + * Which port are we mapping? + */ + uint16_t port; + +}; + + +/** + * Run "upnpc -l" to find out if our mapping changed. + * + * @param cls the `struct GNUNET_NAT_MiniHandle` + */ +static void +do_refresh (void *cls); + + +/** + * Process the output from the "upnpc -r" command. + * + * @param cls the `struct GNUNET_NAT_MiniHandle` + * @param line line of output, NULL at the end + */ +static void +process_map_output (void *cls, + const char *line); + + +/** + * Run "upnpc -r" to map our internal port. + * + * @param mini our handle + */ +static void +run_upnpc_r (struct GNUNET_NAT_MiniHandle *mini) +{ + char pstr[6]; + + GNUNET_snprintf (pstr, + sizeof (pstr), + "%u", + (unsigned int) mini->port); + mini->map_cmd + = GNUNET_OS_command_run (&process_map_output, + mini, + MAP_TIMEOUT, + "upnpc", + "upnpc", + "-r", + pstr, + mini->is_tcp ? "tcp" : "udp", + NULL); + if (NULL == mini->map_cmd) + { + mini->ac (mini->ac_cls, + GNUNET_SYSERR, + NULL, + 0, + GNUNET_NAT_ERROR_UPNPC_FAILED); + return; + } +} + + +/** + * Process the output from "upnpc -l" to see if our + * external mapping changed. If so, do the notifications. + * + * @param cls the `struct GNUNET_NAT_MiniHandle` + * @param line line of output, NULL at the end + */ +static void +process_refresh_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + char pstr[9]; + const char *s; + unsigned int nport; + struct in_addr exip; + + if (NULL == line) + { + GNUNET_OS_command_stop (mini->refresh_cmd); + mini->refresh_cmd = NULL; + if (GNUNET_NO == mini->found) + { + /* mapping disappeared, try to re-create */ + if (GNUNET_YES == mini->did_map) + { + mini->ac (mini->ac_cls, + GNUNET_NO, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); + mini->did_map = GNUNET_NO; + } + run_upnpc_r (mini); + } + return; + } + if (!mini->did_map) + return; /* never mapped, won't find our mapping anyway */ + + /* we're looking for output of the form: + * "ExternalIPAddress = 12.134.41.124" */ + + s = strstr (line, + "ExternalIPAddress = "); + if (NULL != s) + { + s += strlen ("ExternalIPAddress = "); + if (1 != inet_pton (AF_INET, + s, + &exip)) + return; /* skip */ + if (exip.s_addr == mini->current_addr.sin_addr.s_addr) + return; /* no change */ + /* update mapping */ + mini->ac (mini->ac_cls, + GNUNET_NO, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); + mini->current_addr.sin_addr = exip; + mini->ac (mini->ac_cls, + GNUNET_YES, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); + return; + } + /* + * we're looking for output of the form: + * + * "0 TCP 3000->192.168.2.150:3000 'libminiupnpc' ''" + * "1 UDP 3001->192.168.2.150:3001 'libminiupnpc' ''" + * + * the pattern we look for is: + * + * "%s TCP PORT->STRING:OURPORT *" or + * "%s UDP PORT->STRING:OURPORT *" + */ + GNUNET_snprintf (pstr, + sizeof (pstr), + ":%u ", + mini->port); + if (NULL == (s = strstr (line, "->"))) + return; /* skip */ + if (NULL == strstr (s, pstr)) + return; /* skip */ + if (1 != + SSCANF (line, + (mini->is_tcp) ? "%*u TCP %u->%*s:%*u %*s" : + "%*u UDP %u->%*s:%*u %*s", &nport)) + return; /* skip */ + mini->found = GNUNET_YES; + if (nport == ntohs (mini->current_addr.sin_port)) + return; /* no change */ + + /* external port changed, update mapping */ + mini->ac (mini->ac_cls, + GNUNET_NO, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); + mini->current_addr.sin_port = htons ((uint16_t) nport); + mini->ac (mini->ac_cls, + GNUNET_YES, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); +} + + +/** + * Run "upnpc -l" to find out if our mapping changed. + * + * @param cls the 'struct GNUNET_NAT_MiniHandle' + */ +static void +do_refresh (void *cls) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + int ac; + + mini->refresh_task + = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, + &do_refresh, + mini); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Running `upnpc' to check if our mapping still exists\n"); + mini->found = GNUNET_NO; + ac = GNUNET_NO; + if (NULL != mini->map_cmd) + { + /* took way too long, abort it! */ + GNUNET_OS_command_stop (mini->map_cmd); + mini->map_cmd = NULL; + ac = GNUNET_YES; + } + if (NULL != mini->refresh_cmd) + { + /* took way too long, abort it! */ + GNUNET_OS_command_stop (mini->refresh_cmd); + mini->refresh_cmd = NULL; + ac = GNUNET_YES; + } + mini->refresh_cmd = + GNUNET_OS_command_run (&process_refresh_output, + mini, + MAP_TIMEOUT, + "upnpc", + "upnpc", + "-l", + NULL); + if (GNUNET_YES == ac) + mini->ac (mini->ac_cls, + GNUNET_SYSERR, + NULL, + 0, + GNUNET_NAT_ERROR_UPNPC_TIMEOUT); +} + + +/** + * Process the output from the 'upnpc -r' command. + * + * @param cls the `struct GNUNET_NAT_MiniHandle` + * @param line line of output, NULL at the end + */ +static void +process_map_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + const char *ipaddr; + char *ipa; + const char *pstr; + unsigned int port; + + if (NULL == line) + { + GNUNET_OS_command_stop (mini->map_cmd); + mini->map_cmd = NULL; + if (GNUNET_YES != mini->did_map) + mini->ac (mini->ac_cls, + GNUNET_SYSERR, + NULL, 0, + GNUNET_NAT_ERROR_UPNPC_PORTMAP_FAILED); + if (NULL == mini->refresh_task) + mini->refresh_task = + GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, + &do_refresh, + mini); + return; + } + /* + * The upnpc output we're after looks like this: + * + * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000" + */ + if ((NULL == (ipaddr = strstr (line, " "))) || + (NULL == (pstr = strstr (ipaddr, ":"))) || + (1 != SSCANF (pstr + 1, "%u", &port))) + { + return; /* skip line */ + } + ipa = GNUNET_strdup (ipaddr + 1); + strstr (ipa, ":")[0] = '\0'; + if (1 != inet_pton (AF_INET, ipa, &mini->current_addr.sin_addr)) + { + GNUNET_free (ipa); + return; /* skip line */ + } + GNUNET_free (ipa); + + mini->current_addr.sin_port = htons (port); + mini->current_addr.sin_family = AF_INET; +#if HAVE_SOCKADDR_IN_SIN_LEN + mini->current_addr.sin_len = sizeof (struct sockaddr_in); +#endif + mini->did_map = GNUNET_YES; + mini->ac (mini->ac_cls, GNUNET_YES, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); +} + + +/** + * Start mapping the given port using (mini)upnpc. This function + * should typically not be used directly (it is used within the + * general-purpose #GNUNET_NAT_register() code). However, it can be + * used if specifically UPnP-based NAT traversal is to be used or + * tested. + * + * @param port port to map + * @param is_tcp #GNUNET_YES to map TCP, #GNUNET_NO for UDP + * @param ac function to call with mapping result + * @param ac_cls closure for @a ac + * @return NULL on error (no 'upnpc' installed) + */ +struct GNUNET_NAT_MiniHandle * +GNUNET_NAT_mini_map_start (uint16_t port, + int is_tcp, + GNUNET_NAT_MiniAddressCallback ac, + void *ac_cls) +{ + struct GNUNET_NAT_MiniHandle *ret; + + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("upnpc", + GNUNET_NO, + NULL)) + { + LOG (GNUNET_ERROR_TYPE_INFO, + _("`upnpc' command not found\n")); + ac (ac_cls, + GNUNET_SYSERR, + NULL, 0, + GNUNET_NAT_ERROR_UPNPC_NOT_FOUND); + return NULL; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Running `upnpc' to install mapping\n"); + ret = GNUNET_new (struct GNUNET_NAT_MiniHandle); + ret->ac = ac; + ret->ac_cls = ac_cls; + ret->is_tcp = is_tcp; + ret->port = port; + ret->refresh_task = + GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, + &do_refresh, + ret); + run_upnpc_r (ret); + return ret; +} + + +/** + * Process output from our 'unmap' command. + * + * @param cls the `struct GNUNET_NAT_MiniHandle` + * @param line line of output, NULL at the end + */ +static void +process_unmap_output (void *cls, + const char *line) +{ + struct GNUNET_NAT_MiniHandle *mini = cls; + + if (NULL == line) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "UPnP unmap done\n"); + GNUNET_OS_command_stop (mini->unmap_cmd); + mini->unmap_cmd = NULL; + GNUNET_free (mini); + return; + } + /* we don't really care about the output... */ +} + + +/** + * Remove a mapping created with (mini)upnpc. Calling + * this function will give 'upnpc' 1s to remove tha mapping, + * so while this function is non-blocking, a task will be + * left with the scheduler for up to 1s past this call. + * + * @param mini the handle + */ +void +GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini) +{ + char pstr[6]; + + if (NULL != mini->refresh_task) + { + GNUNET_SCHEDULER_cancel (mini->refresh_task); + mini->refresh_task = NULL; + } + if (NULL != mini->refresh_cmd) + { + GNUNET_OS_command_stop (mini->refresh_cmd); + mini->refresh_cmd = NULL; + } + if (NULL != mini->map_cmd) + { + GNUNET_OS_command_stop (mini->map_cmd); + mini->map_cmd = NULL; + } + if (GNUNET_NO == mini->did_map) + { + GNUNET_free (mini); + return; + } + mini->ac (mini->ac_cls, + GNUNET_NO, + (const struct sockaddr *) &mini->current_addr, + sizeof (mini->current_addr), + GNUNET_NAT_ERROR_SUCCESS); + /* Note: oddly enough, deletion uses the external port whereas + * addition uses the internal port; this rarely matters since they + * often are the same, but it might... */ + GNUNET_snprintf (pstr, + sizeof (pstr), + "%u", + (unsigned int) ntohs (mini->current_addr.sin_port)); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Unmapping port %u with UPnP\n", + ntohs (mini->current_addr.sin_port)); + mini->unmap_cmd + = GNUNET_OS_command_run (&process_unmap_output, + mini, + UNMAP_TIMEOUT, + "upnpc", + "upnpc", + "-d", + pstr, + mini->is_tcp ? "tcp" : "udp", + NULL); +} + + +/* end of gnunet-service-nat_mini.c */ diff --git a/src/nat/gnunet-service-nat_mini.h b/src/nat/gnunet-service-nat_mini.h new file mode 100644 index 000000000..2c0dd3445 --- /dev/null +++ b/src/nat/gnunet-service-nat_mini.h @@ -0,0 +1,127 @@ +/* + This file is part of GNUnet. + Copyright (C) 2011-2014, 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/gnunet-service-nat_mini.c + * @brief functions for interaction with miniupnp; tested with miniupnpc 1.5 + * @author Christian Grothoff + */ +#ifndef GNUNET_SERVICE_NAT_MINI_H +#define GNUNET_SERVICE_NAT_MINI_H + + +/** + * Signature of a callback that is given an IP address. + * + * @param cls closure + * @param addr the address, NULL on errors + * @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code + */ +typedef void +(*GNUNET_NAT_IPCallback) (void *cls, + const struct in_addr *addr, + enum GNUNET_NAT_StatusCode result); + + +/** + * Opaque handle to cancel #GNUNET_NAT_mini_get_external_ipv4() operation. + */ +struct GNUNET_NAT_ExternalHandle; + + +/** + * Try to get the external IPv4 address of this peer. + * + * @param cb function to call with result + * @param cb_cls closure for @a cb + * @return handle for cancellation (can only be used until @a cb is called), NULL on error + */ +struct GNUNET_NAT_ExternalHandle * +GNUNET_NAT_mini_get_external_ipv4_ (GNUNET_NAT_IPCallback cb, + void *cb_cls); + + +/** + * Cancel operation. + * + * @param eh operation to cancel + */ +void +GNUNET_NAT_mini_get_external_ipv4_cancel_ (struct GNUNET_NAT_ExternalHandle *eh); + + +/** + * Handle to a mapping created with upnpc. + */ +struct GNUNET_NAT_MiniHandle; + + +/** + * Signature of the callback passed to #GNUNET_NAT_register() for + * a function to call whenever our set of 'valid' addresses changes. + * + * @param cls closure + * @param add_remove #GNUNET_YES to mean the new public IP address, #GNUNET_NO to mean + * the previous (now invalid) one, #GNUNET_SYSERR indicates an error + * @param addr either the previous or the new public IP address + * @param addrlen actual length of the @a addr + * @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code + */ +typedef void +(*GNUNET_NAT_MiniAddressCallback) (void *cls, + int add_remove, + const struct sockaddr *addr, + socklen_t addrlen, + enum GNUNET_NAT_StatusCode result); + + +/** + * Start mapping the given port using (mini)upnpc. This function + * should typically not be used directly (it is used within the + * general-purpose #GNUNET_NAT_register() code). However, it can be + * used if specifically UPnP-based NAT traversal is to be used or + * tested. + * + * @param port port to map + * @param is_tcp #GNUNET_YES to map TCP, #GNUNET_NO for UDP + * @param ac function to call with mapping result + * @param ac_cls closure for @a ac + * @return NULL on error + */ +struct GNUNET_NAT_MiniHandle * +GNUNET_NAT_mini_map_start (uint16_t port, + int is_tcp, + GNUNET_NAT_MiniAddressCallback ac, + void *ac_cls); + + +/** + * Remove a mapping created with (mini)upnpc. Calling + * this function will give 'upnpc' 1s to remove the mapping, + * so while this function is non-blocking, a task will be + * left with the scheduler for up to 1s past this call. + * + * @param mini the handle + */ +void +GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini); + + +#endif diff --git a/src/nat/nat.h b/src/nat/nat.h index 6df72c0ab..3356b19ce 100644 --- a/src/nat/nat.h +++ b/src/nat/nat.h @@ -78,7 +78,7 @@ enum GNUNET_NAT_RegisterFlags /** * This client wants to be informed about changes to our - * external addresses. + * applicable addresses. */ GNUNET_NAT_RF_ADDRESSES = 1, diff --git a/src/nat/nat_api.c b/src/nat/nat_api.c index 58ed3e675..3fe97ed85 100644 --- a/src/nat/nat_api.c +++ b/src/nat/nat_api.c @@ -331,6 +331,7 @@ do_connect (void *cls) nh), GNUNET_MQ_handler_end () }; + struct GNUNET_MQ_Envelope *env; nh->reconnect_task = NULL; nh->mq = GNUNET_CLIENT_connecT (nh->cfg, @@ -339,7 +340,13 @@ do_connect (void *cls) &mq_error_handler, nh); if (NULL == nh->mq) + { reconnect (nh); + return; + } + env = GNUNET_MQ_msg_copy (nh->reg); + GNUNET_MQ_send (nh->mq, + env); } diff --git a/src/util/util.conf b/src/util/util.conf index 6b9c52d00..ecc94ead0 100644 --- a/src/util/util.conf +++ b/src/util/util.conf @@ -48,8 +48,18 @@ GNUNET_USER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/gnunet-${USERHOME:-${USER:-use [PEER] +# Where do we store our private key? PRIVATE_KEY = $GNUNET_DATA_HOME/private_key.ecc +# What kind of system are we on? Choices are +# INFRASTRUCTURE (always-on, grid, data center) +# DESKTOP (sometimes-on, grid, office) +# NOTEBOOK (sometimes-on, mobile, often limited network, +# if on-battery than large battery) +# MOBILE (sometimes-on, mobile, always limited network, +# always battery limited) +# UNKNOWN (not configured/specified/known) +SYSTEM_TYPE = UNKNOWN [TESTING] SPEEDUP_INTERVAL = 0 ms -- cgit v1.2.3