- doxygen
[oweals/gnunet.git] / src / integration-tests / connection_watchdog.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file integration-tests/connection_watchdog.c
22  * @brief tool to monitor core and transport connections for consistency
23  * @author Matthias Wachs
24  */
25 #include "platform.h"
26 #include "gnunet_common.h"
27 #include "gnunet_constants.h"
28 #include "gnunet_arm_service.h"
29 #include "gnunet_core_service.h"
30 #include "gnunet_getopt_lib.h"
31 #include "gnunet_os_lib.h"
32 #include "gnunet_program_lib.h"
33 #include "gnunet_scheduler_lib.h"
34 #include "gnunet_transport_service.h"
35 #include "gnunet_statistics_service.h"
36
37
38 #define CHECK_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
39 #define STATS_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
40 #define REPEATED_STATS_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10)
41 #define STATS_VALUES 4
42
43 /**
44  * Final status code.
45  */
46 static int ret;
47
48 static struct GNUNET_TRANSPORT_Handle *th;
49 static struct GNUNET_CORE_Handle *ch;
50 static struct GNUNET_PeerIdentity my_peer_id;
51 static const struct GNUNET_CONFIGURATION_Handle *mycfg;
52 static struct GNUNET_STATISTICS_Handle *stats;
53
54
55 static unsigned int transport_connections;
56 static unsigned int core_connections;
57
58 static GNUNET_SCHEDULER_TaskIdentifier check_task;
59 static GNUNET_SCHEDULER_TaskIdentifier statistics_task;
60
61 static uint64_t statistics_transport_connections;
62 static uint64_t statistics_transport_tcp_connections;
63 static uint64_t statistics_core_neighbour_entries;
64 static uint64_t statistics_core_entries_session_map;
65
66 int stat_check_running;
67
68 static struct GNUNET_CONTAINER_MultiHashMap *peers;
69
70 struct PeerContainer
71 {
72   struct GNUNET_PeerIdentity id;
73   int transport_connected;
74   int core_connected;
75 };
76
77
78 int map_check_it (void *cls,
79                   const GNUNET_HashCode * key,
80                   void *value)
81 {
82   int *fail = cls;
83   struct PeerContainer *pc = value;
84   if (pc->core_connected != pc->transport_connected)
85   {
86     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
87      "Inconsistend peer `%s': TRANSPORT %s <-> CORE %s\n",
88      GNUNET_i2s (&pc->id),
89      (GNUNET_YES == pc->transport_connected) ? "YES" : "NO",
90      (GNUNET_YES == pc->core_connected) ? "YES" : "NO");
91     (*fail) ++;
92   }
93
94   return GNUNET_OK;
95 }
96
97
98 int map_cleanup_it (void *cls,
99                   const GNUNET_HashCode * key,
100                   void *value)
101 {
102   struct PeerContainer *pc = value;
103   GNUNET_CONTAINER_multihashmap_remove(peers, key, value);
104   GNUNET_free (pc);
105   return GNUNET_OK;
106 }
107
108 static void
109 map_cleanup (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
110 {
111   GNUNET_CONTAINER_multihashmap_iterate (peers, &map_cleanup_it, NULL);
112   GNUNET_CONTAINER_multihashmap_destroy(peers);
113 }
114
115 static void
116 map_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
117 {
118   int fail = 0;
119   check_task = GNUNET_SCHEDULER_NO_TASK;
120   GNUNET_CONTAINER_multihashmap_iterate (peers, &map_check_it, &fail);
121   if (0 > fail)
122     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
123        "Inconsistent peers after connection consistency check: %u\n", fail);
124   else
125     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
126        "Inconsistent peers after connection consistency check: %u\n", fail);
127
128
129   if (NULL != cls)
130   {
131     GNUNET_SCHEDULER_add_now (cls, NULL);
132   }
133 }
134
135 static void
136 stats_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
137
138 int stats_check_cb (void *cls, const char *subsystem,
139                    const char *name, uint64_t value,
140                    int is_persistent)
141 {
142   static int counter;
143   uint64_t *val = cls;
144
145   if (NULL != val)
146     (*val) = value;
147
148   counter ++;
149   if (STATS_VALUES == counter)
150   {
151     int fail = GNUNET_NO;
152     if (transport_connections != core_connections)
153     {
154       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
155            "Transport connections are inconsistent: %u transport notifications <-> %u core notifications\n",
156            transport_connections, core_connections);
157       fail = GNUNET_YES;
158     }
159
160     if (transport_connections != statistics_transport_connections)
161     {
162       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
163            "Transport connections are inconsistent: %u transport notifications <-> %u in statistics (peers connected)\n",
164            transport_connections, statistics_transport_connections);
165       fail = GNUNET_YES;
166     }
167     if (core_connections != statistics_core_entries_session_map)
168     {
169       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
170            "Transport connections are inconsistent: %u core notifications <-> %u in statistics (entries session map)\n",
171            core_connections, statistics_core_entries_session_map);
172       fail = GNUNET_YES;
173     }
174
175     if (core_connections != statistics_core_neighbour_entries)
176     {
177       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
178            "Transport connections are inconsistent: %u core notifications <-> %u in statistics (neighbour entries allocated)\n",
179            core_connections, statistics_core_neighbour_entries);
180       fail = GNUNET_YES;
181     }
182
183     if (GNUNET_NO == fail)
184       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
185          "Statistics consistency check successful : (%u transport / %u core) connections established\n", transport_connections, core_connections);
186
187     /* This is only an issue when transport_connections > statistics_transport_tcp_connections */
188     if (transport_connections > statistics_transport_tcp_connections)
189     {
190       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
191            "Transport connections are inconsistent: %u transport notifications <-> %u in statistics (statistics_transport_tcp_connections)\n",
192            transport_connections, statistics_transport_tcp_connections);
193       fail = GNUNET_YES;
194     }
195     else
196     {
197       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
198            "Transport connections are inconsistent: %u transport notifications <-> %u in statistics (statistics_transport_tcp_connections)\n",
199            transport_connections, statistics_transport_tcp_connections);
200     }
201
202     if (GNUNET_SCHEDULER_NO_TASK == statistics_task)
203       statistics_task = GNUNET_SCHEDULER_add_delayed(REPEATED_STATS_DELAY, &stats_check, NULL);
204
205     stat_check_running = GNUNET_NO;
206     counter = 0;
207   }
208
209   return GNUNET_OK;
210 }
211
212 static void
213 stats_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
214 {
215   statistics_task = GNUNET_SCHEDULER_NO_TASK;
216
217   if (GNUNET_YES == stat_check_running)
218   {
219     statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
220   }
221
222   stat_check_running = GNUNET_YES;
223
224   statistics_transport_connections = 0 ;
225   statistics_core_entries_session_map = 0;
226   statistics_core_neighbour_entries = 0;
227
228   GNUNET_STATISTICS_get (stats, "transport", "# peers connected", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_transport_connections);
229   GNUNET_STATISTICS_get (stats, "transport", "# TCP sessions active", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_transport_tcp_connections);
230   GNUNET_STATISTICS_get (stats, "core", "# neighbour entries allocated", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_core_neighbour_entries);
231   GNUNET_STATISTICS_get (stats, "core", "# entries in session map", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_core_entries_session_map);
232 }
233
234
235 static void
236 map_connect (const struct GNUNET_PeerIdentity *peer, void * source)
237 {
238   struct PeerContainer * pc;
239   if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains(peers, &peer->hashPubKey))
240   {
241     pc = GNUNET_malloc (sizeof (struct PeerContainer));
242     pc->id = *peer;
243     pc->core_connected = GNUNET_NO;
244     pc->transport_connected = GNUNET_NO;
245     GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put(peers, &peer->hashPubKey, pc, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
246   }
247
248   pc = GNUNET_CONTAINER_multihashmap_get(peers, &peer->hashPubKey);
249   if (source == th)
250   {
251     if (GNUNET_NO == pc->transport_connected)
252     {
253       pc->transport_connected = GNUNET_YES;
254     }
255     else
256     {
257       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
258            "%s notified multiple times about for peers `%s' (%s : %s)\n",
259            "TRANSPORT",
260            GNUNET_i2s (&pc->id),
261            "CORE", (pc->core_connected == GNUNET_YES) ? "yes" : "no");
262       GNUNET_break (0);
263     }
264   }
265   if (source == ch)
266   {
267     if (GNUNET_NO == pc->core_connected)
268     {
269       pc->core_connected = GNUNET_YES;
270     }
271     else
272     {
273       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
274            "%s notified multiple times about for peers `%s' (%s : %s)\n",
275            "CORE",
276            GNUNET_i2s (&pc->id),
277                "TRANSPORT", (pc->transport_connected == GNUNET_YES) ? "yes" : "no");
278       GNUNET_break (0);
279     }
280   }
281   if (GNUNET_SCHEDULER_NO_TASK != check_task)
282     GNUNET_SCHEDULER_cancel(check_task);
283   check_task = GNUNET_SCHEDULER_add_delayed(CHECK_DELAY, &map_check, NULL);
284
285   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
286     GNUNET_SCHEDULER_cancel(statistics_task);
287   statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
288 }
289
290
291 static void
292 map_disconnect (const struct GNUNET_PeerIdentity * peer, void * source)
293 {
294
295   struct PeerContainer * pc;
296   if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains(peers, &peer->hashPubKey))
297   {
298     if (source == th)
299     {
300       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
301          "%s disconnect notification for unknown peer `%s'\n",
302          "TRANSPORT", GNUNET_i2s (peer));
303       GNUNET_break (0);
304       return;
305     }
306     if (source == ch)
307     {
308       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
309          "%s disconnect notification for unknown peer `%s'\n",
310          "CORE", GNUNET_i2s (peer));
311       return;
312     }
313   }
314
315   pc = GNUNET_CONTAINER_multihashmap_get(peers, &peer->hashPubKey);
316   if (source == th)
317   {
318     if (GNUNET_YES == pc->transport_connected)
319     {
320       pc->transport_connected = GNUNET_NO;
321     }
322     else
323     {
324       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
325            "%s notified for not connected peer `%s' (%s : %s)\n",
326            "TRANSPORT",
327            GNUNET_i2s (&pc->id),
328            "CORE", (pc->core_connected == GNUNET_YES) ? "yes" : "no");
329       GNUNET_break (0);
330     }
331   }
332   if (source == ch)
333   {
334     if (GNUNET_YES == pc->core_connected)
335     {
336       pc->core_connected = GNUNET_NO;
337     }
338     else
339     {
340       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
341            "%s notified for not connected peer `%s' (%s : %s)\n",
342            "CORE",
343            GNUNET_i2s (&pc->id),
344            "TRANSPORT", (pc->transport_connected == GNUNET_YES) ? "yes" : "no");
345       GNUNET_break (0);
346     }
347   }
348
349   if ((GNUNET_NO == pc->core_connected) && (GNUNET_NO == pc->transport_connected))
350   {
351     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Removing peer `%s'\n", GNUNET_i2s (&pc->id));
352     GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_remove (peers, &peer->hashPubKey, pc));
353     GNUNET_free (pc);
354   }
355
356   if (GNUNET_SCHEDULER_NO_TASK != check_task)
357     GNUNET_SCHEDULER_cancel(check_task);
358   check_task = GNUNET_SCHEDULER_add_delayed(CHECK_DELAY, &map_check, NULL);
359
360   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
361     GNUNET_SCHEDULER_cancel(statistics_task);
362   statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
363 }
364
365
366 static void
367 cleanup_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
368 {
369   if (NULL != th)
370   {
371     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnecting from transport service\n");
372     GNUNET_TRANSPORT_disconnect (th);
373     th = NULL;
374   }
375   if (NULL != ch)
376   {
377     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnecting from core service\n");
378     GNUNET_CORE_disconnect (ch);
379     ch = NULL;
380   }
381
382   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
383   {
384     GNUNET_SCHEDULER_cancel(statistics_task);
385     statistics_task = GNUNET_SCHEDULER_NO_TASK;
386   }
387
388   if (GNUNET_SCHEDULER_NO_TASK != check_task)
389   {
390     GNUNET_SCHEDULER_cancel(check_task);
391     check_task = GNUNET_SCHEDULER_NO_TASK;
392   }
393   check_task = GNUNET_SCHEDULER_add_now (&map_check, &map_cleanup);
394 }
395
396 void
397 transport_notify_connect_cb (void *cls,
398                 const struct GNUNET_PeerIdentity
399                 * peer,
400                 const struct
401                 GNUNET_ATS_Information * ats,
402                 uint32_t ats_count)
403 {
404   transport_connections ++;
405   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "TRANSPORT connect notification for peer `%s' (%u total)\n",
406       GNUNET_i2s (peer), transport_connections);
407   map_connect (peer, th);
408 }
409
410 /**
411  * Function called to notify transport users that another
412  * peer disconnected from us.
413  *
414  * @param cls closure
415  * @param peer the peer that disconnected
416  */
417 void
418 transport_notify_disconnect_cb (void *cls,
419                                const struct
420                                GNUNET_PeerIdentity * peer)
421 {
422   GNUNET_assert (transport_connections > 0);
423   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "TRANSPORT disconnect notification for peer `%s' (%u total)\n",
424       GNUNET_i2s (peer), transport_connections) ;
425   map_disconnect (peer, th);
426   transport_connections --;
427
428 }
429
430
431 static void
432 core_connect_cb (void *cls, const struct GNUNET_PeerIdentity *peer,
433                       const struct GNUNET_ATS_Information *atsi,
434                       unsigned int atsi_count)
435 {
436   if (0 != memcmp (peer, &my_peer_id, sizeof (struct GNUNET_PeerIdentity)))
437   {
438     core_connections ++;
439     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      connect notification for peer `%s' (%u total)\n",
440       GNUNET_i2s (peer), core_connections);
441     map_connect (peer, ch);
442   }
443   else
444   {
445     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      connect notification for myself `%s' (%u total)\n",
446       GNUNET_i2s (peer), core_connections);
447   }
448 }
449
450 static void
451 core_disconnect_cb (void *cls,
452                       const struct
453                       GNUNET_PeerIdentity * peer)
454 {
455   if (0 != memcmp (peer, &my_peer_id, sizeof (struct GNUNET_PeerIdentity)))
456   {
457     GNUNET_assert (core_connections >= 0);
458     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      disconnect notification for peer `%s' (%u total)\n",
459       GNUNET_i2s (peer), core_connections);
460     map_disconnect (peer, ch);
461     core_connections --;
462   }
463   else
464   {
465     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      disconnect notification for myself `%s' (%u total)\n",
466       GNUNET_i2s (peer), core_connections);
467   }
468
469 }
470
471 static void
472 core_init_cb (void *cls, struct GNUNET_CORE_Handle *server,
473                    const struct GNUNET_PeerIdentity *my_identity)
474 {
475   my_peer_id = *my_identity;
476   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Connected to core service\n");
477 }
478
479 /**
480  * Main function that will be run by the scheduler.
481  *
482  * @param cls closure
483  * @param args remaining command-line arguments
484  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
485  * @param cfg configuration
486  */
487 static void
488 run (void *cls, char *const *args, const char *cfgfile,
489      const struct GNUNET_CONFIGURATION_Handle *cfg)
490 {
491   transport_connections = 0;
492   core_connections = 0;
493   mycfg = cfg;
494   stats = GNUNET_STATISTICS_create ("watchdog", cfg);
495   peers = GNUNET_CONTAINER_multihashmap_create (20);
496
497   th = GNUNET_TRANSPORT_connect(cfg, NULL, NULL, NULL,
498                                 &transport_notify_connect_cb,
499                                 &transport_notify_disconnect_cb);
500   GNUNET_assert (th != NULL);
501   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Connected to transport service\n");
502   ch =  GNUNET_CORE_connect (cfg, 1, NULL,
503                              &core_init_cb,
504                              &core_connect_cb,
505                              &core_disconnect_cb,
506                              NULL, GNUNET_NO,
507                              NULL, GNUNET_NO,
508                              NULL);
509   GNUNET_assert (ch != NULL);
510
511   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task, NULL);
512 }
513
514
515 /**
516  * The main function.
517  *
518  * @param argc number of arguments from the command line
519  * @param argv command line arguments
520  * @return 0 ok, 1 on error
521  */
522 int
523 main (int argc, char *const *argv)
524 {
525   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
526     /* FIMXE: add options here */
527     GNUNET_GETOPT_OPTION_END
528   };
529   return (GNUNET_OK ==
530           GNUNET_PROGRAM_run (argc, argv, "connection-watchdog",
531                               gettext_noop ("help text"), options, &run,
532                               NULL)) ? ret : 1;
533 }
534
535 /* end of connection_watchdog.c */