2 This file is part of GNUnet.
3 (C) 2009, 2010 Christian Grothoff (and other contributing authors)
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.
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.
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.
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>
29 * @brief Library handling UPnP and NAT-PMP port forwarding and
30 * external IP address retrieval
32 * @author Milan Bouchet-Valat
35 #include "gnunet_util_lib.h"
36 #include "gnunet_nat_lib.h"
42 * Handle for active NAT registrations.
44 struct GNUNET_NAT_Handle
47 * Handle for UPnP operations.
49 struct GNUNET_NAT_UPNP_Handle *upnp;
52 * Handle for NAT PMP operations.
54 struct GNUNET_NAT_NATPMP_Handle *natpmp;
59 struct GNUNET_SCHEDULER_Handle *sched;
62 * LAN address as passed by the caller
64 struct sockaddr *local_addr;
67 * External address as reported by found NAT box
69 struct sockaddr *ext_addr;
72 * External address as reported by each type of NAT box
74 struct sockaddr *ext_addr_upnp;
75 struct sockaddr *ext_addr_natpmp;
78 * External address and port where packets are redirected
80 struct sockaddr *contact_addr;
82 GNUNET_NAT_AddressCallback callback;
85 * Closure for 'callback'.
89 GNUNET_SCHEDULER_TaskIdentifier pulse_timer;
91 enum GNUNET_NAT_PortState natpmp_status;
93 enum GNUNET_NAT_PortState upnp_status;
109 uint16_t public_port;
115 get_nat_state_str (enum GNUNET_NAT_PortState state)
119 case GNUNET_NAT_PORT_MAPPING:
121 case GNUNET_NAT_PORT_MAPPED:
123 case GNUNET_NAT_PORT_UNMAPPING:
125 case GNUNET_NAT_PORT_UNMAPPED:
126 return "Not forwarded";
127 case GNUNET_NAT_PORT_ERROR:
128 return "Redirection failed";
137 get_traversal_status (const struct GNUNET_NAT_Handle *h)
139 return MAX (h->natpmp_status, h->upnp_status);
144 * Compare the sin(6)_addr fields of AF_INET or AF_INET(6) sockaddr.
145 * @param a first sockaddr
146 * @param b second sockaddr
147 * @return 0 if addresses are equal, non-null value otherwise */
149 GNUNET_NAT_cmp_addr (const struct sockaddr *a, const struct sockaddr *b)
153 if ((a->sa_family == AF_INET) && (b->sa_family == AF_INET))
154 return memcmp (&(((struct sockaddr_in *) a)->sin_addr),
155 &(((struct sockaddr_in *) b)->sin_addr),
156 sizeof (struct in_addr));
157 if ((a->sa_family == AF_INET6) && (b->sa_family == AF_INET6))
158 return memcmp (&(((struct sockaddr_in6 *) a)->sin6_addr),
159 &(((struct sockaddr_in6 *) b)->sin6_addr),
160 sizeof (struct in6_addr));
166 * Deal with a new IP address or port redirection:
167 * Send signals with the appropriate sockaddr (IP and port), free and changes
168 * or nullify the previous sockaddr. Change the port if needed.
171 notify_change (struct GNUNET_NAT_Handle *h,
172 struct sockaddr *addr, size_t addrlen, int new_port_mapped)
174 if (new_port_mapped == h->port_mapped)
176 h->port_mapped = new_port_mapped;
178 if ((NULL != h->contact_addr) && (NULL != h->callback))
179 h->callback (h->callback_cls,
180 GNUNET_NO, h->contact_addr, sizeof (h->contact_addr));
181 GNUNET_free_non_null (h->contact_addr);
182 h->contact_addr = NULL;
183 GNUNET_free_non_null (h->ext_addr);
187 h->ext_addr = GNUNET_malloc (addrlen);
188 memcpy (h->ext_addr, addr, addrlen);
190 /* Recreate the ext_addr:public_port bogus address to pass to the callback */
191 if (h->ext_addr->sa_family == AF_INET)
193 struct sockaddr_in *tmp_addr;
195 tmp_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
196 tmp_addr->sin_family = AF_INET;
197 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
198 tmp_addr->sin_len = sizeof (struct sockaddr_in);
200 tmp_addr->sin_port = h->port_mapped ? htons (h->public_port) : 0;
201 tmp_addr->sin_addr = ((struct sockaddr_in *) h->ext_addr)->sin_addr;
202 h->contact_addr = (struct sockaddr *) tmp_addr;
204 if (NULL != h->callback)
205 h->callback (h->callback_cls,
207 h->contact_addr, sizeof (struct sockaddr_in));
209 else if (h->ext_addr->sa_family == AF_INET6)
211 struct sockaddr_in6 *tmp_addr;
213 tmp_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
214 tmp_addr->sin6_family = AF_INET6;
215 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
216 tmp_addr->sin6_len = sizeof (struct sockaddr_in6);
218 tmp_addr->sin6_port = h->port_mapped ? htons (h->public_port) : 0;
219 tmp_addr->sin6_addr = ((struct sockaddr_in6 *) h->ext_addr)->sin6_addr;
220 h->contact_addr = (struct sockaddr *) tmp_addr;
222 if (NULL != h->callback)
223 h->callback (h->callback_cls,
225 h->contact_addr, sizeof (struct sockaddr_in6));
233 static void nat_pulse (void *cls,
234 const struct GNUNET_SCHEDULER_TaskContext *tc);
237 pulse_cb (struct GNUNET_NAT_Handle *h)
242 /* One of the protocols is still working, wait for it to complete */
246 h->new_status = get_traversal_status (h);
247 if ((h->old_status != h->new_status) &&
248 ((h->new_status == GNUNET_NAT_PORT_UNMAPPED) ||
249 (h->new_status == GNUNET_NAT_PORT_ERROR)))
250 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
253 ("Port redirection failed: no UPnP or NAT-PMP routers supporting this feature found\n"));
255 if (h->new_status != h->old_status)
256 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "NAT",
257 _("State changed from `%s' to `%s'\n"),
258 get_nat_state_str (h->old_status),
259 get_nat_state_str (h->new_status));
262 port_mapped = (h->new_status == GNUNET_NAT_PORT_MAPPED);
263 if (!(h->ext_addr_upnp || h->ext_addr_natpmp))
265 /* Address has just changed and we could not get it, or it's the first try,
266 * and we're not waiting for a reply from UPnP or NAT-PMP */
267 if (((NULL != h->ext_addr) ||
268 (GNUNET_NO == h->did_warn)) && h->processing != 0)
270 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
272 _("Could not determine external IP address\n"));
273 h->did_warn = GNUNET_YES;
275 notify_change (h, NULL, 0, port_mapped);
277 else if (h->ext_addr_upnp
278 && GNUNET_NAT_cmp_addr (h->ext_addr, h->ext_addr_upnp) != 0)
280 addrlen = h->ext_addr_upnp->sa_family == AF_INET ?
281 sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6);
282 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
284 _("External IP address changed to %s\n"),
285 GNUNET_a2s (h->ext_addr_upnp, addrlen));
286 notify_change (h, h->ext_addr_upnp, addrlen, port_mapped);
288 else if (h->ext_addr_natpmp
289 && GNUNET_NAT_cmp_addr (h->ext_addr, h->ext_addr_natpmp) != 0)
291 addrlen = h->ext_addr_natpmp->sa_family == AF_INET ?
292 sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6);
293 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "NAT",
294 _("External IP address changed to `%s'\n"),
295 GNUNET_a2s (h->ext_addr_natpmp, addrlen));
296 notify_change (h, h->ext_addr_natpmp, addrlen, port_mapped);
299 h->pulse_timer = GNUNET_SCHEDULER_add_delayed (h->sched,
300 GNUNET_TIME_UNIT_SECONDS,
305 upnp_pulse_cb (int status, struct sockaddr *ext_addr, void *cls)
307 struct GNUNET_NAT_Handle *h = cls;
309 h->upnp_status = status;
310 h->ext_addr_upnp = ext_addr;
318 natpmp_pulse_cb (int status, struct sockaddr *ext_addr, void *cls)
320 struct GNUNET_NAT_Handle *h = cls;
322 h->natpmp_status = status;
323 h->ext_addr_natpmp = ext_addr;
331 nat_pulse (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
333 struct GNUNET_NAT_Handle *h = cls;
335 /* Stop if we're already waiting for an action to complete */
336 h->pulse_timer = GNUNET_SCHEDULER_NO_TASK;
339 h->old_status = get_traversal_status (h);
341 /* Only update the protocol that has been successful until now */
342 if (h->upnp_status >= GNUNET_NAT_PORT_UNMAPPED)
345 GNUNET_NAT_UPNP_pulse (h->upnp, h->is_enabled, GNUNET_YES);
347 /* Wait for the callback to call pulse_cb() to handle changes */
350 else if (h->natpmp_status >= GNUNET_NAT_PORT_UNMAPPED)
354 GNUNET_NAT_NATPMP_pulse (h->natpmp, h->is_enabled);
361 GNUNET_NAT_UPNP_pulse (h->upnp, h->is_enabled, GNUNET_YES);
363 GNUNET_NAT_NATPMP_pulse (h->natpmp, h->is_enabled, &natpmp_pulse_cb, h);
370 * Attempt to enable port redirection and detect public IP address contacting
371 * UPnP or NAT-PMP routers on the local network. Use addr to specify to which
372 * of the local host's addresses should the external port be mapped. The port
373 * is taken from the corresponding sockaddr_in[6] field.
375 * @param sched the sheduler used in the program
376 * @param addr the local address packets should be redirected to
377 * @param addrlen actual lenght of the address
378 * @param callback function to call everytime the public IP address changes
379 * @param callback_cls closure for callback
380 * @return NULL on error, otherwise handle that can be used to unregister
382 struct GNUNET_NAT_Handle *
383 GNUNET_NAT_register (struct GNUNET_SCHEDULER_Handle
385 const struct sockaddr *addr,
387 GNUNET_NAT_AddressCallback callback, void *callback_cls)
389 struct GNUNET_NAT_Handle *h;
391 h = GNUNET_malloc (sizeof (struct GNUNET_NAT_Handle));
395 GNUNET_assert ((addr->sa_family == AF_INET) ||
396 (addr->sa_family == AF_INET6));
397 h->local_addr = GNUNET_malloc (addrlen);
398 memcpy (h->local_addr, addr, addrlen);
399 if (addr->sa_family == AF_INET)
401 h->public_port = ntohs (((struct sockaddr_in *) addr)->sin_port);
402 ((struct sockaddr_in *) h->local_addr)->sin_port = 0;
404 else if (addr->sa_family == AF_INET6)
406 h->public_port = ntohs (((struct sockaddr_in6 *) addr)->sin6_port);
407 ((struct sockaddr_in6 *) h->local_addr)->sin6_port = 0;
410 h->should_change = GNUNET_YES;
412 h->is_enabled = GNUNET_YES;
413 h->upnp_status = GNUNET_NAT_PORT_UNMAPPED;
414 h->natpmp_status = GNUNET_NAT_PORT_UNMAPPED;
415 h->callback = callback;
416 h->callback_cls = callback_cls;
418 GNUNET_NAT_UPNP_init (h->sched, h->local_addr, addrlen, h->public_port,
422 GNUNET_NAT_NATPMP_init (h->sched, h->local_addr, addrlen, h->public_port,
423 &natpmp_pulse_cb, h);
425 h->pulse_timer = GNUNET_SCHEDULER_add_delayed (sched,
426 GNUNET_TIME_UNIT_SECONDS,
433 * Stop port redirection and public IP address detection for the given handle.
434 * This frees the handle, after having sent the needed commands to close open ports.
436 * @param h the handle to stop
439 GNUNET_NAT_unregister (struct GNUNET_NAT_Handle *h)
441 GNUNET_NAT_UPNP_pulse (h->upnp, GNUNET_NO, GNUNET_NO);
442 GNUNET_NAT_UPNP_close (h->upnp);
445 GNUNET_NAT_NATPMP_pulse (h->natpmp, GNUNET_NO);
446 GNUNET_NAT_NATPMP_close (h->natpmp);
449 if (GNUNET_SCHEDULER_NO_TASK != h->pulse_timer)
450 GNUNET_SCHEDULER_cancel (h->sched, h->pulse_timer);
452 GNUNET_free_non_null (h->local_addr);
453 GNUNET_free_non_null (h->ext_addr);
454 GNUNET_free_non_null (h->ext_addr_upnp);
455 GNUNET_free_non_null (h->ext_addr_natpmp);