2b25dccdd8033e3e2a9f439f719e219943b4310b
[oweals/gnunet.git] / src / topology / gnunet-daemon-topology.c
1 /*
2      This file is part of GNUnet.
3      (C) 2007, 2008, 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 topology/gnunet-daemon-topology.c
23  * @brief code for bootstrapping via topology servers
24  * @author Christian Grothoff
25  */
26
27 #include <stdlib.h>
28 #include "platform.h"
29 #include "gnunet_core_service.h"
30 #include "gnunet_protocols.h"
31 #include "gnunet_peerinfo_service.h"
32 #include "gnunet_util_lib.h"
33
34
35 #define DEBUG_TOPOLOGY GNUNET_NO
36
37 /**
38  * For how long do we blacklist a peer after a failed
39  * connection attempt?
40  */ 
41 #define BLACKLIST_AFTER_ATTEMPT GNUNET_TIME_UNIT_HOURS
42
43 /**
44  * For how long do we blacklist a friend after a failed
45  * connection attempt?
46  */ 
47 #define BLACKLIST_AFTER_ATTEMPT_FRIEND GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
48
49
50 /**
51  * List of neighbours, friends and blacklisted peers.
52  */
53 struct PeerList
54 {
55
56   /**
57    * This is a linked list.
58    */
59   struct PeerList *next;
60
61   /**
62    * Is this peer listed here because he is a friend?
63    */
64   int is_friend;
65
66   /**
67    * Are we connected to this peer right now?
68    */
69   int is_connected;
70
71   /**
72    * Until what time should we not try to connect again
73    * to this peer?
74    */
75   struct GNUNET_TIME_Absolute blacklisted_until;
76
77   /**
78    * ID of the peer.
79    */
80   struct GNUNET_PeerIdentity id;
81   
82 };
83
84
85 /**
86  * Our scheduler.
87  */
88 static struct GNUNET_SCHEDULER_Handle * sched;
89
90 /**
91  * Our configuration.
92  */
93 static struct GNUNET_CONFIGURATION_Handle * cfg;
94    
95 /**
96  * Handle to the core API.
97  */
98 static struct GNUNET_CORE_Handle *handle;
99
100 /**
101  * Identity of this peer.
102  */
103 static struct GNUNET_PeerIdentity my_identity;
104          
105 /**
106  * Linked list of all of our friends and all of our current
107  * neighbours.
108  */
109 static struct PeerList *friends;
110
111 /**
112  * Flag to disallow non-friend connections (pure F2F mode).
113  */
114 static int friends_only;
115
116 /**
117  * Minimum number of friends to have in the
118  * connection set before we allow non-friends.
119  */
120 static unsigned int minimum_friend_count;
121
122 /**
123  * Number of peers (friends and others) that we are currently connected to.
124  */
125 static unsigned int connection_count;
126  
127 /**
128  * Target number of connections.
129  */
130 static unsigned int target_connection_count;
131  
132 /**
133  * Number of friends that we are currently connected to.
134  */
135 static unsigned int friend_count;
136  
137 /**
138  * Should the topology daemon try to establish connections?
139  */
140 static int autoconnect;
141
142
143
144 /**
145  * Force a disconnect from the specified peer.
146  */
147 static void
148 force_disconnect (const struct GNUNET_PeerIdentity *peer)
149 {
150   GNUNET_CORE_peer_configure (handle,
151                               peer,
152                               GNUNET_TIME_UNIT_FOREVER_REL,
153                               0,
154                               0,
155                               0,
156                               NULL,
157                               NULL);
158 }
159
160
161 /**
162  * Function called by core when our attempt to connect
163  * succeeded.  Does nothing.
164  */
165 static size_t 
166 ready_callback (void *cls,
167                 size_t size, void *buf)
168 {
169   return 0;
170 }
171
172
173 /**
174  * Try to connect to the specified peer.
175  *
176  * @param pos NULL if not in friend list yet
177  */
178 static void
179 attempt_connect (const struct GNUNET_PeerIdentity *peer,
180                  struct PeerList *pos)
181 {
182   if (pos == NULL)
183     {
184       pos = friends;
185       while (pos != NULL)
186         {
187           if (0 == memcmp (&pos->id, peer, sizeof (struct GNUNET_PeerIdentity)))
188             break;
189         }
190     }
191   if (pos == NULL)
192     {
193       pos = GNUNET_malloc (sizeof(struct PeerList));
194       pos->id = *peer;
195       pos->next = friends;
196       friends = pos;
197     }
198   if (GNUNET_YES == pos->is_friend)
199     pos->blacklisted_until = GNUNET_TIME_relative_to_absolute (BLACKLIST_AFTER_ATTEMPT_FRIEND);
200   else
201     pos->blacklisted_until = GNUNET_TIME_relative_to_absolute (BLACKLIST_AFTER_ATTEMPT);
202   GNUNET_CORE_notify_transmit_ready (handle,
203                                      0 /* priority */,
204                                      GNUNET_TIME_UNIT_MINUTES,
205                                      peer,
206                                      sizeof(struct GNUNET_MessageHeader),
207                                      &ready_callback,
208                                      NULL);
209 }
210
211
212 /**
213  * Is this peer one of our friends?
214  */
215 static int
216 is_friend (const struct GNUNET_PeerIdentity * peer)
217 {
218   struct PeerList *pos;
219
220   pos = friends;
221   while (pos != NULL)
222     {
223       if ( (GNUNET_YES == pos->is_friend) &&
224            (0 == memcmp (&pos->id, peer, sizeof (struct GNUNET_PeerIdentity))) )
225         return GNUNET_YES;
226       pos = pos->next;
227     }
228   return GNUNET_NO;
229 }
230
231
232 /**
233  * Check if an additional connection from the given peer is allowed.
234  */
235 static int
236 is_connection_allowed (const struct GNUNET_PeerIdentity * peer)
237 {
238   if (0 == memcmp (&my_identity, peer, sizeof (struct GNUNET_PeerIdentity)))
239     return GNUNET_SYSERR;       /* disallow connections to self */
240   if (is_friend (peer))
241     return GNUNET_OK;
242   if (GNUNET_YES == friends_only)
243     return GNUNET_SYSERR;
244   if (friend_count >= minimum_friend_count)
245     return GNUNET_OK;
246   return GNUNET_SYSERR;
247 }
248
249
250 /**
251  * Method called whenever a peer connects.
252  *
253  * @param cls closure
254  * @param peer peer identity this notification is about
255  */
256 static void connect_notify (void *cls,
257                             const struct
258                             GNUNET_PeerIdentity * peer)
259 {
260   struct PeerList *pos;
261
262   connection_count++;
263   pos = friends;
264   while (pos != NULL)
265     {
266       if ( (GNUNET_YES == pos->is_friend) &&
267            (0 == memcmp (&pos->id, peer, sizeof (struct GNUNET_PeerIdentity))) )
268         {
269           GNUNET_assert (GNUNET_NO == pos->is_connected);
270           pos->is_connected = GNUNET_YES;
271           pos->blacklisted_until.value = 0; /* remove blacklisting */
272           friend_count++;         
273           return;
274         }
275       pos = pos->next;
276     }
277   pos = GNUNET_malloc (sizeof(struct PeerList));
278   pos->id = *peer;
279   pos->is_connected = GNUNET_YES;
280   pos->next = friends;
281   friends = pos;
282   if (GNUNET_OK != is_connection_allowed (peer))
283     force_disconnect (peer);
284 }
285
286
287 /**
288  * Disconnect from all non-friends (we're below quota).
289  */
290 static void 
291 drop_non_friends () 
292 {
293   struct PeerList *pos;
294
295   pos = friends;
296   while (pos != NULL)
297     {
298       if (GNUNET_NO == pos->is_friend)
299         {
300           GNUNET_assert (GNUNET_YES == pos->is_connected);
301           force_disconnect (&pos->id);
302         }
303       pos = pos->next;
304     }
305 }
306
307
308 /**
309  * Method called whenever a peer disconnects.
310  *
311  * @param cls closure
312  * @param peer peer identity this notification is about
313  */
314 static void disconnect_notify (void *cls,
315                                const struct
316                                GNUNET_PeerIdentity * peer)
317 {
318   struct PeerList *pos;
319   struct PeerList *prev;
320
321   connection_count--;
322   pos = friends;
323   prev = NULL;
324   while (pos != NULL)
325     {
326       if (0 == memcmp (&pos->id, peer, sizeof (struct GNUNET_PeerIdentity)))
327         {
328           GNUNET_assert (GNUNET_YES == pos->is_connected);
329           pos->is_connected = GNUNET_NO;
330           if (GNUNET_YES == pos->is_friend)
331             {
332               friend_count--;
333               if (friend_count < minimum_friend_count)
334                 {
335                   /* disconnect from all non-friends */
336                   drop_non_friends ();
337                   attempt_connect (peer, pos);
338                 }
339             }
340           else
341             {
342               /* free entry */
343               if (prev == NULL)
344                 friends = pos->next;
345               else
346                 prev->next = pos->next;
347               GNUNET_free (pos);
348             }
349           return;
350         }
351       prev = pos;
352       pos = pos->next;
353     } 
354   GNUNET_break (0);
355 }
356
357
358 /**
359  * Find more peers that we should connect to and ask the
360  * core to establish connections.
361  */
362 static void
363 find_more_peers (void *cls,
364                  const struct GNUNET_SCHEDULER_TaskContext *tc);
365
366
367 /**
368  * Determine when we should try again to find more peers and
369  * schedule the task.
370  */ 
371 static void
372 schedule_peer_search ()
373 {
374   struct GNUNET_TIME_Relative delay;
375   
376   /* Typically, we try again every 15 minutes; the minimum period is
377      15s; if we are above the connection target, we reduce re-trying
378      by the square of how much we are above; so for example, with 200%
379      of the connection target we would only look for more peers once
380      every hour (after all, we're quite busy processing twice as many
381      connections as we intended to have); similarly, if we are at only
382      25% of our connectivity goal, we will try 16x as hard to connect
383      (so roughly once a minute, plus the 15s minimum delay */
384   delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
385                                          15 + 15 * 60 * connection_count * connection_count / target_connection_count / target_connection_count);
386   GNUNET_SCHEDULER_add_delayed (sched,
387                                 GNUNET_NO,
388                                 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
389                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
390                                 delay,
391                                 &find_more_peers,
392                                 NULL);
393 }
394
395
396 /**
397  * Peerinfo calls this function to let us know about a
398  * possible peer that we might want to connect to.
399  */
400 static void
401 process_peer (void *cls,
402               const struct GNUNET_PeerIdentity * peer,
403               const struct GNUNET_HELLO_Message * hello,
404               uint32_t trust)
405 {
406   struct PeerList *pos;
407
408   if (peer == NULL)
409     {
410       /* last call, schedule 'find_more_peers' again... */
411       schedule_peer_search ();
412       return;
413     }
414   if (hello == NULL)
415     {
416       /* no HELLO known; can not connect, ignore! */
417       return;
418     }
419   if (0 == memcmp (&my_identity,
420                    peer, sizeof (struct GNUNET_PeerIdentity)))
421     return;  /* that's me! */
422
423   pos = friends;
424   while (pos != NULL)
425     {
426       if (0 == memcmp (&pos->id, peer, sizeof (struct GNUNET_PeerIdentity)))
427         {
428           if (GNUNET_YES == pos->is_connected)
429             return;
430           if (GNUNET_TIME_absolute_get_remaining (pos->blacklisted_until).value > 0)
431             return; /* peer still blacklisted */
432           if (GNUNET_YES == pos->is_friend)
433             {
434               attempt_connect (peer, pos);
435               return;
436             }
437         }
438       pos = pos->next;
439     }
440   if (GNUNET_YES == friends_only)
441     return;
442   if (friend_count < minimum_friend_count)
443     return;
444   attempt_connect (peer, NULL);
445 }
446
447
448 /**
449  * Try to add more friends to our connection set.
450  */
451 static void
452 try_add_friends ()
453 {
454   struct PeerList *pos;
455
456   pos = friends;
457   while (pos != NULL)
458     {
459       if ( (GNUNET_TIME_absolute_get_remaining (pos->blacklisted_until).value == 0) &&
460            (GNUNET_YES == pos->is_friend) &&
461            (GNUNET_YES != pos->is_connected) )
462         attempt_connect (&pos->id, pos);
463       pos = pos->next;
464     }
465 }
466
467
468 /**
469  * Discard peer entries for blacklisted peers
470  * where the blacklisting has expired.
471  */
472 static void
473 discard_old_blacklist_entries ()
474 {
475   struct PeerList *pos;
476   struct PeerList *next;
477   struct PeerList *prev;
478
479   next = friends;
480   prev = NULL;
481   while (NULL != (pos = next))
482     {
483       next = pos->next;
484       if ( (GNUNET_NO == pos->is_friend) &&
485            (GNUNET_NO == pos->is_connected) &&
486            (0 == GNUNET_TIME_absolute_get_remaining (pos->blacklisted_until).value) )
487         {
488           /* delete 'pos' from list */
489           if (prev == NULL)
490             friends = next;
491           else
492             prev->next = next;
493           GNUNET_free (pos);
494         }
495       else
496         {
497           prev = pos;
498         }
499     }
500 }
501
502
503 /**
504  * Find more peers that we should connect to and ask the
505  * core to establish connections.
506  */
507 static void
508 find_more_peers (void *cls,
509                  const struct GNUNET_SCHEDULER_TaskContext *tc)
510 {
511   discard_old_blacklist_entries ();
512   if (target_connection_count <= connection_count)
513     {
514       schedule_peer_search ();
515       return;
516     }
517   if ( (GNUNET_YES == friends_only) ||
518        (friend_count < minimum_friend_count) )
519     {
520       try_add_friends ();
521       schedule_peer_search ();
522       return;
523     }
524   GNUNET_PEERINFO_for_all (cfg,
525                            sched,
526                            NULL,
527                            0, GNUNET_TIME_UNIT_FOREVER_REL,
528                            &process_peer, NULL);
529 }
530
531
532 /**
533  * Function called after GNUNET_CORE_connect has succeeded
534  * (or failed for good).
535  *
536  * @param cls closure
537  * @param server handle to the server, NULL if we failed
538  * @param my_id ID of this peer, NULL if we failed
539  * @param publicKey public key of this peer, NULL if we failed
540  */
541 static void
542 core_init (void *cls,
543            struct GNUNET_CORE_Handle * server,
544            const struct GNUNET_PeerIdentity *
545            my_id,
546            const struct
547            GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *
548            publicKey)
549 {
550   if (server == NULL)
551     {
552       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
553                   _("Failed to connect to core service, can not manage topology!\n"));
554       return; 
555     }
556   handle = server;
557   my_identity = *my_id;
558   if (autoconnect)
559     GNUNET_SCHEDULER_add_delayed (sched,
560                                   GNUNET_NO,
561                                   GNUNET_SCHEDULER_PRIORITY_DEFAULT,
562                                   GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
563                                   GNUNET_TIME_UNIT_SECONDS /* give core time to tell us about existing connections */,
564                                   &find_more_peers,
565                                   NULL);
566 }
567
568
569 /**
570  * gnunet-daemon-topology command line options.
571  */
572 static struct GNUNET_GETOPT_CommandLineOption options[] = {
573   GNUNET_GETOPT_OPTION_END
574 };
575
576
577 /**
578  * Read the friends file.
579  */
580 static void
581 read_friends_file (struct GNUNET_CONFIGURATION_Handle *cfg)
582 {
583   char *fn;
584   char *data;
585   size_t pos;
586   GNUNET_HashCode hc;
587   struct stat frstat;
588   struct GNUNET_CRYPTO_HashAsciiEncoded enc;
589   unsigned int entries_found;
590   struct PeerList *fl;
591
592   fn = NULL;
593   GNUNET_CONFIGURATION_get_value_filename (cfg,
594                                            "TOPOLOGY",
595                                            "FRIENDS",
596                                            &fn);
597   if (GNUNET_OK != GNUNET_DISK_file_test (fn))
598     GNUNET_DISK_file_write (fn, NULL, 0, "600");
599   if (0 != STAT (fn, &frstat))
600     {
601       if ((friends_only) || (minimum_friend_count > 0))
602         {
603           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
604                       _("Could not read friends list `%s'\n"), fn);
605           GNUNET_free (fn);
606           return;
607         }
608     }
609   if (frstat.st_size == 0)
610     {
611       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
612                   _("Friends file `%s' is empty.\n"),
613                   fn);
614       GNUNET_free (fn);
615       return; 
616     }
617   data = GNUNET_malloc_large (frstat.st_size);
618   if (frstat.st_size !=
619       GNUNET_DISK_file_read (fn, frstat.st_size, data))
620     {
621       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
622                   _("Failed to read friends list from `%s'\n"), fn);
623       GNUNET_free (fn);
624       GNUNET_free (data);
625       return;
626     }
627   entries_found = 0;
628   pos = 0;
629   while ((pos < frstat.st_size) && isspace (data[pos]))
630     pos++;
631   while ((frstat.st_size >= sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)) &&
632          (pos <= frstat.st_size - sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)))
633     {
634       memcpy (&enc, &data[pos], sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
635       if (!isspace (enc.encoding[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1]))
636         {
637           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
638                       _("Syntax error in topology specification at offset %llu, skipping bytes.\n"),
639                       (unsigned long long) pos);
640           pos++;
641           while ((pos < frstat.st_size) && (!isspace (data[pos])))
642             pos++;
643           continue;
644         }
645       enc.encoding[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
646       if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string ((char *) &enc, &hc))
647         {
648           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
649                       _("Syntax error in topology specification at offset %llu, skipping bytes `%s'.\n"),
650                       (unsigned long long) pos,
651                       &enc);
652         }
653       else
654         {
655           entries_found++;
656           fl = GNUNET_malloc (sizeof(struct PeerList));
657           fl->is_friend = GNUNET_YES;
658           fl->id.hashPubKey = hc;
659           fl->next = friends;
660           friends = fl;
661         }
662       pos = pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded);
663       while ((pos < frstat.st_size) && isspace (data[pos]))
664         pos++;
665     }
666   GNUNET_free (data);
667   GNUNET_free (fn);
668   if ( (minimum_friend_count > entries_found) &&
669        (friends_only == GNUNET_NO) )
670     {
671       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
672                   _("Fewer friends specified than required by minimum friend count. Will only connect to friends.\n"));
673     }
674   if ( (minimum_friend_count > target_connection_count) &&
675        (friends_only == GNUNET_NO) )
676     {
677       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
678                   _("More friendly connections required than target total number of connections.\n"));
679     }
680 }
681
682
683 /**
684  * Main function that will be run.
685  *
686  * @param cls closure
687  * @param s the scheduler to use
688  * @param args remaining command-line arguments
689  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
690  * @param c configuration
691  */
692 static void 
693 run (void *cls,
694      struct GNUNET_SCHEDULER_Handle * s,
695      char *const *args,
696      const char *cfgfile,
697      struct GNUNET_CONFIGURATION_Handle * c)
698 {
699   struct GNUNET_CORE_MessageHandler handlers[] = 
700     {
701       { NULL, 0, 0 }
702     };
703   unsigned long long opt;
704
705   sched = s;
706   cfg = c;
707   autoconnect = GNUNET_CONFIGURATION_get_value_yesno (cfg,
708                                                       "TOPOLOGY",
709                                                       "AUTOCONNECT"); 
710   friends_only = GNUNET_CONFIGURATION_get_value_yesno (cfg,
711                                                        "TOPOLOGY",
712                                                        "FRIENDS-ONLY");
713   opt = 0;
714   GNUNET_CONFIGURATION_get_value_number (cfg,
715                                          "TOPOLOGY",
716                                          "MINIMUM-FRIENDS",
717                                          &opt);
718   minimum_friend_count = (unsigned int) opt;
719   opt = 16;
720   GNUNET_CONFIGURATION_get_value_number (cfg,
721                                          "TOPOLOGY",
722                                          "TARGET-CONNECTION-COUNT",
723                                          &opt);
724   target_connection_count = (unsigned int) opt;
725
726   if ( (friends_only == GNUNET_YES) ||
727        (minimum_friend_count > 0) )
728     read_friends_file (cfg);
729   GNUNET_CORE_connect (sched,
730                        cfg,
731                        GNUNET_TIME_UNIT_FOREVER_REL,
732                        NULL,
733                        &core_init,
734                        &connect_notify,
735                        &disconnect_notify,
736                        NULL,
737                        NULL, GNUNET_NO,
738                        NULL, GNUNET_NO,
739                        handlers);
740 }
741
742
743 /**
744  * The main function for the topology daemon.
745  *
746  * @param argc number of arguments from the command line
747  * @param argv command line arguments
748  * @return 0 ok, 1 on error
749  */
750 int
751 main (int argc, char *const *argv)
752 {
753   int ret;
754
755   ret = (GNUNET_OK ==
756          GNUNET_PROGRAM_run (argc,
757                              argv,
758                              "topology", 
759                              _("GNUnet topology control (maintaining P2P mesh and F2F constraints)"),
760                              options,
761                              &run, NULL)) ? 0 : 1;
762   return ret;
763 }
764
765 /* end of gnunet-daemon-topology.c */