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