stuff
[oweals/gnunet.git] / src / transport / gnunet-service-transport_validation.c
index 9b62881d6a076474420b6b82c8cb398cca754478..c77fe7a44788f5a229868fda4e76bd22213a264f 100644 (file)
  */
 #include "platform.h"
 #include "gnunet-service-transport_validation.h"
+#include "gnunet-service-transport_plugins.h"
+#include "gnunet-service-transport_hello.h"
 #include "gnunet-service-transport.h"
 #include "gnunet_hello_lib.h"
 #include "gnunet_peerinfo_service.h"
+#include "gnunet_signatures.h"
 
-/**
- * How long until a HELLO verification attempt should time out?
- * Must be rather small, otherwise a partially successful HELLO
- * validation (some addresses working) might not be available
- * before a client's request for a connection fails for good.
- * Besides, if a single request to an address takes a long time,
- * then the peer is unlikely worthwhile anyway.
- */
-#define HELLO_VERIFICATION_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
 
 /**
  * How long is a PONG signature valid?  We'll recycle a signature until
@@ -55,7 +49,6 @@
  */
 #define HELLO_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 12)
 
-
 /**
  * How long before an existing address expires should we again try to
  * validate it?  Must be (significantly) smaller than
  */
 #define VALIDATION_MAP_SIZE 256
 
+/**
+ * Priority to use for PINGs
+ */ 
+#define PING_PRIORITY 2
+
+/**
+ * Priority to use for PONGs
+ */ 
+#define PONG_PRIORITY 4
+
+
+/**
+ * Message used to ask a peer to validate receipt (to check an address
+ * from a HELLO).  Followed by the address we are trying to validate,
+ * or an empty address if we are just sending a PING to confirm that a
+ * connection which the receiver (of the PING) initiated is still valid.
+ */
+struct TransportPingMessage
+{
+
+  /**
+   * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_PING
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * Challenge code (to ensure fresh reply).
+   */
+  uint32_t challenge GNUNET_PACKED;
+
+  /**
+   * Who is the intended recipient?
+   */
+  struct GNUNET_PeerIdentity target;
+
+};
+
+
+/**
+ * Message used to validate a HELLO.  The challenge is included in the
+ * confirmation to make matching of replies to requests possible.  The
+ * signature signs our public key, an expiration time and our address.<p>
+ *
+ * This message is followed by our transport address that the PING tried
+ * to confirm (if we liked it).  The address can be empty (zero bytes)
+ * if the PING had not address either (and we received the request via
+ * a connection that we initiated).
+ */
+struct TransportPongMessage
+{
+
+  /**
+   * Type will be GNUNET_MESSAGE_TYPE_TRANSPORT_PONG
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * Challenge code from PING (showing freshness).  Not part of what
+   * is signed so that we can re-use signatures.
+   */
+  uint32_t challenge GNUNET_PACKED;
+
+  /**
+   * Signature.
+   */
+  struct GNUNET_CRYPTO_RsaSignature signature;
+
+  /**
+   * What are we signing and why?  Two possible reason codes can be here:
+   * GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_OWN to confirm that this is a
+   * plausible address for this peer (pid is set to identity of signer); or
+   * GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_USING to confirm that this is
+   * an address we used to connect to the peer with the given pid.
+   */
+  struct GNUNET_CRYPTO_RsaSignaturePurpose purpose;
+
+  /**
+   * When does this signature expire?
+   */
+  struct GNUNET_TIME_AbsoluteNBO expiration;
+
+  /**
+   * Either the identity of the peer Who signed this message, or the
+   * identity of the peer that we're connected to using the given
+   * address (depending on purpose.type).
+   */
+  struct GNUNET_PeerIdentity pid;
+
+  /**
+   * Size of address appended to this message (part of what is
+   * being signed, hence not redundant).
+   */
+  uint32_t addrlen;
+
+};
+
 
 /**
  * Information about an address under validation
@@ -86,6 +175,11 @@ struct ValidationEntry
    */
   const void *addr;
 
+  /**
+   * Public key of the peer.
+   */
+  struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key;                                                
+
   /**
    * The identity of the peer.
    */
@@ -102,12 +196,10 @@ struct ValidationEntry
   struct GNUNET_TIME_Absolute send_time;
 
   /**
-   * When did we last succeed with validating this address?
-   * FOREVER if the address has not been validated (we're currently checking)
-   * ZERO if the address was validated a long time ago (from PEERINFO)
-   * otherwise a time in the past if this process validated the address
+   * Until when is this address valid?
+   * ZERO if it is not currently considered valid.
    */
-  struct GNUNET_TIME_Absolute last_validated_at;
+  struct GNUNET_TIME_Absolute valid_until;
 
   /**
    * How long until we can try to validate this address again?
@@ -152,11 +244,6 @@ struct CheckHelloValidatedContext
    */
   const struct GNUNET_HELLO_Message *hello;
 
-  /**
-   * Context for peerinfo iteration.
-   */
-  struct GNUNET_PEERINFO_IteratorContext *piter;
-
 };
 
 
