Merge branch 'master'
[oweals/gnunet.git] / src / util / client.c
index 73b912cc6fd55d29f9b2088e23fee12be7437a32..163ae6eb9f522c6e4fbbf35d5935cc13f7b03643 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet.
 /*
      This file is part of GNUnet.
-     (C) 2001, 2002, 2006, 2008, 2009, 2012 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2001-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
 
      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 2, or (at your
+     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
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
@@ -14,8 +14,8 @@
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
-     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-     Boston, MA 02111-1307, USA.
+     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+     Boston, MA 02110-1301, USA.
 */
 
 /**
 */
 
 /**
 #include "platform.h"
 #include "gnunet_protocols.h"
 #include "gnunet_util_lib.h"
 #include "platform.h"
 #include "gnunet_protocols.h"
 #include "gnunet_util_lib.h"
+#include "gnunet_resolver_service.h"
+#include "gnunet_socks.h"
+
+
+#define LOG(kind,...) GNUNET_log_from (kind, "util-client",__VA_ARGS__)
 
 
 /**
 
 
 /**
- * How often do we re-try tranmsitting requests before giving up?
- * Note that if we succeeded transmitting a request but failed to read
- * a response, we do NOT re-try.
+ * Internal state for a client connected to a GNUnet service.
  */
  */
-#define MAX_ATTEMPTS 50
+struct ClientState;
 
 
-#define LOG(kind,...) GNUNET_log_from (kind, "util",__VA_ARGS__)
 
 /**
 
 /**
- * Handle for a transmission request.
+ * During connect, we try multiple possible IP addresses
+ * to find out which one might work.
  */
  */
