~ubuntu-branches/ubuntu/maverick/libtorrent-rasterbar/maverick

« back to all changes in this revision

Viewing changes to src/upnp.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Cristian Greco
  • Date: 2008-07-02 10:46:21 UTC
  • Revision ID: james.westby@ubuntu.com-20080702104621-jzx3pfke9lkcxfxn
Tags: upstream-0.13.1
ImportĀ upstreamĀ versionĀ 0.13.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 
 
3
Copyright (c) 2007, Arvid Norberg
 
4
All rights reserved.
 
5
 
 
6
Redistribution and use in source and binary forms, with or without
 
7
modification, are permitted provided that the following conditions
 
8
are met:
 
9
 
 
10
    * Redistributions of source code must retain the above copyright
 
11
      notice, this list of conditions and the following disclaimer.
 
12
    * Redistributions in binary form must reproduce the above copyright
 
13
      notice, this list of conditions and the following disclaimer in
 
14
      the documentation and/or other materials provided with the distribution.
 
15
    * Neither the name of the author nor the names of its
 
16
      contributors may be used to endorse or promote products derived
 
17
      from this software without specific prior written permission.
 
18
 
 
19
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 
20
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 
21
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 
22
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 
23
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 
24
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
25
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 
26
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 
27
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 
28
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
29
POSSIBILITY OF SUCH DAMAGE.
 
30
 
 
31
*/
 
32
 
 
33
#include "libtorrent/pch.hpp"
 
34
 
 
35
#include "libtorrent/socket.hpp"
 
36
#include "libtorrent/upnp.hpp"
 
37
#include "libtorrent/io.hpp"
 
38
#include "libtorrent/http_tracker_connection.hpp"
 
39
#include "libtorrent/xml_parse.hpp"
 
40
#include "libtorrent/connection_queue.hpp"
 
41
#include "libtorrent/enum_net.hpp"
 
42
 
 
43
#include <boost/bind.hpp>
 
44
#include <boost/ref.hpp>
 
45
#include <asio/ip/host_name.hpp>
 
46
#include <asio/ip/multicast.hpp>
 
47
#include <boost/thread/mutex.hpp>
 
48
#include <cstdlib>
 
49
 
 
50
#if (defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)) && !defined(TORRENT_UPNP_LOGGING)
 
51
#define TORRENT_UPNP_LOGGING
 
52
#endif
 
53
 
 
54
using boost::bind;
 
55
using namespace libtorrent;
 
56
 
 
57
namespace libtorrent
 
58
{
 
59
        bool is_local(address const& a);
 
60
        address guess_local_address(asio::io_service&);
 
61
}
 
62
 
 
63
upnp::upnp(io_service& ios, connection_queue& cc
 
64
        , address const& listen_interface, std::string const& user_agent
 
65
        , portmap_callback_t const& cb, bool ignore_nonrouters)
 
66
        : m_udp_local_port(0)
 
67
        , m_tcp_local_port(0)
 
68
        , m_user_agent(user_agent)
 
69
        , m_callback(cb)
 
70
        , m_retry_count(0)
 
71
        , m_io_service(ios)
 
72
        , m_strand(ios)
 
73
        , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900)
 
74
                , bind(&upnp::on_reply, self(), _1, _2, _3), false)
 
75
        , m_broadcast_timer(ios)
 
76
        , m_refresh_timer(ios)
 
77
        , m_disabled(false)
 
78
        , m_closing(false)
 
79
        , m_ignore_outside_network(ignore_nonrouters)
 
80
        , m_cc(cc)
 
81
{
 
82
#ifdef TORRENT_UPNP_LOGGING
 
83
        m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc);
 
84
#endif
 
85
        m_retry_count = 0;
 
86
}
 
87
 
 
88
upnp::~upnp()
 
89
{
 
90
}
 
91
 
 
92
void upnp::discover_device() try
 
93
{
 
94
        const char msearch[] = 
 
95
                "M-SEARCH * HTTP/1.1\r\n"
 
96
                "HOST: 239.255.255.250:1900\r\n"
 
97
                "ST:upnp:rootdevice\r\n"
 
98
                "MAN:\"ssdp:discover\"\r\n"
 
99
                "MX:3\r\n"
 
100
                "\r\n\r\n";
 
101
 
 
102
        asio::error_code ec;
 
103
#ifdef TORRENT_DEBUG_UPNP
 
104
        // simulate packet loss
 
105
        if (m_retry_count & 1)
 
106
#endif
 
107
        m_socket.send(msearch, sizeof(msearch) - 1, ec);
 
108
 
 
109
        if (ec)
 
110
        {
 
111
#ifdef TORRENT_UPNP_LOGGING
 
112
                m_log << time_now_string()
 
113
                        << " ==> Broadcast FAILED: " << ec.message() << std::endl
 
114
                        << "aborting" << std::endl;
 
115
#endif
 
116
                disable();
 
117
                return;
 
118
        }
 
119
 
 
120
        ++m_retry_count;
 
121
        m_broadcast_timer.expires_from_now(milliseconds(250 * m_retry_count));
 
122
        m_broadcast_timer.async_wait(bind(&upnp::resend_request
 
123
                , self(), _1));
 
124
 
 
125
#ifdef TORRENT_UPNP_LOGGING
 
126
        m_log << time_now_string()
 
127
                << " ==> Broadcasting search for rootdevice" << std::endl;
 
128
#endif
 
129
}
 
130
catch (std::exception&)
 
131
{
 
132
        disable();
 
133
};
 
134
 
 
135
void upnp::set_mappings(int tcp, int udp)
 