@@ -177,93 +264,15 @@ static struct CheckHelloValidatedContext *chvc_tail;
  */
 static struct GNUNET_CONTAINER_MultiHashMap *validation_map;
 
-
 /**
- * Start the validation subsystem.
+ * Map of PeerIdentities to 'struct GST_ValidationIteratorContext's.
  */
-void 
-GST_validation_start ()
-{
-  validation_map = GNUNET_CONTAINER_multihashmap_create (VALIDATION_MAP_SIZE);
-}
-
+static struct GNUNET_CONTAINER_MultiHashMap *notify_map;
 
 /**
- * Iterate over validation entries and free them.
- *
- * @param cls (unused)
- * @param key peer identity (unused)
- * @param value a 'struct ValidationEntry' to clean up
- * @return GNUNET_YES (continue to iterate)
+ * Context for peerinfo iteration.
  */
-static int
-cleanup_validation_entry (void *cls,
-                         const GNUNET_HashCode *key,
-                         void *value)
-{
-  struct ValidationEntry *ve = value;
-    
-  GNUNET_free (ve->transport_name);
-  if (GNUNET_SCHEDULER_NO_TASK != ve->timeout_task)
-    {
-      GNUNET_SCHEDULER_cancel (ve->timeout_task);
-      ve->timeout_task = GNUNET_SCHEDULER_NO_TASK;
-    }
-  GNUNET_free (ve);
-  return GNUNET_OK;
-}
-
-
-/**
- * Stop the validation subsystem.
- */
-void
-GST_validation_stop ()
-{
-  struct CheckHelloValidatedContext *chvc;
-
-  GNUNET_CONTAINER_multihashmap_iterate (validation_map,
-                                        &cleanup_validation_entry,
-                                        NULL);
-  GNUNET_CONTAINER_multihashmap_destroy (validation_map);
-  validation_map = NULL;
-  while (NULL != (chvc = chvc_head))
-    {
-      GNUNET_CONTAINER_DLL_remove (chvc_head,
-                                  chvc_tail,
-                                  chvc);
-      GNUNET_PEERINFO_iterate_cancel (chvc->piter);      
-      GNUNET_free (chvc);
-    }
-}
-
-
-#if 0
-/**
- * Address validation cleanup task (record no longer needed).
- *
- * @param cls the 'struct ValidationEntry'
- * @param tc scheduler context (unused)
- */
-static void
-timeout_hello_validation (void *cls, 
-                         const struct GNUNET_SCHEDULER_TaskContext *tc)
-{
-  struct ValidationEntry *va = cls;
-
-  va->timeout_task = GNUNET_SCHEDULER_NO_TASK;
-  GNUNET_STATISTICS_update (GST_stats,
-                           gettext_noop ("# address records discarded"),
-                           1,
-                           GNUNET_NO);
-  GNUNET_break (GNUNET_OK ==
-                GNUNET_CONTAINER_multihashmap_remove (validation_map,
-                                                     &va->pid.hashPubKey,
-                                                     va));
-  GNUNET_free (va->transport_name);
-  GNUNET_free (va);
-}
-#endif
+static struct GNUNET_PEERINFO_NotifyContext *pnc;
 
 
 /**
@@ -326,6 +335,7 @@ validation_entry_match (void *cls,
  * the given address and transport.  If none exists, create one (but
  * without starting any validation).
  *
+ * @param public_key public key of the peer, NULL for unknown
  * @param neighbour which peer we care about
  * @param tname name of the transport plugin
  * @param session session to look for, NULL for 'any'; otherwise
@@ -333,10 +343,12 @@ validation_entry_match (void *cls,
  *        if 'addr' matches
  * @param addr binary address
  * @param addrlen length of addr
- * @return validation entry matching the given specifications
+ * @return validation entry matching the given specifications, NULL
+ *         if we don't have an existing entry and no public key was given
  */
 static struct ValidationEntry *
-find_validation_entry (struct GNUNET_PeerIdentity *neighbour,
+find_validation_entry (const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key,
+                      const struct GNUNET_PeerIdentity *neighbour,
                       const char *tname,
                       const char *addr,
                       size_t addrlen)
@@ -354,13 +366,17 @@ find_validation_entry (struct GNUNET_PeerIdentity *neighbour,
                                              &vemc);
   if (NULL != (ve = vemc.ve))
     return ve;
+  if (public_key == NULL)
+    return NULL;
   ve = GNUNET_malloc (sizeof (struct ValidationEntry) + addrlen);
   ve->transport_name = GNUNET_strdup (tname);
   ve->addr = (void*) &ve[1];
+  ve->public_key = *public_key;
   ve->pid = *neighbour;
+  ve->challenge = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+                                           UINT32_MAX);
   memcpy (&ve[1], addr, addrlen);
   ve->addrlen = addrlen;