-struct GNUNET_CLIENT_TransmitHandle
+struct AddressProbe
 {
 {
-  /**
-   * Connection state.
-   */
-  struct GNUNET_CLIENT_Connection *client;
 
   /**
 
   /**
-   * Function to call to get the data for transmission.
+   * This is a linked list.
    */
    */
-  GNUNET_CONNECTION_TransmitReadyNotify notify;
+  struct AddressProbe *next;
 
   /**
 
   /**
-   * Closure for notify.
+   * This is a doubly-linked list.
    */
    */
-  void *notify_cls;
+  struct AddressProbe *prev;
 
   /**
 
   /**
-   * Handle to the transmission with the underlying
-   * connection.
+   * The address; do not free (allocated at the end of this struct).
    */
    */
-  struct GNUNET_CONNECTION_TransmitHandle *th;
+  const struct sockaddr *addr;
 
   /**
 
   /**
-   * If we are re-trying and are delaying to do so,
-   * handle to the scheduled task managing the delay.
+   * Underlying OS's socket.
    */
    */
-  GNUNET_SCHEDULER_TaskIdentifier reconnect_task;
-
-  /**
-   * Timeout for the operation overall.
-   */
-  struct GNUNET_TIME_Absolute timeout;
+  struct GNUNET_NETWORK_Handle *sock;
 
   /**
 
   /**
-   * Number of bytes requested.
+   * Connection for which we are probing.
    */
    */
-  size_t size;
+  struct ClientState *cstate;
 
   /**
 
   /**
-   * Are we allowed to re-try to connect without telling
-   * the user (of this API) about the connection troubles?
+   * Lenth of addr.
    */
    */
-  int auto_retry;
+  socklen_t addrlen;
 
   /**
 
   /**
-   * Number of attempts left for transmitting the request.  We may
-   * fail the first time (say because the service is not yet up), in
-   * which case (if auto_retry is set) we wait a bit and re-try
-   * (timeout permitting).
+   * Task waiting for the connection to finish connecting.
    */
    */
-  unsigned int attempts_left;
-
+  struct GNUNET_SCHEDULER_Task *task;
 };
 
 
 /**
 };
 
 
 /**
- * Context for processing
- * "GNUNET_CLIENT_transmit_and_get_response" requests.
+ * Internal state for a client connected to a GNUnet service.
  */
  */
-struct TransmitGetResponseContext
+struct ClientState
 {
 {
-  /**
-   * Client handle.
-   */
-  struct GNUNET_CLIENT_Connection *client;
 
   /**
 
   /**
-   * Message to transmit; do not free, allocated
-   * right after this struct.
+   * The connection handle, NULL if not live
    */
    */
-  const struct GNUNET_MessageHeader *hdr;
+  struct GNUNET_NETWORK_Handle *sock;
 
   /**
 
   /**
-   * Timeout to use.
+   * Handle to a pending DNS lookup request, NULL if DNS is finished.
    */
    */
-  struct GNUNET_TIME_Absolute timeout;
+  struct GNUNET_RESOLVER_RequestHandle *dns_active;
 
   /**
 
   /**
-   * Function to call when done.
+   * Our configuration.
    */
    */
-  GNUNET_CLIENT_MessageHandler rn;
+  const struct GNUNET_CONFIGURATION_Handle *cfg;
 
   /**
 
   /**
-   * Closure for "rn".
+   * Linked list of sockets we are currently trying out
+   * (during connect).
    */
    */
-  void *rn_cls;
-};
-
-/**
- * Struct to refer to a GNUnet TCP connection.
- * This is more than just a socket because if the server
- * drops the connection, the client automatically tries
- * to reconnect (and for that needs connection information).
- */
-struct GNUNET_CLIENT_Connection
-{
+  struct AddressProbe *ap_head;
 
   /**
 
   /**
-   * The connection handle, NULL if not live
+   * Linked list of sockets we are currently trying out
+   * (during connect).
    */
    */
-  struct GNUNET_CONNECTION_Handle *connection;
+  struct AddressProbe *ap_tail;
 
   /**
 
   /**
-   * Our configuration.
+   * Name of the service we interact with.
    */
    */
-  const struct GNUNET_CONFIGURATION_Handle *cfg;
+  char *service_name;
 
   /**
 
   /**
-   * Name of the service we interact with.
+   * Hostname, if any.
    */
    */
-  char *service_name;
+  char *hostname;
 
   /**
 
   /**
-   * Context of a transmit_and_get_response operation, NULL
-   * if no such operation is pending.
+   * Next message to transmit to the service. NULL for none.
    */
    */
-  struct TransmitGetResponseContext *tag;
+  const struct GNUNET_MessageHeader *msg;
 
   /**
 
   /**
-   * Handler for current receiver task.
+   * Task for trying to connect to the service.
    */
    */
-  GNUNET_CLIENT_MessageHandler receiver_handler;
+  struct GNUNET_SCHEDULER_Task *retry_task;
 
   /**
 
   /**
-   * Closure for receiver_handler.
+   * Task for sending messages to the service.
    */
    */
-  void *receiver_handler_cls;
+  struct GNUNET_SCHEDULER_Task *send_task;
 
   /**
 
   /**
-   * Handle for a pending transmission request, NULL if there is
-   * none pending.
+   * Task for sending messages to the service.
    */
    */
-  struct GNUNET_CLIENT_TransmitHandle *th;
+  struct GNUNET_SCHEDULER_Task *recv_task;
 
   /**
 
   /**
-   * If we are re-trying and are delaying to do so,
-   * handle to the scheduled task managing the delay.
+   * Tokenizer for inbound messages.
    */
    */
-  GNUNET_SCHEDULER_TaskIdentifier receive_task;
+  struct GNUNET_MessageStreamTokenizer *mst;
 
   /**
 
   /**
-   * Buffer for received message.
+   * Message queue under our control.
    */
    */
-  char *received_buf;
+  struct GNUNET_MQ_Handle *mq;
 
   /**
    * Timeout for receiving a response (absolute time).
 
   /**
    * Timeout for receiving a response (absolute time).
@@ -201,1129 +171,717 @@ struct GNUNET_CLIENT_Connection
   struct GNUNET_TIME_Relative back_off;
 
   /**
   struct GNUNET_TIME_Relative back_off;
 
   /**
-   * Number of bytes in received_buf that are valid.
-   */
-  size_t received_pos;
-
-  /**
-   * Size of received_buf.
-   */
-  unsigned int received_size;
-
-  /**
-   * Do we have a complete response in received_buf?
+   * TCP port (0 for disabled).
    */
    */
-  int msg_complete;
+  unsigned long long port;
 
   /**
 
   /**
-   * Are we currently busy doing receive-processing?
-   * GNUNET_YES if so, GNUNET_NO if not.
+   * Offset in the message where we are for transmission.
    */
    */
-  int in_receive;
+  size_t msg_off;
 
   /**
 
   /**
-   * Is this the first message we are sending to the service?
+   * How often have we tried to connect?
    */
    */
-  int first_message;
+  unsigned int attempts;
 
   /**
 
   /**
-   * How often have we tried to connect?
+   * Are we supposed to die?  #GNUNET_SYSERR if destruction must be
+   * deferred, #GNUNET_NO by default, #GNUNET_YES if destruction was
+   * deferred.
    */
    */
-  unsigned int attempts;
+  int in_destroy;
 
 };
 
 
 
 };
 
 
-/**
- * Try connecting to the server using UNIX domain sockets.
- *
- * @param service_name name of service to connect to
- * @param cfg configuration to use
- * @return NULL on error, connection to UNIX otherwise
- */
-static struct GNUNET_CONNECTION_Handle *
-try_unixpath (const char *service_name,
-             const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-#if AF_UNIX
-  struct GNUNET_CONNECTION_Handle *connection;
-  char *unixpath;
-  struct sockaddr_un s_un;
-
-  unixpath = NULL;
-  if ((GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, service_name, "UNIXPATH", &unixpath)) && 
-      (0 < strlen (unixpath)))     
-  {
-    /* We have a non-NULL unixpath, need to validate it */
-    if (strlen (unixpath) >= sizeof (s_un.sun_path))
-    {
-      LOG (GNUNET_ERROR_TYPE_WARNING,
-          _("UNIXPATH `%s' too long, maximum length is %llu\n"), unixpath,
-          (unsigned long long) sizeof (s_un.sun_path));
-      unixpath = GNUNET_NETWORK_shorten_unixpath (unixpath);
-      LOG (GNUNET_ERROR_TYPE_INFO,
-          _("Using `%s' instead\n"), unixpath);
-    }
-    connection = GNUNET_CONNECTION_create_from_connect_to_unixpath (cfg, unixpath);
-    if (NULL != connection)
-    {
-      LOG (GNUNET_ERROR_TYPE_DEBUG, "Connected to unixpath `%s'!\n",
-          unixpath);
-      GNUNET_free (unixpath);
-      return connection;
-    }
-  }
-  GNUNET_free_non_null (unixpath);
-#endif
-  return NULL;
-}
-
-
-/**
- * Try connecting to the server using UNIX domain sockets.
- *
- * @param service_name name of service to connect to
- * @param cfg configuration to use
- * @return GNUNET_OK if the configuration is valid, GNUNET_SYSERR if not
- */
-static int
-test_service_configuration (const char *service_name,
-                           const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-  int ret = GNUNET_SYSERR;
-  char *hostname = NULL;
-  unsigned long long port;
-#if AF_UNIX
-  char *unixpath = NULL;
-
-  if ((GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, service_name, "UNIXPATH", &unixpath)) && 
-      (0 < strlen (unixpath)))     
-    ret = GNUNET_OK;
-  GNUNET_free_non_null (unixpath);
-#endif
-
-  if ( (GNUNET_YES ==
-       GNUNET_CONFIGURATION_have_value (cfg, service_name, "PORT")) &&
-       (GNUNET_OK ==
-       GNUNET_CONFIGURATION_get_value_number (cfg, service_name, "PORT", &port)) && 
-       (port <= 65535) && (0 != port) &&
-       (GNUNET_OK ==
-       GNUNET_CONFIGURATION_get_value_string (cfg, service_name, "HOSTNAME",
-                                              &hostname)) &&
-       (0 != strlen (hostname)) )
-    ret = GNUNET_OK;
-  GNUNET_free_non_null (hostname);
-  return ret;
-}
-
-
 /**
  * Try to connect to the service.
  *
 /**
  * Try to connect to the service.
  *
- * @param service_name name of service to connect to
- * @param cfg configuration to use
- * @param attempt counter used to alternate between IP and UNIX domain sockets
- * @return NULL on error
- */
-static struct GNUNET_CONNECTION_Handle *
-do_connect (const char *service_name,
-            const struct GNUNET_CONFIGURATION_Handle *cfg, unsigned int attempt)
-{
-  struct GNUNET_CONNECTION_Handle *connection;
-  char *hostname;
-  unsigned long long port;
-
-  connection = NULL;
-  if (0 == (attempt % 2))
-  {
-    /* on even rounds, try UNIX first */
-    connection = try_unixpath (service_name, cfg);
-    if (NULL != connection)
-      return connection;
-  }
-  if (GNUNET_YES ==
-      GNUNET_CONFIGURATION_have_value (cfg, service_name, "PORT"))
-  {
-    if ((GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_number (cfg, service_name, "PORT", &port))
-       || (port > 65535) ||
-       (GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_string (cfg, service_name, "HOSTNAME",
-                                               &hostname)))
-    {
-      LOG (GNUNET_ERROR_TYPE_WARNING,
-          _
-          ("Could not determine valid hostname and port for service `%s' from configuration.\n"),
-          service_name);
-      return NULL;
-    }
-    if (0 == strlen (hostname))
-    {
-      GNUNET_free (hostname);
-      LOG (GNUNET_ERROR_TYPE_WARNING,
-          _("Need a non-empty hostname for service `%s'.\n"), service_name);
-      return NULL;
-    }
-  }
-  else
-  {
-    /* unspecified means 0 (disabled) */
-    port = 0;
-    hostname = NULL;
-  }
-  if (0 == port)
-  {
-    /* if port is 0, try UNIX */
-    connection = try_unixpath (service_name, cfg);
-    if (NULL != connection)
-      return connection;
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Port is 0 for service `%s', UNIXPATH did not work, returning NULL!\n",
-         service_name);
-    GNUNET_free_non_null (hostname);
-    return NULL;
-  }
-  connection = GNUNET_CONNECTION_create_from_connect (cfg, hostname, port);
-  GNUNET_free (hostname);
-  return connection;
-}
-
-
-/**
- * Get a connection with a service.
- *
- * @param service_name name of the service
- * @param cfg configuration to use
- * @return NULL on error (service unknown to configuration)
- */
-struct GNUNET_CLIENT_Connection *
-GNUNET_CLIENT_connect (const char *service_name,
-                       const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
-  struct GNUNET_CLIENT_Connection *client;
-  struct GNUNET_CONNECTION_Handle *connection;
-
-  if (GNUNET_OK != 
-      test_service_configuration (service_name,
-                                 cfg))
-    return NULL;
-  connection = do_connect (service_name, cfg, 0);
-  client = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_Connection));
-  client->first_message = GNUNET_YES;
-  client->attempts = 1;
-  client->connection = connection;
-  client->service_name = GNUNET_strdup (service_name);
-  client->cfg = cfg;
-  client->back_off = GNUNET_TIME_UNIT_MILLISECONDS;
-  return client;
-}
-
-
-/**
- * Destroy connection with the service.  This will automatically
- * cancel any pending "receive" request (however, the handler will
- * *NOT* be called, not even with a NULL message).  Any pending
- * transmission request will also be cancelled UNLESS the callback for
- * the transmission request has already been called, in which case the
- * transmission 'finish_pending_write' argument determines whether or
- * not the write is guaranteed to complete before the socket is fully
- * destroyed (unless, of course, there is an error with the server in
- * which case the message may still be lost).
- *
- * @param client handle to the service connection
+ * @param cls the `struct ClientState` to try to connect to the service
  */
  */
-void
-GNUNET_CLIENT_disconnect (struct GNUNET_CLIENT_Connection *client)
-{
-  if (GNUNET_YES == client->in_receive)
-  {
-    GNUNET_CONNECTION_receive_cancel (client->connection);
-    client->in_receive = GNUNET_NO;
-  }
-  if (NULL != client->th)
-  {
-    GNUNET_CLIENT_notify_transmit_ready_cancel (client->th);
-    client->th = NULL;
-  }
-  if (NULL != client->connection)
-  {
-    GNUNET_CONNECTION_destroy (client->connection);
-    client->connection = NULL;
-  }
-  if (GNUNET_SCHEDULER_NO_TASK != client->receive_task)
-  {
-    GNUNET_SCHEDULER_cancel (client->receive_task);
-    client->receive_task = GNUNET_SCHEDULER_NO_TASK;
-  }
-  if (NULL != client->tag)
-  {
-    GNUNET_free (client->tag);
-    client->tag = NULL;
-  }
-  client->receiver_handler = NULL;
-  GNUNET_array_grow (client->received_buf, client->received_size, 0);
-  GNUNET_free (client->service_name);
-  GNUNET_free (client);
-}
+static void
+start_connect (void *cls);
 
 
 /**
 
 
 /**
- * Check if message is complete.  Sets the "msg_complete" member
- * in the client struct.
+ * We've failed for good to establish a connection (timeout or
+ * no more addresses to try).
  *
  *
- * @param client connection with the buffer to check
+ * @param cstate the connection we tried to establish
  */
 static void
  */
 static void
-check_complete (struct GNUNET_CLIENT_Connection *client)
+connect_fail_continuation (struct ClientState *cstate)
 {
 {
-  if ((client->received_pos >= sizeof (struct GNUNET_MessageHeader)) &&
-      (client->received_pos >=
-       ntohs (((const struct GNUNET_MessageHeader *) client->received_buf)->
-              size)))
-    client->msg_complete = GNUNET_YES;
+  GNUNET_break (NULL == cstate->ap_head);
+  GNUNET_break (NULL == cstate->ap_tail);
+  GNUNET_break (NULL == cstate->dns_active);
+  GNUNET_break (NULL == cstate->sock);
+  GNUNET_assert (NULL == cstate->send_task);
+  GNUNET_assert (NULL == cstate->recv_task);
+  // GNUNET_assert (NULL == cstate->proxy_handshake);
+
+  cstate->back_off = GNUNET_TIME_STD_BACKOFF (cstate->back_off);
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Failed to establish connection to `%s', no further addresses to try, will try again in %s.\n",
+       cstate->service_name,
+       GNUNET_STRINGS_relative_time_to_string (cstate->back_off,
+                                               GNUNET_YES));
+  cstate->retry_task
+    = GNUNET_SCHEDULER_add_delayed (cstate->back_off,
+                                    &start_connect,
+                                    cstate);
 }
 
 
 /**
 }
 
 
 /**
- * Callback function for data received from the network.  Note that
- * both "available" and "errCode" would be 0 if the read simply timed out.
+ * We are ready to send a message to the service.
  *
  *
- * @param cls closure
- * @param buf pointer to received data
- * @param available number of bytes availabe in "buf",
- *        possibly 0 (on errors)
- * @param addr address of the sender
- * @param addrlen size of addr
- * @param errCode value of errno (on errors receiving)
+ * @param cls the `struct ClientState` with the `msg` to transmit
  */
 static void
  */
 static void
