#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 <gcrypt.h>
*/
#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.
};
+/**
+ * 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.
*/
*/
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.
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,
network,
&net));
memset (&mask, 0, sizeof (mask));
+ if (0 == memcmp (&mask,
+ ip,
+ sizeof (mask)))
+ return GNUNET_YES;
off = 0;
while (bits > 8)
{
/**
- * 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;i<ch->num_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;i<ch->num_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;i<ch->num_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;i<ch->num_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);
/**
- * 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;i<ch->num_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;i<ch->num_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
"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",
--- /dev/null
+/*
+ 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 */