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