-fix call to stats destroy
[oweals/gnunet.git] / src / vpn / gnunet-service-vpn.c
index 3e8178560b5d01d4991b235c808f48abd51c23df..796a4a6e109e0eaa0ef701f5b91768a40a0542c4 100644 (file)
  *        IP traffic received on those IPs via the GNUnet mesh 
  * @author Philipp Toelke
  * @author Christian Grothoff
- *
- * TODO:
- * Basics:
- * - 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)
- *
- * Code cleanup:
- * - consider moving IP-header building / checksumming code into shared library
- *   with dns/exit/vpn (libgnunettun_tcpip?)
  */
 #include "platform.h"
 #include "gnunet_util_lib.h"
 #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 "gnunet_tun_lib.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.
  */
@@ -66,7 +60,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;
 
@@ -169,6 +163,7 @@ struct TunnelMessageQueueEntry
  */
 struct TunnelState
 {
+
   /**
    * Information about the tunnel to use, NULL if no tunnel
    * is available right now.
@@ -189,12 +184,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
@@ -202,6 +197,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.
    */ 
@@ -216,16 +217,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.
    */
@@ -279,6 +284,11 @@ struct TunnelState
 };
 
 
+/**
+ * Return value from 'main'.
+ */
+static int global_ret;
+
 /**
  * Configuration we use.
  */
@@ -312,6 +322,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".
  */
@@ -478,6 +493,101 @@ send_client_reply (struct GNUNET_SERVER_Client *client,
 }
 
 
+/**
+ * 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);
+  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 (GNUNET_SCHEDULER_NO_TASK != ts->destroy_task)
+  {
+    GNUNET_SCHEDULER_cancel (ts->destroy_task);
+    ts->destroy_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  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);
+}
+
+
+/**
+ * Destroy the mesh tunnel.
+ *
+ * @param cls the 'struct TunnelState' with the tunnel to destroy
+ * @param tc scheduler 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;
+  GNUNET_assert (NULL != ts->tunnel);
+  tunnel = ts->tunnel;
+  ts->tunnel = NULL;
+  GNUNET_MESH_tunnel_destroy (tunnel);
+  free_tunnel_state (ts);
+}
+
+
 /**
  * Method called whenever a peer has disconnected from the tunnel.
  *
@@ -490,7 +600,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);
@@ -500,7 +616,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);
 }
 
 
@@ -521,6 +638,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,
@@ -550,16 +673,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 */,
@@ -568,6 +695,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;
 }
 
@@ -576,8 +706,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
  */
@@ -585,9 +713,32 @@ 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_assert (NULL != ts->tunnel);
+  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);
+    ts->tmq_length--;
+    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 */,
@@ -605,18 +756,24 @@ send_to_tunnel (struct TunnelMessageQueueEntry *tnq,
  *
  * @param de destination entry for which we need to setup a tunnel
  * @param client client to notify on successful tunnel setup, or NULL for none
+ * @param client_af address family of the address returned to the client
  * @param request_id request ID to send in client notification (unused if client is NULL)
  * @return tunnel state of the tunnel that was created
  */
 static struct TunnelState *
 create_tunnel_to_destination (struct DestinationEntry *de,
                              struct GNUNET_SERVER_Client *client,
+                             int client_af,
                              uint64_t request_id)
 {
   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));
+  ts->af = client_af;
   if (NULL != client)
   {
     ts->request_id = request_id;
@@ -627,14 +784,26 @@ create_tunnel_to_destination (struct DestinationEntry *de,
   ts->destination.heap_node = NULL; /* copy is NOT in destination heap */
   de->ts = ts;
   ts->destination_container = de; /* we are referenced from de */
-  ts->af = AF_UNSPEC; /* so far, unknown */
   ts->tunnel = GNUNET_MESH_tunnel_create (mesh_handle,
                                          ts,
                                          &tunnel_peer_connect_handler,
                                          &tunnel_peer_disconnect_handler,
                                          ts);
+  if (NULL == ts->tunnel)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               _("Failed to setup mesh tunnel!\n"));
+    if (NULL != client)
+      GNUNET_SERVER_client_drop (client);
+    GNUNET_free (ts);
+    return NULL;
+  }
   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);  
   }
@@ -645,10 +814,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);
@@ -659,6 +834,24 @@ create_tunnel_to_destination (struct DestinationEntry *de,
 }
 
 
+/**
+ * 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);
+  GNUNET_assert (NULL != ts);
+  if (except == ts)
+    return; /* can't do this */
+  free_tunnel_state (ts);
+}
+
+
 /**
  * Route a packet via mesh to the given destination.  
  *
@@ -684,10 +877,10 @@ 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;
+  const struct GNUNET_TUN_UdpHeader *udp;
+  const struct GNUNET_TUN_TcpHeader *tcp;
+  const struct GNUNET_TUN_IcmpHeader *icmp;
   uint16_t spt;
   uint16_t dpt;
 
@@ -695,7 +888,7 @@ route_packet (struct DestinationEntry *destination,
   {
   case IPPROTO_UDP:
     {
-      if (payload_length < sizeof (struct udp_packet))
+      if (payload_length < sizeof (struct GNUNET_TUN_UdpHeader))
       {
        /* blame kernel? */
        GNUNET_break (0);
@@ -715,7 +908,7 @@ route_packet (struct DestinationEntry *destination,
     break;
   case IPPROTO_TCP:
     {
-      if (payload_length < sizeof (struct tcp_packet))
+      if (payload_length < sizeof (struct GNUNET_TUN_TcpHeader))
       {
        /* blame kernel? */
        GNUNET_break (0);
@@ -733,35 +926,84 @@ route_packet (struct DestinationEntry *destination,
                               &key);
     }
     break;
+  case IPPROTO_ICMP:
+    {
+      if (payload_length < sizeof (struct GNUNET_TUN_IcmpHeader))
+      {
+       /* blame kernel? */
+       GNUNET_break (0);
+       return;
+      }
+      icmp = payload;
+      spt = 0;
+      dpt = 0;
+      get_tunnel_key_from_ips (af,
+                              IPPROTO_ICMP,
+                              source_ip,
+                              0,
+                              destination_ip,
+                              0,
+                              &key);
+    }
+    break;
   default:
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                _("Protocol %u not supported, dropping\n"),
                (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)),
+                 dpt);
+    }
   }
   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 */
@@ -773,9 +1015,11 @@ route_packet (struct DestinationEntry *destination,
        available) or create a fresh one */
     is_new = GNUNET_YES;
     if (NULL == destination->ts)
-      ts = create_tunnel_to_destination (destination, NULL, 0);
+      ts = create_tunnel_to_destination (destination, NULL, af, 0);
     else
       ts = destination->ts;
+    if (NULL == ts)
+      return;
     destination->ts = NULL;
     ts->destination_container = NULL; /* no longer 'contained' */
     /* now bind existing "unbound" tunnel to our IP/port tuple */
@@ -801,7 +1045,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
   {
@@ -810,6 +1058,7 @@ route_packet (struct DestinationEntry *destination,
                                       ts->heap_node,
                                       GNUNET_TIME_absolute_get ().abs_value);
   }
+  GNUNET_assert (NULL != ts->tunnel);
   
   /* send via tunnel */
   switch (protocol)
