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 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
482 LOG (GNUNET_ERROR_TYPE_INFO,
483 "Notifying ATS about peer `%s''s new address `%s'\n",
484 GNUNET_i2s (&address->peer),
485 (0 == address->address_length)
487 : GST_plugins_a2s (address));
488 ar = GNUNET_ATS_address_add (GST_ats,
492 GNUNET_break (NULL != ar);
493 ai = GNUNET_new (struct AddressInfo);
494 ai->address = GNUNET_HELLO_address_copy (address);
496 ai->properties = *prop;
497 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
500 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
501 publish_p2a_stat_update ();
506 * Notify ATS about a new session now existing for the given
509 * @param address the address
510 * @param session the session
513 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
514 struct Session *session)
516 struct AddressInfo *ai;
518 ai = find_ai (address, NULL);
521 /* We may already be aware of the session, even if some other part
522 of the code could not tell if it just created a new session or
523 just got one recycled from the plugin; hence, we may be called
524 with "new" session even for an "old" session; in that case,
525 check that this is the case, but just ignore it. */
526 GNUNET_assert (NULL != (find_ai (address, session)));
529 GNUNET_break (NULL == ai->session);
530 ai->session = session;
531 LOG (GNUNET_ERROR_TYPE_DEBUG,
532 "Telling ATS about new session for peer %s\n",
533 GNUNET_i2s (&address->peer));
535 GNUNET_ATS_address_add_session (ai->ar,
541 * Notify ATS that the session (but not the address) of
542 * a given address is no longer relevant.
544 * @param address the address
545 * @param session the session
548 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
549 struct Session *session)
551 struct AddressInfo *ai;
558 ai = find_ai (address,
562 /* We sometimes create sessions just for sending a PING,
563 and if those are destroyed they were never known to
564 ATS which means we end up here (however, in this
565 case, the address must be an outbound address). */
566 GNUNET_break (GNUNET_YES !=
567 GNUNET_HELLO_address_check_option (address,
568 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
572 GNUNET_assert (session == ai->session);
574 LOG (GNUNET_ERROR_TYPE_DEBUG,
575 "Telling ATS to destroy session %p from peer %s\n",
577 GNUNET_i2s (&address->peer));
580 /* If ATS doesn't know about the address/session, and this was an
581 inbound session or one that expired, then we must forget about
582 the address as well. Otherwise, we are done as we have set
583 `ai->session` to NULL already. */
584 if ( (GNUNET_YES == ai->expired) ||
586 GNUNET_HELLO_address_check_option (address,
587 GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
588 GST_ats_expire_address (address);
592 GNUNET_ATS_address_del_session (ai->ar,
596 GST_ats_expire_address (address);
602 * Notify ATS about DV distance change to an address's.
604 * @param address the address
605 * @param distance new distance value
608 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
611 struct AddressInfo *ai;
613 ai = find_ai_no_session (address);
616 LOG (GNUNET_ERROR_TYPE_DEBUG,
617 "Updated distance for peer `%s' to %u\n",
618 GNUNET_i2s (&address->peer),
620 ai->properties.distance = distance;
621 GST_manipulation_manipulate_metrics (address,
625 GNUNET_ATS_address_update (ai->ar,
631 * Notify ATS about property changes to an address's properties.
633 * @param address the address
634 * @param delay new delay value
637 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
638 struct GNUNET_TIME_Relative delay)
640 struct AddressInfo *ai;
642 ai = find_ai_no_session (address);
645 LOG (GNUNET_ERROR_TYPE_DEBUG,
646 "Updated latency for peer `%s' to %s\n",
647 GNUNET_i2s (&address->peer),
648 GNUNET_STRINGS_relative_time_to_string (delay,
650 ai->properties.delay = delay;
651 GST_manipulation_manipulate_metrics (address,
655 GNUNET_ATS_address_update (ai->ar,
661 * Notify ATS about utilization changes to an address.
663 * @param address our information about the address
664 * @param bps_in new utilization inbound
665 * @param bps_out new utilization outbound
668 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
672 struct AddressInfo *ai;
674 ai = find_ai_no_session (address);
677 LOG (GNUNET_ERROR_TYPE_DEBUG,
678 "Updating utilization for peer `%s' address %s: %u/%u\n",
679 GNUNET_i2s (&address->peer),
680 GST_plugins_a2s (address),
681 (unsigned int) bps_in,
682 (unsigned int) bps_out);
683 ai->properties.utilization_in = bps_in;
684 ai->properties.utilization_out = bps_out;
685 GST_manipulation_manipulate_metrics (address,
689 GNUNET_ATS_address_update (ai->ar,
695 * Notify ATS that the address has expired and thus cannot
696 * be used any longer. This function must only be called
697 * if the corresponding session is already gone.
699 * @param address the address
702 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
704 struct AddressInfo *ai;
706 LOG (GNUNET_ERROR_TYPE_DEBUG,
707 "Address %s of peer %s expired\n",
708 GST_plugins_a2s (address),
709 GNUNET_i2s (&address->peer));
710 ai = find_ai_no_session (address);
716 if (NULL != ai->unblock_task)
718 GNUNET_SCHEDULER_cancel (ai->unblock_task);
719 ai->unblock_task = NULL;
722 if (NULL != ai->session)
724 ai->expired = GNUNET_YES;
727 GNUNET_ATS_address_destroy (ai->ar);
732 GNUNET_assert (GNUNET_YES ==
733 GNUNET_CONTAINER_multipeermap_remove (p2a,
736 LOG (GNUNET_ERROR_TYPE_DEBUG,
737 "Telling ATS to destroy address from peer %s\n",
738 GNUNET_i2s (&address->peer));
741 /* We usually should not have a session here when we
742 expire an address, but during shutdown a session
743 may be active while validation causes the address
744 to 'expire'. So clean up both if necessary. */
745 if ( (NULL == ai->session) ||
747 GNUNET_ATS_address_del_session (ai->ar,
749 GNUNET_ATS_address_destroy (ai->ar);
752 publish_p2a_stat_update ();
753 GNUNET_HELLO_address_free (ai->address);
759 * Initialize ATS subsystem.
764 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
769 * Release memory used by the given address data.
772 * @param key which peer is this about
773 * @param value the `struct AddressInfo`
774 * @return #GNUNET_OK (continue to iterate)
777 destroy_ai (void *cls,
778 const struct GNUNET_PeerIdentity *key,
781 struct AddressInfo *ai = value;
783 GNUNET_assert (GNUNET_YES ==
784 GNUNET_CONTAINER_multipeermap_remove (p2a,
787 if (NULL != ai->unblock_task)
789 GNUNET_SCHEDULER_cancel (ai->unblock_task);
790 ai->unblock_task = NULL;
795 GNUNET_ATS_address_destroy (ai->ar);
798 GNUNET_HELLO_address_free (ai->address);
805 * Shutdown ATS subsystem.
810 GNUNET_CONTAINER_multipeermap_iterate (p2a,
813 publish_p2a_stat_update ();
814 GNUNET_CONTAINER_multipeermap_destroy (p2a);
818 /* end of gnunet-service-transport_ats.c */