2015-09-08 22:12:54 +02:00
|
|
|
/*
|
|
|
|
|
2016-01-18 00:57:46 +01:00
|
|
|
Copyright (c) 2012-2016, Arvid Norberg, Alden Torres
|
2015-09-08 22:12:54 +02: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.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "libtorrent/kademlia/dht_storage.hpp"
|
2017-09-02 23:58:10 +02:00
|
|
|
#include "libtorrent/kademlia/dht_settings.hpp"
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-06-20 17:32:06 +02:00
|
|
|
#include <tuple>
|
2015-09-08 22:12:54 +02:00
|
|
|
#include <algorithm>
|
2016-05-25 06:31:52 +02:00
|
|
|
#include <utility>
|
2015-09-08 22:12:54 +02:00
|
|
|
#include <map>
|
|
|
|
#include <set>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <libtorrent/socket_io.hpp>
|
|
|
|
#include <libtorrent/aux_/time.hpp>
|
|
|
|
#include <libtorrent/config.hpp>
|
|
|
|
#include <libtorrent/bloom_filter.hpp>
|
|
|
|
#include <libtorrent/random.hpp>
|
2017-02-05 04:05:53 +01:00
|
|
|
#include <libtorrent/aux_/vector.hpp>
|
2017-02-07 06:22:30 +01:00
|
|
|
#include <libtorrent/aux_/numeric_cast.hpp>
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2017-04-12 19:00:57 +02:00
|
|
|
namespace libtorrent { namespace dht {
|
|
|
|
namespace {
|
|
|
|
|
2015-09-08 22:12:54 +02:00
|
|
|
// 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;
|
2016-09-23 06:26:07 +02:00
|
|
|
std::vector<peer_entry> peers4;
|
|
|
|
std::vector<peer_entry> peers6;
|
2015-09-08 22:12:54 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: 2 make this configurable in dht_settings
|
2017-08-03 12:57:15 +02:00
|
|
|
constexpr time_duration announce_interval = minutes(30);
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
struct dht_immutable_item
|
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
// the actual value
|
|
|
|
std::unique_ptr<char[]> value;
|
2015-09-08 22:12:54 +02:00
|
|
|
// 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;
|
2016-10-01 02:42:57 +02:00
|
|
|
// the last time we heard about this item
|
|
|
|
// the correct interpretation of this field
|
|
|
|
// requires a time reference
|
2015-09-08 22:12:54 +02:00
|
|
|
time_point last_seen;
|
|
|
|
// number of IPs in the bloom filter
|
2016-07-24 00:57:04 +02:00
|
|
|
int num_announcers = 0;
|
2015-09-08 22:12:54 +02:00
|
|
|
// size of malloced space pointed to by value
|
2016-07-24 00:57:04 +02:00
|
|
|
int size = 0;
|
2015-09-08 22:12:54 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
struct dht_mutable_item : dht_immutable_item
|
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
signature sig;
|
|
|
|
sequence_number seq;
|
|
|
|
public_key key;
|
|
|
|
std::string salt;
|
2015-09-08 22:12:54 +02:00
|
|
|
};
|
|
|
|
|
2016-10-01 02:42:57 +02:00
|
|
|
void set_value(dht_immutable_item& item, span<char const> buf)
|
|
|
|
{
|
|
|
|
int const size = int(buf.size());
|
|
|
|
if (item.size != size)
|
|
|
|
{
|
|
|
|
item.value.reset(new char[size]);
|
|
|
|
item.size = size;
|
|
|
|
}
|
2016-11-27 14:46:53 +01:00
|
|
|
std::memcpy(item.value.get(), buf.data(), buf.size());
|
2016-10-01 02:42:57 +02:00
|
|
|
}
|
|
|
|
|
2016-09-19 02:08:15 +02:00
|
|
|
void touch_item(dht_immutable_item& f, address const& addr)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
f.last_seen = aux::time_now();
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
// maybe increase num_announcers if we haven't seen this IP before
|
2016-09-16 03:13:43 +02:00
|
|
|
sha1_hash const iphash = hash_address(addr);
|
2016-09-19 02:08:15 +02:00
|
|
|
if (!f.ips.find(iphash))
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
f.ips.set(iphash);
|
|
|
|
++f.num_announcers;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-19 16:50:17 +01:00
|
|
|
// return true of the first argument is a better candidate for removal, i.e.
|
2015-09-08 22:12:54 +02:00
|
|
|
// less important to keep
|
|
|
|
struct immutable_item_comparator
|
|
|
|
{
|
2016-07-10 20:27:42 +02:00
|
|
|
explicit immutable_item_comparator(std::vector<node_id> const& node_ids) : m_node_ids(node_ids) {}
|
2016-07-10 13:34:45 +02:00
|
|
|
immutable_item_comparator(immutable_item_comparator const&) = default;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-07-24 00:57:04 +02:00
|
|
|
template <typename Item>
|
|
|
|
bool operator()(std::pair<node_id const, Item> const& lhs
|
|
|
|
, std::pair<node_id const, Item> const& rhs) const
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
int const l_distance = min_distance_exp(lhs.first, m_node_ids);
|
|
|
|
int const r_distance = min_distance_exp(rhs.first, m_node_ids);
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
// 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&);
|
|
|
|
|
2016-06-04 01:44:16 +02:00
|
|
|
std::vector<node_id> const& m_node_ids;
|
2015-09-08 22:12:54 +02:00
|
|
|
};
|
|
|
|
|
2016-06-04 01:44:16 +02:00
|
|
|
// picks the least important one (i.e. the one
|
|
|
|
// the fewest peers are announcing, and farthest
|
|
|
|
// from our node IDs)
|
|
|
|
template<class Item>
|
|
|
|
typename std::map<node_id, Item>::const_iterator pick_least_important_item(
|
|
|
|
std::vector<node_id> const& node_ids, std::map<node_id, Item> const& table)
|
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
return std::min_element(table.begin(), table.end()
|
2016-06-04 01:44:16 +02:00
|
|
|
, immutable_item_comparator(node_ids));
|
|
|
|
}
|
|
|
|
|
2017-01-29 02:24:53 +01:00
|
|
|
constexpr int sample_infohashes_interval_max = 21600;
|
|
|
|
constexpr int infohashes_sample_count_max = 20;
|
|
|
|
|
|
|
|
struct infohashes_sample
|
|
|
|
{
|
2017-02-05 04:05:53 +01:00
|
|
|
aux::vector<sha1_hash> samples;
|
2017-01-29 02:24:53 +01:00
|
|
|
time_point created = min_time();
|
|
|
|
|
|
|
|
int count() const { return int(samples.size()); }
|
|
|
|
};
|
|
|
|
|
2016-04-30 17:05:54 +02:00
|
|
|
class dht_default_storage final : public dht_storage_interface, boost::noncopyable
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
2016-07-10 20:27:42 +02:00
|
|
|
explicit dht_default_storage(dht_settings const& settings)
|
2016-06-04 01:44:16 +02:00
|
|
|
: m_settings(settings)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-06-04 01:44:16 +02:00
|
|
|
m_counters.reset();
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
2016-07-10 13:34:45 +02:00
|
|
|
~dht_default_storage() override = default;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
#ifndef TORRENT_NO_DEPRECATE
|
2016-04-30 17:05:54 +02:00
|
|
|
size_t num_torrents() const override { return m_map.size(); }
|
|
|
|
size_t num_peers() const override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
size_t ret = 0;
|
|
|
|
for (auto const& t : m_map)
|
2016-09-23 06:26:07 +02:00
|
|
|
ret += t.second.peers4.size() + t.second.peers6.size();
|
2015-09-08 22:12:54 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
2016-06-04 01:44:16 +02:00
|
|
|
void update_node_ids(std::vector<node_id> const& ids) override
|
|
|
|
{
|
|
|
|
m_node_ids = ids;
|
|
|
|
}
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-09-22 05:00:39 +02:00
|
|
|
bool get_peers(sha1_hash const& info_hash
|
2016-09-20 19:45:13 +02:00
|
|
|
, bool const noseed, bool const scrape, address const& requester
|
2016-04-30 17:05:54 +02:00
|
|
|
, entry& peers) const override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-20 05:47:17 +02:00
|
|
|
auto const i = m_map.find(info_hash);
|
|
|
|
if (i == m_map.end()) return int(m_map.size()) >= m_settings.max_torrents;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
torrent_entry const& v = i->second;
|
2016-09-23 06:26:07 +02:00
|
|
|
auto const& peersv = requester.is_v4() ? v.peers4 : v.peers6;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
if (!v.name.empty()) peers["n"] = v.name;
|
|
|
|
|
|
|
|
if (scrape)
|
|
|
|
{
|
|
|
|
bloom_filter<256> downloaders;
|
|
|
|
bloom_filter<256> seeds;
|
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
for (auto const& p : peersv)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-16 03:13:43 +02:00
|
|
|
sha1_hash const iphash = hash_address(p.addr.address());
|
|
|
|
if (p.seed) seeds.set(iphash);
|
2015-09-08 22:12:54 +02:00
|
|
|
else downloaders.set(iphash);
|
|
|
|
}
|
|
|
|
|
|
|
|
peers["BFpe"] = downloaders.to_string();
|
|
|
|
peers["BFsd"] = seeds.to_string();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-22 05:00:39 +02:00
|
|
|
tcp const protocol = requester.is_v4() ? tcp::v4() : tcp::v6();
|
2016-09-22 05:04:40 +02:00
|
|
|
int to_pick = m_settings.max_peers_reply;
|
2016-11-21 07:49:56 +01:00
|
|
|
TORRENT_ASSERT(to_pick >= 0);
|
2016-01-16 21:52:34 +01:00
|
|
|
// if these are IPv6 peers their addresses are 4x the size of IPv4
|
|
|
|
// so reduce the max peers 4 fold to compensate
|
|
|
|
// max_peers_reply should probably be specified in bytes
|
2016-09-23 06:26:07 +02:00
|
|
|
if (!peersv.empty() && protocol == tcp::v6())
|
2016-09-22 05:04:40 +02:00
|
|
|
to_pick /= 4;
|
2015-09-08 22:12:54 +02:00
|
|
|
entry::list_type& pe = peers["values"].list();
|
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
int candidates = int(std::count_if(peersv.begin(), peersv.end()
|
|
|
|
, [=](peer_entry const& e) { return !(noseed && e.seed); }));
|
|
|
|
|
2016-11-21 07:49:56 +01:00
|
|
|
to_pick = std::min(to_pick, candidates);
|
2016-09-23 06:26:07 +02:00
|
|
|
|
|
|
|
for (auto iter = peersv.begin(); to_pick > 0; ++iter)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-08-06 19:18:48 +02:00
|
|
|
// if the node asking for peers is a seed, skip seeds from the
|
|
|
|
// peer list
|
2015-09-08 22:12:54 +02:00
|
|
|
if (noseed && iter->seed) continue;
|
2016-08-06 19:18:48 +02:00
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
TORRENT_ASSERT(candidates >= to_pick);
|
2016-09-14 17:29:27 +02:00
|
|
|
|
2016-09-22 05:04:40 +02:00
|
|
|
// pick this peer with probability
|
|
|
|
// <peers left to pick> / <peers left in the set>
|
2016-11-21 07:49:56 +01:00
|
|
|
if (random(std::uint32_t(candidates--)) > std::uint32_t(to_pick))
|
2016-09-22 05:04:40 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
pe.push_back(entry());
|
|
|
|
std::string& str = pe.back().string();
|
|
|
|
|
|
|
|
str.resize(18);
|
|
|
|
std::string::iterator out = str.begin();
|
2016-09-19 02:08:15 +02:00
|
|
|
detail::write_endpoint(iter->addr, out);
|
2017-01-17 03:51:49 +01:00
|
|
|
str.resize(std::size_t(out - str.begin()));
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-09-22 05:04:40 +02:00
|
|
|
--to_pick;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
}
|
2016-09-20 19:45:13 +02:00
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
if (int(peersv.size()) < m_settings.max_peers)
|
2016-09-20 19:45:13 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// we're at the max peers stored for this torrent
|
|
|
|
// only send a write token if the requester is already in the set
|
|
|
|
// only check for a match on IP because the peer may be announcing
|
|
|
|
// a different port than the one it is using to send DHT messages
|
|
|
|
peer_entry requester_entry;
|
|
|
|
requester_entry.addr.address(requester);
|
2016-09-23 06:26:07 +02:00
|
|
|
auto requester_iter = std::lower_bound(peersv.begin(), peersv.end(), requester_entry);
|
|
|
|
return requester_iter == peersv.end()
|
2016-09-20 19:45:13 +02:00
|
|
|
|| requester_iter->addr.address() != requester;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void announce_peer(sha1_hash const& info_hash
|
|
|
|
, tcp::endpoint const& endp
|
2016-08-15 22:17:13 +02:00
|
|
|
, string_view name, bool const seed) override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const ti = m_map.find(info_hash);
|
2015-09-08 22:12:54 +02:00
|
|
|
torrent_entry* v;
|
|
|
|
if (ti == m_map.end())
|
|
|
|
{
|
2016-09-20 06:07:39 +02:00
|
|
|
if (int(m_map.size()) >= m_settings.max_torrents)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-20 06:07:39 +02:00
|
|
|
// we're at capacity, drop the announce
|
|
|
|
return;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
2016-09-20 06:07:39 +02:00
|
|
|
|
2015-09-17 17:11:46 +02:00
|
|
|
m_counters.torrents += 1;
|
2015-09-08 22:12:54 +02:00
|
|
|
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())
|
|
|
|
{
|
2016-08-15 22:17:13 +02:00
|
|
|
v->name = name.substr(0, 100).to_string();
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
auto& peersv = endp.protocol() == tcp::v4() ? v->peers4 : v->peers6;
|
|
|
|
|
2015-09-08 22:12:54 +02:00
|
|
|
peer_entry peer;
|
|
|
|
peer.addr = endp;
|
|
|
|
peer.added = aux::time_now();
|
|
|
|
peer.seed = seed;
|
2016-09-23 06:26:07 +02:00
|
|
|
auto i = std::lower_bound(peersv.begin(), peersv.end(), peer);
|
|
|
|
if (i != peersv.end() && i->addr == endp)
|
2015-09-22 05:49:22 +02:00
|
|
|
{
|
2016-09-23 06:26:07 +02:00
|
|
|
*i = peer;
|
2015-09-22 05:49:22 +02:00
|
|
|
}
|
2016-11-21 07:49:56 +01:00
|
|
|
else if (int(peersv.size()) >= m_settings.max_peers)
|
2015-12-19 08:09:06 +01:00
|
|
|
{
|
2016-09-20 06:07:39 +02:00
|
|
|
// we're at capacity, drop the announce
|
|
|
|
return;
|
2015-12-19 08:09:06 +01:00
|
|
|
}
|
2016-09-23 06:26:07 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
peersv.insert(i, peer);
|
|
|
|
m_counters.peers += 1;
|
|
|
|
}
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool get_immutable_item(sha1_hash const& target
|
2016-04-30 17:05:54 +02:00
|
|
|
, entry& item) const override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const i = m_immutable_table.find(target);
|
2015-09-08 22:12:54 +02:00
|
|
|
if (i == m_immutable_table.end()) return false;
|
|
|
|
|
2016-07-24 00:57:04 +02:00
|
|
|
item["v"] = bdecode(i->second.value.get()
|
|
|
|
, i->second.value.get() + i->second.size);
|
2015-09-08 22:12:54 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void put_immutable_item(sha1_hash const& target
|
2016-07-24 00:57:04 +02:00
|
|
|
, span<char const> buf
|
2016-04-30 17:05:54 +02:00
|
|
|
, address const& addr) override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-06-10 06:55:17 +02:00
|
|
|
TORRENT_ASSERT(!m_node_ids.empty());
|
2016-09-19 02:08:15 +02:00
|
|
|
auto i = m_immutable_table.find(target);
|
2015-09-08 22:12:54 +02:00
|
|
|
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)
|
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const j = pick_least_important_item(m_node_ids
|
2016-06-04 01:44:16 +02:00
|
|
|
, m_immutable_table);
|
2015-09-08 22:12:54 +02:00
|
|
|
|
|
|
|
TORRENT_ASSERT(j != m_immutable_table.end());
|
|
|
|
m_immutable_table.erase(j);
|
2015-09-17 17:11:46 +02:00
|
|
|
m_counters.immutable_data -= 1;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
dht_immutable_item to_add;
|
2016-10-01 02:42:57 +02:00
|
|
|
set_value(to_add, buf);
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-06-20 17:32:06 +02:00
|
|
|
std::tie(i, std::ignore) = m_immutable_table.insert(
|
2016-07-24 00:57:04 +02:00
|
|
|
std::make_pair(target, std::move(to_add)));
|
2015-09-17 17:11:46 +02:00
|
|
|
m_counters.immutable_data += 1;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
2016-05-17 15:24:06 +02:00
|
|
|
// std::fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size()));
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-09-19 02:08:15 +02:00
|
|
|
touch_item(i->second, addr);
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool get_mutable_item_seq(sha1_hash const& target
|
2016-07-24 00:57:04 +02:00
|
|
|
, sequence_number& seq) const override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const i = m_mutable_table.find(target);
|
2015-09-08 22:12:54 +02:00
|
|
|
if (i == m_mutable_table.end()) return false;
|
|
|
|
|
|
|
|
seq = i->second.seq;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_mutable_item(sha1_hash const& target
|
2016-09-19 02:08:15 +02:00
|
|
|
, sequence_number const seq, bool const force_fill
|
2016-04-30 17:05:54 +02:00
|
|
|
, entry& item) const override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const i = m_mutable_table.find(target);
|
2015-09-08 22:12:54 +02:00
|
|
|
if (i == m_mutable_table.end()) return false;
|
|
|
|
|
|
|
|
dht_mutable_item const& f = i->second;
|
2016-07-24 00:57:04 +02:00
|
|
|
item["seq"] = f.seq.value;
|
|
|
|
if (force_fill || (sequence_number(0) <= seq && seq < f.seq))
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-07-24 00:57:04 +02:00
|
|
|
item["v"] = bdecode(f.value.get(), f.value.get() + f.size);
|
|
|
|
item["sig"] = f.sig.bytes;
|
|
|
|
item["k"] = f.key.bytes;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void put_mutable_item(sha1_hash const& target
|
2016-07-24 00:57:04 +02:00
|
|
|
, span<char const> buf
|
|
|
|
, signature const& sig
|
2016-09-19 02:08:15 +02:00
|
|
|
, sequence_number const seq
|
2016-07-24 00:57:04 +02:00
|
|
|
, public_key const& pk
|
|
|
|
, span<char const> salt
|
2016-04-30 17:05:54 +02:00
|
|
|
, address const& addr) override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-06-10 06:55:17 +02:00
|
|
|
TORRENT_ASSERT(!m_node_ids.empty());
|
2016-09-19 02:08:15 +02:00
|
|
|
auto i = m_mutable_table.find(target);
|
2015-09-08 22:12:54 +02:00
|
|
|
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)
|
|
|
|
{
|
2016-09-19 02:08:15 +02:00
|
|
|
auto const j = pick_least_important_item(m_node_ids
|
2016-06-04 01:44:16 +02:00
|
|
|
, m_mutable_table);
|
2016-05-25 06:31:52 +02:00
|
|
|
|
2015-09-08 22:12:54 +02:00
|
|
|
TORRENT_ASSERT(j != m_mutable_table.end());
|
|
|
|
m_mutable_table.erase(j);
|
2015-09-17 17:11:46 +02:00
|
|
|
m_counters.mutable_data -= 1;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
dht_mutable_item to_add;
|
2016-10-01 02:42:57 +02:00
|
|
|
set_value(to_add, buf);
|
2015-09-08 22:12:54 +02:00
|
|
|
to_add.seq = seq;
|
2016-10-01 02:42:57 +02:00
|
|
|
to_add.salt = {salt.begin(), salt.end()};
|
2016-07-24 00:57:04 +02:00
|
|
|
to_add.sig = sig;
|
|
|
|
to_add.key = pk;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-06-20 17:32:06 +02:00
|
|
|
std::tie(i, std::ignore) = m_mutable_table.insert(
|
2016-07-24 00:57:04 +02:00
|
|
|
std::make_pair(target, std::move(to_add)));
|
2015-09-17 17:11:46 +02:00
|
|
|
m_counters.mutable_data += 1;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this is the case where we already
|
2016-07-24 00:57:04 +02:00
|
|
|
dht_mutable_item& item = i->second;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-07-24 00:57:04 +02:00
|
|
|
if (item.seq < seq)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-10-01 02:42:57 +02:00
|
|
|
set_value(item, buf);
|
2016-07-24 00:57:04 +02:00
|
|
|
item.seq = seq;
|
|
|
|
item.sig = sig;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-19 02:08:15 +02:00
|
|
|
touch_item(i->second, addr);
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
2017-01-29 02:24:53 +01:00
|
|
|
int get_infohashes_sample(entry& item) override
|
|
|
|
{
|
2017-02-07 06:22:30 +01:00
|
|
|
item["interval"] = aux::clamp(m_settings.sample_infohashes_interval
|
2017-01-29 02:24:53 +01:00
|
|
|
, 0, sample_infohashes_interval_max);
|
|
|
|
item["num"] = int(m_map.size());
|
|
|
|
|
|
|
|
refresh_infohashes_sample();
|
|
|
|
|
2017-02-05 04:05:53 +01:00
|
|
|
aux::vector<sha1_hash> const& samples = m_infohashes_sample.samples;
|
2017-01-29 02:24:53 +01:00
|
|
|
item["samples"] = span<char const>(
|
|
|
|
reinterpret_cast<char const*>(samples.data()), samples.size() * 20);
|
|
|
|
|
|
|
|
return m_infohashes_sample.count();
|
|
|
|
}
|
|
|
|
|
2016-04-30 17:05:54 +02:00
|
|
|
void tick() override
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
|
|
|
// look through all peers and see if any have timed out
|
2016-09-19 02:08:15 +02:00
|
|
|
for (auto i = m_map.begin(), end(m_map.end()); i != end;)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
|
|
|
torrent_entry& t = i->second;
|
2016-09-23 06:26:07 +02:00
|
|
|
purge_peers(t.peers4);
|
|
|
|
purge_peers(t.peers6);
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
if (!t.peers4.empty() || !t.peers6.empty())
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2015-12-19 08:09:06 +01:00
|
|
|
++i;
|
|
|
|
continue;
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
2015-12-19 08:09:06 +01:00
|
|
|
|
|
|
|
// if there are no more peers, remove the entry altogether
|
2017-03-12 16:34:42 +01:00
|
|
|
i = m_map.erase(i);
|
2015-12-19 08:09:06 +01:00
|
|
|
m_counters.torrents -= 1;// peers is decreased by purge_peers
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
2015-09-10 21:04:19 +02:00
|
|
|
|
|
|
|
if (0 == m_settings.item_lifetime) return;
|
|
|
|
|
2016-10-01 02:42:57 +02:00
|
|
|
time_point const now = aux::time_now();
|
2015-09-10 21:04:19 +02:00
|
|
|
time_duration lifetime = seconds(m_settings.item_lifetime);
|
|
|
|
// item lifetime must >= 120 minutes.
|
|
|
|
if (lifetime < minutes(120)) lifetime = minutes(120);
|
|
|
|
|
2016-09-19 02:08:15 +02:00
|
|
|
for (auto i = m_immutable_table.begin(); i != m_immutable_table.end();)
|
2015-09-10 21:04:19 +02:00
|
|
|
{
|
|
|
|
if (i->second.last_seen + lifetime > now)
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-12 16:34:42 +01:00
|
|
|
i = m_immutable_table.erase(i);
|
2015-09-10 21:04:19 +02:00
|
|
|
m_counters.immutable_data -= 1;
|
|
|
|
}
|
|
|
|
|
2016-09-19 02:08:15 +02:00
|
|
|
for (auto i = m_mutable_table.begin(); i != m_mutable_table.end();)
|
2015-09-10 21:04:19 +02:00
|
|
|
{
|
|
|
|
if (i->second.last_seen + lifetime > now)
|
|
|
|
{
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-12 16:34:42 +01:00
|
|
|
i = m_mutable_table.erase(i);
|
2015-09-10 21:04:19 +02:00
|
|
|
m_counters.mutable_data -= 1;
|
|
|
|
}
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
2016-07-10 02:10:38 +02:00
|
|
|
dht_storage_counters counters() const override
|
2015-09-17 17:11:46 +02:00
|
|
|
{
|
|
|
|
return m_counters;
|
|
|
|
}
|
|
|
|
|
2015-09-08 22:12:54 +02:00
|
|
|
private:
|
|
|
|
dht_settings const& m_settings;
|
2015-09-17 17:11:46 +02:00
|
|
|
dht_storage_counters m_counters;
|
2015-09-08 22:12:54 +02:00
|
|
|
|
2016-06-04 01:44:16 +02:00
|
|
|
std::vector<node_id> m_node_ids;
|
2016-09-19 02:08:15 +02:00
|
|
|
std::map<node_id, torrent_entry> m_map;
|
|
|
|
std::map<node_id, dht_immutable_item> m_immutable_table;
|
|
|
|
std::map<node_id, dht_mutable_item> m_mutable_table;
|
2015-09-22 05:49:22 +02:00
|
|
|
|
2017-01-29 02:24:53 +01:00
|
|
|
infohashes_sample m_infohashes_sample;
|
|
|
|
|
2016-09-23 06:26:07 +02:00
|
|
|
void purge_peers(std::vector<peer_entry>& peers)
|
2015-09-22 05:49:22 +02:00
|
|
|
{
|
2016-09-23 06:26:07 +02:00
|
|
|
auto now = aux::time_now();
|
|
|
|
auto new_end = std::remove_if(peers.begin(), peers.end()
|
|
|
|
, [=](peer_entry const& e)
|
2015-09-22 05:49:22 +02:00
|
|
|
{
|
2017-08-03 12:57:15 +02:00
|
|
|
return e.added + announce_interval * 3 / 2 < now;
|
2016-09-23 06:26:07 +02:00
|
|
|
});
|
|
|
|
|
2016-12-13 16:30:36 +01:00
|
|
|
m_counters.peers -= std::int32_t(std::distance(new_end, peers.end()));
|
2016-09-23 06:26:07 +02:00
|
|
|
peers.erase(new_end, peers.end());
|
2016-09-23 16:30:36 +02:00
|
|
|
// if we're using less than 1/4 of the capacity free up the excess
|
|
|
|
if (!peers.empty() && peers.capacity() / peers.size() >= 4u)
|
|
|
|
peers.shrink_to_fit();
|
2015-09-22 05:49:22 +02:00
|
|
|
}
|
2017-01-29 02:24:53 +01:00
|
|
|
|
|
|
|
void refresh_infohashes_sample()
|
|
|
|
{
|
|
|
|
time_point const now = aux::time_now();
|
2017-02-07 06:22:30 +01:00
|
|
|
int const interval = aux::clamp(m_settings.sample_infohashes_interval
|
2017-01-29 02:24:53 +01:00
|
|
|
, 0, sample_infohashes_interval_max);
|
|
|
|
|
2017-02-07 06:22:30 +01:00
|
|
|
int const max_count = aux::clamp(m_settings.max_infohashes_sample_count
|
2017-01-29 02:24:53 +01:00
|
|
|
, 0, infohashes_sample_count_max);
|
|
|
|
int const count = std::min(max_count, int(m_map.size()));
|
|
|
|
|
|
|
|
if (interval > 0
|
|
|
|
&& m_infohashes_sample.created + seconds(interval) > now
|
|
|
|
&& m_infohashes_sample.count() >= max_count)
|
|
|
|
return;
|
|
|
|
|
2017-02-05 04:05:53 +01:00
|
|
|
aux::vector<sha1_hash>& samples = m_infohashes_sample.samples;
|
2017-01-29 02:24:53 +01:00
|
|
|
samples.clear();
|
|
|
|
samples.reserve(count);
|
|
|
|
|
|
|
|
int to_pick = count;
|
|
|
|
int candidates = int(m_map.size());
|
|
|
|
|
|
|
|
for (auto const& t : m_map)
|
|
|
|
{
|
|
|
|
if (to_pick == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
TORRENT_ASSERT(candidates >= to_pick);
|
|
|
|
|
|
|
|
// pick this key with probability
|
|
|
|
// <keys left to pick> / <keys left in the set>
|
|
|
|
if (random(std::uint32_t(candidates--)) > std::uint32_t(to_pick))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
samples.push_back(t.first);
|
|
|
|
--to_pick;
|
|
|
|
}
|
|
|
|
|
|
|
|
TORRENT_ASSERT(int(samples.size()) == count);
|
|
|
|
m_infohashes_sample.created = now;
|
|
|
|
}
|
2015-09-08 22:12:54 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-06-04 01:44:16 +02:00
|
|
|
void dht_storage_counters::reset()
|
|
|
|
{
|
|
|
|
torrents = 0;
|
|
|
|
peers = 0;
|
|
|
|
immutable_data = 0;
|
|
|
|
mutable_data = 0;
|
|
|
|
}
|
|
|
|
|
2016-06-05 20:07:24 +02:00
|
|
|
std::unique_ptr<dht_storage_interface> dht_default_storage_constructor(
|
|
|
|
dht_settings const& settings)
|
2015-09-08 22:12:54 +02:00
|
|
|
{
|
2016-06-05 20:07:24 +02:00
|
|
|
return std::unique_ptr<dht_default_storage>(new dht_default_storage(settings));
|
2015-09-08 22:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} } // namespace libtorrent::dht
|