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., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, 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;
87 * Map from peer identities to one or more `struct AddressInfo` values
90 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
93 * Number of blocked addresses.
95 static unsigned int num_blocked;
98 * Closure for #find_ai().
104 * Session to look for (only used if the address is inbound).
106 struct Session *session;
109 * Address to look for.
111 const struct GNUNET_HELLO_Address *address;
114 * Where to store the result.
116 struct AddressInfo *ret;
122 * Provide an update on the `p2a` map size to statistics.
123 * This function should be called whenever the `p2a` map
127 publish_p2a_stat_update ()
129 GNUNET_STATISTICS_set (GST_stats,
130 gettext_noop ("# Addresses given to ATS"),
131 GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
133 GNUNET_STATISTICS_set (GST_stats,
134 "# blocked addresses",
141 * Find matching address info.
143 * @param cls the `struct FindClosure`
144 * @param key which peer is this about
145 * @param value the `struct AddressInfo`
146 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
149 find_ai_cb (void *cls,
150 const struct GNUNET_PeerIdentity *key,
153 struct FindClosure *fc = cls;
154 struct AddressInfo *ai = value;
157 GNUNET_HELLO_address_cmp (fc->address,
159 (fc->session == ai->session) )
164 GNUNET_assert ( (fc->session != ai->session) ||
165 (NULL == ai->session) );
171 * Find the address information struct for the
172 * given address and session.
174 * @param address address to look for
175 * @param session session to match for inbound connections
176 * @return NULL if this combination is unknown
178 static struct AddressInfo *
179 find_ai (const struct GNUNET_HELLO_Address *address,
180 struct Session *session)
182 struct FindClosure fc;
184 fc.address = address;
185 fc.session = session;
187 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
196 * Find matching address info, ignoring sessions.
198 * @param cls the `struct FindClosure`
199 * @param key which peer is this about
200 * @param value the `struct AddressInfo`
201 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
204 find_ai_no_session_cb (void *cls,
205 const struct GNUNET_PeerIdentity *key,
208 struct FindClosure *fc = cls;
209 struct AddressInfo *ai = value;
212 GNUNET_HELLO_address_cmp (fc->address,
223 * Find the address information struct for the
224 * given address (ignoring sessions)
226 * @param address address to look for
227 * @return NULL if this combination is unknown
229 static struct AddressInfo *
230 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
232 struct FindClosure fc;
234 fc.address = address;
237 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
239 &find_ai_no_session_cb,
246 * Test if ATS knows about this address.
248 * @param address the address
249 * @param session the session
250 * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
253 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
254 struct Session *session)
256 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
261 * The blocking time for an address has expired, allow ATS to
264 * @param cls the `struct AddressInfo` of the address to unblock
268 unblock_address (void *cls,
269 const struct GNUNET_SCHEDULER_TaskContext *tc)
271 struct AddressInfo *ai = cls;
273 ai->unblock_task = NULL;
274 LOG (GNUNET_ERROR_TYPE_DEBUG,
275 "Unblocking address %s of peer %s\n",
276 GST_plugins_a2s (ai->address),
277 GNUNET_i2s (&ai->address->peer));
278 ai->ar = GNUNET_ATS_address_add (GST_ats,
282 GNUNET_break (NULL != ai->ar);
284 publish_p2a_stat_update ();
289 * Temporarily block a valid address for use by ATS for address
290 * suggestions. This function should be called if an address was
291 * suggested by ATS but failed to perform (i.e. failure to establish a
292 * session or to exchange the PING/PONG).
294 * @param address the address to block
295 * @param session the session (can be NULL)
298 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
299 struct Session *session)
301 struct AddressInfo *ai;
303 ai = find_ai (address, session);
311 /* already blocked, how did it get used!? */
315 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
317 GNUNET_HELLO_address_check_option (address,
318 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
319 LOG (GNUNET_ERROR_TYPE_DEBUG,
320 "Removing address %s of peer %s from use (inbound died)\n",
321 GST_plugins_a2s (address),
322 GNUNET_i2s (&address->peer));
324 LOG (GNUNET_ERROR_TYPE_INFO,
325 "Blocking address %s of peer %s from use for %s\n",
326 GST_plugins_a2s (address),
327 GNUNET_i2s (&address->peer),
328 GNUNET_STRINGS_relative_time_to_string (ai->back_off,
330 /* destroy session and address */
331 if ( (NULL == session) ||
333 GNUNET_ATS_address_del_session (ai->ar, session)) )
334 GNUNET_ATS_address_destroy (ai->ar);
337 /* determine when the address should come back to life */
338 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
339 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
343 publish_p2a_stat_update ();
348 * Reset address blocking time. Resets the exponential
349 * back-off timer for this address to zero. Done when
350 * an address was used to create a successful connection.
352 * @param address the address to reset the blocking timer
353 * @param session the session (can be NULL)
356 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
357 struct Session *session)
359 struct AddressInfo *ai;
361 ai = find_ai (address, session);
367 /* address is in successful use, so it should not be blocked right now */
368 GNUNET_break (NULL == ai->unblock_task);
369 ai->back_off = GNUNET_TIME_UNIT_ZERO;
374 * Notify ATS about the a new inbound address. We may already
375 * know the address (as this is called each time we receive
376 * a message from an inbound connection). If the address is
377 * indeed new, make it available to ATS.
379 * @param address the address
380 * @param session the session
381 * @param prop performance information
384 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
385 struct Session *session,
386 const struct GNUNET_ATS_Properties *prop)
388 struct GNUNET_ATS_AddressRecord *ar;
389 struct AddressInfo *ai;
391 /* valid new address, let ATS know! */
392 if (NULL == address->transport_name)
397 GNUNET_assert (GNUNET_YES ==
398 GNUNET_HELLO_address_check_option (address,
399 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
400 GNUNET_assert (NULL != session);
401 ai = find_ai (address, session);
404 /* This should only be called for new sessions, and thus
405 we should not already have the address */
409 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
410 LOG (GNUNET_ERROR_TYPE_DEBUG,
411 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
412 GNUNET_i2s (&address->peer),
413 (0 == address->address_length)
415 : GST_plugins_a2s (address),
417 GNUNET_ATS_print_network_type (prop->scope));
418 ar = GNUNET_ATS_address_add (GST_ats,
422 GNUNET_break (NULL != ar);
423 ai = GNUNET_new (struct AddressInfo);
424 ai->address = GNUNET_HELLO_address_copy (address);
425 ai->session = session;
426 ai->properties = *prop;
428 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
431 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
432 publish_p2a_stat_update ();
437 * Notify ATS about the new address including the network this address is
438 * located in. The address must NOT be inbound and must be new to ATS.
440 * @param address the address
441 * @param prop performance information
444 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
445 const struct GNUNET_ATS_Properties *prop)
447 struct GNUNET_ATS_AddressRecord *ar;
448 struct AddressInfo *ai;
450 /* valid new address, let ATS know! */
451 if (NULL == address->transport_name)
456 GNUNET_assert (GNUNET_YES !=
457 GNUNET_HELLO_address_check_option (address,
458 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
459 ai = find_ai_no_session (address);
460 GNUNET_assert (NULL == ai);
461 LOG (GNUNET_ERROR_TYPE_INFO,
462 "Notifying ATS about peer `%s''s new address `%s'\n",
463 GNUNET_i2s (&address->peer),
464 (0 == address->address_length)
466 : GST_plugins_a2s (address));
467 ar = GNUNET_ATS_address_add (GST_ats,
471 GNUNET_break (NULL != ar);
472 ai = GNUNET_new (struct AddressInfo);
473 ai->address = GNUNET_HELLO_address_copy (address);
475 ai->properties = *prop;
476 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
479 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
480 publish_p2a_stat_update ();
485 * Notify ATS about a new session now existing for the given
488 * @param address the address
489 * @param session the session
492 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
493 struct Session *session)
495 struct AddressInfo *ai;
497 ai = find_ai (address, NULL);
500 /* We may already be aware of the session, even if some other part
501 of the code could not tell if it just created a new session or
502 just got one recycled from the plugin; hence, we may be called
503 with "new" session even for an "old" session; in that case,
504 check that this is the case, but just ignore it. */
505 GNUNET_assert (NULL != (find_ai (address, session)));
508 GNUNET_break (NULL == ai->session);
509 ai->session = session;
510 LOG (GNUNET_ERROR_TYPE_DEBUG,
511 "Telling ATS about new session for peer %s\n",
512 GNUNET_i2s (&address->peer));
514 GNUNET_ATS_address_add_session (ai->ar,
520 * Notify ATS that the session (but not the address) of
521 * a given address is no longer relevant.
523 * @param address the address
524 * @param session the session
527 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
528 struct Session *session)
530 struct AddressInfo *ai;
537 ai = find_ai (address, session);
540 /* We sometimes create sessions just for sending a PING,
541 and if those are destroyed they were never known to
542 ATS which means we end up here (however, in this
543 case, the address must be an outbound address). */
544 GNUNET_break (GNUNET_YES !=
545 GNUNET_HELLO_address_check_option (address,
546 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
550 GNUNET_assert (session == ai->session);
552 LOG (GNUNET_ERROR_TYPE_DEBUG,
553 "Telling ATS to destroy session %p from peer %s\n",
555 GNUNET_i2s (&address->peer));
558 /* If ATS doesn't know about the address/session, and this
559 was an inbound session that expired, then we must forget
560 about the address as well. Otherwise, we are done as
561 we have set `ai->session` to NULL already. */
563 GNUNET_HELLO_address_check_option (address,
564 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
565 GST_ats_expire_address (address);
569 GNUNET_ATS_address_del_session (ai->ar, session))
572 GST_ats_expire_address (address);
578 * Notify ATS about DV distance change to an address's.
580 * @param address the address
581 * @param distance new distance value
584 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
587 struct AddressInfo *ai;
589 ai = find_ai_no_session (address);
592 LOG (GNUNET_ERROR_TYPE_DEBUG,
593 "Updated distance for peer `%s' to %u\n",
594 GNUNET_i2s (&address->peer),
596 ai->properties.distance = distance;
597 GST_manipulation_manipulate_metrics (address,
601 GNUNET_ATS_address_update (ai->ar,
607 * Notify ATS about property changes to an address's properties.
609 * @param address the address
610 * @param delay new delay value
613 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
614 struct GNUNET_TIME_Relative delay)
616 struct AddressInfo *ai;
618 ai = find_ai_no_session (address);
621 LOG (GNUNET_ERROR_TYPE_DEBUG,
622 "Updated latency for peer `%s' to %s\n",
623 GNUNET_i2s (&address->peer),
624 GNUNET_STRINGS_relative_time_to_string (delay,
626 ai->properties.delay = delay;
627 GST_manipulation_manipulate_metrics (address,
631 GNUNET_ATS_address_update (ai->ar,
637 * Notify ATS about utilization changes to an address.
639 * @param address our information about the address
640 * @param bps_in new utilization inbound
641 * @param bps_out new utilization outbound
644 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
648 struct AddressInfo *ai;
650 ai = find_ai_no_session (address);
653 LOG (GNUNET_ERROR_TYPE_DEBUG,
654 "Updating utilization for peer `%s' address %s: %u/%u\n",
655 GNUNET_i2s (&address->peer),
656 GST_plugins_a2s (address),
657 (unsigned int) bps_in,
658 (unsigned int) bps_out);
659 ai->properties.utilization_in = bps_in;
660 ai->properties.utilization_out = bps_out;
661 GST_manipulation_manipulate_metrics (address,
665 GNUNET_ATS_address_update (ai->ar,
671 * Notify ATS that the address has expired and thus cannot
672 * be used any longer. This function must only be called
673 * if the corresponding session is already gone.
675 * @param address the address
678 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
680 struct AddressInfo *ai;
682 LOG (GNUNET_ERROR_TYPE_DEBUG,
683 "Address %s of peer %s expired\n",
684 GST_plugins_a2s (address),
685 GNUNET_i2s (&address->peer));
686 ai = find_ai_no_session (address);
692 GNUNET_assert (GNUNET_YES ==
693 GNUNET_CONTAINER_multipeermap_remove (p2a,
696 GNUNET_break (NULL == ai->session);
697 LOG (GNUNET_ERROR_TYPE_DEBUG,
698 "Telling ATS to destroy address from peer %s\n",
699 GNUNET_i2s (&address->peer));
702 /* We usually should not have a session here when we
703 expire an address, but during shutdown a session
704 may be active while validation causes the address
705 to 'expire'. So clean up both if necessary. */
706 if ( (NULL == ai->session) ||
708 GNUNET_ATS_address_del_session (ai->ar,
710 GNUNET_ATS_address_destroy (ai->ar);
713 if (NULL != ai->unblock_task)
715 GNUNET_SCHEDULER_cancel (ai->unblock_task);
716 ai->unblock_task = NULL;
719 publish_p2a_stat_update ();
720 GNUNET_HELLO_address_free (ai->address);
726 * Initialize ATS subsystem.
731 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
736 * Release memory used by the given address data.
739 * @param key which peer is this about
740 * @param value the `struct AddressInfo`
741 * @return #GNUNET_OK (continue to iterate)
744 destroy_ai (void *cls,
745 const struct GNUNET_PeerIdentity *key,
748 struct AddressInfo *ai = value;
750 GNUNET_assert (GNUNET_YES ==
751 GNUNET_CONTAINER_multipeermap_remove (p2a,
754 if (NULL != ai->unblock_task)
756 GNUNET_SCHEDULER_cancel (ai->unblock_task);
757 ai->unblock_task = NULL;
762 GNUNET_ATS_address_destroy (ai->ar);
765 GNUNET_HELLO_address_free (ai->address);
772 * Shutdown ATS subsystem.
777 GNUNET_CONTAINER_multipeermap_iterate (p2a,
780 publish_p2a_stat_update ();
781 GNUNET_CONTAINER_multipeermap_destroy (p2a);
785 /* end of gnunet-service-transport_ats.c */