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