136
{
 
137
#ifdef TORRENT_UPNP_LOGGING
 
138
        m_log << time_now_string()
 
139
                << " *** set mappings " << tcp << " " << udp;
 
140
        if (m_disabled) m_log << " DISABLED";
 
141
        m_log << std::endl;
 
142
#endif
 
143
 
 
144
        if (m_disabled) return;
 
145
        if (udp != 0) m_udp_local_port = udp;
 
146
        if (tcp != 0) m_tcp_local_port = tcp;
 
147
 
 
148
        for (std::set<rootdevice>::iterator i = m_devices.begin()
 
149
                , end(m_devices.end()); i != end; ++i)
 
150
        {
 
151
                rootdevice& d = const_cast<rootdevice&>(*i);
 
152
                TORRENT_ASSERT(d.magic == 1337);
 
153
                if (d.mapping[0].local_port != m_tcp_local_port)
 
154
                {
 
155
                        if (d.mapping[0].external_port == 0)
 
156
                                d.mapping[0].external_port = m_tcp_local_port;
 
157
                        d.mapping[0].local_port = m_tcp_local_port;
 
158
                        d.mapping[0].need_update = true;
 
159
                }
 
160
                if (d.mapping[1].local_port != m_udp_local_port)
 
161
                {
 
162
                        if (d.mapping[1].external_port == 0)
 
163
                                d.mapping[1].external_port = m_udp_local_port;
 
164
                        d.mapping[1].local_port = m_udp_local_port;
 
165
                        d.mapping[1].need_update = true;
 
166
                }
 
167
                if (d.service_namespace
 
168
                        && (d.mapping[0].need_update || d.mapping[1].need_update))
 
169
                        map_port(d, 0);
 
170
        }
 
171
}
 
172
 
 
173
void upnp::resend_request(asio::error_code const& e)
 
174
#ifndef NDEBUG
 
175
try
 
176
#endif
 
177
{
 
178
        if (e) return;
 
179
        if (m_retry_count < 9
 
180
                && (m_devices.empty() || m_retry_count < 4))
 
181
        {
 
182
                discover_device();
 
183
                return;
 
184
        }
 
185
 
 
186
        if (m_devices.empty())
 
187
        {
 
188
#ifdef TORRENT_UPNP_LOGGING
 
189
                m_log << time_now_string()
 
190
                        << " *** Got no response in 9 retries. Giving up, "
 
191
                        "disabling UPnP." << std::endl;
 
192
#endif
 
193
                disable();
 
194
                return;
 
195
        }
 
196
        
 
197
        for (std::set<rootdevice>::iterator i = m_devices.begin()
 
198
                , end(m_devices.end()); i != end; ++i)
 
199
        {
 
200
                if (i->control_url.empty() && !i->upnp_connection && !i->disabled)
 
201
                {
 
202
                        // we don't have a WANIP or WANPPP url for this device,
 
203
                        // ask for it
 
204
                        rootdevice& d = const_cast<rootdevice&>(*i);
 
205
                        TORRENT_ASSERT(d.magic == 1337);
 
206
                        try
 
207
                        {
 
208
#ifdef TORRENT_UPNP_LOGGING
 
209
                                m_log << time_now_string()
 
210
                                        << " ==> connecting to " << d.url << std::endl;
 
211
#endif
 
212
                                if (d.upnp_connection) d.upnp_connection->close();
 
213
                                d.upnp_connection.reset(new http_connection(m_io_service
 
214
                                        , m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2
 
215
                                        , boost::ref(d), _5)));
 
216
                                d.upnp_connection->get(d.url);
 
217
                        }
 
218
                        catch (std::exception& e)
 
219
                        {
 
220
                                (void)e;
 
221
#ifdef TORRENT_UPNP_LOGGING
 
222
                                m_log << time_now_string()
 
223
                                        << " *** Connection failed to: " << d.url
 
224
                                        << " " << e.what() << std::endl;
 
225
#endif
 
226
                                d.disabled = true;
 
227
                        }
 
228
                }
 
229
        }
 
230
}
 
231
#ifndef NDEBUG
 
232
catch (std::exception&)
 
233
{
 
234
        TORRENT_ASSERT(false);
 
235
};
 
236
#endif
 
237
 
 
238
void upnp::on_reply(udp::endpoint const& from, char* buffer
 
239
        , std::size_t bytes_transferred)
 
240
#ifndef NDEBUG
 
241
try
 
242
#endif
 
