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.h
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"
32 #define LOG(kind,...) GNUNET_log_from(kind, "transport-ats", __VA_ARGS__)
36 * Information we track for each address known to ATS.
42 * The address (with peer identity).
44 struct GNUNET_HELLO_Address *address;
47 * Session (can be NULL)
49 struct Session *session;
52 * Record with ATS API for the address.
54 struct GNUNET_ATS_AddressRecord *ar;
57 * Performance properties of this address.
59 struct GNUNET_ATS_Properties properties;
62 * Time until when this address is blocked and should thus not be
63 * made available to ATS (@e ar should be NULL until this time).
64 * Used when transport determines that for some reason it
65 * (temporarily) cannot use an address, even though it has been
68 struct GNUNET_TIME_Absolute blocked;
71 * If an address is blocked as part of an exponential back-off,
72 * we track the current size of the backoff here.
74 struct GNUNET_TIME_Relative back_off;
77 * Task scheduled to unblock an ATS-blocked address at
78 * @e blocked time, or NULL if the address is not blocked
79 * (and thus @e ar is non-NULL).
81 struct GNUNET_SCHEDULER_Task *unblock_task;
84 * Set to #GNUNET_YES if the address has expired but we could
85 * not yet remove it because we still have a valid session.
93 * Map from peer identities to one or more `struct AddressInfo` values
96 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
99 * Number of blocked addresses.
101 static unsigned int num_blocked;
104 * Closure for #find_ai().
110 * Session to look for (only used if the address is inbound).
112 struct 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.
149 * @param cls the `struct FindClosure`
150 * @param key which peer is this about
151 * @param value the `struct AddressInfo`
152 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
155 find_ai_cb (void *cls,
156 const struct GNUNET_PeerIdentity *key,
159 struct FindClosure *fc = cls;
160 struct AddressInfo *ai = value;
163 GNUNET_HELLO_address_cmp (fc->address,
165 (fc->session == ai->session) )
170 GNUNET_assert ( (fc->session != ai->session) ||
171 (NULL == ai->session) );
177 * Find the address information struct for the
178 * given address and 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 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.
257 * @param address the address
258 * @param session the session
259 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
262 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
263 struct Session *session)
265 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
270 * Test if ATS knows about this @a address.
272 * @param address the address
273 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
276 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
278 struct AddressInfo *ai;
280 return (NULL != find_ai_no_session (address))
287 * The blocking time for an address has expired, allow ATS to
290 * @param cls the `struct AddressInfo` of the address to unblock
294 unblock_address (void *cls,
295 const struct GNUNET_SCHEDULER_TaskContext *tc)
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 Session *session)
327 struct AddressInfo *ai;
329 ai = find_ai (address, session);
337 /* already blocked, how did it get used!? */
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,
361 GNUNET_ATS_address_destroy (ai->ar);
364 /* determine when the address should come back to life */
365 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
366 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
370 publish_p2a_stat_update ();
375 * Reset address blocking time. Resets the exponential
376 * back-off timer for this address to zero. Done when
377 * an address was used to create a successful connection.
379 * @param address the address to reset the blocking timer
380 * @param session the session (can be NULL)
383 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
384 struct Session *session)
386 struct AddressInfo *ai;
388 ai = find_ai (address, session);
394 /* address is in successful use, so it should not be blocked right now */
395 GNUNET_break (NULL == ai->unblock_task);
396 ai->back_off = GNUNET_TIME_UNIT_ZERO;
401 * Notify ATS about the a new inbound address. We may already
402 * know the address (as this is called each time we receive
403 * a message from an inbound connection). If the address is
404 * indeed new, make it available to ATS.
406 * @param address the address
407 * @param session the session
408 * @param prop performance information
411 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
412 struct Session *session,
413 const struct GNUNET_ATS_Properties *prop)
415 struct GNUNET_ATS_AddressRecord *ar;
416 struct AddressInfo *ai;
418 /* valid new address, let ATS know! */
419 if (NULL == address->transport_name)
424 GNUNET_assert (GNUNET_YES ==
425 GNUNET_HELLO_address_check_option (address,
426 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
427 GNUNET_assert (NULL != session);
428 ai = find_ai (address, session);
431 /* This should only be called for new sessions, and thus
432 we should not already have the address */
436 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
437 LOG (GNUNET_ERROR_TYPE_DEBUG,
438 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
439 GNUNET_i2s (&address->peer),
440 (0 == address->address_length)
442 : GST_plugins_a2s (address),
444 GNUNET_ATS_print_network_type (prop->scope));
445 ar = GNUNET_ATS_address_add (GST_ats,
449 GNUNET_break (NULL != ar);
450 ai = GNUNET_new (struct AddressInfo);
451 ai->address = GNUNET_HELLO_address_copy (address);
452 ai->session = session;
453 ai->properties = *prop;
455 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
458 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
459 publish_p2a_stat_update ();
464 * Notify ATS about the new address including the network this address is
465 * located in. The address must NOT be inbound and must be new to ATS.
467 * @param address the address
468 * @param prop performance information
471 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
472 const struct GNUNET_ATS_Properties *prop)
474 struct GNUNET_ATS_AddressRecord *ar;
475 struct AddressInfo *ai;
477 /* valid new address, let ATS know! */
478 if (NULL == address->transport_name)
483 GNUNET_assert (GNUNET_YES !=
484 GNUNET_HELLO_address_check_option (address,
485 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
486 ai = find_ai_no_session (address);
487 GNUNET_assert (NULL == ai);
488 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
489 LOG (GNUNET_ERROR_TYPE_INFO,
490 "Notifying ATS about peer `%s''s new address `%s'\n",
491 GNUNET_i2s (&address->peer),
492 (0 == address->address_length)
494 : GST_plugins_a2s (address));
495 ar = GNUNET_ATS_address_add (GST_ats,
499 GNUNET_break (NULL != ar);
500 ai = GNUNET_new (struct AddressInfo);
501 ai->address = GNUNET_HELLO_address_copy (address);
503 ai->properties = *prop;
504 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
507 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
508 publish_p2a_stat_update ();
513 * Notify ATS about a new session now existing for the given
516 * @param address the address
517 * @param session the session
520 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
521 struct Session *session)
523 struct AddressInfo *ai;
525 ai = find_ai (address, NULL);
528 /* We may already be aware of the session, even if some other part
529 of the code could not tell if it just created a new session or
530 just got one recycled from the plugin; hence, we may be called
531 with "new" session even for an "old" session; in that case,
532 check that this is the case, but just ignore it. */
533 GNUNET_assert (NULL != (find_ai (address, session)));
536 GNUNET_break (NULL == ai->session);
537 ai->session = session;
538 LOG (GNUNET_ERROR_TYPE_DEBUG,
539 "Telling ATS about new session for peer %s\n",
540 GNUNET_i2s (&address->peer));
542 GNUNET_ATS_address_add_session (ai->ar,
548 * Release memory used by the given address data.
550 * @param ai the `struct AddressInfo`
553 destroy_ai (struct AddressInfo *ai)
555 GNUNET_assert (NULL == ai->session);
556 GNUNET_assert (NULL == ai->unblock_task);
557 GNUNET_assert (GNUNET_YES ==
558 GNUNET_CONTAINER_multipeermap_remove (p2a,
561 LOG (GNUNET_ERROR_TYPE_DEBUG,
562 "Telling ATS to destroy address from peer %s\n",
563 GNUNET_i2s (&ai->address->peer));
566 GNUNET_ATS_address_destroy (ai->ar);
569 publish_p2a_stat_update ();
570 GNUNET_HELLO_address_free (ai->address);
576 * Notify ATS that the session (but not the address) of
577 * a given address is no longer relevant.
579 * @param address the address
580 * @param session the session
583 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
584 struct Session *session)
586 struct AddressInfo *ai;
593 ai = find_ai (address,
597 /* We sometimes create sessions just for sending a PING,
598 and if those are destroyed they were never known to
599 ATS which means we end up here (however, in this
600 case, the address must be an outbound address). */
601 GNUNET_break (GNUNET_YES !=
602 GNUNET_HELLO_address_check_option (address,
603 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
607 GNUNET_assert (session == ai->session);
609 LOG (GNUNET_ERROR_TYPE_DEBUG,
610 "Telling ATS to destroy session %p from peer %s\n",
612 GNUNET_i2s (&address->peer));
613 if (GNUNET_YES == ai->expired)
615 /* last reason to keep this 'ai' around is now gone, the
616 session is dead as well, clean up */
619 GNUNET_break (GNUNET_YES ==
620 GNUNET_ATS_address_del_session (ai->ar,
628 /* If ATS doesn't know about the address/session, and this was an
629 inbound session, so we must forget about the address as well.
630 Otherwise, we are done as we have set `ai->session` to NULL
633 GNUNET_HELLO_address_check_option (address,
634 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
635 GST_ats_expire_address (address);
639 GNUNET_ATS_address_del_session (ai->ar,
643 GST_ats_expire_address (address);
649 * Notify ATS about DV distance change to an address's.
651 * @param address the address
652 * @param distance new distance value
655 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
658 struct AddressInfo *ai;
660 ai = find_ai_no_session (address);
663 LOG (GNUNET_ERROR_TYPE_DEBUG,
664 "Updated distance for peer `%s' to %u\n",
665 GNUNET_i2s (&address->peer),
667 ai->properties.distance = distance;
668 GST_manipulation_manipulate_metrics (address,
672 GNUNET_ATS_address_update (ai->ar,
678 * Notify ATS about property changes to an address's properties.
680 * @param address the address
681 * @param delay new delay value
684 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
685 struct GNUNET_TIME_Relative delay)
687 struct AddressInfo *ai;
689 ai = find_ai_no_session (address);
692 LOG (GNUNET_ERROR_TYPE_DEBUG,
693 "Updated latency for peer `%s' to %s\n",
694 GNUNET_i2s (&address->peer),
695 GNUNET_STRINGS_relative_time_to_string (delay,
697 ai->properties.delay = delay;
698 GST_manipulation_manipulate_metrics (address,
702 GNUNET_ATS_address_update (ai->ar,
708 * Notify ATS about utilization changes to an address.
710 * @param address our information about the address
711 * @param bps_in new utilization inbound
712 * @param bps_out new utilization outbound
715 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
719 struct AddressInfo *ai;
721 ai = find_ai_no_session (address);
724 LOG (GNUNET_ERROR_TYPE_DEBUG,
725 "Updating utilization for peer `%s' address %s: %u/%u\n",
726 GNUNET_i2s (&address->peer),
727 GST_plugins_a2s (address),
728 (unsigned int) bps_in,
729 (unsigned int) bps_out);
730 ai->properties.utilization_in = bps_in;
731 ai->properties.utilization_out = bps_out;
732 GST_manipulation_manipulate_metrics (address,
736 GNUNET_ATS_address_update (ai->ar,
742 * Notify ATS that the address has expired and thus cannot
743 * be used any longer. This function must only be called
744 * if the corresponding session is already gone.
746 * @param address the address
749 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
751 struct AddressInfo *ai;
753 LOG (GNUNET_ERROR_TYPE_DEBUG,
754 "Address %s of peer %s expired\n",
755 GST_plugins_a2s (address),
756 GNUNET_i2s (&address->peer));
757 ai = find_ai_no_session (address);
763 if (NULL != ai->unblock_task)
765 GNUNET_SCHEDULER_cancel (ai->unblock_task);
766 ai->unblock_task = NULL;
769 if (NULL != ai->session)
771 ai->expired = GNUNET_YES;
779 * Initialize ATS subsystem.
784 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
789 * Release memory used by the given address data.
792 * @param key which peer is this about
793 * @param value the `struct AddressInfo`
794 * @return #GNUNET_OK (continue to iterate)
797 destroy_ai_cb (void *cls,
798 const struct GNUNET_PeerIdentity *key,
801 struct AddressInfo *ai = value;
803 if (NULL != ai->unblock_task)
805 GNUNET_SCHEDULER_cancel (ai->unblock_task);
806 ai->unblock_task = NULL;
815 * Shutdown ATS subsystem.
820 GNUNET_CONTAINER_multipeermap_iterate (p2a,
823 publish_p2a_stat_update ();
824 GNUNET_CONTAINER_multipeermap_destroy (p2a);
828 /* end of gnunet-service-transport_ats.c */