increase timeout to give sparc buildbot a chance
[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 <miniupnp/miniupnpc.h>
39 #include <miniupnp/upnpcommands.h>
40
41 #include "platform.h"
42 #include "gnunet_common.h"
43 #include "gnunet_nat_lib.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   struct UPNPUrls urls;
62   struct IGDdatas data;
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 };
71
72 static int
73 process_if (void *cls,
74             const char *name,
75             int isDefault,
76             const struct sockaddr *addr,
77             socklen_t addrlen)
78 {
79   struct GNUNET_NAT_UPNP_Handle *upnp = cls;
80
81   if (addr && GNUNET_NAT_cmp_addr (upnp->addr, addr) == 0)
82     {
83       upnp->iface = name; // BADNESS!
84       return GNUNET_SYSERR;
85     }
86
87   return GNUNET_OK;
88 }
89
90
91 GNUNET_NAT_UPNP_Handle *
92 GNUNET_NAT_UPNP_init (const struct sockaddr *addr, 
93                       socklen_t addrlen,
94                       u_short port)
95 {
96   GNUNET_NAT_UPNP_Handle *upnp;
97
98   upnp = GNUNET_malloc (sizeof (GNUNET_NAT_UPNP_Handle));
99   upnp->state = UPNP_DISCOVER;
100   upnp->addr = addr;
101   upnp->addrlen = addrlen;
102   upnp->port = port;
103   /* Find the interface corresponding to the address,
104    * on which we should broadcast call for routers */
105   GNUNET_OS_network_interfaces_list (&process_if, upnp);
106   if (!upnp->iface)
107       GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, 
108                        COMP_NAT_UPNP, 
109                        "Could not find an interface matching the wanted address.\n");
110   return upnp;
111 }
112
113
114 void
115 GNUNET_NAT_UPNP_close (GNUNET_NAT_UPNP_Handle * handle)
116 {
117   GNUNET_assert (!handle->is_mapped);
118   GNUNET_assert ((handle->state == UPNP_IDLE)
119           || (handle->state == UPNP_ERR) || (handle->state == UPNP_DISCOVER));
120
121   if (handle->hasDiscovered)
122     FreeUPNPUrls (&handle->urls);
123   GNUNET_free (handle);
124 }
125
126 /**
127  * Check state of UPnP NAT: port redirection, external IP address.
128  * 
129  * 
130  * @param handle the handle for UPnP object
131  * @param is_enabled whether enable port redirection
132  * @param doPortCheck FIXME
133  * @param ext_addr pointer for returning external IP address.
134  *     Will be set to NULL if address could not be found. Don't free the sockaddr.
135  */
136 int
137 GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled,
138                        int doPortCheck, struct sockaddr **ext_addr)
139 {
140   int ret;
141
142   if (is_enabled && (handle->state == UPNP_DISCOVER))
143     {
144       struct UPNPDev *devlist;
145       errno = 0;
146       devlist = upnpDiscover (2000, handle->iface, handle->addr, NULL, 0);
147       if (devlist == NULL)
148         {
149 #ifdef DEBUG
150           GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
151                       "upnpDiscover failed (errno %d - %s)\n", errno,
152                       strerror (errno));
153 #endif
154         }
155       errno = 0;
156       if (UPNP_GetValidIGD (devlist, &handle->urls, &handle->data,
157                             NULL, 0))
158         {
159           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
160                       _("Found Internet Gateway Device \"%s\"\n"),
161                       handle->urls.controlURL);
162           handle->state = UPNP_IDLE;
163           handle->hasDiscovered = 1;
164         }
165       else
166         {
167           handle->state = UPNP_ERR;
168 #ifdef DEBUG
169           GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
170                       "UPNP_GetValidIGD failed (errno %d - %s)\n",
171                       errno, strerror (errno));
172 #endif
173         }
174       freeUPNPDevlist (devlist);
175     }
176
177   if (handle->state == UPNP_IDLE)
178     {
179       if (handle->is_mapped && !is_enabled)
180         handle->state = UPNP_UNMAP;
181     }
182
183   if (is_enabled && handle->is_mapped && doPortCheck)
184     {
185       char portStr[8];
186       char intPort[8];
187       char intClient[128];
188       int i;
189
190       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
191       i = UPNP_GetSpecificPortMappingEntry (handle->urls.controlURL,
192                                             handle->data.servicetype, portStr,
193                                             "TCP", intClient, intPort);
194       if (i != UPNPCOMMAND_SUCCESS)
195         {
196           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
197                       _("Port %d isn't forwarded\n"), handle->port);
198           handle->is_mapped = GNUNET_NO;
199         }
200     }
201
202   if (handle->state == UPNP_UNMAP)
203     {
204       char portStr[16];
205       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
206       UPNP_DeletePortMapping (handle->urls.controlURL,
207                               handle->data.servicetype, portStr, "TCP", NULL);
208       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
209                   _
210                   ("Stopping port forwarding through \"%s\", service \"%s\"\n"),
211                   handle->urls.controlURL, handle->data.servicetype);
212       handle->is_mapped = 0;
213       handle->state = UPNP_IDLE;
214       handle->port = -1;
215     }
216
217   if (handle->state == UPNP_IDLE)
218     {
219       if (is_enabled && !handle->is_mapped)
220         handle->state = UPNP_MAP;
221     }
222
223   if (handle->state == UPNP_MAP)
224     {
225       int err = -1;
226       errno = 0;
227
228       if (!handle->urls.controlURL)
229         handle->is_mapped = 0;
230       else
231         {
232           char portStr[16];
233           char desc[64];
234           GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
235           GNUNET_snprintf (desc, sizeof (desc), "GNUnet at %d", handle->port);
236           err = UPNP_AddPortMapping (handle->urls.controlURL,
237                                      handle->data.servicetype,
238                                      portStr, portStr, GNUNET_a2s (handle->addr, handle->addrlen),              
239                                      desc, "TCP", NULL);
240           handle->is_mapped = !err;
241         }
242       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
243                   _
244                   ("Port forwarding through \"%s\", service \"%s\"\n"),
245                   handle->urls.controlURL, handle->data.servicetype);
246       if (handle->is_mapped)
247         {
248           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
249                       _("Port %d forwarded successfully\n"), handle->port);
250           handle->state = UPNP_IDLE;
251         }
252       else
253         {
254           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
255                       "Port forwarding failed with error %d (errno %d - %s)\n",
256                       err, errno, strerror (errno));
257           handle->state = UPNP_ERR;
258         }
259     }
260
261   if (ext_addr && handle->state != UPNP_DISCOVER)
262     {
263       int err;
264       char addr_str[128];
265       struct in_addr addr;
266       struct in6_addr addr6;
267
268       /* Keep to NULL if address could not be found */
269       *ext_addr = NULL;
270       err = UPNP_GetExternalIPAddress (handle->urls.controlURL,
271                                        handle->data.servicetype, addr_str);
272       if (err == 0)
273         {
274           if (handle->ext_addr)
275             {
276               GNUNET_free (handle->ext_addr);
277               handle->ext_addr = NULL;
278             }
279
280           /* Try IPv4 and IPv6 as we don't know what's the format */
281           if (inet_aton (addr_str, &addr) != 0)
282             {
283               handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
284               handle->ext_addr->sa_family = AF_INET;
285               ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr;
286               *ext_addr = handle->ext_addr;
287             }
288           else if (inet_pton (AF_INET6, addr_str, &addr6) != 1)
289             {
290               handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
291               handle->ext_addr->sa_family = AF_INET6;
292               ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6;
293               *ext_addr = handle->ext_addr;
294             }
295           else
296             GNUNET_assert (GNUNET_YES);
297 #ifdef DEBUG
298           GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
299                       _("Found public IP address %s\n"),
300                       addr_str);
301 #endif
302         }
303       else
304         {
305           *ext_addr = NULL;
306           if (handle->ext_addr)
307             {
308               GNUNET_free (handle->ext_addr);
309               handle->ext_addr = NULL;
310             }
311 #ifdef DEBUG
312           GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
313                       "UPNP_GetExternalIPAddress failed (error %d)\n", err);
314 #endif
315         }
316     }
317
318   switch (handle->state)
319     {
320     case UPNP_DISCOVER:
321       ret = GNUNET_NAT_PORT_UNMAPPED;
322       break;
323
324     case UPNP_MAP:
325       ret = GNUNET_NAT_PORT_MAPPING;
326       break;
327
328     case UPNP_UNMAP:
329       ret = GNUNET_NAT_PORT_UNMAPPING;
330       break;
331
332     case UPNP_IDLE:
333       ret =
334         handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
335       break;
336
337     default:
338       ret = GNUNET_NAT_PORT_ERROR;
339       break;
340     }
341
342   return ret;
343 }