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