p2p: avoid node update if DNS lookup returns unchanged IP

This commit is contained in:
Felix Lange 2024-12-12 23:40:29 +01:00
parent 8bec25759c
commit 7ffb97c414

View file

@ -24,6 +24,7 @@ import (
"fmt" "fmt"
mrand "math/rand" mrand "math/rand"
"net" "net"
"net/netip"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -93,6 +94,7 @@ var (
type dialScheduler struct { type dialScheduler struct {
dialConfig dialConfig
setupFunc dialSetupFunc setupFunc dialSetupFunc
dnsLookupFunc func(ctx context.Context, network string, name string) ([]netip.Addr, error)
wg sync.WaitGroup wg sync.WaitGroup
cancel context.CancelFunc cancel context.CancelFunc
ctx context.Context ctx context.Context
@ -164,6 +166,7 @@ func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupF
dialConfig: cfg, dialConfig: cfg,
historyTimer: mclock.NewAlarm(cfg.clock), historyTimer: mclock.NewAlarm(cfg.clock),
setupFunc: setupFunc, setupFunc: setupFunc,
dnsLookupFunc: net.DefaultResolver.LookupNetIP,
dialing: make(map[enode.ID]*dialTask), dialing: make(map[enode.ID]*dialTask),
static: make(map[enode.ID]*dialTask), static: make(map[enode.ID]*dialTask),
peers: make(map[enode.ID]struct{}), peers: make(map[enode.ID]struct{}),
@ -442,37 +445,53 @@ func (d *dialScheduler) dnsResolveHostname(n *enode.Node) (*enode.Node, error) {
return n, nil return n, nil
} }
d.log.Trace("Attempting DNS resolution", "id", n.ID(), "name", n.Hostname()) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ips, err := net.LookupIP(n.Hostname()) defer cancel()
foundIPs, err := d.dnsLookupFunc(ctx, "ip", n.Hostname())
if err != nil { if err != nil {
return n, err return n, err
} }
d.log.Debug("DNS lookup succeeded", "id", n.ID(), "name", n.Hostname(), "ipcount", len(ips))
// Set new IPs in node record. // Check for IP updates.
rec := n.Record() var (
var foundIP bool nodeIP4, nodeIP6 netip.Addr
for _, ip := range ips { foundIP4, foundIP6 netip.Addr
if ip4 := ip.To4(); ip4 != nil { )
rec.Set(enr.IPv4(ip4)) n.Load((*enr.IPv4Addr)(&nodeIP4))
foundIP = true n.Load((*enr.IPv6Addr)(&nodeIP6))
break for _, ip := range foundIPs {
if ip.Is4() && !foundIP4.IsValid() {
foundIP4 = ip
} }
} if ip.Is6() && !foundIP6.IsValid() {
for _, ip := range ips { foundIP6 = ip
if ip6 := ip.To16(); ip6 != nil {
rec.Set(enr.IPv6(ip6))
foundIP = true
break
} }
} }
if !foundIP { if !foundIP4.IsValid() && !foundIP6.IsValid() {
// Lookup failed.
return n, errNoResolvedIP return n, errNoResolvedIP
} }
if foundIP4 == nodeIP4 && foundIP6 == nodeIP6 {
// No updates necessary.
d.log.Trace("Node DNS lookup had no update", "id", n.ID(), "name", n.Hostname(), "ip", foundIP4, "ip6", foundIP6)
return n, nil
}
// Update the node. // Update the node. Note this invalidates the ENR signature, because we use SignNull
// to create a modified copy. But this should be OK, since we just use the node as a
// dial target. And nodes will usually only have a DNS hostname if they came from a
// enode:// URL, which has no signature anyway. If it ever becomes a problem, the
// resolved IP could also be stored into dialTask instead of the node.
rec := n.Record()
if foundIP4.IsValid() {
rec.Set(enr.IPv4Addr(foundIP4))
}
if foundIP6.IsValid() {
rec.Set(enr.IPv6Addr(foundIP6))
}
newNode := enode.SignNull(rec, n.ID()).WithHostname(n.Hostname()) newNode := enode.SignNull(rec, n.ID()).WithHostname(n.Hostname())
d.log.Trace("Node updated from DNS lookup", "id", n.ID(), "name", n.Hostname(), "ip", newNode.IP())
return newNode, nil return newNode, nil
} }
@ -518,19 +537,18 @@ func (t *dialTask) dest() *enode.Node {
func (t *dialTask) run(d *dialScheduler) { func (t *dialTask) run(d *dialScheduler) {
if t.isStatic() { if t.isStatic() {
// Resolve DNS. // Resolve DNS.
node := t.dest() if n := t.dest(); n.Hostname() != "" {
if node.Hostname() != "" { resolved, err := d.dnsResolveHostname(n)
resolved, err := d.dnsResolveHostname(node)
if err != nil { if err != nil {
d.log.Warn("DNS resolve of static node failed", "id", node.ID(), "name", node.Hostname(), "err", err) d.log.Warn("DNS lookup of static node failed", "id", n.ID(), "name", n.Hostname(), "err", err)
} else { } else {
t.destPtr.Store(resolved) t.destPtr.Store(resolved)
} }
} }
// Try resolving node ID through the DHT if there is no IP address. // Try resolving node ID through the DHT if there is no IP address.
if !node.IPAddr().IsValid() { if !t.dest().IPAddr().IsValid() {
if !t.resolve(d) { if !t.resolve(d) {
return // DHT resolve failed return // DHT resolve failed, skip dial.
} }
} }
} }