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.
204 * @param cls the `struct FindClosure`
205 * @param key which peer is this about
206 * @param value the `struct AddressInfo`
207 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
210 find_ai_no_session_cb (void *cls,
211 const struct GNUNET_PeerIdentity *key,
214 struct FindClosure *fc = cls;
215 struct AddressInfo *ai = value;
218 GNUNET_HELLO_address_cmp (fc->address,
229 * Find the address information struct for the
230 * given address (ignoring sessions)
232 * @param address address to look for
233 * @return NULL if this combination is unknown
235 static struct AddressInfo *
236 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
238 struct FindClosure fc;
240 fc.address = address;
243 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
245 &find_ai_no_session_cb,
252 * Test if ATS knows about this @a address and @a session.
254 * @param address the address
255 * @param session the session
256 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
259 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
260 struct Session *session)
262 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
267 * Test if ATS knows about this @a address.
269 * @param address the address
270 * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
273 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
275 return (NULL != find_ai_no_session (address)) ? GNUNET_YES : GNUNET_NO;
280 * The blocking time for an address has expired, allow ATS to
283 * @param cls the `struct AddressInfo` of the address to unblock
287 unblock_address (void *cls,
288 const struct GNUNET_SCHEDULER_TaskContext *tc)
290 struct AddressInfo *ai = cls;
292 ai->unblock_task = NULL;
293 LOG (GNUNET_ERROR_TYPE_DEBUG,
294 "Unblocking address %s of peer %s\n",
295 GST_plugins_a2s (ai->address),
296 GNUNET_i2s (&ai->address->peer));
297 ai->ar = GNUNET_ATS_address_add (GST_ats,
301 GNUNET_break (NULL != ai->ar);
303 publish_p2a_stat_update ();
308 * Temporarily block a valid address for use by ATS for address
309 * suggestions. This function should be called if an address was
310 * suggested by ATS but failed to perform (i.e. failure to establish a
311 * session or to exchange the PING/PONG).
313 * @param address the address to block
314 * @param session the session (can be NULL)
317 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
318 struct Session *session)
320 struct AddressInfo *ai;
322 ai = find_ai (address, session);
330 /* already blocked, how did it get used!? */
334 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
336 GNUNET_HELLO_address_check_option (address,
337 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
338 LOG (GNUNET_ERROR_TYPE_DEBUG,
339 "Removing address %s of peer %s from use (inbound died)\n",
340 GST_plugins_a2s (address),
341 GNUNET_i2s (&address->peer));
343 LOG (GNUNET_ERROR_TYPE_INFO,
344 "Blocking address %s of peer %s from use for %s\n",
345 GST_plugins_a2s (address),
346 GNUNET_i2s (&address->peer),
347 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
349 /* destroy session and address */
350 if ( (NULL == session) ||
352 GNUNET_ATS_address_del_session (ai->ar,
354 GNUNET_ATS_address_destroy (ai->ar);
357 /* determine when the address should come back to life */
358 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
359 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
363 publish_p2a_stat_update ();
368 * Reset address blocking time. Resets the exponential
369 * back-off timer for this address to zero. Done when
370 * an address was used to create a successful connection.
372 * @param address the address to reset the blocking timer
373 * @param session the session (can be NULL)
376 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
377 struct Session *session)
379 struct AddressInfo *ai;
381 ai = find_ai (address, session);
387 /* address is in successful use, so it should not be blocked right now */
388 GNUNET_break (NULL == ai->unblock_task);
389 ai->back_off = GNUNET_TIME_UNIT_ZERO;
394 * Notify ATS about the a new inbound address. We may already
395 * know the address (as this is called each time we receive
396 * a message from an inbound connection). If the address is
397 * indeed new, make it available to ATS.
399 * @param address the address
400 * @param session the session
401 * @param prop performance information
404 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
405 struct Session *session,
406 const struct GNUNET_ATS_Properties *prop)
408 struct GNUNET_ATS_AddressRecord *ar;
409 struct AddressInfo *ai;
411 /* valid new address, let ATS know! */
412 if (NULL == address->transport_name)
417 GNUNET_assert (GNUNET_YES ==
418 GNUNET_HELLO_address_check_option (address,
419 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
420 GNUNET_assert (NULL != session);
421 ai = find_ai (address, session);
424 /* This should only be called for new sessions, and thus
425 we should not already have the address */
429 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
430 LOG (GNUNET_ERROR_TYPE_DEBUG,
431 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
432 GNUNET_i2s (&address->peer),
433 (0 == address->address_length)
435 : GST_plugins_a2s (address),
437 GNUNET_ATS_print_network_type (prop->scope));
438 ar = GNUNET_ATS_address_add (GST_ats,
442 GNUNET_break (NULL != ar);
443 ai = GNUNET_new (struct AddressInfo);
444 ai->address = GNUNET_HELLO_address_copy (address);
445 ai->session = session;
446 ai->properties = *prop;
448 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
451 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
452 publish_p2a_stat_update ();
457 * Notify ATS about the new address including the network this address is
458 * located in. The address must NOT be inbound and must be new to ATS.
460 * @param address the address
461 * @param prop performance information
464 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
465 const struct GNUNET_ATS_Properties *prop)
467 struct GNUNET_ATS_AddressRecord *ar;
468 struct AddressInfo *ai;
470 /* valid new address, let ATS know! */
471 if (NULL == address->transport_name)
476 GNUNET_assert (GNUNET_YES !=
477 GNUNET_HELLO_address_check_option (address,
478 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
479 ai = find_ai_no_session (address);
480 GNUNET_assert (NULL == ai);
481 LOG (GNUNET_ERROR_TYPE_INFO,
482 "Notifying ATS about peer `%s''s new address `%s'\n",
483 GNUNET_i2s (&address->peer),
484 (0 == address->address_length)
486 : GST_plugins_a2s (address));
487 ar = GNUNET_ATS_address_add (GST_ats,
491 GNUNET_break (NULL != ar);
492 ai = GNUNET_new (struct AddressInfo);
493 ai->address = GNUNET_HELLO_address_copy (address);
495 ai->properties = *prop;
496 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
499 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
500 publish_p2a_stat_update ();
505 * Notify ATS about a new session now existing for the given
508 * @param address the address
509 * @param session the session
512 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
513 struct Session *session)
515 struct AddressInfo *ai;
517 ai = find_ai (address, NULL);
520 /* We may already be aware of the session, even if some other part
521 of the code could not tell if it just created a new session or
522 just got one recycled from the plugin; hence, we may be called
523 with "new" session even for an "old" session; in that case,
524 check that this is the case, but just ignore it. */
525 GNUNET_assert (NULL != (find_ai (address, session)));
528 GNUNET_break (NULL == ai->session);
529 ai->session = session;
530 LOG (GNUNET_ERROR_TYPE_DEBUG,
531 "Telling ATS about new session for peer %s\n",
532 GNUNET_i2s (&address->peer));
534 GNUNET_ATS_address_add_session (ai->ar,
540 * Notify ATS that the session (but not the address) of
541 * a given address is no longer relevant.
543 * @param address the address
544 * @param session the session
547 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
548 struct Session *session)
550 struct AddressInfo *ai;
557 ai = find_ai (address,
561 /* We sometimes create sessions just for sending a PING,
562 and if those are destroyed they were never known to
563 ATS which means we end up here (however, in this
564 case, the address must be an outbound address). */
565 GNUNET_break (GNUNET_YES !=
566 GNUNET_HELLO_address_check_option (address,
567 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
571 GNUNET_assert (session == ai->session);
573 LOG (GNUNET_ERROR_TYPE_DEBUG,
574 "Telling ATS to destroy session %p from peer %s\n",
576 GNUNET_i2s (&address->peer));
579 /* If ATS doesn't know about the address/session, and this was an
580 inbound session or one that expired, then we must forget about
581 the address as well. Otherwise, we are done as we have set
582 `ai->session` to NULL already. */
583 if ( (GNUNET_YES == ai->expired) ||
585 GNUNET_HELLO_address_check_option (address,
586 GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
587 GST_ats_expire_address (address);
591 GNUNET_ATS_address_del_session (ai->ar,
595 GST_ats_expire_address (address);
601 * Notify ATS about DV distance change to an address's.
603 * @param address the address
604 * @param distance new distance value
607 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
610 struct AddressInfo *ai;
612 ai = find_ai_no_session (address);
615 LOG (GNUNET_ERROR_TYPE_DEBUG,
616 "Updated distance for peer `%s' to %u\n",
617 GNUNET_i2s (&address->peer),
619 ai->properties.distance = distance;
620 GST_manipulation_manipulate_metrics (address,
624 GNUNET_ATS_address_update (ai->ar,
630 * Notify ATS about property changes to an address's properties.
632 * @param address the address
633 * @param delay new delay value
636 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
637 struct GNUNET_TIME_Relative delay)
639 struct AddressInfo *ai;
641 ai = find_ai_no_session (address);
644 LOG (GNUNET_ERROR_TYPE_DEBUG,
645 "Updated latency for peer `%s' to %s\n",
646 GNUNET_i2s (&address->peer),
647 GNUNET_STRINGS_relative_time_to_string (delay,
649 ai->properties.delay = delay;
650 GST_manipulation_manipulate_metrics (address,
654 GNUNET_ATS_address_update (ai->ar,
660 * Notify ATS about utilization changes to an address.
662 * @param address our information about the address
663 * @param bps_in new utilization inbound
664 * @param bps_out new utilization outbound
667 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
671 struct AddressInfo *ai;
673 ai = find_ai_no_session (address);
676 LOG (GNUNET_ERROR_TYPE_DEBUG,
677 "Updating utilization for peer `%s' address %s: %u/%u\n",
678 GNUNET_i2s (&address->peer),
679 GST_plugins_a2s (address),
680 (unsigned int) bps_in,
681 (unsigned int) bps_out);
682 ai->properties.utilization_in = bps_in;
683 ai->properties.utilization_out = bps_out;
684 GST_manipulation_manipulate_metrics (address,
688 GNUNET_ATS_address_update (ai->ar,
694 * Notify ATS that the address has expired and thus cannot
695 * be used any longer. This function must only be called
696 * if the corresponding session is already gone.
698 * @param address the address
701 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
703 struct AddressInfo *ai;
705 LOG (GNUNET_ERROR_TYPE_DEBUG,
706 "Address %s of peer %s expired\n",
707 GST_plugins_a2s (address),
708 GNUNET_i2s (&address->peer));
709 ai = find_ai_no_session (address);
715 if (NULL != ai->unblock_task)
717 GNUNET_SCHEDULER_cancel (ai->unblock_task);
718 ai->unblock_task = NULL;
721 if (NULL != ai->session)
723 ai->expired = GNUNET_YES;
726 GNUNET_ATS_address_destroy (ai->ar);
731 GNUNET_assert (GNUNET_YES ==
732 GNUNET_CONTAINER_multipeermap_remove (p2a,
735 LOG (GNUNET_ERROR_TYPE_DEBUG,
736 "Telling ATS to destroy address from peer %s\n",
737 GNUNET_i2s (&address->peer));
740 /* We usually should not have a session here when we
741 expire an address, but during shutdown a session
742 may be active while validation causes the address
743 to 'expire'. So clean up both if necessary. */
744 if ( (NULL == ai->session) ||
746 GNUNET_ATS_address_del_session (ai->ar,
748 GNUNET_ATS_address_destroy (ai->ar);
751 publish_p2a_stat_update ();
752 GNUNET_HELLO_address_free (ai->address);
758 * Initialize ATS subsystem.
763 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
768 * Release memory used by the given address data.
771 * @param key which peer is this about
772 * @param value the `struct AddressInfo`
773 * @return #GNUNET_OK (continue to iterate)
776 destroy_ai (void *cls,
777 const struct GNUNET_PeerIdentity *key,
780 struct AddressInfo *ai = value;
782 GNUNET_assert (GNUNET_YES ==
783 GNUNET_CONTAINER_multipeermap_remove (p2a,
786 if (NULL != ai->unblock_task)
788 GNUNET_SCHEDULER_cancel (ai->unblock_task);
789 ai->unblock_task = NULL;
794 GNUNET_ATS_address_destroy (ai->ar);
797 GNUNET_HELLO_address_free (ai->address);
804 * Shutdown ATS subsystem.
809 GNUNET_CONTAINER_multipeermap_iterate (p2a,
812 publish_p2a_stat_update ();
813 GNUNET_CONTAINER_multipeermap_destroy (p2a);
817 /* end of gnunet-service-transport_ats.c */