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