(no commit message)
[oweals/gnunet.git] / src / nat / upnp.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /*
22  * This file has been adapted from the Transmission project:
23  * Originally licensed by the GPL version 2.
24  * Copyright (C) 2007-2009 Charles Kerr <charles@transmissionbt.com>
25  */
26
27 /**
28  * @file nat/upnp.c
29  * @brief UPnP support for the NAT library
30  *
31  * @author Milan Bouchet-Valat
32  */
33 #include <stdlib.h>
34 #include <assert.h>
35 #include <errno.h>
36 #include <string.h>
37
38 #include "platform.h"
39 #include "gnunet_common.h"
40 #include "gnunet_nat_lib.h"
41 #include "nat.h"
42 #include "upnp-discover.h"
43 #include "upnp-commands.h"
44 #include "upnp.h"
45
46 /* Component name for logging */
47 #define COMP_NAT_UPNP _("NAT (UPnP)")
48
49 enum UPNP_State
50 {
51   UPNP_IDLE,
52   UPNP_ERR,
53   UPNP_DISCOVER,
54   UPNP_MAP,
55   UPNP_UNMAP
56 };
57
58 struct GNUNET_NAT_UPNP_Handle
59 {
60   int hasDiscovered;
61   char *control_url;
62   char *service_type;
63   int port;
64   const struct sockaddr *addr;
65   socklen_t addrlen;
66   unsigned int is_mapped;
67   enum UPNP_State state;
68   struct sockaddr *ext_addr;
69   const char *iface;
70   int processing;
71   GNUNET_NAT_UPNP_pulse_cb pulse_cb;
72   void *pulse_cls;
73 };
74
75 static int
76 process_if (void *cls,
77             const char *name,
78             int isDefault, const struct sockaddr *addr, socklen_t addrlen)
79 {
80   struct GNUNET_NAT_UPNP_Handle *upnp = cls;
81
82   if (addr && GNUNET_NAT_cmp_addr (upnp->addr, addr) == 0)
83     {
84       upnp->iface = name;       // BADNESS!
85       return GNUNET_SYSERR;
86     }
87
88   return GNUNET_OK;
89 }
90
91
92 struct GNUNET_NAT_UPNP_Handle *
93 GNUNET_NAT_UPNP_init (const struct sockaddr *addr,
94                       socklen_t addrlen,
95                       u_short port,
96                       GNUNET_NAT_UPNP_pulse_cb pulse_cb, void *pulse_cls)
97 {
98   struct GNUNET_NAT_UPNP_Handle *handle;
99
100   handle = GNUNET_malloc (sizeof (struct GNUNET_NAT_UPNP_Handle));
101   handle->processing = GNUNET_NO;
102   handle->state = UPNP_DISCOVER;
103   handle->addr = addr;
104   handle->addrlen = addrlen;
105   handle->port = port;
106   handle->pulse_cb = pulse_cb;
107   handle->pulse_cls = pulse_cls;
108   handle->control_url = NULL;
109   handle->service_type = NULL;
110
111   /* Find the interface corresponding to the address,
112    * on which we should broadcast call for routers */
113   GNUNET_OS_network_interfaces_list (&process_if, handle);
114   if (!handle->iface)
115     GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
116                      COMP_NAT_UPNP,
117                      "Could not find an interface matching the wanted address.\n");
118   return handle;
119 }
120
121
122 void
123 GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle *handle)
124 {
125   GNUNET_assert (!handle->is_mapped);
126   GNUNET_assert ((handle->state == UPNP_IDLE)
127                  || (handle->state == UPNP_ERR)
128                  || (handle->state == UPNP_DISCOVER));
129
130   GNUNET_free_non_null (handle->control_url);
131   GNUNET_free_non_null (handle->service_type);
132   GNUNET_free (handle);
133 }
134
135 static void
136 pulse_finish (struct GNUNET_NAT_UPNP_Handle *handle)
137 {
138   enum GNUNET_NAT_PortState status;
139   handle->processing = GNUNET_NO;
140
141   switch (handle->state)
142     {
143     case UPNP_DISCOVER:
144       status = GNUNET_NAT_PORT_UNMAPPED;
145       break;
146
147     case UPNP_MAP:
148       status = GNUNET_NAT_PORT_MAPPING;
149       break;
150
151     case UPNP_UNMAP:
152       status = GNUNET_NAT_PORT_UNMAPPING;
153       break;
154
155     case UPNP_IDLE:
156       status =
157         handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
158       break;
159
160     default:
161       status = GNUNET_NAT_PORT_ERROR;
162       break;
163     }
164
165   handle->pulse_cb (status, handle->ext_addr, handle->pulse_cls);
166 }
167
168 static void
169 discover_cb (const char *control_url, const char *service_type, void *cls)
170 {
171   struct GNUNET_NAT_UPNP_Handle *handle = cls;
172
173   if (control_url)
174     {
175       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
176                        _("Found Internet Gateway Device \"%s\"\n"),
177                        control_url);
178
179       GNUNET_free_non_null (handle->control_url);
180       GNUNET_free_non_null (handle->service_type);
181
182       handle->control_url = GNUNET_strdup (control_url);
183       handle->service_type = GNUNET_strdup (service_type);
184       handle->state = UPNP_IDLE;
185       handle->hasDiscovered = 1;
186     }
187   else
188     {
189       handle->control_url = NULL;
190       handle->service_type = NULL;
191       handle->state = UPNP_ERR;
192 #ifdef DEBUG_UPNP
193       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
194                        "UPNP device discovery failed\n");
195 #endif
196     }
197
198   pulse_finish (handle);
199 }
200
201 static void
202 check_port_mapping_cb (int error, const char *control_url,
203                        const char *service_type, const char *extPort,
204                        const char *inPort, const char *proto,
205                        const char *remoteHost, void *cls)
206 {
207   struct GNUNET_NAT_UPNP_Handle *handle = cls;
208
209   if (error)
210     {
211       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
212                        _("Port %d isn't forwarded\n"), handle->port);
213       handle->is_mapped = GNUNET_NO;
214     }
215
216   pulse_finish (handle);
217 }
218
219 static void
220 delete_port_mapping_cb (int error, const char *control_url,
221                         const char *service_type, const char *extPort,
222                         const char *inPort, const char *proto,
223                         const char *remoteHost, void *cls)
224 {
225   struct GNUNET_NAT_UPNP_Handle *handle = cls;
226
227   if (error)
228     GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
229                      _
230                      ("Could not stop port forwarding through \"%s\", service \"%s\": error %d\n"),
231                      handle->control_url, handle->service_type, error);
232   else
233     {
234       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
235                        _
236                        ("Stopped port forwarding through \"%s\", service \"%s\"\n"),
237                        handle->control_url, handle->service_type);
238       handle->is_mapped = !error;
239       handle->state = UPNP_IDLE;
240       handle->port = -1;
241     }
242
243   pulse_finish (handle);
244 }
245
246 static void
247 add_port_mapping_cb (int error, const char *control_url,
248                      const char *service_type, const char *extPort,
249                      const char *inPort, const char *proto,
250                      const char *remoteHost, void *cls)
251 {
252   struct GNUNET_NAT_UPNP_Handle *handle = cls;
253
254   if (error)
255     {
256       handle->is_mapped = GNUNET_NO;
257       handle->state = UPNP_ERR;
258       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
259                        _
260                        ("Port forwarding through \"%s\", service \"%s\" failed with error %d\n"),
261                        handle->control_url, handle->service_type, error);
262       return;
263     }
264   else
265     {
266       handle->is_mapped = GNUNET_NO;
267       handle->state = UPNP_IDLE;
268       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
269                        _("Port %d forwarded successfully\n"), handle->port);
270     }
271
272   pulse_finish (handle);
273 }
274
275 static void
276 get_ip_address_cb (int error, char *ext_addr, void *cls)
277 {
278   struct GNUNET_NAT_UPNP_Handle *handle = cls;
279
280   if (error)
281     {
282       if (handle->ext_addr)
283         {
284           GNUNET_free (handle->ext_addr);
285           handle->ext_addr = NULL;
286         }
287 #ifdef DEBUG_UPNP
288       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
289                        "UPNP_get_external_ip_address_ failed (error %d)\n",
290                        error);
291 #endif
292     }
293   else
294     {
295       struct in_addr addr;
296       struct in6_addr addr6;
297
298       if (handle->ext_addr)
299         {
300           GNUNET_free (handle->ext_addr);
301           handle->ext_addr = NULL;
302         }
303
304       /* Try IPv4 and IPv6 as we don't know what's the format */
305       if (inet_aton (ext_addr, &addr) != 0)
306         {
307           handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
308           handle->ext_addr->sa_family = AF_INET;
309           ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr;
310         }
311       else if (inet_pton (AF_INET6, ext_addr, &addr6) != 1)
312         {
313           handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
314           handle->ext_addr->sa_family = AF_INET6;
315           ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6;
316         }
317       else
318         GNUNET_assert (GNUNET_YES);
319
320 #ifdef DEBUG_UPNP
321       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
322                        _("Found public IP address %s\n"), ext_addr);
323 #endif
324     }
325
326   pulse_finish (handle);
327 }
328
329 /**
330  * Check state of UPnP NAT: port redirection, external IP address.
331  * 
332  * 
333  * @param handle the handle for UPnP object
334  * @param is_enabled whether enable port redirection
335  * @param doPortCheck FIXME
336  * @param ext_addr pointer for returning external IP address.
337  *     Will be set to NULL if address could not be found. Don't free the sockaddr.
338  */
339 void
340 GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *handle,
341                        int is_enabled, int doPortCheck)
342 {
343   /* Stop if we're already waiting for an action to complete */
344   if (handle->processing == GNUNET_YES)
345     return;
346
347   if (is_enabled && (handle->state == UPNP_DISCOVER))
348     {
349       handle->processing = GNUNET_YES;
350       UPNP_discover_ (handle->iface, handle->addr, discover_cb,
351                       handle);
352     }
353
354   if (handle->state == UPNP_IDLE)
355     {
356       if (handle->is_mapped && !is_enabled)
357         handle->state = UPNP_UNMAP;
358     }
359
360   if (is_enabled && handle->is_mapped && doPortCheck)
361     {
362       char portStr[8];
363
364       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
365
366       handle->processing = GNUNET_YES;
367       UPNP_get_specific_port_mapping_entry_ (handle->control_url,
368                                              handle->service_type, portStr,
369                                              "TCP", check_port_mapping_cb,
370                                              handle);
371     }
372
373   if (handle->state == UPNP_UNMAP)
374     {
375       char portStr[16];
376       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
377
378       handle->processing = GNUNET_YES;
379       UPNP_delete_port_mapping_ (handle->control_url,
380                                  handle->service_type, portStr, "TCP", NULL,
381                                  delete_port_mapping_cb, handle);
382     }
383
384   if (handle->state == UPNP_IDLE)
385     {
386       if (is_enabled && !handle->is_mapped)
387         handle->state = UPNP_MAP;
388     }
389
390   if (handle->state == UPNP_MAP)
391     {
392       if (!handle->control_url)
393         handle->is_mapped = 0;
394       else
395         {
396           char portStr[16];
397           char desc[64];
398           GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
399           GNUNET_snprintf (desc, sizeof (desc), "GNUnet at %d", handle->port);
400
401           handle->processing = GNUNET_YES;
402           UPNP_add_port_mapping_ (handle->control_url,
403                                   handle->service_type,
404                                   portStr, portStr, GNUNET_a2s (handle->addr,
405                                                                 handle->addrlen),
406                                   desc, "TCP", NULL, add_port_mapping_cb,
407                                   handle);
408         }
409     }
410
411   if (handle->state != UPNP_DISCOVER)
412     {
413       handle->processing = GNUNET_YES;
414       UPNP_get_external_ip_address_ (handle->control_url,
415                                      handle->service_type,
416                                      get_ip_address_cb, handle);
417     }
418 }