ccdc832658d406228eb8245f8ea2cca56c2f0b3a
[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
32 /**
33  * How long do we give upnpc to create a mapping?
34  */
35 #define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
36
37 /**
38  * How long do we give upnpc to remove a mapping?
39  */
40 #define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
41
42 /**
43  * How often do we check for changes in the mapping?
44  */
45 #define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
46
47
48
49 /**
50  * Opaque handle to cancel "GNUNET_NAT_mini_get_external_ipv4" operation.
51  */
52 struct GNUNET_NAT_ExternalHandle
53 {
54
55   /**
56    * Function to call with the result.
57    */
58   GNUNET_NAT_IPCallback cb;
59
60   /**
61    * Closure for 'cb'.
62    */
63   void *cb_cls;
64
65   /**
66    * Read task.
67    */
68   GNUNET_SCHEDULER_TaskIdentifier task;
69
70   /**
71    * Handle to 'external-ip' process.
72    */
73   struct GNUNET_OS_Process *eip;
74
75   /**
76    * Handle to stdout pipe of 'external-ip'.
77    */
78   struct GNUNET_DISK_PipeHandle *opipe;
79
80   /**
81    * Read handle of 'opipe'.
82    */
83   const struct GNUNET_DISK_FileHandle *r;
84
85   /**
86    * When should this operation time out?
87    */
88   struct GNUNET_TIME_Absolute timeout;
89
90   /**
91    * Number of bytes in 'buf' that are valid.
92    */
93   size_t off;
94
95   /**
96    * Destination of our read operation (output of 'external-ip').
97    */
98   char buf[17];
99
100 };
101
102
103 /**
104  * Read the output of 'external-ip' into buf.  When complete, parse the
105  * address and call our callback.
106  *
107  * @param cls the 'struct GNUNET_NAT_ExternalHandle'
108  * @param tc scheduler context
109  */
110 static void
111 read_external_ipv4 (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
112 {
113   struct GNUNET_NAT_ExternalHandle *eh = cls;
114   ssize_t ret;
115   struct in_addr addr;
116   int iret;
117
118   eh->task = GNUNET_SCHEDULER_NO_TASK;
119   if (GNUNET_YES == GNUNET_NETWORK_fdset_handle_isset (tc->read_ready, eh->r))
120     ret =
121         GNUNET_DISK_file_read (eh->r, &eh->buf[eh->off],
122                                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 =
176       GNUNET_OS_start_process (NULL, eh->opipe, "external-ip", "external-ip",
177                                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 =
188       GNUNET_SCHEDULER_add_read_file (timeout, 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
292 process_map_output (void *cls, const char *line);
293
294
295 /**
296  * Process the output from 'upnpc -l' to see if our
297  * external mapping changed.  If so, do the notifications.
298  *
299  * @param cls the 'struct GNUNET_NAT_MiniHandle'
300  * @param line line of output, NULL at the end
301  */
302 static void
303 process_refresh_output (void *cls, const char *line)
304 {
305   struct GNUNET_NAT_MiniHandle *mini = cls;
306   char pstr[9];
307   const char *s;
308   unsigned int nport;
309   struct in_addr exip;
310
311   if (NULL == line)
312   {
313     GNUNET_OS_command_stop (mini->refresh_cmd);
314     mini->refresh_cmd = NULL;
315     if (mini->found == GNUNET_NO)
316     {
317       /* mapping disappeared, try to re-create */
318       if (mini->did_map)
319       {
320         mini->ac (mini->ac_cls, GNUNET_NO,
321                   (const struct sockaddr *) &mini->current_addr,
322                   sizeof (mini->current_addr));
323         mini->did_map = GNUNET_NO;
324       }
325       GNUNET_snprintf (pstr, sizeof (pstr), "%u", (unsigned int) mini->port);
326       mini->map_cmd =
327           GNUNET_OS_command_run (&process_map_output, mini, MAP_TIMEOUT,
328                                  "upnpc", "upnpc", "-r", pstr,
329                                  mini->is_tcp ? "tcp" : "udp", NULL);
330       if (NULL != mini->map_cmd)
331         return;
332     }
333     mini->refresh_task =
334         GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, &do_refresh, mini);
335     return;
336   }
337   if (!mini->did_map)
338     return;                     /* never mapped, won't find our mapping anyway */
339
340   /* we're looking for output of the form:
341    * "ExternalIPAddress = 12.134.41.124" */
342
343   s = strstr (line, "ExternalIPAddress = ");
344   if (NULL != s)
345   {
346     s += strlen ("ExternalIPAddress = ");
347     if (1 != inet_pton (AF_INET, s, &exip))
348       return;                   /* skip */
349     if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
350       return;                   /* no change */
351     /* update mapping */
352     mini->ac (mini->ac_cls, GNUNET_NO,
353               (const struct sockaddr *) &mini->current_addr,
354               sizeof (mini->current_addr));
355     mini->current_addr.sin_addr = exip;
356     mini->ac (mini->ac_cls, GNUNET_YES,
357               (const struct sockaddr *) &mini->current_addr,
358               sizeof (mini->current_addr));
359     return;
360   }
361   /*
362    * we're looking for output of the form:
363    *
364    * "0 TCP  3000->192.168.2.150:3000  'libminiupnpc' ''"
365    * "1 UDP  3001->192.168.2.150:3001  'libminiupnpc' ''"
366    *
367    * the pattern we look for is:
368    *
369    * "%s TCP  PORT->STRING:OURPORT *" or
370    * "%s UDP  PORT->STRING:OURPORT *"
371    */
372   GNUNET_snprintf (pstr, sizeof (pstr), ":%u ", mini->port);
373   if (NULL == (s = strstr (line, "->")))
374     return;                     /* skip */
375   if (NULL == strstr (s, pstr))
376     return;                     /* skip */
377   if (1 !=
378       sscanf (line,
379               (mini->is_tcp) ? "%*u TCP  %u->%*s:%*u %*s" :
380               "%*u UDP  %u->%*s:%*u %*s", &nport))
381     return;                     /* skip */
382   mini->found = GNUNET_YES;
383   if (nport == ntohs (mini->current_addr.sin_port))
384     return;                     /* no change */
385
386   /* external port changed, update mapping */
387   mini->ac (mini->ac_cls, GNUNET_NO,
388             (const struct sockaddr *) &mini->current_addr,
389             sizeof (mini->current_addr));
390   mini->current_addr.sin_port = htons ((uint16_t) nport);
391   mini->ac (mini->ac_cls, GNUNET_YES,
392             (const struct sockaddr *) &mini->current_addr,
393             sizeof (mini->current_addr));
394 }
395
396
397 /**
398  * Run upnpc -l to find out if our mapping changed.
399  *
400  * @param cls the 'struct GNUNET_NAT_MiniHandle'
401  * @param tc scheduler context
402  */
403 static void
404 do_refresh (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
405 {
406   struct GNUNET_NAT_MiniHandle *mini = cls;
407
408   mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
409   mini->found = GNUNET_NO;
410   mini->refresh_cmd =
411       GNUNET_OS_command_run (&process_refresh_output, mini, MAP_TIMEOUT,
412                              "upnpc", "upnpc", "-l", NULL);
413 }
414
415
416 /**
417  * Process the output from the 'upnpc -r' command.
418  *
419  * @param cls the 'struct GNUNET_NAT_MiniHandle'
420  * @param line line of output, NULL at the end
421  */
422 static void
423 process_map_output (void *cls, const char *line)
424 {
425   struct GNUNET_NAT_MiniHandle *mini = cls;
426   const char *ipaddr;
427   char *ipa;
428   const char *pstr;
429   unsigned int port;
430
431   if (NULL == line)
432   {
433     GNUNET_OS_command_stop (mini->map_cmd);
434     mini->map_cmd = NULL;
435     mini->refresh_task =
436         GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, &do_refresh, mini);
437     return;
438   }
439   /*
440    * The upnpc output we're after looks like this:
441    *
442    * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
443    */
444   if ((NULL == (ipaddr = strstr (line, " "))) ||
445       (NULL == (pstr = strstr (ipaddr, ":"))) ||
446       (1 != sscanf (pstr + 1, "%u", &port)))
447   {
448     return;                     /* skip line */
449   }
450   ipa = GNUNET_strdup (ipaddr + 1);
451   strstr (ipa, ":")[0] = '\0';
452   if (1 != inet_pton (AF_INET, ipa, &mini->current_addr.sin_addr))
453   {
454     GNUNET_free (ipa);
455     return;                     /* skip line */
456   }
457   GNUNET_free (ipa);
458
459   mini->current_addr.sin_port = htons (port);
460   mini->current_addr.sin_family = AF_INET;
461 #if HAVE_SOCKADDR_IN_SIN_LEN
462   mini->current_addr.sin_len = sizeof (struct sockaddr_in);
463 #endif
464   mini->did_map = GNUNET_YES;
465   mini->ac (mini->ac_cls, GNUNET_YES,
466             (const struct sockaddr *) &mini->current_addr,
467             sizeof (mini->current_addr));
468 }
469
470
471 /**
472  * Start mapping the given port using (mini)upnpc.  This function
473  * should typically not be used directly (it is used within the
474  * general-purpose 'GNUNET_NAT_register' code).  However, it can be
475  * used if specifically UPnP-based NAT traversal is to be used or
476  * tested.
477  *
478  * @param port port to map
479  * @param is_tcp GNUNET_YES to map TCP, GNUNET_NO for UDP
480  * @param ac function to call with mapping result
481  * @param ac_cls closure for 'ac'
482  * @return NULL on error (no 'upnpc' installed)
483  */
484 struct GNUNET_NAT_MiniHandle *
485 GNUNET_NAT_mini_map_start (uint16_t port, int is_tcp,
486                            GNUNET_NAT_AddressCallback ac, void *ac_cls)
487 {
488   struct GNUNET_NAT_MiniHandle *ret;
489   char pstr[6];
490
491   if (GNUNET_SYSERR == GNUNET_OS_check_helper_binary ("upnpc"))
492     return NULL;
493   ret = GNUNET_malloc (sizeof (struct GNUNET_NAT_MiniHandle));
494   ret->ac = ac;
495   ret->ac_cls = ac_cls;
496   ret->is_tcp = is_tcp;
497   ret->port = port;
498   GNUNET_snprintf (pstr, sizeof (pstr), "%u", (unsigned int) port);
499   ret->map_cmd =
500       GNUNET_OS_command_run (&process_map_output, ret, MAP_TIMEOUT, "upnpc",
501                              "upnpc", "-r", pstr, is_tcp ? "tcp" : "udp", NULL);
502   if (NULL != ret->map_cmd)
503     return ret;
504   ret->refresh_task =
505       GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ, &do_refresh, ret);
506
507   return ret;
508 }
509
510
511 /**
512  * Process output from our 'unmap' command.
513  *
514  * @param cls the 'struct GNUNET_NAT_MiniHandle'
515  * @param line line of output, NULL at the end
516  */
517 static void
518 process_unmap_output (void *cls, const char *line)
519 {
520   struct GNUNET_NAT_MiniHandle *mini = cls;
521
522   if (NULL == line)
523   {
524 #if DEBUG_NAT
525     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "nat", "UPnP unmap done\n");
526 #endif
527     GNUNET_OS_command_stop (mini->unmap_cmd);
528     mini->unmap_cmd = NULL;
529     GNUNET_free (mini);
530     return;
531   }
532   /* we don't really care about the output... */
533 }
534
535
536 /**
537  * Remove a mapping created with (mini)upnpc.  Calling
538  * this function will give 'upnpc' 1s to remove tha mapping,
539  * so while this function is non-blocking, a task will be
540  * left with the scheduler for up to 1s past this call.
541  *
542  * @param mini the handle
543  */
544 void
545 GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini)
546 {
547   char pstr[6];
548
549   if (GNUNET_SCHEDULER_NO_TASK != mini->refresh_task)
550   {
551     GNUNET_SCHEDULER_cancel (mini->refresh_task);
552     mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
553   }
554   if (mini->refresh_cmd != NULL)
555   {
556     GNUNET_OS_command_stop (mini->refresh_cmd);
557     mini->refresh_cmd = NULL;
558   }
559   if (!mini->did_map)
560   {
561     if (mini->map_cmd != NULL)
562     {
563       GNUNET_OS_command_stop (mini->map_cmd);
564       mini->map_cmd = NULL;
565     }
566     GNUNET_free (mini);
567     return;
568   }
569   mini->ac (mini->ac_cls, GNUNET_NO,
570             (const struct sockaddr *) &mini->current_addr,
571             sizeof (mini->current_addr));
572   /* Note: oddly enough, deletion uses the external port whereas
573    * addition uses the internal port; this rarely matters since they
574    * often are the same, but it might... */
575   GNUNET_snprintf (pstr, sizeof (pstr), "%u",
576                    (unsigned int) ntohs (mini->current_addr.sin_port));
577 #if DEBUG_NAT
578   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "nat",
579                    "Unmapping port %u with UPnP\n",
580                    ntohs (mini->current_addr.sin_port));
581 #endif
582   mini->unmap_cmd =
583       GNUNET_OS_command_run (&process_unmap_output, mini, UNMAP_TIMEOUT,
584                              "upnpc", "upnpc", "-d", pstr,
585                              mini->is_tcp ? "tcp" : "udp", NULL);
586 }
587
588
589 /* end of nat_mini.c */