Refactoring gnunet time
[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   struct GNUNET_SCHEDULER_Handle *sched;
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   const 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
83   if (addr && GNUNET_NAT_cmp_addr (upnp->addr, addr) == 0)
84     {
85       upnp->iface = 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 (struct GNUNET_SCHEDULER_Handle *sched,
95                       const struct sockaddr *addr,
96                       socklen_t addrlen,
97                       u_short port,
98                       GNUNET_NAT_UPNP_pulse_cb pulse_cb, void *pulse_cls)
99 {
100   struct GNUNET_NAT_UPNP_Handle *handle;
101
102   handle = GNUNET_malloc (sizeof (struct GNUNET_NAT_UPNP_Handle));
103   handle->sched = sched;
104   handle->processing = GNUNET_NO;
105   handle->state = UPNP_DISCOVER;
106   handle->addr = addr;
107   handle->addrlen = addrlen;
108   handle->port = port;
109   handle->pulse_cb = pulse_cb;
110   handle->pulse_cls = pulse_cls;
111   handle->control_url = NULL;
112   handle->service_type = NULL;
113
114   /* Find the interface corresponding to the address,
115    * on which we should broadcast call for routers */
116   GNUNET_OS_network_interfaces_list (&process_if, handle);
117   if (!handle->iface)
118     GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
119                      COMP_NAT_UPNP,
120                      "Could not find an interface matching the wanted address.\n");
121   return handle;
122 }
123
124
125 void
126 GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle *handle)
127 {
128   GNUNET_assert (!handle->is_mapped);
129   GNUNET_assert ((handle->state == UPNP_IDLE)
130                  || (handle->state == UPNP_ERR)
131                  || (handle->state == UPNP_DISCOVER));
132
133   GNUNET_free_non_null (handle->control_url);
134   GNUNET_free_non_null (handle->service_type);
135   GNUNET_free (handle);
136 }
137
138 static void
139 pulse_finish (struct GNUNET_NAT_UPNP_Handle *handle)
140 {
141   enum GNUNET_NAT_PortState status;
142   handle->processing = GNUNET_NO;
143
144   switch (handle->state)
145     {
146     case UPNP_DISCOVER:
147       status = GNUNET_NAT_PORT_UNMAPPED;
148       break;
149
150     case UPNP_MAP:
151       status = GNUNET_NAT_PORT_MAPPING;
152       break;
153
154     case UPNP_UNMAP:
155       status = GNUNET_NAT_PORT_UNMAPPING;
156       break;
157
158     case UPNP_IDLE:
159       status =
160         handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
161       break;
162
163     default:
164       status = GNUNET_NAT_PORT_ERROR;
165       break;
166     }
167
168   handle->pulse_cb (status, handle->ext_addr, handle->pulse_cls);
169 }
170
171 static void
172 discover_cb (const char *control_url, const char *service_type, void *cls)
173 {
174   struct GNUNET_NAT_UPNP_Handle *handle = cls;
175
176   if (control_url)
177     {
178       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
179                        _("Found Internet Gateway Device \"%s\"\n"),
180                        control_url);
181
182       GNUNET_free_non_null (handle->control_url);
183       GNUNET_free_non_null (handle->service_type);
184
185       handle->control_url = GNUNET_strdup (control_url);
186       handle->service_type = GNUNET_strdup (service_type);
187       handle->state = UPNP_IDLE;
188       handle->hasDiscovered = 1;
189     }
190   else
191     {
192       handle->control_url = NULL;
193       handle->service_type = NULL;
194       handle->state = UPNP_ERR;
195 #ifdef DEBUG_UPNP
196       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
197                        "UPNP device discovery failed\n");
198 #endif
199     }
200
201   pulse_finish (handle);
202 }
203
204 static void
205 check_port_mapping_cb (int error, const char *control_url,
206                        const char *service_type, const char *extPort,
207                        const char *inPort, const char *proto,
208                        const char *remoteHost, void *cls)
209 {
210   struct GNUNET_NAT_UPNP_Handle *handle = cls;
211
212   if (error)
213     {
214       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
215                        _("Port %d isn't forwarded\n"), handle->port);
216       handle->is_mapped = GNUNET_NO;
217     }
218
219   pulse_finish (handle);
220 }
221
222 static void
223 delete_port_mapping_cb (int error, const char *control_url,
224                         const char *service_type, const char *extPort,
225                         const char *inPort, const char *proto,
226                         const char *remoteHost, void *cls)
227 {
228   struct GNUNET_NAT_UPNP_Handle *handle = cls;
229
230   if (error)
231     GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
232                      _
233                      ("Could not stop port forwarding through \"%s\", service \"%s\": error %d\n"),
234                      handle->control_url, handle->service_type, error);
235   else
236     {
237       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
238                        _
239                        ("Stopped port forwarding through \"%s\", service \"%s\"\n"),
240                        handle->control_url, handle->service_type);
241       handle->is_mapped = !error;
242       handle->state = UPNP_IDLE;
243       handle->port = -1;
244     }
245
246   pulse_finish (handle);
247 }
248
249 static void
250 add_port_mapping_cb (int error, const char *control_url,
251                      const char *service_type, const char *extPort,
252                      const char *inPort, const char *proto,
253                      const char *remoteHost, void *cls)
254 {
255   struct GNUNET_NAT_UPNP_Handle *handle = cls;
256
257   if (error)
258     {
259       handle->is_mapped = GNUNET_NO;
260       handle->state = UPNP_ERR;
261       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
262                        _
263                        ("Port forwarding through \"%s\", service \"%s\" failed with error %d\n"),
264                        handle->control_url, handle->service_type, error);
265       return;
266     }
267   else
268     {
269       handle->is_mapped = GNUNET_NO;
270       handle->state = UPNP_IDLE;
271       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP,
272                        _("Port %d forwarded successfully\n"), handle->port);
273     }
274
275   pulse_finish (handle);
276 }
277
278 static void
279 get_ip_address_cb (int error, char *ext_addr, void *cls)
280 {
281   struct GNUNET_NAT_UPNP_Handle *handle = cls;
282
283   if (error)
284     {
285       if (handle->ext_addr)
286         {
287           GNUNET_free (handle->ext_addr);
288           handle->ext_addr = NULL;
289         }
290 #ifdef DEBUG_UPNP
291       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
292                        "UPNP_get_external_ip_address_ failed (error %d)\n",
293                        error);
294 #endif
295     }
296   else
297     {
298       struct in_addr addr;
299       struct in6_addr addr6;
300
301       if (handle->ext_addr)
302         {
303           GNUNET_free (handle->ext_addr);
304           handle->ext_addr = NULL;
305         }
306
307       /* Try IPv4 and IPv6 as we don't know what's the format */
308       if (inet_aton (ext_addr, &addr) != 0)
309         {
310           handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
311           handle->ext_addr->sa_family = AF_INET;
312           ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr;
313         }
314       else if (inet_pton (AF_INET6, ext_addr, &addr6) != 1)
315         {
316           handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
317           handle->ext_addr->sa_family = AF_INET6;
318           ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6;
319         }
320       else
321         GNUNET_assert (GNUNET_YES);
322
323 #ifdef DEBUG_UPNP
324       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP,
325                        _("Found public IP address %s\n"), ext_addr);
326 #endif
327     }
328
329   pulse_finish (handle);
330 }
331
332 /**
333  * Check state of UPnP NAT: port redirection, external IP address.
334  * 
335  * 
336  * @param handle the handle for UPnP object
337  * @param is_enabled whether enable port redirection
338  * @param doPortCheck FIXME
339  * @param ext_addr pointer for returning external IP address.
340  *     Will be set to NULL if address could not be found. Don't free the sockaddr.
341  */
342 void
343 GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *handle,
344                        int is_enabled, int doPortCheck)
345 {
346   /* Stop if we're already waiting for an action to complete */
347   if (handle->processing == GNUNET_YES)
348     return;
349
350   if (is_enabled && (handle->state == UPNP_DISCOVER))
351     {
352       handle->processing = GNUNET_YES;
353       UPNP_discover_ (handle->sched, handle->iface, handle->addr, discover_cb,
354                       handle);
355     }
356
357   if (handle->state == UPNP_IDLE)
358     {
359       if (handle->is_mapped && !is_enabled)
360         handle->state = UPNP_UNMAP;
361     }
362
363   if (is_enabled && handle->is_mapped && doPortCheck)
364     {
365       char portStr[8];
366
367       GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port);
368
369       handle->processing = GNUNET_YES;
370       UPNP_get_specific_port_mapping_entry_ (handle->sched,
371                                              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->sched, 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->sched, 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->sched, handle->control_url,
419                                      handle->service_type,
420                                      get_ip_address_cb, handle);
421     }
422 }