Files
thehub/libs/p2p/PeerAddressDB.h
tomFlowee 1125e5d329 Add new field 'tried'.
For addresses that we tried to connect to, even though it may
have failed to actually reach the handshake phase.
2024-02-09 18:52:04 +01:00

263 lines
8.2 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2020-2024 Tom Zander <tom@flowee.org>
*
* 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/>.
*/
#ifndef FLOWEE_PEERADDRESSDB_H
#define FLOWEE_PEERADDRESSDB_H
#include <NetworkEndPoint.h>
#include <boost/filesystem.hpp>
#include <map>
#include <mutex>
class PeerAddressDB;
class Message;
class ConnectionManager;
// this is the cut-off punishment score that makes a peer get
// insta disconnected.
constexpr int PUNISHMENT_MAX = 1000;
/**
* Represents a single peer we could or have connected to.
*/
class PeerAddress
{
public:
/// returns the internet address over which the peer can be reached
const EndPoint &peerAddress() const;
/**
* Calling this updates the peer database to register that we have successfully connected to this peer.
* @param the services that the remote peer advertised in the version handshake.
*/
void successfullyConnected(uint64_t services);
/**
* Calling this updates the database to register that we received good headers.
*/
void gotGoodHeaders();
/**
* Register a punishment for this peer.
* This is used to keep track of how much this peer misbehaves. 1000 points means they are banned.
*/
short punishPeer(short amount);
/**
* Current level of punishment.
*/
short punishment() const;
/**
* Reset the punishment.
*/
void resetPunishment();
/**
* Returns simply if this instance represents an actual entry in the peer adddress DB.
*/
bool isValid() const;
/// Returns true if an p2p message for addresses was received.
bool askedAddresses() const;
/// Register if we asked for adddresses.
void setAskedAddresses(bool on);
/**
* Returns true if we have ever made a full connection to this peer.
* Notice that connecting to a peer on a different chain still may return true here.
* @see successfullyConnected()
*/
bool hasEverConnected() const;
/**
* Return a unit timestamp (sec since epoch) when we received good headers from this peer.
* Or 0 if the peer never did.
*/
uint32_t lastReceivedGoodHeaders() const;
/**
* THe PrivacySegment ID that this peer is associated with.
* For privacy reasons we only allow this peer to see this privacy segments bloom filter.
*/
uint16_t segment() const;
/**
* Set the privacy segment ID.
*/
void setSegment(uint16_t segment);
/**
* Register on the peerAddressDB (non persistent) that this peer is being used.
* This typically menas we are connecting or connected to the peer.
*/
void setInUse(bool on);
/**
* Remember which services the peer announces.
* @see Peer::services()
*/
void setServices(uint64_t services);
/**
* Return the unix date (sec since epoch) when we last made a successsful full connection.
*/
uint32_t lastConnected() const;
/// \internal This returns the peerAddressDB internal ID for this specific peer.
int id() const;
protected:
friend class PeerAddressDB;
explicit PeerAddress(PeerAddressDB *parent, int peerId);
private:
PeerAddressDB *d;
int m_id;
};
struct AddressDBStats
{
int count; ///< Total count of IPs known by the DB
int tried; ///< Total count of IPs we tried to connect to
int banned; ///< The number of IPs that have higher/equal PUNISHMENT_MAX punishment
int partialBanned; ///< Number with > 500 punishment (includes the 'banned' ones)
int ipv6Addresses; ///< Number IPs using ipv6 addressing scheme
int everConnected; ///< Number of peers that were ever connected to.
bool usesIPv4;
bool usesIPv6;
};
/**
* The addresses database to collect and manage all the peeer addresses we learned about.
* In the p2p net, peers notify each other about peers they recently successsfully connected to.
* We store and score and manage those addresses for ourselves.
*
* This is backed by the on-disk file 'peers.dat'
*/
class PeerAddressDB
{
public:
PeerAddressDB(ConnectionManager *parent);
/**
* Try to find a (semi)random peer that has good specs to connect to.
* The peer returned will have a good score (low punishment) and not already in use.
*
* \param required services. A mask of services that a peer must have.
*
* \param segment The segment (privacySegment ID) is matched or a peer with no segment is returned,
* we guarentee this method never returns a match that has a different segment.
*
* Please note that the PeerAddressDB instance has to outlive the PeerAddress instance.
*/
PeerAddress findBest(uint64_t requiredServices = 0, uint16_t segment = 0);
/**
* Reset all properties on our list of addresses.
*/
void resetAllStats();
/// return number of peers this DB holds.
int peerCount() const;
void processAddressMessage(const Message &message, int sourcePeerId);
/**
* Try to add a peer to the DB.
* Duplicates are Ok, we notice them and decrese their punishment if multiple sources repeat it.
*/
void addOne(const EndPoint &endPoint);
/// \internal
inline PeerAddress peer(int id) {
assert(0 <= id);
return PeerAddress(this, id);
}
void saveDatabase(const boost::filesystem::path &basedir);
void loadDatabase(const boost::filesystem::path &basedir);
/// return the current network's default port number for peers that don't supply one.
int defaultPortNr() const;
/**
* Peers that we get from DNS do not have a port number, this is used to store
* the current network's default port number for those peers.
*/
void setDefaultPortNr(int defaultPortNr);
/**
* This checks all known data and returns basic statistics.
*/
AddressDBStats createStats() const;
/**
* If true, findBest() will include IPv4 options in its reply.
*/
bool supportIPv4Net() const;
void setSupportIPv4Net(bool on);
/**
* If true, findBest() will include IPv6 options in its reply.
*/
bool supportIPv6Net() const;
void setSupportIPv6Net(bool on);
private:
friend class PeerAddress;
struct PeerInfo {
EndPoint address;
uint64_t services = 0;
uint32_t lastConnected = 0;
uint32_t lastReceivedGoodHeaders = 0;
short punishment = 0;
uint16_t segment = 0;
bool everConnected = false; // if false, lastConnected comes from untrusted peers
// Not persisted properties.
bool inUse = false;
bool askedAddr = false;
};
void insert(PeerInfo &pi);
mutable std::mutex m_lock;
std::map<int, PeerInfo> m_peers;
int m_nextPeerId = 0;
int m_disabledPeerCount = 0; // amount of peers with punishment >= 1000
int m_defaultPortNr = 8333;
bool m_needsSave = false;
bool m_supportIPv4Net = true;
bool m_supportIPv6Net = false;
ConnectionManager *m_parent;
};
inline Log::Item operator<<(Log::Item item, const PeerAddress &pa) {
if (item.isEnabled()) {
const bool old = item.useSpace();
item.nospace() << pa.id() << "-{";
const EndPoint &ep = pa.peerAddress();
if (ep.ipAddress.is_unspecified())
item << ep.hostname;
else
item << ep.ipAddress.to_string().c_str();
if (ep.announcePort != 8333 && ep.announcePort != 28333)
item << ':' << ep.announcePort;
item << '}';
if (old)
return item.space();
}
return item;
}
inline Log::SilentItem operator<<(Log::SilentItem item, const PeerAddress&) { return item; }
#endif