2 This file is part of GNUnet.
3 Copyright (C) 2015 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 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 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 * @file transport/gnunet-service-transport_ats.c
22 * @brief interfacing between transport and ATS service
23 * @author Christian Grothoff
26 #include "gnunet-service-transport.h"
27 #include "gnunet-service-transport_ats.h"
28 #include "gnunet-service-transport_manipulation.h"
29 #include "gnunet-service-transport_plugins.h"
30 #include "gnunet_ats_service.h"
33 * Log convenience function.
35 #define LOG(kind,...) GNUNET_log_from(kind, "transport-ats", __VA_ARGS__)
39 * Information we track for each address known to ATS.
45 * The address (with peer identity). Must never change
46 * while this struct is in the #p2a map.
48 struct GNUNET_HELLO_Address *address;
51 * Session (can be NULL)
53 struct GNUNET_ATS_Session *session;
56 * Record with ATS API for the address.
58 struct GNUNET_ATS_AddressRecord *ar;
61 * Performance properties of this address.
63 struct GNUNET_ATS_Properties properties;
66 * Time until when this address is blocked and should thus not be
67 * made available to ATS (@e ar should be NULL until this time).
68 * Used when transport determines that for some reason it
69 * (temporarily) cannot use an address, even though it has been
72 struct GNUNET_TIME_Absolute blocked;
75 * If an address is blocked as part of an exponential back-off,
76 * we track the current size of the backoff here.
78 struct GNUNET_TIME_Relative back_off;
81 * Task scheduled to unblock an ATS-blocked address at
82 * @e blocked time, or NULL if the address is not blocked
83 * (and thus @e ar is non-NULL).
85 struct GNUNET_SCHEDULER_Task *unblock_task;
88 * Set to #GNUNET_YES if the address has expired but we could
89 * not yet remove it because we still have a valid session.
97 * Map from peer identities to one or more `struct AddressInfo` values
100 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
103 * Number of blocked addresses.
105 static unsigned int num_blocked;
109 * Closure for #find_ai_cb() and #find_ai_no_session_cb().
115 * Session to look for (only used if the address is inbound).
117 struct GNUNET_ATS_Session *session;
120 * Address to look for.
122 const struct GNUNET_HELLO_Address *address;
125 * Where to store the result.
127 struct AddressInfo *ret;
133 * Provide an update on the `p2a` map size to statistics.
134 * This function should be called whenever the `p2a` map
138 publish_p2a_stat_update ()
140 GNUNET_STATISTICS_set (GST_stats,
141 gettext_noop ("# Addresses given to ATS"),
142 GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
144 GNUNET_STATISTICS_set (GST_stats,
145 "# blocked addresses",
152 * Find matching address info. Both the address and the session
153 * must match; note that expired addresses are still found (as
154 * the session kind-of keeps those alive).
156 * @param cls the `struct FindClosure`
157 * @param key which peer is this about
158 * @param value the `struct AddressInfo`
159 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
162 find_ai_cb (void *cls,
163 const struct GNUNET_PeerIdentity *key,
166 struct FindClosure *fc = cls;
167 struct AddressInfo *ai = value;
170 GNUNET_HELLO_address_cmp (fc->address,
172 (fc->session == ai->session) )
182 * Find the address information struct for the
183 * given @a address and @a session.
185 * @param address address to look for
186 * @param session session to match for inbound connections
187 * @return NULL if this combination is unknown
189 static struct AddressInfo *
190 find_ai (const struct GNUNET_HELLO_Address *address,
191 struct GNUNET_ATS_Session *session)
193 struct FindClosure fc;
195 fc.address = address;
196 fc.session = session;
198 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
207 * Find matching address info, ignoring sessions and expired
210 * @param cls the `struct FindClosure`
211 * @param key which peer is this about
212 * @param value the `struct AddressInfo`
213 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
216 find_ai_no_session_cb (void *cls,
217 const struct GNUNET_PeerIdentity *key,
220 struct FindClosure *fc = cls;
221 struct AddressInfo *ai = value;
224 return GNUNET_YES; /* expired do not count here */
226 GNUNET_HELLO_address_cmp (fc->address,
237 * Find the address information struct for the
238 * given address (ignoring sessions)
240 * @param address address to look for
241 * @return NULL if this combination is unknown
243 static struct AddressInfo *
244 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
246 struct FindClosure fc;
248 fc.address = address;
251 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
253 &find_ai_no_session_cb,
260 * Test if ATS knows about this @a address and @a session.
261 * Note that even if the address is expired, we return
262 * #GNUNET_YES if the respective session matches.
264 * @param address the address
265 * @param session the session
266 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
269 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
270 struct GNUNET_ATS_Session *session)
272 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
277 * Test if ATS knows about this @a address. Note that
278 * expired addresses do not count.
280 * @param address the address
281 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
284 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
286 return (NULL != find_ai_no_session (address))
293 * The blocking time for an address has expired, allow ATS to
296 * @param cls the `struct AddressInfo` of the address to unblock
300 unblock_address (void *cls,
301 const struct GNUNET_SCHEDULER_TaskContext *tc)
303 struct AddressInfo *ai = cls;
305 ai->unblock_task = NULL;
306 LOG (GNUNET_ERROR_TYPE_DEBUG,
307 "Unblocking address %s of peer %s\n",
308 GST_plugins_a2s (ai->address),
309 GNUNET_i2s (&ai->address->peer));
310 ai->ar = GNUNET_ATS_address_add (GST_ats,
314 GNUNET_break (NULL != ai->ar);
316 publish_p2a_stat_update ();
321 * Temporarily block a valid address for use by ATS for address
322 * suggestions. This function should be called if an address was
323 * suggested by ATS but failed to perform (i.e. failure to establish a
324 * session or to exchange the PING/PONG).
326 * @param address the address to block
327 * @param session the session (can be NULL)
330 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
331 struct GNUNET_ATS_Session *session)
333 struct AddressInfo *ai;
336 memcmp (&GST_my_identity,
338 sizeof (struct GNUNET_PeerIdentity)))
339 return; /* our own, ignore! */
340 ai = find_ai (address,
349 /* already blocked, how did it get used!? */
353 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
355 GNUNET_HELLO_address_check_option (address,
356 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
357 LOG (GNUNET_ERROR_TYPE_DEBUG,
358 "Removing address %s of peer %s from use (inbound died)\n",
359 GST_plugins_a2s (address),
360 GNUNET_i2s (&address->peer));
362 LOG (GNUNET_ERROR_TYPE_INFO,
363 "Blocking address %s of peer %s from use for %s\n",
364 GST_plugins_a2s (address),
365 GNUNET_i2s (&address->peer),
366 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
368 /* destroy session and address */
369 if ( (NULL == session) ||
371 GNUNET_ATS_address_del_session (ai->ar,
374 GNUNET_ATS_address_destroy (ai->ar);
376 /* "ar" has been freed, regardless how the branch
377 above played out: it was either freed in
378 #GNUNET_ATS_address_del_session() because it was
379 incoming, or explicitly in
380 #GNUNET_ATS_address_del_session(). */
383 /* determine when the address should come back to life */
384 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
385 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
389 publish_p2a_stat_update ();
394 * Reset address blocking time. Resets the exponential
395 * back-off timer for this address to zero. Called when
396 * an address was used to create a successful connection.
398 * @param address the address to reset the blocking timer
399 * @param session the session (can be NULL)
402 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
403 struct GNUNET_ATS_Session *session)
405 struct AddressInfo *ai;
408 memcmp (&GST_my_identity,
410 sizeof (struct GNUNET_PeerIdentity)))
411 return; /* our own, ignore! */
412 ai = find_ai (address, session);
418 /* address is in successful use, so it should not be blocked right now */
419 GNUNET_break (NULL == ai->unblock_task);
420 ai->back_off = GNUNET_TIME_UNIT_ZERO;
425 * Notify ATS about a new inbound @a address. The @a address in
426 * combination with the @a session must be new, but this function will
427 * perform a santiy check. If the @a address is indeed new, make it
430 * @param address the address
431 * @param session the session
432 * @param prop performance information
435 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
436 struct GNUNET_ATS_Session *session,
437 const struct GNUNET_ATS_Properties *prop)
439 struct GNUNET_ATS_AddressRecord *ar;
440 struct AddressInfo *ai;
443 memcmp (&GST_my_identity,
445 sizeof (struct GNUNET_PeerIdentity)))
446 return; /* our own, ignore! */
448 /* Sanity checks for a valid inbound address */
449 if (NULL == address->transport_name)
454 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
455 GNUNET_assert (GNUNET_YES ==
456 GNUNET_HELLO_address_check_option (address,
457 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
458 GNUNET_assert (NULL != session);
459 ai = find_ai (address, session);
462 /* This should only be called for new sessions, and thus
463 we should not already have the address */
467 /* Is indeed new, let's tell ATS */
468 LOG (GNUNET_ERROR_TYPE_DEBUG,
469 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
470 GNUNET_i2s (&address->peer),
471 GST_plugins_a2s (address),
473 GNUNET_ATS_print_network_type (prop->scope));
474 ar = GNUNET_ATS_address_add (GST_ats,
478 GNUNET_assert (NULL != ar);
479 ai = GNUNET_new (struct AddressInfo);
480 ai->address = GNUNET_HELLO_address_copy (address);
481 ai->session = session;
482 ai->properties = *prop;
484 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
487 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
488 publish_p2a_stat_update ();
493 * Notify ATS about the new address including the network this address is
494 * located in. The address must NOT be inbound and must be new to ATS.
496 * @param address the address
497 * @param prop performance information
500 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
501 const struct GNUNET_ATS_Properties *prop)
503 struct GNUNET_ATS_AddressRecord *ar;
504 struct AddressInfo *ai;
507 memcmp (&GST_my_identity,
509 sizeof (struct GNUNET_PeerIdentity)))
510 return; /* our own, ignore! */
511 /* validadte address */
512 if (NULL == address->transport_name)
517 GNUNET_assert (GNUNET_YES !=
518 GNUNET_HELLO_address_check_option (address,
519 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
520 ai = find_ai_no_session (address);
521 GNUNET_assert (NULL == ai);
522 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
524 /* address seems sane, let's tell ATS */
525 LOG (GNUNET_ERROR_TYPE_INFO,
526 "Notifying ATS about peer %s's new address `%s'\n",
527 GNUNET_i2s (&address->peer),
528 GST_plugins_a2s (address));
529 ar = GNUNET_ATS_address_add (GST_ats,
533 GNUNET_assert (NULL != ar);
534 ai = GNUNET_new (struct AddressInfo);
535 ai->address = GNUNET_HELLO_address_copy (address);
537 ai->properties = *prop;
538 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
541 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
542 publish_p2a_stat_update ();
547 * Notify ATS about a new @a session now existing for the given
548 * @a address. Essentially, an outbound @a address was used
549 * to establish a @a session. It is safe to call this function
550 * repeatedly for the same @a address and @a session pair.
552 * @param address the address
553 * @param session the session
556 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
557 struct GNUNET_ATS_Session *session)
559 struct AddressInfo *ai;
562 memcmp (&GST_my_identity,
564 sizeof (struct GNUNET_PeerIdentity)))
565 return; /* our own, ignore! */
566 ai = find_ai (address, NULL);
569 /* We may simply already be aware of the session, even if some
570 other part of the code could not tell if it just created a new
571 session or just got one recycled from the plugin; hence, we may
572 be called with "new" session even for an "old" session; in that
573 case, check that this is the case, but just ignore it. */
574 GNUNET_assert (NULL != (find_ai (address, session)));
577 GNUNET_assert (NULL == ai->session);
578 ai->session = session;
579 LOG (GNUNET_ERROR_TYPE_DEBUG,
580 "Telling ATS about new session for peer %s\n",
581 GNUNET_i2s (&address->peer));
582 /* Note that the address might currently be blocked; we only
583 tell ATS about the session if the address is currently not
584 blocked; otherwise, ATS will be told about the session on
587 GNUNET_ATS_address_add_session (ai->ar,
590 GNUNET_assert (NULL != ai->unblock_task);
595 * Release memory used by the given address data.
597 * @param ai the `struct AddressInfo`
600 destroy_ai (struct AddressInfo *ai)
602 GNUNET_assert (NULL == ai->session);
603 if (NULL != ai->unblock_task)
605 GNUNET_SCHEDULER_cancel (ai->unblock_task);
606 ai->unblock_task = NULL;
609 GNUNET_assert (GNUNET_YES ==
610 GNUNET_CONTAINER_multipeermap_remove (p2a,
613 LOG (GNUNET_ERROR_TYPE_DEBUG,
614 "Telling ATS to destroy address from peer %s\n",
615 GNUNET_i2s (&ai->address->peer));
618 GNUNET_ATS_address_destroy (ai->ar);
621 publish_p2a_stat_update ();
622 GNUNET_HELLO_address_free (ai->address);
628 * Notify ATS that the @a session (but not the @a address) of
629 * a given @a address is no longer relevant. (The @a session
630 * went down.) This function may be called even if for the
631 * respective outbound address #GST_ats_new_session() was
632 * never called and thus the pair is unknown to ATS. In this
633 * case, the call is simply ignored.
635 * @param address the address
636 * @param session the session
639 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
640 struct GNUNET_ATS_Session *session)
642 struct AddressInfo *ai;
645 memcmp (&GST_my_identity,
647 sizeof (struct GNUNET_PeerIdentity)))
648 return; /* our own, ignore! */
654 ai = find_ai (address,
658 /* We sometimes create sessions just for sending a PING,
659 and if those are destroyed they were never known to
660 ATS which means we end up here (however, in this
661 case, the address must be an outbound address). */
662 GNUNET_break (GNUNET_YES !=
663 GNUNET_HELLO_address_check_option (address,
664 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
667 GNUNET_assert (session == ai->session);
669 LOG (GNUNET_ERROR_TYPE_DEBUG,
670 "Telling ATS to destroy session %p from peer %s\n",
672 GNUNET_i2s (&address->peer));
673 if (GNUNET_YES == ai->expired)
675 /* last reason to keep this 'ai' around is now gone, the
676 session is dead as well, clean up */
679 /* Address expired but not blocked, and thus 'ar' was still
680 live because of the session; deleting just the session
681 will do for an inbound session, but for an outbound we
682 then also need to destroy the address with ATS. */
684 GNUNET_ATS_address_del_session (ai->ar,
687 GNUNET_ATS_address_destroy (ai->ar);
689 /* "ar" has been freed, regardless how the branch
690 above played out: it was either freed in
691 #GNUNET_ATS_address_del_session() because it was
692 incoming, or explicitly in
693 #GNUNET_ATS_address_del_session(). */
702 /* If ATS doesn't know about the address/session, this means
703 this address was blocked. */
705 GNUNET_HELLO_address_check_option (address,
706 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
708 /* This was a blocked inbound session, which now lost the
709 session. But inbound addresses are by themselves useless,
710 so we must forget about the address as well. */
714 /* Otherwise, we are done as we have set `ai->session` to NULL
715 already and ATS will simply not be told about the session when
716 the connection is unblocked and the outbound address becomes
717 available again. . */
721 /* This is the "simple" case where ATS knows about the session and
722 the address is neither blocked nor expired. Delete the session,
723 and if it was inbound, free the address as well. */
725 GNUNET_ATS_address_del_session (ai->ar,
728 /* This was an inbound address, the session is now gone, so we
729 need to also forget about the address itself. */
737 * Notify ATS about DV @a distance change to an @a address.
738 * Does nothing if the @a address is not known to us.
740 * @param address the address
741 * @param distance new distance value
744 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
747 struct AddressInfo *ai;
749 ai = find_ai_no_session (address);
752 /* We do not know about this address, do nothing. */
755 LOG (GNUNET_ERROR_TYPE_DEBUG,
756 "Updated distance for peer `%s' to %u\n",
757 GNUNET_i2s (&address->peer),
759 ai->properties.distance = distance;
760 /* Give manipulation its chance to change metrics */
761 GST_manipulation_manipulate_metrics (address,
764 /* Address may be blocked, only give ATS if address is
767 GNUNET_ATS_address_update (ai->ar,
773 * Notify ATS about @a delay changes to properties of an @a address.
774 * Does nothing if the @a address is not known to us.
776 * @param address the address
777 * @param delay new delay value
780 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
781 struct GNUNET_TIME_Relative delay)
783 struct AddressInfo *ai;
785 ai = find_ai_no_session (address);
788 /* We do not know about this address, do nothing. */
791 LOG (GNUNET_ERROR_TYPE_DEBUG,
792 "Updated latency for peer `%s' to %s\n",
793 GNUNET_i2s (&address->peer),
794 GNUNET_STRINGS_relative_time_to_string (delay,
796 ai->properties.delay = delay;
797 /* Give manipulation its chance to change metrics */
798 GST_manipulation_manipulate_metrics (address,
801 /* Address may be blocked, only give ATS if address is
804 GNUNET_ATS_address_update (ai->ar,
810 * Notify ATS about utilization changes to an @a address.
811 * Does nothing if the @a address is not known to us.
813 * @param address our information about the address
814 * @param bps_in new utilization inbound
815 * @param bps_out new utilization outbound
818 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
822 struct AddressInfo *ai;
824 ai = find_ai_no_session (address);
827 /* We do not know about this address, do nothing. */
830 LOG (GNUNET_ERROR_TYPE_DEBUG,
831 "Updating utilization for peer `%s' address %s: %u/%u\n",
832 GNUNET_i2s (&address->peer),
833 GST_plugins_a2s (address),
834 (unsigned int) bps_in,
835 (unsigned int) bps_out);
836 ai->properties.utilization_in = bps_in;
837 ai->properties.utilization_out = bps_out;
838 /* Give manipulation its chance to change metrics */
839 GST_manipulation_manipulate_metrics (address,
842 /* Address may be blocked, only give ATS if address is
845 GNUNET_ATS_address_update (ai->ar,
851 * Notify ATS that the address has expired and thus cannot
852 * be used any longer. This function must only be called
853 * if the corresponding session is already gone.
855 * @param address the address
858 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
860 struct AddressInfo *ai;
863 memcmp (&GST_my_identity,
865 sizeof (struct GNUNET_PeerIdentity)))
866 return; /* our own, ignore! */
867 LOG (GNUNET_ERROR_TYPE_DEBUG,
868 "Address %s of peer %s expired\n",
869 GST_plugins_a2s (address),
870 GNUNET_i2s (&address->peer));
871 ai = find_ai_no_session (address);
877 if (NULL != ai->session)
879 /* Got an active session, just remember the expiration
880 and act upon it when the session goes down. */
881 ai->expired = GNUNET_YES;
884 /* Address expired, no session, free resources */
890 * Initialize ATS subsystem.
895 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
900 * Release memory used by the given address data.
903 * @param key which peer is this about
904 * @param value the `struct AddressInfo`
905 * @return #GNUNET_OK (continue to iterate)
908 destroy_ai_cb (void *cls,
909 const struct GNUNET_PeerIdentity *key,
912 struct AddressInfo *ai = value;
920 * Shutdown ATS subsystem.
925 GNUNET_CONTAINER_multipeermap_iterate (p2a,
928 publish_p2a_stat_update ();
929 GNUNET_CONTAINER_multipeermap_destroy (p2a);
933 /* end of gnunet-service-transport_ats.c */