2006-11-26 18:44:51 +01:00
|
|
|
/*
|
|
|
|
|
2016-01-18 00:57:46 +01:00
|
|
|
Copyright (c) 2006-2016, MassaRoddel, Arvid Norberg
|
2006-11-26 18:44:51 +01:00
|
|
|
All rights reserved.
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions
|
|
|
|
are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in
|
|
|
|
the documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the author nor the names of its
|
|
|
|
contributors may be used to endorse or promote products derived
|
|
|
|
from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2015-04-21 06:30:34 +02:00
|
|
|
#include "libtorrent/config.hpp"
|
2006-11-26 18:44:51 +01:00
|
|
|
#include "libtorrent/bt_peer_connection.hpp"
|
2015-07-25 18:15:24 +02:00
|
|
|
#include "libtorrent/peer_connection_handle.hpp"
|
2006-11-26 18:44:51 +01:00
|
|
|
#include "libtorrent/bencode.hpp"
|
|
|
|
#include "libtorrent/torrent.hpp"
|
|
|
|
#include "libtorrent/extensions.hpp"
|
2008-12-01 09:48:54 +01:00
|
|
|
#include "libtorrent/broadcast_socket.hpp"
|
2009-09-16 05:46:36 +02:00
|
|
|
#include "libtorrent/socket_io.hpp"
|
2009-11-26 06:45:43 +01:00
|
|
|
#include "libtorrent/peer_info.hpp"
|
2014-07-06 21:18:00 +02:00
|
|
|
#include "libtorrent/socket_type.hpp" // for is_utp
|
|
|
|
#include "libtorrent/performance_counters.hpp" // for counters
|
2006-11-26 18:44:51 +01:00
|
|
|
#include "libtorrent/extensions/ut_pex.hpp"
|
2016-08-17 20:30:24 +02:00
|
|
|
#include "libtorrent/aux_/time.hpp"
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2015-04-26 03:25:45 +02:00
|
|
|
#ifndef TORRENT_DISABLE_EXTENSIONS
|
|
|
|
|
2006-11-26 18:44:51 +01:00
|
|
|
namespace libtorrent { namespace
|
|
|
|
{
|
|
|
|
const char extension_name[] = "ut_pex";
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
extension_index = 1,
|
|
|
|
max_peer_entries = 100
|
|
|
|
};
|
|
|
|
|
2007-03-28 22:23:30 +02:00
|
|
|
bool send_peer(peer_connection const& p)
|
|
|
|
{
|
2012-01-04 21:49:54 +01:00
|
|
|
// don't send out those peers that we haven't connected to
|
|
|
|
// (that have connected to us) and that aren't sharing their
|
|
|
|
// listening port
|
2012-02-07 04:46:21 +01:00
|
|
|
if (!p.is_outgoing() && !p.received_listen_port()) return false;
|
2007-03-28 22:23:30 +02:00
|
|
|
// don't send out peers that we haven't successfully connected to
|
|
|
|
if (p.is_connecting()) return false;
|
2013-01-09 08:48:06 +01:00
|
|
|
if (p.in_handshake()) return false;
|
2007-03-28 22:23:30 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-30 17:05:54 +02:00
|
|
|
struct ut_pex_plugin final
|
2015-11-20 05:37:45 +01:00
|
|
|
: torrent_plugin
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2011-03-24 04:34:06 +01:00
|
|
|
// randomize when we rebuild the pex message
|
|
|
|
// to evenly spread it out across all torrents
|
2013-08-04 07:09:20 +02:00
|
|
|
// the more torrents we have, the longer we can
|
|
|
|
// delay the rebuilding
|
2016-07-10 20:27:42 +02:00
|
|
|
explicit ut_pex_plugin(torrent& t)
|
2013-08-04 07:09:20 +02:00
|
|
|
: m_torrent(t)
|
2013-10-18 10:14:49 +02:00
|
|
|
, m_last_msg(min_time())
|
2013-08-04 07:09:20 +02:00
|
|
|
, m_peers_in_message(0) {}
|
2015-11-20 05:37:45 +01:00
|
|
|
|
2016-08-17 20:30:24 +02:00
|
|
|
std::shared_ptr<peer_plugin> new_connection(
|
2016-04-30 17:05:54 +02:00
|
|
|
peer_connection_handle const& pc) override;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
|
|
|
std::vector<char>& get_ut_pex_msg()
|
|
|
|
{
|
|
|
|
return m_ut_pex_msg;
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:27:57 +01:00
|
|
|
int peers_in_msg() const
|
|
|
|
{
|
|
|
|
return m_peers_in_message;
|
|
|
|
}
|
|
|
|
|
2006-11-26 18:44:51 +01:00
|
|
|
// the second tick of the torrent
|
|
|
|
// each minute the new lists of "added" + "added.f" and "dropped"
|
|
|
|
// are calculated here and the pex message is created
|
|
|
|
// each peer connection will use this message
|
|
|
|
// max_peer_entries limits the packet size
|
2016-07-10 02:10:38 +02:00
|
|
|
void tick() override
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2015-03-12 05:34:54 +01:00
|
|
|
time_point now = aux::time_now();
|
2014-07-06 21:18:00 +02:00
|
|
|
if (now - seconds(60) < m_last_msg) return;
|
2013-10-18 10:14:49 +02:00
|
|
|
m_last_msg = now;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2013-10-18 10:14:49 +02:00
|
|
|
int num_peers = m_torrent.num_peers();
|
|
|
|
if (num_peers == 0) return;
|
2007-03-28 03:06:15 +02:00
|
|
|
|
|
|
|
entry pex;
|
|
|
|
std::string& pla = pex["added"].string();
|
|
|
|
std::string& pld = pex["dropped"].string();
|
|
|
|
std::string& plf = pex["added.f"].string();
|
|
|
|
std::back_insert_iterator<std::string> pla_out(pla);
|
|
|
|
std::back_insert_iterator<std::string> pld_out(pld);
|
|
|
|
std::back_insert_iterator<std::string> plf_out(plf);
|
2009-04-04 18:59:53 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
|
|
|
std::string& pla6 = pex["added6"].string();
|
|
|
|
std::string& pld6 = pex["dropped6"].string();
|
|
|
|
std::string& plf6 = pex["added6.f"].string();
|
2007-09-19 23:54:26 +02:00
|
|
|
std::back_insert_iterator<std::string> pla6_out(pla6);
|
|
|
|
std::back_insert_iterator<std::string> pld6_out(pld6);
|
|
|
|
std::back_insert_iterator<std::string> plf6_out(plf6);
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2007-03-28 03:06:15 +02:00
|
|
|
|
|
|
|
std::set<tcp::endpoint> dropped;
|
|
|
|
m_old_peers.swap(dropped);
|
|
|
|
|
2008-11-26 09:27:57 +01:00
|
|
|
m_peers_in_message = 0;
|
2007-03-28 03:06:15 +02:00
|
|
|
int num_added = 0;
|
2006-11-28 19:18:37 +01:00
|
|
|
for (torrent::peer_iterator i = m_torrent.begin()
|
|
|
|
, end(m_torrent.end()); i != end; ++i)
|
2007-03-28 22:23:30 +02:00
|
|
|
{
|
2008-05-12 08:03:31 +02:00
|
|
|
peer_connection* peer = *i;
|
2007-10-31 10:48:20 +01:00
|
|
|
if (!send_peer(*peer)) continue;
|
2007-03-28 03:06:15 +02:00
|
|
|
|
2012-01-04 21:49:54 +01:00
|
|
|
tcp::endpoint remote = peer->remote();
|
2007-10-31 10:48:20 +01:00
|
|
|
m_old_peers.insert(remote);
|
2007-03-28 03:06:15 +02:00
|
|
|
|
2007-10-31 10:48:20 +01:00
|
|
|
std::set<tcp::endpoint>::iterator di = dropped.find(remote);
|
2007-03-28 03:06:15 +02:00
|
|
|
if (di == dropped.end())
|
|
|
|
{
|
|
|
|
// don't write too big of a package
|
2007-06-06 02:41:20 +02:00
|
|
|
if (num_added >= max_peer_entries) break;
|
|
|
|
|
|
|
|
// only send proper bittorrent peers
|
2016-09-24 19:47:17 +02:00
|
|
|
if (peer->type() != connection_type::bittorrent)
|
2009-11-02 21:43:38 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
bt_peer_connection* p = static_cast<bt_peer_connection*>(peer);
|
2007-03-28 03:06:15 +02:00
|
|
|
|
2012-01-04 21:49:54 +01:00
|
|
|
// if the peer has told us which port its listening on,
|
|
|
|
// use that port. But only if we didn't connect to the peer.
|
|
|
|
// if we connected to it, use the port we know works
|
2016-04-17 22:56:07 +02:00
|
|
|
if (!p->is_outgoing())
|
|
|
|
{
|
2016-04-18 06:29:50 +02:00
|
|
|
torrent_peer const* const pi = peer->peer_info_struct();
|
2016-06-20 17:32:06 +02:00
|
|
|
if (pi != nullptr && pi->port > 0)
|
2016-04-17 22:56:07 +02:00
|
|
|
remote.port(pi->port);
|
|
|
|
}
|
2012-01-04 21:49:54 +01:00
|
|
|
|
2007-03-28 03:06:15 +02:00
|
|
|
// no supported flags to set yet
|
|
|
|
// 0x01 - peer supports encryption
|
|
|
|
// 0x02 - peer is a seed
|
2010-11-29 02:33:05 +01:00
|
|
|
// 0x04 - supports uTP. This is only a positive flags
|
|
|
|
// passing 0 doesn't mean the peer doesn't
|
|
|
|
// support uTP
|
|
|
|
// 0x08 - supports holepunching protocol. If this
|
|
|
|
// flag is received from a peer, it can be
|
|
|
|
// used as a rendezvous point in case direct
|
|
|
|
// connections to the peer fail
|
2007-06-06 02:41:20 +02:00
|
|
|
int flags = p->is_seed() ? 2 : 0;
|
2014-11-23 07:14:47 +01:00
|
|
|
#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS)
|
2007-06-06 02:41:20 +02:00
|
|
|
flags |= p->supports_encryption() ? 1 : 0;
|
|
|
|
#endif
|
2012-10-05 05:20:40 +02:00
|
|
|
flags |= is_utp(*p->get_socket()) ? 4 : 0;
|
2010-11-29 02:33:05 +01:00
|
|
|
flags |= p->supports_holepunch() ? 8 : 0;
|
|
|
|
|
2007-09-19 23:54:26 +02:00
|
|
|
// i->first was added since the last time
|
2007-10-31 10:48:20 +01:00
|
|
|
if (remote.address().is_v4())
|
2007-09-19 23:54:26 +02:00
|
|
|
{
|
2007-10-31 10:48:20 +01:00
|
|
|
detail::write_endpoint(remote, pla_out);
|
2007-09-19 23:54:26 +02:00
|
|
|
detail::write_uint8(flags, plf_out);
|
|
|
|
}
|
2009-04-04 18:59:53 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
2007-09-19 23:54:26 +02:00
|
|
|
else
|
|
|
|
{
|
2007-10-31 10:48:20 +01:00
|
|
|
detail::write_endpoint(remote, pla6_out);
|
2007-09-19 23:54:26 +02:00
|
|
|
detail::write_uint8(flags, plf6_out);
|
|
|
|
}
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2007-03-28 03:06:15 +02:00
|
|
|
++num_added;
|
2008-11-26 09:27:57 +01:00
|
|
|
++m_peers_in_message;
|
2007-03-28 03:06:15 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this was in the previous message
|
|
|
|
// so, it wasn't dropped
|
|
|
|
dropped.erase(di);
|
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2007-03-28 03:06:15 +02:00
|
|
|
for (std::set<tcp::endpoint>::const_iterator i = dropped.begin()
|
2007-10-31 10:48:20 +01:00
|
|
|
, end(dropped.end()); i != end; ++i)
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2007-09-19 23:54:26 +02:00
|
|
|
if (i->address().is_v4())
|
|
|
|
detail::write_endpoint(*i, pld_out);
|
2009-04-04 18:59:53 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
2007-09-19 23:54:26 +02:00
|
|
|
else
|
|
|
|
detail::write_endpoint(*i, pld6_out);
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2008-11-26 09:27:57 +01:00
|
|
|
++m_peers_in_message;
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
m_ut_pex_msg.clear();
|
|
|
|
bencode(std::back_inserter(m_ut_pex_msg), pex);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
torrent& m_torrent;
|
|
|
|
|
2007-03-28 03:06:15 +02:00
|
|
|
std::set<tcp::endpoint> m_old_peers;
|
2015-03-12 05:34:54 +01:00
|
|
|
time_point m_last_msg;
|
2006-11-26 18:44:51 +01:00
|
|
|
std::vector<char> m_ut_pex_msg;
|
2008-11-26 09:27:57 +01:00
|
|
|
int m_peers_in_message;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2015-11-20 05:37:45 +01:00
|
|
|
// explicitly disallow assignment, to silence msvc warning
|
|
|
|
ut_pex_plugin& operator=(ut_pex_plugin const&);
|
|
|
|
};
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2016-04-30 17:05:54 +02:00
|
|
|
struct ut_pex_peer_plugin final
|
2016-08-16 03:05:39 +02:00
|
|
|
: ut_pex_peer_store, peer_plugin
|
2015-11-20 05:37:45 +01:00
|
|
|
{
|
2006-11-26 18:44:51 +01:00
|
|
|
ut_pex_peer_plugin(torrent& t, peer_connection& pc, ut_pex_plugin& tp)
|
|
|
|
: m_torrent(t)
|
|
|
|
, m_pc(pc)
|
|
|
|
, m_tp(tp)
|
2013-10-18 10:14:49 +02:00
|
|
|
, m_last_msg(min_time())
|
2006-11-26 18:44:51 +01:00
|
|
|
, m_message_index(0)
|
2007-03-28 22:23:30 +02:00
|
|
|
, m_first_time(true)
|
2011-01-23 06:57:11 +01:00
|
|
|
{
|
2016-08-16 03:05:39 +02:00
|
|
|
const int num_pex_timers = sizeof(m_last_pex) / sizeof(m_last_pex[0]);
|
2011-01-23 06:57:11 +01:00
|
|
|
for (int i = 0; i < num_pex_timers; ++i)
|
|
|
|
{
|
2016-08-16 03:05:39 +02:00
|
|
|
m_last_pex[i] = min_time();
|
2011-01-23 06:57:11 +01:00
|
|
|
}
|
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2016-07-10 02:10:38 +02:00
|
|
|
void add_handshake(entry& h) override
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
|
|
|
entry& messages = h["m"];
|
|
|
|
messages[extension_name] = extension_index;
|
|
|
|
}
|
|
|
|
|
2016-07-10 02:10:38 +02:00
|
|
|
bool on_extension_handshake(bdecode_node const& h) override
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2007-12-30 10:41:26 +01:00
|
|
|
m_message_index = 0;
|
2015-03-12 06:20:12 +01:00
|
|
|
if (h.type() != bdecode_node::dict_t) return false;
|
|
|
|
bdecode_node messages = h.dict_find_dict("m");
|
|
|
|
if (!messages) return false;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2015-03-12 06:20:12 +01:00
|
|
|
int index = int(messages.dict_find_int_value(extension_name, -1));
|
2008-07-01 10:04:12 +02:00
|
|
|
if (index == -1) return false;
|
|
|
|
m_message_index = index;
|
2007-12-30 10:41:26 +01:00
|
|
|
return true;
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2016-07-28 04:31:08 +02:00
|
|
|
bool on_extended(int length, int msg, span<char const> body) override
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
|
|
|
if (msg != extension_index) return false;
|
|
|
|
if (m_message_index == 0) return false;
|
|
|
|
|
|
|
|
if (length > 500 * 1024)
|
2008-04-07 05:31:41 +02:00
|
|
|
{
|
2015-02-15 06:17:09 +01:00
|
|
|
m_pc.disconnect(errors::pex_message_too_large, op_bittorrent, 2);
|
2008-04-07 05:31:41 +02:00
|
|
|
return true;
|
|
|
|
}
|
2015-11-20 05:37:45 +01:00
|
|
|
|
2016-07-28 04:31:08 +02:00
|
|
|
if (int(body.size()) < length) return true;
|
2011-01-23 06:57:11 +01:00
|
|
|
|
2015-03-12 05:34:54 +01:00
|
|
|
time_point now = aux::time_now();
|
2014-07-06 21:18:00 +02:00
|
|
|
if (now - seconds(60) < m_last_pex[0])
|
2010-11-29 02:33:05 +01:00
|
|
|
{
|
|
|
|
// this client appears to be trying to flood us
|
|
|
|
// with pex messages. Don't allow that.
|
2015-02-15 06:17:09 +01:00
|
|
|
m_pc.disconnect(errors::too_frequent_pex, op_bittorrent);
|
2010-11-29 02:33:05 +01:00
|
|
|
return true;
|
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2016-08-16 03:05:39 +02:00
|
|
|
int const num_pex_timers = sizeof(m_last_pex) / sizeof(m_last_pex[0]);
|
|
|
|
for (int i = 0; i < num_pex_timers - 1; ++i)
|
|
|
|
m_last_pex[i] = m_last_pex[i + 1];
|
|
|
|
m_last_pex[num_pex_timers - 1] = now;
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2015-03-12 06:20:12 +01:00
|
|
|
bdecode_node pex_msg;
|
2010-10-28 06:01:59 +02:00
|
|
|
error_code ec;
|
2016-07-28 04:31:08 +02:00
|
|
|
int const ret = bdecode(body.begin(), body.end(), pex_msg, ec);
|
2015-03-12 06:20:12 +01:00
|
|
|
if (ret != 0 || pex_msg.type() != bdecode_node::dict_t)
|
2008-06-24 00:00:27 +02:00
|
|
|
{
|
2015-02-15 06:17:09 +01:00
|
|
|
m_pc.disconnect(errors::invalid_pex_message, op_bittorrent, 2);
|
2008-06-24 00:00:27 +02:00
|
|
|
return true;
|
|
|
|
}
|
2007-12-30 10:41:26 +01:00
|
|
|
|
2015-03-12 06:20:12 +01:00
|
|
|
bdecode_node p = pex_msg.dict_find_string("dropped");
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2011-03-24 04:34:06 +01:00
|
|
|
int num_dropped = 0;
|
|
|
|
int num_added = 0;
|
2016-08-16 03:05:39 +02:00
|
|
|
if (p) num_dropped += p.string_length() / 6;
|
2010-11-29 02:33:05 +01:00
|
|
|
#endif
|
|
|
|
if (p)
|
|
|
|
{
|
2016-04-27 00:09:11 +02:00
|
|
|
int const num_peers = p.string_length() / 6;
|
2015-03-12 06:20:12 +01:00
|
|
|
char const* in = p.string_ptr();
|
2010-11-29 02:33:05 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < num_peers; ++i)
|
|
|
|
{
|
|
|
|
tcp::endpoint adr = detail::read_v4_endpoint<tcp::endpoint>(in);
|
|
|
|
peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port());
|
|
|
|
peers4_t::iterator j = std::lower_bound(m_peers.begin(), m_peers.end(), v);
|
|
|
|
if (j != m_peers.end() && *j == v) m_peers.erase(j);
|
2015-08-18 13:55:50 +02:00
|
|
|
}
|
2010-11-29 02:33:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
p = pex_msg.dict_find_string("added");
|
2015-03-12 06:20:12 +01:00
|
|
|
bdecode_node pf = pex_msg.dict_find_string("added.f");
|
2007-12-30 10:41:26 +01:00
|
|
|
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2015-03-12 06:20:12 +01:00
|
|
|
if (p) num_added += p.string_length() / 6;
|
2010-11-29 02:33:05 +01:00
|
|
|
#endif
|
2015-03-12 06:20:12 +01:00
|
|
|
if (p && pf && pf.string_length() == p.string_length() / 6)
|
2007-08-08 23:22:59 +02:00
|
|
|
{
|
2016-04-27 00:09:11 +02:00
|
|
|
int const num_peers = pf.string_length();
|
2015-03-12 06:20:12 +01:00
|
|
|
char const* in = p.string_ptr();
|
|
|
|
char const* fin = pf.string_ptr();
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2007-08-08 23:22:59 +02:00
|
|
|
for (int i = 0; i < num_peers; ++i)
|
|
|
|
{
|
|
|
|
tcp::endpoint adr = detail::read_v4_endpoint<tcp::endpoint>(in);
|
2016-04-27 00:09:11 +02:00
|
|
|
char const flags = *fin++;
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2014-07-06 21:18:00 +02:00
|
|
|
if (int(m_peers.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers))
|
|
|
|
break;
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2008-12-01 09:48:54 +01:00
|
|
|
// ignore local addresses unless the peer is local to us
|
|
|
|
if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue;
|
2010-11-29 02:33:05 +01:00
|
|
|
|
|
|
|
peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port());
|
|
|
|
peers4_t::iterator j = std::lower_bound(m_peers.begin(), m_peers.end(), v);
|
|
|
|
// do we already know about this peer?
|
|
|
|
if (j != m_peers.end() && *j == v) continue;
|
|
|
|
m_peers.insert(j, v);
|
2014-07-06 21:18:00 +02:00
|
|
|
m_torrent.add_peer(adr, peer_info::pex, flags);
|
2015-08-18 13:55:50 +02:00
|
|
|
}
|
2007-08-08 23:22:59 +02:00
|
|
|
}
|
2007-12-30 10:41:26 +01:00
|
|
|
|
2009-04-04 18:59:53 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2015-03-12 06:20:12 +01:00
|
|
|
bdecode_node p6 = pex_msg.dict_find("dropped6");
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2015-03-12 06:20:12 +01:00
|
|
|
if (p6) num_dropped += p6.string_length() / 18;
|
2011-03-24 04:34:06 +01:00
|
|
|
#endif
|
2015-03-12 06:20:12 +01:00
|
|
|
if (p6 != 0 && p6.type() == bdecode_node::string_t)
|
2010-11-29 02:33:05 +01:00
|
|
|
{
|
2016-04-27 00:09:11 +02:00
|
|
|
int const num_peers = p6.string_length() / 18;
|
2015-03-12 06:20:12 +01:00
|
|
|
char const* in = p6.string_ptr();
|
2010-11-29 02:33:05 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < num_peers; ++i)
|
|
|
|
{
|
|
|
|
tcp::endpoint adr = detail::read_v6_endpoint<tcp::endpoint>(in);
|
|
|
|
peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port());
|
|
|
|
peers6_t::iterator j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v);
|
|
|
|
if (j != m_peers6.end() && *j == v) m_peers6.erase(j);
|
2015-11-20 05:37:45 +01:00
|
|
|
}
|
2010-11-29 02:33:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
p6 = pex_msg.dict_find("added6");
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2015-03-12 06:20:12 +01:00
|
|
|
if (p6) num_added += p6.string_length() / 18;
|
2011-03-24 04:34:06 +01:00
|
|
|
#endif
|
2015-03-12 06:20:12 +01:00
|
|
|
bdecode_node p6f = pex_msg.dict_find("added6.f");
|
2008-07-01 11:02:03 +02:00
|
|
|
if (p6 != 0
|
|
|
|
&& p6f != 0
|
2015-03-12 06:20:12 +01:00
|
|
|
&& p6.type() == bdecode_node::string_t
|
|
|
|
&& p6f.type() == bdecode_node::string_t
|
|
|
|
&& p6f.string_length() == p6.string_length() / 18)
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2016-04-27 00:09:11 +02:00
|
|
|
int const num_peers = p6f.string_length();
|
2015-03-12 06:20:12 +01:00
|
|
|
char const* in = p6.string_ptr();
|
|
|
|
char const* fin = p6f.string_ptr();
|
2007-12-30 10:41:26 +01:00
|
|
|
|
|
|
|
for (int i = 0; i < num_peers; ++i)
|
|
|
|
{
|
|
|
|
tcp::endpoint adr = detail::read_v6_endpoint<tcp::endpoint>(in);
|
2016-04-27 00:09:11 +02:00
|
|
|
char const flags = *fin++;
|
2008-12-01 09:48:54 +01:00
|
|
|
// ignore local addresses unless the peer is local to us
|
|
|
|
if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue;
|
2014-07-06 21:18:00 +02:00
|
|
|
if (int(m_peers6.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers))
|
|
|
|
break;
|
2010-11-29 02:33:05 +01:00
|
|
|
|
|
|
|
peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port());
|
|
|
|
peers6_t::iterator j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v);
|
|
|
|
// do we already know about this peer?
|
|
|
|
if (j != m_peers6.end() && *j == v) continue;
|
|
|
|
m_peers6.insert(j, v);
|
2014-07-06 21:18:00 +02:00
|
|
|
m_torrent.add_peer(adr, peer_info::pex, flags);
|
2016-04-27 00:09:11 +02:00
|
|
|
}
|
2007-08-08 23:22:59 +02:00
|
|
|
}
|
2011-03-24 04:34:06 +01:00
|
|
|
#endif
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2015-05-03 04:53:54 +02:00
|
|
|
m_pc.peer_log(peer_log_alert::incoming_message, "PEX", "dropped: %d added: %d"
|
2011-03-24 04:34:06 +01:00
|
|
|
, num_dropped, num_added);
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2014-07-06 21:18:00 +02:00
|
|
|
|
2014-07-13 06:56:53 +02:00
|
|
|
m_pc.stats_counters().inc_stats_counter(counters::num_incoming_pex);
|
2007-03-28 03:06:15 +02:00
|
|
|
return true;
|
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
|
|
|
|
// the peers second tick
|
|
|
|
// every minute we send a pex message
|
2016-07-10 02:10:38 +02:00
|
|
|
void tick() override
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2013-08-04 07:09:20 +02:00
|
|
|
// no handshake yet
|
|
|
|
if (!m_message_index) return;
|
|
|
|
|
2016-04-27 00:09:11 +02:00
|
|
|
time_point const now = aux::time_now();
|
2014-07-06 21:18:00 +02:00
|
|
|
if (now - seconds(60) < m_last_msg)
|
2013-10-18 10:14:49 +02:00
|
|
|
{
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2016-01-10 17:50:56 +01:00
|
|
|
// m_pc.peer_log(peer_log_alert::info, "PEX", "waiting: %d seconds to next msg"
|
|
|
|
// , int(total_seconds(seconds(60) - (now - m_last_msg))));
|
2013-10-18 10:14:49 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
2015-03-12 05:34:54 +01:00
|
|
|
static time_point global_last = min_time();
|
2013-10-18 10:14:49 +02:00
|
|
|
|
2016-04-27 00:09:11 +02:00
|
|
|
int const num_peers = m_torrent.num_peers();
|
2014-02-02 11:05:32 +01:00
|
|
|
if (num_peers <= 1) return;
|
2013-10-18 10:14:49 +02:00
|
|
|
|
|
|
|
// don't send pex messages more often than 1 every 100 ms, and
|
|
|
|
// allow pex messages to be sent 5 seconds apart if there isn't
|
|
|
|
// contention
|
|
|
|
int delay = (std::min)((std::max)(60000 / num_peers, 100), 3000);
|
|
|
|
|
2014-07-06 21:18:00 +02:00
|
|
|
if (now - milliseconds(delay) < global_last)
|
2013-10-18 10:14:49 +02:00
|
|
|
{
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2016-01-10 17:50:56 +01:00
|
|
|
// m_pc.peer_log(peer_log_alert::info, "PEX", "global-wait: %d"
|
|
|
|
// , int(total_seconds(milliseconds(delay) - (now - global_last))));
|
2013-10-18 10:14:49 +02:00
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this will allow us to catch up, even if our timer
|
|
|
|
// has lower resolution than delay
|
|
|
|
if (global_last == min_time())
|
|
|
|
global_last = now;
|
|
|
|
else
|
|
|
|
global_last += milliseconds(delay);
|
2013-08-04 07:09:20 +02:00
|
|
|
|
2013-10-18 10:14:49 +02:00
|
|
|
m_last_msg = now;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2007-03-28 22:23:30 +02:00
|
|
|
if (m_first_time)
|
|
|
|
{
|
|
|
|
send_ut_peer_list();
|
|
|
|
m_first_time = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
send_ut_peer_diff();
|
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2007-03-28 22:23:30 +02:00
|
|
|
void send_ut_peer_diff()
|
|
|
|
{
|
2008-11-26 09:27:57 +01:00
|
|
|
// if there's no change in out peer set, don't send anything
|
|
|
|
if (m_tp.peers_in_msg() == 0) return;
|
|
|
|
|
2007-03-28 22:23:30 +02:00
|
|
|
std::vector<char> const& pex_msg = m_tp.get_ut_pex_msg();
|
|
|
|
|
2011-05-19 04:41:28 +02:00
|
|
|
char msg[6];
|
|
|
|
char* ptr = msg;
|
2007-03-28 22:23:30 +02:00
|
|
|
|
2016-04-25 23:22:09 +02:00
|
|
|
detail::write_uint32(1 + 1 + int(pex_msg.size()), ptr);
|
2011-05-19 04:41:28 +02:00
|
|
|
detail::write_uint8(bt_peer_connection::msg_extended, ptr);
|
|
|
|
detail::write_uint8(m_message_index, ptr);
|
|
|
|
m_pc.send_buffer(msg, sizeof(msg));
|
2016-04-25 23:22:09 +02:00
|
|
|
m_pc.send_buffer(&pex_msg[0], int(pex_msg.size()));
|
2011-03-24 04:34:06 +01:00
|
|
|
|
2014-07-13 06:56:53 +02:00
|
|
|
m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended);
|
|
|
|
m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex);
|
2014-07-06 21:18:00 +02:00
|
|
|
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2016-09-14 04:46:07 +02:00
|
|
|
if (m_pc.should_log(peer_log_alert::outgoing_message))
|
|
|
|
{
|
|
|
|
bdecode_node m;
|
|
|
|
error_code ec;
|
|
|
|
int ret = bdecode(&pex_msg[0], &pex_msg[0] + pex_msg.size(), m, ec);
|
|
|
|
TORRENT_ASSERT(ret == 0);
|
|
|
|
TORRENT_ASSERT(!ec);
|
|
|
|
TORRENT_UNUSED(ret);
|
|
|
|
int num_dropped = 0;
|
|
|
|
int num_added = 0;
|
|
|
|
bdecode_node e = m.dict_find_string("added");
|
|
|
|
if (e) num_added += e.string_length() / 6;
|
|
|
|
e = m.dict_find_string("dropped");
|
|
|
|
if (e) num_dropped += e.string_length() / 6;
|
|
|
|
e = m.dict_find_string("added6");
|
|
|
|
if (e) num_added += e.string_length() / 18;
|
|
|
|
e = m.dict_find_string("dropped6");
|
|
|
|
if (e) num_dropped += e.string_length() / 18;
|
|
|
|
m_pc.peer_log(peer_log_alert::outgoing_message, "PEX_DIFF", "dropped: %d added: %d msg_size: %d"
|
|
|
|
, num_dropped, num_added, int(pex_msg.size()));
|
|
|
|
}
|
2011-03-24 04:34:06 +01:00
|
|
|
#endif
|
2007-03-28 22:23:30 +02:00
|
|
|
}
|
|
|
|
|
2006-11-26 18:44:51 +01:00
|
|
|
void send_ut_peer_list()
|
|
|
|
{
|
2007-03-28 22:23:30 +02:00
|
|
|
entry pex;
|
|
|
|
// leave the dropped string empty
|
|
|
|
pex["dropped"].string();
|
|
|
|
std::string& pla = pex["added"].string();
|
|
|
|
std::string& plf = pex["added.f"].string();
|
2009-04-04 18:59:53 +02:00
|
|
|
std::back_insert_iterator<std::string> pla_out(pla);
|
|
|
|
std::back_insert_iterator<std::string> plf_out(plf);
|
|
|
|
|
|
|
|
#if TORRENT_USE_IPV6
|
2007-09-19 23:54:26 +02:00
|
|
|
pex["dropped6"].string();
|
|
|
|
std::string& pla6 = pex["added6"].string();
|
|
|
|
std::string& plf6 = pex["added6.f"].string();
|
|
|
|
std::back_insert_iterator<std::string> pla6_out(pla6);
|
|
|
|
std::back_insert_iterator<std::string> plf6_out(plf6);
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2007-03-28 22:23:30 +02:00
|
|
|
|
|
|
|
int num_added = 0;
|
|
|
|
for (torrent::peer_iterator i = m_torrent.begin()
|
|
|
|
, end(m_torrent.end()); i != end; ++i)
|
|
|
|
{
|
2007-10-31 10:48:20 +01:00
|
|
|
peer_connection* peer = *i;
|
|
|
|
if (!send_peer(*peer)) continue;
|
2007-03-28 22:23:30 +02:00
|
|
|
|
|
|
|
// don't write too big of a package
|
2007-06-06 02:41:20 +02:00
|
|
|
if (num_added >= max_peer_entries) break;
|
|
|
|
|
|
|
|
// only send proper bittorrent peers
|
2016-09-24 19:47:17 +02:00
|
|
|
if (peer->type() != connection_type::bittorrent)
|
2009-11-02 21:43:38 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
bt_peer_connection* p = static_cast<bt_peer_connection*>(peer);
|
2007-03-28 22:23:30 +02:00
|
|
|
|
|
|
|
// no supported flags to set yet
|
|
|
|
// 0x01 - peer supports encryption
|
|
|
|
// 0x02 - peer is a seed
|
2012-01-04 21:49:54 +01:00
|
|
|
// 0x04 - supports uTP. This is only a positive flags
|
|
|
|
// passing 0 doesn't mean the peer doesn't
|
|
|
|
// support uTP
|
|
|
|
// 0x08 - supports holepunching protocol. If this
|
|
|
|
// flag is received from a peer, it can be
|
|
|
|
// used as a rendezvous point in case direct
|
|
|
|
// connections to the peer fail
|
2007-06-06 02:41:20 +02:00
|
|
|
int flags = p->is_seed() ? 2 : 0;
|
2014-11-23 07:14:47 +01:00
|
|
|
#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS)
|
2007-06-06 02:41:20 +02:00
|
|
|
flags |= p->supports_encryption() ? 1 : 0;
|
|
|
|
#endif
|
2012-10-05 05:20:40 +02:00
|
|
|
flags |= is_utp(*p->get_socket()) ? 4 : 0;
|
2012-01-04 21:49:54 +01:00
|
|
|
flags |= p->supports_holepunch() ? 8 : 0;
|
|
|
|
|
|
|
|
tcp::endpoint remote = peer->remote();
|
|
|
|
|
2016-04-17 22:56:07 +02:00
|
|
|
if (!p->is_outgoing())
|
|
|
|
{
|
2016-04-18 06:29:50 +02:00
|
|
|
torrent_peer const* const pi = peer->peer_info_struct();
|
2016-06-20 17:32:06 +02:00
|
|
|
if (pi != nullptr && pi->port > 0)
|
2016-04-17 22:56:07 +02:00
|
|
|
remote.port(pi->port);
|
|
|
|
}
|
2012-01-04 21:49:54 +01:00
|
|
|
|
2007-09-19 23:54:26 +02:00
|
|
|
// i->first was added since the last time
|
2007-10-31 10:48:20 +01:00
|
|
|
if (remote.address().is_v4())
|
2007-09-19 23:54:26 +02:00
|
|
|
{
|
2007-10-31 10:48:20 +01:00
|
|
|
detail::write_endpoint(remote, pla_out);
|
2007-09-19 23:54:26 +02:00
|
|
|
detail::write_uint8(flags, plf_out);
|
|
|
|
}
|
2009-04-04 18:59:53 +02:00
|
|
|
#if TORRENT_USE_IPV6
|
2007-09-19 23:54:26 +02:00
|
|
|
else
|
|
|
|
{
|
2007-10-31 10:48:20 +01:00
|
|
|
detail::write_endpoint(remote, pla6_out);
|
2007-09-19 23:54:26 +02:00
|
|
|
detail::write_uint8(flags, plf6_out);
|
|
|
|
}
|
2009-04-04 18:59:53 +02:00
|
|
|
#endif
|
2007-03-28 22:23:30 +02:00
|
|
|
++num_added;
|
|
|
|
}
|
|
|
|
std::vector<char> pex_msg;
|
|
|
|
bencode(std::back_inserter(pex_msg), pex);
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2011-05-19 04:41:28 +02:00
|
|
|
char msg[6];
|
|
|
|
char* ptr = msg;
|
2006-11-26 18:44:51 +01:00
|
|
|
|
2016-04-25 23:22:09 +02:00
|
|
|
detail::write_uint32(1 + 1 + int(pex_msg.size()), ptr);
|
2011-05-19 04:41:28 +02:00
|
|
|
detail::write_uint8(bt_peer_connection::msg_extended, ptr);
|
|
|
|
detail::write_uint8(m_message_index, ptr);
|
|
|
|
m_pc.send_buffer(msg, sizeof(msg));
|
2016-04-25 23:22:09 +02:00
|
|
|
m_pc.send_buffer(&pex_msg[0], int(pex_msg.size()));
|
2011-03-24 04:34:06 +01:00
|
|
|
|
2014-07-13 06:56:53 +02:00
|
|
|
m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended);
|
|
|
|
m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex);
|
2014-07-06 21:18:00 +02:00
|
|
|
|
2015-04-17 03:15:33 +02:00
|
|
|
#ifndef TORRENT_DISABLE_LOGGING
|
2015-05-03 04:53:54 +02:00
|
|
|
m_pc.peer_log(peer_log_alert::outgoing_message, "PEX_FULL"
|
|
|
|
, "added: %d msg_size: %d", num_added, int(pex_msg.size()));
|
2011-03-24 04:34:06 +01:00
|
|
|
#endif
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
torrent& m_torrent;
|
|
|
|
peer_connection& m_pc;
|
|
|
|
ut_pex_plugin& m_tp;
|
2016-08-16 03:05:39 +02:00
|
|
|
|
2011-01-23 06:57:11 +01:00
|
|
|
// the last pex messages we received
|
|
|
|
// [0] is the oldest one. There is a problem with
|
|
|
|
// rate limited connections, because we may sit
|
|
|
|
// for a long time, accumulating pex messages, and
|
|
|
|
// then once we read from the socket it will look like
|
|
|
|
// we received them all back to back. That's why
|
|
|
|
// we look at 6 pex messages back.
|
2015-03-12 05:34:54 +01:00
|
|
|
time_point m_last_pex[6];
|
2010-11-29 02:33:05 +01:00
|
|
|
|
2015-03-12 05:34:54 +01:00
|
|
|
time_point m_last_msg;
|
2006-11-26 18:44:51 +01:00
|
|
|
int m_message_index;
|
2007-03-28 22:23:30 +02:00
|
|
|
|
|
|
|
// this is initialized to true, and set to
|
|
|
|
// false after the first pex message has been sent.
|
2013-10-18 10:14:49 +02:00
|
|
|
// it is used to know if a diff message or a) ful
|
2007-03-28 22:23:30 +02:00
|
|
|
// message should be sent.
|
|
|
|
bool m_first_time;
|
2015-11-20 05:37:45 +01:00
|
|
|
|
|
|
|
// explicitly disallow assignment, to silence msvc warning
|
|
|
|
ut_pex_peer_plugin& operator=(ut_pex_peer_plugin const&);
|
2006-11-26 18:44:51 +01:00
|
|
|
};
|
|
|
|
|
2016-08-17 20:30:24 +02:00
|
|
|
std::shared_ptr<peer_plugin> ut_pex_plugin::new_connection(peer_connection_handle const& pc)
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2016-09-24 19:47:17 +02:00
|
|
|
if (pc.type() != connection_type::bittorrent)
|
2016-08-17 20:30:24 +02:00
|
|
|
return std::shared_ptr<peer_plugin>();
|
2009-11-02 21:43:38 +01:00
|
|
|
|
2016-08-16 03:05:39 +02:00
|
|
|
bt_peer_connection* c = static_cast<bt_peer_connection*>(pc.native_handle().get());
|
2016-08-17 20:30:24 +02:00
|
|
|
auto p = std::make_shared<ut_pex_peer_plugin>(m_torrent, *c, *this);
|
2016-08-16 03:05:39 +02:00
|
|
|
c->set_ut_pex(p);
|
|
|
|
return p;
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
2010-11-29 02:33:05 +01:00
|
|
|
} }
|
2006-11-26 18:44:51 +01:00
|
|
|
|
|
|
|
namespace libtorrent
|
|
|
|
{
|
2016-08-17 20:30:24 +02:00
|
|
|
std::shared_ptr<torrent_plugin> create_ut_pex_plugin(torrent_handle const& th, void*)
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2015-07-22 04:11:41 +02:00
|
|
|
torrent* t = th.native_handle().get();
|
2009-08-20 05:19:12 +02:00
|
|
|
if (t->torrent_file().priv() || (t->torrent_file().is_i2p()
|
2014-07-06 21:18:00 +02:00
|
|
|
&& !t->settings().get_bool(settings_pack::allow_i2p_mixed)))
|
2006-11-26 18:44:51 +01:00
|
|
|
{
|
2016-08-17 20:30:24 +02:00
|
|
|
return std::shared_ptr<torrent_plugin>();
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
2016-08-17 20:30:24 +02:00
|
|
|
return std::make_shared<ut_pex_plugin>(*t);
|
2010-11-29 02:33:05 +01:00
|
|
|
}
|
2006-11-26 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2011-01-29 13:13:49 +01:00
|
|
|
#endif
|