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-discover.c
53 * @brief Look for UPnP IGD devices
55 * @author Milan Bouchet-Valat
60 #include <curl/curl.h>
63 #include "gnunet_util_lib.h"
64 #include "upnp-discover.h"
65 #include "upnp-reply-parse.h"
66 #include "upnp-igd-parse.h"
67 #include "upnp-minixml.h"
69 #define DISCOVER_BUFSIZE 512
70 #define DESCRIPTION_BUFSIZE 2048
71 #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)
72 #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));
73 #define PRINT_SOCKET_ERROR_STR(a, b) GNUNET_log_from(GNUNET_ERROR_TYPE_WARNING, "UPnP", _("%s failed at %s:%d: '%s' on `%s'\n"), a, __FILE__, __LINE__, strerror (errno), b);
76 * Callback function called when download is finished.
78 * @param data the contents of the downloaded file, or NULL
79 * @param cls closure passed via download_device_description()
81 typedef void (*download_cb) (char *data, void *cls);
84 * Private closure used by download_device_description() and it's callbacks.
99 * URL of the file to download.
104 * Time corresponding to timeout wanted by the caller.
106 struct GNUNET_TIME_Absolute end_time;
109 * Buffer to store downloaded content.
111 char download_buffer[DESCRIPTION_BUFSIZE];
114 * Size of the already downloaded content.
119 * User callback to trigger when done.
121 download_cb caller_cb;
124 * User closure to pass to caller_cb.
130 * Clean up the state of CURL multi handle and that of
131 * the only easy handle it uses.
134 download_clean_up (struct download_cls *cls)
138 mret = curl_multi_cleanup (cls->multi);
139 if (mret != CURLM_OK)
140 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
141 _("%s failed at %s:%d: `%s'\n"),
142 "curl_multi_cleanup", __FILE__, __LINE__,
143 curl_multi_strerror (mret));
145 curl_easy_cleanup (cls->curl);
150 * Process downloaded bits by calling callback on each HELLO.
152 * @param ptr buffer with downloaded data
153 * @param size size of a record
154 * @param nmemb number of records downloaded
156 * @return number of bytes that were processed (always size*nmemb)
159 callback_download (void *ptr, size_t size, size_t nmemb, void *ctx)
161 struct download_cls *cls = ctx;
162 const char *cbuf = ptr;
166 total = size * nmemb;
168 return total; /* ok, no data */
170 cpy = GNUNET_MIN (total, DESCRIPTION_BUFSIZE - cls->download_pos - 1);
171 memcpy (&cls->download_buffer[cls->download_pos], cbuf, cpy);
173 cls->download_pos += cpy;
176 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
177 "Downloaded %d records of size %d, download position: %d\n",
178 size, nmemb, cls->download_pos);
185 task_download (void *cls,
186 const struct GNUNET_SCHEDULER_TaskContext *tc);
189 * Ask CURL for the select set and then schedule the
190 * receiving task with the scheduler.
193 download_prepare (struct download_cls *cls)
200 struct GNUNET_NETWORK_FDSet *grs;
201 struct GNUNET_NETWORK_FDSet *gws;
203 struct GNUNET_TIME_Relative rtime;
209 mret = curl_multi_fdset (cls->multi, &rs, &ws, &es, &max);
210 if (mret != CURLM_OK)
212 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
213 _("%s failed at %s:%d: `%s'\n"),
214 "curl_multi_fdset", __FILE__, __LINE__,
215 curl_multi_strerror (mret));
216 download_clean_up (cls);
217 cls->caller_cb (NULL, cls->caller_cls);
220 mret = curl_multi_timeout (cls->multi, &timeout);
221 if (mret != CURLM_OK)
223 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
224 _("%s failed at %s:%d: `%s'\n"),
225 "curl_multi_timeout", __FILE__, __LINE__,
226 curl_multi_strerror (mret));
227 download_clean_up (cls);
228 cls->caller_cb (NULL, cls->caller_cls);
232 GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining
234 GNUNET_TIME_relative_multiply
235 (GNUNET_TIME_UNIT_MILLISECONDS, timeout));
236 grs = GNUNET_NETWORK_fdset_create ();
237 gws = GNUNET_NETWORK_fdset_create ();
238 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
239 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
241 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
242 GNUNET_SCHEDULER_NO_TASK,
246 & task_download, cls);
247 GNUNET_NETWORK_fdset_destroy (gws);
248 GNUNET_NETWORK_fdset_destroy (grs);
252 * Task that is run when we are ready to receive more data from the device.
255 * @param tc task context
258 task_download (void *cls,
259 const struct GNUNET_SCHEDULER_TaskContext *tc)
261 struct download_cls *dc = cls;
266 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
269 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
270 "Shutdown requested while trying to download device description from `%s'\n",
273 dc->caller_cb (NULL, dc->caller_cls);
274 download_clean_up (dc);
277 if (GNUNET_TIME_absolute_get_remaining (dc->end_time).rel_value == 0)
279 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
281 ("Timeout trying to download UPnP device description from '%s'\n"),
283 dc->caller_cb (NULL, dc->caller_cls);
284 download_clean_up (dc);
291 mret = curl_multi_perform (dc->multi, &running);
297 msg = curl_multi_info_read (dc->multi, &running);
298 GNUNET_break (msg != NULL);
302 if ((msg->data.result != CURLE_OK) &&
303 (msg->data.result != CURLE_GOT_NOTHING))
305 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
306 _("%s failed for `%s' at %s:%d: `%s'\n"),
307 "curl_multi_perform",
311 curl_easy_strerror (msg->data.result));
312 dc->caller_cb (NULL, dc->caller_cls);
316 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
318 ("Download of device description `%s' completed.\n"),
320 dc->caller_cb (GNUNET_strdup (dc->download_buffer),
324 download_clean_up (dc);
327 while ((running > 0));
330 while (mret == CURLM_CALL_MULTI_PERFORM);
332 if (mret != CURLM_OK)
334 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "UPnP",
335 _("%s failed at %s:%d: `%s'\n"),
336 "curl_multi_perform", __FILE__, __LINE__,
337 curl_multi_strerror (mret));
338 download_clean_up (dc);
339 dc->caller_cb (NULL, dc->caller_cls);
342 download_prepare (dc);
347 * Download description from devices.
349 * @param url URL of the file to download
350 * @param caller_cb user function to call when done
351 * @param caller_cls closure to pass to caller_cb
354 download_device_description (char *url, download_cb caller_cb,
361 struct download_cls *cls;
363 cls = GNUNET_malloc (sizeof (struct download_cls));
365 curl = curl_easy_init ();
369 CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &callback_download);
373 CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cls);
377 CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
378 CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
379 /* no need to abort if the above failed */
380 CURL_EASY_SETOPT (curl, CURLOPT_URL, url);
384 CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1);
385 CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, DESCRIPTION_BUFSIZE);
386 CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
387 CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 60L);
388 CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 60L);
390 multi = curl_multi_init ();
397 mret = curl_multi_add_handle (multi, curl);
398 if (mret != CURLM_OK)
400 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
401 _("%s failed at %s:%d: `%s'\n"),
402 "curl_multi_add_handle", __FILE__, __LINE__,
403 curl_multi_strerror (mret));
404 mret = curl_multi_cleanup (multi);
405 if (mret != CURLM_OK)
406 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
407 _("%s failed at %s:%d: `%s'\n"),
408 "curl_multi_cleanup", __FILE__, __LINE__,
409 curl_multi_strerror (mret));
415 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
416 "Preparing to download device description from '%s'\n",
423 cls->end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
424 memset (cls->download_buffer, 0, DESCRIPTION_BUFSIZE);
425 cls->download_pos = 0;
426 cls->caller_cb = caller_cb;
427 cls->caller_cls = caller_cls;
428 download_prepare (cls);
435 curl_easy_cleanup (curl);
436 caller_cb (NULL, caller_cls);
440 * Parse SSDP packet received in reply to a M-SEARCH message.
442 * @param reply contents of the packet
443 * @param size length of reply
444 * @param location address of a pointer that will be set to the start
445 * of the "location" field
446 * @param location_size pointer where to store the length of the "location" field
447 * @param st pointer address of a pointer that will be set to the start
448 * of the "st" (search target) field
449 * @param st_size pointer where to store the length of the "st" field
450 * The strings are NOT null terminated */
452 parse_msearch_reply (const char *reply, int size,
453 const char **location, int *location_size,
454 const char **st, int *st_size)
460 /* Start of the line */
469 /* End of the "header" */
480 while (reply[b] == ' ');
482 if (0 == strncasecmp (reply + a, "location", 8))
484 *location = reply + b;
485 *location_size = i - b;
487 else if (0 == strncasecmp (reply + a, "st", 2))
507 * Standard port for UPnP discovery (SSDP protocol).
512 * Convert a constant integer into a string.
514 #define XSTR(s) STR(s)
518 * Standard IPv4 multicast adress for UPnP discovery (SSDP protocol).
520 #define UPNP_MCAST_ADDR "239.255.255.250"
523 * Standard IPv6 multicast adress for UPnP discovery (SSDP protocol).
525 #define UPNP_MCAST_ADDR6 "FF02:0:0:0:0:0:0:F"
528 * Size of the buffer needed to store SSDP requests we send.
530 #define UPNP_DISCOVER_BUFSIZE 1536
533 * Description of a UPnP device containing everything
534 * we may need to control it.
536 * Meant to be member of a chained list.
541 * Next device in the list, if any.
543 struct UPNP_Dev_ *pNext;
546 * Path to the file describing the device.
551 * UPnP search target.
556 * Service type associated with the control_url for the device.
561 * URL to send commands to.
566 * Whether the device is currently connected to the WAN.
571 * IGD Data associated with the device.
573 struct UPNP_IGD_Data_ *data;
577 * Private closure used by UPNP_discover() and its callbacks.
579 struct UPNP_discover_cls
582 * Remote address used for multicast emission and reception.
584 struct sockaddr *multicast_addr;
587 * Network handle used to send and receive discovery messages.
589 struct GNUNET_NETWORK_Handle *sudp;
592 * fdset used with sudp.
594 struct GNUNET_NETWORK_FDSet *fdset;
597 * Connection handle used to download device description.
599 struct GNUNET_CONNECTION_Handle *s;
602 * Transmission handle used with s.
604 struct GNUNET_CONNECTION_TransmitHandle *th;
607 * Index of the UPnP device type we're currently sending discovery messages to.
612 * List of discovered devices.
614 struct UPNP_Dev_ *dev_list;
617 * Device we're currently fetching description from.
619 struct UPNP_Dev_ *current_dev;
622 * User callback to trigger when done.
624 UPNP_discover_cb_ caller_cb;
627 * Closure passed to caller_cb.
633 * Check that raw_url is absolute, and if not, use ref_url to resolve it:
634 * if is_desc_file is GNUNET_YES, the path to the parent of the file is used;
635 * if it is GNUNET_NO, ref_url will be considered as the base URL for raw URL.
637 * @param ref_url base URL for the device
638 * @param is_desc_file whether ref_url is a path to the description file
639 * @param raw_url a possibly relative URL
640 * @returns a new string with an absolute URL
643 get_absolute_url (const char *ref_url, int is_desc_file, const char *raw_url)
647 if ((raw_url[0] == 'h')
648 && (raw_url[1] == 't')
649 && (raw_url[2] == 't')
650 && (raw_url[3] == 'p')
651 && (raw_url[4] == ':') && (raw_url[5] == '/') && (raw_url[6] == '/'))
653 final_url = GNUNET_strdup (raw_url);
657 int n = strlen (raw_url);
658 int l = strlen (ref_url);
662 /* If base URL is a path to the description file, go one level higher */
663 if (is_desc_file == GNUNET_YES)
665 slash = strrchr (ref_url, '/');
666 cpy_len = slash - ref_url;
669 final_url = GNUNET_malloc (l + n + 1);
671 /* Add trailing slash to base URL if needed */
672 if (raw_url[0] != '/' && ref_url[cpy_len] != '\0')
673 final_url[cpy_len++] = '/';
675 strncpy (final_url, ref_url, cpy_len);
676 strcpy (final_url + cpy_len, raw_url);
677 final_url[cpy_len + n] = '\0';
685 * Construct control URL and service type for device from its description URL
686 * and UPNP_IGD_Data_ information. This involves resolving relative paths
687 * and choosing between Common Interface Config and interface-specific
690 * @param desc_url URL to the description file of the device
691 * @param data IGD information obtained from the description file
692 * @param control_url place to store a URL to control the IGD device (will be
693 * the empty string in case of failure)
694 * @param service_type place to store the service type corresponding to control_url
695 * (will be the empty string in case of failure)
698 format_control_urls (const char *desc_url, struct UPNP_IGD_Data_ *data, char **control_url, char **service_type)
703 if (data->base_url[0] != '\0')
705 ref_url = data->base_url;
706 is_desc_file = GNUNET_NO;
711 is_desc_file = GNUNET_YES;
714 if (data->control_url[0] != '\0')
716 *control_url = get_absolute_url (ref_url, is_desc_file, data->control_url);
717 *service_type = GNUNET_strdup (data->service_type);
719 else if (data->control_url_CIF[0] != '\0')
721 *control_url = get_absolute_url (ref_url, is_desc_file, data->control_url_CIF);
722 *service_type = GNUNET_strdup (data->service_type_CIF);
726 /* If no suitable URL-service type pair was found, set both to empty
727 * to avoid pretending things will work */
728 *control_url = GNUNET_strdup ("");
729 *service_type = GNUNET_strdup ("");
733 static void get_valid_igd (struct UPNP_discover_cls *cls);
736 * Called when "GetStatusInfo" command finishes. Check whether IGD device reports
737 * to be currently connected or not.
739 * @param response content of the UPnP message answered by the device
740 * @param received number of received bytes stored in response
741 * @param data closure from UPNP_discover()
744 get_valid_igd_connected_cb (char *response, size_t received, void *data)
746 struct UPNP_discover_cls *cls = data;
747 struct UPNP_REPLY_NameValueList_ pdata;
751 UPNP_REPLY_parse_ (response, received, &pdata);
753 status = UPNP_REPLY_get_value_ (&pdata, "NewConnectionStatus");
754 error = UPNP_REPLY_get_value_ (&pdata, "errorCode");
757 cls->current_dev->is_connected = (strcmp ("Connected", status) == 0);
759 cls->current_dev->is_connected = GNUNET_NO;
762 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
763 _("Could not get UPnP device status: error %s\n"),
766 GNUNET_free (response);
767 UPNP_REPLY_free_ (&pdata);
769 /* Go on to next device, or finish discovery process */
770 cls->current_dev = cls->current_dev->pNext;
775 * Receive contents of the downloaded UPnP IGD description file,
776 * and fill UPNP_Dev_ and UPNP_IGD_Data_ structs with this data.
777 * Then, schedule UPnP command to check whether device is connected.
779 * @param desc UPnP IGD description (in XML)
780 * @param data closure from UPNP_discover()
783 get_valid_igd_receive (char *desc, void *data)
785 struct UPNP_discover_cls *cls = data;
786 struct UPNP_IGD_Data_ *igd_data;
789 if (!desc || strlen (desc) == 0)
791 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
792 "Error getting IGD XML description at %s:%d\n",
796 cls->current_dev->data = NULL;
797 cls->current_dev->is_connected = GNUNET_NO;
801 igd_data = GNUNET_malloc (sizeof (struct UPNP_IGD_Data_));
802 memset (igd_data, 0, sizeof (struct UPNP_IGD_Data_));
803 UPNP_IGD_parse_desc_ (desc, strlen (desc), igd_data);
805 format_control_urls (cls->current_dev->desc_url, igd_data,
806 &cls->current_dev->control_url,
807 &cls->current_dev->service_type);
809 cls->current_dev->data = igd_data;
811 /* Check whether device is connected */
812 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
813 UPNP_command_ (cls->current_dev->control_url,
814 cls->current_dev->data->service_type,
815 "GetStatusInfo", NULL, buffer, UPNP_COMMAND_BUFSIZE,
816 get_valid_igd_connected_cb, cls);
822 * Free a chained list of UPnP devices.
825 free_dev_list (struct UPNP_Dev_ *devlist)
827 struct UPNP_Dev_ *next;
831 next = devlist->pNext;
832 GNUNET_free (devlist->control_url);
833 GNUNET_free (devlist->service_type);
834 GNUNET_free (devlist->desc_url);
835 GNUNET_free (devlist->data);
836 GNUNET_free (devlist->st);
837 GNUNET_free (devlist);
843 * Walk over the list of found devices looking for a connected IGD,
844 * if present, or at least a disconnected one.
847 get_valid_igd (struct UPNP_discover_cls *cls)
849 struct UPNP_Dev_ *dev;
852 /* No device was discovered */
855 cls->caller_cb (NULL, NULL, cls->caller_cls);
860 /* We already walked over all devices, see what we got,
861 * and return the device with the best state we have. */
862 else if (cls->current_dev == NULL)
864 for (step = 1; step <= 3; step++)
866 for (dev = cls->dev_list; dev; dev = dev->pNext)
869 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
870 "Found device: control_url: %s, service_type: %s\n",
871 dev->control_url, dev->service_type);
873 /* Accept connected IGDs on step 1, non-connected IGDs
874 * on step 2, and other device types on step 3. */
875 if ((step == 1 && dev->is_connected)
876 || (step < 3 && 0 != strcmp (dev->service_type,
877 "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")))
880 cls->caller_cb (dev->control_url,
881 dev->service_type, cls->caller_cls);
883 free_dev_list (cls->dev_list);
889 /* We cannot reach this... */
890 GNUNET_assert (GNUNET_NO);
893 /* There are still devices to ask, go on */
894 download_device_description (cls->current_dev->desc_url,
895 get_valid_igd_receive, cls);
898 static const char *const discover_type_list[] = {
899 "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
900 "urn:schemas-upnp-org:service:WANIPConnection:1",
901 "urn:schemas-upnp-org:service:WANPPPConnection:1",
906 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc);
909 * Handle response from device. Stop when all device types have been tried,
910 * and get their descriptions.
912 * @param data closure from UPNP_discover()
913 * @param tc task context
916 discover_recv (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
918 struct UPNP_discover_cls *cls = data;
919 GNUNET_SCHEDULER_TaskIdentifier task_w;
920 struct UPNP_Dev_ *tmp;
923 char buf[DISCOVER_BUFSIZE];
924 const char *desc_url = NULL;
926 const char *st = NULL;
929 /* Free fdset that was used for this sned/receive operation */
930 GNUNET_NETWORK_fdset_destroy (cls->fdset);
932 if (cls->multicast_addr->sa_family == AF_INET)
933 addrlen = sizeof (struct sockaddr_in);
935 addrlen = sizeof (struct sockaddr_in6);
939 GNUNET_NETWORK_socket_recvfrom (cls->sudp, &buf, DISCOVER_BUFSIZE - 1,
940 (struct sockaddr *) cls->multicast_addr,
942 if (received == GNUNET_SYSERR)
945 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_recvfrom");
950 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
951 "Received %d bytes from %s\n", received,
952 GNUNET_a2s (cls->multicast_addr, addrlen));
956 parse_msearch_reply (buf, received, &desc_url, &urlsize, &st, &stsize);
960 tmp = (struct UPNP_Dev_ *) GNUNET_malloc (sizeof (struct UPNP_Dev_));
961 tmp->pNext = cls->dev_list;
963 tmp->desc_url = GNUNET_malloc (urlsize + 1);
964 strncpy (tmp->desc_url, desc_url, urlsize);
965 tmp->desc_url[urlsize] = '\0';
967 tmp->st = GNUNET_malloc (stsize + 1);
968 strncpy (tmp->st, st, stsize);
969 tmp->st[stsize] = '\0';
972 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
973 "Found device %s when looking for type %s\n",
974 tmp->desc_url, tmp->st);
978 /* Continue discovery until all types of devices have been tried */
979 if (discover_type_list[cls->type_index])
981 /* Send queries for each device type and wait for a possible reply.
982 * receiver callback takes care of trying another device type,
983 * and eventually calls the caller's callback. */
984 cls->fdset = GNUNET_NETWORK_fdset_create ();
985 GNUNET_NETWORK_fdset_zero (cls->fdset);
986 GNUNET_NETWORK_fdset_set (cls->fdset, cls->sudp);
988 task_w = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
989 GNUNET_SCHEDULER_NO_TASK,
990 GNUNET_TIME_relative_multiply
991 (GNUNET_TIME_UNIT_SECONDS, 15),
992 NULL, cls->fdset, &discover_send,
995 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
997 GNUNET_TIME_relative_multiply
998 (GNUNET_TIME_UNIT_SECONDS, 5), cls->fdset,
999 NULL, &discover_recv, cls);
1003 GNUNET_NETWORK_socket_close (cls->sudp);
1004 GNUNET_free (cls->multicast_addr);
1005 cls->current_dev = cls->dev_list;
1006 get_valid_igd (cls);
1011 * Send the SSDP M-SEARCH packet.
1013 * @param data closure from UPNP_discover()
1014 * @param tc task context
1017 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
1019 struct UPNP_discover_cls *cls = data;
1022 char buf[DISCOVER_BUFSIZE];
1023 static const char msearch_msg[] =
1024 "M-SEARCH * HTTP/1.1\r\n"
1025 "HOST: " UPNP_MCAST_ADDR ":" XSTR (PORT) "\r\n"
1026 "ST: %s\r\n" "MAN: \"ssdp:discover\"\r\n" "MX: 3\r\n" "\r\n";
1028 if (cls->multicast_addr->sa_family == AF_INET)
1029 addrlen = sizeof (struct sockaddr_in);
1031 addrlen = sizeof (struct sockaddr_in6);
1034 snprintf (buf, DISCOVER_BUFSIZE, msearch_msg,
1035 discover_type_list[cls->type_index++]);
1038 sent = GNUNET_NETWORK_socket_sendto (cls->sudp, buf, n,
1040 cls->multicast_addr, addrlen);
1041 if (sent == GNUNET_SYSERR)
1043 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_sendto");
1047 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1048 "Could only send %d bytes to %s, needed %d bytes\n",
1049 sent, GNUNET_a2s (cls->multicast_addr, addrlen), n);
1054 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1055 "Sent %d bytes to %s\n", sent,
1056 GNUNET_a2s (cls->multicast_addr, addrlen));
1062 * Search for UPnP Internet Gateway Devices (IGD) on a given network interface.
1063 * If several devices are found, a device that is connected to the WAN
1064 * is returned first (if any).
1066 * @param multicastif network interface to send discovery messages, or NULL
1067 * @param addr address used to send messages on multicastif, or NULL
1068 * @param caller_cb user function to call when done
1069 * @param caller_cls closure to pass to caller_cb
1072 UPNP_discover_ (const char *multicastif,
1073 const struct sockaddr *addr,
1074 UPNP_discover_cb_ caller_cb, void *caller_cls)
1077 int domain = PF_INET;
1079 struct in6_addr any_addr = IN6ADDR_ANY_INIT;
1080 struct sockaddr_in sockudp_r, sockudp_w;
1081 struct sockaddr_in6 sockudp6_r, sockudp6_w;
1082 GNUNET_SCHEDULER_TaskIdentifier task_w;
1083 struct GNUNET_NETWORK_Handle *sudp;
1084 struct UPNP_discover_cls *cls;
1087 if (addr && addr->sa_family == AF_INET)
1091 else if (addr && addr->sa_family == AF_INET6)
1098 caller_cb (NULL, NULL, caller_cls);
1103 sudp = GNUNET_NETWORK_socket_create (domain, SOCK_DGRAM, 0);
1107 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_create");
1108 caller_cb (NULL, NULL, caller_cls);
1113 cls = GNUNET_malloc (sizeof (struct UPNP_discover_cls));
1115 cls->type_index = 0;
1116 cls->dev_list = NULL;
1117 cls->current_dev = NULL;
1118 cls->caller_cb = caller_cb;
1119 cls->caller_cls = caller_cls;
1122 if (domain == PF_INET)
1125 memset (&sockudp_r, 0, sizeof (struct sockaddr_in));
1126 sockudp_r.sin_family = AF_INET;
1127 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1128 sockudp_r.sin_len = sizeof (struct sockaddr_in);
1130 sockudp_r.sin_port = 0;
1131 sockudp_r.sin_addr.s_addr = INADDR_ANY;
1134 memset (&sockudp_w, 0, sizeof (struct sockaddr_in));
1135 sockudp_w.sin_family = AF_INET;
1136 sockudp_w.sin_port = htons (PORT);
1137 sockudp_w.sin_addr.s_addr = inet_addr (UPNP_MCAST_ADDR);
1138 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1139 sockudp_w.sin_len = sizeof (struct sockaddr_in);
1142 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
1143 memcpy (cls->multicast_addr, &sockudp_w, sizeof (struct sockaddr_in));
1148 memcpy (&sockudp6_r, addr, sizeof (struct sockaddr_in6));
1149 sockudp6_r.sin6_port = 0;
1150 sockudp6_r.sin6_addr = any_addr;
1151 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1152 sockudp6_r.sin6_len = sizeof (struct sockaddr_in6);
1156 memset (&sockudp6_w, 0, sizeof (struct sockaddr_in6));
1157 sockudp6_w.sin6_family = AF_INET6;
1158 sockudp6_w.sin6_port = htons (PORT);
1159 if (inet_pton (AF_INET6, UPNP_MCAST_ADDR6, &sockudp6_w.sin6_addr) != 1)
1161 PRINT_SOCKET_ERROR ("inet_pton");
1162 caller_cb (NULL, NULL, caller_cls);
1165 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1166 sockudp6_w.sin6_len = sizeof (struct sockaddr_in6);
1169 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
1170 memcpy (cls->multicast_addr, &sockudp6_w, sizeof (struct sockaddr_in6));
1173 if (GNUNET_NETWORK_socket_setsockopt
1174 (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) == GNUNET_SYSERR)
1176 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1177 GNUNET_NETWORK_socket_close (sudp);
1178 caller_cb (NULL, NULL, caller_cls);
1184 if (domain == PF_INET)
1186 sockudp_r.sin_addr.s_addr =
1187 ((struct sockaddr_in *) addr)->sin_addr.s_addr;
1188 if (GNUNET_NETWORK_socket_setsockopt
1189 (sudp, IPPROTO_IP, IP_MULTICAST_IF,
1190 (const char *) &sockudp_r.sin_addr,
1191 sizeof (struct in_addr)) == GNUNET_SYSERR)
1193 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1201 if_index = if_nametoindex (multicastif);
1207 PRINT_SOCKET_ERROR_STR ("if_nametoindex", multicastif);
1209 if (GNUNET_NETWORK_socket_setsockopt
1210 (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index,
1211 sizeof (if_index)) == GNUNET_SYSERR)
1213 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1217 memcpy (&sockudp6_r.sin6_addr,
1218 &((struct sockaddr_in6 *) addr)->sin6_addr,
1219 sizeof (sockudp6_r.sin6_addr));
1223 if (domain == PF_INET)
1225 /* Bind to receive response before sending packet */
1226 if (GNUNET_NETWORK_socket_bind
1227 (sudp, (struct sockaddr *) &sockudp_r,
1228 sizeof (struct sockaddr_in)) != GNUNET_OK)
1230 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1231 GNUNET_NETWORK_socket_close (sudp);
1232 GNUNET_free (cls->multicast_addr);
1233 caller_cb (NULL, NULL, caller_cls);
1239 /* Bind to receive response before sending packet */
1240 if (GNUNET_NETWORK_socket_bind
1241 (sudp, (struct sockaddr *) &sockudp6_r,
1242 sizeof (struct sockaddr_in6)) != GNUNET_OK)
1244 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1245 GNUNET_free (cls->multicast_addr);
1246 GNUNET_NETWORK_socket_close (sudp);
1247 caller_cb (NULL, NULL, caller_cls);
1252 /* Send queries for each device type and wait for a possible reply.
1253 * receiver callback takes care of trying another device type,
1254 * and eventually calls the caller's callback. */
1255 cls->fdset = GNUNET_NETWORK_fdset_create ();
1256 GNUNET_NETWORK_fdset_zero (cls->fdset);
1257 GNUNET_NETWORK_fdset_set (cls->fdset, sudp);
1259 task_w = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1260 GNUNET_SCHEDULER_NO_TASK,
1261 GNUNET_TIME_relative_multiply
1262 (GNUNET_TIME_UNIT_SECONDS, 15), NULL,
1263 cls->fdset, &discover_send, cls);
1265 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1267 GNUNET_TIME_relative_multiply
1268 (GNUNET_TIME_UNIT_SECONDS, 15), cls->fdset,
1269 NULL, &discover_recv, cls);