@@ -820,13 +1069,15 @@ route_packet (struct DestinationEntry *destination,
       struct GNUNET_EXIT_UdpServiceMessage *usm;
 
       mlen = sizeof (struct GNUNET_EXIT_UdpServiceMessage) + 
-       payload_length - sizeof (struct udp_packet);
+       payload_length - sizeof (struct GNUNET_TUN_UdpHeader);
       if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
       {
        GNUNET_break (0);
        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);
@@ -837,7 +1088,7 @@ route_packet (struct DestinationEntry *destination,
       usm->service_descriptor = destination->details.service_destination.service_descriptor;
       memcpy (&usm[1],
              &udp[1],
-             payload_length - sizeof (struct udp_packet));
+             payload_length - sizeof (struct GNUNET_TUN_UdpHeader));
     }
     else
     {
@@ -847,7 +1098,7 @@ route_packet (struct DestinationEntry *destination,
       void *payload;
 
       mlen = sizeof (struct GNUNET_EXIT_UdpInternetMessage) + 
-       alen + payload_length - sizeof (struct udp_packet);
+       alen + payload_length - sizeof (struct GNUNET_TUN_UdpHeader);
       if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
       {
        GNUNET_break (0);
@@ -855,6 +1106,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); 
@@ -878,7 +1131,7 @@ route_packet (struct DestinationEntry *destination,
       }
       memcpy (payload,
              &udp[1],
-             payload_length - sizeof (struct udp_packet));
+             payload_length - sizeof (struct GNUNET_TUN_UdpHeader));
     }
     break;
   case IPPROTO_TCP:
@@ -889,13 +1142,15 @@ route_packet (struct DestinationEntry *destination,
        struct GNUNET_EXIT_TcpServiceStartMessage *tsm;
 
        mlen = sizeof (struct GNUNET_EXIT_TcpServiceStartMessage) + 
-         payload_length - sizeof (struct tcp_packet);
+         payload_length - sizeof (struct GNUNET_TUN_TcpHeader);
        if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
        {
          GNUNET_break (0);
          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);
@@ -904,7 +1159,7 @@ route_packet (struct DestinationEntry *destination,
        tsm->tcp_header = *tcp;
        memcpy (&tsm[1],
                &tcp[1],
-               payload_length - sizeof (struct tcp_packet));
+               payload_length - sizeof (struct GNUNET_TUN_TcpHeader));
       }
       else
       {
@@ -914,13 +1169,15 @@ route_packet (struct DestinationEntry *destination,
        void *payload;
 
        mlen = sizeof (struct GNUNET_EXIT_TcpInternetStartMessage) + 
-         alen + payload_length - sizeof (struct tcp_packet);
+         alen + payload_length - sizeof (struct GNUNET_TUN_TcpHeader);
        if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
        {
          GNUNET_break (0);
          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);
@@ -943,7 +1200,7 @@ route_packet (struct DestinationEntry *destination,
        }
        memcpy (payload,
                &tcp[1],
-               payload_length - sizeof (struct tcp_packet));
+               payload_length - sizeof (struct GNUNET_TUN_TcpHeader));
       }
     }
     else
@@ -951,23 +1208,260 @@ route_packet (struct DestinationEntry *destination,
       struct GNUNET_EXIT_TcpDataMessage *tdm;
 
       mlen = sizeof (struct GNUNET_EXIT_TcpDataMessage) + 
-       alen + payload_length - sizeof (struct tcp_packet);
+       payload_length - sizeof (struct GNUNET_TUN_TcpHeader);
       if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
       {
        GNUNET_break (0);
        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);
+      tdm->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_TCP_DATA_TO_EXIT);
       tdm->reserved = htonl (0);
       tdm->tcp_header = *tcp;
       memcpy (&tdm[1],
              &tcp[1],
-             payload_length - sizeof (struct tcp_packet));
+             payload_length - sizeof (struct GNUNET_TUN_TcpHeader));
      }
     break;