243
{
 
244
        using namespace libtorrent::detail;
 
245
 
 
246
        // parse out the url for the device
 
247
 
 
248
/*
 
249
        the response looks like this:
 
250
 
 
251
        HTTP/1.1 200 OK
 
252
        ST:upnp:rootdevice
 
253
        USN:uuid:000f-66d6-7296000099dc::upnp:rootdevice
 
254
        Location: http://192.168.1.1:5431/dyndev/uuid:000f-66d6-7296000099dc
 
255
        Server: Custom/1.0 UPnP/1.0 Proc/Ver
 
256
        EXT:
 
257
        Cache-Control:max-age=180
 
258
        DATE: Fri, 02 Jan 1970 08:10:38 GMT
 
259
 
 
260
        a notification looks like this:
 
261
 
 
262
        NOTIFY * HTTP/1.1
 
263
        Host:239.255.255.250:1900
 
264
        NT:urn:schemas-upnp-org:device:MediaServer:1
 
265
        NTS:ssdp:alive
 
266
        Location:http://10.0.3.169:2869/upnphost/udhisapi.dll?content=uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e
 
267
        USN:uuid:c17f0c32-d19b-4938-ae94-65f945c3a26e::urn:schemas-upnp-org:device:MediaServer:1
 
268
        Cache-Control:max-age=900
 
269
        Server:Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
 
270
 
 
271
*/
 
272
        asio::error_code ec;
 
273
        if (m_ignore_outside_network && !in_local_network(m_io_service, from.address(), ec))
 
274
        {
 
275
                // this upnp device is filtered because it's not in the
 
276
                // list of configured routers
 
277
#ifdef TORRENT_UPNP_LOGGING
 
278
                if (ec)
 
279
                {
 
280
                        m_log << time_now_string() << " <== (" << from << ") error: "
 
281
                                << ec.message() << std::endl;
 
282
                }
 
283
                else
 
284
                {
 
285
                        std::vector<ip_interface> const& net = enum_net_interfaces(m_io_service, ec);
 
286
                        m_log << time_now_string() << " <== (" << from << ") UPnP device "
 
287
                                "ignored because it's not on our network ";
 
288
                        for (std::vector<ip_interface>::const_iterator i = net.begin()
 
289
                                , end(net.end()); i != end; ++i)
 
290
                        {
 
291
                                m_log << "(" << i->interface_address << ", " << i->netmask << ") ";
 
292
                        }
 
293
                        m_log << std::endl;
 
294
                }
 
295
#endif
 
296
                return;
 
297
        }
 
298
 
 
299
        http_parser p;
 
300
        try
 
301
        {
 
302
                p.incoming(buffer::const_interval(buffer
 
303
                        , buffer + bytes_transferred));
 
304
        }
 
305
        catch (std::exception& e)
 
306
        {
 
307
                (void)e;
 
308
#ifdef TORRENT_UPNP_LOGGING
 
309
                m_log << time_now_string()
 
310
                        << " <== (" << from << ") Rootdevice responded with incorrect HTTP packet. Ignoring device (" << e.what() << ")" << std::endl;
 
311
#endif
 
312
                return;
 
313
        }
 
314
 
 
315
        if (p.status_code() != 200 && p.method() != "notify")
 
316
        {
 
317
#ifdef TORRENT_UPNP_LOGGING
 
318
                if (p.method().empty())
 
319
                        m_log << time_now_string()
 
320
                                << " <== (" << from << ") Device responded with HTTP status: " << p.status_code()
 
321
                                << ". Ignoring device" << std::endl;
 
322
                else
 
323
                        m_log << time_now_string()
 
324
                                << " <== (" << from << ") Device with HTTP method: " << p.method()
 
325
                                << ". Ignoring device" << std::endl;
 
326
#endif
 
327
                return;
 
328
        }
 
329
 
 
330
        if (!p.header_finished())
 
331
        {
 
332
#ifdef TORRENT_UPNP_LOGGING
 
333
                m_log << time_now_string()
 
334
                        << " <== (" << from << ") Rootdevice responded with incomplete HTTP "
 
335
                        "packet. Ignoring device" << std::endl;
 
336
#endif
 
337
                return;
 
338
        }
 
339
 
 
340
        std::string url = p.header("location");
 
341
        if (url.empty())
 
342
        {
 
343
#ifdef TORRENT_UPNP_LOGGING
 
344
                m_log << time_now_string()
 
345
                        << " <== (" << from << ") Rootdevice response is missing a location header. "
 
346
                        "Ignoring device" << std::endl;
 
347
#endif
 
348
                return;
 
349
        }
 
350
 
 
351
        rootdevice d;
 
352
        d.url = url;
 
353
 
 
354
        std::set<rootdevice>::iterator i = m_devices.find(d);
 
355
 
 
356
        if (i == m_devices.end())
 
357
        {
 
358
 
 
359
                std::string protocol;
 
360
                std::string auth;
 
361
                // we don't have this device in our list. Add it
 
362
                try
 
363
                {
 
364
                        boost::tie(protocol, auth, d.hostname, d.port, d.path)
 
365
                                = parse_url_components(d.url);
 
366
                }
 
367
                catch (std::exception& e)
 
368
                {
 
369
#ifdef TORRENT_UPNP_LOGGING
 
370
                        m_log << time_now_string()
 
371
                                << " <== (" << from << ") invalid url: '" << d.url
 
372
                                << "'. Ignoring device" << std::endl;
 
373
#endif
 
374
                        return;
 
375
                }
 
376
 
 
377
                // ignore the auth here. It will be re-parsed
 
378
                // by the http connection later
 
379
 
 
380
                if (protocol != "http")
 
381
                {
 
382
#ifdef TORRENT_UPNP_LOGGING
 
383
                        m_log << time_now_string()
 
384
                                << " <== (" << from << ") Rootdevice uses unsupported protocol: '" << protocol
 
385
                                << "'. Ignoring device" << std::endl;
 
386
#endif
 
387
                        return;
 
388
                }
 
389
 
 
390
                if (d.port == 0)
 
391
                {
 
392
#ifdef TORRENT_UPNP_LOGGING
 
393
                        m_log << time_now_string()
 
394
                                << " <== (" << from << ") Rootdevice responded with a url with port 0. "
 
395
                                "Ignoring device" << std::endl;
 
396
#endif
 
397
                        return;
 
398
                }
 
399
#ifdef TORRENT_UPNP_LOGGING
 
400
                m_log << time_now_string()
 
401
                        << " <== (" << from << ") Found rootdevice: " << d.url
 
402
                        << " total: " << m_devices.size() << std::endl;
 
403
#endif
 
404
 
 
405
                if (m_devices.size() >= 50)
 
406
                {
 
407
#ifdef TORRENT_UPNP_LOGGING
 
408
                        m_log << time_now_string()
 
409
                                << " <== (" << from << ") Too many devices (" << m_devices.size() << "), "
 
410
                                "ignoring: " << d.url << std::endl;
 
411
#endif
 
412
                        return;
 
413
                }
 
414
 
 
415
                if (m_tcp_local_port != 0)
 
416
                {
 
417
                        d.mapping[0].need_update = true;
 
418
                        d.mapping[0].local_port = m_tcp_local_port;
 
419
                        if (d.mapping[0].external_port == 0)
 
420
                                d.mapping[0].external_port = d.mapping[0].local_port;
 
421
#ifdef TORRENT_UPNP_LOGGING
 
422
                        m_log << time_now_string() << " *** Mapping 0 will be updated" << std::endl;
 
423
#endif
 
424
                }
 
425
                if (m_udp_local_port != 0)
 
426
                {
 
427
                        d.mapping[1].need_update = true;
 
428
                        d.mapping[1].local_port = m_udp_local_port;
 
429
                        if (d.mapping[1].external_port == 0)
 
430
                                d.mapping[1].external_port = d.mapping[1].local_port;
 
431
#ifdef TORRENT_UPNP_LOGGING
 
432
                        m_log << time_now_string() << " *** Mapping 1 will be updated" << std::endl;
 
433
#endif
 
434
                }
 
435
                boost::tie(i, boost::tuples::ignore) = m_devices.insert(d);
 
436
        }
 
437
 
 
438
 
 
439
        // since we're using udp, send the query 4 times
 
440
        // just to make sure we find all devices
 
441
        if (m_retry_count >= 4 && !m_devices.empty())
 
442
        {
 
443
                m_broadcast_timer.cancel();
 
444
 
 
445
                for (std::set<rootdevice>::iterator i = m_devices.begin()
 
446
                        , end(m_devices.end()); i != end; ++i)
 
447
                {
 
448
                        if (i->control_url.empty() && !i->upnp_connection && !i->disabled)
 
449
                        {
 
450
                                // we don't have a WANIP or WANPPP url for this device,
 
451
                                // ask for it
 
452
                                rootdevice& d = const_cast<rootdevice&>(*i);
 
453
                                TORRENT_ASSERT(d.magic == 1337);
 
454
                                try
 
455
                                {
 
456
#ifdef TORRENT_UPNP_LOGGING
 
457
                                        m_log << time_now_string()
 
458
                                                << " ==> connecting to " << d.url << std::endl;
 
459
#endif
 
460
                                        if (d.upnp_connection) d.upnp_connection->close();
 
461
                                        d.upnp_connection.reset(new http_connection(m_io_service
 
462
                                                , m_cc, bind(&upnp::on_upnp_xml, self(), _1, _2
 
463
                                                , boost::ref(d), _5)));
 
464
                                        d.upnp_connection->get(d.url);
 
465
                                }
 
466
                                catch (std::exception& e)
 
467
                                {
 
468
                                        (void)e;
 
469
#ifdef TORRENT_UPNP_LOGGING
 
470
                                        m_log << time_now_string()
 
471
                                                << " *** Connection failed to: " << d.url
 
472
                                                << " " << e.what() << std::endl;
 
473
#endif
 
474
                                        d.disabled = true;
 
475
                                }
 
476
                        }
 
477
                }
 
478
        }
 
479
}
 
