-fix #2079
[oweals/gnunet.git] / src / vpn / gnunet-service-vpn.c
index 7219471bf061100f174d4a03c14ee8f99f46e745..aa95e429f75b6c0eb56f307002dba490671934ab 100644 (file)
  *
  * TODO:
  * Basics:
- * - need some logging
- * - need some statistics
  * - test!
- * - better message queue management (bounded state, drop oldest/RED?)
- * - actually destroy "stale" tunnels once we have too many!
  *
  * Features:
  * - add back ICMP support (especially needed for IPv6)
 #include "gnunet_protocols.h"
 #include "gnunet_applications.h"
 #include "gnunet_mesh_service.h"
+#include "gnunet_statistics_service.h"
 #include "gnunet_constants.h"
 #include "tcpip_tun.h"
 #include "vpn.h"
 #include "exit.h"
 
 
+/**
+ * Maximum number of messages we allow in the queue for mesh.
+ */
+#define MAX_MESSAGE_QUEUE_SIZE 4
+
+
 /**
  * State we keep for each of our tunnels.
  */
@@ -68,7 +71,7 @@ struct DestinationEntry
 
   /**
    * Key under which this entry is in the 'destination_map' (only valid
-   * if 'heap_node != NULL'.
+   * if 'heap_node != NULL').
    */
   GNUNET_HashCode key;
 
@@ -171,6 +174,7 @@ struct TunnelMessageQueueEntry
  */
 struct TunnelState
 {
+
   /**
    * Information about the tunnel to use, NULL if no tunnel
    * is available right now.
@@ -191,12 +195,12 @@ struct TunnelState
   /**
    * Head of list of messages scheduled for transmission.
    */
-  struct TunnelMessageQueueEntry *head;
+  struct TunnelMessageQueueEntry *tmq_head;
 
   /**
    * Tail of list of messages scheduled for transmission.
    */
-  struct TunnelMessageQueueEntry *tail;
+  struct TunnelMessageQueueEntry *tmq_tail;  
 
   /**
    * Client that needs to be notified about the tunnel being
@@ -204,6 +208,12 @@ struct TunnelState
    */
   struct GNUNET_SERVER_Client *client;
 
+  /**
+   * Destination entry that has a pointer to this tunnel state;
+   * NULL if this tunnel state is in the tunnel map.
+   */
+  struct DestinationEntry *destination_container;
+
   /**
    * ID of the client request that caused us to setup this entry.
    */ 
@@ -218,16 +228,20 @@ struct TunnelState
   struct DestinationEntry destination;
 
   /**
-   * Destination entry that has a pointer to this tunnel state;
-   * NULL if this tunnel state is in the tunnel map.
+   * Task scheduled to destroy the tunnel (or NO_TASK).
    */
-  struct DestinationEntry *destination_container;
+  GNUNET_SCHEDULER_TaskIdentifier destroy_task;
 
   /**
    * Addess family used for this tunnel on the local TUN interface.
    */
   int af;
 
+  /**
+   * Length of the doubly linked 'tmq_head/tmq_tail' list.
+   */
+  unsigned int tmq_length;
+
   /**
    * IPPROTO_TCP or IPPROTO_UDP once bound.
    */
@@ -314,6 +328,11 @@ static struct GNUNET_CONTAINER_MultiHashMap *tunnel_map;
  */
 static struct GNUNET_CONTAINER_Heap *tunnel_heap;
 
+/**
+ * Statistics.
+ */
+static struct GNUNET_STATISTICS_Handle *stats;
+
 /**
  * The handle to the VPN helper process "gnunet-helper-vpn".
  */
@@ -480,6 +499,27 @@ send_client_reply (struct GNUNET_SERVER_Client *client,
 }
 
 
+/**
+ * Destroy the mesh tunnel.
+ *
+ * @param cls the 'struct TunnelState' with the tunnel to destroy
+ * @param ts schedule context
+ */
+static void
+destroy_tunnel_task (void *cls,
+                    const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct TunnelState *ts = cls;
+  struct GNUNET_MESH_Tunnel *tunnel;
+
+  ts->destroy_task = GNUNET_SCHEDULER_NO_TASK;
+  if (NULL == (tunnel = ts->tunnel))
+    return;
+  ts->tunnel = NULL;
+  GNUNET_MESH_tunnel_destroy (tunnel);
+}
+
+
 /**
  * Method called whenever a peer has disconnected from the tunnel.
  *
@@ -492,7 +532,13 @@ tunnel_peer_disconnect_handler (void *cls,
                                GNUNET_PeerIdentity * peer)
 {
   struct TunnelState *ts = cls;
-  
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Peer %s disconnected from tunnel.\n",
+             GNUNET_i2s (peer));
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Peers connected to mesh tunnels"),
+                           -1, GNUNET_NO);
   if (NULL != ts->th)
   {
     GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
@@ -502,7 +548,8 @@ tunnel_peer_disconnect_handler (void *cls,
     return; /* hope for reconnect eventually */
   /* as we are most likely going to change the exit node now,
      we should just destroy the tunnel entirely... */
-  GNUNET_MESH_tunnel_destroy (ts->tunnel);
+  if (GNUNET_SCHEDULER_NO_TASK == ts->destroy_task)
+    ts->destroy_task = GNUNET_SCHEDULER_add_now (&destroy_tunnel_task, ts);
 }
 
 
