rework UPnP code to use GNUnet scheduler and network API
[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 licence:
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
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    * Scheduler used for the download task.
90    */
91   struct GNUNET_SCHEDULER_Handle *sched;
92
93   /**
94    * curl_easy handle.
95    */
96   CURL *curl;
97
98   /**
99    * curl_multi handle.
100    */
101   CURLM *multi;
102
103   /**
104    * URL of the file to download.
105    */
106   char *url;
107
108   /**
109    * Time corresponding to timeout wanted by the caller.
110    */
111   struct GNUNET_TIME_Absolute end_time;
112
113   /**
114    * Buffer to store downloaded content.
115    */
116   char download_buffer[DESCRIPTION_BUFSIZE];
117
118   /**
119    * Size of the already downloaded content.
120    */
121   size_t download_pos;
122
123   /**
124    * User callback to trigger when done.
125    */
126   download_cb caller_cb;
127
128   /**
129    * User closure to pass to caller_cb.
130    */
131   void *caller_cls;
132 };
133
134 /**
135  * Clean up the state of CURL multi handle and that of
136  * the only easy handle it uses.
137  */
138 static void
139 download_clean_up (struct download_cls *cls)
140 {
141   CURLMcode mret;
142
143   mret = curl_multi_cleanup (cls->multi);
144   if (mret != CURLM_OK)
145     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
146                      _("%s failed at %s:%d: `%s'\n"),
147                      "curl_multi_cleanup", __FILE__, __LINE__,
148                      curl_multi_strerror (mret));
149
150   curl_easy_cleanup (cls->curl);
151   GNUNET_free (cls);
152 }
153
154 /**
155  * Process downloaded bits by calling callback on each HELLO.
156  *
157  * @param ptr buffer with downloaded data
158  * @param size size of a record
159  * @param nmemb number of records downloaded
160  * @param ctx closure
161  * @return number of bytes that were processed (always size*nmemb)
162  */
163 static size_t
164 callback_download (void *ptr, size_t size, size_t nmemb, void *ctx)
165 {
166   struct download_cls *cls = ctx;
167   const char *cbuf = ptr;
168   size_t total;
169   size_t cpy;
170
171   total = size * nmemb;
172   if (total == 0)
173     return total;               /* ok, no data */
174
175   cpy = GNUNET_MIN (total, DESCRIPTION_BUFSIZE - cls->download_pos - 1);
176   memcpy (&cls->download_buffer[cls->download_pos], cbuf, cpy);
177   cbuf += cpy;
178   cls->download_pos += cpy;
179
180 #if DEBUG_UPNP
181   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
182                    "Downloaded %d records of size %d, download position: %d\n",
183                    size, nmemb, cls->download_pos);
184 #endif
185
186   return total;
187 }
188
189 static void
190 task_download (struct download_cls *cls,
191                const struct GNUNET_SCHEDULER_TaskContext *tc);
192
193 /**
194  * Ask CURL for the select set and then schedule the
195  * receiving task with the scheduler.
196  */
197 static void
198 download_prepare (struct download_cls *cls)
199 {
200   CURLMcode mret;
201   fd_set rs;
202   fd_set ws;
203   fd_set es;
204   int max;
205   struct GNUNET_NETWORK_FDSet *grs;
206   struct GNUNET_NETWORK_FDSet *gws;
207   long timeout;
208   struct GNUNET_TIME_Relative rtime;
209
210   max = -1;
211   FD_ZERO (&rs);
212   FD_ZERO (&ws);
213   FD_ZERO (&es);
214   mret = curl_multi_fdset (cls->multi, &rs, &ws, &es, &max);
215   if (mret != CURLM_OK)
216     {
217       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
218                        _("%s failed at %s:%d: `%s'\n"),
219                        "curl_multi_fdset", __FILE__, __LINE__,
220                        curl_multi_strerror (mret));
221       download_clean_up (cls);
222       cls->caller_cb (NULL, cls->caller_cls);
223       return;
224     }
225   mret = curl_multi_timeout (cls->multi, &timeout);
226   if (mret != CURLM_OK)
227     {
228       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
229                        _("%s failed at %s:%d: `%s'\n"),
230                        "curl_multi_timeout", __FILE__, __LINE__,
231                        curl_multi_strerror (mret));
232       download_clean_up (cls);
233       cls->caller_cb (NULL, cls->caller_cls);
234       return;
235     }
236   rtime =
237     GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining
238                               (cls->end_time),
239                               GNUNET_TIME_relative_multiply
240                               (GNUNET_TIME_UNIT_MILLISECONDS, timeout));
241   grs = GNUNET_NETWORK_fdset_create ();
242   gws = GNUNET_NETWORK_fdset_create ();
243   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
244   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
245
246   GNUNET_SCHEDULER_add_select (cls->sched,
247                                GNUNET_SCHEDULER_PRIORITY_DEFAULT,
248                                GNUNET_SCHEDULER_NO_TASK,
249                                rtime,
250                                grs,
251                                gws,
252                                (GNUNET_SCHEDULER_Task) & task_download, cls);
253   GNUNET_NETWORK_fdset_destroy (gws);
254   GNUNET_NETWORK_fdset_destroy (grs);
255 }
256
257 /**
258  * Task that is run when we are ready to receive more data from the device.
259  *
260  * @param cls closure
261  * @param tc task context
262  */
263 static void
264 task_download (struct download_cls *cls,
265                const struct GNUNET_SCHEDULER_TaskContext *tc)
266 {
267
268   int running;
269   struct CURLMsg *msg;
270   CURLMcode mret;
271
272   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
273     {
274 #if DEBUG_UPNP
275       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
276                        "Shutdown requested while trying to download device description from `%s'\n",
277                        cls->url);
278 #endif
279       cls->caller_cb (NULL, cls->caller_cls);
280       download_clean_up (cls);
281       return;
282     }
283   if (GNUNET_TIME_absolute_get_remaining (cls->end_time).value == 0)
284     {
285       GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
286                        _
287                        ("Timeout trying to download UPnP device description from '%s'\n"),
288                        cls->url);
289       cls->caller_cb (NULL, cls->caller_cls);
290       download_clean_up (cls);
291       return;
292     }
293
294   do
295     {
296       running = 0;
297       mret = curl_multi_perform (cls->multi, &running);
298
299       if (running == 0)
300         {
301           do
302             {
303               msg = curl_multi_info_read (cls->multi, &running);
304               GNUNET_break (msg != NULL);
305               if (msg == NULL)
306                 break;
307
308               if ((msg->data.result != CURLE_OK) &&
309                   (msg->data.result != CURLE_GOT_NOTHING))
310                 {
311                   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
312                               _("%s failed for `%s' at %s:%d: `%s'\n"),
313                               "curl_multi_perform",
314                               cls->url,
315                               __FILE__,
316                               __LINE__,
317                               curl_easy_strerror (msg->data.result));
318                   cls->caller_cb (NULL, cls->caller_cls);
319                 }
320               else
321                 {
322                   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
323                                    _
324                                    ("Download of device description `%s' completed.\n"),
325                                    cls->url);
326                   cls->caller_cb (GNUNET_strdup (cls->download_buffer),
327                                   cls->caller_cls);
328                 }
329
330               download_clean_up (cls);
331               return;
332             }
333           while ((running > 0));
334         }
335     }
336   while (mret == CURLM_CALL_MULTI_PERFORM);
337
338   if (mret != CURLM_OK)
339     {
340       GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "UPnP",
341                        _("%s failed at %s:%d: `%s'\n"),
342                        "curl_multi_perform", __FILE__, __LINE__,
343                        curl_multi_strerror (mret));
344       download_clean_up (cls);
345       cls->caller_cb (NULL, cls->caller_cls);
346     }
347
348   download_prepare (cls);
349 }
350
351
352 /**
353  * Download description from devices.
354  *
355  * @param sched the scheduler to use for the download task
356  * @param url URL of the file to download
357  * @param caller_cb user function to call when done
358  * @caller_cls closure to pass to caller_cb
359  */
360 void
361 download_device_description (struct GNUNET_SCHEDULER_Handle *sched,
362                              char *url, download_cb caller_cb,
363                              void *caller_cls)
364 {
365   CURL *curl;
366   CURLM *multi;
367   CURLcode ret;
368   CURLMcode mret;
369   struct download_cls *cls;
370
371   cls = GNUNET_malloc (sizeof (struct download_cls));
372
373   curl = curl_easy_init ();
374   if (curl == NULL)
375     goto error;
376
377   CURL_EASY_SETOPT (curl, CURLOPT_WRITEFUNCTION, &callback_download);
378   if (ret != CURLE_OK)
379     goto error;
380
381   CURL_EASY_SETOPT (curl, CURLOPT_WRITEDATA, cls);
382   if (ret != CURLE_OK)
383     goto error;
384
385   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
386   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
387   /* no need to abort if the above failed */
388   CURL_EASY_SETOPT (curl, CURLOPT_URL, url);
389   if (ret != CURLE_OK)
390     goto error;
391
392   CURL_EASY_SETOPT (curl, CURLOPT_FAILONERROR, 1);
393   CURL_EASY_SETOPT (curl, CURLOPT_BUFFERSIZE, DESCRIPTION_BUFSIZE);
394   CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
395   CURL_EASY_SETOPT (curl, CURLOPT_CONNECTTIMEOUT, 60L);
396   CURL_EASY_SETOPT (curl, CURLOPT_TIMEOUT, 60L);
397
398   multi = curl_multi_init ();
399   if (multi == NULL)
400     {
401       GNUNET_break (0);
402       /* clean_up (); */
403       return;
404     }
405   mret = curl_multi_add_handle (multi, curl);
406   if (mret != CURLM_OK)
407     {
408       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
409                        _("%s failed at %s:%d: `%s'\n"),
410                        "curl_multi_add_handle", __FILE__, __LINE__,
411                        curl_multi_strerror (mret));
412       mret = curl_multi_cleanup (multi);
413       if (mret != CURLM_OK)
414         GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "UPnP",
415                          _("%s failed at %s:%d: `%s'\n"),
416                          "curl_multi_cleanup", __FILE__, __LINE__,
417                          curl_multi_strerror (mret));
418       goto error;
419       return;
420     }
421
422 #if DEBUG_UPNP
423   GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
424                    "Preparing to download device description from '%s'\n",
425                    url);
426 #endif
427
428   cls->sched = sched;
429   cls->curl = curl;
430   cls->multi = multi;
431   cls->url = url;
432   cls->end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
433   memset (cls->download_buffer, 0, DESCRIPTION_BUFSIZE);
434   cls->download_pos = 0;
435   cls->caller_cb = caller_cb;
436   cls->caller_cls = caller_cls;
437   download_prepare (cls);
438   return;
439
440
441 error:
442   GNUNET_break (0);
443   GNUNET_free (cls);
444   curl_easy_cleanup (curl);
445   caller_cb (NULL, caller_cls);
446 }
447
448 /**
449  * Parse SSDP packet received in reply to a M-SEARCH message.
450  *
451  * @param reply contents of the packet
452  * @param size length of reply
453  * @param location address of a pointer that will be set to the start
454  *   of the "location" field
455  * @param location_size pointer where to store the length of the "location" field
456  * @param st pointer address of a pointer that will be set to the start
457  *   of the "st" (search target) field
458  * @param st_size pointer where to store the length of the "st" field
459  * The strings are NOT null terminated */
460 static void
461 parse_msearch_reply (const char *reply, int size,
462                      const char **location, int *location_size,
463                      const char **st, int *st_size)
464 {
465   int a, b, i;
466
467   i = 0;
468   b = 0;
469   /* Start of the line */
470   a = i;
471
472   while (i < size)
473     {
474       switch (reply[i])
475         {
476         case ':':
477           if (b == 0)
478             /* End of the "header" */
479             b = i;
480           break;
481         case '\x0a':
482         case '\x0d':
483           if (b != 0)
484             {
485               do
486                 {
487                   b++;
488                 }
489               while (reply[b] == ' ');
490
491               if (0 == strncasecmp (reply + a, "location", 8))
492                 {
493                   *location = reply + b;
494                   *location_size = i - b;
495                 }
496               else if (0 == strncasecmp (reply + a, "st", 2))
497                 {
498                   *st = reply + b;
499                   *st_size = i - b;
500                 }
501
502               b = 0;
503             }
504
505           a = i + 1;
506           break;
507         default:
508           break;
509         }
510
511       i++;
512     }
513 }
514
515 /**
516  * Standard port for UPnP discovery (SSDP protocol).
517  */
518 #define PORT 1900
519
520 /**
521  * Convert a constant integer into a string.
522  */
523 #define XSTR(s) STR(s)
524 #define STR(s) #s
525
526 /**
527  * Standard IPv4 multicast adress for UPnP discovery (SSDP protocol).
528  */
529 #define UPNP_MCAST_ADDR "239.255.255.250"
530
531 /**
532  * Standard IPv6 multicast adress for UPnP discovery (SSDP protocol).
533  */
534 #define UPNP_MCAST_ADDR6 "FF02:0:0:0:0:0:0:F"
535
536 /**
537  * Size of the buffer needed to store SSDP requests we send.
538  */
539 #define UPNP_DISCOVER_BUFSIZE 1536
540
541 /**
542  * Description of a UPnP device containing everything
543  * we may need to control it.
544  *
545  * Meant to be member of a chained list.
546  */
547 struct UPNP_Dev_
548 {
549   /**
550    * Next device in the list, if any.
551    */
552   struct UPNP_Dev_ *pNext;
553
554   /**
555    * Path to the file describing the device.
556    */
557   char *desc_url;
558
559   /**
560    * UPnP search target.
561    */
562   char *st;
563
564   /**
565    * Service type associated with the control_url for the device.
566    */
567   char *service_type;
568
569   /**
570    * URL to send commands to.
571    */
572   char *control_url;
573
574   /**
575    * Whether the device is currently connected to the WAN.
576    */
577   int is_connected;
578
579   /**
580    * IGD Data associated with the device.
581    */
582   struct UPNP_IGD_Data_ *data;
583 };
584
585 /**
586  * Private closure used by UPNP_discover() and its callbacks.
587  */
588 struct UPNP_discover_cls
589 {
590   /**
591    * Scheduler to use for networking tasks.
592    */
593   struct GNUNET_SCHEDULER_Handle *sched;
594
595   /**
596    * Remote address used for multicast emission and reception.
597    */
598   struct sockaddr *multicast_addr;
599
600   /**
601    * Network handle used to send and receive discovery messages.
602    */
603   struct GNUNET_NETWORK_Handle *sudp;
604
605   /**
606    * fdset used with sudp.
607    */
608   struct GNUNET_NETWORK_FDSet *fdset;
609
610   /**
611    * Connection handle used to download device description.
612    */
613   struct GNUNET_CONNECTION_Handle *s;
614
615   /**
616    * Transmission handle used with s.
617    */
618   struct GNUNET_CONNECTION_TransmitHandle *th;
619
620   /**
621    * Index of the UPnP device type we're currently sending discovery messages to.
622    */
623   int type_index;
624
625   /**
626    * List of discovered devices.
627    */
628   struct UPNP_Dev_ *dev_list;
629
630   /**
631    * Device we're currently fetching description from.
632    */
633   struct UPNP_Dev_ *current_dev;
634
635   /**
636    * User callback to trigger when done.
637    */
638   UPNP_discover_cb_ caller_cb;
639
640   /**
641    * Closure passed to caller_cb.
642    */
643   void *caller_cls;
644 };
645
646 /**
647  * Check that raw_url is absolute, and if not, use ref_url to resolve it:
648  * if is_desc_file is GNUNET_YES, the path to the parent of the file is used;
649  * if it is GNUNET_NO, ref_url will be considered as the base URL for raw URL.
650  *
651  * @param ref_url base URL for the device
652  * @param is_desc_file whether ref_url is a path to the description file
653  * @param raw_url a possibly relative URL
654  * @returns a new string with an absolute URL
655  */
656 static char *
657 get_absolute_url (const char *ref_url, int is_desc_file, const char *raw_url)
658 {
659   char *final_url;
660
661   if ((raw_url[0] == 'h')
662       && (raw_url[1] == 't')
663       && (raw_url[2] == 't')
664       && (raw_url[3] == 'p')
665       && (raw_url[4] == ':') && (raw_url[5] == '/') && (raw_url[6] == '/'))
666     {
667       final_url = GNUNET_strdup (raw_url);
668     }
669   else
670     {
671       int n = strlen (raw_url);
672       int l = strlen (ref_url);
673       int cpy_len = l;
674       char *slash;
675
676       /* If base URL is a path to the description file, go one level higher */
677       if (is_desc_file == GNUNET_YES)
678         {
679           slash = strrchr (ref_url, '/');
680           cpy_len = slash - ref_url;
681         }
682
683       final_url = GNUNET_malloc (l + n + 1);
684
685       /* Add trailing slash to base URL if needed */
686       if (raw_url[0] != '/' && ref_url[cpy_len] != '\0')
687         final_url[cpy_len++] = '/';
688
689       strncpy (final_url, ref_url, cpy_len);
690       strcpy (final_url + cpy_len, raw_url);
691       final_url[cpy_len + n] = '\0';
692     }
693
694   return final_url;
695 }
696
697
698 /**
699  * Construct control URL for device from its description URL and
700  * UPNP_IGD_Data_ information. This involves resolving relative paths
701  * and choosing between Common Interface Config and interface-specific
702  * paths.
703  *
704  * @param desc_url URL to the description file of the device
705  * @param data IGD information obtained from the description file
706  * @returns a URL to control the IGD device, or the empty string
707  *   in case of failure
708  */
709 static char *
710 format_control_urls (const char *desc_url, struct UPNP_IGD_Data_ *data)
711 {
712   const char *ref_url;
713   int is_desc_file;
714
715   if (data->base_url[0] != '\0')
716     {
717       ref_url = data->base_url;
718       is_desc_file = GNUNET_NO;
719     }
720   else
721     {
722       ref_url = desc_url;
723       is_desc_file = GNUNET_YES;
724     }
725
726   if (data->control_url[0] != '\0')
727     return get_absolute_url (ref_url, is_desc_file, data->control_url);
728   else if (data->control_url_CIF[0] != '\0')
729     return get_absolute_url (ref_url, is_desc_file, data->control_url_CIF);
730   else
731     return GNUNET_strdup ("");
732 }
733
734 static void get_valid_igd (struct UPNP_discover_cls *cls);
735
736 /**
737  * Called when "GetStatusInfo" command finishes. Check whether IGD device reports
738  * to be currently connected or not.
739  *
740  * @param response content of the UPnP message answered by the device
741  * @param received number of received bytes stored in response
742  * @param data closure from UPNP_discover()
743  */
744 static void
745 get_valid_igd_connected_cb (char *response, size_t received, void *data)
746 {
747   struct UPNP_discover_cls *cls = data;
748   struct UPNP_REPLY_NameValueList_ pdata;
749   char *status;
750   char *error;
751
752   UPNP_REPLY_parse_ (response, received, &pdata);
753
754   status = UPNP_REPLY_get_value_ (&pdata, "NewConnectionStatus");
755   error = UPNP_REPLY_get_value_ (&pdata, "errorCode");
756
757   if (status)
758     cls->current_dev->is_connected = (strcmp ("Connected", status) == 0);
759   else
760     cls->current_dev->is_connected = GNUNET_NO;
761
762   if (error)
763     GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
764                      _("Could not get UPnP device status: error %s\n"),
765                      error);
766
767   GNUNET_free (response);
768   UPNP_REPLY_free_ (&pdata);
769
770   /* Go on to next device, or finish discovery process */
771   cls->current_dev = cls->current_dev->pNext;
772   get_valid_igd (cls);
773 }
774
775 /**
776  * Receive contents of the downloaded UPnP IGD description file,
777  * and fill UPNP_Dev_ and UPNP_IGD_Data_ structs with this data.
778  * Then, schedule UPnP command to check whether device is connected.
779  *
780  * @param desc UPnP IGD description (in XML)
781  * @data closure from UPNP_discover()
782  */
783 static void
784 get_valid_igd_receive (char *desc, void *data)
785 {
786   struct UPNP_discover_cls *cls = data;
787   struct UPNP_IGD_Data_ *igd_data;
788   char *buffer;
789
790   if (!desc || strlen (desc) == 0)
791     {
792       GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING, "UPnP",
793                        "Error getting IGD XML description at %s:%d\n",
794                        __FILE__, __LINE__);
795
796       /* Skip device */
797       cls->current_dev->data = NULL;
798       cls->current_dev->is_connected = GNUNET_NO;
799       get_valid_igd (cls);
800     }
801
802   igd_data = GNUNET_malloc (sizeof (struct UPNP_IGD_Data_));
803   memset (igd_data, 0, sizeof (struct UPNP_IGD_Data_));
804   UPNP_IGD_parse_desc_ (desc, strlen (desc), igd_data);
805
806   cls->current_dev->control_url =
807     format_control_urls (cls->current_dev->desc_url, igd_data);
808
809   if (igd_data->service_type != '\0')
810     cls->current_dev->service_type = GNUNET_strdup (igd_data->service_type);
811   else if (igd_data->service_type_CIF != '\0')
812     cls->current_dev->service_type =
813       GNUNET_strdup (igd_data->service_type_CIF);
814   else
815     cls->current_dev->service_type = GNUNET_strdup ("");
816
817   cls->current_dev->data = igd_data;
818
819   /* Check whether device is connected */
820   buffer = GNUNET_malloc (UPNP_COMMAND_BUFSIZE);
821   UPNP_command_ (cls->sched,
822                  cls->current_dev->control_url,
823                  cls->current_dev->data->service_type,
824                  "GetStatusInfo", NULL, buffer, UPNP_COMMAND_BUFSIZE,
825                  get_valid_igd_connected_cb, cls);
826
827   GNUNET_free (desc);
828 }
829
830 /**
831  * Free a chained list of UPnP devices.
832  */
833 static void
834 free_dev_list (struct UPNP_Dev_ *devlist)
835 {
836   struct UPNP_Dev_ *next;
837
838   while (devlist)
839     {
840       next = devlist->pNext;
841       GNUNET_free (devlist->control_url);
842       GNUNET_free (devlist->service_type);
843       GNUNET_free (devlist->desc_url);
844       GNUNET_free (devlist->data);
845       GNUNET_free (devlist->st);
846       GNUNET_free (devlist);
847       devlist = next;
848     }
849 }
850
851 /**
852  * Walk over the list of found devices looking for a connected IGD,
853  * if present, or at least a disconnected one.
854  */
855 static void
856 get_valid_igd (struct UPNP_discover_cls *cls)
857 {
858   struct UPNP_Dev_ *dev;
859   int step;
860
861   /* No device was discovered */
862   if (!cls->dev_list)
863     {
864       cls->caller_cb (NULL, NULL, cls->caller_cls);
865
866       GNUNET_free (cls);
867       return;
868     }
869   /* We already walked over all devices, see what we got,
870    * and return the device with the best state we have. */
871   else if (cls->current_dev == NULL)
872     {
873       for (step = 1; step <= 3; step++)
874         {
875           for (dev = cls->dev_list; dev; dev = dev->pNext)
876             {
877 #if DEBUG_UPNP
878               GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
879                                "Found device: control_url: %s, service_type: %s\n",
880                                dev->control_url, dev->service_type);
881 #endif
882               /* Accept connected IGDs on step 1, non-connected IGDs
883                * on step 2, and other device types on step 3. */
884               if ((step == 1 && dev->is_connected)
885                   || (step < 3 && 0 != strcmp (dev->service_type,
886                                                "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")))
887                 continue;
888
889               cls->caller_cb (dev->control_url,
890                               dev->service_type, cls->caller_cls);
891
892               free_dev_list (cls->dev_list);
893               GNUNET_free (cls);
894               return;
895             }
896         }
897
898       /* We cannot reach this... */
899       GNUNET_assert (GNUNET_NO);
900     }
901
902   /* There are still devices to ask, go on */
903   download_device_description (cls->sched, cls->current_dev->desc_url,
904                                get_valid_igd_receive, cls);
905 }
906
907 static const char *const discover_type_list[] = {
908   "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
909   "urn:schemas-upnp-org:service:WANIPConnection:1",
910   "urn:schemas-upnp-org:service:WANPPPConnection:1",
911   "upnp:rootdevice",
912   NULL
913 };
914
915 static void
916 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc);
917
918 /**
919  * Handle response from device. Stop when all device types have been tried,
920  * and get their descriptions.
921  *
922  * @param data closure from UPNP_discover()
923  * @buf content of the reply
924  * @available number of bytes stored in buf
925  * @addr address of the sender
926  * @addrlen size of addr
927  * @param errCode value of errno
928  */
929 static void
930 discover_recv (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
931 {
932   struct UPNP_discover_cls *cls = data;
933   GNUNET_SCHEDULER_TaskIdentifier task_w;
934   struct UPNP_Dev_ *tmp;
935   socklen_t addrlen;
936   ssize_t received;
937   char buf[DISCOVER_BUFSIZE];
938   const char *desc_url = NULL;
939   int urlsize = 0;
940   const char *st = NULL;
941   int stsize = 0;
942
943   /* Free fdset that was used for this sned/receive operation */
944   GNUNET_NETWORK_fdset_destroy (cls->fdset);
945
946   if (cls->multicast_addr->sa_family == AF_INET)
947     addrlen = sizeof (struct sockaddr_in);
948   else
949     addrlen = sizeof (struct sockaddr_in6);
950
951   errno = 0;
952   received =
953     GNUNET_NETWORK_socket_recvfrom (cls->sudp, &buf, DISCOVER_BUFSIZE - 1,
954                                     (struct sockaddr *) cls->multicast_addr,
955                                     &addrlen);
956   if (received == GNUNET_SYSERR)
957     {
958       PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_recvfrom");
959     }
960 #if DEBUG_UPNP
961   else
962     {
963       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
964                        "Received %d bytes from %s\n", received,
965                        GNUNET_a2s (cls->multicast_addr, addrlen));
966     }
967 #endif
968
969   parse_msearch_reply (buf, received, &desc_url, &urlsize, &st, &stsize);
970
971   if (st && desc_url)
972     {
973       tmp = (struct UPNP_Dev_ *) GNUNET_malloc (sizeof (struct UPNP_Dev_));
974       tmp->pNext = cls->dev_list;
975
976       tmp->desc_url = GNUNET_malloc (urlsize + 1);
977       strncpy (tmp->desc_url, desc_url, urlsize);
978       tmp->desc_url[urlsize] = '\0';
979
980       tmp->st = GNUNET_malloc (stsize + 1);
981       strncpy (tmp->st, st, stsize);
982       tmp->st[stsize] = '\0';
983       cls->dev_list = tmp;
984 #if DEBUG_UPNP
985       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
986                        "Found device %s when looking for type %s\n",
987                        tmp->desc_url, tmp->st);
988 #endif
989     }
990
991   /* Continue discovery until all types of devices have been tried */
992   if (discover_type_list[cls->type_index])
993     {
994       /* Send queries for each device type and wait for a possible reply.
995        * receiver callback takes care of trying another device type,
996        * and eventually calls the caller's callback. */
997       cls->fdset = GNUNET_NETWORK_fdset_create ();
998       GNUNET_NETWORK_fdset_zero (cls->fdset);
999       GNUNET_NETWORK_fdset_set (cls->fdset, cls->sudp);
1000
1001       task_w = GNUNET_SCHEDULER_add_select (cls->sched,
1002                                             GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1003                                             GNUNET_SCHEDULER_NO_TASK,
1004                                             GNUNET_TIME_relative_multiply
1005                                             (GNUNET_TIME_UNIT_SECONDS, 15),
1006                                             NULL, cls->fdset, &discover_send,
1007                                             cls);
1008
1009       GNUNET_SCHEDULER_add_select (cls->sched,
1010                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1011                                    task_w,
1012                                    GNUNET_TIME_relative_multiply
1013                                    (GNUNET_TIME_UNIT_SECONDS, 5), cls->fdset,
1014                                    NULL, &discover_recv, cls);
1015     }
1016   else
1017     {
1018       GNUNET_NETWORK_socket_close (cls->sudp);
1019       GNUNET_free (cls->multicast_addr);
1020       cls->current_dev = cls->dev_list;
1021       get_valid_igd (cls);
1022     }
1023 }
1024
1025 /**
1026  * Send the SSDP M-SEARCH packet.
1027  *
1028  * @param data closure from UPNP_discover()
1029  * @param tc task context
1030  */
1031 static void
1032 discover_send (void *data, const struct GNUNET_SCHEDULER_TaskContext *tc)
1033 {
1034   struct UPNP_discover_cls *cls = data;
1035   socklen_t addrlen;
1036   ssize_t n, sent;
1037   char buf[DISCOVER_BUFSIZE];
1038   static const char msearch_msg[] =
1039     "M-SEARCH * HTTP/1.1\r\n"
1040     "HOST: " UPNP_MCAST_ADDR ":" XSTR (PORT) "\r\n"
1041     "ST: %s\r\n" "MAN: \"ssdp:discover\"\r\n" "MX: 3\r\n" "\r\n";
1042
1043   if (cls->multicast_addr->sa_family == AF_INET)
1044     addrlen = sizeof (struct sockaddr_in);
1045   else
1046     addrlen = sizeof (struct sockaddr_in6);
1047
1048   n =
1049     snprintf (buf, DISCOVER_BUFSIZE, msearch_msg,
1050               discover_type_list[cls->type_index++]);
1051
1052   errno = 0;
1053   sent = GNUNET_NETWORK_socket_sendto (cls->sudp, buf, n,
1054                                        (struct sockaddr *)
1055                                        cls->multicast_addr, addrlen);
1056   if (sent == GNUNET_SYSERR)
1057     {
1058       PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_sendto");
1059     }
1060   else if (sent < n)
1061     {
1062       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1063                        "Could only send %d bytes to %s, needed %d bytes\n",
1064                        sent, GNUNET_a2s (cls->multicast_addr, addrlen), n);
1065     }
1066 #if DEBUG_UPNP
1067   else
1068     {
1069       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "UPnP",
1070                        "Sent %d bytes to %s\n", sent,
1071                        GNUNET_a2s (cls->multicast_addr, addrlen));
1072     }
1073 #endif
1074 }
1075
1076 /**
1077  * Search for UPnP Internet Gateway Devices (IGD) on a given network interface.
1078  * If several devices are found, a device that is connected to the WAN
1079  * is returned first (if any).
1080  *
1081  * @param sched scheduler to use for network tasks
1082  * @param multicastif network interface to send discovery messages, or NULL
1083  * @param addr address used to send messages on multicastif, or NULL
1084  * @param caller_cb user function to call when done
1085  * @param caller_cls closure to pass to caller_cb
1086  */
1087 void
1088 UPNP_discover_ (struct GNUNET_SCHEDULER_Handle *sched,
1089                 const char *multicastif,
1090                 const struct sockaddr *addr,
1091                 UPNP_discover_cb_ caller_cb, void *caller_cls)
1092 {
1093   int opt = 1;
1094   int domain = PF_INET;
1095   int if_index;
1096   struct in6_addr any_addr = IN6ADDR_ANY_INIT;
1097   struct sockaddr_in sockudp_r, sockudp_w;
1098   struct sockaddr_in6 sockudp6_r, sockudp6_w;
1099   GNUNET_SCHEDULER_TaskIdentifier task_w;
1100   struct GNUNET_NETWORK_Handle *sudp;
1101   struct UPNP_discover_cls *cls;
1102
1103
1104   if (addr && addr->sa_family == AF_INET)
1105     {
1106       domain = PF_INET;
1107     }
1108   else if (addr && addr->sa_family == AF_INET6)
1109     {
1110       domain = PF_INET6;
1111     }
1112   else if (addr)
1113     {
1114       GNUNET_break (0);
1115       caller_cb (NULL, NULL, caller_cls);
1116       return;
1117     }
1118
1119   errno = 0;
1120   sudp = GNUNET_NETWORK_socket_create (domain, SOCK_DGRAM, 0);
1121
1122   if (sudp == NULL)
1123     {
1124       PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_create");
1125       caller_cb (NULL, NULL, caller_cls);
1126       return;
1127     }
1128
1129
1130   cls = GNUNET_malloc (sizeof (struct UPNP_discover_cls));
1131   cls->sched = sched;
1132   cls->sudp = sudp;
1133   cls->type_index = 0;
1134   cls->dev_list = NULL;
1135   cls->current_dev = NULL;
1136   cls->caller_cb = caller_cb;
1137   cls->caller_cls = caller_cls;
1138
1139
1140   if (domain == PF_INET)
1141     {
1142       /* receive */
1143       memset (&sockudp_r, 0, sizeof (struct sockaddr_in));
1144       sockudp_r.sin_family = AF_INET;
1145 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1146       sockudp_r.sin_len = sizeof (struct sockaddr_in);
1147 #endif
1148       sockudp_r.sin_port = 0;
1149       sockudp_r.sin_addr.s_addr = INADDR_ANY;
1150
1151       /* send */
1152       memset (&sockudp_w, 0, sizeof (struct sockaddr_in));
1153       sockudp_w.sin_family = AF_INET;
1154       sockudp_w.sin_port = htons (PORT);
1155       sockudp_w.sin_addr.s_addr = inet_addr (UPNP_MCAST_ADDR);
1156 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1157       sockudp_w.sin_len = sizeof (struct sockaddr_in);
1158 #endif
1159
1160       cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in));
1161       memcpy (cls->multicast_addr, &sockudp_w, sizeof (struct sockaddr_in));
1162     }
1163   else
1164     {
1165       /* receive */
1166       memcpy (&sockudp6_r, addr, sizeof (struct sockaddr_in6));
1167       sockudp6_r.sin6_port = 0;
1168       sockudp6_r.sin6_addr = any_addr;
1169 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1170       sockudp6_r.sin6_len = sizeof (struct sockaddr_in6);
1171 #endif
1172
1173       /* send */
1174       memset (&sockudp6_w, 0, sizeof (struct sockaddr_in6));
1175       sockudp6_w.sin6_family = AF_INET6;
1176       sockudp6_w.sin6_port = htons (PORT);
1177       if (inet_pton (AF_INET6, UPNP_MCAST_ADDR6, &sockudp6_w.sin6_addr) != 1)
1178         {
1179           PRINT_SOCKET_ERROR ("inet_pton");
1180           caller_cb (NULL, NULL, caller_cls);
1181           return;
1182         }
1183 #ifdef HAVE_SOCKADDR_IN_SIN_LEN
1184       sockudp6_w.sin6_len = sizeof (struct sockaddr_in6);
1185 #endif
1186
1187       cls->multicast_addr = GNUNET_malloc (sizeof (struct sockaddr_in6));
1188       memcpy (cls->multicast_addr, &sockudp6_w, sizeof (struct sockaddr_in6));
1189     }
1190
1191   if (GNUNET_NETWORK_socket_setsockopt
1192       (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) == GNUNET_SYSERR)
1193     {
1194       PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1195       GNUNET_NETWORK_socket_close (sudp);
1196       caller_cb (NULL, NULL, caller_cls);
1197       return;
1198     }
1199
1200   if (addr)
1201     {
1202       if (domain == PF_INET)
1203         {
1204           sockudp_r.sin_addr.s_addr =
1205             ((struct sockaddr_in *) addr)->sin_addr.s_addr;
1206           if (GNUNET_NETWORK_socket_setsockopt
1207               (sudp, IPPROTO_IP, IP_MULTICAST_IF,
1208                (const char *) &sockudp_r.sin_addr,
1209                sizeof (struct in_addr)) == GNUNET_SYSERR)
1210             {
1211               PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1212             }
1213         }
1214       else
1215         {
1216           if (multicastif)
1217             {
1218               if_index = if_nametoindex (multicastif);
1219               if (!if_index)
1220                 PRINT_SOCKET_ERROR ("if_nametoindex");
1221
1222               if (GNUNET_NETWORK_socket_setsockopt
1223                   (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index,
1224                    sizeof (if_index)) == GNUNET_SYSERR)
1225                 {
1226                   PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_setsockopt");
1227                 }
1228             }
1229
1230           memcpy (&sockudp6_r.sin6_addr,
1231                   &((struct sockaddr_in6 *) addr)->sin6_addr,
1232                   sizeof (sockudp6_r.sin6_addr));
1233         }
1234     }
1235
1236   if (domain == PF_INET)
1237     {
1238       /* Bind to receive response before sending packet */
1239       if (GNUNET_NETWORK_socket_bind
1240           (sudp, (struct sockaddr *) &sockudp_r,
1241            sizeof (struct sockaddr_in)) != GNUNET_OK)
1242         {
1243           PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1244           GNUNET_NETWORK_socket_close (sudp);
1245           GNUNET_free (cls->multicast_addr);
1246           caller_cb (NULL, NULL, caller_cls);
1247           return;
1248         }
1249     }
1250   else
1251     {
1252       /* Bind to receive response before sending packet */
1253       if (GNUNET_NETWORK_socket_bind
1254           (sudp, (struct sockaddr *) &sockudp6_r,
1255            sizeof (struct sockaddr_in6)) != GNUNET_OK)
1256         {
1257           PRINT_SOCKET_ERROR ("GNUNET_NETWORK_socket_bind");
1258           GNUNET_free (cls->multicast_addr);
1259           GNUNET_NETWORK_socket_close (sudp);
1260           caller_cb (NULL, NULL, caller_cls);
1261           return;
1262         }
1263     }
1264
1265   /* Send queries for each device type and wait for a possible reply.
1266    * receiver callback takes care of trying another device type,
1267    * and eventually calls the caller's callback. */
1268   cls->fdset = GNUNET_NETWORK_fdset_create ();
1269   GNUNET_NETWORK_fdset_zero (cls->fdset);
1270   GNUNET_NETWORK_fdset_set (cls->fdset, sudp);
1271
1272   task_w = GNUNET_SCHEDULER_add_select (sched,
1273                                         GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1274                                         GNUNET_SCHEDULER_NO_TASK,
1275                                         GNUNET_TIME_relative_multiply
1276                                         (GNUNET_TIME_UNIT_SECONDS, 15), NULL,
1277                                         cls->fdset, &discover_send, cls);
1278
1279   GNUNET_SCHEDULER_add_select (sched,
1280                                GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1281                                task_w,
1282                                GNUNET_TIME_relative_multiply
1283                                (GNUNET_TIME_UNIT_SECONDS, 15), cls->fdset,
1284                                NULL, &discover_recv, cls);
1285 }