4ffcc27f41086ba230d2034cfbc233c5f937835c
[oweals/gnunet.git] / src / hostlist / hostlist-server.c
1 /*
2      This file is part of GNUnet.
3      (C) 2008, 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 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 hostlist/hostlist-server.c
23  * @author Christian Grothoff
24  * @brief application to provide an integrated hostlist HTTP server
25  */
26
27 #include "platform.h"
28 #include <microhttpd.h>
29 #include "hostlist-server.h"
30 #include "gnunet_hello_lib.h"
31 #include "gnunet_peerinfo_service.h"
32 #include "gnunet-daemon-hostlist.h"
33 #include "gnunet_resolver_service.h"
34
35 #define DEBUG_HOSTLIST_SERVER GNUNET_NO
36
37 /**
38  * How often should we recalculate our response to hostlist requests?
39  */
40 #define RESPONSE_UPDATE_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
41
42 /**
43  * Handle to the HTTP server as provided by libmicrohttpd for IPv6.
44  */
45 static struct MHD_Daemon *daemon_handle_v6;
46
47 /**
48  * Handle to the HTTP server as provided by libmicrohttpd for IPv4.
49  */
50 static struct MHD_Daemon *daemon_handle_v4;
51
52 /**
53  * Our configuration.
54  */
55 static const struct GNUNET_CONFIGURATION_Handle *cfg;
56
57 /**
58  * Our scheduler.
59  */
60 static struct GNUNET_SCHEDULER_Handle *sched;
61
62 /**
63  * For keeping statistics.
64  */ 
65 static struct GNUNET_STATISTICS_Handle *stats;
66
67 /**
68  * Our primary task for IPv4.
69  */
70 static GNUNET_SCHEDULER_TaskIdentifier hostlist_task_v4;
71
72 /**
73  * Our primary task for IPv6.
74  */
75 static GNUNET_SCHEDULER_TaskIdentifier hostlist_task_v6;
76
77 /**
78  * Task that updates our HTTP response.
79  */
80 static GNUNET_SCHEDULER_TaskIdentifier response_task;
81
82 /**
83  * Our canonical response.
84  */
85 static struct MHD_Response *response;
86
87 /**
88  * NULL if we are not currenlty iterating over peer information.
89  */
90 static struct GNUNET_PEERINFO_IteratorContext *pitr;
91
92 /**
93  * Context for host processor.
94  */
95 struct HostSet
96 {
97   unsigned int size;
98
99   char *data;
100 };
101
102 /**
103  * Task that will produce a new response object.
104  */
105 static void
106 update_response (void *cls,
107                  const struct GNUNET_SCHEDULER_TaskContext *tc);
108
109 /**
110  * Function that assembles our hostlist adv message.
111  */
112 static int
113 create_hostlist_adv_message (struct GNUNET_HOSTLIST_ADV_Message *adv_msg)
114 {
115   int length  = 0;
116   int size    = 0;
117   unsigned long long port;
118
119   char *uri;
120   char hostname[HOST_NAME_MAX];
121   char *protocol = "http://";
122   char *port_s = GNUNET_malloc(6 * sizeof(char));
123
124   if (0 != gethostname (hostname, sizeof (hostname) - 1))
125   {
126     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
127         "Could not get system's hostname, unable to create advertisement message");
128     return GNUNET_NO;
129   }
130   if (-1 == GNUNET_CONFIGURATION_get_value_number (cfg,
131                                                    "HOSTLIST",
132                                                    "HTTPPORT",
133                                                    &port))
134     return GNUNET_SYSERR;
135
136   sprintf(port_s, "%llu", port);
137   length = strlen(hostname)+strlen(protocol)+strlen(port_s)+2;
138   size = (length+1) * sizeof (char);
139   uri = GNUNET_malloc(size);
140   uri = strcpy(uri, protocol);
141   uri = strcat(uri, hostname);
142   uri = strcat(uri, ":");
143   uri = strcat(uri, port_s);
144   uri = strcat(uri, "/");
145   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Address to obtain hostlist: %s\n", uri);
146
147
148   adv_msg = GNUNET_malloc ( sizeof(struct GNUNET_HOSTLIST_ADV_Message) + size);
149   if ( NULL == adv_msg)
150     {
151     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
152         "Could not allocate memory for the message");
153     return GNUNET_NO;
154     }
155   GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
156       "size ADV_Message: %u\n",sizeof(struct GNUNET_HOSTLIST_ADV_Message));
157   GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
158       "size uri: %u\n", (length + 1) * sizeof (char));
159
160
161   if ( ( size + sizeof( struct GNUNET_HOSTLIST_ADV_Message )) > GNUNET_SERVER_MAX_MESSAGE_SIZE)
162     {
163       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
164           "Advertisement message is bigger than GNUNET allows");
165       return GNUNET_NO;
166     }
167
168   adv_msg->header.type = htons (GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT);
169   adv_msg->header.size = htons (sizeof (struct GNUNET_HOSTLIST_ADV_Message) + size);
170   memcpy(&adv_msg[1],uri,size);
171
172   return GNUNET_OK;
173 }
174
175 /**
176  * Function that assembles our response.
177  */
178 static void
179 finish_response (struct HostSet *results)
180 {
181   struct GNUNET_TIME_Relative freq;
182
183   if (response != NULL)
184     MHD_destroy_response (response);
185 #if DEBUG_HOSTLIST_SERVER
186   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
187               "Creating hostlist response with %u bytes\n",
188               (unsigned int) results->size);
189 #endif
190   response = MHD_create_response_from_data (results->size,
191                                             results->data, MHD_YES, MHD_NO);
192   if ( (daemon_handle_v4 != NULL) ||
193        (daemon_handle_v6 != NULL) )
194     {
195       freq = RESPONSE_UPDATE_FREQUENCY;
196       if (results->size == 0)
197         freq = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250);
198       /* schedule next update of the response */
199       response_task = GNUNET_SCHEDULER_add_delayed (sched,
200                                                     freq,
201                                                     &update_response,
202                                                     NULL);
203     }
204   else
205     {
206       /* already past shutdown */
207       MHD_destroy_response (response);
208       response = NULL;
209     }
210   GNUNET_STATISTICS_set (stats,
211                          gettext_noop("bytes in hostlist"),
212                          results->size,
213                          GNUNET_YES);
214   GNUNET_free (results);
215 }
216
217
218 /**
219  * Set 'cls' to GNUNET_YES (we have an address!).
220  *
221  * @param cls closure, an 'int*'
222  * @param tname name of the transport (ignored)
223  * @param expiration expiration time (call is ignored if this is in the past)
224  * @param addr the address (ignored)
225  * @param addrlen length of the address (ignored)
226  * @return  GNUNET_SYSERR to stop iterating (unless expiration has occured)
227  */
228 static int
229 check_has_addr (void *cls,
230                 const char *tname,
231                 struct GNUNET_TIME_Absolute expiration,
232                 const void *addr, size_t addrlen)
233 {
234   int *arg = cls;
235
236   if (GNUNET_TIME_absolute_get_remaining (expiration).value == 0)
237     {
238       GNUNET_STATISTICS_update (stats,
239                                 gettext_noop("expired addresses encountered"),
240                                 1,
241                                 GNUNET_YES);
242       return GNUNET_YES; /* ignore this address */
243     }
244   *arg = GNUNET_YES;
245   return GNUNET_SYSERR;
246 }
247
248
249 /**
250  * Callback that processes each of the known HELLOs for the
251  * hostlist response construction.
252  */
253 static void
254 host_processor (void *cls,
255                 const struct GNUNET_PeerIdentity * peer,
256                 const struct GNUNET_HELLO_Message *hello,
257                 uint32_t trust)
258 {
259   struct HostSet *results = cls;
260   size_t old;
261   size_t s;
262   int has_addr;
263   
264   if (peer == NULL)
265     {
266       pitr = NULL;
267       finish_response (results);
268       return;
269     }
270   has_addr = GNUNET_NO;
271   GNUNET_HELLO_iterate_addresses (hello,
272                                   GNUNET_NO,
273                                   &check_has_addr,
274                                   &has_addr);
275   if (GNUNET_NO == has_addr)
276     {
277       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
278                   "HELLO for peer `%4s' has no address, not suitable for hostlist!\n",
279                   GNUNET_i2s (peer));
280       GNUNET_STATISTICS_update (stats,
281                                 gettext_noop("HELLOs without addresses encountered (ignored)"),
282                                 1,
283                                 GNUNET_NO);
284       return; 
285     }
286   old = results->size;
287   s = GNUNET_HELLO_size(hello);
288 #if DEBUG_HOSTLIST_SERVER
289   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
290               "Received %u bytes of `%s' from peer `%s' for hostlist.\n",
291               (unsigned int) s,
292               "HELLO",
293               GNUNET_i2s (peer));
294 #endif
295   if (old + s >= GNUNET_MAX_MALLOC_CHECKED)
296     {
297       GNUNET_STATISTICS_update (stats,
298                                 gettext_noop("bytes not included in hostlist (size limit)"),
299                                 s,
300                                 GNUNET_NO);
301       return; /* too large, skip! */
302     }
303   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
304               "Adding peer `%s' to hostlist (%u bytes)\n",
305               GNUNET_i2s (peer),
306               (unsigned int) s);
307   GNUNET_array_grow (results->data,
308                      results->size,
309                      old + s);
310   memcpy (&results->data[old], hello, s);
311 }
312
313
314 /**
315  * Task that will produce a new response object.
316  */
317 static void
318 update_response (void *cls,
319                  const struct GNUNET_SCHEDULER_TaskContext *tc)
320 {
321   struct HostSet *results;
322
323   response_task = GNUNET_SCHEDULER_NO_TASK;
324   results = GNUNET_malloc(sizeof(struct HostSet));
325   pitr = GNUNET_PEERINFO_iterate (cfg, sched, 
326                                   NULL,
327                                   0, 
328                                   GNUNET_TIME_UNIT_MINUTES,
329                                   &host_processor,
330                                   results);
331 }
332
333
334 /**
335  * Hostlist access policy (very permissive, allows everything).
336  */
337 static int
338 accept_policy_callback (void *cls,
339                         const struct sockaddr *addr, socklen_t addrlen)
340 {
341   if (NULL == response)
342     {
343 #if DEBUG_HOSTLIST_SERVER
344       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
345                   "Received request for hostlist, but I am not yet ready; rejecting!\n");
346 #endif
347       return MHD_NO;
348     }
349   return MHD_YES;               /* accept all */
350 }
351
352
353 /**
354  * Main request handler.
355  */
356 static int
357 access_handler_callback (void *cls,
358                          struct MHD_Connection *connection,
359                          const char *url,
360                          const char *method,
361                          const char *version,
362                          const char *upload_data,
363                          size_t*upload_data_size, void **con_cls)
364 {
365   static int dummy;
366   
367   if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
368     {
369       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
370                   _("Refusing `%s' request to hostlist server\n"),
371                   method);
372       GNUNET_STATISTICS_update (stats,
373                                 gettext_noop("hostlist requests refused (not HTTP GET)"),
374                                 1,
375                                 GNUNET_YES);
376       return MHD_NO;
377     }
378   if (NULL == *con_cls)
379     {
380       (*con_cls) = &dummy;
381 #if DEBUG_HOSTLIST_SERVER
382       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
383                   _("Sending 100 CONTINUE reply\n"));
384 #endif
385       return MHD_YES;           /* send 100 continue */
386     }
387   if (*upload_data_size != 0)
388     {
389       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
390                   _("Refusing `%s' request with %llu bytes of upload data\n"),
391                   method,
392                   (unsigned long long) *upload_data_size);
393       GNUNET_STATISTICS_update (stats,
394                                 gettext_noop("hostlist requests refused (upload data)"),
395                                 1,
396                                 GNUNET_YES);
397       return MHD_NO;              /* do not support upload data */
398     }
399   if (response == NULL)
400     {
401       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
402                   _("Could not handle hostlist request since I do not have a response yet\n"));
403       GNUNET_STATISTICS_update (stats,
404                                 gettext_noop("hostlist requests refused (not ready)"),
405                                 1,
406                                 GNUNET_YES);
407       return MHD_NO;              /* internal error, no response yet */
408     }
409   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
410               _("Received request for our hostlist\n"));
411   GNUNET_STATISTICS_update (stats,
412                             gettext_noop("hostlist requests processed"),
413                             1,
414                             GNUNET_YES);
415   return MHD_queue_response (connection, MHD_HTTP_OK, response);
416 }
417
418 /**
419  * Method called whenever a given peer connects.
420  *
421  * @param cls closure
422  * @param peer peer identity this notification is about
423  * @param latency reported latency of the connection with 'other'
424  * @param distance reported distance (DV) to 'other'
425  */
426 static void
427 connect_handler (void *cls,
428                  const struct
429                  GNUNET_PeerIdentity * peer,
430                  struct GNUNET_TIME_Relative latency,
431                  uint32_t distance)
432 {
433   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
434               "A new peer connected to the server, preparing to send hostlist advertisement\n");
435   /* create a new advertisement message */
436   struct GNUNET_HOSTLIST_ADV_Message *adv_msg;
437   create_hostlist_adv_message(adv_msg);
438
439 }
440
441
442 /**
443  * Method called whenever a given peer disconnects.
444  *
445  * @param cls closure
446  * @param peer peer identity this notification is about
447  */
448 static void
449 disconnect_handler (void *cls,
450                     const struct
451                     GNUNET_PeerIdentity * peer)
452 {
453
454 }
455
456
457 /**
458  * Function that queries MHD's select sets and
459  * starts the task waiting for them.
460  */
461 static GNUNET_SCHEDULER_TaskIdentifier
462 prepare_daemon (struct MHD_Daemon *daemon_handle);
463
464 /**
465  * Call MHD to process pending requests and then go back
466  * and schedule the next run.
467  */
468 static void
469 run_daemon (void *cls,
470             const struct GNUNET_SCHEDULER_TaskContext *tc)
471 {
472   struct MHD_Daemon *daemon_handle = cls;
473
474   if (daemon_handle == daemon_handle_v4)
475     hostlist_task_v4 = GNUNET_SCHEDULER_NO_TASK;
476   else
477     hostlist_task_v6 = GNUNET_SCHEDULER_NO_TASK;
478
479   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
480     return;    
481   GNUNET_assert (MHD_YES == MHD_run (daemon_handle));
482   if (daemon_handle == daemon_handle_v4)
483     hostlist_task_v4 = prepare_daemon (daemon_handle);
484   else
485     hostlist_task_v6 = prepare_daemon (daemon_handle);
486 }
487
488
489 /**
490  * Function that queries MHD's select sets and
491  * starts the task waiting for them.
492  */
493 static GNUNET_SCHEDULER_TaskIdentifier
494 prepare_daemon (struct MHD_Daemon *daemon_handle)
495 {
496   GNUNET_SCHEDULER_TaskIdentifier ret;
497   fd_set rs;
498   fd_set ws;
499   fd_set es;
500   struct GNUNET_NETWORK_FDSet *wrs;
501   struct GNUNET_NETWORK_FDSet *wws;
502   struct GNUNET_NETWORK_FDSet *wes;
503   int max;
504   unsigned long long timeout;
505   int haveto;
506   struct GNUNET_TIME_Relative tv;
507   
508   FD_ZERO(&rs);
509   FD_ZERO(&ws);
510   FD_ZERO(&es);
511   wrs = GNUNET_NETWORK_fdset_create ();
512   wes = GNUNET_NETWORK_fdset_create ();
513   wws = GNUNET_NETWORK_fdset_create ();
514   max = -1;
515   GNUNET_assert (MHD_YES ==
516                  MHD_get_fdset (daemon_handle,
517                                 &rs,
518                                 &ws,
519                                 &es,
520                                 &max));
521   haveto = MHD_get_timeout (daemon_handle, &timeout);
522   if (haveto == MHD_YES)
523     tv.value = (uint64_t) timeout;
524   else
525     tv = GNUNET_TIME_UNIT_FOREVER_REL;
526   GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max);
527   GNUNET_NETWORK_fdset_copy_native (wws, &ws, max);
528   GNUNET_NETWORK_fdset_copy_native (wes, &es, max);
529   ret = GNUNET_SCHEDULER_add_select (sched,
530                                      GNUNET_SCHEDULER_PRIORITY_HIGH,
531                                      GNUNET_SCHEDULER_NO_TASK,
532                                      tv,
533                                      wrs,
534                                      wws,
535                                      &run_daemon,
536                                      daemon_handle);
537   GNUNET_NETWORK_fdset_destroy (wrs);
538   GNUNET_NETWORK_fdset_destroy (wws);
539   GNUNET_NETWORK_fdset_destroy (wes);
540   return ret;
541 }
542
543
544
545 /**
546  * Start server offering our hostlist.
547  *
548  * @return GNUNET_OK on success
549  */
550 int
551 GNUNET_HOSTLIST_server_start (const struct GNUNET_CONFIGURATION_Handle *c,
552                               struct GNUNET_SCHEDULER_Handle *s,
553                               struct GNUNET_STATISTICS_Handle *st,
554                               GNUNET_CORE_ConnectEventHandler *server_ch,
555                               GNUNET_CORE_DisconnectEventHandler *server_dh)
556 {
557   unsigned long long port;
558
559   sched = s;
560   cfg = c;
561   stats = st;
562   if (-1 == GNUNET_CONFIGURATION_get_value_number (cfg,
563                                                    "HOSTLIST",
564                                                    "HTTPPORT", 
565                                                    &port))
566     return GNUNET_SYSERR;
567   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
568               _("Hostlist service starts on port %llu\n"),
569               port);
570   daemon_handle_v6 = MHD_start_daemon (MHD_USE_IPv6 
571 #if DEBUG_HOSTLIST_SERVER
572                                        | MHD_USE_DEBUG
573 #endif
574                                        ,
575                                        (unsigned short) port,
576                                        &accept_policy_callback,
577                                        NULL,
578                                        &access_handler_callback,
579                                        NULL,
580                                        MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 16,
581                                        MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
582                                        MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
583                                        MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (16 * 1024),
584                                        MHD_OPTION_END);
585   daemon_handle_v4 = MHD_start_daemon (MHD_NO_FLAG
586 #if DEBUG_HOSTLIST_SERVER
587                                        | MHD_USE_DEBUG
588 #endif
589                                        ,
590                                        (unsigned short) port,
591                                        &accept_policy_callback,
592                                        NULL,
593                                        &access_handler_callback,
594                                        NULL,
595                                        MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 16,
596                                        MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
597                                        MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
598                                        MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (16 * 1024),
599                                        MHD_OPTION_END);
600
601   if ( (daemon_handle_v6 == NULL) &&
602        (daemon_handle_v4 == NULL) )
603     {
604       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
605                   _("Could not start hostlist HTTP server on port %u\n"),
606                   (unsigned short) port);
607       return GNUNET_SYSERR;    
608     }
609
610   *server_ch = &connect_handler;
611   *server_dh = &disconnect_handler;
612
613   if (daemon_handle_v4 != NULL)
614     hostlist_task_v4 = prepare_daemon (daemon_handle_v4);
615   if (daemon_handle_v6 != NULL)
616     hostlist_task_v6 = prepare_daemon (daemon_handle_v6);
617   response_task = GNUNET_SCHEDULER_add_now (sched,
618                                             &update_response,
619                                             NULL);
620   return GNUNET_OK;
621 }
622
623 /**
624  * Stop server offering our hostlist.
625  */
626 void
627 GNUNET_HOSTLIST_server_stop ()
628 {
629 #if DEBUG_HOSTLIST_SERVER
630   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
631               "Hostlist server shutdown\n");
632 #endif
633   if (GNUNET_SCHEDULER_NO_TASK != hostlist_task_v6)
634     {
635       GNUNET_SCHEDULER_cancel (sched, hostlist_task_v6);
636       hostlist_task_v6 = GNUNET_SCHEDULER_NO_TASK;
637     }
638   if (GNUNET_SCHEDULER_NO_TASK != hostlist_task_v4)
639     {
640       GNUNET_SCHEDULER_cancel (sched, hostlist_task_v4);
641       hostlist_task_v4 = GNUNET_SCHEDULER_NO_TASK;
642     }
643   if (pitr != NULL)
644     {
645       GNUNET_PEERINFO_iterate_cancel (pitr);
646       pitr = NULL;
647     }
648   if (GNUNET_SCHEDULER_NO_TASK != response_task)
649     {
650       GNUNET_SCHEDULER_cancel (sched, response_task);
651       response_task = GNUNET_SCHEDULER_NO_TASK;
652     }
653   if (NULL != daemon_handle_v4)
654     {
655       MHD_stop_daemon (daemon_handle_v4);
656       daemon_handle_v4 = NULL;
657     }
658   if (NULL != daemon_handle_v6)
659     {
660       MHD_stop_daemon (daemon_handle_v6);
661       daemon_handle_v6 = NULL;
662     }
663   if (response != NULL)
664     {
665       MHD_destroy_response (response);
666       response = NULL;
667     }
668 }
669
670 /* end of hostlist-server.c */