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));
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.
89 * Scheduler used for the download task.
91 struct GNUNET_SCHEDULER_Handle *sched;
104 * URL of the file to download.
109 * Time corresponding to timeout wanted by the caller.
111 struct GNUNET_TIME_Absolute end_time;
114 * Buffer to store downloaded content.
116 char download_buffer[DESCRIPTION_BUFSIZE];
119 * Size of the already downloaded content.
124 * User callback to trigger when done.
126 download_cb caller_cb;
129 * User closure to pass to caller_cb.
135 * Clean up the state of CURL multi handle and that of
136 * the only easy handle it uses.
139 download_clean_up (struct download_cls *cls)
143 mret = curl_multi_cleanup (cls->multi);
144 if (mret != CURLM_OK)
145 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
146 _("%s failed at %s:%d: `%s'\n"),
147 "curl_multi_cleanup", __FILE__, __LINE__,
148 curl_multi_strerror (mret));
150 curl_easy_cleanup (cls->curl);
155 * Process downloaded bits by calling callback on each HELLO.
157 * @param ptr buffer with downloaded data
158 * @param size size of a record
159 * @param nmemb number of records downloaded
161 * @return number of bytes that were processed (always size*nmemb)
164 callback_download (void *ptr, size_t size, size_t nmemb, void *ctx)
166 struct download_cls *cls = ctx;
167 const char *cbuf = ptr;
171 total = size * nmemb;
173 return total; /* ok, no data */
175 cpy = GNUNET_MIN (total, DESCRIPTION_BUFSIZE - cls->download_pos - 1);
176 memcpy (&cls->download_buffer[cls->download_pos], cbuf, cpy);
178 cls->download_pos += cpy;
181 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
182 "Downloaded %d records of size %d, download position: %d\n",
183 size, nmemb, cls->download_pos);
190 task_download (struct download_cls *cls,
191 const struct GNUNET_SCHEDULER_TaskContext *tc);
194 * Ask CURL for the select set and then schedule the
195 * receiving task with the scheduler.
198 download_prepare (struct download_cls *cls)
205 struct GNUNET_NETWORK_FDSet *grs;
206 struct GNUNET_NETWORK_FDSet *gws;
208 struct GNUNET_TIME_Relative rtime;
214 mret = curl_multi_fdset (cls->multi, &rs, &ws, &es, &max);
215 if (mret != CURLM_OK)
217 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
218 _("%s failed at %s:%d: `%s'\n"),
219 "curl_multi_fdset", __FILE__, __LINE__,
220 curl_multi_strerror (mret));
221 download_clean_up (cls);
222 cls->caller_cb (NULL, cls->caller_cls);
225 mret = curl_multi_timeout (cls->multi, &timeout);
226 if (mret != CURLM_OK)
228 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
229 _("%s failed at %s:%d: `%s'\n"),
230 "curl_multi_timeout", __FILE__, __LINE__,
231 curl_multi_strerror (mret));
232 download_clean_up (cls);
233 cls->caller_cb (NULL, cls->caller_cls);
237 GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining
239 GNUNET_TIME_relative_multiply
240 (GNUNET_TIME_UNIT_MILLISECONDS, timeout));
241 grs = GNUNET_NETWORK_fdset_create ();
242 gws = GNUNET_NETWORK_fdset_create ();
243 GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
244 GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
246 GNUNET_SCHEDULER_add_select (cls->sched,
247 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
248 GNUNET_SCHEDULER_NO_TASK,
252 (GNUNET_SCHEDULER_Task) & task_download, cls);
253 GNUNET_NETWORK_fdset_destroy (gws);
254 GNUNET_NETWORK_fdset_destroy (grs);
258 * Task that is run when we are ready to receive more data from the device.
261 * @param tc task context
264 task_download (struct download_cls *cls,
265 const struct GNUNET_SCHEDULER_TaskContext *tc)
272 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
275 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
276 "Shutdown requested while trying to download device description from `%s'\n",
279 cls->caller_cb (NULL, cls->caller_cls);
280 download_clean_up (cls);
283 if (GNUNET_TIME_absolute_get_remaining (cls->end_time).value == 0)
285 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
287 ("Timeout trying to download UPnP device description from '%s'\n"),
289 cls->caller_cb (NULL, cls->caller_cls);
290 download_clean_up (cls);
297 mret = curl_multi_perform (cls->multi, &running);
303 msg = curl_multi_info_read (cls->multi, &running);
304 GNUNET_break (msg != NULL);
308 if ((msg->data.result != CURLE_OK) &&
309 (msg->data.result != CURLE_GOT_NOTHING))
311 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
312 _("%s failed for `%s' at %s:%d: `%s'\n"),
313 "curl_multi_perform",
317 curl_easy_strerror (msg->data.result));
318 cls->caller_cb (NULL, cls->caller_cls);
322 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
324 ("Download of device description `%s' completed.\n"),
326 cls->caller_cb (GNUNET_strdup (cls->download_buffer),
330 download_clean_up (cls);
333 while ((running > 0));
336 while (mret == CURLM_CALL_MULTI_PERFORM);
338 if (mret != CURLM_OK)
340 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "UPnP",
341 _("%s failed at %s:%d: `%s'\n"),
342 "curl_multi_perform", __FILE__, __LINE__,
343 curl_multi_strerror (mret));
344 download_clean_up (cls);
345 cls->caller_cb (NULL, cls->caller_cls);
348 download_prepare (cls);
353 * Download description from devices.
355 * @param sched the scheduler to use for the download task
356 * @param url URL of the file to download
357 * @param caller_cb user function to call when done
358 * @caller_cls closure to pass to caller_cb
361 download_device_description (struct GNUNET_SCHEDULER_Handle *sched,
362 char *url, download_cb caller_cb,
369 struct download_cls *cls;
371 cls = GNUNET_malloc (sizeof (struct download_cls));
373 curl = curl_easy_init ();
377 CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &callback_download);
381 CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cls);
385 CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
386 CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
387 /* no need to abort if the above failed */
388 CURL_EASY_SETOPT (curl, CURLOPT_URL, url);
392 CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1);
393 CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, DESCRIPTION_BUFSIZE);
394 CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
395 CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 60L);
396 CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 60L);
398 multi = curl_multi_init ();
405 mret = curl_multi_add_handle (multi, curl);
406 if (mret != CURLM_OK)
408 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
409 _("%s failed at %s:%d: `%s'\n"),
410 "curl_multi_add_handle", __FILE__, __LINE__,
411 curl_multi_strerror (mret));
412 mret = curl_multi_cleanup (multi);
413 if (mret != CURLM_OK)
414 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
415 _("%s failed at %s:%d: `%s'\n"),
416 "curl_multi_cleanup", __FILE__, __LINE__,
417 curl_multi_strerror (mret));
423 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
424 "Preparing to download device description from '%s'\n",
432 cls->end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
433 memset (cls->download_buffer, 0, DESCRIPTION_BUFSIZE);
434 cls->download_pos = 0;
435 cls->caller_cb = caller_cb;
436 cls->caller_cls = caller_cls;
437 download_prepare (cls);
444 curl_easy_cleanup (curl);
445 caller_cb (NULL, caller_cls);
449 * Parse SSDP packet received in reply to a M-SEARCH message.
451 * @param reply contents of the packet
452 * @param size length of reply
453 * @param location address of a pointer that will be set to the start
454 * of the "location" field
455 * @param location_size pointer where to store the length of the "location" field
456 * @param st pointer address of a pointer that will be set to the start
457 * of the "st" (search target) field
458 * @param st_size pointer where to store the length of the "st" field
459 * The strings are NOT null terminated */
461 parse_msearch_reply (const char *reply, int size,
462 const char **location, int *location_size,
463 const char **st, int *st_size)
469 /* Start of the line */
478 /* End of the "header" */
489 while (reply[b] == ' ');
491 if (0 == strncasecmp (reply + a, "location", 8))
493 *location = reply + b;
494 *location_size = i - b;
496 else if (0 == strncasecmp (reply + a, "st", 2))
516 * Standard port for UPnP discovery (SSDP protocol).
521 * Convert a constant integer into a string.
523 #define XSTR(s) STR(s)
527 * Standard IPv4 multicast adress for UPnP discovery (SSDP protocol).
529 #define UPNP_MCAST_ADDR "239.255.255.250"
532 * Standard IPv6 multicast adress for UPnP discovery (SSDP protocol).
534 #define UPNP_MCAST_ADDR6 "FF02:0:0:0:0:0:0:F"
537 * Size of the buffer needed to store SSDP requests we send.
539 #define UPNP_DISCOVER_BUFSIZE 1536
542 * Description of a UPnP device containing everything
543 * we may need to control it.
545 * Meant to be member of a chained list.
550 * Next device in the list, if any.
552 struct UPNP_Dev_ *pNext;
555 * Path to the file describing the device.
560 * UPnP search target.
565 * Service type associated with the control_url for the device.
570 * URL to send commands to.
575 * Whether the device is currently connected to the WAN.
580 * IGD Data associated with the device.
582 struct UPNP_IGD_Data_ *data;
586 * Private closure used by UPNP_discover() and its callbacks.
588 struct UPNP_discover_cls
591 * Scheduler to use for networking tasks.
593 struct GNUNET_SCHEDULER_Handle *sched;
596 * Remote address used for multicast emission and reception.
598 struct sockaddr *multicast_addr;
601 * Network handle used to send and receive discovery messages.
603 struct GNUNET_NETWORK_Handle *sudp;
606 * fdset used with sudp.
608 struct GNUNET_NETWORK_FDSet *fdset;
611 * Connection handle used to download device description.
613 struct GNUNET_CONNECTION_Handle *s;
616 * Transmission handle used with s.
618 struct GNUNET_CONNECTION_TransmitHandle *th;
621 * Index of the UPnP device type we're currently sending discovery messages to.
626 * List of discovered devices.
628 struct UPNP_Dev_ *dev_list;
631 * Device we're currently fetching description from.
633 struct UPNP_Dev_ *current_dev;
636 * User callback to trigger when done.
638 UPNP_discover_cb_ caller_cb;
641 * Closure passed to caller_cb.
647 * Check that raw_url is absolute, and if not, use ref_url to resolve it:
648 * if is_desc_file is GNUNET_YES, the path to the parent of the file is used;
649 * if it is GNUNET_NO, ref_url will be considered as the base URL for raw URL.
651 * @param ref_url base URL for the device
652 * @param is_desc_file whether ref_url is a path to the description file
653 * @param raw_url a possibly relative URL
654 * @returns a new string with an absolute URL
657 get_absolute_url (const char *ref_url, int is_desc_file, const char *raw_url)
661 if ((raw_url[0] == 'h')
662 && (raw_url[1] == 't')
663 && (raw_url[2] == 't')
664 && (raw_url[3] == 'p')
665 && (raw_url[4] == ':') && (raw_url[5] == '/') && (raw_url[6] == '/'))
667 final_url = GNUNET_strdup (raw_url);
671 int n = strlen (raw_url);
672 int l = strlen (ref_url);
676 /* If base URL is a path to the description file, go one level higher */
677 if (is_desc_file == GNUNET_YES)
679 slash = strrchr (ref_url, '/');
680 cpy_len = slash - ref_url;
683 final_url = GNUNET_malloc (l + n + 1);
685 /* Add trailing slash to base URL if needed */
686 if (raw_url[0] != '/' && ref_url[cpy_len] != '\0')
687 final_url[cpy_len++] = '/';
689 strncpy (final_url, ref_url, cpy_len);
690 strcpy (final_url + cpy_len, raw_url);
691 final_url[cpy_len + n] = '\0';
699 * Construct control URL for device from its description URL and
700 * UPNP_IGD_Data_ information. This involves resolving relative paths
701 * and choosing between Common Interface Config and interface-specific
704 * @param desc_url URL to the description file of the device
705 * @param data IGD information obtained from the description file
706 * @returns a URL to control the IGD device, or the empty string
710 format_control_urls (const char *desc_url, struct UPNP_IGD_Data_ *data)
715 if (data->base_url[0] != '\0')
717 ref_url = data->base_url;
718 is_desc_file = GNUNET_NO;
723 is_desc_file = GNUNET_YES;
726 if (data->control_url[0] != '\0')
727 return get_absolute_url (ref_url, is_desc_file, data->control_url);
728 else if (data->control_url_CIF[0] != '\0')
729 return get_absolute_url (ref_url, is_desc_file, data->control_url_CIF);
731 return GNUNET_strdup ("");
734 static void get_valid_igd (struct UPNP_discover_cls *cls);
737 * Called when "GetStatusInfo" command finishes. Check whether IGD device reports
738 * to be currently connected or not.
740 * @param response content of the UPnP message answered by the device
741 * @param received number of received bytes stored in response
742 * @param data closure from UPNP_discover()
745 get_valid_igd_connected_cb (char *response, size_t received, void *data)
747 struct UPNP_discover_cls *cls = data;
748 struct UPNP_REPLY_NameValueList_ pdata;
752 UPNP_REPLY_parse_ (response, received, &pdata);
754 status = UPNP_REPLY_get_value_ (&pdata, "NewConnectionStatus");
755 error = UPNP_REPLY_get_value_ (&pdata, "errorCode");
758 cls->current_dev->is_connected = (strcmp ("Connected", status) == 0);
760 cls->current_dev->is_connected = GNUNET_NO;
763 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
764 _("Could not get UPnP device status: error %s\n"),
767 GNUNET_free (response);
768 UPNP_REPLY_free_ (&pdata);
770 /* Go on to next device, or finish discovery process */
771 cls->current_dev = cls->current_dev->pNext;
776 * Receive contents of the downloaded UPnP IGD description file,
777 * and fill UPNP_Dev_ and UPNP_IGD_Data_ structs with this data.
778 * Then, schedule UPnP command to check whether device is connected.
780 * @param desc UPnP IGD description (in XML)
781 * @data closure from UPNP_discover()
784 get_valid_igd_receive (char *desc, void *data)
786 struct UPNP_discover_cls *cls = data;
787 struct UPNP_IGD_Data_ *igd_data;
790 if (!desc || strlen (desc) == 0)
792 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
793 "Error getting IGD XML description at %s:%d\n",
797 cls->current_dev->data = NULL;
798 cls->current_dev->is_connected = GNUNET_NO;
802 igd_data = GNUNET_malloc (sizeof (struct UPNP_IGD_Data_));
803 memset (igd_data, 0, sizeof (struct UPNP_IGD_Data_));
804 UPNP_IGD_parse_desc_ (desc, strlen (desc), igd_data);
806 cls->current_dev->control_url =
807 format_control_urls (cls->current_dev->desc_url, igd_data);
809 if (igd_data->service_type != '\0')
810 cls->current_dev->service_type = GNUNET_strdup (igd_data->service_type);
811 else if (igd_data->service_type_CIF != '\0')
812 cls->current_dev->service_type =
813 GNUNET_strdup (igd_data->service_type_CIF);
815 cls->current_dev->service_type = GNUNET_strdup ("");
817 cls->current_dev->data = igd_data;
819 /* Check whether device is connected */
820 buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
821 UPNP_command_ (cls->sched,
822 cls->current_dev->control_url,
823 cls->current_dev->data->service_type,
824 "GetStatusInfo", NULL, buffer, UPNP_COMMAND_BUFSIZE,
825 get_valid_igd_connected_cb, cls);
831 * Free a chained list of UPnP devices.
834 free_dev_list (struct UPNP_Dev_ *devlist)
836 struct UPNP_Dev_ *next;
840 next = devlist->pNext;
841 GNUNET_free (devlist->control_url);
842 GNUNET_free (devlist->service_type);
843 GNUNET_free (devlist->desc_url);
844 GNUNET_free (devlist->data);
845 GNUNET_free (devlist->st);
846 GNUNET_free (devlist);
852 * Walk over the list of found devices looking for a connected IGD,
853 * if present, or at least a disconnected one.
856 get_valid_igd (struct UPNP_discover_cls *cls)
858 struct UPNP_Dev_ *dev;
861 /* No device was discovered */
864 cls->caller_cb (NULL, NULL, cls->caller_cls);
869 /* We already walked over all devices, see what we got,
870 * and return the device with the best state we have. */
871 else if (cls->current_dev == NULL)
873 for (step = 1; step <= 3; step++)
875 for (dev = cls->dev_list; dev; dev = dev->pNext)
878 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
879 "Found device: control_url: %s, service_type: %s\n",
880 dev->control_url, dev->service_type);
882 /* Accept connected IGDs on step 1, non-connected IGDs
883 * on step 2, and other device types on step 3. */
884 if ((step == 1 && dev->is_connected)
885 || (step < 3 && 0 != strcmp (dev->service_type,
886 "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")))
889 cls->caller_cb (dev->control_url,
890 dev->service_type, cls->caller_cls);
892 free_dev_list (cls->dev_list);
898 /* We cannot reach this... */
899 GNUNET_assert (GNUNET_NO);
902 /* There are still devices to ask, go on */
903 download_device_description (cls->sched, cls->current_dev->desc_url,
904 get_valid_igd_receive, cls);
907 static const char *const discover_type_list[] = {
908 "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
909 "urn:schemas-upnp-org:service:WANIPConnection:1",
910 "urn:schemas-upnp-org:service:WANPPPConnection:1",
916 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc);
919 * Handle response from device. Stop when all device types have been tried,
920 * and get their descriptions.
922 * @param data closure from UPNP_discover()
923 * @buf content of the reply
924 * @available number of bytes stored in buf
925 * @addr address of the sender
926 * @addrlen size of addr
927 * @param errCode value of errno
930 discover_recv (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
932 struct UPNP_discover_cls *cls = data;
933 GNUNET_SCHEDULER_TaskIdentifier task_w;
934 struct UPNP_Dev_ *tmp;
937 char buf[DISCOVER_BUFSIZE];
938 const char *desc_url = NULL;
940 const char *st = NULL;
943 /* Free fdset that was used for this sned/receive operation */
944 GNUNET_NETWORK_fdset_destroy (cls->fdset);
946 if (cls->multicast_addr->sa_family == AF_INET)
947 addrlen = sizeof (struct sockaddr_in);
949 addrlen = sizeof (struct sockaddr_in6);
953 GNUNET_NETWORK_socket_recvfrom (cls->sudp, &buf, DISCOVER_BUFSIZE - 1,
954 (struct sockaddr *) cls->multicast_addr,
956 if (received == GNUNET_SYSERR)
959 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_recvfrom");
964 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
965 "Received %d bytes from %s\n", received,
966 GNUNET_a2s (cls->multicast_addr, addrlen));
970 parse_msearch_reply (buf, received, &desc_url, &urlsize, &st, &stsize);
974 tmp = (struct UPNP_Dev_ *) GNUNET_malloc (sizeof (struct UPNP_Dev_));
975 tmp->pNext = cls->dev_list;
977 tmp->desc_url = GNUNET_malloc (urlsize + 1);
978 strncpy (tmp->desc_url, desc_url, urlsize);
979 tmp->desc_url[urlsize] = '\0';
981 tmp->st = GNUNET_malloc (stsize + 1);
982 strncpy (tmp->st, st, stsize);
983 tmp->st[stsize] = '\0';
986 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
987 "Found device %s when looking for type %s\n",
988 tmp->desc_url, tmp->st);
992 /* Continue discovery until all types of devices have been tried */
993 if (discover_type_list[cls->type_index])
995 /* Send queries for each device type and wait for a possible reply.
996 * receiver callback takes care of trying another device type,
997 * and eventually calls the caller's callback. */
998 cls->fdset = GNUNET_NETWORK_fdset_create ();
999 GNUNET_NETWORK_fdset_zero (cls->fdset);
1000 GNUNET_NETWORK_fdset_set (cls->fdset, cls->sudp);
1002 task_w = GNUNET_SCHEDULER_add_select (cls->sched,
1003 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1004 GNUNET_SCHEDULER_NO_TASK,
1005 GNUNET_TIME_relative_multiply
1006 (GNUNET_TIME_UNIT_SECONDS, 15),
1007 NULL, cls->fdset, &discover_send,
1010 GNUNET_SCHEDULER_add_select (cls->sched,
1011 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1013 GNUNET_TIME_relative_multiply
1014 (GNUNET_TIME_UNIT_SECONDS, 5), cls->fdset,
1015 NULL, &discover_recv, cls);
1019 GNUNET_NETWORK_socket_close (cls->sudp);
1020 GNUNET_free (cls->multicast_addr);
1021 cls->current_dev = cls->dev_list;
1022 get_valid_igd (cls);
1027 * Send the SSDP M-SEARCH packet.
1029 * @param data closure from UPNP_discover()
1030 * @param tc task context
1033 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
1035 struct UPNP_discover_cls *cls = data;
1038 char buf[DISCOVER_BUFSIZE];
1039 static const char msearch_msg[] =
1040 "M-SEARCH * HTTP/1.1\r\n"
1041 "HOST: " UPNP_MCAST_ADDR ":" XSTR (PORT) "\r\n"
1042 "ST: %s\r\n" "MAN: \"ssdp:discover\"\r\n" "MX: 3\r\n" "\r\n";
1044 if (cls->multicast_addr->sa_family == AF_INET)
1045 addrlen = sizeof (struct sockaddr_in);
1047 addrlen = sizeof (struct sockaddr_in6);
1050 snprintf (buf, DISCOVER_BUFSIZE, msearch_msg,
1051 discover_type_list[cls->type_index++]);
1054 sent = GNUNET_NETWORK_socket_sendto (cls->sudp, buf, n,
1056 cls->multicast_addr, addrlen);
1057 if (sent == GNUNET_SYSERR)
1059 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_sendto");
1063 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1064 "Could only send %d bytes to %s, needed %d bytes\n",
1065 sent, GNUNET_a2s (cls->multicast_addr, addrlen), n);
1070 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1071 "Sent %d bytes to %s\n", sent,
1072 GNUNET_a2s (cls->multicast_addr, addrlen));
1078 * Search for UPnP Internet Gateway Devices (IGD) on a given network interface.
1079 * If several devices are found, a device that is connected to the WAN
1080 * is returned first (if any).
1082 * @param sched scheduler to use for network tasks
1083 * @param multicastif network interface to send discovery messages, or NULL
1084 * @param addr address used to send messages on multicastif, or NULL
1085 * @param caller_cb user function to call when done
1086 * @param caller_cls closure to pass to caller_cb
1089 UPNP_discover_ (struct GNUNET_SCHEDULER_Handle *sched,
1090 const char *multicastif,
1091 const struct sockaddr *addr,
1092 UPNP_discover_cb_ caller_cb, void *caller_cls)
1095 int domain = PF_INET;
1097 struct in6_addr any_addr = IN6ADDR_ANY_INIT;
1098 struct sockaddr_in sockudp_r, sockudp_w;
1099 struct sockaddr_in6 sockudp6_r, sockudp6_w;
1100 GNUNET_SCHEDULER_TaskIdentifier task_w;
1101 struct GNUNET_NETWORK_Handle *sudp;
1102 struct UPNP_discover_cls *cls;
1105 if (addr && addr->sa_family == AF_INET)
1109 else if (addr && addr->sa_family == AF_INET6)
1116 caller_cb (NULL, NULL, caller_cls);
1121 sudp = GNUNET_NETWORK_socket_create (domain, SOCK_DGRAM, 0);
1125 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_create");
1126 caller_cb (NULL, NULL, caller_cls);
1131 cls = GNUNET_malloc (sizeof (struct UPNP_discover_cls));
1134 cls->type_index = 0;
1135 cls->dev_list = NULL;
1136 cls->current_dev = NULL;
1137 cls->caller_cb = caller_cb;
1138 cls->caller_cls = caller_cls;
1141 if (domain == PF_INET)
1144 memset (&sockudp_r, 0, sizeof (struct sockaddr_in));
1145 sockudp_r.sin_family = AF_INET;
1146 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1147 sockudp_r.sin_len = sizeof (struct sockaddr_in);
1149 sockudp_r.sin_port = 0;
1150 sockudp_r.sin_addr.s_addr = INADDR_ANY;
1153 memset (&sockudp_w, 0, sizeof (struct sockaddr_in));
1154 sockudp_w.sin_family = AF_INET;
1155 sockudp_w.sin_port = htons (PORT);
1156 sockudp_w.sin_addr.s_addr = inet_addr (UPNP_MCAST_ADDR);
1157 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1158 sockudp_w.sin_len = sizeof (struct sockaddr_in);
1161 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
1162 memcpy (cls->multicast_addr, &sockudp_w, sizeof (struct sockaddr_in));
1167 memcpy (&sockudp6_r, addr, sizeof (struct sockaddr_in6));
1168 sockudp6_r.sin6_port = 0;
1169 sockudp6_r.sin6_addr = any_addr;
1170 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1171 sockudp6_r.sin6_len = sizeof (struct sockaddr_in6);
1175 memset (&sockudp6_w, 0, sizeof (struct sockaddr_in6));
1176 sockudp6_w.sin6_family = AF_INET6;
1177 sockudp6_w.sin6_port = htons (PORT);
1178 if (inet_pton (AF_INET6, UPNP_MCAST_ADDR6, &sockudp6_w.sin6_addr) != 1)
1180 PRINT_SOCKET_ERROR ("inet_pton");
1181 caller_cb (NULL, NULL, caller_cls);
1184 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1185 sockudp6_w.sin6_len = sizeof (struct sockaddr_in6);
1188 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
1189 memcpy (cls->multicast_addr, &sockudp6_w, sizeof (struct sockaddr_in6));
1192 if (GNUNET_NETWORK_socket_setsockopt
1193 (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) == GNUNET_SYSERR)
1195 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1196 GNUNET_NETWORK_socket_close (sudp);
1197 caller_cb (NULL, NULL, caller_cls);
1203 if (domain == PF_INET)
1205 sockudp_r.sin_addr.s_addr =
1206 ((struct sockaddr_in *) addr)->sin_addr.s_addr;
1207 if (GNUNET_NETWORK_socket_setsockopt
1208 (sudp, IPPROTO_IP, IP_MULTICAST_IF,
1209 (const char *) &sockudp_r.sin_addr,
1210 sizeof (struct in_addr)) == GNUNET_SYSERR)
1212 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1219 if_index = if_nametoindex (multicastif);
1221 PRINT_SOCKET_ERROR ("if_nametoindex");
1223 if (GNUNET_NETWORK_socket_setsockopt
1224 (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index,
1225 sizeof (if_index)) == GNUNET_SYSERR)
1227 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1231 memcpy (&sockudp6_r.sin6_addr,
1232 &((struct sockaddr_in6 *) addr)->sin6_addr,
1233 sizeof (sockudp6_r.sin6_addr));
1237 if (domain == PF_INET)
1239 /* Bind to receive response before sending packet */
1240 if (GNUNET_NETWORK_socket_bind
1241 (sudp, (struct sockaddr *) &sockudp_r,
1242 sizeof (struct sockaddr_in)) != GNUNET_OK)
1244 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1245 GNUNET_NETWORK_socket_close (sudp);
1246 GNUNET_free (cls->multicast_addr);
1247 caller_cb (NULL, NULL, caller_cls);
1253 /* Bind to receive response before sending packet */
1254 if (GNUNET_NETWORK_socket_bind
1255 (sudp, (struct sockaddr *) &sockudp6_r,
1256 sizeof (struct sockaddr_in6)) != GNUNET_OK)
1258 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1259 GNUNET_free (cls->multicast_addr);
1260 GNUNET_NETWORK_socket_close (sudp);
1261 caller_cb (NULL, NULL, caller_cls);
1266 /* Send queries for each device type and wait for a possible reply.
1267 * receiver callback takes care of trying another device type,
1268 * and eventually calls the caller's callback. */
1269 cls->fdset = GNUNET_NETWORK_fdset_create ();
1270 GNUNET_NETWORK_fdset_zero (cls->fdset);
1271 GNUNET_NETWORK_fdset_set (cls->fdset, sudp);
1273 task_w = GNUNET_SCHEDULER_add_select (sched,
1274 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1275 GNUNET_SCHEDULER_NO_TASK,
1276 GNUNET_TIME_relative_multiply
1277 (GNUNET_TIME_UNIT_SECONDS, 15), NULL,
1278 cls->fdset, &discover_send, cls);
1280 GNUNET_SCHEDULER_add_select (sched,
1281 GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1283 GNUNET_TIME_relative_multiply
1284 (GNUNET_TIME_UNIT_SECONDS, 15), cls->fdset,
1285 NULL, &discover_recv, cls);