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