starting NAT code cleanup
[oweals/gnunet.git] / src / nat / nat.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010 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  * Parts of this file have 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/nat.c
29  * @brief Library handling UPnP and NAT-PMP port forwarding and
30  *     external IP address retrieval
31  *
32  * @author Milan Bouchet-Valat
33  */
34 #include "platform.h"
35 #include "gnunet_util_lib.h"
36 #include "gnunet_nat_lib.h"
37 #include "nat.h"
38 #include "natpmp.h"
39 #include "upnp.h"
40
41 /**
42  * Handle for active NAT registrations.
43  */
44 struct GNUNET_NAT_Handle
45 {
46   /**
47    * Handle for UPnP operations.
48    */
49   GNUNET_NAT_UPNP_Handle *upnp;
50
51   /**
52    * Handle for NAT PMP operations.
53    */
54   GNUNET_NAT_NATPMP_Handle *natpmp;
55
56   /**
57    * Scheduler.
58    */
59   struct GNUNET_SCHEDULER_Handle *sched;
60
61   /**
62    * LAN address as passed by the caller 
63    */
64   struct sockaddr *local_addr; 
65
66   /**
67    * External address as reported by NAT box 
68    */
69   struct sockaddr *ext_addr; 
70
71   /**
72    * External address and port where packets are redirected
73    */
74   struct sockaddr *contact_addr; 
75
76   GNUNET_NAT_AddressCallback callback;
77
78   /**
79    * Closure for 'callback'.
80    */
81   void *callback_cls;
82
83   GNUNET_SCHEDULER_TaskIdentifier pulse_timer;
84
85   enum GNUNET_NAT_PortState natpmp_status;
86
87   enum GNUNET_NAT_PortState upnp_status;
88
89   int is_enabled;
90
91   int should_change;
92
93   int port_mapped;
94
95   int did_warn;
96
97   uint16_t public_port;
98
99 };
100
101 #ifdef DEBUG
102 static const char *
103 get_nat_state_str (enum GNUNET_NAT_PortState state)
104 {
105   switch (state)
106     {
107     case GNUNET_NAT_PORT_MAPPING:
108       return "Starting";
109     case GNUNET_NAT_PORT_MAPPED:
110       return "Forwarded";
111     case GNUNET_NAT_PORT_UNMAPPING:
112       return "Stopping";
113     case GNUNET_NAT_PORT_UNMAPPED:
114       return "Not forwarded";
115     case GNUNET_NAT_PORT_ERROR:
116       return "Redirection failed";
117     default:
118       return "not found";
119     }
120 }
121 #endif
122
123
124 static int
125 get_traversal_status (const struct GNUNET_NAT_Handle * s)
126 {
127   return MAX (s->natpmp_status, s->upnp_status);
128 }
129
130
131 /**
132  * Compare the sin(6)_addr fields of AF_INET or AF_INET(6) sockaddr.
133  * @param a first sockaddr
134  * @param b second sockaddr
135  * @return 0 if addresses are equal, non-null value otherwise */
136 int
137 GNUNET_NAT_cmp_addr (const struct sockaddr *a, 
138                      const struct sockaddr *b)
139 {
140   if (!(a && b))
141     return -1;
142   if ( (a->sa_family == AF_INET) && (b->sa_family == AF_INET) )
143     return memcmp (&(((struct sockaddr_in *) a)->sin_addr),
144                    &(((struct sockaddr_in *) b)->sin_addr),
145                    sizeof (struct in_addr));
146   if ( (a->sa_family == AF_INET6) && (b->sa_family == AF_INET6) )
147     return memcmp (&(((struct sockaddr_in6 *) a)->sin6_addr),
148                    &(((struct sockaddr_in6 *) b)->sin6_addr),
149                    sizeof (struct in6_addr));
150   return -1;
151 }
152
153
154 /**
155  * Deal with a new IP address or port redirection:
156  * Send signals with the appropriate sockaddr (IP and port), free and changes
157  * or nullify the previous sockaddr. Change the port if needed.
158  */
159 static void
160 notify_change (struct GNUNET_NAT_Handle *nat,
161                struct sockaddr *addr, 
162                size_t addrlen,
163                int new_port_mapped)
164 {
165   if (new_port_mapped == nat->port_mapped)
166     return;
167   nat->port_mapped = new_port_mapped;
168
169   if ( (NULL != nat->contact_addr) &&
170        (NULL != nat->callback) )
171     nat->callback (nat->callback_cls, 
172                    GNUNET_NO, 
173                    nat->contact_addr,
174                    sizeof (nat->contact_addr));
175   GNUNET_free_non_null (nat->contact_addr);
176   nat->contact_addr = NULL;
177   GNUNET_free_non_null (nat->ext_addr);
178   nat->ext_addr = NULL;
179   if (NULL == addr)
180     return;    
181   nat->ext_addr = GNUNET_malloc (addrlen);
182   memcpy (nat->ext_addr, addr, addrlen);
183
184   /* Recreate the ext_addr:public_port bogus address to pass to the callback */
185   if (nat->ext_addr->sa_family == AF_INET)
186     {
187       struct sockaddr_in tmp_addr;
188
189       tmp_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
190       tmp_addr->sin_family = AF_INET;
191 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
192       tmp_addr->sin_len = sizeof (struct sockaddr_in);
193 #endif
194       tmp_addr->sin_port = port_mapped ? htons (nat->public_port) : 0;
195       tmp_addr->sin_addr = ((struct sockaddr_in *) nat->ext_addr)->sin_addr;
196       nat->contact_addr = (struct sockaddr *) tmp_addr;
197       if (NULL != nat->callback)
198         nat->callback (nat->callback_cls, 
199                        GNUNET_YES, 
200                        nat->contact_addr,
201                        sizeof (struct sockaddr_in));
202     }
203   else if (nat->ext_addr->sa_family == AF_INET6)
204     {
205       struct sockaddr_in6 *tmp_addr;
206
207       tmp_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
208       tmp_addr->sin6_family = AF_INET6;
209 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
210       tmp_addr->sin6_len = sizeof (struct sockaddr_in6);
211 #endif
212       tmp_addr->sin6_port = port_mapped ? htons (nat->public_port) : 0;
213       tmp_addr->sin6_addr = ((struct sockaddr_in6 *) nat->ext_addr)->sin6_addr;
214       nat->contact_addr = (struct sockaddr *) tmp_addr;
215       if (NULL != nat->callback)
216         nat->callback (nat->callback_cls,
217                        GNUNET_YES, 
218                        nat->contact_addr,
219                        sizeof (struct sockaddr_in6));
220     }
221   else
222     {
223       GNUNET_break (0);
224     }
225 }
226
227
228 static void
229 nat_pulse (void *cls,
230            const struct GNUNET_SCHEDULER_TaskContext *tc)
231 {
232   struct GNUNET_NAT_Handle *nat = cls;
233   int old_status;
234   int new_status;
235   int port_mapped;
236   struct sockaddr *ext_addr_upnp = NULL;
237   struct sockaddr *ext_addr_natpmp = NULL;
238
239   nat->pulse_timer = GNUNET_SCHEDULER_NO_TASK;
240   old_status = get_traversal_status (nat);
241
242   /* Only update the protocol that has been successful until now */
243   if (nat->upnp_status >= GNUNET_NAT_PORT_UNMAPPED)
244     nat->upnp_status =
245       GNUNET_NAT_UPNP_pulse (nat->upnp, nat->is_enabled, GNUNET_YES,
246                              &ext_addr_upnp);
247   else if (nat->natpmp_status >= GNUNET_NAT_PORT_UNMAPPED)
248     nat->natpmp_status =
249       GNUNET_NAT_NATPMP_pulse (nat->natpmp, nat->is_enabled,
250                                &ext_addr_natpmp);
251   else
252     {
253       /* try both */
254       nat->upnp_status =
255         GNUNET_NAT_UPNP_pulse (nat->upnp, nat->is_enabled, GNUNET_YES,
256                                &ext_addr_upnp);
257       nat->natpmp_status =
258         GNUNET_NAT_NATPMP_pulse (nat->natpmp, nat->is_enabled,
259                                  &ext_addr_natpmp);
260     }
261   new_status = get_traversal_status (nat);
262   if ( (old_status != new_status) &&
263        ( (new_status == GNUNET_NAT_PORT_UNMAPPED) || 
264          (new_status == GNUNET_NAT_PORT_ERROR) ) )
265     GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
266                      "NAT",
267                      _("Port redirection failed: no UPnP or NAT-PMP routers supporting this feature found\n"));
268 #ifdef DEBUG
269   if (new_status != old_status)
270     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "NAT",
271                      _("State changed from `%s' to `%s'\n"),
272                      get_nat_state_str (old_status),
273                      get_nat_state_str (new_status));
274 #endif
275
276   port_mapped = (new_status == GNUNET_NAT_PORT_MAPPED);
277   if (!(ext_addr_upnp || ext_addr_natpmp))
278     {
279       /* Address has just changed and we could not get it, or it's the first try */
280       if ( (NULL != nat->ext_addr) || 
281            (GNUNET_NO == nat->did_warn) )
282         {
283           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, 
284                            "NAT",
285                            _("Could not determine external IP address\n"));
286           nat->did_warn = GNUNET_YES;
287         }
288       notify_change (nat, NULL, port_mapped);
289     }
290   else if (ext_addr_upnp && GNUNET_NAT_cmp_addr (nat->ext_addr, ext_addr_upnp) != 0)
291     {
292       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
293                        "NAT",
294                        _("External IP address changed to %s\n"),
295                        GNUNET_a2s (ext_addr_upnp, sizeof (ext_addr_upnp)));
296       notify_change (nat, ext_addr_upnp, port_mapped);
297     }
298   else if (ext_addr_natpmp && GNUNET_NAT_cmp_addr (nat->ext_addr, ext_addr_natpmp) != 0)
299     {
300       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "NAT",
301                        _("External IP address changed to `%s'\n"),
302                        GNUNET_a2s (ext_addr_natpmp, sizeof (ext_addr_natpmp)));      
303       notify_change (nat, ext_addr_natpmp, port_mapped);
304     }
305   nat->pulse_timer = GNUNET_SCHEDULER_add_delayed (nat->sched, 
306                                                    GNUNET_TIME_UNIT_SECONDS,
307                                                    &nat_pulse, nat);
308 }
309
310
311 /**
312  * Attempt to enable port redirection and detect public IP address contacting
313  * UPnP or NAT-PMP routers on the local network. Use addr to specify to which
314  * of the local host's addresses should the external port be mapped. The port
315  * is taken from the corresponding sockaddr_in[6] field.
316  *
317  * @param sched the sheduler used in the program
318  * @param addr the local address packets should be redirected to
319  * @param addrlen actual lenght of the address
320  * @param callback function to call everytime the public IP address changes
321  * @param callback_cls closure for callback
322  * @return NULL on error, otherwise handle that can be used to unregister 
323  */
324 struct GNUNET_NAT_Handle *
325 GNUNET_NAT_register (struct GNUNET_SCHEDULER_Handle *sched,
326                      const struct sockaddr *addr, socklen_t addrlen,
327                      GNUNET_NAT_AddressCallback callback, void *callback_cls)
328 {
329   struct GNUNET_NAT_Handle *nat;
330
331   nat = GNUNET_malloc (sizeof (struct GNUNET_NAT_Handle));
332   if (addr)
333     {
334       GNUNET_assert ( (addr->sa_family == AF_INET) ||
335                       (addr->sa_family == AF_INET6) );
336       nat->local_addr = GNUNET_malloc (addrlen);
337       memcpy (nat->local_addr, addr, addrlen);
338       if (addr->sa_family == AF_INET)
339         {
340           nat->public_port = ntohs (((struct sockaddr_in *) addr)->sin_port);
341           ((struct sockaddr_in *) nat->local_addr)->sin_port = 0;
342         }
343       else if (addr->sa_family == AF_INET6)
344         {
345           nat->public_port = ntohs (((struct sockaddr_in6 *) addr)->sin6_port);
346           ((struct sockaddr_in6 *) nat->local_addr)->sin6_port = 0;
347         }
348     }
349   nat->should_change = GNUNET_YES;
350   nat->sched = sched;
351   nat->is_enabled = GNUNET_YES;
352   nat->upnp_status = GNUNET_NAT_PORT_UNMAPPED;
353   nat->natpmp_status = GNUNET_NAT_PORT_UNMAPPED;
354   nat->callback = callback;
355   nat->callback_cls = callback_cls;
356   nat->natpmp = GNUNET_NAT_NATPMP_init (nat->local_addr, addrlen, nat->public_port);
357   nat->upnp = GNUNET_NAT_UPNP_init (nat->local_addr, addrlen, nat->public_port);
358   nat->pulse_timer = GNUNET_SCHEDULER_add_delayed (sched, 
359                                                    GNUNET_TIME_UNIT_SECONDS,
360                                                    &nat_pulse, nat);
361   return nat;
362 }
363
364
365 /**
366  * Stop port redirection and public IP address detection for the given handle.
367  * This frees the handle, after having sent the needed commands to close open ports.
368  *
369  * @param h the handle to stop
370  */
371 void
372 GNUNET_NAT_unregister (struct GNUNET_NAT_Handle *nat)
373 {
374   struct sockaddr *addr;
375
376   GNUNET_SCHEDULER_cancel (nat->sched, 
377                            nat->pulse_timer);
378   nat->upnp_status =
379     GNUNET_NAT_UPNP_pulse (nat->upnp, 
380                            GNUNET_NO, GNUNET_NO,
381                            &addr);
382   nat->natpmp_status =
383     GNUNET_NAT_NATPMP_pulse (nat->natpmp, GNUNET_NO,
384                              &addr);
385   GNUNET_NAT_NATPMP_close (nat->natpmp);
386   GNUNET_NAT_UPNP_close (nat->upnp);
387   GNUNET_free_non_null (nat->local_addr);
388   GNUNET_free_non_null (nat->ext_addr);
389   GNUNET_free (nat);
390 }
391
392 /* end of nat.c */
393