fix
[oweals/gnunet.git] / src / peerinfo / gnunet-service-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2004, 2005, 2007, 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 /**
22  * @file peerinfo/gnunet-service-peerinfo.c
23  * @brief maintains list of known peers
24  *
25  * Code to maintain the list of currently known hosts (in memory
26  * structure of data/hosts/).
27  *
28  * @author Christian Grothoff
29  *
30  * TODO:
31  * - HostEntries are never 'free'd (add expiration, upper bound?)
32  */
33
34 #include "platform.h"
35 #include "gnunet_crypto_lib.h"
36 #include "gnunet_disk_lib.h"
37 #include "gnunet_hello_lib.h"
38 #include "gnunet_protocols.h"
39 #include "gnunet_service_lib.h"
40 #include "gnunet_statistics_service.h"
41 #include "peerinfo.h"
42
43 /**
44  * How often do we scan the HOST_DIR for new entries?
45  */
46 #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
47
48 /**
49  * How often do we discard old entries in data/hosts/?
50  */
51 #define DATA_HOST_CLEAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60)
52
53 /**
54  * In-memory cache of known hosts.
55  */
56 struct HostEntry
57 {
58
59   /**
60    * This is a linked list.
61    */
62   struct HostEntry *next;
63
64   /**
65    * Identity of the peer.
66    */
67   struct GNUNET_PeerIdentity identity;
68
69   /**
70    * Hello for the peer (can be NULL)
71    */
72   struct GNUNET_HELLO_Message *hello;
73
74 };
75
76
77 /**
78  * The in-memory list of known hosts.
79  */
80 static struct HostEntry *hosts;
81
82 /**
83  * Clients to immediately notify about all changes.
84  */
85 static struct GNUNET_SERVER_NotificationContext *notify_list;
86
87 /**
88  * Directory where the hellos are stored in (data/hosts)
89  */
90 static char *networkIdDirectory;
91
92 /**
93  * Handle for reporting statistics.
94  */
95 static struct GNUNET_STATISTICS_Handle *stats;
96
97
98 /**
99  * Notify all clients in the notify list about the
100  * given host entry changing.
101  */
102 static struct InfoMessage *
103 make_info_message (const struct HostEntry *he)
104 {
105   struct InfoMessage *im;
106   size_t hs;
107
108   hs = (he->hello == NULL) ? 0 : GNUNET_HELLO_size (he->hello);
109   im = GNUNET_malloc (sizeof (struct InfoMessage) + hs);
110   im->header.size = htons (hs + sizeof (struct InfoMessage));
111   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
112   im->peer = he->identity;
113   if (he->hello != NULL)
114     memcpy (&im[1], he->hello, hs);
115   return im;
116 }
117
118
119 /**
120  * Address iterator that causes expired entries to be discarded.
121  *
122  * @param cls pointer to the current time
123  * @param tname name of the transport
124  * @param expiration expiration time for the address
125  * @param addr the address
126  * @param addrlen length of addr in bytes
127  * @return GNUNET_NO if expiration smaller than the current time
128  */
129 static int
130 discard_expired (void *cls,
131                  const char *tname,
132                  struct GNUNET_TIME_Absolute expiration,
133                  const void *addr, uint16_t addrlen)
134 {
135   const struct GNUNET_TIME_Absolute *now = cls;
136   if (now->value > expiration.value)
137     {
138       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
139                   _("Removing expired address of transport `%s'\n"),
140                   tname);
141       return GNUNET_NO;
142     }
143   return GNUNET_OK;
144 }
145
146
147 /**
148  * Get the filename under which we would store the GNUNET_HELLO_Message
149  * for the given host and protocol.
150  * @return filename of the form DIRECTORY/HOSTID
151  */
152 static char *
153 get_host_filename (const struct GNUNET_PeerIdentity *id)
154 {
155   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
156   char *fn;
157
158   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
159   GNUNET_asprintf (&fn,
160                    "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil);
161   return fn;
162 }
163
164
165 /**
166  * Find the host entry for the given peer.  FIXME: replace by hash map!
167  * @return NULL if not found
168  */
169 static struct HostEntry *
170 lookup_host_entry (const struct GNUNET_PeerIdentity *id)
171 {
172   struct HostEntry *pos;
173
174   pos = hosts;
175   while ((pos != NULL) &&
176          (0 !=
177           memcmp (id, &pos->identity, sizeof (struct GNUNET_PeerIdentity))))
178     pos = pos->next;
179   return pos;
180 }
181
182
183 /**
184  * Broadcast information about the given entry to all 
185  * clients that care.
186  *
187  * @param entry entry to broadcast about
188  */
189 static void
190 notify_all (struct HostEntry *entry)
191 {
192   struct InfoMessage *msg;
193
194   msg = make_info_message (entry);
195   GNUNET_SERVER_notification_context_broadcast (notify_list,
196                                                 &msg->header,
197                                                 GNUNET_NO);
198   GNUNET_free (msg);
199 }
200
201
202 /**
203  * Add a host to the list.
204  *
205  * @param identity the identity of the host
206  */
207 static void
208 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
209 {
210   struct HostEntry *entry;
211   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
212   const struct GNUNET_HELLO_Message *hello;
213   struct GNUNET_HELLO_Message *hello_clean;
214   int size;
215   struct GNUNET_TIME_Absolute now;
216   char *fn;
217
218   entry = lookup_host_entry (identity);
219   if (entry != NULL)
220     return;
221   GNUNET_STATISTICS_update (stats,
222                             gettext_noop ("# peers known"),
223                             1,
224                             GNUNET_NO);
225   entry = GNUNET_malloc (sizeof (struct HostEntry));
226   entry->identity = *identity;
227
228   fn = get_host_filename (identity);
229   if (GNUNET_DISK_file_test (fn) == GNUNET_YES)
230     {
231       size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
232       hello = (const struct GNUNET_HELLO_Message *) buffer;
233       if ( (size < sizeof (struct GNUNET_MessageHeader)) ||
234            (size != ntohs((((const struct GNUNET_MessageHeader*) hello)->size))) ||
235            (size != GNUNET_HELLO_size (hello)) )
236         {
237           GNUNET_break (0);
238           if (0 != UNLINK (fn))
239             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
240                                       "unlink",
241                                       fn);
242         }
243       else
244         {
245           now = GNUNET_TIME_absolute_get ();
246           hello_clean = GNUNET_HELLO_iterate_addresses (hello,
247                                                         GNUNET_YES,
248                                                         &discard_expired, &now);
249           entry->hello = hello_clean;
250         }
251     }
252   GNUNET_free (fn);
253   entry->next = hosts;
254   hosts = entry;
255   notify_all (entry);
256 }
257
258
259 /**
260  * Remove a file that should not be there.  LOG
261  * success or failure.
262  */
263 static void
264 remove_garbage (const char *fullname)
265 {
266   if (0 == UNLINK (fullname))
267     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
268                 _
269                 ("File `%s' in directory `%s' does not match naming convention. "
270                  "Removed.\n"), fullname, networkIdDirectory);
271   else
272     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR |
273                               GNUNET_ERROR_TYPE_BULK, "unlink", fullname);
274 }
275
276
277 static int
278 hosts_directory_scan_callback (void *cls, const char *fullname)
279 {
280   unsigned int *matched = cls;
281   struct GNUNET_PeerIdentity identity;
282   const char *filename;
283
284   if (GNUNET_DISK_file_test (fullname) != GNUNET_YES)
285     return GNUNET_OK;           /* ignore non-files */
286   if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded))
287     {
288       remove_garbage (fullname);
289       return GNUNET_OK;
290     }
291   filename =
292     &fullname[strlen (fullname) -
293               sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1];
294   if (filename[-1] != DIR_SEPARATOR)
295     {
296       remove_garbage (fullname);
297       return GNUNET_OK;
298     }
299   if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (filename,
300                                                    &identity.hashPubKey))
301     {
302       remove_garbage (fullname);
303       return GNUNET_OK;
304     }
305   (*matched)++;
306   add_host_to_known_hosts (&identity);
307   return GNUNET_OK;
308 }
309
310
311 /**
312  * Call this method periodically to scan data/hosts for new hosts.
313  */
314 static void
315 cron_scan_directory_data_hosts (void *cls,
316                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
317 {
318   static unsigned int retries;
319   unsigned int count;
320
321   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
322     return;
323   count = 0;
324   GNUNET_DISK_directory_create (networkIdDirectory);
325   GNUNET_DISK_directory_scan (networkIdDirectory,
326                               &hosts_directory_scan_callback, &count);
327   if ((0 == count) && (0 == (++retries & 31)))
328     GNUNET_log (GNUNET_ERROR_TYPE_WARNING |
329                 GNUNET_ERROR_TYPE_BULK,
330                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
331   GNUNET_SCHEDULER_add_delayed (tc->sched,
332                                 DATA_HOST_FREQ,
333                                 &cron_scan_directory_data_hosts, NULL);
334 }
335
336
337 /**
338  * Bind a host address (hello) to a hostId.
339  *
340  * @param peer the peer for which this is a hello
341  * @param hello the verified (!) hello message
342  */
343 static void
344 bind_address (const struct GNUNET_PeerIdentity *peer,
345               const struct GNUNET_HELLO_Message *hello)
346 {
347   char *fn;
348   struct HostEntry *host;
349   struct GNUNET_HELLO_Message *mrg;
350   struct GNUNET_TIME_Absolute delta;
351
352   add_host_to_known_hosts (peer);
353   host = lookup_host_entry (peer);
354   GNUNET_assert (host != NULL);
355   if (host->hello == NULL)
356     {
357       host->hello = GNUNET_malloc (GNUNET_HELLO_size (hello));
358       memcpy (host->hello, hello, GNUNET_HELLO_size (hello));
359     }
360   else
361     {
362       mrg = GNUNET_HELLO_merge (host->hello, hello);
363       delta = GNUNET_HELLO_equals (mrg,
364                                    host->hello,
365                                    GNUNET_TIME_absolute_get ());
366       if (delta.value == GNUNET_TIME_UNIT_FOREVER_ABS.value)
367         {
368           GNUNET_free (mrg);
369           return;
370         }
371       GNUNET_free (host->hello);
372       host->hello = mrg;
373     }
374   fn = get_host_filename (peer);
375   GNUNET_DISK_directory_create_for_file (fn);
376   GNUNET_DISK_fn_write (fn, 
377                         host->hello, 
378                         GNUNET_HELLO_size (host->hello),
379                         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
380                         | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ);
381   GNUNET_free (fn);
382   notify_all (host);
383 }
384
385
386 /**
387  * Do transmit info either for only the host matching the given
388  * argument or for all known hosts.
389  *
390  * @param only NULL to hit all hosts, otherwise specifies a particular target
391  * @param client who is making the request (and will thus receive our confirmation)
392  */
393 static void
394 send_to_each_host (const struct GNUNET_PeerIdentity *only,
395                    struct GNUNET_SERVER_Client *client)
396 {
397   struct HostEntry *pos;
398   struct InfoMessage *im;
399   uint16_t hs;
400   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
401   struct GNUNET_SERVER_TransmitContext *tc;
402   int match;
403
404   tc = GNUNET_SERVER_transmit_context_create (client);
405   match = GNUNET_NO;
406   pos = hosts;  
407   while (pos != NULL)
408     {
409       if ((only == NULL) ||
410           (0 ==
411            memcmp (only, &pos->identity,
412                    sizeof (struct GNUNET_PeerIdentity))))
413         {
414           hs = 0;
415           im = (struct InfoMessage *) buf;
416           if (pos->hello != NULL)
417             {
418               hs = GNUNET_HELLO_size (pos->hello);
419               GNUNET_assert (hs <
420                              GNUNET_SERVER_MAX_MESSAGE_SIZE -
421                              sizeof (struct InfoMessage));
422               memcpy (&im[1], pos->hello, hs);
423               match = GNUNET_YES;
424             }
425           im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
426           im->header.size = htons (sizeof (struct InfoMessage) + hs);
427           im->reserved = htonl (0);
428           im->peer = pos->identity;
429           GNUNET_SERVER_transmit_context_append_message (tc,
430                                                          &im->header);
431         }
432       pos = pos->next;
433     }
434   if ( (only != NULL) &&
435        (match == GNUNET_NO) )
436     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
437                 "No `%s' message was found for peer `%4s'\n",
438                 "HELLO",
439                 GNUNET_i2s (only));
440   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
441                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
442   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
443 }
444
445
446 /**
447  * @brief delete expired HELLO entries in data/hosts/
448  */
449 static int
450 discard_hosts_helper (void *cls, const char *fn)
451 {
452   struct GNUNET_TIME_Absolute *now = cls;
453   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
454   const struct GNUNET_HELLO_Message *hello;
455   struct GNUNET_HELLO_Message *new_hello;
456   int size;
457
458   size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
459   if (size < sizeof (struct GNUNET_MessageHeader))
460     {
461       if (0 != UNLINK (fn))
462         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
463                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);
464       return GNUNET_OK;
465     }
466   hello = (const struct GNUNET_HELLO_Message *) buffer;
467   new_hello = GNUNET_HELLO_iterate_addresses (hello,
468                                               GNUNET_YES,
469                                               &discard_expired, now);
470   if (new_hello != NULL)
471     {
472       GNUNET_DISK_fn_write (fn, 
473                             new_hello,
474                             GNUNET_HELLO_size (new_hello),
475                             GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
476                             | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ);
477       GNUNET_free (new_hello);
478     }
479   else
480     {
481       if (0 != UNLINK (fn))
482         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
483                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);      
484     }
485   return GNUNET_OK;
486 }
487
488
489 /**
490  * Call this method periodically to scan data/hosts for new hosts.
491  */
492 static void
493 cron_clean_data_hosts (void *cls,
494                        const struct GNUNET_SCHEDULER_TaskContext *tc)
495 {
496   struct GNUNET_TIME_Absolute now;
497
498   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
499     return;
500   now = GNUNET_TIME_absolute_get ();
501   GNUNET_DISK_directory_scan (networkIdDirectory,
502                               &discard_hosts_helper, &now);
503
504   GNUNET_SCHEDULER_add_delayed (tc->sched,
505                                 DATA_HOST_CLEAN_FREQ,
506                                 &cron_clean_data_hosts, NULL);
507 }
508
509
510 /**
511  * Handle HELLO-message.
512  *
513  * @param cls closure
514  * @param client identification of the client
515  * @param message the actual message
516  */
517 static void
518 handle_hello (void *cls,
519               struct GNUNET_SERVER_Client *client,
520               const struct GNUNET_MessageHeader *message)
521 {
522   const struct GNUNET_HELLO_Message *hello;
523   struct GNUNET_PeerIdentity pid;
524
525   hello = (const struct GNUNET_HELLO_Message *) message;
526   if (GNUNET_OK !=  GNUNET_HELLO_get_id (hello, &pid))
527     {
528       GNUNET_break (0);
529       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
530       return;
531     }
532 #if DEBUG_PEERINFO
533   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
534               "`%s' message received for peer `%4s'\n",
535               "HELLO",
536               GNUNET_i2s (&pid));
537 #endif
538   bind_address (&pid, hello);
539   GNUNET_SERVER_receive_done (client, GNUNET_OK);
540 }
541
542
543 /**
544  * Handle GET-message.
545  *
546  * @param cls closure
547  * @param client identification of the client
548  * @param message the actual message
549  */
550 static void
551 handle_get (void *cls,
552             struct GNUNET_SERVER_Client *client,
553             const struct GNUNET_MessageHeader *message)
554 {
555   const struct ListPeerMessage *lpm;
556
557   lpm = (const struct ListPeerMessage *) message;
558 #if DEBUG_PEERINFO
559   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
560               "`%s' message received for peer `%4s'\n",
561               "GET",
562               GNUNET_i2s (&lpm->peer));
563 #endif
564   send_to_each_host (&lpm->peer, client);
565 }
566
567
568 /**
569  * Handle GET-ALL-message.
570  *
571  * @param cls closure
572  * @param client identification of the client
573  * @param message the actual message
574  */
575 static void
576 handle_get_all (void *cls,
577                 struct GNUNET_SERVER_Client *client,
578                 const struct GNUNET_MessageHeader *message)
579 {
580 #if DEBUG_PEERINFO
581   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
582               "`%s' message received\n",
583               "GET_ALL");
584 #endif
585   send_to_each_host (NULL, client);
586 }
587
588
589 /**
590  * Handle NOTIFY-message.
591  *
592  * @param cls closure
593  * @param client identification of the client
594  * @param message the actual message
595  */
596 static void
597 handle_notify (void *cls,
598             struct GNUNET_SERVER_Client *client,
599             const struct GNUNET_MessageHeader *message)
600 {
601   struct InfoMessage *msg;
602   struct HostEntry *pos;
603
604 #if DEBUG_PEERINFO
605   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
606               "`%s' message received\n",
607               "NOTIFY");
608 #endif
609   GNUNET_SERVER_notification_context_add (notify_list,
610                                           client);
611   pos = hosts;
612   while (NULL != pos)
613     {
614       msg = make_info_message (pos);
615       GNUNET_SERVER_notification_context_unicast (notify_list,
616                                                   client,
617                                                   &msg->header,
618                                                   GNUNET_NO);
619       GNUNET_free (msg);
620       pos = pos->next;
621     }
622 }
623
624
625 /**
626  * Clean up our state.  Called during shutdown.
627  *
628  * @param cls unused
629  * @param tc scheduler task context, unused
630  */
631 static void
632 shutdown_task (void *cls,
633                const struct GNUNET_SCHEDULER_TaskContext *tc)
634 {
635   GNUNET_SERVER_notification_context_destroy (notify_list);
636   notify_list = NULL;
637   if (stats != NULL)
638     {
639       GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
640       stats = NULL;
641     }
642 }
643
644
645 /**
646  * Process statistics requests.
647  *
648  * @param cls closure
649  * @param sched scheduler to use
650  * @param server the initialized server
651  * @param cfg configuration to use
652  */
653 static void
654 run (void *cls,
655      struct GNUNET_SCHEDULER_Handle *sched,
656      struct GNUNET_SERVER_Handle *server,
657      const struct GNUNET_CONFIGURATION_Handle *cfg)
658 {
659   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
660     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
661     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
662      sizeof (struct ListPeerMessage)},
663     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
664      sizeof (struct GNUNET_MessageHeader)},
665     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
666      sizeof (struct GNUNET_MessageHeader)},
667     {NULL, NULL, 0, 0}
668   };
669   stats = GNUNET_STATISTICS_create (sched, "peerinfo", cfg);
670   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
671   GNUNET_assert (GNUNET_OK ==
672                  GNUNET_CONFIGURATION_get_value_filename (cfg,
673                                                           "peerinfo",
674                                                           "HOSTS",
675                                                           &networkIdDirectory));
676   GNUNET_DISK_directory_create (networkIdDirectory);
677   GNUNET_SCHEDULER_add_with_priority (sched,
678                                       GNUNET_SCHEDULER_PRIORITY_IDLE,
679                                       &cron_scan_directory_data_hosts, NULL);
680   GNUNET_SCHEDULER_add_with_priority (sched,
681                                       GNUNET_SCHEDULER_PRIORITY_IDLE,
682                                       &cron_clean_data_hosts, NULL);
683   GNUNET_SCHEDULER_add_delayed (sched,
684                                 GNUNET_TIME_UNIT_FOREVER_REL,
685                                 &shutdown_task, NULL);
686   GNUNET_SERVER_add_handlers (server, handlers);
687 }
688
689
690 /**
691  * The main function for the statistics service.
692  *
693  * @param argc number of arguments from the command line
694  * @param argv command line arguments
695  * @return 0 ok, 1 on error
696  */
697 int
698 main (int argc, char *const *argv)
699 {
700   int ret;
701
702   ret = (GNUNET_OK ==
703          GNUNET_SERVICE_run (argc,
704                              argv,
705                               "peerinfo",
706                              GNUNET_SERVICE_OPTION_NONE,
707                              &run, NULL)) ? 0 : 1;
708   GNUNET_free_non_null (networkIdDirectory);
709   return ret;
710 }
711
712
713 /* end of gnunet-service-peerinfo.c */