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