480
#ifndef NDEBUG
 
481
catch (std::exception&)
 
482
{
 
483
        TORRENT_ASSERT(false);
 
484
};
 
485
#endif
 
486
 
 
487
void upnp::post(upnp::rootdevice const& d, std::string const& soap
 
488
        , std::string const& soap_action)
 
489
{
 
490
        TORRENT_ASSERT(d.magic == 1337);
 
491
        TORRENT_ASSERT(d.upnp_connection);
 
492
 
 
493
        std::stringstream header;
 
494
        
 
495
        header << "POST " << d.control_url << " HTTP/1.1\r\n"
 
496
                "Host: " << d.hostname << ":" << d.port << "\r\n"
 
497
                "Content-Type: text/xml; charset=\"utf-8\"\r\n"
 
498
                "Content-Length: " << soap.size() << "\r\n"
 
499
                "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap;
 
500
 
 
501
        d.upnp_connection->sendbuffer = header.str();
 
502
 
 
503
#ifdef TORRENT_UPNP_LOGGING
 
504
        m_log << time_now_string()
 
505
                << " ==> sending: " << header.str() << std::endl;
 
506
#endif
 
507
        
 
508
}
 
509
 
 
510
void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i)
 
511
{
 
512
        TORRENT_ASSERT(d.magic == 1337);
 
513
 
 
514
        if (!d.upnp_connection)
 
515
        {
 
516
                TORRENT_ASSERT(d.disabled);
 
517
#ifdef TORRENT_UPNP_LOGGING
 
518
                m_log << time_now_string() << " *** mapping (" << i
 
519
                        << ") aborted" << std::endl;
 
520
#endif
 
521
                return;
 
522
        }
 
523
        
 
524
        std::string soap_action = "AddPortMapping";
 
525
 
 
526
        std::stringstream soap;
 
527
        
 
528
        soap << "<?xml version=\"1.0\"?>\n"
 
529
                "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
 
530
                "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
 
531
                "<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
 
532
 
 
533
        soap << "<NewRemoteHost></NewRemoteHost>"
 
534
                "<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
 
535
                "<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>"
 
536
                "<NewInternalPort>" << d.mapping[i].local_port << "</NewInternalPort>"
 
537
                "<NewInternalClient>" << c.socket().local_endpoint().address().to_string() << "</NewInternalClient>"
 
538
                "<NewEnabled>1</NewEnabled>"
 
539
                "<NewPortMappingDescription>" << m_user_agent << "</NewPortMappingDescription>"
 
540
                "<NewLeaseDuration>" << d.lease_duration << "</NewLeaseDuration>";
 
541
        soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
 
542
 
 
543
        post(d, soap.str(), soap_action);
 
544
}
 
