-indent, doxygen
[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    * Error code for better debugging and user feedback
146    */
147   enum GNUNET_NAT_FailureCode ret;
148 };
149
150
151 /**
152  * Run the next phase of the auto test.
153  *
154  * @param ah auto test handle
155  */
156 static void
157 next_phase (struct GNUNET_NAT_AutoHandle *ah);
158
159
160 /**
161  * Function called if NAT failed to confirm success.
162  * Clean up and update GUI (with failure).
163  *
164  * @param cls closure with setup context
165  * @param tc scheduler callback
166  */
167 static void
168 fail_timeout (void *cls,
169               const struct GNUNET_SCHEDULER_TaskContext *tc)
170 {
171   struct GNUNET_NAT_AutoHandle *ah = cls;
172
173   ah->ret = GNUNET_NAT_ERROR_NAT_TEST_TIMEOUT; 
174   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
175               _("NAT traversal with ICMP Server timed out.\n"));
176   GNUNET_assert (NULL != ah->tst);
177   ah->task = GNUNET_SCHEDULER_NO_TASK;
178   GNUNET_NAT_test_stop (ah->tst);
179   ah->tst = NULL;
180   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat",
181                                          "ENABLE_ICMP_SERVER",
182                                          "NO");
183   next_phase (ah);
184 }
185
186
187 /**
188  * Function called by NAT on success.
189  * Clean up and update GUI (with success).
190  *
191  * @param cls the auto handle
192  * @param success currently always #GNUNET_OK
193  * @param emsg NULL on success, otherwise an error message
194  */
195 static void
196 result_callback (void *cls,
197                  enum GNUNET_NAT_FailureCode ret)
198 {
199   struct GNUNET_NAT_AutoHandle *ah = cls;
200
201   GNUNET_SCHEDULER_cancel (ah->task);
202   ah->task = GNUNET_SCHEDULER_NO_TASK;
203   GNUNET_NAT_test_stop (ah->tst);
204   ah->tst = NULL;
205   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
206               ret
207               ? _("NAT traversal with ICMP Server succeeded.\n")
208               : _("NAT traversal with ICMP Server failed.\n"));
209   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_ICMP_SERVER",
210                                          ret ? "NO" : "YES");
211   next_phase (ah);
212 }
213
214
215 /**
216  * Main function for the connection reversal test.
217  *
218  * @param cls the `struct GNUNET_NAT_AutoHandle`
219  * @param tc scheduler context
220  */
221 static void
222 reversal_test (void *cls,
223                const struct GNUNET_SCHEDULER_TaskContext *tc)
224 {
225   struct GNUNET_NAT_AutoHandle *ah = cls;
226
227   ah->task = GNUNET_SCHEDULER_NO_TASK;
228   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
229               _("Testing connection reversal with ICMP server.\n"));
230   GNUNET_RESOLVER_connect (ah->cfg);
231   ah->tst = GNUNET_NAT_test_start (ah->cfg, GNUNET_YES, 0, 0,
232                                    &result_callback, ah);
233   if (NULL == ah->tst)
234   {
235     ah->ret = GNUNET_NAT_ERROR_NAT_TEST_START_FAILED;
236     next_phase (ah);
237     return;
238   }
239   ah->task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &fail_timeout, ah);
240 }
241
242
243 /**
244  * Test if we are online at all.
245  *
246  * @param ah auto setup context
247  */
248 static void
249 test_online (struct GNUNET_NAT_AutoHandle *ah)
250 {
251   // FIXME: not implemented
252   /*
253    * if (failure)
254    *  ah->ret = GNUNET_NAT_ERROR_NOT_ONLINE;
255    */
256   next_phase (ah);
257 }
258
259
260 /**
261  * Set our external IPv4 address.
262  *
263  * @param cls closure with our setup context
264  * @param addr the address, NULL on errors
265  * @param emsg NULL on success, otherwise an error message
266  */
267 static void
268 set_external_ipv4 (void *cls,
269                    const struct in_addr *addr,
270                    enum GNUNET_NAT_FailureCode ret)
271 {
272   struct GNUNET_NAT_AutoHandle *ah = cls;
273   char buf[INET_ADDRSTRLEN];
274
275   ah->eh = NULL;
276   ah->ret = ret;
277   if (GNUNET_NAT_ERROR_SUCCESS != ret)
278   {
279     next_phase (ah);
280     return;
281   }
282   /* enable 'behind nat' */
283   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
284               _("Detected external IP `%s'\n"),
285               inet_ntop (AF_INET,
286                          addr,
287                          buf,
288                          sizeof (buf)));
289   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "BEHIND_NAT", "YES");
290
291   /* set external IP address */
292   if (NULL == inet_ntop (AF_INET, addr, buf, sizeof (buf)))
293   {
294     GNUNET_break (0);
295     /* actually, this should never happen, as the caller already executed just
296      * this check, but for consistency (eg: future changes in the caller) 
297      * we still need to report this error...
298      */
299     ah->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID;
300     next_phase (ah);
301     return;
302   }
303   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS",
304                                          buf);
305   next_phase (ah);
306 }
307
308
309 /**
310  * Determine our external IPv4 address.
311  *
312  * @param ah auto setup context
313  */
314 static void
315 test_external_ip (struct GNUNET_NAT_AutoHandle *ah)
316 {
317   if (GNUNET_NAT_ERROR_SUCCESS != ah->ret)
318     next_phase (ah);
319   
320   // FIXME: CPS?
321   /* try to detect external IP */
322   ah->eh = GNUNET_NAT_mini_get_external_ipv4 (TIMEOUT,
323                                               &set_external_ipv4, ah);
324 }
325
326
327 /**
328  * Process list of local IP addresses.  Find and set the
329  * one of the default interface.
330  *
331  * @param cls our `struct GNUNET_NAT_AutoHandle`
332  * @param name name of the interface (can be NULL for unknown)
333  * @param isDefault is this presumably the default interface
334  * @param addr address of this interface (can be NULL for unknown or unassigned)
335  * @param broadcast_addr the broadcast address (can be NULL for unknown or unassigned)
336  * @param netmask the network mask (can be NULL for unknown or unassigned))
337  * @param addrlen length of the @a addr and @a broadcast_addr
338  * @return GNUNET_OK to continue iteration, #GNUNET_SYSERR to abort
339  */
340 static int
341 process_if (void *cls,
342       const char *name,
343       int isDefault,
344       const struct sockaddr *addr,
345       const struct sockaddr *broadcast_addr,
346       const struct sockaddr *netmask,
347       socklen_t addrlen)
348 {
349   struct GNUNET_NAT_AutoHandle *ah = cls;
350   const struct sockaddr_in *in;
351   char buf[INET_ADDRSTRLEN];
352
353   if (!isDefault)
354     return GNUNET_OK;
355   if ( (sizeof (struct sockaddr_in6) == addrlen) &&
356        (0 != memcmp (&in6addr_loopback, &((const struct sockaddr_in6 *) addr)->sin6_addr,
357                      sizeof (struct in6_addr))) &&
358        (! IN6_IS_ADDR_LINKLOCAL(&((const struct sockaddr_in6 *) addr)->sin6_addr)) )
359   {
360     ah->have_v6 = GNUNET_YES;
361     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
362                 _("This system has a global IPv6 address, setting IPv6 to supported.\n"));
363     return GNUNET_OK;
364   }
365   if (addrlen != sizeof (struct sockaddr_in))
366     return GNUNET_OK;
367   in = (const struct sockaddr_in *) addr;
368
369   /* set internal IP address */
370   if (NULL == inet_ntop (AF_INET, &in->sin_addr, buf, sizeof (buf)))
371   {
372     GNUNET_break (0);
373     return GNUNET_OK;
374   }
375   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS",
376                                          buf);
377   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
378               _("Detected internal network address `%s'.\n"),
379               buf);
380   ah->ret = GNUNET_NAT_ERROR_SUCCESS;
381   /* no need to continue iteration */
382   return GNUNET_SYSERR;
383 }
384
385
386 /**
387  * Determine our local IP addresses; detect internal IP & IPv6-support
388  *
389  * @param ah auto setup context
390  */
391 static void
392 test_local_ip (struct GNUNET_NAT_AutoHandle *ah)
393 {
394   ah->have_v6 = GNUNET_NO;
395   ah->ret = GNUNET_NAT_ERROR_NO_VALID_IF_IP_COMBO; // reset to success if any of the IFs in below iterator has a valid IP
396   GNUNET_OS_network_interfaces_list (&process_if, ah);
397   
398   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "DISABLEV6",
399                                          (GNUNET_YES == ah->have_v6) ? "NO" : "YES");
400   next_phase (ah);
401 }
402
403
404 /**
405  * Test if NAT has been punched
406  *
407  * @param ah auto setup context
408  */
409 static void
410 test_nat_punched (struct GNUNET_NAT_AutoHandle *ah)
411 {
412   if (GNUNET_NAT_ERROR_SUCCESS != ah->ret)
413     next_phase (ah);
414   
415   // FIXME: not implemented
416   
417   next_phase (ah);
418 }
419
420
421 /**
422  * Test if UPnPC works.
423  *
424  * @param ah auto setup context
425  */
426 static void
427 test_upnpc (struct GNUNET_NAT_AutoHandle *ah)
428 {
429   int have_upnpc;
430
431   if (GNUNET_NAT_ERROR_SUCCESS != ah->ret)
432     next_phase (ah);
433   
434   /* test if upnpc is available */
435   have_upnpc = (GNUNET_SYSERR !=
436                 GNUNET_OS_check_helper_binary ("upnpc", GNUNET_NO, NULL));
437   /* FIXME: test if upnpc is actually working, that is, if transports
438      start to work once we use UPnP */
439   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
440               (have_upnpc)
441               ? _("upnpc found, enabling its use\n")
442               : _("upnpc not found\n"));
443   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat", "ENABLE_UPNP",
444                                          (GNUNET_YES == have_upnpc) ? "YES" : "NO");
445   next_phase (ah);
446 }
447
448
449 /**
450  * Test if ICMP server is working
451  *
452  * @param ah auto setup context
453  */
454 static void
455 test_icmp_server (struct GNUNET_NAT_AutoHandle *ah)
456 {
457   int ext_ip;
458   int nated;
459   int binary;
460   char *tmp;
461   char *helper;
462   ext_ip = GNUNET_NO;
463   nated = GNUNET_NO;
464   binary = GNUNET_NO;
465   
466   tmp = NULL;
467   helper = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-server");
468   if ((GNUNET_OK ==
469         GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "EXTERNAL_ADDRESS",
470                                                &tmp)) && (0 < strlen (tmp))){
471     ext_ip = GNUNET_OK;
472     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("test_icmp_server not possible, as we have no public IPv4 address\n"));
473   }
474   else
475     goto err;
476     
477   if (GNUNET_YES ==
478         GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")){
479     nated = GNUNET_YES;
480     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("test_icmp_server not possible, as we are not behind NAT\n"));
481   }
482   else
483     goto err;
484   
485   if (GNUNET_YES ==
486         GNUNET_OS_check_helper_binary (helper, GNUNET_YES, "-d 127.0.0.1" )){
487     binary = GNUNET_OK; // use localhost as source for that one udp-port, ok for testing
488     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("No working gnunet-helper-nat-server found\n"));
489   }
490 err:
491   GNUNET_free_non_null (tmp);
492   GNUNET_free (helper);
493
494   if (GNUNET_OK == ext_ip && GNUNET_YES == nated && GNUNET_OK == binary)
495     ah->task = GNUNET_SCHEDULER_add_now (&reversal_test, ah);
496   else
497     next_phase (ah);
498 }
499
500
501 /**
502  * Test if ICMP client is working
503  *
504  * @param ah auto setup context
505  */
506 static void
507 test_icmp_client (struct GNUNET_NAT_AutoHandle *ah)
508 {
509   int ext_ip;
510   int nated;
511   int binary;
512   char *tmp;
513   char *helper;
514   
515   ext_ip = GNUNET_NO;
516   nated = GNUNET_NO;
517   binary = GNUNET_NO;
518
519   tmp = NULL;
520   helper = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-nat-client");
521   if ((GNUNET_OK ==
522         GNUNET_CONFIGURATION_get_value_string (ah->cfg, "nat", "INTERNAL_ADDRESS",
523                                                &tmp)) && (0 < strlen (tmp)))
524   {
525     ext_ip = GNUNET_OK;
526     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("test_icmp_client not possible, as we have no internal IPv4 address\n"));
527   }
528   else
529     goto err;
530   
531   if (GNUNET_YES !=
532       GNUNET_CONFIGURATION_get_value_yesno (ah->cfg, "nat", "BEHIND_NAT")){
533         nated = GNUNET_YES;
534     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("test_icmp_server not possible, as we are not behind NAT\n"));
535   }
536   else
537     goto err;
538   
539   if (GNUNET_YES ==
540       GNUNET_OS_check_helper_binary (helper, GNUNET_YES, "-d 127.0.0.1 127.0.0.2 42")){
541           // none of these parameters are actually used in privilege testing mode
542     binary = GNUNET_OK;
543     GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("No working gnunet-helper-nat-server found\n"));
544   }
545 err:
546   GNUNET_free_non_null (tmp);
547   GNUNET_free (helper);
548
549   next_phase (ah);
550 }
551
552
553 /**
554  * Run the next phase of the auto test.
555  */
556 static void
557 next_phase (struct GNUNET_NAT_AutoHandle *ah)
558 {
559   struct GNUNET_CONFIGURATION_Handle *diff;
560
561   ah->phase++;
562   switch (ah->phase)
563   {
564   case AUTO_INIT:
565     GNUNET_assert (0);
566     break;
567   case AUTO_ONLINE:
568     test_online (ah);
569     break;
570   case AUTO_EXTERNAL_IP:
571     test_external_ip (ah);
572     break;
573   case AUTO_LOCAL_IP:
574     test_local_ip (ah);
575     break;
576   case AUTO_NAT_PUNCHED:
577     test_nat_punched (ah);
578     break;
579   case AUTO_UPNPC:
580     test_upnpc (ah);
581     break;
582   case AUTO_ICMP_SERVER:
583     test_icmp_server (ah);
584     break;
585   case AUTO_ICMP_CLIENT:
586     test_icmp_client (ah);
587     break;
588   case AUTO_DONE:
589     diff = GNUNET_CONFIGURATION_get_diff (ah->initial_cfg,
590                                           ah->cfg);
591     ah->fin_cb (ah->fin_cb_cls,
592                 diff,
593                 ah->ret);
594     GNUNET_CONFIGURATION_destroy (diff);
595     GNUNET_NAT_autoconfig_cancel (ah);
596     return;
597   }
598 }
599
600
601 /**
602  * Start auto-configuration routine.  The resolver service should
603  * be available when this function is called.
604  *
605  * @param cfg initial configuration
606  * @param cb function to call with autoconfiguration result
607  * @param cb_cls closure for @a cb
608  * @return handle to cancel operation
609  */
610 struct GNUNET_NAT_AutoHandle *
611 GNUNET_NAT_autoconfig_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
612                              GNUNET_NAT_AutoResultCallback cb,
613                              void *cb_cls)
614 {
615   struct GNUNET_NAT_AutoHandle *ah;
616
617   ah = GNUNET_new (struct GNUNET_NAT_AutoHandle);
618   ah->fin_cb = cb;
619   ah->fin_cb_cls = cb_cls;
620   ah->ret = GNUNET_NAT_ERROR_SUCCESS;
621   ah->cfg = GNUNET_CONFIGURATION_dup (cfg);
622   ah->initial_cfg = GNUNET_CONFIGURATION_dup (cfg);
623
624   /* never use loopback addresses if user wanted autoconfiguration */
625   GNUNET_CONFIGURATION_set_value_string (ah->cfg, "nat",
626                                          "USE_LOCALADDR",
627                                          "NO");
628   next_phase (ah);
629   return ah;
630 }
631
632
633 /**
634  * Abort autoconfiguration.
635  *
636  * @param ah handle for operation to abort
637  */
638 void
639 GNUNET_NAT_autoconfig_cancel (struct GNUNET_NAT_AutoHandle *ah)
640 {
641   if (NULL != ah->tst)
642   {
643     GNUNET_NAT_test_stop (ah->tst);
644     ah->tst = NULL;
645   }
646   if (NULL != ah->eh)
647   {
648     GNUNET_NAT_mini_get_external_ipv4_cancel (ah->eh);
649     ah->eh = NULL;
650   }
651   if (GNUNET_SCHEDULER_NO_TASK != ah->task)
652   {
653     GNUNET_SCHEDULER_cancel (ah->task);
654     ah->task = GNUNET_SCHEDULER_NO_TASK;
655   }
656   GNUNET_CONFIGURATION_destroy (ah->cfg);
657   GNUNET_CONFIGURATION_destroy (ah->initial_cfg);
658   GNUNET_free (ah);
659 }
660
661
662 /* end of nat_auto.c */