3976ae3d94ee64ab07b0d073b701404e06b9ad63
[oweals/gnunet.git] / src / transport / gnunet-service-transport_ats.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2015 Christian Grothoff (and other contributing authors)
4
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.
9
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.
14
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.
19 */
20 /**
21  * @file transport/gnunet-service-transport_ats.h
22  * @brief interfacing between transport and ATS service
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
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"
31
32 #define LOG(kind,...) GNUNET_log_from(kind, "transport-ats", __VA_ARGS__)
33
34
35 /**
36  * Information we track for each address known to ATS.
37  */
38 struct AddressInfo
39 {
40
41   /**
42    * The address (with peer identity).
43    */
44   struct GNUNET_HELLO_Address *address;
45
46   /**
47    * Session (can be NULL)
48    */
49   struct Session *session;
50
51   /**
52    * Record with ATS API for the address.
53    */
54   struct GNUNET_ATS_AddressRecord *ar;
55
56   /**
57    * Performance properties of this address.
58    */
59   struct GNUNET_ATS_Properties properties;
60
61   /**
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
66    * validated.
67    */
68   struct GNUNET_TIME_Absolute blocked;
69
70   /**
71    * If an address is blocked as part of an exponential back-off,
72    * we track the current size of the backoff here.
73    */
74   struct GNUNET_TIME_Relative back_off;
75
76   /**
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).
80    */
81   struct GNUNET_SCHEDULER_Task *unblock_task;
82
83 };
84
85
86 /**
87  * Map from peer identities to one or more `struct AddressInfo` values
88  * for the peer.
89  */
90 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
91
92
93 /**
94  * Closure for #find_ai().
95  */
96 struct FindClosure
97 {
98
99   /**
100    * Session to look for (only used if the address is inbound).
101    */
102   struct Session *session;
103
104   /**
105    * Address to look for.
106    */
107   const struct GNUNET_HELLO_Address *address;
108
109   /**
110    * Where to store the result.
111    */
112   struct AddressInfo *ret;
113
114 };
115
116
117 /**
118  * Provide an update on the `p2a` map size to statistics.
119  * This function should be called whenever the `p2a` map
120  * is changed.
121  */
122 static void
123 publish_p2a_stat_update ()
124 {
125   GNUNET_STATISTICS_set (GST_stats,
126                          gettext_noop ("# Addresses given to ATS"),
127                          GNUNET_CONTAINER_multipeermap_size (p2a),
128                          GNUNET_NO);
129 }
130
131
132 /**
133  * Find matching address info.
134  *
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
139  */
140 static int
141 find_ai_cb (void *cls,
142             const struct GNUNET_PeerIdentity *key,
143             void *value)
144 {
145   struct FindClosure *fc = cls;
146   struct AddressInfo *ai = value;
147
148   if ( (0 ==
149         GNUNET_HELLO_address_cmp (fc->address,
150                                   ai->address) ) &&
151        (fc->session == ai->session) )
152   {
153     fc->ret = ai;
154     return GNUNET_NO;
155   }
156   GNUNET_assert ( (fc->session != ai->session) ||
157                   (NULL == ai->session) );
158   return GNUNET_YES;
159 }
160
161
162 /**
163  * Find the address information struct for the
164  * given address and session.
165  *
166  * @param address address to look for
167  * @param session session to match for inbound connections
168  * @return NULL if this combination is unknown
169  */
170 static struct AddressInfo *
171 find_ai (const struct GNUNET_HELLO_Address *address,
172          struct Session *session)
173 {
174   struct FindClosure fc;
175
176   fc.address = address;
177   fc.session = session;
178   fc.ret = NULL;
179   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
180                                               &address->peer,
181                                               &find_ai_cb,
182                                               &fc);
183   return fc.ret;
184 }
185
186
187 /**
188  * Find matching address info, ignoring sessions.
189  *
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
194  */
195 static int
196 find_ai_no_session_cb (void *cls,
197                        const struct GNUNET_PeerIdentity *key,
198                        void *value)
199 {
200   struct FindClosure *fc = cls;
201   struct AddressInfo *ai = value;
202
203   if (0 ==
204       GNUNET_HELLO_address_cmp (fc->address,
205                                 ai->address))
206   {
207     fc->ret = ai;
208     return GNUNET_NO;
209   }
210   return GNUNET_YES;
211 }
212
213
214 /**
215  * Find the address information struct for the
216  * given address (ignoring sessions)
217  *
218  * @param address address to look for
219  * @return NULL if this combination is unknown
220  */
221 static struct AddressInfo *
222 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
223 {
224   struct FindClosure fc;
225
226   fc.address = address;
227   fc.session = NULL;
228   fc.ret = NULL;
229   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
230                                               &address->peer,
231                                               &find_ai_no_session_cb,
232                                               &fc);
233   return fc.ret;
234 }
235
236
237 /**
238  * Test if ATS knows about this address.
239  *
240  * @param address the address
241  * @param session the session
242  * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
243  */
244 int
245 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
246                   struct Session *session)
247 {
248   return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
249 }
250
251
252 /**
253  * The blocking time for an address has expired, allow ATS to
254  * suggest it again.
255  *
256  * @param cls the `struct AddressInfo` of the address to unblock
257  * @param tc unused
258  */
259 static void
260 unblock_address (void *cls,
261                  const struct GNUNET_SCHEDULER_TaskContext *tc)
262 {
263   struct AddressInfo *ai = cls;
264
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,
271                                    ai->address,
272                                    ai->session,
273                                    &ai->properties);
274   GNUNET_break (NULL != ai->ar);
275 }
276
277
278 /**
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).
283  *
284  * @param address the address to block
285  * @param session the session (can be NULL)
286  */
287 void
288 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
289                        struct Session *session)
290 {
291   struct AddressInfo *ai;
292
293   ai = find_ai (address, session);
294   if (NULL == ai)
295   {
296     GNUNET_break (0);
297     return;
298   }
299   if (NULL == ai->ar)
300   {
301     /* already blocked, how did it get used!? */
302     GNUNET_break (0);
303     return;
304   }
305   if (GNUNET_YES ==
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));
312   else
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) ||
319        (GNUNET_NO ==
320         GNUNET_ATS_address_del_session (ai->ar, session)) )
321     GNUNET_ATS_address_destroy (ai->ar);
322   ai->ar = NULL;
323
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,
328                                                    &unblock_address,
329                                                    ai);
330 }
331
332
333 /**
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.
338  *
339  * @param address the address
340  * @param session the session
341  * @param prop performance information
342  */
343 void
344 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
345                              struct Session *session,
346                              const struct GNUNET_ATS_Properties *prop)
347 {
348   struct GNUNET_ATS_AddressRecord *ar;
349   struct AddressInfo *ai;
350
351   /* valid new address, let ATS know! */
352   if (NULL == address->transport_name)
353   {
354     GNUNET_break(0);
355     return;
356   }
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);
362   if (NULL != ai)
363   {
364     /* This should only be called for new sessions, and thus
365        we should not already have the address */
366     GNUNET_break (0);
367     return;
368   }
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)
374        ? "<inbound>"
375        : GST_plugins_a2s (address),
376        session,
377        GNUNET_ATS_print_network_type (prop->scope));
378   ar = GNUNET_ATS_address_add (GST_ats,
379                                address,
380                                session,
381                                prop);
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;
387   ai->ar = ar;
388   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
389                                             &ai->address->peer,
390                                             ai,
391                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
392   publish_p2a_stat_update ();
393 }
394
395
396 /**
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.
399  *
400  * @param address the address
401  * @param prop performance information
402  */
403 void
404 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
405                      const struct GNUNET_ATS_Properties *prop)
406 {
407   struct GNUNET_ATS_AddressRecord *ar;
408   struct AddressInfo *ai;
409
410   /* valid new address, let ATS know! */
411   if (NULL == address->transport_name)
412   {
413     GNUNET_break(0);
414     return;
415   }
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)
425        ? "<inbound>"
426        : GST_plugins_a2s (address));
427   ar = GNUNET_ATS_address_add (GST_ats,
428                                address,
429                                NULL,
430                                prop);
431   GNUNET_break (NULL != ar);
432   ai = GNUNET_new (struct AddressInfo);
433   ai->address = GNUNET_HELLO_address_copy (address);
434   ai->ar = ar;
435   ai->properties = *prop;
436   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
437                                             &ai->address->peer,
438                                             ai,
439                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
440   publish_p2a_stat_update ();
441 }
442
443
444 /**
445  * Notify ATS about a new session now existing for the given
446  * address.
447  *
448  * @param address the address
449  * @param session the session
450  */
451 void
452 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
453                      struct Session *session)
454 {
455   struct AddressInfo *ai;
456
457   ai = find_ai (address, NULL);
458   if (NULL == ai)
459   {
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)));
466     return;
467   }
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));
473   if (NULL != ai->ar)
474     GNUNET_ATS_address_add_session (ai->ar,
475                                     session);
476 }
477
478
479 /**
480  * Notify ATS that the session (but not the address) of
481  * a given address is no longer relevant.
482  *
483  * @param address the address
484  * @param session the session
485  */
486 void
487 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
488                      struct Session *session)
489 {
490   struct AddressInfo *ai;
491
492   if (NULL == session)
493   {
494     GNUNET_break (0);
495     return;
496   }
497   ai = find_ai (address, session);
498   if (NULL == ai)
499   {
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));
507
508     return;
509   }
510   GNUNET_assert (session == ai->session);
511   ai->session = NULL;
512   LOG (GNUNET_ERROR_TYPE_DEBUG,
513        "Telling ATS to destroy session %p from peer %s\n",
514        session,
515        GNUNET_i2s (&address->peer));
516   if (NULL == ai->ar)
517   {
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. */
522     if (GNUNET_YES ==
523         GNUNET_HELLO_address_check_option (address,
524                                            GNUNET_HELLO_ADDRESS_INFO_INBOUND))
525       GST_ats_expire_address (address);
526     return;
527   }
528   if (GNUNET_YES ==
529       GNUNET_ATS_address_del_session (ai->ar, session))
530   {
531     ai->ar = NULL;
532     GST_ats_expire_address (address);
533   }
534 }
535
536
537 /**
538  * Notify ATS about DV distance change to an address's.
539  *
540  * @param address the address
541  * @param distance new distance value
542  */
543 void
544 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
545                          uint32_t distance)
546 {
547   struct AddressInfo *ai;
548
549   ai = find_ai_no_session (address);
550   if (NULL == ai)
551     return;
552   LOG (GNUNET_ERROR_TYPE_DEBUG,
553        "Updated distance for peer `%s' to %u\n",
554        GNUNET_i2s (&address->peer),
555        distance);
556   ai->properties.distance = distance;
557   GST_manipulation_manipulate_metrics (address,
558                                        ai->session,
559                                        &ai->properties);
560   if (NULL != ai->ar)
561     GNUNET_ATS_address_update (ai->ar,
562                                &ai->properties);
563 }
564
565
566 /**
567  * Notify ATS about property changes to an address's properties.
568  *
569  * @param address the address
570  * @param delay new delay value
571  */
572 void
573 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
574                       struct GNUNET_TIME_Relative delay)
575 {
576   struct AddressInfo *ai;
577
578   ai = find_ai_no_session (address);
579   if (NULL == ai)
580     return;
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,
585                                                GNUNET_YES));
586   ai->properties.delay = delay;
587   GST_manipulation_manipulate_metrics (address,
588                                        ai->session,
589                                        &ai->properties);
590   if (NULL != ai->ar)
591     GNUNET_ATS_address_update (ai->ar,
592                                &ai->properties);
593 }
594
595
596 /**
597  * Notify ATS about utilization changes to an address.
598  *
599  * @param address our information about the address
600  * @param bps_in new utilization inbound
601  * @param bps_out new utilization outbound
602  */
603 void
604 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
605                             uint32_t bps_in,
606                             uint32_t bps_out)
607 {
608   struct AddressInfo *ai;
609
610   ai = find_ai_no_session (address);
611   if (NULL == ai)
612     return;
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,
622                                        ai->session,
623                                        &ai->properties);
624   if (NULL != ai->ar)
625     GNUNET_ATS_address_update (ai->ar,
626                                &ai->properties);
627 }
628
629
630 /**
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.
634  *
635  * @param address the address
636  */
637 void
638 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
639 {
640   struct AddressInfo *ai;
641
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);
647   if (NULL == ai)
648   {
649     GNUNET_assert (0);
650     return;
651   }
652   GNUNET_assert (GNUNET_YES ==
653                  GNUNET_CONTAINER_multipeermap_remove (p2a,
654                                                        &address->peer,
655                                                        ai));
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));
661   if (NULL != ai->ar)
662   {
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) ||
668          (GNUNET_NO ==
669           GNUNET_ATS_address_del_session (ai->ar,
670                                           ai->session)) )
671       GNUNET_ATS_address_destroy (ai->ar);
672     ai->ar = NULL;
673   }
674   if (NULL != ai->unblock_task)
675   {
676     GNUNET_SCHEDULER_cancel (ai->unblock_task);
677     ai->unblock_task = NULL;
678   }
679   GNUNET_HELLO_address_free (ai->address);
680   GNUNET_free (ai);
681 }
682
683
684 /**
685  * Initialize ATS subsystem.
686  */
687 void
688 GST_ats_init ()
689 {
690   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
691 }
692
693
694 /**
695  * Release memory used by the given address data.
696  *
697  * @param cls NULL
698  * @param key which peer is this about
699  * @param value the `struct AddressInfo`
700  * @return #GNUNET_OK (continue to iterate)
701  */
702 static int
703 destroy_ai (void *cls,
704             const struct GNUNET_PeerIdentity *key,
705             void *value)
706 {
707   struct AddressInfo *ai = value;
708
709   GNUNET_assert (GNUNET_YES ==
710                  GNUNET_CONTAINER_multipeermap_remove (p2a,
711                                                        key,
712                                                        ai));
713   if (NULL != ai->unblock_task)
714   {
715     GNUNET_SCHEDULER_cancel (ai->unblock_task);
716     ai->unblock_task = NULL;
717   }
718   if (NULL != ai->ar)
719   {
720     GNUNET_ATS_address_destroy (ai->ar);
721     ai->ar = NULL;
722   }
723   GNUNET_HELLO_address_free (ai->address);
724   GNUNET_free (ai);
725   return GNUNET_OK;
726 }
727
728
729 /**
730  * Shutdown ATS subsystem.
731  */
732 void
733 GST_ats_done ()
734 {
735   GNUNET_CONTAINER_multipeermap_iterate (p2a,
736                                          &destroy_ai,
737                                          NULL);
738   publish_p2a_stat_update ();
739   GNUNET_CONTAINER_multipeermap_destroy (p2a);
740   p2a = NULL;
741 }
742
743 /* end of gnunet-service-transport_ats.c */