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