glitch in the license text detected by hyazinthe, thank you!
[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 it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14 */
15
16 /**
17  * @file test_gnunet_vpn.c
18  * @brief testcase for tunneling HTTP over the GNUnet VPN
19  * @author Christian Grothoff
20  */
21 #include "platform.h"
22 #if HAVE_CURL_CURL_H
23 #include <curl/curl.h>
24 #elif HAVE_GNURL_CURL_H
25 #include <gnurl/curl.h>
26 #endif
27 #include <microhttpd.h>
28 #include "gnunet_vpn_service.h"
29 #include "gnunet_testing_lib.h"
30
31 #define PORT 48080
32
33 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 300)
34
35
36 /**
37  * Return value for 'main'.
38  */
39 static int global_ret;
40
41 static struct GNUNET_VPN_Handle *vpn;
42
43 static struct MHD_Daemon *mhd;
44
45 static struct GNUNET_SCHEDULER_Task *mhd_task_id;
46
47 static struct GNUNET_SCHEDULER_Task *curl_task_id;
48
49 static struct GNUNET_SCHEDULER_Task *timeout_task_id;
50
51 static struct GNUNET_VPN_RedirectionRequest *rr;
52
53 static CURL *curl;
54
55 static CURLM *multi;
56
57 static char *url;
58
59 /**
60  * IP address of the ultimate destination.
61  */
62 static const char *dest_ip;
63
64 /**
65  * Address family of the dest_ip.
66  */
67 static int dest_af;
68
69 /**
70  * Address family to use by the curl client.
71  */
72 static int src_af;
73
74
75 struct CBC
76 {
77   char buf[1024];
78   size_t pos;
79 };
80
81 static struct CBC cbc;
82
83
84 static size_t
85 copy_buffer (void *ptr, size_t size, size_t nmemb, void *ctx)
86 {
87   struct CBC *cbc = ctx;
88
89   if (cbc->pos + size * nmemb > sizeof (cbc->buf))
90     return 0;                   /* overflow */
91   GNUNET_memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
92   cbc->pos += size * nmemb;
93   return size * nmemb;
94 }
95
96
97 static int
98 mhd_ahc (void *cls,
99          struct MHD_Connection *connection,
100          const char *url,
101          const char *method,
102          const char *version,
103          const char *upload_data,
104          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,
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 (void *cls)
134 {
135   if (NULL != mhd_task_id)
136   {
137     GNUNET_SCHEDULER_cancel (mhd_task_id);
138     mhd_task_id = NULL;
139   }
140   if (NULL != curl_task_id)
141   {
142     GNUNET_SCHEDULER_cancel (curl_task_id);
143     curl_task_id = NULL;
144   }
145   if (NULL != timeout_task_id)
146   {
147     GNUNET_SCHEDULER_cancel (timeout_task_id);
148     timeout_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     GNUNET_SCHEDULER_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,
263              "VPN failed to allocate appropriate address\n");
264     GNUNET_SCHEDULER_shutdown ();
265     return;
266   }
267   if (AF_INET6 == af)
268     GNUNET_asprintf (&url,
269                      "http://[%s]:%u/hello_world",
270                      inet_ntop (af,
271                                 address,
272                                 ips,
273                                 sizeof (ips)),
274                      (unsigned int) PORT);
275   else
276     GNUNET_asprintf (&url,
277                      "http://%s:%u/hello_world",
278                      inet_ntop (af,
279                                 address,
280                                 ips,
281                                 sizeof (ips)),
282                      (unsigned int) PORT);
283   curl = curl_easy_init ();
284   curl_easy_setopt (curl, CURLOPT_URL, url);
285   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &copy_buffer);
286   curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbc);
287   curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
288   curl_easy_setopt (curl, CURLOPT_TIMEOUT, 150L);
289   curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15L);
290   curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
291   curl_easy_setopt (curl, CURLOPT_VERBOSE, 0);
292
293   multi = curl_multi_init ();
294   GNUNET_assert (multi != NULL);
295   GNUNET_assert (CURLM_OK == curl_multi_add_handle (multi, curl));
296   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
297               "Beginning HTTP download from `%s'\n",
298               url);
299   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
300                                 &curl_main,
301                                 NULL);
302 }
303
304
305 /**
306  * Function to keep the HTTP server running.
307  */
308 static void
309 mhd_main (void);
310
311
312 static void
313 mhd_task (void *cls)
314 {
315   mhd_task_id = NULL;
316   MHD_run (mhd);
317   mhd_main ();
318 }
319
320
321 static void
322 do_timeout (void *cls)
323 {
324   timeout_task_id = NULL;
325   GNUNET_SCHEDULER_shutdown ();
326   GNUNET_break (0);
327   global_ret = 1;
328 }
329
330
331 static void
332 mhd_main ()
333 {
334   struct GNUNET_NETWORK_FDSet nrs;
335   struct GNUNET_NETWORK_FDSet nws;
336   fd_set rs;
337   fd_set ws;
338   fd_set es;
339   int max_fd;
340   unsigned MHD_LONG_LONG timeout;
341   struct GNUNET_TIME_Relative delay;
342
343   GNUNET_assert (NULL == mhd_task_id);
344   FD_ZERO (&rs);
345   FD_ZERO (&ws);
346   FD_ZERO (&es);
347   max_fd = -1;
348   GNUNET_assert (MHD_YES == MHD_get_fdset (mhd, &rs, &ws, &es, &max_fd));
349   if (MHD_YES == MHD_get_timeout (mhd, &timeout))
350     delay =
351         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
352                                        (unsigned int) timeout);
353   else
354     delay = GNUNET_TIME_UNIT_FOREVER_REL;
355   GNUNET_NETWORK_fdset_copy_native (&nrs, &rs, max_fd + 1);
356   GNUNET_NETWORK_fdset_copy_native (&nws, &ws, max_fd + 1);
357   mhd_task_id =
358       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, delay,
359                                    &nrs, &nws, &mhd_task, NULL);
360 }
361
362
363 static void
364 run (void *cls,
365      const struct GNUNET_CONFIGURATION_Handle *cfg,
366      struct GNUNET_TESTING_Peer *peer)
367 {
368   struct in_addr v4;
369   struct in6_addr v6;
370   void *addr;
371   enum MHD_FLAG flags;
372
373   vpn = GNUNET_VPN_connect (cfg);
374   GNUNET_assert (NULL != vpn);
375   flags = MHD_USE_DEBUG;
376   if (AF_INET6 == dest_af)
377     flags |= MHD_USE_IPv6;
378   mhd =
379       MHD_start_daemon (flags, PORT, NULL, NULL, &mhd_ahc, NULL,
380                         MHD_OPTION_END);
381
382
383   GNUNET_assert (NULL != mhd);
384   mhd_main ();
385   addr = NULL;
386   switch (dest_af)
387   {
388   case AF_INET:
389     GNUNET_assert (1 == inet_pton (dest_af, dest_ip, &v4));
390     addr = &v4;
391     break;
392   case AF_INET6:
393     GNUNET_assert (1 == inet_pton (dest_af, dest_ip, &v6));
394     addr = &v6;
395     break;
396   default:
397     GNUNET_assert (0);
398   }
399   rr = GNUNET_VPN_redirect_to_ip (vpn, src_af, dest_af, addr,
400                                   GNUNET_TIME_UNIT_FOREVER_ABS, &allocation_cb,
401                                   NULL);
402   timeout_task_id =
403       GNUNET_SCHEDULER_add_delayed (TIMEOUT,
404                                     &do_timeout,
405                                     NULL);
406   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
407                                  NULL);
408 }
409
410
411 int
412 main (int argc, char *const *argv)
413 {
414   const char *type;
415   const char *bin;
416   char *vpn_binary;
417   char *exit_binary;
418   int ret = 0;
419
420 #ifndef MINGW
421   if (0 != ACCESS ("/dev/net/tun", R_OK))
422   {
423     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
424                               "access",
425                               "/dev/net/tun");
426     fprintf (stderr,
427              "WARNING: System unable to run test, skipping.\n");
428     return 77;
429   }
430 #endif
431   vpn_binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-vpn");
432   exit_binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-exit");
433   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!
434       (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
435   {
436     GNUNET_free (vpn_binary);
437     GNUNET_free (exit_binary);
438     fprintf (stderr,
439              "WARNING: gnunet-helper-{exit,vpn} binaries are not SUID, refusing to run test (as it would have to fail). %d\n", ret);
440     return 77;
441   }
442
443   GNUNET_free (vpn_binary);
444   GNUNET_free (exit_binary);
445   bin = argv[0];
446   if (NULL != strstr (bin, "lt-"))
447     bin = strstr (bin, "lt-") + 4;
448   type = strstr (bin, "-");
449   if (NULL == type)
450   {
451     fprintf (stderr,
452              "invalid binary name\n");
453     return 1;
454   }
455   type++;
456   /* on Windows, .exe is suffixed to these binaries,
457    * thus cease comparison after the 6th char.
458    */
459   if (0 == strncmp (type, "4_to_6",6))
460   {
461     dest_ip = "FC5A:04E1:C2BA::1";
462     dest_af = AF_INET6;
463     src_af = AF_INET;
464   }
465   else if (0 == strncmp (type, "6_to_4",6))
466   {
467     dest_ip = "169.254.86.1";
468     dest_af = AF_INET;
469     src_af = AF_INET6;
470   }
471   else if (0 == strncmp (type, "4_over",6))
472   {
473     dest_ip = "169.254.86.1";
474     dest_af = AF_INET;
475     src_af = AF_INET;
476   }
477   else if (0 == strncmp (type, "6_over",6))
478   {
479     dest_ip = "FC5A:04E1:C2BA::1";
480     dest_af = AF_INET6;
481     src_af = AF_INET6;
482   }
483   else
484   {
485     fprintf (stderr, "invalid binary suffix `%s'\n", type);
486     return 1;
487   }
488   if ((GNUNET_OK != GNUNET_NETWORK_test_pf (src_af)) ||
489       (GNUNET_OK != GNUNET_NETWORK_test_pf (dest_af)))
490   {
491     fprintf (stderr,
492              "Required address families not supported by this system, skipping test.\n");
493     return 0;
494   }
495   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
496   {
497     fprintf (stderr, "failed to initialize curl\n");
498     return 2;
499   }
500   if (0 !=
501       GNUNET_TESTING_peer_run ("test-gnunet-vpn", "test_gnunet_vpn.conf", &run,
502                                NULL))
503     return 1;
504   GNUNET_DISK_directory_remove ("/tmp/gnunet-test-vpn");
505   return global_ret;
506 }
507
508 /* end of test_gnunet_vpn.c */