*/
#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 is a PONG signature valid? We'll recycle a signature until
+ * 1/4 of this time is remaining. PONGs should expire so that if our
+ * external addresses change an adversary cannot replay them indefinitely.
+ * OTOH, we don't want to spend too much time generating PONG signatures,
+ * so they must have some lifetime to reduce our CPU usage.
+ */
+#define PONG_SIGNATURE_LIFETIME GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 1)
+
+/**
+ * After how long do we expire an address in a HELLO that we just
+ * validated? This value is also used for our own addresses when we
+ * create a HELLO.
+ */
+#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
+ * HELLO_ADDRESS_EXPIRATION.
+ */
+#define HELLO_REVALIDATION_START_TIME GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 1)
+
+/**
+ * Size of the validation map hashmap.
+ */
+#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
+ */
+struct ValidationEntry
+{
+
+ /**
+ * Name of the transport.
+ */
+ char *transport_name;
+
+ /**
+ * The address, actually a pointer to the end
+ * of this struct. Do not free!
+ */
+ const void *addr;
+
+ /**
+ * Public key of the peer.
+ */
+ struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded public_key;
+
+ /**
+ * The identity of the peer.
+ */
+ struct GNUNET_PeerIdentity pid;
+
+ /**
+ * ID of task that will clean up this entry if nothing happens.
+ */
+ GNUNET_SCHEDULER_TaskIdentifier timeout_task;
+
+ /**
+ * At what time did we send the latest validation request?
+ */
+ struct GNUNET_TIME_Absolute send_time;
+
+ /**
+ * Until when is this address valid?
+ * ZERO if it is not currently considered valid.
+ */
+ struct GNUNET_TIME_Absolute valid_until;
+
+ /**
+ * How long until we can try to validate this address again?
+ * FOREVER if the address is for an unsupported plugin (from PEERINFO)
+ * ZERO if the address is considered valid (no validation needed)
+ * otherwise a time in the future if we're currently denying re-validation
+ */
+ struct GNUNET_TIME_Absolute validation_block;
+
+ /**
+ * Challenge number we used.
+ */
+ uint32_t challenge;
+
+ /**
+ * Length of addr.
+ */
+ size_t addrlen;
+
+};
+
+
+/**
+ * Context of currently active requests to peerinfo
+ * for validation of HELLOs.
+ */
+struct CheckHelloValidatedContext
+{
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct CheckHelloValidatedContext *next;
+
+ /**
+ * This is a doubly-linked list.
+ */
+ struct CheckHelloValidatedContext *prev;
+
+ /**
+ * Hello that we are validating.
+ */
+ const struct GNUNET_HELLO_Message *hello;
+
+};
+
+
+/**
+ * Head of linked list of HELLOs awaiting validation.
+ */
+static struct CheckHelloValidatedContext *chvc_head;
+
+/**
+ * Tail of linked list of HELLOs awaiting validation
+ */
+static struct CheckHelloValidatedContext *chvc_tail;
+
+/**
+ * Map of PeerIdentities to 'struct ValidationEntry*'s (addresses
+ * of the given peer that we are currently validating, have validated
+ * or are blocked from re-validation for a while).
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *validation_map;
+
+/**
+ * Map of PeerIdentities to 'struct GST_ValidationIteratorContext's.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *notify_map;
+
+/**
+ * Context for peerinfo iteration.
+ */
+static struct GNUNET_PEERINFO_NotifyContext *pnc;
+
+
+/**
+ * Context for the validation entry match function.
+ */
+struct ValidationEntryMatchContext
+{
+ /**
+ * Where to store the result?
+ */
+ struct ValidationEntry *ve;
+
+ /**
+ * Transport name we're looking for.
+ */
+ const char *transport_name;
+
+ /**
+ * Address we're interested in.
+ */
+ const char *addr;
+
+ /**
+ * Number of bytes in 'addr'.
+ */
+ size_t addrlen;
+};
+
+
+/**
+ * Iterate over validation entries until a matching one is found.
+ *
+ * @param cls the 'struct ValidationEntryMatchContext'
+ * @param key peer identity (unused)
+ * @param value a 'struct ValidationEntry' to match
+ * @return GNUNET_YES if the entry does not match,
+ * GNUNET_NO if the entry does match
+ */
+static int
+validation_entry_match (void *cls,
+ const GNUNET_HashCode *key,
+ void *value)
+{
+ struct ValidationEntryMatchContext *vemc = cls;
+ struct ValidationEntry *ve = value;
+
+ if ( (ve->addrlen == vemc->addrlen) &&
+ (0 == memcmp (ve->addr, vemc->addr, ve->addrlen)) &&
+ (0 == strcmp (ve->transport_name, vemc->transport_name)) )
+ {
+ vemc->ve = ve;
+ return GNUNET_NO;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Find a ValidationEntry entry for the given neighbour that matches
+ * 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
+ * can be used for the service to "learn" this session ID
+ * if 'addr' matches
+ * @param addr binary address
+ * @param addrlen length of addr
+ * @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 (const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *public_key,
+ const struct GNUNET_PeerIdentity *neighbour,
+ const char *tname,
+ const char *addr,
+ size_t addrlen)
+{
+ struct ValidationEntryMatchContext vemc;
+ struct ValidationEntry *ve;
+
+ vemc.ve = NULL;
+ vemc.transport_name = tname;
+ vemc.addr = addr;
+ vemc.addrlen = addrlen;
+ GNUNET_CONTAINER_multihashmap_get_multiple (validation_map,
+ &neighbour->hashPubKey,
+ &validation_entry_match,
+ &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;
+ GNUNET_CONTAINER_multihashmap_put (validation_map,
+ &neighbour->hashPubKey,
+ ve,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ return ve;
+}
+
+
+/**
+ * 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));
+}
/**
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;
}
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);
}
*
* @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
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);
+}
+
+
+/**
+ * Context for the 'validate_address' function
+ */
+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 a 'struct ValidateAddressContext'
+ * @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
+validate_address (void *cls,
+ const char *tname,
+ struct GNUNET_TIME_Absolute expiration,
+ const void *addr,
+ uint16_t addrlen)
+{
+ 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 (&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);
}
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);
}
void
GST_validation_handle_hello (const struct GNUNET_MessageHeader *hello)
{
+ const struct GNUNET_HELLO_Message* hm = (const struct GNUNET_HELLO_Message*) hello;
+ struct ValidateAddressContext vac;
+
+ 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);
+ return;
+ }
+ GNUNET_assert (NULL ==
+ GNUNET_HELLO_iterate_addresses (hm,
+ GNUNET_NO,
+ &validate_address,
+ &vac));
}
*/
struct GST_ValidationIteratorContext
{
+ /**
+ * Function to call on each address.
+ */
+ GST_ValidationAddressCallback cb;
+
+ /**
+ * Closure for 'cb'.
+ */
+ void *cb_cls;
+
+ /**
+ * Which peer are we monitoring?
+ */
+ struct GNUNET_PeerIdentity target;
};
+/**
+ * Call the callback in the closure for each validation entry.
+ *
+ * @param cls the 'struct GST_ValidationIteratorContext'
+ * @param key the peer's identity
+ * @param value the 'struct ValidationEntry'
+ * @return GNUNET_OK (continue to iterate)
+ */
+static int
+iterate_addresses (void *cls,
+ const GNUNET_HashCode *key,
+ void *value)
+{
+ struct GST_ValidationIteratorContext *vic = cls;
+ struct ValidationEntry *ve = value;
+
+ vic->cb (vic->cb_cls,
+ &ve->public_key,
+ &ve->pid,
+ ve->valid_until,
+ ve->validation_block,
+ ve->transport_name,
+ ve->addr,
+ ve->addrlen);
+ return GNUNET_OK;
+}
+
+
/**
* Call the given function for each address for the given target.
* Can either give a snapshot (synchronous API) or be continuous.
GST_ValidationAddressCallback cb,
void *cb_cls)
{
- return NULL;
+ struct GST_ValidationIteratorContext *vic;
+
+ 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,
+ vic);
+ if (GNUNET_YES == snapshot_only)
+ {
+ GNUNET_free (vic);
+ return NULL;
+ }
+ GNUNET_CONTAINER_multihashmap_put (notify_map,
+ &target->hashPubKey,
+ vic,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ return vic;
}
void
GST_validation_get_addresses_cancel (struct GST_ValidationIteratorContext *ctx)
{
- GNUNET_break (0);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (notify_map,
+ &ctx->target.hashPubKey,
+ ctx));
+ GNUNET_free (ctx);
}