545
 
 
546
void upnp::map_port(rootdevice& d, int i)
 
547
{
 
548
        TORRENT_ASSERT(d.magic == 1337);
 
549
        if (d.upnp_connection) return;
 
550
 
 
551
        if (!d.mapping[i].need_update)
 
552
        {
 
553
#ifdef TORRENT_UPNP_LOGGING
 
554
                m_log << time_now_string() << " *** mapping (" << i
 
555
                        << ") does not need update, skipping" << std::endl;
 
556
#endif
 
557
                if (i < num_mappings - 1)
 
558
                        map_port(d, i + 1);
 
559
                return;
 
560
        }
 
561
        d.mapping[i].need_update = false;
 
562
        TORRENT_ASSERT(!d.upnp_connection);
 
563
        TORRENT_ASSERT(d.service_namespace);
 
564
 
 
565
#ifdef TORRENT_UPNP_LOGGING
 
566
                m_log << time_now_string()
 
567
                        << " ==> connecting to " << d.hostname << std::endl;
 
568
#endif
 
569
        if (d.upnp_connection) d.upnp_connection->close();
 
570
        d.upnp_connection.reset(new http_connection(m_io_service
 
571
                , m_cc, bind(&upnp::on_upnp_map_response, self(), _1, _2
 
572
                , boost::ref(d), i, _5), true
 
573
                , bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i)));
 
574
 
 
575
        d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
 
576
                , seconds(10));
 
577
}
 
578
 
 
579
void upnp::delete_port_mapping(rootdevice& d, int i)
 
580
{
 
581
        TORRENT_ASSERT(d.magic == 1337);
 
582
 
 
583
        if (!d.upnp_connection)
 
584
        {
 
585
                TORRENT_ASSERT(d.disabled);
 
586
#ifdef TORRENT_UPNP_LOGGING
 
587
                m_log << time_now_string() << " *** unmapping (" << i
 
588
                        << ") aborted" << std::endl;
 
589
#endif
 
590
                return;
 
591
        }
 
592
 
 
593
        std::stringstream soap;
 
594
        
 
595
        std::string soap_action = "DeletePortMapping";
 
596
 
 
597
        soap << "<?xml version=\"1.0\"?>\n"
 
598
                "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
 
599
                "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
 
600
                "<s:Body><u:" << soap_action << " xmlns:u=\"" << d.service_namespace << "\">";
 
601
 
 
602
        soap << "<NewRemoteHost></NewRemoteHost>"
 
603
                "<NewExternalPort>" << d.mapping[i].external_port << "</NewExternalPort>"
 
604
                "<NewProtocol>" << (d.mapping[i].protocol ? "UDP" : "TCP") << "</NewProtocol>";
 
605
        soap << "</u:" << soap_action << "></s:Body></s:Envelope>";
 
606
        
 
607
        post(d, soap.str(), soap_action);
 
608
}
 
609
 
 
610
// requires the mutex to be locked
 
611
void upnp::unmap_port(rootdevice& d, int i)
 
612
{
 
613
        TORRENT_ASSERT(d.magic == 1337);
 
614
        if (d.mapping[i].external_port == 0
 
615
                || d.disabled)
 
616
        {
 
617
                if (i < num_mappings - 1)
 
618
                {
 
619
                        unmap_port(d, i + 1);
 
620
                }
 
621
                return;
 
622
        }
 
623
#ifdef TORRENT_UPNP_LOGGING
 
624
                m_log << time_now_string()
 
625
                        << " ==> connecting to " << d.hostname << std::endl;
 
626
#endif
 
627
 
 
628
        if (d.upnp_connection) d.upnp_connection->close();
 
629
        d.upnp_connection.reset(new http_connection(m_io_service
 
630
                , m_cc, bind(&upnp::on_upnp_unmap_response, self(), _1, _2
 
631
                , boost::ref(d), i, _5), true
 
632
                , bind(&upnp::delete_port_mapping, self(), boost::ref(d), i)));
 
633
        d.upnp_connection->start(d.hostname, boost::lexical_cast<std::string>(d.port)
 
634
                , seconds(10));
 
635
}
 
636
 
 
637
namespace
 
638
{
 
639
        struct parse_state
 
640
        {
 
641
                parse_state(): found_service(false), exit(false) {}
 
642
                void reset(char const* st)
 
643
                {
 
644
                        found_service = false;
 
645
                        exit = false;
 
646
                        service_type = st;
 
647
                }
 
648
                bool found_service;
 
649
                bool exit;
 
650
                std::string top_tag;
 
651
                std::string control_url;
 
652
                char const* service_type;
 
653
        };
 
654
        
 
655
        void find_control_url(int type, char const* string, parse_state& state)
 
656
        {
 
657
                if (state.exit) return;
 
658
 
 
659
                if (type == xml_start_tag)
 
660
                {
 
661
                        if ((!state.top_tag.empty() && state.top_tag == "service")
 
662
                                || !strcmp(string, "service"))
 
663
                        {
 
664
                                state.top_tag = string;
 
665
                        }
 
666
                }
 
667
                else if (type == xml_end_tag)
 
668
                {
 
669
                        if (!strcmp(string, "service"))
 
670
                        {
 
671
                                state.top_tag.clear();
 
672
                                if (state.found_service) state.exit = true;
 
673
                        }
 
674
                        else if (!state.top_tag.empty() && state.top_tag != "service")
 
675
                                state.top_tag = "service";
 
676
                }
 
677
                else if (type == xml_string)
 
678
                {
 
679
                        if (state.top_tag == "serviceType")
 
680
                        {
 
681
                                if (!strcmp(string, state.service_type))
 
682
                                        state.found_service = true;
 
683
                        }
 
684
                        else if (state.top_tag == "controlURL")
 
685
                        {
 
686
                                state.control_url = string;
 
687
                                if (state.found_service) state.exit = true;
 
688
                        }
 
689
                }
 
690
        }
 
691
 
 
692
}
 
