(no commit message)
[oweals/gnunet.git] / src / nat / upnp.c
index 84abe339dbed900a05d88d6e910f7b4e3b1f9505..8dd7d626cf2bff1928fcca562b95038b1fd04c77 100644 (file)
 #include <errno.h>
 #include <string.h>
 
-#include <miniupnp/miniupnpc.h>
-#include <miniupnp/upnpcommands.h>
-
 #include "platform.h"
 #include "gnunet_common.h"
 #include "gnunet_nat_lib.h"
+#include "nat.h"
+#include "upnp-discover.h"
+#include "upnp-commands.h"
 #include "upnp.h"
 
 /* Component name for logging */
@@ -58,8 +58,8 @@ enum UPNP_State
 struct GNUNET_NAT_UPNP_Handle
 {
   int hasDiscovered;
-  struct UPNPUrls urls;
-  struct IGDdatas data;
+  char *control_url;
+  char *service_type;
   int port;
   const struct sockaddr *addr;
   socklen_t addrlen;
@@ -67,20 +67,21 @@ struct GNUNET_NAT_UPNP_Handle
   enum UPNP_State state;
   struct sockaddr *ext_addr;
   const char *iface;
+  int processing;
+  GNUNET_NAT_UPNP_pulse_cb pulse_cb;
+  void *pulse_cls;
 };
 
 static int
 process_if (void *cls,
             const char *name,
-            int isDefault,
-            const struct sockaddr *addr,
-            socklen_t addrlen)
+            int isDefault, const struct sockaddr *addr, socklen_t addrlen)
 {
   struct GNUNET_NAT_UPNP_Handle *upnp = cls;
 
   if (addr && GNUNET_NAT_cmp_addr (upnp->addr, addr) == 0)
     {
-      upnp->iface = name; // BADNESS!
+      upnp->iface = name;       // BADNESS!
       return GNUNET_SYSERR;
     }
 
@@ -88,41 +89,243 @@ process_if (void *cls,
 }
 
 
-GNUNET_NAT_UPNP_Handle *
-GNUNET_NAT_UPNP_init (const struct sockaddr *addr, 
-                     socklen_t addrlen,
-                      u_short port)
+struct GNUNET_NAT_UPNP_Handle *
+GNUNET_NAT_UPNP_init (const struct sockaddr *addr,
+                      socklen_t addrlen,
+                      u_short port,
+                      GNUNET_NAT_UPNP_pulse_cb pulse_cb, void *pulse_cls)
 {
-  GNUNET_NAT_UPNP_Handle *upnp;
+  struct GNUNET_NAT_UPNP_Handle *handle;
+
+  handle = GNUNET_malloc (sizeof (struct GNUNET_NAT_UPNP_Handle));
+  handle->processing = GNUNET_NO;
+  handle->state = UPNP_DISCOVER;
+  handle->addr = addr;
+  handle->addrlen = addrlen;
+  handle->port = port;
+  handle->pulse_cb = pulse_cb;
+  handle->pulse_cls = pulse_cls;
+  handle->control_url = NULL;
+  handle->service_type = NULL;
 
-  upnp = GNUNET_malloc (sizeof (GNUNET_NAT_UPNP_Handle));
-  upnp->state = UPNP_DISCOVER;
-  upnp->addr = addr;
-  upnp->addrlen = addrlen;
-  upnp->port = port;
   /* Find the interface corresponding to the address,
    * on which we should broadcast call for routers */
-  GNUNET_OS_network_interfaces_list (&process_if, upnp);
-  if (!upnp->iface)
-      GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, 
-                      COMP_NAT_UPNP, 
-                      "Could not find an interface matching the wanted address.\n");
-  return upnp;
+  GNUNET_OS_network_interfaces_list (&process_if, handle);
+  if (!handle->iface)
+    GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
+                     COMP_NAT_UPNP,
+                     "Could not find an interface matching the wanted address.\n");
+  return handle;
 }
 
 
 void
-GNUNET_NAT_UPNP_close (GNUNET_NAT_UPNP_Handle * handle)
+GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle *handle)
 {
   GNUNET_assert (!handle->is_mapped);
   GNUNET_assert ((handle->state == UPNP_IDLE)
-          || (handle->state == UPNP_ERR) || (handle->state == UPNP_DISCOVER));
+                 || (handle->state == UPNP_ERR)
+                 || (handle->state == UPNP_DISCOVER));
 
