GNUnet  0.10.x
gnunet-service-nat_mini.c
Go to the documentation of this file.
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 
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_nat_service.h"
30 #include "nat.h"
31 
32 #define LOG(kind,...) GNUNET_log_from (kind, "nat", __VA_ARGS__)
33 
37 #define MAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
38 
42 #define UNMAP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
43 
47 #define MAP_REFRESH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
48 
49 
50 /* ************************* external-ip calling ************************ */
51 
56 {
57 
62 
66  void *cb_cls;
67 
72 
77 
82 
86  const struct GNUNET_DISK_FileHandle *r;
87 
91  size_t off;
92 
96  char buf[17];
97 
102 };
103 
104 
111 static void
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
128  eh->r,
130  eh);
131  return;
132  }
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
146  }
147  }
148  eh->cb (eh->cb_cls,
149  (GNUNET_NAT_ERROR_SUCCESS == eh->ret) ? &addr : NULL,
150  eh->ret);
152 }
153 
154 
160 static void
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 
182  void *cb_cls)
183 {
184  struct GNUNET_NAT_ExternalHandle *eh;
185 
187  eh->cb = cb;
188  eh->cb_cls = cb_cls;
190  if (GNUNET_SYSERR ==
191  GNUNET_OS_check_helper_binary ("external-ip",
192  GNUNET_NO,
193  NULL))
194  {
196  _("`external-ip' command not found\n"));
199  eh);
200  return eh;
201  }
203  "Running `external-ip' to determine our external IP\n");
205  GNUNET_YES,
206  GNUNET_NO,
207  GNUNET_YES);
208  if (NULL == eh->opipe)
209  {
212  eh);
213  return eh;
214  }
215  eh->eip =
217  0,
218  NULL,
219  eh->opipe,
220  NULL,
221  "external-ip",
222  "external-ip",
223  NULL);
224  if (NULL == eh->eip)
225  {
229  eh);
230  return eh;
231  }
234  eh->r = GNUNET_DISK_pipe_handle (eh->opipe,
236  eh->task
238  eh->r,
240  eh);
241  return eh;
242 }
243 
244 
250 void
252 {
253  if (NULL != eh->eip)
254  {
255  (void) GNUNET_OS_process_kill (eh->eip,
256  SIGKILL);
260  }
261  if (NULL != eh->opipe)
262  {
264  eh->opipe = NULL;
265  }
266  if (NULL != eh->task)
267  {
269  eh->task = NULL;
270  }
271  GNUNET_free (eh);
272 }
273 
274 
275 /* ************************* upnpc calling ************************ */
276 
277 
282 {
283 
288 
292  void *ac_cls;
293 
298 
303 
308 
312  struct sockaddr_in current_addr;
313 
319 
323  int is_tcp;
324 
328  int did_map;
329 
333  int found;
334 
338  uint16_t port;
339 
340 };
341 
342 
348 static void
349 do_refresh (void *cls);
350 
351 
358 static void
359 process_map_output (void *cls,
360  const char *line);
361 
362 
368 static void
370 {
371  char pstr[6];
372 
373  GNUNET_snprintf (pstr,
374  sizeof (pstr),
375  "%u",
376  (unsigned int) mini->port);
377  mini->map_cmd
379  mini,
380  MAP_TIMEOUT,
381  "upnpc",
382  "upnpc",
383  "-r",
384  pstr,
385  mini->is_tcp ? "tcp" : "udp",
386  NULL);
387  if (NULL == mini->map_cmd)
388  {
389  mini->ac (mini->ac_cls,
391  NULL,
392  0,
394  return;
395  }
396 }
397 
398 
406 static void
408  const char *line)
409 {
410  struct GNUNET_NAT_MiniHandle *mini = cls;
411  char pstr[9];
412  const char *s;
413  unsigned int nport;
414  struct in_addr exip;
415 
416  if (NULL == line)
417  {
419  mini->refresh_cmd = NULL;
420  if (GNUNET_NO == mini->found)
421  {
422  /* mapping disappeared, try to re-create */
423  if (GNUNET_YES == mini->did_map)
424  {
425  mini->ac (mini->ac_cls,
426  GNUNET_NO,
427  (const struct sockaddr *) &mini->current_addr,
428  sizeof (mini->current_addr),
430  mini->did_map = GNUNET_NO;
431  }
432  run_upnpc_r (mini);
433  }
434  return;
435  }
436  if (! mini->did_map)
437  return; /* never mapped, won't find our mapping anyway */
438 
439  /* we're looking for output of the form:
440  * "ExternalIPAddress = 12.134.41.124" */
441 
442  s = strstr (line,
443  "ExternalIPAddress = ");
444  if (NULL != s)
445  {
446  s += strlen ("ExternalIPAddress = ");
447  if (1 != inet_pton (AF_INET,
448  s,
449  &exip))
450  return; /* skip */
451  if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
452  return; /* no change */
453  /* update mapping */
454  mini->ac (mini->ac_cls,
455  GNUNET_NO,
456  (const struct sockaddr *) &mini->current_addr,
457  sizeof (mini->current_addr),
459  mini->current_addr.sin_addr = exip;
460  mini->ac (mini->ac_cls,
461  GNUNET_YES,
462  (const struct sockaddr *) &mini->current_addr,
463  sizeof (mini->current_addr),
465  return;
466  }
467  /*
468  * we're looking for output of the form:
469  *
470  * "0 TCP 3000->192.168.2.150:3000 'libminiupnpc' ''"
471  * "1 UDP 3001->192.168.2.150:3001 'libminiupnpc' ''"
472  *
473  * the pattern we look for is:
474  *
475  * "%s TCP PORT->STRING:OURPORT *" or
476  * "%s UDP PORT->STRING:OURPORT *"
477  */
478  GNUNET_snprintf (pstr,
479  sizeof (pstr),
480  ":%u ",
481  mini->port);
482  if (NULL == (s = strstr (line, "->")))
483  return; /* skip */
484  if (NULL == strstr (s, pstr))
485  return; /* skip */
486  if (1 !=
487  SSCANF (line,
488  (mini->is_tcp) ? "%*u TCP %u->%*s:%*u %*s" :
489  "%*u UDP %u->%*s:%*u %*s", &nport))
490  return; /* skip */
491  mini->found = GNUNET_YES;
492  if (nport == ntohs (mini->current_addr.sin_port))
493  return; /* no change */
494 
495  /* external port changed, update mapping */
496  mini->ac (mini->ac_cls,
497  GNUNET_NO,
498  (const struct sockaddr *) &mini->current_addr,
499  sizeof (mini->current_addr),
501  mini->current_addr.sin_port = htons ((uint16_t) nport);
502  mini->ac (mini->ac_cls,
503  GNUNET_YES,
504  (const struct sockaddr *) &mini->current_addr,
505  sizeof (mini->current_addr),
507 }
508 
509 
515 static void
516 do_refresh (void *cls)
517 {
518  struct GNUNET_NAT_MiniHandle *mini = cls;
519  int ac;
520 
521  mini->refresh_task
523  &do_refresh,
524  mini);
526  "Running `upnpc' to check if our mapping still exists\n");
527  mini->found = GNUNET_NO;
528  ac = GNUNET_NO;
529  if (NULL != mini->map_cmd)
530  {
531  /* took way too long, abort it! */
533  mini->map_cmd = NULL;
534  ac = GNUNET_YES;
535  }
536  if (NULL != mini->refresh_cmd)
537  {
538  /* took way too long, abort it! */
540  mini->refresh_cmd = NULL;
541  ac = GNUNET_YES;
542  }
543  mini->refresh_cmd
545  mini,
546  MAP_TIMEOUT,
547  "upnpc",
548  "upnpc",
549  "-l",
550  NULL);
551  if (GNUNET_YES == ac)
552  mini->ac (mini->ac_cls,
554  NULL,
555  0,
557 }
558 
559 
566 static void
568  const char *line)
569 {
570  struct GNUNET_NAT_MiniHandle *mini = cls;
571  const char *ipaddr;
572  char *ipa;
573  const char *pstr;
574  unsigned int port;
575 
576  if (NULL == line)
577  {
579  mini->map_cmd = NULL;
580  if (GNUNET_YES != mini->did_map)
581  mini->ac (mini->ac_cls,
583  NULL,
584  0,
586  if (NULL == mini->refresh_task)
587  mini->refresh_task
589  &do_refresh,
590  mini);
591  return;
592  }
593  /*
594  * The upnpc output we're after looks like this:
595  *
596  * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
597  */
598  if ((NULL == (ipaddr = strstr (line, " "))) ||
599  (NULL == (pstr = strstr (ipaddr, ":"))) ||
600  (1 != SSCANF (pstr + 1, "%u", &port)))
601  {
602  return; /* skip line */
603  }
604  ipa = GNUNET_strdup (ipaddr + 1);
605  strstr (ipa, ":")[0] = '\0';
606  if (1 != inet_pton (AF_INET,
607  ipa,
608  &mini->current_addr.sin_addr))
609  {
610  GNUNET_free (ipa);
611  return; /* skip line */
612  }
613  GNUNET_free (ipa);
614 
615  mini->current_addr.sin_port = htons (port);
616  mini->current_addr.sin_family = AF_INET;
617 #if HAVE_SOCKADDR_IN_SIN_LEN
618  mini->current_addr.sin_len = sizeof (struct sockaddr_in);
619 #endif
620  mini->did_map = GNUNET_YES;
621  mini->ac (mini->ac_cls,
622  GNUNET_YES,
623  (const struct sockaddr *) &mini->current_addr,
624  sizeof (mini->current_addr),
626 }
627 
628 
642 struct GNUNET_NAT_MiniHandle *
644  int is_tcp,
646  void *ac_cls)
647 {
648  struct GNUNET_NAT_MiniHandle *ret;
649 
650  if (GNUNET_SYSERR ==
652  GNUNET_NO,
653  NULL))
654  {
656  _("`upnpc' command not found\n"));
657  ac (ac_cls,
659  NULL, 0,
661  return NULL;
662  }
664  "Running `upnpc' to install mapping\n");
665  ret = GNUNET_new (struct GNUNET_NAT_MiniHandle);
666  ret->ac = ac;
667  ret->ac_cls = ac_cls;
668  ret->is_tcp = is_tcp;
669  ret->port = port;
670  ret->refresh_task =
672  &do_refresh,
673  ret);
674  run_upnpc_r (ret);
675  return ret;
676 }
677 
678 
685 static void
687  const char *line)
688 {
689  struct GNUNET_NAT_MiniHandle *mini = cls;
690 
691  if (NULL == line)
692  {
694  "UPnP unmap done\n");
696  mini->unmap_cmd = NULL;
697  GNUNET_free (mini);
698  return;
699  }
700  /* we don't really care about the output... */
701 }
702 
703 
712 void
714 {
715  char pstr[6];
716 
717  if (NULL != mini->refresh_task)
718  {
720  mini->refresh_task = NULL;
721  }
722  if (NULL != mini->refresh_cmd)
723  {
725  mini->refresh_cmd = NULL;
726  }
727  if (NULL != mini->map_cmd)
728  {
730  mini->map_cmd = NULL;
731  }
732  if (GNUNET_NO == mini->did_map)
733  {
734  GNUNET_free (mini);
735  return;
736  }
737  mini->ac (mini->ac_cls,
738  GNUNET_NO,
739  (const struct sockaddr *) &mini->current_addr,
740  sizeof (mini->current_addr),
742  /* Note: oddly enough, deletion uses the external port whereas
743  * addition uses the internal port; this rarely matters since they
744  * often are the same, but it might... */
745  GNUNET_snprintf (pstr,
746  sizeof (pstr),
747  "%u",
748  (unsigned int) ntohs (mini->current_addr.sin_port));
750  "Unmapping port %u with UPnP\n",
751  ntohs (mini->current_addr.sin_port));
752  mini->unmap_cmd
754  mini,
756  "upnpc",
757  "upnpc",
758  "-d",
759  pstr,
760  mini->is_tcp ? "tcp" : "udp",
761  NULL);
762 }
763 
764 
765 /* end of gnunet-service-nat_mini.c */
static void process_unmap_output(void *cls, const char *line)
Process output from our &#39;unmap&#39; command.
struct GNUNET_NAT_MiniHandle * GNUNET_NAT_mini_map_start(uint16_t port, int is_tcp, GNUNET_NAT_MiniAddressCallback ac, void *ac_cls)
Start mapping the given port using (mini)upnpc.
Opaque handle to cancel "GNUNET_NAT_mini_get_external_ipv4" operation.
#define MAP_REFRESH_FREQ
How often do we check for changes in the mapping?
void GNUNET_OS_process_destroy(struct GNUNET_OS_Process *proc)
Cleans up process structure contents (OS-dependent) and deallocates it.
Definition: os_priority.c:364
struct GNUNET_OS_CommandHandle * refresh_cmd
Command used to refresh our map information.
Handle to a mapping created with upnpc.
int GNUNET_snprintf(char *buf, size_t size, const char *format,...)
Like snprintf, just aborts if the buffer is of insufficient size.
`upnpc&#39; command failed to establish port mapping
ssize_t GNUNET_DISK_file_read(const struct GNUNET_DISK_FileHandle *h, void *result, size_t len)
Read the contents of a binary file into a buffer.
Definition: disk.c:881
static void do_refresh(void *cls)
Run "upnpc -l" to find out if our mapping changed.
enum GNUNET_NAT_StatusCode ret
Error code for better debugging and user feedback.
struct GNUNET_OS_Process * GNUNET_OS_start_process(int pipe_control, enum GNUNET_OS_InheritStdioFlags std_inheritance, struct GNUNET_DISK_PipeHandle *pipe_stdin, struct GNUNET_DISK_PipeHandle *pipe_stdout, struct GNUNET_DISK_PipeHandle *pipe_stderr, const char *filename,...)
Start a process.
Definition: os_priority.c:1400
#define LOG(kind,...)
`upnpc&#39; command took too long, process killed
int is_tcp
Are we mapping TCP or UDP?
struct GNUNET_OS_Process * eip
Handle to external-ip process.
#define GNUNET_NO
Definition: gnunet_common.h:81
struct GNUNET_SCHEDULER_Task * GNUNET_SCHEDULER_add_read_file(struct GNUNET_TIME_Relative delay, const struct GNUNET_DISK_FileHandle *rfd, GNUNET_SCHEDULER_TaskCallback task, void *task_cls)
Schedule a new task to be run with a specified delay or when the specified file descriptor is ready f...
Definition: scheduler.c:1643
#define GNUNET_OK
Named constants for return values.
Definition: gnunet_common.h:78
static void process_map_output(void *cls, const char *line)
Process the output from the "upnpc -r" command.
int GNUNET_OS_check_helper_binary(const char *binary, int check_suid, const char *params)
Check whether an executable exists and possibly if the suid bit is set on the file.
#define GNUNET_new(type)
Allocate a struct or union of the given type.
struct GNUNET_SCHEDULER_Task * task
Read task.
struct GNUNET_OS_CommandHandle * unmap_cmd
Command used to remove the mapping.
#define MAP_TIMEOUT
How long do we give upnpc to create a mapping?
#define GNUNET_strdup(a)
Wrapper around GNUNET_xstrdup_.
GNUNET_NAT_IPCallback cb
Function to call with the result.
#define GNUNET_break(cond)
Use this for internal assertion violations that are not fatal (can be handled) but should not occur...
const struct GNUNET_DISK_FileHandle * GNUNET_DISK_pipe_handle(const struct GNUNET_DISK_PipeHandle *p, enum GNUNET_DISK_PipeEnd n)
Get the handle to a particular pipe end.
Definition: disk.c:2641
`external-ip&#39; command output invalid
#define _(String)
GNU gettext support macro.
Definition: platform.h:208
void GNUNET_NAT_mini_map_stop(struct GNUNET_NAT_MiniHandle *mini)
Remove a mapping created with (mini)upnpc.
int did_map
Did we succeed with creating a mapping?
struct GNUNET_SCHEDULER_Task * GNUNET_SCHEDULER_add_delayed(struct GNUNET_TIME_Relative delay, GNUNET_SCHEDULER_TaskCallback task, void *task_cls)
Schedule a new task to be run with a specified delay.
Definition: scheduler.c:1246
static char * line
Desired phone line (string to be converted to a hash).
struct GNUNET_SCHEDULER_Task * GNUNET_SCHEDULER_add_now(GNUNET_SCHEDULER_TaskCallback task, void *task_cls)
Schedule a new task to be run as soon as possible.
Definition: scheduler.c:1273
static void signal_external_ip_error(void *cls)
(Asynchronously) signal error invoking external-ip to client.
GNUNET_NAT_StatusCode
Error Types for the NAT subsystem (which can then later be converted/resolved to a string) ...
#define GNUNET_TIME_UNIT_FOREVER_REL
Constant used to specify "forever".
The writing-end of a pipe.
The reading-end of a pipe.
struct GNUNET_SCHEDULER_Task * refresh_task
We check the mapping periodically to see if it still works.
void * ac_cls
Closure for ac.
struct GNUNET_DISK_PipeHandle * GNUNET_DISK_pipe(int blocking_read, int blocking_write, int inherit_read, int inherit_write)
Creates an interprocess channel.
Definition: disk.c:2289
struct GNUNET_NAT_ExternalHandle * GNUNET_NAT_mini_get_external_ipv4_(GNUNET_NAT_IPCallback cb, void *cb_cls)
Try to get the external IPv4 address of this peer.
#define GNUNET_SYSERR
Definition: gnunet_common.h:79
uint16_t port
Which port are we mapping?
void(* GNUNET_NAT_MiniAddressCallback)(void *cls, int add_remove, const struct sockaddr *addr, socklen_t addrlen, enum GNUNET_NAT_StatusCode result)
Signature of the callback passed to GNUNET_NAT_register() for a function to call whenever our set of ...
size_t off
Number of bytes in buf that are valid.
static struct GNUNET_MQ_Envelope * ac
Handle to current GNUNET_PEERINFO_add_peer() operation.
struct GNUNET_OS_CommandHandle * GNUNET_OS_command_run(GNUNET_OS_LineProcessor proc, void *proc_cls, struct GNUNET_TIME_Relative timeout, const char *binary,...)
Run the given command line and call the given function for each line of the output.
Definition: os_priority.c:1948
`external-ip&#39; command not found
static void read_external_ipv4(void *cls)
Read the output of external-ip into buf.
const struct GNUNET_DISK_FileHandle * r
Read handle of opipe.
void(* GNUNET_NAT_IPCallback)(void *cls, const struct in_addr *addr, enum GNUNET_NAT_StatusCode result)
Signature of a callback that is given an IP address.
int GNUNET_DISK_pipe_close(struct GNUNET_DISK_PipeHandle *p)
Closes an interprocess channel.
Definition: disk.c:2603
int GNUNET_DISK_pipe_close_end(struct GNUNET_DISK_PipeHandle *p, enum GNUNET_DISK_PipeEnd end)
Closes one half of an interprocess channel.
Definition: disk.c:2532
Messages for interaction with gnunet-nat-server and gnunet-nat-service.
int GNUNET_OS_process_wait(struct GNUNET_OS_Process *proc)
Wait for a process to terminate.
Definition: os_priority.c:1762
#define SSCANF
Definition: plibc.h:691
static uint16_t port
Port number.
Definition: gnunet-bcd.c:79
struct GNUNET_DISK_PipeHandle * opipe
Handle to stdout pipe of external-ip.
Handle to a command.
Definition: os_priority.c:1804
static void process_refresh_output(void *cls, const char *line)
Process the output from "upnpc -l" to see if our external mapping changed.
struct GNUNET_OS_CommandHandle * map_cmd
Command used to install the map.
Failed to run upnpc command.
int found
Did we find our mapping during refresh scan?
static int inet_pton(int af, const char *cp, struct in_addr *buf)
Convert IPv4 address from text to binary form.
Entry in list of pending tasks.
Definition: scheduler.c:134
"no valid address was returned by `external-ip&#39;"
void GNUNET_OS_command_stop(struct GNUNET_OS_CommandHandle *cmd)
Stop/kill a command.
Definition: os_priority.c:1862
#define GNUNET_YES
Definition: gnunet_common.h:80
GNUNET_NAT_MiniAddressCallback ac
Function to call on mapping changes.
char buf[17]
Destination of our read operation (output of &#39;external-ip&#39;).
struct sockaddr_in current_addr
Our current external mapping (if we have one).
static void run_upnpc_r(struct GNUNET_NAT_MiniHandle *mini)
Run "upnpc -r" to map our internal port.
Handle used to access files (and pipes).
Handle used to manage a pipe.
Definition: disk.c:66
Failed to run external-ip command.
void GNUNET_NAT_mini_get_external_ipv4_cancel_(struct GNUNET_NAT_ExternalHandle *eh)
Cancel operation.
void * cb_cls
Closure for cb.
#define GNUNET_free(ptr)
Wrapper around free.
int GNUNET_OS_process_kill(struct GNUNET_OS_Process *proc, int sig)
Sends a signal to the process.
Definition: os_priority.c:251
#define UNMAP_TIMEOUT
How long do we give upnpc to remove a mapping?
void * GNUNET_SCHEDULER_cancel(struct GNUNET_SCHEDULER_Task *task)
Cancel the task with the specified identifier.
Definition: scheduler.c:965