Files
core/Peer ID.go
Kleissner f084edc316 New function record.IsBadQuality to make sure bad peers returned in the Response message are not considered.
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.
2021-05-09 00:49:34 +02:00

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
}
}
}