74658822ecbe626a24f57e9d6469d1f96c633f0f
[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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, 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 @a address and @a session.
253  *
254  * @param address the address
255  * @param session the session
256  * @return #GNUNET_YES if @a 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  * Test if ATS knows about this @a address.
268  *
269  * @param address the address
270  * @return #GNUNET_YES if @a address is known, #GNUNET_NO if not.
271  */
272 int
273 GST_ats_is_known_no_session (const struct GNUNET_HELLO_Address *address)
274 {
275   return (NULL != find_ai_no_session (address)) ? GNUNET_YES : GNUNET_NO;
276 }
277
278
279 /**
280  * The blocking time for an address has expired, allow ATS to
281  * suggest it again.
282  *
283  * @param cls the `struct AddressInfo` of the address to unblock
284  * @param tc unused
285  */
286 static void
287 unblock_address (void *cls,
288                  const struct GNUNET_SCHEDULER_TaskContext *tc)
289 {
290   struct AddressInfo *ai = cls;
291
292   ai->unblock_task = NULL;
293   LOG (GNUNET_ERROR_TYPE_DEBUG,
294        "Unblocking address %s of peer %s\n",
295        GST_plugins_a2s (ai->address),
296        GNUNET_i2s (&ai->address->peer));
297   ai->ar = GNUNET_ATS_address_add (GST_ats,
298                                    ai->address,
299                                    ai->session,
300                                    &ai->properties);
301   GNUNET_break (NULL != ai->ar);
302   num_blocked--;
303   publish_p2a_stat_update ();
304 }
305
306
307 /**
308  * Temporarily block a valid address for use by ATS for address
309  * suggestions.  This function should be called if an address was
310  * suggested by ATS but failed to perform (i.e. failure to establish a
311  * session or to exchange the PING/PONG).
312  *
313  * @param address the address to block
314  * @param session the session (can be NULL)
315  */
316 void
317 GST_ats_block_address (const struct GNUNET_HELLO_Address *address,
318                        struct Session *session)
319 {
320   struct AddressInfo *ai;
321
322   ai = find_ai (address, session);
323   if (NULL == ai)
324   {
325     GNUNET_assert (0);
326     return;
327   }
328   if (NULL == ai->ar)
329   {
330     /* already blocked, how did it get used!? */
331     GNUNET_break (0);
332     return;
333   }
334   ai->back_off = GNUNET_TIME_STD_BACKOFF (ai->back_off);
335   if (GNUNET_YES ==
336       GNUNET_HELLO_address_check_option (address,
337                                          GNUNET_HELLO_ADDRESS_INFO_INBOUND))
338     LOG (GNUNET_ERROR_TYPE_DEBUG,
339          "Removing address %s of peer %s from use (inbound died)\n",
340          GST_plugins_a2s (address),
341          GNUNET_i2s (&address->peer));
342   else
343     LOG (GNUNET_ERROR_TYPE_INFO,
344          "Blocking address %s of peer %s from use for %s\n",
345          GST_plugins_a2s (address),
346          GNUNET_i2s (&address->peer),
347          GNUNET_STRINGS_relative_time_to_string (ai->back_off,
348                                                  GNUNET_YES));
349   /* destroy session and address */
350   if ( (NULL == session) ||
351        (GNUNET_NO ==
352         GNUNET_ATS_address_del_session (ai->ar,
353                                         session)) )
354     GNUNET_ATS_address_destroy (ai->ar);
355   ai->ar = NULL;
356
357   /* determine when the address should come back to life */
358   ai->blocked = GNUNET_TIME_relative_to_absolute (ai->back_off);
359   ai->unblock_task = GNUNET_SCHEDULER_add_delayed (ai->back_off,
360                                                    &unblock_address,
361                                                    ai);
362   num_blocked++;
363   publish_p2a_stat_update ();
364 }
365
366
367 /**
368  * Reset address blocking time.  Resets the exponential
369  * back-off timer for this address to zero.  Done when
370  * an address was used to create a successful connection.
371  *
372  * @param address the address to reset the blocking timer
373  * @param session the session (can be NULL)
374  */
375 void
376 GST_ats_block_reset (const struct GNUNET_HELLO_Address *address,
377                      struct Session *session)
378 {
379   struct AddressInfo *ai;
380
381   ai = find_ai (address, session);
382   if (NULL == ai)
383   {
384     GNUNET_break (0);
385     return;
386   }
387   /* address is in successful use, so it should not be blocked right now */
388   GNUNET_break (NULL == ai->unblock_task);
389   ai->back_off = GNUNET_TIME_UNIT_ZERO;
390 }
391
392
393 /**
394  * Notify ATS about the a new inbound address. We may already
395  * know the address (as this is called each time we receive
396  * a message from an inbound connection).  If the address is
397  * indeed new, make it available to ATS.
398  *
399  * @param address the address
400  * @param session the session
401  * @param prop performance information
402  */
403 void
404 GST_ats_add_inbound_address (const struct GNUNET_HELLO_Address *address,
405                              struct Session *session,
406                              const struct GNUNET_ATS_Properties *prop)
407 {
408   struct GNUNET_ATS_AddressRecord *ar;
409   struct AddressInfo *ai;
410
411   /* valid new address, let ATS know! */
412   if (NULL == address->transport_name)
413   {
414     GNUNET_break(0);
415     return;
416   }
417   GNUNET_assert (GNUNET_YES ==
418                  GNUNET_HELLO_address_check_option (address,
419                                                     GNUNET_HELLO_ADDRESS_INFO_INBOUND));
420   GNUNET_assert (NULL != session);
421   ai = find_ai (address, session);
422   if (NULL != ai)
423   {
424     /* This should only be called for new sessions, and thus
425        we should not already have the address */
426     GNUNET_break (0);
427     return;
428   }
429   GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
430   LOG (GNUNET_ERROR_TYPE_DEBUG,
431        "Notifying ATS about peer `%s''s new inbound address `%s' session %p in network %s\n",
432        GNUNET_i2s (&address->peer),
433        (0 == address->address_length)
434        ? "<inbound>"
435        : GST_plugins_a2s (address),
436        session,
437        GNUNET_ATS_print_network_type (prop->scope));
438   ar = GNUNET_ATS_address_add (GST_ats,
439                                address,
440                                session,
441                                prop);
442   GNUNET_break (NULL != ar);
443   ai = GNUNET_new (struct AddressInfo);
444   ai->address = GNUNET_HELLO_address_copy (address);
445   ai->session = session;
446   ai->properties = *prop;
447   ai->ar = ar;
448   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
449                                             &ai->address->peer,
450                                             ai,
451                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
452   publish_p2a_stat_update ();
453 }
454
455
456 /**
457  * Notify ATS about the new address including the network this address is
458  * located in.  The address must NOT be inbound and must be new to ATS.
459  *
460  * @param address the address
461  * @param prop performance information
462  */
463 void
464 GST_ats_add_address (const struct GNUNET_HELLO_Address *address,
465                      const struct GNUNET_ATS_Properties *prop)
466 {
467   struct GNUNET_ATS_AddressRecord *ar;
468   struct AddressInfo *ai;
469
470   /* valid new address, let ATS know! */
471   if (NULL == address->transport_name)
472   {
473     GNUNET_break(0);
474     return;
475   }
476   GNUNET_assert (GNUNET_YES !=
477                  GNUNET_HELLO_address_check_option (address,
478                                                     GNUNET_HELLO_ADDRESS_INFO_INBOUND));
479   ai = find_ai_no_session (address);
480   GNUNET_assert (NULL == ai);
481   LOG (GNUNET_ERROR_TYPE_INFO,
482        "Notifying ATS about peer `%s''s new address `%s'\n",
483        GNUNET_i2s (&address->peer),
484        (0 == address->address_length)
485        ? "<inbound>"
486        : GST_plugins_a2s (address));
487   ar = GNUNET_ATS_address_add (GST_ats,
488                                address,
489                                NULL,
490                                prop);
491   GNUNET_break (NULL != ar);
492   ai = GNUNET_new (struct AddressInfo);
493   ai->address = GNUNET_HELLO_address_copy (address);
494   ai->ar = ar;
495   ai->properties = *prop;
496   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
497                                             &ai->address->peer,
498                                             ai,
499                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
500   publish_p2a_stat_update ();
501 }
502
503
504 /**
505  * Notify ATS about a new session now existing for the given
506  * address.
507  *
508  * @param address the address
509  * @param session the session
510  */
511 void
512 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
513                      struct Session *session)
514 {
515   struct AddressInfo *ai;
516
517   ai = find_ai (address, NULL);
518   if (NULL == ai)
519   {
520     /* We may already be aware of the session, even if some other part
521        of the code could not tell if it just created a new session or
522        just got one recycled from the plugin; hence, we may be called
523        with "new" session even for an "old" session; in that case,
524        check that this is the case, but just ignore it. */
525     GNUNET_assert (NULL != (find_ai (address, session)));
526     return;
527   }
528   GNUNET_break (NULL == ai->session);
529   ai->session = session;
530   LOG (GNUNET_ERROR_TYPE_DEBUG,
531        "Telling ATS about new session for peer %s\n",
532        GNUNET_i2s (&address->peer));
533   if (NULL != ai->ar)
534     GNUNET_ATS_address_add_session (ai->ar,
535                                     session);
536 }
537
538
539 /**
540  * Notify ATS that the session (but not the address) of
541  * a given address is no longer relevant.
542  *
543  * @param address the address
544  * @param session the session
545  */
546 void
547 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
548                      struct Session *session)
549 {
550   struct AddressInfo *ai;
551
552   if (NULL == session)
553   {
554     GNUNET_break (0);
555     return;
556   }
557   ai = find_ai (address,
558                 session);
559   if (NULL == ai)
560   {
561     /* We sometimes create sessions just for sending a PING,
562        and if those are destroyed they were never known to
563        ATS which means we end up here (however, in this
564        case, the address must be an outbound address). */
565     GNUNET_break (GNUNET_YES !=
566                   GNUNET_HELLO_address_check_option (address,
567                                                      GNUNET_HELLO_ADDRESS_INFO_INBOUND));
568
569     return;
570   }
571   GNUNET_assert (session == ai->session);
572   ai->session = NULL;
573   LOG (GNUNET_ERROR_TYPE_DEBUG,
574        "Telling ATS to destroy session %p from peer %s\n",
575        session,
576        GNUNET_i2s (&address->peer));
577   if (NULL == ai->ar)
578   {
579     /* If ATS doesn't know about the address/session, and this was an
580        inbound session or one that expired, then we must forget about
581        the address as well.  Otherwise, we are done as we have set
582        `ai->session` to NULL already. */
583     if ( (GNUNET_YES == ai->expired) ||
584          (GNUNET_YES ==
585           GNUNET_HELLO_address_check_option (address,
586                                              GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
587       GST_ats_expire_address (address);
588     return;
589   }
590   if (GNUNET_YES ==
591       GNUNET_ATS_address_del_session (ai->ar,
592                                       session))
593   {
594     ai->ar = NULL;
595     GST_ats_expire_address (address);
596   }
597 }
598
599
600 /**
601  * Notify ATS about DV distance change to an address's.
602  *
603  * @param address the address
604  * @param distance new distance value
605  */
606 void
607 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
608                          uint32_t distance)
609 {
610   struct AddressInfo *ai;
611
612   ai = find_ai_no_session (address);
613   if (NULL == ai)
614     return;
615   LOG (GNUNET_ERROR_TYPE_DEBUG,
616        "Updated distance for peer `%s' to %u\n",
617        GNUNET_i2s (&address->peer),
618        distance);
619   ai->properties.distance = distance;
620   GST_manipulation_manipulate_metrics (address,
621                                        ai->session,
622                                        &ai->properties);
623   if (NULL != ai->ar)
624     GNUNET_ATS_address_update (ai->ar,
625                                &ai->properties);
626 }
627
628
629 /**
630  * Notify ATS about property changes to an address's properties.
631  *
632  * @param address the address
633  * @param delay new delay value
634  */
635 void
636 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
637                       struct GNUNET_TIME_Relative delay)
638 {
639   struct AddressInfo *ai;
640
641   ai = find_ai_no_session (address);
642   if (NULL == ai)
643     return;
644   LOG (GNUNET_ERROR_TYPE_DEBUG,
645        "Updated latency for peer `%s' to %s\n",
646        GNUNET_i2s (&address->peer),
647        GNUNET_STRINGS_relative_time_to_string (delay,
648                                                GNUNET_YES));
649   ai->properties.delay = delay;
650   GST_manipulation_manipulate_metrics (address,
651                                        ai->session,
652                                        &ai->properties);
653   if (NULL != ai->ar)
654     GNUNET_ATS_address_update (ai->ar,
655                                &ai->properties);
656 }
657
658
659 /**
660  * Notify ATS about utilization changes to an address.
661  *
662  * @param address our information about the address
663  * @param bps_in new utilization inbound
664  * @param bps_out new utilization outbound
665  */
666 void
667 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
668                             uint32_t bps_in,
669                             uint32_t bps_out)
670 {
671   struct AddressInfo *ai;
672
673   ai = find_ai_no_session (address);
674   if (NULL == ai)
675     return;
676   LOG (GNUNET_ERROR_TYPE_DEBUG,
677        "Updating utilization for peer `%s' address %s: %u/%u\n",
678        GNUNET_i2s (&address->peer),
679        GST_plugins_a2s (address),
680        (unsigned int) bps_in,
681        (unsigned int) bps_out);
682   ai->properties.utilization_in = bps_in;
683   ai->properties.utilization_out = bps_out;
684   GST_manipulation_manipulate_metrics (address,
685                                        ai->session,
686                                        &ai->properties);
687   if (NULL != ai->ar)
688     GNUNET_ATS_address_update (ai->ar,
689                                &ai->properties);
690 }
691
692
693 /**
694  * Notify ATS that the address has expired and thus cannot
695  * be used any longer.  This function must only be called
696  * if the corresponding session is already gone.
697  *
698  * @param address the address
699  */
700 void
701 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
702 {
703   struct AddressInfo *ai;
704
705   LOG (GNUNET_ERROR_TYPE_DEBUG,
706        "Address %s of peer %s expired\n",
707        GST_plugins_a2s (address),
708        GNUNET_i2s (&address->peer));
709   ai = find_ai_no_session (address);
710   if (NULL == ai)
711   {
712     GNUNET_assert (0);
713     return;
714   }
715   if (NULL != ai->unblock_task)
716   {
717     GNUNET_SCHEDULER_cancel (ai->unblock_task);
718     ai->unblock_task = NULL;
719     num_blocked--;
720   }
721   if (NULL != ai->session)
722   {
723     ai->expired = GNUNET_YES;
724     if (NULL != ai->ar)
725     {
726       GNUNET_ATS_address_destroy (ai->ar);
727       ai->ar = NULL;
728     }
729     return;
730   }
731   GNUNET_assert (GNUNET_YES ==
732                  GNUNET_CONTAINER_multipeermap_remove (p2a,
733                                                        &address->peer,
734                                                        ai));
735   LOG (GNUNET_ERROR_TYPE_DEBUG,
736        "Telling ATS to destroy address from peer %s\n",
737        GNUNET_i2s (&address->peer));
738   if (NULL != ai->ar)
739   {
740     /* We usually should not have a session here when we
741        expire an address, but during shutdown a session
742        may be active while validation causes the address
743        to 'expire'.  So clean up both if necessary. */
744     if ( (NULL == ai->session) ||
745          (GNUNET_NO ==
746           GNUNET_ATS_address_del_session (ai->ar,
747                                           ai->session)) )
748       GNUNET_ATS_address_destroy (ai->ar);
749     ai->ar = NULL;
750   }
751   publish_p2a_stat_update ();
752   GNUNET_HELLO_address_free (ai->address);
753   GNUNET_free (ai);
754 }
755
756
757 /**
758  * Initialize ATS subsystem.
759  */
760 void
761 GST_ats_init ()
762 {
763   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
764 }
765
766
767 /**
768  * Release memory used by the given address data.
769  *
770  * @param cls NULL
771  * @param key which peer is this about
772  * @param value the `struct AddressInfo`
773  * @return #GNUNET_OK (continue to iterate)
774  */
775 static int
776 destroy_ai (void *cls,
777             const struct GNUNET_PeerIdentity *key,
778             void *value)
779 {
780   struct AddressInfo *ai = value;
781
782   GNUNET_assert (GNUNET_YES ==
783                  GNUNET_CONTAINER_multipeermap_remove (p2a,
784                                                        key,
785                                                        ai));
786   if (NULL != ai->unblock_task)
787   {
788     GNUNET_SCHEDULER_cancel (ai->unblock_task);
789     ai->unblock_task = NULL;
790     num_blocked--;
791   }
792   if (NULL != ai->ar)
793   {
794     GNUNET_ATS_address_destroy (ai->ar);
795     ai->ar = NULL;
796   }
797   GNUNET_HELLO_address_free (ai->address);
798   GNUNET_free (ai);
799   return GNUNET_OK;
800 }
801
802
803 /**
804  * Shutdown ATS subsystem.
805  */
806 void
807 GST_ats_done ()
808 {
809   GNUNET_CONTAINER_multipeermap_iterate (p2a,
810                                          &destroy_ai,
811                                          NULL);
812   publish_p2a_stat_update ();
813   GNUNET_CONTAINER_multipeermap_destroy (p2a);
814   p2a = NULL;
815 }
816
817 /* end of gnunet-service-transport_ats.c */