-  if (handle->hasDiscovered)
-    FreeUPNPUrls (&handle->urls);
+  GNUNET_free_non_null (handle->control_url);
+  GNUNET_free_non_null (handle->service_type);
   GNUNET_free (handle);
 }
 
+static void
+pulse_finish (struct GNUNET_NAT_UPNP_Handle *handle)
+{
+  enum GNUNET_NAT_PortState status;
+  handle->processing = GNUNET_NO;
+
+  switch (handle->state)
+    {
+    case UPNP_DISCOVER:
+      status = GNUNET_NAT_PORT_UNMAPPED;
+      break;
+
+    case UPNP_MAP:
+      status = GNUNET_NAT_PORT_MAPPING;
+      break;
+
+    case UPNP_UNMAP:
+      status = GNUNET_NAT_PORT_UNMAPPING;
+      break;
+
+    case UPNP_IDLE:
+      status =
+        handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
+      break;
+
+    default:
+      status = GNUNET_NAT_PORT_ERROR;
+      break;
+    }
+
+  handle->pulse_cb (status, handle->ext_addr, handle->pulse_cls);
+}
+
+static void
+discover_cb (const char *control_url, const char *service_type, void *cls)
+{
+  struct GNUNET_NAT_UPNP_Handle *handle = cls;
+
+  if (control_url)
+    {
+      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                       _("Found Internet Gateway Device \"%s\"\n"),
+                       control_url);
+
+      GNUNET_free_non_null (handle->control_url);
+      GNUNET_free_non_null (handle->service_type);
+
+      handle->control_url = GNUNET_strdup (control_url);
+      handle->service_type = GNUNET_strdup (service_type);
+      handle->state = UPNP_IDLE;
+      handle->hasDiscovered = 1;
+    }
+  else
+    {
+      handle->control_url = NULL;
+      handle->service_type = NULL;
+      handle->state = UPNP_ERR;
+#ifdef DEBUG_UPNP
+      GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
+                       "UPNP device discovery failed\n");
+#endif
+    }
+
+  pulse_finish (handle);
+}
+
+static void
+check_port_mapping_cb (int error, const char *control_url,
+                       const char *service_type, const char *extPort,
+                       const char *inPort, const char *proto,
+                       const char *remoteHost, void *cls)
+{
+  struct GNUNET_NAT_UPNP_Handle *handle = cls;
+
+  if (error)
+    {
+      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                       _("Port %d isn't forwarded\n"), handle->port);
+      handle->is_mapped = GNUNET_NO;
+    }
+
+  pulse_finish (handle);
+}
+
+static void
+delete_port_mapping_cb (int error, const char *control_url,
+                        const char *service_type, const char *extPort,
+                        const char *inPort, const char *proto,
+                        const char *remoteHost, void *cls)
+{
+  struct GNUNET_NAT_UPNP_Handle *handle = cls;
+
+  if (error)
+    GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                     _
+                     ("Could not stop port forwarding through \"%s\", service \"%s\": error %d\n"),
+                     handle->control_url, handle->service_type, error);
+  else
+    {
+      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                       _
+                       ("Stopped port forwarding through \"%s\", service \"%s\"\n"),
+                       handle->control_url, handle->service_type);
+      handle->is_mapped = !error;
+      handle->state = UPNP_IDLE;
+      handle->port = -1;
+    }
+
+  pulse_finish (handle);
+}
+
+static void
+add_port_mapping_cb (int error, const char *control_url,
+                     const char *service_type, const char *extPort,
+                     const char *inPort, const char *proto,
+                     const char *remoteHost, void *cls)
+{
+  struct GNUNET_NAT_UPNP_Handle *handle = cls;
+
+  if (error)
+    {
+      handle->is_mapped = GNUNET_NO;
+      handle->state = UPNP_ERR;
+      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                       _
+                       ("Port forwarding through \"%s\", service \"%s\" failed with error %d\n"),
+                       handle->control_url, handle->service_type, error);
+      return;
+    }
+  else
+    {
+      handle->is_mapped = GNUNET_NO;
+      handle->state = UPNP_IDLE;
+      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
+                       _("Port %d forwarded successfully\n"), handle->port);
+    }
+
+  pulse_finish (handle);
+}
+
+static void
+get_ip_address_cb (int error, char *ext_addr, void *cls)
+{
+  struct GNUNET_NAT_UPNP_Handle *handle = cls;
+
+  if (error)
+    {
+      if (handle->ext_addr)
+        {
+          GNUNET_free (handle->ext_addr);
+          handle->ext_addr = NULL;
+        }
+#ifdef DEBUG_UPNP
+      GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
+                       "UPNP_get_external_ip_address_ failed (error %d)\n",
+                       error);
+#endif
+    }
+  else
+    {
+      struct in_addr addr;
+      struct in6_addr addr6;
+
+      if (handle->ext_addr)
+        {
+          GNUNET_free (handle->ext_addr);
+          handle->ext_addr = NULL;
+        }
+
+      /* Try IPv4 and IPv6 as we don't know what's the format */
+      if (inet_aton (ext_addr, &addr) != 0)
+        {
+          handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
+          handle->ext_addr->sa_family = AF_INET;
+          ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr;
+        }
+      else if (inet_pton (AF_INET6, ext_addr, &addr6) != 1)
+        {
+          handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
+          handle->ext_addr->sa_family = AF_INET6;
+          ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6;
+        }
+      else
+        GNUNET_assert (GNUNET_YES);
+
+#ifdef DEBUG_UPNP
+      GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
+                       _("Found public IP address %s\n"), ext_addr);
+#endif
+    }
+
+  pulse_finish (handle);
+}
+
 /**
  * Check state of UPnP NAT: port redirection, external IP address.
  * 
@@ -133,45 +336,19 @@ GNUNET_NAT_UPNP_close (GNUNET_NAT_UPNP_Handle * handle)
  * @param ext_addr pointer for returning external IP address.
  *     Will be set to NULL if address could not be found. Don't free the sockaddr.
  */
