diff options
Diffstat (limited to 'src/nat/upnp-discover.c')
-rw-r--r-- | src/nat/upnp-discover.c | 1270 |
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 | */ | ||
81 | typedef void (*download_cb) (char *data, void *cls); | ||
82 | |||
83 | /** | ||
84 | * Private closure used by download_device_description() and it's callbacks. | ||
85 | */ | ||
86 | struct 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 | */ | ||
133 | static void | ||
134 | download_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 | */ | ||
158 | static size_t | ||
159 | callback_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 | |||
184 | static void | ||
185 | task_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 | */ | ||
192 | static void | ||
193 | download_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 | */ | ||
257 | static void | ||
258 | task_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 | */ | ||
353 | void | ||
354 | download_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 | |||
432 | error: | ||
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 */ | ||
451 | static void | ||
452 | parse_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 | */ | ||
538 | struct 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 | */ | ||
579 | struct 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 | */ | ||
642 | static char * | ||
643 | get_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 | */ | ||
697 | static void | ||
698 | format_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 | |||
733 | static 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 | */ | ||
743 | static void | ||
744 | get_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 | */ | ||
782 | static void | ||
783 | get_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 | */ | ||
824 | static void | ||
825 | free_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 | */ | ||
846 | static void | ||
847 | get_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 | |||
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", | ||
902 | NULL | ||
903 | }; | ||
904 | |||
905 | static void | ||
906 | discover_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 | */ | ||
915 | static void | ||
916 | discover_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 | */ | ||
1016 | static void | ||
1017 | discover_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 | */ | ||
1071 | void | ||
1072 | UPNP_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 | } | ||