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