+  case IPPROTO_ICMP:
+    if (destination->is_service)
+    {
+      struct GNUNET_EXIT_IcmpServiceMessage *ism;
+
+      mlen = sizeof (struct GNUNET_EXIT_IcmpServiceMessage) + 
+       payload_length - sizeof (struct GNUNET_TUN_IcmpHeader);
+      if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
+      {
+       GNUNET_break (0);
+       return;
+      }
+      tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + mlen);
+      tnq->msg = &tnq[1];
+      ism = (struct GNUNET_EXIT_IcmpServiceMessage *) &tnq[1];
+      ism->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_ICMP_TO_SERVICE);
+      ism->af = htonl (af); /* need to tell destination ICMP protocol family! */
+      ism->service_descriptor = destination->details.service_destination.service_descriptor;
+      ism->icmp_header = *icmp;
+      /* ICMP protocol translation will be done by the receiver (as we don't know
+        the target AF); however, we still need to possibly discard the payload
+        depending on the ICMP type */
+      switch (af)
+      {
+      case AF_INET:
+       switch (icmp->type)
+       {
+       case GNUNET_TUN_ICMPTYPE_ECHO_REPLY:
+       case GNUNET_TUN_ICMPTYPE_ECHO_REQUEST:
+         break;
+       case GNUNET_TUN_ICMPTYPE_DESTINATION_UNREACHABLE:
+       case GNUNET_TUN_ICMPTYPE_SOURCE_QUENCH:
+       case GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED:
+         /* throw away ICMP payload, won't be useful for the other side anyway */
+         payload_length = sizeof (struct GNUNET_TUN_IcmpHeader); 
+         break;
+       default:
+         GNUNET_STATISTICS_update (stats,
+                                   gettext_noop ("# ICMPv4 packets dropped (not allowed)"),
+                                   1, GNUNET_NO);
+         return;
+       }
+       /* end of AF_INET */
+       break;
+      case AF_INET6:
+       switch (icmp->type)
+       {
+       case GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE:
+       case GNUNET_TUN_ICMPTYPE6_PACKET_TOO_BIG:
+       case GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED:
+       case GNUNET_TUN_ICMPTYPE6_PARAMETER_PROBLEM:
+         /* throw away ICMP payload, won't be useful for the other side anyway */
+         payload_length = sizeof (struct GNUNET_TUN_IcmpHeader); 
+         break;
+       case GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST:
+       case GNUNET_TUN_ICMPTYPE6_ECHO_REPLY:
+         break;
+       default:
+         GNUNET_STATISTICS_update (stats,
+                                   gettext_noop ("# ICMPv6 packets dropped (not allowed)"),
+                                   1, GNUNET_NO);
+         return;
+       }       
+       /* end of AF_INET6 */
+       break;
+      default:
+       GNUNET_assert (0);
+       break;
+      }
+
+      /* update length calculations, as payload_length may have changed */
+      mlen = sizeof (struct GNUNET_EXIT_IcmpServiceMessage) + 
+       alen + payload_length - sizeof (struct GNUNET_TUN_IcmpHeader);      
+      tnq->len = mlen;
+      ism->header.size = htons ((uint16_t) mlen);
+      /* finally, copy payload (if there is any left...) */
+      memcpy (&ism[1],
+             &icmp[1],
+             payload_length - sizeof (struct GNUNET_TUN_IcmpHeader));
+    }
+    else
+    {
+      struct GNUNET_EXIT_IcmpInternetMessage *iim;
+      struct in_addr *ip4dst;
+      struct in6_addr *ip6dst;
+      void *payload;
+
+      mlen = sizeof (struct GNUNET_EXIT_IcmpInternetMessage) + 
+       alen + payload_length - sizeof (struct GNUNET_TUN_IcmpHeader);
+      if (mlen >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
+      {
+       GNUNET_break (0);
+       return;
+      }
+      tnq = GNUNET_malloc (sizeof (struct TunnelMessageQueueEntry) + 
+                          mlen);
+      tnq->msg = &tnq[1];
+      iim = (struct GNUNET_EXIT_IcmpInternetMessage *) &tnq[1];
+      iim->header.type = htons (GNUNET_MESSAGE_TYPE_VPN_ICMP_TO_INTERNET); 
+      iim->icmp_header = *icmp;
+      /* Perform ICMP protocol-translation (depending on destination AF and source AF)
+        and throw away ICMP payload depending on ICMP message type */
+      switch (af)
+      {
+      case AF_INET:
+       switch (icmp->type)
+       {
+       case GNUNET_TUN_ICMPTYPE_ECHO_REPLY:      
+         if (destination->details.exit_destination.af == AF_INET6)
+           iim->icmp_header.type = GNUNET_TUN_ICMPTYPE6_ECHO_REPLY;
+         break;
+       case GNUNET_TUN_ICMPTYPE_ECHO_REQUEST:    
+         if (destination->details.exit_destination.af == AF_INET6)
+           iim->icmp_header.type = GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST;
+         break;
+       case GNUNET_TUN_ICMPTYPE_DESTINATION_UNREACHABLE:
+         if (destination->details.exit_destination.af == AF_INET6)
+           iim->icmp_header.type = GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE;
+         /* throw away IP-payload, exit will have to make it up anyway */
+         payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+         break;
+       case GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED: 
+         if (destination->details.exit_destination.af == AF_INET6)
+           iim->icmp_header.type = GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED;
+         /* throw away IP-payload, exit will have to make it up anyway */
+         payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+         break;
+       case GNUNET_TUN_ICMPTYPE_SOURCE_QUENCH:
+         if (destination->details.exit_destination.af == AF_INET6)
+           {
+             GNUNET_STATISTICS_update (stats,
+                                       gettext_noop ("# ICMPv4 packets dropped (impossible PT to v6)"),
+                                       1, GNUNET_NO);
+             GNUNET_free (tnq);
+             return;
+           }
+         /* throw away IP-payload, exit will have to make it up anyway */
+         payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+         break;
+       default:
+         GNUNET_STATISTICS_update (stats,
+                                   gettext_noop ("# ICMPv4 packets dropped (type not allowed)"),
+                                   1, GNUNET_NO);
+         GNUNET_free (tnq);        
+         return;
+       }
+       /* end of AF_INET */
+       break;
+      case AF_INET6:
+       switch (icmp->type)
+         {
+         case GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE:
+           if (destination->details.exit_destination.af == AF_INET6)
+             iim->icmp_header.type = GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE;
+           /* throw away IP-payload, exit will have to make it up anyway */
+           payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+           break;
+         case GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED:
+           if (destination->details.exit_destination.af == AF_INET)
+             iim->icmp_header.type = GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED;
+           /* throw away IP-payload, exit will have to make it up anyway */
+           payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+           break;
+         case GNUNET_TUN_ICMPTYPE6_PACKET_TOO_BIG:
+           if (destination->details.exit_destination.af == AF_INET)
+           {
+             GNUNET_STATISTICS_update (stats,
+                                       gettext_noop ("# ICMPv6 packets dropped (impossible PT to v4)"),
+                                       1, GNUNET_NO);
+             GNUNET_free (tnq);
+             return;
+           }
+           /* throw away IP-payload, exit will have to make it up anyway */
+           payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+           break;
+         case GNUNET_TUN_ICMPTYPE6_PARAMETER_PROBLEM:
+           if (destination->details.exit_destination.af == AF_INET)
+           {
+             GNUNET_STATISTICS_update (stats,
+                                       gettext_noop ("# ICMPv6 packets dropped (impossible PT to v4)"),
+                                       1, GNUNET_NO);
+             GNUNET_free (tnq);
+             return;
+           }
+           /* throw away IP-payload, exit will have to make it up anyway */
+           payload_length = sizeof (struct GNUNET_TUN_IcmpHeader);
+           break;
+         case GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST:
+           if (destination->details.exit_destination.af == AF_INET)
+             iim->icmp_header.type = GNUNET_TUN_ICMPTYPE_ECHO_REQUEST;
+           break;
+         case GNUNET_TUN_ICMPTYPE6_ECHO_REPLY:
+           if (destination->details.exit_destination.af == AF_INET)
+             iim->icmp_header.type = GNUNET_TUN_ICMPTYPE_ECHO_REPLY;
+           break;
+         default:
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv6 packets dropped (type not allowed)"),
+                                     1, GNUNET_NO);
+           GNUNET_free (tnq);      
+           return;
+         }
+       /* end of AF_INET6 */
+       break;
+      default:
+       GNUNET_assert (0);
+      } 
+      /* update length calculations, as payload_length may have changed */
+      mlen = sizeof (struct GNUNET_EXIT_IcmpInternetMessage) + 
+       alen + payload_length - sizeof (struct GNUNET_TUN_IcmpHeader);      
+      tnq->len = mlen;
+      iim->header.size = htons ((uint16_t) mlen);
+
+      /* need to tell destination ICMP protocol family! */
+      iim->af = htonl (destination->details.exit_destination.af);
+      switch (destination->details.exit_destination.af)
+      {
+      case AF_INET:
+       ip4dst = (struct in_addr *) &iim[1];
+       *ip4dst = destination->details.exit_destination.ip.v4;
+       payload = &ip4dst[1];
+       break;
+      case AF_INET6:
+       ip6dst = (struct in6_addr *) &iim[1];
+       *ip6dst = destination->details.exit_destination.ip.v6;
+       payload = &ip6dst[1];
+       break;
+      default:
+       GNUNET_assert (0);
+      }
+      memcpy (payload,
+             &icmp[1],
+             payload_length - sizeof (struct GNUNET_TUN_IcmpHeader));
+    }
+    break;
   default:
     /* not supported above, how can we get here !? */
     GNUNET_assert (0);
@@ -991,33 +1485,36 @@ static void
 message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
                const struct GNUNET_MessageHeader *message)
 {
-  const struct tun_header *tun;
+  const struct GNUNET_TUN_Layer2PacketHeader *tun;
   size_t mlen;
   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)) )
+       (mlen < sizeof (struct GNUNET_MessageHeader) + sizeof (struct GNUNET_TUN_Layer2PacketHeader)) )
   {
     GNUNET_break (0);
     return;
   }
