bugfix
[oweals/gnunet.git] / src / util / network.c
index 021f5afaf83349fb51eff87bc927c53901293609..674000d8e3ab957899b89890bb412aeae07e1219 100644 (file)
 #include "gnunet_network_lib.h"
 #include "gnunet_scheduler_lib.h"
 
-#define DEBUG_NETWORK GNUNET_NO
+#define DEBUG_NETWORK GNUNET_YES
+
+/**
+ * List of address families to give as hints to
+ * getaddrinfo, in reverse order of  preference.
+ */
+static int address_families[] = 
+  { AF_INET, AF_INET6, AF_UNSPEC };
 
 struct GNUNET_NETWORK_TransmitHandle
 {
@@ -105,6 +112,12 @@ struct GNUNET_NETWORK_SocketHandle
    */
   struct sockaddr *addr;
 
+  /**
+   * Pointer to the hostname if socket was
+   * created using DNS lookup, otherwise NULL.
+   */
+  char *hostname;
+
   /**
    * Pointer to our write buffer.
    */
@@ -132,6 +145,12 @@ struct GNUNET_NETWORK_SocketHandle
    */
   socklen_t addrlen;
 
+  /**
+   * Offset in our address family list
+   * that we used last.
+   */
+  int af_fam_offset;
+
   /**
    * Connect task that we may need to wait for.
    */
@@ -389,6 +408,37 @@ socket_set_blocking (int handle, int doBlock)
 }
 
 
+/**
+ * Perform a DNS lookup for the hostname associated
+ * with the current socket, iterating over the address
+ * families as specified in the "address_families" array.
+ */
+static void
+try_lookup (struct GNUNET_NETWORK_SocketHandle *sock)
+{
+  struct addrinfo hints;
+  int ec;
+
+  while ( (sock->ai_pos == NULL) &&
+         (sock->af_fam_offset > 0) )
+    {
+      if (sock->ai != NULL)
+       freeaddrinfo (sock->ai);
+      memset (&hints, 0, sizeof (hints));
+      hints.ai_family = address_families[--sock->af_fam_offset];
+      hints.ai_socktype = SOCK_STREAM;
+      if (0 != (ec = getaddrinfo (sock->hostname, NULL, &hints, &sock->ai)))
+       {
+         GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
+                     "`%s' failed for hostname `%s': %s\n",
+                     "getaddrinfo", sock->hostname, gai_strerror (ec));
+         sock->ai = NULL;
+       }
+      sock->ai_pos = sock->ai;
+    }
+}
+
+
 /**
  * Initiate asynchronous TCP connect request.
  *
@@ -408,6 +458,8 @@ try_connect (struct GNUNET_NETWORK_SocketHandle *sock)
     }
   while (1)
     {
+      if (sock->ai_pos == NULL)
+       try_lookup (sock);
       if (sock->ai_pos == NULL)
         {
           /* no more addresses to try, fatal! */
@@ -559,8 +611,6 @@ GNUNET_NETWORK_socket_create_from_connect (struct GNUNET_SCHEDULER_Handle
                                            uint16_t port, size_t maxbuf)
 {
   struct GNUNET_NETWORK_SocketHandle *ret;
-  struct addrinfo hints;
-  int ec;
 
   ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_SocketHandle) + maxbuf);
   ret->sock = -1;
@@ -568,21 +618,13 @@ GNUNET_NETWORK_socket_create_from_connect (struct GNUNET_SCHEDULER_Handle
   ret->write_buffer = (char *) &ret[1];
   ret->write_buffer_size = maxbuf;
   ret->port = port;
-  memset (&hints, 0, sizeof (hints));
-  hints.ai_family = AF_UNSPEC;
-  hints.ai_socktype = SOCK_STREAM;
-  if (0 != (ec = getaddrinfo (hostname, NULL, &hints, &ret->ai)))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
-                  "`%s' failed for hostname `%s': %s\n",
-                  "getaddrinfo", hostname, gai_strerror (ec));
-      GNUNET_free (ret);
-      return NULL;
-    }
-  ret->ai_pos = ret->ai;
+  ret->af_fam_offset = sizeof (address_families) / sizeof(address_families[0]);
+  ret->hostname = GNUNET_strdup (hostname);
   if (GNUNET_SYSERR == try_connect (ret))
     {
-      freeaddrinfo (ret->ai);
+      if (NULL != ret->ai)
+       freeaddrinfo (ret->ai);
+      GNUNET_free (ret->hostname);
       GNUNET_free (ret);
       return NULL;
     }
