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