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