added multiple iteration support for averaging
[oweals/gnunet.git] / src / nat / nat_mini.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011-2014 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_mini.c
23  * @brief functions for interaction with miniupnp; tested with miniupnpc 1.5
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_nat_lib.h"
29 #include "nat.h"
30
31 #define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__)
32
33 /**
34  * How long do we give upnpc to create a mapping?
35  */
36 #define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
37
38 /**
39  * How long do we give upnpc to remove a mapping?
40  */
41 #define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
42
43 /**
44  * How often do we check for changes in the mapping?
45  */
46 #define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
47
48
49
50 /**
51  * Opaque handle to cancel "GNUNET_NAT_mini_get_external_ipv4" operation.
52  */
53 struct GNUNET_NAT_ExternalHandle
54 {
55
56   /**
57    * Function to call with the result.
58    */
59   GNUNET_NAT_IPCallback cb;
60
61   /**
62    * Closure for @e cb.
63    */
64   void *cb_cls;
65
66   /**
67    * Read task.
68    */
69   GNUNET_SCHEDULER_TaskIdentifier task;
70
71   /**
72    * Handle to 'external-ip' process.
73    */
74   struct GNUNET_OS_Process *eip;
75
76   /**
77    * Handle to stdout pipe of 'external-ip'.
78    */
79   struct GNUNET_DISK_PipeHandle *opipe;
80
81   /**
82    * Read handle of @e opipe.
83    */
84   const struct GNUNET_DISK_FileHandle *r;
85
86   /**
87    * When should this operation time out?
88    */
89   struct GNUNET_TIME_Absolute timeout;
90
91   /**
92    * Number of bytes in 'buf' that are valid.
93    */
94   size_t off;
95
96   /**
97    * Destination of our read operation (output of 'external-ip').
98    */
99   char buf[17];
100
101 };
102
103
104 /**
105  * Read the output of 'external-ip' into buf.  When complete, parse the
106  * address and call our callback.
107  *
108  * @param cls the `struct GNUNET_NAT_ExternalHandle`
109  * @param tc scheduler context
110  */
111 static void
112 read_external_ipv4 (void *cls,
113                     const struct GNUNET_SCHEDULER_TaskContext *tc)
114 {
115   struct GNUNET_NAT_ExternalHandle *eh = cls;
116   ssize_t ret;
117   struct in_addr addr;
118   int iret;
119
120   eh->task = GNUNET_SCHEDULER_NO_TASK;
121   if (GNUNET_YES == GNUNET_NETWORK_fdset_handle_isset (tc->read_ready, eh->r))
122     ret =
123         GNUNET_DISK_file_read (eh->r, &eh->buf[eh->off],
124                                sizeof (eh->buf) - eh->off);
125   else
126     ret = -1;                   /* error reading, timeout, etc. */
127   if (ret > 0)
128   {
129     /* try to read more */
130     eh->off += ret;
131     eh->task =
132         GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_absolute_get_remaining
133                                         (eh->timeout), eh->r,
134                                         &read_external_ipv4, eh);
135     return;
136   }
137   iret = GNUNET_NO;
138   if ((eh->off > 7) && (eh->buf[eh->off - 1] == '\n'))
139   {
140     eh->buf[eh->off - 1] = '\0';
141     if (1 == inet_pton (AF_INET, eh->buf, &addr))
142     {
143       if (0 == addr.s_addr)
144         iret = GNUNET_NO;       /* got 0.0.0.0 */
145       else
146         iret = GNUNET_OK;
147     }
148   }
149   eh->cb (eh->cb_cls,
150           (GNUNET_OK == iret)
151           ? &addr :
152           NULL,
153           (GNUNET_OK == iret)
154           ? NULL
155           : _("no valid address was returned by `external-ip'"));
156   GNUNET_NAT_mini_get_external_ipv4_cancel (eh);
157 }
158
159
160 /**
161  * (Asynchronously) signal error invoking "external-ip" to client.
162  *
163  * @param cls the `struct GNUNET_NAT_ExternalHandle` (freed)
164  * @param tc scheduler context
165  */
166 static void
167 signal_external_ip_error (void *cls,
168                           const struct GNUNET_SCHEDULER_TaskContext *tc)
169 {
170   struct GNUNET_NAT_ExternalHandle *eh = cls;
171
172   eh->task = GNUNET_SCHEDULER_NO_TASK;
173   eh->cb (eh->cb_cls,
174           NULL,
175           _("`external-ip' command not found"));
176   GNUNET_free (eh);
177 }
178
179
180 /**
181  * Try to get the external IPv4 address of this peer.
182  *
183  * @param timeout when to fail
184  * @param cb function to call with result
185  * @param cb_cls closure for @a cb
186  * @return handle for cancellation (can only be used until @a cb is called), NULL on error
187  */
188 struct GNUNET_NAT_ExternalHandle *
189 GNUNET_NAT_mini_get_external_ipv4 (struct GNUNET_TIME_Relative timeout,
190                                    GNUNET_NAT_IPCallback cb, void *cb_cls)
191 {
192   struct GNUNET_NAT_ExternalHandle *eh;
193
194   eh = GNUNET_new (struct GNUNET_NAT_ExternalHandle);
195   eh->cb = cb;
196   eh->cb_cls = cb_cls;
197   if (GNUNET_SYSERR ==
198       GNUNET_OS_check_helper_binary ("external-ip", GNUNET_NO, NULL))
199   {
200     LOG (GNUNET_ERROR_TYPE_INFO,
201          _("`external-ip' command not found\n"));
202     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
203                                          eh);
204     return eh;
205   }
206   LOG (GNUNET_ERROR_TYPE_DEBUG,
207        "Running `external-ip' to determine our external IP\n");
208   eh->opipe = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
209   if (NULL == eh->opipe)
210   {
211     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
212                                          eh);
213     return eh;
214   }
215   eh->eip =
216     GNUNET_OS_start_process (GNUNET_NO, 0, NULL, eh->opipe,
217                              "external-ip", "external-ip",
218                                NULL);
219   if (NULL == eh->eip)
220   {
221     GNUNET_DISK_pipe_close (eh->opipe);
222     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
223                                          eh);
224     return eh;
225   }
226   GNUNET_DISK_pipe_close_end (eh->opipe, GNUNET_DISK_PIPE_END_WRITE);
227   eh->timeout = GNUNET_TIME_relative_to_absolute (timeout);
228   eh->r = GNUNET_DISK_pipe_handle (eh->opipe, GNUNET_DISK_PIPE_END_READ);
229   eh->task =
230       GNUNET_SCHEDULER_add_read_file (timeout,
231                                       eh->r,
232                                       &read_external_ipv4, eh);
233   return eh;
234 }
235
236
237 /**
238  * Cancel operation.
239  *
240  * @param eh operation to cancel
241  */
242 void
243 GNUNET_NAT_mini_get_external_ipv4_cancel (struct GNUNET_NAT_ExternalHandle *eh)
244 {
245   if (NULL != eh->eip)
246   {
247     (void) GNUNET_OS_process_kill (eh->eip, SIGKILL);
248     GNUNET_OS_process_destroy (eh->eip);
249   }
250   if (NULL != eh->opipe)
251     GNUNET_DISK_pipe_close (eh->opipe);
252   if (GNUNET_SCHEDULER_NO_TASK != eh->task)
253     GNUNET_SCHEDULER_cancel (eh->task);
254   GNUNET_free (eh);
255 }
256
257
258 /**
259  * Handle to a mapping created with upnpc.
260  */
261 struct GNUNET_NAT_MiniHandle
262 {
263
264   /**
265    * Function to call on mapping changes.
266    */
267   GNUNET_NAT_MiniAddressCallback ac;
268
269   /**
270    * Closure for @e ac.
271    */
272   void *ac_cls;
273
274   /**
275    * Command used to install the map.
276    */
277   struct GNUNET_OS_CommandHandle *map_cmd;
278
279   /**
280    * Command used to refresh our map information.
281    */
282   struct GNUNET_OS_CommandHandle *refresh_cmd;
283
284   /**
285    * Command used to remove the mapping.
286    */
287   struct GNUNET_OS_CommandHandle *unmap_cmd;
288
289   /**
290    * Our current external mapping (if we have one).
291    */
292   struct sockaddr_in current_addr;
293
294   /**
295    * We check the mapping periodically to see if it
296    * still works.  This task triggers the check.
297    */
298   GNUNET_SCHEDULER_TaskIdentifier refresh_task;
299
300   /**
301    * Are we mapping TCP or UDP?
302    */
303   int is_tcp;
304
305   /**
306    * Did we succeed with creating a mapping?
307    */
308   int did_map;
309
310   /**
311    * Did we find our mapping during refresh scan?
312    */
313   int found;
314
315   /**
316    * Which port are we mapping?
317    */
318   uint16_t port;
319
320 };
321
322
323 /**
324  * Run "upnpc -l" to find out if our mapping changed.
325  *
326  * @param cls the `struct GNUNET_NAT_MiniHandle`
327  * @param tc scheduler context
328  */
329 static void
330 do_refresh (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
331
332
333 /**
334  * Process the output from the "upnpc -r" command.
335  *
336  * @param cls the `struct GNUNET_NAT_MiniHandle`
337  * @param line line of output, NULL at the end
338  */
339 static void
340 process_map_output (void *cls, const char *line);
341
342
343 /**
344  * Run "upnpc -r" to map our internal port.
345  *
346  * @param mini our handle
347  */
348 static void
349 run_upnpc_r (struct GNUNET_NAT_MiniHandle *mini)
350 {
351   char pstr[6];
352
353   GNUNET_snprintf (pstr,
354                    sizeof (pstr),
355                    "%u",
356                    (unsigned int) mini->port);
357   mini->map_cmd =
358     GNUNET_OS_command_run (&process_map_output, mini, MAP_TIMEOUT,
359                            "upnpc", "upnpc", "-r", pstr,
360                            mini->is_tcp ? "tcp" : "udp", NULL);
361   if (NULL == mini->map_cmd)
362   {
363     mini->ac (mini->ac_cls,
364               GNUNET_SYSERR,
365               NULL, 0,
366               _("Failed to run `upnpc` command"));
367     return;
368   }
369 }
370
371
372 /**
373  * Process the output from "upnpc -l" to see if our
374  * external mapping changed.  If so, do the notifications.
375  *
376  * @param cls the `struct GNUNET_NAT_MiniHandle`
377  * @param line line of output, NULL at the end
378  */
379 static void
380 process_refresh_output (void *cls, const char *line)
381 {
382   struct GNUNET_NAT_MiniHandle *mini = cls;
383   char pstr[9];
384   const char *s;
385   unsigned int nport;
386   struct in_addr exip;
387
388   if (NULL == line)
389   {
390     GNUNET_OS_command_stop (mini->refresh_cmd);
391     mini->refresh_cmd = NULL;
392     if (GNUNET_NO == mini->found)
393     {
394       /* mapping disappeared, try to re-create */
395       if (GNUNET_YES == mini->did_map)
396       {
397         mini->ac (mini->ac_cls,
398                   GNUNET_NO,
399                   (const struct sockaddr *) &mini->current_addr,
400                   sizeof (mini->current_addr),
401                   NULL);
402         mini->did_map = GNUNET_NO;
403       }
404       run_upnpc_r (mini);
405     }
406     return;
407   }
408   if (!mini->did_map)
409     return;                     /* never mapped, won't find our mapping anyway */
410
411   /* we're looking for output of the form:
412    * "ExternalIPAddress = 12.134.41.124" */
413
414   s = strstr (line, "ExternalIPAddress = ");
415   if (NULL != s)
416   {
417     s += strlen ("ExternalIPAddress = ");
418     if (1 != inet_pton (AF_INET, s, &exip))
419       return;                   /* skip */
420     if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
421       return;                   /* no change */
422     /* update mapping */
423     mini->ac (mini->ac_cls, GNUNET_NO,
424               (const struct sockaddr *) &mini->current_addr,
425               sizeof (mini->current_addr),
426               NULL);
427     mini->current_addr.sin_addr = exip;
428     mini->ac (mini->ac_cls, GNUNET_YES,
429               (const struct sockaddr *) &mini->current_addr,
430               sizeof (mini->current_addr),
431               NULL);
432     return;
433   }
434   /*
435    * we're looking for output of the form:
436    *
437    * "0 TCP  3000->192.168.2.150:3000  'libminiupnpc' ''"
438    * "1 UDP  3001->192.168.2.150:3001  'libminiupnpc' ''"
439    *
440    * the pattern we look for is:
441    *
442    * "%s TCP  PORT->STRING:OURPORT *" or
443    * "%s UDP  PORT->STRING:OURPORT *"
444    */
445   GNUNET_snprintf (pstr, sizeof (pstr), ":%u ", mini->port);
446   if (NULL == (s = strstr (line, "->")))
447     return;                     /* skip */
448   if (NULL == strstr (s, pstr))
449     return;                     /* skip */
450   if (1 !=
451       SSCANF (line,
452               (mini->is_tcp) ? "%*u TCP  %u->%*s:%*u %*s" :
453               "%*u UDP  %u->%*s:%*u %*s", &nport))
454     return;                     /* skip */
455   mini->found = GNUNET_YES;
456   if (nport == ntohs (mini->current_addr.sin_port))
457     return;                     /* no change */
458
459   /* external port changed, update mapping */
460   mini->ac (mini->ac_cls, GNUNET_NO,
461             (const struct sockaddr *) &mini->current_addr,
462             sizeof (mini->current_addr),
463             NULL);
464   mini->current_addr.sin_port = htons ((uint16_t) nport);
465   mini->ac (mini->ac_cls, GNUNET_YES,
466             (const struct sockaddr *) &mini->current_addr,
467             sizeof (mini->current_addr),
468             NULL);
469 }
470
471
472 /**
473  * Run "upnpc -l" to find out if our mapping changed.
474  *
475  * @param cls the 'struct GNUNET_NAT_MiniHandle'
476  * @param tc scheduler context
477  */
478 static void
479 do_refresh (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
480 {
481   struct GNUNET_NAT_MiniHandle *mini = cls;
482   int ac;
483
484   mini->refresh_task =
485     GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
486                                   &do_refresh, mini);
487   LOG (GNUNET_ERROR_TYPE_DEBUG,
488        "Running `upnpc' to check if our mapping still exists\n");
489   mini->found = GNUNET_NO;
490   ac = GNUNET_NO;
491   if (NULL != mini->map_cmd)
492   {
493     /* took way too long, abort it! */
494     GNUNET_OS_command_stop (mini->map_cmd);
495     mini->map_cmd = NULL;
496     ac = GNUNET_YES;
497   }
498   if (NULL != mini->refresh_cmd)
499   {
500     /* took way too long, abort it! */
501     GNUNET_OS_command_stop (mini->refresh_cmd);
502     mini->refresh_cmd = NULL;
503     ac = GNUNET_YES;
504   }
505   mini->refresh_cmd =
506       GNUNET_OS_command_run (&process_refresh_output, mini, MAP_TIMEOUT,
507                              "upnpc", "upnpc", "-l", NULL);
508   if (GNUNET_YES == ac)
509     mini->ac (mini->ac_cls,
510               GNUNET_SYSERR,
511               NULL, 0,
512               _("`upnpc' command took too long, process killed"));
513 }
514
515
516 /**
517  * Process the output from the 'upnpc -r' command.
518  *
519  * @param cls the `struct GNUNET_NAT_MiniHandle`
520  * @param line line of output, NULL at the end
521  */
522 static void
523 process_map_output (void *cls,
524                     const char *line)
525 {
526   struct GNUNET_NAT_MiniHandle *mini = cls;
527   const char *ipaddr;
528   char *ipa;
529   const char *pstr;
530   unsigned int port;
531
532   if (NULL == line)
533   {
534     GNUNET_OS_command_stop (mini->map_cmd);
535     mini->map_cmd = NULL;
536     if (GNUNET_YES != mini->did_map)
537       mini->ac (mini->ac_cls,
538                 GNUNET_SYSERR,
539                 NULL, 0,
540                 _("`upnpc' command failed to establish port mapping"));
541     if (GNUNET_SCHEDULER_NO_TASK == mini->refresh_task)
542       mini->refresh_task =
543         GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, &do_refresh, mini);
544     return;
545   }
546   /*
547    * The upnpc output we're after looks like this:
548    *
549    * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
550    */
551   if ((NULL == (ipaddr = strstr (line, " "))) ||
552       (NULL == (pstr = strstr (ipaddr, ":"))) ||
553       (1 != SSCANF (pstr + 1, "%u", &port)))
554   {
555     return;                     /* skip line */
556   }
557   ipa = GNUNET_strdup (ipaddr + 1);
558   strstr (ipa, ":")[0] = '\0';
559   if (1 != inet_pton (AF_INET, ipa, &mini->current_addr.sin_addr))
560   {
561     GNUNET_free (ipa);
562     return;                     /* skip line */
563   }
564   GNUNET_free (ipa);
565
566   mini->current_addr.sin_port = htons (port);
567   mini->current_addr.sin_family = AF_INET;
568 #if HAVE_SOCKADDR_IN_SIN_LEN
569   mini->current_addr.sin_len = sizeof (struct sockaddr_in);
570 #endif
571   mini->did_map = GNUNET_YES;
572   mini->ac (mini->ac_cls, GNUNET_YES,
573             (const struct sockaddr *) &mini->current_addr,
574             sizeof (mini->current_addr),
575             NULL);
576 }
577
578
579 /**
580  * Start mapping the given port using (mini)upnpc.  This function
581  * should typically not be used directly (it is used within the
582  * general-purpose #GNUNET_NAT_register() code).  However, it can be
583  * used if specifically UPnP-based NAT traversal is to be used or
584  * tested.
585  *
586  * @param port port to map
587  * @param is_tcp #GNUNET_YES to map TCP, #GNUNET_NO for UDP
588  * @param ac function to call with mapping result
589  * @param ac_cls closure for @a ac
590  * @return NULL on error (no 'upnpc' installed)
591  */
592 struct GNUNET_NAT_MiniHandle *
593 GNUNET_NAT_mini_map_start (uint16_t port,
594                            int is_tcp,
595                            GNUNET_NAT_MiniAddressCallback ac,
596                            void *ac_cls)
597 {
598   struct GNUNET_NAT_MiniHandle *ret;
599
600   if (GNUNET_SYSERR ==
601       GNUNET_OS_check_helper_binary ("upnpc", GNUNET_NO, NULL))
602   {
603     LOG (GNUNET_ERROR_TYPE_INFO,
604          _("`upnpc' command not found\n"));
605     ac (ac_cls,
606         GNUNET_SYSERR,
607         NULL, 0,
608         _("`upnpc` command not found"));
609     return NULL;
610   }
611   LOG (GNUNET_ERROR_TYPE_DEBUG,
612        "Running `upnpc' to install mapping\n");
613   ret = GNUNET_new (struct GNUNET_NAT_MiniHandle);
614   ret->ac = ac;
615   ret->ac_cls = ac_cls;
616   ret->is_tcp = is_tcp;
617   ret->port = port;
618   ret->refresh_task =
619     GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, &do_refresh, ret);
620   run_upnpc_r (ret);
621   return ret;
622 }
623
624
625 /**
626  * Process output from our 'unmap' command.
627  *
628  * @param cls the 'struct GNUNET_NAT_MiniHandle'
629  * @param line line of output, NULL at the end
630  */
631 static void
632 process_unmap_output (void *cls, const char *line)
633 {
634   struct GNUNET_NAT_MiniHandle *mini = cls;
635
636   if (NULL == line)
637   {
638     LOG (GNUNET_ERROR_TYPE_DEBUG,
639          "UPnP unmap done\n");
640     GNUNET_OS_command_stop (mini->unmap_cmd);
641     mini->unmap_cmd = NULL;
642     GNUNET_free (mini);
643     return;
644   }
645   /* we don't really care about the output... */
646 }
647
648
649 /**
650  * Remove a mapping created with (mini)upnpc.  Calling
651  * this function will give 'upnpc' 1s to remove tha mapping,
652  * so while this function is non-blocking, a task will be
653  * left with the scheduler for up to 1s past this call.
654  *
655  * @param mini the handle
656  */
657 void
658 GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini)
659 {
660   char pstr[6];
661
662   if (GNUNET_SCHEDULER_NO_TASK != mini->refresh_task)
663   {
664     GNUNET_SCHEDULER_cancel (mini->refresh_task);
665     mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
666   }
667   if (NULL != mini->refresh_cmd)
668   {
669     GNUNET_OS_command_stop (mini->refresh_cmd);
670     mini->refresh_cmd = NULL;
671   }
672   if (GNUNET_NO == mini->did_map)
673   {
674     if (NULL != mini->map_cmd)
675     {
676       GNUNET_OS_command_stop (mini->map_cmd);
677       mini->map_cmd = NULL;
678     }
679     GNUNET_free (mini);
680     return;
681   }
682   mini->ac (mini->ac_cls, GNUNET_NO,
683             (const struct sockaddr *) &mini->current_addr,
684             sizeof (mini->current_addr),
685             NULL);
686   /* Note: oddly enough, deletion uses the external port whereas
687    * addition uses the internal port; this rarely matters since they
688    * often are the same, but it might... */
689   GNUNET_snprintf (pstr,
690                    sizeof (pstr),
691                    "%u",
692                    (unsigned int) ntohs (mini->current_addr.sin_port));
693   LOG (GNUNET_ERROR_TYPE_DEBUG,
694        "Unmapping port %u with UPnP\n",
695        ntohs (mini->current_addr.sin_port));
696   mini->unmap_cmd =
697       GNUNET_OS_command_run (&process_unmap_output, mini, UNMAP_TIMEOUT,
698                              "upnpc", "upnpc", "-d", pstr,
699                              mini->is_tcp ? "tcp" : "udp", NULL);
700 }
701
702
703 /* end of nat_mini.c */