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 address.
254 * @param address the address
255 * @param session the session
256 * @return #GNUNET_YES if 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 * The blocking time for an address has expired, allow ATS to
270 * @param cls the `struct AddressInfo` of the address to unblock
274 unblock_address (void *cls,
275 const struct GNUNET_SCHEDULER_TaskContext *tc)
277 struct AddressInfo *ai = cls;
279 ai->unblock_task = NULL;
280 LOG (GNUNET_ERROR_TYPE_DEBUG,
281 "Unblocking address %s of peer %s\n",
282 GST_plugins_a2s (ai->address),
283 GNUNET_i2s (&ai->address->peer));
284 ai->ar = GNUNET_ATS_address_add (GST_ats,
288 GNUNET_break (NULL != ai->ar);
290 publish_p2a_stat_update ();
295 * Temporarily block a valid address for use by ATS for address
296 * suggestions. This function should be called if an address was
297 * suggested by ATS but failed to perform (i.e. failure to establish a
298 * session or to exchange the PING/PONG).
300 * @param address the address to block
301 * @param session the session (can be NULL)
304 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
305 struct Session *session)
307 struct AddressInfo *ai;
309 ai = find_ai (address, session);
317 /* already blocked, how did it get used!? */
321 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
323 GNUNET_HELLO_address_check_option (address,
324 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
325 LOG (GNUNET_ERROR_TYPE_DEBUG,
326 "Removing address %s of peer %s from use (inbound died)\n",
327 GST_plugins_a2s (address),
328 GNUNET_i2s (&address->peer));
330 LOG (GNUNET_ERROR_TYPE_INFO,
331 "Blocking address %s of peer %s from use for %s\n",
332 GST_plugins_a2s (address),
333 GNUNET_i2s (&address->peer),
334 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
336 /* destroy session and address */
337 if ( (NULL == session) ||
339 GNUNET_ATS_address_del_session (ai->ar,
341 GNUNET_ATS_address_destroy (ai->ar);
344 /* determine when the address should come back to life */
345 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
346 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
350 publish_p2a_stat_update ();
355 * Reset address blocking time. Resets the exponential
356 * back-off timer for this address to zero. Done when
357 * an address was used to create a successful connection.
359 * @param address the address to reset the blocking timer
360 * @param session the session (can be NULL)
363 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
364 struct Session *session)
366 struct AddressInfo *ai;
368 ai = find_ai (address, session);
374 /* address is in successful use, so it should not be blocked right now */
375 GNUNET_break (NULL == ai->unblock_task);
376 ai->back_off = GNUNET_TIME_UNIT_ZERO;
381 * Notify ATS about the a new inbound address. We may already
382 * know the address (as this is called each time we receive
383 * a message from an inbound connection). If the address is
384 * indeed new, make it available to ATS.
386 * @param address the address
387 * @param session the session
388 * @param prop performance information
391 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
392 struct Session *session,
393 const struct GNUNET_ATS_Properties *prop)
395 struct GNUNET_ATS_AddressRecord *ar;
396 struct AddressInfo *ai;
398 /* valid new address, let ATS know! */
399 if (NULL == address->transport_name)
404 GNUNET_assert (GNUNET_YES ==
405 GNUNET_HELLO_address_check_option (address,
406 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
407 GNUNET_assert (NULL != session);
408 ai = find_ai (address, session);
411 /* This should only be called for new sessions, and thus
412 we should not already have the address */
416 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
417 LOG (GNUNET_ERROR_TYPE_DEBUG,
418 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
419 GNUNET_i2s (&address->peer),
420 (0 == address->address_length)
422 : GST_plugins_a2s (address),
424 GNUNET_ATS_print_network_type (prop->scope));
425 ar = GNUNET_ATS_address_add (GST_ats,
429 GNUNET_break (NULL != ar);
430 ai = GNUNET_new (struct AddressInfo);
431 ai->address = GNUNET_HELLO_address_copy (address);
432 ai->session = session;
433 ai->properties = *prop;
435 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
438 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
439 publish_p2a_stat_update ();
444 * Notify ATS about the new address including the network this address is
445 * located in. The address must NOT be inbound and must be new to ATS.
447 * @param address the address
448 * @param prop performance information
451 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
452 const struct GNUNET_ATS_Properties *prop)
454 struct GNUNET_ATS_AddressRecord *ar;
455 struct AddressInfo *ai;
457 /* valid new address, let ATS know! */
458 if (NULL == address->transport_name)
463 GNUNET_assert (GNUNET_YES !=
464 GNUNET_HELLO_address_check_option (address,
465 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
466 ai = find_ai_no_session (address);
467 GNUNET_assert (NULL == ai);
468 LOG (GNUNET_ERROR_TYPE_INFO,
469 "Notifying ATS about peer `%s''s new address `%s'\n",
470 GNUNET_i2s (&address->peer),
471 (0 == address->address_length)
473 : GST_plugins_a2s (address));
474 ar = GNUNET_ATS_address_add (GST_ats,
478 GNUNET_break (NULL != ar);
479 ai = GNUNET_new (struct AddressInfo);
480 ai->address = GNUNET_HELLO_address_copy (address);
482 ai->properties = *prop;
483 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
486 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
487 publish_p2a_stat_update ();
492 * Notify ATS about a new session now existing for the given
495 * @param address the address
496 * @param session the session
499 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
500 struct Session *session)
502 struct AddressInfo *ai;
504 ai = find_ai (address, NULL);
507 /* We may already be aware of the session, even if some other part
508 of the code could not tell if it just created a new session or
509 just got one recycled from the plugin; hence, we may be called
510 with "new" session even for an "old" session; in that case,
511 check that this is the case, but just ignore it. */
512 GNUNET_assert (NULL != (find_ai (address, session)));
515 GNUNET_break (NULL == ai->session);
516 ai->session = session;
517 LOG (GNUNET_ERROR_TYPE_DEBUG,
518 "Telling ATS about new session for peer %s\n",
519 GNUNET_i2s (&address->peer));
521 GNUNET_ATS_address_add_session (ai->ar,
527 * Notify ATS that the session (but not the address) of
528 * a given address is no longer relevant.
530 * @param address the address
531 * @param session the session
534 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
535 struct Session *session)
537 struct AddressInfo *ai;
544 ai = find_ai (address,
548 /* We sometimes create sessions just for sending a PING,
549 and if those are destroyed they were never known to
550 ATS which means we end up here (however, in this
551 case, the address must be an outbound address). */
552 GNUNET_break (GNUNET_YES !=
553 GNUNET_HELLO_address_check_option (address,
554 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
558 GNUNET_assert (session == ai->session);
560 LOG (GNUNET_ERROR_TYPE_DEBUG,
561 "Telling ATS to destroy session %p from peer %s\n",
563 GNUNET_i2s (&address->peer));
566 /* If ATS doesn't know about the address/session, and this was an
567 inbound session or one that expired, then we must forget about
568 the address as well. Otherwise, we are done as we have set
569 `ai->session` to NULL already. */
570 if ( (GNUNET_YES == ai->expired) ||
572 GNUNET_HELLO_address_check_option (address,
573 GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
574 GST_ats_expire_address (address);
578 GNUNET_ATS_address_del_session (ai->ar,
582 GST_ats_expire_address (address);
588 * Notify ATS about DV distance change to an address's.
590 * @param address the address
591 * @param distance new distance value
594 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
597 struct AddressInfo *ai;
599 ai = find_ai_no_session (address);
602 LOG (GNUNET_ERROR_TYPE_DEBUG,
603 "Updated distance for peer `%s' to %u\n",
604 GNUNET_i2s (&address->peer),
606 ai->properties.distance = distance;
607 GST_manipulation_manipulate_metrics (address,
611 GNUNET_ATS_address_update (ai->ar,
617 * Notify ATS about property changes to an address's properties.
619 * @param address the address
620 * @param delay new delay value
623 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
624 struct GNUNET_TIME_Relative delay)
626 struct AddressInfo *ai;
628 ai = find_ai_no_session (address);
631 LOG (GNUNET_ERROR_TYPE_DEBUG,
632 "Updated latency for peer `%s' to %s\n",
633 GNUNET_i2s (&address->peer),
634 GNUNET_STRINGS_relative_time_to_string (delay,
636 ai->properties.delay = delay;
637 GST_manipulation_manipulate_metrics (address,
641 GNUNET_ATS_address_update (ai->ar,
647 * Notify ATS about utilization changes to an address.
649 * @param address our information about the address
650 * @param bps_in new utilization inbound
651 * @param bps_out new utilization outbound
654 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
658 struct AddressInfo *ai;
660 ai = find_ai_no_session (address);
663 LOG (GNUNET_ERROR_TYPE_DEBUG,
664 "Updating utilization for peer `%s' address %s: %u/%u\n",
665 GNUNET_i2s (&address->peer),
666 GST_plugins_a2s (address),
667 (unsigned int) bps_in,
668 (unsigned int) bps_out);
669 ai->properties.utilization_in = bps_in;
670 ai->properties.utilization_out = bps_out;
671 GST_manipulation_manipulate_metrics (address,
675 GNUNET_ATS_address_update (ai->ar,
681 * Notify ATS that the address has expired and thus cannot
682 * be used any longer. This function must only be called
683 * if the corresponding session is already gone.
685 * @param address the address
688 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
690 struct AddressInfo *ai;
692 LOG (GNUNET_ERROR_TYPE_DEBUG,
693 "Address %s of peer %s expired\n",
694 GST_plugins_a2s (address),
695 GNUNET_i2s (&address->peer));
696 ai = find_ai_no_session (address);
702 if (NULL != ai->unblock_task)
704 GNUNET_SCHEDULER_cancel (ai->unblock_task);
705 ai->unblock_task = NULL;
708 if (NULL != ai->session)
710 ai->expired = GNUNET_YES;
713 GNUNET_ATS_address_destroy (ai->ar);
718 GNUNET_assert (GNUNET_YES ==
719 GNUNET_CONTAINER_multipeermap_remove (p2a,
722 LOG (GNUNET_ERROR_TYPE_DEBUG,
723 "Telling ATS to destroy address from peer %s\n",
724 GNUNET_i2s (&address->peer));
727 /* We usually should not have a session here when we
728 expire an address, but during shutdown a session
729 may be active while validation causes the address
730 to 'expire'. So clean up both if necessary. */
731 if ( (NULL == ai->session) ||
733 GNUNET_ATS_address_del_session (ai->ar,
735 GNUNET_ATS_address_destroy (ai->ar);
738 publish_p2a_stat_update ();
739 GNUNET_HELLO_address_free (ai->address);
745 * Initialize ATS subsystem.
750 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
755 * Release memory used by the given address data.
758 * @param key which peer is this about
759 * @param value the `struct AddressInfo`
760 * @return #GNUNET_OK (continue to iterate)
763 destroy_ai (void *cls,
764 const struct GNUNET_PeerIdentity *key,
767 struct AddressInfo *ai = value;
769 GNUNET_assert (GNUNET_YES ==
770 GNUNET_CONTAINER_multipeermap_remove (p2a,
773 if (NULL != ai->unblock_task)
775 GNUNET_SCHEDULER_cancel (ai->unblock_task);
776 ai->unblock_task = NULL;
781 GNUNET_ATS_address_destroy (ai->ar);
784 GNUNET_HELLO_address_free (ai->address);
791 * Shutdown ATS subsystem.
796 GNUNET_CONTAINER_multipeermap_iterate (p2a,
799 publish_p2a_stat_update ();
800 GNUNET_CONTAINER_multipeermap_destroy (p2a);
804 /* end of gnunet-service-transport_ats.c */