-  tun = (const struct tun_header *) &message[1];
-  mlen -= (sizeof (struct GNUNET_MessageHeader) + sizeof (struct tun_header));
+  tun = (const struct GNUNET_TUN_Layer2PacketHeader *) &message[1];
+  mlen -= (sizeof (struct GNUNET_MessageHeader) + sizeof (struct GNUNET_TUN_Layer2PacketHeader));
   switch (ntohs (tun->proto))
   {
   case ETH_P_IPV6:
     {
-      const struct ip6_header *pkt6;
+      const struct GNUNET_TUN_IPv6Header *pkt6;
       
-      if (mlen < sizeof (struct ip6_header))
+      if (mlen < sizeof (struct GNUNET_TUN_IPv6Header))
       {
        /* blame kernel */
        GNUNET_break (0);
        return;
       }
-      pkt6 = (const struct ip6_header *) &tun[1];
+      pkt6 = (const struct GNUNET_TUN_IPv6Header *) &tun[1];
       get_destination_key_from_ip (AF_INET6,
                                   &pkt6->destination_address,
                                   &key);
@@ -1045,20 +1542,20 @@ message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
                    &pkt6->source_address,                  
                    &pkt6->destination_address,             
                    &pkt6[1],
-                   mlen - sizeof (struct ip6_header));
+                   mlen - sizeof (struct GNUNET_TUN_IPv6Header));
     }
     break;
   case ETH_P_IPV4:
     {
-      struct ip4_header *pkt4;
+      struct GNUNET_TUN_IPv4Header *pkt4;
 
-      if (mlen < sizeof (struct ip4_header))
+      if (mlen < sizeof (struct GNUNET_TUN_IPv4Header))
       {
        /* blame kernel */
        GNUNET_break (0);
        return;
       }
-      pkt4 = (struct ip4_header *) &tun[1];
+      pkt4 = (struct GNUNET_TUN_IPv4Header *) &tun[1];
       get_destination_key_from_ip (AF_INET,
                                   &pkt4->destination_address,
                                   &key);
@@ -1080,7 +1577,7 @@ message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
                               sizeof (buf)));
        return;
       }
-      if (pkt4->header_length * 4 != sizeof (struct ip4_header))
+      if (pkt4->header_length * 4 != sizeof (struct GNUNET_TUN_IPv4Header))
       {
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    _("Received IPv4 packet with options (dropping it)\n"));                
@@ -1092,7 +1589,7 @@ message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
                    &pkt4->source_address,                  
                    &pkt4->destination_address,             
                    &pkt4[1],
-                   mlen - sizeof (struct ip4_header));
+                   mlen - sizeof (struct GNUNET_TUN_IPv4Header));
     }
     break;
   default:
@@ -1104,6 +1601,399 @@ message_token (void *cls GNUNET_UNUSED, void *client GNUNET_UNUSED,
 }
 
 
