5124cd57852d22cde8e108bb70883f5aa99468d6
[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   GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
482   LOG (GNUNET_ERROR_TYPE_INFO,
483        "Notifying ATS about peer `%s''s new address `%s'\n",
484        GNUNET_i2s (&address->peer),
485        (0 == address->address_length)
486        ? "<inbound>"
487        : GST_plugins_a2s (address));
488   ar = GNUNET_ATS_address_add (GST_ats,
489                                address,
490                                NULL,
491                                prop);
492   GNUNET_break (NULL != ar);
493   ai = GNUNET_new (struct AddressInfo);
494   ai->address = GNUNET_HELLO_address_copy (address);
495   ai->ar = ar;
496   ai->properties = *prop;
497   (void) GNUNET_CONTAINER_multipeermap_put (p2a,
498                                             &ai->address->peer,
499                                             ai,
500                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
501   publish_p2a_stat_update ();
502 }
503
504
505 /**
506  * Notify ATS about a new session now existing for the given
507  * address.
508  *
509  * @param address the address
510  * @param session the session
511  */
512 void
513 GST_ats_new_session (const struct GNUNET_HELLO_Address *address,
514                      struct Session *session)
515 {
516   struct AddressInfo *ai;
517
518   ai = find_ai (address, NULL);
519   if (NULL == ai)
520   {
521     /* We may already be aware of the session, even if some other part
522        of the code could not tell if it just created a new session or
523        just got one recycled from the plugin; hence, we may be called
524        with "new" session even for an "old" session; in that case,
525        check that this is the case, but just ignore it. */
526     GNUNET_assert (NULL != (find_ai (address, session)));
527     return;
528   }
529   GNUNET_break (NULL == ai->session);
530   ai->session = session;
531   LOG (GNUNET_ERROR_TYPE_DEBUG,
532        "Telling ATS about new session for peer %s\n",
533        GNUNET_i2s (&address->peer));
534   if (NULL != ai->ar)
535     GNUNET_ATS_address_add_session (ai->ar,
536                                     session);
537 }
538
539
540 /**
541  * Notify ATS that the session (but not the address) of
542  * a given address is no longer relevant.
543  *
544  * @param address the address
545  * @param session the session
546  */
547 void
548 GST_ats_del_session (const struct GNUNET_HELLO_Address *address,
549                      struct Session *session)
550 {
551   struct AddressInfo *ai;
552
553   if (NULL == session)
554   {
555     GNUNET_break (0);
556     return;
557   }
558   ai = find_ai (address,
559                 session);
560   if (NULL == ai)
561   {
562     /* We sometimes create sessions just for sending a PING,
563        and if those are destroyed they were never known to
564        ATS which means we end up here (however, in this
565        case, the address must be an outbound address). */
566     GNUNET_break (GNUNET_YES !=
567                   GNUNET_HELLO_address_check_option (address,
568                                                      GNUNET_HELLO_ADDRESS_INFO_INBOUND));
569
570     return;
571   }
572   GNUNET_assert (session == ai->session);
573   ai->session = NULL;
574   LOG (GNUNET_ERROR_TYPE_DEBUG,
575        "Telling ATS to destroy session %p from peer %s\n",
576        session,
577        GNUNET_i2s (&address->peer));
578   if (NULL == ai->ar)
579   {
580     /* If ATS doesn't know about the address/session, and this was an
581        inbound session or one that expired, then we must forget about
582        the address as well.  Otherwise, we are done as we have set
583        `ai->session` to NULL already. */
584     if ( (GNUNET_YES == ai->expired) ||
585          (GNUNET_YES ==
586           GNUNET_HELLO_address_check_option (address,
587                                              GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
588       GST_ats_expire_address (address);
589     return;
590   }
591   if (GNUNET_YES ==
592       GNUNET_ATS_address_del_session (ai->ar,
593                                       session))
594   {
595     ai->ar = NULL;
596     GST_ats_expire_address (address);
597   }
598 }
599
600
601 /**
602  * Notify ATS about DV distance change to an address's.
603  *
604  * @param address the address
605  * @param distance new distance value
606  */
607 void
608 GST_ats_update_distance (const struct GNUNET_HELLO_Address *address,
609                          uint32_t distance)
610 {
611   struct AddressInfo *ai;
612
613   ai = find_ai_no_session (address);
614   if (NULL == ai)
615     return;
616   LOG (GNUNET_ERROR_TYPE_DEBUG,
617        "Updated distance for peer `%s' to %u\n",
618        GNUNET_i2s (&address->peer),
619        distance);
620   ai->properties.distance = distance;
621   GST_manipulation_manipulate_metrics (address,
622                                        ai->session,
623                                        &ai->properties);
624   if (NULL != ai->ar)
625     GNUNET_ATS_address_update (ai->ar,
626                                &ai->properties);
627 }
628
629
630 /**
631  * Notify ATS about property changes to an address's properties.
632  *
633  * @param address the address
634  * @param delay new delay value
635  */
636 void
637 GST_ats_update_delay (const struct GNUNET_HELLO_Address *address,
638                       struct GNUNET_TIME_Relative delay)
639 {
640   struct AddressInfo *ai;
641
642   ai = find_ai_no_session (address);
643   if (NULL == ai)
644     return;
645   LOG (GNUNET_ERROR_TYPE_DEBUG,
646        "Updated latency for peer `%s' to %s\n",
647        GNUNET_i2s (&address->peer),
648        GNUNET_STRINGS_relative_time_to_string (delay,
649                                                GNUNET_YES));
650   ai->properties.delay = delay;
651   GST_manipulation_manipulate_metrics (address,
652                                        ai->session,
653                                        &ai->properties);
654   if (NULL != ai->ar)
655     GNUNET_ATS_address_update (ai->ar,
656                                &ai->properties);
657 }
658
659
660 /**
661  * Notify ATS about utilization changes to an address.
662  *
663  * @param address our information about the address
664  * @param bps_in new utilization inbound
665  * @param bps_out new utilization outbound
666  */
667 void
668 GST_ats_update_utilization (const struct GNUNET_HELLO_Address *address,
669                             uint32_t bps_in,
670                             uint32_t bps_out)
671 {
672   struct AddressInfo *ai;
673
674   ai = find_ai_no_session (address);
675   if (NULL == ai)
676     return;
677   LOG (GNUNET_ERROR_TYPE_DEBUG,
678        "Updating utilization for peer `%s' address %s: %u/%u\n",
679        GNUNET_i2s (&address->peer),
680        GST_plugins_a2s (address),
681        (unsigned int) bps_in,
682        (unsigned int) bps_out);
683   ai->properties.utilization_in = bps_in;
684   ai->properties.utilization_out = bps_out;
685   GST_manipulation_manipulate_metrics (address,
686                                        ai->session,
687                                        &ai->properties);
688   if (NULL != ai->ar)
689     GNUNET_ATS_address_update (ai->ar,
690                                &ai->properties);
691 }
692
693
694 /**
695  * Notify ATS that the address has expired and thus cannot
696  * be used any longer.  This function must only be called
697  * if the corresponding session is already gone.
698  *
699  * @param address the address
700  */
701 void
702 GST_ats_expire_address (const struct GNUNET_HELLO_Address *address)
703 {
704   struct AddressInfo *ai;
705
706   LOG (GNUNET_ERROR_TYPE_DEBUG,
707        "Address %s of peer %s expired\n",
708        GST_plugins_a2s (address),
709        GNUNET_i2s (&address->peer));
710   ai = find_ai_no_session (address);
711   if (NULL == ai)
712   {
713     GNUNET_assert (0);
714     return;
715   }
716   if (NULL != ai->unblock_task)
717   {
718     GNUNET_SCHEDULER_cancel (ai->unblock_task);
719     ai->unblock_task = NULL;
720     num_blocked--;
721   }
722   if (NULL != ai->session)
723   {
724     ai->expired = GNUNET_YES;
725     if (NULL != ai->ar)
726     {
727       GNUNET_ATS_address_destroy (ai->ar);
728       ai->ar = NULL;
729     }
730     return;
731   }
732   GNUNET_assert (GNUNET_YES ==
733                  GNUNET_CONTAINER_multipeermap_remove (p2a,
734                                                        &address->peer,
735                                                        ai));
736   LOG (GNUNET_ERROR_TYPE_DEBUG,
737        "Telling ATS to destroy address from peer %s\n",
738        GNUNET_i2s (&address->peer));
739   if (NULL != ai->ar)
740   {
741     /* We usually should not have a session here when we
742        expire an address, but during shutdown a session
743        may be active while validation causes the address
744        to 'expire'.  So clean up both if necessary. */
745     if ( (NULL == ai->session) ||
746          (GNUNET_NO ==
747           GNUNET_ATS_address_del_session (ai->ar,
748                                           ai->session)) )
749       GNUNET_ATS_address_destroy (ai->ar);
750     ai->ar = NULL;
751   }
752   publish_p2a_stat_update ();
753   GNUNET_HELLO_address_free (ai->address);
754   GNUNET_free (ai);
755 }
756
757
758 /**
759  * Initialize ATS subsystem.
760  */
761 void
762 GST_ats_init ()
763 {
764   p2a = GNUNET_CONTAINER_multipeermap_create (4, GNUNET_YES);
765 }
766
767
768 /**
769  * Release memory used by the given address data.
770  *
771  * @param cls NULL
772  * @param key which peer is this about
773  * @param value the `struct AddressInfo`
774  * @return #GNUNET_OK (continue to iterate)
775  */
776 static int
777 destroy_ai (void *cls,
778             const struct GNUNET_PeerIdentity *key,
779             void *value)
780 {
781   struct AddressInfo *ai = value;
782
783   GNUNET_assert (GNUNET_YES ==
784                  GNUNET_CONTAINER_multipeermap_remove (p2a,
785                                                        key,
786                                                        ai));
787   if (NULL != ai->unblock_task)
788   {
789     GNUNET_SCHEDULER_cancel (ai->unblock_task);
790     ai->unblock_task = NULL;
791     num_blocked--;
792   }
793   if (NULL != ai->ar)
794   {
795     GNUNET_ATS_address_destroy (ai->ar);
796     ai->ar = NULL;
797   }
798   GNUNET_HELLO_address_free (ai->address);
799   GNUNET_free (ai);
800   return GNUNET_OK;
801 }
802
803
804 /**
805  * Shutdown ATS subsystem.
806  */
807 void
808 GST_ats_done ()
809 {
810   GNUNET_CONTAINER_multipeermap_iterate (p2a,
811                                          &destroy_ai,
812                                          NULL);
813   publish_p2a_stat_update ();
814   GNUNET_CONTAINER_multipeermap_destroy (p2a);
815   p2a = NULL;
816 }
817
818 /* end of gnunet-service-transport_ats.c */