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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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.
44 * The address (with peer identity). Must never change
45 * while this struct is in the #p2a map.
47 struct GNUNET_HELLO_Address *address;
50 * Session (can be NULL)
52 struct GNUNET_ATS_Session *session;
55 * Record with ATS API for the address.
57 struct GNUNET_ATS_AddressRecord *ar;
60 * Performance properties of this address.
62 struct GNUNET_ATS_Properties properties;
65 * Time until when this address is blocked and should thus not be
66 * made available to ATS (@e ar should be NULL until this time).
67 * Used when transport determines that for some reason it
68 * (temporarily) cannot use an address, even though it has been
71 struct GNUNET_TIME_Absolute blocked;
74 * If an address is blocked as part of an exponential back-off,
75 * we track the current size of the backoff here.
77 struct GNUNET_TIME_Relative back_off;
80 * Task scheduled to unblock an ATS-blocked address at
81 * @e blocked time, or NULL if the address is not blocked
82 * (and thus @e ar is non-NULL).
84 struct GNUNET_SCHEDULER_Task *unblock_task;
87 * Set to #GNUNET_YES if the address has expired but we could
88 * 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().
112 * Session to look for (only used if the address is inbound).
114 struct GNUNET_ATS_Session *session;
117 * Address to look for.
119 const struct GNUNET_HELLO_Address *address;
122 * Where to store the result.
124 struct AddressInfo *ret;
129 * Provide an update on the `p2a` map size to statistics.
130 * This function should be called whenever the `p2a` map
134 publish_p2a_stat_update ()
136 GNUNET_STATISTICS_set (GST_stats,
137 gettext_noop ("# Addresses given to ATS"),
138 GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
140 GNUNET_STATISTICS_set (GST_stats,
141 "# blocked addresses",
148 * Find matching address info. Both the address and the session
149 * must match; note that expired addresses are still found (as
150 * the session kind-of keeps those alive).
152 * @param cls the `struct FindClosure`
153 * @param key which peer is this about
154 * @param value the `struct AddressInfo`
155 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
158 find_ai_cb (void *cls,
159 const struct GNUNET_PeerIdentity *key,
162 struct FindClosure *fc = cls;
163 struct AddressInfo *ai = value;
166 GNUNET_HELLO_address_cmp (fc->address,
168 (fc->session == ai->session))
178 * Find the address information struct for the
179 * given @a address and @a session.
181 * @param address address to look for
182 * @param session session to match for inbound connections
183 * @return NULL if this combination is unknown
185 static struct AddressInfo *
186 find_ai (const struct GNUNET_HELLO_Address *address,
187 struct GNUNET_ATS_Session *session)
189 struct FindClosure fc;
191 fc.address = address;
192 fc.session = session;
194 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
203 * Find matching address info, ignoring sessions and expired
206 * @param cls the `struct FindClosure`
207 * @param key which peer is this about
208 * @param value the `struct AddressInfo`
209 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
212 find_ai_no_session_cb (void *cls,
213 const struct GNUNET_PeerIdentity *key,
216 struct FindClosure *fc = cls;
217 struct AddressInfo *ai = value;
220 return GNUNET_YES; /* expired do not count here */
222 GNUNET_HELLO_address_cmp (fc->address,
233 * Find the address information struct for the
234 * given address (ignoring sessions)
236 * @param address address to look for
237 * @return NULL if this combination is unknown
239 static struct AddressInfo *
240 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
242 struct FindClosure fc;
244 fc.address = address;
247 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
249 &find_ai_no_session_cb,
256 * Test if ATS knows about this @a address and @a session.
257 * Note that even if the address is expired, we return
258 * #GNUNET_YES if the respective session matches.
260 * @param address the address
261 * @param session the session
262 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
265 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
266 struct GNUNET_ATS_Session *session)
268 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
273 * Test if ATS knows about this @a address. Note that
274 * expired addresses do not count.
276 * @param address the address
277 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
280 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
282 return (NULL != find_ai_no_session (address))
289 * The blocking time for an address has expired, allow ATS to
292 * @param cls the `struct AddressInfo` of the address to unblock
295 unblock_address (void *cls)
297 struct AddressInfo *ai = cls;
299 ai->unblock_task = NULL;
300 LOG (GNUNET_ERROR_TYPE_DEBUG,
301 "Unblocking address %s of peer %s\n",
302 GST_plugins_a2s (ai->address),
303 GNUNET_i2s (&ai->address->peer));
304 ai->ar = GNUNET_ATS_address_add (GST_ats,
308 GNUNET_break (NULL != ai->ar);
310 publish_p2a_stat_update ();
315 * Temporarily block a valid address for use by ATS for address
316 * suggestions. This function should be called if an address was
317 * suggested by ATS but failed to perform (i.e. failure to establish a
318 * session or to exchange the PING/PONG).
320 * @param address the address to block
321 * @param session the session (can be NULL)
324 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
325 struct GNUNET_ATS_Session *session)
327 struct AddressInfo *ai;
330 memcmp (&GST_my_identity,
332 sizeof(struct GNUNET_PeerIdentity)))
333 return; /* our own, ignore! */
334 ai = find_ai (address,
336 if ((NULL == ai)||(NULL == ai->ar))
338 /* The address is already gone/blocked, this can happen during a blacklist
342 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
344 GNUNET_HELLO_address_check_option (address,
345 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
346 LOG (GNUNET_ERROR_TYPE_DEBUG,
347 "Removing address %s of peer %s from use (inbound died)\n",
348 GST_plugins_a2s (address),
349 GNUNET_i2s (&address->peer));
351 LOG (GNUNET_ERROR_TYPE_INFO,
352 "Blocking address %s of peer %s from use for %s\n",
353 GST_plugins_a2s (address),
354 GNUNET_i2s (&address->peer),
355 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
357 /* destroy session and address */
358 if ((NULL == session) ||
360 GNUNET_ATS_address_del_session (ai->ar,
363 GNUNET_ATS_address_destroy (ai->ar);
365 /* "ar" has been freed, regardless how the branch
366 above played out: it was either freed in
367 #GNUNET_ATS_address_del_session() because it was
368 incoming, or explicitly in
369 #GNUNET_ATS_address_del_session(). */
372 /* determine when the address should come back to life */
373 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
374 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
378 publish_p2a_stat_update ();
383 * Reset address blocking time. Resets the exponential
384 * back-off timer for this address to zero. Called when
385 * an address was used to create a successful connection.
387 * @param address the address to reset the blocking timer
388 * @param session the session (can be NULL)
391 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
392 struct GNUNET_ATS_Session *session)
394 struct AddressInfo *ai;
397 memcmp (&GST_my_identity,
399 sizeof(struct GNUNET_PeerIdentity)))
400 return; /* our own, ignore! */
401 ai = find_ai (address, session);
407 /* address is in successful use, so it should not be blocked right now */
408 GNUNET_break (NULL == ai->unblock_task);
409 ai->back_off = GNUNET_TIME_UNIT_ZERO;
414 * Notify ATS about a new inbound @a address. The @a address in
415 * combination with the @a session must be new, but this function will
416 * perform a santiy check. If the @a address is indeed new, make it
419 * @param address the address
420 * @param session the session
421 * @param prop performance information
424 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
425 struct GNUNET_ATS_Session *session,
426 const struct GNUNET_ATS_Properties *prop)
428 struct GNUNET_ATS_AddressRecord *ar;
429 struct AddressInfo *ai;
432 memcmp (&GST_my_identity,
434 sizeof(struct GNUNET_PeerIdentity)))
435 return; /* our own, ignore! */
437 /* Sanity checks for a valid inbound address */
438 if (NULL == address->transport_name)
443 GNUNET_break (GNUNET_NT_UNSPECIFIED != prop->scope);
444 GNUNET_assert (GNUNET_YES ==
445 GNUNET_HELLO_address_check_option (address,
446 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
447 GNUNET_assert (NULL != session);
448 ai = find_ai (address, session);
451 /* This should only be called for new sessions, and thus
452 we should not already have the address */
456 /* Is indeed new, let's tell ATS */
457 LOG (GNUNET_ERROR_TYPE_DEBUG,
458 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
459 GNUNET_i2s (&address->peer),
460 GST_plugins_a2s (address),
462 GNUNET_NT_to_string (prop->scope));
463 ar = GNUNET_ATS_address_add (GST_ats,
467 GNUNET_assert (NULL != ar);
468 ai = GNUNET_new (struct AddressInfo);
469 ai->address = GNUNET_HELLO_address_copy (address);
470 ai->session = session;
471 ai->properties = *prop;
473 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
476 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
477 publish_p2a_stat_update ();
482 * Notify ATS about the new address including the network this address is
483 * located in. The address must NOT be inbound and must be new to ATS.
485 * @param address the address
486 * @param prop performance information
489 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
490 const struct GNUNET_ATS_Properties *prop)
492 struct GNUNET_ATS_AddressRecord *ar;
493 struct AddressInfo *ai;
496 memcmp (&GST_my_identity,
498 sizeof(struct GNUNET_PeerIdentity)))
499 return; /* our own, ignore! */
500 /* validadte address */
501 if (NULL == address->transport_name)
506 GNUNET_assert (GNUNET_YES !=
507 GNUNET_HELLO_address_check_option (address,
508 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
509 ai = find_ai_no_session (address);
510 GNUNET_assert (NULL == ai);
511 GNUNET_break (GNUNET_NT_UNSPECIFIED != prop->scope);
513 /* address seems sane, let's tell ATS */
514 LOG (GNUNET_ERROR_TYPE_INFO,
515 "Notifying ATS about peer %s's new address `%s'\n",
516 GNUNET_i2s (&address->peer),
517 GST_plugins_a2s (address));
518 ar = GNUNET_ATS_address_add (GST_ats,
522 GNUNET_assert (NULL != ar);
523 ai = GNUNET_new (struct AddressInfo);
524 ai->address = GNUNET_HELLO_address_copy (address);
526 ai->properties = *prop;
527 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
530 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
531 publish_p2a_stat_update ();
536 * Notify ATS about a new @a session now existing for the given
537 * @a address. Essentially, an outbound @a address was used
538 * to establish a @a session. It is safe to call this function
539 * repeatedly for the same @a address and @a session pair.
541 * @param address the address
542 * @param session the session
545 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
546 struct GNUNET_ATS_Session *session)
548 struct AddressInfo *ai;
551 memcmp (&GST_my_identity,
553 sizeof(struct GNUNET_PeerIdentity)))
554 return; /* our own, ignore! */
555 ai = find_ai (address, NULL);
558 /* We may simply already be aware of the session, even if some
559 other part of the code could not tell if it just created a new
560 session or just got one recycled from the plugin; hence, we may
561 be called with "new" session even for an "old" session; in that
562 case, check that this is the case, but just ignore it. */
563 GNUNET_assert (NULL != (find_ai (address, session)));
566 GNUNET_assert (NULL == ai->session);
567 ai->session = session;
568 LOG (GNUNET_ERROR_TYPE_DEBUG,
569 "Telling ATS about new session for peer %s\n",
570 GNUNET_i2s (&address->peer));
571 /* Note that the address might currently be blocked; we only
572 tell ATS about the session if the address is currently not
573 blocked; otherwise, ATS will be told about the session on
576 GNUNET_ATS_address_add_session (ai->ar,
579 GNUNET_assert (NULL != ai->unblock_task);
584 * Release memory used by the given address data.
586 * @param ai the `struct AddressInfo`
589 destroy_ai (struct AddressInfo *ai)
591 GNUNET_assert (NULL == ai->session);
592 if (NULL != ai->unblock_task)
594 GNUNET_SCHEDULER_cancel (ai->unblock_task);
595 ai->unblock_task = NULL;
598 GNUNET_assert (GNUNET_YES ==
599 GNUNET_CONTAINER_multipeermap_remove (p2a,
602 LOG (GNUNET_ERROR_TYPE_DEBUG,
603 "Telling ATS to destroy address from peer %s\n",
604 GNUNET_i2s (&ai->address->peer));
607 GNUNET_ATS_address_destroy (ai->ar);
610 publish_p2a_stat_update ();
611 GNUNET_HELLO_address_free (ai->address);
617 * Notify ATS that the @a session (but not the @a address) of
618 * a given @a address is no longer relevant. (The @a session
619 * went down.) This function may be called even if for the
620 * respective outbound address #GST_ats_new_session() was
621 * never called and thus the pair is unknown to ATS. In this
622 * case, the call is simply ignored.
624 * @param address the address
625 * @param session the session
628 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
629 struct GNUNET_ATS_Session *session)
631 struct AddressInfo *ai;
634 memcmp (&GST_my_identity,
636 sizeof(struct GNUNET_PeerIdentity)))
637 return; /* our own, ignore! */
643 ai = find_ai (address,
647 /* We sometimes create sessions just for sending a PING,
648 and if those are destroyed they were never known to
649 ATS which means we end up here (however, in this
650 case, the address must be an outbound address). */
651 GNUNET_break (GNUNET_YES !=
652 GNUNET_HELLO_address_check_option (address,
653 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
656 GNUNET_assert (session == ai->session);
658 LOG (GNUNET_ERROR_TYPE_DEBUG,
659 "Telling ATS to destroy session %p from peer %s\n",
661 GNUNET_i2s (&address->peer));
662 if (GNUNET_YES == ai->expired)
664 /* last reason to keep this 'ai' around is now gone, the
665 session is dead as well, clean up */
668 /* Address expired but not blocked, and thus 'ar' was still
669 live because of the session; deleting just the session
670 will do for an inbound session, but for an outbound we
671 then also need to destroy the address with ATS. */
673 GNUNET_ATS_address_del_session (ai->ar,
676 GNUNET_ATS_address_destroy (ai->ar);
678 /* "ar" has been freed, regardless how the branch
679 above played out: it was either freed in
680 #GNUNET_ATS_address_del_session() because it was
681 incoming, or explicitly in
682 #GNUNET_ATS_address_del_session(). */
691 /* If ATS doesn't know about the address/session, this means
692 this address was blocked. */
694 GNUNET_HELLO_address_check_option (address,
695 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
697 /* This was a blocked inbound session, which now lost the
698 session. But inbound addresses are by themselves useless,
699 so we must forget about the address as well. */
703 /* Otherwise, we are done as we have set `ai->session` to NULL
704 already and ATS will simply not be told about the session when
705 the connection is unblocked and the outbound address becomes
706 available again. . */
710 /* This is the "simple" case where ATS knows about the session and
711 the address is neither blocked nor expired. Delete the session,
712 and if it was inbound, free the address as well. */
714 GNUNET_ATS_address_del_session (ai->ar,
717 /* This was an inbound address, the session is now gone, so we
718 need to also forget about the address itself. */
726 * Notify ATS about DV @a distance change to an @a address.
727 * Does nothing if the @a address is not known to us.
729 * @param address the address
730 * @param distance new distance value
733 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
736 struct AddressInfo *ai;
738 ai = find_ai_no_session (address);
741 /* We do not know about this address, do nothing. */
744 LOG (GNUNET_ERROR_TYPE_DEBUG,
745 "Updated distance for peer `%s' to %u\n",
746 GNUNET_i2s (&address->peer),
748 ai->properties.distance = distance;
749 /* Give manipulation its chance to change metrics */
750 GST_manipulation_manipulate_metrics (address,
753 /* Address may be blocked, only give ATS if address is
756 GNUNET_ATS_address_update (ai->ar,
762 * Notify ATS about @a delay changes to properties of an @a address.
763 * Does nothing if the @a address is not known to us.
765 * @param address the address
766 * @param delay new delay value
769 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
770 struct GNUNET_TIME_Relative delay)
772 struct AddressInfo *ai;
774 ai = find_ai_no_session (address);
777 /* We do not know about this address, do nothing. */
780 LOG (GNUNET_ERROR_TYPE_DEBUG,
781 "Updated latency for peer `%s' to %s\n",
782 GNUNET_i2s (&address->peer),
783 GNUNET_STRINGS_relative_time_to_string (delay,
785 ai->properties.delay = delay;
786 /* Give manipulation its chance to change metrics */
787 GST_manipulation_manipulate_metrics (address,
790 /* Address may be blocked, only give ATS if address is
793 GNUNET_ATS_address_update (ai->ar,
799 * Notify ATS about utilization changes to an @a address.
800 * Does nothing if the @a address is not known to us.
802 * @param address our information about the address
803 * @param bps_in new utilization inbound
804 * @param bps_out new utilization outbound
807 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
811 struct AddressInfo *ai;
813 ai = find_ai_no_session (address);
816 /* We do not know about this address, do nothing. */
819 LOG (GNUNET_ERROR_TYPE_DEBUG,
820 "Updating utilization for peer `%s' address %s: %u/%u\n",
821 GNUNET_i2s (&address->peer),
822 GST_plugins_a2s (address),
823 (unsigned int) bps_in,
824 (unsigned int) bps_out);
825 ai->properties.utilization_in = bps_in;
826 ai->properties.utilization_out = bps_out;
827 /* Give manipulation its chance to change metrics */
828 GST_manipulation_manipulate_metrics (address,
831 /* Address may be blocked, only give ATS if address is
834 GNUNET_ATS_address_update (ai->ar,
840 * Notify ATS that the address has expired and thus cannot
841 * be used any longer. This function must only be called
842 * if the corresponding session is already gone.
844 * @param address the address
847 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
849 struct AddressInfo *ai;
852 memcmp (&GST_my_identity,
854 sizeof(struct GNUNET_PeerIdentity)))
855 return; /* our own, ignore! */
856 LOG (GNUNET_ERROR_TYPE_DEBUG,
857 "Address %s of peer %s expired\n",
858 GST_plugins_a2s (address),
859 GNUNET_i2s (&address->peer));
860 ai = find_ai_no_session (address);
866 if (NULL != ai->session)
868 /* Got an active session, just remember the expiration
869 and act upon it when the session goes down. */
870 ai->expired = GNUNET_YES;
873 /* Address expired, no session, free resources */
879 * Initialize ATS subsystem.
884 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
889 * Release memory used by the given address data.
892 * @param key which peer is this about
893 * @param value the `struct AddressInfo`
894 * @return #GNUNET_OK (continue to iterate)
897 destroy_ai_cb (void *cls,
898 const struct GNUNET_PeerIdentity *key,
901 struct AddressInfo *ai = value;
909 * Shutdown ATS subsystem.
914 GNUNET_CONTAINER_multipeermap_iterate (p2a,
917 publish_p2a_stat_update ();
918 GNUNET_CONTAINER_multipeermap_destroy (p2a);
922 /* end of gnunet-service-transport_ats.c */