-receive_helper (void *cls, const void *buf, size_t available,
-                const struct sockaddr *addr, socklen_t addrlen, int errCode)
+transmit_ready (void *cls)
 {
 {
-  struct GNUNET_CLIENT_Connection *client = cls;
-  struct GNUNET_TIME_Relative remaining;
-  GNUNET_CLIENT_MessageHandler receive_handler;
-  void *receive_handler_cls;
-
-  GNUNET_assert (GNUNET_NO == client->msg_complete);
-  GNUNET_assert (GNUNET_YES == client->in_receive);
-  client->in_receive = GNUNET_NO;
-  if ((0 == available) || (NULL == client->connection) || (0 != errCode))
-  {
-    /* signal timeout! */
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Timeout in receive_helper, available %u, client->connection %s, errCode `%s'\n",
-         (unsigned int) available, NULL == client->connection ? "NULL" : "non-NULL",
-         STRERROR (errCode));
-    if (NULL != (receive_handler = client->receiver_handler))
-    {
-      receive_handler_cls = client->receiver_handler_cls;
-      client->receiver_handler = NULL;
-      receive_handler (receive_handler_cls, NULL);
-    }
+  struct ClientState *cstate = cls;
+  ssize_t ret;
+  size_t len;
+  const char *pos;
+  int notify_in_flight;
+
+  cstate->send_task = NULL;
+  pos = (const char *) cstate->msg;
+  len = ntohs (cstate->msg->size);
+  GNUNET_assert (cstate->msg_off < len);
+ RETRY:
+  ret = GNUNET_NETWORK_socket_send (cstate->sock,
+                                    &pos[cstate->msg_off],
+                                    len - cstate->msg_off);
+  if (-1 == ret)
+  {
+    if (EINTR == errno)
+      goto RETRY;
+    GNUNET_MQ_inject_error (cstate->mq,
+                            GNUNET_MQ_ERROR_WRITE);
     return;
   }
     return;
   }
-  /* FIXME: optimize for common fast case where buf contains the
-   * entire message and we need no copying... */
-
-  /* slow path: append to array */
-  if (client->received_size < client->received_pos + available)
-    GNUNET_array_grow (client->received_buf, client->received_size,
-                       client->received_pos + available);
-  memcpy (&client->received_buf[client->received_pos], buf, available);
-  client->received_pos += available;
-  check_complete (client);
-  /* check for timeout */
-  remaining = GNUNET_TIME_absolute_get_remaining (client->receive_timeout);
-  if (0 == remaining.rel_value)
-  {
-    /* signal timeout! */
-    if (NULL != client->receiver_handler)
-      client->receiver_handler (client->receiver_handler_cls, NULL);
+  notify_in_flight = (0 == cstate->msg_off);
+  cstate->msg_off += ret;
+  if (cstate->msg_off < len)
+  {
+    cstate->send_task
+      = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                        cstate->sock,
+                                        &transmit_ready,
+                                        cstate);
+    if (notify_in_flight)
+      GNUNET_MQ_impl_send_in_flight (cstate->mq);
     return;
   }
     return;
   }
-  /* back to receive -- either for more data or to call callback! */
-  GNUNET_CLIENT_receive (client, client->receiver_handler,
-                         client->receiver_handler_cls, remaining);
+  cstate->msg = NULL;
+  GNUNET_MQ_impl_send_continue (cstate->mq);
 }
 
 
 /**
 }
 
 
 /**
- * Continuation to call the receive callback.
+ * We have received a full message, pass to the MQ dispatcher.
+ * Called by the tokenizer via #receive_ready().
  *
  *
- * @param cls  our handle to the client connection
- * @param tc scheduler context
+ * @param cls the `struct ClientState`
+ * @param msg message we received.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing
  */
  */
-static void
-receive_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+static int
+recv_message (void *cls,
+              const struct GNUNET_MessageHeader *msg)
 {
 {
-  struct GNUNET_CLIENT_Connection *client = cls;
-  GNUNET_CLIENT_MessageHandler handler = client->receiver_handler;
-  const struct GNUNET_MessageHeader *cmsg =
-      (const struct GNUNET_MessageHeader *) client->received_buf;
-  void *handler_cls = client->receiver_handler_cls;
-  uint16_t msize = ntohs (cmsg->size);
-  char mbuf[msize];
-  struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) mbuf;
-
-  LOG (GNUNET_ERROR_TYPE_DEBUG, "Received message of type %u and size %u\n",
-       ntohs (cmsg->type), msize);
-  client->receive_task = GNUNET_SCHEDULER_NO_TASK;
-  GNUNET_assert (GNUNET_YES == client->msg_complete);
-  GNUNET_assert (client->received_pos >= msize);
-  memcpy (msg, cmsg, msize);
-  memmove (client->received_buf, &client->received_buf[msize],
-           client->received_pos - msize);
-  client->received_pos -= msize;
-  client->msg_complete = GNUNET_NO;
-  client->receiver_handler = NULL;
-  check_complete (client);
-  if (NULL != handler)
-    handler (handler_cls, msg);
-}
+  struct ClientState *cstate = cls;
 
 
-
-/**
- * Read from the service.
- *
- * @param client the service
- * @param handler function to call with the message
- * @param handler_cls closure for handler
- * @param timeout how long to wait until timing out
- */
-void
-GNUNET_CLIENT_receive (struct GNUNET_CLIENT_Connection *client,
-                       GNUNET_CLIENT_MessageHandler handler, void *handler_cls,
-                       struct GNUNET_TIME_Relative timeout)
-{
-  if (NULL == client->connection)
-  {
-    /* already disconnected, fail instantly! */
-    GNUNET_break (0);           /* this should not happen in well-written code! */
-    if (NULL != handler)
-      handler (handler_cls, NULL);
-    return;
-  }
-  client->receiver_handler = handler;
-  client->receiver_handler_cls = handler_cls;
-  client->receive_timeout = GNUNET_TIME_relative_to_absolute (timeout);
-  if (GNUNET_YES == client->msg_complete)
-  {
-    GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == client->receive_task);
-    client->receive_task = GNUNET_SCHEDULER_add_now (&receive_task, client);
-  }
-  else
-  {
-    LOG (GNUNET_ERROR_TYPE_DEBUG, "calling GNUNET_CONNECTION_receive\n");
-    GNUNET_assert (GNUNET_NO == client->in_receive);
-    client->in_receive = GNUNET_YES;
-    GNUNET_CONNECTION_receive (client->connection, GNUNET_SERVER_MAX_MESSAGE_SIZE - 1,
-                               timeout, &receive_helper, client);
-  }
+  if (GNUNET_YES == cstate->in_destroy)
+    return GNUNET_SYSERR;
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Received message of type %u and size %u from %s\n",
+       ntohs (msg->type),
+       ntohs (msg->size),
+       cstate->service_name);
+  GNUNET_MQ_inject_message (cstate->mq,
+                            msg);
+  if (GNUNET_YES == cstate->in_destroy)
+    return GNUNET_SYSERR;
+  return GNUNET_OK;
 }
 
 
 /**
 }
 
 
 /**
- * Handle for a test to check if a service is running.
- */
-struct GNUNET_CLIENT_TestHandle 
-{
-  /**
-   * Function to call with the result of the test.
-   */
-  GNUNET_CLIENT_TestResultCallback cb;
-
-  /**
-   * Closure for 'cb'.
-   */
-  void *cb_cls;
-
-  /**
-   * Client connection we are using for the test, if any.
-   */ 
-  struct GNUNET_CLIENT_Connection *client;
-
-  /**
-   * Handle for the transmission request, if any.
-   */
-  struct GNUNET_CLIENT_TransmitHandle *th;
-
-  /**
-   * Deadline for calling 'cb'.
-   */
-  struct GNUNET_TIME_Absolute test_deadline;
-
-  /**
-   * ID of task used for asynchronous operations.
-   */
-  GNUNET_SCHEDULER_TaskIdentifier task;
-
-  /**
-   * Final result to report back (once known).
-   */
-  int result;
-};
-
-
-/**
- * Abort testing for service.
+ * Cancel all remaining connect attempts
  *
  *
- * @param th test handle
+ * @param cstate handle of the client state to process
  */
  */
-void
-GNUNET_CLIENT_service_test_cancel (struct GNUNET_CLIENT_TestHandle *th)
+static void
+cancel_aps (struct ClientState *cstate)
 {
 {
-  if (NULL != th->th)
-  {
-    GNUNET_CLIENT_notify_transmit_ready_cancel (th->th);
-    th->th = NULL;
-  }
-  if (NULL != th->client)
-  {
-    GNUNET_CLIENT_disconnect (th->client);
-    th->client = NULL;
-  }
-  if (GNUNET_SCHEDULER_NO_TASK != th->task)
+  struct AddressProbe *pos;
+
+  while (NULL != (pos = cstate->ap_head))
   {
   {
-    GNUNET_SCHEDULER_cancel (th->task);
-    th->task = GNUNET_SCHEDULER_NO_TASK;
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_NETWORK_socket_close (pos->sock));
+    GNUNET_SCHEDULER_cancel (pos->task);
+    GNUNET_CONTAINER_DLL_remove (cstate->ap_head,
+                                cstate->ap_tail,
+                                pos);
+    GNUNET_free (pos);
   }
   }
-  GNUNET_free (th);
 }
 
 
 /**
 }
 
 
 /**
- * Task that reports back the result by calling the callback
- * and then cleans up.
+ * Implement the destruction of a message queue.  Implementations must
+ * not free @a mq, but should take care of @a impl_state.
  *
  *
- * @param cls the 'struct GNUNET_CLIENT_TestHandle'
- * @param tc scheduler context
+ * @param mq the message queue to destroy
+ * @param impl_state our `struct ClientState`
  */
 static void
  */
 static void
-report_result (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc)
+connection_client_destroy_impl (struct GNUNET_MQ_Handle *mq,
+                                void *impl_state)
 {
 {
-  struct GNUNET_CLIENT_TestHandle *th = cls;
-  
-  th->task = GNUNET_SCHEDULER_NO_TASK;
-  th->cb (th->cb_cls, th->result);
-  GNUNET_CLIENT_service_test_cancel (th);
-}
+  struct ClientState *cstate = impl_state;
 
 
-
-/**
- * Report service test result asynchronously back to callback.
- *
- * @param th test handle with the result and the callback
- * @param result result to report
- */
-static void
-service_test_report (struct GNUNET_CLIENT_TestHandle *th,
-                    int result)
-{
-  th->result = result;
-  th->task = GNUNET_SCHEDULER_add_now (&report_result,
-                                      th);                                    
+  if (GNUNET_SYSERR == cstate->in_destroy)
+  {
+    /* defer destruction */
+    cstate->in_destroy = GNUNET_YES;
+    cstate->mq = NULL;
+    return;
+  }
+  if (NULL != cstate->dns_active)
+    GNUNET_RESOLVER_request_cancel (cstate->dns_active);
+  if (NULL != cstate->send_task)
+    GNUNET_SCHEDULER_cancel (cstate->send_task);
+  if (NULL != cstate->recv_task)
+    GNUNET_SCHEDULER_cancel (cstate->recv_task);
+  if (NULL != cstate->retry_task)
+    GNUNET_SCHEDULER_cancel (cstate->retry_task);
+  if (NULL != cstate->sock)
+    GNUNET_NETWORK_socket_close (cstate->sock);
+  cancel_aps (cstate);
+  GNUNET_free (cstate->service_name);
+  GNUNET_free_non_null (cstate->hostname);
+  GNUNET_MST_destroy (cstate->mst);
+  GNUNET_free (cstate);
 }
 
 
 /**
 }
 
 
 /**
- * Receive confirmation from test, service is up.
+ * This function is called once we have data ready to read.
  *
  *
- * @param cls closure with the 'struct GNUNET_CLIENT_TestHandle'
- * @param msg message received, NULL on timeout or fatal error
+ * @param cls `struct ClientState` with connection to read from
  */
 static void
  */
 static void
-confirm_handler (void *cls, const struct GNUNET_MessageHeader *msg)
+receive_ready (void *cls)
 {
 {
-  struct GNUNET_CLIENT_TestHandle *th = cls;
-
-  /* We may want to consider looking at the reply in more
-   * detail in the future, for example, is this the
-   * correct service? FIXME! */
-  if (NULL != msg)
-  {
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Received confirmation that service is running.\n");
-    service_test_report (th, GNUNET_YES);
+  struct ClientState *cstate = cls;
+  int ret;
+
+  cstate->recv_task = NULL;
+  cstate->in_destroy = GNUNET_SYSERR;
+  ret = GNUNET_MST_read (cstate->mst,
+                         cstate->sock,
+                         GNUNET_NO,
+                         GNUNET_NO);
+  if (GNUNET_SYSERR == ret)
+  {
+    if (NULL != cstate->mq)
+      GNUNET_MQ_inject_error (cstate->mq,
+                             GNUNET_MQ_ERROR_READ);
+    if (GNUNET_YES == cstate->in_destroy)
+      connection_client_destroy_impl (cstate->mq,
+                                     cstate);
+    return;
   }
   }
-  else
+  if (GNUNET_YES == cstate->in_destroy)
   {
   {
-    service_test_report (th, GNUNET_NO);
+    connection_client_destroy_impl (cstate->mq,
+                                    cstate);
+    return;
   }
   }
+  cstate->in_destroy = GNUNET_NO;
+  cstate->recv_task
+    = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                     cstate->sock,
+                                     &receive_ready,
+                                     cstate);
 }
 
 
 /**
 }
 
 
 /**
- * Send the 'TEST' message to the service.  If successful, prepare to
- * receive the reply.
+ * We've succeeded in establishing a connection.
  *
  *
- * @param cls the 'struct GNUNET_CLIENT_TestHandle' of the test
- * @param size number of bytes available in buf
- * @param buf where to write the message
- * @return number of bytes written to buf
+ * @param cstate the connection we tried to establish
  */
  */
-static size_t
-write_test (void *cls, size_t size, void *buf)
+static void
+connect_success_continuation (struct ClientState *cstate)
 {
 {
-  struct GNUNET_CLIENT_TestHandle *th = cls;
-  struct GNUNET_MessageHeader *msg;
-
-  th->th = NULL;
-  if (size < sizeof (struct GNUNET_MessageHeader))
-  {
-    LOG (GNUNET_ERROR_TYPE_DEBUG, 
-        "Failed to transmit TEST request.\n");
-    service_test_report (th, GNUNET_NO);
-    return 0;                   /* client disconnected */
+  GNUNET_assert (NULL == cstate->recv_task);
+  cstate->recv_task
+    = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                     cstate->sock,
+                                     &receive_ready,
+                                     cstate);
+  if (NULL != cstate->msg)
+  {
+    GNUNET_assert (NULL == cstate->send_task);
+    cstate->send_task
+      = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                        cstate->sock,
+                                        &transmit_ready,
+                                        cstate);
   }
   }
-  LOG (GNUNET_ERROR_TYPE_DEBUG, 
-       "Transmitting `%s' request.\n", 
-       "TEST");
-  msg = (struct GNUNET_MessageHeader *) buf;
-  msg->type = htons (GNUNET_MESSAGE_TYPE_TEST);
-  msg->size = htons (sizeof (struct GNUNET_MessageHeader));
-  GNUNET_CLIENT_receive (th->client, 
-                        &confirm_handler, th,
-                         GNUNET_TIME_absolute_get_remaining
-                         (th->test_deadline));
-  return sizeof (struct GNUNET_MessageHeader);
 }
 
 
 /**
 }
 
 
 /**
- * Test if the service is running.  If we are given a UNIXPATH or a
- * local address, we do this NOT by trying to connect to the service,
- * but by trying to BIND to the same port.  If the BIND fails, we know
- * the service is running.
+ * Try connecting to the server using UNIX domain sockets.
  *
  *
- * @param service name of the service to wait for
+ * @param service_name name of service to connect to
  * @param cfg configuration to use
  * @param cfg configuration to use
- * @param timeout how long to wait at most
- * @param cb function to call with the result
- * @param cb_cls closure for 'cb'
- * @return handle to cancel the test
+ * @return NULL on error, socket connected to UNIX otherwise
  */
  */
-struct GNUNET_CLIENT_TestHandle *
-GNUNET_CLIENT_service_test (const char *service,
-                            const struct GNUNET_CONFIGURATION_Handle *cfg,
-                            struct GNUNET_TIME_Relative timeout,
-                            GNUNET_CLIENT_TestResultCallback cb, void *cb_cls)
+static struct GNUNET_NETWORK_Handle *
+try_unixpath (const char *service_name,
+             const struct GNUNET_CONFIGURATION_Handle *cfg)
 {
 {
-  struct GNUNET_CLIENT_TestHandle *th;
-  char *hostname;
-  unsigned long long port;
+#if AF_UNIX
   struct GNUNET_NETWORK_Handle *sock;
   struct GNUNET_NETWORK_Handle *sock;
+  char *unixpath;
+  struct sockaddr_un s_un;
 
 
-  th = GNUNET_new (struct GNUNET_CLIENT_TestHandle);
-  th->cb = cb;
-  th->cb_cls = cb_cls;
-  th->test_deadline = GNUNET_TIME_relative_to_absolute (timeout);
-  LOG (GNUNET_ERROR_TYPE_DEBUG, 
-       "Testing if service `%s' is running.\n",
-       service);
-#ifdef AF_UNIX
+  unixpath = NULL;
+  if ((GNUNET_OK ==
+       GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                                service_name,
+                                                "UNIXPATH",
+                                                &unixpath)) &&
+      (0 < strlen (unixpath)))
   {
   {
-    /* probe UNIX support */
-    struct sockaddr_un s_un;
-    size_t slen;
-    char *unixpath;
-
-    unixpath = NULL;
-    if ((GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, service, "UNIXPATH", &unixpath)) && (0 < strlen (unixpath)))  /* We have a non-NULL unixpath, does that mean it's valid? */
+    /* We have a non-NULL unixpath, need to validate it */
+    if (strlen (unixpath) >= sizeof (s_un.sun_path))
     {
     {
-      if (strlen (unixpath) >= sizeof (s_un.sun_path))
-      {
-        LOG (GNUNET_ERROR_TYPE_WARNING,
-             _("UNIXPATH `%s' too long, maximum length is %llu\n"), unixpath,
-             (unsigned long long) sizeof (s_un.sun_path));
-       unixpath = GNUNET_NETWORK_shorten_unixpath (unixpath);
-        LOG (GNUNET_ERROR_TYPE_INFO,
-             _("Using `%s' instead\n"), unixpath);
-      }
+      LOG (GNUNET_ERROR_TYPE_WARNING,
+          _("UNIXPATH `%s' too long, maximum length is %llu\n"),
+           unixpath,
+          (unsigned long long) sizeof (s_un.sun_path));
+      unixpath = GNUNET_NETWORK_shorten_unixpath (unixpath);
+      LOG (GNUNET_ERROR_TYPE_INFO,
+          _("Using `%s' instead\n"),
+           unixpath);
+      if (NULL == unixpath)
+       return NULL;
     }
     }
-    if (NULL != unixpath)
+    memset (&s_un,
+            0,
+            sizeof (s_un));
+    s_un.sun_family = AF_UNIX;
+    strncpy (s_un.sun_path,
+             unixpath,
+             sizeof (s_un.sun_path) - 1);
+#ifdef LINUX
     {
     {
-      sock = GNUNET_NETWORK_socket_create (PF_UNIX, SOCK_STREAM, 0);
-      if (NULL != sock)
-      {
-       memset (&s_un, 0, sizeof (s_un));
-       s_un.sun_family = AF_UNIX;
-       slen = strlen (unixpath) + 1;
-       if (slen >= sizeof (s_un.sun_path))
-         slen = sizeof (s_un.sun_path) - 1;
-       memcpy (s_un.sun_path, unixpath, slen);
-       s_un.sun_path[slen] = '\0';
-       slen = sizeof (struct sockaddr_un);
-#if LINUX
-       s_un.sun_path[0] = '\0';
-#endif
-#if HAVE_SOCKADDR_IN_SIN_LEN
-       s_un.sun_len = (u_char) slen;
-#endif
-       if (GNUNET_OK !=
-           GNUNET_NETWORK_socket_bind (sock, (const struct sockaddr *) &s_un,
-                                       slen))
-        {
-         /* failed to bind => service must be running */
-         GNUNET_free (unixpath);
-         (void) GNUNET_NETWORK_socket_close (sock);
-         service_test_report (th, GNUNET_YES);
-         return th;
-       }
-       (void) GNUNET_NETWORK_socket_close (sock);        
-        /* let's try IP */
-      }
-    }
-    GNUNET_free_non_null (unixpath);
-  }
-#endif
-
-  hostname = NULL;
-  if ((GNUNET_OK !=
-       GNUNET_CONFIGURATION_get_value_number (cfg, service, "PORT", &port)) ||
-      (port > 65535) ||
-      (GNUNET_OK !=
-       GNUNET_CONFIGURATION_get_value_string (cfg, service, "HOSTNAME",
-                                              &hostname)))
-  {
-    /* UNIXPATH failed (if possible) AND IP failed => error */
-    service_test_report (th, GNUNET_SYSERR);
-    return th;
-  }
+      int abstract;
 
 
-  if (0 == strcmp ("localhost", hostname)
-#if !LINUX
-      && 0
-#endif
-      )
-  {
-    /* can test using 'bind' */
-    struct sockaddr_in s_in;
-
-    memset (&s_in, 0, sizeof (s_in));
-#if HAVE_SOCKADDR_IN_SIN_LEN
-    s_in.sin_len = sizeof (struct sockaddr_in);
-#endif
-    s_in.sin_family = AF_INET;
-    s_in.sin_port = htons (port);
-
-    sock = GNUNET_NETWORK_socket_create (AF_INET, SOCK_STREAM, 0);
-    if (NULL != sock)
-    {
-      if (GNUNET_OK !=
-          GNUNET_NETWORK_socket_bind (sock, (const struct sockaddr *) &s_in,
-                                      sizeof (s_in)))
-      {
-        /* failed to bind => service must be running */
-        GNUNET_free (hostname);
-        (void) GNUNET_NETWORK_socket_close (sock);
-        service_test_report (th, GNUNET_YES);
-        return th;
-      }
-      (void) GNUNET_NETWORK_socket_close (sock);
+      abstract = GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                                       "TESTING",
+                                                       "USE_ABSTRACT_SOCKETS");
+      if (GNUNET_YES == abstract)
+        s_un.sun_path[0] = '\0';
     }
     }
-  }
-
-  if (0 == strcmp ("ip6-localhost", hostname)
-#if !LINUX
-      && 0
 #endif
 #endif
-      )
-  {
-    /* can test using 'bind' */
-    struct sockaddr_in6 s_in6;
-
-    memset (&s_in6, 0, sizeof (s_in6));
-#if HAVE_SOCKADDR_IN_SIN_LEN
-    s_in6.sin6_len = sizeof (struct sockaddr_in6);
+#if HAVE_SOCKADDR_UN_SUN_LEN
+    s_un.sun_len = (u_char) sizeof (struct sockaddr_un);
 #endif
 #endif
-    s_in6.sin6_family = AF_INET6;
-    s_in6.sin6_port = htons (port);
-
-    sock = GNUNET_NETWORK_socket_create (AF_INET6, SOCK_STREAM, 0);
-    if (NULL != sock)
+    sock = GNUNET_NETWORK_socket_create (AF_UNIX,
+                                         SOCK_STREAM,
+                                         0);
+    if ( (NULL != sock) &&
+         ( (GNUNET_OK ==
+            GNUNET_NETWORK_socket_connect (sock,
+                                           (struct sockaddr *) &s_un,
+                                           sizeof (s_un))) ||
+           (EINPROGRESS == errno) ) )
     {
     {
-      if (GNUNET_OK !=
-          GNUNET_NETWORK_socket_bind (sock, (const struct sockaddr *) &s_in6,
-                                      sizeof (s_in6)))
-      {
-        /* failed to bind => service must be running */
-        GNUNET_free (hostname);
-        (void) GNUNET_NETWORK_socket_close (sock);
-        service_test_report (th, GNUNET_YES);
-        return th;
-      }
-      (void) GNUNET_NETWORK_socket_close (sock);
+      LOG (GNUNET_ERROR_TYPE_DEBUG,
+           "Successfully connected to unixpath `%s'!\n",
+          unixpath);
+      GNUNET_free (unixpath);
+      return sock;
     }
     }
+    if (NULL != sock)
+      GNUNET_NETWORK_socket_close (sock);
   }
   }