-  ve->last_validated_at = GNUNET_TIME_UNIT_FOREVER_ABS;
   GNUNET_CONTAINER_multihashmap_put (validation_map,
                                     &neighbour->hashPubKey,
                                     ve,
@@ -369,11 +385,216 @@ find_validation_entry (struct GNUNET_PeerIdentity *neighbour,
 }
 
 
+/**
+ * Iterator which adds the given address to the set of validated
+ * addresses.
+ *
+ * @param cls original HELLO message
+ * @param tname name of the transport
+ * @param expiration expiration time
+ * @param addr the address
+ * @param addrlen length of the address
+ * @return GNUNET_OK (keep the address)
+ */
+static int
+add_valid_address (void *cls,
+                  const char *tname,
+                  struct GNUNET_TIME_Absolute expiration,
+                  const void *addr, 
+                  uint16_t addrlen)
+{
+  const struct GNUNET_HELLO_Message *hello = cls;
+  struct ValidationEntry *ve;
+  struct GNUNET_PeerIdentity pid;
+  struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key;
+
+  if (GNUNET_TIME_absolute_get_remaining (expiration).rel_value == 0)
+    return GNUNET_OK; /* expired */
+  if ( (GNUNET_OK !=
+       GNUNET_HELLO_get_id (hello, &pid)) ||
+       (GNUNET_OK !=
+       GNUNET_HELLO_get_key (hello, &public_key)) )
+    {
+      GNUNET_break (0);
+      return GNUNET_OK; /* invalid HELLO !? */
+    }
+    
+  ve = find_validation_entry (&public_key, &pid, tname, addr, addrlen);
+  ve->valid_until = GNUNET_TIME_absolute_max (ve->valid_until,
+                                             expiration);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called for any HELLO known to PEERINFO. 
+ *
+ * @param cls unused
+ * @param peer id of the peer, NULL for last call
+ * @param hello hello message for the peer (can be NULL)
+ * @param error message
+ */
+static void
+process_peerinfo_hello (void *cls,
+                       const struct GNUNET_PeerIdentity *peer,
+                       const struct GNUNET_HELLO_Message *hello,
+                       const char *err_msg)
+{
+  GNUNET_assert (NULL != peer);
+  if (NULL == hello)
+    return;
+  GNUNET_assert (NULL ==
+                GNUNET_HELLO_iterate_addresses (hello,
+                                                GNUNET_NO,
+                                                &add_valid_address,
+                                                (void*) hello));  
+}
+
+
+/**
+ * Start the validation subsystem.
+ */
+void 
+GST_validation_start ()
+{
+  validation_map = GNUNET_CONTAINER_multihashmap_create (VALIDATION_MAP_SIZE);
+  notify_map = GNUNET_CONTAINER_multihashmap_create (VALIDATION_MAP_SIZE);
+  pnc = GNUNET_PEERINFO_notify (GST_cfg,
+                               &process_peerinfo_hello,
+                               NULL);
+}
+
+
+/**
+ * Iterate over validation entries and free them.
+ *
+ * @param cls (unused)
+ * @param key peer identity (unused)
+ * @param value a 'struct ValidationEntry' to clean up
+ * @return GNUNET_YES (continue to iterate)
+ */
+static int
+cleanup_validation_entry (void *cls,
+                         const GNUNET_HashCode *key,
+                         void *value)
+{
+  struct ValidationEntry *ve = value;
+    
+  GNUNET_free (ve->transport_name);
+  if (GNUNET_SCHEDULER_NO_TASK != ve->timeout_task)
+    {
+      GNUNET_SCHEDULER_cancel (ve->timeout_task);
+      ve->timeout_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  GNUNET_free (ve);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Stop the validation subsystem.
+ */
+void
+GST_validation_stop ()
+{
+  struct CheckHelloValidatedContext *chvc;
+
+  GNUNET_CONTAINER_multihashmap_iterate (validation_map,
+                                        &cleanup_validation_entry,
+                                        NULL);
+  GNUNET_CONTAINER_multihashmap_destroy (validation_map);
+  validation_map = NULL;
+  GNUNET_assert (GNUNET_CONTAINER_multihashmap_size (notify_map) == 0);
+  GNUNET_CONTAINER_multihashmap_destroy (notify_map);
+  notify_map = NULL;
+  while (NULL != (chvc = chvc_head))
+    {
+      GNUNET_CONTAINER_DLL_remove (chvc_head,
+                                  chvc_tail,
+                                  chvc);
+      GNUNET_free (chvc);
+    }
+  GNUNET_PEERINFO_notify_cancel (pnc);
+}
+
+
+/**
+ * Address validation cleanup task (record no longer needed).
+ *
+ * @param cls the 'struct ValidationEntry'
+ * @param tc scheduler context (unused)
+ */
+static void
+timeout_hello_validation (void *cls, 
+                         const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct ValidationEntry *va = cls;
+
+  va->timeout_task = GNUNET_SCHEDULER_NO_TASK;
+  GNUNET_STATISTICS_update (GST_stats,
+                           gettext_noop ("# address records discarded"),
+                           1,
+                           GNUNET_NO);
+  GNUNET_break (GNUNET_OK ==
+                GNUNET_CONTAINER_multihashmap_remove (validation_map,
+                                                     &va->pid.hashPubKey,
+                                                     va));
+  GNUNET_free (va->transport_name);
+  GNUNET_free (va);
+}
+
+
+/**
+ * Send the given PONG to the given address.
+ *
+ * @param cls the PONG message
+ * @param public_key public key for the peer, never NULL
+ * @param target peer this change is about, never NULL
+ * @param valid_until is ZERO if we never validated the address,
+ *                    otherwise a time up to when we consider it (or was) valid
+ * @param validation_block  is FOREVER if the address is for an unsupported plugin (from PEERINFO)
+ *                          is ZERO if the address is considered valid (no validation needed)
+ *                          otherwise a time in the future if we're currently denying re-validation
+ * @param plugin_name name of the plugin
+ * @param plugin_address binary address
+ * @param plugin_address_len length of address
+ */
+static void
+multicast_pong (void *cls,
+               const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key,
+               const struct GNUNET_PeerIdentity *target,
+               struct GNUNET_TIME_Absolute valid_until,
+               struct GNUNET_TIME_Absolute validation_block,
+               const char *plugin_name,
+               const void *plugin_address,
+               size_t plugin_address_len)
+{
+  struct TransportPongMessage *pong = cls;
+  struct GNUNET_TRANSPORT_PluginFunctions *papi;
+
+  papi = GST_plugins_find (plugin_name);
+  if (papi == NULL)
+    return;
+  (void) papi->send (papi->cls,
+                    target,
+                    (const char*) pong,
+                    ntohs (pong->header.size),
+                    PONG_PRIORITY,
+                    HELLO_REVALIDATION_START_TIME,
+                    NULL,
+                    plugin_address,
+                    plugin_address_len,
+                    GNUNET_YES,
+                    NULL, NULL);
+}
+
+
 /**
  * We've received a PING.  If appropriate, generate a PONG.
  *
  * @param sender peer sending the PING
  * @param hdr the PING
+ * @param session session we got the PING from
  * @param plugin_name name of plugin that received the PING
  * @param sender_address address of the sender as known to the plugin, NULL
  *                       if we did not initiate the connection
@@ -383,38 +604,254 @@ void
 GST_validation_handle_ping (const struct GNUNET_PeerIdentity *sender,
                            const struct GNUNET_MessageHeader *hdr,
                            const char *plugin_name,
+                           struct Session *session,
                            const void *sender_address,
                            size_t sender_address_len)
 {
+
+  const struct TransportPingMessage *ping;
+  struct TransportPongMessage *pong;
+  struct GNUNET_TRANSPORT_PluginFunctions *papi;
+  struct SessionHeader *session_header;
+  const char *addr;
+  const char *addrend;
+  size_t alen;
+  size_t slen;
+  ssize_t ret;
+
+  if (ntohs (hdr->size) < sizeof (struct TransportPingMessage))
+    {
+      GNUNET_break_op (0);
+      return;
+    }
+  ping = (const struct TransportPingMessage *) hdr;
+  if (0 != memcmp (&ping->target,
+                   &GST_my_identity,
+                   sizeof (struct GNUNET_PeerIdentity)))
+    {
+#if DEBUG_TRANSPORT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  _("Received `%s' message from `%s' destined for `%s' which is not me!\n"),
+                 "PING",
+                 (sender_address != NULL)
+                 ? GST_plugin_a2s (plugin_name,
+                                   sender_address,
+                                   sender_address_len)
+                 : "<inbound>",
+                 GNUNET_i2s (&ping->target));
+#endif
+      return;
+    }
+#if DEBUG_TRANSPORT
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
+             "Processing `%s' from `%s'\n",
+             "PING",
+             (sender_address != NULL)
+             ? GST_plugin_a2s (plugin_name,
+                               sender_address,
+                               sender_address_len)
+             : "<inbound>");
+#endif
+  GNUNET_STATISTICS_update (GST_stats,
+                           gettext_noop ("# PING messages received"),
+                           1,
+                           GNUNET_NO);
+  addr = (const char*) &ping[1];
+  alen = ntohs (hdr->size) - sizeof (struct TransportPingMessage);
+  if (alen == 0)
+    {
+      /* peer wants to confirm that we have an outbound connection to him; 
+        we handle this case here even though it has nothing to do with
+        address validation (!) */
+      if ( (sender_address == NULL) || (session == NULL) )
+       {
+         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                     _("Refusing to create PONG since I do initiate the session with `%s'.\n"),
+                     GNUNET_i2s (sender));
+         return;
+       }
+      session_header = (struct SessionHeader *)session;
+#if DEBUG_TRANSPORT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Creating PONG indicating that we initiated a connection to peer `%s' using address `%s' \n",
+                 GNUNET_i2s (peer),
+                 GST_plugin_a2s (plugin_name,
+                                 sender_address,
+                                 sender_address_len));
+#endif
+      slen = strlen (plugin_name) + 1;
+      pong = GNUNET_malloc (sizeof (struct TransportPongMessage) + sender_address_len + slen);
+      pong->header.size = htons (sizeof (struct TransportPongMessage) + sender_address_len + slen);
+      pong->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PONG);
+      pong->purpose.size =
+       htonl (sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose) +
+              sizeof (uint32_t) +
+              sizeof (struct GNUNET_TIME_AbsoluteNBO) +
+              sizeof (struct GNUNET_PeerIdentity) + sender_address_len + slen);
+      pong->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_USING);
+      pong->challenge = ping->challenge;
+      pong->addrlen = htonl(sender_address_len + slen);
+      pong->pid = *sender;
+      memcpy (&pong[1],
+             plugin_name,
+             slen);
+      memcpy (&((char*)&pong[1])[slen],
+             sender_address,
+             sender_address_len);
+      if (GNUNET_TIME_absolute_get_remaining (session_header->pong_sig_expires).rel_value < 
+         PONG_SIGNATURE_LIFETIME.rel_value / 4)
+       {
+         /* create / update cached sig */
+#if DEBUG_TRANSPORT
+         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                     "Creating PONG signature to indicate active connection.\n");
+#endif
+         session_header->pong_sig_expires = GNUNET_TIME_relative_to_absolute (PONG_SIGNATURE_LIFETIME);
+         pong->expiration = GNUNET_TIME_absolute_hton (session_header->pong_sig_expires);
+         GNUNET_assert (GNUNET_OK ==
+                        GNUNET_CRYPTO_rsa_sign (GST_my_private_key,
+                                                &pong->purpose,
+                                                &session_header->pong_signature));
+       }
+      else
+       {
+         pong->expiration = GNUNET_TIME_absolute_hton (session_header->pong_sig_expires);
+       }
+      pong->signature = session_header->pong_signature;
+    }
+  else
+    {
+      /* peer wants to confirm that this is one of our addresses, this is what is
+        used for address validation */
+      struct GNUNET_CRYPTO_RsaSignature *sig_cache;
+      struct GNUNET_TIME_Absolute *sig_cache_exp;
+
+      addrend = memchr (addr, '\0', alen);
+      if (NULL == addrend)
+       {
+         GNUNET_break_op (0);
+         return;
+       }
+      addrend++;
+      slen = strlen(addr);
+      alen -= slen;
+
+      if (GNUNET_YES !=
+         GST_hello_test_address (addr,
+                                 addrend,
+                                 alen,
+                                 &sig_cache,
+                                 &sig_cache_exp))
+       {
+         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                     _("Not confirming PING with address `%s' since I cannot confirm having this address.\n"),
+                     GST_plugins_a2s (addr,
+                                      addrend,
+                                      alen));
+         return;
+       }
+
+      pong = GNUNET_malloc (sizeof (struct TransportPongMessage) + alen + slen);
+      pong->header.size = htons (sizeof (struct TransportPongMessage) + alen + slen);
+      pong->header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_PONG);
+      pong->purpose.size =
+       htonl (sizeof (struct GNUNET_CRYPTO_RsaSignaturePurpose) +
+              sizeof (uint32_t) +
+              sizeof (struct GNUNET_TIME_AbsoluteNBO) +
+              sizeof (struct GNUNET_PeerIdentity) + alen + slen);
+      pong->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_OWN);
+      pong->challenge = ping->challenge;
+      pong->addrlen = htonl(alen + slen);
+      pong->pid = GST_my_identity;
+      memcpy (&pong[1], addr, slen);
+      memcpy (&((char*)&pong[1])[slen], addrend, alen);
+      if (GNUNET_TIME_absolute_get_remaining (*sig_cache_exp).rel_value < PONG_SIGNATURE_LIFETIME.rel_value / 4)
+       {
+         /* create / update cached sig */
+#if DEBUG_TRANSPORT
+         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                     "Creating PONG signature to indicate ownership.\n");
+#endif
+         *sig_cache_exp = GNUNET_TIME_relative_to_absolute (PONG_SIGNATURE_LIFETIME);
+         pong->expiration = GNUNET_TIME_absolute_hton (*sig_cache_exp);
+         GNUNET_assert (GNUNET_OK ==
+                        GNUNET_CRYPTO_rsa_sign (GST_my_private_key,
+                                                &pong->purpose,
+                                                sig_cache));
+       }
+      else
+       {
+         pong->expiration = GNUNET_TIME_absolute_hton (*sig_cache_exp);
+       }
+      pong->signature = *sig_cache;
+    }
+
+  /* first see if the session we got this PING from can be used to transmit
+     a response reliably */
+  papi = GST_plugins_find (plugin_name);
+  if (papi == NULL)
+    ret = -1;
+  else
+    ret = papi->send (papi->cls,
+                     sender,
+                     (const char*) pong,
+                     ntohs (pong->header.size),
+                     PONG_PRIORITY,
+                     HELLO_REVALIDATION_START_TIME,
+                     session,
+                     sender_address,
+                     sender_address_len,
+                     GNUNET_SYSERR,
+                     NULL, NULL);
+  if (ret != -1)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmitted PONG to `%s' via reliable mechanism\n",
+                 GNUNET_i2s (sender));
+      /* done! */
+      GNUNET_STATISTICS_update (GST_stats,
+                               gettext_noop ("# PONGs unicast via reliable transport"),
+                               1,
+                               GNUNET_NO);
+      GNUNET_free (pong);
+      return;
+    }
+  
+  /* no reliable method found, try transmission via all known addresses */
+  GNUNET_STATISTICS_update (GST_stats,
+                           gettext_noop ("# PONGs multicast to all available addresses"),
+                           1,
+                           GNUNET_NO);
+  (void) GST_validation_get_addresses (sender,
+                                      GNUNET_YES,
+                                      &multicast_pong,
+                                      pong);
+  GNUNET_free (pong);
 }
 
 
 /**
- * We've received a PONG.  Check if it matches a pending PING and
- * mark the respective address as confirmed.
- *
- * @param sender peer sending the PONG
- * @param hdr the PONG
- * @param plugin_name name of plugin that received the PONG
- * @param sender_address address of the sender as known to the plugin, NULL
- *                       if we did not initiate the connection
- * @param sender_address_len number of bytes in sender_address
+ * Context for the 'validate_address' function
  */
-void
-GST_validation_handle_pong (const struct GNUNET_PeerIdentity *sender,
-                           const struct GNUNET_MessageHeader *hdr,
-                           const char *plugin_name,
-                           const void *sender_address,
-                           size_t sender_address_len)
+struct ValidateAddressContext
 {
-}
+  /**
+   * Hash of the public key of the peer whose address is being validated.
+   */ 
+  struct GNUNET_PeerIdentity pid;
+
+  /**
+   * Public key of the peer whose address is being validated.
+   */
+  struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key;
+};
 
 
 /**
  * Iterator callback to go over all addresses and try to validate them
  * (unless blocked or already validated).
  *
- * @param cls pointer to the 'struct PeerIdentity' of the peer
+ * @param cls pointer to a 'struct ValidateAddressContext'
  * @param tname name of the transport
  * @param expiration expiration time
  * @param addr the address
@@ -428,18 +865,262 @@ validate_address (void *cls,
                  const void *addr, 
                  uint16_t addrlen)
 {
-  struct GNUNET_PeerIdentity *pid = cls;
+  const struct ValidateAddressContext *vac = cls;
+  const struct GNUNET_PeerIdentity *pid = &vac->pid;
   struct ValidationEntry *ve;
-  
+  struct TransportPingMessage ping;
+  struct GNUNET_TRANSPORT_PluginFunctions *papi;
+  ssize_t ret;
+  size_t tsize;
+  size_t slen;
+
   if (GNUNET_TIME_absolute_get_remaining (expiration).rel_value == 0)
     return GNUNET_OK; /* expired */
-  ve = find_validation_entry (pid, tname, addr, addrlen);
-  // FIXME: check if validated/blocked, if not start validation...
-  ve++; // make compiler happy
+  ve = find_validation_entry (&vac->public_key, pid, tname, addr, addrlen);
+  if (GNUNET_TIME_absolute_get_remaining (ve->validation_block).rel_value > 0)
+    return GNUNET_OK; /* blocked */
+  if ( (GNUNET_SCHEDULER_NO_TASK != ve->timeout_task) &&
+       (GNUNET_TIME_absolute_get_remaining (ve->valid_until).rel_value > 0) )
+    return GNUNET_OK; /* revalidation task already scheduled & still  valid */
+  ve->validation_block = GNUNET_TIME_relative_to_absolute (HELLO_REVALIDATION_START_TIME);
+  if (GNUNET_SCHEDULER_NO_TASK != ve->timeout_task)
+    GNUNET_SCHEDULER_cancel (ve->timeout_task);
+  ve->timeout_task = GNUNET_SCHEDULER_add_delayed (HELLO_REVALIDATION_START_TIME,
+                                                  &timeout_hello_validation,
+                                                  ve);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Transmitting plain PING to `%s'\n",
+             GNUNET_i2s (pid));  
+  ping.header.size = htons(sizeof(struct TransportPingMessage));
+  ping.header.type = htons(GNUNET_MESSAGE_TYPE_TRANSPORT_PING);
+  ping.challenge = htonl(ve->challenge);
+  ping.target = *pid;
+  
+  slen = strlen(ve->transport_name) + 1;
+  tsize = sizeof(struct TransportPingMessage) + ve->addrlen + slen;
+  {
+    char message_buf[tsize];
+
+    memcpy(message_buf, &ping, sizeof (struct TransportPingMessage));
+    memcpy(&message_buf[sizeof (struct TransportPingMessage)],
+          ve->transport_name,
+          slen);
+    memcpy(&message_buf[sizeof (struct TransportPingMessage) + slen],
+          ve->addr,
+          ve->addrlen);
+    papi = GST_plugins_find (ve->transport_name);
+    if (papi == NULL)
+      ret = -1;
+    else
+      ret = papi->send (papi->cls,
+                       pid,
+                       message_buf,
+                       tsize,
+                       PING_PRIORITY,
+                       HELLO_REVALIDATION_START_TIME,
+                       NULL /* no session */,
+                       ve->addr,
+                       ve->addrlen,
+                       GNUNET_YES,
+                       NULL, NULL);
+  }
+  if (-1 != ret)
+    {
+      ve->send_time = GNUNET_TIME_absolute_get ();
+      GNUNET_STATISTICS_update (GST_stats,
+                               gettext_noop ("# PING without HELLO messages sent"),
+                               1,
+                               GNUNET_NO);
+    }
   return GNUNET_OK;
 }
 
 
