@ -24,6 +24,7 @@ import (
"fmt"
mrand "math/rand"
"net"
"net/netip"
"sync"
"sync/atomic"
"time"
@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"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"
)
@ -77,6 +79,7 @@ var (
errRecentlyDialed = errors . New ( "recently dialed" )
errNetRestrict = errors . New ( "not contained in netrestrict list" )
errNoPort = errors . New ( "node does not provide TCP port" )
errNoResolvedIP = errors . New ( "node does not provide a resolved IP" )
)
// dialer creates outbound connections and submits them into Server.
@ -90,16 +93,17 @@ var (
// to create peer connections to nodes arriving through the iterator.
type dialScheduler struct {
dialConfig
setupFunc dialSetupFunc
wg sync . WaitGroup
cancel context . CancelFunc
ctx context . Context
nodesIn chan * enode . Node
doneCh chan * dialTask
addStaticCh chan * enode . Node
remStaticCh chan * enode . Node
addPeerCh chan * conn
remPeerCh chan * conn
setupFunc dialSetupFunc
dnsLookupFunc func ( ctx context . Context , network string , name string ) ( [ ] netip . Addr , error )
wg sync . WaitGroup
cancel context . CancelFunc
ctx context . Context
nodesIn chan * enode . Node
doneCh chan * dialTask
addStaticCh chan * enode . Node
remStaticCh chan * enode . Node
addPeerCh chan * conn
remPeerCh chan * conn
// Everything below here belongs to loop and
// should only be accessed by code on the loop goroutine.
@ -159,18 +163,19 @@ func (cfg dialConfig) withDefaults() dialConfig {
func newDialScheduler ( config dialConfig , it enode . Iterator , setupFunc dialSetupFunc ) * dialScheduler {
cfg := config . withDefaults ( )
d := & dialScheduler {
dialConfig : cfg ,
historyTimer : mclock . NewAlarm ( cfg . clock ) ,
setupFunc : setupFunc ,
dialing : make ( map [ enode . ID ] * dialTask ) ,
static : make ( map [ enode . ID ] * dialTask ) ,
peers : make ( map [ enode . ID ] struct { } ) ,
doneCh : make ( chan * dialTask ) ,
nodesIn : make ( chan * enode . Node ) ,
addStaticCh : make ( chan * enode . Node ) ,
remStaticCh : make ( chan * enode . Node ) ,
addPeerCh : make ( chan * conn ) ,
remPeerCh : make ( chan * conn ) ,
dialConfig : cfg ,
historyTimer : mclock . NewAlarm ( cfg . clock ) ,
setupFunc : setupFunc ,
dnsLookupFunc : net . DefaultResolver . LookupNetIP ,
dialing : make ( map [ enode . ID ] * dialTask ) ,
static : make ( map [ enode . ID ] * dialTask ) ,
peers : make ( map [ enode . ID ] struct { } ) ,
doneCh : make ( chan * dialTask ) ,
nodesIn : make ( chan * enode . Node ) ,
addStaticCh : make ( chan * enode . Node ) ,
remStaticCh : make ( chan * enode . Node ) ,
addPeerCh : make ( chan * conn ) ,
remPeerCh : make ( chan * conn ) ,
}
d . lastStatsLog = d . clock . Now ( )
d . ctx , d . cancel = context . WithCancel ( context . Background ( ) )
@ -274,7 +279,7 @@ loop:
case node := <- d . addStaticCh :
id := node . ID ( )
_ , exists := d . static [ id ]
d . log . Trace ( "Adding static node" , "id" , id , "ip" , node . IPAddr ( ) , "added" , ! exists )
d . log . Trace ( "Adding static node" , "id" , id , "endpoint" , nodeEndpointForLog ( node ) , "added" , ! exists )
if exists {
continue loop
}
@ -433,10 +438,68 @@ func (d *dialScheduler) removeFromStaticPool(idx int) {
task . staticPoolIndex = - 1
}
// dnsResolveHostname updates the given node from its DNS hostname.
// This is used to resolve static dial targets.
func ( d * dialScheduler ) dnsResolveHostname ( n * enode . Node ) ( * enode . Node , error ) {
if n . Hostname ( ) == "" {
return n , nil
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
foundIPs , err := d . dnsLookupFunc ( ctx , "ip" , n . Hostname ( ) )
if err != nil {
return n , err
}
// Check for IP updates.
var (
nodeIP4 , nodeIP6 netip . Addr
foundIP4 , foundIP6 netip . Addr
)
n . Load ( ( * enr . IPv4Addr ) ( & nodeIP4 ) )
n . Load ( ( * enr . IPv6Addr ) ( & nodeIP6 ) )
for _ , ip := range foundIPs {
if ip . Is4 ( ) && ! foundIP4 . IsValid ( ) {
foundIP4 = ip
}
if ip . Is6 ( ) && ! foundIP6 . IsValid ( ) {
foundIP6 = ip
}
}
if ! foundIP4 . IsValid ( ) && ! foundIP6 . IsValid ( ) {
// Lookup failed.
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. 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 ) )
}
rec . SetSeq ( n . Seq ( ) ) // ensure seq not bumped by update
newNode := enode . SignNull ( rec , n . ID ( ) ) . WithHostname ( n . Hostname ( ) )
d . log . Debug ( "Node updated from DNS lookup" , "id" , n . ID ( ) , "name" , n . Hostname ( ) , "ip" , newNode . IP ( ) )
return newNode , nil
}
// startDial runs the given dial task in a separate goroutine.
func ( d * dialScheduler ) startDial ( task * dialTask ) {
node := task . dest ( )
d . log . Trace ( "Starting p2p dial" , "id" , node . ID ( ) , "ip" , node . IPAddr ( ) , "flag" , task . flags )
d . log . Trace ( "Starting p2p dial" , "id" , node . ID ( ) , "endpoint" , nodeEndpointForLog ( node ) , "flag" , task . flags )
hkey := string ( node . ID ( ) . Bytes ( ) )
d . history . add ( hkey , d . clock . Now ( ) . Add ( dialHistoryExpiration ) )
d . dialing [ node . ID ( ) ] = task
@ -473,14 +536,29 @@ func (t *dialTask) dest() *enode.Node {
}
func ( t * dialTask ) run ( d * dialScheduler ) {
if t . needResolve ( ) && ! t . resolve ( d ) {
return
if t . isStatic ( ) {
// Resolve DNS.
if n := t . dest ( ) ; n . Hostname ( ) != "" {
resolved , err := d . dnsResolveHostname ( n )
if err != nil {
d . log . Warn ( "DNS lookup of static node failed" , "id" , n . ID ( ) , "name" , n . Hostname ( ) , "err" , err )
} else {
t . destPtr . Store ( resolved )
}
}
// Try resolving node ID through the DHT if there is no IP address.
if ! t . dest ( ) . IPAddr ( ) . IsValid ( ) {
if ! t . resolve ( d ) {
return // DHT resolve failed, skip dial.
}
}
}
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 {
var dialErr * dialError
if errors . As ( err , & dialErr ) && t . isStatic ( ) {
if t . resolve ( d ) {
t . dial ( d , t . dest ( ) )
}
@ -488,8 +566,8 @@ func (t *dialTask) run(d *dialScheduler) {
}
}
func ( t * dialTask ) needResolve ( ) bool {
return t . flags & staticDialedConn != 0 && ! t . dest ( ) . IPAddr ( ) . IsValid ( )
func ( t * dialTask ) isStatic ( ) bool {
return t . flags & staticDialedConn != 0
}
// resolve attempts to find the current endpoint for the destination
@ -553,3 +631,10 @@ func cleanupDialErr(err error) error {
}
return err
}
func nodeEndpointForLog ( n * enode . Node ) string {
if n . Hostname ( ) != "" {
return n . Hostname ( )
}
return n . IPAddr ( ) . String ( )
}