@@ -523,6 +570,12 @@ tunnel_peer_connect_handler (void *cls,
 {
   struct TunnelState *ts = cls;
 
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Peer %s connected to tunnel.\n",
+             GNUNET_i2s (peer));
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Peers connected to mesh tunnels"),
+                           1, GNUNET_NO);
   if (NULL == ts->client)
     return; /* nothing to do */
   send_client_reply (ts->client,
@@ -552,16 +605,20 @@ send_to_peer_notify_callback (void *cls, size_t size, void *buf)
   ts->th = NULL;
   if (NULL == buf)
     return 0;
-  tnq = ts->head;
+  tnq = ts->tmq_head;
   GNUNET_assert (NULL != tnq);
   GNUNET_assert (size >= tnq->len);
-  GNUNET_CONTAINER_DLL_remove (ts->head,
-                              ts->tail,
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Sending %u bytes via mesh tunnel\n",
+             tnq->len);
+  GNUNET_CONTAINER_DLL_remove (ts->tmq_head,
+                              ts->tmq_tail,
                               tnq);
+  ts->tmq_length--;
   memcpy (buf, tnq->msg, tnq->len);
   ret = tnq->len;
   GNUNET_free (tnq);
-  if (NULL != (tnq = ts->head))
+  if (NULL != (tnq = ts->tmq_head))
     ts->th = GNUNET_MESH_notify_transmit_ready (ts->tunnel, 
                                                GNUNET_NO /* cork */, 
                                                42 /* priority */,
@@ -570,6 +627,9 @@ send_to_peer_notify_callback (void *cls, size_t size, void *buf)
                                                tnq->len,
                                                &send_to_peer_notify_callback,
                                                ts);
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Bytes given to mesh for transmission"),
+                           ret, GNUNET_NO);
   return ret;
 }
 
@@ -578,8 +638,6 @@ send_to_peer_notify_callback (void *cls, size_t size, void *buf)
  * Add the given message to the given tunnel and trigger the
  * transmission process.
  *
- * FIXME: bound queue length!
- *
  * @param tnq message to queue
  * @param ts tunnel to queue the message for
  */
