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