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