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