@@ -724,7 +766,8 @@ destroy_continuation (void *cls,
     GNUNET_break (0 == CLOSE (sock->sock));
   GNUNET_free_non_null (sock->addr);
   if (sock->ai != NULL)
-    freeaddrinfo (sock->ai);
+    freeaddrinfo (sock->ai);      
+  GNUNET_free_non_null (sock->hostname);
   GNUNET_free (sock);
 }
 
@@ -1077,18 +1120,29 @@ transmit_ready (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
                                       sock);
       return;
     }
-  if (sock->sock == -1)
+  if ( (sock->sock == -1) ||
+       ( (0 != (tc->reason & GNUNET_SCHEDULER_REASON_TIMEOUT)) &&
+        (0 == (tc->reason & GNUNET_SCHEDULER_REASON_PREREQ_DONE)) &&
+        (!FD_ISSET (sock->sock, tc->write_ready)))  )
     {
 #if DEBUG_NETWORK
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   _("Could not satisfy pending transmission request, socket closed or connect failed.\n"));
 #endif
+      if (-1 != sock->sock)
+       {
+         SHUTDOWN (sock->sock, SHUT_RDWR);
+         GNUNET_break (0 == CLOSE (sock->sock));
+         sock->sock = -1;
+       }
       transmit_error (sock);
       return;                   /* connect failed for good, we're finished */
     }
 if ((tc->write_ready == NULL) || (!FD_ISSET (sock->sock, tc->write_ready)))
+ if ((tc->write_ready == NULL) || (!FD_ISSET (sock->sock, tc->write_ready)))
     {
-      /* special circumstances: not yet ready to write */
+      /* special circumstances (in particular,
+        PREREQ_DONE after connect): not yet ready to write,
+        but no "fatal" error either.  Hence retry.  */
       goto SCHEDULE_WRITE;
     }
   GNUNET_assert (sock->write_buffer_off >= sock->write_buffer_pos);
@@ -1147,7 +1201,7 @@ SCHEDULE_WRITE:
                                   GNUNET_NO,
                                   GNUNET_SCHEDULER_PRIORITY_KEEP,
                                   GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
-                                  GNUNET_TIME_UNIT_FOREVER_REL,
+                                  GNUNET_TIME_absolute_get_remaining (sock->nth.transmit_timeout),
                                   sock->sock, &transmit_ready, sock);
 }
 
@@ -1204,12 +1258,23 @@ GNUNET_NETWORK_notify_transmit_ready (struct GNUNET_NETWORK_SocketHandle
                                                          &transmit_timeout,
                                                          sock);
   if (sock->write_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK)
-    sock->write_task = GNUNET_SCHEDULER_add_delayed (sock->sched,
-                                                     GNUNET_NO,
-                                                     GNUNET_SCHEDULER_PRIORITY_KEEP,
-                                                     sock->connect_task,
-                                                     GNUNET_TIME_UNIT_ZERO,
-                                                     &transmit_ready, sock);
+    {
+      if (sock->connect_task == GNUNET_SCHEDULER_NO_PREREQUISITE_TASK)
+       sock->write_task = GNUNET_SCHEDULER_add_write (sock->sched,
+                                                      GNUNET_NO,
+                                                      GNUNET_SCHEDULER_PRIORITY_KEEP,
+                                                      GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
+                                                      GNUNET_TIME_absolute_get_remaining (sock->nth.transmit_timeout),
+                                                      sock->sock,
+                                                      &transmit_ready, sock);
+      else
+       sock->write_task = GNUNET_SCHEDULER_add_delayed (sock->sched,
+                                                        GNUNET_NO,
+                                                        GNUNET_SCHEDULER_PRIORITY_KEEP,
+                                                        sock->connect_task,
+                                                        GNUNET_TIME_UNIT_ZERO,
+                                                        &transmit_ready, sock);
+    }
   return &sock->nth;
 }