- updated NAT callback function defintion to no longer carry around strings,
[oweals/gnunet.git] / src / nat / nat_auto.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012 Christian Grothoff (and other contributing authors)
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 nat/nat_auto.c
23  * @brief functions for auto-configuration of the network
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_resolver_service.h"
29 #include "gnunet_nat_lib.h"
30 #include "nat.h"
31
32 #define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__)
33
34
35 /**
36  * How long do we wait for the NAT test to report success?
37  */
38 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
39
40 /**
41  * Phases of the auto configuration.
42  */
43 enum AutoPhase
44 {
45   /**
46    * Initial start value.
47    */
48   AUTO_INIT = 0,
49
50   /**
51    * Test if we are online.
52    */
53   AUTO_ONLINE,
54
55   /**
56    * Test our external IP.
57    */
58   AUTO_EXTERNAL_IP,
59
60   /**
61    * Test our internal IP.
62    */
63   AUTO_LOCAL_IP,
64
65   /**
66    * Test if NAT was punched.
67    */
68   AUTO_NAT_PUNCHED,
69
70   /**
71    * Test if UPnP is working.
72    */
73   AUTO_UPNPC,
74
75   /**
76    * Test if ICMP server works.
77    */
78   AUTO_ICMP_SERVER,
79
80   /**
81    * Test if ICMP client works.
82    */
83   AUTO_ICMP_CLIENT,
84
85   /**
86    * Last phase, we're done.
87    */
88   AUTO_DONE
89
90 };
91
92
93 /**
94  * Handle to auto-configuration in progress.
95  */
96 struct GNUNET_NAT_AutoHandle
97 {
98
99   /**
100    * Handle to the active NAT test.
101    */
102   struct GNUNET_NAT_Test *tst;
103
104   /**
105    * Function to call when done.
106    */
107   GNUNET_NAT_AutoResultCallback fin_cb;
108
109   /**
110    * Closure for @e fin_cb.
111    */
112   void *fin_cb_cls;
113
114   /**
115    * Handle for active 'GNUNET_NAT_mini_get_external_ipv4'-operation.
116    */
117   struct GNUNET_NAT_ExternalHandle *eh;
118
119   /**
120    * Current configuration (with updates from previous phases)
121    */
122   struct GNUNET_CONFIGURATION_Handle *cfg;
123
124   /**
125    * Original configuration (used to calculate differences)
126    */
127   struct GNUNET_CONFIGURATION_Handle *initial_cfg;
128
129   /**
130    * Task identifier for the timeout.
131    */
132   GNUNET_SCHEDULER_TaskIdentifier task;
133
134   /**
135    * Where are we in the test?
136    */
137   enum AutoPhase phase;
138
139   /**
140    * Do we have IPv6?
141    */
142   int have_v6;
143
144 };
145
146
147 /**
148  * Run the next phase of the auto test.
149  *
150  * @param ah auto test handle
151  */
152 static void
153 next_phase (struct GNUNET_NAT_AutoHandle *ah);
154
155
156 /**
157  * Function called if NAT failed to confirm success.
158  * Clean up and update GUI (with failure).
159  *
160  * @param cls closure with setup context
161  * @param tc scheduler callback
162  */
163 static void
164 fail_timeout (void *cls,
165               const struct GNUNET_SCHEDULER_TaskContext *tc)
166 {
167   struct GNUNET_NAT_AutoHandle *ah = cls;
168
169   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
170               _("NAT traversal with ICMP Server timed out.\n"));
171   GNUNET_assert (NULL != ah->tst);
172   ah->task = GNUNET_SCHEDULER_NO_TASK;
173   GNUNET_NAT_test_stop (ah->tst);
174   ah->tst = NULL;
175   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat",
176                                          "ENABLE_ICMP_SERVER",
177                                          "NO");
178   next_phase (ah);
179 }
180
181
182 /**
183  * Function called by NAT on success.
184  * Clean up and update GUI (with success).
185  *
186  * @param cls the auto handle
187  * @param success currently always #GNUNET_OK
188  * @param emsg NULL on success, otherwise an error message
189  */
190 static void
191 result_callback (void *cls,
192                  enum GNUNET_NAT_FailureCode ret)
193 {
194   struct GNUNET_NAT_AutoHandle *ah = cls;
195
196   GNUNET_SCHEDULER_cancel (ah->task);
197   ah->task = GNUNET_SCHEDULER_NO_TASK;
198   GNUNET_NAT_test_stop (ah->tst);
199   ah->tst = NULL;
200   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
201               ret
202               ? _("NAT traversal with ICMP Server succeeded.\n")
203               : _("NAT traversal with ICMP Server failed.\n"));
204   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_ICMP_SERVER",
205                                          ret ? "NO" : "YES");
206   next_phase (ah);
207 }
208
209
210 /**
211  * Main function for the connection reversal test.
212  *
213  * @param cls the `struct GNUNET_NAT_AutoHandle`
214  * @param tc scheduler context
215  */
216 static void
217 reversal_test (void *cls,
218                const struct GNUNET_SCHEDULER_TaskContext *tc)
219 {
220   struct GNUNET_NAT_AutoHandle *ah = cls;
221
222   ah->task = GNUNET_SCHEDULER_NO_TASK;
223   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
224               _("Testing connection reversal with ICMP server.\n"));
225   GNUNET_RESOLVER_connect (ah->cfg);
226   ah->tst = GNUNET_NAT_test_start (ah->cfg, GNUNET_YES, 0, 0,
227                                    &result_callback, ah);
228   if (NULL == ah->tst)
229   {
230     next_phase (ah);
231     return;
232   }
233   ah->task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &fail_timeout, ah);
234 }
235
236
237 /**
238  * Test if we are online at all.
239  *
240  * @param ah auto setup context
241  */
242 static void
243 test_online (struct GNUNET_NAT_AutoHandle *ah)
244 {
245   // FIXME: not implemented
246   next_phase (ah);
247 }
248
249
250 /**
251  * Set our external IPv4 address.
252  *
253  * @param cls closure with our setup context
254  * @param addr the address, NULL on errors
255  * @param emsg NULL on success, otherwise an error message
256  */
257 static void
258 set_external_ipv4 (void *cls,
259                    const struct in_addr *addr,
260                    enum GNUNET_NAT_FailureCode ret)
261 {
262   struct GNUNET_NAT_AutoHandle *ah = cls;
263   char buf[INET_ADDRSTRLEN];
264
265   ah->eh = NULL;
266   if (NULL == addr)
267   {
268     next_phase (ah);
269     return;
270   }
271   /* enable 'behind nat' */
272   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
273               _("Detected external IP `%s'\n"),
274               inet_ntop (AF_INET,
275                          addr,
276                          buf,
277                          sizeof (buf)));
278   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "BEHIND_NAT", "YES");
279
280   /* set external IP address */
281   if (NULL == inet_ntop (AF_INET, addr, buf, sizeof (buf)))
282   {
283     GNUNET_break (0);
284     next_phase (ah);
285     return;
286   }
287   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS",
288                                          buf);
289   next_phase (ah);
290 }
291
292
293 /**
294  * Determine our external IPv4 address.
295  *
296  * @param ah auto setup context
297  */
298 static void
299 test_external_ip (struct GNUNET_NAT_AutoHandle *ah)
300 {
301   // FIXME: CPS?
302   /* try to detect external IP */
303   ah->eh = GNUNET_NAT_mini_get_external_ipv4 (TIMEOUT,
304                                               &set_external_ipv4, ah);
305 }
306
307
308 /**
309  * Process list of local IP addresses.  Find and set the
310  * one of the default interface.
311  *
312  * @param cls our `struct GNUNET_NAT_AutoHandle`
313  * @param name name of the interface (can be NULL for unknown)
314  * @param isDefault is this presumably the default interface
315  * @param addr address of this interface (can be NULL for unknown or unassigned)
316  * @param broadcast_addr the broadcast address (can be NULL for unknown or unassigned)
317  * @param netmask the network mask (can be NULL for unknown or unassigned))
318  * @param addrlen length of the @a addr and @a broadcast_addr
319  * @return GNUNET_OK to continue iteration, #GNUNET_SYSERR to abort
320  */
321 static int
322 nipo (void *cls,
323       const char *name,
324       int isDefault,
325       const struct sockaddr *addr,
326       const struct sockaddr *broadcast_addr,
327       const struct sockaddr *netmask,
328       socklen_t addrlen)
329 {
330   struct GNUNET_NAT_AutoHandle *ah = cls;
331   const struct sockaddr_in *in;
332   char buf[INET_ADDRSTRLEN];
333
334   if (!isDefault)
335     return GNUNET_OK;
336   if ( (sizeof (struct sockaddr_in6) == addrlen) &&
337        (0 != memcmp (&in6addr_loopback, &((const struct sockaddr_in6 *) addr)->sin6_addr,
338                      sizeof (struct in6_addr))) &&
339        (! IN6_IS_ADDR_LINKLOCAL(&((const struct sockaddr_in6 *) addr)->sin6_addr)) )
340   {
341     ah->have_v6 = GNUNET_YES;
342     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
343                 _("This system has a global IPv6 address, setting IPv6 to supported.\n"));
344     return GNUNET_OK;
345   }
346   if (addrlen != sizeof (struct sockaddr_in))
347     return GNUNET_OK;
348   in = (const struct sockaddr_in *) addr;
349
350   /* set internal IP address */
351   if (NULL == inet_ntop (AF_INET, &in->sin_addr, buf, sizeof (buf)))
352   {
353     GNUNET_break (0);
354     return GNUNET_OK;
355   }
356   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS",
357                                          buf);
358   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
359               _("Detected internal network address `%s'.\n"),
360               buf);
361   /* no need to continue iteration */
362   return GNUNET_SYSERR;
363 }
364
365
366 /**
367  * Determine our local IP addresses; detect internal IP & IPv6-support
368  *
369  * @param ah auto setup context
370  */
371 static void
372 test_local_ip (struct GNUNET_NAT_AutoHandle *ah)
373 {
374   ah->have_v6 = GNUNET_NO;
375   GNUNET_OS_network_interfaces_list (&nipo, ah);
376   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "DISABLEV6",
377                                          (GNUNET_YES == ah->have_v6) ? "NO" : "YES");
378   next_phase (ah);
379 }
380
381
382 /**
383  * Test if NAT has been punched
384  *
385  * @param ah auto setup context
386  */
387 static void
388 test_nat_punched (struct GNUNET_NAT_AutoHandle *ah)
389 {
390   // FIXME: not implemented
391   next_phase (ah);
392 }
393
394
395 /**
396  * Test if UPnPC works.
397  *
398  * @param ah auto setup context
399  */
400 static void
401 test_upnpc (struct GNUNET_NAT_AutoHandle *ah)
402 {
403   int have_upnpc;
404
405   /* test if upnpc is available */
406   have_upnpc = (GNUNET_SYSERR !=
407                 GNUNET_OS_check_helper_binary ("upnpc", GNUNET_NO, NULL));
408   /* FIXME: test if upnpc is actually working, that is, if transports
409      start to work once we use UPnP */
410   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
411               (have_upnpc)
412               ? _("upnpc found, enabling its use\n")
413               : _("upnpc not found\n"));
414   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_UPNP",
415                                          (GNUNET_YES == have_upnpc) ? "YES" : "NO");
416   next_phase (ah);
417 }
418
419
420 /**
421  * Test if ICMP server is working
422  *
423  * @param ah auto setup context
424  */
425 static void
426 test_icmp_server (struct GNUNET_NAT_AutoHandle *ah)
427 {
428   int hns;
429   char *tmp;
430   char *binary;
431
432   tmp = NULL;
433   binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-server");
434   hns =
435       ((GNUNET_OK ==
436         GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS",
437                                                &tmp)) && (0 < strlen (tmp)) &&
438        (GNUNET_YES ==
439         GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")) &&
440        (GNUNET_YES ==
441         GNUNET_OS_check_helper_binary (binary, GNUNET_YES, "-d 127.0.0.1" ))); // use localhost as source for that one udp-port, ok for testing
442   GNUNET_free_non_null (tmp);
443   GNUNET_free (binary);
444   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
445               (hns)
446               ? _("gnunet-helper-nat-server found, testing it\n")
447               : _("No working gnunet-helper-nat-server found\n"));
448   if (hns)
449     ah->task = GNUNET_SCHEDULER_add_now (&reversal_test, ah);
450   else
451     next_phase (ah);
452 }
453
454
455 /**
456  * Test if ICMP client is working
457  *
458  * @param ah auto setup context
459  */
460 static void
461 test_icmp_client (struct GNUNET_NAT_AutoHandle *ah)
462 {
463   int hnc;
464   char *tmp;
465   char *binary;
466
467   tmp = NULL;
468   binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-client");
469   hnc =
470       ((GNUNET_OK ==
471         GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS",
472                                                &tmp)) && (0 < strlen (tmp)) &&
473        (GNUNET_YES !=
474         GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")) &&
475        (GNUNET_YES ==
476         GNUNET_OS_check_helper_binary (binary, GNUNET_YES, "-d 127.0.0.1 127.0.0.2 42"))); // none of these parameters are actually used in privilege testing mode
477   GNUNET_free_non_null (tmp);
478   GNUNET_free (binary);
479   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
480               (hnc)
481               ? _("gnunet-helper-nat-client found, enabling it\n")
482               : _("gnunet-helper-nat-client not found or behind NAT, disabling it\n"));
483   next_phase (ah);
484 }
485
486
487 /**
488  * Run the next phase of the auto test.
489  */
490 static void
491 next_phase (struct GNUNET_NAT_AutoHandle *ah)
492 {
493   struct GNUNET_CONFIGURATION_Handle *diff;
494
495   ah->phase++;
496   switch (ah->phase)
497   {
498   case AUTO_INIT:
499     GNUNET_assert (0);
500     break;
501   case AUTO_ONLINE:
502     test_online (ah);
503     break;
504   case AUTO_EXTERNAL_IP:
505     test_external_ip (ah);
506     break;
507   case AUTO_LOCAL_IP:
508     test_local_ip (ah);
509     break;
510   case AUTO_NAT_PUNCHED:
511     test_nat_punched (ah);
512     break;
513   case AUTO_UPNPC:
514     test_upnpc (ah);
515     break;
516   case AUTO_ICMP_SERVER:
517     test_icmp_server (ah);
518     break;
519   case AUTO_ICMP_CLIENT:
520     test_icmp_client (ah);
521     break;
522   case AUTO_DONE:
523     diff = GNUNET_CONFIGURATION_get_diff (ah->initial_cfg,
524                                           ah->cfg);
525     ah->fin_cb (ah->fin_cb_cls,
526                 diff,
527                 GNUNET_NAT_ERROR_SUCCESS);
528     GNUNET_CONFIGURATION_destroy (diff);
529     GNUNET_NAT_autoconfig_cancel (ah);
530     return;
531   }
532 }
533
534
535 /**
536  * Start auto-configuration routine.  The resolver service should
537  * be available when this function is called.
538  *
539  * @param cfg initial configuration
540  * @param cb function to call with autoconfiguration result
541  * @param cb_cls closure for @a cb
542  * @return handle to cancel operation
543  */
544 struct GNUNET_NAT_AutoHandle *
545 GNUNET_NAT_autoconfig_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
546                              GNUNET_NAT_AutoResultCallback cb,
547                              void *cb_cls)
548 {
549   struct GNUNET_NAT_AutoHandle *ah;
550
551   ah = GNUNET_new (struct GNUNET_NAT_AutoHandle);
552   ah->fin_cb = cb;
553   ah->fin_cb_cls = cb_cls;
554   ah->cfg = GNUNET_CONFIGURATION_dup (cfg);
555   ah->initial_cfg = GNUNET_CONFIGURATION_dup (cfg);
556
557   /* never use loopback addresses if user wanted autoconfiguration */
558   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat",
559                                          "USE_LOCALADDR",
560                                          "NO");
561   next_phase (ah);
562   return ah;
563 }
564
565
566 /**
567  * Abort autoconfiguration.
568  *
569  * @param ah handle for operation to abort
570  */
571 void
572 GNUNET_NAT_autoconfig_cancel (struct GNUNET_NAT_AutoHandle *ah)
573 {
574   if (NULL != ah->tst)
575   {
576     GNUNET_NAT_test_stop (ah->tst);
577     ah->tst = NULL;
578   }
579   if (NULL != ah->eh)
580   {
581     GNUNET_NAT_mini_get_external_ipv4_cancel (ah->eh);
582     ah->eh = NULL;
583   }
584   if (GNUNET_SCHEDULER_NO_TASK != ah->task)
585   {
586     GNUNET_SCHEDULER_cancel (ah->task);
587     ah->task = GNUNET_SCHEDULER_NO_TASK;
588   }
589   GNUNET_CONFIGURATION_destroy (ah->cfg);
590   GNUNET_CONFIGURATION_destroy (ah->initial_cfg);
591   GNUNET_free (ah);
592 }
593
594
595 /* end of nat_auto.c */