@@ -587,9 +645,30 @@ static void
 send_to_tunnel (struct TunnelMessageQueueEntry *tnq,
                struct TunnelState *ts)
 {
-  GNUNET_CONTAINER_DLL_insert_tail (ts->head,
-                                   ts->tail,
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Queueing %u bytes for transmission via mesh tunnel\n",
+             tnq->len);
+  GNUNET_CONTAINER_DLL_insert_tail (ts->tmq_head,
+                                   ts->tmq_tail,
                                    tnq);
+  ts->tmq_length++;
+  if (ts->tmq_length > MAX_MESSAGE_QUEUE_SIZE)
+  {
+    struct TunnelMessageQueueEntry *dq;
+
+    dq = ts->tmq_head;
+    GNUNET_assert (dq != tnq);
+    GNUNET_CONTAINER_DLL_remove (ts->tmq_head,
+                                ts->tmq_tail,
+                                dq);
+    GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
+    ts->th = NULL;
+    GNUNET_STATISTICS_update (stats,
+                             gettext_noop ("# Bytes dropped in mesh queue (overflow)"),
+                             dq->len, 
+                             GNUNET_NO);
+    GNUNET_free (dq);
+  }
   if (NULL == ts->th)
     ts->th = GNUNET_MESH_notify_transmit_ready (ts->tunnel, 
                                                GNUNET_NO /* cork */,
@@ -617,6 +696,9 @@ create_tunnel_to_destination (struct DestinationEntry *de,
 {
   struct TunnelState *ts;
 
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Mesh tunnels created"),
+                           1, GNUNET_NO);
   GNUNET_assert (NULL == de->ts);
   ts = GNUNET_malloc (sizeof (struct TunnelState));
   if (NULL != client)
@@ -637,6 +719,10 @@ create_tunnel_to_destination (struct DestinationEntry *de,
                                          ts);
   if (de->is_service)
   {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Creating tunnel to peer %s offering service %s\n",
+               GNUNET_i2s (&de->details.service_destination.target),
+               GNUNET_h2s (&de->details.service_destination.service_descriptor));
     GNUNET_MESH_peer_request_connect_add (ts->tunnel,
                                          &de->details.service_destination.target);  
   }
@@ -647,10 +733,16 @@ create_tunnel_to_destination (struct DestinationEntry *de,
     case AF_INET:
       GNUNET_MESH_peer_request_connect_by_type (ts->tunnel,
                                                GNUNET_APPLICATION_TYPE_IPV4_GATEWAY);
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Creating tunnel to exit peer for %s\n",
+                 "IPv4");
      break;
     case AF_INET6:
       GNUNET_MESH_peer_request_connect_by_type (ts->tunnel,
                                                GNUNET_APPLICATION_TYPE_IPV6_GATEWAY);
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Creating tunnel to exit peer for %s\n",
+                 "IPv6");
       break;
     default:
       GNUNET_assert (0);
@@ -661,6 +753,96 @@ create_tunnel_to_destination (struct DestinationEntry *de,
 }
 
 
+/**
+ * Free resources associated with a tunnel state.
+ *
+ * @param ts state to free
+ */
+static void
+free_tunnel_state (struct TunnelState *ts)
+{
+  GNUNET_HashCode key;
+  struct TunnelMessageQueueEntry *tnq;
+  struct GNUNET_MESH_Tunnel *tunnel;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Cleaning up tunnel state\n");
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Active tunnels"),
+                           -1, GNUNET_NO);
+  if (GNUNET_SCHEDULER_NO_TASK != ts->destroy_task)
+  {
+    GNUNET_SCHEDULER_cancel (ts->destroy_task);
+    ts->destroy_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  while (NULL != (tnq = ts->tmq_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (ts->tmq_head,
+                                ts->tmq_tail,
+                                tnq);
+    ts->tmq_length--;
+    GNUNET_free (tnq);
+  }
+  GNUNET_assert (0 == ts->tmq_length);
+  if (NULL != ts->client)
+  {
+    GNUNET_SERVER_client_drop (ts->client);
+    ts->client = NULL;
+  }
+  if (NULL != ts->th)
+  {
+    GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
+    ts->th = NULL;
+  }
+  GNUNET_assert (NULL == ts->destination.heap_node);
+  if (NULL != (tunnel = ts->tunnel))
+  {
+    ts->tunnel = NULL;
+    GNUNET_MESH_tunnel_destroy (tunnel);
+  }
+  if (NULL != ts->heap_node)
+  {
+    GNUNET_CONTAINER_heap_remove_node (ts->heap_node);
+    ts->heap_node = NULL;
+    get_tunnel_key_from_ips (ts->af,
+                            ts->protocol,
+                            &ts->source_ip,
+                            ts->source_port,
+                            &ts->destination_ip,
+                            ts->destination_port,
+                            &key);
+    GNUNET_assert (GNUNET_YES ==
+                  GNUNET_CONTAINER_multihashmap_remove (tunnel_map,
+                                                        &key,
+                                                        ts));
+  }
+  if (NULL != ts->destination_container)
+  {
+    GNUNET_assert (ts == ts->destination_container->ts);
+    ts->destination_container->ts = NULL;
+    ts->destination_container = NULL;
+  }
+  GNUNET_free (ts);
+}
+
+
+/**
+ * We have too many active tunnels.  Clean up the oldest tunnel.
+ *
+ * @param except tunnel that must NOT be cleaned up, even if it is the oldest
+ */
+static void
+expire_tunnel (struct TunnelState *except)
+{
+  struct TunnelState *ts;
+
+  ts = GNUNET_CONTAINER_heap_peek (tunnel_heap);
+  if (except == ts)
+    return; /* can't do this */
+  free_tunnel_state (ts);
+}
+
+
 /**
  * Route a packet via mesh to the given destination.  
  *
@@ -686,7 +868,6 @@ route_packet (struct DestinationEntry *destination,
   struct TunnelMessageQueueEntry *tnq;
   size_t alen;
   size_t mlen;
-  GNUNET_MESH_ApplicationType app_type;
   int is_new;
   const struct udp_packet *udp;
   const struct tcp_packet *tcp;
@@ -741,29 +922,57 @@ route_packet (struct DestinationEntry *destination,
                (unsigned int) protocol);
     return;
   }
-
   if (! destination->is_service)
   {  
     switch (destination->details.exit_destination.af)
     {
     case AF_INET:
       alen = sizeof (struct in_addr);
-      app_type = GNUNET_APPLICATION_TYPE_IPV4_GATEWAY; 
      break;
     case AF_INET6:
       alen = sizeof (struct in6_addr);
-      app_type = GNUNET_APPLICATION_TYPE_IPV6_GATEWAY; 
       break;
     default:
       alen = 0;
       GNUNET_assert (0);
     }
+
+    {
+      char sbuf[INET6_ADDRSTRLEN];
+      char dbuf[INET6_ADDRSTRLEN];
+      char xbuf[INET6_ADDRSTRLEN];
+      
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Routing %s packet from %s:%u -> %s:%u to destination %s:%u\n",
+                 (protocol == IPPROTO_TCP) ? "TCP" : "UDP",
+                 inet_ntop (af, source_ip, sbuf, sizeof (sbuf)),
+                 spt,
+                 inet_ntop (af, destination_ip, dbuf, sizeof (dbuf)),
+                 dpt,
+                 inet_ntop (destination->details.exit_destination.af,
+                            &destination->details.exit_destination.ip,
+                            xbuf, sizeof (xbuf)));
+    }
   }
   else
   {
     /* make compiler happy */
     alen = 0;
-    app_type = 0;
+    {
+      char sbuf[INET6_ADDRSTRLEN];
+      char dbuf[INET6_ADDRSTRLEN];
+      
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Routing %s packet from %s:%u -> %s:%u to service %s at peer %s\n",
+                 (protocol == IPPROTO_TCP) ? "TCP" : "UDP",
+                 inet_ntop (af, source_ip, sbuf, sizeof (sbuf)),
+                 spt,
+                 inet_ntop (af, destination_ip, dbuf, sizeof (dbuf)),
+                 dpt,
+                 GNUNET_h2s (&destination->details.service_destination.service_descriptor),
+                 GNUNET_i2s (&destination->details.service_destination.target));
+    }
+
   }
 
   /* see if we have an existing tunnel for this destination */
