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