aboutsummaryrefslogtreecommitdiff
path: root/src/nat/upnp-discover.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nat/upnp-discover.c')
-rw-r--r--src/nat/upnp-discover.c1270
1 files changed, 0 insertions, 1270 deletions
diff --git a/src/nat/upnp-discover.c b/src/nat/upnp-discover.c
deleted file mode 100644
index 2e609d790..000000000
--- a/src/nat/upnp-discover.c
+++ /dev/null
@@ -1,1270 +0,0 @@
1/*
2 This file is part of GNUnet.
3 (C) 2009, 2010 Christian Grothoff (and other contributing authors)
4
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.
9
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.
14
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.
19*/
20
21/*
22 * Code in this file is originally based on the miniupnp library.
23 * Copyright (c) 2005-2009, Thomas BERNARD. All rights reserved.
24 *
25 * Original license:
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
29 *
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.
37 *
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.
49 */
50
51/**
52 * @file nat/upnp-discover.c
53 * @brief Look for UPnP IGD devices
54 *
55 * @author Milan Bouchet-Valat
56 */
57#include <stdio.h>
58#include <stdlib.h>
59#include <string.h>
60#include <curl/curl.h>
61
62#include "platform.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"
68
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);
74
75/**
76 * Callback function called when download is finished.
77 *
78 * @param data the contents of the downloaded file, or NULL
79 * @param cls closure passed via download_device_description()
80 */
81typedef void (*download_cb) (char *data, void *cls);
82
83/**
84 * Private closure used by download_device_description() and it's callbacks.
85 */
86struct download_cls
87{
88 /**
89 * curl_easy handle.
90 */
91 CURL *curl;
92
93 /**
94 * curl_multi handle.
95 */
96 CURLM *multi;
97
98 /**
99 * URL of the file to download.
100 */
101 char *url;
102
103 /**
104 * Time corresponding to timeout wanted by the caller.
105 */
106 struct GNUNET_TIME_Absolute end_time;
107
108 /**
109 * Buffer to store downloaded content.
110 */
111 char download_buffer[DESCRIPTION_BUFSIZE];
112
113 /**
114 * Size of the already downloaded content.
115 */
116 size_t download_pos;
117
118 /**
119 * User callback to trigger when done.
120 */
121 download_cb caller_cb;
122
123 /**
124 * User closure to pass to caller_cb.
125 */
126 void *caller_cls;
127};
128
129/**
130 * Clean up the state of CURL multi handle and that of
131 * the only easy handle it uses.
132 */
133static void
134download_clean_up (struct download_cls *cls)
135{
136 CURLMcode mret;
137
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));
144
145 curl_easy_cleanup (cls->curl);
146 GNUNET_free (cls);
147}
148
149/**
150 * Process downloaded bits by calling callback on each HELLO.
151 *
152 * @param ptr buffer with downloaded data
153 * @param size size of a record
154 * @param nmemb number of records downloaded
155 * @param ctx closure
156 * @return number of bytes that were processed (always size*nmemb)
157 */
158static size_t
159callback_download (void *ptr, size_t size, size_t nmemb, void *ctx)
160{
161 struct download_cls *cls = ctx;
162 const char *cbuf = ptr;
163 size_t total;
164 size_t cpy;
165
166 total = size * nmemb;
167 if (total == 0)
168 return total; /* ok, no data */
169
170 cpy = GNUNET_MIN (total, DESCRIPTION_BUFSIZE - cls->download_pos - 1);
171 memcpy (&cls->download_buffer[cls->download_pos], cbuf, cpy);
172 cbuf += cpy;
173 cls->download_pos += cpy;
174
175#if DEBUG_UPNP
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);
179#endif
180
181 return total;
182}
183
184static void
185task_download (void *cls,
186 const struct GNUNET_SCHEDULER_TaskContext *tc);
187
188/**
189 * Ask CURL for the select set and then schedule the
190 * receiving task with the scheduler.
191 */
192static void
193download_prepare (struct download_cls *cls)
194{
195 CURLMcode mret;
196 fd_set rs;
197 fd_set ws;
198 fd_set es;
199 int max;
200 struct GNUNET_NETWORK_FDSet *grs;
201 struct GNUNET_NETWORK_FDSet *gws;
202 long timeout;
203 struct GNUNET_TIME_Relative rtime;
204
205 max = -1;
206 FD_ZERO (&rs);
207 FD_ZERO (&ws);
208 FD_ZERO (&es);
209 mret = curl_multi_fdset (cls->multi, &rs, &ws, &es, &max);
210 if (mret != CURLM_OK)
211 {
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);
218 return;
219 }
220 mret = curl_multi_timeout (cls->multi, &timeout);
221 if (mret != CURLM_OK)
222 {
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);
229 return;
230 }
231 rtime =
232 GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining
233 (cls->end_time),
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);
240
241 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
242 GNUNET_SCHEDULER_NO_TASK,
243 rtime,
244 grs,
245 gws,
246 & task_download, cls);
247 GNUNET_NETWORK_fdset_destroy (gws);
248 GNUNET_NETWORK_fdset_destroy (grs);
249}
250
251/**
252 * Task that is run when we are ready to receive more data from the device.
253 *
254 * @param cls closure
255 * @param tc task context
256 */
257static void
258task_download (void *cls,
259 const struct GNUNET_SCHEDULER_TaskContext *tc)
260{
261 struct download_cls *dc = cls;
262 int running;
263 struct CURLMsg *msg;
264 CURLMcode mret;
265
266 if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
267 {
268#if DEBUG_UPNP
269 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
270 "Shutdown requested while trying to download device description from `%s'\n",
271 dc->url);
272#endif
273 dc->caller_cb (NULL, dc->caller_cls);
274 download_clean_up (dc);
275 return;
276 }
277 if (GNUNET_TIME_absolute_get_remaining (dc->end_time).rel_value == 0)
278 {
279 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
280 _
281 ("Timeout trying to download UPnP device description from '%s'\n"),
282 dc->url);
283 dc->caller_cb (NULL, dc->caller_cls);
284 download_clean_up (dc);
285 return;
286 }
287
288 do
289 {
290 running = 0;
291 mret = curl_multi_perform (dc->multi, &running);
292
293 if (running == 0)
294 {
295 do
296 {
297 msg = curl_multi_info_read (dc->multi, &running);
298 GNUNET_break (msg != NULL);
299 if (msg == NULL)
300 break;
301
302 if ((msg->data.result != CURLE_OK) &&
303 (msg->data.result != CURLE_GOT_NOTHING))
304 {
305 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
306 _("%s failed for `%s' at %s:%d: `%s'\n"),
307 "curl_multi_perform",
308 dc->url,
309 __FILE__,
310 __LINE__,
311 curl_easy_strerror (msg->data.result));
312 dc->caller_cb (NULL, dc->caller_cls);
313 }
314 else
315 {
316 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
317 _
318 ("Download of device description `%s' completed.\n"),
319 dc->url);
320 dc->caller_cb (GNUNET_strdup (dc->download_buffer),
321 dc->caller_cls);
322 }
323
324 download_clean_up (dc);
325 return;
326 }
327 while ((running > 0));
328 }
329 }
330 while (mret == CURLM_CALL_MULTI_PERFORM);
331
332 if (mret != CURLM_OK)
333 {
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);
340 }
341
342 download_prepare (dc);
343}
344
345
346/**
347 * Download description from devices.
348 *
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
352 */
353void
354download_device_description (char *url, download_cb caller_cb,
355 void *caller_cls)
356{
357 CURL *curl;
358 CURLM *multi;
359 CURLcode ret;
360 CURLMcode mret;
361 struct download_cls *cls;
362
363 cls = GNUNET_malloc (sizeof (struct download_cls));
364
365 curl = curl_easy_init ();
366 if (curl == NULL)
367 goto error;
368
369 CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &callback_download);
370 if (ret != CURLE_OK)
371 goto error;
372
373 CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cls);
374 if (ret != CURLE_OK)
375 goto error;
376
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);
381 if (ret != CURLE_OK)
382 goto error;
383
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);
389
390 multi = curl_multi_init ();
391 if (multi == NULL)
392 {
393 GNUNET_break (0);
394 /* clean_up (); */
395 return;
396 }
397 mret = curl_multi_add_handle (multi, curl);
398 if (mret != CURLM_OK)
399 {
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));
410 goto error;
411 return;
412 }
413
414#if DEBUG_UPNP
415 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
416 "Preparing to download device description from '%s'\n",
417 url);
418#endif
419
420 cls->curl = curl;
421 cls->multi = multi;
422 cls->url = url;
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);
429 return;
430
431
432error:
433 GNUNET_break (0);
434 GNUNET_free (cls);
435 curl_easy_cleanup (curl);
436 caller_cb (NULL, caller_cls);
437}
438
439/**
440 * Parse SSDP packet received in reply to a M-SEARCH message.
441 *
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 */
451static void
452parse_msearch_reply (const char *reply, int size,
453 const char **location, int *location_size,
454 const char **st, int *st_size)
455{
456 int a, b, i;
457
458 i = 0;
459 b = 0;
460 /* Start of the line */
461 a = i;
462
463 while (i < size)
464 {
465 switch (reply[i])
466 {
467 case ':':
468 if (b == 0)
469 /* End of the "header" */
470 b = i;
471 break;
472 case '\x0a':
473 case '\x0d':
474 if (b != 0)
475 {
476 do
477 {
478 b++;
479 }
480 while (reply[b] == ' ');
481
482 if (0 == strncasecmp (reply + a, "location", 8))
483 {
484 *location = reply + b;
485 *location_size = i - b;
486 }
487 else if (0 == strncasecmp (reply + a, "st", 2))
488 {
489 *st = reply + b;
490 *st_size = i - b;
491 }
492
493 b = 0;
494 }
495
496 a = i + 1;
497 break;
498 default:
499 break;
500 }
501
502 i++;
503 }
504}
505
506/**
507 * Standard port for UPnP discovery (SSDP protocol).
508 */
509#define PORT 1900
510
511/**
512 * Convert a constant integer into a string.
513 */
514#define XSTR(s) STR(s)
515#define STR(s) #s
516
517/**
518 * Standard IPv4 multicast adress for UPnP discovery (SSDP protocol).
519 */
520#define UPNP_MCAST_ADDR "239.255.255.250"
521
522/**
523 * Standard IPv6 multicast adress for UPnP discovery (SSDP protocol).
524 */
525#define UPNP_MCAST_ADDR6 "FF02:0:0:0:0:0:0:F"
526
527/**
528 * Size of the buffer needed to store SSDP requests we send.
529 */
530#define UPNP_DISCOVER_BUFSIZE 1536
531
532/**
533 * Description of a UPnP device containing everything
534 * we may need to control it.
535 *
536 * Meant to be member of a chained list.
537 */
538struct UPNP_Dev_
539{
540 /**
541 * Next device in the list, if any.
542 */
543 struct UPNP_Dev_ *pNext;
544
545 /**
546 * Path to the file describing the device.
547 */
548 char *desc_url;
549
550 /**
551 * UPnP search target.
552 */
553 char *st;
554
555 /**
556 * Service type associated with the control_url for the device.
557 */
558 char *service_type;
559
560 /**
561 * URL to send commands to.
562 */
563 char *control_url;
564
565 /**
566 * Whether the device is currently connected to the WAN.
567 */
568 int is_connected;
569
570 /**
571 * IGD Data associated with the device.
572 */
573 struct UPNP_IGD_Data_ *data;
574};
575
576/**
577 * Private closure used by UPNP_discover() and its callbacks.
578 */
579struct UPNP_discover_cls
580{
581 /**
582 * Remote address used for multicast emission and reception.
583 */
584 struct sockaddr *multicast_addr;
585
586 /**
587 * Network handle used to send and receive discovery messages.
588 */
589 struct GNUNET_NETWORK_Handle *sudp;
590
591 /**
592 * fdset used with sudp.
593 */
594 struct GNUNET_NETWORK_FDSet *fdset;
595
596 /**
597 * Connection handle used to download device description.
598 */
599 struct GNUNET_CONNECTION_Handle *s;
600
601 /**
602 * Transmission handle used with s.
603 */
604 struct GNUNET_CONNECTION_TransmitHandle *th;
605
606 /**
607 * Index of the UPnP device type we're currently sending discovery messages to.
608 */
609 int type_index;
610
611 /**
612 * List of discovered devices.
613 */
614 struct UPNP_Dev_ *dev_list;
615
616 /**
617 * Device we're currently fetching description from.
618 */
619 struct UPNP_Dev_ *current_dev;
620
621 /**
622 * User callback to trigger when done.
623 */
624 UPNP_discover_cb_ caller_cb;
625
626 /**
627 * Closure passed to caller_cb.
628 */
629 void *caller_cls;
630};
631
632/**
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.
636 *
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
641 */
642static char *
643get_absolute_url (const char *ref_url, int is_desc_file, const char *raw_url)
644{
645 char *final_url;
646
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] == '/'))
652 {
653 final_url = GNUNET_strdup (raw_url);
654 }
655 else
656 {
657 int n = strlen (raw_url);
658 int l = strlen (ref_url);
659 int cpy_len = l;
660 char *slash;
661
662 /* If base URL is a path to the description file, go one level higher */
663 if (is_desc_file == GNUNET_YES)
664 {
665 slash = strrchr (ref_url, '/');
666 cpy_len = slash - ref_url;
667 }
668
669 final_url = GNUNET_malloc (l + n + 1);
670
671 /* Add trailing slash to base URL if needed */
672 if (raw_url[0] != '/' && ref_url[cpy_len] != '\0')
673 final_url[cpy_len++] = '/';
674
675 strncpy (final_url, ref_url, cpy_len);
676 strcpy (final_url + cpy_len, raw_url);
677 final_url[cpy_len + n] = '\0';
678 }
679
680 return final_url;
681}
682
683
684/**
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
688 * paths.
689 *
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)
696 */
697static void
698format_control_urls (const char *desc_url, struct UPNP_IGD_Data_ *data, char **control_url, char **service_type)
699{
700 const char *ref_url;
701 int is_desc_file;
702
703 if (data->base_url[0] != '\0')
704 {
705 ref_url = data->base_url;
706 is_desc_file = GNUNET_NO;
707 }
708 else
709 {
710 ref_url = desc_url;
711 is_desc_file = GNUNET_YES;
712 }
713
714 if (data->control_url[0] != '\0')
715 {
716 *control_url = get_absolute_url (ref_url, is_desc_file, data->control_url);
717 *service_type = GNUNET_strdup (data->service_type);
718 }
719 else if (data->control_url_CIF[0] != '\0')
720 {
721 *control_url = get_absolute_url (ref_url, is_desc_file, data->control_url_CIF);
722 *service_type = GNUNET_strdup (data->service_type_CIF);
723 }
724 else
725 {
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 ("");
730 }
731}
732
733static void get_valid_igd (struct UPNP_discover_cls *cls);
734
735/**
736 * Called when "GetStatusInfo" command finishes. Check whether IGD device reports
737 * to be currently connected or not.
738 *
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()
742 */
743static void
744get_valid_igd_connected_cb (char *response, size_t received, void *data)
745{
746 struct UPNP_discover_cls *cls = data;
747 struct UPNP_REPLY_NameValueList_ pdata;
748 char *status;
749 char *error;
750
751 UPNP_REPLY_parse_ (response, received, &pdata);
752
753 status = UPNP_REPLY_get_value_ (&pdata, "NewConnectionStatus");
754 error = UPNP_REPLY_get_value_ (&pdata, "errorCode");
755
756 if (status)
757 cls->current_dev->is_connected = (strcmp ("Connected", status) == 0);
758 else
759 cls->current_dev->is_connected = GNUNET_NO;
760
761 if (error)
762 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
763 _("Could not get UPnP device status: error %s\n"),
764 error);
765
766 GNUNET_free (response);
767 UPNP_REPLY_free_ (&pdata);
768
769 /* Go on to next device, or finish discovery process */
770 cls->current_dev = cls->current_dev->pNext;
771 get_valid_igd (cls);
772}
773
774/**
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.
778 *
779 * @param desc UPnP IGD description (in XML)
780 * @param data closure from UPNP_discover()
781 */
782static void
783get_valid_igd_receive (char *desc, void *data)
784{
785 struct UPNP_discover_cls *cls = data;
786 struct UPNP_IGD_Data_ *igd_data;
787 char *buffer;
788
789 if (!desc || strlen (desc) == 0)
790 {
791 GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
792 "Error getting IGD XML description at %s:%d\n",
793 __FILE__, __LINE__);
794
795 /* Skip device */
796 cls->current_dev->data = NULL;
797 cls->current_dev->is_connected = GNUNET_NO;
798 get_valid_igd (cls);
799 }
800
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);
804
805 format_control_urls (cls->current_dev->desc_url, igd_data,
806 &cls->current_dev->control_url,
807 &cls->current_dev->service_type);
808
809 cls->current_dev->data = igd_data;
810
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);
817
818 GNUNET_free (desc);
819}
820
821/**
822 * Free a chained list of UPnP devices.
823 */
824static void
825free_dev_list (struct UPNP_Dev_ *devlist)
826{
827 struct UPNP_Dev_ *next;
828
829 while (devlist)
830 {
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);
838 devlist = next;
839 }
840}
841
842/**
843 * Walk over the list of found devices looking for a connected IGD,
844 * if present, or at least a disconnected one.
845 */
846static void
847get_valid_igd (struct UPNP_discover_cls *cls)
848{
849 struct UPNP_Dev_ *dev;
850 int step;
851
852 /* No device was discovered */
853 if (!cls->dev_list)
854 {
855 cls->caller_cb (NULL, NULL, cls->caller_cls);
856
857 GNUNET_free (cls);
858 return;
859 }
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)
863 {
864 for (step = 1; step <= 3; step++)
865 {
866 for (dev = cls->dev_list; dev; dev = dev->pNext)
867 {
868#if DEBUG_UPNP
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);
872#endif
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")))
878 continue;
879
880 cls->caller_cb (dev->control_url,
881 dev->service_type, cls->caller_cls);
882
883 free_dev_list (cls->dev_list);
884 GNUNET_free (cls);
885 return;
886 }
887 }
888
889 /* We cannot reach this... */
890 GNUNET_assert (GNUNET_NO);
891 }
892
893 /* There are still devices to ask, go on */
894 download_device_description (cls->current_dev->desc_url,
895 get_valid_igd_receive, cls);
896}
897
898static 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",
902 NULL
903};
904
905static void
906discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc);
907
908/**
909 * Handle response from device. Stop when all device types have been tried,
910 * and get their descriptions.
911 *
912 * @param data closure from UPNP_discover()
913 * @param tc task context
914 */
915static void
916discover_recv (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
917{
918 struct UPNP_discover_cls *cls = data;
919 GNUNET_SCHEDULER_TaskIdentifier task_w;
920 struct UPNP_Dev_ *tmp;
921 socklen_t addrlen;
922 ssize_t received;
923 char buf[DISCOVER_BUFSIZE];
924 const char *desc_url = NULL;
925 int urlsize = 0;
926 const char *st = NULL;
927 int stsize = 0;
928
929 /* Free fdset that was used for this sned/receive operation */
930 GNUNET_NETWORK_fdset_destroy (cls->fdset);
931
932 if (cls->multicast_addr->sa_family == AF_INET)
933 addrlen = sizeof (struct sockaddr_in);
934 else
935 addrlen = sizeof (struct sockaddr_in6);
936
937 errno = 0;
938 received =
939 GNUNET_NETWORK_socket_recvfrom (cls->sudp, &buf, DISCOVER_BUFSIZE - 1,
940 (struct sockaddr *) cls->multicast_addr,
941 &addrlen);
942 if (received == GNUNET_SYSERR)
943 {
944 if (errno != EAGAIN)
945 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_recvfrom");
946 }
947#if DEBUG_UPNP
948 else
949 {
950 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
951 "Received %d bytes from %s\n", received,
952 GNUNET_a2s (cls->multicast_addr, addrlen));
953 }
954#endif
955
956 parse_msearch_reply (buf, received, &desc_url, &urlsize, &st, &stsize);
957
958 if (st && desc_url)
959 {
960 tmp = (struct UPNP_Dev_ *) GNUNET_malloc (sizeof (struct UPNP_Dev_));
961 tmp->pNext = cls->dev_list;
962
963 tmp->desc_url = GNUNET_malloc (urlsize + 1);
964 strncpy (tmp->desc_url, desc_url, urlsize);
965 tmp->desc_url[urlsize] = '\0';
966
967 tmp->st = GNUNET_malloc (stsize + 1);
968 strncpy (tmp->st, st, stsize);
969 tmp->st[stsize] = '\0';
970 cls->dev_list = tmp;
971#if DEBUG_UPNP
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);
975#endif
976 }
977
978 /* Continue discovery until all types of devices have been tried */
979 if (discover_type_list[cls->type_index])
980 {
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);
987
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,
993 cls);
994
995 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
996 task_w,
997 GNUNET_TIME_relative_multiply
998 (GNUNET_TIME_UNIT_SECONDS, 5), cls->fdset,
999 NULL, &discover_recv, cls);
1000 }
1001 else
1002 {
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);
1007 }
1008}
1009
1010/**
1011 * Send the SSDP M-SEARCH packet.
1012 *
1013 * @param data closure from UPNP_discover()
1014 * @param tc task context
1015 */
1016static void
1017discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
1018{
1019 struct UPNP_discover_cls *cls = data;
1020 socklen_t addrlen;
1021 ssize_t n, sent;
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";
1027
1028 if (cls->multicast_addr->sa_family == AF_INET)
1029 addrlen = sizeof (struct sockaddr_in);
1030 else
1031 addrlen = sizeof (struct sockaddr_in6);
1032
1033 n =
1034 snprintf (buf, DISCOVER_BUFSIZE, msearch_msg,
1035 discover_type_list[cls->type_index++]);
1036
1037 errno = 0;
1038 sent = GNUNET_NETWORK_socket_sendto (cls->sudp, buf, n,
1039 (struct sockaddr *)
1040 cls->multicast_addr, addrlen);
1041 if (sent == GNUNET_SYSERR)
1042 {
1043 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_sendto");
1044 }
1045 else if (sent < n)
1046 {
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);
1050 }
1051#if DEBUG_UPNP
1052 else
1053 {
1054 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1055 "Sent %d bytes to %s\n", sent,
1056 GNUNET_a2s (cls->multicast_addr, addrlen));
1057 }
1058#endif
1059}
1060
1061/**
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).
1065 *
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
1070 */
1071void
1072UPNP_discover_ (const char *multicastif,
1073 const struct sockaddr *addr,
1074 UPNP_discover_cb_ caller_cb, void *caller_cls)
1075{
1076 int opt = 1;
1077 int domain = PF_INET;
1078 int if_index;
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;
1085
1086
1087 if (addr && addr->sa_family == AF_INET)
1088 {
1089 domain = PF_INET;
1090 }
1091 else if (addr && addr->sa_family == AF_INET6)
1092 {
1093 domain = PF_INET6;
1094 }
1095 else if (addr)
1096 {
1097 GNUNET_break (0);
1098 caller_cb (NULL, NULL, caller_cls);
1099 return;
1100 }
1101
1102 errno = 0;
1103 sudp = GNUNET_NETWORK_socket_create (domain, SOCK_DGRAM, 0);
1104
1105 if (sudp == NULL)
1106 {
1107 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_create");
1108 caller_cb (NULL, NULL, caller_cls);
1109 return;
1110 }
1111
1112
1113 cls = GNUNET_malloc (sizeof (struct UPNP_discover_cls));
1114 cls->sudp = sudp;
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;
1120
1121
1122 if (domain == PF_INET)
1123 {
1124 /* receive */
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);
1129#endif
1130 sockudp_r.sin_port = 0;
1131 sockudp_r.sin_addr.s_addr = INADDR_ANY;
1132
1133 /* send */
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);
1140#endif
1141
1142 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
1143 memcpy (cls->multicast_addr, &sockudp_w, sizeof (struct sockaddr_in));
1144 }
1145 else
1146 {
1147 /* receive */
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);
1153#endif
1154
1155 /* send */
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)
1160 {
1161 PRINT_SOCKET_ERROR ("inet_pton");
1162 caller_cb (NULL, NULL, caller_cls);
1163 return;
1164 }
1165#ifdef HAVE_SOCKADDR_IN_SIN_LEN
1166 sockudp6_w.sin6_len = sizeof (struct sockaddr_in6);
1167#endif
1168
1169 cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
1170 memcpy (cls->multicast_addr, &sockudp6_w, sizeof (struct sockaddr_in6));
1171 }
1172
1173 if (GNUNET_NETWORK_socket_setsockopt
1174 (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) == GNUNET_SYSERR)
1175 {
1176 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1177 GNUNET_NETWORK_socket_close (sudp);
1178 caller_cb (NULL, NULL, caller_cls);
1179 return;
1180 }
1181
1182 if (addr)
1183 {
1184 if (domain == PF_INET)
1185 {
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)
1192 {
1193 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1194 }
1195 }
1196 else
1197 {
1198 if (multicastif)
1199 {
1200#ifndef MINGW
1201 if_index = if_nametoindex (multicastif);
1202#else
1203 // FIXME
1204 if_index = 0;
1205#endif
1206 if (!if_index)
1207 PRINT_SOCKET_ERROR_STR ("if_nametoindex", multicastif);
1208
1209 if (GNUNET_NETWORK_socket_setsockopt
1210 (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index,
1211 sizeof (if_index)) == GNUNET_SYSERR)
1212 {
1213 PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1214 }
1215 }
1216
1217 memcpy (&sockudp6_r.sin6_addr,
1218 &((struct sockaddr_in6 *) addr)->sin6_addr,
1219 sizeof (sockudp6_r.sin6_addr));
1220 }
1221 }
1222
1223 if (domain == PF_INET)
1224 {
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)
1229 {
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);
1234 return;
1235 }
1236 }
1237 else
1238 {
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)
1243 {
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);
1248 return;
1249 }
1250 }
1251
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);
1258
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);
1264
1265 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1266 task_w,
1267 GNUNET_TIME_relative_multiply
1268 (GNUNET_TIME_UNIT_SECONDS, 15), cls->fdset,
1269 NULL, &discover_recv, cls);
1270}