+/**
+ * Do address validation again to keep address valid.
+ *
+ * @param cls the 'struct ValidationEntry'
+ * @param tc scheduler context (unused)
+ */
+static void
+revalidate_address (void *cls, 
+                   const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct ValidationEntry *ve = cls;
+  struct GNUNET_TIME_Relative delay;
+  struct ValidateAddressContext vac;
+
+  ve->timeout_task = GNUNET_SCHEDULER_NO_TASK;
+  delay = GNUNET_TIME_absolute_get_remaining (ve->validation_block);
+  if (delay.rel_value > 0)
+    {
+      /* should wait a bit longer */
+      ve->timeout_task = GNUNET_SCHEDULER_add_delayed (delay,
+                                                      &revalidate_address,
+                                                      ve);
+      return;
+    }
+  GNUNET_STATISTICS_update (GST_stats,
+                           gettext_noop ("# address revalidations started"),
+                           1,
+                           GNUNET_NO);
+  vac.pid = ve->pid;
+  vac.public_key = ve->public_key;
+  validate_address (&vac,
+                   ve->transport_name,
+                   ve->valid_until,
+                   ve->addr,
+                   (uint16_t) ve->addrlen);
+}
+
+
+/**
+ * Add the validated peer address to the HELLO.
+ *
+ * @param cls the 'struct ValidationEntry' with the validated address
+ * @param max space in buf
+ * @param buf where to add the address
+ */
+static size_t
+add_valid_peer_address (void *cls,
+                       size_t max,
+                       void *buf)
+{
+  struct ValidationEntry *ve = cls;
+
+  return GNUNET_HELLO_add_address (ve->transport_name,
+                                  ve->valid_until,
+                                  ve->addr,
+                                  ve->addrlen,
+                                  buf,
+                                  max);
+}
+
+
+/**
+ * We've received a PONG.  Check if it matches a pending PING and
+ * mark the respective address as confirmed.
+ *
+ * @param sender peer sending the PONG
+ * @param hdr the PONG
+ * @param plugin_name name of plugin that received the PONG
+ * @param sender_address address of the sender as known to the plugin, NULL
+ *                       if we did not initiate the connection
+ * @param sender_address_len number of bytes in sender_address
+ */
+void
+GST_validation_handle_pong (const struct GNUNET_PeerIdentity *sender,
+                           const struct GNUNET_MessageHeader *hdr,
+                           const char *plugin_name,
+                           const void *sender_address,
+                           size_t sender_address_len)
+{
+  const struct TransportPongMessage *pong;
+  struct ValidationEntry *ve;
+  const char *addr;
+  const char *addrend;
+  size_t alen;
+  size_t slen;
+  uint32_t rdelay;
+  struct GNUNET_TIME_Relative delay;
+  struct GNUNET_HELLO_Message *hello;
+
+  if (ntohs (hdr->size) < sizeof (struct TransportPongMessage))
+    {
+      GNUNET_break_op (0);
+      return;
+    }
+  GNUNET_STATISTICS_update (GST_stats,
+                           gettext_noop ("# PONG messages received"),
+                           1,
+                           GNUNET_NO);
+  pong = (const struct TransportPongMessage *) hdr;
+  if (0 != memcmp (&pong->pid,
+                   sender,
+                   sizeof (struct GNUNET_PeerIdentity)))
+    {
+      /* PONG is validating inbound session, not an address, not the case
+        used for address validation, ignore here! */
+      return;
+    }
+#if DEBUG_TRANSPORT
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | GNUNET_ERROR_TYPE_BULK,
+             "Processing `%s' from `%s'\n",
+             "PONG",
+             (sender_address != NULL)
+             ? GST_plugin_a2s (plugin_name,
+                               sender_address,
+                               sender_address_len)
+             : "<inbound>");
+#endif
+  addr = (const char*) &pong[1];
+  alen = ntohs (hdr->size) - sizeof (struct TransportPongMessage);
+  addrend = memchr (addr, '\0', alen);
+  if (NULL == addrend)
+    {
+      GNUNET_break_op (0);
+      return;
+    }
+  addrend++;
+  slen = strlen(addr);
+  alen -= slen;
+  ve = find_validation_entry (NULL,
+                             sender,
+                             addr,
+                             addrend,
+                             alen);
+  if (NULL == ve)
+    {
+      GNUNET_STATISTICS_update (GST_stats,
+                               gettext_noop ("# PONGs dropped, no matching pending validation"),
+                               1,
+                               GNUNET_NO);
+      return;
+    }
+  /* now check that PONG is well-formed */
+  if (GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_ntoh (pong->expiration)).rel_value == 0)
+    {
+      GNUNET_STATISTICS_update (GST_stats,
+                               gettext_noop ("# PONGs dropped, signature expired"),
+                               1,
+                               GNUNET_NO);
+      return;
+    }
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_rsa_verify (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_PONG_OWN,
+                               &pong->purpose,
+                               &pong->signature,
+                               &ve->public_key))
+    {
+      GNUNET_break_op (0);
+      return;
+    }
+  
+  /* validity achieved, remember it! */
+  ve->valid_until = GNUNET_TIME_relative_to_absolute (HELLO_ADDRESS_EXPIRATION);
+
+  /* build HELLO to store in PEERINFO */
+  hello = GNUNET_HELLO_create (&ve->public_key,
+                              &add_valid_peer_address,
+                              ve);
+  GNUNET_PEERINFO_add_peer (GST_peerinfo,
+                           hello);
+  GNUNET_free (hello);
+
+  if (GNUNET_SCHEDULER_NO_TASK != ve->timeout_task)
+    GNUNET_SCHEDULER_cancel (ve->timeout_task);
+
+  /* randomly delay by up to 1h to avoid synchronous validations */
+  rdelay = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                    60 * 60);
+  delay = GNUNET_TIME_relative_add (HELLO_REVALIDATION_START_TIME,
+                                   GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+                                                                  rdelay));
+  ve->timeout_task = GNUNET_SCHEDULER_add_delayed (delay,
+                                                  &revalidate_address,
+                                                  ve);
+}
+
 
 /**
  * We've received a HELLO, check which addresses are new and trigger
@@ -451,10 +1132,12 @@ void
 GST_validation_handle_hello (const struct GNUNET_MessageHeader *hello)
 {
   const struct GNUNET_HELLO_Message* hm = (const struct GNUNET_HELLO_Message*) hello;
-  struct GNUNET_PeerIdentity pid;
+  struct ValidateAddressContext vac;
 
-  if (GNUNET_OK !=
-      GNUNET_HELLO_get_id (hm, &pid))    
+  if ( (GNUNET_OK !=
+       GNUNET_HELLO_get_id (hm, &vac.pid)) ||
+       (GNUNET_OK !=
+       GNUNET_HELLO_get_key (hm, &vac.public_key)) )
     {
       /* malformed HELLO */
       GNUNET_break (0);