693
 
 
694
void upnp::on_upnp_xml(asio::error_code const& e
 
695
        , libtorrent::http_parser const& p, rootdevice& d
 
696
        , http_connection& c) try
 
697
{
 
698
        TORRENT_ASSERT(d.magic == 1337);
 
699
        if (d.upnp_connection && d.upnp_connection.get() == &c)
 
700
        {
 
701
                d.upnp_connection->close();
 
702
                d.upnp_connection.reset();
 
703
        }
 
704
 
 
705
        if (e && e != asio::error::eof)
 
706
        {
 
707
#ifdef TORRENT_UPNP_LOGGING
 
708
                m_log << time_now_string()
 
709
                        << " <== (" << d.url << ") error while fetching control url: "
 
710
                        << e.message() << std::endl;
 
711
#endif
 
712
                d.disabled = true;
 
713
                return;
 
714
        }
 
715
 
 
716
        if (!p.header_finished())
 
717
        {
 
718
#ifdef TORRENT_UPNP_LOGGING
 
719
                m_log << time_now_string()
 
720
                        << " <== (" << d.url << ") error while fetching control url: incomplete http message" << std::endl;
 
721
#endif
 
722
                d.disabled = true;
 
723
                return;
 
724
        }
 
725
 
 
726
        if (p.status_code() != 200)
 
727
        {
 
728
#ifdef TORRENT_UPNP_LOGGING
 
729
                m_log << time_now_string()
 
730
                        << " <== (" << d.url << ") error while fetching control url: " << p.message() << std::endl;
 
731
#endif
 
732
                d.disabled = true;
 
733
                return;
 
734
        }
 
735
 
 
736
        parse_state s;
 
737
        s.reset("urn:schemas-upnp-org:service:WANIPConnection:1");
 
738
        xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
 
739
                , bind(&find_control_url, _1, _2, boost::ref(s)));
 
740
        if (s.found_service)
 
741
        {
 
742
                d.service_namespace = s.service_type;
 
743
        }
 
744
        else
 
745
        {
 
746
                // we didn't find the WAN IP connection, look for
 
747
                // a PPP connection
 
748
                s.reset("urn:schemas-upnp-org:service:WANPPPConnection:1");
 
749
                xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
 
750
                        , bind(&find_control_url, _1, _2, boost::ref(s)));
 
751
                if (s.found_service)
 
752
                {
 
753
                        d.service_namespace = s.service_type;
 
754
                }
 
755
                else
 
756
                {
 
757
#ifdef TORRENT_UPNP_LOGGING
 
758
                        m_log << time_now_string()
 
759
                                << " <== (" << d.url << ") Rootdevice response, did not find "
 
760
                                "a port mapping interface" << std::endl;
 
761
#endif
 
762
                        d.disabled = true;
 
763
                        return;
 
764
                }
 
765
        }
 
766
        
 
767
#ifdef TORRENT_UPNP_LOGGING
 
768
        m_log << time_now_string()
 
769
                << " <== (" << d.url << ") Rootdevice response, found control URL: " << s.control_url
 
770
                << " namespace: " << d.service_namespace << std::endl;
 
771
#endif
 
772
 
 
773
        d.control_url = s.control_url;
 
774
 
 
775
        map_port(d, 0);
 
776
}
 
777
catch (std::exception&)
 
778
{
 
779
        disable();
 
780
};
 
781
 
 
782
void upnp::disable()
 
783
{
 
784
        m_disabled = true;
 
785
        m_devices.clear();
 
786
        m_broadcast_timer.cancel();
 
787
        m_refresh_timer.cancel();
 
788
        m_socket.close();
 
789
}
 
790
 
 
791
namespace
 
792
{
 
793
        struct error_code_parse_state
 
794
        {
 
795
                error_code_parse_state(): in_error_code(false), exit(false), error_code(-1) {}
 
796
                bool in_error_code;
 
797
                bool exit;
 
798
                int error_code;
 
799
        };
 
800
        
 
801
        void find_error_code(int type, char const* string, error_code_parse_state& state)
 
802
        {
 
803
                if (state.exit) return;
 
804
                if (type == xml_start_tag && !strcmp("errorCode", string))
 
805
                {
 
806
                        state.in_error_code = true;
 
807
                }
 
808
                else if (type == xml_string && state.in_error_code)
 
809
                {
 
810
                        state.error_code = std::atoi(string);
 
811
                        state.exit = true;
 
812
                }
 
813
        }
 
814
}
 
815
 
 
816
namespace
 
