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