38dcfcd1f4da1e01e3d66dd637cc4e3dbe2bd97b
[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  * How long do we give upnpc to create a mapping?
33  */
34 #define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
35
36 /**
37  * How long do we give upnpc to remove a mapping?
38  */
39 #define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
40
41 /**
42  * How often do we check for changes in the mapping?
43  */
44 #define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
45
46
47 /**
48  * Try to get the external IPv4 address of this peer.
49  * Note: calling this function may block this process
50  * for a few seconds (!).
51  *
52  * @param addr address to set
53  * @return GNUNET_OK on success,
54  *         GNUNET_NO if the result is questionable,
55  *         GNUNET_SYSERR on error
56  */
57 int
58 GNUNET_NAT_mini_get_external_ipv4 (struct in_addr *addr)
59 {
60   struct GNUNET_OS_Process *eip;
61   struct GNUNET_DISK_PipeHandle *opipe;
62   const struct GNUNET_DISK_FileHandle *r;
63   size_t off;
64   char buf[17];
65   ssize_t ret;
66   int iret;
67
68   opipe = GNUNET_DISK_pipe (GNUNET_YES,
69                             GNUNET_NO,
70                             GNUNET_YES);
71   if (NULL == opipe)
72     return GNUNET_SYSERR;
73   eip = GNUNET_OS_start_process (NULL,
74                                  opipe,
75                                  "external-ip",
76                                  "external-ip", NULL);
77   if (NULL == eip)
78     {
79       GNUNET_DISK_pipe_close (opipe);
80       return GNUNET_SYSERR;
81     }
82   GNUNET_DISK_pipe_close_end (opipe, GNUNET_DISK_PIPE_END_WRITE);
83   iret = GNUNET_SYSERR;
84   r = GNUNET_DISK_pipe_handle (opipe,
85                                GNUNET_DISK_PIPE_END_READ);
86   off = 0;
87   while (0 < (ret = GNUNET_DISK_file_read (r, &buf[off], sizeof (buf)-off)))
88     off += ret;
89   if ( (off > 7) &&    
90        (buf[off-1] == '\n') )    
91     {
92       buf[off-1] = '\0';
93       if (1 == inet_pton (AF_INET, buf, addr))
94         {
95           if (addr->s_addr == 0)
96             iret = GNUNET_NO; /* got 0.0.0.0 */
97           iret = GNUNET_OK;
98         }
99     }
100   (void) GNUNET_OS_process_kill (eip, SIGKILL);
101   GNUNET_OS_process_close (eip);
102   GNUNET_DISK_pipe_close (opipe);
103   return iret; 
104 }
105
106
107 /**
108  * Handle to a mapping created with upnpc.
109  */ 
110 struct GNUNET_NAT_MiniHandle
111 {
112
113   /**
114    * Function to call on mapping changes.
115    */
116   GNUNET_NAT_AddressCallback ac;
117
118   /**
119    * Closure for 'ac'.
120    */
121   void *ac_cls;
122
123   /**
124    * Command used to install the map.
125    */
126   struct GNUNET_OS_CommandHandle *map_cmd;
127
128   /**
129    * Command used to refresh our map information.
130    */
131   struct GNUNET_OS_CommandHandle *refresh_cmd;
132
133   /**
134    * Command used to remove the mapping.
135    */
136   struct GNUNET_OS_CommandHandle *unmap_cmd;
137
138   /**
139    * Our current external mapping (if we have one).
140    */
141   struct sockaddr_in current_addr;
142
143   /**
144    * We check the mapping periodically to see if it
145    * still works.  This task triggers the check.
146    */
147   GNUNET_SCHEDULER_TaskIdentifier refresh_task;
148
149   /**
150    * Are we mapping TCP or UDP?
151    */
152   int is_tcp;
153
154   /**
155    * Did we succeed with creating a mapping?
156    */
157   int did_map;
158
159   /**
160    * Did we find our mapping during refresh scan?
161    */ 
162   int found;
163
164   /**
165    * Which port are we mapping?
166    */
167   uint16_t port;
168
169 };
170
171
172 /**
173  * Run upnpc -l to find out if our mapping changed.
174  *
175  * @param cls the 'struct GNUNET_NAT_MiniHandle'
176  * @param tc scheduler context
177  */
178 static void
179 do_refresh (void *cls,
180             const struct GNUNET_SCHEDULER_TaskContext *tc);
181
182
183 /**
184  * Process the output from the 'upnpc -r' command.
185  *
186  * @param cls the 'struct GNUNET_NAT_MiniHandle'
187  * @param line line of output, NULL at the end
188  */
189 static void
190 process_map_output (void *cls,
191                     const char *line);
192
193
194 /**
195  * Process the output from 'upnpc -l' to see if our
196  * external mapping changed.  If so, do the notifications.
197  *
198  * @param cls the 'struct GNUNET_NAT_MiniHandle'
199  * @param line line of output, NULL at the end
200  */
201 static void
202 process_refresh_output (void *cls,
203                         const char *line)
204 {
205   struct GNUNET_NAT_MiniHandle *mini = cls;
206   char pstr[9];
207   const char *s;
208   unsigned int nport;
209   struct in_addr exip;
210
211   if (NULL == line)
212     {
213       GNUNET_OS_command_stop (mini->refresh_cmd);
214       mini->refresh_cmd = NULL;
215       if (mini->found == GNUNET_NO)
216         {
217           /* mapping disappeared, try to re-create */
218           if (mini->did_map)
219             {
220               mini->ac (mini->ac_cls, GNUNET_NO,
221                         (const struct sockaddr*) &mini->current_addr,
222                         sizeof (mini->current_addr));
223               mini->did_map = GNUNET_NO;
224             }
225           GNUNET_snprintf (pstr, sizeof (pstr),
226                            "%u",
227                            (unsigned int) mini->port);
228           mini->map_cmd = GNUNET_OS_command_run (&process_map_output,
229                                                  mini,
230                                                  MAP_TIMEOUT,
231                                                  "upnpc",
232                                                  "upnpc",
233                                                  "-r", pstr, 
234                                                  mini->is_tcp ? "tcp" : "udp",
235                                                  NULL);
236           if (NULL != mini->map_cmd)
237             return;
238         }
239       mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
240                                                          &do_refresh,
241                                                          mini);
242       return;
243     }
244   if (! mini->did_map)
245     return; /* never mapped, won't find our mapping anyway */
246
247   /* we're looking for output of the form:
248      "ExternalIPAddress = 12.134.41.124" */
249
250   s = strstr (line, "ExternalIPAddress = ");
251   if (NULL != s)
252     {
253       s += strlen ("ExternalIPAddress = ");
254       if (1 != inet_pton (AF_INET,
255                           s, &exip))
256         return; /* skip */
257       if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
258         return; /* no change */
259       /* update mapping */
260       mini->ac (mini->ac_cls, GNUNET_NO,
261                 (const struct sockaddr*) &mini->current_addr,
262                 sizeof (mini->current_addr));
263       mini->current_addr.sin_addr = exip;
264       mini->ac (mini->ac_cls, GNUNET_YES,
265                 (const struct sockaddr*) &mini->current_addr,
266                 sizeof (mini->current_addr));     
267       return;
268     }
269   /*
270     we're looking for output of the form:
271      
272      "0 TCP  3000->192.168.2.150:3000  'libminiupnpc' ''"
273      "1 UDP  3001->192.168.2.150:3001  'libminiupnpc' ''"
274
275     the pattern we look for is:
276
277      "%s TCP  PORT->STRING:OURPORT *" or
278      "%s UDP  PORT->STRING:OURPORT *"
279   */
280   GNUNET_snprintf (pstr, sizeof (pstr),
281                    ":%u ",
282                    mini->port);
283   if (NULL == (s = strstr (line, "->")))
284     return; /* skip */
285   if (NULL == (s = strstr (s, pstr)))
286     return; /* skip */
287   if (1 != sscanf (line,
288                    (mini->is_tcp) 
289                    ? "%*u TCP  %u->%*s:%*u %*s" 
290                    : "%*u UDP  %u->%*s:%*u %*s",
291                    &nport))
292     return; /* skip */
293   mini->found = GNUNET_YES;
294   if (nport == ntohs (mini->current_addr.sin_port))
295     return; /* no change */    
296
297   /* external port changed, update mapping */
298   mini->ac (mini->ac_cls, GNUNET_NO,
299             (const struct sockaddr*) &mini->current_addr,
300             sizeof (mini->current_addr));
301   mini->current_addr.sin_port = htons ((uint16_t) nport);
302   mini->ac (mini->ac_cls, GNUNET_YES,
303             (const struct sockaddr*) &mini->current_addr,
304             sizeof (mini->current_addr));     
305 }
306
307
308 /**
309  * Run upnpc -l to find out if our mapping changed.
310  *
311  * @param cls the 'struct GNUNET_NAT_MiniHandle'
312  * @param tc scheduler context
313  */
314 static void
315 do_refresh (void *cls,
316             const struct GNUNET_SCHEDULER_TaskContext *tc)
317 {
318   struct GNUNET_NAT_MiniHandle *mini = cls;
319
320   mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
321   mini->found = GNUNET_NO;
322   mini->refresh_cmd = GNUNET_OS_command_run (&process_refresh_output,
323                                              mini,
324                                              MAP_TIMEOUT,
325                                              "upnpc",
326                                              "upnpc",
327                                              "-l",
328                                              NULL);
329 }
330
331
332 /**
333  * Process the output from the 'upnpc -r' command.
334  *
335  * @param cls the 'struct GNUNET_NAT_MiniHandle'
336  * @param line line of output, NULL at the end
337  */
338 static void
339 process_map_output (void *cls,
340                     const char *line)
341 {
342   struct GNUNET_NAT_MiniHandle *mini = cls;
343   const char *ipaddr;
344   char *ipa;
345   const char *pstr;
346   unsigned int port;
347
348   if (NULL == line)
349     {
350       GNUNET_OS_command_stop (mini->map_cmd);
351       mini->map_cmd = NULL;
352       mini->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
353                                                          &do_refresh,
354                                                          mini);
355       return;
356     }
357   /*
358     The upnpc output we're after looks like this:
359
360      "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
361   */
362   if ( (NULL == (ipaddr = strstr (line, " "))) ||
363        (NULL == (pstr = strstr (ipaddr, ":"))) ||
364        (1 != sscanf (pstr + 1, "%u", &port)) )
365     {
366       return; /* skip line */
367     }
368   ipa = GNUNET_strdup (ipaddr + 1);
369   strstr (ipa, ":")[0] = '\0';
370   if (1 != inet_pton (AF_INET,
371                       ipa, 
372                       &mini->current_addr.sin_addr))
373     {
374       GNUNET_free (ipa);
375       return; /* skip line */
376     }
377   GNUNET_free (ipa);          
378
379   mini->current_addr.sin_port = htons (port);
380   mini->current_addr.sin_family = AF_INET;
381 #if HAVE_SOCKADDR_IN_SIN_LEN
382   mini->current_addr.sin_len = sizeof (struct sockaddr_in);
383 #endif
384   mini->did_map = GNUNET_YES;
385   mini->ac (mini->ac_cls, GNUNET_YES,
386             (const struct sockaddr*) &mini->current_addr,
387             sizeof (mini->current_addr));
388 }
389
390
391 /**
392  * Start mapping the given port using (mini)upnpc.  This function
393  * should typically not be used directly (it is used within the
394  * general-purpose 'GNUNET_NAT_register' code).  However, it can be
395  * used if specifically UPnP-based NAT traversal is to be used or
396  * tested.
397  * 
398  * @param port port to map
399  * @param is_tcp GNUNET_YES to map TCP, GNUNET_NO for UDP
400  * @param ac function to call with mapping result
401  * @param ac_cls closure for 'ac'
402  * @return NULL on error
403  */
404 struct GNUNET_NAT_MiniHandle *
405 GNUNET_NAT_mini_map_start (uint16_t port,
406                            int is_tcp,
407                            GNUNET_NAT_AddressCallback ac,
408                            void *ac_cls)
409 {
410   struct GNUNET_NAT_MiniHandle *ret;
411   char pstr[6];
412
413   ret = GNUNET_malloc (sizeof (struct GNUNET_NAT_MiniHandle));
414   ret->ac = ac;
415   ret->ac_cls = ac_cls;
416   ret->is_tcp = is_tcp;
417   ret->port = port;
418   GNUNET_snprintf (pstr, sizeof (pstr),
419                    "%u",
420                    (unsigned int) port);
421   ret->map_cmd = GNUNET_OS_command_run (&process_map_output,
422                                         ret,
423                                         MAP_TIMEOUT,
424                                         "upnpc",
425                                         "upnpc",
426                                         "-r", pstr, 
427                                         is_tcp ? "tcp" : "udp",
428                                         NULL);
429   if (NULL != ret->map_cmd)
430     return ret;
431   ret->refresh_task = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
432                                                     &do_refresh,
433                                                     ret);
434
435   return ret;
436 }
437
438
439 /**
440  * Process output from our 'unmap' command.
441  *
442  * @param cls the 'struct GNUNET_NAT_MiniHandle'
443  * @param line line of output, NULL at the end
444  */
445 static void
446 process_unmap_output (void *cls,
447                       const char *line)
448 {
449   struct GNUNET_NAT_MiniHandle *mini = cls;
450
451   if (NULL == line)
452     {
453       GNUNET_OS_command_stop (mini->unmap_cmd);
454       mini->unmap_cmd = NULL;
455       GNUNET_free (mini);
456       return;
457     }
458   /* we don't really care about the output... */
459 }
460
461
462 /**
463  * Remove a mapping created with (mini)upnpc.  Calling
464  * this function will give 'upnpc' 1s to remove tha mapping,
465  * so while this function is non-blocking, a task will be
466  * left with the scheduler for up to 1s past this call.
467  * 
468  * @param mini the handle
469  */
470 void
471 GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini)
472 {
473   char pstr[6];
474
475   if (! mini->did_map)
476     {
477       if (mini->map_cmd != NULL)
478         {
479           GNUNET_OS_command_stop (mini->map_cmd);
480           mini->map_cmd = NULL;
481         }
482       GNUNET_free (mini);
483       return;
484     }
485   if (GNUNET_SCHEDULER_NO_TASK != mini->refresh_task)
486     {
487       GNUNET_SCHEDULER_cancel (mini->refresh_task);
488       mini->refresh_task = GNUNET_SCHEDULER_NO_TASK;
489     }
490   if (mini->refresh_cmd != NULL)
491     {
492       GNUNET_OS_command_stop (mini->refresh_cmd);
493       mini->refresh_cmd = NULL;
494     }
495   mini->ac (mini->ac_cls, GNUNET_NO,
496             (const struct sockaddr*) &mini->current_addr,
497             sizeof (mini->current_addr));
498   /* Note: oddly enough, deletion uses the external port whereas
499      addition uses the internal port; this rarely matters since they
500      often are the same, but it might... */
501   GNUNET_snprintf (pstr, sizeof (pstr),
502                    "%u",
503                    (unsigned int) mini->current_addr.sin_port);
504   mini->unmap_cmd = GNUNET_OS_command_run (&process_unmap_output,
505                                            mini,
506                                            UNMAP_TIMEOUT,
507                                            "upnpc",
508                                            "upnpc",
509                                            "-d", pstr, 
510                                            mini->is_tcp ? "tcp" : "udp",
511                                            NULL);
512 }
513
514
515 /* end of nat_mini.c */