Temporal workaround for #2070
[oweals/gnunet.git] / src / pt / gnunet-daemon-pt.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010, 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 pt/gnunet-daemon-pt.c
23  * @brief tool to manipulate DNS and VPN services to perform protocol translation (IPvX over GNUnet)
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_dns_service.h"
29 #include "gnunet_dnsparser_lib.h"
30 #include "gnunet_vpn_service.h"
31 #include "gnunet_statistics_service.h"
32
33
34 /**
35  * After how long do we time out if we could not get an IP from VPN?
36  */
37 #define TIMEOUT GNUNET_TIME_UNIT_MINUTES
38
39
40 /**
41  * How many bytes of payload do we allow at most for a DNS reply?
42  * Given that this is pretty much limited to loopback, we can be
43  * pretty high (Linux loopback defaults to 16k, most local UDP packets
44  * should survive up to 9k (NFS), so 8k should be pretty safe in
45  * general).
46  */
47 #define MAX_DNS_SIZE (8 * 1024)
48
49
50 /**
51  * Which group of DNS records are we currently processing?
52  */
53 enum RequestGroup
54   {
55     /**
56      * DNS answers
57      */
58     ANSWERS = 0, 
59
60     /**
61      * DNS authority records
62      */
63     AUTHORITY_RECORDS = 1,
64
65     /**
66      * DNS additional records
67      */
68     ADDITIONAL_RECORDS = 2,
69
70     /**
71      * We're done processing.
72      */
73     END = 3
74   };
75
76
77 /**
78  * Information tracked per DNS request that we are processing.
79  */
80 struct RequestContext
81 {
82   /**
83    * Handle to submit the final result.
84    */
85   struct GNUNET_DNS_RequestHandle *rh;
86   
87   /**
88    * DNS packet that is being modified.
89    */
90   struct GNUNET_DNSPARSER_Packet *dns;
91
92   /**
93    * Active redirection request with the VPN.
94    */
95   struct GNUNET_VPN_RedirectionRequest *rr;
96
97   /**
98    * Record for which we have an active redirection request.
99    */
100   struct GNUNET_DNSPARSER_Record *rec;
101
102   /**
103    * Offset in the current record group that is being modified.
104    */
105   unsigned int offset;
106
107   /**
108    * Group that is being modified
109    */
110   enum RequestGroup group;
111   
112 };
113
114
115 /**
116  * The handle to the configuration used throughout the process
117  */
118 static const struct GNUNET_CONFIGURATION_Handle *cfg;
119
120 /**
121  * The handle to the VPN
122  */
123 static struct GNUNET_VPN_Handle *vpn_handle;
124
125 /**
126  * Statistics.
127  */
128 static struct GNUNET_STATISTICS_Handle *stats;
129
130 /**
131  * The handle to DNS
132  */
133 static struct GNUNET_DNS_Handle *dns_handle;
134
135 /**
136  * Are we doing IPv4-pt?
137  */
138 static int ipv4_pt;
139
140 /**
141  * Are we doing IPv6-pt?
142  */
143 static int ipv6_pt;
144
145
146 /**
147  * We're done modifying all records in the response.  Submit the reply
148  * and free the resources of the rc.
149  *
150  * @param rc context to process
151  */
152 static void
153 finish_request (struct RequestContext *rc)
154 {
155   char *buf;
156   size_t buf_len;
157
158   if (GNUNET_SYSERR ==
159       GNUNET_DNSPARSER_pack (rc->dns,
160                              MAX_DNS_SIZE,
161                              &buf,
162                              &buf_len))
163   {
164     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
165                 _("Failed to pack DNS request.  Dropping.\n"));
166     GNUNET_DNS_request_drop (rc->rh);
167   }
168   else
169   {
170     GNUNET_STATISTICS_update (stats,
171                               gettext_noop ("# DNS requests mapped to VPN"),
172                               1, GNUNET_NO);
173     GNUNET_DNS_request_answer (rc->rh,
174                                buf_len, buf);
175     GNUNET_free (buf);
176   }
177   GNUNET_DNSPARSER_free_packet (rc->dns);
178   GNUNET_free (rc);
179 }
180
181
182 /**
183  * Process the next record of the given request context.
184  * When done, submit the reply and free the resources of
185  * the rc.
186  *
187  * @param rc context to process
188  */
189 static void
190 submit_request (struct RequestContext *rc);
191
192
193 /**
194  * Callback invoked from the VPN service once a redirection is
195  * available.  Provides the IP address that can now be used to
196  * reach the requested destination.  We substitute the active
197  * record and then continue with 'submit_request' to look at
198  * the other records.
199  *
200  * @param cls our 'struct RequestContext'
201  * @param af address family, AF_INET or AF_INET6; AF_UNSPEC on error;
202  *                will match 'result_af' from the request
203  * @param address IP address (struct in_addr or struct in_addr6, depending on 'af')
204  *                that the VPN allocated for the redirection;
205  *                traffic to this IP will now be redirected to the 
206  *                specified target peer; NULL on error
207  */
208 static void
209 vpn_allocation_callback (void *cls,
210                          int af,
211                          const void *address)
212 {
213   struct RequestContext *rc = cls;
214
215   rc->rr = NULL;
216   if (af == AF_UNSPEC)
217   {
218     GNUNET_DNS_request_drop (rc->rh);
219     GNUNET_DNSPARSER_free_packet (rc->dns);
220     GNUNET_free (rc);
221     return;
222   }
223   GNUNET_STATISTICS_update (stats,
224                             gettext_noop ("# DNS records modified"),
225                             1, GNUNET_NO);
226   switch (rc->rec->type)
227   {
228   case GNUNET_DNSPARSER_TYPE_A:
229     GNUNET_assert (AF_INET == af);
230     memcpy (rc->rec->data.raw.data, address, sizeof (struct in_addr));
231     break;
232   case GNUNET_DNSPARSER_TYPE_AAAA:
233     GNUNET_assert (AF_INET6 == af);
234     memcpy (rc->rec->data.raw.data, address, sizeof (struct in6_addr));
235     break;
236   default:
237     GNUNET_assert (0);
238     return;
239   }
240   rc->rec = NULL;
241   submit_request (rc);
242 }
243
244
245 /**
246  * Modify the given DNS record by asking VPN to create a tunnel
247  * to the given address.  When done, continue with submitting
248  * other records from the request context ('submit_request' is
249  * our continuation).
250  *
251  * @param rc context to process
252  * @param rec record to modify
253  */
254 static void
255 modify_address (struct RequestContext *rc,
256                 struct GNUNET_DNSPARSER_Record *rec)
257 {
258   int af;
259
260   switch (rec->type)
261   {
262   case GNUNET_DNSPARSER_TYPE_A:
263     af = AF_INET;
264     GNUNET_assert (rec->data.raw.data_len == sizeof (struct in_addr));
265     break;
266   case GNUNET_DNSPARSER_TYPE_AAAA:
267     af = AF_INET6;
268     GNUNET_assert (rec->data.raw.data_len == sizeof (struct in6_addr));
269     break;
270   default:
271     GNUNET_assert (0);
272     return;
273   }
274   rc->rec = rec;
275   rc->rr = GNUNET_VPN_redirect_to_ip (vpn_handle,
276                                       af, af,
277                                       rec->data.raw.data,
278                                       GNUNET_NO /* nac */,
279                                       GNUNET_TIME_relative_to_absolute (TIMEOUT),
280                                       &vpn_allocation_callback,
281                                       rc);
282 }
283
284
285 /**
286  * Process the next record of the given request context.
287  * When done, submit the reply and free the resources of
288  * the rc.
289  *
290  * @param rc context to process
291  */
292 static void
293 submit_request (struct RequestContext *rc)
294 {
295   struct GNUNET_DNSPARSER_Record *ra;
296   unsigned int ra_len;
297   unsigned int i;
298
299   while (1)
300   {
301     switch (rc->group)
302     {
303     case ANSWERS:
304       ra = rc->dns->answers;
305       ra_len = rc->dns->num_answers;
306       break;
307     case AUTHORITY_RECORDS:
308       ra = rc->dns->authority_records;
309       ra_len = rc->dns->num_authority_records;
310       break;
311     case ADDITIONAL_RECORDS:
312       ra = rc->dns->additional_records;
313       ra_len = rc->dns->num_additional_records;
314       break;
315     case END:
316       finish_request (rc);
317       return;
318     default:
319       GNUNET_assert (0);      
320     }
321     for (i=rc->offset;i<ra_len;i++)
322     {
323       switch (ra[i].type)
324       {
325       case GNUNET_DNSPARSER_TYPE_A:
326         if (ipv4_pt)
327         {
328           rc->offset = i + 1;
329           modify_address (rc, &ra[i]);
330           return;
331         }
332         break;
333       case GNUNET_DNSPARSER_TYPE_AAAA:
334         if (ipv6_pt)
335         {
336           rc->offset = i + 1;
337           modify_address (rc, &ra[i]);
338           return;
339         }
340         break;
341       }
342     }
343     rc->group++;
344   }
345 }
346
347
348 /**
349  * Test if any of the given records need protocol-translation work.
350  *
351  * @param ra array of records
352  * @param ra_len number of entries in ra
353  * @return GNUNET_YES if any of the given records require protocol-translation
354  */
355 static int
356 work_test (const struct GNUNET_DNSPARSER_Record *ra,
357            unsigned int ra_len)
358 {
359   unsigned int i;
360
361   for (i=0;i<ra_len;i++)
362   {
363     switch (ra[i].type)
364     {
365     case GNUNET_DNSPARSER_TYPE_A:
366       if (ipv4_pt)
367         return GNUNET_YES;
368       break;
369     case GNUNET_DNSPARSER_TYPE_AAAA:
370       if (ipv6_pt)
371         return GNUNET_YES;
372       break;
373     }
374   }
375   return GNUNET_NO;
376 }
377
378
379 /**
380  * Signature of a function that is called whenever the DNS service
381  * encounters a DNS request and needs to do something with it.  The
382  * function has then the chance to generate or modify the response by
383  * calling one of the three "GNUNET_DNS_request_*" continuations.
384  *
385  * When a request is intercepted, this function is called first to
386  * give the client a chance to do the complete address resolution;
387  * "rdata" will be NULL for this first call for a DNS request, unless
388  * some other client has already filled in a response.
389  *
390  * If multiple clients exist, all of them are called before the global
391  * DNS.  The global DNS is only called if all of the clients'
392  * functions call GNUNET_DNS_request_forward.  Functions that call
393  * GNUNET_DNS_request_forward will be called again before a final
394  * response is returned to the application.  If any of the clients'
395  * functions call GNUNET_DNS_request_drop, the response is dropped.
396  *
397  * @param cls closure
398  * @param rh request handle to user for reply
399  * @param request_length number of bytes in request
400  * @param request udp payload of the DNS request
401  */
402 static void 
403 dns_request_handler (void *cls,
404                      struct GNUNET_DNS_RequestHandle *rh,
405                      size_t request_length,
406                      const char *request)
407 {
408   struct GNUNET_DNSPARSER_Packet *dns;
409   struct RequestContext *rc;
410   int work;
411
412   GNUNET_STATISTICS_update (stats,
413                             gettext_noop ("# DNS requests intercepted"),
414                             1, GNUNET_NO);
415   dns = GNUNET_DNSPARSER_parse (request, request_length);
416   if (NULL == dns)
417   {
418     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
419                 _("Failed to parse DNS request.  Dropping.\n"));
420     GNUNET_DNS_request_drop (rh);
421     return;
422   }
423   work = GNUNET_NO;
424   work |= work_test (dns->answers, dns->num_answers);
425   work |= work_test (dns->authority_records, dns->num_authority_records);
426   work |= work_test (dns->additional_records, dns->num_additional_records);
427   if (! work)
428   {
429     GNUNET_DNS_request_forward (rh);
430     return;
431   }
432   rc = GNUNET_malloc (sizeof (struct RequestContext));
433   rc->rh = rh;
434   rc->dns = dns;
435   rc->offset = 0;
436   rc->group = ANSWERS;
437   submit_request (rc);
438 }
439
440
441 /**
442  * Function scheduled as very last function, cleans up after us
443  */
444 static void
445 cleanup (void *cls GNUNET_UNUSED,
446          const struct GNUNET_SCHEDULER_TaskContext *tskctx)
447 {
448   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
449               "Protocol translation daemon is shutting down now\n");
450   if (vpn_handle != NULL)
451   {
452     GNUNET_VPN_disconnect (vpn_handle);
453     vpn_handle = NULL;
454   }
455   if (dns_handle != NULL)
456   {
457     GNUNET_DNS_disconnect (dns_handle);
458     dns_handle = NULL;
459   }
460   if (stats != NULL)
461   {
462     GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
463     stats = NULL;
464   }
465 }
466
467
468 /**
469  * @brief Main function that will be run by the scheduler.
470  *
471  * @param cls closure
472  * @param args remaining command-line arguments
473  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
474  * @param cfg_ configuration
475  */
476 static void
477 run (void *cls, char *const *args GNUNET_UNUSED,
478      const char *cfgfile GNUNET_UNUSED,
479      const struct GNUNET_CONFIGURATION_Handle *cfg_)
480 {
481   cfg = cfg_;
482   stats = GNUNET_STATISTICS_create ("pt", cfg);
483   ipv4_pt = GNUNET_CONFIGURATION_get_value_yesno (cfg, "pt", "TUNNEL_IPV4");
484   ipv6_pt = GNUNET_CONFIGURATION_get_value_yesno (cfg, "pt", "TUNNEL_IPV6"); 
485   if (! (ipv4_pt || ipv6_pt))
486   {
487     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
488                 _("No useful service enabled.  Exiting.\n"));
489     GNUNET_SCHEDULER_shutdown ();
490     return;    
491   }
492   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup, cls);
493   dns_handle 
494     = GNUNET_DNS_connect (cfg, 
495                           GNUNET_DNS_FLAG_POST_RESOLUTION,
496                           &dns_request_handler, NULL);
497   if (NULL == dns_handle)
498   {
499     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
500                 _("Failed to connect to %s service.  Exiting.\n"),
501                 "DNS");
502     GNUNET_SCHEDULER_shutdown ();
503     return;
504   }
505   vpn_handle = GNUNET_VPN_connect (cfg);
506   if (NULL == vpn_handle)
507   {
508     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
509                 _("Failed to connect to %s service.  Exiting.\n"),
510                 "VPN");
511     GNUNET_SCHEDULER_shutdown ();
512     return;
513   }
514 }
515
516
517 /**
518  * The main function
519  *
520  * @param argc number of arguments from the command line
521  * @param argv command line arguments
522  * @return 0 ok, 1 on error
523  */
524 int
525 main (int argc, char *const *argv)
526 {
527   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
528     GNUNET_GETOPT_OPTION_END
529   };
530
531   return (GNUNET_OK ==
532           GNUNET_PROGRAM_run (argc, argv, "gnunet-daemon-pt",
533                               gettext_noop
534                               ("Daemon to run to perform IP protocol translation to GNUnet"),
535                               options, &run, NULL)) ? 0 : 1;
536 }
537
538
539 /* end of gnunet-daemon-pt.c */