-Merge branch 'master' of ssh://gnunet.org/gnunet into gsoc2018/rest_api
[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 it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20  * @file nat/gnunet-service-nat_mini.c
21  * @brief functions for interaction with miniupnp; tested with miniupnpc 1.5
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet_util_lib.h"
26 #include "gnunet_nat_service.h"
27 #include "gnunet-service-nat_mini.h"
28 #include "nat.h"
29
30 #define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__)
31
32 /**
33  * How long do we give upnpc to create a mapping?
34  */
35 #define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
36
37 /**
38  * How long do we give upnpc to remove a mapping?
39  */
40 #define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
41
42 /**
43  * How often do we check for changes in the mapping?
44  */
45 #define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
46
47
48 /* ************************* external-ip calling ************************ */
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   struct GNUNET_SCHEDULER_Task *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    * Number of bytes in @e buf that are valid.
88    */
89   size_t off;
90
91   /**
92    * Destination of our read operation (output of 'external-ip').
93    */
94   char buf[17];
95
96   /**
97    * Error code for better debugging and user feedback
98    */
99   enum GNUNET_NAT_StatusCode ret;
100 };
101
102
103 /**
104  * Read the output of `external-ip` into `buf`.  When complete, parse
105  * the address and call our callback.
106  *
107  * @param cls the `struct GNUNET_NAT_ExternalHandle`
108  */
109 static void
110 read_external_ipv4 (void *cls)
111 {
112   struct GNUNET_NAT_ExternalHandle *eh = cls;
113   ssize_t ret;
114   struct in_addr addr;
115
116   eh->task = NULL;
117   ret = GNUNET_DISK_file_read (eh->r,
118                                &eh->buf[eh->off],
119                                sizeof (eh->buf) - eh->off);
120   if (ret > 0)
121   {
122     /* try to read more */
123     eh->off += ret;
124     eh->task
125       = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
126                                         eh->r,
127                                         &read_external_ipv4,
128                                         eh);
129     return;
130   }
131   eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_OUTPUT_INVALID;
132   if ( (eh->off > 7) &&
133        (eh->buf[eh->off - 1] == '\n') )
134   {
135     eh->buf[eh->off - 1] = '\0';
136     if (1 == inet_pton (AF_INET,
137                         eh->buf,
138                         &addr))
139     {
140       if (0 == addr.s_addr)
141         eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_ADDRESS_INVALID;       /* got 0.0.0.0 */
142       else
143         eh->ret = GNUNET_NAT_ERROR_SUCCESS;
144     }
145   }
146   eh->cb (eh->cb_cls,
147           (GNUNET_NAT_ERROR_SUCCESS == eh->ret) ? &addr : NULL,
148           eh->ret);
149   GNUNET_NAT_mini_get_external_ipv4_cancel_ (eh);
150 }
151
152
153 /**
154  * (Asynchronously) signal error invoking `external-ip` to client.
155  *
156  * @param cls the `struct GNUNET_NAT_ExternalHandle` (freed)
157  */
158 static void
159 signal_external_ip_error (void *cls)
160 {
161   struct GNUNET_NAT_ExternalHandle *eh = cls;
162
163   eh->task = NULL;
164   eh->cb (eh->cb_cls,
165           NULL,
166           eh->ret);
167   GNUNET_free (eh);
168 }
169
170
171 /**
172  * Try to get the external IPv4 address of this peer.
173  *
174  * @param cb function to call with result
175  * @param cb_cls closure for @a cb
176  * @return handle for cancellation (can only be used until @a cb is called), never NULL
177  */
178 struct GNUNET_NAT_ExternalHandle *
179 GNUNET_NAT_mini_get_external_ipv4_ (GNUNET_NAT_IPCallback cb,
180                                     void *cb_cls)
181 {
182   struct GNUNET_NAT_ExternalHandle *eh;
183
184   eh = GNUNET_new (struct GNUNET_NAT_ExternalHandle);
185   eh->cb = cb;
186   eh->cb_cls = cb_cls;
187   eh->ret = GNUNET_NAT_ERROR_SUCCESS;
188   if (GNUNET_SYSERR ==
189       GNUNET_OS_check_helper_binary ("external-ip",
190                                      GNUNET_NO,
191                                      NULL))
192   {
193     LOG (GNUNET_ERROR_TYPE_INFO,
194          _("`external-ip' command not found\n"));
195     eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_NOT_FOUND;
196     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
197                                          eh);
198     return eh;
199   }
200   LOG (GNUNET_ERROR_TYPE_DEBUG,
201        "Running `external-ip' to determine our external IP\n");
202   eh->opipe = GNUNET_DISK_pipe (GNUNET_YES,
203                                 GNUNET_YES,
204                                 GNUNET_NO,
205                                 GNUNET_YES);
206   if (NULL == eh->opipe)
207   {
208     eh->ret = GNUNET_NAT_ERROR_IPC_FAILURE;
209     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
210                                          eh);
211     return eh;
212   }
213   eh->eip =
214       GNUNET_OS_start_process (GNUNET_NO,
215                                0,
216                                NULL,
217                                eh->opipe,
218                                NULL,
219                                "external-ip",
220                                "external-ip",
221                                NULL);
222   if (NULL == eh->eip)
223   {
224     GNUNET_DISK_pipe_close (eh->opipe);
225     eh->ret = GNUNET_NAT_ERROR_EXTERNAL_IP_UTILITY_FAILED;
226     eh->task = GNUNET_SCHEDULER_add_now (&signal_external_ip_error,
227                                          eh);
228     return eh;
229   }
230   GNUNET_DISK_pipe_close_end (eh->opipe,
231                               GNUNET_DISK_PIPE_END_WRITE);
232   eh->r = GNUNET_DISK_pipe_handle (eh->opipe,
233                                    GNUNET_DISK_PIPE_END_READ);
234   eh->task
235     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
236                                       eh->r,
237                                       &read_external_ipv4,
238                                       eh);
239   return eh;
240 }
241
242
243 /**
244  * Cancel operation.
245  *
246  * @param eh operation to cancel
247  */
248 void
249 GNUNET_NAT_mini_get_external_ipv4_cancel_ (struct GNUNET_NAT_ExternalHandle *eh)
250 {
251   if (NULL != eh->eip)
252   {
253     (void) GNUNET_OS_process_kill (eh->eip,
254                                    SIGKILL);
255     GNUNET_break (GNUNET_OK ==
256                   GNUNET_OS_process_wait (eh->eip));
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 */