-
-  if (((0 == strcmp ("localhost", hostname)) ||
-       (0 == strcmp ("ip6-localhost", hostname)))
-#if !LINUX
-      && 0
+  GNUNET_free_non_null (unixpath);
 #endif
 #endif
-      )
-  {
-    /* all binds succeeded => claim service not running right now */
-    GNUNET_free_non_null (hostname);
-    service_test_report (th, GNUNET_NO);
-    return th;
-  }
-  GNUNET_free_non_null (hostname);
-
-  /* non-localhost, try 'connect' method */
-  th->client = GNUNET_CLIENT_connect (service, cfg);
-  if (NULL == th->client)
-  {
-    LOG (GNUNET_ERROR_TYPE_INFO,
-         _("Could not connect to service `%s', configuration broken.\n"),
-         service);
-    service_test_report (th, GNUNET_SYSERR);
-    return th;
-  }
-  th->th = GNUNET_CLIENT_notify_transmit_ready (th->client,
-                                               sizeof (struct GNUNET_MessageHeader),
-                                               timeout, GNUNET_YES,
-                                               &write_test, th);
-  if (NULL == th->th)
-  {
-    LOG (GNUNET_ERROR_TYPE_WARNING,
-         _("Failure to transmit request to service `%s'\n"), service);
-    service_test_report (th, GNUNET_SYSERR);
-    return th;
-  }
-  return th;
+  return NULL;
 }
 
 
 /**
 }
 
 
 /**
- * Connection notifies us about failure or success of
- * a transmission request.  Either pass it on to our
- * user or, if possible, retry.
+ * Scheduler let us know that we're either ready to write on the
+ * socket OR connect timed out.  Do the right thing.
  *
  *
- * @param cls our "struct GNUNET_CLIENT_TransmissionHandle"
- * @param size number of bytes available for transmission
- * @param buf where to write them
- * @return number of bytes written to buf
+ * @param cls the `struct AddressProbe *` with the address that we are probing
  */
  */
