mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-05-20 13:56:45 +00:00

* Address manager refactor stage 1 * Use a simpler weightedRand function which makes it easier parameterize to the process * Switch back to connectionFailedCount * Simplify selected entry deletion * Fix function comment Co-authored-by: Constantine Bitensky <cbitensky1@gmail.com> Co-authored-by: Ori Newman <orinewman1@gmail.com>
300 lines
8.3 KiB
Go
300 lines
8.3 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package addressmanager
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
|
"github.com/kaspanet/kaspad/util/mstime"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/kaspanet/kaspad/app/appmessage"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
maxAddresses = 4096
|
|
connectionFailedCountForRemove = 4
|
|
)
|
|
|
|
// addressRandomizer is the interface for the randomizer needed for the AddressManager.
|
|
type addressRandomizer interface {
|
|
RandomAddresses(addresses []*address, count int) []*appmessage.NetAddress
|
|
}
|
|
|
|
// addressKey represents a pair of IP and port, the IP is always in V6 representation
|
|
type addressKey struct {
|
|
port uint16
|
|
address ipv6
|
|
}
|
|
|
|
type address struct {
|
|
netAddress *appmessage.NetAddress
|
|
connectionFailedCount uint64
|
|
}
|
|
|
|
type ipv6 [net.IPv6len]byte
|
|
|
|
func (i ipv6) equal(other ipv6) bool {
|
|
return i == other
|
|
}
|
|
|
|
// ErrAddressNotFound is an error returned from some functions when a
|
|
// given address is not found in the address manager
|
|
var ErrAddressNotFound = errors.New("address not found")
|
|
|
|
// NetAddressKey returns a key of the ip address to use it in maps.
|
|
func netAddressKey(netAddress *appmessage.NetAddress) addressKey {
|
|
key := addressKey{port: netAddress.Port}
|
|
// all IPv4 can be represented as IPv6.
|
|
copy(key.address[:], netAddress.IP.To16())
|
|
return key
|
|
}
|
|
|
|
// AddressManager provides a concurrency safe address manager for caching potential
|
|
// peers on the Kaspa network.
|
|
type AddressManager struct {
|
|
store *addressStore
|
|
localAddresses *localAddressManager
|
|
mutex sync.Mutex
|
|
cfg *Config
|
|
random addressRandomizer
|
|
}
|
|
|
|
// New returns a new Kaspa address manager.
|
|
func New(cfg *Config, database database.Database) (*AddressManager, error) {
|
|
addressStore, err := newAddressStore(database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
localAddresses, err := newLocalAddressManager(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &AddressManager{
|
|
store: addressStore,
|
|
localAddresses: localAddresses,
|
|
random: NewAddressRandomize(connectionFailedCountForRemove),
|
|
cfg: cfg,
|
|
}, nil
|
|
}
|
|
|
|
func (am *AddressManager) addAddressNoLock(netAddress *appmessage.NetAddress) error {
|
|
if !IsRoutable(netAddress, am.cfg.AcceptUnroutable) {
|
|
return nil
|
|
}
|
|
|
|
key := netAddressKey(netAddress)
|
|
// We mark `connectionFailedCount` as 0 only after first success
|
|
address := &address{netAddress: netAddress, connectionFailedCount: 1}
|
|
err := am.store.add(key, address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if am.store.notBannedCount() > maxAddresses {
|
|
allAddresses := am.store.getAllNotBanned()
|
|
|
|
maxConnectionFailedCount := uint64(0)
|
|
toRemove := allAddresses[0]
|
|
for _, address := range allAddresses[1:] {
|
|
if address.connectionFailedCount > maxConnectionFailedCount {
|
|
maxConnectionFailedCount = address.connectionFailedCount
|
|
toRemove = address
|
|
}
|
|
}
|
|
|
|
err := am.removeAddressNoLock(toRemove.netAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (am *AddressManager) removeAddressNoLock(address *appmessage.NetAddress) error {
|
|
key := netAddressKey(address)
|
|
return am.store.remove(key)
|
|
}
|
|
|
|
// AddAddress adds address to the address manager
|
|
func (am *AddressManager) AddAddress(address *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
return am.addAddressNoLock(address)
|
|
}
|
|
|
|
// AddAddresses adds addresses to the address manager
|
|
func (am *AddressManager) AddAddresses(addresses ...*appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
for _, address := range addresses {
|
|
err := am.addAddressNoLock(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveAddress removes addresses from the address manager
|
|
func (am *AddressManager) RemoveAddress(address *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
return am.removeAddressNoLock(address)
|
|
}
|
|
|
|
// MarkConnectionFailure notifies the address manager that the given address
|
|
// has failed to connect
|
|
func (am *AddressManager) MarkConnectionFailure(address *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
key := netAddressKey(address)
|
|
entry, ok := am.store.getNotBanned(key)
|
|
if !ok {
|
|
return errors.Errorf("address %s is not registered with the address manager", address.TCPAddress())
|
|
}
|
|
entry.connectionFailedCount = entry.connectionFailedCount + 1
|
|
|
|
if entry.connectionFailedCount >= connectionFailedCountForRemove {
|
|
log.Debugf("Address %s has failed %d connection attempts - removing from address manager",
|
|
address, entry.connectionFailedCount)
|
|
return am.store.remove(key)
|
|
}
|
|
return am.store.updateNotBanned(key, entry)
|
|
}
|
|
|
|
// MarkConnectionSuccess notifies the address manager that the given address
|
|
// has successfully connected
|
|
func (am *AddressManager) MarkConnectionSuccess(address *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
key := netAddressKey(address)
|
|
entry, ok := am.store.getNotBanned(key)
|
|
if !ok {
|
|
return errors.Errorf("address %s is not registered with the address manager", address.TCPAddress())
|
|
}
|
|
entry.connectionFailedCount = 0
|
|
return am.store.updateNotBanned(key, entry)
|
|
}
|
|
|
|
// Addresses returns all addresses
|
|
func (am *AddressManager) Addresses() []*appmessage.NetAddress {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
return am.store.getAllNotBannedNetAddresses()
|
|
}
|
|
|
|
// BannedAddresses returns all banned addresses
|
|
func (am *AddressManager) BannedAddresses() []*appmessage.NetAddress {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
return am.store.getAllBannedNetAddresses()
|
|
}
|
|
|
|
// notBannedAddressesWithException returns all not banned addresses with excpetion
|
|
func (am *AddressManager) notBannedAddressesWithException(exceptions []*appmessage.NetAddress) []*address {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
return am.store.getAllNotBannedNetAddressesWithout(exceptions)
|
|
}
|
|
|
|
// RandomAddresses returns count addresses at random that aren't banned and aren't in exceptions
|
|
func (am *AddressManager) RandomAddresses(count int, exceptions []*appmessage.NetAddress) []*appmessage.NetAddress {
|
|
validAddresses := am.notBannedAddressesWithException(exceptions)
|
|
return am.random.RandomAddresses(validAddresses, count)
|
|
}
|
|
|
|
// BestLocalAddress returns the most appropriate local address to use
|
|
// for the given remote address.
|
|
func (am *AddressManager) BestLocalAddress(remoteAddress *appmessage.NetAddress) *appmessage.NetAddress {
|
|
return am.localAddresses.bestLocalAddress(remoteAddress)
|
|
}
|
|
|
|
// Ban marks the given address as banned
|
|
func (am *AddressManager) Ban(addressToBan *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
keyToBan := netAddressKey(addressToBan)
|
|
keysToDelete := make([]addressKey, 0)
|
|
for _, address := range am.store.getAllNotBannedNetAddresses() {
|
|
key := netAddressKey(address)
|
|
if key.address.equal(keyToBan.address) {
|
|
keysToDelete = append(keysToDelete, key)
|
|
}
|
|
}
|
|
for _, key := range keysToDelete {
|
|
err := am.store.remove(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
address := &address{netAddress: addressToBan}
|
|
return am.store.addBanned(keyToBan, address)
|
|
}
|
|
|
|
// Unban unmarks the given address as banned
|
|
func (am *AddressManager) Unban(address *appmessage.NetAddress) error {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
key := netAddressKey(address)
|
|
if !am.store.isBanned(key) {
|
|
return errors.Wrapf(ErrAddressNotFound, "address %s "+
|
|
"is not registered with the address manager as banned", address.TCPAddress())
|
|
}
|
|
|
|
return am.store.removeBanned(key)
|
|
}
|
|
|
|
// IsBanned returns true if the given address is marked as banned
|
|
func (am *AddressManager) IsBanned(address *appmessage.NetAddress) (bool, error) {
|
|
am.mutex.Lock()
|
|
defer am.mutex.Unlock()
|
|
|
|
key := netAddressKey(address)
|
|
err := am.unbanIfOldEnough(key)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !am.store.isBanned(key) {
|
|
if !am.store.isNotBanned(key) {
|
|
return false, errors.Wrapf(ErrAddressNotFound, "address %s "+
|
|
"is not registered with the address manager", address.TCPAddress())
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (am *AddressManager) unbanIfOldEnough(key addressKey) error {
|
|
address, ok := am.store.getBanned(key)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
const maxBanTime = 24 * time.Hour
|
|
if mstime.Since(address.netAddress.Timestamp) > maxBanTime {
|
|
err := am.store.removeBanned(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|