2 This file is part of GNUnet.
3 Copyright (C) 2015 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
16 * @file transport/gnunet-service-transport_ats.c
17 * @brief interfacing between transport and ATS service
18 * @author Christian Grothoff
21 #include "gnunet-service-transport.h"
22 #include "gnunet-service-transport_ats.h"
23 #include "gnunet-service-transport_manipulation.h"
24 #include "gnunet-service-transport_plugins.h"
25 #include "gnunet_ats_service.h"
28 * Log convenience function.
30 #define LOG(kind,...) GNUNET_log_from(kind, "transport-ats", __VA_ARGS__)
34 * Information we track for each address known to ATS.
40 * The address (with peer identity). Must never change
41 * while this struct is in the #p2a map.
43 struct GNUNET_HELLO_Address *address;
46 * Session (can be NULL)
48 struct GNUNET_ATS_Session *session;
51 * Record with ATS API for the address.
53 struct GNUNET_ATS_AddressRecord *ar;
56 * Performance properties of this address.
58 struct GNUNET_ATS_Properties properties;
61 * Time until when this address is blocked and should thus not be
62 * made available to ATS (@e ar should be NULL until this time).
63 * Used when transport determines that for some reason it
64 * (temporarily) cannot use an address, even though it has been
67 struct GNUNET_TIME_Absolute blocked;
70 * If an address is blocked as part of an exponential back-off,
71 * we track the current size of the backoff here.
73 struct GNUNET_TIME_Relative back_off;
76 * Task scheduled to unblock an ATS-blocked address at
77 * @e blocked time, or NULL if the address is not blocked
78 * (and thus @e ar is non-NULL).
80 struct GNUNET_SCHEDULER_Task *unblock_task;
83 * Set to #GNUNET_YES if the address has expired but we could
84 * not yet remove it because we still have a valid session.
92 * Map from peer identities to one or more `struct AddressInfo` values
95 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
98 * Number of blocked addresses.
100 static unsigned int num_blocked;
104 * Closure for #find_ai_cb() and #find_ai_no_session_cb().
110 * Session to look for (only used if the address is inbound).
112 struct GNUNET_ATS_Session *session;
115 * Address to look for.
117 const struct GNUNET_HELLO_Address *address;
120 * Where to store the result.
122 struct AddressInfo *ret;
128 * Provide an update on the `p2a` map size to statistics.
129 * This function should be called whenever the `p2a` map
133 publish_p2a_stat_update ()
135 GNUNET_STATISTICS_set (GST_stats,
136 gettext_noop ("# Addresses given to ATS"),
137 GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
139 GNUNET_STATISTICS_set (GST_stats,
140 "# blocked addresses",
147 * Find matching address info. Both the address and the session
148 * must match; note that expired addresses are still found (as
149 * the session kind-of keeps those alive).
151 * @param cls the `struct FindClosure`
152 * @param key which peer is this about
153 * @param value the `struct AddressInfo`
154 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
157 find_ai_cb (void *cls,
158 const struct GNUNET_PeerIdentity *key,
161 struct FindClosure *fc = cls;
162 struct AddressInfo *ai = value;
165 GNUNET_HELLO_address_cmp (fc->address,
167 (fc->session == ai->session) )
177 * Find the address information struct for the
178 * given @a address and @a session.
180 * @param address address to look for
181 * @param session session to match for inbound connections
182 * @return NULL if this combination is unknown
184 static struct AddressInfo *
185 find_ai (const struct GNUNET_HELLO_Address *address,
186 struct GNUNET_ATS_Session *session)
188 struct FindClosure fc;
190 fc.address = address;
191 fc.session = session;
193 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
202 * Find matching address info, ignoring sessions and expired
205 * @param cls the `struct FindClosure`
206 * @param key which peer is this about
207 * @param value the `struct AddressInfo`
208 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
211 find_ai_no_session_cb (void *cls,
212 const struct GNUNET_PeerIdentity *key,
215 struct FindClosure *fc = cls;
216 struct AddressInfo *ai = value;
219 return GNUNET_YES; /* expired do not count here */
221 GNUNET_HELLO_address_cmp (fc->address,
232 * Find the address information struct for the
233 * given address (ignoring sessions)
235 * @param address address to look for
236 * @return NULL if this combination is unknown
238 static struct AddressInfo *
239 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
241 struct FindClosure fc;
243 fc.address = address;
246 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
248 &find_ai_no_session_cb,
255 * Test if ATS knows about this @a address and @a session.
256 * Note that even if the address is expired, we return
257 * #GNUNET_YES if the respective session matches.
259 * @param address the address
260 * @param session the session
261 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
264 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
265 struct GNUNET_ATS_Session *session)
267 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
272 * Test if ATS knows about this @a address. Note that
273 * expired addresses do not count.
275 * @param address the address
276 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
279 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
281 return (NULL != find_ai_no_session (address))
288 * The blocking time for an address has expired, allow ATS to
291 * @param cls the `struct AddressInfo` of the address to unblock
294 unblock_address (void *cls)
296 struct AddressInfo *ai = cls;
298 ai->unblock_task = NULL;
299 LOG (GNUNET_ERROR_TYPE_DEBUG,
300 "Unblocking address %s of peer %s\n",
301 GST_plugins_a2s (ai->address),
302 GNUNET_i2s (&ai->address->peer));
303 ai->ar = GNUNET_ATS_address_add (GST_ats,
307 GNUNET_break (NULL != ai->ar);
309 publish_p2a_stat_update ();
314 * Temporarily block a valid address for use by ATS for address
315 * suggestions. This function should be called if an address was
316 * suggested by ATS but failed to perform (i.e. failure to establish a
317 * session or to exchange the PING/PONG).
319 * @param address the address to block
320 * @param session the session (can be NULL)
323 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
324 struct GNUNET_ATS_Session *session)
326 struct AddressInfo *ai;
329 memcmp (&GST_my_identity,
331 sizeof (struct GNUNET_PeerIdentity)))
332 return; /* our own, ignore! */
333 ai = find_ai (address,
335 if (NULL == ai || NULL == ai->ar)
337 /* The address is already gone/blocked, this can happen during a blacklist
341 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
343 GNUNET_HELLO_address_check_option (address,
344 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
345 LOG (GNUNET_ERROR_TYPE_DEBUG,
346 "Removing address %s of peer %s from use (inbound died)\n",
347 GST_plugins_a2s (address),
348 GNUNET_i2s (&address->peer));
350 LOG (GNUNET_ERROR_TYPE_INFO,
351 "Blocking address %s of peer %s from use for %s\n",
352 GST_plugins_a2s (address),
353 GNUNET_i2s (&address->peer),
354 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
356 /* destroy session and address */
357 if ( (NULL == session) ||
359 GNUNET_ATS_address_del_session (ai->ar,
362 GNUNET_ATS_address_destroy (ai->ar);
364 /* "ar" has been freed, regardless how the branch
365 above played out: it was either freed in
366 #GNUNET_ATS_address_del_session() because it was
367 incoming, or explicitly in
368 #GNUNET_ATS_address_del_session(). */
371 /* determine when the address should come back to life */
372 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
373 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
377 publish_p2a_stat_update ();
382 * Reset address blocking time. Resets the exponential
383 * back-off timer for this address to zero. Called when
384 * an address was used to create a successful connection.
386 * @param address the address to reset the blocking timer
387 * @param session the session (can be NULL)
390 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
391 struct GNUNET_ATS_Session *session)
393 struct AddressInfo *ai;
396 memcmp (&GST_my_identity,
398 sizeof (struct GNUNET_PeerIdentity)))
399 return; /* our own, ignore! */
400 ai = find_ai (address, session);
406 /* address is in successful use, so it should not be blocked right now */
407 GNUNET_break (NULL == ai->unblock_task);
408 ai->back_off = GNUNET_TIME_UNIT_ZERO;
413 * Notify ATS about a new inbound @a address. The @a address in
414 * combination with the @a session must be new, but this function will
415 * perform a santiy check. If the @a address is indeed new, make it
418 * @param address the address
419 * @param session the session
420 * @param prop performance information
423 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
424 struct GNUNET_ATS_Session *session,
425 const struct GNUNET_ATS_Properties *prop)
427 struct GNUNET_ATS_AddressRecord *ar;
428 struct AddressInfo *ai;
431 memcmp (&GST_my_identity,
433 sizeof (struct GNUNET_PeerIdentity)))
434 return; /* our own, ignore! */
436 /* Sanity checks for a valid inbound address */
437 if (NULL == address->transport_name)
442 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
443 GNUNET_assert (GNUNET_YES ==
444 GNUNET_HELLO_address_check_option (address,
445 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
446 GNUNET_assert (NULL != session);
447 ai = find_ai (address, session);
450 /* This should only be called for new sessions, and thus
451 we should not already have the address */
455 /* Is indeed new, let's tell ATS */
456 LOG (GNUNET_ERROR_TYPE_DEBUG,
457 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
458 GNUNET_i2s (&address->peer),
459 GST_plugins_a2s (address),
461 GNUNET_ATS_print_network_type (prop->scope));
462 ar = GNUNET_ATS_address_add (GST_ats,
466 GNUNET_assert (NULL != ar);
467 ai = GNUNET_new (struct AddressInfo);
468 ai->address = GNUNET_HELLO_address_copy (address);
469 ai->session = session;
470 ai->properties = *prop;
472 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
475 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
476 publish_p2a_stat_update ();
481 * Notify ATS about the new address including the network this address is
482 * located in. The address must NOT be inbound and must be new to ATS.
484 * @param address the address
485 * @param prop performance information
488 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
489 const struct GNUNET_ATS_Properties *prop)
491 struct GNUNET_ATS_AddressRecord *ar;
492 struct AddressInfo *ai;
495 memcmp (&GST_my_identity,
497 sizeof (struct GNUNET_PeerIdentity)))
498 return; /* our own, ignore! */
499 /* validadte address */
500 if (NULL == address->transport_name)
505 GNUNET_assert (GNUNET_YES !=
506 GNUNET_HELLO_address_check_option (address,
507 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
508 ai = find_ai_no_session (address);
509 GNUNET_assert (NULL == ai);
510 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
512 /* address seems sane, let's tell ATS */
513 LOG (GNUNET_ERROR_TYPE_INFO,
514 "Notifying ATS about peer %s's new address `%s'\n",
515 GNUNET_i2s (&address->peer),
516 GST_plugins_a2s (address));
517 ar = GNUNET_ATS_address_add (GST_ats,
521 GNUNET_assert (NULL != ar);
522 ai = GNUNET_new (struct AddressInfo);
523 ai->address = GNUNET_HELLO_address_copy (address);
525 ai->properties = *prop;
526 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
529 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
530 publish_p2a_stat_update ();
535 * Notify ATS about a new @a session now existing for the given
536 * @a address. Essentially, an outbound @a address was used
537 * to establish a @a session. It is safe to call this function
538 * repeatedly for the same @a address and @a session pair.
540 * @param address the address
541 * @param session the session
544 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
545 struct GNUNET_ATS_Session *session)
547 struct AddressInfo *ai;
550 memcmp (&GST_my_identity,
552 sizeof (struct GNUNET_PeerIdentity)))
553 return; /* our own, ignore! */
554 ai = find_ai (address, NULL);
557 /* We may simply already be aware of the session, even if some
558 other part of the code could not tell if it just created a new
559 session or just got one recycled from the plugin; hence, we may
560 be called with "new" session even for an "old" session; in that
561 case, check that this is the case, but just ignore it. */
562 GNUNET_assert (NULL != (find_ai (address, session)));
565 GNUNET_assert (NULL == ai->session);
566 ai->session = session;
567 LOG (GNUNET_ERROR_TYPE_DEBUG,
568 "Telling ATS about new session for peer %s\n",
569 GNUNET_i2s (&address->peer));
570 /* Note that the address might currently be blocked; we only
571 tell ATS about the session if the address is currently not
572 blocked; otherwise, ATS will be told about the session on
575 GNUNET_ATS_address_add_session (ai->ar,
578 GNUNET_assert (NULL != ai->unblock_task);
583 * Release memory used by the given address data.
585 * @param ai the `struct AddressInfo`
588 destroy_ai (struct AddressInfo *ai)
590 GNUNET_assert (NULL == ai->session);
591 if (NULL != ai->unblock_task)
593 GNUNET_SCHEDULER_cancel (ai->unblock_task);
594 ai->unblock_task = NULL;
597 GNUNET_assert (GNUNET_YES ==
598 GNUNET_CONTAINER_multipeermap_remove (p2a,
601 LOG (GNUNET_ERROR_TYPE_DEBUG,
602 "Telling ATS to destroy address from peer %s\n",
603 GNUNET_i2s (&ai->address->peer));
606 GNUNET_ATS_address_destroy (ai->ar);
609 publish_p2a_stat_update ();
610 GNUNET_HELLO_address_free (ai->address);
616 * Notify ATS that the @a session (but not the @a address) of
617 * a given @a address is no longer relevant. (The @a session
618 * went down.) This function may be called even if for the
619 * respective outbound address #GST_ats_new_session() was
620 * never called and thus the pair is unknown to ATS. In this
621 * case, the call is simply ignored.
623 * @param address the address
624 * @param session the session
627 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
628 struct GNUNET_ATS_Session *session)
630 struct AddressInfo *ai;
633 memcmp (&GST_my_identity,
635 sizeof (struct GNUNET_PeerIdentity)))
636 return; /* our own, ignore! */
642 ai = find_ai (address,
646 /* We sometimes create sessions just for sending a PING,
647 and if those are destroyed they were never known to
648 ATS which means we end up here (however, in this
649 case, the address must be an outbound address). */
650 GNUNET_break (GNUNET_YES !=
651 GNUNET_HELLO_address_check_option (address,
652 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
655 GNUNET_assert (session == ai->session);
657 LOG (GNUNET_ERROR_TYPE_DEBUG,
658 "Telling ATS to destroy session %p from peer %s\n",
660 GNUNET_i2s (&address->peer));
661 if (GNUNET_YES == ai->expired)
663 /* last reason to keep this 'ai' around is now gone, the
664 session is dead as well, clean up */
667 /* Address expired but not blocked, and thus 'ar' was still
668 live because of the session; deleting just the session
669 will do for an inbound session, but for an outbound we
670 then also need to destroy the address with ATS. */
672 GNUNET_ATS_address_del_session (ai->ar,
675 GNUNET_ATS_address_destroy (ai->ar);
677 /* "ar" has been freed, regardless how the branch
678 above played out: it was either freed in
679 #GNUNET_ATS_address_del_session() because it was
680 incoming, or explicitly in
681 #GNUNET_ATS_address_del_session(). */
690 /* If ATS doesn't know about the address/session, this means
691 this address was blocked. */
693 GNUNET_HELLO_address_check_option (address,
694 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
696 /* This was a blocked inbound session, which now lost the
697 session. But inbound addresses are by themselves useless,
698 so we must forget about the address as well. */
702 /* Otherwise, we are done as we have set `ai->session` to NULL
703 already and ATS will simply not be told about the session when
704 the connection is unblocked and the outbound address becomes
705 available again. . */
709 /* This is the "simple" case where ATS knows about the session and
710 the address is neither blocked nor expired. Delete the session,
711 and if it was inbound, free the address as well. */
713 GNUNET_ATS_address_del_session (ai->ar,
716 /* This was an inbound address, the session is now gone, so we
717 need to also forget about the address itself. */
725 * Notify ATS about DV @a distance change to an @a address.
726 * Does nothing if the @a address is not known to us.
728 * @param address the address
729 * @param distance new distance value
732 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
735 struct AddressInfo *ai;
737 ai = find_ai_no_session (address);
740 /* We do not know about this address, do nothing. */
743 LOG (GNUNET_ERROR_TYPE_DEBUG,
744 "Updated distance for peer `%s' to %u\n",
745 GNUNET_i2s (&address->peer),
747 ai->properties.distance = distance;
748 /* Give manipulation its chance to change metrics */
749 GST_manipulation_manipulate_metrics (address,
752 /* Address may be blocked, only give ATS if address is
755 GNUNET_ATS_address_update (ai->ar,
761 * Notify ATS about @a delay changes to properties of an @a address.
762 * Does nothing if the @a address is not known to us.
764 * @param address the address
765 * @param delay new delay value
768 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
769 struct GNUNET_TIME_Relative delay)
771 struct AddressInfo *ai;
773 ai = find_ai_no_session (address);
776 /* We do not know about this address, do nothing. */
779 LOG (GNUNET_ERROR_TYPE_DEBUG,
780 "Updated latency for peer `%s' to %s\n",
781 GNUNET_i2s (&address->peer),
782 GNUNET_STRINGS_relative_time_to_string (delay,
784 ai->properties.delay = delay;
785 /* Give manipulation its chance to change metrics */
786 GST_manipulation_manipulate_metrics (address,
789 /* Address may be blocked, only give ATS if address is
792 GNUNET_ATS_address_update (ai->ar,
798 * Notify ATS about utilization changes to an @a address.
799 * Does nothing if the @a address is not known to us.
801 * @param address our information about the address
802 * @param bps_in new utilization inbound
803 * @param bps_out new utilization outbound
806 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
810 struct AddressInfo *ai;
812 ai = find_ai_no_session (address);
815 /* We do not know about this address, do nothing. */
818 LOG (GNUNET_ERROR_TYPE_DEBUG,
819 "Updating utilization for peer `%s' address %s: %u/%u\n",
820 GNUNET_i2s (&address->peer),
821 GST_plugins_a2s (address),
822 (unsigned int) bps_in,
823 (unsigned int) bps_out);
824 ai->properties.utilization_in = bps_in;
825 ai->properties.utilization_out = bps_out;
826 /* Give manipulation its chance to change metrics */
827 GST_manipulation_manipulate_metrics (address,
830 /* Address may be blocked, only give ATS if address is
833 GNUNET_ATS_address_update (ai->ar,
839 * Notify ATS that the address has expired and thus cannot
840 * be used any longer. This function must only be called
841 * if the corresponding session is already gone.
843 * @param address the address
846 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
848 struct AddressInfo *ai;
851 memcmp (&GST_my_identity,
853 sizeof (struct GNUNET_PeerIdentity)))
854 return; /* our own, ignore! */
855 LOG (GNUNET_ERROR_TYPE_DEBUG,
856 "Address %s of peer %s expired\n",
857 GST_plugins_a2s (address),
858 GNUNET_i2s (&address->peer));
859 ai = find_ai_no_session (address);
865 if (NULL != ai->session)
867 /* Got an active session, just remember the expiration
868 and act upon it when the session goes down. */
869 ai->expired = GNUNET_YES;
872 /* Address expired, no session, free resources */
878 * Initialize ATS subsystem.
883 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
888 * Release memory used by the given address data.
891 * @param key which peer is this about
892 * @param value the `struct AddressInfo`
893 * @return #GNUNET_OK (continue to iterate)
896 destroy_ai_cb (void *cls,
897 const struct GNUNET_PeerIdentity *key,
900 struct AddressInfo *ai = value;
908 * Shutdown ATS subsystem.
913 GNUNET_CONTAINER_multipeermap_iterate (p2a,
916 publish_p2a_stat_update ();
917 GNUNET_CONTAINER_multipeermap_destroy (p2a);
921 /* end of gnunet-service-transport_ats.c */