-int
-GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled,
-                       int doPortCheck, struct sockaddr **ext_addr)
+void
+GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *handle,
+                       int is_enabled, int doPortCheck)
 {
-  int ret;
+  /* Stop if we're already waiting for an action to complete */
+  if (handle->processing == GNUNET_YES)
+    return;
 
   if (is_enabled && (handle->state == UPNP_DISCOVER))
     {
-      struct UPNPDev *devlist;
-      errno = 0;
-      devlist = upnpDiscover (2000, handle->iface, handle->addr, NULL, 0);
-      if (devlist == NULL)
-        {
-#ifdef DEBUG
-          GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
-                      "upnpDiscover failed (errno %d - %s)\n", errno,
-                      strerror (errno));
-#endif
-        }
-      errno = 0;
-      if (UPNP_GetValidIGD (devlist, &handle->urls, &handle->data,
-                            NULL, 0))
-        {
-          GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                      _("Found Internet Gateway Device \"%s\"\n"),
-                      handle->urls.controlURL);
-          handle->state = UPNP_IDLE;
-          handle->hasDiscovered = 1;
-        }
-      else
-        {
-          handle->state = UPNP_ERR;
-#ifdef DEBUG
-          GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
-                      "UPNP_GetValidIGD failed (errno %d - %s)\n",
-                      errno, strerror (errno));
-#endif
-        }
-      freeUPNPDevlist (devlist);
+      handle->processing = GNUNET_YES;
+      UPNP_discover_ (handle->iface, handle->addr, discover_cb,
+                      handle);
     }
 
   if (handle->state == UPNP_IDLE)
@@ -183,35 +360,25 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled,
   if (is_enabled && handle->is_mapped && doPortCheck)
     {
       char portStr[8];
-      char intPort[8];
-      char intClient[128];
-      int i;
 
       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
-      i = UPNP_GetSpecificPortMappingEntry (handle->urls.controlURL,
-                                            handle->data.servicetype, portStr,
-                                            "TCP", intClient, intPort);
-      if (i != UPNPCOMMAND_SUCCESS)
-        {
-          GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                      _("Port %d isn't forwarded\n"), handle->port);
-          handle->is_mapped = GNUNET_NO;
-        }
+
+      handle->processing = GNUNET_YES;
+      UPNP_get_specific_port_mapping_entry_ (handle->control_url,
+                                             handle->service_type, portStr,
+                                             "TCP", check_port_mapping_cb,
+                                             handle);
     }
 
   if (handle->state == UPNP_UNMAP)
     {
       char portStr[16];
       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
-      UPNP_DeletePortMapping (handle->urls.controlURL,
-                              handle->data.servicetype, portStr, "TCP", NULL);
-      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                  _
-                  ("Stopping port forwarding through \"%s\", service \"%s\"\n"),
-                  handle->urls.controlURL, handle->data.servicetype);
-      handle->is_mapped = 0;
-      handle->state = UPNP_IDLE;
-      handle->port = -1;
+
+      handle->processing = GNUNET_YES;
+      UPNP_delete_port_mapping_ (handle->control_url,
+                                 handle->service_type, portStr, "TCP", NULL,
+                                 delete_port_mapping_cb, handle);
     }
 
   if (handle->state == UPNP_IDLE)