@@ -803,7 +1012,11 @@ route_packet (struct DestinationEntry *destination,
                                                      &key,
                                                      ts,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); 
-    /* FIXME: expire OLD tunnels if we have too many! */
+    GNUNET_STATISTICS_update (stats,
+                             gettext_noop ("# Active tunnels"),
+                             1, GNUNET_NO);
+    while (GNUNET_CONTAINER_multihashmap_size (tunnel_map) > max_tunnel_mappings)
+      expire_tunnel (ts);
   }
   else
   {
@@ -829,6 +1042,8 @@ route_packet (struct DestinationEntry *destination,
        return;
       }
       tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + mlen);
+      tnq->len = mlen;
+      tnq->msg = &tnq[1];
       usm = (struct GNUNET_EXIT_UdpServiceMessage *) &tnq[1];
       usm->header.size = htons ((uint16_t) mlen);
       usm->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_UDP_TO_SERVICE);
@@ -857,6 +1072,8 @@ route_packet (struct DestinationEntry *destination,
       }
       tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + 
                           mlen);
+      tnq->len = mlen;
+      tnq->msg = &tnq[1];
       uim = (struct GNUNET_EXIT_UdpInternetMessage *) &tnq[1];
       uim->header.size = htons ((uint16_t) mlen);
       uim->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_UDP_TO_INTERNET); 
@@ -898,6 +1115,8 @@ route_packet (struct DestinationEntry *destination,
          return;
        }
        tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + mlen);
