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"
34 * Information we track for each address known to ATS.
40 * The address (with peer identity).
42 struct GNUNET_HELLO_Address *address;
45 * Session (can be NULL)
47 struct Session *session;
50 * Record with ATS API for the address.
52 struct GNUNET_ATS_AddressRecord *ar;
55 * Time until when this address is blocked and should thus not be
56 * made available to ATS (@e ar should be NULL until this time).
57 * Used when transport determines that for some reason it
58 * (temporarily) cannot use an address, even though it has been
61 struct GNUNET_TIME_Absolute blocked;
64 * If an address is blocked as part of an exponential back-off,
65 * we track the current size of the backoff here.
67 struct GNUNET_TIME_Relative back_off;
70 * Task scheduled to unblock an ATS-blocked address at
71 * @e blocked time, or NULL if the address is not blocked
72 * (and thus @e ar is non-NULL).
74 struct GNUNET_SCHEDULER_Task *unblock_task;
80 * Map from peer identities to one or more `struct AddressInfo` values
83 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
87 * Closure for #find_ai().
93 * Session to look for (only used if the address is inbound).
95 struct Session *session;
98 * Address to look for.
100 const struct GNUNET_HELLO_Address *address;
103 * Where to store the result.
105 struct AddressInfo *ret;
111 * Provide an update on the `p2a` map size to statistics.
112 * This function should be called whenever the `p2a` map
116 publish_p2a_stat_update ()
118 GNUNET_STATISTICS_set (GST_stats,
119 gettext_noop ("# Addresses given to ATS"),
120 GNUNET_CONTAINER_multipeermap_size (p2a),
126 * Find matching address info.
128 * @param cls the `struct FindClosure`
129 * @param key which peer is this about
130 * @param value the `struct AddressInfo`
131 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
134 find_ai_cb (void *cls,
135 const struct GNUNET_PeerIdentity *key,
138 struct FindClosure *fc = cls;
139 struct AddressInfo *ai = value;
142 GNUNET_HELLO_address_cmp (fc->address,
144 (fc->session == ai->session) )
149 GNUNET_assert ( (fc->session != ai->session) ||
150 (NULL == ai->session) );
156 * Find the address information struct for the
157 * given address and session.
159 * @param address address to look for
160 * @param session session to match for inbound connections
161 * @return NULL if this combination is unknown
163 static struct AddressInfo *
164 find_ai (const struct GNUNET_HELLO_Address *address,
165 struct Session *session)
167 struct FindClosure fc;
169 fc.address = address;
170 fc.session = session;
172 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
181 * Find matching address info, ignoring sessions.
183 * @param cls the `struct FindClosure`
184 * @param key which peer is this about
185 * @param value the `struct AddressInfo`
186 * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
189 find_ai_no_session_cb (void *cls,
190 const struct GNUNET_PeerIdentity *key,
193 struct FindClosure *fc = cls;
194 struct AddressInfo *ai = value;
197 GNUNET_HELLO_address_cmp (fc->address,
208 * Find the address information struct for the
209 * given address (ignoring sessions)
211 * @param address address to look for
212 * @return NULL if this combination is unknown
214 static struct AddressInfo *
215 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
217 struct FindClosure fc;
219 fc.address = address;
222 GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
224 &find_ai_no_session_cb,
231 * Test if ATS knows about this address.
233 * @param address the address
234 * @param session the session
235 * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
238 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
239 struct Session *session)
241 return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
246 * The blocking time for an address has expired, allow ATS to
249 * @param cls the `struct AddressInfo` of the address to unblock
253 unblock_address (void *cls,
254 const struct GNUNET_SCHEDULER_TaskContext *tc)
256 struct AddressInfo *ai = cls;
258 ai->unblock_task = NULL;
259 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
260 "Unblocking address %s of peer %s\n",
261 GST_plugins_a2s (ai->address),
262 GNUNET_i2s (&ai->address->peer));
263 ai->ar = GNUNET_ATS_address_add (GST_ats,
267 /* FIXME: should pass ATS information here! */
272 * Temporarily block a valid address for use by ATS for address
273 * suggestions. This function should be called if an address was
274 * suggested by ATS but failed to perform (i.e. failure to establish a
275 * session or to exchange the PING/PONG).
277 * @param address the address to block
278 * @param session the session (can be NULL)
281 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
282 struct Session *session)
284 struct AddressInfo *ai;
286 ai = find_ai (address, session);
294 /* already blocked, how did it get used!? */
299 GNUNET_HELLO_address_check_option (address,
300 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
301 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
302 "Removing address %s of peer %s from use (inbound died)\n",
303 GST_plugins_a2s (address),
304 GNUNET_i2s (&address->peer));
306 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
307 "Blocking address %s of peer %s from use for a while\n",
308 GST_plugins_a2s (address),
309 GNUNET_i2s (&address->peer));
310 /* destroy session and address */
311 if ( (NULL == session) ||
313 GNUNET_ATS_address_del_session (ai->ar, session)) )
314 GNUNET_ATS_address_destroy (ai->ar);
317 /* determine when the address should come back to life */
318 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
319 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
320 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
327 * Notify ATS about the a new inbound address. We may already
328 * know the address (as this is called each time we receive
329 * a message from an inbound connection). If the address is
330 * indeed new, make it available to ATS.
332 * @param address the address
333 * @param session the session
334 * @param ats ats information
335 * @param ats_count number of @a ats information
338 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
339 struct Session *session,
340 const struct GNUNET_ATS_Information *ats,
343 struct GNUNET_TRANSPORT_PluginFunctions *papi;
344 struct GNUNET_ATS_Information ats2[ats_count + 1];
345 struct GNUNET_ATS_AddressRecord *ar;
346 struct AddressInfo *ai;
349 /* valid new address, let ATS know! */
350 if (NULL == address->transport_name)
355 GNUNET_assert (GNUNET_YES ==
356 GNUNET_HELLO_address_check_option (address,
357 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
358 GNUNET_assert (NULL != session);
359 ai = find_ai (address, session);
362 /* This should only be called for new sessions, and thus
363 we should not already have the address */
367 papi = GST_plugins_find (address->transport_name);
368 GNUNET_assert (NULL != papi);
369 net = papi->get_network (papi->cls, session);
370 if (GNUNET_ATS_NET_UNSPECIFIED == net)
372 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
373 _("Could not obtain a valid network for `%s' %s (%s)\n"),
374 GNUNET_i2s (&address->peer),
375 GST_plugins_a2s (address),
376 address->transport_name);
379 ats2[0].type = htonl (GNUNET_ATS_NETWORK_TYPE);
380 ats2[0].value = htonl (net);
383 sizeof(struct GNUNET_ATS_Information) * ats_count);
384 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
385 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
386 GNUNET_i2s (&address->peer),
387 (0 == address->address_length)
389 : GST_plugins_a2s (address),
391 GNUNET_ATS_print_network_type (net));
392 ar = GNUNET_ATS_address_add (GST_ats,
395 (NULL != session) ? ats2 : ats,
396 (NULL != session) ? ats_count + 1 : ats_count);
397 ai = GNUNET_new (struct AddressInfo);
398 ai->address = GNUNET_HELLO_address_copy (address);
399 ai->session = session;
401 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
404 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
405 publish_p2a_stat_update ();
410 * Notify ATS about the new address including the network this address is
411 * located in. The address must NOT be inbound and must be new to ATS.
413 * @param address the address
414 * @param ats ats information
415 * @param ats_count number of @a ats information
418 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
419 const struct GNUNET_ATS_Information *ats,
422 struct GNUNET_ATS_AddressRecord *ar;
423 struct AddressInfo *ai;
425 /* valid new address, let ATS know! */
426 if (NULL == address->transport_name)
431 GNUNET_assert (GNUNET_YES !=
432 GNUNET_HELLO_address_check_option (address,
433 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
434 ai = find_ai_no_session (address);
435 GNUNET_assert (NULL == ai);
436 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
437 "Notifying ATS about peer `%s''s new address `%s'\n",
438 GNUNET_i2s (&address->peer),
439 (0 == address->address_length)
441 : GST_plugins_a2s (address));
442 ar = GNUNET_ATS_address_add (GST_ats,
447 ai = GNUNET_new (struct AddressInfo);
448 ai->address = GNUNET_HELLO_address_copy (address);
450 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
453 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
454 publish_p2a_stat_update ();
459 * Notify ATS about a new session now existing for the given
462 * @param address the address
463 * @param session the session
466 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
467 struct Session *session)
469 struct AddressInfo *ai;
471 ai = find_ai (address, NULL);
474 /* We may already be aware of the session, even if some other part
475 of the code could not tell if it just created a new session or
476 just got one recycled from the plugin; hence, we may be called
477 with "new" session even for an "old" session; in that case,
478 check that this is the case, but just ignore it. */
479 GNUNET_assert (NULL != (find_ai (address, session)));
482 GNUNET_break (NULL == ai->session);
483 ai->session = session;
484 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
486 "Telling ATS about new session %p for peer %s\n",
488 GNUNET_i2s (&address->peer));
490 GNUNET_ATS_address_add_session (ai->ar,
496 * Notify ATS that the session (but not the address) of
497 * a given address is no longer relevant.
499 * @param address the address
500 * @param session the session
503 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
504 struct Session *session)
506 struct AddressInfo *ai;
513 ai = find_ai (address, session);
516 /* We sometimes create sessions just for sending a PING,
517 and if those are destroyed they were never known to
518 ATS which means we end up here (however, in this
519 case, the address must be an outbound address). */
520 GNUNET_break (GNUNET_YES !=
521 GNUNET_HELLO_address_check_option (address,
522 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
526 GNUNET_assert (session == ai->session);
528 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
530 "Telling ATS to destroy session %p from peer %s\n",
532 GNUNET_i2s (&address->peer));
535 /* If ATS doesn't know about the address/session, and this
536 was an inbound session that expired, then we must forget
537 about the address as well. Otherwise, we are done as
538 we have set `ai->session` to NULL already. */
540 GNUNET_HELLO_address_check_option (address,
541 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
542 GST_ats_expire_address (address);
546 GNUNET_ATS_address_del_session (ai->ar, session))
549 GST_ats_expire_address (address);
555 * Notify ATS about property changes to an address.
557 * @param address our information about the address
558 * @param session the session
559 * @param ats performance information
560 * @param ats_count number of elements in @a ats
563 GST_ats_update_metrics (const struct GNUNET_HELLO_Address *address,
564 struct Session *session,
565 const struct GNUNET_ATS_Information *ats,
568 struct GNUNET_ATS_Information *ats_new;
569 struct AddressInfo *ai;
571 ai = find_ai (address, session);
574 /* We sometimes create sessions just for sending a PING,
575 and if we get metrics for those, they were never known to
576 ATS which means we end up here (however, in this
577 case, the address must be an outbound address). */
578 GNUNET_break (GNUNET_YES !=
579 GNUNET_HELLO_address_check_option (address,
580 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
583 /* Call to manipulation to manipulate ATS information */
584 GNUNET_assert (NULL != GST_ats);
585 if ((NULL == ats) || (0 == ats_count))
587 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
588 "Updating metrics for peer `%s' address %s session %p\n",
589 GNUNET_i2s (&address->peer),
590 GST_plugins_a2s (address),
592 ats_new = GST_manipulation_manipulate_metrics (address,
597 GNUNET_ATS_address_update (ai->ar,
600 GNUNET_free_non_null (ats_new);
605 * Notify ATS that the address has expired and thus cannot
606 * be used any longer. This function must only be called
607 * if the corresponding session is already gone.
609 * @param address the address
612 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
614 struct AddressInfo *ai;
616 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
617 "Address %s of peer %s expired\n",
618 GST_plugins_a2s (address),
619 GNUNET_i2s (&address->peer));
620 ai = find_ai_no_session (address);
626 GNUNET_assert (GNUNET_YES ==
627 GNUNET_CONTAINER_multipeermap_remove (p2a,
630 publish_p2a_stat_update ();
631 GNUNET_break (NULL == ai->session);
632 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
634 "Telling ATS to destroy address from peer %s\n",
635 GNUNET_i2s (&address->peer));
638 /* We usually should not have a session here when we
639 expire an address, but during shutdown a session
640 may be active while validation causes the address
641 to 'expire'. So clean up both if necessary. */
642 if ( (NULL == ai->session) ||
644 GNUNET_ATS_address_del_session (ai->ar,
646 GNUNET_ATS_address_destroy (ai->ar);
649 if (NULL != ai->unblock_task)
651 GNUNET_SCHEDULER_cancel (ai->unblock_task);
652 ai->unblock_task = NULL;
654 GNUNET_HELLO_address_free (ai->address);
660 * Initialize ATS subsystem.
665 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
670 * Release memory used by the given address data.
673 * @param key which peer is this about
674 * @param value the `struct AddressInfo`
675 * @return #GNUNET_OK (continue to iterate)
678 destroy_ai (void *cls,
679 const struct GNUNET_PeerIdentity *key,
682 struct AddressInfo *ai = value;
684 GNUNET_assert (GNUNET_YES ==
685 GNUNET_CONTAINER_multipeermap_remove (p2a,
688 if (NULL != ai->unblock_task)
690 GNUNET_SCHEDULER_cancel (ai->unblock_task);
691 ai->unblock_task = NULL;
695 GNUNET_ATS_address_destroy (ai->ar);
698 GNUNET_HELLO_address_free (ai->address);
705 * Shutdown ATS subsystem.
710 GNUNET_CONTAINER_multipeermap_iterate (p2a,
713 publish_p2a_stat_update ();
714 GNUNET_CONTAINER_multipeermap_destroy (p2a);
718 /* end of gnunet-service-transport_ats.c */