mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-17 02:47:51 +01:00
Improved remote peer NAT detection. Placeholder for sending the Traverse message. Fix in handling incoming FIND_PEER request. Do not return self as closest node. Do not share peers for which the internal port is not known.
257 lines
8.3 KiB
Go
257 lines
8.3 KiB
Go
/*
|
|
File Name: Peer ID.go
|
|
Copyright: 2021 Peernet s.r.o.
|
|
Author: Peter Kleissner
|
|
*/
|
|
|
|
package core
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/PeernetOfficial/core/dht"
|
|
"github.com/btcsuite/btcd/btcec"
|
|
)
|
|
|
|
// peerID is the current peers ID. It is a ECDSA (secp256k1) 257-bit public key.
|
|
// The node ID is the blake3 hash of the public key compressed form.
|
|
var peerPrivateKey *btcec.PrivateKey
|
|
var peerPublicKey *btcec.PublicKey
|
|
var nodeID []byte
|
|
|
|
func initPeerID() {
|
|
peerList = make(map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo)
|
|
|
|
// load existing key from config, if available
|
|
if len(config.PrivateKey) > 0 {
|
|
configPK, err := hex.DecodeString(config.PrivateKey)
|
|
if err == nil {
|
|
peerPrivateKey, peerPublicKey = btcec.PrivKeyFromBytes(btcec.S256(), configPK)
|
|
nodeID = publicKey2NodeID(peerPublicKey)
|
|
|
|
if config.AutoUpdateSeedList {
|
|
configUpdateSeedList()
|
|
}
|
|
return
|
|
}
|
|
|
|
log.Printf("Private key in config is corrupted! Error: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
// if the peer ID is empty, create a new user public-private key pair
|
|
var err error
|
|
peerPrivateKey, peerPublicKey, err = Secp256k1NewPrivateKey()
|
|
if err != nil {
|
|
log.Printf("Error generating public-private key pairs: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
nodeID = publicKey2NodeID(peerPublicKey)
|
|
|
|
// save the newly generated private key into the config
|
|
config.PrivateKey = hex.EncodeToString(peerPrivateKey.Serialize())
|
|
|
|
saveConfig()
|
|
}
|
|
|
|
// Secp256k1NewPrivateKey creates a new public-private key pair
|
|
func Secp256k1NewPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey, err error) {
|
|
key, err := btcec.NewPrivateKey(btcec.S256())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return key, (*btcec.PublicKey)(&key.PublicKey), nil
|
|
}
|
|
|
|
// ExportPrivateKey returns the peers public and private key
|
|
func ExportPrivateKey() (privateKey *btcec.PrivateKey, publicKey *btcec.PublicKey) {
|
|
return peerPrivateKey, peerPublicKey
|
|
}
|
|
|
|
// SelfNodeID returns the node ID used for DHT
|
|
func SelfNodeID() []byte {
|
|
return nodeID
|
|
}
|
|
|
|
// PeerInfo stores information about a single remote peer
|
|
type PeerInfo struct {
|
|
PublicKey *btcec.PublicKey // Public key
|
|
NodeID []byte // Node ID in Kademlia network = blake3(Public Key).
|
|
connectionActive []*Connection // List of active established connections to the peer.
|
|
connectionInactive []*Connection // List of former connections that are no longer valid. They may be removed after a while.
|
|
connectionLatest *Connection // Latest valid connection.
|
|
sync.RWMutex // Mutex for access to list of connections.
|
|
messageSequence uint32 // Sequence number. Increased with every message.
|
|
IsRootPeer bool // Whether the peer is a trusted root peer.
|
|
UserAgent string // User Agent reported by remote peer. Empty if no Announcement/Response message was yet received.
|
|
|
|
// statistics
|
|
StatsPacketSent uint64 // Count of packets sent
|
|
StatsPacketReceived uint64 // Count of packets received
|
|
}
|
|
|
|
// peerList keeps track of all peers
|
|
var peerList map[[btcec.PubKeyBytesLenCompressed]byte]*PeerInfo
|
|
var peerlistMutex sync.RWMutex
|
|
|
|
// PeerlistAdd adds a new peer to the peer list. It does not validate the peer info. If the peer is already added, it does nothing. Connections must be live.
|
|
func PeerlistAdd(PublicKey *btcec.PublicKey, connections ...*Connection) (peer *PeerInfo, added bool) {
|
|
if len(connections) == 0 {
|
|
return nil, false
|
|
}
|
|
publicKeyCompressed := publicKey2Compressed(PublicKey)
|
|
|
|
peerlistMutex.Lock()
|
|
defer peerlistMutex.Unlock()
|
|
|
|
peer, ok := peerList[publicKeyCompressed]
|
|
if ok {
|
|
return peer, false
|
|
}
|
|
|
|
peer = &PeerInfo{PublicKey: PublicKey, connectionActive: connections, connectionLatest: connections[0], NodeID: publicKey2NodeID(PublicKey), messageSequence: rand.Uint32()}
|
|
_, peer.IsRootPeer = rootPeers[publicKeyCompressed]
|
|
|
|
peerList[publicKeyCompressed] = peer
|
|
|
|
// add to Kademlia
|
|
nodesDHT.AddNode(&dht.Node{ID: peer.NodeID, Info: peer})
|
|
|
|
// TODO: If the node isn't added to Kademlia, it should be either added temporarily to the peerList with an expiration, or to a temp list, or not at all.
|
|
|
|
// send to all channels non-blocking
|
|
for _, monitor := range peerMonitor {
|
|
select {
|
|
case monitor <- peer:
|
|
default:
|
|
}
|
|
}
|
|
|
|
return peer, true
|
|
}
|
|
|
|
// PeerlistRemove removes a peer from the peer list.
|
|
func PeerlistRemove(peer *PeerInfo) {
|
|
peerlistMutex.Lock()
|
|
defer peerlistMutex.Unlock()
|
|
|
|
// remove from Kademlia
|
|
nodesDHT.RemoveNode(peer.NodeID)
|
|
|
|
delete(peerList, publicKey2Compressed(peer.PublicKey))
|
|
}
|
|
|
|
// PeerlistGet returns the full peer list
|
|
func PeerlistGet() (peers []*PeerInfo) {
|
|
peerlistMutex.RLock()
|
|
defer peerlistMutex.RUnlock()
|
|
|
|
for _, peer := range peerList {
|
|
peers = append(peers, peer)
|
|
}
|
|
|
|
return peers
|
|
}
|
|
|
|
// PeerlistLookup returns the peer from the list with the public key
|
|
func PeerlistLookup(publicKey *btcec.PublicKey) (peer *PeerInfo) {
|
|
peerlistMutex.RLock()
|
|
defer peerlistMutex.RUnlock()
|
|
|
|
peer, _ = peerList[publicKey2Compressed(publicKey)]
|
|
return peer
|
|
}
|
|
|
|
// PeerlistCount returns the current count of peers in the peer list
|
|
func PeerlistCount() (count int) {
|
|
peerlistMutex.RLock()
|
|
defer peerlistMutex.RUnlock()
|
|
|
|
return len(peerList)
|
|
}
|
|
|
|
func publicKey2Compressed(publicKey *btcec.PublicKey) [btcec.PubKeyBytesLenCompressed]byte {
|
|
var key [btcec.PubKeyBytesLenCompressed]byte
|
|
copy(key[:], publicKey.SerializeCompressed())
|
|
return key
|
|
}
|
|
|
|
// publicKey2NodeID translates the Public Key into the node ID used in the Kademlia network.
|
|
// This is very important for lookup of data in the DHT.
|
|
func publicKey2NodeID(publicKey *btcec.PublicKey) (nodeID []byte) {
|
|
return hashData(publicKey.SerializeCompressed())
|
|
}
|
|
|
|
// records2Nodes translates infoPeer structures to nodes. If the reported nodes are not in the peer table, it will create temporary PeerInfo structures.
|
|
// LastContact is passed on in the Node.LastSeen field.
|
|
// It requires the network parameter which must be the same as caller/supplier. This ensures that peer details do not "jump" between physical network adapters.
|
|
func records2Nodes(records []PeerRecord, network *Network, peerSource *PeerInfo) (nodes []*dht.Node) {
|
|
for _, record := range records {
|
|
if record.IsBadQuality() {
|
|
continue
|
|
}
|
|
|
|
var peer *PeerInfo
|
|
if peer = PeerlistLookup(record.PublicKey); peer == nil {
|
|
// Create temporary peer which is not added to the global list and not added to Kademlia.
|
|
port := record.Port
|
|
if record.PortReportedExternal > 0 { // Use the external port if available
|
|
port = record.PortReportedExternal
|
|
}
|
|
|
|
// traversePeer is set to the peer who provided the node information.
|
|
|
|
connection := &Connection{Network: network, Address: &net.UDPAddr{IP: record.IP, Port: int(port)}, Status: ConnectionActive, PortInternal: record.PortReportedInternal, PortExternal: record.PortReportedExternal, traversePeer: peerSource}
|
|
peer = &PeerInfo{PublicKey: record.PublicKey, connectionActive: []*Connection{connection}, connectionLatest: connection, NodeID: publicKey2NodeID(record.PublicKey), messageSequence: rand.Uint32()}
|
|
}
|
|
|
|
nodes = append(nodes, &dht.Node{ID: peer.NodeID, LastSeen: record.LastContactT, Info: peer})
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// selfPeerRecord returns self as peer record
|
|
func selfPeerRecord(network *Network) (result PeerRecord) {
|
|
return PeerRecord{
|
|
PublicKey: peerPublicKey,
|
|
NodeID: nodeID,
|
|
IP: network.address.IP,
|
|
Port: uint16(network.address.Port),
|
|
LastContact: 0,
|
|
}
|
|
}
|
|
|
|
var peerMonitor []chan<- *PeerInfo
|
|
|
|
// registerPeerMonitor registers a channel to receive all new peers
|
|
func registerPeerMonitor(channel chan<- *PeerInfo) {
|
|
peerlistMutex.Lock()
|
|
defer peerlistMutex.Unlock()
|
|
|
|
peerMonitor = append(peerMonitor, channel)
|
|
}
|
|
|
|
// unregisterPeerMonitor unregisters a channel
|
|
func unregisterPeerMonitor(channel chan<- *PeerInfo) {
|
|
peerlistMutex.Lock()
|
|
defer peerlistMutex.Unlock()
|
|
|
|
for n, channel2 := range peerMonitor {
|
|
if channel == channel2 {
|
|
peerMonitorNew := peerMonitor[:n]
|
|
if n < len(peerMonitor)-1 {
|
|
peerMonitorNew = append(peerMonitorNew, peerMonitor[n+1:]...)
|
|
}
|
|
peerMonitor = peerMonitorNew
|
|
break
|
|
}
|
|
}
|
|
}
|