@@ -222,10 +389,7 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled,
 
   if (handle->state == UPNP_MAP)
     {
-      int err = -1;
-      errno = 0;
-
-      if (!handle->urls.controlURL)
+      if (!handle->control_url)
         handle->is_mapped = 0;
       else
         {
@@ -233,111 +397,22 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled,
           char desc[64];
           GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
           GNUNET_snprintf (desc, sizeof (desc), "GNUnet at %d", handle->port);
-          err = UPNP_AddPortMapping (handle->urls.controlURL,
-                                     handle->data.servicetype,
-                                     portStr, portStr, GNUNET_a2s (handle->addr, handle->addrlen),              
-                                     desc, "TCP", NULL);
-          handle->is_mapped = !err;
-        }
-      GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                  _
-                  ("Port forwarding through \"%s\", service \"%s\"\n"),
-                  handle->urls.controlURL, handle->data.servicetype);
-      if (handle->is_mapped)
-        {
-          GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                      _("Port %d forwarded successfully\n"), handle->port);
-          handle->state = UPNP_IDLE;
-        }
-      else
-        {
-          GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
-                      "Port forwarding failed with error %d (errno %d - %s)\n",
-                      err, errno, strerror (errno));
-          handle->state = UPNP_ERR;
-        }
-    }
 
-  if (ext_addr && handle->state != UPNP_DISCOVER)
-    {
-      int err;
-      char addr_str[128];
-      struct in_addr addr;
-      struct in6_addr addr6;
-
-      /* Keep to NULL if address could not be found */
-      *ext_addr = NULL;
-      err = UPNP_GetExternalIPAddress (handle->urls.controlURL,
-                                       handle->data.servicetype, addr_str);
-      if (err == 0)
-        {
-          if (handle->ext_addr)
-            {
-              GNUNET_free (handle->ext_addr);
-              handle->ext_addr = NULL;
-            }
-
-          /* Try IPv4 and IPv6 as we don't know what's the format */
-          if (inet_aton (addr_str, &addr) != 0)
-            {
-              handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
-              handle->ext_addr->sa_family = AF_INET;
-              ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr;
-              *ext_addr = handle->ext_addr;
-            }
-          else if (inet_pton (AF_INET6, addr_str, &addr6) != 1)
-            {
-              handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
-              handle->ext_addr->sa_family = AF_INET6;
-              ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6;
-              *ext_addr = handle->ext_addr;
-            }
-          else
-            GNUNET_assert (GNUNET_YES);
-#ifdef DEBUG
-          GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
-                      _("Found public IP address %s\n"),
-                      addr_str);
-#endif
-        }
-      else
-        {
-          *ext_addr = NULL;
-          if (handle->ext_addr)
-            {
-              GNUNET_free (handle->ext_addr);
-              handle->ext_addr = NULL;
-            }
-#ifdef DEBUG
-          GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
-                      "UPNP_GetExternalIPAddress failed (error %d)\n", err);
-#endif
+          handle->processing = GNUNET_YES;
+          UPNP_add_port_mapping_ (handle->control_url,
+                                  handle->service_type,
+                                  portStr, portStr, GNUNET_a2s (handle->addr,
+                                                                handle->addrlen),
+                                  desc, "TCP", NULL, add_port_mapping_cb,
+                                  handle);
         }
     }
 
-  switch (handle->state)
+  if (handle->state != UPNP_DISCOVER)
     {
-    case UPNP_DISCOVER:
-      ret = GNUNET_NAT_PORT_UNMAPPED;
-      break;
-
-    case UPNP_MAP:
-      ret = GNUNET_NAT_PORT_MAPPING;
-      break;
-
-    case UPNP_UNMAP:
-      ret = GNUNET_NAT_PORT_UNMAPPING;
-      break;
-
-    case UPNP_IDLE:
-      ret =
-        handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
-      break;
-
-    default:
-      ret = GNUNET_NAT_PORT_ERROR;
-      break;
+      handle->processing = GNUNET_YES;
+      UPNP_get_external_ip_address_ (handle->control_url,
+                                     handle->service_type,
+                                     get_ip_address_cb, handle);
     }
-
-  return ret;
 }