+/**
+ * Synthesize a plausible ICMP payload for an ICMP error
+ * response on the given tunnel.
+ *
+ * @param ts tunnel information
+ * @param ipp IPv4 header to fill in (ICMP payload)
+ * @param udp "UDP" header to fill in (ICMP payload); might actually
+ *            also be the first 8 bytes of the TCP header
+ */
+static void
+make_up_icmpv4_payload (struct TunnelState *ts,
+                       struct GNUNET_TUN_IPv4Header *ipp,
+                       struct GNUNET_TUN_UdpHeader *udp)
+{
+  GNUNET_TUN_initialize_ipv4_header (ipp,
+                                    ts->protocol,
+                                    sizeof (struct GNUNET_TUN_TcpHeader),
+                                    &ts->source_ip.v4,
+                                    &ts->destination_ip.v4);
+  udp->spt = htons (ts->source_port);
+  udp->dpt = htons (ts->destination_port);
+  udp->len = htons (0);
+  udp->crc = htons (0);
+}
+
+
+/**
+ * Synthesize a plausible ICMP payload for an ICMP error
+ * response on the given tunnel.
+ *
+ * @param ts tunnel information
+ * @param ipp IPv6 header to fill in (ICMP payload)
+ * @param udp "UDP" header to fill in (ICMP payload); might actually
+ *            also be the first 8 bytes of the TCP header
+ */
+static void
+make_up_icmpv6_payload (struct TunnelState *ts,
+                       struct GNUNET_TUN_IPv6Header *ipp,
+                       struct GNUNET_TUN_UdpHeader *udp)
+{
+  GNUNET_TUN_initialize_ipv6_header (ipp,
+                                    ts->protocol,
+                                    sizeof (struct GNUNET_TUN_TcpHeader),
+                                    &ts->source_ip.v6,
+                                    &ts->destination_ip.v6);
+  udp->spt = htons (ts->source_port);
+  udp->dpt = htons (ts->destination_port);
+  udp->len = htons (0);
+  udp->crc = htons (0);
+}
+
+
+/**
+ * We got an ICMP packet back from the MESH tunnel.  Pass it on to the
+ * local virtual interface via the helper.
+ *
+ * @param cls closure, NULL
+ * @param tunnel connection to the other end
+ * @param tunnel_ctx pointer to our 'struct TunnelState *'
+ * @param sender who sent the message
+ * @param message the actual message
+ * @param atsi performance data for the connection
+ * @return GNUNET_OK to keep the connection open,
+ *         GNUNET_SYSERR to close it (signal serious error)
+ */ 
+static int
+receive_icmp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
+                  void **tunnel_ctx, const struct GNUNET_PeerIdentity *sender,
+                  const struct GNUNET_MessageHeader *message,
+                  const struct GNUNET_ATS_Information *atsi GNUNET_UNUSED)
+{
+  struct TunnelState *ts = *tunnel_ctx;
+  const struct GNUNET_EXIT_IcmpToVPNMessage *i2v;
+  size_t mlen;
+
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# ICMP packets received from mesh"),
+                           1, GNUNET_NO);
+  mlen = ntohs (message->size);
+  if (mlen < sizeof (struct GNUNET_EXIT_IcmpToVPNMessage))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == ts->heap_node)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (AF_UNSPEC == ts->af)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  i2v = (const struct GNUNET_EXIT_IcmpToVPNMessage *) message;
+  mlen -= sizeof (struct GNUNET_EXIT_IcmpToVPNMessage);
+  {
+    char sbuf[INET6_ADDRSTRLEN];
+    char dbuf[INET6_ADDRSTRLEN];
+    
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Received ICMP packet from mesh, sending %u bytes from %s -> %s via TUN\n",
+               (unsigned int) mlen,
+               inet_ntop (ts->af, &ts->destination_ip, sbuf, sizeof (sbuf)),
+               inet_ntop (ts->af, &ts->source_ip, dbuf, sizeof (dbuf)));
+  }
+  switch (ts->af)
+  {
+  case AF_INET:
+    {
+      size_t size = sizeof (struct GNUNET_TUN_IPv4Header) 
+       + sizeof (struct GNUNET_TUN_IcmpHeader) 
+       + sizeof (struct GNUNET_MessageHeader) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
+       mlen;
+      {
+       /* reserve some extra space in case we have an ICMP type here where
+          we will need to make up the payload ourselves */
+       char buf[size + sizeof (struct GNUNET_TUN_IPv4Header) + 8];
+       struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv4Header *ipv4 = (struct GNUNET_TUN_IPv4Header *) &tun[1];
+       struct GNUNET_TUN_IcmpHeader *icmp = (struct GNUNET_TUN_IcmpHeader *) &ipv4[1];
+       msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
+       tun->flags = htons (0);
+       tun->proto = htons (ETH_P_IPV4);
+       GNUNET_TUN_initialize_ipv4_header (ipv4,
+                                          IPPROTO_ICMP,
+                                          sizeof (struct GNUNET_TUN_IcmpHeader) + mlen,
+                                          &ts->destination_ip.v4,
+                                          &ts->source_ip.v4);
+       *icmp = i2v->icmp_header;
+       memcpy (&icmp[1],
+               &i2v[1],
+               mlen);
+       /* For some ICMP types, we need to adjust (make up) the payload here. 
+          Also, depending on the AF used on the other side, we have to 
+          do ICMP PT (translate ICMP types) */
+       switch (ntohl (i2v->af))
+       {
+       case AF_INET:     
+         switch (icmp->type)
+         {
+         case GNUNET_TUN_ICMPTYPE_ECHO_REPLY:
+         case GNUNET_TUN_ICMPTYPE_ECHO_REQUEST:
+           break;
+         case GNUNET_TUN_ICMPTYPE_DESTINATION_UNREACHABLE:
+         case GNUNET_TUN_ICMPTYPE_SOURCE_QUENCH:
+         case GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED:         
+           {
+             struct GNUNET_TUN_IPv4Header *ipp = (struct GNUNET_TUN_IPv4Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv4Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv4_payload (ts, ipp, udp);
+           }
+           break;
+         default:
+           GNUNET_break_op (0);
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv4 packets dropped (type not allowed)"),
+                                     1, GNUNET_NO);
+           return GNUNET_SYSERR;
+         }
+         /* end AF_INET */
+         break;
+       case AF_INET6:
+         /* ICMP PT 6-to-4 and possibly making up payloads */
+         switch (icmp->type)
+         {
+         case GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE:
+           icmp->type = GNUNET_TUN_ICMPTYPE_DESTINATION_UNREACHABLE;
+           {
+             struct GNUNET_TUN_IPv4Header *ipp = (struct GNUNET_TUN_IPv4Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv4Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv4_payload (ts, ipp, udp);
+           }
+           break;
+         case GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED:
+           icmp->type = GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED;
+           {
+             struct GNUNET_TUN_IPv4Header *ipp = (struct GNUNET_TUN_IPv4Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv4Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv4_payload (ts, ipp, udp);
+           }
+           break;
+         case GNUNET_TUN_ICMPTYPE6_PACKET_TOO_BIG:
+         case GNUNET_TUN_ICMPTYPE6_PARAMETER_PROBLEM:
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv6 packets dropped (impossible PT to v4)"),
+                                     1, GNUNET_NO);
+           return GNUNET_OK;
+         case GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST:
+           icmp->type = GNUNET_TUN_ICMPTYPE_ECHO_REQUEST;
+           break;
+         case GNUNET_TUN_ICMPTYPE6_ECHO_REPLY:
+           icmp->type = GNUNET_TUN_ICMPTYPE_ECHO_REPLY;
+           break;
+         default:
+           GNUNET_break_op (0);
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv6 packets dropped (type not allowed)"),
+                                     1, GNUNET_NO);
+           return GNUNET_SYSERR;
+         }
+         /* end AF_INET6 */
+         break;
+       default:
+         GNUNET_break_op (0);
+         return GNUNET_SYSERR;
+       }       
+       msg->size = htons (size);
+       GNUNET_TUN_calculate_icmp_checksum (icmp,
+                                           &i2v[1],
+                                           mlen);
+       (void) GNUNET_HELPER_send (helper_handle,
+                                  msg,
+                                  GNUNET_YES,
+                                  NULL, NULL);
+      }
+    }
+    break;
+  case AF_INET6:
+    {
+      size_t size = sizeof (struct GNUNET_TUN_IPv6Header) 
+       + sizeof (struct GNUNET_TUN_IcmpHeader) 
+       + sizeof (struct GNUNET_MessageHeader) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
+       mlen;
+      {
+       char buf[size + sizeof (struct GNUNET_TUN_IPv6Header) + 8];
+       struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv6Header *ipv6 = (struct GNUNET_TUN_IPv6Header *) &tun[1];
+       struct GNUNET_TUN_IcmpHeader *icmp = (struct GNUNET_TUN_IcmpHeader *) &ipv6[1];
+       msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
+       tun->flags = htons (0);
+       tun->proto = htons (ETH_P_IPV6);
+       GNUNET_TUN_initialize_ipv6_header (ipv6,
+                                          IPPROTO_ICMP,
+                                          sizeof (struct GNUNET_TUN_IcmpHeader) + mlen,
+                                          &ts->destination_ip.v6,
+                                          &ts->source_ip.v6);
+       *icmp = i2v->icmp_header;
+       memcpy (&icmp[1],
+               &i2v[1],
+               mlen);
+
+       /* For some ICMP types, we need to adjust (make up) the payload here. 
+          Also, depending on the AF used on the other side, we have to 
+          do ICMP PT (translate ICMP types) */
+       switch (ntohl (i2v->af))
+       {
+       case AF_INET:     
+         /* ICMP PT 4-to-6 and possibly making up payloads */
+         switch (icmp->type)
+         {
+         case GNUNET_TUN_ICMPTYPE_ECHO_REPLY:
+           icmp->type = GNUNET_TUN_ICMPTYPE6_ECHO_REPLY;
+           break;
+         case GNUNET_TUN_ICMPTYPE_ECHO_REQUEST:
+           icmp->type = GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST;
+           break;
+         case GNUNET_TUN_ICMPTYPE_DESTINATION_UNREACHABLE:
+           icmp->type = GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE;
+           {
+             struct GNUNET_TUN_IPv6Header *ipp = (struct GNUNET_TUN_IPv6Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv6Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv6_payload (ts, ipp, udp);
+           }
+           break;
+         case GNUNET_TUN_ICMPTYPE_TIME_EXCEEDED:         
+           icmp->type = GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED;
+           {
+             struct GNUNET_TUN_IPv6Header *ipp = (struct GNUNET_TUN_IPv6Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv6Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv6_payload (ts, ipp, udp);
+           }
+           break;
+         case GNUNET_TUN_ICMPTYPE_SOURCE_QUENCH:
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv4 packets dropped (impossible PT to v6)"),
+                                     1, GNUNET_NO);        
+           return GNUNET_OK;
+         default:
+           GNUNET_break_op (0);
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv4 packets dropped (type not allowed)"),
+                                     1, GNUNET_NO);
+           return GNUNET_SYSERR;
+         }
+         /* end AF_INET */
+         break;
+       case AF_INET6:
+         switch (icmp->type)
+         {
+         case GNUNET_TUN_ICMPTYPE6_DESTINATION_UNREACHABLE:
+         case GNUNET_TUN_ICMPTYPE6_TIME_EXCEEDED:
+         case GNUNET_TUN_ICMPTYPE6_PACKET_TOO_BIG:
+         case GNUNET_TUN_ICMPTYPE6_PARAMETER_PROBLEM:
+           {
+             struct GNUNET_TUN_IPv6Header *ipp = (struct GNUNET_TUN_IPv6Header *) &icmp[1];
+             struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipp[1];
+             
+             if (mlen != 0)
+               {
+                 /* sender did not strip ICMP payload? */
+                 GNUNET_break_op (0);
+                 return GNUNET_SYSERR;
+               }
+             size += sizeof (struct GNUNET_TUN_IPv6Header) + 8;
+             GNUNET_assert (8 == sizeof (struct GNUNET_TUN_UdpHeader));
+             make_up_icmpv6_payload (ts, ipp, udp);
+           }
+           break;
+         case GNUNET_TUN_ICMPTYPE6_ECHO_REQUEST:
+           break;
+         default:
+           GNUNET_break_op (0);
+           GNUNET_STATISTICS_update (stats,
+                                     gettext_noop ("# ICMPv6 packets dropped (type not allowed)"),
+                                     1, GNUNET_NO);
+           return GNUNET_SYSERR;
+         }
+         /* end AF_INET6 */
+         break;
+       default:
+         GNUNET_break_op (0);
+         return GNUNET_SYSERR;
+       }
+       msg->size = htons (size);
+       GNUNET_TUN_calculate_icmp_checksum (icmp,
+                                           &i2v[1], mlen);
+       (void) GNUNET_HELPER_send (helper_handle,
+                                  msg,
+                                  GNUNET_YES,
+                                  NULL, NULL);
+      }
+    }
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  GNUNET_CONTAINER_heap_update_cost (tunnel_heap, 
+                                    ts->heap_node,
+                                    GNUNET_TIME_absolute_get ().abs_value);
+  return GNUNET_OK;
+}
+
+
 /**
  * We got a UDP packet back from the MESH tunnel.  Pass it on to the
  * local virtual interface via the helper.
@@ -1127,6 +2017,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))
   {
@@ -1145,42 +2038,42 @@ 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:
     {
-      size_t size = sizeof (struct ip4_header) 
-       + sizeof (struct udp_packet
+      size_t size = sizeof (struct GNUNET_TUN_IPv4Header) 
+       + sizeof (struct GNUNET_TUN_UdpHeader
        + sizeof (struct GNUNET_MessageHeader) +
-       sizeof (struct tun_header) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
        mlen;
       {
        char buf[size];
        struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
-       struct tun_header *tun = (struct tun_header*) &msg[1];
-       struct ip4_header *ipv4 = (struct ip4_header *) &tun[1];
-       struct udp_packet *udp = (struct udp_packet *) &ipv4[1];
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv4Header *ipv4 = (struct GNUNET_TUN_IPv4Header *) &tun[1];
+       struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipv4[1];
        msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
        msg->size = htons (size);
        tun->flags = htons (0);
        tun->proto = htons (ETH_P_IPV4);
-       ipv4->version = 4;
-       ipv4->header_length = sizeof (struct ip4_header) / 4;
-       ipv4->diff_serv = 0;
-       ipv4->total_length = htons (sizeof (struct ip4_header) +
-                                   sizeof (struct udp_packet) +
-                                   mlen);
-       ipv4->identification = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 
-                                                                   UINT16_MAX + 1);
-       ipv4->flags = 0;
-       ipv4->fragmentation_offset = 0;
-       ipv4->ttl = 255;
-       ipv4->protocol = IPPROTO_UDP;
-       ipv4->checksum = 0; 
-       ipv4->source_address = ts->destination_ip.v4;
-       ipv4->destination_address = ts->source_ip.v4;
-       ipv4->checksum =
-         GNUNET_CRYPTO_crc16_n (ipv4, sizeof (struct ip4_header));
+       GNUNET_TUN_initialize_ipv4_header (ipv4,
+                                          IPPROTO_UDP,
+                                          sizeof (struct GNUNET_TUN_UdpHeader) + mlen,
+                                          &ts->destination_ip.v4,
+                                          &ts->source_ip.v4);
        if (0 == ntohs (reply->source_port))
          udp->spt = htons (ts->destination_port);
        else
@@ -1189,8 +2082,11 @@ receive_udp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
          udp->dpt = htons (ts->source_port);
        else
          udp->dpt = reply->destination_port;
-       udp->len = htons (mlen + sizeof (struct udp_packet));
-       udp->crc = 0; // FIXME: optional, but we might want to calculate this one anyway
+       udp->len = htons (mlen + sizeof (struct GNUNET_TUN_UdpHeader));
+       GNUNET_TUN_calculate_udp4_checksum (ipv4,
+                                           udp,
+                                           &reply[1],
+                                           mlen);
        memcpy (&udp[1],
                &reply[1],
                mlen);
@@ -1203,30 +2099,26 @@ receive_udp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
     break;
   case AF_INET6:
     {
-      size_t size = sizeof (struct ip6_header) 
-       + sizeof (struct udp_packet
+      size_t size = sizeof (struct GNUNET_TUN_IPv6Header) 
+       + sizeof (struct GNUNET_TUN_UdpHeader
        + sizeof (struct GNUNET_MessageHeader) +
-       sizeof (struct tun_header) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
        mlen;
       {
        char buf[size];
        struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
-       struct tun_header *tun = (struct tun_header*) &msg[1];
-       struct ip6_header *ipv6 = (struct ip6_header *) &tun[1];
-       struct udp_packet *udp = (struct udp_packet *) &ipv6[1];
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv6Header *ipv6 = (struct GNUNET_TUN_IPv6Header *) &tun[1];
+       struct GNUNET_TUN_UdpHeader *udp = (struct GNUNET_TUN_UdpHeader *) &ipv6[1];
        msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
        msg->size = htons (size);
        tun->flags = htons (0);
        tun->proto = htons (ETH_P_IPV6);
-       ipv6->traffic_class_h = 0;
-       ipv6->version = 6;
-       ipv6->traffic_class_l = 0;
-       ipv6->flow_label = 0;
-       ipv6->payload_length = htons (sizeof (struct udp_packet) + sizeof (struct ip6_header) + mlen);
-       ipv6->next_header = IPPROTO_UDP;
-       ipv6->hop_limit = 255;
-       ipv6->source_address = ts->destination_ip.v6;
-       ipv6->destination_address = ts->source_ip.v6;
+       GNUNET_TUN_initialize_ipv6_header (ipv6,
+                                          IPPROTO_UDP,
+                                          sizeof (struct GNUNET_TUN_UdpHeader) + mlen,
+                                          &ts->destination_ip.v6,
+                                          &ts->source_ip.v6);
        if (0 == ntohs (reply->source_port))
          udp->spt = htons (ts->destination_port);
        else
@@ -1235,25 +2127,13 @@ receive_udp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
          udp->dpt = htons (ts->source_port);
        else
          udp->dpt = reply->destination_port;
-       udp->len = htons (mlen + sizeof (struct udp_packet));
-       udp->crc = 0;
+       udp->len = htons (mlen + sizeof (struct GNUNET_TUN_UdpHeader));
+       GNUNET_TUN_calculate_udp6_checksum (ipv6,
+                                           udp,
+                                           &reply[1], mlen);
        memcpy (&udp[1],
                &reply[1],
                mlen);
-       {
-         uint32_t sum = 0;
-         sum =
-           GNUNET_CRYPTO_crc16_step (sum, &ipv6->source_address, 
-                                     sizeof (struct in6_addr) * 2);
-         uint32_t tmp = udp->len;
-         sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t));
-         tmp = htons (IPPROTO_UDP);
-         sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t));
-         sum = GNUNET_CRYPTO_crc16_step (sum, 
-                                         udp,
-                                         ntohs (udp->len));
-         udp->crc = GNUNET_CRYPTO_crc16_finish (sum);
-       }
        (void) GNUNET_HELPER_send (helper_handle,
                                   msg,
                                   GNUNET_YES,
@@ -1295,6 +2175,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))
   {
@@ -1308,61 +2191,52 @@ 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:
     {
-      size_t size = sizeof (struct ip4_header) 
-       + sizeof (struct tcp_packet
+      size_t size = sizeof (struct GNUNET_TUN_IPv4Header) 
+       + sizeof (struct GNUNET_TUN_TcpHeader
        + sizeof (struct GNUNET_MessageHeader) +
-       sizeof (struct tun_header) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
        mlen;
       {
        char buf[size];
        struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
-       struct tun_header *tun = (struct tun_header*) &msg[1];
-       struct ip4_header *ipv4 = (struct ip4_header *) &tun[1];
-       struct tcp_packet *tcp = (struct tcp_packet *) &ipv4[1];
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv4Header *ipv4 = (struct GNUNET_TUN_IPv4Header *) &tun[1];
+       struct GNUNET_TUN_TcpHeader *tcp = (struct GNUNET_TUN_TcpHeader *) &ipv4[1];
        msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
        msg->size = htons (size);
        tun->flags = htons (0);
        tun->proto = htons (ETH_P_IPV4);
-       ipv4->version = 4;
-       ipv4->header_length = sizeof (struct ip4_header) / 4;
-       ipv4->diff_serv = 0;
-       ipv4->total_length = htons (sizeof (struct ip4_header) +
-                                   sizeof (struct tcp_packet) +
-                                   mlen);
-       ipv4->identification = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 
-                                                                   UINT16_MAX + 1);
-       ipv4->flags = 0;
-       ipv4->fragmentation_offset = 0;
-       ipv4->ttl = 255;
-       ipv4->protocol = IPPROTO_TCP;
-       ipv4->checksum = 0; 
-       ipv4->source_address = ts->destination_ip.v4;
-       ipv4->destination_address = ts->source_ip.v4;
-       ipv4->checksum =
-         GNUNET_CRYPTO_crc16_n (ipv4, sizeof (struct ip4_header));
+       GNUNET_TUN_initialize_ipv4_header (ipv4,
+                                          IPPROTO_TCP,
+                                          sizeof (struct GNUNET_TUN_TcpHeader) + mlen,
+                                          &ts->destination_ip.v4,
+                                          &ts->source_ip.v4);
        *tcp = data->tcp_header;
        tcp->spt = htons (ts->destination_port);
        tcp->dpt = htons (ts->source_port);
-       tcp->crc = 0;
+       GNUNET_TUN_calculate_tcp4_checksum (ipv4,
+                                           tcp,
+                                           &data[1],
+                                           mlen);
        memcpy (&tcp[1],
                &data[1],
                mlen);
-       {
-         uint32_t sum = 0;
-         uint32_t tmp;
-         
-         sum = GNUNET_CRYPTO_crc16_step (sum, 
-                                         &ipv4->source_address,
-                                         2 * sizeof (struct in_addr));   
-         tmp = htonl ((IPPROTO_TCP << 16) | (mlen + sizeof (struct tcp_packet)));
-         sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t));
-         sum = GNUNET_CRYPTO_crc16_step (sum, tcp, mlen + sizeof (struct tcp_packet));
-         tcp->crc = GNUNET_CRYPTO_crc16_finish (sum);
-       }
        (void) GNUNET_HELPER_send (helper_handle,
                                   msg,
                                   GNUNET_YES,
@@ -1372,46 +2246,32 @@ receive_tcp_back (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
     break;
   case AF_INET6:
     {
-      size_t size = sizeof (struct ip6_header) 
-       + sizeof (struct tcp_packet
+      size_t size = sizeof (struct GNUNET_TUN_IPv6Header) 
+       + sizeof (struct GNUNET_TUN_TcpHeader
        + sizeof (struct GNUNET_MessageHeader) +
-       sizeof (struct tun_header) +
+       sizeof (struct GNUNET_TUN_Layer2PacketHeader) +
        mlen;
       {
        char buf[size];
        struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) buf;
-       struct tun_header *tun = (struct tun_header*) &msg[1];
-       struct ip6_header *ipv6 = (struct ip6_header *) &tun[1];
-       struct tcp_packet *tcp = (struct tcp_packet *) &ipv6[1];
+       struct GNUNET_TUN_Layer2PacketHeader *tun = (struct GNUNET_TUN_Layer2PacketHeader*) &msg[1];
+       struct GNUNET_TUN_IPv6Header *ipv6 = (struct GNUNET_TUN_IPv6Header *) &tun[1];
+       struct GNUNET_TUN_TcpHeader *tcp = (struct GNUNET_TUN_TcpHeader *) &ipv6[1];
        msg->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
        msg->size = htons (size);
        tun->flags = htons (0);
        tun->proto = htons (ETH_P_IPV6);
-       ipv6->traffic_class_h = 0;
-       ipv6->version = 6;
-       ipv6->traffic_class_l = 0;
-       ipv6->flow_label = 0;
-       ipv6->payload_length = htons (sizeof (struct tcp_packet) + sizeof (struct ip6_header) + mlen);
-       ipv6->next_header = IPPROTO_TCP;
-       ipv6->hop_limit = 255;
-       ipv6->source_address = ts->destination_ip.v6;
-       ipv6->destination_address = ts->source_ip.v6;
+       GNUNET_TUN_initialize_ipv6_header (ipv6,
+                                          IPPROTO_TCP,
+                                          sizeof (struct GNUNET_TUN_TcpHeader) + mlen,
+                                          &ts->destination_ip.v6,
+                                          &ts->source_ip.v6);
        tcp->spt = htons (ts->destination_port);
        tcp->dpt = htons (ts->source_port);
-       tcp->crc = 0;
-       {
-         uint32_t sum = 0;
-         uint32_t tmp;
-
-         sum = GNUNET_CRYPTO_crc16_step (sum, &ipv6->source_address, 2 * sizeof (struct in6_addr));
-         tmp = htonl (sizeof (struct tcp_packet) + mlen);
-         sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t));
-         tmp = htonl (IPPROTO_TCP);
-         sum = GNUNET_CRYPTO_crc16_step (sum, &tmp, sizeof (uint32_t));
-         sum = GNUNET_CRYPTO_crc16_step (sum, tcp,
-                                         sizeof (struct tcp_packet) + mlen);
-         tcp->crc = GNUNET_CRYPTO_crc16_finish (sum);
-       }
+       GNUNET_TUN_calculate_tcp6_checksum (ipv6,
+                                           tcp,
+                                           &tcp[1],
+                                           mlen);
        (void) GNUNET_HELPER_send (helper_handle,
                                   msg,
                                   GNUNET_YES,
@@ -1541,6 +2401,55 @@ 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);
+  GNUNET_assert (NULL != de);
+  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
@@ -1564,7 +2473,7 @@ 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;
+  struct TunnelState *ts;
   
   /* validate and parse request */
   mlen = ntohs (message->size);
