-fixing some memory leaks from #3667, also reindentation and code cleanup
[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   /* FIXME: should pass ATS information here! */
268 }
269
270
271 /**
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).
276  *
277  * @param address the address to block
278  * @param session the session (can be NULL)
279  */
280 void
281 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
282                        struct Session *session)
283 {
284   struct AddressInfo *ai;
285
286   ai = find_ai (address, session);
287   if (NULL == ai)
288   {
289     GNUNET_break (0);
290     return;
291   }
292   if (NULL == ai->ar)
293   {
294     /* already blocked, how did it get used!? */
295     GNUNET_break (0);
296     return;
297   }
298   if (GNUNET_YES ==
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));
305   else
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) ||
312        (GNUNET_NO ==
313         GNUNET_ATS_address_del_session (ai->ar, session)) )
314     GNUNET_ATS_address_destroy (ai->ar);
315   ai->ar = NULL;
316
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,
321                                                    &unblock_address,
322                                                    ai);
323 }
324
325
326 /**
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.
331  *
332  * @param address the address
333  * @param session the session
334  * @param ats ats information
335  * @param ats_count number of @a ats information
336  */
337 void
338 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
339                              struct Session *session,
340                              const struct GNUNET_ATS_Information *ats,
341                              uint32_t ats_count)
342 {
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;
347   uint32_t net;
348
349   /* valid new address, let ATS know! */
350   if (NULL == address->transport_name)
351   {
352     GNUNET_break(0);
353     return;
354   }
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);
360   if (NULL != ai)
361   {
362     /* This should only be called for new sessions, and thus
363        we should not already have the address */
364     GNUNET_break (0);
365     return;
366   }
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)
371   {
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);
377     return;
378   }
379   ats2[0].type = htonl (GNUNET_ATS_NETWORK_TYPE);
380   ats2[0].value = htonl (net);
381   memcpy (&ats2[1],
382           ats,
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)
388               ? "<inbound>"
389               : GST_plugins_a2s (address),
390               session,
391               GNUNET_ATS_print_network_type (net));
392   ar = GNUNET_ATS_address_add (GST_ats,
393                                address,
394                                session,
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;
400   ai->ar = ar;
401   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
402                                             &ai->address->peer,
403                                             ai,
404                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
405   publish_p2a_stat_update ();
406 }
407
408
409 /**
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.
412  *
413  * @param address the address
414  * @param ats ats information
415  * @param ats_count number of @a ats information
416  */
417 void
418 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
419                      const struct GNUNET_ATS_Information *ats,
420                      uint32_t ats_count)
421 {
422   struct GNUNET_ATS_AddressRecord *ar;
423   struct AddressInfo *ai;
424
425   /* valid new address, let ATS know! */
426   if (NULL == address->transport_name)
427   {
428     GNUNET_break(0);
429     return;
430   }
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)
440               ? "<inbound>"
441               : GST_plugins_a2s (address));
442   ar = GNUNET_ATS_address_add (GST_ats,
443                                address,
444                                NULL,
445                                ats,
446                                ats_count);
447   ai = GNUNET_new (struct AddressInfo);
448   ai->address = GNUNET_HELLO_address_copy (address);
449   ai->ar = ar;
450   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
451                                             &ai->address->peer,
452                                             ai,
453                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
454   publish_p2a_stat_update ();
455 }
456
457
458 /**
459  * Notify ATS about a new session now existing for the given
460  * address.
461  *
462  * @param address the address
463  * @param session the session
464  */
465 void
466 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
467                      struct Session *session)
468 {
469   struct AddressInfo *ai;
470
471   ai = find_ai (address, NULL);
472   if (NULL == ai)
473   {
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)));
480     return;
481   }
482   GNUNET_break (NULL == ai->session);
483   ai->session = session;
484   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
485                    "transport-ats",
486                    "Telling ATS about new session %p for peer %s\n",
487                    session,
488                    GNUNET_i2s (&address->peer));
489   if (NULL != ai->ar)
490     GNUNET_ATS_address_add_session (ai->ar,
491                                     session);
492 }
493
494
495 /**
496  * Notify ATS that the session (but not the address) of
497  * a given address is no longer relevant.
498  *
499  * @param address the address
500  * @param session the session
501  */
502 void
503 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
504                      struct Session *session)
505 {
506   struct AddressInfo *ai;
507
508   if (NULL == session)
509   {
510     GNUNET_break (0);
511     return;
512   }
513   ai = find_ai (address, session);
514   if (NULL == ai)
515   {
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));
523
524     return;
525   }
526   GNUNET_assert (session == ai->session);
527   ai->session = NULL;
528   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
529                    "transport-ats",
530                    "Telling ATS to destroy session %p from peer %s\n",
531                    session,
532                    GNUNET_i2s (&address->peer));
533   if (NULL == ai->ar)
534   {
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. */
539     if (GNUNET_YES ==
540         GNUNET_HELLO_address_check_option (address,
541                                            GNUNET_HELLO_ADDRESS_INFO_INBOUND))
542       GST_ats_expire_address (address);
543     return;
544   }
545   if (GNUNET_YES ==
546       GNUNET_ATS_address_del_session (ai->ar, session))
547   {
548     ai->ar = NULL;
549     GST_ats_expire_address (address);
550   }
551 }
552
553
554 /**
555  * Notify ATS about property changes to an address.
556  *
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
561  */
562 void
563 GST_ats_update_metrics (const struct GNUNET_HELLO_Address *address,
564                         struct Session *session,
565                         const struct GNUNET_ATS_Information *ats,
566                         uint32_t ats_count)
567 {
568   struct GNUNET_ATS_Information *ats_new;
569   struct AddressInfo *ai;
570
571   ai = find_ai (address, session);
572   if (NULL == ai)
573   {
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));
581     return;
582   }
583   /* Call to manipulation to manipulate ATS information */
584   GNUNET_assert (NULL != GST_ats);
585   if ((NULL == ats) || (0 == ats_count))
586     return;
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),
591               session);
592   ats_new = GST_manipulation_manipulate_metrics (address,
593                                                  session,
594                                                  ats,
595                                                  ats_count);
596   if (NULL != ai->ar)
597     GNUNET_ATS_address_update (ai->ar,
598                                ats_new,
599                                ats_count);
600   GNUNET_free_non_null (ats_new);
601 }
602
603
604 /**
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.
608  *
609  * @param address the address
610  */
611 void
612 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
613 {
614   struct AddressInfo *ai;
615
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);
621   if (NULL == ai)
622   {
623     GNUNET_assert (0);
624     return;
625   }
626   GNUNET_assert (GNUNET_YES ==
627                  GNUNET_CONTAINER_multipeermap_remove (p2a,
628                                                        &address->peer,
629                                                        ai));
630   publish_p2a_stat_update ();
631   GNUNET_break (NULL == ai->session);
632   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
633                    "transport-ats",
634                    "Telling ATS to destroy address from peer %s\n",
635                    GNUNET_i2s (&address->peer));
636   if (NULL != ai->ar)
637   {
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) ||
643          (GNUNET_NO ==
644           GNUNET_ATS_address_del_session (ai->ar,
645                                           ai->session)) )
646       GNUNET_ATS_address_destroy (ai->ar);
647     ai->ar = NULL;
648   }
649   if (NULL != ai->unblock_task)
650   {
651     GNUNET_SCHEDULER_cancel (ai->unblock_task);
652     ai->unblock_task = NULL;
653   }
654   GNUNET_HELLO_address_free (ai->address);
655   GNUNET_free (ai);
656 }
657
658
659 /**
660  * Initialize ATS subsystem.
661  */
662 void
663 GST_ats_init ()
664 {
665   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
666 }
667
668
669 /**
670  * Release memory used by the given address data.
671  *
672  * @param cls NULL
673  * @param key which peer is this about
674  * @param value the `struct AddressInfo`
675  * @return #GNUNET_OK (continue to iterate)
676  */
677 static int
678 destroy_ai (void *cls,
679             const struct GNUNET_PeerIdentity *key,
680             void *value)
681 {
682   struct AddressInfo *ai = value;
683
684   GNUNET_assert (GNUNET_YES ==
685                  GNUNET_CONTAINER_multipeermap_remove (p2a,
686                                                        key,
687                                                        ai));
688   if (NULL != ai->unblock_task)
689   {
690     GNUNET_SCHEDULER_cancel (ai->unblock_task);
691     ai->unblock_task = NULL;
692   }
693   if (NULL != ai->ar)
694   {
695     GNUNET_ATS_address_destroy (ai->ar);
696     ai->ar = NULL;
697   }
698   GNUNET_HELLO_address_free (ai->address);
699   GNUNET_free (ai);
700   return GNUNET_OK;
701 }
702
703
704 /**
705  * Shutdown ATS subsystem.
706  */
707 void
708 GST_ats_done ()
709 {
710   GNUNET_CONTAINER_multipeermap_iterate (p2a,
711                                          &destroy_ai,
712                                          NULL);
713   publish_p2a_stat_update ();
714   GNUNET_CONTAINER_multipeermap_destroy (p2a);
715   p2a = NULL;
716 }
717
718 /* end of gnunet-service-transport_ats.c */