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