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;
94 * Closure for #find_ai().
100 * Session to look for (only used if the address is inbound).
102 struct Session *session;
105 * Address to look for.
107 const struct GNUNET_HELLO_Address *address;
110 * Where to store the result.
112 struct AddressInfo *ret;
118 * Provide an update on the `p2a` map size to statistics.
119 * This function should be called whenever the `p2a` map
123 publish_p2a_stat_update ()
125 GNUNET_STATISTICS_set (GST_stats,
126 gettext_noop ("# Addresses given to ATS"),
127 GNUNET_CONTAINER_multipeermap_size (p2a),
133 * Find matching address info.
135 * @param cls the `struct FindClosure`
136 * @param key which peer is this about
137 * @param value the `struct AddressInfo`
138 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
141 find_ai_cb (void *cls,
142 const struct GNUNET_PeerIdentity *key,
145 struct FindClosure *fc = cls;
146 struct AddressInfo *ai = value;
149 GNUNET_HELLO_address_cmp (fc->address,
151 (fc->session == ai->session) )
156 GNUNET_assert ( (fc->session != ai->session) ||
157 (NULL == ai->session) );
163 * Find the address information struct for the
164 * given address and session.
166 * @param address address to look for
167 * @param session session to match for inbound connections
168 * @return NULL if this combination is unknown
170 static struct AddressInfo *
171 find_ai (const struct GNUNET_HELLO_Address *address,
172 struct Session *session)
174 struct FindClosure fc;
176 fc.address = address;
177 fc.session = session;
179 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
188 * Find matching address info, ignoring sessions.
190 * @param cls the `struct FindClosure`
191 * @param key which peer is this about
192 * @param value the `struct AddressInfo`
193 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
196 find_ai_no_session_cb (void *cls,
197 const struct GNUNET_PeerIdentity *key,
200 struct FindClosure *fc = cls;
201 struct AddressInfo *ai = value;
204 GNUNET_HELLO_address_cmp (fc->address,
215 * Find the address information struct for the
216 * given address (ignoring sessions)
218 * @param address address to look for
219 * @return NULL if this combination is unknown
221 static struct AddressInfo *
222 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
224 struct FindClosure fc;
226 fc.address = address;
229 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
231 &find_ai_no_session_cb,
238 * Test if ATS knows about this address.
240 * @param address the address
241 * @param session the session
242 * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
245 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
246 struct Session *session)
248 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
253 * The blocking time for an address has expired, allow ATS to
256 * @param cls the `struct AddressInfo` of the address to unblock
260 unblock_address (void *cls,
261 const struct GNUNET_SCHEDULER_TaskContext *tc)
263 struct AddressInfo *ai = cls;
265 ai->unblock_task = NULL;
266 LOG (GNUNET_ERROR_TYPE_DEBUG,
267 "Unblocking address %s of peer %s\n",
268 GST_plugins_a2s (ai->address),
269 GNUNET_i2s (&ai->address->peer));
270 ai->ar = GNUNET_ATS_address_add (GST_ats,
274 GNUNET_break (NULL != ai->ar);
279 * Temporarily block a valid address for use by ATS for address
280 * suggestions. This function should be called if an address was
281 * suggested by ATS but failed to perform (i.e. failure to establish a
282 * session or to exchange the PING/PONG).
284 * @param address the address to block
285 * @param session the session (can be NULL)
288 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
289 struct Session *session)
291 struct AddressInfo *ai;
293 ai = find_ai (address, session);
301 /* already blocked, how did it get used!? */
306 GNUNET_HELLO_address_check_option (address,
307 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
308 LOG (GNUNET_ERROR_TYPE_DEBUG,
309 "Removing address %s of peer %s from use (inbound died)\n",
310 GST_plugins_a2s (address),
311 GNUNET_i2s (&address->peer));
313 LOG (GNUNET_ERROR_TYPE_DEBUG,
314 "Blocking address %s of peer %s from use for a while\n",
315 GST_plugins_a2s (address),
316 GNUNET_i2s (&address->peer));
317 /* destroy session and address */
318 if ( (NULL == session) ||
320 GNUNET_ATS_address_del_session (ai->ar, session)) )
321 GNUNET_ATS_address_destroy (ai->ar);
324 /* determine when the address should come back to life */
325 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
326 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
327 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
334 * Notify ATS about the a new inbound address. We may already
335 * know the address (as this is called each time we receive
336 * a message from an inbound connection). If the address is
337 * indeed new, make it available to ATS.
339 * @param address the address
340 * @param session the session
341 * @param prop performance information
344 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
345 struct Session *session,
346 const struct GNUNET_ATS_Properties *prop)
348 struct GNUNET_ATS_AddressRecord *ar;
349 struct AddressInfo *ai;
351 /* valid new address, let ATS know! */
352 if (NULL == address->transport_name)
357 GNUNET_assert (GNUNET_YES ==
358 GNUNET_HELLO_address_check_option (address,
359 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
360 GNUNET_assert (NULL != session);
361 ai = find_ai (address, session);
364 /* This should only be called for new sessions, and thus
365 we should not already have the address */
369 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
370 LOG (GNUNET_ERROR_TYPE_DEBUG,
371 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
372 GNUNET_i2s (&address->peer),
373 (0 == address->address_length)
375 : GST_plugins_a2s (address),
377 GNUNET_ATS_print_network_type (prop->scope));
378 ar = GNUNET_ATS_address_add (GST_ats,
382 GNUNET_break (NULL != ar);
383 ai = GNUNET_new (struct AddressInfo);
384 ai->address = GNUNET_HELLO_address_copy (address);
385 ai->session = session;
386 ai->properties = *prop;
388 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
391 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
392 publish_p2a_stat_update ();
397 * Notify ATS about the new address including the network this address is
398 * located in. The address must NOT be inbound and must be new to ATS.
400 * @param address the address
401 * @param prop performance information
404 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
405 const struct GNUNET_ATS_Properties *prop)
407 struct GNUNET_ATS_AddressRecord *ar;
408 struct AddressInfo *ai;
410 /* valid new address, let ATS know! */
411 if (NULL == address->transport_name)
416 GNUNET_assert (GNUNET_YES !=
417 GNUNET_HELLO_address_check_option (address,
418 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
419 ai = find_ai_no_session (address);
420 GNUNET_assert (NULL == ai);
421 LOG (GNUNET_ERROR_TYPE_INFO,
422 "Notifying ATS about peer `%s''s new address `%s'\n",
423 GNUNET_i2s (&address->peer),
424 (0 == address->address_length)
426 : GST_plugins_a2s (address));
427 ar = GNUNET_ATS_address_add (GST_ats,
431 GNUNET_break (NULL != ar);
432 ai = GNUNET_new (struct AddressInfo);
433 ai->address = GNUNET_HELLO_address_copy (address);
435 ai->properties = *prop;
436 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
439 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
440 publish_p2a_stat_update ();
445 * Notify ATS about a new session now existing for the given
448 * @param address the address
449 * @param session the session
452 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
453 struct Session *session)
455 struct AddressInfo *ai;
457 ai = find_ai (address, NULL);
460 /* We may already be aware of the session, even if some other part
461 of the code could not tell if it just created a new session or
462 just got one recycled from the plugin; hence, we may be called
463 with "new" session even for an "old" session; in that case,
464 check that this is the case, but just ignore it. */
465 GNUNET_assert (NULL != (find_ai (address, session)));
468 GNUNET_break (NULL == ai->session);
469 ai->session = session;
470 LOG (GNUNET_ERROR_TYPE_DEBUG,
471 "Telling ATS about new session for peer %s\n",
472 GNUNET_i2s (&address->peer));
474 GNUNET_ATS_address_add_session (ai->ar,
480 * Notify ATS that the session (but not the address) of
481 * a given address is no longer relevant.
483 * @param address the address
484 * @param session the session
487 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
488 struct Session *session)
490 struct AddressInfo *ai;
497 ai = find_ai (address, session);
500 /* We sometimes create sessions just for sending a PING,
501 and if those are destroyed they were never known to
502 ATS which means we end up here (however, in this
503 case, the address must be an outbound address). */
504 GNUNET_break (GNUNET_YES !=
505 GNUNET_HELLO_address_check_option (address,
506 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
510 GNUNET_assert (session == ai->session);
512 LOG (GNUNET_ERROR_TYPE_DEBUG,
513 "Telling ATS to destroy session %p from peer %s\n",
515 GNUNET_i2s (&address->peer));
518 /* If ATS doesn't know about the address/session, and this
519 was an inbound session that expired, then we must forget
520 about the address as well. Otherwise, we are done as
521 we have set `ai->session` to NULL already. */
523 GNUNET_HELLO_address_check_option (address,
524 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
525 GST_ats_expire_address (address);
529 GNUNET_ATS_address_del_session (ai->ar, session))
532 GST_ats_expire_address (address);
538 * Notify ATS about DV distance change to an address's.
540 * @param address the address
541 * @param distance new distance value
544 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
547 struct AddressInfo *ai;
549 ai = find_ai_no_session (address);
552 LOG (GNUNET_ERROR_TYPE_DEBUG,
553 "Updated distance for peer `%s' to %u\n",
554 GNUNET_i2s (&address->peer),
556 ai->properties.distance = distance;
557 GST_manipulation_manipulate_metrics (address,
561 GNUNET_ATS_address_update (ai->ar,
567 * Notify ATS about property changes to an address's properties.
569 * @param address the address
570 * @param delay new delay value
573 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
574 struct GNUNET_TIME_Relative delay)
576 struct AddressInfo *ai;
578 ai = find_ai_no_session (address);
581 LOG (GNUNET_ERROR_TYPE_DEBUG,
582 "Updated latency for peer `%s' to %s\n",
583 GNUNET_i2s (&address->peer),
584 GNUNET_STRINGS_relative_time_to_string (delay,
586 ai->properties.delay = delay;
587 GST_manipulation_manipulate_metrics (address,
591 GNUNET_ATS_address_update (ai->ar,
597 * Notify ATS about utilization changes to an address.
599 * @param address our information about the address
600 * @param bps_in new utilization inbound
601 * @param bps_out new utilization outbound
604 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
608 struct AddressInfo *ai;
610 ai = find_ai_no_session (address);
613 LOG (GNUNET_ERROR_TYPE_DEBUG,
614 "Updating utilization for peer `%s' address %s: %u/%u\n",
615 GNUNET_i2s (&address->peer),
616 GST_plugins_a2s (address),
617 (unsigned int) bps_in,
618 (unsigned int) bps_out);
619 ai->properties.utilization_in = bps_in;
620 ai->properties.utilization_out = bps_out;
621 GST_manipulation_manipulate_metrics (address,
625 GNUNET_ATS_address_update (ai->ar,
631 * Notify ATS that the address has expired and thus cannot
632 * be used any longer. This function must only be called
633 * if the corresponding session is already gone.
635 * @param address the address
638 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
640 struct AddressInfo *ai;
642 LOG (GNUNET_ERROR_TYPE_DEBUG,
643 "Address %s of peer %s expired\n",
644 GST_plugins_a2s (address),
645 GNUNET_i2s (&address->peer));
646 ai = find_ai_no_session (address);
652 GNUNET_assert (GNUNET_YES ==
653 GNUNET_CONTAINER_multipeermap_remove (p2a,
656 publish_p2a_stat_update ();
657 GNUNET_break (NULL == ai->session);
658 LOG (GNUNET_ERROR_TYPE_DEBUG,
659 "Telling ATS to destroy address from peer %s\n",
660 GNUNET_i2s (&address->peer));
663 /* We usually should not have a session here when we
664 expire an address, but during shutdown a session
665 may be active while validation causes the address
666 to 'expire'. So clean up both if necessary. */
667 if ( (NULL == ai->session) ||
669 GNUNET_ATS_address_del_session (ai->ar,
671 GNUNET_ATS_address_destroy (ai->ar);
674 if (NULL != ai->unblock_task)
676 GNUNET_SCHEDULER_cancel (ai->unblock_task);
677 ai->unblock_task = NULL;
679 GNUNET_HELLO_address_free (ai->address);
685 * Initialize ATS subsystem.
690 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
695 * Release memory used by the given address data.
698 * @param key which peer is this about
699 * @param value the `struct AddressInfo`
700 * @return #GNUNET_OK (continue to iterate)
703 destroy_ai (void *cls,
704 const struct GNUNET_PeerIdentity *key,
707 struct AddressInfo *ai = value;
709 GNUNET_assert (GNUNET_YES ==
710 GNUNET_CONTAINER_multipeermap_remove (p2a,
713 if (NULL != ai->unblock_task)
715 GNUNET_SCHEDULER_cancel (ai->unblock_task);
716 ai->unblock_task = NULL;
720 GNUNET_ATS_address_destroy (ai->ar);
723 GNUNET_HELLO_address_free (ai->address);
730 * Shutdown ATS subsystem.
735 GNUNET_CONTAINER_multipeermap_iterate (p2a,
738 publish_p2a_stat_update ();
739 GNUNET_CONTAINER_multipeermap_destroy (p2a);
743 /* end of gnunet-service-transport_ats.c */