817
{
 
818
        struct error_code_t
 
819
        {
 
820
                int code;
 
821
                char const* msg;
 
822
        };
 
823
        
 
824
        error_code_t error_codes[] =
 
825
        {
 
826
                {402, "Invalid Arguments"}
 
827
                , {501, "Action Failed"}
 
828
                , {714, "The specified value does not exist in the array"}
 
829
                , {715, "The source IP address cannot be wild-carded"}
 
830
                , {716, "The external port cannot be wild-carded"}
 
831
                , {718, "The port mapping entry specified conflicts with "
 
832
                        "a mapping assigned previously to another client"}
 
833
                , {724, "Internal and External port values must be the same"}
 
834
                , {725, "The NAT implementation only supports permanent "
 
835
                        "lease times on port mappings"}
 
836
                , {726, "RemoteHost must be a wildcard and cannot be a "
 
837
                        "specific IP address or DNS name"}
 
838
                , {727, "ExternalPort must be a wildcard and cannot be a specific port "}
 
839
        };
 
840
 
 
841
}
 
842
 
 
843
void upnp::on_upnp_map_response(asio::error_code const& e
 
844
        , libtorrent::http_parser const& p, rootdevice& d, int mapping
 
845
        , http_connection& c) try
 
846
{
 
847
        TORRENT_ASSERT(d.magic == 1337);
 
848
        if (d.upnp_connection && d.upnp_connection.get() == &c)
 
849
        {
 
850
                d.upnp_connection->close();
 
851
                d.upnp_connection.reset();
 
852
        }
 
853
 
 
854
        if (e && e != asio::error::eof)
 
855
        {
 
856
#ifdef TORRENT_UPNP_LOGGING
 
857
                m_log << time_now_string()
 
858
                        << " <== error while adding portmap: " << e.message() << std::endl;
 
859
#endif
 
860
                d.disabled = true;
 
861
                return;
 
862
        }
 
863
        
 
864
        if (m_closing) return;
 
865
        
 
866
//       error code response may look like this:
 
867
//      <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
 
868
//              s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 
869
//       <s:Body>
 
870
//        <s:Fault>
 
871
//              <faultcode>s:Client</faultcode>
 
872
//              <faultstring>UPnPError</faultstring>
 
873
//              <detail>
 
874
//               <UPnPErrorxmlns="urn:schemas-upnp-org:control-1-0">
 
875
//                <errorCode>402</errorCode>
 
876
//                <errorDescription>Invalid Args</errorDescription>
 
877
//               </UPnPError>
 
878
//              </detail>
 
879
//        </s:Fault>
 
880
//       </s:Body>
 
881
//      </s:Envelope>
 
882
 
 
883
        if (!p.header_finished())
 
884
        {
 
885
#ifdef TORRENT_UPNP_LOGGING
 
886
                m_log << time_now_string()
 
887
                        << " <== error while adding portmap: incomplete http message" << std::endl;
 
888
#endif
 
889
                d.disabled = true;
 
890
                return;
 
891
        }
 
892
 
 
893
        // We don't want to ignore responses with return codes other than 200
 
894
        // since those might contain valid UPnP error codes
 
895
 
 
896
        error_code_parse_state s;
 
897
        xml_parse((char*)p.get_body().begin, (char*)p.get_body().end
 
898
                , bind(&find_error_code, _1, _2, boost::ref(s)));
 
899
 
 
900
#ifdef TORRENT_UPNP_LOGGING
 
901
        if (s.error_code != -1)
 
902
        {
 
903
                m_log << time_now_string()
 
904
                        << " <== got error message: " << s.error_code << std::endl;
 
905
        }
 
906
#endif
 
907
        
 
908
        if (s.error_code == 725)
 
909
        {
 
910
                // only permanent leases supported
 
911
                d.lease_duration = 0;
 
912
                d.mapping[mapping].need_update = true;
 
913
                map_port(d, mapping);
 
914
                return;
 
915
        }
 
916
        else if (s.error_code == 718)
 
917
        {
 
918
                // conflict in mapping, try next external port
 
919
                ++d.mapping[mapping].external_port;
 
920
                d.mapping[mapping].need_update = true;
 
921
                map_port(d, mapping);
 
922
                return;
 
923
        }
 
924
        else if (s.error_code != -1)
 
925
        {
 
926
                int num_errors = sizeof(error_codes) / sizeof(error_codes[0]);
 
927
                error_code_t* end = error_codes + num_errors;
 
928
                error_code_t tmp = {s.error_code, 0};
 
929
                error_code_t* e = std::lower_bound(error_codes, end, tmp
 
930
                        , bind(&error_code_t::code, _1) < bind(&error_code_t::code, _2));
 
931
                std::string error_string = "UPnP mapping error ";
 
932
                error_string += boost::lexical_cast<std::string>(s.error_code);
 
933
                if (e != end  && e->code == s.error_code)
 
934
                {
 
935
                        error_string += ": ";
 
936
                        error_string += e->msg;
 
937
                }
 
938
                m_callback(0, 0, error_string);
 
939
        }
 
940
 
 
941
#ifdef TORRENT_UPNP_LOGGING
 
942
        m_log << time_now_string()
 
943
                << " <== map response: " << std::string(p.get_body().begin, p.get_body().end)
 
944
                << std::endl;
 
945
#endif
 
946
 
 
947
        if (s.error_code == -1)
 
948
        {
 
949
                int tcp = 0;
 
950
                int udp = 0;
 
951
                
 
952
                if (mapping == 0)
 
953
                        tcp = d.mapping[mapping].external_port;
 
954
                else
 
955
                        udp = d.mapping[mapping].external_port;
 
956
 
 
957
                m_callback(tcp, udp, "");
 
958
                if (d.lease_duration > 0)
 
959
                {
 
960
                        d.mapping[mapping].expires = time_now()
 
961
                                + seconds(int(d.lease_duration * 0.75f));
 
962
                        ptime next_expire = m_refresh_timer.expires_at();
 
963
                        if (next_expire < time_now()
 
964
                                || next_expire > d.mapping[mapping].expires)
 
965
                        {
 
966
                                m_refresh_timer.expires_at(d.mapping[mapping].expires);
 
967
                                m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
 
968
                        }
 
969
                }
 
970
                else
 
971
                {
 
972
                        d.mapping[mapping].expires = max_time();
 
973
                }
 
974
        }
 
975
 
 
976
        for (int i = 0; i < num_mappings; ++i)
 
977
        {
 
978
                if (d.mapping[i].need_update)
 
979
                {
 
980
                        map_port(d, i);
 
981
                        return;
 
982
                }
 
983
        }
 
984
}
 