-static size_t
-client_notify (void *cls, size_t size, void *buf);
+static void
+connect_probe_continuation (void *cls)
+{
+  struct AddressProbe *ap = cls;
+  struct ClientState *cstate = ap->cstate;
+  const struct GNUNET_SCHEDULER_TaskContext *tc;
+  int error;
+  socklen_t len;
+
+  ap->task = NULL;
+  GNUNET_assert (NULL != ap->sock);
+  GNUNET_CONTAINER_DLL_remove (cstate->ap_head,
+                              cstate->ap_tail,
+                              ap);
+  len = sizeof (error);
+  error = 0;
+  tc = GNUNET_SCHEDULER_get_task_context ();
+  if ( (0 == (tc->reason & GNUNET_SCHEDULER_REASON_WRITE_READY)) ||
+       (GNUNET_OK !=
+       GNUNET_NETWORK_socket_getsockopt (ap->sock,
+                                         SOL_SOCKET,
+                                         SO_ERROR,
+                                         &error,
+                                         &len)) ||
+       (0 != error) )
+  {
+    GNUNET_break (GNUNET_OK ==
+                 GNUNET_NETWORK_socket_close (ap->sock));
+    GNUNET_free (ap);
+    if ( (NULL == cstate->ap_head) &&
+         //     (NULL == cstate->proxy_handshake) &&
+        (NULL == cstate->dns_active) )
+      connect_fail_continuation (cstate);
+    return;
+  }
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Connection to `%s' succeeded!\n",
+       cstate->service_name);
+  /* trigger jobs that waited for the connection */
+  GNUNET_assert (NULL == cstate->sock);
+  cstate->sock = ap->sock;
+  GNUNET_free (ap);
+  cancel_aps (cstate);
+  connect_success_continuation (cstate);
+}
 
 
 /**
 
 
 /**
- * This task is run if we should re-try connection to the
- * service after a while.
+ * Try to establish a connection given the specified address.
+ * This function is called by the resolver once we have a DNS reply.
  *
  *
- * @param cls our "struct GNUNET_CLIENT_TransmitHandle" of the request
- * @param tc unused
+ * @param cls our `struct ClientState *`
+ * @param addr address to try, NULL for "last call"
+ * @param addrlen length of @a addr
  */
 static void
  */
 static void
-client_delayed_retry (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+try_connect_using_address (void *cls,
+                           const struct sockaddr *addr,
+                           socklen_t addrlen)
 {
 {
-  struct GNUNET_CLIENT_TransmitHandle *th = cls;
-  struct GNUNET_TIME_Relative delay;
-  
-  th->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
-  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
-  {
-    /* give up, was shutdown */
-    th->client->th = NULL;
-    th->notify (th->notify_cls, 0, NULL);
-    GNUNET_free (th);
-    return;
-  }
-  th->client->connection =
-      do_connect (th->client->service_name, th->client->cfg, th->client->attempts++);
-  th->client->first_message = GNUNET_YES;
-  if (NULL == th->client->connection)
+  struct ClientState *cstate = cls;
+  struct AddressProbe *ap;
+
+  if (NULL == addr)
   {
   {
-    /* could happen if we're out of sockets */
-    delay =
-        GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining
-                                  (th->timeout), th->client->back_off);
-    th->client->back_off =
-        GNUNET_TIME_relative_min (GNUNET_TIME_relative_multiply
-                                  (th->client->back_off, 2),
-                                  GNUNET_TIME_UNIT_SECONDS);
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Transmission failed %u times, trying again in %s.\n",
-         MAX_ATTEMPTS - th->attempts_left,
-         GNUNET_STRINGS_relative_time_to_string (delay, GNUNET_YES));
-    th->reconnect_task =
-        GNUNET_SCHEDULER_add_delayed (delay, &client_delayed_retry, th);
+    cstate->dns_active = NULL;
+    if ( (NULL == cstate->ap_head) &&
+         //  (NULL == cstate->proxy_handshake) &&
+         (NULL == cstate->sock) )
+      connect_fail_continuation (cstate);
     return;
   }
     return;
   }
-  th->th =
-      GNUNET_CONNECTION_notify_transmit_ready (th->client->connection, th->size,
-                                               GNUNET_TIME_absolute_get_remaining
-                                               (th->timeout), &client_notify,
-                                               th);
-  if (NULL == th->th)
-  {
+  if (NULL != cstate->sock)
+    return;                     /* already connected */
+  /* try to connect */
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Trying to connect using address `%s:%u'\n",
+       GNUNET_a2s (addr,
+                   addrlen),
+       cstate->port);
+  ap = GNUNET_malloc (sizeof (struct AddressProbe) + addrlen);
+  ap->addr = (const struct sockaddr *) &ap[1];
+  GNUNET_memcpy (&ap[1],
+                 addr,
+                 addrlen);
+  ap->addrlen = addrlen;
+  ap->cstate = cstate;
+
+  switch (ap->addr->sa_family)
+  {
+  case AF_INET:
+    ((struct sockaddr_in *) ap->addr)->sin_port = htons (cstate->port);
+    break;
+  case AF_INET6:
+    ((struct sockaddr_in6 *) ap->addr)->sin6_port = htons (cstate->port);
+    break;
+  default:
     GNUNET_break (0);
     GNUNET_break (0);
-    th->client->th = NULL;
-    th->notify (th->notify_cls, 0, NULL);
-    GNUNET_free (th);
+    GNUNET_free (ap);
+    return;                     /* not supported by us */
+  }
+  ap->sock = GNUNET_NETWORK_socket_create (ap->addr->sa_family,
+                                          SOCK_STREAM,
+                                           0);
+  if (NULL == ap->sock)
+  {
+    GNUNET_free (ap);
+    return;                     /* not supported by OS */
+  }
+  if ( (GNUNET_OK !=
+        GNUNET_NETWORK_socket_connect (ap->sock,
+                                       ap->addr,
+                                       ap->addrlen)) &&
+       (EINPROGRESS != errno) )
+  {
+    /* maybe refused / unsupported address, try next */
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO,
+                         "connect");
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_NETWORK_socket_close (ap->sock));
+    GNUNET_free (ap);
     return;
   }
     return;
   }
