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