985
catch (std::exception&)
 
986
{
 
987
        disable();
 
988
};
 
989
 
 
990
void upnp::on_upnp_unmap_response(asio::error_code const& e
 
991
        , libtorrent::http_parser const& p, rootdevice& d, int mapping
 
992
        , http_connection& c) try
 
993
{
 
994
        TORRENT_ASSERT(d.magic == 1337);
 
995
        if (d.upnp_connection && d.upnp_connection.get() == &c)
 
996
        {
 
997
                d.upnp_connection->close();
 
998
                d.upnp_connection.reset();
 
999
        }
 
1000
 
 
1001
        if (e && e != asio::error::eof)
 
1002
        {
 
1003
#ifdef TORRENT_UPNP_LOGGING
 
1004
                m_log << time_now_string()
 
1005
                        << " <== error while deleting portmap: " << e.message() << std::endl;
 
1006
#endif
 
1007
        }
 
1008
 
 
1009
        if (!p.header_finished())
 
1010
        {
 
1011
#ifdef TORRENT_UPNP_LOGGING
 
1012
                m_log << time_now_string()
 
1013
                        << " <== error while deleting portmap: incomplete http message" << std::endl;
 
1014
#endif
 
1015
                return;
 
1016
        }
 
1017
 
 
1018
        if (p.status_code() != 200)
 
1019
        {
 
1020
#ifdef TORRENT_UPNP_LOGGING
 
1021
                m_log << time_now_string()
 
1022
                        << " <== error while deleting portmap: " << p.message() << std::endl;
 
1023
#endif
 
1024
                d.disabled = true;
 
1025
                return;
 
1026
        }
 
1027
 
 
1028
#ifdef TORRENT_UPNP_LOGGING
 
1029
        m_log << time_now_string()
 
1030
                << " <== unmap response: " << std::string(p.get_body().begin, p.get_body().end)
 
1031
                << std::endl;
 
1032
#endif
 
1033
 
 
1034
        // ignore errors and continue with the next mapping for this device
 
1035
        if (mapping < num_mappings - 1)
 
1036
        {
 
1037
                unmap_port(d, mapping + 1);
 
1038
                return;
 
1039
        }
 
1040
}
 
1041
catch (std::exception&)
 
1042
{
 
1043
        disable();
 
1044
};
 
1045
 
 
1046
void upnp::on_expire(asio::error_code const& e) try
 
1047
{
 
1048
        if (e) return;
 
1049
 
 
1050
        ptime now = time_now();
 
1051
        ptime next_expire = max_time();
 
1052
 
 
1053
        for (std::set<rootdevice>::iterator i = m_devices.begin()
 
1054
                , end(m_devices.end()); i != end; ++i)
 
1055
        {
 
1056
                rootdevice& d = const_cast<rootdevice&>(*i);
 
1057
                TORRENT_ASSERT(d.magic == 1337);
 
1058
                for (int m = 0; m < num_mappings; ++m)
 
1059
                {
 
1060
                        if (d.mapping[m].expires != max_time())
 
1061
                                continue;
 
1062
 
 
1063
                        if (d.mapping[m].expires < now)
 
1064
                        {
 
1065
                                d.mapping[m].expires = max_time();
 
1066
                                map_port(d, m);
 
1067
                        }
 
1068
                        else if (d.mapping[m].expires < next_expire)
 
1069
                        {
 
1070
                                next_expire = d.mapping[m].expires;
 
1071
                        }
 
1072
                }
 
1073
        }
 
1074
        if (next_expire != max_time())
 
1075
        {
 
1076
                m_refresh_timer.expires_at(next_expire);
 
1077
                m_refresh_timer.async_wait(bind(&upnp::on_expire, self(), _1));
 
1078
        }
 
1079
}
 
1080
catch (std::exception&)
 
1081
{
 
1082
        disable();
 
1083
};
 
1084
 
 
1085
void upnp::close()
 
1086
{
 
1087
        m_refresh_timer.cancel();
 
1088
        m_broadcast_timer.cancel();
 
1089
        m_closing = true;
 
1090
        m_socket.close();
 
1091
 
 
1092
        if (m_disabled)
 
1093
        {
 
1094
                m_devices.clear();
 
1095
                return;
 
1096
        }
 
1097
 
 
1098
        for (std::set<rootdevice>::iterator i = m_devices.begin()
 
1099
                , end(m_devices.end()); i != end; ++i)
 
1100
        {
 
1101
                rootdevice& d = const_cast<rootdevice&>(*i);
 
1102
                TORRENT_ASSERT(d.magic == 1337);
 
1103
                if (d.control_url.empty()) continue;
 
1104
                unmap_port(d, 0);
 
1105
        }
 
1106
}
 
1107