mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
Merge pull request #1267 from maticnetwork/psp-p2p-upstream
p2p: cherry-pick commits from geth for peering issues
This commit is contained in:
commit
bd5d54e9fe
28 changed files with 1888 additions and 1075 deletions
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -30,9 +31,11 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -46,6 +49,7 @@ var (
|
|||
discv4ResolveJSONCommand,
|
||||
discv4CrawlCommand,
|
||||
discv4TestCommand,
|
||||
discv4ListenCommand,
|
||||
},
|
||||
}
|
||||
discv4PingCommand = &cli.Command{
|
||||
|
|
@ -76,6 +80,14 @@ var (
|
|||
Flags: discoveryNodeFlags,
|
||||
ArgsUsage: "<nodes.json file>",
|
||||
}
|
||||
discv4ListenCommand = &cli.Command{
|
||||
Name: "listen",
|
||||
Usage: "Runs a discovery node",
|
||||
Action: discv4Listen,
|
||||
Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{
|
||||
httpAddrFlag,
|
||||
}),
|
||||
}
|
||||
discv4CrawlCommand = &cli.Command{
|
||||
Name: "crawl",
|
||||
Usage: "Updates a nodes.json file with random nodes found in the DHT",
|
||||
|
|
@ -132,6 +144,10 @@ var (
|
|||
Usage: "Enode of the remote node under test",
|
||||
EnvVars: []string{"REMOTE_ENODE"},
|
||||
}
|
||||
httpAddrFlag = &cli.StringFlag{
|
||||
Name: "rpc",
|
||||
Usage: "HTTP server listening address",
|
||||
}
|
||||
)
|
||||
|
||||
var discoveryNodeFlags = []cli.Flag{
|
||||
|
|
@ -158,6 +174,27 @@ func discv4Ping(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func discv4Listen(ctx *cli.Context) error {
|
||||
disc, _ := startV4(ctx)
|
||||
defer disc.Close()
|
||||
|
||||
fmt.Println(disc.Self())
|
||||
|
||||
httpAddr := ctx.String(httpAddrFlag.Name)
|
||||
if httpAddr == "" {
|
||||
// Non-HTTP mode.
|
||||
select {}
|
||||
}
|
||||
|
||||
api := &discv4API{disc}
|
||||
log.Info("Starting RPC API server", "addr", httpAddr)
|
||||
srv := rpc.NewServer("", 0, 0)
|
||||
srv.RegisterName("discv4", api)
|
||||
http.DefaultServeMux.Handle("/", srv)
|
||||
httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux}
|
||||
return httpsrv.ListenAndServe()
|
||||
}
|
||||
|
||||
func discv4RequestRecord(ctx *cli.Context) error {
|
||||
n := getNodeArg(ctx)
|
||||
disc, _ := startV4(ctx)
|
||||
|
|
@ -401,3 +438,23 @@ func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
|||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
type discv4API struct {
|
||||
host *discover.UDPv4
|
||||
}
|
||||
|
||||
func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) {
|
||||
it := api.host.RandomNodes()
|
||||
for len(ns) < n && it.Next() {
|
||||
ns = append(ns, it.Node())
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (api *discv4API) Buckets() [][]discover.BucketNode {
|
||||
return api.host.TableBuckets()
|
||||
}
|
||||
|
||||
func (api *discv4API) Self() *enode.Node {
|
||||
return api.host.Self()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint {
|
|||
}
|
||||
|
||||
func (te *testenv) remoteEndpoint() v4wire.Endpoint {
|
||||
return v4wire.NewEndpoint(te.remoteAddr, 0)
|
||||
return v4wire.NewEndpoint(te.remoteAddr.AddrPort(), 0)
|
||||
}
|
||||
|
||||
func contains(ns []v4wire.Node, key v4wire.Pubkey) bool {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (h *bufHandler) Handle(_ context.Context, r slog.Record) error {
|
|||
}
|
||||
|
||||
func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool {
|
||||
return lvl <= h.level
|
||||
return lvl >= h.level
|
||||
}
|
||||
|
||||
func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
|
|
|
|||
17
node/api.go
17
node/api.go
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
|
@ -40,6 +41,9 @@ func (n *Node) apis() []rpc.API {
|
|||
}, {
|
||||
Namespace: "debug",
|
||||
Service: debug.Handler,
|
||||
}, {
|
||||
Namespace: "debug",
|
||||
Service: &p2pDebugAPI{n},
|
||||
}, {
|
||||
Namespace: "web3",
|
||||
Service: &web3API{n},
|
||||
|
|
@ -477,3 +481,16 @@ func (api *adminAPI) SetHttpExecutionPoolSize(n int) *ExecutionPoolSize {
|
|||
|
||||
return api.GetExecutionPoolSize()
|
||||
}
|
||||
|
||||
// p2pDebugAPI provides access to p2p internals for debugging.
|
||||
type p2pDebugAPI struct {
|
||||
stack *Node
|
||||
}
|
||||
|
||||
func (s *p2pDebugAPI) DiscoveryV4Table() [][]discover.BucketNode {
|
||||
disc := s.stack.server.DiscoveryV4()
|
||||
if disc != nil {
|
||||
return disc.TableBuckets()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
51
p2p/dial.go
51
p2p/dial.go
|
|
@ -25,6 +25,7 @@ import (
|
|||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
|
|
@ -254,7 +255,7 @@ loop:
|
|||
}
|
||||
|
||||
case task := <-d.doneCh:
|
||||
id := task.dest.ID()
|
||||
id := task.dest().ID()
|
||||
delete(d.dialing, id)
|
||||
d.updateStaticPool(id)
|
||||
d.doneSinceLastLog++
|
||||
|
|
@ -431,7 +432,7 @@ func (d *dialScheduler) startStaticDials(n int) (started int) {
|
|||
// updateStaticPool attempts to move the given static dial back into staticPool.
|
||||
func (d *dialScheduler) updateStaticPool(id enode.ID) {
|
||||
task, ok := d.static[id]
|
||||
if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil {
|
||||
if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest()) == nil {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
}
|
||||
|
|
@ -459,11 +460,11 @@ func (d *dialScheduler) removeFromStaticPool(idx int) {
|
|||
|
||||
// startDial runs the given dial task in a separate goroutine.
|
||||
func (d *dialScheduler) startDial(task *dialTask) {
|
||||
d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags)
|
||||
hkey := string(task.dest.ID().Bytes())
|
||||
node := task.dest()
|
||||
d.log.Trace("Starting p2p dial", "id", node.ID(), "ip", node.IP(), "flag", task.flags)
|
||||
hkey := string(node.ID().Bytes())
|
||||
d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
|
||||
d.dialing[task.dest.ID()] = task
|
||||
|
||||
d.dialing[node.ID()] = task
|
||||
go func() {
|
||||
task.run(d)
|
||||
d.doneCh <- task
|
||||
|
|
@ -474,39 +475,46 @@ func (d *dialScheduler) startDial(task *dialTask) {
|
|||
type dialTask struct {
|
||||
staticPoolIndex int
|
||||
flags connFlag
|
||||
|
||||
// These fields are private to the task and should not be
|
||||
// accessed by dialScheduler while the task is running.
|
||||
dest *enode.Node
|
||||
destPtr atomic.Pointer[enode.Node]
|
||||
lastResolved mclock.AbsTime
|
||||
resolveDelay time.Duration
|
||||
}
|
||||
|
||||
func newDialTask(dest *enode.Node, flags connFlag) *dialTask {
|
||||
return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1}
|
||||
t := &dialTask{flags: flags, staticPoolIndex: -1}
|
||||
t.destPtr.Store(dest)
|
||||
return t
|
||||
}
|
||||
|
||||
type dialError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (t *dialTask) dest() *enode.Node {
|
||||
return t.destPtr.Load()
|
||||
}
|
||||
|
||||
func (t *dialTask) run(d *dialScheduler) {
|
||||
if t.needResolve() && !t.resolve(d) {
|
||||
return
|
||||
}
|
||||
|
||||
err := t.dial(d, t.dest)
|
||||
err := t.dial(d, t.dest())
|
||||
if err != nil {
|
||||
// For static nodes, resolve one more time if dialing fails.
|
||||
if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
|
||||
if t.resolve(d) {
|
||||
t.dial(d, t.dest)
|
||||
t.dial(d, t.dest())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *dialTask) needResolve() bool {
|
||||
return t.flags&staticDialedConn != 0 && t.dest.IP() == nil
|
||||
return t.flags&staticDialedConn != 0 && t.dest().IP() == nil
|
||||
}
|
||||
|
||||
// resolve attempts to find the current endpoint for the destination
|
||||
|
|
@ -528,7 +536,8 @@ func (t *dialTask) resolve(d *dialScheduler) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
resolved := d.resolver.Resolve(t.dest)
|
||||
node := t.dest()
|
||||
resolved := d.resolver.Resolve(node)
|
||||
t.lastResolved = d.clock.Now()
|
||||
|
||||
if resolved == nil {
|
||||
|
|
@ -536,25 +545,22 @@ func (t *dialTask) resolve(d *dialScheduler) bool {
|
|||
if t.resolveDelay > maxResolveDelay {
|
||||
t.resolveDelay = maxResolveDelay
|
||||
}
|
||||
|
||||
d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay)
|
||||
|
||||
d.log.Debug("Resolving node failed", "id", node.ID(), "newdelay", t.resolveDelay)
|
||||
return false
|
||||
}
|
||||
// The node was found.
|
||||
t.resolveDelay = initialResolveDelay
|
||||
t.dest = resolved
|
||||
d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
|
||||
|
||||
t.destPtr.Store(resolved)
|
||||
d.log.Debug("Resolved node", "id", resolved.ID(), "addr", &net.TCPAddr{IP: resolved.IP(), Port: resolved.TCP()})
|
||||
return true
|
||||
}
|
||||
|
||||
// dial performs the actual connection attempt.
|
||||
func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
|
||||
dialMeter.Mark(1)
|
||||
fd, err := d.dialer.Dial(d.ctx, t.dest)
|
||||
fd, err := d.dialer.Dial(d.ctx, dest)
|
||||
if err != nil {
|
||||
d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err))
|
||||
d.log.Trace("Dial error", "id", dest.ID(), "addr", nodeAddr(dest), "conn", t.flags, "err", cleanupDialErr(err))
|
||||
dialConnectionError.Mark(1)
|
||||
return &dialError{err}
|
||||
}
|
||||
|
|
@ -562,8 +568,9 @@ func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
|
|||
}
|
||||
|
||||
func (t *dialTask) String() string {
|
||||
id := t.dest.ID()
|
||||
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
|
||||
node := t.dest()
|
||||
id := node.ID()
|
||||
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], node.IP(), node.TCP())
|
||||
}
|
||||
|
||||
func cleanupDialErr(err error) error {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,12 @@ package discover
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
|
|
@ -30,8 +35,8 @@ import (
|
|||
|
||||
// UDPConn is a network connection on which discovery can operate.
|
||||
type UDPConn interface {
|
||||
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
|
||||
WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
|
||||
ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error)
|
||||
WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (n int, err error)
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
|
@ -62,7 +67,7 @@ type Config struct {
|
|||
func (cfg Config) withDefaults() Config {
|
||||
// Node table configuration:
|
||||
if cfg.PingInterval == 0 {
|
||||
cfg.PingInterval = 10 * time.Second
|
||||
cfg.PingInterval = 3 * time.Second
|
||||
}
|
||||
if cfg.RefreshInterval == 0 {
|
||||
cfg.RefreshInterval = 30 * time.Minute
|
||||
|
|
@ -93,13 +98,46 @@ func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
|
|||
// channel if configured.
|
||||
type ReadPacket struct {
|
||||
Data []byte
|
||||
Addr *net.UDPAddr
|
||||
Addr netip.AddrPort
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x > y {
|
||||
return y
|
||||
}
|
||||
|
||||
return x
|
||||
type randomSource interface {
|
||||
Intn(int) int
|
||||
Int63n(int64) int64
|
||||
Shuffle(int, func(int, int))
|
||||
}
|
||||
|
||||
// reseedingRandom is a random number generator that tracks when it was last re-seeded.
|
||||
type reseedingRandom struct {
|
||||
mu sync.Mutex
|
||||
cur *rand.Rand
|
||||
}
|
||||
|
||||
func (r *reseedingRandom) seed() {
|
||||
var b [8]byte
|
||||
crand.Read(b[:])
|
||||
seed := binary.BigEndian.Uint64(b[:])
|
||||
new := rand.New(rand.NewSource(int64(seed)))
|
||||
|
||||
r.mu.Lock()
|
||||
r.cur = new
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *reseedingRandom) Intn(n int) int {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.cur.Intn(n)
|
||||
}
|
||||
|
||||
func (r *reseedingRandom) Int63n(n int64) int64 {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.cur.Int63n(n)
|
||||
}
|
||||
|
||||
func (r *reseedingRandom) Shuffle(n int, swap func(i, j int)) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.cur.Shuffle(n, swap)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@ import (
|
|||
// not need to be an actual node identifier.
|
||||
type lookup struct {
|
||||
tab *Table
|
||||
queryfunc func(*node) ([]*node, error)
|
||||
replyCh chan []*node
|
||||
queryfunc queryFunc
|
||||
replyCh chan []*enode.Node
|
||||
cancelCh <-chan struct{}
|
||||
asked, seen map[enode.ID]bool
|
||||
result nodesByDistance
|
||||
replyBuffer []*node
|
||||
replyBuffer []*enode.Node
|
||||
queries int
|
||||
}
|
||||
|
||||
type queryFunc func(*node) ([]*node, error)
|
||||
type queryFunc func(*enode.Node) ([]*enode.Node, error)
|
||||
|
||||
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
|
||||
it := &lookup{
|
||||
|
|
@ -47,7 +47,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
|
|||
asked: make(map[enode.ID]bool),
|
||||
seen: make(map[enode.ID]bool),
|
||||
result: nodesByDistance{target: target},
|
||||
replyCh: make(chan []*node, alpha),
|
||||
replyCh: make(chan []*enode.Node, alpha),
|
||||
cancelCh: ctx.Done(),
|
||||
queries: -1,
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l
|
|||
func (it *lookup) run() []*enode.Node {
|
||||
for it.advance() {
|
||||
}
|
||||
return unwrapNodes(it.result.entries)
|
||||
return it.result.entries
|
||||
}
|
||||
|
||||
// advance advances the lookup until any new nodes have been found.
|
||||
|
|
@ -147,36 +147,14 @@ func (it *lookup) slowdown() {
|
|||
}
|
||||
}
|
||||
|
||||
func (it *lookup) query(n *node, reply chan<- []*node) {
|
||||
fails := it.tab.db.FindFails(n.ID(), n.IP())
|
||||
func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) {
|
||||
r, err := it.queryfunc(n)
|
||||
|
||||
if errors.Is(err, errClosed) {
|
||||
// Avoid recording failures on shutdown.
|
||||
reply <- nil
|
||||
return
|
||||
} else if len(r) == 0 {
|
||||
fails++
|
||||
it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
|
||||
// Remove the node from the local table if it fails to return anything useful too
|
||||
// many times, but only if there are enough other nodes in the bucket.
|
||||
dropped := false
|
||||
if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 {
|
||||
dropped = true
|
||||
|
||||
it.tab.delete(n)
|
||||
if !errors.Is(err, errClosed) { // avoid recording failures on shutdown.
|
||||
success := len(r) > 0
|
||||
it.tab.trackRequest(n, success, r)
|
||||
if err != nil {
|
||||
it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "err", err)
|
||||
}
|
||||
|
||||
it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err)
|
||||
} else if fails > 0 {
|
||||
// Reset failure counter because it counts _consecutive_ failures.
|
||||
it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0)
|
||||
}
|
||||
|
||||
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
|
||||
// just remove those again during revalidation.
|
||||
for _, n := range r {
|
||||
it.tab.addSeenNode(n)
|
||||
}
|
||||
reply <- r
|
||||
}
|
||||
|
|
@ -184,7 +162,7 @@ func (it *lookup) query(n *node, reply chan<- []*node) {
|
|||
// lookupIterator performs lookup operations and iterates over all seen nodes.
|
||||
// When a lookup finishes, a new one is created through nextLookup.
|
||||
type lookupIterator struct {
|
||||
buffer []*node
|
||||
buffer []*enode.Node
|
||||
nextLookup lookupFunc
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
|
|
@ -203,8 +181,7 @@ func (it *lookupIterator) Node() *enode.Node {
|
|||
if len(it.buffer) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return unwrapNode(it.buffer[0])
|
||||
return it.buffer[0]
|
||||
}
|
||||
|
||||
// Next moves to the next node.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package discover
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
|
@ -58,16 +58,16 @@ func newMeteredConn(conn UDPConn) UDPConn {
|
|||
return &meteredUdpConn{UDPConn: conn}
|
||||
}
|
||||
|
||||
// Read delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
||||
func (c *meteredUdpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
n, addr, err = c.UDPConn.ReadFromUDP(b)
|
||||
// ReadFromUDPAddrPort delegates a network read to the underlying connection, bumping the udp ingress traffic meter along the way.
|
||||
func (c *meteredUdpConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||
n, addr, err = c.UDPConn.ReadFromUDPAddrPort(b)
|
||||
ingressTrafficMeter.Mark(int64(n))
|
||||
return n, addr, err
|
||||
}
|
||||
|
||||
// Write delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
|
||||
func (c *meteredUdpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
|
||||
n, err = c.UDPConn.WriteToUDP(b, addr)
|
||||
// WriteToUDP delegates a network write to the underlying connection, bumping the udp egress traffic meter along the way.
|
||||
func (c *meteredUdpConn) WriteToUDP(b []byte, addr netip.AddrPort) (n int, err error) {
|
||||
n, err = c.UDPConn.WriteToUDPAddrPort(b, addr)
|
||||
egressTrafficMeter.Mark(int64(n))
|
||||
return n, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import (
|
|||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -29,12 +30,22 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// node represents a host on the network.
|
||||
// The fields of Node may not be modified.
|
||||
type node struct {
|
||||
enode.Node
|
||||
addedAt time.Time // time when the node was added to the table
|
||||
livenessChecks uint // how often liveness was checked
|
||||
type BucketNode struct {
|
||||
Node *enode.Node `json:"node"`
|
||||
AddedToTable time.Time `json:"addedToTable"`
|
||||
AddedToBucket time.Time `json:"addedToBucket"`
|
||||
Checks int `json:"checks"`
|
||||
Live bool `json:"live"`
|
||||
}
|
||||
|
||||
// tableNode is an entry in Table.
|
||||
type tableNode struct {
|
||||
*enode.Node
|
||||
revalList *revalidationList
|
||||
addedToTable time.Time // first time node was added to bucket or replacement list
|
||||
addedToBucket time.Time // time it was added in the actual bucket
|
||||
livenessChecks uint // how often liveness was checked
|
||||
isValidatedLive bool // true if existence of node is considered validated right now
|
||||
}
|
||||
|
||||
type encPubkey [64]byte
|
||||
|
|
@ -69,36 +80,60 @@ func (e encPubkey) id() enode.ID {
|
|||
return enode.ID(crypto.Keccak256Hash(e[:]))
|
||||
}
|
||||
|
||||
func wrapNode(n *enode.Node) *node {
|
||||
return &node{Node: *n}
|
||||
}
|
||||
|
||||
func wrapNodes(ns []*enode.Node) []*node {
|
||||
result := make([]*node, len(ns))
|
||||
for i, n := range ns {
|
||||
result[i] = wrapNode(n)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func unwrapNode(n *node) *enode.Node {
|
||||
return &n.Node
|
||||
}
|
||||
|
||||
func unwrapNodes(ns []*node) []*enode.Node {
|
||||
func unwrapNodes(ns []*tableNode) []*enode.Node {
|
||||
result := make([]*enode.Node, len(ns))
|
||||
for i, n := range ns {
|
||||
result[i] = unwrapNode(n)
|
||||
result[i] = n.Node
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *node) addr() *net.UDPAddr {
|
||||
return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
}
|
||||
|
||||
func (n *node) String() string {
|
||||
func (n *tableNode) String() string {
|
||||
return n.Node.String()
|
||||
}
|
||||
|
||||
// nodesByDistance is a list of nodes, ordered by distance to target.
|
||||
type nodesByDistance struct {
|
||||
entries []*enode.Node
|
||||
target enode.ID
|
||||
}
|
||||
|
||||
// push adds the given node to the list, keeping the total size below maxElems.
|
||||
func (h *nodesByDistance) push(n *enode.Node, maxElems int) {
|
||||
ix := sort.Search(len(h.entries), func(i int) bool {
|
||||
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
|
||||
})
|
||||
|
||||
end := len(h.entries)
|
||||
if len(h.entries) < maxElems {
|
||||
h.entries = append(h.entries, n)
|
||||
}
|
||||
if ix < end {
|
||||
// Slide existing entries down to make room.
|
||||
// This will overwrite the entry we just appended.
|
||||
copy(h.entries[ix+1:], h.entries[ix:])
|
||||
h.entries[ix] = n
|
||||
}
|
||||
}
|
||||
|
||||
type nodeType interface {
|
||||
ID() enode.ID
|
||||
}
|
||||
|
||||
// containsID reports whether ns contains a node with the given ID.
|
||||
func containsID[N nodeType](ns []N, id enode.ID) bool {
|
||||
for _, n := range ns {
|
||||
if n.ID() == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// deleteNode removes a node from the list.
|
||||
func deleteNode[N nodeType](list []N, id enode.ID) []N {
|
||||
return slices.DeleteFunc(list, func(n N) bool {
|
||||
return n.ID() == id
|
||||
})
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
244
p2p/discover/table_reval.go
Normal file
244
p2p/discover/table_reval.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
const never = mclock.AbsTime(math.MaxInt64)
|
||||
|
||||
const slowRevalidationFactor = 3
|
||||
|
||||
// tableRevalidation implements the node revalidation process.
|
||||
// It tracks all nodes contained in Table, and schedules sending PING to them.
|
||||
type tableRevalidation struct {
|
||||
fast revalidationList
|
||||
slow revalidationList
|
||||
activeReq map[enode.ID]struct{}
|
||||
}
|
||||
|
||||
type revalidationResponse struct {
|
||||
n *tableNode
|
||||
newRecord *enode.Node
|
||||
didRespond bool
|
||||
}
|
||||
|
||||
func (tr *tableRevalidation) init(cfg *Config) {
|
||||
tr.activeReq = make(map[enode.ID]struct{})
|
||||
tr.fast.nextTime = never
|
||||
tr.fast.interval = cfg.PingInterval
|
||||
tr.fast.name = "fast"
|
||||
tr.slow.nextTime = never
|
||||
tr.slow.interval = cfg.PingInterval * slowRevalidationFactor
|
||||
tr.slow.name = "slow"
|
||||
}
|
||||
|
||||
// nodeAdded is called when the table receives a new node.
|
||||
func (tr *tableRevalidation) nodeAdded(tab *Table, n *tableNode) {
|
||||
tr.fast.push(n, tab.cfg.Clock.Now(), &tab.rand)
|
||||
}
|
||||
|
||||
// nodeRemoved is called when a node was removed from the table.
|
||||
func (tr *tableRevalidation) nodeRemoved(n *tableNode) {
|
||||
if n.revalList == nil {
|
||||
panic(fmt.Errorf("removed node %v has nil revalList", n.ID()))
|
||||
}
|
||||
n.revalList.remove(n)
|
||||
}
|
||||
|
||||
// nodeEndpointChanged is called when a change in IP or port is detected.
|
||||
func (tr *tableRevalidation) nodeEndpointChanged(tab *Table, n *tableNode) {
|
||||
n.isValidatedLive = false
|
||||
tr.moveToList(&tr.fast, n, tab.cfg.Clock.Now(), &tab.rand)
|
||||
}
|
||||
|
||||
// run performs node revalidation.
|
||||
// It returns the next time it should be invoked, which is used in the Table main loop
|
||||
// to schedule a timer. However, run can be called at any time.
|
||||
func (tr *tableRevalidation) run(tab *Table, now mclock.AbsTime) (nextTime mclock.AbsTime) {
|
||||
if n := tr.fast.get(now, &tab.rand, tr.activeReq); n != nil {
|
||||
tr.startRequest(tab, n)
|
||||
tr.fast.schedule(now, &tab.rand)
|
||||
}
|
||||
if n := tr.slow.get(now, &tab.rand, tr.activeReq); n != nil {
|
||||
tr.startRequest(tab, n)
|
||||
tr.slow.schedule(now, &tab.rand)
|
||||
}
|
||||
|
||||
return min(tr.fast.nextTime, tr.slow.nextTime)
|
||||
}
|
||||
|
||||
// startRequest spawns a revalidation request for node n.
|
||||
func (tr *tableRevalidation) startRequest(tab *Table, n *tableNode) {
|
||||
if _, ok := tr.activeReq[n.ID()]; ok {
|
||||
panic(fmt.Errorf("duplicate startRequest (node %v)", n.ID()))
|
||||
}
|
||||
tr.activeReq[n.ID()] = struct{}{}
|
||||
resp := revalidationResponse{n: n}
|
||||
|
||||
// Fetch the node while holding lock.
|
||||
tab.mutex.Lock()
|
||||
node := n.Node
|
||||
tab.mutex.Unlock()
|
||||
|
||||
go tab.doRevalidate(resp, node)
|
||||
}
|
||||
|
||||
func (tab *Table) doRevalidate(resp revalidationResponse, node *enode.Node) {
|
||||
// Ping the selected node and wait for a pong response.
|
||||
remoteSeq, err := tab.net.ping(node)
|
||||
resp.didRespond = err == nil
|
||||
|
||||
// Also fetch record if the node replied and returned a higher sequence number.
|
||||
if remoteSeq > node.Seq() {
|
||||
newrec, err := tab.net.RequestENR(node)
|
||||
if err != nil {
|
||||
tab.log.Debug("ENR request failed", "id", node.ID(), "err", err)
|
||||
} else {
|
||||
resp.newRecord = newrec
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case tab.revalResponseCh <- resp:
|
||||
case <-tab.closed:
|
||||
}
|
||||
}
|
||||
|
||||
// handleResponse processes the result of a revalidation request.
|
||||
func (tr *tableRevalidation) handleResponse(tab *Table, resp revalidationResponse) {
|
||||
var (
|
||||
now = tab.cfg.Clock.Now()
|
||||
n = resp.n
|
||||
b = tab.bucket(n.ID())
|
||||
)
|
||||
delete(tr.activeReq, n.ID())
|
||||
|
||||
// If the node was removed from the table while getting checked, we need to stop
|
||||
// processing here to avoid re-adding it.
|
||||
if n.revalList == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Store potential seeds in database.
|
||||
// This is done via defer to avoid holding Table lock while writing to DB.
|
||||
defer func() {
|
||||
if n.isValidatedLive && n.livenessChecks > 5 {
|
||||
tab.db.UpdateNode(resp.n.Node)
|
||||
}
|
||||
}()
|
||||
|
||||
// Remaining logic needs access to Table internals.
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
if !resp.didRespond {
|
||||
n.livenessChecks /= 3
|
||||
if n.livenessChecks <= 0 {
|
||||
tab.deleteInBucket(b, n.ID())
|
||||
} else {
|
||||
tab.log.Debug("Node revalidation failed", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
|
||||
tr.moveToList(&tr.fast, n, now, &tab.rand)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// The node responded.
|
||||
n.livenessChecks++
|
||||
n.isValidatedLive = true
|
||||
tab.log.Debug("Node revalidated", "b", b.index, "id", n.ID(), "checks", n.livenessChecks, "q", n.revalList.name)
|
||||
var endpointChanged bool
|
||||
if resp.newRecord != nil {
|
||||
_, endpointChanged = tab.bumpInBucket(b, resp.newRecord, false)
|
||||
}
|
||||
|
||||
// Node moves to slow list if it passed and hasn't changed.
|
||||
if !endpointChanged {
|
||||
tr.moveToList(&tr.slow, n, now, &tab.rand)
|
||||
}
|
||||
}
|
||||
|
||||
// moveToList ensures n is in the 'dest' list.
|
||||
func (tr *tableRevalidation) moveToList(dest *revalidationList, n *tableNode, now mclock.AbsTime, rand randomSource) {
|
||||
if n.revalList == dest {
|
||||
return
|
||||
}
|
||||
if n.revalList != nil {
|
||||
n.revalList.remove(n)
|
||||
}
|
||||
dest.push(n, now, rand)
|
||||
}
|
||||
|
||||
// revalidationList holds a list nodes and the next revalidation time.
|
||||
type revalidationList struct {
|
||||
nodes []*tableNode
|
||||
nextTime mclock.AbsTime
|
||||
interval time.Duration
|
||||
name string
|
||||
}
|
||||
|
||||
// get returns a random node from the queue. Nodes in the 'exclude' map are not returned.
|
||||
func (list *revalidationList) get(now mclock.AbsTime, rand randomSource, exclude map[enode.ID]struct{}) *tableNode {
|
||||
if now < list.nextTime || len(list.nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < len(list.nodes)*3; i++ {
|
||||
n := list.nodes[rand.Intn(len(list.nodes))]
|
||||
_, excluded := exclude[n.ID()]
|
||||
if !excluded {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (list *revalidationList) schedule(now mclock.AbsTime, rand randomSource) {
|
||||
list.nextTime = now.Add(time.Duration(rand.Int63n(int64(list.interval))))
|
||||
}
|
||||
|
||||
func (list *revalidationList) push(n *tableNode, now mclock.AbsTime, rand randomSource) {
|
||||
list.nodes = append(list.nodes, n)
|
||||
if list.nextTime == never {
|
||||
list.schedule(now, rand)
|
||||
}
|
||||
n.revalList = list
|
||||
}
|
||||
|
||||
func (list *revalidationList) remove(n *tableNode) {
|
||||
i := slices.Index(list.nodes, n)
|
||||
if i == -1 {
|
||||
panic(fmt.Errorf("node %v not found in list", n.ID()))
|
||||
}
|
||||
list.nodes = slices.Delete(list.nodes, i, i+1)
|
||||
if len(list.nodes) == 0 {
|
||||
list.nextTime = never
|
||||
}
|
||||
n.revalList = nil
|
||||
}
|
||||
|
||||
func (list *revalidationList) contains(id enode.ID) bool {
|
||||
return slices.ContainsFunc(list.nodes, func(n *tableNode) bool {
|
||||
return n.ID() == id
|
||||
})
|
||||
}
|
||||
119
p2p/discover/table_reval_test.go
Normal file
119
p2p/discover/table_reval_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
)
|
||||
|
||||
// This test checks that revalidation can handle a node disappearing while
|
||||
// a request is active.
|
||||
func TestRevalidation_nodeRemoved(t *testing.T) {
|
||||
var (
|
||||
clock mclock.Simulated
|
||||
transport = newPingRecorder()
|
||||
tab, db = newInactiveTestTable(transport, Config{Clock: &clock})
|
||||
tr = &tab.revalidation
|
||||
)
|
||||
defer db.Close()
|
||||
|
||||
// Add a node to the table.
|
||||
node := nodeAtDistance(tab.self().ID(), 255, net.IP{77, 88, 99, 1})
|
||||
tab.handleAddNode(addNodeOp{node: node})
|
||||
|
||||
// Start a revalidation request. Schedule once to get the next start time,
|
||||
// then advance the clock to that point and schedule again to start.
|
||||
next := tr.run(tab, clock.Now())
|
||||
clock.Run(time.Duration(next + 1))
|
||||
tr.run(tab, clock.Now())
|
||||
if len(tr.activeReq) != 1 {
|
||||
t.Fatal("revalidation request did not start:", tr.activeReq)
|
||||
}
|
||||
|
||||
// Delete the node.
|
||||
tab.deleteInBucket(tab.bucket(node.ID()), node.ID())
|
||||
|
||||
// Now finish the revalidation request.
|
||||
var resp revalidationResponse
|
||||
select {
|
||||
case resp = <-tab.revalResponseCh:
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("timed out waiting for revalidation")
|
||||
}
|
||||
tr.handleResponse(tab, resp)
|
||||
|
||||
// Ensure the node was not re-added to the table.
|
||||
if tab.getNode(node.ID()) != nil {
|
||||
t.Fatal("node was re-added to Table")
|
||||
}
|
||||
if tr.fast.contains(node.ID()) || tr.slow.contains(node.ID()) {
|
||||
t.Fatal("removed node contained in revalidation list")
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks that nodes with an updated endpoint remain in the fast revalidation list.
|
||||
func TestRevalidation_endpointUpdate(t *testing.T) {
|
||||
var (
|
||||
clock mclock.Simulated
|
||||
transport = newPingRecorder()
|
||||
tab, db = newInactiveTestTable(transport, Config{Clock: &clock})
|
||||
tr = &tab.revalidation
|
||||
)
|
||||
defer db.Close()
|
||||
|
||||
// Add node to table.
|
||||
node := nodeAtDistance(tab.self().ID(), 255, net.IP{77, 88, 99, 1})
|
||||
tab.handleAddNode(addNodeOp{node: node})
|
||||
|
||||
// Update the record in transport, including endpoint update.
|
||||
record := node.Record()
|
||||
record.Set(enr.IP{100, 100, 100, 100})
|
||||
record.Set(enr.UDP(9999))
|
||||
nodev2 := enode.SignNull(record, node.ID())
|
||||
transport.updateRecord(nodev2)
|
||||
|
||||
// Start a revalidation request. Schedule once to get the next start time,
|
||||
// then advance the clock to that point and schedule again to start.
|
||||
next := tr.run(tab, clock.Now())
|
||||
clock.Run(time.Duration(next + 1))
|
||||
tr.run(tab, clock.Now())
|
||||
if len(tr.activeReq) != 1 {
|
||||
t.Fatal("revalidation request did not start:", tr.activeReq)
|
||||
}
|
||||
|
||||
// Now finish the revalidation request.
|
||||
var resp revalidationResponse
|
||||
select {
|
||||
case resp = <-tab.revalResponseCh:
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("timed out waiting for revalidation")
|
||||
}
|
||||
tr.handleResponse(tab, resp)
|
||||
|
||||
if tr.fast.nodes[0].ID() != node.ID() {
|
||||
t.Fatal("node not contained in fast revalidation list")
|
||||
}
|
||||
if tr.fast.nodes[0].isValidatedLive {
|
||||
t.Fatal("node is marked live after endpoint change")
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,17 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"net"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/testlog"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
|
|
@ -49,33 +52,60 @@ func TestTable_pingReplace(t *testing.T) {
|
|||
}
|
||||
|
||||
func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
|
||||
simclock := new(mclock.Simulated)
|
||||
transport := newPingRecorder()
|
||||
|
||||
tab, db := newTestTable(transport)
|
||||
tab, db := newTestTable(transport, Config{
|
||||
Clock: simclock,
|
||||
Log: testlog.Logger(t, log.LevelTrace),
|
||||
})
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
<-tab.initDone
|
||||
|
||||
// Fill up the sender's bucket.
|
||||
pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
||||
pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99))
|
||||
last := fillBucket(tab, pingSender)
|
||||
replacementNodeKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
||||
replacementNode := enode.NewV4(&replacementNodeKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99)
|
||||
last := fillBucket(tab, replacementNode.ID())
|
||||
tab.mutex.Lock()
|
||||
nodeEvents := newNodeEventRecorder(128)
|
||||
tab.nodeAddedHook = nodeEvents.nodeAdded
|
||||
tab.nodeRemovedHook = nodeEvents.nodeRemoved
|
||||
tab.mutex.Unlock()
|
||||
|
||||
// Add the sender as if it just pinged us. Revalidate should replace the last node in
|
||||
// its bucket if it is unresponsive. Revalidate again to ensure that
|
||||
// The revalidation process should replace
|
||||
// this node in the bucket if it is unresponsive.
|
||||
transport.dead[last.ID()] = !lastInBucketIsResponding
|
||||
transport.dead[pingSender.ID()] = !newNodeIsResponding
|
||||
transport.dead[replacementNode.ID()] = !newNodeIsResponding
|
||||
|
||||
tab.addSeenNode(pingSender)
|
||||
tab.doRevalidate(make(chan struct{}, 1))
|
||||
tab.doRevalidate(make(chan struct{}, 1))
|
||||
// Add replacement node to table.
|
||||
tab.addFoundNode(replacementNode, false)
|
||||
|
||||
if !transport.pinged[last.ID()] {
|
||||
// Oldest node in bucket is pinged to see whether it is still alive.
|
||||
t.Error("table did not ping last node in bucket")
|
||||
t.Log("last:", last.ID())
|
||||
t.Log("replacement:", replacementNode.ID())
|
||||
|
||||
// Wait until the last node was pinged.
|
||||
waitForRevalidationPing(t, transport, tab, last.ID())
|
||||
|
||||
if !lastInBucketIsResponding {
|
||||
if !nodeEvents.waitNodeAbsent(last.ID(), 2*time.Second) {
|
||||
t.Error("last node was not removed")
|
||||
}
|
||||
if !nodeEvents.waitNodePresent(replacementNode.ID(), 2*time.Second) {
|
||||
t.Error("replacement node was not added")
|
||||
}
|
||||
|
||||
// If a replacement is expected, we also need to wait until the replacement node
|
||||
// was pinged and added/removed.
|
||||
waitForRevalidationPing(t, transport, tab, replacementNode.ID())
|
||||
if !newNodeIsResponding {
|
||||
if !nodeEvents.waitNodeAbsent(replacementNode.ID(), 2*time.Second) {
|
||||
t.Error("replacement node was not removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check bucket content.
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
|
|
@ -83,86 +113,50 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
|
|||
if !lastInBucketIsResponding && !newNodeIsResponding {
|
||||
wantSize--
|
||||
}
|
||||
|
||||
if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize {
|
||||
t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize)
|
||||
bucket := tab.bucket(replacementNode.ID())
|
||||
if l := len(bucket.entries); l != wantSize {
|
||||
t.Errorf("wrong bucket size after revalidation: got %d, want %d", l, wantSize)
|
||||
}
|
||||
|
||||
if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding {
|
||||
t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding)
|
||||
if ok := containsID(bucket.entries, last.ID()); ok != lastInBucketIsResponding {
|
||||
t.Errorf("revalidated node found: %t, want: %t", ok, lastInBucketIsResponding)
|
||||
}
|
||||
|
||||
wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
|
||||
if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry {
|
||||
t.Errorf("new entry found: %t, want: %t", found, wantNewEntry)
|
||||
if ok := containsID(bucket.entries, replacementNode.ID()); ok != wantNewEntry {
|
||||
t.Errorf("replacement node found: %t, want: %t", ok, wantNewEntry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBucket_bumpNoDuplicates(t *testing.T) {
|
||||
t.Parallel()
|
||||
// waitForRevalidationPing waits until a PING message is sent to a node with the given id.
|
||||
func waitForRevalidationPing(t *testing.T, transport *pingRecorder, tab *Table, id enode.ID) *enode.Node {
|
||||
t.Helper()
|
||||
|
||||
cfg := &quick.Config{
|
||||
MaxCount: 1000,
|
||||
Rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
Values: func(args []reflect.Value, rand *rand.Rand) {
|
||||
// generate a random list of nodes. this will be the content of the bucket.
|
||||
n := rand.Intn(bucketSize-1) + 1
|
||||
nodes := make([]*node, n)
|
||||
for i := range nodes {
|
||||
nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200))
|
||||
}
|
||||
args[0] = reflect.ValueOf(nodes)
|
||||
// generate random bump positions.
|
||||
bumps := make([]int, rand.Intn(100))
|
||||
for i := range bumps {
|
||||
bumps[i] = rand.Intn(len(nodes))
|
||||
}
|
||||
args[1] = reflect.ValueOf(bumps)
|
||||
},
|
||||
}
|
||||
|
||||
prop := func(nodes []*node, bumps []int) (ok bool) {
|
||||
tab, db := newTestTable(newPingRecorder())
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
b := &bucket{entries: make([]*node, len(nodes))}
|
||||
copy(b.entries, nodes)
|
||||
|
||||
for i, pos := range bumps {
|
||||
tab.bumpInBucket(b, b.entries[pos])
|
||||
|
||||
if hasDuplicates(b.entries) {
|
||||
t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
|
||||
|
||||
for _, n := range b.entries {
|
||||
t.Logf(" %p", n)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
simclock := tab.cfg.Clock.(*mclock.Simulated)
|
||||
maxAttempts := tab.len() * 8
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
simclock.Run(tab.cfg.PingInterval * slowRevalidationFactor)
|
||||
p := transport.waitPing(2 * time.Second)
|
||||
if p == nil {
|
||||
t.Fatal("Table did not send revalidation ping")
|
||||
}
|
||||
if id == (enode.ID{}) || p.ID() == id {
|
||||
return p
|
||||
}
|
||||
|
||||
checkIPLimitInvariant(t, tab)
|
||||
|
||||
return true
|
||||
}
|
||||
if err := quick.Check(prop, cfg); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Fatalf("Table did not ping node %v (%d attempts)", id, maxAttempts)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This checks that the table-wide IP limit is applied correctly.
|
||||
func TestTable_IPLimit(t *testing.T) {
|
||||
transport := newPingRecorder()
|
||||
|
||||
tab, db := newTestTable(transport)
|
||||
tab, db := newTestTable(transport, Config{})
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
for i := 0; i < tableIPLimit+1; i++ {
|
||||
n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
|
||||
tab.addSeenNode(n)
|
||||
tab.addFoundNode(n, false)
|
||||
}
|
||||
|
||||
if tab.len() > tableIPLimit {
|
||||
|
|
@ -175,15 +169,14 @@ func TestTable_IPLimit(t *testing.T) {
|
|||
// This checks that the per-bucket IP limit is applied correctly.
|
||||
func TestTable_BucketIPLimit(t *testing.T) {
|
||||
transport := newPingRecorder()
|
||||
|
||||
tab, db := newTestTable(transport)
|
||||
tab, db := newTestTable(transport, Config{})
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
d := 3
|
||||
for i := 0; i < bucketIPLimit+1; i++ {
|
||||
n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
|
||||
tab.addSeenNode(n)
|
||||
tab.addFoundNode(n, false)
|
||||
}
|
||||
|
||||
if tab.len() > bucketIPLimit {
|
||||
|
|
@ -217,8 +210,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
|||
test := func(test *closeTest) bool {
|
||||
// for any node table, Target and N
|
||||
transport := newPingRecorder()
|
||||
|
||||
tab, db := newTestTable(transport)
|
||||
tab, db := newTestTable(transport, Config{})
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
fillTable(tab, test.All, true)
|
||||
|
|
@ -251,7 +243,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
|||
// check that the result nodes have minimum distance to target.
|
||||
for _, b := range tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
if contains(result, n.ID()) {
|
||||
if containsID(result, n.ID()) {
|
||||
continue // don't run the check below for nodes in result
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +269,7 @@ func TestTable_findnodeByID(t *testing.T) {
|
|||
type closeTest struct {
|
||||
Self enode.ID
|
||||
Target enode.ID
|
||||
All []*node
|
||||
All []*enode.Node
|
||||
N int
|
||||
}
|
||||
|
||||
|
|
@ -291,16 +283,15 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
|
|||
for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
|
||||
r := new(enr.Record)
|
||||
r.Set(enr.IP(genIP(rand)))
|
||||
n := wrapNode(enode.SignNull(r, id))
|
||||
n.livenessChecks = 1
|
||||
n := enode.SignNull(r, id)
|
||||
t.All = append(t.All, n)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(t)
|
||||
}
|
||||
|
||||
func TestTable_addVerifiedNode(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder())
|
||||
func TestTable_addInboundNode(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||
<-tab.initDone
|
||||
|
||||
defer db.Close()
|
||||
|
|
@ -309,32 +300,29 @@ func TestTable_addVerifiedNode(t *testing.T) {
|
|||
// Insert two nodes.
|
||||
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
||||
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
||||
tab.addSeenNode(n1)
|
||||
tab.addSeenNode(n2)
|
||||
tab.addFoundNode(n1, false)
|
||||
tab.addFoundNode(n2, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2})
|
||||
|
||||
// Verify bucket content:
|
||||
bcontent := []*node{n1, n2}
|
||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
|
||||
t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
|
||||
}
|
||||
|
||||
// Add a changed version of n2.
|
||||
// Add a changed version of n2. The bucket should be updated.
|
||||
newrec := n2.Record()
|
||||
newrec.Set(enr.IP{99, 99, 99, 99})
|
||||
newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
|
||||
tab.addVerifiedNode(newn2)
|
||||
n2v2 := enode.SignNull(newrec, n2.ID())
|
||||
tab.addInboundNode(n2v2)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||
|
||||
// Check that bucket is updated correctly.
|
||||
newBcontent := []*node{newn2, n1}
|
||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) {
|
||||
t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
|
||||
}
|
||||
|
||||
checkIPLimitInvariant(t, tab)
|
||||
// Try updating n2 without sequence number change. The update is accepted
|
||||
// because it's inbound.
|
||||
newrec = n2.Record()
|
||||
newrec.Set(enr.IP{100, 100, 100, 100})
|
||||
newrec.SetSeq(n2.Seq())
|
||||
n2v3 := enode.SignNull(newrec, n2.ID())
|
||||
tab.addInboundNode(n2v3)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2v3})
|
||||
}
|
||||
|
||||
func TestTable_addSeenNode(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder())
|
||||
func TestTable_addFoundNode(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||
<-tab.initDone
|
||||
|
||||
defer db.Close()
|
||||
|
|
@ -343,26 +331,86 @@ func TestTable_addSeenNode(t *testing.T) {
|
|||
// Insert two nodes.
|
||||
n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
|
||||
n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
|
||||
tab.addSeenNode(n1)
|
||||
tab.addSeenNode(n2)
|
||||
tab.addFoundNode(n1, false)
|
||||
tab.addFoundNode(n2, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2})
|
||||
|
||||
// Verify bucket content:
|
||||
bcontent := []*node{n1, n2}
|
||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
|
||||
t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
|
||||
}
|
||||
|
||||
// Add a changed version of n2.
|
||||
// Add a changed version of n2. The bucket should be updated.
|
||||
newrec := n2.Record()
|
||||
newrec.Set(enr.IP{99, 99, 99, 99})
|
||||
newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
|
||||
tab.addSeenNode(newn2)
|
||||
n2v2 := enode.SignNull(newrec, n2.ID())
|
||||
tab.addFoundNode(n2v2, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||
|
||||
// Check that bucket content is unchanged.
|
||||
if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
|
||||
t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
|
||||
// Try updating n2 without a sequence number change.
|
||||
// The update should not be accepted.
|
||||
newrec = n2.Record()
|
||||
newrec.Set(enr.IP{100, 100, 100, 100})
|
||||
newrec.SetSeq(n2.Seq())
|
||||
n2v3 := enode.SignNull(newrec, n2.ID())
|
||||
tab.addFoundNode(n2v3, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1, n2v2})
|
||||
}
|
||||
|
||||
// This test checks that discv4 nodes can update their own endpoint via PING.
|
||||
func TestTable_addInboundNodeUpdateV4Accept(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||
<-tab.initDone
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
// Add a v4 node.
|
||||
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
|
||||
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
|
||||
tab.addInboundNode(n1)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||
|
||||
// Add an updated version with changed IP.
|
||||
// The update will be accepted because it is inbound.
|
||||
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
|
||||
tab.addInboundNode(n1v2)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1v2})
|
||||
}
|
||||
|
||||
// This test checks that discv4 node entries will NOT be updated when a
|
||||
// changed record is found.
|
||||
func TestTable_addFoundNodeV4UpdateReject(t *testing.T) {
|
||||
tab, db := newTestTable(newPingRecorder(), Config{})
|
||||
<-tab.initDone
|
||||
defer db.Close()
|
||||
defer tab.close()
|
||||
|
||||
// Add a v4 node.
|
||||
key, _ := crypto.HexToECDSA("dd3757a8075e88d0f2b1431e7d3c5b1562e1c0aab9643707e8cbfcc8dae5cfe3")
|
||||
n1 := enode.NewV4(&key.PublicKey, net.IP{88, 77, 66, 1}, 9000, 9000)
|
||||
tab.addFoundNode(n1, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||
|
||||
// Add an updated version with changed IP.
|
||||
// The update won't be accepted because it isn't inbound.
|
||||
n1v2 := enode.NewV4(&key.PublicKey, net.IP{99, 99, 99, 99}, 9000, 9000)
|
||||
tab.addFoundNode(n1v2, false)
|
||||
checkBucketContent(t, tab, []*enode.Node{n1})
|
||||
}
|
||||
|
||||
func checkBucketContent(t *testing.T, tab *Table, nodes []*enode.Node) {
|
||||
t.Helper()
|
||||
|
||||
b := tab.bucket(nodes[0].ID())
|
||||
if reflect.DeepEqual(unwrapNodes(b.entries), nodes) {
|
||||
return
|
||||
}
|
||||
t.Log("wrong bucket content. have nodes:")
|
||||
for _, n := range b.entries {
|
||||
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
|
||||
}
|
||||
t.Log("want nodes:")
|
||||
for _, n := range nodes {
|
||||
t.Logf(" %v (seq=%v, ip=%v)", n.ID(), n.Seq(), n.IP())
|
||||
}
|
||||
t.FailNow()
|
||||
|
||||
// Also check IP limits.
|
||||
checkIPLimitInvariant(t, tab)
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +418,10 @@ func TestTable_addSeenNode(t *testing.T) {
|
|||
// announces a new sequence number, the new record should be pulled.
|
||||
func TestTable_revalidateSyncRecord(t *testing.T) {
|
||||
transport := newPingRecorder()
|
||||
tab, db := newTestTable(transport)
|
||||
tab, db := newTestTable(transport, Config{
|
||||
Clock: new(mclock.Simulated),
|
||||
Log: testlog.Logger(t, log.LevelTrace),
|
||||
})
|
||||
<-tab.initDone
|
||||
|
||||
defer db.Close()
|
||||
|
|
@ -382,15 +433,18 @@ func TestTable_revalidateSyncRecord(t *testing.T) {
|
|||
r.Set(enr.IP(net.IP{127, 0, 0, 1}))
|
||||
|
||||
id := enode.ID{1}
|
||||
n1 := wrapNode(enode.SignNull(&r, id))
|
||||
tab.addSeenNode(n1)
|
||||
n1 := enode.SignNull(&r, id)
|
||||
tab.addFoundNode(n1, false)
|
||||
|
||||
// Update the node record.
|
||||
r.Set(enr.WithEntry("foo", "bar"))
|
||||
n2 := enode.SignNull(&r, id)
|
||||
transport.updateRecord(n2)
|
||||
|
||||
tab.doRevalidate(make(chan struct{}, 1))
|
||||
// Wait for revalidation. We wait for the node to be revalidated two times
|
||||
// in order to synchronize with the update in the able.
|
||||
waitForRevalidationPing(t, transport, tab, n2.ID())
|
||||
waitForRevalidationPing(t, transport, tab, n2.ID())
|
||||
|
||||
intable := tab.getNode(id)
|
||||
if !reflect.DeepEqual(intable, n2) {
|
||||
|
|
@ -405,7 +459,7 @@ func TestNodesPush(t *testing.T) {
|
|||
n1 := nodeAtDistance(target, 255, intIP(1))
|
||||
n2 := nodeAtDistance(target, 254, intIP(2))
|
||||
n3 := nodeAtDistance(target, 253, intIP(3))
|
||||
perm := [][]*node{
|
||||
perm := [][]*enode.Node{
|
||||
{n3, n2, n1},
|
||||
{n3, n1, n2},
|
||||
{n2, n3, n1},
|
||||
|
|
@ -420,8 +474,7 @@ func TestNodesPush(t *testing.T) {
|
|||
for _, n := range nodes {
|
||||
list.push(n, 3)
|
||||
}
|
||||
|
||||
if !slicesEqual(list.entries, perm[0], nodeIDEqual) {
|
||||
if !slices.EqualFunc(list.entries, perm[0], nodeIDEqual) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
|
|
@ -432,31 +485,16 @@ func TestNodesPush(t *testing.T) {
|
|||
for _, n := range nodes {
|
||||
list.push(n, 2)
|
||||
}
|
||||
|
||||
if !slicesEqual(list.entries, perm[0][:2], nodeIDEqual) {
|
||||
if !slices.EqualFunc(list.entries, perm[0][:2], nodeIDEqual) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nodeIDEqual(n1, n2 *node) bool {
|
||||
func nodeIDEqual[N nodeType](n1, n2 N) bool {
|
||||
return n1.ID() == n2.ID()
|
||||
}
|
||||
|
||||
func slicesEqual[T any](s1, s2 []T, check func(e1, e2 T) bool) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range s1 {
|
||||
if !check(s1[i], s2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// gen wraps quick.Value so it's easier to use.
|
||||
// it generates a random value of the given value's type.
|
||||
func gen(typ interface{}, rand *rand.Rand) interface{} {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import (
|
|||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
|
@ -41,29 +43,33 @@ func init() {
|
|||
nullNode = enode.SignNull(&r, enode.ID{})
|
||||
}
|
||||
|
||||
func newTestTable(t transport) (*Table, *enode.DB) {
|
||||
cfg := Config{}
|
||||
func newTestTable(t transport, cfg Config) (*Table, *enode.DB) {
|
||||
tab, db := newInactiveTestTable(t, cfg)
|
||||
go tab.loop()
|
||||
return tab, db
|
||||
}
|
||||
|
||||
// newInactiveTestTable creates a Table without running the main loop.
|
||||
func newInactiveTestTable(t transport, cfg Config) (*Table, *enode.DB) {
|
||||
db, _ := enode.OpenDB("")
|
||||
tab, _ := newTable(t, db, cfg)
|
||||
go tab.loop()
|
||||
|
||||
return tab, db
|
||||
}
|
||||
|
||||
// nodeAtDistance creates a node for which enode.LogDist(base, n.id) == ld.
|
||||
func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node {
|
||||
func nodeAtDistance(base enode.ID, ld int, ip net.IP) *enode.Node {
|
||||
var r enr.Record
|
||||
|
||||
r.Set(enr.IP(ip))
|
||||
r.Set(enr.UDP(30303))
|
||||
return wrapNode(enode.SignNull(&r, idAtDistance(base, ld)))
|
||||
return enode.SignNull(&r, idAtDistance(base, ld))
|
||||
}
|
||||
|
||||
// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld.
|
||||
func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node {
|
||||
results := make([]*enode.Node, n)
|
||||
for i := range results {
|
||||
results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i)))
|
||||
results[i] = nodeAtDistance(base, ld, intIP(i))
|
||||
}
|
||||
|
||||
return results
|
||||
|
|
@ -106,12 +112,14 @@ func intIP(i int) net.IP {
|
|||
}
|
||||
|
||||
// fillBucket inserts nodes into the given bucket until it is full.
|
||||
func fillBucket(tab *Table, n *node) (last *node) {
|
||||
ld := enode.LogDist(tab.self().ID(), n.ID())
|
||||
b := tab.bucket(n.ID())
|
||||
|
||||
func fillBucket(tab *Table, id enode.ID) (last *tableNode) {
|
||||
ld := enode.LogDist(tab.self().ID(), id)
|
||||
b := tab.bucket(id)
|
||||
for len(b.entries) < bucketSize {
|
||||
b.entries = append(b.entries, nodeAtDistance(tab.self().ID(), ld, intIP(ld)))
|
||||
node := nodeAtDistance(tab.self().ID(), ld, intIP(ld))
|
||||
if !tab.addFoundNode(node, false) {
|
||||
panic("node not added")
|
||||
}
|
||||
}
|
||||
|
||||
return b.entries[bucketSize-1]
|
||||
|
|
@ -119,20 +127,19 @@ func fillBucket(tab *Table, n *node) (last *node) {
|
|||
|
||||
// fillTable adds nodes the table to the end of their corresponding bucket
|
||||
// if the bucket is not full. The caller must not hold tab.mutex.
|
||||
func fillTable(tab *Table, nodes []*node, setLive bool) {
|
||||
func fillTable(tab *Table, nodes []*enode.Node, setLive bool) {
|
||||
for _, n := range nodes {
|
||||
if setLive {
|
||||
n.livenessChecks = 1
|
||||
}
|
||||
tab.addSeenNode(n)
|
||||
tab.addFoundNode(n, setLive)
|
||||
}
|
||||
}
|
||||
|
||||
type pingRecorder struct {
|
||||
mu sync.Mutex
|
||||
dead, pinged map[enode.ID]bool
|
||||
records map[enode.ID]*enode.Node
|
||||
n *enode.Node
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
dead map[enode.ID]bool
|
||||
records map[enode.ID]*enode.Node
|
||||
pinged []*enode.Node
|
||||
n *enode.Node
|
||||
}
|
||||
|
||||
func newPingRecorder() *pingRecorder {
|
||||
|
|
@ -141,12 +148,13 @@ func newPingRecorder() *pingRecorder {
|
|||
r.Set(enr.IP{0, 0, 0, 0})
|
||||
n := enode.SignNull(&r, enode.ID{})
|
||||
|
||||
return &pingRecorder{
|
||||
t := &pingRecorder{
|
||||
dead: make(map[enode.ID]bool),
|
||||
pinged: make(map[enode.ID]bool),
|
||||
records: make(map[enode.ID]*enode.Node),
|
||||
n: n,
|
||||
}
|
||||
t.cond = sync.NewCond(&t.mu)
|
||||
return t
|
||||
}
|
||||
|
||||
// updateRecord updates a node record. Future calls to ping and
|
||||
|
|
@ -162,12 +170,40 @@ func (t *pingRecorder) Self() *enode.Node { return nullNode }
|
|||
func (t *pingRecorder) lookupSelf() []*enode.Node { return nil }
|
||||
func (t *pingRecorder) lookupRandom() []*enode.Node { return nil }
|
||||
|
||||
func (t *pingRecorder) waitPing(timeout time.Duration) *enode.Node {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
// Wake up the loop on timeout.
|
||||
var timedout atomic.Bool
|
||||
timer := time.AfterFunc(timeout, func() {
|
||||
timedout.Store(true)
|
||||
t.cond.Broadcast()
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
// Wait for a ping.
|
||||
for {
|
||||
if timedout.Load() {
|
||||
return nil
|
||||
}
|
||||
if len(t.pinged) > 0 {
|
||||
n := t.pinged[0]
|
||||
t.pinged = append(t.pinged[:0], t.pinged[1:]...)
|
||||
return n
|
||||
}
|
||||
t.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// ping simulates a ping request.
|
||||
func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.pinged[n.ID()] = true
|
||||
t.pinged = append(t.pinged, n)
|
||||
t.cond.Broadcast()
|
||||
|
||||
if t.dead[n.ID()] {
|
||||
return 0, errTimeout
|
||||
}
|
||||
|
|
@ -191,7 +227,7 @@ func (t *pingRecorder) RequestENR(n *enode.Node) (*enode.Node, error) {
|
|||
return t.records[n.ID()], nil
|
||||
}
|
||||
|
||||
func hasDuplicates(slice []*node) bool {
|
||||
func hasDuplicates(slice []*enode.Node) bool {
|
||||
seen := make(map[enode.ID]bool, len(slice))
|
||||
for i, e := range slice {
|
||||
if e == nil {
|
||||
|
|
@ -241,14 +277,14 @@ func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool {
|
|||
return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP())
|
||||
}
|
||||
|
||||
func sortByID(nodes []*enode.Node) {
|
||||
slices.SortFunc(nodes, func(a, b *enode.Node) int {
|
||||
func sortByID[N nodeType](nodes []N) {
|
||||
slices.SortFunc(nodes, func(a, b N) int {
|
||||
return bytes.Compare(a.ID().Bytes(), b.ID().Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
|
||||
return slices.IsSortedFunc(slice, func(a, b *node) int {
|
||||
func sortedByDistanceTo(distbase enode.ID, slice []*enode.Node) bool {
|
||||
return slices.IsSortedFunc(slice, func(a, b *enode.Node) int {
|
||||
return enode.DistCmp(distbase, a.ID(), b.ID())
|
||||
})
|
||||
}
|
||||
|
|
@ -283,3 +319,57 @@ func hexEncPubkey(h string) (ret encPubkey) {
|
|||
|
||||
return ret
|
||||
}
|
||||
|
||||
type nodeEventRecorder struct {
|
||||
evc chan recordedNodeEvent
|
||||
}
|
||||
|
||||
type recordedNodeEvent struct {
|
||||
node *tableNode
|
||||
added bool
|
||||
}
|
||||
|
||||
func newNodeEventRecorder(buffer int) *nodeEventRecorder {
|
||||
return &nodeEventRecorder{
|
||||
evc: make(chan recordedNodeEvent, buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func (set *nodeEventRecorder) nodeAdded(b *bucket, n *tableNode) {
|
||||
select {
|
||||
case set.evc <- recordedNodeEvent{n, true}:
|
||||
default:
|
||||
panic("no space in event buffer")
|
||||
}
|
||||
}
|
||||
|
||||
func (set *nodeEventRecorder) nodeRemoved(b *bucket, n *tableNode) {
|
||||
select {
|
||||
case set.evc <- recordedNodeEvent{n, false}:
|
||||
default:
|
||||
panic("no space in event buffer")
|
||||
}
|
||||
}
|
||||
|
||||
func (set *nodeEventRecorder) waitNodePresent(id enode.ID, timeout time.Duration) bool {
|
||||
return set.waitNodeEvent(id, timeout, true)
|
||||
}
|
||||
|
||||
func (set *nodeEventRecorder) waitNodeAbsent(id enode.ID, timeout time.Duration) bool {
|
||||
return set.waitNodeEvent(id, timeout, false)
|
||||
}
|
||||
|
||||
func (set *nodeEventRecorder) waitNodeEvent(id enode.ID, timeout time.Duration, added bool) bool {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case ev := <-set.evc:
|
||||
if ev.node.ID() == id && ev.added == added {
|
||||
return true
|
||||
}
|
||||
case <-timer.C:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ package discover
|
|||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestUDPv4_Lookup(t *testing.T) {
|
||||
|
|
@ -40,7 +40,7 @@ func TestUDPv4_Lookup(t *testing.T) {
|
|||
}
|
||||
|
||||
// Seed table with initial node.
|
||||
fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true)
|
||||
fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true)
|
||||
|
||||
// Start the lookup.
|
||||
resultC := make(chan []*enode.Node, 1)
|
||||
|
|
@ -76,9 +76,9 @@ func TestUDPv4_LookupIterator(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
// Seed table with initial nodes.
|
||||
bootnodes := make([]*node, len(lookupTestnet.dists[256]))
|
||||
bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
|
||||
for i := range lookupTestnet.dists[256] {
|
||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
||||
bootnodes[i] = lookupTestnet.node(256, i)
|
||||
}
|
||||
fillTable(test.table, bootnodes, true)
|
||||
go serveTestnet(test, lookupTestnet)
|
||||
|
|
@ -115,9 +115,9 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
// Seed table with initial nodes.
|
||||
bootnodes := make([]*node, len(lookupTestnet.dists[256]))
|
||||
bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
|
||||
for i := range lookupTestnet.dists[256] {
|
||||
bootnodes[i] = wrapNode(lookupTestnet.node(256, i))
|
||||
bootnodes[i] = lookupTestnet.node(256, i)
|
||||
}
|
||||
fillTable(test.table, bootnodes, true)
|
||||
go serveTestnet(test, lookupTestnet)
|
||||
|
|
@ -148,7 +148,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
|
|||
|
||||
func serveTestnet(test *udpTest, testnet *preminedTestnet) {
|
||||
for done := false; !done; {
|
||||
done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) {
|
||||
done = test.waitPacketOut(func(p v4wire.Packet, to netip.AddrPort, hash []byte) {
|
||||
n, key := testnet.nodeByAddr(to)
|
||||
switch p.(type) {
|
||||
case *v4wire.Ping:
|
||||
|
|
@ -171,12 +171,10 @@ func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node
|
|||
for _, e := range results {
|
||||
t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes())
|
||||
}
|
||||
|
||||
if hasDuplicates(wrapNodes(results)) {
|
||||
if hasDuplicates(results) {
|
||||
t.Errorf("result set contains duplicate entries")
|
||||
}
|
||||
|
||||
if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) {
|
||||
if !sortedByDistanceTo(tn.target.id(), results) {
|
||||
t.Errorf("result set not sorted by distance to target")
|
||||
}
|
||||
|
||||
|
|
@ -285,9 +283,10 @@ func (tn *preminedTestnet) node(dist, index int) *enode.Node {
|
|||
return n
|
||||
}
|
||||
|
||||
func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) {
|
||||
dist := int(addr.IP[1])<<8 + int(addr.IP[2])
|
||||
index := int(addr.IP[3])
|
||||
func (tn *preminedTestnet) nodeByAddr(addr netip.AddrPort) (*enode.Node, *ecdsa.PrivateKey) {
|
||||
ip := addr.Addr().As4()
|
||||
dist := int(ip[1])<<8 + int(ip[2])
|
||||
index := int(ip[3])
|
||||
key := tn.dists[dist][index]
|
||||
|
||||
return tn.node(dist, index), key
|
||||
|
|
@ -296,7 +295,7 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr
|
|||
func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
|
||||
result := make([]v4wire.Node, len(tn.dists[dist]))
|
||||
for i := range result {
|
||||
result[i] = nodeToRPC(wrapNode(tn.node(dist, i)))
|
||||
result[i] = nodeToRPC(tn.node(dist, i))
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ var (
|
|||
errClockWarp = errors.New("reply deadline too far in the future")
|
||||
errClosed = errors.New("socket closed")
|
||||
errLowPort = errors.New("low port")
|
||||
errNoUDPEndpoint = errors.New("node has no UDP endpoint")
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -93,7 +95,7 @@ type UDPv4 struct {
|
|||
type replyMatcher struct {
|
||||
// these fields must match in the reply.
|
||||
from enode.ID
|
||||
ip net.IP
|
||||
ip netip.Addr
|
||||
ptype byte
|
||||
|
||||
// time when the request must complete
|
||||
|
|
@ -119,7 +121,7 @@ type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
|
|||
// reply is a reply packet from a certain node.
|
||||
type reply struct {
|
||||
from enode.ID
|
||||
ip net.IP
|
||||
ip netip.Addr
|
||||
data v4wire.Packet
|
||||
// loop indicates whether there was
|
||||
// a matching request by sending on this channel.
|
||||
|
|
@ -142,7 +144,7 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
|
|||
log: cfg.Log,
|
||||
}
|
||||
|
||||
tab, err := newMeteredTable(t, ln.Database(), cfg)
|
||||
tab, err := newTable(t, ln.Database(), cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -206,10 +208,12 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
|
|||
}
|
||||
|
||||
func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
|
||||
n := t.Self()
|
||||
a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
|
||||
return v4wire.NewEndpoint(a, uint16(n.TCP()))
|
||||
node := t.Self()
|
||||
addr, ok := node.UDPEndpoint()
|
||||
if !ok {
|
||||
return v4wire.Endpoint{}
|
||||
}
|
||||
return v4wire.NewEndpoint(addr, uint16(node.TCP()))
|
||||
}
|
||||
|
||||
// Ping sends a ping message to the given node.
|
||||
|
|
@ -220,7 +224,11 @@ func (t *UDPv4) Ping(n *enode.Node) error {
|
|||
|
||||
// ping sends a ping message to the given node and waits for a reply.
|
||||
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
||||
rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
|
||||
addr, ok := n.UDPEndpoint()
|
||||
if !ok {
|
||||
return 0, errNoUDPEndpoint
|
||||
}
|
||||
rm := t.sendPing(n.ID(), addr, nil)
|
||||
if err = <-rm.errc; err == nil {
|
||||
seq = rm.reply.(*v4wire.Pong).ENRSeq
|
||||
}
|
||||
|
|
@ -230,7 +238,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
|||
|
||||
// sendPing sends a ping message to the given node and invokes the callback
|
||||
// when the reply arrives.
|
||||
func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
|
||||
func (t *UDPv4) sendPing(toid enode.ID, toaddr netip.AddrPort, callback func()) *replyMatcher {
|
||||
req := t.makePing(toaddr)
|
||||
|
||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||
|
|
@ -242,7 +250,7 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
|
|||
}
|
||||
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
|
||||
// reference the ping we're about to send.
|
||||
rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
|
||||
rm := t.pending(toid, toaddr.Addr(), v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
|
||||
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
|
||||
if matched && callback != nil {
|
||||
callback()
|
||||
|
|
@ -251,13 +259,14 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
|
|||
return matched, matched
|
||||
})
|
||||
// Send the packet.
|
||||
t.localNode.UDPContact(toaddr)
|
||||
toUDPAddr := &net.UDPAddr{IP: toaddr.Addr().AsSlice()}
|
||||
t.localNode.UDPContact(toUDPAddr)
|
||||
t.write(toaddr, toid, req.Name(), packet)
|
||||
|
||||
return rm
|
||||
}
|
||||
|
||||
func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
|
||||
func (t *UDPv4) makePing(toaddr netip.AddrPort) *v4wire.Ping {
|
||||
return &v4wire.Ping{
|
||||
Version: 4,
|
||||
From: t.ourEndpoint(),
|
||||
|
|
@ -304,8 +313,12 @@ func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
|
|||
func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
|
||||
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
|
||||
ekey := v4wire.Pubkey(targetKey)
|
||||
it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
||||
return t.findnode(n.ID(), n.addr(), ekey)
|
||||
it := newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
|
||||
addr, ok := n.UDPEndpoint()
|
||||
if !ok {
|
||||
return nil, errNoUDPEndpoint
|
||||
}
|
||||
return t.findnode(n.ID(), addr, ekey)
|
||||
})
|
||||
|
||||
return it
|
||||
|
|
@ -313,21 +326,20 @@ func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
|
|||
|
||||
// findnode sends a findnode request to the given node and waits until
|
||||
// the node has sent up to k neighbors.
|
||||
func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
|
||||
t.ensureBond(toid, toaddr)
|
||||
func (t *UDPv4) findnode(toid enode.ID, toAddrPort netip.AddrPort, target v4wire.Pubkey) ([]*enode.Node, error) {
|
||||
t.ensureBond(toid, toAddrPort)
|
||||
|
||||
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
|
||||
// active until enough nodes have been received.
|
||||
nodes := make([]*node, 0, bucketSize)
|
||||
nodes := make([]*enode.Node, 0, bucketSize)
|
||||
nreceived := 0
|
||||
rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
rm := t.pending(toid, toAddrPort.Addr(), v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
reply := r.(*v4wire.Neighbors)
|
||||
for _, rn := range reply.Nodes {
|
||||
nreceived++
|
||||
|
||||
n, err := t.nodeFromRPC(toaddr, rn)
|
||||
n, err := t.nodeFromRPC(toAddrPort, rn)
|
||||
if err != nil {
|
||||
t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
|
||||
t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toAddrPort, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -336,8 +348,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke
|
|||
|
||||
return true, nreceived >= bucketSize
|
||||
})
|
||||
|
||||
t.send(toaddr, toid, &v4wire.Findnode{
|
||||
t.send(toAddrPort, toid, &v4wire.Findnode{
|
||||
Target: target,
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
})
|
||||
|
|
@ -356,7 +367,7 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubke
|
|||
|
||||
// RequestENR sends ENRRequest to the given node and waits for a response.
|
||||
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||
addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
addr, _ := n.UDPEndpoint()
|
||||
t.ensureBond(n.ID(), addr)
|
||||
|
||||
req := &v4wire.ENRRequest{
|
||||
|
|
@ -370,7 +381,7 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
|||
|
||||
// Add a matcher for the reply to the pending reply queue. Responses are matched if
|
||||
// they reference the request we're about to send.
|
||||
rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
rm := t.pending(n.ID(), addr.Addr(), v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
|
||||
return matched, matched
|
||||
})
|
||||
|
|
@ -393,17 +404,20 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
|||
if respN.Seq() < n.Seq() {
|
||||
return n, nil // response record is older
|
||||
}
|
||||
|
||||
if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil {
|
||||
if err := netutil.CheckRelayIP(addr.Addr().AsSlice(), respN.IP()); err != nil {
|
||||
return nil, fmt.Errorf("invalid IP in response record: %v", err)
|
||||
}
|
||||
|
||||
return respN, nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) TableBuckets() [][]BucketNode {
|
||||
return t.tab.Nodes()
|
||||
}
|
||||
|
||||
// pending adds a reply matcher to the pending reply queue.
|
||||
// see the documentation of type replyMatcher for a detailed explanation.
|
||||
func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
|
||||
func (t *UDPv4) pending(id enode.ID, ip netip.Addr, ptype byte, callback replyMatchFunc) *replyMatcher {
|
||||
ch := make(chan error, 1)
|
||||
p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
|
||||
select {
|
||||
|
|
@ -418,7 +432,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF
|
|||
|
||||
// handleReply dispatches a reply packet, invoking reply matchers. It returns
|
||||
// whether any matcher considered the packet acceptable.
|
||||
func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
|
||||
func (t *UDPv4) handleReply(from enode.ID, fromIP netip.Addr, req v4wire.Packet) bool {
|
||||
matched := make(chan bool, 1)
|
||||
select {
|
||||
case t.gotreply <- reply{from, fromIP, req, matched}:
|
||||
|
|
@ -491,7 +505,7 @@ func (t *UDPv4) loop() {
|
|||
|
||||
for el := plist.Front(); el != nil; el = el.Next() {
|
||||
p := el.Value.(*replyMatcher)
|
||||
if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
|
||||
if p.from == r.from && p.ptype == r.data.Kind() && p.ip == r.ip {
|
||||
ok, requestDone := p.callback(r.data)
|
||||
matched = matched || ok
|
||||
p.reply = r.data
|
||||
|
|
@ -535,7 +549,7 @@ func (t *UDPv4) loop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
|
||||
func (t *UDPv4) send(toaddr netip.AddrPort, toid enode.ID, req v4wire.Packet) ([]byte, error) {
|
||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
|
|
@ -544,8 +558,8 @@ func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]b
|
|||
return hash, t.write(toaddr, toid, req.Name(), packet)
|
||||
}
|
||||
|
||||
func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
|
||||
_, err := t.conn.WriteToUDP(packet, toaddr)
|
||||
func (t *UDPv4) write(toaddr netip.AddrPort, toid enode.ID, what string, packet []byte) error {
|
||||
_, err := t.conn.WriteToUDPAddrPort(packet, toaddr)
|
||||
t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
|
||||
|
||||
return err
|
||||
|
|
@ -562,7 +576,7 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
|||
buf := make([]byte, maxPacketSize)
|
||||
|
||||
for {
|
||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
||||
nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
|
||||
if netutil.IsTemporaryError(err) {
|
||||
// Ignore temporary read errors.
|
||||
t.log.Debug("Temporary UDP read error", "err", err)
|
||||
|
|
@ -585,7 +599,7 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
|
||||
func (t *UDPv4) handlePacket(from netip.AddrPort, buf []byte) error {
|
||||
rawpacket, fromKey, hash, err := v4wire.Decode(buf)
|
||||
if err != nil {
|
||||
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
|
||||
|
|
@ -609,15 +623,16 @@ func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
|
|||
}
|
||||
|
||||
// checkBond checks if the given node has a recent enough endpoint proof.
|
||||
func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
|
||||
return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
|
||||
func (t *UDPv4) checkBond(id enode.ID, ip netip.AddrPort) bool {
|
||||
return time.Since(t.db.LastPongReceived(id, ip.Addr().AsSlice())) < bondExpiration
|
||||
}
|
||||
|
||||
// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
|
||||
// This ensures there is a valid endpoint proof on the remote end.
|
||||
func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
|
||||
tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
|
||||
if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
|
||||
func (t *UDPv4) ensureBond(toid enode.ID, toaddr netip.AddrPort) {
|
||||
ip := toaddr.Addr().AsSlice()
|
||||
tooOld := time.Since(t.db.LastPingReceived(toid, ip)) > bondExpiration
|
||||
if tooOld || t.db.FindFails(toid, ip) > maxFindnodeFailures {
|
||||
rm := t.sendPing(toid, toaddr, nil)
|
||||
<-rm.errc
|
||||
// Wait for them to ping back and process our pong.
|
||||
|
|
@ -625,12 +640,11 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
|
||||
func (t *UDPv4) nodeFromRPC(sender netip.AddrPort, rn v4wire.Node) (*enode.Node, error) {
|
||||
if rn.UDP <= 1024 {
|
||||
return nil, errLowPort
|
||||
}
|
||||
|
||||
if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
|
||||
if err := netutil.CheckRelayIP(sender.Addr().AsSlice(), rn.IP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -642,14 +656,13 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
|
||||
n := enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP))
|
||||
err = n.ValidateComplete()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func nodeToRPC(n *node) v4wire.Node {
|
||||
func nodeToRPC(n *enode.Node) v4wire.Node {
|
||||
var key ecdsa.PublicKey
|
||||
|
||||
var ekey v4wire.Pubkey
|
||||
|
|
@ -692,14 +705,14 @@ type packetHandlerV4 struct {
|
|||
senderKey *ecdsa.PublicKey // used for ping
|
||||
|
||||
// preverify checks whether the packet is valid and should be handled at all.
|
||||
preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
|
||||
preverify func(p *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error
|
||||
// handle handles the packet.
|
||||
handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
|
||||
handle func(req *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte)
|
||||
}
|
||||
|
||||
// PING/v4
|
||||
|
||||
func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
func (t *UDPv4) verifyPing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Ping)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
|
|
@ -714,7 +727,7 @@ func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||
req := h.Packet.(*v4wire.Ping)
|
||||
|
||||
// Reply.
|
||||
|
|
@ -726,49 +739,51 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I
|
|||
})
|
||||
|
||||
// Ping back if our last pong on file is too far in the past.
|
||||
n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
|
||||
if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
|
||||
fromIP := from.Addr().AsSlice()
|
||||
n := enode.NewV4(h.senderKey, fromIP, int(req.From.TCP), int(from.Port()))
|
||||
if time.Since(t.db.LastPongReceived(n.ID(), fromIP)) > bondExpiration {
|
||||
t.sendPing(fromID, from, func() {
|
||||
t.tab.addVerifiedNode(n)
|
||||
t.tab.addInboundNode(n)
|
||||
})
|
||||
} else {
|
||||
t.tab.addVerifiedNode(n)
|
||||
t.tab.addInboundNode(n)
|
||||
}
|
||||
|
||||
// Update node database and endpoint predictor.
|
||||
t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
|
||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
||||
t.db.UpdateLastPingReceived(n.ID(), fromIP, time.Now())
|
||||
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
|
||||
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
|
||||
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||
}
|
||||
|
||||
// PONG/v4
|
||||
|
||||
func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
func (t *UDPv4) verifyPong(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Pong)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
|
||||
if !t.handleReply(fromID, from.IP, req) {
|
||||
if !t.handleReply(fromID, from.Addr(), req) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
|
||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
||||
t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
|
||||
|
||||
fromIP := from.Addr().AsSlice()
|
||||
fromUDPAddr := &net.UDPAddr{IP: fromIP, Port: int(from.Port())}
|
||||
toUDPAddr := &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)}
|
||||
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||
t.db.UpdateLastPongReceived(fromID, fromIP, time.Now())
|
||||
return nil
|
||||
}
|
||||
|
||||
// FINDNODE/v4
|
||||
|
||||
func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Findnode)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
|
||||
if !t.checkBond(fromID, from.IP) {
|
||||
if !t.checkBond(fromID, from) {
|
||||
// No endpoint proof pong exists, we don't process the packet. This prevents an
|
||||
// attack vector where the discovery protocol could be used to amplify traffic in a
|
||||
// DDOS attack. A malicious actor would send a findnode request with the IP address
|
||||
|
|
@ -781,7 +796,7 @@ func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
func (t *UDPv4) handleFindnode(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||
req := h.Packet.(*v4wire.Findnode)
|
||||
|
||||
// Determine closest nodes.
|
||||
|
|
@ -795,7 +810,8 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
|||
var sent bool
|
||||
|
||||
for _, n := range closest {
|
||||
if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
|
||||
fromIP := from.Addr().AsSlice()
|
||||
if netutil.CheckRelayIP(fromIP, n.IP()) == nil {
|
||||
p.Nodes = append(p.Nodes, nodeToRPC(n))
|
||||
}
|
||||
|
||||
|
|
@ -813,14 +829,13 @@ func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID eno
|
|||
|
||||
// NEIGHBORS/v4
|
||||
|
||||
func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Neighbors)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
|
||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
||||
if !t.handleReply(fromID, from.Addr(), h.Packet) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
|
||||
|
|
@ -829,21 +844,20 @@ func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID en
|
|||
|
||||
// ENRREQUEST/v4
|
||||
|
||||
func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.ENRRequest)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
|
||||
if !t.checkBond(fromID, from.IP) {
|
||||
if !t.checkBond(fromID, from) {
|
||||
return errUnknownNode
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
|
||||
t.send(from, fromID, &v4wire.ENRResponse{
|
||||
ReplyTok: mac,
|
||||
Record: *t.localNode.Node().Record(),
|
||||
|
|
@ -852,8 +866,8 @@ func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID e
|
|||
|
||||
// ENRRESPONSE/v4
|
||||
|
||||
func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
||||
func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
if !t.handleReply(fromID, from.Addr(), h.Packet) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
|
@ -55,7 +56,7 @@ type udpTest struct {
|
|||
udp *UDPv4
|
||||
sent [][]byte
|
||||
localkey, remotekey *ecdsa.PrivateKey
|
||||
remoteaddr *net.UDPAddr
|
||||
remoteaddr netip.AddrPort
|
||||
}
|
||||
|
||||
func newUDPTest(t *testing.T) *udpTest {
|
||||
|
|
@ -64,7 +65,7 @@ func newUDPTest(t *testing.T) *udpTest {
|
|||
pipe: newpipe(),
|
||||
localkey: newkey(),
|
||||
remotekey: newkey(),
|
||||
remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
|
||||
remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
|
||||
}
|
||||
|
||||
test.db, _ = enode.OpenDB("")
|
||||
|
|
@ -93,7 +94,7 @@ func (test *udpTest) packetIn(wantError error, data v4wire.Packet) {
|
|||
}
|
||||
|
||||
// handles a packet as if it had been sent to the transport by the key/endpoint.
|
||||
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) {
|
||||
func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr netip.AddrPort, data v4wire.Packet) {
|
||||
test.t.Helper()
|
||||
|
||||
enc, _, err := v4wire.Encode(key, data)
|
||||
|
|
@ -108,7 +109,7 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *
|
|||
}
|
||||
|
||||
// waits for a packet to be sent by the transport.
|
||||
// validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type.
|
||||
// validate should have type func(X, netip.AddrPort, []byte), where X is a packet type.
|
||||
func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
|
||||
test.t.Helper()
|
||||
|
||||
|
|
@ -133,9 +134,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
|
|||
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
||||
return false
|
||||
}
|
||||
|
||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)})
|
||||
|
||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(hash)})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +256,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) {
|
|||
test := newUDPTest(t)
|
||||
defer test.close()
|
||||
|
||||
toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
|
||||
toaddr := netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 2222)
|
||||
toid := enode.ID{1, 2, 3, 4}
|
||||
target := v4wire.Pubkey{4, 5, 6, 7}
|
||||
|
||||
|
|
@ -285,28 +284,25 @@ func TestUDPv4_findnode(t *testing.T) {
|
|||
for i := 0; i < numCandidates; i++ {
|
||||
key := newkey()
|
||||
ip := net.IP{10, 13, 0, byte(i)}
|
||||
n := wrapNode(enode.NewV4(&key.PublicKey, ip, 0, 2000))
|
||||
n := enode.NewV4(&key.PublicKey, ip, 0, 2000)
|
||||
// Ensure half of table content isn't verified live yet.
|
||||
if i > numCandidates/2 {
|
||||
n.livenessChecks = 1
|
||||
live[n.ID()] = true
|
||||
}
|
||||
|
||||
test.table.addFoundNode(n, live[n.ID()])
|
||||
nodes.push(n, numCandidates)
|
||||
}
|
||||
fillTable(test.table, nodes.entries, false)
|
||||
|
||||
// ensure there's a bond with the test node,
|
||||
// findnode won't be accepted otherwise.
|
||||
remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
|
||||
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now())
|
||||
test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr().AsSlice(), time.Now())
|
||||
|
||||
// check that closest neighbors are returned.
|
||||
expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
|
||||
test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
|
||||
|
||||
waitNeighbors := func(want []*node) {
|
||||
test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) {
|
||||
waitNeighbors := func(want []*enode.Node) {
|
||||
test.waitPacketOut(func(p *v4wire.Neighbors, to netip.AddrPort, hash []byte) {
|
||||
if len(p.Nodes) != len(want) {
|
||||
t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize)
|
||||
return
|
||||
|
|
@ -338,11 +334,10 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
rid := enode.PubkeyToIDV4(&test.remotekey.PublicKey)
|
||||
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now())
|
||||
test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.Addr().AsSlice(), time.Now())
|
||||
|
||||
// queue a pending findnode request
|
||||
resultc, errc := make(chan []*node, 1), make(chan error, 1)
|
||||
|
||||
resultc, errc := make(chan []*enode.Node, 1), make(chan error, 1)
|
||||
go func() {
|
||||
rid := encodePubkey(&test.remotekey.PublicKey).id()
|
||||
|
||||
|
|
@ -356,18 +351,18 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
|
|||
|
||||
// wait for the findnode to be sent.
|
||||
// after it is sent, the transport is waiting for a reply
|
||||
test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) {
|
||||
test.waitPacketOut(func(p *v4wire.Findnode, to netip.AddrPort, hash []byte) {
|
||||
if p.Target != testTarget {
|
||||
t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
|
||||
}
|
||||
})
|
||||
|
||||
// send the reply as two packets.
|
||||
list := []*node{
|
||||
wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304")),
|
||||
wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303")),
|
||||
wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
|
||||
wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
|
||||
list := []*enode.Node{
|
||||
enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"),
|
||||
enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"),
|
||||
enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"),
|
||||
enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"),
|
||||
}
|
||||
rpclist := make([]v4wire.Node, len(list))
|
||||
|
||||
|
|
@ -401,8 +396,8 @@ func TestUDPv4_pingMatch(t *testing.T) {
|
|||
crand.Read(randToken)
|
||||
|
||||
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||
test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
|
||||
test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {})
|
||||
test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
|
||||
test.waitPacketOut(func(*v4wire.Ping, netip.AddrPort, []byte) {})
|
||||
test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
|
||||
}
|
||||
|
||||
|
|
@ -412,10 +407,10 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||
test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
|
||||
test.waitPacketOut(func(*v4wire.Pong, netip.AddrPort, []byte) {})
|
||||
|
||||
test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
|
||||
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
|
||||
test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
|
||||
wrongAddr := netip.MustParseAddrPort("33.44.1.2:30000")
|
||||
test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
|
||||
ReplyTok: hash,
|
||||
To: testLocalAnnounced,
|
||||
|
|
@ -426,43 +421,36 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
|
|||
|
||||
func TestUDPv4_successfulPing(t *testing.T) {
|
||||
test := newUDPTest(t)
|
||||
added := make(chan *node, 1)
|
||||
test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n }
|
||||
added := make(chan *tableNode, 1)
|
||||
test.table.nodeAddedHook = func(b *bucket, n *tableNode) { added <- n }
|
||||
defer test.close()
|
||||
|
||||
// The remote side sends a ping packet to initiate the exchange.
|
||||
go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
|
||||
|
||||
// The ping is replied to.
|
||||
test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) {
|
||||
test.waitPacketOut(func(p *v4wire.Pong, to netip.AddrPort, hash []byte) {
|
||||
pinghash := test.sent[0][:32]
|
||||
if !bytes.Equal(p.ReplyTok, pinghash) {
|
||||
t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
|
||||
}
|
||||
|
||||
wantTo := v4wire.Endpoint{
|
||||
// The mirrored UDP address is the UDP packet sender
|
||||
IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
|
||||
// The mirrored TCP port is the one from the ping packet
|
||||
TCP: testRemote.TCP,
|
||||
}
|
||||
// The mirrored UDP address is the UDP packet sender.
|
||||
// The mirrored TCP port is the one from the ping packet.
|
||||
wantTo := v4wire.NewEndpoint(test.remoteaddr, testRemote.TCP)
|
||||
if !reflect.DeepEqual(p.To, wantTo) {
|
||||
t.Errorf("got pong.To %v, want %v", p.To, wantTo)
|
||||
}
|
||||
})
|
||||
|
||||
// Remote is unknown, the table pings back.
|
||||
test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
|
||||
if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
|
||||
test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) {
|
||||
wantFrom := test.udp.ourEndpoint()
|
||||
wantFrom.IP = net.IP{}
|
||||
if !reflect.DeepEqual(p.From, wantFrom) {
|
||||
t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
|
||||
}
|
||||
|
||||
wantTo := v4wire.Endpoint{
|
||||
// The mirrored UDP address is the UDP packet sender.
|
||||
IP: test.remoteaddr.IP,
|
||||
UDP: uint16(test.remoteaddr.Port),
|
||||
TCP: 0,
|
||||
}
|
||||
// The mirrored UDP address is the UDP packet sender.
|
||||
wantTo := v4wire.NewEndpoint(test.remoteaddr, 0)
|
||||
if !reflect.DeepEqual(p.To, wantTo) {
|
||||
t.Errorf("got ping.To %v, want %v", p.To, wantTo)
|
||||
}
|
||||
|
|
@ -478,13 +466,11 @@ func TestUDPv4_successfulPing(t *testing.T) {
|
|||
if n.ID() != rid {
|
||||
t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid)
|
||||
}
|
||||
|
||||
if !n.IP().Equal(test.remoteaddr.IP) {
|
||||
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP)
|
||||
if !n.IP().Equal(test.remoteaddr.Addr().AsSlice()) {
|
||||
t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.Addr())
|
||||
}
|
||||
|
||||
if n.UDP() != test.remoteaddr.Port {
|
||||
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port)
|
||||
if n.UDP() != int(test.remoteaddr.Port()) {
|
||||
t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port())
|
||||
}
|
||||
|
||||
if n.TCP() != int(testRemote.TCP) {
|
||||
|
|
@ -508,12 +494,12 @@ func TestUDPv4_EIP868(t *testing.T) {
|
|||
|
||||
// Perform endpoint proof and check for sequence number in packet tail.
|
||||
test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
|
||||
test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) {
|
||||
test.waitPacketOut(func(p *v4wire.Pong, addr netip.AddrPort, hash []byte) {
|
||||
if p.ENRSeq != wantNode.Seq() {
|
||||
t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq())
|
||||
}
|
||||
})
|
||||
test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) {
|
||||
test.waitPacketOut(func(p *v4wire.Ping, addr netip.AddrPort, hash []byte) {
|
||||
if p.ENRSeq != wantNode.Seq() {
|
||||
t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq())
|
||||
}
|
||||
|
|
@ -523,7 +509,7 @@ func TestUDPv4_EIP868(t *testing.T) {
|
|||
|
||||
// Request should work now.
|
||||
test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
|
||||
test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) {
|
||||
test.waitPacketOut(func(p *v4wire.ENRResponse, addr netip.AddrPort, hash []byte) {
|
||||
n, err := enode.New(enode.ValidSchemes, &p.Record)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid record: %v", err)
|
||||
|
|
@ -634,7 +620,7 @@ type dgramPipe struct {
|
|||
}
|
||||
|
||||
type dgram struct {
|
||||
to net.UDPAddr
|
||||
to netip.AddrPort
|
||||
data []byte
|
||||
}
|
||||
|
||||
|
|
@ -648,8 +634,8 @@ func newpipe() *dgramPipe {
|
|||
}
|
||||
}
|
||||
|
||||
// WriteToUDP queues a datagram.
|
||||
func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
|
||||
// WriteToUDPAddrPort queues a datagram.
|
||||
func (c *dgramPipe) WriteToUDPAddrPort(b []byte, to netip.AddrPort) (n int, err error) {
|
||||
msg := make([]byte, len(b))
|
||||
copy(msg, b)
|
||||
c.mu.Lock()
|
||||
|
|
@ -658,17 +644,16 @@ func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) {
|
|||
if c.closed {
|
||||
return 0, errors.New("closed")
|
||||
}
|
||||
|
||||
c.queue = append(c.queue, dgram{*to, b})
|
||||
c.queue = append(c.queue, dgram{to, b})
|
||||
c.cond.Signal()
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// ReadFromUDP just hangs until the pipe is closed.
|
||||
func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
// ReadFromUDPAddrPort just hangs until the pipe is closed.
|
||||
func (c *dgramPipe) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||
<-c.closing
|
||||
return 0, nil, io.EOF
|
||||
return 0, netip.AddrPort{}, io.EOF
|
||||
}
|
||||
|
||||
func (c *dgramPipe) Close() error {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -150,15 +151,15 @@ type Endpoint struct {
|
|||
}
|
||||
|
||||
// NewEndpoint creates an endpoint.
|
||||
func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
|
||||
ip := net.IP{}
|
||||
if ip4 := addr.IP.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
} else if ip6 := addr.IP.To16(); ip6 != nil {
|
||||
ip = ip6
|
||||
func NewEndpoint(addr netip.AddrPort, tcpPort uint16) Endpoint {
|
||||
var ip net.IP
|
||||
if addr.Addr().Is4() || addr.Addr().Is4In6() {
|
||||
ip4 := addr.Addr().As4()
|
||||
ip = ip4[:]
|
||||
} else {
|
||||
ip = addr.Addr().AsSlice()
|
||||
}
|
||||
|
||||
return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
|
||||
return Endpoint{IP: ip, UDP: addr.Port(), TCP: tcpPort}
|
||||
}
|
||||
|
||||
type Packet interface {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package discover
|
|||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ func (t *talkSystem) register(protocol string, handler TalkRequestHandler) {
|
|||
}
|
||||
|
||||
// handleRequest handles a talk request.
|
||||
func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.TalkRequest) {
|
||||
func (t *talkSystem) handleRequest(id enode.ID, addr netip.AddrPort, req *v5wire.TalkRequest) {
|
||||
t.mutex.Lock()
|
||||
handler, ok := t.handlers[req.Protocol]
|
||||
t.mutex.Unlock()
|
||||
|
|
@ -88,7 +89,8 @@ func (t *talkSystem) handleRequest(id enode.ID, addr *net.UDPAddr, req *v5wire.T
|
|||
case <-t.slots:
|
||||
go func() {
|
||||
defer func() { t.slots <- struct{}{} }()
|
||||
respMessage := handler(id, addr, req.Message)
|
||||
udpAddr := &net.UDPAddr{IP: addr.Addr().AsSlice(), Port: int(addr.Port())}
|
||||
respMessage := handler(id, udpAddr, req.Message)
|
||||
resp := &v5wire.TalkResponse{ReqID: req.ReqID, Message: respMessage}
|
||||
t.transport.sendFromAnotherThread(id, addr, resp)
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -100,14 +101,14 @@ type UDPv5 struct {
|
|||
|
||||
type sendRequest struct {
|
||||
destID enode.ID
|
||||
destAddr *net.UDPAddr
|
||||
destAddr netip.AddrPort
|
||||
msg v5wire.Packet
|
||||
}
|
||||
|
||||
// callV5 represents a remote procedure call against another node.
|
||||
type callV5 struct {
|
||||
id enode.ID
|
||||
addr *net.UDPAddr
|
||||
addr netip.AddrPort
|
||||
node *enode.Node // This is required to perform handshakes.
|
||||
|
||||
packet v5wire.Packet
|
||||
|
|
@ -177,7 +178,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
|
|||
cancelCloseCtx: cancelCloseCtx,
|
||||
}
|
||||
t.talk = newTalkSystem(t)
|
||||
tab, err := newMeteredTable(t, t.db, cfg)
|
||||
tab, err := newTable(t, t.db, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -239,7 +240,7 @@ func (t *UDPv5) AllNodes() []*enode.Node {
|
|||
|
||||
for _, b := range &t.tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
nodes = append(nodes, unwrapNode(n))
|
||||
nodes = append(nodes, n.Node)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +274,7 @@ func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]b
|
|||
}
|
||||
|
||||
// TalkRequestToID sends a talk request to a node and waits for a response.
|
||||
func (t *UDPv5) TalkRequestToID(id enode.ID, addr *net.UDPAddr, protocol string, request []byte) ([]byte, error) {
|
||||
func (t *UDPv5) TalkRequestToID(id enode.ID, addr netip.AddrPort, protocol string, request []byte) ([]byte, error) {
|
||||
req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
|
||||
resp := t.callToID(id, addr, v5wire.TalkResponseMsg, req)
|
||||
defer t.callDone(resp)
|
||||
|
|
@ -323,13 +324,13 @@ func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
|
|||
}
|
||||
|
||||
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
|
||||
return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
||||
return newLookup(ctx, t.tab, target, func(n *enode.Node) ([]*enode.Node, error) {
|
||||
return t.lookupWorker(n, target)
|
||||
})
|
||||
}
|
||||
|
||||
// lookupWorker performs FINDNODE calls against a single node during lookup.
|
||||
func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
|
||||
func (t *UDPv5) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
|
||||
var (
|
||||
dists = lookupDistances(target, destNode.ID())
|
||||
nodes = nodesByDistance{target: target}
|
||||
|
|
@ -337,15 +338,14 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
|
|||
)
|
||||
|
||||
var r []*enode.Node
|
||||
r, err = t.findnode(unwrapNode(destNode), dists)
|
||||
|
||||
r, err = t.findnode(destNode, dists)
|
||||
if errors.Is(err, errClosed) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, n := range r {
|
||||
if n.ID() != t.Self().ID() {
|
||||
nodes.push(wrapNode(n), findnodeResultLimit)
|
||||
nodes.push(n, findnodeResultLimit)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +449,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := netutil.CheckRelayIP(c.addr.IP, node.IP()); err != nil {
|
||||
if err := netutil.CheckRelayIP(c.addr.Addr().AsSlice(), node.IP()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -489,14 +489,14 @@ func containsUint(x uint, xs []uint) bool {
|
|||
// callToNode sends the given call and sets up a handler for response packets (of message
|
||||
// type responseType). Responses are dispatched to the call's response channel.
|
||||
func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {
|
||||
addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
addr, _ := n.UDPEndpoint()
|
||||
c := &callV5{id: n.ID(), addr: addr, node: n}
|
||||
t.initCall(c, responseType, req)
|
||||
return c
|
||||
}
|
||||
|
||||
// callToID is like callToNode, but for cases where the node record is not available.
|
||||
func (t *UDPv5) callToID(id enode.ID, addr *net.UDPAddr, responseType byte, req v5wire.Packet) *callV5 {
|
||||
func (t *UDPv5) callToID(id enode.ID, addr netip.AddrPort, responseType byte, req v5wire.Packet) *callV5 {
|
||||
c := &callV5{id: id, addr: addr}
|
||||
t.initCall(c, responseType, req)
|
||||
return c
|
||||
|
|
@ -667,12 +667,12 @@ func (t *UDPv5) sendCall(c *callV5) {
|
|||
|
||||
// sendResponse sends a response packet to the given node.
|
||||
// This doesn't trigger a handshake even if no keys are available.
|
||||
func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
|
||||
func (t *UDPv5) sendResponse(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) error {
|
||||
_, err := t.send(toID, toAddr, packet, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) {
|
||||
func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet) {
|
||||
select {
|
||||
case t.sendCh <- sendRequest{toID, toAddr, packet}:
|
||||
case <-t.closeCtx.Done():
|
||||
|
|
@ -680,7 +680,7 @@ func (t *UDPv5) sendFromAnotherThread(toID enode.ID, toAddr *net.UDPAddr, packet
|
|||
}
|
||||
|
||||
// send sends a packet to the given node.
|
||||
func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
|
||||
func (t *UDPv5) send(toID enode.ID, toAddr netip.AddrPort, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
|
||||
addr := toAddr.String()
|
||||
t.logcontext = append(t.logcontext[:0], "id", toID, "addr", addr)
|
||||
t.logcontext = packet.AppendLogInfo(t.logcontext)
|
||||
|
|
@ -693,7 +693,7 @@ func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c
|
|||
return nonce, err
|
||||
}
|
||||
|
||||
_, err = t.conn.WriteToUDP(enc, toAddr)
|
||||
_, err = t.conn.WriteToUDPAddrPort(enc, toAddr)
|
||||
t.log.Trace(">> "+packet.Name(), t.logcontext...)
|
||||
|
||||
return nonce, err
|
||||
|
|
@ -705,7 +705,7 @@ func (t *UDPv5) readLoop() {
|
|||
|
||||
buf := make([]byte, maxPacketSize)
|
||||
for range t.readNextCh {
|
||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
||||
nbytes, from, err := t.conn.ReadFromUDPAddrPort(buf)
|
||||
if netutil.IsTemporaryError(err) {
|
||||
// Ignore temporary read errors.
|
||||
t.log.Debug("Temporary UDP read error", "err", err)
|
||||
|
|
@ -724,7 +724,7 @@ func (t *UDPv5) readLoop() {
|
|||
}
|
||||
|
||||
// dispatchReadPacket sends a packet into the dispatch loop.
|
||||
func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
|
||||
func (t *UDPv5) dispatchReadPacket(from netip.AddrPort, content []byte) bool {
|
||||
select {
|
||||
case t.packetInCh <- ReadPacket{content, from}:
|
||||
return true
|
||||
|
|
@ -734,7 +734,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
|
|||
}
|
||||
|
||||
// handlePacket decodes and processes an incoming packet from the network.
|
||||
func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
||||
func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr netip.AddrPort) error {
|
||||
addr := fromAddr.String()
|
||||
|
||||
fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
|
||||
|
|
@ -756,7 +756,7 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
|||
|
||||
if fromNode != nil {
|
||||
// Handshake succeeded, add to table.
|
||||
t.tab.addSeenNode(wrapNode(fromNode))
|
||||
t.tab.addInboundNode(fromNode)
|
||||
}
|
||||
|
||||
if packet.Kind() != v5wire.WhoareyouPacket {
|
||||
|
|
@ -772,13 +772,13 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
|||
}
|
||||
|
||||
// handleCallResponse dispatches a response packet to the call waiting for it.
|
||||
func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
|
||||
func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr netip.AddrPort, p v5wire.Packet) bool {
|
||||
ac := t.activeCallByNode[fromID]
|
||||
if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
|
||||
t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
|
||||
return false
|
||||
}
|
||||
if !fromAddr.IP.Equal(ac.addr.IP) || fromAddr.Port != ac.addr.Port {
|
||||
if fromAddr != ac.addr {
|
||||
t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
|
||||
return false
|
||||
}
|
||||
|
|
@ -808,7 +808,7 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node {
|
|||
}
|
||||
|
||||
// handle processes incoming packets according to their message type.
|
||||
func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
switch p := p.(type) {
|
||||
case *v5wire.Unknown:
|
||||
t.handleUnknown(p, fromID, fromAddr)
|
||||
|
|
@ -818,7 +818,9 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
|
|||
t.handlePing(p, fromID, fromAddr)
|
||||
case *v5wire.Pong:
|
||||
if t.handleCallResponse(fromID, fromAddr, p) {
|
||||
t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
|
||||
fromUDPAddr := &net.UDPAddr{IP: fromAddr.Addr().AsSlice(), Port: int(fromAddr.Port())}
|
||||
toUDPAddr := &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}
|
||||
t.localNode.UDPEndpointStatement(fromUDPAddr, toUDPAddr)
|
||||
}
|
||||
case *v5wire.Findnode:
|
||||
t.handleFindnode(p, fromID, fromAddr)
|
||||
|
|
@ -832,7 +834,7 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr)
|
|||
}
|
||||
|
||||
// handleUnknown initiates a handshake by responding with WHOAREYOU.
|
||||
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
|
||||
crand.Read(challenge.IDNonce[:])
|
||||
|
||||
|
|
@ -850,7 +852,7 @@ var (
|
|||
)
|
||||
|
||||
// handleWhoareyou resends the active call as a handshake packet.
|
||||
func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
c, err := t.matchWithCall(fromID, p.Nonce)
|
||||
if err != nil {
|
||||
t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
|
||||
|
|
@ -887,33 +889,36 @@ func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, err
|
|||
}
|
||||
|
||||
// handlePing sends a PONG response.
|
||||
func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
remoteIP := fromAddr.IP
|
||||
// Handle IPv4 mapped IPv6 addresses in the
|
||||
// event the local node is binded to an
|
||||
// ipv6 interface.
|
||||
if remoteIP.To4() != nil {
|
||||
remoteIP = remoteIP.To4()
|
||||
func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
var remoteIP net.IP
|
||||
// Handle IPv4 mapped IPv6 addresses in the event the local node is binded
|
||||
// to an ipv6 interface.
|
||||
if fromAddr.Addr().Is4() || fromAddr.Addr().Is4In6() {
|
||||
ip4 := fromAddr.Addr().As4()
|
||||
remoteIP = ip4[:]
|
||||
} else {
|
||||
remoteIP = fromAddr.Addr().AsSlice()
|
||||
}
|
||||
|
||||
t.sendResponse(fromID, fromAddr, &v5wire.Pong{
|
||||
ReqID: p.ReqID,
|
||||
ToIP: remoteIP,
|
||||
ToPort: uint16(fromAddr.Port),
|
||||
ToPort: fromAddr.Port(),
|
||||
ENRSeq: t.localNode.Node().Seq(),
|
||||
})
|
||||
}
|
||||
|
||||
// handleFindnode returns nodes to the requester.
|
||||
func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
|
||||
func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
nodes := t.collectTableNodes(fromAddr.Addr(), p.Distances, findnodeResultLimit)
|
||||
for _, resp := range packNodes(p.ReqID, nodes) {
|
||||
t.sendResponse(fromID, fromAddr, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// collectTableNodes creates a FINDNODE result set for the given distances.
|
||||
func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
|
||||
func (t *UDPv5) collectTableNodes(rip netip.Addr, distances []uint, limit int) []*enode.Node {
|
||||
ripSlice := rip.AsSlice()
|
||||
var bn []*enode.Node
|
||||
var nodes []*enode.Node
|
||||
|
||||
|
|
@ -929,7 +934,7 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en
|
|||
for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) {
|
||||
// Apply some pre-checks to avoid sending invalid nodes.
|
||||
// Note liveness is checked by appendLiveNodes.
|
||||
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
||||
if netutil.CheckRelayIP(ripSlice, n.IP()) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -113,7 +114,7 @@ func TestUDPv5_pingHandling(t *testing.T) {
|
|||
defer test.close()
|
||||
|
||||
test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
|
||||
test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
||||
t.Error("wrong request ID in response:", p.ReqID)
|
||||
}
|
||||
|
|
@ -150,16 +151,16 @@ func TestUDPv5_unknownPacket(t *testing.T) {
|
|||
|
||||
// Unknown packet from unknown node.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, 0)
|
||||
})
|
||||
|
||||
// Make node known.
|
||||
n := test.getNode(test.remotekey, test.remoteaddr).Node()
|
||||
test.table.addSeenNode(wrapNode(n))
|
||||
test.table.addFoundNode(n, false)
|
||||
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, n.Seq())
|
||||
})
|
||||
}
|
||||
|
|
@ -175,9 +176,9 @@ func TestUDPv5_findnodeHandling(t *testing.T) {
|
|||
nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16)
|
||||
nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
|
||||
nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
|
||||
fillTable(test.table, wrapNodes(nodes253), true)
|
||||
fillTable(test.table, wrapNodes(nodes249), true)
|
||||
fillTable(test.table, wrapNodes(nodes248), true)
|
||||
fillTable(test.table, nodes253, true)
|
||||
fillTable(test.table, nodes249, true)
|
||||
fillTable(test.table, nodes248, true)
|
||||
|
||||
// Requesting with distance zero should return the node's own record.
|
||||
test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
|
||||
|
|
@ -216,7 +217,7 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes
|
|||
}
|
||||
|
||||
for {
|
||||
test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Nodes, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if !bytes.Equal(p.ReqID, wantReqID) {
|
||||
test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
|
||||
}
|
||||
|
|
@ -262,8 +263,7 @@ func TestUDPv5_pingCall(t *testing.T) {
|
|||
_, err := test.udp.ping(remote)
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {})
|
||||
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {})
|
||||
if err := <-done; err != errTimeout {
|
||||
t.Fatalf("want errTimeout, got %q", err)
|
||||
}
|
||||
|
|
@ -273,7 +273,7 @@ func TestUDPv5_pingCall(t *testing.T) {
|
|||
_, err := test.udp.ping(remote)
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
|
||||
})
|
||||
|
||||
|
|
@ -286,8 +286,8 @@ func TestUDPv5_pingCall(t *testing.T) {
|
|||
_, err := test.udp.ping(remote)
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101}
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
wrongAddr := netip.MustParseAddrPort("33.44.55.22:10101")
|
||||
test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
|
||||
})
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ func TestUDPv5_findnodeCall(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Serve the responses:
|
||||
test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if !reflect.DeepEqual(p.Distances, distances) {
|
||||
t.Fatalf("wrong distances in request: %v", p.Distances)
|
||||
}
|
||||
|
|
@ -369,15 +369,15 @@ func TestUDPv5_callResend(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Ping answered by WHOAREYOU.
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||
})
|
||||
// Ping should be re-sent.
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
||||
})
|
||||
// Answer the other ping.
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
|
||||
})
|
||||
|
||||
|
|
@ -406,11 +406,11 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Ping answered by WHOAREYOU.
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||
})
|
||||
// Ping answered by WHOAREYOU again.
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Ping, addr netip.AddrPort, nonce v5wire.Nonce) {
|
||||
test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
|
||||
})
|
||||
|
||||
|
|
@ -440,7 +440,7 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Serve two responses, slowly.
|
||||
test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Findnode, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
time.Sleep(respTimeout - 50*time.Millisecond)
|
||||
test.packetIn(&v5wire.Nodes{
|
||||
ReqID: p.ReqID,
|
||||
|
|
@ -481,7 +481,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
|||
Protocol: "test",
|
||||
Message: []byte("test request"),
|
||||
})
|
||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if !bytes.Equal(p.ReqID, []byte("foo")) {
|
||||
t.Error("wrong request ID in response:", p.ReqID)
|
||||
}
|
||||
|
|
@ -503,7 +503,7 @@ func TestUDPv5_talkHandling(t *testing.T) {
|
|||
Protocol: "wrong",
|
||||
Message: []byte("test request"),
|
||||
})
|
||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.TalkResponse, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if !bytes.Equal(p.ReqID, []byte("2")) {
|
||||
t.Error("wrong request ID in response:", p.ReqID)
|
||||
}
|
||||
|
|
@ -533,8 +533,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
|||
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {})
|
||||
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {})
|
||||
if err := <-done; err != errTimeout {
|
||||
t.Fatalf("want errTimeout, got %q", err)
|
||||
}
|
||||
|
|
@ -544,7 +543,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
|||
_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if p.Protocol != "test" {
|
||||
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
||||
}
|
||||
|
|
@ -568,7 +567,7 @@ func TestUDPv5_talkRequest(t *testing.T) {
|
|||
_, err := test.udp.TalkRequestToID(remote.ID(), test.remoteaddr, "test", []byte("test request 2"))
|
||||
done <- err
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.TalkRequest, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if p.Protocol != "test" {
|
||||
t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
|
||||
}
|
||||
|
|
@ -646,13 +645,14 @@ func TestUDPv5_lookup(t *testing.T) {
|
|||
for d, nn := range lookupTestnet.dists {
|
||||
for i, key := range nn {
|
||||
n := lookupTestnet.node(d, i)
|
||||
test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()})
|
||||
addr, _ := n.UDPEndpoint()
|
||||
test.getNode(key, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Seed table with initial node.
|
||||
initialNode := lookupTestnet.node(256, 0)
|
||||
fillTable(test.table, []*node{wrapNode(initialNode)}, true)
|
||||
fillTable(test.table, []*enode.Node{initialNode}, true)
|
||||
|
||||
// Start the lookup.
|
||||
resultC := make(chan []*enode.Node, 1)
|
||||
|
|
@ -666,7 +666,7 @@ func TestUDPv5_lookup(t *testing.T) {
|
|||
asked := make(map[enode.ID]bool)
|
||||
|
||||
for done := false; !done; {
|
||||
done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) {
|
||||
done = test.waitPacketOut(func(p v5wire.Packet, to netip.AddrPort, _ v5wire.Nonce) {
|
||||
recipient, key := lookupTestnet.nodeByAddr(to)
|
||||
switch p := p.(type) {
|
||||
case *v5wire.Ping:
|
||||
|
|
@ -723,11 +723,8 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
|
|||
test := newUDPV5Test(t)
|
||||
defer test.close()
|
||||
|
||||
rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5)
|
||||
test.remoteaddr = &net.UDPAddr{
|
||||
IP: rawIP.To16(),
|
||||
Port: 0,
|
||||
}
|
||||
rawIP := netip.AddrFrom4([4]byte{0xFF, 0x12, 0x33, 0xE5})
|
||||
test.remoteaddr = netip.AddrPortFrom(netip.AddrFrom16(rawIP.As16()), 0)
|
||||
remote := test.getNode(test.remotekey, test.remoteaddr).Node()
|
||||
done := make(chan struct{}, 1)
|
||||
|
||||
|
|
@ -736,7 +733,7 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
|
|||
test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
|
||||
test.waitPacketOut(func(p *v5wire.Pong, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
if len(p.ToIP) == net.IPv6len {
|
||||
t.Error("Received untruncated ip address")
|
||||
}
|
||||
|
|
@ -744,8 +741,7 @@ func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
|
|||
if len(p.ToIP) != net.IPv4len {
|
||||
t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
|
||||
}
|
||||
|
||||
if !p.ToIP.Equal(rawIP) {
|
||||
if !p.ToIP.Equal(rawIP.AsSlice()) {
|
||||
t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
|
||||
}
|
||||
})
|
||||
|
|
@ -761,9 +757,9 @@ type udpV5Test struct {
|
|||
db *enode.DB
|
||||
udp *UDPv5
|
||||
localkey, remotekey *ecdsa.PrivateKey
|
||||
remoteaddr *net.UDPAddr
|
||||
remoteaddr netip.AddrPort
|
||||
nodesByID map[enode.ID]*enode.LocalNode
|
||||
nodesByIP map[string]*enode.LocalNode
|
||||
nodesByIP map[netip.Addr]*enode.LocalNode
|
||||
}
|
||||
|
||||
// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
|
||||
|
|
@ -829,9 +825,9 @@ func newUDPV5Test(t *testing.T) *udpV5Test {
|
|||
pipe: newpipe(),
|
||||
localkey: newkey(),
|
||||
remotekey: newkey(),
|
||||
remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303},
|
||||
remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"),
|
||||
nodesByID: make(map[enode.ID]*enode.LocalNode),
|
||||
nodesByIP: make(map[string]*enode.LocalNode),
|
||||
nodesByIP: make(map[netip.Addr]*enode.LocalNode),
|
||||
}
|
||||
test.db, _ = enode.OpenDB("")
|
||||
ln := enode.NewLocalNode(test.db, test.localkey)
|
||||
|
|
@ -857,8 +853,8 @@ func (test *udpV5Test) packetIn(packet v5wire.Packet) {
|
|||
test.packetInFrom(test.remotekey, test.remoteaddr, packet)
|
||||
}
|
||||
|
||||
// handles a packet as if it had been sent to the transport by the key/endpoint.
|
||||
func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) {
|
||||
// packetInFrom handles a packet as if it had been sent to the transport by the key/endpoint.
|
||||
func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr netip.AddrPort, packet v5wire.Packet) {
|
||||
test.t.Helper()
|
||||
|
||||
ln := test.getNode(key, addr)
|
||||
|
|
@ -875,25 +871,23 @@ func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, pa
|
|||
}
|
||||
|
||||
// getNode ensures the test knows about a node at the given endpoint.
|
||||
func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode {
|
||||
func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr netip.AddrPort) *enode.LocalNode {
|
||||
id := encodePubkey(&key.PublicKey).id()
|
||||
|
||||
ln := test.nodesByID[id]
|
||||
if ln == nil {
|
||||
db, _ := enode.OpenDB("")
|
||||
ln = enode.NewLocalNode(db, key)
|
||||
ln.SetStaticIP(addr.IP)
|
||||
ln.Set(enr.UDP(addr.Port))
|
||||
ln.SetStaticIP(addr.Addr().AsSlice())
|
||||
ln.Set(enr.UDP(addr.Port()))
|
||||
test.nodesByID[id] = ln
|
||||
}
|
||||
|
||||
test.nodesByIP[string(addr.IP)] = ln
|
||||
|
||||
test.nodesByIP[addr.Addr()] = ln
|
||||
return ln
|
||||
}
|
||||
|
||||
// waitPacketOut waits for the next output packet and handles it using the given 'validate'
|
||||
// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is
|
||||
// function. The function must be of type func (X, netip.AddrPort, v5wire.Nonce) where X is
|
||||
// assignable to packetV5.
|
||||
func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
||||
test.t.Helper()
|
||||
|
|
@ -910,8 +904,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
|||
test.t.Fatalf("timed out waiting for %v", exptype)
|
||||
return false
|
||||
}
|
||||
|
||||
ln := test.nodesByIP[string(dgram.to.IP)]
|
||||
ln := test.nodesByIP[dgram.to.Addr()]
|
||||
if ln == nil {
|
||||
test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to)
|
||||
return false
|
||||
|
|
@ -929,9 +922,7 @@ func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
|
|||
test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype)
|
||||
return false
|
||||
}
|
||||
|
||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)})
|
||||
|
||||
fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(frame.AuthTag)})
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -172,6 +172,5 @@ func SignNull(r *enr.Record, id ID) *Node {
|
|||
if err := r.SetSig(NullID{}, []byte{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Node{r: *r, id: id}
|
||||
return newNodeWithID(r, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"fmt"
|
||||
"math/bits"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
|
|
@ -36,6 +37,10 @@ var errMissingPrefix = errors.New("missing 'enr:' prefix for base64-encoded reco
|
|||
type Node struct {
|
||||
r enr.Record
|
||||
id ID
|
||||
// endpoint information
|
||||
ip netip.Addr
|
||||
udp uint16
|
||||
tcp uint16
|
||||
}
|
||||
|
||||
// New wraps a node record. The record must be valid according to the given
|
||||
|
|
@ -44,13 +49,76 @@ func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
|
|||
if err := r.VerifySignature(validSchemes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := &Node{r: *r}
|
||||
if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) {
|
||||
return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{}))
|
||||
var id ID
|
||||
if n := copy(id[:], validSchemes.NodeAddr(r)); n != len(id) {
|
||||
return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(id))
|
||||
}
|
||||
return newNodeWithID(r, id), nil
|
||||
}
|
||||
|
||||
return node, nil
|
||||
func newNodeWithID(r *enr.Record, id ID) *Node {
|
||||
n := &Node{r: *r, id: id}
|
||||
// Set the preferred endpoint.
|
||||
// Here we decide between IPv4 and IPv6, choosing the 'most global' address.
|
||||
var ip4 netip.Addr
|
||||
var ip6 netip.Addr
|
||||
n.Load((*enr.IPv4Addr)(&ip4))
|
||||
n.Load((*enr.IPv6Addr)(&ip6))
|
||||
valid4 := validIP(ip4)
|
||||
valid6 := validIP(ip6)
|
||||
switch {
|
||||
case valid4 && valid6:
|
||||
if localityScore(ip4) >= localityScore(ip6) {
|
||||
n.setIP4(ip4)
|
||||
} else {
|
||||
n.setIP6(ip6)
|
||||
}
|
||||
case valid4:
|
||||
n.setIP4(ip4)
|
||||
case valid6:
|
||||
n.setIP6(ip6)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// validIP reports whether 'ip' is a valid node endpoint IP address.
|
||||
func validIP(ip netip.Addr) bool {
|
||||
return ip.IsValid() && !ip.IsMulticast()
|
||||
}
|
||||
|
||||
func localityScore(ip netip.Addr) int {
|
||||
switch {
|
||||
case ip.IsUnspecified():
|
||||
return 0
|
||||
case ip.IsLoopback():
|
||||
return 1
|
||||
case ip.IsLinkLocalUnicast():
|
||||
return 2
|
||||
case ip.IsPrivate():
|
||||
return 3
|
||||
default:
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) setIP4(ip netip.Addr) {
|
||||
n.ip = ip
|
||||
n.Load((*enr.UDP)(&n.udp))
|
||||
n.Load((*enr.TCP)(&n.tcp))
|
||||
}
|
||||
|
||||
func (n *Node) setIP6(ip netip.Addr) {
|
||||
if ip.Is4In6() {
|
||||
n.setIP4(ip)
|
||||
return
|
||||
}
|
||||
n.ip = ip
|
||||
if err := n.Load((*enr.UDP6)(&n.udp)); err != nil {
|
||||
n.Load((*enr.UDP)(&n.udp))
|
||||
}
|
||||
if err := n.Load((*enr.TCP6)(&n.tcp)); err != nil {
|
||||
n.Load((*enr.TCP)(&n.tcp))
|
||||
}
|
||||
}
|
||||
|
||||
// MustParse parses a node record or enode:// URL. It panics if the input is invalid.
|
||||
|
|
@ -96,50 +164,45 @@ func (n *Node) Seq() uint64 {
|
|||
return n.r.Seq()
|
||||
}
|
||||
|
||||
// Incomplete returns true for nodes with no IP address.
|
||||
func (n *Node) Incomplete() bool {
|
||||
return n.IP() == nil
|
||||
}
|
||||
|
||||
// Load retrieves an entry from the underlying record.
|
||||
func (n *Node) Load(k enr.Entry) error {
|
||||
return n.r.Load(k)
|
||||
}
|
||||
|
||||
// IP returns the IP address of the node. This prefers IPv4 addresses.
|
||||
// IP returns the IP address of the node.
|
||||
func (n *Node) IP() net.IP {
|
||||
var (
|
||||
ip4 enr.IPv4
|
||||
ip6 enr.IPv6
|
||||
)
|
||||
return net.IP(n.ip.AsSlice())
|
||||
}
|
||||
|
||||
if n.Load(&ip4) == nil {
|
||||
return net.IP(ip4)
|
||||
}
|
||||
|
||||
if n.Load(&ip6) == nil {
|
||||
return net.IP(ip6)
|
||||
}
|
||||
|
||||
return nil
|
||||
// IPAddr returns the IP address of the node.
|
||||
func (n *Node) IPAddr() netip.Addr {
|
||||
return n.ip
|
||||
}
|
||||
|
||||
// UDP returns the UDP port of the node.
|
||||
func (n *Node) UDP() int {
|
||||
var port enr.UDP
|
||||
|
||||
n.Load(&port)
|
||||
|
||||
return int(port)
|
||||
return int(n.udp)
|
||||
}
|
||||
|
||||
// TCP returns the TCP port of the node.
|
||||
func (n *Node) TCP() int {
|
||||
var port enr.TCP
|
||||
return int(n.tcp)
|
||||
}
|
||||
|
||||
n.Load(&port)
|
||||
// UDPEndpoint returns the announced UDP endpoint.
|
||||
func (n *Node) UDPEndpoint() (netip.AddrPort, bool) {
|
||||
if !n.ip.IsValid() || n.ip.IsUnspecified() || n.udp == 0 {
|
||||
return netip.AddrPort{}, false
|
||||
}
|
||||
return netip.AddrPortFrom(n.ip, n.udp), true
|
||||
}
|
||||
|
||||
return int(port)
|
||||
// TCPEndpoint returns the announced TCP endpoint.
|
||||
func (n *Node) TCPEndpoint() (netip.AddrPort, bool) {
|
||||
if !n.ip.IsValid() || n.ip.IsUnspecified() || n.tcp == 0 {
|
||||
return netip.AddrPort{}, false
|
||||
}
|
||||
return netip.AddrPortFrom(n.ip, n.tcp), true
|
||||
}
|
||||
|
||||
// Pubkey returns the secp256k1 public key of the node, if present.
|
||||
|
|
@ -162,18 +225,15 @@ func (n *Node) Record() *enr.Record {
|
|||
// ValidateComplete checks whether n has a valid IP and UDP port.
|
||||
// Deprecated: don't use this method.
|
||||
func (n *Node) ValidateComplete() error {
|
||||
if n.Incomplete() {
|
||||
if !n.ip.IsValid() {
|
||||
return errors.New("missing IP address")
|
||||
}
|
||||
|
||||
if n.UDP() == 0 {
|
||||
return errors.New("missing UDP port")
|
||||
}
|
||||
|
||||
ip := n.IP()
|
||||
if ip.IsMulticast() || ip.IsUnspecified() {
|
||||
if n.ip.IsMulticast() || n.ip.IsUnspecified() {
|
||||
return errors.New("invalid IP (multicast/unspecified)")
|
||||
}
|
||||
if n.udp == 0 {
|
||||
return errors.New("missing UDP port")
|
||||
}
|
||||
// Validate the node key (on curve, etc.).
|
||||
var key Secp256k1
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
|
|
@ -68,6 +69,167 @@ func TestPythonInterop(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNodeEndpoints(t *testing.T) {
|
||||
id := HexID("00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
|
||||
type endpointTest struct {
|
||||
name string
|
||||
node *Node
|
||||
wantIP netip.Addr
|
||||
wantUDP int
|
||||
wantTCP int
|
||||
}
|
||||
tests := []endpointTest{
|
||||
{
|
||||
name: "no-addr",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "udp-only",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.UDP(9000))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "tcp-only",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.TCP(9000))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "ipv4-only-loopback",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("127.0.0.1")))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("127.0.0.1"),
|
||||
},
|
||||
{
|
||||
name: "ipv4-only-unspecified",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("0.0.0.0")))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("0.0.0.0"),
|
||||
},
|
||||
{
|
||||
name: "ipv4-only",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("99.22.33.1")))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("99.22.33.1"),
|
||||
},
|
||||
{
|
||||
name: "ipv6-only",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("2001::ff00:0042:8329")))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("2001::ff00:0042:8329"),
|
||||
},
|
||||
{
|
||||
name: "ipv4-loopback-and-ipv6-global",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("127.0.0.1")))
|
||||
r.Set(enr.UDP(30304))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("2001::ff00:0042:8329")))
|
||||
r.Set(enr.UDP6(30306))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("2001::ff00:0042:8329"),
|
||||
wantUDP: 30306,
|
||||
},
|
||||
{
|
||||
name: "ipv4-unspecified-and-ipv6-loopback",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("0.0.0.0")))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("::1")))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("::1"),
|
||||
},
|
||||
{
|
||||
name: "ipv4-private-and-ipv6-global",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("192.168.2.2")))
|
||||
r.Set(enr.UDP(30304))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("2001::ff00:0042:8329")))
|
||||
r.Set(enr.UDP6(30306))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("2001::ff00:0042:8329"),
|
||||
wantUDP: 30306,
|
||||
},
|
||||
{
|
||||
name: "ipv4-local-and-ipv6-global",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("169.254.2.6")))
|
||||
r.Set(enr.UDP(30304))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("2001::ff00:0042:8329")))
|
||||
r.Set(enr.UDP6(30306))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("2001::ff00:0042:8329"),
|
||||
wantUDP: 30306,
|
||||
},
|
||||
{
|
||||
name: "ipv4-private-and-ipv6-private",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("192.168.2.2")))
|
||||
r.Set(enr.UDP(30304))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("fd00::abcd:1")))
|
||||
r.Set(enr.UDP6(30306))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("192.168.2.2"),
|
||||
wantUDP: 30304,
|
||||
},
|
||||
{
|
||||
name: "ipv4-private-and-ipv6-link-local",
|
||||
node: func() *Node {
|
||||
var r enr.Record
|
||||
r.Set(enr.IPv4Addr(netip.MustParseAddr("192.168.2.2")))
|
||||
r.Set(enr.UDP(30304))
|
||||
r.Set(enr.IPv6Addr(netip.MustParseAddr("fe80::1")))
|
||||
r.Set(enr.UDP6(30306))
|
||||
return SignNull(&r, id)
|
||||
}(),
|
||||
wantIP: netip.MustParseAddr("192.168.2.2"),
|
||||
wantUDP: 30304,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.wantIP != test.node.IPAddr() {
|
||||
t.Errorf("node has wrong IP %v, want %v", test.node.IPAddr(), test.wantIP)
|
||||
}
|
||||
if test.wantUDP != test.node.UDP() {
|
||||
t.Errorf("node has wrong UDP port %d, want %d", test.node.UDP(), test.wantUDP)
|
||||
}
|
||||
if test.wantTCP != test.node.TCP() {
|
||||
t.Errorf("node has wrong TCP port %d, want %d", test.node.TCP(), test.wantTCP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHexID(t *testing.T) {
|
||||
ref := ID{0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
|
||||
id1 := HexID("0x00000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
|
|
@ -264,14 +265,14 @@ func (db *DB) Node(id ID) *Node {
|
|||
}
|
||||
|
||||
func mustDecodeNode(id, data []byte) *Node {
|
||||
node := new(Node)
|
||||
if err := rlp.DecodeBytes(data, &node.r); err != nil {
|
||||
var r enr.Record
|
||||
if err := rlp.DecodeBytes(data, &r); err != nil {
|
||||
panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err))
|
||||
}
|
||||
// Restore node id cache.
|
||||
copy(node.id[:], id)
|
||||
|
||||
return node
|
||||
if len(id) != len(ID{}) {
|
||||
panic(fmt.Errorf("invalid id length %d", len(id)))
|
||||
}
|
||||
return newNodeWithID(&r, ID(id))
|
||||
}
|
||||
|
||||
// UpdateNode inserts - potentially overwriting - a node into the peer database.
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func (n *Node) URLv4() string {
|
|||
}
|
||||
|
||||
u := url.URL{Scheme: "enode"}
|
||||
if n.Incomplete() {
|
||||
if !n.ip.IsValid() {
|
||||
u.Host = nodeid
|
||||
} else {
|
||||
addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
|
@ -178,6 +179,60 @@ func (v *IPv6) DecodeRLP(s *rlp.Stream) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// IPv4Addr is the "ip" key, which holds the IP address of the node.
|
||||
type IPv4Addr netip.Addr
|
||||
|
||||
func (v IPv4Addr) ENRKey() string { return "ip" }
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v IPv4Addr) EncodeRLP(w io.Writer) error {
|
||||
addr := netip.Addr(v)
|
||||
if !addr.Is4() {
|
||||
return fmt.Errorf("address is not IPv4")
|
||||
}
|
||||
enc := rlp.NewEncoderBuffer(w)
|
||||
bytes := addr.As4()
|
||||
enc.WriteBytes(bytes[:])
|
||||
return enc.Flush()
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *IPv4Addr) DecodeRLP(s *rlp.Stream) error {
|
||||
var bytes [4]byte
|
||||
if err := s.ReadBytes(bytes[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*v = IPv4Addr(netip.AddrFrom4(bytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPv6Addr is the "ip6" key, which holds the IP address of the node.
|
||||
type IPv6Addr netip.Addr
|
||||
|
||||
func (v IPv6Addr) ENRKey() string { return "ip6" }
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v IPv6Addr) EncodeRLP(w io.Writer) error {
|
||||
addr := netip.Addr(v)
|
||||
if !addr.Is6() {
|
||||
return fmt.Errorf("address is not IPv6")
|
||||
}
|
||||
enc := rlp.NewEncoderBuffer(w)
|
||||
bytes := addr.As16()
|
||||
enc.WriteBytes(bytes[:])
|
||||
return enc.Flush()
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *IPv6Addr) DecodeRLP(s *rlp.Stream) error {
|
||||
var bytes [16]byte
|
||||
if err := s.ReadBytes(bytes[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*v = IPv6Addr(netip.AddrFrom16(bytes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyError is an error related to a key.
|
||||
type KeyError struct {
|
||||
Key string
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -38,7 +40,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -194,8 +195,8 @@ type Server struct {
|
|||
|
||||
nodedb *enode.DB
|
||||
localnode *enode.LocalNode
|
||||
ntab *discover.UDPv4
|
||||
DiscV5 *discover.UDPv5
|
||||
discv4 *discover.UDPv4
|
||||
discv5 *discover.UDPv5
|
||||
discmix *enode.FairMix
|
||||
dialsched *dialScheduler
|
||||
|
||||
|
|
@ -456,6 +457,16 @@ func (srv *Server) Self() *enode.Node {
|
|||
return ln.Node()
|
||||
}
|
||||
|
||||
// DiscoveryV4 returns the discovery v4 instance, if configured.
|
||||
func (srv *Server) DiscoveryV4() *discover.UDPv4 {
|
||||
return srv.discv4
|
||||
}
|
||||
|
||||
// DiscoveryV4 returns the discovery v5 instance, if configured.
|
||||
func (srv *Server) DiscoveryV5() *discover.UDPv5 {
|
||||
return srv.discv5
|
||||
}
|
||||
|
||||
// Stop terminates the server and all active peer connections.
|
||||
// It blocks until all active connections have been closed.
|
||||
func (srv *Server) Stop() {
|
||||
|
|
@ -483,11 +494,11 @@ type sharedUDPConn struct {
|
|||
unhandled chan discover.ReadPacket
|
||||
}
|
||||
|
||||
// ReadFromUDP implements discover.UDPConn
|
||||
func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
// ReadFromUDPAddrPort implements discover.UDPConn
|
||||
func (s *sharedUDPConn) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||
packet, ok := <-s.unhandled
|
||||
if !ok {
|
||||
return 0, nil, errors.New("connection was closed")
|
||||
return 0, netip.AddrPort{}, errors.New("connection was closed")
|
||||
}
|
||||
|
||||
l := len(packet.Data)
|
||||
|
|
@ -621,13 +632,13 @@ func (srv *Server) setupDiscovery() error {
|
|||
)
|
||||
// If both versions of discovery are running, setup a shared
|
||||
// connection, so v5 can read unhandled messages from v4.
|
||||
if srv.DiscoveryV4 && srv.DiscoveryV5 {
|
||||
if srv.Config.DiscoveryV4 && srv.Config.DiscoveryV5 {
|
||||
unhandled = make(chan discover.ReadPacket, 100)
|
||||
sconn = &sharedUDPConn{conn, unhandled}
|
||||
}
|
||||
|
||||
// Start discovery services.
|
||||
if srv.DiscoveryV4 {
|
||||
if srv.Config.DiscoveryV4 {
|
||||
cfg := discover.Config{
|
||||
PrivateKey: srv.PrivateKey,
|
||||
NetRestrict: srv.NetRestrict,
|
||||
|
|
@ -640,18 +651,17 @@ func (srv *Server) setupDiscovery() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv.ntab = ntab
|
||||
srv.discv4 = ntab
|
||||
srv.discmix.AddSource(ntab.RandomNodes())
|
||||
}
|
||||
if srv.DiscoveryV5 {
|
||||
if srv.Config.DiscoveryV5 {
|
||||
cfg := discover.Config{
|
||||
PrivateKey: srv.PrivateKey,
|
||||
NetRestrict: srv.NetRestrict,
|
||||
Bootnodes: srv.BootstrapNodesV5,
|
||||
Log: srv.log,
|
||||
}
|
||||
srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg)
|
||||
srv.discv5, err = discover.ListenV5(sconn, srv.localnode, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -678,8 +688,8 @@ func (srv *Server) setupDialScheduler() {
|
|||
dialer: srv.Dialer,
|
||||
clock: srv.clock,
|
||||
}
|
||||
if srv.ntab != nil {
|
||||
config.resolver = srv.ntab
|
||||
if srv.discv4 != nil {
|
||||
config.resolver = srv.discv4
|
||||
}
|
||||
|
||||
if config.dialer == nil {
|
||||
|
|
@ -877,12 +887,11 @@ running:
|
|||
srv.log.Trace("P2P networking is spinning down")
|
||||
|
||||
// Terminate discovery. If there is a running lookup it will terminate soon.
|
||||
if srv.ntab != nil {
|
||||
srv.ntab.Close()
|
||||
if srv.discv4 != nil {
|
||||
srv.discv4.Close()
|
||||
}
|
||||
|
||||
if srv.DiscV5 != nil {
|
||||
srv.DiscV5.Close()
|
||||
if srv.discv5 != nil {
|
||||
srv.discv5.Close()
|
||||
}
|
||||
// Disconnect all peers.
|
||||
for _, p := range peers {
|
||||
|
|
|
|||
Loading…
Reference in a new issue