2 This file is part of GNUnet.
3 (C) 2009, 2010 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
22 * Code in this file is originally based on the miniupnp library.
23 * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
30 * * Redistributions of source code must retain the above copyright notice,
31 * this list of conditions and the following disclaimer.
32 * * Redistributions in binary form must reproduce the above copyright notice,
33 * this list of conditions and the following disclaimer in the documentation
34 * and/or other materials provided with the distribution.
35 * * The name of the author may not be used to endorse or promote products
36 * derived from this software without specific prior written permission.
38 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
39 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
40 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
41 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
42 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
43 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
44 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
45 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
46 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
47 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
48 * POSSIBILITY OF SUCH DAMAGE.
52 * @file nat/upnp-commands.c
53 * @brief Implementation of a basic set of UPnP commands
55 * @author Milan Bouchet-Valat
59 #include "gnunet_util_lib.h"
65 #include "upnp-reply-parse.h"
66 #include "upnp-igd-parse.h"
67 #include "upnp-discover.h"
68 #include "upnp-commands.h"
70 #define SOAP_PREFIX "s"
71 #define SERVICE_PREFIX "u"
72 #define SERVICE_PREFIX2 'u'
73 #define MAX_HOSTNAME_LEN 64
75 #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);
79 * Private closure used by UPNP_command() and its callbacks.
81 struct UPNP_command_cls
84 * Connection handle used for sending and receiving.
86 struct GNUNET_CONNECTION_Handle *s;
89 * Transmission handle used for sending command.
91 struct GNUNET_CONNECTION_TransmitHandle *th;
94 * HTML content to send to run command.
99 * Buffer where to copy received data to pass to caller.
109 * User callback to trigger when done.
111 UPNP_command_cb_ caller_cb;
114 * User closure to pass to caller_cb.
120 * Get the length of content included in an HTML line.
122 * @param p line to parse
124 * @return the length of the content
127 get_content_len_from_line (const char *p, int n)
129 static const char cont_len_str[] = "content-length";
130 const char *p2 = cont_len_str;
138 if (*p2 != *p && *p2 != (*p + 32))
164 while (*p >= '0' && *p <= '9')
169 a = (a * 10) + (*p - '0');
178 * Get the respective lengths of content and header from an HTML reply.
180 * @param p HTML to parse
182 * @param content_len pointer to store content length to
183 * @param content_len pointer to store header length to
186 get_content_and_header_len (const char *p, int n,
187 int *content_len, int *header_len)
199 while (line[line_len] != '\r' && line[line_len] != '\r')
201 if (line + line_len >= p + n)
207 r = get_content_len_from_line (line, line_len);
212 line = line + line_len + 2;
214 if (line[0] == '\r' && line[1] == '\n')
216 *header_len = (line - p) + 2;
223 * Receive reply of the device to our UPnP command.
225 * @param data closure from UPNP_command()
226 * @param buf struct UPNP_command_cls *cls
227 * @param available number of bytes in buf
228 * @param addr address of the sender
229 * @param addrlen size of addr
230 * @errCode value of errno
233 UPNP_command_receiver (void *data,
236 const struct sockaddr *addr,
237 socklen_t addrlen, int errCode)
239 struct UPNP_command_cls *cls = data;
247 get_content_and_header_len (buf, available, &content_len, &header_len);
249 strncpy (cls->buffer, (char *) buf, cls->buf_size - 2);
250 cls->buffer[cls->buf_size - 2] = '\0';
254 cls->buffer[0] = '\0';
257 GNUNET_CONNECTION_destroy (cls->s, GNUNET_NO);
259 cls->caller_cb (cls->buffer, cls->buf_size, cls->caller_cls);
261 GNUNET_free (cls->content);
266 * Send UPnP command to device.
269 UPNP_command_transmit (void *data, size_t size, void *buf)
271 struct UPNP_command_cls *cls = data;
273 char *content = cls->content;
275 n = strlen (content);
276 memcpy (buf, content, size);
278 GNUNET_CONNECTION_receive (cls->s, cls->buf_size, GNUNET_TIME_UNIT_MINUTES,
279 UPNP_command_receiver, cls);
285 * Parse a HTTP URL string to extract hostname, port and path it points to.
287 * @param url source string corresponding to URL
288 * @param hostname pointer where to store hostname (size of MAX_HOSTNAME_LEN+1)
289 * @param port pointer where to store port
290 * @param path pointer where to store path
292 * @return GNUNET_OK on success, GNUNET_SYSERR on failure
295 parse_url (const char *url, char *hostname, unsigned short *port, char **path)
300 return GNUNET_SYSERR;
302 p1 = strstr (url, "://");
305 return GNUNET_SYSERR;
309 if ((url[0] != 'h') || (url[1] != 't')
310 || (url[2] != 't') || (url[3] != 'p'))
311 return GNUNET_SYSERR;
313 p2 = strchr (p1, ':');
314 p3 = strchr (p1, '/');
317 return GNUNET_SYSERR;
319 memset (hostname, 0, MAX_HOSTNAME_LEN + 1);
321 if (!p2 || (p2 > p3))
323 strncpy (hostname, p1, GNUNET_MIN (MAX_HOSTNAME_LEN, (int) (p3 - p1)));
328 strncpy (hostname, p1, GNUNET_MIN (MAX_HOSTNAME_LEN, (int) (p2 - p1)));
332 while ((*p2 >= '0') && (*p2 <= '9'))
335 *port += (unsigned short) (*p2 - '0');
345 * Send UPnP command to the device identified by url and service.
347 * @param url control URL of the device
348 * @param service type of the service corresponding to the command
349 * @param action action to send
350 * @param args arguments for action
351 * @param caller_cb user callback to trigger when done
352 * @param caller_cls closure to pass to caller_cb
355 UPNP_command_ (const char *url, const char *service,
356 const char *action, struct UPNP_Arg_ *args,
357 char *buffer, size_t buf_size,
358 UPNP_command_cb_ caller_cb, void *caller_cls)
360 struct GNUNET_CONNECTION_Handle *s;
361 struct UPNP_command_cls *cls;
362 struct sockaddr_in dest;
363 struct sockaddr_in6 dest6;
364 char hostname[MAX_HOSTNAME_LEN + 1];
365 unsigned short port = 0;
368 char soap_body[2048];
374 snprintf (soap_act, sizeof (soap_act), "%s#%s", service, action);
378 snprintf (soap_body, sizeof (soap_body),
379 "<?xml version=\"1.0\"?>\r\n"
380 "<" SOAP_PREFIX ":Envelope "
382 "=\"http://schemas.xmlsoap.org/soap/envelope/\" "
384 ":encodingStyle=\"http://schema GNUNET_free (content_buf);s.xmlsoap.org/soap/encoding/\">"
385 "<" SOAP_PREFIX ":Body>" "<" SERVICE_PREFIX
386 ":%s xmlns:" SERVICE_PREFIX "=\"%s\">" "</"
387 SERVICE_PREFIX ":%s>" "</" SOAP_PREFIX
388 ":Body></" SOAP_PREFIX ":Envelope>" "\r\n",
389 action, service, action);
397 soap_body_len = snprintf (soap_body, sizeof (soap_body),
398 "<?xml version=\"1.0\"?>\r\n"
399 "<" SOAP_PREFIX ":Envelope "
401 "=\"http://schemas.xmlsoap.org/soap/envelope/\" "
403 ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
404 "<" SOAP_PREFIX ":Body>" "<" SERVICE_PREFIX
405 ":%s xmlns:" SERVICE_PREFIX "=\"%s\">",
408 p = soap_body + soap_body_len;
412 /* check that we are never overflowing the string... */
413 if (soap_body + sizeof (soap_body) <= p + 100)
415 GNUNET_assert (GNUNET_NO);
416 caller_cb (buffer, 0, caller_cls);
424 if ((pv = args->val))
439 *(p++) = SERVICE_PREFIX2;
446 strncpy (p, "></" SOAP_PREFIX ":Body></" SOAP_PREFIX ":Envelope>\r\n",
447 soap_body + sizeof (soap_body) - p);
450 if (GNUNET_OK != parse_url (url, hostname, &port, &path))
452 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
453 "Invalid URL passed to UPNP_command(): %s\n", url);
454 caller_cb (buffer, 0, caller_cls);
459 /* Test IPv4 address, else use IPv6 */
460 memset (&dest, 0, sizeof (dest));
461 memset (&dest6, 0, sizeof (dest6));
463 if (inet_pton (AF_INET, hostname, &dest.sin_addr) == 1)
465 dest.sin_family = AF_INET;
466 dest.sin_port = htons (port);
467 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
468 dest.sin_len = sizeof (dest);
471 s = GNUNET_CONNECTION_create_from_sockaddr (PF_INET,
472 (struct sockaddr *) &dest,
475 else if (inet_pton (AF_INET6, hostname, &dest6.sin6_addr) == 1)
477 dest6.sin6_family = AF_INET6;
478 dest6.sin6_port = htons (port);
479 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
480 dest6.sin6_len = sizeof (dest6);
483 s = GNUNET_CONNECTION_create_from_sockaddr (PF_INET6,
484 (struct sockaddr *) &dest6,
489 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d\n"),
490 "UPnP", "inet_pton", __FILE__, __LINE__);
492 caller_cb (buffer, 0, caller_cls);
496 body_size = (int) strlen (soap_body);
497 content_buf = GNUNET_malloc (512 + body_size);
499 /* We are not using keep-alive HTTP connections.
500 * HTTP/1.1 needs the header Connection: close to do that.
501 * This is the default with HTTP/1.0 */
502 /* Connection: Close is normally there only in HTTP/1.1 but who knows */
506 snprintf (port_str, sizeof (port_str), ":%hu", port);
508 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" /* ??? */
509 "Pragma: no-cache\r\n"
510 "\r\n", path, hostname, port_str, body_size,
512 memcpy (content_buf + headers_size, soap_body, body_size);
515 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
516 "Sending command '%s' to '%s' (service '%s')\n",
517 action, url, service);
520 cls = GNUNET_malloc (sizeof (struct UPNP_command_cls));
522 cls->content = content_buf;
523 cls->buffer = buffer;
524 cls->buf_size = buf_size;
525 cls->caller_cb = caller_cb;
526 cls->caller_cls = caller_cls;
529 GNUNET_CONNECTION_notify_transmit_ready (s, body_size + headers_size,
530 GNUNET_TIME_relative_multiply
531 (GNUNET_TIME_UNIT_SECONDS, 15),
532 &UPNP_command_transmit, cls);
538 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
539 "Error sending SOAP request at %s:%d\n", __FILE__,
543 caller_cb (buffer, 0, caller_cls);
545 GNUNET_free (content_buf);
547 GNUNET_CONNECTION_destroy (s, GNUNET_NO);
552 struct get_external_ip_address_cls
554 UPNP_get_external_ip_address_cb_ caller_cb;
559 get_external_ip_address_receiver (char *response, size_t received, void *data)
561 struct get_external_ip_address_cls *cls = data;
562 struct UPNP_REPLY_NameValueList_ pdata;
565 int ret = UPNP_COMMAND_UNKNOWN_ERROR;
567 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Response: %s", response);
569 UPNP_REPLY_parse_ (response, received, &pdata);
570 p = UPNP_REPLY_get_value_ (&pdata, "NewExternalIPAddress");
573 strncpy (extIpAdd, p, 128);
574 extIpAdd[127] = '\0';
575 ret = UPNP_COMMAND_SUCCESS;
580 p = UPNP_REPLY_get_value_ (&pdata, "errorCode");
583 ret = UPNP_COMMAND_UNKNOWN_ERROR;
584 sscanf (p, "%d", &ret);
586 cls->caller_cb (ret, extIpAdd, cls->caller_cls);
588 UPNP_REPLY_free_ (&pdata);
589 GNUNET_free (response);
593 /* UPNP_get_external_ip_address_() call the corresponding UPNP method.
597 * NON ZERO : ERROR Either an UPnP error code or an unknown error.
599 * 402 Invalid Args - See UPnP Device Architecture section on Control.
600 * 501 Action Failed - See UPnP Device Architecture section on Control.
603 UPNP_get_external_ip_address_ (const char *control_url,
604 const char *service_type,
605 UPNP_get_external_ip_address_cb_ caller_cb,
608 struct get_external_ip_address_cls *cls;
611 if (!control_url || !service_type)
612 caller_cb (UPNP_COMMAND_INVALID_ARGS, NULL, caller_cls);
614 cls = GNUNET_malloc (sizeof (struct get_external_ip_address_cls));
615 cls->caller_cb = caller_cb;
616 cls->caller_cls = caller_cls;
618 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
620 UPNP_command_ (control_url, service_type, "GetExternalIPAddress",
621 NULL, buffer, UPNP_COMMAND_BUFSIZE,
622 (UPNP_command_cb_) get_external_ip_address_receiver, cls);
625 struct PortMapping_cls
627 const char *control_url;
628 const char *service_type;
629 const char *ext_port;
632 const char *remoteHost;
633 UPNP_port_mapping_cb_ caller_cb;
638 add_delete_port_mapping_receiver (char *response, size_t received, void *data)
640 struct PortMapping_cls *cls = data;
641 struct UPNP_REPLY_NameValueList_ pdata;
645 UPNP_REPLY_parse_ (response, received, &pdata);
646 resVal = UPNP_REPLY_get_value_ (&pdata, "errorCode");
649 ret = UPNP_COMMAND_UNKNOWN_ERROR;
650 sscanf (resVal, "%d", &ret);
654 ret = UPNP_COMMAND_SUCCESS;
657 cls->caller_cb (ret, cls->control_url, cls->service_type,
658 cls->ext_port, cls->in_port, cls->proto,
659 cls->remoteHost, cls->caller_cls);
661 UPNP_REPLY_free_ (&pdata);
662 GNUNET_free (response);
667 UPNP_add_port_mapping_ (const char *control_url, const char *service_type,
668 const char *ext_port,
670 const char *inClient,
672 const char *proto, const char *remoteHost,
673 UPNP_port_mapping_cb_ caller_cb, void *caller_cls)
675 struct UPNP_Arg_ args[9];
676 struct PortMapping_cls *cls;
679 if (!in_port || !inClient || !proto || !ext_port)
681 caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type,
682 ext_port, in_port, proto, remoteHost, caller_cls);
686 args[0].elt = "NewRemoteHost";
687 args[0].val = remoteHost;
688 args[1].elt = "NewExternalPort";
689 args[1].val = ext_port;
690 args[2].elt = "NewProtocol";
692 args[3].elt = "NewInternalPort";
693 args[3].val = in_port;
694 args[4].elt = "NewInternalClient";
695 args[4].val = inClient;
696 args[5].elt = "NewEnabled";
698 args[6].elt = "NewPortMappingDescription";
699 args[6].val = desc ? desc : "GNUnet";
700 args[7].elt = "NewLeaseDuration";
705 cls = GNUNET_malloc (sizeof (struct PortMapping_cls));
706 cls->control_url = control_url;
707 cls->service_type = service_type;
708 cls->ext_port = ext_port;;
709 cls->in_port = in_port;
711 cls->remoteHost = remoteHost;
712 cls->caller_cb = caller_cb;
713 cls->caller_cls = caller_cls;
715 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
717 UPNP_command_ (control_url, service_type, "AddPortMapping",
718 args, buffer, UPNP_COMMAND_BUFSIZE,
719 add_delete_port_mapping_receiver, cls);
723 UPNP_delete_port_mapping_ (const char *control_url, const char *service_type,
724 const char *ext_port, const char *proto,
725 const char *remoteHost,
726 UPNP_port_mapping_cb_ caller_cb, void *caller_cls)
728 struct UPNP_Arg_ args[4];
729 struct PortMapping_cls *cls;
732 if (!ext_port || !proto)
734 caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type,
735 ext_port, NULL, proto, remoteHost, caller_cls);
739 args[0].elt = "NewRemoteHost";
740 args[0].val = remoteHost;
741 args[1].elt = "NewExternalPort";
742 args[1].val = ext_port;
743 args[2].elt = "NewProtocol";
748 cls = GNUNET_malloc (sizeof (struct PortMapping_cls));
749 cls->control_url = control_url;
750 cls->service_type = service_type;
751 cls->ext_port = ext_port;
754 cls->remoteHost = remoteHost;
755 cls->caller_cb = caller_cb;
756 cls->caller_cls = caller_cls;
758 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
760 UPNP_command_ (control_url, service_type,
762 args, buffer, UPNP_COMMAND_BUFSIZE,
763 add_delete_port_mapping_receiver, cls);
767 struct get_specific_port_mapping_entry_cls
769 const char *control_url;
770 const char *service_type;
771 const char *ext_port;
773 UPNP_port_mapping_cb_ caller_cb;
778 get_specific_port_mapping_entry_receiver (char *response, size_t received,
781 struct PortMapping_cls *cls = data;
782 struct UPNP_REPLY_NameValueList_ pdata;
788 UPNP_REPLY_parse_ (response, received, &pdata);
790 p = UPNP_REPLY_get_value_ (&pdata, "NewInternalClient");
793 strncpy (in_client, p, 128);
794 in_client[127] = '\0';
799 p = UPNP_REPLY_get_value_ (&pdata, "NewInternalPort");
802 strncpy (in_port, p, 6);
808 p = UPNP_REPLY_get_value_ (&pdata, "errorCode");
813 ret = UPNP_COMMAND_UNKNOWN_ERROR;
814 sscanf (p, "%d", &ret);
817 PRINT_UPNP_ERROR ("GetSpecificPortMappingEntry", p);
821 cls->caller_cb (ret, cls->control_url, cls->service_type,
822 cls->ext_port, cls->proto, in_port, in_client,
825 UPNP_REPLY_free_ (&pdata);
826 GNUNET_free (response);
830 /* UPNP_get_specific_port_mapping_entry _ retrieves an existing port mapping
831 * the result is returned in the in_client and in_port strings
832 * please provide 128 and 6 bytes of data */
834 UPNP_get_specific_port_mapping_entry_ (const char *control_url,
835 const char *service_type,
836 const char *ext_port,
838 UPNP_get_specific_port_mapping_entry_cb_
839 caller_cb, void *caller_cls)
841 struct UPNP_Arg_ args[4];
842 struct get_specific_port_mapping_entry_cls *cls;
845 if (!ext_port || !proto)
847 caller_cb (UPNP_COMMAND_INVALID_ARGS, control_url, service_type,
848 ext_port, proto, NULL, NULL, caller_cls);
852 args[0].elt = "NewRemoteHost";
854 args[1].elt = "NewExternalPort";
855 args[1].val = ext_port;
856 args[2].elt = "NewProtocol";
861 cls = GNUNET_malloc (sizeof (struct PortMapping_cls));
862 cls->control_url = control_url;
863 cls->service_type = service_type;
864 cls->ext_port = ext_port;
866 cls->caller_cb = caller_cb;
867 cls->caller_cls = caller_cls;
869 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
871 UPNP_command_ (control_url, service_type,
872 "GetSpecificPortMappingEntry",
873 args, buffer, UPNP_COMMAND_BUFSIZE,
874 get_specific_port_mapping_entry_receiver, cls);