check
[oweals/gnunet.git] / src / nat / upnp-discover.c
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 }