-fix
[oweals/gnunet.git] / src / vpn / test_gnunet_vpn.c
1 /*
2      This file is part of GNUnet
3      (C) 2007, 2009, 2011, 2012 Christian Grothoff
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 test_gnunet_vpn.c
23  * @brief testcase for tunneling HTTP over the GNUnet VPN
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include <curl/curl.h>
28 #include <microhttpd.h>
29 #include "gnunet_vpn_service.h"
30 #include "gnunet_arm_service.h"
31
32 #define PORT 48080
33
34 #define START_ARM GNUNET_YES
35
36 #define VERBOSE GNUNET_NO
37
38 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 45)
39
40 struct PeerContext
41 {
42   struct GNUNET_CONFIGURATION_Handle *cfg;
43   struct GNUNET_PeerIdentity id;
44 #if START_ARM
45   struct GNUNET_OS_Process *arm_proc;
46 #endif
47 };
48
49 static struct PeerContext p1;
50
51 /**
52  * Return value for 'main'.
53  */
54 static int global_ret;
55
56 static struct GNUNET_VPN_Handle *vpn;
57
58 static struct MHD_Daemon *mhd;
59
60 static GNUNET_SCHEDULER_TaskIdentifier mhd_task_id;
61
62 static GNUNET_SCHEDULER_TaskIdentifier curl_task_id;
63
64 static GNUNET_SCHEDULER_TaskIdentifier ctrl_c_task_id;
65
66 static struct GNUNET_VPN_RedirectionRequest *rr;
67
68 static CURL *curl;
69
70 static CURLM *multi;
71
72 static char *url;
73
74 /**
75  * IP address of the ultimate destination.
76  */
77 static const char *dest_ip;
78
79 /**
80  * Address family of the dest_ip.
81  */
82 static int dest_af;
83
84 /**
85  * Address family to use by the curl client.
86  */
87 static int src_af;
88
89
90 struct CBC
91 {
92   char buf[1024];
93   size_t pos;
94 };
95
96 static struct CBC cbc;
97
98
99
100 static size_t
101 copy_buffer (void *ptr, size_t size, size_t nmemb, void *ctx)
102 {
103   struct CBC *cbc = ctx;
104
105   if (cbc->pos + size * nmemb > sizeof(cbc->buf))
106     return 0;                   /* overflow */
107   memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
108   cbc->pos += size * nmemb;
109   return size * nmemb;
110 }
111
112
113 static int
114 mhd_ahc (void *cls,
115           struct MHD_Connection *connection,
116           const char *url,
117           const char *method,
118           const char *version,
119           const char *upload_data, size_t *upload_data_size,
120           void **unused)
121 {
122   static int ptr;
123   struct MHD_Response *response;
124   int ret;
125
126   if (0 != strcmp ("GET", method))
127     return MHD_NO;              /* unexpected method */
128   if (&ptr != *unused)
129   {
130     *unused = &ptr;
131     return MHD_YES;
132   }
133   *unused = NULL;
134 #if VERBOSE
135   fprintf (stderr, "MHD sends respose for request to URL `%s'\n", url);
136 #endif
137   response = MHD_create_response_from_buffer (strlen (url),
138                                               (void *) url,
139                                               MHD_RESPMEM_MUST_COPY);
140   ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
141   MHD_destroy_response (response);
142   if (ret == MHD_NO)
143     abort ();
144   return ret;
145 }
146
147
148 static void
149 do_shutdown ()
150 {
151   if (mhd_task_id != GNUNET_SCHEDULER_NO_TASK)
152   {
153     GNUNET_SCHEDULER_cancel (mhd_task_id);
154     mhd_task_id = GNUNET_SCHEDULER_NO_TASK;
155   }
156   if (curl_task_id != GNUNET_SCHEDULER_NO_TASK)
157   {
158     GNUNET_SCHEDULER_cancel (curl_task_id);
159     curl_task_id = GNUNET_SCHEDULER_NO_TASK;
160   }
161   if (ctrl_c_task_id != GNUNET_SCHEDULER_NO_TASK)
162   {
163     GNUNET_SCHEDULER_cancel (ctrl_c_task_id);
164     ctrl_c_task_id = GNUNET_SCHEDULER_NO_TASK;
165   }
166   if (NULL != mhd)
167   {
168     MHD_stop_daemon (mhd);
169     mhd = NULL;
170   }
171   if (NULL != rr)
172   {
173     GNUNET_VPN_cancel_request (rr);
174     rr = NULL;
175   }
176   if (NULL != vpn)
177   {
178     GNUNET_VPN_disconnect (vpn);
179     vpn = NULL;
180   }
181   GNUNET_free_non_null (url);
182   url = NULL;
183 }
184
185
186 /**
187  * Function to run the HTTP client.
188  */
189 static void
190 curl_main (void);
191
192
193 static void
194 curl_task (void *cls,
195           const struct GNUNET_SCHEDULER_TaskContext *tc)
196 {
197   curl_task_id = GNUNET_SCHEDULER_NO_TASK;
198   curl_main ();
199 }
200
201
202 static void
203 curl_main ()
204 {
205   fd_set rs;
206   fd_set ws;
207   fd_set es;
208   int max;
209   struct GNUNET_NETWORK_FDSet nrs;
210   struct GNUNET_NETWORK_FDSet nws;
211   struct GNUNET_TIME_Relative delay;
212   long timeout;
213   int running;
214   struct CURLMsg *msg;
215
216   max = 0;
217   FD_ZERO (&rs);
218   FD_ZERO (&ws);
219   FD_ZERO (&es);
220   curl_multi_perform (multi, &running);
221   if (running == 0)
222   {
223     GNUNET_assert (NULL != (msg = curl_multi_info_read (multi, &running)));
224     if (msg->msg == CURLMSG_DONE)
225     {
226       if (msg->data.result != CURLE_OK)
227       {
228         printf ("%s failed at %s:%d: `%s'\n",
229                 "curl_multi_perform",
230                 __FILE__,
231                 __LINE__, curl_easy_strerror (msg->data.result));
232         global_ret = 1;
233       }
234     }
235     curl_multi_remove_handle (multi, curl);
236     curl_multi_cleanup (multi);
237     curl_easy_cleanup (curl);
238     curl = NULL;
239     multi = NULL;
240     if (cbc.pos != strlen ("/hello_world"))
241       global_ret = 2;
242     if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
243       global_ret = 3;
244 #if VERBOSE
245     fprintf (stderr, "Download complete, shutting down!\n");
246 #endif
247     do_shutdown ();
248     return;    
249   }
250   GNUNET_assert (CURLM_OK == curl_multi_fdset (multi, &rs, &ws, &es, &max)); 
251   if ( (CURLM_OK != curl_multi_timeout (multi, &timeout)) ||
252        (-1 == timeout) )
253     delay = GNUNET_TIME_UNIT_SECONDS;
254   else
255     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, (unsigned int) timeout);
256   GNUNET_NETWORK_fdset_copy_native (&nrs,
257                                     &rs,
258                                     max + 1);
259   GNUNET_NETWORK_fdset_copy_native (&nws,
260                                     &ws,
261                                     max + 1);
262   curl_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
263                                               delay,
264                                               &nrs,
265                                               &nws,
266                                               &curl_task,
267                                               NULL);  
268 }
269
270
271 /**
272  * Callback invoked from the VPN service once a redirection is
273  * available.  Provides the IP address that can now be used to
274  * reach the requested destination (in our case, the MHD server)
275  *
276  * @param cls closure
277  * @param af address family, AF_INET or AF_INET6; AF_UNSPEC on error;
278  *                will match 'result_af' from the request
279  * @param address IP address (struct in_addr or struct in_addr6, depending on 'af')
280  *                that the VPN allocated for the redirection;
281  *                traffic to this IP will now be redirected to the 
282  *                specified target peer; NULL on error
283  */
284 static void
285 allocation_cb (void *cls,
286                int af,
287                const void *address)
288 {
289   char ips[INET6_ADDRSTRLEN];
290
291   rr = NULL;
292   if (src_af != af)
293   {
294     fprintf (stderr, 
295              "VPN failed to allocate appropriate address\n");
296     GNUNET_SCHEDULER_shutdown ();
297     return;
298   }
299   GNUNET_asprintf (&url, 
300                    "http://%s:%u/hello_world",  
301                    inet_ntop (af, address, ips, sizeof (ips)),
302                    (unsigned int) PORT);
303   curl = curl_easy_init ();
304   curl_easy_setopt (curl, CURLOPT_URL, url);
305   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &copy_buffer);
306   curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbc);
307   curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
308   curl_easy_setopt (curl, CURLOPT_TIMEOUT, 150L);
309   curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15L);
310   curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
311
312   multi = curl_multi_init ();
313   GNUNET_assert (multi != NULL);
314   GNUNET_assert (CURLM_OK == curl_multi_add_handle (multi, curl));
315 #if VERBOSE
316   fprintf (stderr, "Beginning HTTP download from `%s'\n", url);
317 #endif
318   curl_main ();
319 }
320
321
322 /**
323  * Function to keep the HTTP server running.
324  */
325 static void 
326 mhd_main (void);
327
328
329 static void
330 mhd_task (void *cls,
331           const struct GNUNET_SCHEDULER_TaskContext *tc)
332 {
333   mhd_task_id = GNUNET_SCHEDULER_NO_TASK;
334   MHD_run (mhd);
335   mhd_main ();
336 }
337
338
339 static void
340 ctrl_c_shutdown (void *cls,
341                  const struct GNUNET_SCHEDULER_TaskContext *tc)
342 {
343   ctrl_c_task_id = GNUNET_SCHEDULER_NO_TASK;
344   do_shutdown ();
345   global_ret = 1;
346 }
347
348
349 static void 
350 mhd_main ()
351 {
352   struct GNUNET_NETWORK_FDSet nrs;
353   struct GNUNET_NETWORK_FDSet nws;
354   fd_set rs;
355   fd_set ws;
356   fd_set es;
357   int max_fd;
358   unsigned MHD_LONG_LONG timeout;
359   struct GNUNET_TIME_Relative delay;
360
361   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == mhd_task_id);
362   FD_ZERO (&rs);
363   FD_ZERO (&ws);
364   FD_ZERO (&es);
365   max_fd = -1;
366   GNUNET_assert (MHD_YES ==
367                  MHD_get_fdset (mhd, &rs, &ws, &es, &max_fd));
368   if (MHD_YES == MHD_get_timeout (mhd, &timeout))
369     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
370                                            (unsigned int) timeout);
371   else
372     delay = GNUNET_TIME_UNIT_FOREVER_REL;
373   GNUNET_NETWORK_fdset_copy_native (&nrs,
374                                     &rs,
375                                     max_fd + 1);
376   GNUNET_NETWORK_fdset_copy_native (&nws,
377                                     &ws,
378                                     max_fd + 1);
379   mhd_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
380                                              delay,
381                                              &nrs,
382                                              &nws,
383                                              &mhd_task,
384                                              NULL);  
385 }
386
387
388 static void
389 run (void *cls, char *const *args, const char *cfgfile,
390      const struct GNUNET_CONFIGURATION_Handle *cfg)
391 {
392   struct in_addr v4;
393   struct in6_addr v6;
394   void *addr;
395   enum MHD_FLAG flags;
396
397   vpn = GNUNET_VPN_connect (cfg);
398   GNUNET_assert (NULL != vpn); 
399   flags = MHD_USE_DEBUG;
400   if (AF_INET6 == dest_af)
401     flags |= MHD_USE_IPv6;
402   mhd = MHD_start_daemon (flags,
403                           PORT,
404                           NULL, NULL,
405                           &mhd_ahc, NULL,
406                           MHD_OPTION_END);
407   GNUNET_assert (NULL != mhd);
408   mhd_main ();
409   addr = NULL;
410   switch (dest_af)
411   {
412   case AF_INET:
413     GNUNET_assert (1 == inet_pton (dest_af, dest_ip, &v4));
414     addr = &v4;
415     break;
416   case AF_INET6:
417     GNUNET_assert (1 == inet_pton (dest_af, dest_ip, &v6));
418     addr = &v6;
419     break;
420   default:
421     GNUNET_assert (0);
422   }
423   rr = GNUNET_VPN_redirect_to_ip (vpn,
424                                   src_af,
425                                   dest_af,
426                                   addr,
427                                   GNUNET_YES,
428                                   GNUNET_TIME_UNIT_FOREVER_ABS,
429                                   &allocation_cb, NULL);
430   ctrl_c_task_id = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
431                                                  &ctrl_c_shutdown,
432                                                  NULL);
433 }
434
435
436 static void
437 setup_peer (struct PeerContext *p, const char *cfgname)
438 {
439   p->cfg = GNUNET_CONFIGURATION_create ();
440 #if START_ARM
441   p->arm_proc =
442       GNUNET_OS_start_process (GNUNET_YES, NULL, NULL, "gnunet-service-arm",
443                                "gnunet-service-arm",
444 #if VERBOSE
445                                "-L", "DEBUG",
446 #endif
447                                "-c", cfgname, NULL);
448 #endif
449   GNUNET_assert (NULL != p->arm_proc);
450   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
451 }
452
453
454 static void
455 stop_peer (struct PeerContext *p)
456 {
457 #if START_ARM
458   if (NULL != p->arm_proc)
459   {
460     if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
461       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
462     if (GNUNET_OS_process_wait (p->arm_proc) != GNUNET_OK)
463       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
464     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "ARM process %u stopped\n",
465                 GNUNET_OS_process_get_pid (p->arm_proc));
466     GNUNET_OS_process_close (p->arm_proc);
467     p->arm_proc = NULL;
468   }
469 #endif
470   GNUNET_CONFIGURATION_destroy (p->cfg);
471 }
472
473
474 /**
475  * Test if the given AF is supported by this system.
476  * 
477  * @param af to test
478  * @return GNUNET_OK if the AF is supported
479  */
480 static int
481 test_af (int af)
482 {
483   int s;
484
485   s = socket (af, SOCK_STREAM, 0);
486   if (-1 == s)
487   {
488     if (EAFNOSUPPORT == errno)
489       return GNUNET_NO;
490     fprintf (stderr, "Failed to create test socket: %s\n", STRERROR (errno));
491     return GNUNET_SYSERR;
492   }
493   close (s);
494   return GNUNET_OK;
495 }
496
497
498
499 int
500 main (int argc, char *const *argv)
501 {
502   const char *type;
503   const char *bin;
504   char *const argvx[] = {
505     "test_gnunet_vpn",
506     "-c",
507     "test_gnunet_vpn.conf",
508 #if VERBOSE
509     "-L", "DEBUG",
510 #endif
511     NULL
512   };
513   struct GNUNET_GETOPT_CommandLineOption options[] = {
514     GNUNET_GETOPT_OPTION_END
515   };
516   
517   if (0 != ACCESS ("/dev/net/tun", R_OK))
518   {
519     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
520                               "access",
521                               "/dev/net/tun");
522     fprintf (stderr,
523              "WARNING: System unable to run test, skipping.\n");
524     return 0;
525   }
526   if ( (GNUNET_YES !=
527         GNUNET_OS_check_helper_binary ("gnunet-helper-vpn")) ||
528        (GNUNET_YES !=
529         GNUNET_OS_check_helper_binary ("gnunet-helper-exit")) )
530   {
531     fprintf (stderr,
532              "WARNING: gnunet-helper-{exit,vpn} binaries in $PATH are not SUID, refusing to run test (as it would have to fail).\n");
533     fprintf (stderr,
534              "Change $PATH ('.' in $PATH before $GNUNET_PREFIX/bin is problematic) or permissions (run 'make install' as root) to fix this!\n");
535     return 0;
536   }
537   GNUNET_CRYPTO_setup_hostkey ("test_gnunet_vpn.conf");
538   bin = argv[0];
539   if (NULL != strstr (bin, "lt-"))
540     bin = strstr (bin, "lt-") + 4;
541   type = strstr (bin, "-");
542   if (NULL == type)
543   {
544     fprintf (stderr, "invalid binary name\n");
545     return 1;
546   }
547   type++;
548   if (0 == strcmp (type, "4_to_6"))
549   {
550     dest_ip = "FC5A:04E1:C2BA::1";
551     dest_af = AF_INET6;
552     src_af = AF_INET;
553   } 
554   else if (0 == strcmp (type, "6_to_4"))
555   {
556     dest_ip = "169.254.86.1";
557     dest_af = AF_INET;
558     src_af = AF_INET6;
559   } 
560   else if (0 == strcmp (type, "4_over"))
561   {
562     dest_ip = "169.254.86.1";
563     dest_af = AF_INET;
564     src_af = AF_INET;
565   } 
566   else if (0 == strcmp (type, "6_over"))
567   {
568     dest_ip = "FC5A:04E1:C2BA::1";
569     dest_af = AF_INET6;
570     src_af = AF_INET6;
571   }
572   else
573   {
574     fprintf (stderr, "invalid binary suffix `%s'\n", type);
575     return 1;
576   }
577   if ( (GNUNET_OK != test_af (src_af)) ||
578        (GNUNET_OK != test_af (dest_af)) )
579   {
580     fprintf (stderr, 
581              "Required address families not supported by this system, skipping test.\n");
582     return 0;
583   }
584
585
586   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
587     return 2;
588   setup_peer (&p1, "test_gnunet_vpn.conf");
589   GNUNET_log_setup ("test_gnunet_vpn",
590 #if VERBOSE
591                     "DEBUG",
592 #else
593                     "WARNING",
594 #endif
595                     NULL);
596   GNUNET_PROGRAM_run ((sizeof (argvx) / sizeof (char *)) - 1, argvx,
597                       "test_gnunet_vpn", "nohelp", options, &run, NULL);
598   stop_peer (&p1);
599   GNUNET_DISK_directory_remove ("/tmp/gnunet-test-vpn");
600   return global_ret;
601 }
602
603 /* end of test_gnunet_vpn.c */
604