+  GNUNET_CONTAINER_DLL_insert (cstate->ap_head,
+                               cstate->ap_tail,
+                               ap);
+  ap->task = GNUNET_SCHEDULER_add_write_net (GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT,
+                                            ap->sock,
+                                            &connect_probe_continuation,
+                                            ap);
 }
 
 
 /**
 }
 
 
 /**
- * Connection notifies us about failure or success of a transmission
- * request.  Either pass it on to our user or, if possible, retry.
+ * Test whether the configuration has proper values for connection
+ * (UNIXPATH || (PORT && HOSTNAME)).
  *
  *
- * @param cls our "struct GNUNET_CLIENT_TransmissionHandle"
- * @param size number of bytes available for transmission
- * @param buf where to write them
- * @return number of bytes written to buf
+ * @param service_name name of service to connect to
+ * @param cfg configuration to use
+ * @return #GNUNET_OK if the configuration is valid, #GNUNET_SYSERR if not
  */
  */
-static size_t
-client_notify (void *cls, size_t size, void *buf)
+static int
+test_service_configuration (const char *service_name,
+                           const struct GNUNET_CONFIGURATION_Handle *cfg)
 {
 {
-  struct GNUNET_CLIENT_TransmitHandle *th = cls;
-  struct GNUNET_CLIENT_Connection *client = th->client;
-  size_t ret;
-  struct GNUNET_TIME_Relative delay;
-
-  LOG (GNUNET_ERROR_TYPE_DEBUG, "client_notify is running\n");
-  th->th = NULL;
-  client->th = NULL;
-  if (NULL == buf)
-  {
-    delay = GNUNET_TIME_absolute_get_remaining (th->timeout);
-    delay.rel_value /= 2;
-    if ((GNUNET_YES != th->auto_retry) || (0 == --th->attempts_left) ||
-        (delay.rel_value < 1)||
-       (0 != (GNUNET_SCHEDULER_get_reason() & GNUNET_SCHEDULER_REASON_SHUTDOWN)))
-    {
-      LOG (GNUNET_ERROR_TYPE_DEBUG,
-           "Transmission failed %u times, giving up.\n",
-           MAX_ATTEMPTS - th->attempts_left);
-      GNUNET_break (0 == th->notify (th->notify_cls, 0, NULL));
-      GNUNET_free (th);
-      return 0;
-    }
-    /* auto-retry */
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Failed to connect to `%s', automatically trying again.\n",
-         client->service_name);
-    if (GNUNET_YES == client->in_receive)
-    {
-      GNUNET_CONNECTION_receive_cancel (client->connection);
-      client->in_receive = GNUNET_NO;
-    }    
-    GNUNET_CONNECTION_destroy (client->connection);
-    client->connection = NULL;
-    delay = GNUNET_TIME_relative_min (delay, client->back_off);
-    client->back_off =
-        GNUNET_TIME_relative_min (GNUNET_TIME_relative_multiply
-                                  (client->back_off, 2),
-                                  GNUNET_TIME_UNIT_SECONDS);
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         "Transmission failed %u times, trying again in %s.\n",
-         MAX_ATTEMPTS - th->attempts_left,
-         GNUNET_STRINGS_relative_time_to_string (delay, GNUNET_YES));
-    client->th = th;
-    th->reconnect_task =
-        GNUNET_SCHEDULER_add_delayed (delay, &client_delayed_retry, th);
-    return 0;
-  }
-  GNUNET_assert (size >= th->size);
-  ret = th->notify (th->notify_cls, size, buf);
-  GNUNET_free (th);
+  int ret = GNUNET_SYSERR;
+  char *hostname = NULL;
+  unsigned long long port;
+#if AF_UNIX
+  char *unixpath = NULL;
+
+  if ((GNUNET_OK ==
+       GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                                service_name,
+                                                "UNIXPATH",
+                                                &unixpath)) &&
+      (0 < strlen (unixpath)))
+    ret = GNUNET_OK;
+  GNUNET_free_non_null (unixpath);
+#endif
+
+  if ( (GNUNET_YES ==
+       GNUNET_CONFIGURATION_have_value (cfg,
+                                         service_name,
+                                         "PORT")) &&
+       (GNUNET_OK ==
+       GNUNET_CONFIGURATION_get_value_number (cfg,
+                                               service_name,
+                                               "PORT",
+                                               &port)) &&
+       (port <= 65535) &&
+       (0 != port) &&
+       (GNUNET_OK ==
+       GNUNET_CONFIGURATION_get_value_string (cfg,
+                                               service_name,
+                                               "HOSTNAME",
+                                              &hostname)) &&
+       (0 != strlen (hostname)) )
+    ret = GNUNET_OK;
+  GNUNET_free_non_null (hostname);
   return ret;
 }
 
 
 /**
   return ret;
 }
 
 
 /**
- * Ask the client to call us once the specified number of bytes
- * are free in the transmission buffer.  May call the notify
- * method immediately if enough space is available.
+ * Try to connect to the service.
  *
  *
- * @param client connection to the service
- * @param size number of bytes to send
- * @param timeout after how long should we give up (and call
- *        notify with buf NULL and size 0)?
- * @param auto_retry if the connection to the service dies, should we
- *        automatically re-connect and retry (within the timeout period)
- *        or should we immediately fail in this case?  Pass GNUNET_YES
- *        if the caller does not care about temporary connection errors,
- *        for example because the protocol is stateless
- * @param notify function to call
- * @param notify_cls closure for notify
- * @return NULL if our buffer will never hold size bytes,
- *         a handle if the notify callback was queued (can be used to cancel)
+ * @param cls the `struct ClientState` to try to connect to the service
  */
  */
-struct GNUNET_CLIENT_TransmitHandle *
-GNUNET_CLIENT_notify_transmit_ready (struct GNUNET_CLIENT_Connection *client,
-                                     size_t size,
-                                     struct GNUNET_TIME_Relative timeout,
-                                     int auto_retry,
-                                     GNUNET_CONNECTION_TransmitReadyNotify
-                                     notify, void *notify_cls)
+static void
+start_connect (void *cls)
 {
 {
-  struct GNUNET_CLIENT_TransmitHandle *th;
+  struct ClientState *cstate = cls;
 
 
-  if (NULL != client->th)
+  cstate->retry_task = NULL;
+#if 0
+  /* Never use a local source if a proxy is configured */
+  if (GNUNET_YES ==
+      GNUNET_SOCKS_check_service (cstate->service_name,
+                                  cstate->cfg))
   {
   {
-    /* If this breaks, you most likley called this function twice without waiting
-     * for completion or canceling the request */
-    GNUNET_break (0);
-    return NULL;
+    socks_connect (cstate);
+    return;
   }
   }
-  th = GNUNET_malloc (sizeof (struct GNUNET_CLIENT_TransmitHandle));
-  th->client = client;
-  th->size = size;
-  th->timeout = GNUNET_TIME_relative_to_absolute (timeout);
-  /* always auto-retry on first message to service */
-  th->auto_retry = (GNUNET_YES == client->first_message) ? GNUNET_YES : auto_retry;
-  client->first_message = GNUNET_NO;
-  th->notify = notify;
-  th->notify_cls = notify_cls;
-  th->attempts_left = MAX_ATTEMPTS;
-  client->th = th;
-  if (NULL == client->connection)
-  {
-    th->reconnect_task =
-        GNUNET_SCHEDULER_add_delayed (client->back_off, &client_delayed_retry,
-                                      th);
+#endif
 
 
-  }
-  else
+  if ( (0 == (cstate->attempts++ % 2)) ||
+       (0 == cstate->port) ||
+       (NULL == cstate->hostname) )
   {
   {
-    th->th =
-        GNUNET_CONNECTION_notify_transmit_ready (client->connection, size, timeout,
-                                                 &client_notify, th);
-    if (NULL == th->th)
+    /* on even rounds, try UNIX first, or always
+       if we do not have a DNS name and TCP port. */
+    cstate->sock = try_unixpath (cstate->service_name,
+                                 cstate->cfg);
+    if (NULL != cstate->sock)
     {
     {
-      GNUNET_break (0);
-      GNUNET_free (th);
-      client->th = NULL;
-      return NULL;
+      connect_success_continuation (cstate);
+      return;
     }
   }
     }
   }
-  return th;
+  if ( (NULL == cstate->hostname) ||
+       (0 == cstate->port) )
+  {
+    /* All options failed. Boo! */
+    connect_fail_continuation (cstate);
+    return;
+  }
+  cstate->dns_active
+    = GNUNET_RESOLVER_ip_get (cstate->hostname,
+                             AF_UNSPEC,
+                              GNUNET_CONNECTION_CONNECT_RETRY_TIMEOUT,
+                              &try_connect_using_address,
+                             cstate);
 }
 
 
 /**
 }
 
 
 /**
- * Cancel a request for notification.
+ * Implements the transmission functionality of a message queue.
  *
  *
- * @param th handle from the original request.
+ * @param mq the message queue
+ * @param msg the message to send
+ * @param impl_state our `struct ClientState`
  */
  */
-void
-GNUNET_CLIENT_notify_transmit_ready_cancel (struct GNUNET_CLIENT_TransmitHandle
-                                            *th)
+static void
+connection_client_send_impl (struct GNUNET_MQ_Handle *mq,
+                             const struct GNUNET_MessageHeader *msg,
+                             void *impl_state)
 {
 {
-  if (GNUNET_SCHEDULER_NO_TASK != th->reconnect_task)
-  {
-    GNUNET_assert (NULL == th->th);
-    GNUNET_SCHEDULER_cancel (th->reconnect_task);
-    th->reconnect_task = GNUNET_SCHEDULER_NO_TASK;
-  }
-  else
-  {
-    GNUNET_assert (NULL != th->th);
-    GNUNET_CONNECTION_notify_transmit_ready_cancel (th->th);
-  }
-  th->client->th = NULL;
-  GNUNET_free (th);
+  struct ClientState *cstate = impl_state;
+
+  /* only one message at a time allowed */
+  GNUNET_assert (NULL == cstate->msg);
+  GNUNET_assert (NULL == cstate->send_task);
+  cstate->msg = msg;
+  cstate->msg_off = 0;
+  if (NULL == cstate->sock)
+    return; /* still waiting for connection */
+  cstate->send_task
+    = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                      cstate->sock,
+                                      &transmit_ready,
+                                      cstate);
 }
 
 
 /**
 }
 
 
 /**
- * Function called to notify a client about the socket
- * begin ready to queue the message.  "buf" will be
- * NULL and "size" zero if the socket was closed for
- * writing in the meantime.
+ * Cancel the currently sent message.
  *
  *
- * @param cls closure of type "struct TransmitGetResponseContext*"
- * @param size number of bytes available in buf
- * @param buf where the callee should write the message
- * @return number of bytes written to buf
+ * @param mq message queue
+ * @param impl_state our `struct ClientState`
  */
  */
-static size_t
-transmit_for_response (void *cls, size_t size, void *buf)
+static void
+connection_client_cancel_impl (struct GNUNET_MQ_Handle *mq,
+                               void *impl_state)
 {
 {
-  struct TransmitGetResponseContext *tc = cls;
-  uint16_t msize;
+  struct ClientState *cstate = impl_state;
 
 
-  tc->client->tag = NULL;
-  msize = ntohs (tc->hdr->size);
-  if (NULL == buf)
+  GNUNET_assert (NULL != cstate->msg);
+  GNUNET_assert (0 == cstate->msg_off);
+  cstate->msg = NULL;
+  if (NULL != cstate->send_task)
   {
   {
-    LOG (GNUNET_ERROR_TYPE_DEBUG,
-         _("Could not submit request, not expecting to receive a response.\n"));
-    if (NULL != tc->rn)
-      tc->rn (tc->rn_cls, NULL);
-    GNUNET_free (tc);
-    return 0;
+    GNUNET_SCHEDULER_cancel (cstate->send_task);
+    cstate->send_task = NULL;
   }
   }
-  GNUNET_assert (size >= msize);
-  memcpy (buf, tc->hdr, msize);
-  GNUNET_CLIENT_receive (tc->client, tc->rn, tc->rn_cls,
-                         GNUNET_TIME_absolute_get_remaining (tc->timeout));
-  GNUNET_free (tc);
-  return msize;
 }
 
 
 /**
 }
 
 
 /**
- * Convenience API that combines sending a request
- * to the service and waiting for a response.
- * If either operation times out, the callback
- * will be called with a "NULL" response (in which
- * case the connection should probably be destroyed).
+ * Create a message queue to connect to a GNUnet service.
+ * If handlers are specfied, receive messages from the connection.
  *
  *
- * @param client connection to use
- * @param hdr message to transmit
- * @param timeout when to give up (for both transmission
- *         and for waiting for a response)
- * @param auto_retry if the connection to the service dies, should we
- *        automatically re-connect and retry (within the timeout period)
- *        or should we immediately fail in this case?  Pass GNUNET_YES
- *        if the caller does not care about temporary connection errors,
- *        for example because the protocol is stateless
- * @param rn function to call with the response
- * @param rn_cls closure for rn
- * @return GNUNET_OK on success, GNUNET_SYSERR if a request
- *         is already pending
+ * @param cfg our configuration
+ * @param service_name name of the service to connect to
+ * @param handlers handlers for receiving messages, can be NULL
+ * @param error_handler error handler
+ * @param error_handler_cls closure for the @a error_handler
+ * @return the message queue, NULL on error
  */
  */
-int
-GNUNET_CLIENT_transmit_and_get_response (struct GNUNET_CLIENT_Connection *client,
-                                         const struct GNUNET_MessageHeader *hdr,
-                                         struct GNUNET_TIME_Relative timeout,
-                                         int auto_retry,
-                                         GNUNET_CLIENT_MessageHandler rn,
-                                         void *rn_cls)
+struct GNUNET_MQ_Handle *
+GNUNET_CLIENT_connect (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                      const char *service_name,
+                      const struct GNUNET_MQ_MessageHandler *handlers,
+                      GNUNET_MQ_ErrorHandler error_handler,
+                      void *error_handler_cls)
 {
 {
-  struct TransmitGetResponseContext *tc;
-  uint16_t msize;
+  struct ClientState *cstate;
 
 
-  if (NULL != client->th)
-    return GNUNET_SYSERR;
-  GNUNET_assert (NULL == client->tag);
-  msize = ntohs (hdr->size);
-  tc = GNUNET_malloc (sizeof (struct TransmitGetResponseContext) + msize);
-  tc->client = client;
-  tc->hdr = (const struct GNUNET_MessageHeader *) &tc[1];
-  memcpy (&tc[1], hdr, msize);
-  tc->timeout = GNUNET_TIME_relative_to_absolute (timeout);
-  tc->rn = rn;
-  tc->rn_cls = rn_cls;
-  if (NULL ==
-      GNUNET_CLIENT_notify_transmit_ready (client, msize, timeout, auto_retry,
-                                           &transmit_for_response, tc))
-  {
-    GNUNET_break (0);
-    GNUNET_free (tc);
-    return GNUNET_SYSERR;
+  if (GNUNET_OK !=
+      test_service_configuration (service_name,
+                                 cfg))
+    return NULL;
+  cstate = GNUNET_new (struct ClientState);
+  cstate->service_name = GNUNET_strdup (service_name);
+  cstate->cfg = cfg;
+  cstate->retry_task = GNUNET_SCHEDULER_add_now (&start_connect,
+                                                 cstate);
+  cstate->mst = GNUNET_MST_create (&recv_message,
+                                   cstate);
+  if (GNUNET_YES ==
+      GNUNET_CONFIGURATION_have_value (cfg,
+                                       service_name,
+                                       "PORT"))
+  {
+    if (! ( (GNUNET_OK !=
+            GNUNET_CONFIGURATION_get_value_number (cfg,
+                                                   service_name,
+                                                   "PORT",
+                                                   &cstate->port)) ||
+           (cstate->port > 65535) ||
+           (GNUNET_OK !=
+            GNUNET_CONFIGURATION_get_value_string (cfg,
+                                                   service_name,
+                                                   "HOSTNAME",
+                                                   &cstate->hostname)) ) &&
+       (0 == strlen (cstate->hostname)) )
+    {
+      GNUNET_free (cstate->hostname);
+      cstate->hostname = NULL;
+      LOG (GNUNET_ERROR_TYPE_WARNING,
+          _("Need a non-empty hostname for service `%s'.\n"),
+          service_name);
+    }
   }
   }
-  client->tag = tc;
-  return GNUNET_OK;
+  cstate->mq = GNUNET_MQ_queue_for_callbacks (&connection_client_send_impl,
+                                             &connection_client_destroy_impl,
+                                             &connection_client_cancel_impl,
+                                             cstate,
+                                             handlers,
+                                             error_handler,
+                                             error_handler_cls);
+  return cstate->mq;
 }
 
 }
 
-
-/*  end of client.c */
+/* end of client.c */