-misc stream hxing
[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                                               GNUNET_SCHEDULER_NO_TASK,
264                                               delay,
265                                               &nrs,
266                                               &nws,
267                                               &curl_task,
268                                               NULL);  
269 }
270
271
272 /**
273  * Callback invoked from the VPN service once a redirection is
274  * available.  Provides the IP address that can now be used to
275  * reach the requested destination (in our case, the MHD server)
276  *
277  * @param cls closure
278  * @param af address family, AF_INET or AF_INET6; AF_UNSPEC on error;
279  *                will match 'result_af' from the request
280  * @param address IP address (struct in_addr or struct in_addr6, depending on 'af')
281  *                that the VPN allocated for the redirection;
282  *                traffic to this IP will now be redirected to the 
283  *                specified target peer; NULL on error
284  */
285 static void
286 allocation_cb (void *cls,
287                int af,
288                const void *address)
289 {
290   char ips[INET6_ADDRSTRLEN];
291
292   rr = NULL;
293   if (src_af != af)
294   {
295     fprintf (stderr, 
296              "VPN failed to allocate appropriate address\n");
297     GNUNET_SCHEDULER_shutdown ();
298     return;
299   }
300   GNUNET_asprintf (&url, 
301                    "http://%s:%u/hello_world",  
302                    inet_ntop (af, address, ips, sizeof (ips)),
303                    (unsigned int) PORT);
304   curl = curl_easy_init ();
305   curl_easy_setopt (curl, CURLOPT_URL, url);
306   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &copy_buffer);
307   curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbc);
308   curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
309   curl_easy_setopt (curl, CURLOPT_TIMEOUT, 150L);
310   curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15L);
311   curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
312
313   multi = curl_multi_init ();
314   GNUNET_assert (multi != NULL);
315   GNUNET_assert (CURLM_OK == curl_multi_add_handle (multi, curl));
316 #if VERBOSE
317   fprintf (stderr, "Beginning HTTP download from `%s'\n", url);
318 #endif
319   curl_main ();
320 }
321
322
323 /**
324  * Function to keep the HTTP server running.
325  */
326 static void 
327 mhd_main (void);
328
329
330 static void
331 mhd_task (void *cls,
332           const struct GNUNET_SCHEDULER_TaskContext *tc)
333 {
334   mhd_task_id = GNUNET_SCHEDULER_NO_TASK;
335   MHD_run (mhd);
336   mhd_main ();
337 }
338
339
340 static void
341 ctrl_c_shutdown (void *cls,
342                  const struct GNUNET_SCHEDULER_TaskContext *tc)
343 {
344   ctrl_c_task_id = GNUNET_SCHEDULER_NO_TASK;
345   do_shutdown ();
346   global_ret = 1;
347 }
348
349
350 static void 
351 mhd_main ()
352 {
353   struct GNUNET_NETWORK_FDSet nrs;
354   struct GNUNET_NETWORK_FDSet nws;
355   fd_set rs;
356   fd_set ws;
357   fd_set es;
358   int max_fd;
359   unsigned MHD_LONG_LONG timeout;
360   struct GNUNET_TIME_Relative delay;
361
362   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == mhd_task_id);
363   FD_ZERO (&rs);
364   FD_ZERO (&ws);
365   FD_ZERO (&es);
366   max_fd = -1;
367   GNUNET_assert (MHD_YES ==
368                  MHD_get_fdset (mhd, &rs, &ws, &es, &max_fd));
369   if (MHD_YES == MHD_get_timeout (mhd, &timeout))
370     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
371                                            (unsigned int) timeout);
372   else
373     delay = GNUNET_TIME_UNIT_FOREVER_REL;
374   GNUNET_NETWORK_fdset_copy_native (&nrs,
375                                     &rs,
376                                     max_fd + 1);
377   GNUNET_NETWORK_fdset_copy_native (&nws,
378                                     &ws,
379                                     max_fd + 1);
380   mhd_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
381                                              GNUNET_SCHEDULER_NO_TASK,
382                                              delay,
383                                              &nrs,
384                                              &nws,
385                                              &mhd_task,
386                                              NULL);  
387 }
388
389
390 static void
391 run (void *cls, char *const *args, const char *cfgfile,
392      const struct GNUNET_CONFIGURATION_Handle *cfg)
393 {
394   struct in_addr v4;
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   GNUNET_assert (1 == inet_pton (dest_af, dest_ip, &v4));
410   rr = GNUNET_VPN_redirect_to_ip (vpn,
411                                   src_af,
412                                   dest_af,
413                                   &v4,
414                                   GNUNET_YES,
415                                   GNUNET_TIME_UNIT_FOREVER_ABS,
416                                   &allocation_cb, NULL);
417   ctrl_c_task_id = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
418                                                  &ctrl_c_shutdown,
419                                                  NULL);
420 }
421
422
423 static void
424 setup_peer (struct PeerContext *p, const char *cfgname)
425 {
426   p->cfg = GNUNET_CONFIGURATION_create ();
427 #if START_ARM
428   p->arm_proc =
429       GNUNET_OS_start_process (NULL, NULL, "gnunet-service-arm",
430                                "gnunet-service-arm",
431 #if VERBOSE
432                                "-L", "DEBUG",
433 #endif
434                                "-c", cfgname, NULL);
435 #endif
436   GNUNET_assert (NULL != p->arm_proc);
437   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (p->cfg, cfgname));
438 }
439
440
441 static void
442 stop_peer (struct PeerContext *p)
443 {
444 #if START_ARM
445   if (NULL != p->arm_proc)
446   {
447     if (0 != GNUNET_OS_process_kill (p->arm_proc, SIGTERM))
448       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
449     if (GNUNET_OS_process_wait (p->arm_proc) != GNUNET_OK)
450       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "waitpid");
451     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "ARM process %u stopped\n",
452                 GNUNET_OS_process_get_pid (p->arm_proc));
453     GNUNET_OS_process_close (p->arm_proc);
454     p->arm_proc = NULL;
455   }
456 #endif
457   GNUNET_CONFIGURATION_destroy (p->cfg);
458 }
459
460
461 /**
462  * Test if the given AF is supported by this system.
463  * 
464  * @param af to test
465  * @return GNUNET_OK if the AF is supported
466  */
467 static int
468 test_af (int af)
469 {
470   int s;
471
472   s = socket (af, SOCK_STREAM, 0);
473   if (-1 == s)
474   {
475     if (EAFNOSUPPORT == errno)
476       return GNUNET_NO;
477     fprintf (stderr, "Failed to create test socket: %s\n", STRERROR (errno));
478     return GNUNET_SYSERR;
479   }
480   close (s);
481   return GNUNET_OK;
482 }
483
484
485
486 int
487 main (int argc, char *const *argv)
488 {
489   const char *type;
490   const char *bin;
491   char *const argvx[] = {
492     "test_gnunet_vpn",
493     "-c",
494     "test_gnunet_vpn.conf",
495 #if VERBOSE
496     "-L", "DEBUG",
497 #endif
498     NULL
499   };
500   struct GNUNET_GETOPT_CommandLineOption options[] = {
501     GNUNET_GETOPT_OPTION_END
502   };
503
504   if (0 != ACCESS ("/dev/net/tun", R_OK))
505   {
506     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
507                               "access",
508                               "/dev/net/tun");
509     fprintf (stderr,
510              "WARNING: System unable to run test, skipping.\n");
511     return 0;
512   }
513   if ( (GNUNET_YES !=
514         GNUNET_OS_check_helper_binary ("gnunet-helper-vpn")) ||
515        (GNUNET_YES !=
516         GNUNET_OS_check_helper_binary ("gnunet-helper-exit")) )
517   {
518     fprintf (stderr,
519              "WARNING: gnunet-helper-{exit,vpn} binaries in $PATH are not SUID, refusing to run test (as it would have to fail).\n");
520     fprintf (stderr,
521              "Change $PATH ('.' in $PATH before $GNUNET_PREFIX/bin is problematic) or permissions (run 'make install' as root) to fix this!\n");
522     return 0;
523   }
524   bin = argv[0];
525   if (NULL != strstr (bin, "lt-"))
526     bin = strstr (bin, "lt-") + 4;
527   type = strstr (bin, "-");
528   if (NULL == type)
529   {
530     fprintf (stderr, "invalid binary name\n");
531     return 1;
532   }
533   type++;
534   if (0 == strcmp (type, "4_to_6"))
535   {
536     dest_ip = "FC5A:04E1:C2BA::1";
537     dest_af = AF_INET6;
538     src_af = AF_INET;
539   } 
540   else if (0 == strcmp (type, "6_to_4"))
541   {
542     dest_ip = "169.254.86.1";
543     dest_af = AF_INET;
544     src_af = AF_INET6;
545   } 
546   else if (0 == strcmp (type, "4_over"))
547   {
548     dest_ip = "169.254.86.1";
549     dest_af = AF_INET;
550     src_af = AF_INET;
551   } 
552   else if (0 == strcmp (type, "6_over"))
553   {
554     dest_ip = "FC5A:04E1:C2BA::1";
555     dest_af = AF_INET6;
556     src_af = AF_INET6;
557   }
558   else
559   {
560     fprintf (stderr, "invalid binary suffix `%s'\n", type);
561     return 1;
562   }
563   if ( (GNUNET_OK != test_af (src_af)) ||
564        (GNUNET_OK != test_af (dest_af)) )
565   {
566     fprintf (stderr, 
567              "Required address families not supported by this system, skipping test.\n");
568     return 0;
569   }
570
571
572   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
573     return 2;
574   setup_peer (&p1, "test_gnunet_vpn.conf");
575   GNUNET_log_setup ("test_gnunet_vpn",
576 #if VERBOSE
577                     "DEBUG",
578 #else
579                     "WARNING",
580 #endif
581                     NULL);
582   GNUNET_PROGRAM_run ((sizeof (argvx) / sizeof (char *)) - 1, argvx,
583                       "test_gnunet_vpn", "nohelp", options, &run, NULL);
584   stop_peer (&p1);
585   GNUNET_DISK_directory_remove ("/tmp/gnunet-test-vpn");
586   return global_ret;
587 }
588
589 /* end of test_gnunet_vpn.c */
590