-simplify logic
[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  * Number of blocked addresses.
94  */
95 static unsigned int num_blocked;
96
97 /**
98  * Closure for #find_ai().
99  */
100 struct FindClosure
101 {
102
103   /**
104    * Session to look for (only used if the address is inbound).
105    */
106   struct Session *session;
107
108   /**
109    * Address to look for.
110    */
111   const struct GNUNET_HELLO_Address *address;
112
113   /**
114    * Where to store the result.
115    */
116   struct AddressInfo *ret;
117
118 };
119
120
121 /**
122  * Provide an update on the `p2a` map size to statistics.
123  * This function should be called whenever the `p2a` map
124  * is changed.
125  */
126 static void
127 publish_p2a_stat_update ()
128 {
129   GNUNET_STATISTICS_set (GST_stats,
130                          gettext_noop ("# Addresses given to ATS"),
131                          GNUNET_CONTAINER_multipeermap_size (p2a) - num_blocked,
132                          GNUNET_NO);
133   GNUNET_STATISTICS_set (GST_stats,
134                          "# blocked addresses",
135                          num_blocked,
136                          GNUNET_NO);
137 }
138
139
140 /**
141  * Find matching address info.
142  *
143  * @param cls the `struct FindClosure`
144  * @param key which peer is this about
145  * @param value the `struct AddressInfo`
146  * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
147  */
148 static int
149 find_ai_cb (void *cls,
150             const struct GNUNET_PeerIdentity *key,
151             void *value)
152 {
153   struct FindClosure *fc = cls;
154   struct AddressInfo *ai = value;
155
156   if ( (0 ==
157         GNUNET_HELLO_address_cmp (fc->address,
158                                   ai->address) ) &&
159        (fc->session == ai->session) )
160   {
161     fc->ret = ai;
162     return GNUNET_NO;
163   }
164   GNUNET_assert ( (fc->session != ai->session) ||
165                   (NULL == ai->session) );
166   return GNUNET_YES;
167 }
168
169
170 /**
171  * Find the address information struct for the
172  * given address and session.
173  *
174  * @param address address to look for
175  * @param session session to match for inbound connections
176  * @return NULL if this combination is unknown
177  */
178 static struct AddressInfo *
179 find_ai (const struct GNUNET_HELLO_Address *address,
180          struct Session *session)
181 {
182   struct FindClosure fc;
183
184   fc.address = address;
185   fc.session = session;
186   fc.ret = NULL;
187   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
188                                               &address->peer,
189                                               &find_ai_cb,
190                                               &fc);
191   return fc.ret;
192 }
193
194
195 /**
196  * Find matching address info, ignoring sessions.
197  *
198  * @param cls the `struct FindClosure`
199  * @param key which peer is this about
200  * @param value the `struct AddressInfo`
201  * @return #GNUNET_YES to continue to iterate, #GNUNET_NO if we found the value
202  */
203 static int
204 find_ai_no_session_cb (void *cls,
205                        const struct GNUNET_PeerIdentity *key,
206                        void *value)
207 {
208   struct FindClosure *fc = cls;
209   struct AddressInfo *ai = value;
210
211   if (0 ==
212       GNUNET_HELLO_address_cmp (fc->address,
213                                 ai->address))
214   {
215     fc->ret = ai;
216     return GNUNET_NO;
217   }
218   return GNUNET_YES;
219 }
220
221
222 /**
223  * Find the address information struct for the
224  * given address (ignoring sessions)
225  *
226  * @param address address to look for
227  * @return NULL if this combination is unknown
228  */
229 static struct AddressInfo *
230 find_ai_no_session (const struct GNUNET_HELLO_Address *address)
231 {
232   struct FindClosure fc;
233
234   fc.address = address;
235   fc.session = NULL;
236   fc.ret = NULL;
237   GNUNET_CONTAINER_multipeermap_get_multiple (p2a,
238                                               &address->peer,
239                                               &find_ai_no_session_cb,
240                                               &fc);
241   return fc.ret;
242 }
243
244
245 /**
246  * Test if ATS knows about this address.
247  *
248  * @param address the address
249  * @param session the session
250  * @return #GNUNET_YES if address is known, #GNUNET_NO if not.
251  */
252 int
253 GST_ats_is_known (const struct GNUNET_HELLO_Address *address,
254                   struct Session *session)
255 {
256   return (NULL != find_ai (address, session)) ? GNUNET_YES : GNUNET_NO;
257 }
258
259
260 /**
261  * The blocking time for an address has expired, allow ATS to
262  * suggest it again.
263  *
264  * @param cls the `struct AddressInfo` of the address to unblock
265  * @param tc unused
266  */
267 static void
268 unblock_address (void *cls,
269                  const struct GNUNET_SCHEDULER_TaskContext *tc)
270 {
271   struct AddressInfo *ai = cls;
272
273   ai->unblock_task = NULL;
274   LOG (GNUNET_ERROR_TYPE_DEBUG,
275        "Unblocking address %s of peer %s\n",
276        GST_plugins_a2s (ai->address),
277        GNUNET_i2s (&ai->address->peer));
278   ai->ar = GNUNET_ATS_address_add (GST_ats,
279                                    ai->address,
280                                    ai->session,
281                                    &ai->properties);
282   GNUNET_break (NULL != ai->ar);
283   num_blocked--;
284   publish_p2a_stat_update ();
285 }
286
287
288 /**
289  * Temporarily block a valid address for use by ATS for address
290  * suggestions.  This function should be called if an address was
291  * suggested by ATS but failed to perform (i.e. failure to establish a
292  * session or to exchange the PING/PONG).
293  *
294  * @param address the address to block
295  * @param session the session (can be NULL)
296  */
297 void
298 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
299                        struct Session *session)
300 {
301   struct AddressInfo *ai;
302
303   ai = find_ai (address, session);
304   if (NULL == ai)
305   {
306     GNUNET_assert (0);
307     return;
308   }
309   if (NULL == ai->ar)
310   {
311     /* already blocked, how did it get used!? */
312     GNUNET_break (0);
313     return;
314   }
315   ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
316   if (GNUNET_YES ==
317       GNUNET_HELLO_address_check_option (address,
318                                          GNUNET_HELLO_ADDRESS_INFO_INBOUND))
319     LOG (GNUNET_ERROR_TYPE_DEBUG,
320          "Removing address %s of peer %s from use (inbound died)\n",
321          GST_plugins_a2s (address),
322          GNUNET_i2s (&address->peer));
323   else
324     LOG (GNUNET_ERROR_TYPE_INFO,
325          "Blocking address %s of peer %s from use for %s\n",
326          GST_plugins_a2s (address),
327          GNUNET_i2s (&address->peer),
328          GNUNET_STRINGS_relative_time_to_string (ai->back_off,
329                                                  GNUNET_YES));
330   /* destroy session and address */
331   if ( (NULL == session) ||
332        (GNUNET_NO ==
333         GNUNET_ATS_address_del_session (ai->ar, session)) )
334     GNUNET_ATS_address_destroy (ai->ar);
335   ai->ar = NULL;
336
337   /* determine when the address should come back to life */
338   ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
339   ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
340                                                    &unblock_address,
341                                                    ai);
342   num_blocked++;
343   publish_p2a_stat_update ();
344 }
345
346
347 /**
348  * Reset address blocking time.  Resets the exponential
349  * back-off timer for this address to zero.  Done when
350  * an address was used to create a successful connection.
351  *
352  * @param address the address to reset the blocking timer
353  * @param session the session (can be NULL)
354  */
355 void
356 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
357                      struct Session *session)
358 {
359   struct AddressInfo *ai;
360
361   ai = find_ai (address, session);
362   if (NULL == ai)
363   {
364     GNUNET_break (0);
365     return;
366   }
367   /* address is in successful use, so it should not be blocked right now */
368   GNUNET_break (NULL == ai->unblock_task);
369   ai->back_off = GNUNET_TIME_UNIT_ZERO;
370 }
371
372
373 /**
374  * Notify ATS about the a new inbound address. We may already
375  * know the address (as this is called each time we receive
376  * a message from an inbound connection).  If the address is
377  * indeed new, make it available to ATS.
378  *
379  * @param address the address
380  * @param session the session
381  * @param prop performance information
382  */
383 void
384 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
385                              struct Session *session,
386                              const struct GNUNET_ATS_Properties *prop)
387 {
388   struct GNUNET_ATS_AddressRecord *ar;
389   struct AddressInfo *ai;
390
391   /* valid new address, let ATS know! */
392   if (NULL == address->transport_name)
393   {
394     GNUNET_break(0);
395     return;
396   }
397   GNUNET_assert (GNUNET_YES ==
398                  GNUNET_HELLO_address_check_option (address,
399                                                     GNUNET_HELLO_ADDRESS_INFO_INBOUND));
400   GNUNET_assert (NULL != session);
401   ai = find_ai (address, session);
402   if (NULL != ai)
403   {
404     /* This should only be called for new sessions, and thus
405        we should not already have the address */
406     GNUNET_break (0);
407     return;
408   }
409   GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
410   LOG (GNUNET_ERROR_TYPE_DEBUG,
411        "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
412        GNUNET_i2s (&address->peer),
413        (0 == address->address_length)
414        ? "<inbound>"
415        : GST_plugins_a2s (address),
416        session,
417        GNUNET_ATS_print_network_type (prop->scope));
418   ar = GNUNET_ATS_address_add (GST_ats,
419                                address,
420                                session,
421                                prop);
422   GNUNET_break (NULL != ar);
423   ai = GNUNET_new (struct AddressInfo);
424   ai->address = GNUNET_HELLO_address_copy (address);
425   ai->session = session;
426   ai->properties = *prop;
427   ai->ar = ar;
428   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
429                                             &ai->address->peer,
430                                             ai,
431                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
432   publish_p2a_stat_update ();
433 }
434
435
436 /**
437  * Notify ATS about the new address including the network this address is
438  * located in.  The address must NOT be inbound and must be new to ATS.
439  *
440  * @param address the address
441  * @param prop performance information
442  */
443 void
444 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
445                      const struct GNUNET_ATS_Properties *prop)
446 {
447   struct GNUNET_ATS_AddressRecord *ar;
448   struct AddressInfo *ai;
449
450   /* valid new address, let ATS know! */
451   if (NULL == address->transport_name)
452   {
453     GNUNET_break(0);
454     return;
455   }
456   GNUNET_assert (GNUNET_YES !=
457                  GNUNET_HELLO_address_check_option (address,
458                                                     GNUNET_HELLO_ADDRESS_INFO_INBOUND));
459   ai = find_ai_no_session (address);
460   GNUNET_assert (NULL == ai);
461   LOG (GNUNET_ERROR_TYPE_INFO,
462        "Notifying ATS about peer `%s''s new address `%s'\n",
463        GNUNET_i2s (&address->peer),
464        (0 == address->address_length)
465        ? "<inbound>"
466        : GST_plugins_a2s (address));
467   ar = GNUNET_ATS_address_add (GST_ats,
468                                address,
469                                NULL,
470                                prop);
471   GNUNET_break (NULL != ar);
472   ai = GNUNET_new (struct AddressInfo);
473   ai->address = GNUNET_HELLO_address_copy (address);
474   ai->ar = ar;
475   ai->properties = *prop;
476   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
477                                             &ai->address->peer,
478                                             ai,
479                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
480   publish_p2a_stat_update ();
481 }
482
483
484 /**
485  * Notify ATS about a new session now existing for the given
486  * address.
487  *
488  * @param address the address
489  * @param session the session
490  */
491 void
492 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
493                      struct Session *session)
494 {
495   struct AddressInfo *ai;
496
497   ai = find_ai (address, NULL);
498   if (NULL == ai)
499   {
500     /* We may already be aware of the session, even if some other part
501        of the code could not tell if it just created a new session or
502        just got one recycled from the plugin; hence, we may be called
503        with "new" session even for an "old" session; in that case,
504        check that this is the case, but just ignore it. */
505     GNUNET_assert (NULL != (find_ai (address, session)));
506     return;
507   }
508   GNUNET_break (NULL == ai->session);
509   ai->session = session;
510   LOG (GNUNET_ERROR_TYPE_DEBUG,
511        "Telling ATS about new session for peer %s\n",
512        GNUNET_i2s (&address->peer));
513   if (NULL != ai->ar)
514     GNUNET_ATS_address_add_session (ai->ar,
515                                     session);
516 }
517
518
519 /**
520  * Notify ATS that the session (but not the address) of
521  * a given address is no longer relevant.
522  *
523  * @param address the address
524  * @param session the session
525  */
526 void
527 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
528                      struct Session *session)
529 {
530   struct AddressInfo *ai;
531
532   if (NULL == session)
533   {
534     GNUNET_break (0);
535     return;
536   }
537   ai = find_ai (address, session);
538   if (NULL == ai)
539   {
540     /* We sometimes create sessions just for sending a PING,
541        and if those are destroyed they were never known to
542        ATS which means we end up here (however, in this
543        case, the address must be an outbound address). */
544     GNUNET_break (GNUNET_YES !=
545                   GNUNET_HELLO_address_check_option (address,
546                                                      GNUNET_HELLO_ADDRESS_INFO_INBOUND));
547
548     return;
549   }
550   GNUNET_assert (session == ai->session);
551   ai->session = NULL;
552   LOG (GNUNET_ERROR_TYPE_DEBUG,
553        "Telling ATS to destroy session %p from peer %s\n",
554        session,
555        GNUNET_i2s (&address->peer));
556   if (NULL == ai->ar)
557   {
558     /* If ATS doesn't know about the address/session, and this
559        was an inbound session that expired, then we must forget
560        about the address as well.  Otherwise, we are done as
561        we have set `ai->session` to NULL already. */
562     if (GNUNET_YES ==
563         GNUNET_HELLO_address_check_option (address,
564                                            GNUNET_HELLO_ADDRESS_INFO_INBOUND))
565       GST_ats_expire_address (address);
566     return;
567   }
568   if (GNUNET_YES ==
569       GNUNET_ATS_address_del_session (ai->ar, session))
570   {
571     ai->ar = NULL;
572     GST_ats_expire_address (address);
573   }
574 }
575
576
577 /**
578  * Notify ATS about DV distance change to an address's.
579  *
580  * @param address the address
581  * @param distance new distance value
582  */
583 void
584 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
585                          uint32_t distance)
586 {
587   struct AddressInfo *ai;
588
589   ai = find_ai_no_session (address);
590   if (NULL == ai)
591     return;
592   LOG (GNUNET_ERROR_TYPE_DEBUG,
593        "Updated distance for peer `%s' to %u\n",
594        GNUNET_i2s (&address->peer),
595        distance);
596   ai->properties.distance = distance;
597   GST_manipulation_manipulate_metrics (address,
598                                        ai->session,
599                                        &ai->properties);
600   if (NULL != ai->ar)
601     GNUNET_ATS_address_update (ai->ar,
602                                &ai->properties);
603 }
604
605
606 /**
607  * Notify ATS about property changes to an address's properties.
608  *
609  * @param address the address
610  * @param delay new delay value
611  */
612 void
613 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
614                       struct GNUNET_TIME_Relative delay)
615 {
616   struct AddressInfo *ai;
617
618   ai = find_ai_no_session (address);
619   if (NULL == ai)
620     return;
621   LOG (GNUNET_ERROR_TYPE_DEBUG,
622        "Updated latency for peer `%s' to %s\n",
623        GNUNET_i2s (&address->peer),
624        GNUNET_STRINGS_relative_time_to_string (delay,
625                                                GNUNET_YES));
626   ai->properties.delay = delay;
627   GST_manipulation_manipulate_metrics (address,
628                                        ai->session,
629                                        &ai->properties);
630   if (NULL != ai->ar)
631     GNUNET_ATS_address_update (ai->ar,
632                                &ai->properties);
633 }
634
635
636 /**
637  * Notify ATS about utilization changes to an address.
638  *
639  * @param address our information about the address
640  * @param bps_in new utilization inbound
641  * @param bps_out new utilization outbound
642  */
643 void
644 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
645                             uint32_t bps_in,
646                             uint32_t bps_out)
647 {
648   struct AddressInfo *ai;
649
650   ai = find_ai_no_session (address);
651   if (NULL == ai)
652     return;
653   LOG (GNUNET_ERROR_TYPE_DEBUG,
654        "Updating utilization for peer `%s' address %s: %u/%u\n",
655        GNUNET_i2s (&address->peer),
656        GST_plugins_a2s (address),
657        (unsigned int) bps_in,
658        (unsigned int) bps_out);
659   ai->properties.utilization_in = bps_in;
660   ai->properties.utilization_out = bps_out;
661   GST_manipulation_manipulate_metrics (address,
662                                        ai->session,
663                                        &ai->properties);
664   if (NULL != ai->ar)
665     GNUNET_ATS_address_update (ai->ar,
666                                &ai->properties);
667 }
668
669
670 /**
671  * Notify ATS that the address has expired and thus cannot
672  * be used any longer.  This function must only be called
673  * if the corresponding session is already gone.
674  *
675  * @param address the address
676  */
677 void
678 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
679 {
680   struct AddressInfo *ai;
681
682   LOG (GNUNET_ERROR_TYPE_DEBUG,
683        "Address %s of peer %s expired\n",
684        GST_plugins_a2s (address),
685        GNUNET_i2s (&address->peer));
686   ai = find_ai_no_session (address);
687   if (NULL == ai)
688   {
689     GNUNET_assert (0);
690     return;
691   }
692   GNUNET_assert (GNUNET_YES ==
693                  GNUNET_CONTAINER_multipeermap_remove (p2a,
694                                                        &address->peer,
695                                                        ai));
696   GNUNET_break (NULL == ai->session);
697   LOG (GNUNET_ERROR_TYPE_DEBUG,
698        "Telling ATS to destroy address from peer %s\n",
699        GNUNET_i2s (&address->peer));
700   if (NULL != ai->ar)
701   {
702     /* We usually should not have a session here when we
703        expire an address, but during shutdown a session
704        may be active while validation causes the address
705        to 'expire'.  So clean up both if necessary. */
706     if ( (NULL == ai->session) ||
707          (GNUNET_NO ==
708           GNUNET_ATS_address_del_session (ai->ar,
709                                           ai->session)) )
710       GNUNET_ATS_address_destroy (ai->ar);
711     ai->ar = NULL;
712   }
713   if (NULL != ai->unblock_task)
714   {
715     GNUNET_SCHEDULER_cancel (ai->unblock_task);
716     ai->unblock_task = NULL;
717     num_blocked--;
718   }
719   publish_p2a_stat_update ();
720   GNUNET_HELLO_address_free (ai->address);
721   GNUNET_free (ai);
722 }
723
724
725 /**
726  * Initialize ATS subsystem.
727  */
728 void
729 GST_ats_init ()
730 {
731   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
732 }
733
734
735 /**
736  * Release memory used by the given address data.
737  *
738  * @param cls NULL
739  * @param key which peer is this about
740  * @param value the `struct AddressInfo`
741  * @return #GNUNET_OK (continue to iterate)
742  */
743 static int
744 destroy_ai (void *cls,
745             const struct GNUNET_PeerIdentity *key,
746             void *value)
747 {
748   struct AddressInfo *ai = value;
749
750   GNUNET_assert (GNUNET_YES ==
751                  GNUNET_CONTAINER_multipeermap_remove (p2a,
752                                                        key,
753                                                        ai));
754   if (NULL != ai->unblock_task)
755   {
756     GNUNET_SCHEDULER_cancel (ai->unblock_task);
757     ai->unblock_task = NULL;
758     num_blocked--;
759   }
760   if (NULL != ai->ar)
761   {
762     GNUNET_ATS_address_destroy (ai->ar);
763     ai->ar = NULL;
764   }
765   GNUNET_HELLO_address_free (ai->address);
766   GNUNET_free (ai);
767   return GNUNET_OK;
768 }
769
770
771 /**
772  * Shutdown ATS subsystem.
773  */
774 void
775 GST_ats_done ()
776 {
777   GNUNET_CONTAINER_multipeermap_iterate (p2a,
778                                          &destroy_ai,
779                                          NULL);
780   publish_p2a_stat_update ();
781   GNUNET_CONTAINER_multipeermap_destroy (p2a);
782   p2a = NULL;
783 }
784
785 /* end of gnunet-service-transport_ats.c */