+       tnq->len = mlen;
+       tnq->msg = &tnq[1];
        tsm = (struct  GNUNET_EXIT_TcpServiceStartMessage *) &tnq[1];
        tsm->header.size = htons ((uint16_t) mlen);
        tsm->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_TCP_TO_SERVICE_START);
@@ -923,6 +1142,8 @@ route_packet (struct DestinationEntry *destination,
          return;
        }
        tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + mlen);
+       tnq->len = mlen;
+       tnq->msg = &tnq[1];
        tim = (struct  GNUNET_EXIT_TcpInternetStartMessage *) &tnq[1];
        tim->header.size = htons ((uint16_t) mlen);
        tim->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_TCP_TO_INTERNET_START);
@@ -960,6 +1181,8 @@ route_packet (struct DestinationEntry *destination,
        return;
       }
       tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + mlen);
+      tnq->len = mlen;
+      tnq->msg = &tnq[1];
       tdm = (struct  GNUNET_EXIT_TcpDataMessage *) &tnq[1];
       tdm->header.size = htons ((uint16_t) mlen);
       tdm->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_TCP_DATA);
@@ -998,6 +1221,9 @@ message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
   GNUNET_HashCode key;
   struct DestinationEntry *de;
 
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Packets received from TUN interface"),
+                           1, GNUNET_NO);
   mlen = ntohs (message->size);
   if ( (ntohs (message->type) != GNUNET_MESSAGE_TYPE_VPN_HELPER) ||
        (mlen < sizeof (struct GNUNET_MessageHeader) + sizeof (struct tun_header)) )
@@ -1129,6 +1355,9 @@ receive_udp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
   const struct GNUNET_EXIT_UdpReplyMessage *reply;
   size_t mlen;
 
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# UDP packets received from mesh"),
+                           1, GNUNET_NO);
   mlen = ntohs (message->size);
   if (mlen < sizeof (struct GNUNET_EXIT_UdpReplyMessage))
   {
@@ -1147,6 +1376,18 @@ receive_udp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
   }
   reply = (const struct GNUNET_EXIT_UdpReplyMessage *) message;
   mlen -= sizeof (struct GNUNET_EXIT_UdpReplyMessage);
+  {
+    char sbuf[INET6_ADDRSTRLEN];
+    char dbuf[INET6_ADDRSTRLEN];
+    
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Received UDP reply from mesh, sending %u bytes from %s:%u -> %s:%u via TUN\n",
+               (unsigned int) mlen,
+               inet_ntop (ts->af, &ts->destination_ip, sbuf, sizeof (sbuf)),
+               ts->destination_port,
+               inet_ntop (ts->af, &ts->source_ip, dbuf, sizeof (dbuf)),
+               ts->source_port);
+  }
   switch (ts->af)
   {
   case AF_INET:
@@ -1297,6 +1538,9 @@ receive_tcp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
   const struct GNUNET_EXIT_TcpDataMessage *data;
   size_t mlen;
 
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# TCP packets received from mesh"),
+                           1, GNUNET_NO);
   mlen = ntohs (message->size);
   if (mlen < sizeof (struct GNUNET_EXIT_TcpDataMessage))
   {
@@ -1310,6 +1554,18 @@ receive_tcp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
   }
   data = (const struct GNUNET_EXIT_TcpDataMessage *) message;
   mlen -= sizeof (struct GNUNET_EXIT_TcpDataMessage);
+  {
+    char sbuf[INET6_ADDRSTRLEN];
+    char dbuf[INET6_ADDRSTRLEN];
+    
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Received TCP reply from mesh, sending %u bytes from %s:%u -> %s:%u via TUN\n",
+               (unsigned int) mlen,
+               inet_ntop (ts->af, &ts->destination_ip, sbuf, sizeof (sbuf)),
+               ts->destination_port,
+               inet_ntop (ts->af, &ts->source_ip, dbuf, sizeof (dbuf)),
+               ts->source_port);
+  }
   switch (ts->af)
   {
   case AF_INET:
@@ -1543,6 +1799,54 @@ allocate_v6_address (struct in6_addr *v6)
 }
 
 
