Files
thehub/libs/p2p/PeerAddressDB.cpp
T

524 lines
16 KiB
C++
Raw Permalink Normal View History

2020-04-17 19:33:06 +02:00
/*
* This file is part of the Flowee project
* Copyright (C) 2020-2024 Tom Zander <tom@flowee.org>
2020-04-17 19:33:06 +02:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PeerAddressDB.h"
#include "ConnectionManager.h"
2024-01-28 21:25:26 +01:00
#include "Peer.h"
2020-04-17 19:33:06 +02:00
#include <Message.h>
2020-05-11 12:49:10 +02:00
#include <streaming/BufferPool.h>
2023-12-21 15:13:56 +01:00
#include <streaming/BufferPools.h>
2020-05-11 12:49:10 +02:00
#include <streaming/MessageBuilder.h>
#include <streaming/MessageParser.h>
2020-04-17 19:33:06 +02:00
#include <streaming/P2PParser.h>
2020-05-18 19:49:24 +02:00
#include <random.h>
2020-04-17 19:33:06 +02:00
2022-06-20 16:27:00 +02:00
#include <fstream>
2020-05-11 12:49:10 +02:00
enum SavingTags {
Separator = 0,
Hostname,
IPAddress,
Port,
Services,
LastConnected,
Punishment,
Segment,
EverConnected,
2024-01-26 13:32:39 +01:00
LastReceivedGoodHeaders,
2020-05-11 12:49:10 +02:00
// AskedAddr?
2024-01-26 13:32:39 +01:00
DBVersion = 40,
2020-05-11 12:49:10 +02:00
};
2020-04-17 19:33:06 +02:00
const EndPoint &PeerAddress::peerAddress() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.address;
}
2024-01-28 17:01:01 +01:00
void PeerAddress::successfullyConnected(uint64_t services)
2020-04-17 19:33:06 +02:00
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
2024-01-28 17:01:01 +01:00
time_t now = time(nullptr);
2020-04-17 19:33:06 +02:00
assert(now > 0);
i->second.lastConnected = static_cast<uint32_t>(now);
2024-01-28 17:01:01 +01:00
i->second.services = services;
2024-01-28 19:53:59 +01:00
if (i->second.punishment >= 200)
2020-04-17 19:33:06 +02:00
i->second.punishment -= 125;
i->second.inUse = true;
i->second.everConnected = true;
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
void PeerAddress::gotGoodHeaders()
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
2020-05-11 18:49:16 +02:00
time_t now = time(nullptr);
assert(now > 0);
i->second.lastConnected = static_cast<uint32_t>(now);
2024-01-26 13:32:39 +01:00
if (i->second.punishment > 300)
2023-04-23 11:58:18 +02:00
i->second.punishment -= 270;
2020-05-11 18:49:16 +02:00
i->second.lastReceivedGoodHeaders = now;
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
}
2020-04-17 19:33:06 +02:00
short PeerAddress::punishPeer(short amount)
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
int newPunishment = int(i->second.punishment) + amount;
if (i->second.punishment < PUNISHMENT_MAX && newPunishment >= PUNISHMENT_MAX) {
d->m_disabledPeerCount++;
} else if (i->second.punishment >= PUNISHMENT_MAX && newPunishment < PUNISHMENT_MAX) {
d->m_disabledPeerCount--;
}
i->second.punishment = std::min(newPunishment, 0xeFFF); // avoid overflow
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-17 19:33:06 +02:00
return i->second.punishment;
}
short PeerAddress::punishment() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.punishment;
}
2020-04-26 16:20:45 +02:00
void PeerAddress::resetPunishment()
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
i->second.punishment = 0;
2020-05-17 22:31:30 +02:00
// update lastConnected to now without setting 'everConnected' to
// at least remember when we reset the punishment.
i->second.lastConnected = static_cast<uint32_t>(time(nullptr));
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-26 16:20:45 +02:00
}
2020-04-17 19:33:06 +02:00
bool PeerAddress::isValid() const
{
return d && m_id >= 0 && d->m_nextPeerId > m_id;
}
bool PeerAddress::askedAddresses() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.askedAddr;
}
void PeerAddress::setAskedAddresses(bool on)
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
i->second.askedAddr = on;
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
2020-04-26 16:20:45 +02:00
bool PeerAddress::hasEverConnected() const
2020-04-17 19:33:06 +02:00
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.everConnected;
}
2020-11-05 21:53:08 +01:00
uint32_t PeerAddress::lastReceivedGoodHeaders() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
2020-05-11 18:49:16 +02:00
return i->second.lastReceivedGoodHeaders;
}
2020-04-17 19:33:06 +02:00
uint16_t PeerAddress::segment() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.segment;
}
void PeerAddress::setSegment(uint16_t segment)
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
i->second.segment = segment;
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
void PeerAddress::setInUse(bool on)
{
2024-01-26 13:32:39 +01:00
// this is an in-memory-only property
2020-04-17 19:33:06 +02:00
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
i->second.inUse = on;
}
void PeerAddress::setServices(uint64_t services)
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
i->second.services = services;
2024-01-26 13:32:39 +01:00
d->m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
uint32_t PeerAddress::lastConnected() const
{
auto i = d->m_peers.find(m_id);
assert(d->m_peers.end() != i);
return i->second.lastConnected;
}
PeerAddress::PeerAddress(PeerAddressDB *parent, int peerId)
: d(parent), m_id(peerId)
{
assert(parent);
}
int PeerAddress::id() const
{
return m_id;
}
// ////////////////////////////////////////
PeerAddressDB::PeerAddressDB(ConnectionManager *parent)
: m_parent(parent)
{
}
PeerAddress PeerAddressDB::findBest(uint64_t requiredServices, uint16_t segment)
{
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2020-04-17 19:33:06 +02:00
if (m_nextPeerId == 0)
return PeerAddress(this, -1);
2023-04-23 11:58:18 +02:00
int usefulCount = 0;
int best = -1;
2020-04-17 19:33:06 +02:00
int bestScore = 0;
2024-01-28 17:01:01 +01:00
const uint64_t now = time(nullptr);
2024-01-29 11:54:40 +01:00
for (int attempts = 0; attempts < 10000 && usefulCount < 1000; ++attempts) {
2023-04-23 11:58:18 +02:00
const int i = GetRandInt(m_nextPeerId - 1);
assert(i >= 0);
assert(m_peers.size() > static_cast<size_t>(i));
const PeerInfo &info = m_peers[i];
if (info.inUse)
continue;
2024-01-28 17:01:01 +01:00
if (info.everConnected && (info.services & requiredServices) != requiredServices)
2023-04-23 11:58:18 +02:00
continue;
if (segment != 0 && info.segment != 0 && segment != info.segment)
continue;
if (!m_supportIPv4Net && info.address.ipAddress.is_v4())
continue;
if (!m_supportIPv6Net && info.address.ipAddress.is_v6())
continue;
2020-04-17 19:33:06 +02:00
2023-04-23 11:58:18 +02:00
++usefulCount;
2024-01-28 17:01:01 +01:00
const uint64_t hoursAgoConnected = (now - info.lastConnected) / 3600;
2023-04-23 11:58:18 +02:00
/*
* A punishment-score (aka ban-score) isn't everything.
* If its been a long time since we connected, maybe the peer
* has changed to be better.
*/
int punishment = info.punishment;
if (info.everConnected && hoursAgoConnected > 7 * 24 * 6)
punishment /= 2;
int score = std::max(0, 1000 - punishment);
2020-04-17 19:33:06 +02:00
2023-04-23 11:58:18 +02:00
// If its all the same, I prefer nodes that we recently connected to (3 weeks)
if (info.everConnected)
2024-01-28 17:01:01 +01:00
score += static_cast<int32_t>(250 - std::min<uint64_t>(250, hoursAgoConnected / 2));
2023-04-23 11:58:18 +02:00
if (info.address.announcePort != m_defaultPortNr) // SLIGHTLY prefer non-default port
score += 50;
2020-04-17 19:33:06 +02:00
if (score > bestScore) {
bestScore = score;
best = i;
}
}
2023-04-23 11:58:18 +02:00
return PeerAddress(this, best);
2020-04-17 19:33:06 +02:00
}
void PeerAddressDB::resetAllStats()
2024-01-14 17:07:38 +01:00
{
std::unique_lock<std::mutex> lock(m_lock);
m_disabledPeerCount = 0;
2024-01-14 17:07:38 +01:00
for (auto iter = m_peers.begin(); iter != m_peers.end(); ++iter) {
iter->second.punishment = 0;
iter->second.everConnected = false;
iter->second.lastReceivedGoodHeaders = 0;
iter->second.segment = 0;
iter->second.askedAddr = false;
m_needsSave = true;
2024-01-14 17:07:38 +01:00
}
}
2020-04-17 19:33:06 +02:00
int PeerAddressDB::peerCount() const
{
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2020-04-17 19:33:06 +02:00
return static_cast<int>(m_peers.size()) - m_disabledPeerCount;
}
void PeerAddressDB::processAddressMessage(const Message &message, int sourcePeerId)
{
2024-01-28 21:25:26 +01:00
auto sourcePeer = m_parent->peer(sourcePeerId);
if (sourcePeer.get() == nullptr) // since disconnected.
return;
// make sure that this peer is actually following our chain.
const uint32_t goodHeaders = sourcePeer->peerAddress().lastReceivedGoodHeaders();
if (goodHeaders == 0) // ignore addresses from unvalidated peer
return;
// if we haven't validated them for 60 days, ignore message.
if (time(nullptr) - goodHeaders > 3600 * 24 * 60)
return;
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2020-04-17 19:33:06 +02:00
size_t oldCount = m_peers.size();
try {
Streaming::P2PParser parser(message);
const size_t count = parser.readCompactInt();
2023-04-17 19:25:07 +02:00
logInfo() << "Received" << count << "addresses" << "from peer:" << sourcePeerId;
2020-04-17 19:33:06 +02:00
for (size_t i = 0; i < count; ++i) {
PeerInfo info;
2024-01-26 13:32:39 +01:00
info.lastConnected = parser.readInt();
2020-04-17 19:33:06 +02:00
info.services = parser.readLong();
auto ip = parser.readBytes(16);
auto port = parser.readWordBE();
info.address = EndPoint::fromAddr(ip, port);
insert(info);
}
} catch (const std::runtime_error &err) {
logInfo() << "Failed to read address message from peer:" << sourcePeerId;
2023-05-08 11:34:47 +02:00
m_parent->punish(sourcePeerId, 250);
2020-04-17 19:33:06 +02:00
return;
}
2022-11-11 19:25:19 +01:00
if (oldCount != m_peers.size()) {
2021-02-03 14:38:23 +01:00
logInfo().nospace() << "We now have " << m_peers.size() << " addresses (thanks! peer: " << sourcePeerId << ")";
2022-11-11 19:25:19 +01:00
m_needsSave = true;
}
2020-04-17 19:33:06 +02:00
}
void PeerAddressDB::addOne(const EndPoint &endPoint)
{
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2020-04-17 19:33:06 +02:00
PeerInfo info;
info.address = endPoint;
info.services = 5;
insert(info);
2022-11-11 19:25:19 +01:00
m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
2020-05-11 12:49:10 +02:00
void PeerAddressDB::saveDatabase(const boost::filesystem::path &basedir)
{
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2022-11-11 19:25:19 +01:00
if (!m_needsSave)
return;
2023-12-21 15:13:56 +01:00
Streaming::MessageBuilder builder(Streaming::pool(m_peers.size() * 40));
2024-01-26 13:32:39 +01:00
builder.add(DBVersion, 1);
2020-05-11 12:49:10 +02:00
char ip[16];
for (const auto &item : m_peers) {
if (item.second.address.ipAddress.is_unspecified()) {
builder.add(Hostname, item.second.address.hostname);
} else {
item.second.address.toAddr(ip);
builder.addByteArray(IPAddress, ip, 16);
}
2020-10-29 21:47:53 +01:00
if (item.second.address.announcePort != m_defaultPortNr)
2020-05-11 12:49:10 +02:00
builder.add(Port, item.second.address.announcePort);
builder.add(Services, item.second.services);
builder.add(LastConnected, uint64_t(item.second.lastConnected));
if (item.second.punishment > 0)
builder.add(Punishment, item.second.punishment);
if (item.second.segment != 0)
builder.add(Segment, item.second.segment);
if (item.second.everConnected)
builder.add(EverConnected, true);
2020-05-11 18:49:16 +02:00
if (item.second.lastReceivedGoodHeaders)
builder.add(LastReceivedGoodHeaders, uint64_t(item.second.lastReceivedGoodHeaders));
2020-05-11 12:49:10 +02:00
builder.add(Separator, true); // separator
}
auto data = builder.buffer();
try {
2020-11-14 23:59:01 +01:00
boost::system::error_code error;
boost::filesystem::create_directories(basedir, error);
if (error && !boost::filesystem::exists(basedir) && !boost::filesystem::is_directory(basedir)) {
logFatal() << "P2P.PeerAddressDB can't save. Failed creating the dir:" << basedir.string();
return;
}
2020-05-11 12:49:10 +02:00
boost::filesystem::remove(basedir / "peers.dat~");
std::ofstream outFile((basedir / "peers.dat~").string());
outFile.write(data.begin(), data.size());
boost::filesystem::rename(basedir / "peers.dat~", basedir / "peers.dat");
2022-11-11 19:25:19 +01:00
m_needsSave = false;
2020-05-11 12:49:10 +02:00
} catch (const std::exception &e) {
logFatal() << "Failed to save the database. Reason:" << e.what();
}
}
void PeerAddressDB::loadDatabase(const boost::filesystem::path &basedir)
{
2020-05-16 10:28:08 +02:00
std::unique_lock<std::mutex> lock(m_lock);
2020-05-11 12:49:10 +02:00
std::ifstream in((basedir / "peers.dat").string());
if (!in.is_open())
return;
2020-05-19 08:27:53 +02:00
m_nextPeerId = 0;
2020-05-11 12:49:10 +02:00
const auto dataSize = boost::filesystem::file_size(basedir / "peers.dat");
2023-12-21 15:13:56 +01:00
auto pool = Streaming::pool(dataSize);
in.read(pool->begin(), dataSize);
Streaming::MessageParser parser(pool->commit(dataSize));
2020-05-11 12:49:10 +02:00
PeerInfo info;
2024-01-26 13:32:39 +01:00
int version = 0;
2020-05-11 12:49:10 +02:00
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Separator) {
if (info.address.isValid())
2020-05-19 08:27:53 +02:00
m_peers.insert(std::make_pair(m_nextPeerId++, info));
2020-05-11 12:49:10 +02:00
info = PeerInfo();
2024-01-26 13:32:39 +01:00
}
else if (parser.tag() == DBVersion) {
version = parser.intData();
2020-05-11 12:49:10 +02:00
}
else if (parser.tag() == IPAddress) {
2020-10-29 21:47:53 +01:00
info.address = EndPoint::fromAddr(parser.bytesData(), m_defaultPortNr);
2020-05-11 12:49:10 +02:00
}
else if (parser.tag() == Hostname) {
2020-10-29 21:47:53 +01:00
info.address = EndPoint(parser.stringData(), m_defaultPortNr);
2020-05-11 12:49:10 +02:00
}
else if (parser.tag() == Port) {
info.address.announcePort = info.address.peerPort = parser.intData();
}
else if (parser.tag() == Services) {
info.services = parser.longData();
}
else if (parser.tag() == LastConnected) {
info.lastConnected = parser.longData();
}
else if (parser.tag() == Punishment) {
info.punishment = parser.intData();
2023-02-20 18:15:22 +01:00
if (info.punishment >= PUNISHMENT_MAX)
m_disabledPeerCount++;
2020-05-11 12:49:10 +02:00
}
else if (parser.tag() == Segment) {
info.segment = parser.intData();
}
else if (parser.tag() == EverConnected) {
info.everConnected = parser.boolData();
}
2020-05-11 18:49:16 +02:00
else if (parser.tag() == LastReceivedGoodHeaders) {
info.lastReceivedGoodHeaders = parser.longData();
2020-05-11 12:49:10 +02:00
}
}
2022-11-11 19:25:19 +01:00
m_needsSave = false;
2023-02-20 18:15:22 +01:00
2024-01-26 13:32:39 +01:00
if (version == 0) {
// the old version had bugs, we will reset all the 'everConnected' to false
// and restart fresh with the punishment too.
for (auto iter = m_peers.begin(); iter != m_peers.end(); ++iter) {
iter->second.punishment = 0;
iter->second.everConnected = false;
}
m_disabledPeerCount = 0;
m_needsSave = true;
}
2023-02-20 18:15:22 +01:00
logInfo() << "Peer database loaded:" << m_peers.size() << "disabled peer count:" << m_disabledPeerCount;
2020-05-11 12:49:10 +02:00
}
2020-05-09 19:58:44 +02:00
void PeerAddressDB::insert(PeerInfo &pi)
2020-04-17 19:33:06 +02:00
{
2020-05-09 19:58:44 +02:00
if (pi.address.ipAddress.is_unspecified()) // try to see if hostname is an IP. If so, bypass DNS lookup
try { pi.address.ipAddress = boost::asio::ip::address::from_string(pi.address.hostname); } catch (...) {}
2020-05-10 00:46:41 +02:00
const bool hasIp = !pi.address.ipAddress.is_unspecified();
if (!hasIp && pi.address.hostname.empty())
return;
2020-04-17 19:33:06 +02:00
for (auto i = m_peers.begin(); i != m_peers.end(); ++i) {
2021-01-13 18:15:33 +01:00
if (hasIp && i->second.address.ipAddress == pi.address.ipAddress) {
if (i->second.punishment > 400)
2023-04-23 11:58:18 +02:00
i->second.punishment -= 100;
2020-05-10 00:46:41 +02:00
return;
2021-01-13 18:15:33 +01:00
}
2020-05-10 00:46:41 +02:00
if (!hasIp && i->second.address.hostname == pi.address.hostname)
2020-04-17 19:33:06 +02:00
return;
}
m_peers.insert(std::make_pair(m_nextPeerId++, pi));
2022-11-11 19:25:19 +01:00
m_needsSave = true;
2020-04-17 19:33:06 +02:00
}
2020-10-29 21:47:53 +01:00
2024-01-06 21:45:00 +01:00
bool PeerAddressDB::supportIPv6Net() const
{
return m_supportIPv6Net;
}
void PeerAddressDB::setSupportIPv6Net(bool on)
{
m_supportIPv6Net = on;
}
bool PeerAddressDB::supportIPv4Net() const
{
return m_supportIPv4Net;
}
void PeerAddressDB::setSupportIPv4Net(bool on)
{
m_supportIPv4Net = on;
}
2020-10-29 21:47:53 +01:00
int PeerAddressDB::defaultPortNr() const
{
return m_defaultPortNr;
}
void PeerAddressDB::setDefaultPortNr(int defaultPortNr)
{
m_defaultPortNr = defaultPortNr;
}
AddressDBStats PeerAddressDB::createStats() const
{
std::unique_lock<std::mutex> lock(m_lock);
AddressDBStats stats;
stats.count = m_peers.size();
2024-01-26 13:32:39 +01:00
stats.banned = 0;
stats.partialBanned = 0;
stats.ipv6Addresses = 0;
stats.everConnected = 0;
stats.usesIPv4 = m_supportIPv4Net;
stats.usesIPv6 = m_supportIPv6Net;
for (auto iter = m_peers.begin(); iter != m_peers.end(); ++iter) {
2024-01-26 13:32:39 +01:00
if (iter->second.everConnected)
stats.everConnected++;
2024-01-26 13:32:39 +01:00
const auto punishment = iter->second.punishment;
static_assert(PUNISHMENT_MAX > 500);
if (punishment >= PUNISHMENT_MAX)
stats.banned++;
2024-01-28 19:53:59 +01:00
else if (punishment >= 200)
stats.partialBanned++;
if (iter->second.address.ipAddress.is_v6())
stats.ipv6Addresses++;
}
return stats;
}