2021-11-16 14:22:01 +00:00
|
|
|
package discv5
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
2022-03-30 00:02:33 +00:00
|
|
|
"errors"
|
2021-11-17 16:19:42 +00:00
|
|
|
"fmt"
|
|
|
|
"math"
|
2021-11-16 14:22:01 +00:00
|
|
|
"math/rand"
|
|
|
|
"net"
|
2022-03-30 00:02:33 +00:00
|
|
|
"strconv"
|
2021-11-16 14:22:01 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
2021-11-23 14:24:05 +00:00
|
|
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
2021-11-16 14:22:01 +00:00
|
|
|
"github.com/libp2p/go-libp2p-core/discovery"
|
|
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
2022-03-30 00:02:33 +00:00
|
|
|
ma "github.com/multiformats/go-multiaddr"
|
2021-12-10 15:29:50 +00:00
|
|
|
"github.com/status-im/go-discover/discover"
|
2021-11-16 14:22:01 +00:00
|
|
|
"github.com/status-im/go-waku/waku/v2/utils"
|
2022-01-18 18:17:06 +00:00
|
|
|
"go.uber.org/zap"
|
2021-11-16 14:22:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type DiscoveryV5 struct {
|
2021-11-17 16:19:42 +00:00
|
|
|
sync.Mutex
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
discovery.Discovery
|
|
|
|
|
|
|
|
params *discV5Parameters
|
|
|
|
host host.Host
|
|
|
|
config discover.Config
|
|
|
|
udpAddr *net.UDPAddr
|
|
|
|
listener *discover.UDPv5
|
|
|
|
localnode *enode.LocalNode
|
2021-11-23 14:24:05 +00:00
|
|
|
NAT nat.Interface
|
|
|
|
quit chan struct{}
|
2021-11-16 14:22:01 +00:00
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
log *zap.SugaredLogger
|
|
|
|
|
2021-11-23 15:03:12 +00:00
|
|
|
wg *sync.WaitGroup
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
peerCache peerCache
|
2022-04-01 01:17:14 +00:00
|
|
|
|
|
|
|
// Used for those weird cases where updateAddress
|
|
|
|
// receives the same external address twice both with the original port
|
2022-04-25 19:31:26 +00:00
|
|
|
// and the nat port. Ideally this attribute should be removed by doing
|
2022-04-01 01:17:14 +00:00
|
|
|
// hole punching before starting waku
|
|
|
|
ogTCPPort int
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type peerCache struct {
|
|
|
|
sync.RWMutex
|
|
|
|
recs map[peer.ID]peerRecord
|
|
|
|
rng *rand.Rand
|
|
|
|
}
|
|
|
|
|
|
|
|
type peerRecord struct {
|
|
|
|
expire int64
|
|
|
|
peer peer.AddrInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
type discV5Parameters struct {
|
2021-11-17 16:19:42 +00:00
|
|
|
autoUpdate bool
|
|
|
|
bootnodes []*enode.Node
|
|
|
|
udpPort int
|
|
|
|
advertiseAddr *net.IP
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DiscoveryV5Option func(*discV5Parameters)
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
func WithAutoUpdate(autoUpdate bool) DiscoveryV5Option {
|
|
|
|
return func(params *discV5Parameters) {
|
|
|
|
params.autoUpdate = autoUpdate
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
func WithBootnodes(bootnodes []*enode.Node) DiscoveryV5Option {
|
|
|
|
return func(params *discV5Parameters) {
|
|
|
|
params.bootnodes = bootnodes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
func WithAdvertiseAddr(addr net.IP) DiscoveryV5Option {
|
2021-11-16 14:22:01 +00:00
|
|
|
return func(params *discV5Parameters) {
|
2021-11-17 16:19:42 +00:00
|
|
|
params.advertiseAddr = &addr
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithUDPPort(port int) DiscoveryV5Option {
|
|
|
|
return func(params *discV5Parameters) {
|
|
|
|
params.udpPort = port
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func DefaultOptions() []DiscoveryV5Option {
|
|
|
|
return []DiscoveryV5Option{
|
|
|
|
WithUDPPort(9000),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
func NewDiscoveryV5(host host.Host, addresses []ma.Multiaddr, priv *ecdsa.PrivateKey, wakuFlags utils.WakuEnrBitfield, log *zap.SugaredLogger, opts ...DiscoveryV5Option) (*DiscoveryV5, error) {
|
2021-11-16 14:22:01 +00:00
|
|
|
params := new(discV5Parameters)
|
|
|
|
optList := DefaultOptions()
|
|
|
|
optList = append(optList, opts...)
|
|
|
|
for _, opt := range optList {
|
|
|
|
opt(params)
|
|
|
|
}
|
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
logger := log.Named("discv5")
|
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
ipAddr, err := selectIPAddr(addresses)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-17 16:19:42 +00:00
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
localnode, err := newLocalnode(priv, ipAddr, params.udpPort, wakuFlags, params.advertiseAddr, logger)
|
2021-11-16 14:22:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-11-23 14:24:05 +00:00
|
|
|
var NAT nat.Interface = nil
|
|
|
|
if params.advertiseAddr == nil {
|
|
|
|
NAT = nat.Any()
|
|
|
|
}
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
return &DiscoveryV5{
|
|
|
|
host: host,
|
|
|
|
params: params,
|
2021-11-23 14:24:05 +00:00
|
|
|
NAT: NAT,
|
2021-11-23 15:03:12 +00:00
|
|
|
wg: &sync.WaitGroup{},
|
2021-11-16 14:22:01 +00:00
|
|
|
peerCache: peerCache{
|
|
|
|
rng: rand.New(rand.NewSource(rand.Int63())),
|
|
|
|
recs: make(map[peer.ID]peerRecord),
|
|
|
|
},
|
|
|
|
localnode: localnode,
|
|
|
|
config: discover.Config{
|
|
|
|
PrivateKey: priv,
|
|
|
|
Bootnodes: params.bootnodes,
|
2021-12-10 15:29:50 +00:00
|
|
|
ValidNodeFn: func(n enode.Node) bool {
|
|
|
|
// TODO: track https://github.com/status-im/nim-waku/issues/770 for improvements over validation func
|
|
|
|
return evaluateNode(&n)
|
|
|
|
},
|
2022-02-28 18:28:09 +00:00
|
|
|
V5Config: discover.V5Config{
|
|
|
|
ProtocolID: [6]byte{'d', '5', 'w', 'a', 'k', 'u'},
|
|
|
|
},
|
2021-11-16 14:22:01 +00:00
|
|
|
},
|
|
|
|
udpAddr: &net.UDPAddr{
|
2021-11-17 16:19:42 +00:00
|
|
|
IP: net.IPv4zero,
|
2021-11-16 14:22:01 +00:00
|
|
|
Port: params.udpPort,
|
|
|
|
},
|
2022-04-01 01:17:14 +00:00
|
|
|
log: logger,
|
|
|
|
ogTCPPort: ipAddr.Port,
|
2021-11-16 14:22:01 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
func newLocalnode(priv *ecdsa.PrivateKey, ipAddr *net.TCPAddr, udpPort int, wakuFlags utils.WakuEnrBitfield, advertiseAddr *net.IP, log *zap.SugaredLogger) (*enode.LocalNode, error) {
|
2021-11-16 14:22:01 +00:00
|
|
|
db, err := enode.OpenDB("")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
localnode := enode.NewLocalNode(db, priv)
|
|
|
|
localnode.SetFallbackUDP(udpPort)
|
2021-12-03 13:40:51 +00:00
|
|
|
localnode.Set(enr.WithEntry(utils.WakuENRField, wakuFlags))
|
2022-03-30 00:02:33 +00:00
|
|
|
localnode.SetFallbackIP(net.IP{127, 0, 0, 1})
|
|
|
|
localnode.SetStaticIP(ipAddr.IP)
|
2021-11-16 14:22:01 +00:00
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
if udpPort > 0 && udpPort <= math.MaxUint16 {
|
|
|
|
localnode.Set(enr.UDP(uint16(udpPort))) // lgtm [go/incorrect-integer-conversion]
|
|
|
|
} else {
|
|
|
|
log.Error("could not set udpPort ", udpPort)
|
|
|
|
}
|
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
if ipAddr.Port > 0 && ipAddr.Port <= math.MaxUint16 {
|
|
|
|
localnode.Set(enr.TCP(uint16(ipAddr.Port))) // lgtm [go/incorrect-integer-conversion]
|
2021-11-17 16:19:42 +00:00
|
|
|
} else {
|
2022-03-30 00:02:33 +00:00
|
|
|
log.Error("could not set tcpPort ", ipAddr.Port)
|
2021-11-17 16:19:42 +00:00
|
|
|
}
|
2021-11-16 14:22:01 +00:00
|
|
|
|
|
|
|
if advertiseAddr != nil {
|
|
|
|
localnode.SetStaticIP(*advertiseAddr)
|
|
|
|
}
|
2021-11-23 14:24:05 +00:00
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
return localnode, nil
|
|
|
|
}
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
func (d *DiscoveryV5) listen() error {
|
2021-11-16 14:22:01 +00:00
|
|
|
conn, err := net.ListenUDP("udp", d.udpAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-23 14:24:05 +00:00
|
|
|
d.udpAddr = conn.LocalAddr().(*net.UDPAddr)
|
|
|
|
|
|
|
|
if d.NAT != nil && !d.udpAddr.IP.IsLoopback() {
|
2021-11-23 15:03:12 +00:00
|
|
|
d.wg.Add(1)
|
2021-11-23 14:24:05 +00:00
|
|
|
go func() {
|
2021-11-23 15:03:12 +00:00
|
|
|
defer d.wg.Done()
|
2021-11-23 14:24:05 +00:00
|
|
|
nat.Map(d.NAT, d.quit, "udp", d.udpAddr.Port, d.udpAddr.Port, "go-waku discv5 discovery")
|
|
|
|
}()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
d.localnode.SetFallbackUDP(d.udpAddr.Port)
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
listener, err := discover.ListenV5(conn, d.localnode, d.config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.listener = listener
|
|
|
|
|
2022-03-30 00:02:33 +00:00
|
|
|
d.log.Info(fmt.Sprintf("Started Discovery V5 at %s:%d, advertising IP: %s:%d", d.udpAddr.IP, d.udpAddr.Port, d.localnode.Node().IP(), d.localnode.Node().TCP()))
|
2022-05-05 19:17:35 +00:00
|
|
|
d.log.Info("Discv5: discoverable ENR ", d.localnode.Node())
|
2021-11-23 14:24:05 +00:00
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiscoveryV5) Start() error {
|
|
|
|
d.Lock()
|
|
|
|
defer d.Unlock()
|
|
|
|
|
2021-11-23 15:03:12 +00:00
|
|
|
d.wg.Wait() // Waiting for other go routines to stop
|
|
|
|
|
|
|
|
d.quit = make(chan struct{}, 1)
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
err := d.listen()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiscoveryV5) Stop() {
|
2021-11-17 16:19:42 +00:00
|
|
|
d.Lock()
|
|
|
|
defer d.Unlock()
|
|
|
|
|
2021-11-23 15:03:12 +00:00
|
|
|
close(d.quit)
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
d.listener.Close()
|
2021-11-17 16:19:42 +00:00
|
|
|
d.listener = nil
|
2021-11-23 14:24:05 +00:00
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
d.log.Info("Stopped Discovery V5")
|
2021-11-23 15:03:12 +00:00
|
|
|
|
|
|
|
d.wg.Wait()
|
2021-11-17 16:19:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-01 01:17:14 +00:00
|
|
|
func (d *DiscoveryV5) UpdateAddr(addr *net.TCPAddr) error {
|
2021-11-17 16:19:42 +00:00
|
|
|
if !d.params.autoUpdate {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
d.Lock()
|
|
|
|
defer d.Unlock()
|
|
|
|
|
2022-04-01 01:17:14 +00:00
|
|
|
// TODO: This code is not elegant and should be improved
|
|
|
|
|
|
|
|
if !isExternal(addr) && !isExternal(&net.TCPAddr{IP: d.localnode.Node().IP()}) {
|
|
|
|
if !((d.localnode.Node().IP().IsLoopback() && isPrivate(addr)) || (isPrivate(&net.TCPAddr{IP: d.localnode.Node().IP()}) && isExternal(addr))) {
|
|
|
|
return nil
|
|
|
|
}
|
2021-11-17 16:19:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-01 01:17:14 +00:00
|
|
|
if addr.IP.IsUnspecified() || (d.localnode.Node().IP().Equal(addr.IP) && addr.Port == d.ogTCPPort) {
|
2021-11-17 16:19:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-01 01:17:14 +00:00
|
|
|
d.localnode.SetStaticIP(addr.IP)
|
|
|
|
d.localnode.Set(enr.TCP(uint16(addr.Port))) // lgtm [go/incorrect-integer-conversion]
|
|
|
|
d.log.Info(fmt.Sprintf("Updated Discovery V5 node: %s:%d", d.localnode.Node().IP(), d.localnode.Node().TCP()))
|
2022-01-18 18:17:06 +00:00
|
|
|
d.log.Info("Discovery V5 ", d.localnode.Node())
|
2021-11-17 16:19:42 +00:00
|
|
|
|
|
|
|
return nil
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
2022-03-18 19:50:10 +00:00
|
|
|
/*
|
2021-11-16 14:22:01 +00:00
|
|
|
func isWakuNode(node *enode.Node) bool {
|
2021-12-03 13:40:51 +00:00
|
|
|
enrField := new(utils.WakuEnrBitfield)
|
|
|
|
if err := node.Record().Load(enr.WithEntry(utils.WakuENRField, &enrField)); err != nil {
|
2021-11-16 14:22:01 +00:00
|
|
|
if !enr.IsNotFound(err) {
|
2022-01-18 18:17:06 +00:00
|
|
|
utils.Logger().Named("discv5").Error("could not retrieve port for enr ", zap.Any("node", node))
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if enrField != nil {
|
|
|
|
return *enrField != uint8(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
2022-03-18 19:50:10 +00:00
|
|
|
*/
|
2021-11-16 14:22:01 +00:00
|
|
|
|
|
|
|
func hasTCPPort(node *enode.Node) bool {
|
|
|
|
enrTCP := new(enr.TCP)
|
|
|
|
if err := node.Record().Load(enr.WithEntry(enrTCP.ENRKey(), enrTCP)); err != nil {
|
|
|
|
if !enr.IsNotFound(err) {
|
2022-01-18 18:17:06 +00:00
|
|
|
utils.Logger().Named("discv5").Error("could not retrieve port for enr ", zap.Any("node", node))
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-12-10 15:29:50 +00:00
|
|
|
func evaluateNode(node *enode.Node) bool {
|
2021-11-16 14:22:01 +00:00
|
|
|
if node == nil || node.IP() == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-03-10 22:14:50 +00:00
|
|
|
// TODO: consider node filtering based on ENR; we do not filter based on ENR in the first waku discv5 beta stage
|
|
|
|
if /*!isWakuNode(node) ||*/ !hasTCPPort(node) {
|
2021-11-16 14:22:01 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := utils.EnodeToPeerInfo(node)
|
2021-11-17 16:19:42 +00:00
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
utils.Logger().Named("discv5").Error("could not obtain peer info from enode:", zap.Error(err))
|
2021-11-16 14:22:01 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-01-18 18:17:06 +00:00
|
|
|
func (d *DiscoveryV5) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) {
|
2021-11-16 14:22:01 +00:00
|
|
|
// Get options
|
|
|
|
var options discovery.Options
|
|
|
|
err := options.Apply(opts...)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: once discv5 spec introduces capability and topic discovery, implement this function
|
|
|
|
|
|
|
|
return 20 * time.Minute, nil
|
|
|
|
}
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
func (d *DiscoveryV5) iterate(ctx context.Context, iterator enode.Iterator, limit int, doneCh chan struct{}) {
|
2021-11-23 15:03:12 +00:00
|
|
|
defer d.wg.Done()
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
for {
|
|
|
|
if len(d.peerCache.recs) >= limit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-11-17 16:19:42 +00:00
|
|
|
if ctx.Err() != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
exists := iterator.Next()
|
|
|
|
if !exists {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2022-03-08 12:58:32 +00:00
|
|
|
addresses, err := utils.Multiaddress(iterator.Node())
|
2021-11-16 14:22:01 +00:00
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
d.log.Error(err)
|
2021-11-16 14:22:01 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-03-08 12:58:32 +00:00
|
|
|
peerAddrs, err := peer.AddrInfosFromP2pAddrs(addresses...)
|
2021-11-16 14:22:01 +00:00
|
|
|
if err != nil {
|
2022-01-18 18:17:06 +00:00
|
|
|
d.log.Error(err)
|
2021-11-16 14:22:01 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-03-08 12:58:32 +00:00
|
|
|
for _, p := range peerAddrs {
|
|
|
|
d.peerCache.recs[p.ID] = peerRecord{
|
|
|
|
expire: time.Now().Unix() + 3600, // Expires in 1hr
|
|
|
|
peer: p,
|
|
|
|
}
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
2022-03-08 12:58:32 +00:00
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
close(doneCh)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiscoveryV5) removeExpiredPeers() int {
|
|
|
|
// Remove all expired entries from cache
|
|
|
|
currentTime := time.Now().Unix()
|
|
|
|
newCacheSize := len(d.peerCache.recs)
|
|
|
|
|
|
|
|
for p := range d.peerCache.recs {
|
|
|
|
rec := d.peerCache.recs[p]
|
|
|
|
if rec.expire < currentTime {
|
|
|
|
newCacheSize--
|
|
|
|
delete(d.peerCache.recs, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newCacheSize
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *DiscoveryV5) FindPeers(ctx context.Context, topic string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) {
|
|
|
|
// Get options
|
|
|
|
var options discovery.Options
|
|
|
|
err := options.Apply(opts...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
const maxLimit = 100
|
|
|
|
limit := options.Limit
|
|
|
|
if limit == 0 || limit > maxLimit {
|
|
|
|
limit = maxLimit
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are ignoring the topic. Future versions might use a map[string]*peerCache instead where the string represents the pubsub topic
|
|
|
|
|
|
|
|
d.peerCache.Lock()
|
|
|
|
defer d.peerCache.Unlock()
|
|
|
|
|
|
|
|
cacheSize := d.removeExpiredPeers()
|
|
|
|
|
|
|
|
// Discover new records if we don't have enough
|
2021-11-17 16:19:42 +00:00
|
|
|
if cacheSize < limit && d.listener != nil {
|
|
|
|
d.Lock()
|
|
|
|
|
2021-11-16 14:22:01 +00:00
|
|
|
iterator := d.listener.RandomNodes()
|
2021-12-10 15:29:50 +00:00
|
|
|
iterator = enode.Filter(iterator, evaluateNode)
|
2021-11-16 14:22:01 +00:00
|
|
|
defer iterator.Close()
|
|
|
|
|
|
|
|
doneCh := make(chan struct{})
|
2021-11-23 15:03:12 +00:00
|
|
|
|
|
|
|
d.wg.Add(1)
|
2021-11-17 16:19:42 +00:00
|
|
|
go d.iterate(ctx, iterator, limit, doneCh)
|
2021-11-16 14:22:01 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case <-doneCh:
|
|
|
|
}
|
2021-11-17 16:19:42 +00:00
|
|
|
|
|
|
|
d.Unlock()
|
2021-11-16 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Randomize and fill channel with available records
|
|
|
|
count := len(d.peerCache.recs)
|
|
|
|
if limit < count {
|
|
|
|
count = limit
|
|
|
|
}
|
|
|
|
|
|
|
|
chPeer := make(chan peer.AddrInfo, count)
|
|
|
|
|
|
|
|
perm := d.peerCache.rng.Perm(len(d.peerCache.recs))[0:count]
|
|
|
|
permSet := make(map[int]int)
|
|
|
|
for i, v := range perm {
|
|
|
|
permSet[v] = i
|
|
|
|
}
|
|
|
|
|
|
|
|
sendLst := make([]*peer.AddrInfo, count)
|
|
|
|
iter := 0
|
|
|
|
for k := range d.peerCache.recs {
|
|
|
|
if sendIndex, ok := permSet[iter]; ok {
|
|
|
|
peerInfo := d.peerCache.recs[k].peer
|
|
|
|
sendLst[sendIndex] = &peerInfo
|
|
|
|
}
|
|
|
|
iter++
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, send := range sendLst {
|
|
|
|
chPeer <- *send
|
|
|
|
}
|
|
|
|
|
|
|
|
close(chPeer)
|
|
|
|
|
|
|
|
return chPeer, err
|
|
|
|
}
|
2022-03-30 00:02:33 +00:00
|
|
|
|
|
|
|
// IsPrivate reports whether ip is a private address, according to
|
|
|
|
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
|
|
|
|
// Copied/Adapted from https://go-review.googlesource.com/c/go/+/272668/11/src/net/ip.go
|
|
|
|
// Copyright (c) The Go Authors. All rights reserved.
|
|
|
|
// @TODO: once Go 1.17 is released in Q42021, remove this function as it will become part of the language
|
|
|
|
func isPrivate(ip *net.TCPAddr) bool {
|
|
|
|
if ip4 := ip.IP.To4(); ip4 != nil {
|
|
|
|
// Following RFC 4193, Section 3. Local IPv6 Unicast Addresses which says:
|
|
|
|
// The Internet Assigned Numbers Authority (IANA) has reserved the
|
|
|
|
// following three blocks of the IPv4 address space for private internets:
|
|
|
|
// 10.0.0.0 - 10.255.255.255 (10/8 prefix)
|
|
|
|
// 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
|
|
|
|
// 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
|
|
|
|
return ip4[0] == 10 ||
|
|
|
|
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
|
|
|
|
(ip4[0] == 192 && ip4[1] == 168)
|
|
|
|
}
|
|
|
|
// Following RFC 4193, Section 3. Private Address Space which says:
|
|
|
|
// The Internet Assigned Numbers Authority (IANA) has reserved the
|
|
|
|
// following block of the IPv6 address space for local internets:
|
|
|
|
// FC00:: - FDFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF (FC00::/7 prefix)
|
|
|
|
return len(ip.IP) == net.IPv6len && ip.IP[0]&0xfe == 0xfc
|
|
|
|
}
|
|
|
|
|
|
|
|
func isExternal(ip *net.TCPAddr) bool {
|
|
|
|
return !isPrivate(ip) && !ip.IP.IsLoopback() && !ip.IP.IsUnspecified()
|
|
|
|
}
|
|
|
|
|
|
|
|
func isLoopback(ip *net.TCPAddr) bool {
|
|
|
|
return ip.IP.IsLoopback()
|
|
|
|
}
|
|
|
|
func filter(ss []*net.TCPAddr, fn func(*net.TCPAddr) bool) (ret []*net.TCPAddr) {
|
|
|
|
for _, s := range ss {
|
|
|
|
if fn(s) {
|
|
|
|
ret = append(ret, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectIPAddr(addresses []ma.Multiaddr) (*net.TCPAddr, error) {
|
|
|
|
var ipAddrs []*net.TCPAddr
|
|
|
|
for _, addr := range addresses {
|
|
|
|
ipStr, err := addr.ValueForProtocol(ma.P_IP4)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
portStr, err := addr.ValueForProtocol(ma.P_TCP)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
port, err := strconv.Atoi(portStr)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ipAddrs = append(ipAddrs, &net.TCPAddr{
|
|
|
|
IP: net.ParseIP(ipStr),
|
|
|
|
Port: port,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
externalIPs := filter(ipAddrs, isExternal)
|
|
|
|
if len(externalIPs) > 0 {
|
|
|
|
return externalIPs[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
privateIPs := filter(ipAddrs, isPrivate)
|
|
|
|
if len(privateIPs) > 0 {
|
|
|
|
return privateIPs[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
loopback := filter(ipAddrs, isLoopback)
|
|
|
|
if len(loopback) > 0 {
|
|
|
|
return loopback[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("could not obtain ip address")
|
|
|
|
}
|