+/**
+ * Free resources occupied by a destination entry.
+ *
+ * @param de entry to free
+ */
+static void
+free_destination_entry (struct DestinationEntry *de)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Cleaning up destination entry\n");
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Active destinations"),
+                           -1, GNUNET_NO);
+  if (NULL != de->ts)
+  {
+    free_tunnel_state (de->ts);
+    GNUNET_assert (NULL == de->ts);
+  }
+  if (NULL != de->heap_node)
+  {
+    GNUNET_CONTAINER_heap_remove_node (de->heap_node);
+    de->heap_node = NULL;  
+    GNUNET_assert (GNUNET_YES ==
+                  GNUNET_CONTAINER_multihashmap_remove (destination_map,
+                                                        &de->key,
+                                                        de));
+  }
+  GNUNET_free (de);
+}
+
+
+/**
+ * We have too many active destinations.  Clean up the oldest destination.
+ *
+ * @param except destination that must NOT be cleaned up, even if it is the oldest
+ */
+static void 
+expire_destination (struct DestinationEntry *except)
+{
+  struct DestinationEntry *de;
+
+  de = GNUNET_CONTAINER_heap_peek (destination_heap);
+  if (except == de)
+    return; /* can't do this */
+  free_destination_entry (de);
+}
+
+
 /**
  * A client asks us to setup a redirection via some exit
  * node to a particular IP.  Setup the redirection and
@@ -1566,7 +1870,6 @@ service_redirect_to_ip (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Client *cl
   void *addr;
   struct DestinationEntry *de;
   GNUNET_HashCode key;
-  GNUNET_MESH_ApplicationType app_type;
   
   /* validate and parse request */
   mlen = ntohs (message->size);
@@ -1588,7 +1891,6 @@ service_redirect_to_ip (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Client *cl
       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
       return;      
     }
-    app_type = GNUNET_APPLICATION_TYPE_IPV4_GATEWAY; 
     break;
   case AF_INET6:
     if (alen != sizeof (struct in6_addr))
@@ -1597,7 +1899,6 @@ service_redirect_to_ip (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Client *cl
       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
       return;      
     }
-    app_type = GNUNET_APPLICATION_TYPE_IPV6_GATEWAY; 
     break;
   default:
     GNUNET_break (0);
@@ -1658,6 +1959,17 @@ service_redirect_to_ip (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Client *cl
     GNUNET_SERVER_receive_done (client, GNUNET_OK);
     return;
   }
+
+  {
+    char sbuf[INET6_ADDRSTRLEN];
+    char dbuf[INET6_ADDRSTRLEN];
+    
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Allocated address %s for redirection via exit to %s\n",
+               inet_ntop (result_af, addr, sbuf, sizeof (sbuf)),
+               inet_ntop (addr_af,
+                          &msg[1], dbuf, sizeof (dbuf)));
+  }
   
   /* setup destination record */
   de = GNUNET_malloc (sizeof (struct DestinationEntry));
@@ -1678,7 +1990,12 @@ service_redirect_to_ip (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Client *cl
   de->heap_node = GNUNET_CONTAINER_heap_insert (destination_heap,
                                                de,
                                                GNUNET_TIME_absolute_ntoh (msg->expiration_time).abs_value);
-  /* FIXME: expire OLD destinations if we have too many! */
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Active destinations"),
+                           1, GNUNET_NO);
+  while (GNUNET_CONTAINER_multihashmap_size (destination_map) > max_destination_mappings)
+    expire_destination (de);
+
   /* setup tunnel to destination */
   (void) create_tunnel_to_destination (de, 
                                       (GNUNET_NO == ntohl (msg->nac)) ? NULL : client,
@@ -1763,9 +2080,21 @@ service_redirect_to_service (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Clien
   if (result_af == AF_UNSPEC)
   {
     /* failure, we're done */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               _("Failed to allocate IP address for new destination\n"));
     GNUNET_SERVER_receive_done (client, GNUNET_OK);
     return;
   }
+
+  {
+    char sbuf[INET6_ADDRSTRLEN];
+    
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Allocated address %s for redirection to service %s on peer %s\n",
+               inet_ntop (result_af, addr, sbuf, sizeof (sbuf)),
+               GNUNET_h2s (&msg->service_descriptor),
+               GNUNET_i2s (&msg->target));
+  }
   
   /* setup destination record */
   de = GNUNET_malloc (sizeof (struct DestinationEntry));