@@ -464,7 +1147,7 @@ GST_validation_handle_hello (const struct GNUNET_MessageHeader *hello)
                 GNUNET_HELLO_iterate_addresses (hm,
                                                 GNUNET_NO,
                                                 &validate_address,
-                                                &pid));
+                                                &vac));
 }
 
 
@@ -482,6 +1165,11 @@ struct GST_ValidationIteratorContext
    * Closure for 'cb'.
    */
   void *cb_cls;
+
+  /**
+   * Which peer are we monitoring?
+   */   
+  struct GNUNET_PeerIdentity target;
 };
 
 
@@ -502,8 +1190,9 @@ iterate_addresses (void *cls,
   struct ValidationEntry *ve = value;
 
   vic->cb (vic->cb_cls,
+          &ve->public_key,
           &ve->pid,
-          ve->last_validated_at,
+          ve->valid_until,
           ve->validation_block,
           ve->transport_name,
           ve->addr,
@@ -535,6 +1224,7 @@ GST_validation_get_addresses (const struct GNUNET_PeerIdentity *target,
   vic = GNUNET_malloc (sizeof (struct GST_ValidationIteratorContext));
   vic->cb = cb;
   vic->cb_cls = cb_cls;
+  vic->target = *target;
   GNUNET_CONTAINER_multihashmap_get_multiple (validation_map,
                                              &target->hashPubKey,
                                              &iterate_addresses,
@@ -544,7 +1234,10 @@ GST_validation_get_addresses (const struct GNUNET_PeerIdentity *target,
       GNUNET_free (vic);
       return NULL;
     }
-  /* FIXME: install 'vic' somewhere */
+  GNUNET_CONTAINER_multihashmap_put (notify_map,
+                                    &target->hashPubKey,
+                                    vic,
+                                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
   return vic;
 }
 
@@ -557,7 +1250,10 @@ GST_validation_get_addresses (const struct GNUNET_PeerIdentity *target,
 void
 GST_validation_get_addresses_cancel (struct GST_ValidationIteratorContext *ctx)
 {
-  /* FIXME: remove 'vic' from DS */
+  GNUNET_assert (GNUNET_OK ==
+                GNUNET_CONTAINER_multihashmap_remove (notify_map,
+                                                      &ctx->target.hashPubKey,
+                                                      ctx));
   GNUNET_free (ctx);
 }