/* * This file is part of the Flowee project * Copyright (C) 2020-2024 Tom Zander * * 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 . */ #ifndef FLOWEE_PEERADDRESSDB_H #define FLOWEE_PEERADDRESSDB_H #include #include #include #include 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 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 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