@@ -1784,7 +2113,8 @@ service_redirect_to_service (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Clien
   de->heap_node = GNUNET_CONTAINER_heap_insert (destination_heap,
                                                de,
                                                GNUNET_TIME_absolute_ntoh (msg->expiration_time).abs_value);
-  /* FIXME: expire OLD destinations if we have too many! */
+  while (GNUNET_CONTAINER_multihashmap_size (destination_map) > max_destination_mappings)
+    expire_destination (de);
   (void) create_tunnel_to_destination (de,
                                       (GNUNET_NO == ntohl (msg->nac)) ? NULL : client,
                                       msg->request_id);
@@ -1816,92 +2146,6 @@ inbound_tunnel_cb (void *cls, struct GNUNET_MESH_Tunnel *tunnel,
 }
 
 
-/**
- * Free resources associated with a tunnel state.
- *
- * @param ts state to free
- */
-static void
-free_tunnel_state (struct TunnelState *ts)
-{
-  GNUNET_HashCode key;
-  struct TunnelMessageQueueEntry *tnq;
-
-  while (NULL != (tnq = ts->head))
-  {
-    GNUNET_CONTAINER_DLL_remove (ts->head,
-                                ts->tail,
-                                tnq);
-    GNUNET_free (tnq);
-  }
-  if (NULL != ts->client)
-  {
-    GNUNET_SERVER_client_drop (ts->client);
-    ts->client = NULL;
-  }
-  if (NULL != ts->th)
-  {
-    GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
-    ts->th = NULL;
-  }
-  GNUNET_assert (NULL == ts->destination.heap_node);
-  if (NULL != ts->tunnel)
-  {
-    GNUNET_MESH_tunnel_destroy (ts->tunnel);
-    ts->tunnel = NULL;
-  }
-  if (NULL != ts->heap_node)
-  {
-    GNUNET_CONTAINER_heap_remove_node (ts->heap_node);
-    ts->heap_node = NULL;
-    get_tunnel_key_from_ips (ts->af,
-                            ts->protocol,
-                            &ts->source_ip,
-                            ts->source_port,
-                            &ts->destination_ip,
-                            ts->destination_port,
-                            &key);
-    GNUNET_assert (GNUNET_YES ==
-                  GNUNET_CONTAINER_multihashmap_remove (tunnel_map,
-                                                        &key,
-                                                        ts));
-  }
-  if (NULL != ts->destination_container)
-  {
-    GNUNET_assert (ts == ts->destination_container->ts);
-    ts->destination_container->ts = NULL;
-    ts->destination_container = NULL;
-  }
-  GNUNET_free (ts);
-}
-
-
-/**
- * Free resources occupied by a destination entry.
- *
- * @param de entry to free
- */
-static void
-free_destination_entry (struct DestinationEntry *de)
-{
-  if (NULL != de->ts)
-  {
-    free_tunnel_state (de->ts);
-    GNUNET_assert (NULL == de->ts);
-  }
-  if (NULL != de->heap_node)
-  {
-    GNUNET_CONTAINER_heap_remove_node (de->heap_node);
-    de->heap_node = NULL;  
-    GNUNET_assert (GNUNET_YES ==
-                  GNUNET_CONTAINER_multihashmap_remove (destination_map,
-                                                        &de->key,
-                                                        de));
-  }
-  GNUNET_free (de);
-}
-
-
 /**
  * Function called whenever an inbound tunnel is destroyed.  Should clean up
  * any associated state.
@@ -1979,6 +2223,8 @@ cleanup (void *cls GNUNET_UNUSED,
 {
   unsigned int i;
 
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "VPN is shutting down\n");
   if (NULL != destination_map)
   {  
     GNUNET_CONTAINER_multihashmap_iterate (destination_map,
@@ -2020,6 +2266,11 @@ cleanup (void *cls GNUNET_UNUSED,
     GNUNET_SERVER_notification_context_destroy (nc);
     nc = NULL;
   }
+  if (stats != NULL)
+  {
+    GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
+    stats = NULL;
+  }
   for (i=0;i<5;i++)
     GNUNET_free_non_null (vpn_argv[i]);
 }
@@ -2138,6 +2389,7 @@ run (void *cls,
   struct in6_addr v6;
 
   cfg = cfg_;
+  stats = GNUNET_STATISTICS_create ("vpn", cfg);
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_number (cfg, "vpn", "MAX_MAPPING",
                                             &max_destination_mappings))