indentation
[oweals/gnunet.git] / src / nat / nat_mini.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 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 DEBUG_NAT GNUNET_NO
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 '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 '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, const struct GNUNET_SCHEDULER_TaskContext *tc)
113 {
114   struct GNUNET_NAT_ExternalHandle *eh = cls;
115   ssize_t ret;
116   struct in_addr addr;
117   int iret;
118
119   eh->task = GNUNET_SCHEDULER_NO_TASK;
120   if (GNUNET_YES == GNUNET_NETWORK_fdset_handle_isset (tc->read_ready, eh->r))
121     ret = GNUNET_DISK_file_read (eh->r,
122                                  &eh->buf[eh->off], sizeof (eh->buf) - eh->off);
123   else
124     ret = -1;                   /* error reading, timeout, etc. */
125   if (ret > 0)
126   {
127     /* try to read more */
128     eh->off += ret;
129     eh->task =
130         GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_absolute_get_remaining
131                                         (eh->timeout), eh->r,
132                                         &read_external_ipv4, eh);
133     return;
134   }
135   iret = GNUNET_NO;
136   if ((eh->off > 7) && (eh->buf[eh->off - 1] == '\n'))
137   {
138     eh->buf[eh->off - 1] = '\0';
139     if (1 == inet_pton (AF_INET, eh->buf, &addr))
140     {
141       if (addr.s_addr == 0)
142         iret = GNUNET_NO;       /* got 0.0.0.0 */
143       else
144         iret = GNUNET_OK;
145     }
146   }
147   eh->cb (eh->cb_cls, (iret == GNUNET_OK) ? &addr : NULL);
148   GNUNET_NAT_mini_get_external_ipv4_cancel (eh);
149 }
150
151
152 /**
153  * Try to get the external IPv4 address of this peer.
154  *
155  * @param timeout when to fail
156  * @param cb function to call with result
157  * @param cb_cls closure for 'cb'
158  * @return handle for cancellation (can only be used until 'cb' is called), NULL on error
159  */
160 struct GNUNET_NAT_ExternalHandle *
161 GNUNET_NAT_mini_get_external_ipv4 (struct GNUNET_TIME_Relative timeout,
162                                    GNUNET_NAT_IPCallback cb, void *cb_cls)
163 {
164   struct GNUNET_NAT_ExternalHandle *eh;
165
166   eh = GNUNET_malloc (sizeof (struct GNUNET_NAT_ExternalHandle));
167   eh->cb = cb;
168   eh->cb_cls = cb_cls;
169   eh->opipe = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_NO, GNUNET_YES);
170   if (NULL == eh->opipe)
171   {
172     GNUNET_free (eh);
173     return NULL;
174   }
175   eh->eip = GNUNET_OS_start_process (NULL,
176                                      eh->opipe,
177                                      "external-ip", "external-ip", NULL);
178   if (NULL == eh->eip)
179   {
180     GNUNET_DISK_pipe_close (eh->opipe);
181     GNUNET_free (eh);
182     return NULL;
183   }
184   GNUNET_DISK_pipe_close_end (eh->opipe, GNUNET_DISK_PIPE_END_WRITE);
185   eh->timeout = GNUNET_TIME_relative_to_absolute (timeout);
186   eh->r = GNUNET_DISK_pipe_handle (eh->opipe, GNUNET_DISK_PIPE_END_READ);
187   eh->task = GNUNET_SCHEDULER_add_read_file (timeout,
188                                              eh->r, &read_external_ipv4, eh);
189   return eh;
190 }
191
192
193 /**
194  * Cancel operation.
195  *
196  * @param eh operation to cancel
197  */
198 void
199 GNUNET_NAT_mini_get_external_ipv4_cancel (struct GNUNET_NAT_ExternalHandle *eh)
200 {
201   (void) GNUNET_OS_process_kill (eh->eip, SIGKILL);
202   GNUNET_OS_process_close (eh->eip);
203   GNUNET_DISK_pipe_close (eh->opipe);
204   if (GNUNET_SCHEDULER_NO_TASK != eh->task)
205     GNUNET_SCHEDULER_cancel (eh->task);
206   GNUNET_free (eh);
207 }
208
209
210 /**
211  * Handle to a mapping created with upnpc.
212  */
213 struct GNUNET_NAT_MiniHandle
214 {
215
216   /**
217    * Function to call on mapping changes.
218    */
219   GNUNET_NAT_AddressCallback ac;
220
221   /**
222    * Closure for 'ac'.
223    */
224   void *ac_cls;
225
226   /**
227    * Command used to install the map.
228    */
229   struct GNUNET_OS_CommandHandle *map_cmd;
230
231   /**
232    * Command used to refresh our map information.
233    */
234   struct GNUNET_OS_CommandHandle *refresh_cmd;
235
236   /**
237    * Command used to remove the mapping.
238    */
239   struct GNUNET_OS_CommandHandle *unmap_cmd;
240
241   /**
242    * Our current external mapping (if we have one).
243    */
244   struct sockaddr_in current_addr;
245
246   /**
247    * We check the mapping periodically to see if it
248    * still works.  This task triggers the check.
249    */
250   GNUNET_SCHEDULER_TaskIdentifier refresh_task;
251
252   /**
253    * Are we mapping TCP or UDP?
254    */
255   int is_tcp;
256
257   /**
258    * Did we succeed with creating a mapping?
259    */
260   int did_map;
261
262   /**
263    * Did we find our mapping during refresh scan?
264    */
265   int found;
266
267   /**
268    * Which port are we mapping?
269    */
270   uint16_t port;
271
272 };
273
274
275 /**
276  * Run upnpc -l to find out if our mapping changed.
277  *
278  * @param cls the 'struct GNUNET_NAT_MiniHandle'
279  * @param tc scheduler context
280  */
281 static void
282 do_refresh (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
283
284
285 /**
286  * Process the output from the 'upnpc -r' command.
287  *
288  * @param cls the 'struct GNUNET_NAT_MiniHandle'
289  * @param line line of output, NULL at the end
290  */
291 static void process_map_output (void *cls, const char *line);
292
293
294 /**
295  * Process the output from 'upnpc -l' to see if our
296  * external mapping changed.  If so, do the notifications.
297  *
298  * @param cls the 'struct GNUNET_NAT_MiniHandle'
299  * @param line line of output, NULL at the end
300  */
301 static void
302 process_refresh_output (void *cls, const char *line)
303 {
304   struct GNUNET_NAT_MiniHandle *mini = cls;
305   char pstr[9];
306   const char *s;
307   unsigned int nport;
308   struct in_addr exip;
309
310   if (NULL == line)
311   {
312     GNUNET_OS_command_stop (mini->refresh_cmd);
313     mini->refresh_cmd = NULL;
314     if (mini->found == GNUNET_NO)
315     {
316       /* mapping disappeared, try to re-create */
317       if (mini->did_map)
318       {
319         mini->ac (mini->ac_cls, GNUNET_NO,
320                   (const struct sockaddr *) &mini->current_addr,
321                   sizeof (mini->current_addr));
322         mini->did_map = GNUNET_NO;
323       }
324       GNUNET_snprintf (pstr, sizeof (pstr), "%u", (unsigned int) mini->port);
325       mini->map_cmd = GNUNET_OS_command_run (&process_map_output,
326                                              mini,
327                                              MAP_TIMEOUT,
328                                              "upnpc",
329                                              "upnpc",
330                                              "-r", pstr,
331                                              mini->is_tcp ? "tcp" : "udp",
332                                              NULL);
333       if (NULL != mini->map_cmd)
334         return;
335     }
336     mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
337                                                        &do_refresh, mini);
338     return;
339   }
340   if (!mini->did_map)
341     return;                     /* never mapped, won't find our mapping anyway */
342
343   /* we're looking for output of the form:
344    * "ExternalIPAddress = 12.134.41.124" */
345
346   s = strstr (line, "ExternalIPAddress = ");
347   if (NULL != s)
348   {
349     s += strlen ("ExternalIPAddress = ");
350     if (1 != inet_pton (AF_INET, s, &exip))
351       return;                   /* skip */
352     if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
353       return;                   /* no change */
354     /* update mapping */
355     mini->ac (mini->ac_cls, GNUNET_NO,
356               (const struct sockaddr *) &mini->current_addr,
357               sizeof (mini->current_addr));
358     mini->current_addr.sin_addr = exip;
359     mini->ac (mini->ac_cls, GNUNET_YES,
360               (const struct sockaddr *) &mini->current_addr,
361               sizeof (mini->current_addr));
362     return;
363   }
364   /*
365    * we're looking for output of the form:
366    * 
367    * "0 TCP  3000->192.168.2.150:3000  'libminiupnpc' ''"
368    * "1 UDP  3001->192.168.2.150:3001  'libminiupnpc' ''"
369    * 
370    * the pattern we look for is:
371    * 
372    * "%s TCP  PORT->STRING:OURPORT *" or
373    * "%s UDP  PORT->STRING:OURPORT *"
374    */
375   GNUNET_snprintf (pstr, sizeof (pstr), ":%u ", mini->port);
376   if (NULL == (s = strstr (line, "->")))
377     return;                     /* skip */
378   if (NULL == strstr (s, pstr))
379     return;                     /* skip */
380   if (1 != sscanf (line,
381                    (mini->is_tcp)
382                    ? "%*u TCP  %u->%*s:%*u %*s"
383                    : "%*u UDP  %u->%*s:%*u %*s", &nport))
384     return;                     /* skip */
385   mini->found = GNUNET_YES;
386   if (nport == ntohs (mini->current_addr.sin_port))
387     return;                     /* no change */
388
389   /* external port changed, update mapping */
390   mini->ac (mini->ac_cls, GNUNET_NO,
391             (const struct sockaddr *) &mini->current_addr,
392             sizeof (mini->current_addr));
393   mini->current_addr.sin_port = htons ((uint16_t) nport);
394   mini->ac (mini->ac_cls, GNUNET_YES,
395             (const struct sockaddr *) &mini->current_addr,
396             sizeof (mini->current_addr));
397 }
398
399
400 /**
401  * Run upnpc -l to find out if our mapping changed.
402  *
403  * @param cls the 'struct GNUNET_NAT_MiniHandle'
404  * @param tc scheduler context
405  */
406 static void
407 do_refresh (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
408 {
409   struct GNUNET_NAT_MiniHandle *mini = cls;
410
411   mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
412   mini->found = GNUNET_NO;
413   mini->refresh_cmd = GNUNET_OS_command_run (&process_refresh_output,
414                                              mini,
415                                              MAP_TIMEOUT,
416                                              "upnpc", "upnpc", "-l", NULL);
417 }
418
419
420 /**
421  * Process the output from the 'upnpc -r' command.
422  *
423  * @param cls the 'struct GNUNET_NAT_MiniHandle'
424  * @param line line of output, NULL at the end
425  */
426 static void
427 process_map_output (void *cls, const char *line)
428 {
429   struct GNUNET_NAT_MiniHandle *mini = cls;
430   const char *ipaddr;
431   char *ipa;
432   const char *pstr;
433   unsigned int port;
434
435   if (NULL == line)
436   {
437     GNUNET_OS_command_stop (mini->map_cmd);
438     mini->map_cmd = NULL;
439     mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
440                                                        &do_refresh, mini);
441     return;
442   }
443   /*
444    * The upnpc output we're after looks like this:
445    * 
446    * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
447    */
448   if ((NULL == (ipaddr = strstr (line, " "))) ||
449       (NULL == (pstr = strstr (ipaddr, ":"))) ||
450       (1 != sscanf (pstr + 1, "%u", &port)))
451   {
452     return;                     /* skip line */
453   }
454   ipa = GNUNET_strdup (ipaddr + 1);
455   strstr (ipa, ":")[0] = '\0';
456   if (1 != inet_pton (AF_INET, ipa, &mini->current_addr.sin_addr))
457   {
458     GNUNET_free (ipa);
459     return;                     /* skip line */
460   }
461   GNUNET_free (ipa);
462
463   mini->current_addr.sin_port = htons (port);
464   mini->current_addr.sin_family = AF_INET;
465 #if HAVE_SOCKADDR_IN_SIN_LEN
466   mini->current_addr.sin_len = sizeof (struct sockaddr_in);
467 #endif
468   mini->did_map = GNUNET_YES;
469   mini->ac (mini->ac_cls, GNUNET_YES,
470             (const struct sockaddr *) &mini->current_addr,
471             sizeof (mini->current_addr));
472 }
473
474
475 /**
476  * Start mapping the given port using (mini)upnpc.  This function
477  * should typically not be used directly (it is used within the
478  * general-purpose 'GNUNET_NAT_register' code).  However, it can be
479  * used if specifically UPnP-based NAT traversal is to be used or
480  * tested.
481  * 
482  * @param port port to map
483  * @param is_tcp GNUNET_YES to map TCP, GNUNET_NO for UDP
484  * @param ac function to call with mapping result
485  * @param ac_cls closure for 'ac'
486  * @return NULL on error (no 'upnpc' installed)
487  */
488 struct GNUNET_NAT_MiniHandle *
489 GNUNET_NAT_mini_map_start (uint16_t port,
490                            int is_tcp,
491                            GNUNET_NAT_AddressCallback ac, void *ac_cls)
492 {
493   struct GNUNET_NAT_MiniHandle *ret;
494   char pstr[6];
495
496   if (GNUNET_SYSERR == GNUNET_OS_check_helper_binary ("upnpc"))
497     return NULL;
498   ret = GNUNET_malloc (sizeof (struct GNUNET_NAT_MiniHandle));
499   ret->ac = ac;
500   ret->ac_cls = ac_cls;
501   ret->is_tcp = is_tcp;
502   ret->port = port;
503   GNUNET_snprintf (pstr, sizeof (pstr), "%u", (unsigned int) port);
504   ret->map_cmd = GNUNET_OS_command_run (&process_map_output,
505                                         ret,
506                                         MAP_TIMEOUT,
507                                         "upnpc",
508                                         "upnpc",
509                                         "-r", pstr,
510                                         is_tcp ? "tcp" : "udp", NULL);
511   if (NULL != ret->map_cmd)
512     return ret;
513   ret->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
514                                                     &do_refresh, ret);
515
516   return ret;
517 }
518
519
520 /**
521  * Process output from our 'unmap' command.
522  *
523  * @param cls the 'struct GNUNET_NAT_MiniHandle'
524  * @param line line of output, NULL at the end
525  */
526 static void
527 process_unmap_output (void *cls, const char *line)
528 {
529   struct GNUNET_NAT_MiniHandle *mini = cls;
530
531   if (NULL == line)
532   {
533 #if DEBUG_NAT
534     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "nat", "UPnP unmap done\n");
535 #endif
536     GNUNET_OS_command_stop (mini->unmap_cmd);
537     mini->unmap_cmd = NULL;
538     GNUNET_free (mini);
539     return;
540   }
541   /* we don't really care about the output... */
542 }
543
544
545 /**
546  * Remove a mapping created with (mini)upnpc.  Calling
547  * this function will give 'upnpc' 1s to remove tha mapping,
548  * so while this function is non-blocking, a task will be
549  * left with the scheduler for up to 1s past this call.
550  * 
551  * @param mini the handle
552  */
553 void
554 GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini)
555 {
556   char pstr[6];
557
558   if (GNUNET_SCHEDULER_NO_TASK != mini->refresh_task)
559   {
560     GNUNET_SCHEDULER_cancel (mini->refresh_task);
561     mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
562   }
563   if (mini->refresh_cmd != NULL)
564   {
565     GNUNET_OS_command_stop (mini->refresh_cmd);
566     mini->refresh_cmd = NULL;
567   }
568   if (!mini->did_map)
569   {
570     if (mini->map_cmd != NULL)
571     {
572       GNUNET_OS_command_stop (mini->map_cmd);
573       mini->map_cmd = NULL;
574     }
575     GNUNET_free (mini);
576     return;
577   }
578   mini->ac (mini->ac_cls, GNUNET_NO,
579             (const struct sockaddr *) &mini->current_addr,
580             sizeof (mini->current_addr));
581   /* Note: oddly enough, deletion uses the external port whereas
582    * addition uses the internal port; this rarely matters since they
583    * often are the same, but it might... */
584   GNUNET_snprintf (pstr, sizeof (pstr),
585                    "%u", (unsigned int) ntohs (mini->current_addr.sin_port));
586 #if DEBUG_NAT
587   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
588                    "nat",
589                    "Unmapping port %u with UPnP\n",
590                    ntohs (mini->current_addr.sin_port));
591 #endif
592   mini->unmap_cmd = GNUNET_OS_command_run (&process_unmap_output,
593                                            mini,
594                                            UNMAP_TIMEOUT,
595                                            "upnpc",
596                                            "upnpc",
597                                            "-d", pstr,
598                                            mini->is_tcp ? "tcp" : "udp", NULL);
599 }
600
601
602 /* end of nat_mini.c */