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.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @file transport/gnunet-service-transport_ats.c
20 * @brief interfacing between transport and ATS service
21 * @author Christian Grothoff
24 #include "gnunet-service-transport.h"
25 #include "gnunet-service-transport_ats.h"
26 #include "gnunet-service-transport_manipulation.h"
27 #include "gnunet-service-transport_plugins.h"
28 #include "gnunet_ats_service.h"
31 * Log convenience function.
33 #define LOG(kind,...) GNUNET_log_from(kind, "transport-ats", __VA_ARGS__)
37 * Information we track for each address known to ATS.
43 * The address (with peer identity). Must never change
44 * while this struct is in the #p2a map.
46 struct GNUNET_HELLO_Address *address;
49 * Session (can be NULL)
51 struct GNUNET_ATS_Session *session;
54 * Record with ATS API for the address.
56 struct GNUNET_ATS_AddressRecord *ar;
59 * Performance properties of this address.
61 struct GNUNET_ATS_Properties properties;
64 * Time until when this address is blocked and should thus not be
65 * made available to ATS (@e ar should be NULL until this time).
66 * Used when transport determines that for some reason it
67 * (temporarily) cannot use an address, even though it has been
70 struct GNUNET_TIME_Absolute blocked;
73 * If an address is blocked as part of an exponential back-off,
74 * we track the current size of the backoff here.
76 struct GNUNET_TIME_Relative back_off;
79 * Task scheduled to unblock an ATS-blocked address at
80 * @e blocked time, or NULL if the address is not blocked
81 * (and thus @e ar is non-NULL).
83 struct GNUNET_SCHEDULER_Task *unblock_task;
86 * Set to #GNUNET_YES if the address has expired but we could
87 * not yet remove it because we still have a valid session.
95 * Map from peer identities to one or more `struct AddressInfo` values
98 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
101 * Number of blocked addresses.
103 static unsigned int num_blocked;
107 * Closure for #find_ai_cb() and #find_ai_no_session_cb().
113 * Session to look for (only used if the address is inbound).
115 struct GNUNET_ATS_Session *session;
118 * Address to look for.
120 const struct GNUNET_HELLO_Address *address;
123 * Where to store the result.
125 struct AddressInfo *ret;
131 * Provide an update on the `p2a` map size to statistics.
132 * This function should be called whenever the `p2a` map
136 publish_p2a_stat_update ()
138 GNUNET_STATISTICS_set (GST_stats,
139 gettext_noop ("# Addresses given to ATS"),
140 GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
142 GNUNET_STATISTICS_set (GST_stats,
143 "# blocked addresses",
150 * Find matching address info. Both the address and the session
151 * must match; note that expired addresses are still found (as
152 * the session kind-of keeps those alive).
154 * @param cls the `struct FindClosure`
155 * @param key which peer is this about
156 * @param value the `struct AddressInfo`
157 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
160 find_ai_cb (void *cls,
161 const struct GNUNET_PeerIdentity *key,
164 struct FindClosure *fc = cls;
165 struct AddressInfo *ai = value;
168 GNUNET_HELLO_address_cmp (fc->address,
170 (fc->session == ai->session) )
180 * Find the address information struct for the
181 * given @a address and @a session.
183 * @param address address to look for
184 * @param session session to match for inbound connections
185 * @return NULL if this combination is unknown
187 static struct AddressInfo *
188 find_ai (const struct GNUNET_HELLO_Address *address,
189 struct GNUNET_ATS_Session *session)
191 struct FindClosure fc;
193 fc.address = address;
194 fc.session = session;
196 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
205 * Find matching address info, ignoring sessions and expired
208 * @param cls the `struct FindClosure`
209 * @param key which peer is this about
210 * @param value the `struct AddressInfo`
211 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
214 find_ai_no_session_cb (void *cls,
215 const struct GNUNET_PeerIdentity *key,
218 struct FindClosure *fc = cls;
219 struct AddressInfo *ai = value;
222 return GNUNET_YES; /* expired do not count here */
224 GNUNET_HELLO_address_cmp (fc->address,
235 * Find the address information struct for the
236 * given address (ignoring sessions)
238 * @param address address to look for
239 * @return NULL if this combination is unknown
241 static struct AddressInfo *
242 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
244 struct FindClosure fc;
246 fc.address = address;
249 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
251 &find_ai_no_session_cb,
258 * Test if ATS knows about this @a address and @a session.
259 * Note that even if the address is expired, we return
260 * #GNUNET_YES if the respective session matches.
262 * @param address the address
263 * @param session the session
264 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
267 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
268 struct GNUNET_ATS_Session *session)
270 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
275 * Test if ATS knows about this @a address. Note that
276 * expired addresses do not count.
278 * @param address the address
279 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
282 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
284 return (NULL != find_ai_no_session (address))
291 * The blocking time for an address has expired, allow ATS to
294 * @param cls the `struct AddressInfo` of the address to unblock
297 unblock_address (void *cls)
299 struct AddressInfo *ai = cls;
301 ai->unblock_task = NULL;
302 LOG (GNUNET_ERROR_TYPE_DEBUG,
303 "Unblocking address %s of peer %s\n",
304 GST_plugins_a2s (ai->address),
305 GNUNET_i2s (&ai->address->peer));
306 ai->ar = GNUNET_ATS_address_add (GST_ats,
310 GNUNET_break (NULL != ai->ar);
312 publish_p2a_stat_update ();
317 * Temporarily block a valid address for use by ATS for address
318 * suggestions. This function should be called if an address was
319 * suggested by ATS but failed to perform (i.e. failure to establish a
320 * session or to exchange the PING/PONG).
322 * @param address the address to block
323 * @param session the session (can be NULL)
326 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
327 struct GNUNET_ATS_Session *session)
329 struct AddressInfo *ai;
332 memcmp (&GST_my_identity,
334 sizeof (struct GNUNET_PeerIdentity)))
335 return; /* our own, ignore! */
336 ai = find_ai (address,
338 if (NULL == ai || NULL == ai->ar)
340 /* The address is already gone/blocked, this can happen during a blacklist
344 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
346 GNUNET_HELLO_address_check_option (address,
347 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
348 LOG (GNUNET_ERROR_TYPE_DEBUG,
349 "Removing address %s of peer %s from use (inbound died)\n",
350 GST_plugins_a2s (address),
351 GNUNET_i2s (&address->peer));
353 LOG (GNUNET_ERROR_TYPE_INFO,
354 "Blocking address %s of peer %s from use for %s\n",
355 GST_plugins_a2s (address),
356 GNUNET_i2s (&address->peer),
357 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
359 /* destroy session and address */
360 if ( (NULL == session) ||
362 GNUNET_ATS_address_del_session (ai->ar,
365 GNUNET_ATS_address_destroy (ai->ar);
367 /* "ar" has been freed, regardless how the branch
368 above played out: it was either freed in
369 #GNUNET_ATS_address_del_session() because it was
370 incoming, or explicitly in
371 #GNUNET_ATS_address_del_session(). */
374 /* determine when the address should come back to life */
375 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
376 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
380 publish_p2a_stat_update ();
385 * Reset address blocking time. Resets the exponential
386 * back-off timer for this address to zero. Called when
387 * an address was used to create a successful connection.
389 * @param address the address to reset the blocking timer
390 * @param session the session (can be NULL)
393 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
394 struct GNUNET_ATS_Session *session)
396 struct AddressInfo *ai;
399 memcmp (&GST_my_identity,
401 sizeof (struct GNUNET_PeerIdentity)))
402 return; /* our own, ignore! */
403 ai = find_ai (address, session);
409 /* address is in successful use, so it should not be blocked right now */
410 GNUNET_break (NULL == ai->unblock_task);
411 ai->back_off = GNUNET_TIME_UNIT_ZERO;
416 * Notify ATS about a new inbound @a address. The @a address in
417 * combination with the @a session must be new, but this function will
418 * perform a santiy check. If the @a address is indeed new, make it
421 * @param address the address
422 * @param session the session
423 * @param prop performance information
426 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
427 struct GNUNET_ATS_Session *session,
428 const struct GNUNET_ATS_Properties *prop)
430 struct GNUNET_ATS_AddressRecord *ar;
431 struct AddressInfo *ai;
434 memcmp (&GST_my_identity,
436 sizeof (struct GNUNET_PeerIdentity)))
437 return; /* our own, ignore! */
439 /* Sanity checks for a valid inbound address */
440 if (NULL == address->transport_name)
445 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
446 GNUNET_assert (GNUNET_YES ==
447 GNUNET_HELLO_address_check_option (address,
448 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
449 GNUNET_assert (NULL != session);
450 ai = find_ai (address, session);
453 /* This should only be called for new sessions, and thus
454 we should not already have the address */
458 /* Is indeed new, let's tell ATS */
459 LOG (GNUNET_ERROR_TYPE_DEBUG,
460 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
461 GNUNET_i2s (&address->peer),
462 GST_plugins_a2s (address),
464 GNUNET_ATS_print_network_type (prop->scope));
465 ar = GNUNET_ATS_address_add (GST_ats,
469 GNUNET_assert (NULL != ar);
470 ai = GNUNET_new (struct AddressInfo);
471 ai->address = GNUNET_HELLO_address_copy (address);
472 ai->session = session;
473 ai->properties = *prop;
475 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
478 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
479 publish_p2a_stat_update ();
484 * Notify ATS about the new address including the network this address is
485 * located in. The address must NOT be inbound and must be new to ATS.
487 * @param address the address
488 * @param prop performance information
491 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
492 const struct GNUNET_ATS_Properties *prop)
494 struct GNUNET_ATS_AddressRecord *ar;
495 struct AddressInfo *ai;
498 memcmp (&GST_my_identity,
500 sizeof (struct GNUNET_PeerIdentity)))
501 return; /* our own, ignore! */
502 /* validadte address */
503 if (NULL == address->transport_name)
508 GNUNET_assert (GNUNET_YES !=
509 GNUNET_HELLO_address_check_option (address,
510 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
511 ai = find_ai_no_session (address);
512 GNUNET_assert (NULL == ai);
513 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
515 /* address seems sane, let's tell ATS */
516 LOG (GNUNET_ERROR_TYPE_INFO,
517 "Notifying ATS about peer %s's new address `%s'\n",
518 GNUNET_i2s (&address->peer),
519 GST_plugins_a2s (address));
520 ar = GNUNET_ATS_address_add (GST_ats,
524 GNUNET_assert (NULL != ar);
525 ai = GNUNET_new (struct AddressInfo);
526 ai->address = GNUNET_HELLO_address_copy (address);
528 ai->properties = *prop;
529 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
532 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
533 publish_p2a_stat_update ();
538 * Notify ATS about a new @a session now existing for the given
539 * @a address. Essentially, an outbound @a address was used
540 * to establish a @a session. It is safe to call this function
541 * repeatedly for the same @a address and @a session pair.
543 * @param address the address
544 * @param session the session
547 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
548 struct GNUNET_ATS_Session *session)
550 struct AddressInfo *ai;
553 memcmp (&GST_my_identity,
555 sizeof (struct GNUNET_PeerIdentity)))
556 return; /* our own, ignore! */
557 ai = find_ai (address, NULL);
560 /* We may simply already be aware of the session, even if some
561 other part of the code could not tell if it just created a new
562 session or just got one recycled from the plugin; hence, we may
563 be called with "new" session even for an "old" session; in that
564 case, check that this is the case, but just ignore it. */
565 GNUNET_assert (NULL != (find_ai (address, session)));
568 GNUNET_assert (NULL == ai->session);
569 ai->session = session;
570 LOG (GNUNET_ERROR_TYPE_DEBUG,
571 "Telling ATS about new session for peer %s\n",
572 GNUNET_i2s (&address->peer));
573 /* Note that the address might currently be blocked; we only
574 tell ATS about the session if the address is currently not
575 blocked; otherwise, ATS will be told about the session on
578 GNUNET_ATS_address_add_session (ai->ar,
581 GNUNET_assert (NULL != ai->unblock_task);
586 * Release memory used by the given address data.
588 * @param ai the `struct AddressInfo`
591 destroy_ai (struct AddressInfo *ai)
593 GNUNET_assert (NULL == ai->session);
594 if (NULL != ai->unblock_task)
596 GNUNET_SCHEDULER_cancel (ai->unblock_task);
597 ai->unblock_task = NULL;
600 GNUNET_assert (GNUNET_YES ==
601 GNUNET_CONTAINER_multipeermap_remove (p2a,
604 LOG (GNUNET_ERROR_TYPE_DEBUG,
605 "Telling ATS to destroy address from peer %s\n",
606 GNUNET_i2s (&ai->address->peer));
609 GNUNET_ATS_address_destroy (ai->ar);
612 publish_p2a_stat_update ();
613 GNUNET_HELLO_address_free (ai->address);
619 * Notify ATS that the @a session (but not the @a address) of
620 * a given @a address is no longer relevant. (The @a session
621 * went down.) This function may be called even if for the
622 * respective outbound address #GST_ats_new_session() was
623 * never called and thus the pair is unknown to ATS. In this
624 * case, the call is simply ignored.
626 * @param address the address
627 * @param session the session
630 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
631 struct GNUNET_ATS_Session *session)
633 struct AddressInfo *ai;
636 memcmp (&GST_my_identity,
638 sizeof (struct GNUNET_PeerIdentity)))
639 return; /* our own, ignore! */
645 ai = find_ai (address,
649 /* We sometimes create sessions just for sending a PING,
650 and if those are destroyed they were never known to
651 ATS which means we end up here (however, in this
652 case, the address must be an outbound address). */
653 GNUNET_break (GNUNET_YES !=
654 GNUNET_HELLO_address_check_option (address,
655 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
658 GNUNET_assert (session == ai->session);
660 LOG (GNUNET_ERROR_TYPE_DEBUG,
661 "Telling ATS to destroy session %p from peer %s\n",
663 GNUNET_i2s (&address->peer));
664 if (GNUNET_YES == ai->expired)
666 /* last reason to keep this 'ai' around is now gone, the
667 session is dead as well, clean up */
670 /* Address expired but not blocked, and thus 'ar' was still
671 live because of the session; deleting just the session
672 will do for an inbound session, but for an outbound we
673 then also need to destroy the address with ATS. */
675 GNUNET_ATS_address_del_session (ai->ar,
678 GNUNET_ATS_address_destroy (ai->ar);
680 /* "ar" has been freed, regardless how the branch
681 above played out: it was either freed in
682 #GNUNET_ATS_address_del_session() because it was
683 incoming, or explicitly in
684 #GNUNET_ATS_address_del_session(). */
693 /* If ATS doesn't know about the address/session, this means
694 this address was blocked. */
696 GNUNET_HELLO_address_check_option (address,
697 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
699 /* This was a blocked inbound session, which now lost the
700 session. But inbound addresses are by themselves useless,
701 so we must forget about the address as well. */
705 /* Otherwise, we are done as we have set `ai->session` to NULL
706 already and ATS will simply not be told about the session when
707 the connection is unblocked and the outbound address becomes
708 available again. . */
712 /* This is the "simple" case where ATS knows about the session and
713 the address is neither blocked nor expired. Delete the session,
714 and if it was inbound, free the address as well. */
716 GNUNET_ATS_address_del_session (ai->ar,
719 /* This was an inbound address, the session is now gone, so we
720 need to also forget about the address itself. */
728 * Notify ATS about DV @a distance change to an @a address.
729 * Does nothing if the @a address is not known to us.
731 * @param address the address
732 * @param distance new distance value
735 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
738 struct AddressInfo *ai;
740 ai = find_ai_no_session (address);
743 /* We do not know about this address, do nothing. */
746 LOG (GNUNET_ERROR_TYPE_DEBUG,
747 "Updated distance for peer `%s' to %u\n",
748 GNUNET_i2s (&address->peer),
750 ai->properties.distance = distance;
751 /* Give manipulation its chance to change metrics */
752 GST_manipulation_manipulate_metrics (address,
755 /* Address may be blocked, only give ATS if address is
758 GNUNET_ATS_address_update (ai->ar,
764 * Notify ATS about @a delay changes to properties of an @a address.
765 * Does nothing if the @a address is not known to us.
767 * @param address the address
768 * @param delay new delay value
771 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
772 struct GNUNET_TIME_Relative delay)
774 struct AddressInfo *ai;
776 ai = find_ai_no_session (address);
779 /* We do not know about this address, do nothing. */
782 LOG (GNUNET_ERROR_TYPE_DEBUG,
783 "Updated latency for peer `%s' to %s\n",
784 GNUNET_i2s (&address->peer),
785 GNUNET_STRINGS_relative_time_to_string (delay,
787 ai->properties.delay = delay;
788 /* Give manipulation its chance to change metrics */
789 GST_manipulation_manipulate_metrics (address,
792 /* Address may be blocked, only give ATS if address is
795 GNUNET_ATS_address_update (ai->ar,
801 * Notify ATS about utilization changes to an @a address.
802 * Does nothing if the @a address is not known to us.
804 * @param address our information about the address
805 * @param bps_in new utilization inbound
806 * @param bps_out new utilization outbound
809 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
813 struct AddressInfo *ai;
815 ai = find_ai_no_session (address);
818 /* We do not know about this address, do nothing. */
821 LOG (GNUNET_ERROR_TYPE_DEBUG,
822 "Updating utilization for peer `%s' address %s: %u/%u\n",
823 GNUNET_i2s (&address->peer),
824 GST_plugins_a2s (address),
825 (unsigned int) bps_in,
826 (unsigned int) bps_out);
827 ai->properties.utilization_in = bps_in;
828 ai->properties.utilization_out = bps_out;
829 /* Give manipulation its chance to change metrics */
830 GST_manipulation_manipulate_metrics (address,
833 /* Address may be blocked, only give ATS if address is
836 GNUNET_ATS_address_update (ai->ar,
842 * Notify ATS that the address has expired and thus cannot
843 * be used any longer. This function must only be called
844 * if the corresponding session is already gone.
846 * @param address the address
849 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
851 struct AddressInfo *ai;
854 memcmp (&GST_my_identity,
856 sizeof (struct GNUNET_PeerIdentity)))
857 return; /* our own, ignore! */
858 LOG (GNUNET_ERROR_TYPE_DEBUG,
859 "Address %s of peer %s expired\n",
860 GST_plugins_a2s (address),
861 GNUNET_i2s (&address->peer));
862 ai = find_ai_no_session (address);
868 if (NULL != ai->session)
870 /* Got an active session, just remember the expiration
871 and act upon it when the session goes down. */
872 ai->expired = GNUNET_YES;
875 /* Address expired, no session, free resources */
881 * Initialize ATS subsystem.
886 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
891 * Release memory used by the given address data.
894 * @param key which peer is this about
895 * @param value the `struct AddressInfo`
896 * @return #GNUNET_OK (continue to iterate)
899 destroy_ai_cb (void *cls,
900 const struct GNUNET_PeerIdentity *key,
903 struct AddressInfo *ai = value;
911 * Shutdown ATS subsystem.
916 GNUNET_CONTAINER_multipeermap_iterate (p2a,
919 publish_p2a_stat_update ();
920 GNUNET_CONTAINER_multipeermap_destroy (p2a);
924 /* end of gnunet-service-transport_ats.c */