@@ -1586,7 +2495,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))
@@ -1595,7 +2503,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);
@@ -1656,6 +2563,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));
@@ -1676,12 +2594,28 @@ 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,
-                                      msg->request_id);
-  GNUNET_assert (NULL != de->ts);
+  ts = create_tunnel_to_destination (de, 
+                                    (GNUNET_NO == ntohl (msg->nac)) ? NULL : client,
+                                    result_af,
+                                    msg->request_id);
+  switch (result_af)
+  {
+  case AF_INET:
+    ts->destination_ip.v4 = v4;
+    break;
+  case AF_INET6:
+    ts->destination_ip.v6 = v6;
+    break;
+  default:
+    GNUNET_assert (0);
+  }
   /* we're done */
   GNUNET_SERVER_receive_done (client, GNUNET_OK);
 }
@@ -1707,6 +2641,7 @@ service_redirect_to_service (void *cls GNUNET_UNUSED, struct GNUNET_SERVER_Clien
   void *addr;
   struct DestinationEntry *de;
   GNUNET_HashCode key;
+  struct TunnelState *ts;
   
   /*  parse request */
   msg = (const struct RedirectToServiceRequestMessage *) message;
@@ -1761,9 +2696,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));
@@ -1782,10 +2729,23 @@ 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! */
-  (void) create_tunnel_to_destination (de,
-                                      (GNUNET_NO == ntohl (msg->nac)) ? NULL : client,
-                                      msg->request_id);
+  while (GNUNET_CONTAINER_multihashmap_size (destination_map) > max_destination_mappings)
+    expire_destination (de);
+  ts = create_tunnel_to_destination (de,
+                                    (GNUNET_NO == ntohl (msg->nac)) ? NULL : client,
+                                    result_af,
+                                    msg->request_id);
+  switch (result_af)
+  {
+  case AF_INET:
+    ts->destination_ip.v4 = v4;
+    break;
+  case AF_INET6:
+    ts->destination_ip.v6 = v6;
+    break;
+  default:
+    GNUNET_assert (0);
+  }
   /* we're done */
   GNUNET_SERVER_receive_done (client, GNUNET_OK);
 }
