Implementing and using new dht storage interface
This commit is contained in:
parent
4b27ad80b8
commit
e2d682275a
|
@ -15,3 +15,5 @@ Makefile
|
|||
build-aux
|
||||
test_tmp_*
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
|
|
@ -117,6 +117,7 @@ set(sources
|
|||
|
||||
# -- kademlia --
|
||||
set(kademlia_sources
|
||||
dht_storage
|
||||
dos_blocker
|
||||
dht_tracker
|
||||
node
|
||||
|
|
1
Jamfile
1
Jamfile
|
@ -701,6 +701,7 @@ SOURCES =
|
|||
;
|
||||
|
||||
KADEMLIA_SOURCES =
|
||||
dht_storage
|
||||
dht_tracker
|
||||
node
|
||||
node_entry
|
||||
|
|
|
@ -179,6 +179,7 @@ nobase_include_HEADERS = \
|
|||
extensions/ut_metadata.hpp \
|
||||
extensions/ut_pex.hpp \
|
||||
\
|
||||
kademlia/dht_storage.hpp \
|
||||
kademlia/dht_tracker.hpp \
|
||||
kademlia/dht_observer.hpp \
|
||||
kademlia/direct_request.hpp \
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
|
||||
Copyright (c) 2012-2015, Arvid Norberg, Alden Torres
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef DHT_STORAGE_HPP
|
||||
#define DHT_STORAGE_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <libtorrent/config.hpp>
|
||||
#include <libtorrent/time.hpp>
|
||||
#include <libtorrent/socket.hpp>
|
||||
#include <libtorrent/sha1_hash.hpp>
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/session_settings.hpp>
|
||||
#include <libtorrent/performance_counters.hpp>
|
||||
#include <libtorrent/kademlia/item.hpp>
|
||||
|
||||
namespace libtorrent {
|
||||
namespace dht
|
||||
{
|
||||
// The DHT storage interface is a pure virtual class that can
|
||||
// be implemented to customize how the data for the DHT is stored.
|
||||
//
|
||||
// The default storage implementation uses three maps in RAM to save
|
||||
// the peers, mutable and immutable items and it's designed to
|
||||
// provide a fast and fully compliant behavior of the BEPs.
|
||||
//
|
||||
// libtorrent comes with one built-in storage implementation:
|
||||
// ``dht_default_storage`` (private non-accessible class). Its
|
||||
// constructor function is called dht_default_storage_constructor().
|
||||
//
|
||||
struct TORRENT_EXPORT dht_storage_interface
|
||||
{
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
// This function returns the number of torrents tracked by
|
||||
// the DHT at the moment. It's used to fill session_status.
|
||||
// It's deprecated.
|
||||
//
|
||||
virtual size_t num_torrents() const = 0;
|
||||
|
||||
// This function returns the sum of all of peers per torrent
|
||||
// tracker byt the DHT at the moment.
|
||||
// It's deprecated.
|
||||
//
|
||||
virtual size_t num_peers() const = 0;
|
||||
#endif
|
||||
|
||||
// This function retrieve the peers tracked by the DHT
|
||||
// corresponding to the given info_hash. You can specify if
|
||||
// you want only seeds and/or you are scraping the data.
|
||||
//
|
||||
// For future implementers:
|
||||
// If the torrent tracked contains a name, such a name
|
||||
// must be stored as a string in peers["n"]
|
||||
//
|
||||
// If the scrape parameter is true, you should fill these keys:
|
||||
// peers["BFpe"] - with the standard bit representation of a
|
||||
// 256 bloom filter containing the downloaders
|
||||
// peers["BFsd"] - with the standard bit representation of a
|
||||
// 256 bloom filter containing the seeders
|
||||
//
|
||||
// If the scrape parameter is false, you should fill the
|
||||
// key peers["values"] with a list containing a subset of
|
||||
// peers tracked by the given info_hash. Such a list should
|
||||
// consider the value of dht_settings::max_peers_reply.
|
||||
// If noseed is true only peers marked as no seed should be included.
|
||||
//
|
||||
// returns true if an entry with the info_hash is found and
|
||||
// the data is returned inside the (entry) out parameter peers.
|
||||
//
|
||||
virtual bool get_peers(sha1_hash const& info_hash
|
||||
, bool noseed, bool scrape
|
||||
, entry& peers) const = 0;
|
||||
// This function is named announce_peer for consistency with the
|
||||
// upper layers, but has nothing to do with networking. Its only
|
||||
// responsibility is store the peer in such a way that it's returned
|
||||
// in the entry with the lookup_peers.
|
||||
//
|
||||
// The ``name`` parameter is the name of the torrent if provided in
|
||||
// the announce_peer DHT message. The length of this value should
|
||||
// have a maximum length in the final storage. The default
|
||||
// implementation truncate the value for a maximum of 50 characters.
|
||||
//
|
||||
virtual void announce_peer(sha1_hash const& info_hash
|
||||
, tcp::endpoint const& endp
|
||||
, std::string const& name, bool seed) = 0;
|
||||
|
||||
// This function retrieves the immutable item given its target hash.
|
||||
//
|
||||
// For future implementers:
|
||||
// The value should be returned as an entry in the key item["v"].
|
||||
//
|
||||
// returns true if the item is found and the data is returned
|
||||
// inside the (entry) out parameter item.
|
||||
//
|
||||
virtual bool get_immutable_item(sha1_hash const& target
|
||||
, entry& item) const = 0;
|
||||
// Store the item's data. This layer is only for storage.
|
||||
// The authentication of the item is performed by the upper layer.
|
||||
//
|
||||
// For implementers:
|
||||
// This data can be stored only if the target is not already
|
||||
// present. The implementation should consider the value of
|
||||
// dht_settings::max_dht_items.
|
||||
//
|
||||
virtual void put_immutable_item(sha1_hash const& target
|
||||
, char const* buf, int size
|
||||
, address const& addr) = 0;
|
||||
|
||||
// This function retrieves the sequence number of a mutable item.
|
||||
//
|
||||
// returns true if the item is found and the data is returned
|
||||
// inside the out parameter seq.
|
||||
//
|
||||
virtual bool get_mutable_item_seq(sha1_hash const& target
|
||||
, boost::int64_t& seq) const = 0;
|
||||
// This function retrieves the mutable stored in the DHT.
|
||||
//
|
||||
// For implementers:
|
||||
// The item sequence should be stored in the key item["seq"].
|
||||
// if force_fill is true or (0 <= seq and seq < item["seq"])
|
||||
// the following keys should be filled
|
||||
// item["v"] - with the value no encoded.
|
||||
// item["sig"] - with a string representation of the signature.
|
||||
// item["k"] - with a string represnetation of the public key.
|
||||
//
|
||||
// returns true if the item is found and the data is returned
|
||||
// inside the (entry) out parameter item.
|
||||
//
|
||||
virtual bool get_mutable_item(sha1_hash const& target
|
||||
, boost::int64_t seq, bool force_fill
|
||||
, entry& item) const = 0;
|
||||
// Store the item's data. This layer is only for storage.
|
||||
// The authentication of the item is performed by the upper layer.
|
||||
//
|
||||
// For implementers:
|
||||
// The sequence number should be checked if the item is already
|
||||
// present. The implementation should consider the value of
|
||||
// dht_settings::max_dht_items.
|
||||
//
|
||||
virtual void put_mutable_item(sha1_hash const& target
|
||||
, char const* buf, int size
|
||||
, char const* sig
|
||||
, boost::int64_t seq
|
||||
, char const* pk
|
||||
, char const* salt, int salt_size
|
||||
, address const& addr) = 0;
|
||||
|
||||
// This function is called periodically (non-constant frequency).
|
||||
//
|
||||
// For implementers:
|
||||
// Use this functions for expire peers or items or any other
|
||||
// storage cleanup.
|
||||
//
|
||||
virtual void tick() = 0;
|
||||
|
||||
virtual ~dht_storage_interface() {}
|
||||
};
|
||||
|
||||
TORRENT_EXPORT dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id
|
||||
, dht_settings const& settings
|
||||
, counters& counters);
|
||||
|
||||
} } // namespace libtorrent::dht
|
||||
|
||||
#endif //DHT_STORAGE_HPP
|
|
@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include <set>
|
||||
|
||||
#include <libtorrent/config.hpp>
|
||||
#include <libtorrent/kademlia/dht_storage.hpp>
|
||||
#include <libtorrent/kademlia/routing_table.hpp>
|
||||
#include <libtorrent/kademlia/rpc_manager.hpp>
|
||||
#include <libtorrent/kademlia/node_id.hpp>
|
||||
|
@ -102,65 +103,6 @@ void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes);
|
|||
|
||||
void incoming_error(entry& e, char const* msg, int error_code = 203);
|
||||
|
||||
// this is the entry for every peer
|
||||
// the timestamp is there to make it possible
|
||||
// to remove stale peers
|
||||
struct peer_entry
|
||||
{
|
||||
time_point added;
|
||||
tcp::endpoint addr;
|
||||
bool seed;
|
||||
};
|
||||
|
||||
// this is a group. It contains a set of group members
|
||||
struct torrent_entry
|
||||
{
|
||||
std::string name;
|
||||
std::set<peer_entry> peers;
|
||||
};
|
||||
|
||||
struct dht_immutable_item
|
||||
{
|
||||
dht_immutable_item() : value(0), num_announcers(0), size(0) {}
|
||||
// malloced space for the actual value
|
||||
char* value;
|
||||
// this counts the number of IPs we have seen
|
||||
// announcing this item, this is used to determine
|
||||
// popularity if we reach the limit of items to store
|
||||
bloom_filter<128> ips;
|
||||
// the last time we heard about this
|
||||
time_point last_seen;
|
||||
// number of IPs in the bloom filter
|
||||
int num_announcers;
|
||||
// size of malloced space pointed to by value
|
||||
int size;
|
||||
};
|
||||
|
||||
struct ed25519_public_key { char bytes[item_pk_len]; };
|
||||
|
||||
struct dht_mutable_item : dht_immutable_item
|
||||
{
|
||||
char sig[item_sig_len];
|
||||
boost::uint64_t seq;
|
||||
ed25519_public_key key;
|
||||
char* salt;
|
||||
int salt_size;
|
||||
};
|
||||
|
||||
// internal
|
||||
inline bool operator<(ed25519_public_key const& lhs, ed25519_public_key const& rhs)
|
||||
{
|
||||
return memcmp(lhs.bytes, rhs.bytes, sizeof(lhs.bytes)) < 0;
|
||||
}
|
||||
|
||||
// internal
|
||||
inline bool operator<(peer_entry const& lhs, peer_entry const& rhs)
|
||||
{
|
||||
return lhs.addr.address() == rhs.addr.address()
|
||||
? lhs.addr.port() < rhs.addr.port()
|
||||
: lhs.addr.address() < rhs.addr.address();
|
||||
}
|
||||
|
||||
struct null_type {};
|
||||
|
||||
class announce_observer : public observer
|
||||
|
@ -174,17 +116,6 @@ public:
|
|||
void reply(msg const&) { flags |= flag_done; }
|
||||
};
|
||||
|
||||
struct count_peers
|
||||
{
|
||||
int* count;
|
||||
count_peers(int* c): count(c) {}
|
||||
void operator()(std::pair<libtorrent::dht::node_id
|
||||
, libtorrent::dht::torrent_entry> const& t)
|
||||
{
|
||||
*count += t.second.peers.size();
|
||||
}
|
||||
};
|
||||
|
||||
struct udp_socket_interface
|
||||
{
|
||||
virtual bool has_quota() = 0;
|
||||
|
@ -195,16 +126,12 @@ protected:
|
|||
|
||||
class TORRENT_EXTRA_EXPORT node : boost::noncopyable
|
||||
{
|
||||
typedef std::map<node_id, torrent_entry> table_t;
|
||||
typedef std::map<node_id, dht_immutable_item> dht_immutable_table_t;
|
||||
typedef std::map<node_id, dht_mutable_item> dht_mutable_table_t;
|
||||
|
||||
public:
|
||||
node(udp_socket_interface* sock
|
||||
, libtorrent::dht_settings const& settings, node_id nid
|
||||
, dht_observer* observer, counters& cnt);
|
||||
|
||||
virtual ~node() {}
|
||||
virtual ~node();
|
||||
|
||||
void tick();
|
||||
void bootstrap(std::vector<udp::endpoint> const& nodes
|
||||
|
@ -214,13 +141,10 @@ public:
|
|||
void unreachable(udp::endpoint const& ep);
|
||||
void incoming(msg const& m);
|
||||
|
||||
int num_torrents() const { return m_map.size(); }
|
||||
int num_peers() const
|
||||
{
|
||||
int ret = 0;
|
||||
std::for_each(m_map.begin(), m_map.end(), count_peers(&ret));
|
||||
return ret;
|
||||
}
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
int num_torrents() const { return m_storage->num_torrents(); }
|
||||
int num_peers() const { return m_storage->num_peers(); }
|
||||
#endif
|
||||
|
||||
int bucket_size(int bucket);
|
||||
|
||||
|
@ -230,7 +154,9 @@ public:
|
|||
boost::int64_t num_global_nodes() const
|
||||
{ return m_table.num_global_nodes(); }
|
||||
|
||||
int data_size() const { return int(m_map.size()); }
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
int data_size() const { return int(m_storage->num_torrents()); }
|
||||
#endif
|
||||
|
||||
#if defined TORRENT_DEBUG
|
||||
void print_state(std::ostream& os) const
|
||||
|
@ -326,10 +252,6 @@ public:
|
|||
private:
|
||||
dht_observer* m_observer;
|
||||
|
||||
table_t m_map;
|
||||
dht_immutable_table_t m_immutable_table;
|
||||
dht_mutable_table_t m_mutable_table;
|
||||
|
||||
time_point m_last_tracker_tick;
|
||||
|
||||
// the last time we issued a bootstrap or a refresh on our own ID, to expand
|
||||
|
@ -341,10 +263,10 @@ private:
|
|||
|
||||
udp_socket_interface* m_sock;
|
||||
counters& m_counters;
|
||||
};
|
||||
|
||||
boost::scoped_ptr<dht_storage_interface> m_storage;
|
||||
};
|
||||
|
||||
} } // namespace libtorrent::dht
|
||||
|
||||
#endif // NODE_HPP
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ lib_LTLIBRARIES = libtorrent-rasterbar.la
|
|||
|
||||
if ENABLE_DHT
|
||||
KADEMLIA_SOURCES = \
|
||||
kademlia/dht_storage.cpp \
|
||||
kademlia/dht_tracker.cpp \
|
||||
kademlia/find_data.cpp \
|
||||
kademlia/node.cpp \
|
||||
|
|
|
@ -0,0 +1,527 @@
|
|||
/*
|
||||
|
||||
Copyright (c) 2012-2015, Arvid Norberg, Alden Torres
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include "libtorrent/kademlia/dht_storage.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/function/function1.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <libtorrent/socket_io.hpp>
|
||||
#include <libtorrent/aux_/time.hpp>
|
||||
#include <libtorrent/config.hpp>
|
||||
#include <libtorrent/time.hpp>
|
||||
#include <libtorrent/socket.hpp>
|
||||
#include <libtorrent/sha1_hash.hpp>
|
||||
#include <libtorrent/bloom_filter.hpp>
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/session_settings.hpp>
|
||||
#include <libtorrent/performance_counters.hpp>
|
||||
#include <libtorrent/random.hpp>
|
||||
|
||||
#include <libtorrent/kademlia/item.hpp>
|
||||
#include <libtorrent/kademlia/node_id.hpp>
|
||||
|
||||
namespace libtorrent {
|
||||
namespace dht {
|
||||
namespace
|
||||
{
|
||||
using detail::write_endpoint;
|
||||
|
||||
// this is the entry for every peer
|
||||
// the timestamp is there to make it possible
|
||||
// to remove stale peers
|
||||
struct peer_entry
|
||||
{
|
||||
time_point added;
|
||||
tcp::endpoint addr;
|
||||
bool seed;
|
||||
};
|
||||
|
||||
// internal
|
||||
bool operator<(peer_entry const& lhs, peer_entry const& rhs)
|
||||
{
|
||||
return lhs.addr.address() == rhs.addr.address()
|
||||
? lhs.addr.port() < rhs.addr.port()
|
||||
: lhs.addr.address() < rhs.addr.address();
|
||||
}
|
||||
|
||||
// this is a group. It contains a set of group members
|
||||
struct torrent_entry
|
||||
{
|
||||
std::string name;
|
||||
std::set<peer_entry> peers;
|
||||
};
|
||||
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
struct count_peers
|
||||
{
|
||||
int* count;
|
||||
count_peers(int* c): count(c) {}
|
||||
void operator()(std::pair<libtorrent::sha1_hash
|
||||
, torrent_entry> const& t)
|
||||
{
|
||||
*count += t.second.peers.size();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// TODO: 2 make this configurable in dht_settings
|
||||
enum { announce_interval = 30 };
|
||||
|
||||
void purge_peers(std::set<peer_entry>& peers)
|
||||
{
|
||||
for (std::set<peer_entry>::iterator i = peers.begin()
|
||||
, end(peers.end()); i != end;)
|
||||
{
|
||||
// the peer has timed out
|
||||
if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now())
|
||||
peers.erase(i++);
|
||||
else
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
struct dht_immutable_item
|
||||
{
|
||||
dht_immutable_item() : value(0), num_announcers(0), size(0) {}
|
||||
// malloced space for the actual value
|
||||
char* value;
|
||||
// this counts the number of IPs we have seen
|
||||
// announcing this item, this is used to determine
|
||||
// popularity if we reach the limit of items to store
|
||||
bloom_filter<128> ips;
|
||||
// the last time we heard about this
|
||||
time_point last_seen;
|
||||
// number of IPs in the bloom filter
|
||||
int num_announcers;
|
||||
// size of malloced space pointed to by value
|
||||
int size;
|
||||
};
|
||||
|
||||
struct ed25519_public_key { char bytes[item_pk_len]; };
|
||||
|
||||
struct dht_mutable_item : dht_immutable_item
|
||||
{
|
||||
char sig[item_sig_len];
|
||||
boost::int64_t seq;
|
||||
ed25519_public_key key;
|
||||
char* salt;
|
||||
int salt_size;
|
||||
};
|
||||
|
||||
void touch_item(dht_immutable_item* f, address const& address)
|
||||
{
|
||||
f->last_seen = aux::time_now();
|
||||
|
||||
// maybe increase num_announcers if we haven't seen this IP before
|
||||
sha1_hash iphash;
|
||||
hash_address(address, iphash);
|
||||
if (!f->ips.find(iphash))
|
||||
{
|
||||
f->ips.set(iphash);
|
||||
++f->num_announcers;
|
||||
}
|
||||
}
|
||||
|
||||
// return true of the first argument is a better canidate for removal, i.e.
|
||||
// less important to keep
|
||||
struct immutable_item_comparator
|
||||
{
|
||||
immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {}
|
||||
immutable_item_comparator(immutable_item_comparator const& c)
|
||||
: m_our_id(c.m_our_id) {}
|
||||
|
||||
bool operator() (std::pair<node_id, dht_immutable_item> const& lhs
|
||||
, std::pair<node_id, dht_immutable_item> const& rhs) const
|
||||
{
|
||||
int l_distance = distance_exp(lhs.first, m_our_id);
|
||||
int r_distance = distance_exp(rhs.first, m_our_id);
|
||||
|
||||
// this is a score taking the popularity (number of announcers) and the
|
||||
// fit, in terms of distance from ideal storing node, into account.
|
||||
// each additional 5 announcers is worth one extra bit in the distance.
|
||||
// that is, an item with 10 announcers is allowed to be twice as far
|
||||
// from another item with 5 announcers, from our node ID. Twice as far
|
||||
// because it gets one more bit.
|
||||
return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// explicitly disallow assignment, to silence msvc warning
|
||||
immutable_item_comparator& operator=(immutable_item_comparator const&);
|
||||
|
||||
node_id const& m_our_id;
|
||||
};
|
||||
|
||||
class dht_default_storage TORRENT_FINAL : public dht_storage_interface, boost::noncopyable
|
||||
{
|
||||
typedef std::map<node_id, torrent_entry> table_t;
|
||||
typedef std::map<node_id, dht_immutable_item> dht_immutable_table_t;
|
||||
typedef std::map<node_id, dht_mutable_item> dht_mutable_table_t;
|
||||
|
||||
public:
|
||||
|
||||
dht_default_storage(sha1_hash const& id, dht_settings const& settings
|
||||
, counters& cnt)
|
||||
: m_id(id)
|
||||
, m_settings(settings)
|
||||
, m_counters(cnt)
|
||||
{
|
||||
}
|
||||
|
||||
~dht_default_storage() {}
|
||||
|
||||
#ifndef TORRENT_NO_DEPRECATE
|
||||
size_t num_torrents() const TORRENT_OVERRIDE { return m_map.size(); }
|
||||
size_t num_peers() const TORRENT_OVERRIDE
|
||||
{
|
||||
int ret = 0;
|
||||
std::for_each(m_map.begin(), m_map.end(), count_peers(&ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool get_peers(sha1_hash const& info_hash
|
||||
, bool noseed, bool scrape
|
||||
, entry& peers) const TORRENT_OVERRIDE
|
||||
{
|
||||
table_t::const_iterator i = m_map.lower_bound(info_hash);
|
||||
if (i == m_map.end()) return false;
|
||||
if (i->first != info_hash) return false;
|
||||
|
||||
torrent_entry const& v = i->second;
|
||||
|
||||
if (!v.name.empty()) peers["n"] = v.name;
|
||||
|
||||
if (scrape)
|
||||
{
|
||||
bloom_filter<256> downloaders;
|
||||
bloom_filter<256> seeds;
|
||||
|
||||
for (std::set<peer_entry>::const_iterator peer_it = v.peers.begin()
|
||||
, end(v.peers.end()); peer_it != end; ++peer_it)
|
||||
{
|
||||
sha1_hash iphash;
|
||||
hash_address(peer_it->addr.address(), iphash);
|
||||
if (peer_it->seed) seeds.set(iphash);
|
||||
else downloaders.set(iphash);
|
||||
}
|
||||
|
||||
peers["BFpe"] = downloaders.to_string();
|
||||
peers["BFsd"] = seeds.to_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply);
|
||||
std::set<peer_entry>::const_iterator iter = v.peers.begin();
|
||||
entry::list_type& pe = peers["values"].list();
|
||||
std::string endpoint;
|
||||
|
||||
for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t)
|
||||
{
|
||||
if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue;
|
||||
if (noseed && iter->seed) continue;
|
||||
endpoint.resize(18);
|
||||
std::string::iterator out = endpoint.begin();
|
||||
write_endpoint(iter->addr, out);
|
||||
endpoint.resize(out - endpoint.begin());
|
||||
pe.push_back(entry(endpoint));
|
||||
|
||||
++m;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void announce_peer(sha1_hash const& info_hash
|
||||
, tcp::endpoint const& endp
|
||||
, std::string const& name, bool seed) TORRENT_OVERRIDE
|
||||
{
|
||||
table_t::iterator ti = m_map.find(info_hash);
|
||||
torrent_entry* v;
|
||||
if (ti == m_map.end())
|
||||
{
|
||||
// we don't have this torrent, add it
|
||||
// do we need to remove another one first?
|
||||
if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents)
|
||||
{
|
||||
// we need to remove some. Remove the ones with the
|
||||
// fewest peers
|
||||
int num_peers = m_map.begin()->second.peers.size();
|
||||
table_t::iterator candidate = m_map.begin();
|
||||
for (table_t::iterator i = m_map.begin()
|
||||
, end(m_map.end()); i != end; ++i)
|
||||
{
|
||||
if (int(i->second.peers.size()) > num_peers) continue;
|
||||
if (i->first == info_hash) continue;
|
||||
num_peers = i->second.peers.size();
|
||||
candidate = i;
|
||||
}
|
||||
m_map.erase(candidate);
|
||||
m_counters.inc_stats_counter(counters::dht_torrents, -1);
|
||||
}
|
||||
m_counters.inc_stats_counter(counters::dht_torrents);
|
||||
v = &m_map[info_hash];
|
||||
}
|
||||
else
|
||||
{
|
||||
v = &ti->second;
|
||||
}
|
||||
|
||||
// the peer announces a torrent name, and we don't have a name
|
||||
// for this torrent. Store it.
|
||||
if (!name.empty() && v->name.empty())
|
||||
{
|
||||
std::string tname = name;
|
||||
if (tname.size() > 50) tname.resize(50);
|
||||
v->name = tname;
|
||||
}
|
||||
|
||||
peer_entry peer;
|
||||
peer.addr = endp;
|
||||
peer.added = aux::time_now();
|
||||
peer.seed = seed;
|
||||
std::set<peer_entry>::iterator i = v->peers.find(peer);
|
||||
if (i != v->peers.end()) v->peers.erase(i++);
|
||||
v->peers.insert(i, peer);
|
||||
}
|
||||
|
||||
bool get_immutable_item(sha1_hash const& target
|
||||
, entry& item) const TORRENT_OVERRIDE
|
||||
{
|
||||
dht_immutable_table_t::const_iterator i = m_immutable_table.find(target);
|
||||
if (i == m_immutable_table.end()) return false;
|
||||
|
||||
item["v"] = bdecode(i->second.value, i->second.value + i->second.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void put_immutable_item(sha1_hash const& target
|
||||
, char const* buf, int size
|
||||
, address const& addr) TORRENT_OVERRIDE
|
||||
{
|
||||
dht_immutable_table_t::iterator i = m_immutable_table.find(target);
|
||||
if (i == m_immutable_table.end())
|
||||
{
|
||||
// make sure we don't add too many items
|
||||
if (int(m_immutable_table.size()) >= m_settings.max_dht_items)
|
||||
{
|
||||
// delete the least important one (i.e. the one
|
||||
// the fewest peers are announcing, and farthest
|
||||
// from our node ID)
|
||||
dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin()
|
||||
, m_immutable_table.end()
|
||||
, immutable_item_comparator(m_id));
|
||||
|
||||
TORRENT_ASSERT(j != m_immutable_table.end());
|
||||
free(j->second.value);
|
||||
m_immutable_table.erase(j);
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
|
||||
}
|
||||
dht_immutable_item to_add;
|
||||
to_add.value = static_cast<char*>(malloc(size));
|
||||
to_add.size = size;
|
||||
memcpy(to_add.value, buf, size);
|
||||
|
||||
boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert(
|
||||
std::make_pair(target, to_add));
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data);
|
||||
}
|
||||
|
||||
// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size()));
|
||||
|
||||
touch_item(&i->second, addr);
|
||||
}
|
||||
|
||||
bool get_mutable_item_seq(sha1_hash const& target
|
||||
, boost::int64_t& seq) const TORRENT_OVERRIDE
|
||||
{
|
||||
dht_mutable_table_t::const_iterator i = m_mutable_table.find(target);
|
||||
if (i == m_mutable_table.end()) return false;
|
||||
|
||||
seq = i->second.seq;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_mutable_item(sha1_hash const& target
|
||||
, boost::int64_t seq, bool force_fill
|
||||
, entry& item) const TORRENT_OVERRIDE
|
||||
{
|
||||
dht_mutable_table_t::const_iterator i = m_mutable_table.find(target);
|
||||
if (i == m_mutable_table.end()) return false;
|
||||
|
||||
dht_mutable_item const& f = i->second;
|
||||
item["seq"] = f.seq;
|
||||
if (force_fill || (0 <= seq && seq < f.seq))
|
||||
{
|
||||
item["v"] = bdecode(f.value, f.value + f.size);
|
||||
item["sig"] = std::string(f.sig, f.sig + sizeof(f.sig));
|
||||
item["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void put_mutable_item(sha1_hash const& target
|
||||
, char const* buf, int size
|
||||
, char const* sig
|
||||
, boost::int64_t seq
|
||||
, char const* pk
|
||||
, char const* salt, int salt_size
|
||||
, address const& addr) TORRENT_OVERRIDE
|
||||
{
|
||||
dht_mutable_table_t::iterator i = m_mutable_table.find(target);
|
||||
if (i == m_mutable_table.end())
|
||||
{
|
||||
// this is the case where we don't have an item in this slot
|
||||
// make sure we don't add too many items
|
||||
if (int(m_mutable_table.size()) >= m_settings.max_dht_items)
|
||||
{
|
||||
// delete the least important one (i.e. the one
|
||||
// the fewest peers are announcing)
|
||||
dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin()
|
||||
, m_mutable_table.end()
|
||||
, boost::bind(&dht_immutable_item::num_announcers
|
||||
, boost::bind(&dht_mutable_table_t::value_type::second, _1)));
|
||||
TORRENT_ASSERT(j != m_mutable_table.end());
|
||||
free(j->second.value);
|
||||
free(j->second.salt);
|
||||
m_mutable_table.erase(j);
|
||||
m_counters.inc_stats_counter(counters::dht_mutable_data, -1);
|
||||
}
|
||||
dht_mutable_item to_add;
|
||||
to_add.value = static_cast<char*>(malloc(size));
|
||||
to_add.size = size;
|
||||
to_add.seq = seq;
|
||||
to_add.salt = NULL;
|
||||
to_add.salt_size = 0;
|
||||
if (salt_size > 0)
|
||||
{
|
||||
to_add.salt = static_cast<char*>(malloc(salt_size));
|
||||
to_add.salt_size = salt_size;
|
||||
memcpy(to_add.salt, salt, salt_size);
|
||||
}
|
||||
memcpy(to_add.sig, sig, sizeof(to_add.sig));
|
||||
memcpy(to_add.value, buf, size);
|
||||
memcpy(&to_add.key, pk, sizeof(to_add.key));
|
||||
|
||||
boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert(
|
||||
std::make_pair(target, to_add));
|
||||
m_counters.inc_stats_counter(counters::dht_mutable_data);
|
||||
|
||||
// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is the case where we already
|
||||
dht_mutable_item* item = &i->second;
|
||||
|
||||
if (item->seq < seq)
|
||||
{
|
||||
if (item->size != size)
|
||||
{
|
||||
free(item->value);
|
||||
item->value = static_cast<char*>(malloc(size));
|
||||
item->size = size;
|
||||
}
|
||||
item->seq = seq;
|
||||
memcpy(item->sig, sig, sizeof(item->sig));
|
||||
memcpy(item->value, buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
touch_item(&i->second, addr);
|
||||
}
|
||||
|
||||
void tick() TORRENT_OVERRIDE
|
||||
{
|
||||
time_point now(aux::time_now());
|
||||
|
||||
for (dht_immutable_table_t::iterator i = m_immutable_table.begin();
|
||||
i != m_immutable_table.end();)
|
||||
{
|
||||
if (i->second.last_seen + minutes(60) > now)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
free(i->second.value);
|
||||
m_immutable_table.erase(i++);
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
|
||||
}
|
||||
|
||||
// look through all peers and see if any have timed out
|
||||
for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;)
|
||||
{
|
||||
torrent_entry& t = i->second;
|
||||
node_id const& key = i->first;
|
||||
++i;
|
||||
purge_peers(t.peers);
|
||||
|
||||
if (!t.peers.empty()) continue;
|
||||
|
||||
// if there are no more peers, remove the entry altogether
|
||||
table_t::iterator it = m_map.find(key);
|
||||
if (it != m_map.end())
|
||||
{
|
||||
m_map.erase(it);
|
||||
m_counters.inc_stats_counter(counters::dht_torrents, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
sha1_hash m_id;
|
||||
dht_settings const& m_settings;
|
||||
counters& m_counters;
|
||||
|
||||
table_t m_map;
|
||||
dht_immutable_table_t m_immutable_table;
|
||||
dht_mutable_table_t m_mutable_table;
|
||||
};
|
||||
}
|
||||
|
||||
dht_storage_interface* dht_default_storage_constructor(sha1_hash const& id
|
||||
, dht_settings const& settings
|
||||
, counters& counters)
|
||||
{
|
||||
return new dht_default_storage(id, settings, counters);
|
||||
}
|
||||
|
||||
} } // namespace libtorrent::dht
|
|
@ -69,25 +69,8 @@ namespace libtorrent { namespace dht
|
|||
|
||||
using detail::write_endpoint;
|
||||
|
||||
// TODO: 2 make this configurable in dht_settings
|
||||
enum { announce_interval = 30 };
|
||||
|
||||
namespace {
|
||||
|
||||
// remove peers that have timed out
|
||||
void purge_peers(std::set<peer_entry>& peers)
|
||||
{
|
||||
for (std::set<peer_entry>::iterator i = peers.begin()
|
||||
, end(peers.end()); i != end;)
|
||||
{
|
||||
// the peer has timed out
|
||||
if (i->added + minutes(int(announce_interval * 1.5f)) < aux::time_now())
|
||||
peers.erase(i++);
|
||||
else
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void nop() {}
|
||||
|
||||
node_id calculate_node_id(node_id const& nid, dht_observer* observer)
|
||||
|
@ -115,9 +98,16 @@ node::node(udp_socket_interface* sock
|
|||
, m_last_self_refresh(min_time())
|
||||
, m_sock(sock)
|
||||
, m_counters(cnt)
|
||||
, m_storage(dht_default_storage_constructor(m_id, m_settings, m_counters))
|
||||
{
|
||||
m_secret[0] = random();
|
||||
m_secret[1] = random();
|
||||
|
||||
TORRENT_ASSERT(m_storage.get() != NULL);
|
||||
}
|
||||
|
||||
node::~node()
|
||||
{
|
||||
}
|
||||
|
||||
bool node::verify_token(std::string const& token, char const* info_hash
|
||||
|
@ -590,38 +580,7 @@ time_duration node::connection_timeout()
|
|||
if (now - minutes(2) < m_last_tracker_tick) return d;
|
||||
m_last_tracker_tick = now;
|
||||
|
||||
for (dht_immutable_table_t::iterator i = m_immutable_table.begin();
|
||||
i != m_immutable_table.end();)
|
||||
{
|
||||
if (i->second.last_seen + minutes(60) > now)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
free(i->second.value);
|
||||
m_immutable_table.erase(i++);
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
|
||||
}
|
||||
|
||||
// look through all peers and see if any have timed out
|
||||
for (table_t::iterator i = m_map.begin(), end(m_map.end()); i != end;)
|
||||
{
|
||||
torrent_entry& t = i->second;
|
||||
node_id const& key = i->first;
|
||||
++i;
|
||||
purge_peers(t.peers);
|
||||
|
||||
// if there are no more peers, remove the entry altogether
|
||||
if (t.peers.empty())
|
||||
{
|
||||
table_t::iterator it = m_map.find(key);
|
||||
if (it != m_map.end())
|
||||
{
|
||||
m_map.erase(it);
|
||||
m_counters.inc_stats_counter(counters::dht_torrents, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_storage->tick();
|
||||
|
||||
return d;
|
||||
}
|
||||
|
@ -649,7 +608,7 @@ void node::status(session_status& s)
|
|||
mutex_t::scoped_lock l(m_mutex);
|
||||
|
||||
m_table.status(s);
|
||||
s.dht_torrents = int(m_map.size());
|
||||
s.dht_torrents = int(m_storage->num_torrents());
|
||||
s.active_requests.clear();
|
||||
s.dht_total_allocations = m_rpc.num_allocated_observers();
|
||||
for (std::set<traversal_algorithm*>::iterator i = m_running_requests.begin()
|
||||
|
@ -668,52 +627,7 @@ void node::lookup_peers(sha1_hash const& info_hash, entry& reply
|
|||
if (m_observer)
|
||||
m_observer->get_peers(info_hash);
|
||||
|
||||
table_t::const_iterator i = m_map.lower_bound(info_hash);
|
||||
if (i == m_map.end()) return;
|
||||
if (i->first != info_hash) return;
|
||||
|
||||
torrent_entry const& v = i->second;
|
||||
|
||||
if (!v.name.empty()) reply["n"] = v.name;
|
||||
|
||||
if (scrape)
|
||||
{
|
||||
bloom_filter<256> downloaders;
|
||||
bloom_filter<256> seeds;
|
||||
|
||||
for (std::set<peer_entry>::const_iterator peer_it = v.peers.begin()
|
||||
, end(v.peers.end()); peer_it != end; ++peer_it)
|
||||
{
|
||||
sha1_hash iphash;
|
||||
hash_address(peer_it->addr.address(), iphash);
|
||||
if (peer_it->seed) seeds.set(iphash);
|
||||
else downloaders.set(iphash);
|
||||
}
|
||||
|
||||
reply["BFpe"] = downloaders.to_string();
|
||||
reply["BFsd"] = seeds.to_string();
|
||||
}
|
||||
else
|
||||
{
|
||||
int num = (std::min)(int(v.peers.size()), m_settings.max_peers_reply);
|
||||
std::set<peer_entry>::const_iterator iter = v.peers.begin();
|
||||
entry::list_type& pe = reply["values"].list();
|
||||
std::string endpoint;
|
||||
|
||||
for (int t = 0, m = 0; m < num && iter != v.peers.end(); ++iter, ++t)
|
||||
{
|
||||
if ((random() / float(UINT_MAX + 1.f)) * (num - t) >= num - m) continue;
|
||||
if (noseed && iter->seed) continue;
|
||||
endpoint.resize(18);
|
||||
std::string::iterator out = endpoint.begin();
|
||||
write_endpoint(iter->addr, out);
|
||||
endpoint.resize(out - endpoint.begin());
|
||||
pe.push_back(entry(endpoint));
|
||||
|
||||
++m;
|
||||
}
|
||||
}
|
||||
return;
|
||||
m_storage->get_peers(info_hash, noseed, scrape, reply);
|
||||
}
|
||||
|
||||
void TORRENT_EXTRA_EXPORT write_nodes_entry(entry& r, nodes_t const& nodes)
|
||||
|
@ -833,37 +747,6 @@ void incoming_error(entry& e, char const* msg, int error_code)
|
|||
l.push_back(entry(msg));
|
||||
}
|
||||
|
||||
// return true of the first argument is a better canidate for removal, i.e.
|
||||
// less important to keep
|
||||
struct immutable_item_comparator
|
||||
{
|
||||
immutable_item_comparator(node_id const& our_id) : m_our_id(our_id) {}
|
||||
immutable_item_comparator(immutable_item_comparator const& c)
|
||||
: m_our_id(c.m_our_id) {}
|
||||
|
||||
bool operator() (std::pair<node_id, dht_immutable_item> const& lhs
|
||||
, std::pair<node_id, dht_immutable_item> const& rhs) const
|
||||
{
|
||||
int l_distance = distance_exp(lhs.first, m_our_id);
|
||||
int r_distance = distance_exp(rhs.first, m_our_id);
|
||||
|
||||
// this is a score taking the popularity (number of announcers) and the
|
||||
// fit, in terms of distance from ideal storing node, into account.
|
||||
// each additional 5 announcers is worth one extra bit in the distance.
|
||||
// that is, an item with 10 announcers is allowed to be twice as far
|
||||
// from another item with 5 announcers, from our node ID. Twice as far
|
||||
// because it gets one more bit.
|
||||
return lhs.second.num_announcers / 5 - l_distance < rhs.second.num_announcers / 5 - r_distance;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// explicitly disallow assignment, to silence msvc warning
|
||||
immutable_item_comparator& operator=(immutable_item_comparator const&);
|
||||
|
||||
node_id const& m_our_id;
|
||||
};
|
||||
|
||||
// build response
|
||||
void node::incoming_request(msg const& m, entry& e)
|
||||
{
|
||||
|
@ -1039,53 +922,11 @@ void node::incoming_request(msg const& m, entry& e)
|
|||
// the table get a chance to add it.
|
||||
m_table.node_seen(id, m.addr, 0xffff);
|
||||
|
||||
table_t::iterator ti = m_map.find(info_hash);
|
||||
torrent_entry* v;
|
||||
if (ti == m_map.end())
|
||||
{
|
||||
// we don't have this torrent, add it
|
||||
// do we need to remove another one first?
|
||||
if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents)
|
||||
{
|
||||
// we need to remove some. Remove the ones with the
|
||||
// fewest peers
|
||||
int num_peers = m_map.begin()->second.peers.size();
|
||||
table_t::iterator candidate = m_map.begin();
|
||||
for (table_t::iterator i = m_map.begin()
|
||||
, end(m_map.end()); i != end; ++i)
|
||||
{
|
||||
if (int(i->second.peers.size()) > num_peers) continue;
|
||||
if (i->first == info_hash) continue;
|
||||
num_peers = i->second.peers.size();
|
||||
candidate = i;
|
||||
}
|
||||
m_map.erase(candidate);
|
||||
m_counters.inc_stats_counter(counters::dht_torrents, -1);
|
||||
}
|
||||
m_counters.inc_stats_counter(counters::dht_torrents);
|
||||
v = &m_map[info_hash];
|
||||
}
|
||||
else
|
||||
{
|
||||
v = &ti->second;
|
||||
}
|
||||
tcp::endpoint addr = tcp::endpoint(m.addr.address(), port);
|
||||
std::string name = msg_keys[3] ? msg_keys[3].string_value() : std::string();
|
||||
bool seed = msg_keys[4] && msg_keys[4].int_value();
|
||||
|
||||
// the peer announces a torrent name, and we don't have a name
|
||||
// for this torrent. Store it.
|
||||
if (msg_keys[3] && v->name.empty())
|
||||
{
|
||||
std::string name = msg_keys[3].string_value();
|
||||
if (name.size() > 50) name.resize(50);
|
||||
v->name = name;
|
||||
}
|
||||
|
||||
peer_entry peer;
|
||||
peer.addr = tcp::endpoint(m.addr.address(), port);
|
||||
peer.added = aux::time_now();
|
||||
peer.seed = msg_keys[4] && msg_keys[4].int_value();
|
||||
std::set<peer_entry>::iterator i = v->peers.find(peer);
|
||||
if (i != v->peers.end()) v->peers.erase(i++);
|
||||
v->peers.insert(i, peer);
|
||||
m_storage->announce_peer(info_hash, addr, name, seed);
|
||||
}
|
||||
else if (query_len == 3 && memcmp(query, "put", 3) == 0)
|
||||
{
|
||||
|
@ -1166,41 +1007,9 @@ void node::incoming_request(msg const& m, entry& e)
|
|||
return;
|
||||
}
|
||||
|
||||
dht_immutable_item* f = 0;
|
||||
|
||||
if (!mutable_put)
|
||||
{
|
||||
dht_immutable_table_t::iterator i = m_immutable_table.find(target);
|
||||
if (i == m_immutable_table.end())
|
||||
{
|
||||
// make sure we don't add too many items
|
||||
if (int(m_immutable_table.size()) >= m_settings.max_dht_items)
|
||||
{
|
||||
// delete the least important one (i.e. the one
|
||||
// the fewest peers are announcing, and farthest
|
||||
// from our node ID)
|
||||
dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin()
|
||||
, m_immutable_table.end()
|
||||
, immutable_item_comparator(m_id));
|
||||
|
||||
TORRENT_ASSERT(j != m_immutable_table.end());
|
||||
free(j->second.value);
|
||||
m_immutable_table.erase(j);
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data, -1);
|
||||
}
|
||||
dht_immutable_item to_add;
|
||||
to_add.value = static_cast<char*>(malloc(buf.second));
|
||||
to_add.size = buf.second;
|
||||
memcpy(to_add.value, buf.first, buf.second);
|
||||
|
||||
boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert(
|
||||
std::make_pair(target, to_add));
|
||||
m_counters.inc_stats_counter(counters::dht_immutable_data);
|
||||
}
|
||||
|
||||
// fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size()));
|
||||
|
||||
f = &i->second;
|
||||
m_storage->put_immutable_item(target, buf.first, buf.second, m.addr.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1210,111 +1019,65 @@ void node::incoming_request(msg const& m, entry& e)
|
|||
VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4].string_ptr(), item_sig_len);
|
||||
VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len);
|
||||
#endif
|
||||
boost::int64_t seq = msg_keys[2].int_value();
|
||||
|
||||
if (seq < 0)
|
||||
{
|
||||
m_counters.inc_stats_counter(counters::dht_invalid_put);
|
||||
incoming_error(e, "invalid (negative) sequence number");
|
||||
return;
|
||||
}
|
||||
|
||||
// msg_keys[4] is the signature, msg_keys[3] is the public key
|
||||
if (!verify_mutable_item(buf, salt
|
||||
, msg_keys[2].int_value(), pk, sig))
|
||||
, seq, pk, sig))
|
||||
{
|
||||
m_counters.inc_stats_counter(counters::dht_invalid_put);
|
||||
incoming_error(e, "invalid signature", 206);
|
||||
return;
|
||||
}
|
||||
|
||||
dht_mutable_table_t::iterator i = m_mutable_table.find(target);
|
||||
if (i == m_mutable_table.end())
|
||||
{
|
||||
// this is the case where we don't have an item in this slot
|
||||
// make sure we don't add too many items
|
||||
if (int(m_mutable_table.size()) >= m_settings.max_dht_items)
|
||||
{
|
||||
// delete the least important one (i.e. the one
|
||||
// the fewest peers are announcing)
|
||||
dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin()
|
||||
, m_mutable_table.end()
|
||||
, boost::bind(&dht_immutable_item::num_announcers
|
||||
, boost::bind(&dht_mutable_table_t::value_type::second, _1)));
|
||||
TORRENT_ASSERT(j != m_mutable_table.end());
|
||||
free(j->second.value);
|
||||
free(j->second.salt);
|
||||
m_mutable_table.erase(j);
|
||||
m_counters.inc_stats_counter(counters::dht_mutable_data, -1);
|
||||
}
|
||||
dht_mutable_item to_add;
|
||||
to_add.value = static_cast<char*>(malloc(buf.second));
|
||||
to_add.size = buf.second;
|
||||
to_add.seq = msg_keys[2].int_value();
|
||||
to_add.salt = NULL;
|
||||
to_add.salt_size = 0;
|
||||
if (salt.second > 0)
|
||||
{
|
||||
to_add.salt = static_cast<char*>(malloc(salt.second));
|
||||
to_add.salt_size = salt.second;
|
||||
memcpy(to_add.salt, salt.first, salt.second);
|
||||
}
|
||||
memcpy(to_add.sig, sig, sizeof(to_add.sig));
|
||||
TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4].string_length());
|
||||
memcpy(to_add.value, buf.first, buf.second);
|
||||
memcpy(&to_add.key, pk, sizeof(to_add.key));
|
||||
|
||||
boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert(
|
||||
std::make_pair(target, to_add));
|
||||
m_counters.inc_stats_counter(counters::dht_mutable_data);
|
||||
TORRENT_ASSERT(item_sig_len == msg_keys[4].string_length());
|
||||
|
||||
// fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size()));
|
||||
boost::int64_t item_seq;
|
||||
if (!m_storage->get_mutable_item_seq(target, item_seq))
|
||||
{
|
||||
m_storage->put_mutable_item(target
|
||||
, buf.first, buf.second
|
||||
, sig, seq, pk
|
||||
, salt.first, salt.second
|
||||
, m.addr.address());
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is the case where we already
|
||||
dht_mutable_item* item = &i->second;
|
||||
|
||||
// this is the "cas" field in the put message
|
||||
// if it was specified, we MUST make sure the current sequence
|
||||
// number matches the expected value before replacing it
|
||||
// this is critical for avoiding race conditions when multiple
|
||||
// writers are accessing the same slot
|
||||
if (msg_keys[5] && item->seq != msg_keys[5].int_value())
|
||||
if (msg_keys[5] && item_seq != msg_keys[5].int_value())
|
||||
{
|
||||
m_counters.inc_stats_counter(counters::dht_invalid_put);
|
||||
incoming_error(e, "CAS mismatch", 301);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->seq > boost::uint64_t(msg_keys[2].int_value()))
|
||||
if (item_seq > seq)
|
||||
{
|
||||
m_counters.inc_stats_counter(counters::dht_invalid_put);
|
||||
incoming_error(e, "old sequence number", 302);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->seq < boost::uint64_t(msg_keys[2].int_value()))
|
||||
{
|
||||
if (item->size != buf.second)
|
||||
{
|
||||
free(item->value);
|
||||
item->value = static_cast<char*>(malloc(buf.second));
|
||||
item->size = buf.second;
|
||||
}
|
||||
item->seq = msg_keys[2].int_value();
|
||||
memcpy(item->sig, msg_keys[4].string_ptr(), sizeof(item->sig));
|
||||
TORRENT_ASSERT(sizeof(item->sig) == msg_keys[4].string_length());
|
||||
memcpy(item->value, buf.first, buf.second);
|
||||
}
|
||||
m_storage->put_mutable_item(target
|
||||
, buf.first, buf.second
|
||||
, sig, seq, pk
|
||||
, salt.first, salt.second
|
||||
, m.addr.address());
|
||||
}
|
||||
|
||||
f = &i->second;
|
||||
}
|
||||
|
||||
m_table.node_seen(id, m.addr, 0xffff);
|
||||
|
||||
f->last_seen = aux::time_now();
|
||||
|
||||
// maybe increase num_announcers if we haven't seen this IP before
|
||||
sha1_hash iphash;
|
||||
hash_address(m.addr.address(), iphash);
|
||||
if (!f->ips.find(iphash))
|
||||
{
|
||||
f->ips.set(iphash);
|
||||
++f->num_announcers;
|
||||
}
|
||||
}
|
||||
else if (query_len == 3 && memcmp(query, "get", 3) == 0)
|
||||
{
|
||||
|
@ -1349,32 +1112,20 @@ void node::incoming_request(msg const& m, entry& e)
|
|||
m_table.find_node(target, n, 0);
|
||||
write_nodes_entry(reply, n);
|
||||
|
||||
dht_immutable_table_t::iterator imutable_it = m_immutable_table.end();
|
||||
|
||||
// if the get has a sequence number it must be for a mutable item
|
||||
// so don't bother searching the immutable table
|
||||
if (!msg_keys[0])
|
||||
imutable_it = m_immutable_table.find(target);
|
||||
|
||||
if (imutable_it != m_immutable_table.end())
|
||||
{
|
||||
dht_immutable_item const& f = imutable_it->second;
|
||||
reply["v"] = bdecode(f.value, f.value + f.size);
|
||||
if (!m_storage->get_immutable_item(target, reply)) // ok, check for a mutable one
|
||||
{
|
||||
m_storage->get_mutable_item(target, 0, true, reply);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dht_mutable_table_t::iterator mutable_it = m_mutable_table.find(target);
|
||||
if (mutable_it != m_mutable_table.end())
|
||||
{
|
||||
dht_mutable_item const& f = mutable_it->second;
|
||||
reply["seq"] = f.seq;
|
||||
if (!msg_keys[0] || boost::uint64_t(msg_keys[0].int_value()) < f.seq)
|
||||
{
|
||||
reply["v"] = bdecode(f.value, f.value + f.size);
|
||||
reply["sig"] = std::string(f.sig, f.sig + sizeof(f.sig));
|
||||
reply["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes));
|
||||
}
|
||||
}
|
||||
m_storage->get_mutable_item(target
|
||||
, msg_keys[0].int_value(), false
|
||||
, reply);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1401,7 +1152,4 @@ void node::incoming_request(msg const& m, entry& e)
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} } // namespace libtorrent::dht
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@ test-suite libtorrent :
|
|||
test_xml.cpp
|
||||
test_ip_filter.cpp
|
||||
test_hasher.cpp
|
||||
test_dht_storage.cpp
|
||||
test_dht.cpp
|
||||
test_block_cache.cpp
|
||||
test_peer_classes.cpp
|
||||
|
|
|
@ -149,6 +149,7 @@ test_primitives_SOURCES = \
|
|||
test_xml.cpp \
|
||||
test_ip_filter.cpp \
|
||||
test_hasher.cpp \
|
||||
test_dht_storage.cpp \
|
||||
test_dht.cpp \
|
||||
test_block_cache.cpp \
|
||||
test_peer_classes.cpp \
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
|
||||
Copyright (c) 2015, Alden Torres
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TORRENT_DISABLE_DHT
|
||||
|
||||
#include "libtorrent/config.hpp"
|
||||
#include "libtorrent/session.hpp"
|
||||
#include "libtorrent/kademlia/node.hpp" // for verify_message
|
||||
#include "libtorrent/bencode.hpp"
|
||||
#include "libtorrent/socket_io.hpp" // for hash_address
|
||||
#include "libtorrent/broadcast_socket.hpp" // for supports_ipv6
|
||||
#include "libtorrent/performance_counters.hpp" // for counters
|
||||
#include "libtorrent/random.hpp"
|
||||
#include "libtorrent/ed25519.hpp"
|
||||
|
||||
#include "libtorrent/kademlia/dht_storage.hpp"
|
||||
#include "libtorrent/kademlia/node_id.hpp"
|
||||
#include "libtorrent/kademlia/routing_table.hpp"
|
||||
#include "libtorrent/kademlia/item.hpp"
|
||||
#include "libtorrent/kademlia/dht_observer.hpp"
|
||||
#include "libtorrent/ed25519.hpp"
|
||||
#include <numeric>
|
||||
|
||||
#include "test.hpp"
|
||||
|
||||
using namespace libtorrent;
|
||||
using namespace libtorrent::dht;
|
||||
|
||||
namespace
|
||||
{
|
||||
dht_settings test_settings() {
|
||||
dht_settings sett;
|
||||
sett.max_torrents = 2;
|
||||
sett.max_dht_items = 2;
|
||||
return sett;
|
||||
}
|
||||
|
||||
static sha1_hash to_hash(char const *s) {
|
||||
sha1_hash ret;
|
||||
from_hex(s, 40, (char *) &ret[0]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
TORRENT_TEST(dht_storage)
|
||||
{
|
||||
dht_settings sett = test_settings();
|
||||
counters cnt;
|
||||
dht_storage_interface* s = dht_default_storage_constructor(node_id(0), sett, cnt);
|
||||
|
||||
TEST_CHECK(s != NULL);
|
||||
|
||||
sha1_hash n1 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401");
|
||||
sha1_hash n2 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee402");
|
||||
sha1_hash n3 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee403");
|
||||
sha1_hash n4 = to_hash("5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee404");
|
||||
|
||||
entry peers;
|
||||
s->get_peers(n1, false, false, peers);
|
||||
|
||||
TEST_CHECK(peers["n"].string().empty())
|
||||
TEST_CHECK(peers["values"].list().empty());
|
||||
|
||||
tcp::endpoint p1 = tcp::endpoint(address::from_string("124.31.75.21"), 1);
|
||||
tcp::endpoint p2 = tcp::endpoint(address::from_string("124.31.75.22"), 1);
|
||||
tcp::endpoint p3 = tcp::endpoint(address::from_string("124.31.75.23"), 1);
|
||||
tcp::endpoint p4 = tcp::endpoint(address::from_string("124.31.75.24"), 1);
|
||||
|
||||
s->announce_peer(n1, p1, "torrent_name", false);
|
||||
s->get_peers(n1, false, false, peers);
|
||||
TEST_EQUAL(peers["n"].string(), "torrent_name")
|
||||
TEST_EQUAL(peers["values"].list().size(), 1)
|
||||
|
||||
s->announce_peer(n2, p2, "torrent_name1", false);
|
||||
s->announce_peer(n2, p3, "torrent_name1", false);
|
||||
s->announce_peer(n3, p4, "torrent_name2", false);
|
||||
bool r = s->get_peers(n1, false, false, peers);
|
||||
TEST_CHECK(!r);
|
||||
|
||||
entry item;
|
||||
r = s->get_immutable_item(n4, item);
|
||||
TEST_CHECK(!r);
|
||||
|
||||
s->put_immutable_item(n4, "123", 3, address::from_string("124.31.75.21"));
|
||||
r = s->get_immutable_item(n4, item);
|
||||
TEST_CHECK(r);
|
||||
|
||||
s->put_immutable_item(n1, "123", 3, address::from_string("124.31.75.21"));
|
||||
s->put_immutable_item(n2, "123", 3, address::from_string("124.31.75.21"));
|
||||
s->put_immutable_item(n3, "123", 3, address::from_string("124.31.75.21"));
|
||||
r = s->get_immutable_item(n1, item);
|
||||
TEST_CHECK(!r);
|
||||
|
||||
r = s->get_mutable_item(n4, 0, false, item);
|
||||
TEST_CHECK(!r);
|
||||
|
||||
char public_key[item_pk_len];
|
||||
char signature[item_sig_len];
|
||||
s->put_mutable_item(n4, "123", 3, signature, 1, public_key, "salt", 4, address::from_string("124.31.75.21"));
|
||||
r = s->get_mutable_item(n4, 0, false, item);
|
||||
TEST_CHECK(r);
|
||||
|
||||
delete s;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue