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