mirror of
https://github.com/savoirfairelinux/opendht.git
synced 2026-11-08 12:28:42 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bd959332a |
No files matched your search
@@ -357,6 +357,7 @@ private:
|
||||
struct LocalListener;
|
||||
struct Search;
|
||||
struct ValueStorage;
|
||||
struct StorageBucket;
|
||||
struct Listener;
|
||||
struct Storage;
|
||||
|
||||
@@ -384,6 +385,7 @@ private:
|
||||
RoutingTable buckets6 {};
|
||||
|
||||
std::map<InfoHash, Storage> store;
|
||||
std::map<SockAddr, StorageBucket, SockAddr::ipCmp> store_quota;
|
||||
size_t total_values {0};
|
||||
size_t total_store_size {0};
|
||||
size_t max_store_size {DEFAULT_STORAGE_LIMIT};
|
||||
@@ -420,10 +422,11 @@ private:
|
||||
|
||||
// Storage
|
||||
void storageAddListener(const InfoHash& id, const std::shared_ptr<Node>& node, size_t tid, Query&& = {});
|
||||
bool storageStore(const InfoHash& id, const std::shared_ptr<Value>& value, time_point created);
|
||||
bool storageStore(const InfoHash& id, const std::shared_ptr<Value>& value, time_point created, const SockAddr* sa = nullptr);
|
||||
void expireStorage();
|
||||
void storageChanged(const InfoHash& id, Storage& st, ValueStorage&);
|
||||
std::string printStorageLog(const decltype(store)::value_type&) const;
|
||||
void printStorageQuota(std::ostream& out, const decltype(store_quota)::value_type& ip) const;
|
||||
|
||||
/**
|
||||
* For a given storage, if values don't belong there anymore because this
|
||||
|
||||
@@ -595,7 +595,7 @@ private:
|
||||
};
|
||||
// global limiting should be triggered by at least 8 different IPs
|
||||
using IpLimiter = RateLimiter<MAX_REQUESTS_PER_SEC/8>;
|
||||
using IpLimiterMap = std::map<SockAddr, IpLimiter, cmpSockAddr>;
|
||||
using IpLimiterMap = std::map<SockAddr, IpLimiter, SockAddr::ipCmp>;
|
||||
IpLimiterMap address_rate_limiter {};
|
||||
RateLimiter<MAX_REQUESTS_PER_SEC> rate_limiter {};
|
||||
|
||||
|
||||
@@ -68,6 +68,36 @@ public:
|
||||
return print_addr(first, second);
|
||||
}
|
||||
sa_family_t getFamily() const { return second > sizeof(sa_family_t) ? first.ss_family : AF_UNSPEC; }
|
||||
|
||||
/**
|
||||
* A comparator to classify IP addresses, only considering the
|
||||
* first 64 bits in IPv6.
|
||||
*/
|
||||
struct ipCmp {
|
||||
bool operator()(const SockAddr& a, const SockAddr& b) {
|
||||
if (a.second != b.second)
|
||||
return a.second < b.second;
|
||||
socklen_t start, len;
|
||||
switch(a.getFamily()) {
|
||||
case AF_INET:
|
||||
start = offsetof(sockaddr_in, sin_addr);
|
||||
len = sizeof(in_addr);
|
||||
break;
|
||||
case AF_INET6:
|
||||
start = offsetof(sockaddr_in6, sin6_addr);
|
||||
// don't consider more than 64 bits (IPv6)
|
||||
len = 8;
|
||||
break;
|
||||
default:
|
||||
start = 0;
|
||||
len = a.second;
|
||||
break;
|
||||
}
|
||||
|
||||
return std::memcmp((uint8_t*)&a.first+start, (uint8_t*)&b.first+start, len) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
OPENDHT_PUBLIC bool operator==(const SockAddr& a, const SockAddr& b);
|
||||
|
||||
+150
-39
@@ -107,12 +107,43 @@ struct Dht::Listener {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tracks storage usage per IP or IP range
|
||||
*/
|
||||
class Dht::StorageBucket {
|
||||
public:
|
||||
void insert(const InfoHash& id, const Value& value, time_point expiration) {
|
||||
totalSize_ += value.size();
|
||||
storedValues_.emplace(expiration, std::pair<InfoHash, Value::Id>(id, value.id));
|
||||
}
|
||||
void erase(const InfoHash& id, const Value& value, time_point expiration) {
|
||||
auto size = value.size();
|
||||
totalSize_ -= size;
|
||||
auto range = storedValues_.equal_range(expiration);
|
||||
for (auto rit = range.first; rit != range.second;) {
|
||||
if (rit->second.first == id && rit->second.second == value.id) {
|
||||
storedValues_.erase(rit);
|
||||
break;
|
||||
} else
|
||||
++rit;
|
||||
}
|
||||
}
|
||||
size_t size() const { return totalSize_; }
|
||||
std::pair<InfoHash, Value::Id> getOldest() const { return storedValues_.begin()->second; }
|
||||
private:
|
||||
std::multimap<time_point, std::pair<InfoHash, Value::Id>> storedValues_;
|
||||
size_t totalSize_ {0};
|
||||
};
|
||||
|
||||
struct Dht::ValueStorage {
|
||||
std::shared_ptr<Value> data {};
|
||||
time_point created {};
|
||||
time_point expiration {};
|
||||
StorageBucket* store_bucket {nullptr};
|
||||
|
||||
ValueStorage() {}
|
||||
ValueStorage(const std::shared_ptr<Value>& v, time_point t) : data(v), created(t) {}
|
||||
ValueStorage(const std::shared_ptr<Value>& v, time_point t, time_point e)
|
||||
: data(v), created(t), expiration(e) {}
|
||||
};
|
||||
|
||||
struct Dht::Storage {
|
||||
@@ -180,7 +211,7 @@ struct Dht::Storage {
|
||||
* change_value_num: change of value number (0 or 1)
|
||||
*/
|
||||
std::tuple<ValueStorage*, ssize_t, ssize_t>
|
||||
store(const std::shared_ptr<Value>& value, time_point created, ssize_t size_left);
|
||||
store(const InfoHash& id, const std::shared_ptr<Value>&, time_point created, time_point expiration, StorageBucket*);
|
||||
|
||||
/**
|
||||
* Refreshes the time point of the value's lifetime begining.
|
||||
@@ -198,7 +229,9 @@ struct Dht::Storage {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<ssize_t, ssize_t> expire(const std::map<ValueType::Id, ValueType>& types, time_point now);
|
||||
std::pair<ssize_t, ssize_t> remove(const InfoHash& id, Value::Id);
|
||||
|
||||
std::pair<ssize_t, ssize_t> expire(const InfoHash& id, time_point now);
|
||||
|
||||
private:
|
||||
Storage(const Storage&) = delete;
|
||||
@@ -2225,17 +2258,14 @@ Dht::storageChanged(const InfoHash& id, Storage& st, ValueStorage& v)
|
||||
}
|
||||
|
||||
bool
|
||||
Dht::storageStore(const InfoHash& id, const std::shared_ptr<Value>& value, time_point created)
|
||||
Dht::storageStore(const InfoHash& id, const std::shared_ptr<Value>& value, time_point created, const SockAddr* sa)
|
||||
{
|
||||
const auto& now = scheduler.time();
|
||||
auto expiration = created + getType(value->type).expiration;
|
||||
if (expiration < now) {
|
||||
using namespace std::chrono;
|
||||
DHT_LOG.w(id, "[store %s] won't store already expired value (created %lld s ago, expired %lld s ago)",
|
||||
id.toString().c_str(), duration_cast<seconds>(now - created).count(),
|
||||
duration_cast<seconds>(now - expiration).count());
|
||||
created = std::min(created, now);
|
||||
auto expiration = created + getType(value->id).expiration;
|
||||
|
||||
if ( expiration < now )
|
||||
return false;
|
||||
}
|
||||
|
||||
auto st = store.find(id);
|
||||
if (st == store.end()) {
|
||||
@@ -2247,45 +2277,80 @@ Dht::storageStore(const InfoHash& id, const std::shared_ptr<Value>& value, time_
|
||||
scheduler.add(st->second.maintenance_time, std::bind(&Dht::dataPersistence, this, id));
|
||||
}
|
||||
|
||||
auto store = st->second.store(value, created, max_store_size - total_store_size);
|
||||
if (std::get<0>(store)) {
|
||||
StorageBucket* store_bucket {nullptr};
|
||||
if (sa)
|
||||
store_bucket = &store_quota.emplace(*sa, StorageBucket{}).first->second;
|
||||
|
||||
auto store = st->second.store(id, value, created, expiration, store_bucket);
|
||||
if (auto vs = std::get<0>(store)) {
|
||||
total_store_size += std::get<1>(store);
|
||||
total_values += std::get<2>(store);
|
||||
storageChanged(id, st->second, *std::get<0>(store));
|
||||
if (total_store_size > max_store_size) {
|
||||
expireStorage();
|
||||
}
|
||||
storageChanged(id, st->second, *vs);
|
||||
}
|
||||
|
||||
return std::get<0>(store);
|
||||
}
|
||||
|
||||
std::tuple<Dht::ValueStorage*, ssize_t, ssize_t>
|
||||
Dht::Storage::store(const std::shared_ptr<Value>& value, time_point created, ssize_t size_left) {
|
||||
|
||||
Dht::Storage::store(const InfoHash& id, const std::shared_ptr<Value>& value, time_point created, time_point expiration, StorageBucket* sb)
|
||||
{
|
||||
auto it = std::find_if (values.begin(), values.end(), [&](const ValueStorage& vr) {
|
||||
return vr.data == value || vr.data->id == value->id;
|
||||
});
|
||||
size_t size_new = value->size();
|
||||
if (it != values.end()) {
|
||||
/* Already there, only need to refresh */
|
||||
it->created = created;
|
||||
ssize_t size_diff = value->size() - it->data->size();
|
||||
if (size_diff <= size_left and it->data != value) {
|
||||
//DHT_LOG_DEBUG("Updating %s -> %s", id.toString().c_str(), value->toString().c_str());
|
||||
size_t size_old = it->data->size();
|
||||
ssize_t size_diff = size_new - (ssize_t)size_old;
|
||||
if (it->data != value) {
|
||||
//DHT_LOG.DEBUG("Updating %s -> %s", id.toString().c_str(), value->toString().c_str());
|
||||
// clear quota for previous value
|
||||
if (it->store_bucket)
|
||||
it->store_bucket->erase(id, *value, it->expiration);
|
||||
it->expiration = expiration;
|
||||
// update quota for new value
|
||||
it->store_bucket = sb;
|
||||
if (sb)
|
||||
sb->insert(id, *value, expiration);
|
||||
it->data = value;
|
||||
total_size += size_diff;
|
||||
return std::make_tuple(&(*it), size_diff, 0);
|
||||
}
|
||||
return std::make_tuple(nullptr, 0, 0);
|
||||
} else {
|
||||
//DHT_LOG_DEBUG("Storing %s -> %s", id.toString().c_str(), value->toString().c_str());
|
||||
ssize_t size = value->size();
|
||||
if (size <= size_left and values.size() < MAX_VALUES) {
|
||||
total_size += size;
|
||||
values.emplace_back(value, created);
|
||||
return std::make_tuple(&values.back(), size, 1);
|
||||
//DHT_LOG.DEBUG("Storing %s -> %s", id.toString().c_str(), value->toString().c_str());
|
||||
if (values.size() < MAX_VALUES) {
|
||||
total_size += size_new;
|
||||
values.emplace_back(value, created, expiration);
|
||||
values.back().store_bucket = sb;
|
||||
if (sb)
|
||||
sb->insert(id, *value, expiration);
|
||||
return std::make_tuple(&values.back(), size_new, 1);
|
||||
}
|
||||
return std::make_tuple(nullptr, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ssize_t, ssize_t>
|
||||
Dht::Storage::remove(const InfoHash& id, Value::Id vid)
|
||||
{
|
||||
auto it = std::find_if (values.begin(), values.end(), [&](const ValueStorage& vr) {
|
||||
return vr.data->id == vid;
|
||||
});
|
||||
if (it == values.end())
|
||||
return {0, 0};
|
||||
ssize_t size = it->data->size();
|
||||
if (it->store_bucket)
|
||||
it->store_bucket->erase(id, *it->data, it->expiration);
|
||||
total_size -= size;
|
||||
values.erase(it);
|
||||
return {-size, -1};
|
||||
}
|
||||
|
||||
std::pair<ssize_t, ssize_t>
|
||||
Dht::Storage::clear()
|
||||
{
|
||||
@@ -2325,8 +2390,11 @@ void
|
||||
Dht::expireStorage()
|
||||
{
|
||||
const auto& now = scheduler.time();
|
||||
|
||||
// removing expired values
|
||||
auto i = store.begin();
|
||||
while (i != store.end()) {
|
||||
// iterate over node listener entries
|
||||
for (auto nl_it = i->second.listeners.begin(); nl_it != i->second.listeners.end();) {
|
||||
auto& node_listeners = nl_it->second;
|
||||
for (auto l = node_listeners.cbegin(); l != node_listeners.cend();) {
|
||||
@@ -2345,9 +2413,12 @@ Dht::expireStorage()
|
||||
++nl_it;
|
||||
}
|
||||
|
||||
auto stats = i->second.expire(types, now);
|
||||
auto stats = i->second.expire(i->first, now);
|
||||
total_store_size += stats.first;
|
||||
total_values += stats.second;
|
||||
if (stats.second) {
|
||||
DHT_LOG.d(i->first, "[store %s] discarded %ld expired values (%ld bytes)", i->first.toString().c_str(), -stats.second, -stats.first);
|
||||
}
|
||||
|
||||
if (i->second.empty() && i->second.listeners.empty() && i->second.local_listeners.empty()) {
|
||||
DHT_LOG.d(i->first, "[store %s] discarding empty storage", i->first.toString().c_str());
|
||||
@@ -2356,24 +2427,48 @@ Dht::expireStorage()
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
// remove more values if storage limit is exceeded
|
||||
while (total_store_size > max_store_size) {
|
||||
// find IP using the most storage
|
||||
if (store_quota.empty()) {
|
||||
DHT_LOG.w("No space left: local data consumes all the quota!");
|
||||
break;
|
||||
}
|
||||
decltype(store_quota)::iterator largest = store_quota.end();
|
||||
for (auto it = store_quota.begin(); it != store_quota.end(); ++it) {
|
||||
if (largest == store_quota.end() or it->second.size() > largest->second.size())
|
||||
largest = it;
|
||||
}
|
||||
DHT_LOG.w("No space left: discarding value of largest consumer %s", largest->first.toString().c_str());
|
||||
while (true) {
|
||||
auto exp_value = largest->second.getOldest();
|
||||
auto storage = store.find(exp_value.first);
|
||||
if (storage != store.end()) {
|
||||
auto ret = storage->second.remove(exp_value.first, exp_value.second);
|
||||
total_store_size += ret.first;
|
||||
total_values += ret.second;
|
||||
DHT_LOG.w("Discarded %ld bytes, still %ld used", largest->first.toString().c_str(), total_store_size);
|
||||
if (ret.second)
|
||||
break;
|
||||
} else
|
||||
std::cout << "exp_value not found " << exp_value.first << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ssize_t, ssize_t>
|
||||
Dht::Storage::expire(const std::map<ValueType::Id, ValueType>& types, time_point now)
|
||||
Dht::Storage::expire(const InfoHash& id, time_point now)
|
||||
{
|
||||
auto r = std::partition(values.begin(), values.end(), [&](const ValueStorage& v) {
|
||||
if (!v.data) return false; // should not happen
|
||||
auto type_it = types.find(v.data->type);
|
||||
const ValueType& type = (type_it == types.end()) ? ValueType::USER_DATA : type_it->second;
|
||||
bool expired = v.created + type.expiration < now;
|
||||
//if (expired)
|
||||
// DHT_LOG_DEBUG("Discarding expired value %s", v.data->toString().c_str());
|
||||
return !expired;
|
||||
return v.expiration > now;
|
||||
});
|
||||
ssize_t del_num = std::distance(r, values.end());
|
||||
ssize_t size_diff {};
|
||||
std::for_each(r, values.end(), [&](const ValueStorage& v){
|
||||
std::for_each(r, values.end(), [&](const ValueStorage& v) {
|
||||
size_diff -= v.data->size();
|
||||
if (v.store_bucket)
|
||||
v.store_bucket->erase(id, *v.data, v.expiration);
|
||||
});
|
||||
total_size += size_diff;
|
||||
values.erase(r, values.end());
|
||||
@@ -2633,13 +2728,28 @@ Dht::dumpTables() const
|
||||
DHT_LOG.d("%s", out.str().c_str());
|
||||
}
|
||||
|
||||
void
|
||||
Dht::printStorageQuota(std::ostream& out, const decltype(store_quota)::value_type& ip) const
|
||||
{
|
||||
out << "IP " << ip.first.toString() << " uses " << ip.second.size() << " bytes" << std::endl;
|
||||
}
|
||||
|
||||
std::string
|
||||
Dht::getStorageLog() const
|
||||
{
|
||||
std::stringstream out;
|
||||
for (const auto& s : store)
|
||||
out << printStorageLog(s);
|
||||
out << "Total " << store.size() << " storages, " << total_values << " values (" << (total_store_size/1024) << " KB)" << std::endl;
|
||||
out << "Total " << store.size() << " storages, " << total_values << " values (";
|
||||
if (total_store_size < 1024)
|
||||
out << total_store_size << " bytes)";
|
||||
else
|
||||
out << (total_store_size/1024) << " KB)";
|
||||
out << std::endl << std::endl;
|
||||
for (const auto& ip : store_quota) {
|
||||
printStorageQuota(out, ip);
|
||||
}
|
||||
out << std::endl;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
@@ -2742,7 +2852,8 @@ Dht::Dht() : store(), scheduler(DHT_LOG), network_engine(DHT_LOG, scheduler) {}
|
||||
Dht::Dht(int s, int s6, Config config)
|
||||
: myid(config.node_id),
|
||||
is_bootstrap(config.is_bootstrap),
|
||||
maintain_storage(config.maintain_storage), store(), scheduler(DHT_LOG),
|
||||
maintain_storage(config.maintain_storage), store(), store_quota(),
|
||||
scheduler(DHT_LOG),
|
||||
network_engine(myid, config.network, s, s6, DHT_LOG, scheduler,
|
||||
std::bind(&Dht::onError, this, _1, _2),
|
||||
std::bind(&Dht::onNewNode, this, _1, _2),
|
||||
@@ -3377,7 +3488,7 @@ Dht::onAnnounce(std::shared_ptr<Node> node,
|
||||
if (type.editPolicy(hash, lv, vc, node->id, (sockaddr*)&node->addr.first, node->addr.second)) {
|
||||
DHT_LOG.d(hash, node->id, "[store %s] editing %s",
|
||||
hash.toString().c_str(), vc->toString().c_str());
|
||||
storageStore(hash, vc, created);
|
||||
storageStore(hash, vc, created, &node->addr);
|
||||
} else {
|
||||
DHT_LOG.d(hash, node->id, "[store %s] rejecting edition of %s because of storage policy",
|
||||
hash.toString().c_str(), vc->toString().c_str());
|
||||
@@ -3388,7 +3499,7 @@ Dht::onAnnounce(std::shared_ptr<Node> node,
|
||||
const auto& type = getType(vc->type);
|
||||
if (type.storePolicy(hash, vc, node->id, (sockaddr*)&node->addr.first, node->addr.second)) {
|
||||
DHT_LOG.d(hash, node->id, "[store %s] storing %s", hash.toString().c_str(), vc->toString().c_str());
|
||||
storageStore(hash, vc, created);
|
||||
storageStore(hash, vc, created, &node->addr);
|
||||
} else {
|
||||
DHT_LOG.d(hash, node->id, "[store %s] rejecting storage of %s",
|
||||
hash.toString().c_str(), vc->toString().c_str());
|
||||
|
||||
Reference in new issue
Block a user