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