@@ -1814,92 +2774,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.
@@ -1912,16 +2786,8 @@ free_destination_entry (struct DestinationEntry *de)
 static void
 tunnel_cleaner (void *cls, const struct GNUNET_MESH_Tunnel *tunnel, void *tunnel_ctx)
 {
-  struct TunnelState *ts = tunnel_ctx;
-
-  if (NULL == ts)
-  {
-    GNUNET_break (0);
-    return;     
-  }
-  GNUNET_assert (ts->tunnel == tunnel);
-  ts->tunnel = NULL;
-  free_tunnel_state (ts);
+  /* we don't have inbound tunnels, so this function should never be called */
+  GNUNET_break (0);
 }
 
 
@@ -1977,6 +2843,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,
@@ -2018,6 +2886,11 @@ cleanup (void *cls GNUNET_UNUSED,
     GNUNET_SERVER_notification_context_destroy (nc);
     nc = NULL;
   }
+  if (stats != NULL)
+  {
+    GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
+    stats = NULL;
+  }
   for (i=0;i<5;i++)
     GNUNET_free_non_null (vpn_argv[i]);
 }
@@ -2086,12 +2959,14 @@ cleanup_destination_client (void *cls,
 static void
 client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
 {
-  GNUNET_CONTAINER_multihashmap_iterate (tunnel_map,
-                                        &cleanup_tunnel_client,
-                                        client);
-  GNUNET_CONTAINER_multihashmap_iterate (destination_map,
-                                        &cleanup_destination_client,
-                                        client);
+  if (NULL != tunnel_map)
+    GNUNET_CONTAINER_multihashmap_iterate (tunnel_map,
+                                          &cleanup_tunnel_client,
+                                          client);
+  if (NULL != destination_map)
+    GNUNET_CONTAINER_multihashmap_iterate (destination_map,
+                                          &cleanup_destination_client,
+                                          client);
 }
 
 
@@ -2109,17 +2984,16 @@ run (void *cls,
 {
   static const struct GNUNET_SERVER_MessageHandler service_handlers[] = {
     /* callback, cls, type, size */
-    {&service_redirect_to_ip, NULL, GNUNET_MESSAGE_TYPE_VPN_CLIENT_REDIRECT_TO_IP, 0},
-    {&service_redirect_to_service, NULL, 
+    { &service_redirect_to_ip, NULL, GNUNET_MESSAGE_TYPE_VPN_CLIENT_REDIRECT_TO_IP, 0},
+    { &service_redirect_to_service, NULL, 
      GNUNET_MESSAGE_TYPE_VPN_CLIENT_REDIRECT_TO_SERVICE, 
      sizeof (struct RedirectToServiceRequestMessage) },
     {NULL, NULL, 0, 0}
   };
   static const struct GNUNET_MESH_MessageHandler mesh_handlers[] = {
-    {receive_udp_back, GNUNET_MESSAGE_TYPE_VPN_SERVICE_UDP_BACK, 0},
-    {receive_tcp_back, GNUNET_MESSAGE_TYPE_VPN_SERVICE_TCP_BACK, 0},
-    {receive_udp_back, GNUNET_MESSAGE_TYPE_VPN_REMOTE_UDP_BACK, 0},
-    {receive_tcp_back, GNUNET_MESSAGE_TYPE_VPN_REMOTE_TCP_BACK, 0},
+    { &receive_udp_back, GNUNET_MESSAGE_TYPE_VPN_UDP_REPLY, 0},
+    { &receive_tcp_back, GNUNET_MESSAGE_TYPE_VPN_TCP_DATA_TO_VPN, 0},
+    { &receive_icmp_back, GNUNET_MESSAGE_TYPE_VPN_ICMP_TO_VPN, 0},
     {NULL, 0, 0}
   };
   static const GNUNET_MESH_ApplicationType types[] = {
@@ -2133,7 +3007,17 @@ run (void *cls,
   struct in_addr v4;
   struct in6_addr v6;
 
+  if (GNUNET_YES !=
+      GNUNET_OS_check_helper_binary ("gnunet-helper-vpn"))
+  {
+    fprintf (stderr,
+            "`%s' is not SUID, refusing to run.\n",
+            "gnunet-helper-vpn");
+    global_ret = 1;
+    return;
+  }
   cfg = cfg_;
+  stats = GNUNET_STATISTICS_create ("vpn", cfg);
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_number (cfg, "vpn", "MAX_MAPPING",
                                             &max_destination_mappings))
@@ -2242,7 +3126,7 @@ main (int argc, char *const *argv)
   return (GNUNET_OK ==
           GNUNET_SERVICE_run (argc, argv, "vpn", 
                              GNUNET_SERVICE_OPTION_NONE,
-                              &run, NULL)) ? 0 : 1;
+                              &run, NULL)) ? global_ret : 1;
 }
 
 /* end of gnunet-service-vpn.c */