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