first step to remove plibc
[oweals/gnunet.git] / src / nat / gnunet-service-nat_helper.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2010, 2011, 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_helper.c
23  * @brief runs the gnunet-helper-nat-server
24  * @author Milan Bouchet-Valat
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet-service-nat_helper.h"
30
31
32 /**
33  * Information we keep per NAT helper process.
34  */
35 struct HelperContext
36 {
37
38   /**
39    * IP address we pass to the NAT helper.
40    */
41   struct in_addr internal_address;
42
43   /**
44    * Function to call if we receive a reversal request.
45    */
46   GN_ReversalCallback cb;
47
48   /**
49    * Closure for @e cb.
50    */
51   void *cb_cls;
52
53   /**
54    * How long do we wait for restarting a crashed gnunet-helper-nat-server?
55    */
56   struct GNUNET_TIME_Relative server_retry_delay;
57
58   /**
59    * ID of select gnunet-helper-nat-server stdout read task
60    */
61   struct GNUNET_SCHEDULER_Task *server_read_task;
62
63   /**
64    * The process id of the server process (if behind NAT)
65    */
66   struct GNUNET_OS_Process *server_proc;
67
68   /**
69    * stdout pipe handle for the gnunet-helper-nat-server process
70    */
71   struct GNUNET_DISK_PipeHandle *server_stdout;
72
73   /**
74    * stdout file handle (for reading) for the gnunet-helper-nat-server process
75    */
76   const struct GNUNET_DISK_FileHandle *server_stdout_handle;
77
78   /**
79    * Handle to the GNUnet configuration
80    */
81   const struct GNUNET_CONFIGURATION_Handle *cfg;
82 };
83
84
85 /**
86  * Task that restarts the gnunet-helper-nat-server process after a crash
87  * after a certain delay.
88  *
89  * @param cls a `struct HelperContext`
90  */
91 static void
92 restart_nat_server (void *cls);
93
94
95 /**
96  * Try again starting the helper later
97  *
98  * @param h context of the helper
99  */
100 static void
101 try_again (struct HelperContext *h)
102 {
103   GNUNET_assert (NULL == h->server_read_task);
104   h->server_retry_delay = GNUNET_TIME_STD_BACKOFF (h->server_retry_delay);
105   h->server_read_task = GNUNET_SCHEDULER_add_delayed (h->server_retry_delay,
106                                                       &restart_nat_server,
107                                                       h);
108 }
109
110
111 /**
112  * We have been notified that gnunet-helper-nat-server has written
113  * something to stdout.  Handle the output, then reschedule this
114  * function to be called again once more is available.
115  *
116  * @param cls the `struct HelperContext`
117  */
118 static void
119 nat_server_read (void *cls)
120 {
121   struct HelperContext *h = cls;
122   char mybuf[40];
123   ssize_t bytes;
124   int port;
125   const char *port_start;
126   struct sockaddr_in sin_addr;
127
128   h->server_read_task = NULL;
129   memset (mybuf, 0, sizeof (mybuf));
130   bytes =
131     GNUNET_DISK_file_read (h->server_stdout_handle, mybuf, sizeof (mybuf));
132   if (bytes < 1)
133   {
134     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
135                 "Finished reading from server stdout with code: %d\n",
136                 (int) bytes);
137     if (0 != GNUNET_OS_process_kill (h->server_proc, GNUNET_TERM_SIG))
138       GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_WARNING, "nat", "kill");
139     GNUNET_OS_process_wait (h->server_proc);
140     GNUNET_OS_process_destroy (h->server_proc);
141     h->server_proc = NULL;
142     GNUNET_DISK_pipe_close (h->server_stdout);
143     h->server_stdout = NULL;
144     h->server_stdout_handle = NULL;
145     try_again (h);
146     return;
147   }
148
149   port_start = NULL;
150   for (size_t i = 0; i < sizeof (mybuf); i++)
151   {
152     if (mybuf[i] == '\n')
153     {
154       mybuf[i] = '\0';
155       break;
156     }
157     if ((mybuf[i] == ':') && (i + 1 < sizeof (mybuf)))
158     {
159       mybuf[i] = '\0';
160       port_start = &mybuf[i + 1];
161     }
162   }
163
164   /* construct socket address of sender */
165   memset (&sin_addr, 0, sizeof (sin_addr));
166   sin_addr.sin_family = AF_INET;
167 #if HAVE_SOCKADDR_IN_SIN_LEN
168   sin_addr.sin_len = sizeof (sin_addr);
169 #endif
170   if ((NULL == port_start) || (1 != sscanf (port_start, "%d", &port)) ||
171       (-1 == inet_pton (AF_INET, mybuf, &sin_addr.sin_addr)))
172   {
173     /* should we restart gnunet-helper-nat-server? */
174     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
175                 _ (
176                   "gnunet-helper-nat-server generated malformed address `%s'\n"),
177                 mybuf);
178     h->server_read_task =
179       GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
180                                       h->server_stdout_handle,
181                                       &nat_server_read,
182                                       h);
183     return;
184   }
185   sin_addr.sin_port = htons ((uint16_t) port);
186   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
187               "gnunet-helper-nat-server read: %s:%d\n",
188               mybuf,
189               port);
190   h->cb (h->cb_cls, &sin_addr);
191   h->server_read_task =
192     GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
193                                     h->server_stdout_handle,
194                                     &nat_server_read,
195                                     h);
196 }
197
198
199 /**
200  * Task that restarts the gnunet-helper-nat-server process after a crash
201  * after a certain delay.
202  *
203  * @param cls a `struct HelperContext`
204  */
205 static void
206 restart_nat_server (void *cls)
207 {
208   struct HelperContext *h = cls;
209   char *binary;
210   char ia[INET_ADDRSTRLEN];
211
212   h->server_read_task = NULL;
213   GNUNET_assert (NULL !=
214                  inet_ntop (AF_INET, &h->internal_address, ia, sizeof (ia)));
215   /* Start the server process */
216   binary = GNUNET_OS_get_suid_binary_path (h->cfg, "gnunet-helper-nat-server");
217   if (GNUNET_YES != GNUNET_OS_check_helper_binary (binary, GNUNET_YES, ia))
218   {
219     /* move instantly to max delay, as this is unlikely to be fixed */
220     h->server_retry_delay = GNUNET_TIME_STD_EXPONENTIAL_BACKOFF_THRESHOLD;
221     GNUNET_free (binary);
222     try_again (h);
223     return;
224   }
225   h->server_stdout =
226     GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
227   if (NULL == h->server_stdout)
228   {
229     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "pipe");
230     GNUNET_free (binary);
231     try_again (h);
232     return;
233   }
234   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
235               "Starting `%s' at `%s'\n",
236               "gnunet-helper-nat-server",
237               ia);
238   h->server_proc = GNUNET_OS_start_process (GNUNET_NO,
239                                             0,
240                                             NULL,
241                                             h->server_stdout,
242                                             NULL,
243                                             binary,
244                                             "gnunet-helper-nat-server",
245                                             ia,
246                                             NULL);
247   GNUNET_free (binary);
248   if (NULL == h->server_proc)
249   {
250     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
251                 _ ("Failed to start %s\n"),
252                 "gnunet-helper-nat-server");
253     GNUNET_DISK_pipe_close (h->server_stdout);
254     h->server_stdout = NULL;
255     try_again (h);
256     return;
257   }
258   /* Close the write end of the read pipe */
259   GNUNET_DISK_pipe_close_end (h->server_stdout, GNUNET_DISK_PIPE_END_WRITE);
260   h->server_stdout_handle =
261     GNUNET_DISK_pipe_handle (h->server_stdout, GNUNET_DISK_PIPE_END_READ);
262   h->server_read_task =
263     GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
264                                     h->server_stdout_handle,
265                                     &nat_server_read,
266                                     h);
267 }
268
269
270 /**
271  * Start the gnunet-helper-nat-server and process incoming
272  * requests.
273  *
274  * @param internal_address
275  * @param cb function to call if we receive a request
276  * @param cb_cls closure for @a cb
277  * @param cfg Handle to the GNUnet configuration
278  * @return NULL on error
279  */
280 struct HelperContext *
281 GN_start_gnunet_nat_server_ (const struct in_addr *internal_address,
282                              GN_ReversalCallback cb,
283                              void *cb_cls,
284                              const struct GNUNET_CONFIGURATION_Handle *cfg)
285 {
286   struct HelperContext *h;
287
288   h = GNUNET_new (struct HelperContext);
289   h->cb = cb;
290   h->cb_cls = cb_cls;
291   h->internal_address = *internal_address;
292   h->cfg = cfg;
293   restart_nat_server (h);
294   if (NULL == h->server_stdout)
295   {
296     GN_stop_gnunet_nat_server_ (h);
297     return NULL;
298   }
299   return h;
300 }
301
302
303 /**
304  * Start the gnunet-helper-nat-server and process incoming
305  * requests.
306  *
307  * @param h helper context to stop
308  */
309 void
310 GN_stop_gnunet_nat_server_ (struct HelperContext *h)
311 {
312   if (NULL != h->server_read_task)
313   {
314     GNUNET_SCHEDULER_cancel (h->server_read_task);
315     h->server_read_task = NULL;
316   }
317   if (NULL != h->server_proc)
318   {
319     if (0 != GNUNET_OS_process_kill (h->server_proc, GNUNET_TERM_SIG))
320       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill");
321     GNUNET_OS_process_wait (h->server_proc);
322     GNUNET_OS_process_destroy (h->server_proc);
323     h->server_proc = NULL;
324     GNUNET_DISK_pipe_close (h->server_stdout);
325     h->server_stdout = NULL;
326     h->server_stdout_handle = NULL;
327   }
328   if (NULL != h->server_stdout)
329   {
330     GNUNET_DISK_pipe_close (h->server_stdout);
331     h->server_stdout = NULL;
332     h->server_stdout_handle = NULL;
333   }
334   GNUNET_free (h);
335 }
336
337
338 /**
339  * We want to connect to a peer that is behind NAT.  Run the
340  * gnunet-helper-nat-client to send dummy ICMP responses to cause
341  * that peer to connect to us (connection reversal).
342  *
343  * @param internal_address out internal address to use
344  * @param internal_port port to use
345  * @param remote_v4 the address of the peer (IPv4-only)
346  * @param cfg handle to the GNUnet configuration
347  * @return #GNUNET_SYSERR on error,
348  *         #GNUNET_OK otherwise
349  */
350 int
351 GN_request_connection_reversal (const struct in_addr *internal_address,
352                                 uint16_t internal_port,
353                                 const struct in_addr *remote_v4,
354                                 const struct GNUNET_CONFIGURATION_Handle *cfg)
355 {
356   char intv4[INET_ADDRSTRLEN];
357   char remv4[INET_ADDRSTRLEN];
358   char port_as_string[6];
359   struct GNUNET_OS_Process *proc;
360   char *binary;
361
362   if (NULL == inet_ntop (AF_INET, internal_address, intv4, INET_ADDRSTRLEN))
363   {
364     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "inet_ntop");
365     return GNUNET_SYSERR;
366   }
367   if (NULL == inet_ntop (AF_INET, remote_v4, remv4, INET_ADDRSTRLEN))
368   {
369     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "inet_ntop");
370     return GNUNET_SYSERR;
371   }
372   GNUNET_snprintf (port_as_string,
373                    sizeof (port_as_string),
374                    "%d",
375                    internal_port);
376   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
377               "Running gnunet-helper-nat-client %s %s %u\n",
378               intv4,
379               remv4,
380               internal_port);
381   binary = GNUNET_OS_get_suid_binary_path (cfg, "gnunet-helper-nat-client");
382   proc = GNUNET_OS_start_process (GNUNET_NO,
383                                   0,
384                                   NULL,
385                                   NULL,
386                                   NULL,
387                                   binary,
388                                   "gnunet-helper-nat-client",
389                                   intv4,
390                                   remv4,
391                                   port_as_string,
392                                   NULL);
393   GNUNET_free (binary);
394   if (NULL == proc)
395     return GNUNET_SYSERR;
396   /* we know that the gnunet-helper-nat-client will terminate virtually
397    * instantly */
398   GNUNET_OS_process_wait (proc);
399   GNUNET_OS_process_destroy (proc);
400   return GNUNET_OK;
401 }
402
403
404 /* end of gnunet-service-nat_helper.c */