- shorten log messages
[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
136 static void
137 stats_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
138
139 enum protocol
140 {
141   tcp,
142   udp,
143   unixdomain
144 };
145
146 static int
147 check_lowlevel_connections (int port, int protocol)
148 {
149   FILE *f;
150   char * cmdline;
151   char * proto;
152   char line[1024];
153   int count = -1;
154 #ifdef MINGW
155   /* not supported */
156   return count;
157 #else
158
159   switch (protocol) {
160     case tcp:
161       proto = "-t";
162       break;
163     case udp:
164       proto = "-u";
165       break;
166     case unixdomain:
167       proto = "-x";
168       break;
169     default:
170       proto = "";
171       break;
172   }
173
174
175   GNUNET_asprintf(&cmdline, "ss %s \\( sport = :%u or dport = :%u \\)", proto, port, port);
176
177   if (system ("ss > /dev/null 2> /dev/null"))
178     if (system ("ss > /dev/null 2> /dev/null") == 0)
179       f = popen (cmdline, "r");
180     else
181       f = NULL;
182   else
183     f = popen (cmdline, "r");
184   if (!f)
185   {
186     GNUNET_log_strerror(GNUNET_ERROR_TYPE_ERROR, "ss");
187     GNUNET_free (cmdline);
188     return -1;
189   }
190
191   while (NULL != fgets (line, sizeof (line), f))
192   {
193     /* read */
194
195     //printf ("%s", line);
196     count ++;
197   }
198
199   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%i TCP connections established with port %u\n",
200        count, port);
201
202   pclose (f);
203   GNUNET_free (cmdline);
204   return count;
205 #endif
206 }
207
208 int stats_check_cb (void *cls, const char *subsystem,
209                    const char *name, uint64_t value,
210                    int is_persistent)
211 {
212   static int counter;
213
214   uint64_t *val = cls;
215
216   if (NULL != val)
217     (*val) = value;
218
219   counter ++;
220   if (STATS_VALUES == counter)
221   {
222     int fail = GNUNET_NO;
223     int low_level_connections_tcp = check_lowlevel_connections (2086, tcp);
224     int low_level_connections_udp = check_lowlevel_connections (2086, udp);
225
226     if (transport_connections != core_connections)
227     {
228       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
229            "Transport connections are inconsistent:\n %u transport notifications <-> %u core notifications\n",
230            transport_connections, core_connections);
231       fail = GNUNET_YES;
232     }
233
234     if (transport_connections != statistics_transport_connections)
235     {
236       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
237            "Transport connections are inconsistent:\n %u transport notifications <-> %u in statistics (peers connected)\n",
238            transport_connections, statistics_transport_connections);
239       fail = GNUNET_YES;
240     }
241     if (core_connections != statistics_core_entries_session_map)
242     {
243       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
244            "Transport connections are inconsistent:\n %u core notifications <-> %u in statistics (entries session map)\n",
245            core_connections, statistics_core_entries_session_map);
246       fail = GNUNET_YES;
247     }
248
249     if (core_connections != statistics_core_neighbour_entries)
250     {
251       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
252            "Transport connections are inconsistent:\n %u core notifications <-> %u in statistics (neighbour entries allocated)\n",
253            core_connections, statistics_core_neighbour_entries);
254       fail = GNUNET_YES;
255     }
256
257     if (GNUNET_NO == fail)
258       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
259          "Statistics consistency check successful : (%u transport / %u core) connections established\n", transport_connections, core_connections);
260
261     /* This is only an issue when transport_connections > statistics_transport_tcp_connections */
262     if ((low_level_connections_tcp != -1) && (statistics_transport_tcp_connections > low_level_connections_tcp))
263     {
264       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
265            "Lowlevel connections are inconsistent:\n %u transport tcp sessions <-> %i established tcp connections\n",
266            statistics_transport_tcp_connections, low_level_connections_tcp);
267       fail = GNUNET_YES;
268     }
269     else if (low_level_connections_tcp != -1)
270     {
271       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
272            "%u TCP connections, %u UDP connections \n",
273            low_level_connections_tcp, low_level_connections_udp);
274     }
275     else
276     {
277       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
278            "Error obtaining TCP connections\n");
279     }
280
281
282     if (transport_connections > statistics_transport_tcp_connections)
283     {
284       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285            "Transport connections are inconsistent: %u transport notifications <-> %u in statistics (statistics_transport_tcp_connections)\n",
286            transport_connections, statistics_transport_tcp_connections);
287       fail = GNUNET_YES;
288     }
289     else
290     {
291       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
292            "Transport connections are inconsistent: %u transport notifications <-> %u in statistics (statistics_transport_tcp_connections)\n",
293            transport_connections, statistics_transport_tcp_connections);
294     }
295
296     if (GNUNET_SCHEDULER_NO_TASK == statistics_task)
297       statistics_task = GNUNET_SCHEDULER_add_delayed(REPEATED_STATS_DELAY, &stats_check, NULL);
298
299     stat_check_running = GNUNET_NO;
300     counter = 0;
301   }
302
303   return GNUNET_OK;
304 }
305
306
307 static void
308 stats_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
309 {
310   statistics_task = GNUNET_SCHEDULER_NO_TASK;
311
312   if (GNUNET_YES == stat_check_running)
313   {
314     statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
315   }
316
317   stat_check_running = GNUNET_YES;
318
319   statistics_transport_connections = 0 ;
320   statistics_core_entries_session_map = 0;
321   statistics_core_neighbour_entries = 0;
322
323   GNUNET_STATISTICS_get (stats, "transport", "# peers connected", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_transport_connections);
324   GNUNET_STATISTICS_get (stats, "transport", "# TCP sessions active", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_transport_tcp_connections);
325   GNUNET_STATISTICS_get (stats, "core", "# neighbour entries allocated", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_core_neighbour_entries);
326   GNUNET_STATISTICS_get (stats, "core", "# entries in session map", GNUNET_TIME_UNIT_MINUTES, NULL, &stats_check_cb, &statistics_core_entries_session_map);
327 }
328
329
330 static void
331 map_connect (const struct GNUNET_PeerIdentity *peer, void * source)
332 {
333   struct PeerContainer * pc;
334   if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains(peers, &peer->hashPubKey))
335   {
336     pc = GNUNET_malloc (sizeof (struct PeerContainer));
337     pc->id = *peer;
338     pc->core_connected = GNUNET_NO;
339     pc->transport_connected = GNUNET_NO;
340     GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put(peers, &peer->hashPubKey, pc, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
341   }
342
343   pc = GNUNET_CONTAINER_multihashmap_get(peers, &peer->hashPubKey);
344   if (source == th)
345   {
346     if (GNUNET_NO == pc->transport_connected)
347     {
348       pc->transport_connected = GNUNET_YES;
349     }
350     else
351     {
352       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
353            "%s notified multiple times about for peers `%s' (%s : %s)\n",
354            "TRANSPORT",
355            GNUNET_i2s (&pc->id),
356            "CORE", (pc->core_connected == GNUNET_YES) ? "yes" : "no");
357       GNUNET_break (0);
358     }
359   }
360   if (source == ch)
361   {
362     if (GNUNET_NO == pc->core_connected)
363     {
364       pc->core_connected = GNUNET_YES;
365     }
366     else
367     {
368       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
369            "%s notified multiple times about for peers `%s' (%s : %s)\n",
370            "CORE",
371            GNUNET_i2s (&pc->id),
372                "TRANSPORT", (pc->transport_connected == GNUNET_YES) ? "yes" : "no");
373       GNUNET_break (0);
374     }
375   }
376   if (GNUNET_SCHEDULER_NO_TASK != check_task)
377     GNUNET_SCHEDULER_cancel(check_task);
378   check_task = GNUNET_SCHEDULER_add_delayed(CHECK_DELAY, &map_check, NULL);
379
380   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
381     GNUNET_SCHEDULER_cancel(statistics_task);
382   statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
383 }
384
385
386 static void
387 map_disconnect (const struct GNUNET_PeerIdentity * peer, void * source)
388 {
389
390   struct PeerContainer * pc;
391   if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_contains(peers, &peer->hashPubKey))
392   {
393     if (source == th)
394     {
395       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
396          "%s disconnect notification for unknown peer `%s'\n",
397          "TRANSPORT", GNUNET_i2s (peer));
398       GNUNET_break (0);
399       return;
400     }
401     if (source == ch)
402     {
403       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
404          "%s disconnect notification for unknown peer `%s'\n",
405          "CORE", GNUNET_i2s (peer));
406       return;
407     }
408   }
409
410   pc = GNUNET_CONTAINER_multihashmap_get(peers, &peer->hashPubKey);
411   if (source == th)
412   {
413     if (GNUNET_YES == pc->transport_connected)
414     {
415       pc->transport_connected = GNUNET_NO;
416     }
417     else
418     {
419       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
420            "%s notified for not connected peer `%s' (%s : %s)\n",
421            "TRANSPORT",
422            GNUNET_i2s (&pc->id),
423            "CORE", (pc->core_connected == GNUNET_YES) ? "yes" : "no");
424       GNUNET_break (0);
425     }
426   }
427   if (source == ch)
428   {
429     if (GNUNET_YES == pc->core_connected)
430     {
431       pc->core_connected = GNUNET_NO;
432     }
433     else
434     {
435       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
436            "%s notified for not connected peer `%s' (%s : %s)\n",
437            "CORE",
438            GNUNET_i2s (&pc->id),
439            "TRANSPORT", (pc->transport_connected == GNUNET_YES) ? "yes" : "no");
440       GNUNET_break (0);
441     }
442   }
443
444   if ((GNUNET_NO == pc->core_connected) && (GNUNET_NO == pc->transport_connected))
445   {
446     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Removing peer `%s'\n", GNUNET_i2s (&pc->id));
447     GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_remove (peers, &peer->hashPubKey, pc));
448     GNUNET_free (pc);
449   }
450
451   if (GNUNET_SCHEDULER_NO_TASK != check_task)
452     GNUNET_SCHEDULER_cancel(check_task);
453   check_task = GNUNET_SCHEDULER_add_delayed(CHECK_DELAY, &map_check, NULL);
454
455   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
456     GNUNET_SCHEDULER_cancel(statistics_task);
457   statistics_task = GNUNET_SCHEDULER_add_delayed(STATS_DELAY, &stats_check, NULL);
458 }
459
460
461 static void
462 cleanup_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
463 {
464   if (NULL != th)
465   {
466     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnecting from transport service\n");
467     GNUNET_TRANSPORT_disconnect (th);
468     th = NULL;
469   }
470   if (NULL != ch)
471   {
472     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnecting from core service\n");
473     GNUNET_CORE_disconnect (ch);
474     ch = NULL;
475   }
476
477   if (GNUNET_SCHEDULER_NO_TASK != statistics_task)
478   {
479     GNUNET_SCHEDULER_cancel(statistics_task);
480     statistics_task = GNUNET_SCHEDULER_NO_TASK;
481   }
482
483   if (GNUNET_SCHEDULER_NO_TASK != check_task)
484   {
485     GNUNET_SCHEDULER_cancel(check_task);
486     check_task = GNUNET_SCHEDULER_NO_TASK;
487   }
488   check_task = GNUNET_SCHEDULER_add_now (&map_check, &map_cleanup);
489 }
490
491 void
492 transport_notify_connect_cb (void *cls,
493                 const struct GNUNET_PeerIdentity
494                 * peer,
495                 const struct
496                 GNUNET_ATS_Information * ats,
497                 uint32_t ats_count)
498 {
499   transport_connections ++;
500   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "TRANSPORT connect for peer `%s' (%u total)\n",
501       GNUNET_i2s (peer), transport_connections);
502   map_connect (peer, th);
503 }
504
505 /**
506  * Function called to notify transport users that another
507  * peer disconnected from us.
508  *
509  * @param cls closure
510  * @param peer the peer that disconnected
511  */
512 void
513 transport_notify_disconnect_cb (void *cls,
514                                const struct
515                                GNUNET_PeerIdentity * peer)
516 {
517   GNUNET_assert (transport_connections > 0);
518   GNUNET_log (GNUNET_ERROR_TYPE_INFO, "TRANSPORT disconnect for peer `%s' (%u total)\n",
519       GNUNET_i2s (peer), transport_connections) ;
520   map_disconnect (peer, th);
521   transport_connections --;
522
523 }
524
525
526 static void
527 core_connect_cb (void *cls, const struct GNUNET_PeerIdentity *peer,
528                       const struct GNUNET_ATS_Information *atsi,
529                       unsigned int atsi_count)
530 {
531   if (0 != memcmp (peer, &my_peer_id, sizeof (struct GNUNET_PeerIdentity)))
532   {
533     core_connections ++;
534     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      connect for peer `%s' (%u total)\n",
535       GNUNET_i2s (peer), core_connections);
536     map_connect (peer, ch);
537   }
538   else
539   {
540     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      connect for myself `%s' (%u total)\n",
541       GNUNET_i2s (peer), core_connections);
542   }
543 }
544
545 static void
546 core_disconnect_cb (void *cls,
547                       const struct
548                       GNUNET_PeerIdentity * peer)
549 {
550   if (0 != memcmp (peer, &my_peer_id, sizeof (struct GNUNET_PeerIdentity)))
551   {
552     GNUNET_assert (core_connections >= 0);
553     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      disconnect for peer `%s' (%u total)\n",
554       GNUNET_i2s (peer), core_connections);
555     map_disconnect (peer, ch);
556     core_connections --;
557   }
558   else
559   {
560     GNUNET_log (GNUNET_ERROR_TYPE_INFO, "CORE      disconnect for myself `%s' (%u total)\n",
561       GNUNET_i2s (peer), core_connections);
562   }
563
564 }
565
566 static void
567 core_init_cb (void *cls, struct GNUNET_CORE_Handle *server,
568                    const struct GNUNET_PeerIdentity *my_identity)
569 {
570   my_peer_id = *my_identity;
571   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Connected to core service\n");
572 }
573
574 /**
575  * Main function that will be run by the scheduler.
576  *
577  * @param cls closure
578  * @param args remaining command-line arguments
579  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
580  * @param cfg configuration
581  */
582 static void
583 run (void *cls, char *const *args, const char *cfgfile,
584      const struct GNUNET_CONFIGURATION_Handle *cfg)
585 {
586   transport_connections = 0;
587   core_connections = 0;
588   mycfg = cfg;
589
590   stats = GNUNET_STATISTICS_create ("watchdog", cfg);
591   peers = GNUNET_CONTAINER_multihashmap_create (20);
592
593   th = GNUNET_TRANSPORT_connect(cfg, NULL, NULL, NULL,
594                                 &transport_notify_connect_cb,
595                                 &transport_notify_disconnect_cb);
596   GNUNET_assert (th != NULL);
597   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Connected to transport service\n");
598   ch =  GNUNET_CORE_connect (cfg, 1, NULL,
599                              &core_init_cb,
600                              &core_connect_cb,
601                              &core_disconnect_cb,
602                              NULL, GNUNET_NO,
603                              NULL, GNUNET_NO,
604                              NULL);
605   GNUNET_assert (ch != NULL);
606
607   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task, NULL);
608
609 }
610
611
612 /**
613  * The main function.
614  *
615  * @param argc number of arguments from the command line
616  * @param argv command line arguments
617  * @return 0 ok, 1 on error
618  */
619 int
620 main (int argc, char *const *argv)
621 {
622   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
623     /* FIMXE: add options here */
624     GNUNET_GETOPT_OPTION_END
625   };
626   return (GNUNET_OK ==
627           GNUNET_PROGRAM_run (argc, argv, "cn",
628                               gettext_noop ("help text"), options, &run,
629                               NULL)) ? ret : 1;
630 }
631
632 /* end of connection_watchdog.c */