From: Moon Date: Sat, 9 Oct 2010 13:53:47 +0000 (+0000) Subject: rework UPnP code to use GNUnet scheduler and network API X-Git-Tag: initial-import-from-subversion-38251~20084 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=922a0672749ba9d496d1dd8f6596bb4f8035e71d;p=oweals%2Fgnunet.git rework UPnP code to use GNUnet scheduler and network API disable NAT-PMP support for now --- diff --git a/src/nat/Makefile.am b/src/nat/Makefile.am index c47348b7c..1b53095f3 100644 --- a/src/nat/Makefile.am +++ b/src/nat/Makefile.am @@ -1,5 +1,3 @@ -SUBDIRS = . - INCLUDES = -I$(top_srcdir)/src/include if MINGW @@ -18,11 +16,16 @@ endif libgnunetnat_la_SOURCES = \ upnp.c upnp.h \ - natpmp.c natpmp.h \ + upnp-commands.c upnp-commands.h \ + upnp-discover.c upnp-discover.h \ + upnp-igd-parse.c upnp-igd-parse.h \ + upnp-minixml.c upnp-minixml.h \ + upnp-reply-parse.c upnp-reply-parse.h bsdqueue.h \ nat.c libgnunetnat_la_CFLAGS = \ - -I$(top_scrdir)/include + -I$(top_scrdir)/include \ + -DDEBUG_UPNP -g -O0 libgnunetnat_la_LIBADD = \ $(top_builddir)/src/util/libgnunetutil.la \ @@ -46,6 +49,6 @@ test_nat_SOURCES = \ test_nat_LDADD = \ $(top_builddir)/src/nat/libgnunetnat.la \ - $(top_builddir)/src/util/libgnunetutil.la - + $(top_builddir)/src/util/libgnunetutil.la \ + @LIBCURL@ endif diff --git a/src/nat/bsdqueue.h b/src/nat/bsdqueue.h new file mode 100644 index 000000000..bdef3ac0b --- /dev/null +++ b/src/nat/bsdqueue.h @@ -0,0 +1,157 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file nat/bsd-queue.h + * @brief BSD implementation of simple lists + * + * @author Milan Bouchet-Valat + */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef BSD_QUEUE_H +#define BSD_QUEUE_H + +/* + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + */ + +#ifdef QUEUE_MACRO_DEBUG +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#endif + +/* end of bsd-queue.h */ diff --git a/src/nat/nat.c b/src/nat/nat.c index 794d9405c..5159e0903 100644 --- a/src/nat/nat.c +++ b/src/nat/nat.c @@ -46,12 +46,12 @@ struct GNUNET_NAT_Handle /** * Handle for UPnP operations. */ - GNUNET_NAT_UPNP_Handle *upnp; + struct GNUNET_NAT_UPNP_Handle *upnp; /** * Handle for NAT PMP operations. */ - GNUNET_NAT_NATPMP_Handle *natpmp; + struct GNUNET_NAT_NATPMP_Handle *natpmp; /** * Scheduler. @@ -61,17 +61,23 @@ struct GNUNET_NAT_Handle /** * LAN address as passed by the caller */ - struct sockaddr *local_addr; + struct sockaddr *local_addr; /** - * External address as reported by NAT box + * External address as reported by found NAT box */ - struct sockaddr *ext_addr; + struct sockaddr *ext_addr; + + /** + * External address as reported by each type of NAT box + */ + struct sockaddr *ext_addr_upnp; + struct sockaddr *ext_addr_natpmp; /** * External address and port where packets are redirected */ - struct sockaddr *contact_addr; + struct sockaddr *contact_addr; GNUNET_NAT_AddressCallback callback; @@ -92,8 +98,14 @@ struct GNUNET_NAT_Handle int port_mapped; + int old_status; + + int new_status; + int did_warn; + int processing; + uint16_t public_port; }; @@ -122,9 +134,9 @@ get_nat_state_str (enum GNUNET_NAT_PortState state) static int -get_traversal_status (const struct GNUNET_NAT_Handle * s) +get_traversal_status (const struct GNUNET_NAT_Handle *h) { - return MAX (s->natpmp_status, s->upnp_status); + return MAX (h->natpmp_status, h->upnp_status); } @@ -134,16 +146,15 @@ get_traversal_status (const struct GNUNET_NAT_Handle * s) * @param b second sockaddr * @return 0 if addresses are equal, non-null value otherwise */ int -GNUNET_NAT_cmp_addr (const struct sockaddr *a, - const struct sockaddr *b) +GNUNET_NAT_cmp_addr (const struct sockaddr *a, const struct sockaddr *b) { if (!(a && b)) return -1; - if ( (a->sa_family == AF_INET) && (b->sa_family == AF_INET) ) + if ((a->sa_family == AF_INET) && (b->sa_family == AF_INET)) return memcmp (&(((struct sockaddr_in *) a)->sin_addr), &(((struct sockaddr_in *) b)->sin_addr), sizeof (struct in_addr)); - if ( (a->sa_family == AF_INET6) && (b->sa_family == AF_INET6) ) + if ((a->sa_family == AF_INET6) && (b->sa_family == AF_INET6)) return memcmp (&(((struct sockaddr_in6 *) a)->sin6_addr), &(((struct sockaddr_in6 *) b)->sin6_addr), sizeof (struct in6_addr)); @@ -157,50 +168,45 @@ GNUNET_NAT_cmp_addr (const struct sockaddr *a, * or nullify the previous sockaddr. Change the port if needed. */ static void -notify_change (struct GNUNET_NAT_Handle *nat, - struct sockaddr *addr, - size_t addrlen, - int new_port_mapped) +notify_change (struct GNUNET_NAT_Handle *h, + struct sockaddr *addr, size_t addrlen, int new_port_mapped) { - if (new_port_mapped == nat->port_mapped) + if (new_port_mapped == h->port_mapped) return; - nat->port_mapped = new_port_mapped; - - if ( (NULL != nat->contact_addr) && - (NULL != nat->callback) ) - nat->callback (nat->callback_cls, - GNUNET_NO, - nat->contact_addr, - sizeof (nat->contact_addr)); - GNUNET_free_non_null (nat->contact_addr); - nat->contact_addr = NULL; - GNUNET_free_non_null (nat->ext_addr); - nat->ext_addr = NULL; + h->port_mapped = new_port_mapped; + + if ((NULL != h->contact_addr) && (NULL != h->callback)) + h->callback (h->callback_cls, + GNUNET_NO, h->contact_addr, sizeof (h->contact_addr)); + GNUNET_free_non_null (h->contact_addr); + h->contact_addr = NULL; + GNUNET_free_non_null (h->ext_addr); + h->ext_addr = NULL; if (NULL == addr) - return; - nat->ext_addr = GNUNET_malloc (addrlen); - memcpy (nat->ext_addr, addr, addrlen); + return; + h->ext_addr = GNUNET_malloc (addrlen); + memcpy (h->ext_addr, addr, addrlen); /* Recreate the ext_addr:public_port bogus address to pass to the callback */ - if (nat->ext_addr->sa_family == AF_INET) + if (h->ext_addr->sa_family == AF_INET) { - struct sockaddr_in tmp_addr; + struct sockaddr_in *tmp_addr; tmp_addr = GNUNET_malloc (sizeof (struct sockaddr_in)); tmp_addr->sin_family = AF_INET; #ifdef HAVE_SOCKADDR_IN_SIN_LEN tmp_addr->sin_len = sizeof (struct sockaddr_in); #endif - tmp_addr->sin_port = port_mapped ? htons (nat->public_port) : 0; - tmp_addr->sin_addr = ((struct sockaddr_in *) nat->ext_addr)->sin_addr; - nat->contact_addr = (struct sockaddr *) tmp_addr; - if (NULL != nat->callback) - nat->callback (nat->callback_cls, - GNUNET_YES, - nat->contact_addr, - sizeof (struct sockaddr_in)); + tmp_addr->sin_port = h->port_mapped ? htons (h->public_port) : 0; + tmp_addr->sin_addr = ((struct sockaddr_in *) h->ext_addr)->sin_addr; + h->contact_addr = (struct sockaddr *) tmp_addr; + + if (NULL != h->callback) + h->callback (h->callback_cls, + GNUNET_YES, + h->contact_addr, sizeof (struct sockaddr_in)); } - else if (nat->ext_addr->sa_family == AF_INET6) + else if (h->ext_addr->sa_family == AF_INET6) { struct sockaddr_in6 *tmp_addr; @@ -209,14 +215,14 @@ notify_change (struct GNUNET_NAT_Handle *nat, #ifdef HAVE_SOCKADDR_IN_SIN_LEN tmp_addr->sin6_len = sizeof (struct sockaddr_in6); #endif - tmp_addr->sin6_port = port_mapped ? htons (nat->public_port) : 0; - tmp_addr->sin6_addr = ((struct sockaddr_in6 *) nat->ext_addr)->sin6_addr; - nat->contact_addr = (struct sockaddr *) tmp_addr; - if (NULL != nat->callback) - nat->callback (nat->callback_cls, - GNUNET_YES, - nat->contact_addr, - sizeof (struct sockaddr_in6)); + tmp_addr->sin6_port = h->port_mapped ? htons (h->public_port) : 0; + tmp_addr->sin6_addr = ((struct sockaddr_in6 *) h->ext_addr)->sin6_addr; + h->contact_addr = (struct sockaddr *) tmp_addr; + + if (NULL != h->callback) + h->callback (h->callback_cls, + GNUNET_YES, + h->contact_addr, sizeof (struct sockaddr_in6)); } else { @@ -224,87 +230,140 @@ notify_change (struct GNUNET_NAT_Handle *nat, } } +static void nat_pulse (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); static void -nat_pulse (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) +pulse_cb (struct GNUNET_NAT_Handle *h) { - struct GNUNET_NAT_Handle *nat = cls; - int old_status; - int new_status; + socklen_t addrlen; int port_mapped; - struct sockaddr *ext_addr_upnp = NULL; - struct sockaddr *ext_addr_natpmp = NULL; - nat->pulse_timer = GNUNET_SCHEDULER_NO_TASK; - old_status = get_traversal_status (nat); + /* One of the protocols is still working, wait for it to complete */ + if (h->processing) + return; - /* Only update the protocol that has been successful until now */ - if (nat->upnp_status >= GNUNET_NAT_PORT_UNMAPPED) - nat->upnp_status = - GNUNET_NAT_UPNP_pulse (nat->upnp, nat->is_enabled, GNUNET_YES, - &ext_addr_upnp); - else if (nat->natpmp_status >= GNUNET_NAT_PORT_UNMAPPED) - nat->natpmp_status = - GNUNET_NAT_NATPMP_pulse (nat->natpmp, nat->is_enabled, - &ext_addr_natpmp); - else - { - /* try both */ - nat->upnp_status = - GNUNET_NAT_UPNP_pulse (nat->upnp, nat->is_enabled, GNUNET_YES, - &ext_addr_upnp); - nat->natpmp_status = - GNUNET_NAT_NATPMP_pulse (nat->natpmp, nat->is_enabled, - &ext_addr_natpmp); - } - new_status = get_traversal_status (nat); - if ( (old_status != new_status) && - ( (new_status == GNUNET_NAT_PORT_UNMAPPED) || - (new_status == GNUNET_NAT_PORT_ERROR) ) ) + h->new_status = get_traversal_status (h); + if ((h->old_status != h->new_status) && + ((h->new_status == GNUNET_NAT_PORT_UNMAPPED) || + (h->new_status == GNUNET_NAT_PORT_ERROR))) GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, - "NAT", - _("Port redirection failed: no UPnP or NAT-PMP routers supporting this feature found\n")); + "NAT", + _ + ("Port redirection failed: no UPnP or NAT-PMP routers supporting this feature found\n")); #ifdef DEBUG - if (new_status != old_status) + if (h->new_status != h->old_status) GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "NAT", _("State changed from `%s' to `%s'\n"), - get_nat_state_str (old_status), - get_nat_state_str (new_status)); + get_nat_state_str (h->old_status), + get_nat_state_str (h->new_status)); #endif - port_mapped = (new_status == GNUNET_NAT_PORT_MAPPED); - if (!(ext_addr_upnp || ext_addr_natpmp)) + port_mapped = (h->new_status == GNUNET_NAT_PORT_MAPPED); + if (!(h->ext_addr_upnp || h->ext_addr_natpmp)) { - /* Address has just changed and we could not get it, or it's the first try */ - if ( (NULL != nat->ext_addr) || - (GNUNET_NO == nat->did_warn) ) + /* Address has just changed and we could not get it, or it's the first try, + * and we're not waiting for a reply from UPnP or NAT-PMP */ + if (((NULL != h->ext_addr) || + (GNUNET_NO == h->did_warn)) && h->processing != 0) { - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, - "NAT", - _("Could not determine external IP address\n")); - nat->did_warn = GNUNET_YES; + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, + "NAT", + _("Could not determine external IP address\n")); + h->did_warn = GNUNET_YES; } - notify_change (nat, NULL, port_mapped); + notify_change (h, NULL, 0, port_mapped); } - else if (ext_addr_upnp && GNUNET_NAT_cmp_addr (nat->ext_addr, ext_addr_upnp) != 0) + else if (h->ext_addr_upnp + && GNUNET_NAT_cmp_addr (h->ext_addr, h->ext_addr_upnp) != 0) { + addrlen = h->ext_addr_upnp->sa_family == AF_INET ? + sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6); GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, - "NAT", - _("External IP address changed to %s\n"), - GNUNET_a2s (ext_addr_upnp, sizeof (ext_addr_upnp))); - notify_change (nat, ext_addr_upnp, port_mapped); + "NAT", + _("External IP address changed to %s\n"), + GNUNET_a2s (h->ext_addr_upnp, addrlen)); + notify_change (h, h->ext_addr_upnp, addrlen, port_mapped); } - else if (ext_addr_natpmp && GNUNET_NAT_cmp_addr (nat->ext_addr, ext_addr_natpmp) != 0) + else if (h->ext_addr_natpmp + && GNUNET_NAT_cmp_addr (h->ext_addr, h->ext_addr_natpmp) != 0) { + addrlen = h->ext_addr_natpmp->sa_family == AF_INET ? + sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6); GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "NAT", - _("External IP address changed to `%s'\n"), - GNUNET_a2s (ext_addr_natpmp, sizeof (ext_addr_natpmp))); - notify_change (nat, ext_addr_natpmp, port_mapped); + _("External IP address changed to `%s'\n"), + GNUNET_a2s (h->ext_addr_natpmp, addrlen)); + notify_change (h, h->ext_addr_natpmp, addrlen, port_mapped); + } + + h->pulse_timer = GNUNET_SCHEDULER_add_delayed (h->sched, + GNUNET_TIME_UNIT_SECONDS, + &nat_pulse, h); +} + +static void +upnp_pulse_cb (int status, struct sockaddr *ext_addr, void *cls) +{ + struct GNUNET_NAT_Handle *h = cls; + + h->upnp_status = status; + h->ext_addr_upnp = ext_addr; + + h->processing--; + pulse_cb (h); +} + +#if 0 +static void +natpmp_pulse_cb (int status, struct sockaddr *ext_addr, void *cls) +{ + struct GNUNET_NAT_Handle *h = cls; + + h->natpmp_status = status; + h->ext_addr_natpmp = ext_addr; + + h->processing--; + pulse_cb (h); +} +#endif + +static void +nat_pulse (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct GNUNET_NAT_Handle *h = cls; + + /* Stop if we're already waiting for an action to complete */ + if (h->processing) + return; + + h->pulse_timer = GNUNET_SCHEDULER_NO_TASK; + h->old_status = get_traversal_status (h); + + /* Only update the protocol that has been successful until now */ + if (h->upnp_status >= GNUNET_NAT_PORT_UNMAPPED) + { + h->processing = 1; + GNUNET_NAT_UPNP_pulse (h->upnp, h->is_enabled, GNUNET_YES); + + /* Wait for the callback to call pulse_cb() to handle changes */ + return; + } + else if (h->natpmp_status >= GNUNET_NAT_PORT_UNMAPPED) + { + h->processing = 1; +#if 0 + GNUNET_NAT_NATPMP_pulse (h->natpmp, h->is_enabled); +#endif + } + else /* try both */ + { + h->processing = 2; + + GNUNET_NAT_UPNP_pulse (h->upnp, h->is_enabled, GNUNET_YES); +#if 0 + GNUNET_NAT_NATPMP_pulse (h->natpmp, h->is_enabled, natpmp_pulse_cb, h); +#endif } - nat->pulse_timer = GNUNET_SCHEDULER_add_delayed (nat->sched, - GNUNET_TIME_UNIT_SECONDS, - &nat_pulse, nat); } @@ -322,43 +381,52 @@ nat_pulse (void *cls, * @return NULL on error, otherwise handle that can be used to unregister */ struct GNUNET_NAT_Handle * -GNUNET_NAT_register (struct GNUNET_SCHEDULER_Handle *sched, - const struct sockaddr *addr, socklen_t addrlen, +GNUNET_NAT_register (struct GNUNET_SCHEDULER_Handle + *sched, + const struct sockaddr *addr, + socklen_t addrlen, GNUNET_NAT_AddressCallback callback, void *callback_cls) { - struct GNUNET_NAT_Handle *nat; + struct GNUNET_NAT_Handle *h; + + h = GNUNET_malloc (sizeof (struct GNUNET_NAT_Handle)); - nat = GNUNET_malloc (sizeof (struct GNUNET_NAT_Handle)); if (addr) { - GNUNET_assert ( (addr->sa_family == AF_INET) || - (addr->sa_family == AF_INET6) ); - nat->local_addr = GNUNET_malloc (addrlen); - memcpy (nat->local_addr, addr, addrlen); + GNUNET_assert ((addr->sa_family == AF_INET) || + (addr->sa_family == AF_INET6)); + h->local_addr = GNUNET_malloc (addrlen); + memcpy (h->local_addr, addr, addrlen); if (addr->sa_family == AF_INET) { - nat->public_port = ntohs (((struct sockaddr_in *) addr)->sin_port); - ((struct sockaddr_in *) nat->local_addr)->sin_port = 0; + h->public_port = ntohs (((struct sockaddr_in *) addr)->sin_port); + ((struct sockaddr_in *) h->local_addr)->sin_port = 0; } else if (addr->sa_family == AF_INET6) { - nat->public_port = ntohs (((struct sockaddr_in6 *) addr)->sin6_port); - ((struct sockaddr_in6 *) nat->local_addr)->sin6_port = 0; + h->public_port = ntohs (((struct sockaddr_in6 *) addr)->sin6_port); + ((struct sockaddr_in6 *) h->local_addr)->sin6_port = 0; } } - nat->should_change = GNUNET_YES; - nat->sched = sched; - nat->is_enabled = GNUNET_YES; - nat->upnp_status = GNUNET_NAT_PORT_UNMAPPED; - nat->natpmp_status = GNUNET_NAT_PORT_UNMAPPED; - nat->callback = callback; - nat->callback_cls = callback_cls; - nat->natpmp = GNUNET_NAT_NATPMP_init (nat->local_addr, addrlen, nat->public_port); - nat->upnp = GNUNET_NAT_UPNP_init (nat->local_addr, addrlen, nat->public_port); - nat->pulse_timer = GNUNET_SCHEDULER_add_delayed (sched, - GNUNET_TIME_UNIT_SECONDS, - &nat_pulse, nat); - return nat; + h->should_change = GNUNET_YES; + h->sched = sched; + h->is_enabled = GNUNET_YES; + h->upnp_status = GNUNET_NAT_PORT_UNMAPPED; + h->natpmp_status = GNUNET_NAT_PORT_UNMAPPED; + h->callback = callback; + h->callback_cls = callback_cls; + h->upnp = + GNUNET_NAT_UPNP_init (h->sched, h->local_addr, addrlen, h->public_port, + upnp_pulse_cb, h); +#if 0 + h->natpmp = + GNUNET_NAT_NATPMP_init (h->sched, h->local_addr, addrlen, h->public_port, + natpmp_pulse_cb, h); +#endif + h->pulse_timer = GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_TIME_UNIT_SECONDS, + &nat_pulse, h); + return h; } @@ -371,23 +439,21 @@ GNUNET_NAT_register (struct GNUNET_SCHEDULER_Handle *sched, void GNUNET_NAT_unregister (struct GNUNET_NAT_Handle *h) { - struct sockaddr *addr; - - GNUNET_SCHEDULER_cancel (h->sched, - h->pulse_timer); - h->upnp_status = - GNUNET_NAT_UPNP_pulse (h->upnp, - GNUNET_NO, GNUNET_NO, - &addr); - h->natpmp_status = - GNUNET_NAT_NATPMP_pulse (h->natpmp, GNUNET_NO, - &addr); - GNUNET_NAT_NATPMP_close (h->natpmp); + GNUNET_NAT_UPNP_pulse (h->upnp, GNUNET_NO, GNUNET_NO); GNUNET_NAT_UPNP_close (h->upnp); + +#if 0 + GNUNET_NAT_NATPMP_pulse (h->natpmp, GNUNET_NO); + GNUNET_NAT_NATPMP_close (h->natpmp); +#endif + + GNUNET_SCHEDULER_cancel (h->sched, h->pulse_timer); + GNUNET_free_non_null (h->local_addr); GNUNET_free_non_null (h->ext_addr); + GNUNET_free_non_null (h->ext_addr_upnp); + GNUNET_free_non_null (h->ext_addr_natpmp); GNUNET_free (h); } /* end of nat.c */ - diff --git a/src/nat/nat.h b/src/nat/nat.h index a0924af08..3a0f21e7b 100644 --- a/src/nat/nat.h +++ b/src/nat/nat.h @@ -34,29 +34,29 @@ * Used to communicate with the UPnP and NAT-PMP plugins */ enum GNUNET_NAT_PortState - { - GNUNET_NAT_PORT_ERROR, +{ + GNUNET_NAT_PORT_ERROR, /** * the port isn't forwarded */ - GNUNET_NAT_PORT_UNMAPPED, + GNUNET_NAT_PORT_UNMAPPED, /** * we're cancelling the port forwarding */ - GNUNET_NAT_PORT_UNMAPPING, + GNUNET_NAT_PORT_UNMAPPING, /** * we're in the process of trying to set up port forwarding */ - GNUNET_NAT_PORT_MAPPING, + GNUNET_NAT_PORT_MAPPING, /** * we've successfully forwarded the port */ - GNUNET_NAT_PORT_MAPPED - }; + GNUNET_NAT_PORT_MAPPED +}; /** @@ -66,8 +66,7 @@ enum GNUNET_NAT_PortState * @param b second sockaddr * @return 0 if addresses are equal, non-null value otherwise */ -int GNUNET_NAT_cmp_addr (const struct sockaddr *a, - const struct sockaddr *b); +int GNUNET_NAT_cmp_addr (const struct sockaddr *a, const struct sockaddr *b); #endif diff --git a/src/nat/natpmp.c b/src/nat/natpmp.c index 308ce7f44..79a6bfd33 100644 --- a/src/nat/natpmp.c +++ b/src/nat/natpmp.c @@ -41,6 +41,7 @@ #include "platform.h" #include "gnunet_common.h" #include "gnunet_nat_lib.h" +#include "nat.h" #include "natpmp.h" #define LIFETIME_SECS 3600 @@ -48,7 +49,7 @@ /* Component name for logging */ #define COMP_NAT_NATPMP _("NAT (NAT-PMP))") -typedef enum +enum NATPMP_state { NATPMP_IDLE, NATPMP_ERR, @@ -59,20 +60,21 @@ typedef enum NATPMP_SEND_UNMAP, NATPMP_RECV_UNMAP } -NATPMP_state; + ; struct GNUNET_NAT_NATPMP_Handle { const struct sockaddr *addr; socklen_t addrlen; - struct sockaddr*ext_addr; + struct sockaddr *ext_addr; int is_mapped; int has_discovered; int port; time_t renew_time; time_t command_time; - NATPMP_state state; - natpmp_t natpmp; + enum NATPMP_state state; + struct natpmp_t natpmp; + struct GNUNET_SCHEDULER_Handle *sched; }; @@ -82,28 +84,27 @@ log_val (const char *func, int ret) #ifdef DEBUG if (ret == NATPMP_TRYAGAIN) GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, - COMP_NAT_NATPMP, _("%s retry (%d)\n"), - func, ret); + COMP_NAT_NATPMP, _("%s retry (%d)\n"), func, ret); if (ret >= 0) GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, - COMP_NAT_NATPMP, _("%s succeeded (%d)\n"), - func, ret); + COMP_NAT_NATPMP, _("%s succeeded (%d)\n"), func, ret); else - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, - COMP_NAT_NATPMP, - "%s failed. natpmp returned %d (%s); errno is %d (%s)\n", - func, ret, - strnatpmperr (ret), errno, strerror (errno)); + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, + COMP_NAT_NATPMP, + "%s failed. natpmp returned %d (%s); errno is %d (%s)\n", + func, ret, strnatpmperr (ret), errno, strerror (errno)); #endif } struct GNUNET_NAT_NATPMP_Handle * -GNUNET_NAT_NATPMP_init (const struct sockaddr *addr, socklen_t addrlen, +GNUNET_NAT_NATPMP_init (struct GNUNET_SCHEDULER_Handle *sched, + const struct sockaddr *addr, socklen_t addrlen, u_short port) { struct GNUNET_NAT_NATPMP_Handle *nat; nat = GNUNET_malloc (sizeof (struct GNUNET_NAT_NATPMP_Handle)); + nat->sched = sched; nat->state = NATPMP_DISCOVER; nat->port = port; nat->addr = addr; @@ -112,7 +113,7 @@ GNUNET_NAT_NATPMP_init (const struct sockaddr *addr, socklen_t addrlen, } void -GNUNET_NAT_NATPMP_close (GNUNET_NAT_NATPMP_Handle * nat) +GNUNET_NAT_NATPMP_close (struct GNUNET_NAT_NATPMP_Handle *nat) { if (nat) { @@ -160,7 +161,7 @@ GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, if ((nat->state == NATPMP_RECV_PUB) && can_send_command (nat)) { - natpmpresp_t response; + struct natpmpresp_t response; const int val = readnatpmpresponseorretry (&nat->natpmp, &response); log_val ("readnatpmpresponseorretry", val); @@ -174,40 +175,38 @@ GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, if (response.pnu.publicaddress.family == AF_INET) { - v4 = GNUNET_malloc (sizeof (struct sockaddr_in)); - nat->ext_addr = (struct sockaddr*) v4; - v4->sin_family = AF_INET; - v4->sin_port = response.pnu.newportmapping.mappedpublicport; + v4 = GNUNET_malloc (sizeof (struct sockaddr_in)); + nat->ext_addr = (struct sockaddr *) v4; + v4->sin_family = AF_INET; + v4->sin_port = response.pnu.newportmapping.mappedpublicport; memcpy (&v4->sin_addr, &response.pnu.publicaddress.addr, sizeof (struct in_addr)); #ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, - _("Found public IP address %s\n"), - inet_ntop (AF_INET, - &response.pnu.publicaddress.addr, - buf, - sizeof(buf))); + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, + _("Found public IP address %s\n"), + inet_ntop (AF_INET, + &response.pnu.publicaddress.addr, + buf, sizeof (buf))); #endif } else { v6 = GNUNET_malloc (sizeof (struct sockaddr_in6)); - nat->ext_addr = (struct sockaddr*) v6; - v6->sin6_family = AF_INET6; - v6->sin6_port = response.pnu.newportmapping.mappedpublicport; - memcpy (&v6->sin6_addr, - &response.pnu.publicaddress.addr6, + nat->ext_addr = (struct sockaddr *) v6; + v6->sin6_family = AF_INET6; + v6->sin6_port = response.pnu.newportmapping.mappedpublicport; + memcpy (&v6->sin6_addr, + &response.pnu.publicaddress.addr6, (sizeof (struct in6_addr))); #ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, - _("Found public IP address %s\n"), - inet_ntop (AF_INET6, - &response.pnu.publicaddress.addr6, - buf, - sizeof(buf))); + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, + _("Found public IP address %s\n"), + inet_ntop (AF_INET6, + &response.pnu.publicaddress.addr6, + buf, sizeof (buf))); #endif - } - *ext_addr = nat->ext_addr; + } + *ext_addr = nat->ext_addr; nat->state = NATPMP_IDLE; } else if (val != NATPMP_TRYAGAIN) @@ -235,14 +234,14 @@ GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, if (nat->state == NATPMP_RECV_UNMAP) { - natpmpresp_t resp; + struct natpmpresp_t resp; const int val = readnatpmpresponseorretry (&nat->natpmp, &resp); log_val ("readnatpmpresponseorretry", val); if (val >= 0) { const int p = resp.pnu.newportmapping.privateport; GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, - _("No longer forwarding port %d\n"), p); + _("No longer forwarding port %d\n"), p); if (nat->port == p) { nat->port = -1; @@ -279,7 +278,7 @@ GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, if (nat->state == NATPMP_RECV_MAP) { - natpmpresp_t resp; + struct natpmpresp_t resp; const int val = readnatpmpresponseorretry (&nat->natpmp, &resp); log_val ("readnatpmpresponseorretry", val); if (val >= 0) @@ -289,7 +288,7 @@ GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled, nat->renew_time = time (NULL) + LIFETIME_SECS; nat->port = resp.pnu.newportmapping.privateport; GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, - _("Port %d forwarded successfully\n"), nat->port); + _("Port %d forwarded successfully\n"), nat->port); } else if (val != NATPMP_TRYAGAIN) { diff --git a/src/nat/natpmp.h b/src/nat/natpmp.h index 8e1531cee..0c198e203 100644 --- a/src/nat/natpmp.h +++ b/src/nat/natpmp.h @@ -32,16 +32,18 @@ struct GNUNET_NAT_NATPMP_Handle; -struct GNUNET_NAT_NATPMP_Handle * -GNUNET_NAT_NATPMP_init (const struct sockaddr *addr, - socklen_t addrlen, - unsigned short port); +struct GNUNET_NAT_NATPMP_Handle *GNUNET_NAT_NATPMP_init (struct + GNUNET_SCHEDULER_Handle + *sched, + const struct sockaddr + *addr, + socklen_t addrlen, + unsigned short port); -void GNUNET_NAT_NATPMP_close (struct GNUNET_NAT_NATPMP_Handle * nat); +void GNUNET_NAT_NATPMP_close (struct GNUNET_NAT_NATPMP_Handle *nat); -int GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle * nat, - int is_enabled, - struct sockaddr **ext_addr); +int GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, + int is_enabled, struct sockaddr **ext_addr); -#endif +#endif /* NATPMP_H */ diff --git a/src/nat/test_nat.c b/src/nat/test_nat.c index f4f0b62ea..e42d76b7f 100644 --- a/src/nat/test_nat.c +++ b/src/nat/test_nat.c @@ -41,14 +41,13 @@ #include "gnunet_nat_lib.h" /* Time to wait before stopping NAT, in seconds */ -#define TIMEOUT 30 +#define TIMEOUT 60 struct addr_cls { const struct sockaddr *addr; socklen_t addrlen; }; -//typedef addr_cls addr_cls; static void addr_callback (void *cls, int add_remove, @@ -73,9 +72,7 @@ stop (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) static int process_if (void *cls, const char *name, - int isDefault, - const struct sockaddr *addr, - socklen_t addrlen) + int isDefault, const struct sockaddr *addr, socklen_t addrlen) { struct addr_cls *data = cls; @@ -85,6 +82,12 @@ process_if (void *cls, data->addrlen = addrlen; } + if (strcmp (name, "eth1") == 0 && addr->sa_family == AF_INET) + return GNUNET_SYSERR; + + return GNUNET_OK; + + if (isDefault && addr) return GNUNET_SYSERR; else @@ -107,7 +110,8 @@ run (void *cls, GNUNET_OS_network_interfaces_list (process_if, &data); if (!data.addr) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not find a valid interface address!\n"); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not find a valid interface address!\n"); exit (GNUNET_SYSERR); } @@ -120,14 +124,17 @@ run (void *cls, else ((struct sockaddr_in6 *) addr)->sin6_port = htons (2086); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Requesting NAT redirection from address %s...\n", GNUNET_a2s (addr, data.addrlen)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting NAT redirection from address %s...\n", + GNUNET_a2s (addr, data.addrlen)); nat = GNUNET_NAT_register (sched, addr, data.addrlen, addr_callback, NULL); GNUNET_free (addr); - GNUNET_SCHEDULER_add_delayed (sched, - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, TIMEOUT), - stop, nat); + GNUNET_SCHEDULER_add_delayed (sched, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, TIMEOUT), stop, + nat); } int diff --git a/src/nat/upnp-commands.c b/src/nat/upnp-commands.c new file mode 100644 index 000000000..ddc5d9473 --- /dev/null +++ b/src/nat/upnp-commands.c @@ -0,0 +1,880 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-commands.c + * @brief Implementation of a basic set of UPnP commands + * + * @author Milan Bouchet-Valat + */ + +#include "platform.h" +#include "gnunet_util_lib.h" + +#include +#include +#include + +#include "upnp-reply-parse.h" +#include "upnp-igd-parse.h" +#include "upnp-discover.h" +#include "upnp-commands.h" + +#define SOAP_PREFIX "s" +#define SERVICE_PREFIX "u" +#define SERVICE_PREFIX2 'u' +#define MAX_HOSTNAME_LEN 64 + +#define PRINT_UPNP_ERROR(a, b) GNUNET_log_from(GNUNET_ERROR_TYPE_WARNING, "UPnP", _("%s failed at %s:%d: %s\n"), a, __FILE__, __LINE__, b); + + +/** + * Private closure used by UPNP_command() and its callbacks. + */ +struct UPNP_command_cls +{ + /** + * Connection handle used for sending and receiving. + */ + struct GNUNET_CONNECTION_Handle *s; + + /** + * Transmission handle used for sending command. + */ + struct GNUNET_CONNECTION_TransmitHandle *th; + + /** + * HTML content to send to run command. + */ + char *content; + + /** + * Buffer where to copy received data to pass to caller. + */ + char *buffer; + + /** + * Size of buffer. + */ + size_t buf_size; + + /** + * User callback to trigger when done. + */ + UPNP_command_cb_ caller_cb; + + /** + * User closure to pass to caller_cb. + */ + void *caller_cls; +}; + +/** + * Get the length of content included in an HTML line. + * + * @param p line to parse + * @param n size of p + * @return the length of the content + */ +static ssize_t +get_content_len_from_line (const char *p, int n) +{ + static const char cont_len_str[] = "content-length"; + const char *p2 = cont_len_str; + int a = 0; + + while (*p2) + { + if (n == 0) + return -1; + + if (*p2 != *p && *p2 != (*p + 32)) + return -1; + + p++; + p2++; + n--; + } + + if (n == 0) + return -1; + + if (*p != ':') + return -1; + + p++; + n--; + + while (*p == ' ') + { + if (n == 0) + return -1; + + p++; + n--; + } + + while (*p >= '0' && *p <= '9') + { + if (n == 0) + return -1; + + a = (a * 10) + (*p - '0'); + p++; + n--; + } + + return a; +} + +/** + * Get the respective lengths of content and header from an HTML reply. + * + * @param p HTML to parse + * @param n size of p + * @param content_len pointer to store content length to + * @param content_len pointer to store header length to + */ +static void +get_content_and_header_len (const char *p, int n, + int *content_len, int *header_len) +{ + const char *line; + int line_len; + int r; + + line = p; + + while (line < p + n) + { + line_len = 0; + + while (line[line_len] != '\r' && line[line_len] != '\r') + { + if (line + line_len >= p + n) + return; + + line_len++; + } + + r = get_content_len_from_line (line, line_len); + + if (r > 0) + *content_len = r; + + line = line + line_len + 2; + + if (line[0] == '\r' && line[1] == '\n') + { + *header_len = (line - p) + 2; + return; + } + } +} + +/** + * Receive reply of the device to our UPnP command. + * + * @param data closure from UPNP_command() + * @param buf struct UPNP_command_cls *cls + * @param available number of bytes in buf + * @param addr address of the sender + * @param addrlen size of addr + * @errCode value of errno + */ +static void +UPNP_command_receiver (void *data, + const void *buf, + size_t available, + const struct sockaddr *addr, + socklen_t addrlen, int errCode) +{ + struct UPNP_command_cls *cls = data; + int content_len; + int header_len; + + if (available > 0) + { + content_len = -1; + header_len = -1; + get_content_and_header_len (buf, available, &content_len, &header_len); + + strncpy (cls->buffer, (char *) buf, cls->buf_size - 2); + cls->buffer[cls->buf_size - 2] = '\0'; + } + else + { + cls->buffer[0] = '\0'; + } + + GNUNET_CONNECTION_destroy (cls->s, GNUNET_NO); + + (*cls->caller_cb) (cls->buffer, cls->buf_size, cls->caller_cls); + + GNUNET_free (cls->content); + GNUNET_free (cls); +} + +/** + * Send UPnP command to device. + */ +static size_t +UPNP_command_transmit (void *data, size_t size, void *buf) +{ + struct UPNP_command_cls *cls = data; + int n; + char *content = cls->content; + + n = strlen (content); + memcpy (buf, content, size); + + GNUNET_CONNECTION_receive (cls->s, cls->buf_size, GNUNET_TIME_UNIT_MINUTES, + UPNP_command_receiver, cls); + + return n; +} + +/** + * Parse a HTTP URL string to extract hostname, port and path it points to. + * + * @param url source string corresponding to URL + * @param hostname pointer where to store hostname (size of MAX_HOSTNAME_LEN+1) + * @param port pointer where to store port + * @param path pointer where to store path + * + * @return GNUNET_OK on success, GNUNET_SYSERR on failure + */ +int +parse_url (const char *url, char *hostname, unsigned short *port, char **path) +{ + char *p1, *p2, *p3; + + if (!url) + return GNUNET_SYSERR; + + p1 = strstr (url, "://"); + + if (!p1) + return GNUNET_SYSERR; + + p1 += 3; + + if ((url[0] != 'h') || (url[1] != 't') + || (url[2] != 't') || (url[3] != 'p')) + return GNUNET_SYSERR; + + p2 = strchr (p1, ':'); + p3 = strchr (p1, '/'); + + if (!p3) + return GNUNET_SYSERR; + + memset (hostname, 0, MAX_HOSTNAME_LEN + 1); + + if (!p2 || (p2 > p3)) + { + strncpy (hostname, p1, MIN (MAX_HOSTNAME_LEN, (int) (p3 - p1))); + *port = 80; + } + else + { + strncpy (hostname, p1, MIN (MAX_HOSTNAME_LEN, (int) (p2 - p1))); + *port = 0; + p2++; + + while ((*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short) (*p2 - '0'); + p2++; + } + } + + *path = p3; + return GNUNET_OK; +} + +/** + * Send UPnP command to the device identified by url and service. + * + * @param sched scheduler to use for network tasks + * @param url control URL of the device + * @param service type of the service corresponding to the command + * @param action action to send + * @param args arguments for action + * @param caller_cb user callback to trigger when done + * @param caller_cls closure to pass to caller_cb + */ +void +UPNP_command_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *url, const char *service, + const char *action, struct UPNP_Arg_ *args, + char *buffer, size_t buf_size, + UPNP_command_cb_ caller_cb, void *caller_cls) +{ + struct GNUNET_CONNECTION_Handle *s; + struct UPNP_command_cls *cls; + struct sockaddr_in dest; + struct sockaddr_in6 dest6; + char hostname[MAX_HOSTNAME_LEN + 1]; + unsigned short port = 0; + char *path; + char soap_act[128]; + char soap_body[2048]; + int body_size; + char *content_buf; + int headers_size; + char port_str[8]; + + snprintf (soap_act, sizeof (soap_act), "%s#%s", service, action); + + if (args == NULL) + { + snprintf (soap_body, sizeof (soap_body), + "\r\n" + "<" SOAP_PREFIX ":Envelope " + "xmlns:" SOAP_PREFIX + "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAP_PREFIX + ":encodingStyle=\"http://schema GNUNET_free (content_buf);s.xmlsoap.org/soap/encoding/\">" + "<" SOAP_PREFIX ":Body>" "<" SERVICE_PREFIX + ":%s xmlns:" SERVICE_PREFIX "=\"%s\">" "" "" "\r\n", + action, service, action); + } + else + { + char *p; + const char *pe, *pv; + int soap_body_len; + + soap_body_len = snprintf (soap_body, sizeof (soap_body), + "\r\n" + "<" SOAP_PREFIX ":Envelope " + "xmlns:" SOAP_PREFIX + "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAP_PREFIX + ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAP_PREFIX ":Body>" "<" SERVICE_PREFIX + ":%s xmlns:" SERVICE_PREFIX "=\"%s\">", + action, service); + + p = soap_body + soap_body_len; + + while (args->elt) + { + /* check that we are never overflowing the string... */ + if (soap_body + sizeof (soap_body) <= p + 100) + { + GNUNET_assert (GNUNET_NO); + (*caller_cb) (buffer, 0, caller_cls); + return; + } + *(p++) = '<'; + pe = args->elt; + while (*pe) + *(p++) = *(pe++); + *(p++) = '>'; + if ((pv = args->val)) + { + while (*pv) + *(p++) = *(pv++); + } + *(p++) = '<'; + *(p++) = '/'; + pe = args->elt; + while (*pe) + *(p++) = *(pe++); + *(p++) = '>'; + args++; + } + *(p++) = '<'; + *(p++) = '/'; + *(p++) = SERVICE_PREFIX2; + *(p++) = ':'; + pe = action; + + while (*pe) + *(p++) = *(pe++); + + strncpy (p, ">\r\n", + soap_body + sizeof (soap_body) - p); + } + + if (GNUNET_OK != parse_url (url, hostname, &port, &path)) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP", + "Invalid URL passed to UPNP_command(): %s\n", url); + return; + } + + + /* Test IPv4 address, else use IPv6 */ + memset (&dest, 0, sizeof (dest)); + memset (&dest6, 0, sizeof (dest6)); + + if (inet_pton (AF_INET, hostname, &dest.sin_addr) == 1) + { + dest.sin_family = AF_INET; + dest.sin_port = htons (port); +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + dest.sin_len = sizeof (dest); +#endif + + s = GNUNET_CONNECTION_create_from_sockaddr (sched, PF_INET, + (struct sockaddr *) &dest, + sizeof (dest)); + } + else if (inet_pton (AF_INET6, hostname, &dest6.sin6_addr) == 1) + { + dest6.sin6_family = AF_INET6; + dest6.sin6_port = htons (port); +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + dest6.sin6_len = sizeof (dest6); +#endif + + s = GNUNET_CONNECTION_create_from_sockaddr (sched, PF_INET6, + (struct sockaddr *) &dest6, + sizeof (dest6)); + } + else + { + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d\n"), + "UPnP", "inet_pton", __FILE__, __LINE__); + + (*caller_cb) (buffer, 0, caller_cls); + return; + } + + body_size = (int) strlen (soap_body); + content_buf = GNUNET_malloc (512 + body_size); + + /* We are not using keep-alive HTTP connections. + * HTTP/1.1 needs the header Connection: close to do that. + * This is the default with HTTP/1.0 */ + /* Connection: Close is normally there only in HTTP/1.1 but who knows */ + port_str[0] = '\0'; + + if (port != 80) + snprintf (port_str, sizeof (port_str), ":%hu", port); + + headers_size = snprintf (content_buf, 512, "POST %s HTTP/1.1\r\n" "Host: %s%s\r\n" "User-Agent: GNU, UPnP/1.0, GNUnet/" PACKAGE_VERSION "\r\n" "Content-Length: %d\r\n" "Content-Type: text/xml\r\n" "SOAPAction: \"%s\"\r\n" "Connection: Close\r\n" "Cache-Control: no-cache\r\n" /* ??? */ + "Pragma: no-cache\r\n" + "\r\n", path, hostname, port_str, body_size, + soap_act); + memcpy (content_buf + headers_size, soap_body, body_size); + +#ifdef DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Sending command '%s' to '%s' (service '%s')\n", + action, url, service); +#endif + + cls = GNUNET_malloc (sizeof (struct UPNP_command_cls)); + cls->s = s; + cls->content = content_buf; + cls->buffer = buffer; + cls->buf_size = buf_size; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + cls->th = + GNUNET_CONNECTION_notify_transmit_ready (s, body_size + headers_size, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 15), + &UPNP_command_transmit, cls); + + + if (cls->th == NULL) + { +#ifdef DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP", + "Error sending SOAP request at %s:%d\n", __FILE__, + __LINE__); +#endif + + (*caller_cb) (buffer, 0, caller_cls); + + GNUNET_free (content_buf); + GNUNET_free (cls); + GNUNET_CONNECTION_destroy (s, GNUNET_NO); + return; + } +} + +struct get_external_ip_address_cls +{ + UPNP_get_external_ip_address_cb_ caller_cb; + void *caller_cls; +}; + +static void +get_external_ip_address_receiver (char *response, size_t received, void *data) +{ + struct get_external_ip_address_cls *cls = data; + struct UPNP_REPLY_NameValueList_ pdata; + char extIpAdd[128]; + char *p; + int ret = UPNP_COMMAND_UNKNOWN_ERROR; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Response: %s", response); + + UPNP_REPLY_parse_ (response, received, &pdata); + p = UPNP_REPLY_get_value_ (&pdata, "NewExternalIPAddress"); + if (p) + { + strncpy (extIpAdd, p, 128); + extIpAdd[127] = '\0'; + ret = UPNP_COMMAND_SUCCESS; + } + else + extIpAdd[0] = '\0'; + + p = UPNP_REPLY_get_value_ (&pdata, "errorCode"); + if (p) + { + ret = UPNP_COMMAND_UNKNOWN_ERROR; + sscanf (p, "%d", &ret); + } + cls->caller_cb (ret, extIpAdd, cls->caller_cls); + + UPNP_REPLY_free_ (&pdata); + GNUNET_free (response); + GNUNET_free (cls); +} + +/* UPNP_get_external_ip_address_() call the corresponding UPNP method. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + */ +void +UPNP_get_external_ip_address_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, + const char *service_type, + UPNP_get_external_ip_address_cb_ caller_cb, + void *caller_cls) +{ + struct get_external_ip_address_cls *cls; + char *buffer; + + if (!control_url || !service_type) + caller_cb (UPNP_COMMAND_INVALID_ARGS, NULL, caller_cls); + + cls = GNUNET_malloc (sizeof (struct get_external_ip_address_cls)); + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE); + + UPNP_command_ (sched, control_url, service_type, "GetExternalIPAddress", + NULL, buffer, UPNP_COMMAND_BUFSIZE, + (UPNP_command_cb_) get_external_ip_address_receiver, cls); +} + +struct PortMapping_cls +{ + const char *control_url; + const char *service_type; + const char *ext_port; + const char *in_port; + const char *proto; + const char *remoteHost; + UPNP_port_mapping_cb_ caller_cb; + void *caller_cls; +}; + +static void +add_delete_port_mapping_receiver (char *response, size_t received, void *data) +{ + struct PortMapping_cls *cls = data; + struct UPNP_REPLY_NameValueList_ pdata; + const char *resVal; + int ret; + + UPNP_REPLY_parse_ (response, received, &pdata); + resVal = UPNP_REPLY_get_value_ (&pdata, "errorCode"); + if (resVal) + { + ret = UPNP_COMMAND_UNKNOWN_ERROR; + sscanf (resVal, "%d", &ret); + } + else + { + ret = UPNP_COMMAND_SUCCESS; + } + + cls->caller_cb (ret, cls->control_url, cls->service_type, + cls->ext_port, cls->in_port, cls->proto, + cls->remoteHost, cls->caller_cls); + + UPNP_REPLY_free_ (&pdata); + GNUNET_free (response); + GNUNET_free (cls); +} + +void +UPNP_add_port_mapping_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, const char *service_type, + const char *ext_port, + const char *in_port, + const char *inClient, + const char *desc, + const char *proto, const char *remoteHost, + UPNP_port_mapping_cb_ caller_cb, void *caller_cls) +{ + struct UPNP_Arg_ args[9]; + struct PortMapping_cls *cls; + char *buffer; + + if (!in_port || !inClient || !proto || !ext_port) + { + caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type, + ext_port, in_port, proto, remoteHost, caller_cls); + return; + } + + args[0].elt = "NewRemoteHost"; + args[0].val = remoteHost; + args[1].elt = "NewExternalPort"; + args[1].val = ext_port; + args[2].elt = "NewProtocol"; + args[2].val = proto; + args[3].elt = "NewInternalPort"; + args[3].val = in_port; + args[4].elt = "NewInternalClient"; + args[4].val = inClient; + args[5].elt = "NewEnabled"; + args[5].val = "1"; + args[6].elt = "NewPortMappingDescription"; + args[6].val = desc ? desc : "GNUnet"; + args[7].elt = "NewLeaseDuration"; + args[7].val = "0"; + args[8].elt = NULL; + args[8].val = NULL; + + cls = GNUNET_malloc (sizeof (struct PortMapping_cls)); + cls->control_url = control_url; + cls->service_type = service_type; + cls->ext_port = ext_port;; + cls->in_port = in_port; + cls->proto = proto; + cls->remoteHost = remoteHost; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE); + + UPNP_command_ (sched, control_url, service_type, "AddPortMapping", + args, buffer, UPNP_COMMAND_BUFSIZE, + add_delete_port_mapping_receiver, cls); +} + +void +UPNP_delete_port_mapping_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, const char *service_type, + const char *ext_port, const char *proto, + const char *remoteHost, + UPNP_port_mapping_cb_ caller_cb, void *caller_cls) +{ + struct UPNP_Arg_ args[4]; + struct PortMapping_cls *cls; + char *buffer; + + if (!ext_port || !proto) + { + caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type, + ext_port, NULL, proto, remoteHost, caller_cls); + return; + } + + args[0].elt = "NewRemoteHost"; + args[0].val = remoteHost; + args[1].elt = "NewExternalPort"; + args[1].val = ext_port; + args[2].elt = "NewProtocol"; + args[2].val = proto; + args[3].elt = NULL; + args[3].val = NULL; + + cls = GNUNET_malloc (sizeof (struct PortMapping_cls)); + cls->control_url = control_url; + cls->service_type = service_type; + cls->ext_port = ext_port; + cls->in_port = "0"; + cls->proto = proto; + cls->remoteHost = remoteHost; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE); + + UPNP_command_ (sched, control_url, service_type, + "DeletePortMapping", + args, buffer, UPNP_COMMAND_BUFSIZE, + add_delete_port_mapping_receiver, cls); +} + + +struct get_specific_port_mapping_entry_cls +{ + const char *control_url; + const char *service_type; + const char *ext_port; + const char *proto; + UPNP_port_mapping_cb_ caller_cb; + void *caller_cls; +}; + +static void +get_specific_port_mapping_entry_receiver (char *response, size_t received, + void *data) +{ + struct PortMapping_cls *cls = data; + struct UPNP_REPLY_NameValueList_ pdata; + char *p; + char in_port[128]; + char in_client[128]; + int ret; + + UPNP_REPLY_parse_ (response, received, &pdata); + + p = UPNP_REPLY_get_value_ (&pdata, "NewInternalClient"); + if (p) + { + strncpy (in_client, p, 128); + in_client[127] = '\0'; + } + else + in_client[0] = '\0'; + + p = UPNP_REPLY_get_value_ (&pdata, "NewInternalPort"); + if (p) + { + strncpy (in_port, p, 6); + in_port[5] = '\0'; + } + else + in_port[0] = '\0'; + + p = UPNP_REPLY_get_value_ (&pdata, "errorCode"); + if (p) + { + if (p) + { + ret = UPNP_COMMAND_UNKNOWN_ERROR; + sscanf (p, "%d", &ret); + } +#if DEBUG_UPNP + PRINT_UPNP_ERROR ("GetSpecificPortMappingEntry", p); +#endif + } + + cls->caller_cb (ret, cls->control_url, cls->service_type, + cls->ext_port, cls->proto, in_port, in_client, + cls->caller_cls); + + UPNP_REPLY_free_ (&pdata); + GNUNET_free (response); + GNUNET_free (cls); +} + +/* UPNP_get_specific_port_mapping_entry _ retrieves an existing port mapping + * the result is returned in the in_client and in_port strings + * please provide 128 and 6 bytes of data */ +void +UPNP_get_specific_port_mapping_entry_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, + const char *service_type, + const char *ext_port, + const char *proto, + UPNP_get_specific_port_mapping_entry_cb_ + caller_cb, void *caller_cls) +{ + struct UPNP_Arg_ args[4]; + struct get_specific_port_mapping_entry_cls *cls; + char *buffer; + + if (!ext_port || !proto) + { + caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type, + ext_port, proto, NULL, NULL, caller_cls); + return; + } + + args[0].elt = "NewRemoteHost"; + args[0].val = NULL; + args[1].elt = "NewExternalPort"; + args[1].val = ext_port; + args[2].elt = "NewProtocol"; + args[2].val = proto; + args[3].elt = NULL; + args[3].val = NULL; + + cls = GNUNET_malloc (sizeof (struct PortMapping_cls)); + cls->control_url = control_url; + cls->service_type = service_type; + cls->ext_port = ext_port; + cls->proto = proto; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE); + + UPNP_command_ (sched, control_url, service_type, + "GetSpecificPortMappingEntry", + args, buffer, UPNP_COMMAND_BUFSIZE, + get_specific_port_mapping_entry_receiver, cls); +} diff --git a/src/nat/upnp-commands.h b/src/nat/upnp-commands.h new file mode 100644 index 000000000..8e7510dbf --- /dev/null +++ b/src/nat/upnp-commands.h @@ -0,0 +1,281 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-commands.h + * @brief Commands to control UPnP IGD devices + * + * @author Milan Bouchet-Valat + */ +#ifndef UPNP_COMMANDS_H +#define UPNP_COMMANDS_H + +#include "platform.h" +#include "gnunet_scheduler_lib.h" + +/** + * Generic UPnP error codes. + */ +#define UPNP_COMMAND_SUCCESS (0) +#define UPNP_COMMAND_UNKNOWN_ERROR (-1) +#define UPNP_COMMAND_INVALID_ARGS (-2) + +/** + * Size of the buffer used to store anwsers to UPnP commands. + */ +#define UPNP_COMMAND_BUFSIZE 4096 + +/** + * Name-value pair containing an argumeny to a UPnP command. + */ +struct UPNP_Arg_ +{ + const char *elt; + const char *val; +}; + +/** + * Callback for UPNP_command_(). + * + * @param response the buffer passed to UPNP_command_(), filled with + * NULL-terminated content (if any) + * @param received length of the content received and stored in response + * @param cls closure passed to UPNP_command_() + */ +typedef void (*UPNP_command_cb_) (char *response, size_t received, void *cls); + +/** + * Send UPnP command to the device identified by url and service. + * + * @param sched scheduler to use for network tasks + * @param url control URL of the device + * @param service type of the service corresponding to the command + * @param action action to send + * @param args arguments for action + * @param caller_cb user callback to trigger when done + * @param caller_cls closure to pass to caller_cb + */ +void UPNP_command_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *url, const char *service, + const char *action, struct UPNP_Arg_ *args, + char *buffer, size_t buf_size, + UPNP_command_cb_ caller_cb, void *caller_cls); + +/** + * Callback to UPNP_get_external_ip_address_(). + * + * Possible UPnP Errors : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * + * @param error GNUNET_OK on success, another value on error (see above) + * @param ext_ip_addr the external IP address reported by the device (IPv4 or v6) + * @param cls the closure passed to UPNP_get_external_ip_address_() + */ +typedef void (*UPNP_get_external_ip_address_cb_) (int error, + char *ext_ip_addr, + void *cls); + +/** + * Get the IP address associated with the WAN connection of the device. + * See UPNP_get_external_ip_address_cb_. + * + * @param sched the scheduler to use for networking + * @param control_url the control URL corresponding to service_type on the device + * @param service_type service type to call the command on + * @param caller_cb function to call when done + * @param cls closure passed to caller_cb + */ +void +UPNP_get_external_ip_address_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, + const char *service_type, + UPNP_get_external_ip_address_cb_ caller_cb, + void *caller_cls); + +/** + * Callback to UPNP_add_port_mapping_() and UPNP_delete_port_mapping_(). + * + * Possible UPnP Errors with UPNP_add_port_mapping_(): + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInext_port - The external port cannot be wild-carded + * 718 ConflictInMappingEntry - The port mapping entry specified conflicts + * with a mapping assigned previously to another client + * 724 SamePortValuesRequired - Internal and External port values + * must be the same + * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports + * permanent lease times on port mappings + * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard + * and cannot be a specific IP address or DNS name + * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and + * cannot be a specific port value + * + * Possible UPnP Errors with UPNP_delete_port_mapping_(): + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 714 NoSuchEntryInArray - The specified value does not exist in the array + * + * @param error GNUNET_OK on success, another value on error (see above) + * @param control_url the control URL the command was called on + * @param service_type service the command was called on + * @param in_port port on the gateway on the LAN side which was requested + * @param in_client address in the LAN which was requested + * @param proto protocol for which port mapping was requested + * @param remote_host remote host for which port mapping was requested + * @param cls the closure passed to the command function + */ +typedef void (*UPNP_port_mapping_cb_) (int error, + const char *control_url, + const char *service_type, + const char *ext_port, + const char *inPort, const char *proto, + const char *remote_host, void *cls); + + +/** + * Request opening a port on the IGD device. + * (remote_host is usually NULL because IGDs don't support it.) + * + * @param sched the scheduler to use for networking + * @param control_url the control URL corresponding to service_type on the device + * @param service_type service type to call the command on + * @param ext_port port that should be opened on the WAN side + * @param in_port port on the gateway on the LAN side which should map ext_port + * @param in_client address in the LAN to which packets should be redirected + * @param proto protocol for which to request port mapping + * @param remote_host remote host for which to request port mapping + * @param caller_cb function to call when done + * @param cls closure passed to caller_cb + */ +void +UPNP_add_port_mapping_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, const char *service_type, + const char *ext_port, + const char *in_port, + const char *in_client, + const char *desc, + const char *proto, const char *remote_host, + UPNP_port_mapping_cb_ caller_cb, void *caller_cls); + +/** + * Request closing a a port on the IGD device that was previously opened + * using UPNP_add_port_mapping_(). Use the same argument values that were + * used when opening the port. + * (remote_host is usually NULL because IGDs don't support it.) + * + * @param sched the scheduler to use for networking + * @param control_url the control URL the command was called on + * @param service_type service the command was called on + * @param in_port port on the gateway on the LAN side which was requested + * @param in_client address in the LAN which was requested + * @param proto protocol for which port mapping was requested + * @param remote_host remote host for which port mapping was requested + * @param caller_cb function to call when done + * @param cls closure passed to caller_cb + */ +void +UPNP_delete_port_mapping_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, const char *service_type, + const char *ext_port, const char *proto, + const char *remote_host, + UPNP_port_mapping_cb_ caller_cb, void *caller_cls); + + +/** + * Callback to UPNP_get_specific_port_mapping_entry _(). + * + * @param error GNUNET_OK if port is currently mapped, another value on error + * @param control_url the control URL the command was called on + * @param service_type service the command was called on + * @param in_port port on the gateway on the LAN side which was requested + * @param in_client address in the LAN which was requested + * @param proto protocol for which port mapping was requested + * @param remote_host remote host for which port mapping was requested + * @param cls the closure passed to the command function + */ +typedef void (*UPNP_get_specific_port_mapping_entry_cb_) (int error, + const char + *control_url, + const char + *service_type, + const char + *ext_port, + const char *proto, + const char *in_port, + const char + *in_client, + void *cls); + +/** + * Check that a port mapping set up with UPNP_add_port_mapping_() + * is alive. + * + * @param sched the scheduler to use for networking + * @param control_url the control URL the command was called on + * @param service_type service the command was called on + * @param in_port port on the gateway on the LAN side which was requested + * @param in_client address in the LAN which was requested + * @param proto protocol for which port mapping was requested + * @param remote_host remote host for which port mapping was requested + * @param caller_cb function to call when done + * @param cls closure passed to caller_cb + */ +void +UPNP_get_specific_port_mapping_entry_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *control_url, + const char *service_type, + const char *ext_port, + const char *proto, + UPNP_get_specific_port_mapping_entry_cb_ + caller_cb, void *caller_cls); + +#endif diff --git a/src/nat/upnp-discover.c b/src/nat/upnp-discover.c new file mode 100644 index 000000000..715f2fcee --- /dev/null +++ b/src/nat/upnp-discover.c @@ -0,0 +1,1285 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-discover.c + * @brief Look for UPnP IGD devices + * + * @author Milan Bouchet-Valat + */ +#include +#include +#include +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "upnp-discover.h" +#include "upnp-reply-parse.h" +#include "upnp-igd-parse.h" +#include "upnp-minixml.h" + +#define DISCOVER_BUFSIZE 512 +#define DESCRIPTION_BUFSIZE 2048 +#define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0) +#define PRINT_SOCKET_ERROR(a) GNUNET_log_from(GNUNET_ERROR_TYPE_WARNING, "UPnP", _("%s failed at %s:%d: '%s'\n"), a, __FILE__, __LINE__, strerror (errno)); + + +/** + * Callback function called when download is finished. + * + * @param data the contents of the downloaded file, or NULL + * @param cls closure passed via download_device_description() + */ +typedef void (*download_cb) (char *data, void *cls); + +/** + * Private closure used by download_device_description() and it's callbacks. + */ +struct download_cls +{ + /** + * Scheduler used for the download task. + */ + struct GNUNET_SCHEDULER_Handle *sched; + + /** + * curl_easy handle. + */ + CURL *curl; + + /** + * curl_multi handle. + */ + CURLM *multi; + + /** + * URL of the file to download. + */ + char *url; + + /** + * Time corresponding to timeout wanted by the caller. + */ + struct GNUNET_TIME_Absolute end_time; + + /** + * Buffer to store downloaded content. + */ + char download_buffer[DESCRIPTION_BUFSIZE]; + + /** + * Size of the already downloaded content. + */ + size_t download_pos; + + /** + * User callback to trigger when done. + */ + download_cb caller_cb; + + /** + * User closure to pass to caller_cb. + */ + void *caller_cls; +}; + +/** + * Clean up the state of CURL multi handle and that of + * the only easy handle it uses. + */ +static void +download_clean_up (struct download_cls *cls) +{ + CURLMcode mret; + + mret = curl_multi_cleanup (cls->multi); + if (mret != CURLM_OK) + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_cleanup", __FILE__, __LINE__, + curl_multi_strerror (mret)); + + curl_easy_cleanup (cls->curl); + GNUNET_free (cls); +} + +/** + * Process downloaded bits by calling callback on each HELLO. + * + * @param ptr buffer with downloaded data + * @param size size of a record + * @param nmemb number of records downloaded + * @param ctx closure + * @return number of bytes that were processed (always size*nmemb) + */ +static size_t +callback_download (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + struct download_cls *cls = ctx; + const char *cbuf = ptr; + size_t total; + size_t cpy; + + total = size * nmemb; + if (total == 0) + return total; /* ok, no data */ + + cpy = GNUNET_MIN (total, DESCRIPTION_BUFSIZE - cls->download_pos - 1); + memcpy (&cls->download_buffer[cls->download_pos], cbuf, cpy); + cbuf += cpy; + cls->download_pos += cpy; + +#if DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Downloaded %d records of size %d, download position: %d\n", + size, nmemb, cls->download_pos); +#endif + + return total; +} + +static void +task_download (struct download_cls *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + +/** + * Ask CURL for the select set and then schedule the + * receiving task with the scheduler. + */ +static void +download_prepare (struct download_cls *cls) +{ + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + struct GNUNET_NETWORK_FDSet *grs; + struct GNUNET_NETWORK_FDSet *gws; + long timeout; + struct GNUNET_TIME_Relative rtime; + + max = -1; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + mret = curl_multi_fdset (cls->multi, &rs, &ws, &es, &max); + if (mret != CURLM_OK) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_fdset", __FILE__, __LINE__, + curl_multi_strerror (mret)); + download_clean_up (cls); + cls->caller_cb (NULL, cls->caller_cls); + return; + } + mret = curl_multi_timeout (cls->multi, &timeout); + if (mret != CURLM_OK) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_timeout", __FILE__, __LINE__, + curl_multi_strerror (mret)); + download_clean_up (cls); + cls->caller_cb (NULL, cls->caller_cls); + return; + } + rtime = + GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining + (cls->end_time), + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_MILLISECONDS, timeout)); + grs = GNUNET_NETWORK_fdset_create (); + gws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1); + GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1); + + GNUNET_SCHEDULER_add_select (cls->sched, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_SCHEDULER_NO_TASK, + rtime, + grs, + gws, + (GNUNET_SCHEDULER_Task) & task_download, cls); + GNUNET_NETWORK_fdset_destroy (gws); + GNUNET_NETWORK_fdset_destroy (grs); +} + +/** + * Task that is run when we are ready to receive more data from the device. + * + * @param cls closure + * @param tc task context + */ +static void +task_download (struct download_cls *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + + int running; + struct CURLMsg *msg; + CURLMcode mret; + + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + { +#if DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Shutdown requested while trying to download device description from `%s'\n", + cls->url); +#endif + cls->caller_cb (NULL, cls->caller_cls); + download_clean_up (cls); + return; + } + if (GNUNET_TIME_absolute_get_remaining (cls->end_time).value == 0) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP", + _ + ("Timeout trying to download UPnP device description from '%s'\n"), + cls->url); + cls->caller_cb (NULL, cls->caller_cls); + download_clean_up (cls); + return; + } + + do + { + running = 0; + mret = curl_multi_perform (cls->multi, &running); + + if (running == 0) + { + do + { + msg = curl_multi_info_read (cls->multi, &running); + GNUNET_break (msg != NULL); + if (msg == NULL) + break; + + if ((msg->data.result != CURLE_OK) && + (msg->data.result != CURLE_GOT_NOTHING)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("%s failed for `%s' at %s:%d: `%s'\n"), + "curl_multi_perform", + cls->url, + __FILE__, + __LINE__, + curl_easy_strerror (msg->data.result)); + cls->caller_cb (NULL, cls->caller_cls); + } + else + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + _ + ("Download of device description `%s' completed.\n"), + cls->url); + cls->caller_cb (GNUNET_strdup (cls->download_buffer), + cls->caller_cls); + } + + download_clean_up (cls); + return; + } + while ((running > 0)); + } + } + while (mret == CURLM_CALL_MULTI_PERFORM); + + if (mret != CURLM_OK) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_perform", __FILE__, __LINE__, + curl_multi_strerror (mret)); + download_clean_up (cls); + cls->caller_cb (NULL, cls->caller_cls); + } + + download_prepare (cls); +} + + +/** + * Download description from devices. + * + * @param sched the scheduler to use for the download task + * @param url URL of the file to download + * @param caller_cb user function to call when done + * @caller_cls closure to pass to caller_cb + */ +void +download_device_description (struct GNUNET_SCHEDULER_Handle *sched, + char *url, download_cb caller_cb, + void *caller_cls) +{ + CURL *curl; + CURLM *multi; + CURLcode ret; + CURLMcode mret; + struct download_cls *cls; + + cls = GNUNET_malloc (sizeof (struct download_cls)); + + curl = curl_easy_init (); + if (curl == NULL) + goto error; + + CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &callback_download); + if (ret != CURLE_OK) + goto error; + + CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cls); + if (ret != CURLE_OK) + goto error; + + CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1); + CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4); + /* no need to abort if the above failed */ + CURL_EASY_SETOPT (curl, CURLOPT_URL, url); + if (ret != CURLE_OK) + goto error; + + CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1); + CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, DESCRIPTION_BUFSIZE); + CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet"); + CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 60L); + CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 60L); + + multi = curl_multi_init (); + if (multi == NULL) + { + GNUNET_break (0); + /* clean_up (); */ + return; + } + mret = curl_multi_add_handle (multi, curl); + if (mret != CURLM_OK) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_add_handle", __FILE__, __LINE__, + curl_multi_strerror (mret)); + mret = curl_multi_cleanup (multi); + if (mret != CURLM_OK) + GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP", + _("%s failed at %s:%d: `%s'\n"), + "curl_multi_cleanup", __FILE__, __LINE__, + curl_multi_strerror (mret)); + goto error; + return; + } + +#if DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Preparing to download device description from '%s'\n", + url); +#endif + + cls->sched = sched; + cls->curl = curl; + cls->multi = multi; + cls->url = url; + cls->end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES); + memset (cls->download_buffer, 0, DESCRIPTION_BUFSIZE); + cls->download_pos = 0; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + download_prepare (cls); + return; + + +error: + GNUNET_break (0); + GNUNET_free (cls); + curl_easy_cleanup (curl); + caller_cb (NULL, caller_cls); +} + +/** + * Parse SSDP packet received in reply to a M-SEARCH message. + * + * @param reply contents of the packet + * @param size length of reply + * @param location address of a pointer that will be set to the start + * of the "location" field + * @param location_size pointer where to store the length of the "location" field + * @param st pointer address of a pointer that will be set to the start + * of the "st" (search target) field + * @param st_size pointer where to store the length of the "st" field + * The strings are NOT null terminated */ +static void +parse_msearch_reply (const char *reply, int size, + const char **location, int *location_size, + const char **st, int *st_size) +{ + int a, b, i; + + i = 0; + b = 0; + /* Start of the line */ + a = i; + + while (i < size) + { + switch (reply[i]) + { + case ':': + if (b == 0) + /* End of the "header" */ + b = i; + break; + case '\x0a': + case '\x0d': + if (b != 0) + { + do + { + b++; + } + while (reply[b] == ' '); + + if (0 == strncasecmp (reply + a, "location", 8)) + { + *location = reply + b; + *location_size = i - b; + } + else if (0 == strncasecmp (reply + a, "st", 2)) + { + *st = reply + b; + *st_size = i - b; + } + + b = 0; + } + + a = i + 1; + break; + default: + break; + } + + i++; + } +} + +/** + * Standard port for UPnP discovery (SSDP protocol). + */ +#define PORT 1900 + +/** + * Convert a constant integer into a string. + */ +#define XSTR(s) STR(s) +#define STR(s) #s + +/** + * Standard IPv4 multicast adress for UPnP discovery (SSDP protocol). + */ +#define UPNP_MCAST_ADDR "239.255.255.250" + +/** + * Standard IPv6 multicast adress for UPnP discovery (SSDP protocol). + */ +#define UPNP_MCAST_ADDR6 "FF02:0:0:0:0:0:0:F" + +/** + * Size of the buffer needed to store SSDP requests we send. + */ +#define UPNP_DISCOVER_BUFSIZE 1536 + +/** + * Description of a UPnP device containing everything + * we may need to control it. + * + * Meant to be member of a chained list. + */ +struct UPNP_Dev_ +{ + /** + * Next device in the list, if any. + */ + struct UPNP_Dev_ *pNext; + + /** + * Path to the file describing the device. + */ + char *desc_url; + + /** + * UPnP search target. + */ + char *st; + + /** + * Service type associated with the control_url for the device. + */ + char *service_type; + + /** + * URL to send commands to. + */ + char *control_url; + + /** + * Whether the device is currently connected to the WAN. + */ + int is_connected; + + /** + * IGD Data associated with the device. + */ + struct UPNP_IGD_Data_ *data; +}; + +/** + * Private closure used by UPNP_discover() and its callbacks. + */ +struct UPNP_discover_cls +{ + /** + * Scheduler to use for networking tasks. + */ + struct GNUNET_SCHEDULER_Handle *sched; + + /** + * Remote address used for multicast emission and reception. + */ + struct sockaddr *multicast_addr; + + /** + * Network handle used to send and receive discovery messages. + */ + struct GNUNET_NETWORK_Handle *sudp; + + /** + * fdset used with sudp. + */ + struct GNUNET_NETWORK_FDSet *fdset; + + /** + * Connection handle used to download device description. + */ + struct GNUNET_CONNECTION_Handle *s; + + /** + * Transmission handle used with s. + */ + struct GNUNET_CONNECTION_TransmitHandle *th; + + /** + * Index of the UPnP device type we're currently sending discovery messages to. + */ + int type_index; + + /** + * List of discovered devices. + */ + struct UPNP_Dev_ *dev_list; + + /** + * Device we're currently fetching description from. + */ + struct UPNP_Dev_ *current_dev; + + /** + * User callback to trigger when done. + */ + UPNP_discover_cb_ caller_cb; + + /** + * Closure passed to caller_cb. + */ + void *caller_cls; +}; + +/** + * Check that raw_url is absolute, and if not, use ref_url to resolve it: + * if is_desc_file is GNUNET_YES, the path to the parent of the file is used; + * if it is GNUNET_NO, ref_url will be considered as the base URL for raw URL. + * + * @param ref_url base URL for the device + * @param is_desc_file whether ref_url is a path to the description file + * @param raw_url a possibly relative URL + * @returns a new string with an absolute URL + */ +static char * +get_absolute_url (const char *ref_url, int is_desc_file, const char *raw_url) +{ + char *final_url; + + if ((raw_url[0] == 'h') + && (raw_url[1] == 't') + && (raw_url[2] == 't') + && (raw_url[3] == 'p') + && (raw_url[4] == ':') && (raw_url[5] == '/') && (raw_url[6] == '/')) + { + final_url = GNUNET_strdup (raw_url); + } + else + { + int n = strlen (raw_url); + int l = strlen (ref_url); + int cpy_len = l; + char *slash; + + /* If base URL is a path to the description file, go one level higher */ + if (is_desc_file == GNUNET_YES) + { + slash = strrchr (ref_url, '/'); + cpy_len = slash - ref_url; + } + + final_url = GNUNET_malloc (l + n + 1); + + /* Add trailing slash to base URL if needed */ + if (raw_url[0] != '/' && ref_url[cpy_len] != '\0') + final_url[cpy_len++] = '/'; + + strncpy (final_url, ref_url, cpy_len); + strcpy (final_url + cpy_len, raw_url); + final_url[cpy_len + n] = '\0'; + } + + return final_url; +} + + +/** + * Construct control URL for device from its description URL and + * UPNP_IGD_Data_ information. This involves resolving relative paths + * and choosing between Common Interface Config and interface-specific + * paths. + * + * @param desc_url URL to the description file of the device + * @param data IGD information obtained from the description file + * @returns a URL to control the IGD device, or the empty string + * in case of failure + */ +static char * +format_control_urls (const char *desc_url, struct UPNP_IGD_Data_ *data) +{ + const char *ref_url; + int is_desc_file; + + if (data->base_url[0] != '\0') + { + ref_url = data->base_url; + is_desc_file = GNUNET_NO; + } + else + { + ref_url = desc_url; + is_desc_file = GNUNET_YES; + } + + if (data->control_url[0] != '\0') + return get_absolute_url (ref_url, is_desc_file, data->control_url); + else if (data->control_url_CIF[0] != '\0') + return get_absolute_url (ref_url, is_desc_file, data->control_url_CIF); + else + return GNUNET_strdup (""); +} + +static void get_valid_igd (struct UPNP_discover_cls *cls); + +/** + * Called when "GetStatusInfo" command finishes. Check whether IGD device reports + * to be currently connected or not. + * + * @param response content of the UPnP message answered by the device + * @param received number of received bytes stored in response + * @param data closure from UPNP_discover() + */ +static void +get_valid_igd_connected_cb (char *response, size_t received, void *data) +{ + struct UPNP_discover_cls *cls = data; + struct UPNP_REPLY_NameValueList_ pdata; + char *status; + char *error; + + UPNP_REPLY_parse_ (response, received, &pdata); + + status = UPNP_REPLY_get_value_ (&pdata, "NewConnectionStatus"); + error = UPNP_REPLY_get_value_ (&pdata, "errorCode"); + + if (status) + cls->current_dev->is_connected = (strcmp ("Connected", status) == 0); + else + cls->current_dev->is_connected = GNUNET_NO; + + if (error) + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP", + _("Could not get UPnP device status: error %s\n"), + error); + + GNUNET_free (response); + UPNP_REPLY_free_ (&pdata); + + /* Go on to next device, or finish discovery process */ + cls->current_dev = cls->current_dev->pNext; + get_valid_igd (cls); +} + +/** + * Receive contents of the downloaded UPnP IGD description file, + * and fill UPNP_Dev_ and UPNP_IGD_Data_ structs with this data. + * Then, schedule UPnP command to check whether device is connected. + * + * @param desc UPnP IGD description (in XML) + * @data closure from UPNP_discover() + */ +static void +get_valid_igd_receive (char *desc, void *data) +{ + struct UPNP_discover_cls *cls = data; + struct UPNP_IGD_Data_ *igd_data; + char *buffer; + + if (!desc || strlen (desc) == 0) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP", + "Error getting IGD XML description at %s:%d\n", + __FILE__, __LINE__); + + /* Skip device */ + cls->current_dev->data = NULL; + cls->current_dev->is_connected = GNUNET_NO; + get_valid_igd (cls); + } + + igd_data = GNUNET_malloc (sizeof (struct UPNP_IGD_Data_)); + memset (igd_data, 0, sizeof (struct UPNP_IGD_Data_)); + UPNP_IGD_parse_desc_ (desc, strlen (desc), igd_data); + + cls->current_dev->control_url = + format_control_urls (cls->current_dev->desc_url, igd_data); + + if (igd_data->service_type != '\0') + cls->current_dev->service_type = GNUNET_strdup (igd_data->service_type); + else if (igd_data->service_type_CIF != '\0') + cls->current_dev->service_type = + GNUNET_strdup (igd_data->service_type_CIF); + else + cls->current_dev->service_type = GNUNET_strdup (""); + + cls->current_dev->data = igd_data; + + /* Check whether device is connected */ + buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE); + UPNP_command_ (cls->sched, + cls->current_dev->control_url, + cls->current_dev->data->service_type, + "GetStatusInfo", NULL, buffer, UPNP_COMMAND_BUFSIZE, + get_valid_igd_connected_cb, cls); + + GNUNET_free (desc); +} + +/** + * Free a chained list of UPnP devices. + */ +static void +free_dev_list (struct UPNP_Dev_ *devlist) +{ + struct UPNP_Dev_ *next; + + while (devlist) + { + next = devlist->pNext; + GNUNET_free (devlist->control_url); + GNUNET_free (devlist->service_type); + GNUNET_free (devlist->desc_url); + GNUNET_free (devlist->data); + GNUNET_free (devlist->st); + GNUNET_free (devlist); + devlist = next; + } +} + +/** + * Walk over the list of found devices looking for a connected IGD, + * if present, or at least a disconnected one. + */ +static void +get_valid_igd (struct UPNP_discover_cls *cls) +{ + struct UPNP_Dev_ *dev; + int step; + + /* No device was discovered */ + if (!cls->dev_list) + { + cls->caller_cb (NULL, NULL, cls->caller_cls); + + GNUNET_free (cls); + return; + } + /* We already walked over all devices, see what we got, + * and return the device with the best state we have. */ + else if (cls->current_dev == NULL) + { + for (step = 1; step <= 3; step++) + { + for (dev = cls->dev_list; dev; dev = dev->pNext) + { +#if DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Found device: control_url: %s, service_type: %s\n", + dev->control_url, dev->service_type); +#endif + /* Accept connected IGDs on step 1, non-connected IGDs + * on step 2, and other device types on step 3. */ + if ((step == 1 && dev->is_connected) + || (step < 3 && 0 != strcmp (dev->service_type, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"))) + continue; + + cls->caller_cb (dev->control_url, + dev->service_type, cls->caller_cls); + + free_dev_list (cls->dev_list); + GNUNET_free (cls); + return; + } + } + + /* We cannot reach this... */ + GNUNET_assert (GNUNET_NO); + } + + /* There are still devices to ask, go on */ + download_device_description (cls->sched, cls->current_dev->desc_url, + get_valid_igd_receive, cls); +} + +static const char *const discover_type_list[] = { + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + NULL +}; + +static void +discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc); + +/** + * Handle response from device. Stop when all device types have been tried, + * and get their descriptions. + * + * @param data closure from UPNP_discover() + * @buf content of the reply + * @available number of bytes stored in buf + * @addr address of the sender + * @addrlen size of addr + * @param errCode value of errno + */ +static void +discover_recv (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct UPNP_discover_cls *cls = data; + GNUNET_SCHEDULER_TaskIdentifier task_w; + struct UPNP_Dev_ *tmp; + socklen_t addrlen; + ssize_t received; + char buf[DISCOVER_BUFSIZE]; + const char *desc_url = NULL; + int urlsize = 0; + const char *st = NULL; + int stsize = 0; + + /* Free fdset that was used for this sned/receive operation */ + GNUNET_NETWORK_fdset_destroy (cls->fdset); + + if (cls->multicast_addr->sa_family == AF_INET) + addrlen = sizeof (struct sockaddr_in); + else + addrlen = sizeof (struct sockaddr_in6); + + errno = 0; + received = + GNUNET_NETWORK_socket_recvfrom (cls->sudp, &buf, DISCOVER_BUFSIZE - 1, + (struct sockaddr *) cls->multicast_addr, + &addrlen); + if (received == GNUNET_SYSERR) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_recvfrom"); + } +#if DEBUG_UPNP + else + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Received %d bytes from %s\n", received, + GNUNET_a2s (cls->multicast_addr, addrlen)); + } +#endif + + parse_msearch_reply (buf, received, &desc_url, &urlsize, &st, &stsize); + + if (st && desc_url) + { + tmp = (struct UPNP_Dev_ *) GNUNET_malloc (sizeof (struct UPNP_Dev_)); + tmp->pNext = cls->dev_list; + + tmp->desc_url = GNUNET_malloc (urlsize + 1); + strncpy (tmp->desc_url, desc_url, urlsize); + tmp->desc_url[urlsize] = '\0'; + + tmp->st = GNUNET_malloc (stsize + 1); + strncpy (tmp->st, st, stsize); + tmp->st[stsize] = '\0'; + cls->dev_list = tmp; +#if DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Found device %s when looking for type %s\n", + tmp->desc_url, tmp->st); +#endif + } + + /* Continue discovery until all types of devices have been tried */ + if (discover_type_list[cls->type_index]) + { + /* Send queries for each device type and wait for a possible reply. + * receiver callback takes care of trying another device type, + * and eventually calls the caller's callback. */ + cls->fdset = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_zero (cls->fdset); + GNUNET_NETWORK_fdset_set (cls->fdset, cls->sudp); + + task_w = GNUNET_SCHEDULER_add_select (cls->sched, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_SCHEDULER_NO_TASK, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 15), + NULL, cls->fdset, &discover_send, + cls); + + GNUNET_SCHEDULER_add_select (cls->sched, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + task_w, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 5), cls->fdset, + NULL, &discover_recv, cls); + } + else + { + GNUNET_NETWORK_socket_close (cls->sudp); + GNUNET_free (cls->multicast_addr); + cls->current_dev = cls->dev_list; + get_valid_igd (cls); + } +} + +/** + * Send the SSDP M-SEARCH packet. + * + * @param data closure from UPNP_discover() + * @param tc task context + */ +static void +discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct UPNP_discover_cls *cls = data; + socklen_t addrlen; + ssize_t n, sent; + char buf[DISCOVER_BUFSIZE]; + static const char msearch_msg[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: " UPNP_MCAST_ADDR ":" XSTR (PORT) "\r\n" + "ST: %s\r\n" "MAN: \"ssdp:discover\"\r\n" "MX: 3\r\n" "\r\n"; + + if (cls->multicast_addr->sa_family == AF_INET) + addrlen = sizeof (struct sockaddr_in); + else + addrlen = sizeof (struct sockaddr_in6); + + n = + snprintf (buf, DISCOVER_BUFSIZE, msearch_msg, + discover_type_list[cls->type_index++]); + + errno = 0; + sent = GNUNET_NETWORK_socket_sendto (cls->sudp, buf, n, + (struct sockaddr *) + cls->multicast_addr, addrlen); + if (sent == GNUNET_SYSERR) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_sendto"); + } + else if (sent < n) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Could only send %d bytes to %s, needed %d bytes\n", + sent, GNUNET_a2s (cls->multicast_addr, addrlen), n); + } +#if DEBUG_UPNP + else + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "Sent %d bytes to %s\n", sent, + GNUNET_a2s (cls->multicast_addr, addrlen)); + } +#endif +} + +/** + * Search for UPnP Internet Gateway Devices (IGD) on a given network interface. + * If several devices are found, a device that is connected to the WAN + * is returned first (if any). + * + * @param sched scheduler to use for network tasks + * @param multicastif network interface to send discovery messages, or NULL + * @param addr address used to send messages on multicastif, or NULL + * @param caller_cb user function to call when done + * @param caller_cls closure to pass to caller_cb + */ +void +UPNP_discover_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *multicastif, + const struct sockaddr *addr, + UPNP_discover_cb_ caller_cb, void *caller_cls) +{ + int opt = 1; + int domain = PF_INET; + int if_index; + struct in6_addr any_addr = IN6ADDR_ANY_INIT; + struct sockaddr_in sockudp_r, sockudp_w; + struct sockaddr_in6 sockudp6_r, sockudp6_w; + GNUNET_SCHEDULER_TaskIdentifier task_w; + struct GNUNET_NETWORK_Handle *sudp; + struct UPNP_discover_cls *cls; + + + if (addr && addr->sa_family == AF_INET) + { + domain = PF_INET; + } + else if (addr && addr->sa_family == AF_INET6) + { + domain = PF_INET6; + } + else if (addr) + { + GNUNET_break (0); + caller_cb (NULL, NULL, caller_cls); + return; + } + + errno = 0; + sudp = GNUNET_NETWORK_socket_create (domain, SOCK_DGRAM, 0); + + if (sudp == NULL) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_create"); + caller_cb (NULL, NULL, caller_cls); + return; + } + + + cls = GNUNET_malloc (sizeof (struct UPNP_discover_cls)); + cls->sched = sched; + cls->sudp = sudp; + cls->type_index = 0; + cls->dev_list = NULL; + cls->current_dev = NULL; + cls->caller_cb = caller_cb; + cls->caller_cls = caller_cls; + + + if (domain == PF_INET) + { + /* receive */ + memset (&sockudp_r, 0, sizeof (struct sockaddr_in)); + sockudp_r.sin_family = AF_INET; +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + sockudp_r.sin_len = sizeof (struct sockaddr_in); +#endif + sockudp_r.sin_port = 0; + sockudp_r.sin_addr.s_addr = INADDR_ANY; + + /* send */ + memset (&sockudp_w, 0, sizeof (struct sockaddr_in)); + sockudp_w.sin_family = AF_INET; + sockudp_w.sin_port = htons (PORT); + sockudp_w.sin_addr.s_addr = inet_addr (UPNP_MCAST_ADDR); +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + sockudp_w.sin_len = sizeof (struct sockaddr_in); +#endif + + cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in)); + memcpy (cls->multicast_addr, &sockudp_w, sizeof (struct sockaddr_in)); + } + else + { + /* receive */ + memcpy (&sockudp6_r, addr, sizeof (struct sockaddr_in6)); + sockudp6_r.sin6_port = 0; + sockudp6_r.sin6_addr = any_addr; +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + sockudp6_r.sin6_len = sizeof (struct sockaddr_in6); +#endif + + /* send */ + memset (&sockudp6_w, 0, sizeof (struct sockaddr_in6)); + sockudp6_w.sin6_family = AF_INET6; + sockudp6_w.sin6_port = htons (PORT); + if (inet_pton (AF_INET6, UPNP_MCAST_ADDR6, &sockudp6_w.sin6_addr) != 1) + { + PRINT_SOCKET_ERROR ("inet_pton"); + caller_cb (NULL, NULL, caller_cls); + return; + } +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + sockudp6_w.sin6_len = sizeof (struct sockaddr_in6); +#endif + + cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in6)); + memcpy (cls->multicast_addr, &sockudp6_w, sizeof (struct sockaddr_in6)); + } + + if (GNUNET_NETWORK_socket_setsockopt + (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) == GNUNET_SYSERR) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt"); + GNUNET_NETWORK_socket_close (sudp); + caller_cb (NULL, NULL, caller_cls); + return; + } + + if (addr) + { + if (domain == PF_INET) + { + sockudp_r.sin_addr.s_addr = + ((struct sockaddr_in *) addr)->sin_addr.s_addr; + if (GNUNET_NETWORK_socket_setsockopt + (sudp, IPPROTO_IP, IP_MULTICAST_IF, + (const char *) &sockudp_r.sin_addr, + sizeof (struct in_addr)) == GNUNET_SYSERR) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt"); + } + } + else + { + if (multicastif) + { + if_index = if_nametoindex (multicastif); + if (!if_index) + PRINT_SOCKET_ERROR ("if_nametoindex"); + + if (GNUNET_NETWORK_socket_setsockopt + (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index, + sizeof (if_index)) == GNUNET_SYSERR) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt"); + } + } + + memcpy (&sockudp6_r.sin6_addr, + &((struct sockaddr_in6 *) addr)->sin6_addr, + sizeof (sockudp6_r.sin6_addr)); + } + } + + if (domain == PF_INET) + { + /* Bind to receive response before sending packet */ + if (GNUNET_NETWORK_socket_bind + (sudp, (struct sockaddr *) &sockudp_r, + sizeof (struct sockaddr_in)) != GNUNET_OK) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind"); + GNUNET_NETWORK_socket_close (sudp); + GNUNET_free (cls->multicast_addr); + caller_cb (NULL, NULL, caller_cls); + return; + } + } + else + { + /* Bind to receive response before sending packet */ + if (GNUNET_NETWORK_socket_bind + (sudp, (struct sockaddr *) &sockudp6_r, + sizeof (struct sockaddr_in6)) != GNUNET_OK) + { + PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind"); + GNUNET_free (cls->multicast_addr); + GNUNET_NETWORK_socket_close (sudp); + caller_cb (NULL, NULL, caller_cls); + return; + } + } + + /* Send queries for each device type and wait for a possible reply. + * receiver callback takes care of trying another device type, + * and eventually calls the caller's callback. */ + cls->fdset = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_zero (cls->fdset); + GNUNET_NETWORK_fdset_set (cls->fdset, sudp); + + task_w = GNUNET_SCHEDULER_add_select (sched, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_SCHEDULER_NO_TASK, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 15), NULL, + cls->fdset, &discover_send, cls); + + GNUNET_SCHEDULER_add_select (sched, + GNUNET_SCHEDULER_PRIORITY_DEFAULT, + task_w, + GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 15), cls->fdset, + NULL, &discover_recv, cls); +} diff --git a/src/nat/upnp-discover.h b/src/nat/upnp-discover.h new file mode 100644 index 000000000..2e996cd88 --- /dev/null +++ b/src/nat/upnp-discover.h @@ -0,0 +1,84 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-discover.h + * @brief Look for UPnP IGD devices + * + * @author Milan Bouchet-Valat + */ +#ifndef UPNPC_H +#define UPNPC_H + +#include "platform.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "upnp-commands.h" + +typedef void (*UPNP_discover_cb_) (const char *control_urls, + const char *service_type, void *cls); + +/** + * Search for UPnP Internet Gateway Devices (IGD) on a given network interface. + * If several devices are found, a device that is connected to the WAN + * is returned first (if any). + * + * @param sched scheduler to use for network tasks + * @param multicastif network interface to send discovery messages, or NULL + * @param addr address used to send messages on multicastif, or NULL + * @param caller_cb user function to call when done + * @param caller_cls closure to pass to caller_cb + */ +void UPNP_discover_ (struct GNUNET_SCHEDULER_Handle *sched, + const char *multicastif, + const struct sockaddr *addr, + UPNP_discover_cb_ caller_cb, void *caller_cls); + +#endif diff --git a/src/nat/upnp-igd-parse.c b/src/nat/upnp-igd-parse.c new file mode 100644 index 000000000..0812065ed --- /dev/null +++ b/src/nat/upnp-igd-parse.c @@ -0,0 +1,207 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2008, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-igd-parse.h + * @brief Parser for XML descriptions of UPnP Internet Gateway Devices + * + * @author Milan Bouchet-Valat + */ +#include +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "upnp-minixml.h" +#include "upnp-igd-parse.h" + +/** + * Start element handler: update nesting level counter + * and copy element name. + */ +static void +start_elt (void *d, const char *name, int l) +{ + struct UPNP_IGD_Data_ *datas = (struct UPNP_IGD_Data_ *) d; + + memcpy (datas->cur_elt_name, name, l); + datas->cur_elt_name[l] = '\0'; + datas->level++; + if ((l == 7) && !memcmp (name, "service", l)) + { + datas->control_url_tmp[0] = '\0'; + datas->event_sub_url_tmp[0] = '\0'; + datas->scpd_url_tmp[0] = '\0'; + datas->service_type_tmp[0] = '\0'; + } +} + +/** + * End element handler: update nesting level counter + * and update parser state if service element is parsed. + */ +static void +end_elt (void *d, const char *name, int l) +{ + struct UPNP_IGD_Data_ *datas = (struct UPNP_IGD_Data_ *) d; + + datas->level--; + + if ((l == 7) && !memcmp (name, "service", l)) + { + if (0 == strcmp (datas->service_type_tmp, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")) + { + memcpy (datas->control_url_CIF, datas->control_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->event_sub_url_CIF, datas->event_sub_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->scpd_url_CIF, datas->scpd_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->service_type_CIF, datas->service_type_tmp, + MINIUPNPC_URL_MAXSIZE); + } + else if (0 == strcmp (datas->service_type_tmp, + "urn:schemas-upnp-org:service:WANIPConnection:1") + || 0 == strcmp (datas->service_type_tmp, + "urn:schemas-upnp-org:service:WANPPPConnection:1")) + { + memcpy (datas->control_url, datas->control_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->event_sub_url, datas->event_sub_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->scpd_url, datas->scpd_url_tmp, + MINIUPNPC_URL_MAXSIZE); + memcpy (datas->service_type, datas->service_type_tmp, + MINIUPNPC_URL_MAXSIZE); + } + } +} + +/** + * Data handler: copy data depending on the current + * element name and state. + */ +static void +IGDdata (void *d, const char *data, int l) +{ + struct UPNP_IGD_Data_ *datas = (struct UPNP_IGD_Data_ *) d; + char *dstmember = NULL; + + if (!strcmp (datas->cur_elt_name, "URLBase")) + dstmember = datas->base_url; + else if (!strcmp (datas->cur_elt_name, "serviceType")) + dstmember = datas->service_type_tmp; + else if (!strcmp (datas->cur_elt_name, "controlURL")) + dstmember = datas->control_url_tmp; + else if (!strcmp (datas->cur_elt_name, "eventSubURL")) + dstmember = datas->event_sub_url_tmp; + else if (!strcmp (datas->cur_elt_name, "SCPDURL")) + dstmember = datas->scpd_url_tmp; + + /* Copy current element name into destination member */ + if (dstmember) + { + if (l >= MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE - 1; + + memcpy (dstmember, data, l); + dstmember[l] = '\0'; + } +} + +#ifdef DEBUG_UPNP +static void +print_IGD (struct UPNP_IGD_Data_ *d) +{ + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "base_url = %s\n", d->base_url); + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "WAN Device (Common interface config) :\n" + " sevice_type = %s\n" + " control_url = %s\n" + " event_sub_url = %s\n" + " scpd_url = %s\n", + d->service_type_CIF, + d->control_url_CIF, d->event_sub_url_CIF, d->scpd_url_CIF); + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "WAN Connection Device (IP or PPP Connection):\n" + " service_type = %s\n" + " control_url = %s\n" + " event_sub_url = %s\n" + " scpd_url = %s\n", + d->service_type, + d->control_url, d->event_sub_url, d->scpd_url); +} +#endif + +/** + * Parse XML description of an IGD device into a UPNP_IGD_Data_ struct. + */ +void +UPNP_IGD_parse_desc_ (const char *buffer, int buf_size, + struct UPNP_IGD_Data_ *data) +{ + struct UPNP_xml_parser_ parser; + + parser.xml_start = buffer; + parser.xml_size = buf_size; + parser.cls = data; + parser.start_elt_func = start_elt; + parser.end_elt_func = end_elt; + parser.data_func = IGDdata; + parser.att_func = 0; + + UPNP_parse_xml_ (&parser); + +#ifdef DEBUG_UPNP + print_IGD (data); +#endif +} diff --git a/src/nat/upnp-igd-parse.h b/src/nat/upnp-igd-parse.h new file mode 100644 index 000000000..8e0b8510c --- /dev/null +++ b/src/nat/upnp-igd-parse.h @@ -0,0 +1,99 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2008, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-igd-parse.h + * @brief Parser for XML descriptions of UPnP Internet Gateway Devices + * + * @author Milan Bouchet-Valat + */ +#ifndef UPNP_IGD_PARSE_H +#define UPNP_IGD_PARSE_H + +#define MINIUPNPC_URL_MAXSIZE (128) + +/** + * Structure to store the result of the parsing of UPnP + * descriptions of Internet Gateway Devices. + */ +struct UPNP_IGD_Data_ +{ + char cur_elt_name[MINIUPNPC_URL_MAXSIZE]; + char base_url[MINIUPNPC_URL_MAXSIZE]; + int level; + + /* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ + char control_url_CIF[MINIUPNPC_URL_MAXSIZE]; + char event_sub_url_CIF[MINIUPNPC_URL_MAXSIZE]; + char scpd_url_CIF[MINIUPNPC_URL_MAXSIZE]; + char service_type_CIF[MINIUPNPC_URL_MAXSIZE]; + + /* "urn:schemas-upnp-org:service:WANIPConnection:1" + * "urn:schemas-upnp-org:service:WANPPPConnection:1" */ + char control_url[MINIUPNPC_URL_MAXSIZE]; + char event_sub_url[MINIUPNPC_URL_MAXSIZE]; + char scpd_url[MINIUPNPC_URL_MAXSIZE]; + char service_type[MINIUPNPC_URL_MAXSIZE]; + + /* Used temporarily by the parser */ + char control_url_tmp[MINIUPNPC_URL_MAXSIZE]; + char event_sub_url_tmp[MINIUPNPC_URL_MAXSIZE]; + char scpd_url_tmp[MINIUPNPC_URL_MAXSIZE]; + char service_type_tmp[MINIUPNPC_URL_MAXSIZE]; +}; + +/** + * Parse UPnP IGD XML description to a UPNP_IGD_Data_ structure. + */ +void +UPNP_IGD_parse_desc_ (const char *buffer, int buf_size, + struct UPNP_IGD_Data_ *data); + +#endif diff --git a/src/nat/upnp-minixml.c b/src/nat/upnp-minixml.c new file mode 100644 index 000000000..9f4bd735d --- /dev/null +++ b/src/nat/upnp-minixml.c @@ -0,0 +1,239 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/minixml.c + * @brief Simple XML parser used by UPnP + * + * @author Milan Bouchet-Valat + */ + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "upnp-minixml.h" + +/** + * Used to parse the argument list. + * + * @returns GNUNET_OK on success, or GNUNET_SYSERR if the end + * of the xmlbuffer is reached + */ +static int +parse_att (struct UPNP_xml_parser_ *p) +{ + const char *att_name; + int att_name_len; + const char *att_value; + int att_value_len; + while (p->xml < p->xml_end) + { + if (*p->xml == '/' || *p->xml == '>') + return GNUNET_OK; + if (!IS_WHITE_SPACE (*p->xml)) + { + char sep; + att_name = p->xml; + att_name_len = 0; + while (*p->xml != '=' && !IS_WHITE_SPACE (*p->xml)) + { + att_name_len++; + p->xml++; + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + } + while (*(p->xml++) != '=') + { + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + } + while (IS_WHITE_SPACE (*p->xml)) + { + p->xml++; + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + } + sep = *p->xml; + if (sep == '\'' || sep == '\"') + { + p->xml++; + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + att_value = p->xml; + att_value_len = 0; + while (*p->xml != sep) + { + att_value_len++; + p->xml++; + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + } + } + else + { + att_value = p->xml; + att_value_len = 0; + while (!IS_WHITE_SPACE (*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + att_value_len++; + p->xml++; + if (p->xml >= p->xml_end) + return GNUNET_SYSERR; + } + } + + if (p->att_func) + p->att_func (p->cls, att_name, att_name_len, att_value, + att_value_len); + } + p->xml++; + } + return GNUNET_SYSERR; +} + +/** + * Parse the xml stream and call the callback + * functions when needed... + */ +void +parse_elt (struct UPNP_xml_parser_ *p) +{ + int i; + const char *element_name; + while (p->xml < (p->xml_end - 1)) + { + /* Element name */ + if ((p->xml)[0] == '<' && (p->xml)[1] != '?') + { + i = 0; + element_name = ++p->xml; + while (!IS_WHITE_SPACE (*p->xml) + && (*p->xml != '>') && (*p->xml != '/')) + { + i++; + p->xml++; + if (p->xml >= p->xml_end) + return; + /* to ignore namespace : */ + if (*p->xml == ':') + { + i = 0; + element_name = ++p->xml; + } + } + + /* Start of element */ + if (i > 0) + { + if (p->start_elt_func) + p->start_elt_func (p->cls, element_name, i); + if (parse_att (p) != GNUNET_OK) + return; + if (*p->xml != '/') + { + const char *data; + i = 0; + data = ++p->xml; + if (p->xml >= p->xml_end) + return; + while (IS_WHITE_SPACE (*p->xml)) + { + p->xml++; + if (p->xml >= p->xml_end) + return; + } + while (*p->xml != '<') + { + i++; + p->xml++; + if (p->xml >= p->xml_end) + return; + } + if (i > 0 && p->data_func) + p->data_func (p->cls, data, i); + } + } + /* End of element */ + else if (*p->xml == '/') + { + i = 0; + element_name = ++p->xml; + if (p->xml >= p->xml_end) + return; + while ((*p->xml != '>')) + { + i++; + p->xml++; + if (p->xml >= p->xml_end) + return; + } + if (p->end_elt_func) + p->end_elt_func (p->cls, element_name, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/** + * Parse XML content according to the values stored in the parser struct. + * The parser must be initialized before calling this function + */ +void +UPNP_parse_xml_ (struct UPNP_xml_parser_ *parser) +{ + parser->xml = parser->xml_start; + parser->xml_end = parser->xml_start + parser->xml_size; + parse_elt (parser); +} diff --git a/src/nat/upnp-minixml.h b/src/nat/upnp-minixml.h new file mode 100644 index 000000000..07cf70939 --- /dev/null +++ b/src/nat/upnp-minixml.h @@ -0,0 +1,131 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally based on the miniupnp library. + * Copyright (c) 2005-2008, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-minixml.h + * @brief Simple XML parser used by UPnP + * + * @author Milan Bouchet-Valat + */ + +#ifndef MINIXML_H +#define MINIXML_H + +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/** + * Structure describing the contents and methods that should be + * used when running parse_xml(); + * + * If a callback function pointer is set to NULL, the function + * is not called */ +struct UPNP_xml_parser_ +{ + /** + * Pointer to the XML data to parse + */ + const char *xml_start; + + /** + * Pointer to the last character to parse (optional) + */ + const char *xml_end; + + /** + * Size of the data stored at xml_start + */ + int xml_size; + + /** + * Pointer to current character (private) + */ + const char *xml; + + /** + * Closure for user-provided callback functions + */ + void *cls; + + /** + * User function called when reaching the start of an XML element. + */ + void (*start_elt_func) (void *cls, const char *elt, int elt_len); + + /** + * User function called when reaching the end of an XML element. + */ + void (*end_elt_func) (void *cls, const char *elt, int elt_len); + + /** + * User function called when an XML element data is found. + */ + void (*data_func) (void *cls, const char *data, int data_len); + + /** + * User function called for every XML element attribute. + */ + void (*att_func) (void *cls, const char *att_name, int att_name_len, + const char *att_value, int att_value_len); +}; + +/** + * Parse data provided to the xml_parser structure, using + * user-provided functions. + * + * The xmlparser structure must be initialized before the call; + * the following structure members have to be set: + * xml_start, xml_size, cls, *func. + * The xml member is for internal usage, xml_end is computed + * automatically. + * + * @param parser the structure used for parsing */ +void UPNP_parse_xml_ (struct UPNP_xml_parser_ *parser); + +#endif diff --git a/src/nat/upnp-reply-parse.c b/src/nat/upnp-reply-parse.c new file mode 100644 index 000000000..398cde834 --- /dev/null +++ b/src/nat/upnp-reply-parse.c @@ -0,0 +1,166 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally inspired by the miniupnp library. + * Copyright (c) 2006, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-reply-parse.c + * @brief Parser for XML replies to UPnP commands + * + * @author Milan Bouchet-Valat + */ + +#include +#include +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "upnp-minixml.h" +#include "upnp-reply-parse.h" + +static void +start_elt (void *d, const char *name, int l) +{ + struct UPNP_REPLY_NameValueList_ *data = + (struct UPNP_REPLY_NameValueList_ *) d; + + if (l > 63) + l = 63; + + memcpy (data->curelt, name, l); + data->curelt[l] = '\0'; +} + +static void +get_data (void *d, const char *datas, int l) +{ + struct UPNP_REPLY_NameValueList_ *data = + (struct UPNP_REPLY_NameValueList_ *) d; + struct UPNP_REPLY_NameValue_ *nv; + + nv = malloc (sizeof (struct UPNP_REPLY_NameValue_)); + + if (l > 63) + l = 63; + + strncpy (nv->name, data->curelt, 64); + nv->name[63] = '\0'; + memcpy (nv->value, datas, l); + nv->value[l] = '\0'; + + LIST_INSERT_HEAD (&(data->head), nv, entries); +} + +void +UPNP_REPLY_parse_ (const char *buffer, int buf_size, + struct UPNP_REPLY_NameValueList_ *data) +{ + struct UPNP_xml_parser_ parser; + + LIST_INIT (&(data->head)); + + /* Init xml_parser object */ + parser.xml_start = buffer; + parser.xml_size = buf_size; + parser.cls = data; + parser.start_elt_func = start_elt; + parser.end_elt_func = 0; + parser.data_func = get_data; + parser.att_func = 0; + + UPNP_parse_xml_ (&parser); +} + +void +UPNP_REPLY_free_ (struct UPNP_REPLY_NameValueList_ *pdata) +{ + struct UPNP_REPLY_NameValue_ *nv; + + while ((nv = pdata->head.lh_first) != NULL) + { + LIST_REMOVE (nv, entries); + GNUNET_free (nv); + } +} + +char * +UPNP_REPLY_get_value_ (struct UPNP_REPLY_NameValueList_ *pdata, + const char *Name) +{ + struct UPNP_REPLY_NameValue_ *nv; + char *p = NULL; + + for (nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); nv = nv->entries.le_next) + { + if (strcmp (nv->name, Name) == 0) + p = nv->value; + } + + return p; +} + +#if DEBUG_UPNP +void +UPNP_REPLY_print_ (char *buffer, int buf_size) +{ + struct UPNP_REPLY_NameValueList_ pdata; + struct UPNP_REPLY_NameValue_ *nv; + + UPNP_REPLY_parse_ (buffer, buf_size, &pdata); + + for (nv = pdata.head.lh_first; nv != NULL; nv = nv->entries.le_next) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP", + "%s = %s", nv->name, nv->value); + } + + UPNP_REPLY_free_ (&pdata); +} +#endif diff --git a/src/nat/upnp-reply-parse.h b/src/nat/upnp-reply-parse.h new file mode 100644 index 000000000..e5bedbd8f --- /dev/null +++ b/src/nat/upnp-reply-parse.h @@ -0,0 +1,107 @@ +/* + This file is part of GNUnet. + (C) 2009, 2010 Christian Grothoff (and other contributing authors) + + GNUnet is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/* + * Code in this file is originally inspired by the miniupnp library. + * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved. + * + * Original licence: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file nat/upnp-reply-parse.h + * @brief Parser for XML replies to UPnP commands + * + * @author Milan Bouchet-Valat + */ + +#ifndef UPNP_PARSE_REPLY_H +#define UPNP_PARSE_REPLY_H + +#include "bsdqueue.h" + + /** + * Name-value pair used by UPNP_REPLY_NameValueList. + */ +struct UPNP_REPLY_NameValue_ +{ + LIST_ENTRY (UPNP_REPLY_NameValue_) entries; + char name[64]; + char value[64]; +}; + + /** + * Name-value list to store data parsed from a UPnP reply. + */ +struct UPNP_REPLY_NameValueList_ +{ + LIST_HEAD (listhead, UPNP_REPLY_NameValue_) head; + char curelt[64]; +}; + + /** + * Parse UPnP XML reply to a name-value list. + */ +void +UPNP_REPLY_parse_ (const char *buffer, int buf_size, + struct UPNP_REPLY_NameValueList_ *data); + + /** + * Free name-value list obtained using UPNP_REPLY_parse(). + */ +void UPNP_REPLY_free_ (struct UPNP_REPLY_NameValueList_ *pdata); + + /** + * Get value corresponding to name from a name-value list. + */ +char *UPNP_REPLY_get_value_ (struct UPNP_REPLY_NameValueList_ *pdata, + const char *name); + +#if DEBUG_UPNP + /** + * Parse a UPnP XMl reply and print the result as names-value pairs. + */ +void UPNP_REPLY_print_ (char *buffer, int buf_size); +#endif + +#endif diff --git a/src/nat/upnp.c b/src/nat/upnp.c index 84abe339d..e383055b5 100644 --- a/src/nat/upnp.c +++ b/src/nat/upnp.c @@ -35,12 +35,12 @@ #include #include -#include -#include - #include "platform.h" #include "gnunet_common.h" #include "gnunet_nat_lib.h" +#include "nat.h" +#include "upnp-discover.h" +#include "upnp-commands.h" #include "upnp.h" /* Component name for logging */ @@ -57,9 +57,10 @@ enum UPNP_State struct GNUNET_NAT_UPNP_Handle { + struct GNUNET_SCHEDULER_Handle *sched; int hasDiscovered; - struct UPNPUrls urls; - struct IGDdatas data; + char *control_url; + char *service_type; int port; const struct sockaddr *addr; socklen_t addrlen; @@ -67,20 +68,21 @@ struct GNUNET_NAT_UPNP_Handle enum UPNP_State state; struct sockaddr *ext_addr; const char *iface; + int processing; + GNUNET_NAT_UPNP_pulse_cb pulse_cb; + void *pulse_cls; }; static int process_if (void *cls, const char *name, - int isDefault, - const struct sockaddr *addr, - socklen_t addrlen) + int isDefault, const struct sockaddr *addr, socklen_t addrlen) { struct GNUNET_NAT_UPNP_Handle *upnp = cls; if (addr && GNUNET_NAT_cmp_addr (upnp->addr, addr) == 0) { - upnp->iface = name; // BADNESS! + upnp->iface = name; // BADNESS! return GNUNET_SYSERR; } @@ -88,41 +90,245 @@ process_if (void *cls, } -GNUNET_NAT_UPNP_Handle * -GNUNET_NAT_UPNP_init (const struct sockaddr *addr, - socklen_t addrlen, - u_short port) +struct GNUNET_NAT_UPNP_Handle * +GNUNET_NAT_UPNP_init (struct GNUNET_SCHEDULER_Handle *sched, + const struct sockaddr *addr, + socklen_t addrlen, + u_short port, + GNUNET_NAT_UPNP_pulse_cb pulse_cb, void *pulse_cls) { - GNUNET_NAT_UPNP_Handle *upnp; + struct GNUNET_NAT_UPNP_Handle *handle; + + handle = GNUNET_malloc (sizeof (struct GNUNET_NAT_UPNP_Handle)); + handle->sched = sched; + handle->processing = GNUNET_NO; + handle->state = UPNP_DISCOVER; + handle->addr = addr; + handle->addrlen = addrlen; + handle->port = port; + handle->pulse_cb = pulse_cb; + handle->pulse_cls = pulse_cls; + handle->control_url = NULL; + handle->service_type = NULL; - upnp = GNUNET_malloc (sizeof (GNUNET_NAT_UPNP_Handle)); - upnp->state = UPNP_DISCOVER; - upnp->addr = addr; - upnp->addrlen = addrlen; - upnp->port = port; /* Find the interface corresponding to the address, * on which we should broadcast call for routers */ - GNUNET_OS_network_interfaces_list (&process_if, upnp); - if (!upnp->iface) - GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, - COMP_NAT_UPNP, - "Could not find an interface matching the wanted address.\n"); - return upnp; + GNUNET_OS_network_interfaces_list (&process_if, handle); + if (!handle->iface) + GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, + COMP_NAT_UPNP, + "Could not find an interface matching the wanted address.\n"); + return handle; } void -GNUNET_NAT_UPNP_close (GNUNET_NAT_UPNP_Handle * handle) +GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle *handle) { GNUNET_assert (!handle->is_mapped); GNUNET_assert ((handle->state == UPNP_IDLE) - || (handle->state == UPNP_ERR) || (handle->state == UPNP_DISCOVER)); + || (handle->state == UPNP_ERR) + || (handle->state == UPNP_DISCOVER)); - if (handle->hasDiscovered) - FreeUPNPUrls (&handle->urls); + GNUNET_free_non_null (handle->control_url); + GNUNET_free_non_null (handle->service_type); GNUNET_free (handle); } +static void +pulse_finish (struct GNUNET_NAT_UPNP_Handle *handle) +{ + enum GNUNET_NAT_PortState status; + handle->processing = GNUNET_NO; + + switch (handle->state) + { + case UPNP_DISCOVER: + status = GNUNET_NAT_PORT_UNMAPPED; + break; + + case UPNP_MAP: + status = GNUNET_NAT_PORT_MAPPING; + break; + + case UPNP_UNMAP: + status = GNUNET_NAT_PORT_UNMAPPING; + break; + + case UPNP_IDLE: + status = + handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED; + break; + + default: + status = GNUNET_NAT_PORT_ERROR; + break; + } + + handle->pulse_cb (status, handle->ext_addr, handle->pulse_cls); +} + +static void +discover_cb (const char *control_url, const char *service_type, void *cls) +{ + struct GNUNET_NAT_UPNP_Handle *handle = cls; + + if (control_url) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _("Found Internet Gateway Device \"%s\"\n"), + control_url); + + GNUNET_free_non_null (handle->control_url); + GNUNET_free_non_null (handle->service_type); + + handle->control_url = GNUNET_strdup (control_url); + handle->service_type = GNUNET_strdup (service_type); + handle->state = UPNP_IDLE; + handle->hasDiscovered = 1; + } + else + { + handle->control_url = NULL; + handle->service_type = NULL; + handle->state = UPNP_ERR; +#ifdef DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, + "UPNP device discovery failed\n"); +#endif + } + + pulse_finish (handle); +} + +static void +check_port_mapping_cb (int error, const char *control_url, + const char *service_type, const char *extPort, + const char *inPort, const char *proto, + const char *remoteHost, void *cls) +{ + struct GNUNET_NAT_UPNP_Handle *handle = cls; + + if (error) + { + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _("Port %d isn't forwarded\n"), handle->port); + handle->is_mapped = GNUNET_NO; + } + + pulse_finish (handle); +} + +static void +delete_port_mapping_cb (int error, const char *control_url, + const char *service_type, const char *extPort, + const char *inPort, const char *proto, + const char *remoteHost, void *cls) +{ + struct GNUNET_NAT_UPNP_Handle *handle = cls; + + if (error) + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _ + ("Could not stop port forwarding through \"%s\", service \"%s\": error %d\n"), + handle->control_url, handle->service_type, error); + else + { + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _ + ("Stopped port forwarding through \"%s\", service \"%s\"\n"), + handle->control_url, handle->service_type); + handle->is_mapped = !error; + handle->state = UPNP_IDLE; + handle->port = -1; + } + + pulse_finish (handle); +} + +static void +add_port_mapping_cb (int error, const char *control_url, + const char *service_type, const char *extPort, + const char *inPort, const char *proto, + const char *remoteHost, void *cls) +{ + struct GNUNET_NAT_UPNP_Handle *handle = cls; + + if (error) + { + handle->is_mapped = GNUNET_NO; + handle->state = UPNP_ERR; + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _ + ("Port forwarding through \"%s\", service \"%s\" failed with error %d\n"), + handle->control_url, handle->service_type, error); + return; + } + else + { + handle->is_mapped = GNUNET_NO; + handle->state = UPNP_IDLE; + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, + _("Port %d forwarded successfully\n"), handle->port); + } + + pulse_finish (handle); +} + +static void +get_ip_address_cb (int error, char *ext_addr, void *cls) +{ + struct GNUNET_NAT_UPNP_Handle *handle = cls; + + if (error) + { + if (handle->ext_addr) + { + GNUNET_free (handle->ext_addr); + handle->ext_addr = NULL; + } +#ifdef DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, + "UPNP_get_external_ip_address_ failed (error %d)\n", + error); +#endif + } + else + { + struct in_addr addr; + struct in6_addr addr6; + + if (handle->ext_addr) + { + GNUNET_free (handle->ext_addr); + handle->ext_addr = NULL; + } + + /* Try IPv4 and IPv6 as we don't know what's the format */ + if (inet_aton (ext_addr, &addr) != 0) + { + handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in)); + handle->ext_addr->sa_family = AF_INET; + ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr; + } + else if (inet_pton (AF_INET6, ext_addr, &addr6) != 1) + { + handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6)); + handle->ext_addr->sa_family = AF_INET6; + ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6; + } + else + GNUNET_assert (GNUNET_YES); + +#ifdef DEBUG_UPNP + GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, + _("Found public IP address %s\n"), ext_addr); +#endif + } + + pulse_finish (handle); +} + /** * Check state of UPnP NAT: port redirection, external IP address. * @@ -133,45 +339,19 @@ GNUNET_NAT_UPNP_close (GNUNET_NAT_UPNP_Handle * handle) * @param ext_addr pointer for returning external IP address. * Will be set to NULL if address could not be found. Don't free the sockaddr. */ -int -GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled, - int doPortCheck, struct sockaddr **ext_addr) +void +GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *handle, + int is_enabled, int doPortCheck) { - int ret; + /* Stop if we're already waiting for an action to complete */ + if (handle->processing == GNUNET_YES) + return; if (is_enabled && (handle->state == UPNP_DISCOVER)) { - struct UPNPDev *devlist; - errno = 0; - devlist = upnpDiscover (2000, handle->iface, handle->addr, NULL, 0); - if (devlist == NULL) - { -#ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, - "upnpDiscover failed (errno %d - %s)\n", errno, - strerror (errno)); -#endif - } - errno = 0; - if (UPNP_GetValidIGD (devlist, &handle->urls, &handle->data, - NULL, 0)) - { - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - _("Found Internet Gateway Device \"%s\"\n"), - handle->urls.controlURL); - handle->state = UPNP_IDLE; - handle->hasDiscovered = 1; - } - else - { - handle->state = UPNP_ERR; -#ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, - "UPNP_GetValidIGD failed (errno %d - %s)\n", - errno, strerror (errno)); -#endif - } - freeUPNPDevlist (devlist); + handle->processing = GNUNET_YES; + UPNP_discover_ (handle->sched, handle->iface, handle->addr, discover_cb, + handle); } if (handle->state == UPNP_IDLE) @@ -183,35 +363,26 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled, if (is_enabled && handle->is_mapped && doPortCheck) { char portStr[8]; - char intPort[8]; - char intClient[128]; - int i; GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port); - i = UPNP_GetSpecificPortMappingEntry (handle->urls.controlURL, - handle->data.servicetype, portStr, - "TCP", intClient, intPort); - if (i != UPNPCOMMAND_SUCCESS) - { - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - _("Port %d isn't forwarded\n"), handle->port); - handle->is_mapped = GNUNET_NO; - } + + handle->processing = GNUNET_YES; + UPNP_get_specific_port_mapping_entry_ (handle->sched, + handle->control_url, + handle->service_type, portStr, + "TCP", check_port_mapping_cb, + handle); } if (handle->state == UPNP_UNMAP) { char portStr[16]; GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port); - UPNP_DeletePortMapping (handle->urls.controlURL, - handle->data.servicetype, portStr, "TCP", NULL); - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - _ - ("Stopping port forwarding through \"%s\", service \"%s\"\n"), - handle->urls.controlURL, handle->data.servicetype); - handle->is_mapped = 0; - handle->state = UPNP_IDLE; - handle->port = -1; + + handle->processing = GNUNET_YES; + UPNP_delete_port_mapping_ (handle->sched, handle->control_url, + handle->service_type, portStr, "TCP", NULL, + delete_port_mapping_cb, handle); } if (handle->state == UPNP_IDLE) @@ -222,10 +393,7 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled, if (handle->state == UPNP_MAP) { - int err = -1; - errno = 0; - - if (!handle->urls.controlURL) + if (!handle->control_url) handle->is_mapped = 0; else { @@ -233,111 +401,22 @@ GNUNET_NAT_UPNP_pulse (GNUNET_NAT_UPNP_Handle * handle, int is_enabled, char desc[64]; GNUNET_snprintf (portStr, sizeof (portStr), "%d", handle->port); GNUNET_snprintf (desc, sizeof (desc), "GNUnet at %d", handle->port); - err = UPNP_AddPortMapping (handle->urls.controlURL, - handle->data.servicetype, - portStr, portStr, GNUNET_a2s (handle->addr, handle->addrlen), - desc, "TCP", NULL); - handle->is_mapped = !err; - } - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - _ - ("Port forwarding through \"%s\", service \"%s\"\n"), - handle->urls.controlURL, handle->data.servicetype); - if (handle->is_mapped) - { - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - _("Port %d forwarded successfully\n"), handle->port); - handle->state = UPNP_IDLE; - } - else - { - GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_UPNP, - "Port forwarding failed with error %d (errno %d - %s)\n", - err, errno, strerror (errno)); - handle->state = UPNP_ERR; - } - } - if (ext_addr && handle->state != UPNP_DISCOVER) - { - int err; - char addr_str[128]; - struct in_addr addr; - struct in6_addr addr6; - - /* Keep to NULL if address could not be found */ - *ext_addr = NULL; - err = UPNP_GetExternalIPAddress (handle->urls.controlURL, - handle->data.servicetype, addr_str); - if (err == 0) - { - if (handle->ext_addr) - { - GNUNET_free (handle->ext_addr); - handle->ext_addr = NULL; - } - - /* Try IPv4 and IPv6 as we don't know what's the format */ - if (inet_aton (addr_str, &addr) != 0) - { - handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in)); - handle->ext_addr->sa_family = AF_INET; - ((struct sockaddr_in *) handle->ext_addr)->sin_addr = addr; - *ext_addr = handle->ext_addr; - } - else if (inet_pton (AF_INET6, addr_str, &addr6) != 1) - { - handle->ext_addr = GNUNET_malloc (sizeof (struct sockaddr_in6)); - handle->ext_addr->sa_family = AF_INET6; - ((struct sockaddr_in6 *) handle->ext_addr)->sin6_addr = addr6; - *ext_addr = handle->ext_addr; - } - else - GNUNET_assert (GNUNET_YES); -#ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, - _("Found public IP address %s\n"), - addr_str); -#endif - } - else - { - *ext_addr = NULL; - if (handle->ext_addr) - { - GNUNET_free (handle->ext_addr); - handle->ext_addr = NULL; - } -#ifdef DEBUG - GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_UPNP, - "UPNP_GetExternalIPAddress failed (error %d)\n", err); -#endif + handle->processing = GNUNET_YES; + UPNP_add_port_mapping_ (handle->sched, handle->control_url, + handle->service_type, + portStr, portStr, GNUNET_a2s (handle->addr, + handle->addrlen), + desc, "TCP", NULL, add_port_mapping_cb, + handle); } } - switch (handle->state) + if (handle->state != UPNP_DISCOVER) { - case UPNP_DISCOVER: - ret = GNUNET_NAT_PORT_UNMAPPED; - break; - - case UPNP_MAP: - ret = GNUNET_NAT_PORT_MAPPING; - break; - - case UPNP_UNMAP: - ret = GNUNET_NAT_PORT_UNMAPPING; - break; - - case UPNP_IDLE: - ret = - handle->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED; - break; - - default: - ret = GNUNET_NAT_PORT_ERROR; - break; + handle->processing = GNUNET_YES; + UPNP_get_external_ip_address_ (handle->sched, handle->control_url, + handle->service_type, + get_ip_address_cb, handle); } - - return ret; } diff --git a/src/nat/upnp.h b/src/nat/upnp.h index ccb04ed00..da95106a0 100644 --- a/src/nat/upnp.h +++ b/src/nat/upnp.h @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - (C) 2009 Christian Grothoff (and other contributing authors) + (C) 2009, 2010 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -32,17 +32,24 @@ struct GNUNET_NAT_UPNP_Handle; -struct GNUNET_NAT_UPNP_Handle * -GNUNET_NAT_UPNP_init (const struct sockaddr *addr, - socklen_t addrlen, - unsigned short port); +typedef void (*GNUNET_NAT_UPNP_pulse_cb) (int status, + struct sockaddr * ext_addr, + void *cls); -void GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle * h); +struct GNUNET_NAT_UPNP_Handle *GNUNET_NAT_UPNP_init (struct + GNUNET_SCHEDULER_Handle + *sched, + const struct sockaddr + *addr, socklen_t addrlen, + unsigned short port, + GNUNET_NAT_UPNP_pulse_cb + pulse_cb, + void *pulse_cls); -int GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *h, - int is_enabled, - int do_port_check, - struct sockaddr **ext_addr); +void GNUNET_NAT_UPNP_close (struct GNUNET_NAT_UPNP_Handle *h); -#endif +void GNUNET_NAT_UPNP_pulse (struct GNUNET_NAT_UPNP_Handle *h, + int is_enabled, int do_port_check); + +#endif /* UPNP_H */