-nicer logging
[oweals/gnunet.git] / src / pt / test_gns_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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file test_gns_vpn.c
23  * @brief testcase for accessing VPN services via GNS
24  * @author Martin Schanzenbach
25  */
26 #include "platform.h"
27 #include <curl/curl.h>
28 #include <microhttpd.h>
29 #include "gnunet_namestore_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "gnunet_gns_service.h"
32 #include "gnunet_testing_lib.h"
33
34 #define PORT 8080
35 #define TEST_DOMAIN "www.gnu"
36
37 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 300)
38
39 /**
40  * Return value for 'main'.
41  */
42 static int global_ret;
43
44 static struct GNUNET_NAMESTORE_Handle *namestore;
45
46 static struct MHD_Daemon *mhd;
47
48 static struct GNUNET_SCHEDULER_Task * mhd_task_id;
49
50 static struct GNUNET_SCHEDULER_Task * curl_task_id;
51
52 static CURL *curl;
53
54 static CURLM *multi;
55
56 static char *url;
57
58 /**
59  * IP address of the ultimate destination.
60  */
61 static const char *dest_ip;
62
63 /**
64  * Address family of the dest_ip.
65  */
66 static int dest_af;
67
68 /**
69  * Address family to use by the curl client.
70  */
71 static int src_af;
72
73 static int use_v6;
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 != NULL)
135   {
136     GNUNET_SCHEDULER_cancel (mhd_task_id);
137     mhd_task_id = NULL;
138   }
139   if (curl_task_id != NULL)
140   {
141     GNUNET_SCHEDULER_cancel (curl_task_id);
142     curl_task_id = NULL;
143   }
144   if (NULL != mhd)
145   {
146     MHD_stop_daemon (mhd);
147     mhd = NULL;
148   }
149   GNUNET_free_non_null (url);
150   url = NULL;
151 }
152
153
154 /**
155  * Function to run the HTTP client.
156  */
157 static void
158 curl_main (void);
159
160
161 static void
162 curl_task (void *cls,
163           const struct GNUNET_SCHEDULER_TaskContext *tc)
164 {
165   curl_task_id = NULL;
166   curl_main ();
167 }
168
169
170 static void
171 curl_main ()
172 {
173   fd_set rs;
174   fd_set ws;
175   fd_set es;
176   int max;
177   struct GNUNET_NETWORK_FDSet nrs;
178   struct GNUNET_NETWORK_FDSet nws;
179   struct GNUNET_TIME_Relative delay;
180   long timeout;
181   int running;
182   struct CURLMsg *msg;
183
184   max = 0;
185   FD_ZERO (&rs);
186   FD_ZERO (&ws);
187   FD_ZERO (&es);
188   curl_multi_perform (multi, &running);
189   if (running == 0)
190   {
191     GNUNET_assert (NULL != (msg = curl_multi_info_read (multi, &running)));
192     if (msg->msg == CURLMSG_DONE)
193     {
194       if (msg->data.result != CURLE_OK)
195       {
196         fprintf (stderr,
197                  "%s failed at %s:%d: `%s'\n",
198                  "curl_multi_perform",
199                 __FILE__,
200                 __LINE__, curl_easy_strerror (msg->data.result));
201         global_ret = 1;
202       }
203     }
204     curl_multi_remove_handle (multi, curl);
205     curl_multi_cleanup (multi);
206     curl_easy_cleanup (curl);
207     curl = NULL;
208     multi = NULL;
209     if (cbc.pos != strlen ("/hello_world"))
210     {
211       GNUNET_break (0);
212       global_ret = 2;
213     }
214     if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
215     {
216       GNUNET_break (0);
217       global_ret = 3;
218     }
219     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Download complete, shutting down!\n");
220     do_shutdown ();
221     return;
222   }
223   GNUNET_assert (CURLM_OK == curl_multi_fdset (multi, &rs, &ws, &es, &max));
224   if ( (CURLM_OK != curl_multi_timeout (multi, &timeout)) ||
225        (-1 == timeout) )
226     delay = GNUNET_TIME_UNIT_SECONDS;
227   else
228     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, (unsigned int) timeout);
229   GNUNET_NETWORK_fdset_copy_native (&nrs,
230                                     &rs,
231                                     max + 1);
232   GNUNET_NETWORK_fdset_copy_native (&nws,
233                                     &ws,
234                                     max + 1);
235   curl_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
236                                               delay,
237                                               &nrs,
238                                               &nws,
239                                               &curl_task,
240                                               NULL);
241 }
242
243
244 static void
245 start_curl (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
246 {
247   GNUNET_asprintf (&url,
248                    "http://%s/hello_world",
249                    TEST_DOMAIN);
250   curl = curl_easy_init ();
251   curl_easy_setopt (curl, CURLOPT_URL, url);
252   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &copy_buffer);
253   curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbc);
254   curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
255   curl_easy_setopt (curl, CURLOPT_TIMEOUT, 150L);
256   curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15L);
257   curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
258
259   multi = curl_multi_init ();
260   GNUNET_assert (multi != NULL);
261   GNUNET_assert (CURLM_OK == curl_multi_add_handle (multi, curl));
262   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Beginning HTTP download from `%s'\n", url);
263   curl_main ();
264 }
265
266
267 static void
268 disco_ns (void* cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
269 {
270   GNUNET_NAMESTORE_disconnect (namestore);
271   namestore = NULL;
272 }
273
274
275 /**
276  * Callback invoked from the namestore service once record is
277  * created.
278  *
279  * @param cls closure
280  * @param af address family, AF_INET or AF_INET6; AF_UNSPEC on error;
281  *                will match 'result_af' from the request
282  * @param address IP address (struct in_addr or struct in_addr6, depending on 'af')
283  *                that the VPN allocated for the redirection;
284  *                traffic to this IP will now be redirected to the
285  *                specified target peer; NULL on error
286  */
287 static void
288 commence_testing (void *cls, int32_t success, const char *emsg)
289 {
290   GNUNET_SCHEDULER_add_now (&disco_ns, NULL);
291
292   if ((emsg != NULL) && (GNUNET_YES != success))
293   {
294     fprintf (stderr,
295              "NS failed to create record %s\n", emsg);
296     GNUNET_SCHEDULER_shutdown ();
297     return;
298   }
299   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 10), &start_curl, NULL);
300 }
301
302
303 /**
304  * Function to keep the HTTP server running.
305  */
306 static void
307 mhd_main (void);
308
309
310 static void
311 mhd_task (void *cls,
312           const struct GNUNET_SCHEDULER_TaskContext *tc)
313 {
314   mhd_task_id = NULL;
315   MHD_run (mhd);
316   mhd_main ();
317 }
318
319
320 static void
321 mhd_main ()
322 {
323   struct GNUNET_NETWORK_FDSet nrs;
324   struct GNUNET_NETWORK_FDSet nws;
325   fd_set rs;
326   fd_set ws;
327   fd_set es;
328   int max_fd;
329   unsigned MHD_LONG_LONG timeout;
330   struct GNUNET_TIME_Relative delay;
331
332   GNUNET_assert (NULL == mhd_task_id);
333   FD_ZERO (&rs);
334   FD_ZERO (&ws);
335   FD_ZERO (&es);
336   max_fd = -1;
337   GNUNET_assert (MHD_YES ==
338                  MHD_get_fdset (mhd, &rs, &ws, &es, &max_fd));
339   if (MHD_YES == MHD_get_timeout (mhd, &timeout))
340     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
341                                            (unsigned int) timeout);
342   else
343     delay = GNUNET_TIME_UNIT_FOREVER_REL;
344   GNUNET_NETWORK_fdset_copy_native (&nrs,
345                                     &rs,
346                                     max_fd + 1);
347   GNUNET_NETWORK_fdset_copy_native (&nws,
348                                     &ws,
349                                     max_fd + 1);
350   mhd_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
351                                              delay,
352                                              &nrs,
353                                              &nws,
354                                              &mhd_task,
355                                              NULL);
356 }
357
358
359 static void
360 run (void *cls,
361      const struct GNUNET_CONFIGURATION_Handle *cfg,
362      struct GNUNET_TESTING_Peer *peer)
363 {
364   enum MHD_FLAG flags;
365   struct GNUNET_PeerIdentity id;
366   char *peername;
367   struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key;
368   struct GNUNET_GNSRECORD_Data rd;
369   char *rd_string;
370   char *zone_keyfile;
371
372   GNUNET_TESTING_peer_get_identity (peer, &id);
373   peername = GNUNET_strdup (GNUNET_i2s_full (&id));
374
375   namestore = GNUNET_NAMESTORE_connect (cfg);
376   GNUNET_assert (NULL != namestore);
377   flags = MHD_USE_DEBUG;
378   if (GNUNET_YES == use_v6)
379     flags |= MHD_USE_IPv6;
380   mhd = MHD_start_daemon (flags,
381                           PORT,
382                           NULL, NULL,
383                           &mhd_ahc, NULL,
384                           MHD_OPTION_END);
385   GNUNET_assert (NULL != mhd);
386   mhd_main ();
387
388   if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gns",
389                                                             "ZONEKEY",
390                                                             &zone_keyfile))
391   {
392     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
393                 "Failed to get key from cfg\n");
394     GNUNET_free (peername);
395     return;
396   }
397
398   zone_key = GNUNET_CRYPTO_ecdsa_key_create_from_file (zone_keyfile);
399   rd.expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us;
400   GNUNET_asprintf (&rd_string,
401                    "6 %s %s",
402                    peername,
403                    "www.gnu.");
404   GNUNET_free (peername);
405   GNUNET_assert (GNUNET_OK ==
406                  GNUNET_GNSRECORD_string_to_value (GNUNET_GNSRECORD_TYPE_VPN,
407                                                    rd_string,
408                                                    (void**) &rd.data,
409                                                    &rd.data_size));
410   rd.record_type = GNUNET_GNSRECORD_TYPE_VPN;
411
412   GNUNET_NAMESTORE_records_store (namestore,
413                                   zone_key,
414                                   "www",
415                                   1, &rd,
416                                   &commence_testing,
417                                   NULL);
418   GNUNET_free ((void**)rd.data);
419   GNUNET_free (rd_string);
420   GNUNET_free (zone_keyfile);
421   GNUNET_free (zone_key);
422 }
423
424
425 /**
426  * Open '/dev/null' and make the result the given
427  * file descriptor.
428  *
429  * @param target_fd desired FD to point to /dev/null
430  * @param flags open flags (O_RDONLY, O_WRONLY)
431  */
432 static void
433 open_dev_null (int target_fd,
434                int flags)
435 {
436   int fd;
437
438   fd = open ("/dev/null", flags);
439   if (-1 == fd)
440     abort ();
441   if (fd == target_fd)
442     return;
443   if (-1 == dup2 (fd, target_fd))
444   {
445     (void) close (fd);
446     abort ();
447   }
448   (void) close (fd);
449 }
450
451
452 /**
453  * Run the given command and wait for it to complete.
454  *
455  * @param file name of the binary to run
456  * @param cmd command line arguments (as given to 'execv')
457  * @return 0 on success, 1 on any error
458  */
459 static int
460 fork_and_exec (const char *file,
461                char *const cmd[])
462 {
463   int status;
464   pid_t pid;
465   pid_t ret;
466
467   pid = fork ();
468   if (-1 == pid)
469   {
470     fprintf (stderr,
471              "fork failed: %s\n",
472              strerror (errno));
473     return 1;
474   }
475   if (0 == pid)
476   {
477     /* we are the child process */
478     /* close stdin/stdout to not cause interference
479        with the helper's main protocol! */
480     (void) close (0);
481     open_dev_null (0, O_RDONLY);
482     (void) close (1);
483     open_dev_null (1, O_WRONLY);
484     (void) execv (file, cmd);
485     /* can only get here on error */
486     fprintf (stderr,
487              "exec `%s' failed: %s\n",
488              file,
489              strerror (errno));
490     _exit (1);
491   }
492   /* keep running waitpid as long as the only error we get is 'EINTR' */
493   while ( (-1 == (ret = waitpid (pid, &status, 0))) &&
494           (errno == EINTR) );
495   if (-1 == ret)
496   {
497     fprintf (stderr,
498              "waitpid failed: %s\n",
499              strerror (errno));
500     return 1;
501   }
502   if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status))))
503     return 1;
504   /* child process completed and returned success, we're happy */
505   return 0;
506 }
507
508 int
509 main (int argc, char *const *argv)
510 {
511   char *sbin_iptables;
512   char *bin_vpn;
513   char *bin_exit;
514   char *bin_dns;
515   char *const iptables_args[] =
516   {
517     "iptables", "-t", "mangle", "-L", "-v", NULL
518   };
519
520   if (0 == access ("/sbin/iptables", X_OK))
521     sbin_iptables = "/sbin/iptables";
522   else if (0 == access ("/usr/sbin/iptables", X_OK))
523     sbin_iptables = "/usr/sbin/iptables";
524   else
525   {
526     fprintf (stderr,
527              "Executable iptables not found in approved directories: %s, skipping\n",
528              strerror (errno));
529     return 0;
530   }
531
532   if (0 != fork_and_exec (sbin_iptables, iptables_args))
533   {
534     fprintf (stderr,
535              "Failed to run `iptables -t mangle -L -v'. Skipping test.\n");
536     return 0;
537   }
538
539   if (0 != ACCESS ("/dev/net/tun", R_OK))
540   {
541     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
542                               "access",
543                               "/dev/net/tun");
544     fprintf (stderr,
545              "WARNING: System unable to run test, skipping.\n");
546     return 0;
547   }
548
549   bin_vpn = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-vpn");
550   bin_exit = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-exit");
551   bin_dns = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-dns");
552   if ( (0 != geteuid ()) &&
553        ( (GNUNET_YES !=
554           GNUNET_OS_check_helper_binary (bin_vpn, GNUNET_YES, "-d gnunet-vpn - - 169.1.3.3.7 255.255.255.0")) || //ipv4 only please!
555          (GNUNET_YES !=
556           GNUNET_OS_check_helper_binary (bin_exit, GNUNET_YES, "-d gnunet-vpn - - - 169.1.3.3.7 255.255.255.0")) || //no nat, ipv4 only
557          (GNUNET_YES !=
558           GNUNET_OS_check_helper_binary (bin_dns, GNUNET_YES, NULL))) ) // TODO: once we have a windows-testcase, add test parameters here
559   {
560     fprintf (stderr,
561              "WARNING: gnunet-helper-{exit,vpn,dns} binaries in $PATH are not SUID, refusing to run test (as it would have to fail).\n");
562     fprintf (stderr,
563              "Change $PATH ('.' in $PATH before $GNUNET_PREFIX/bin is problematic) or permissions (run 'make install' as root) to fix this!\n");
564     GNUNET_free (bin_vpn);
565     GNUNET_free (bin_exit);
566     GNUNET_free (bin_dns);
567     return 0;
568   }
569   GNUNET_free (bin_vpn);
570   GNUNET_free (bin_exit);
571   GNUNET_free (bin_dns);
572
573   dest_ip = "169.254.86.1";
574   dest_af = AF_INET;
575   src_af = AF_INET;
576
577   if (GNUNET_OK == GNUNET_NETWORK_test_pf (PF_INET6))
578     use_v6 = GNUNET_YES;
579   else
580     use_v6 = GNUNET_NO;
581
582   if ( (GNUNET_OK != GNUNET_NETWORK_test_pf (src_af)) ||
583        (GNUNET_OK != GNUNET_NETWORK_test_pf (dest_af)) )
584   {
585     fprintf (stderr,
586              "Required address families not supported by this system, skipping test.\n");
587     return 0;
588   }
589   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
590   {
591     fprintf (stderr, "failed to initialize curl\n");
592     return 2;
593   }
594   if (0 != GNUNET_TESTING_peer_run ("test-gnunet-vpn",
595                                     "test_gns_vpn.conf",
596                                     &run, NULL))
597     return 1;
598   GNUNET_DISK_directory_remove ("/tmp/gnunet-test-vpn");
599   return global_ret;
600 }
601
602 /* end of test_gns_vpn.c */