613f35ec2509fb76e63072af1d5f892c2a1ae89e
[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
33 /**
34  * Information we track for each address known to ATS.
35  */
36 struct AddressInfo
37 {
38
39   /**
40    * The address (with peer identity).
41    */
42   struct GNUNET_HELLO_Address *address;
43
44   /**
45    * Session (can be NULL)
46    */
47   struct Session *session;
48
49   /**
50    * Record with ATS API for the address.
51    */
52   struct GNUNET_ATS_AddressRecord *ar;
53
54   /**
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
59    * validated.
60    */
61   struct GNUNET_TIME_Absolute blocked;
62
63   /**
64    * If an address is blocked as part of an exponential back-off,
65    * we track the current size of the backoff here.
66    */
67   struct GNUNET_TIME_Relative back_off;
68
69   /**
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).
73    */
74   struct GNUNET_SCHEDULER_Task *unblock_task;
75
76 };
77
78
79 /**
80  * Map from peer identities to one or more `struct AddressInfo` values
81  * for the peer.
82  */
83 static struct GNUNET_CONTAINER_MultiPeerMap *p2a;
84
85
86 /**
87  * Closure for #find_ai().
88  */
89 struct FindClosure
90 {
91
92   /**
93    * Session to look for (only used if the address is inbound).
94    */
95   struct Session *session;
96
97   /**
98    * Address to look for.
99    */
100   const struct GNUNET_HELLO_Address *address;
101
102   /**
103    * Where to store the result.
104    */
105   struct AddressInfo *ret;
106
107 };
108
109
110 /**
111  * Provide an update on the `p2a` map size to statistics.
112  * This function should be called whenever the `p2a` map
113  * is changed.
114  */
115 static void
116 publish_p2a_stat_update ()
117 {
118   GNUNET_STATISTICS_set (GST_stats,
119                          gettext_noop ("# Addresses given to ATS"),
120                          GNUNET_CONTAINER_multipeermap_size (p2a),
121                          GNUNET_NO);
122 }
123
124
125 /**
126  * Find matching address info.
127  *
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
132  */
133 static int
134 find_ai_cb (void *cls,
135             const struct GNUNET_PeerIdentity *key,
136             void *value)
137 {
138   struct FindClosure *fc = cls;
139   struct AddressInfo *ai = value;
140
141   if ( (0 ==
142         GNUNET_HELLO_address_cmp (fc->address,
143                                   ai->address) ) &&
144        (fc->session == ai->session) )
145   {
146     fc->ret = ai;
147     return GNUNET_NO;
148   }
149   GNUNET_assert ( (fc->session != ai->session) ||
150                   (NULL == ai->session) );
151   return GNUNET_YES;
152 }
153
154
155 /**
156  * Find the address information struct for the
157  * given address and session.
158  *
159  * @param address address to look for
160  * @param session session to match for inbound connections
161  * @return NULL if this combination is unknown
162  */
163 static struct AddressInfo *
164 find_ai (const struct GNUNET_HELLO_Address *address,
165          struct Session *session)
166 {
167   struct FindClosure fc;
168
169   fc.address = address;
170   fc.session = session;
171   fc.ret = NULL;
172   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
173                                               &address->peer,
174                                               &find_ai_cb,
175                                               &fc);
176   return fc.ret;
177 }
178
179
180 /**
181  * Find matching address info, ignoring sessions.
182  *
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
187  */
188 static int
189 find_ai_no_session_cb (void *cls,
190                        const struct GNUNET_PeerIdentity *key,
191                        void *value)
192 {
193   struct FindClosure *fc = cls;
194   struct AddressInfo *ai = value;
195
196   if (0 ==
197       GNUNET_HELLO_address_cmp (fc->address,
198                                 ai->address))
199   {
200     fc->ret = ai;
201     return GNUNET_NO;
202   }
203   return GNUNET_YES;
204 }
205
206
207 /**
208  * Find the address information struct for the
209  * given address (ignoring sessions)
210  *
211  * @param address address to look for
212  * @return NULL if this combination is unknown
213  */
214 static struct AddressInfo *
215 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
216 {
217   struct FindClosure fc;
218
219   fc.address = address;
220   fc.session = NULL;
221   fc.ret = NULL;
222   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
223                                               &address->peer,
224                                               &find_ai_no_session_cb,
225                                               &fc);
226   return fc.ret;
227 }
228
229
230 /**
231  * Test if ATS knows about this address.
232  *
233  * @param address the address
234  * @param session the session
235  * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
236  */
237 int
238 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
239                   struct Session *session)
240 {
241   return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
242 }
243
244
245 /**
246  * The blocking time for an address has expired, allow ATS to
247  * suggest it again.
248  *
249  * @param cls the `struct AddressInfo` of the address to unblock
250  * @param tc unused
251  */
252 static void
253 unblock_address (void *cls,
254                  const struct GNUNET_SCHEDULER_TaskContext *tc)
255 {
256   struct AddressInfo *ai = cls;
257
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,
264                                    ai->address,
265                                    ai->session,
266                                    NULL, 0);
267   GNUNET_break (NULL != ai->ar);
268   /* FIXME: should pass ATS information here! */
269 }
270
271
272 /**
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).
277  *
278  * @param address the address to block
279  * @param session the session (can be NULL)
280  */
281 void
282 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
283                        struct Session *session)
284 {
285   struct AddressInfo *ai;
286
287   ai = find_ai (address, session);
288   if (NULL == ai)
289   {
290     GNUNET_break (0);
291     return;
292   }
293   if (NULL == ai->ar)
294   {
295     /* already blocked, how did it get used!? */
296     GNUNET_break (0);
297     return;
298   }
299   if (GNUNET_YES ==
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));
306   else
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) ||
313        (GNUNET_NO ==
314         GNUNET_ATS_address_del_session (ai->ar, session)) )
315     GNUNET_ATS_address_destroy (ai->ar);
316   ai->ar = NULL;
317
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,
322                                                    &unblock_address,
323                                                    ai);
324 }
325
326
327 /**
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.
332  *
333  * @param address the address
334  * @param session the session
335  * @param ats ats information
336  * @param ats_count number of @a ats information
337  */
338 void
339 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
340                              struct Session *session,
341                              const struct GNUNET_ATS_Information *ats,
342                              uint32_t ats_count)
343 {
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;
348   uint32_t net;
349
350   /* valid new address, let ATS know! */
351   if (NULL == address->transport_name)
352   {
353     GNUNET_break(0);
354     return;
355   }
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);
361   if (NULL != ai)
362   {
363     /* This should only be called for new sessions, and thus
364        we should not already have the address */
365     GNUNET_break (0);
366     return;
367   }
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)
372   {
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);
378     return;
379   }
380   ats2[0].type = htonl (GNUNET_ATS_NETWORK_TYPE);
381   ats2[0].value = htonl (net);
382   memcpy (&ats2[1],
383           ats,
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)
389               ? "<inbound>"
390               : GST_plugins_a2s (address),
391               session,
392               GNUNET_ATS_print_network_type (net));
393   ar = GNUNET_ATS_address_add (GST_ats,
394                                address,
395                                session,
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;
402   ai->ar = ar;
403   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
404                                             &ai->address->peer,
405                                             ai,
406                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
407   publish_p2a_stat_update ();
408 }
409
410
411 /**
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.
414  *
415  * @param address the address
416  * @param ats ats information
417  * @param ats_count number of @a ats information
418  */
419 void
420 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
421                      const struct GNUNET_ATS_Information *ats,
422                      uint32_t ats_count)
423 {
424   struct GNUNET_ATS_AddressRecord *ar;
425   struct AddressInfo *ai;
426
427   /* valid new address, let ATS know! */
428   if (NULL == address->transport_name)
429   {
430     GNUNET_break(0);
431     return;
432   }
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)
442               ? "<inbound>"
443               : GST_plugins_a2s (address));
444   ar = GNUNET_ATS_address_add (GST_ats,
445                                address,
446                                NULL,
447                                ats,
448                                ats_count);
449   GNUNET_break (NULL != ar);
450   ai = GNUNET_new (struct AddressInfo);
451   ai->address = GNUNET_HELLO_address_copy (address);
452   ai->ar = ar;
453   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
454                                             &ai->address->peer,
455                                             ai,
456                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
457   publish_p2a_stat_update ();
458 }
459
460
461 /**
462  * Notify ATS about a new session now existing for the given
463  * address.
464  *
465  * @param address the address
466  * @param session the session
467  */
468 void
469 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
470                      struct Session *session)
471 {
472   struct AddressInfo *ai;
473
474   ai = find_ai (address, NULL);
475   if (NULL == ai)
476   {
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)));
483     return;
484   }
485   GNUNET_break (NULL == ai->session);
486   ai->session = session;
487   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
488                    "transport-ats",
489                    "Telling ATS about new session %p for peer %s\n",
490                    session,
491                    GNUNET_i2s (&address->peer));
492   if (NULL != ai->ar)
493     GNUNET_ATS_address_add_session (ai->ar,
494                                     session);
495 }
496
497
498 /**
499  * Notify ATS that the session (but not the address) of
500  * a given address is no longer relevant.
501  *
502  * @param address the address
503  * @param session the session
504  */
505 void
506 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
507                      struct Session *session)
508 {
509   struct AddressInfo *ai;
510
511   if (NULL == session)
512   {
513     GNUNET_break (0);
514     return;
515   }
516   ai = find_ai (address, session);
517   if (NULL == ai)
518   {
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));
526
527     return;
528   }
529   GNUNET_assert (session == ai->session);
530   ai->session = NULL;
531   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
532                    "transport-ats",
533                    "Telling ATS to destroy session %p from peer %s\n",
534                    session,
535                    GNUNET_i2s (&address->peer));
536   if (NULL == ai->ar)
537   {
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. */
542     if (GNUNET_YES ==
543         GNUNET_HELLO_address_check_option (address,
544                                            GNUNET_HELLO_ADDRESS_INFO_INBOUND))
545       GST_ats_expire_address (address);
546     return;
547   }
548   if (GNUNET_YES ==
549       GNUNET_ATS_address_del_session (ai->ar, session))
550   {
551     ai->ar = NULL;
552     GST_ats_expire_address (address);
553   }
554 }
555
556
557 /**
558  * Notify ATS about property changes to an address.
559  *
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
564  */
565 void
566 GST_ats_update_metrics (const struct GNUNET_HELLO_Address *address,
567                         struct Session *session,
568                         const struct GNUNET_ATS_Information *ats,
569                         uint32_t ats_count)
570 {
571   struct GNUNET_ATS_Information *ats_new;
572   struct AddressInfo *ai;
573
574   ai = find_ai (address, session);
575   if (NULL == ai)
576   {
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));
584     return;
585   }
586   /* Call to manipulation to manipulate ATS information */
587   GNUNET_assert (NULL != GST_ats);
588   if ((NULL == ats) || (0 == ats_count))
589     return;
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),
594               session);
595   ats_new = GST_manipulation_manipulate_metrics (address,
596                                                  session,
597                                                  ats,
598                                                  ats_count);
599   if (NULL != ai->ar)
600     GNUNET_ATS_address_update (ai->ar,
601                                ats_new,
602                                ats_count);
603   GNUNET_free_non_null (ats_new);
604 }
605
606
607 /**
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.
611  *
612  * @param address the address
613  */
614 void
615 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
616 {
617   struct AddressInfo *ai;
618
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);
624   if (NULL == ai)
625   {
626     GNUNET_assert (0);
627     return;
628   }
629   GNUNET_assert (GNUNET_YES ==
630                  GNUNET_CONTAINER_multipeermap_remove (p2a,
631                                                        &address->peer,
632                                                        ai));
633   publish_p2a_stat_update ();
634   GNUNET_break (NULL == ai->session);
635   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
636                    "transport-ats",
637                    "Telling ATS to destroy address from peer %s\n",
638                    GNUNET_i2s (&address->peer));
639   if (NULL != ai->ar)
640   {
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) ||
646          (GNUNET_NO ==
647           GNUNET_ATS_address_del_session (ai->ar,
648                                           ai->session)) )
649       GNUNET_ATS_address_destroy (ai->ar);
650     ai->ar = NULL;
651   }
652   if (NULL != ai->unblock_task)
653   {
654     GNUNET_SCHEDULER_cancel (ai->unblock_task);
655     ai->unblock_task = NULL;
656   }
657   GNUNET_HELLO_address_free (ai->address);
658   GNUNET_free (ai);
659 }
660
661
662 /**
663  * Initialize ATS subsystem.
664  */
665 void
666 GST_ats_init ()
667 {
668   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
669 }
670
671
672 /**
673  * Release memory used by the given address data.
674  *
675  * @param cls NULL
676  * @param key which peer is this about
677  * @param value the `struct AddressInfo`
678  * @return #GNUNET_OK (continue to iterate)
679  */
680 static int
681 destroy_ai (void *cls,
682             const struct GNUNET_PeerIdentity *key,
683             void *value)
684 {
685   struct AddressInfo *ai = value;
686
687   GNUNET_assert (GNUNET_YES ==
688                  GNUNET_CONTAINER_multipeermap_remove (p2a,
689                                                        key,
690                                                        ai));
691   if (NULL != ai->unblock_task)
692   {
693     GNUNET_SCHEDULER_cancel (ai->unblock_task);
694     ai->unblock_task = NULL;
695   }
696   if (NULL != ai->ar)
697   {
698     GNUNET_ATS_address_destroy (ai->ar);
699     ai->ar = NULL;
700   }
701   GNUNET_HELLO_address_free (ai->address);
702   GNUNET_free (ai);
703   return GNUNET_OK;
704 }
705
706
707 /**
708  * Shutdown ATS subsystem.
709  */
710 void
711 GST_ats_done ()
712 {
713   GNUNET_CONTAINER_multipeermap_iterate (p2a,
714                                          &destroy_ai,
715                                          NULL);
716   publish_p2a_stat_update ();
717   GNUNET_CONTAINER_multipeermap_destroy (p2a);
718   p2a = NULL;
719 }
720
721 /* end of gnunet-service-transport_ats.c */