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 GNUNET_break (NULL != ai->ar);
268 /* FIXME: should pass ATS information here! */
273 * Temporarily block a valid address for use by ATS for address
274 * suggestions. This function should be called if an address was
275 * suggested by ATS but failed to perform (i.e. failure to establish a
276 * session or to exchange the PING/PONG).
278 * @param address the address to block
279 * @param session the session (can be NULL)
282 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
283 struct Session *session)
285 struct AddressInfo *ai;
287 ai = find_ai (address, session);
295 /* already blocked, how did it get used!? */
300 GNUNET_HELLO_address_check_option (address,
301 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
302 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
303 "Removing address %s of peer %s from use (inbound died)\n",
304 GST_plugins_a2s (address),
305 GNUNET_i2s (&address->peer));
307 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
308 "Blocking address %s of peer %s from use for a while\n",
309 GST_plugins_a2s (address),
310 GNUNET_i2s (&address->peer));
311 /* destroy session and address */
312 if ( (NULL == session) ||
314 GNUNET_ATS_address_del_session (ai->ar, session)) )
315 GNUNET_ATS_address_destroy (ai->ar);
318 /* determine when the address should come back to life */
319 ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
320 ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
321 ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
328 * Notify ATS about the a new inbound address. We may already
329 * know the address (as this is called each time we receive
330 * a message from an inbound connection). If the address is
331 * indeed new, make it available to ATS.
333 * @param address the address
334 * @param session the session
335 * @param ats ats information
336 * @param ats_count number of @a ats information
339 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
340 struct Session *session,
341 const struct GNUNET_ATS_Information *ats,
344 struct GNUNET_TRANSPORT_PluginFunctions *papi;
345 struct GNUNET_ATS_Information ats2[ats_count + 1];
346 struct GNUNET_ATS_AddressRecord *ar;
347 struct AddressInfo *ai;
350 /* valid new address, let ATS know! */
351 if (NULL == address->transport_name)
356 GNUNET_assert (GNUNET_YES ==
357 GNUNET_HELLO_address_check_option (address,
358 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
359 GNUNET_assert (NULL != session);
360 ai = find_ai (address, session);
363 /* This should only be called for new sessions, and thus
364 we should not already have the address */
368 papi = GST_plugins_find (address->transport_name);
369 GNUNET_assert (NULL != papi);
370 net = papi->get_network (papi->cls, session);
371 if (GNUNET_ATS_NET_UNSPECIFIED == net)
373 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
374 _("Could not obtain a valid network for `%s' %s (%s)\n"),
375 GNUNET_i2s (&address->peer),
376 GST_plugins_a2s (address),
377 address->transport_name);
380 ats2[0].type = htonl (GNUNET_ATS_NETWORK_TYPE);
381 ats2[0].value = htonl (net);
384 sizeof(struct GNUNET_ATS_Information) * ats_count);
385 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
386 "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
387 GNUNET_i2s (&address->peer),
388 (0 == address->address_length)
390 : GST_plugins_a2s (address),
392 GNUNET_ATS_print_network_type (net));
393 ar = GNUNET_ATS_address_add (GST_ats,
396 (NULL != session) ? ats2 : ats,
397 (NULL != session) ? ats_count + 1 : ats_count);
398 GNUNET_break (NULL != ar);
399 ai = GNUNET_new (struct AddressInfo);
400 ai->address = GNUNET_HELLO_address_copy (address);
401 ai->session = session;
403 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
406 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
407 publish_p2a_stat_update ();
412 * Notify ATS about the new address including the network this address is
413 * located in. The address must NOT be inbound and must be new to ATS.
415 * @param address the address
416 * @param ats ats information
417 * @param ats_count number of @a ats information
420 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
421 const struct GNUNET_ATS_Information *ats,
424 struct GNUNET_ATS_AddressRecord *ar;
425 struct AddressInfo *ai;
427 /* valid new address, let ATS know! */
428 if (NULL == address->transport_name)
433 GNUNET_assert (GNUNET_YES !=
434 GNUNET_HELLO_address_check_option (address,
435 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
436 ai = find_ai_no_session (address);
437 GNUNET_assert (NULL == ai);
438 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
439 "Notifying ATS about peer `%s''s new address `%s'\n",
440 GNUNET_i2s (&address->peer),
441 (0 == address->address_length)
443 : GST_plugins_a2s (address));
444 ar = GNUNET_ATS_address_add (GST_ats,
449 GNUNET_break (NULL != ar);
450 ai = GNUNET_new (struct AddressInfo);
451 ai->address = GNUNET_HELLO_address_copy (address);
453 (void) GNUNET_CONTAINER_multipeermap_put (p2a,
456 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
457 publish_p2a_stat_update ();
462 * Notify ATS about a new session now existing for the given
465 * @param address the address
466 * @param session the session
469 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
470 struct Session *session)
472 struct AddressInfo *ai;
474 ai = find_ai (address, NULL);
477 /* We may already be aware of the session, even if some other part
478 of the code could not tell if it just created a new session or
479 just got one recycled from the plugin; hence, we may be called
480 with "new" session even for an "old" session; in that case,
481 check that this is the case, but just ignore it. */
482 GNUNET_assert (NULL != (find_ai (address, session)));
485 GNUNET_break (NULL == ai->session);
486 ai->session = session;
487 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
489 "Telling ATS about new session %p for peer %s\n",
491 GNUNET_i2s (&address->peer));
493 GNUNET_ATS_address_add_session (ai->ar,
499 * Notify ATS that the session (but not the address) of
500 * a given address is no longer relevant.
502 * @param address the address
503 * @param session the session
506 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
507 struct Session *session)
509 struct AddressInfo *ai;
516 ai = find_ai (address, session);
519 /* We sometimes create sessions just for sending a PING,
520 and if those are destroyed they were never known to
521 ATS which means we end up here (however, in this
522 case, the address must be an outbound address). */
523 GNUNET_break (GNUNET_YES !=
524 GNUNET_HELLO_address_check_option (address,
525 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
529 GNUNET_assert (session == ai->session);
531 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
533 "Telling ATS to destroy session %p from peer %s\n",
535 GNUNET_i2s (&address->peer));
538 /* If ATS doesn't know about the address/session, and this
539 was an inbound session that expired, then we must forget
540 about the address as well. Otherwise, we are done as
541 we have set `ai->session` to NULL already. */
543 GNUNET_HELLO_address_check_option (address,
544 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
545 GST_ats_expire_address (address);
549 GNUNET_ATS_address_del_session (ai->ar, session))
552 GST_ats_expire_address (address);
558 * Notify ATS about property changes to an address.
560 * @param address our information about the address
561 * @param session the session
562 * @param ats performance information
563 * @param ats_count number of elements in @a ats
566 GST_ats_update_metrics (const struct GNUNET_HELLO_Address *address,
567 struct Session *session,
568 const struct GNUNET_ATS_Information *ats,
571 struct GNUNET_ATS_Information *ats_new;
572 struct AddressInfo *ai;
574 ai = find_ai (address, session);
577 /* We sometimes create sessions just for sending a PING,
578 and if we get metrics for those, they were never known to
579 ATS which means we end up here (however, in this
580 case, the address must be an outbound address). */
581 GNUNET_break (GNUNET_YES !=
582 GNUNET_HELLO_address_check_option (address,
583 GNUNET_HELLO_ADDRESS_INFO_INBOUND));
586 /* Call to manipulation to manipulate ATS information */
587 GNUNET_assert (NULL != GST_ats);
588 if ((NULL == ats) || (0 == ats_count))
590 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
591 "Updating metrics for peer `%s' address %s session %p\n",
592 GNUNET_i2s (&address->peer),
593 GST_plugins_a2s (address),
595 ats_new = GST_manipulation_manipulate_metrics (address,
600 GNUNET_ATS_address_update (ai->ar,
603 GNUNET_free_non_null (ats_new);
608 * Notify ATS that the address has expired and thus cannot
609 * be used any longer. This function must only be called
610 * if the corresponding session is already gone.
612 * @param address the address
615 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
617 struct AddressInfo *ai;
619 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
620 "Address %s of peer %s expired\n",
621 GST_plugins_a2s (address),
622 GNUNET_i2s (&address->peer));
623 ai = find_ai_no_session (address);
629 GNUNET_assert (GNUNET_YES ==
630 GNUNET_CONTAINER_multipeermap_remove (p2a,
633 publish_p2a_stat_update ();
634 GNUNET_break (NULL == ai->session);
635 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
637 "Telling ATS to destroy address from peer %s\n",
638 GNUNET_i2s (&address->peer));
641 /* We usually should not have a session here when we
642 expire an address, but during shutdown a session
643 may be active while validation causes the address
644 to 'expire'. So clean up both if necessary. */
645 if ( (NULL == ai->session) ||
647 GNUNET_ATS_address_del_session (ai->ar,
649 GNUNET_ATS_address_destroy (ai->ar);
652 if (NULL != ai->unblock_task)
654 GNUNET_SCHEDULER_cancel (ai->unblock_task);
655 ai->unblock_task = NULL;
657 GNUNET_HELLO_address_free (ai->address);
663 * Initialize ATS subsystem.
668 p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
673 * Release memory used by the given address data.
676 * @param key which peer is this about
677 * @param value the `struct AddressInfo`
678 * @return #GNUNET_OK (continue to iterate)
681 destroy_ai (void *cls,
682 const struct GNUNET_PeerIdentity *key,
685 struct AddressInfo *ai = value;
687 GNUNET_assert (GNUNET_YES ==
688 GNUNET_CONTAINER_multipeermap_remove (p2a,
691 if (NULL != ai->unblock_task)
693 GNUNET_SCHEDULER_cancel (ai->unblock_task);
694 ai->unblock_task = NULL;
698 GNUNET_ATS_address_destroy (ai->ar);
701 GNUNET_HELLO_address_free (ai->address);
708 * Shutdown ATS subsystem.
713 GNUNET_CONTAINER_multipeermap_iterate (p2a,
716 publish_p2a_stat_update ();
717 GNUNET_CONTAINER_multipeermap_destroy (p2a);
721 /* end of gnunet-service-transport_ats.c */