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