@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"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"
"github.com/ethereum/go-ethereum/rlp"
)
@ -47,12 +48,12 @@ var (
errClosed = errors . New ( "socket closed" )
)
// Timeouts
const (
respTimeout = 500 * time . Millisecond
expiration = 20 * time . Second
bondExpiration = 24 * time . Hour
maxFindnodeFailures = 5 // nodes exceeding this limit are dropped
ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP
ntpWarningCooldown = 10 * time . Minute // Minimum amount of time to pass before repeating NTP warning
driftThreshold = 10 * time . Second // Allowed clock drift before warning user
@ -69,6 +70,8 @@ const (
p_pongV4
p_findnodeV4
p_neighborsV4
p_enrRequestV4
p_enrResponseV4
)
// RPC request structures
@ -112,6 +115,21 @@ type (
Rest [ ] rlp . RawValue ` rlp:"tail" `
}
// enrRequestV4 queries for the remote node's record.
enrRequestV4 struct {
Expiration uint64
// Ignore additional fields (for forward compatibility).
Rest [ ] rlp . RawValue ` rlp:"tail" `
}
// enrResponseV4 is the reply to enrRequestV4.
enrResponseV4 struct {
ReplyTok [ ] byte // Hash of the enrRequest packet.
Record enr . Record
// Ignore additional fields (for forward compatibility).
Rest [ ] rlp . RawValue ` rlp:"tail" `
}
rpcNode struct {
IP net . IP // len 4 for IPv4 or 16 for IPv6
UDP uint16 // for discovery protocol
@ -126,14 +144,15 @@ type (
}
)
// packet is implemented by all v4 protocol messages.
// packetV4 is implemented by all v4 protocol messages.
type packetV4 interface {
// preverify checks whether the packet is valid and should be handled at all.
preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error
// handle handles the packet.
handle ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , mac [ ] byte )
// name returns the name of the packet for logging purposes.
// packet name and type for logging purposes.
name ( ) string
kind ( ) byte
}
func makeEndpoint ( addr * net . UDPAddr , tcpPort uint16 ) rpcEndpoint {
@ -191,7 +210,7 @@ type UDPv4 struct {
closing chan struct { }
}
// pending represents a pending reply.
// replyMatcher represents a pending reply.
//
// Some implementations of the protocol wish to send more than one
// reply packet to findnode. In general, any neighbors packet cannot
@ -217,17 +236,20 @@ type replyMatcher struct {
// errc receives nil when the callback indicates completion or an
// error if no further reply is received within the timeout.
errc chan <- error
errc chan error
// reply contains the most recent reply. This field is safe for reading after errc has
// received a value.
reply packetV4
}
type replyMatchFunc func ( interface { } ) ( matched bool , requestDone bool )
// reply is a reply packet from a certain node.
type reply struct {
from enode . ID
ip net . IP
ptype byte
data packetV4
from enode . ID
ip net . IP
data packetV4
// loop indicates whether there was
// a matching request by sending on this channel.
matched chan <- bool
@ -377,7 +399,8 @@ func (t *UDPv4) lookupWorker(n *node, targetKey encPubkey, reply chan<- []*node)
t . tab . delete ( n )
}
} else if fails > 0 {
t . db . UpdateFindFails ( n . ID ( ) , n . IP ( ) , fails - 1 )
// Reset failure counter because it counts _consecutive_ failures.
t . db . UpdateFindFails ( n . ID ( ) , n . IP ( ) , 0 )
}
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
@ -388,23 +411,34 @@ func (t *UDPv4) lookupWorker(n *node, targetKey encPubkey, reply chan<- []*node)
reply <- r
}
// Resolve searches for a specific node with the given ID.
// It returns nil if the node could not be foun d.
// Resolve searches for a specific node with the given ID and tries to get the most recent
// version of the node record for it. It returns n if the node could not be resolve d.
func ( t * UDPv4 ) Resolve ( n * enode . Node ) * enode . Node {
// If the node is present in the local table, no
// network interaction is required.
if intab := t . tab . Resolve ( n ) ; intab != nil {
return intab
}
// Otherwise, do a network lookup.
hash := n . ID ( )
result := t . LookupPubkey ( n . Pubkey ( ) )
for _ , n := range result {
if n . ID ( ) == hash {
return n
// Try asking directly. This works if the node is still responding on the endpoint we have.
if rn , err := t . requestENR ( n ) ; err == nil {
return rn
}
// Check table for the ID, we might have a newer version there.
if intable := t . tab . getNode ( n . ID ( ) ) ; intable != nil && intable . Seq ( ) > n . Seq ( ) {
n = intable
if rn , err := t . requestENR ( n ) ; err == nil {
return rn
}
}
return nil
// Otherwise perform a network lookup.
var key * enode . Secp256k1
if n . Load ( key ) != nil {
return n // no secp256k1 key
}
result := t . LookupPubkey ( ( * ecdsa . PublicKey ) ( key ) )
for _ , rn := range result {
if rn . ID ( ) == n . ID ( ) {
if rn , err := t . requestENR ( rn ) ; err == nil {
return rn
}
}
}
return n
}
func ( t * UDPv4 ) ourEndpoint ( ) rpcEndpoint {
@ -414,28 +448,27 @@ func (t *UDPv4) ourEndpoint() rpcEndpoint {
}
// ping sends a ping message to the given node and waits for a reply.
func ( t * UDPv4 ) ping ( n * enode . Node ) error {
return <- t . sendPing ( n . ID ( ) , & net . UDPAddr { IP : n . IP ( ) , Port : n . UDP ( ) } , nil )
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 )
if err = <- rm . errc ; err == nil {
seq = seqFromTail ( rm . reply . ( * pongV4 ) . Rest )
}
return seq , err
}
// 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 ( ) ) <- chan error {
req := & pingV4 {
Version : 4 ,
From : t . ourEndpoint ( ) ,
To : makeEndpoint ( toaddr , 0 ) , // TODO: maybe use known TCP port from DB
Expiration : uint64 ( time . Now ( ) . Add ( expiration ) . Unix ( ) ) ,
}
packet , hash , err := t . encode ( t . priv , p_pingV4 , req )
func ( t * UDPv4 ) sendPing ( toid enode . ID , toaddr * net . UDPAddr , callback func ( ) ) * replyMatcher {
req := t . makePing ( toaddr )
packet , hash , err := t . encode ( t . priv , req )
if err != nil {
errc := make ( chan error , 1 )
errc <- err
return errc
return & replyMatcher { errc : errc }
}
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
// reference the ping we're about to send.
errc := t . pending ( toid , toaddr . IP , p_pongV4 , func ( p interface { } ) ( matched bool , requestDone bool ) {
rm := t . pending ( toid , toaddr . IP , p_pongV4 , func ( p interface { } ) ( matched bool , requestDone bool ) {
matched = bytes . Equal ( p . ( * pongV4 ) . ReplyTok , hash )
if matched && callback != nil {
callback ( )
@ -445,25 +478,30 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) <-
// Send the packet.
t . localNode . UDPContact ( toaddr )
t . write ( toaddr , toid , req . name ( ) , packet )
return errc
return rm
}
func ( t * UDPv4 ) makePing ( toaddr * net . UDPAddr ) * pingV4 {
seq , _ := rlp . EncodeToBytes ( t . localNode . Node ( ) . Seq ( ) )
return & pingV4 {
Version : 4 ,
From : t . ourEndpoint ( ) ,
To : makeEndpoint ( toaddr , 0 ) ,
Expiration : uint64 ( time . Now ( ) . Add ( expiration ) . Unix ( ) ) ,
Rest : [ ] rlp . RawValue { seq } ,
}
}
// 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 encPubkey ) ( [ ] * node , error ) {
// If we haven't seen a ping from the destination node for a while, it won't remember
// our endpoint proof and reject findnode. Solicit a ping first.
if time . Since ( t . db . LastPingReceived ( toid , toaddr . IP ) ) > bondExpiration {
<- t . sendPing ( toid , toaddr , nil )
// Wait for them to ping back and process our pong.
time . Sleep ( respTimeout )
}
t . ensureBond ( toid , toaddr )
// 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 )
nreceived := 0
errc := t . pending ( toid , toaddr . IP , p_neighborsV4 , func ( r interface { } ) ( matched bool , requestDone bool ) {
rm := t . pending ( toid , toaddr . IP , p_neighborsV4 , func ( r interface { } ) ( matched bool , requestDone bool ) {
reply := r . ( * neighborsV4 )
for _ , rn := range reply . Nodes {
nreceived ++
@ -476,16 +514,56 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) (
}
return true , nreceived >= bucketSize
} )
t . send ( toaddr , toid , p_findnodeV4 , & findnodeV4 {
t . send ( toaddr , toid , & findnodeV4 {
Target : target ,
Expiration : uint64 ( time . Now ( ) . Add ( expiration ) . Unix ( ) ) ,
} )
return nodes , <- errc
return nodes , <- rm . errc
}
// 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 ( ) }
t . ensureBond ( n . ID ( ) , addr )
req := & enrRequestV4 {
Expiration : uint64 ( time . Now ( ) . Add ( expiration ) . Unix ( ) ) ,
}
packet , hash , err := t . encode ( t . priv , req )
if err != nil {
return nil , err
}
// 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 , p_enrResponseV4 , func ( r interface { } ) ( matched bool , requestDone bool ) {
matched = bytes . Equal ( r . ( * enrResponseV4 ) . ReplyTok , hash )
return matched , matched
} )
// Send the packet and wait for the reply.
t . write ( addr , n . ID ( ) , req . name ( ) , packet )
if err := <- rm . errc ; err != nil {
return nil , err
}
// Verify the response record.
respN , err := enode . New ( enode . ValidSchemes , & rm . reply . ( * enrResponseV4 ) . Record )
if err != nil {
return nil , err
}
if respN . ID ( ) != n . ID ( ) {
return nil , fmt . Errorf ( "invalid ID in response record" )
}
if respN . Seq ( ) < n . Seq ( ) {
return n , nil // response record is older
}
if err := netutil . CheckRelayIP ( addr . IP , respN . IP ( ) ) ; err != nil {
return nil , fmt . Errorf ( "invalid IP in response record: %v" , err )
}
return respN , nil
}
// 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 ) <- chan error {
func ( t * UDPv4 ) pending ( id enode . ID , ip net . IP , ptype byte , callback replyMatchFunc ) * replyMatche r {
ch := make ( chan error , 1 )
p := & replyMatcher { from : id , ip : ip , ptype : ptype , callback : callback , errc : ch }
select {
@ -494,15 +572,15 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF
case <- t . closing :
ch <- errClosed
}
return ch
return p
}
// 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 , ptype byte , req packetV4 ) bool {
func ( t * UDPv4 ) handleReply ( from enode . ID , fromIP net . IP , req packetV4 ) bool {
matched := make ( chan bool , 1 )
select {
case t . gotreply <- reply { from , fromIP , ptype , req , matched } :
case t . gotreply <- reply { from , fromIP , req , matched } :
// loop will handle it
return <- matched
case <- t . closing :
@ -565,11 +643,12 @@ func (t *UDPv4) loop() {
var matched bool // whether any replyMatcher considered the reply acceptable.
for el := plist . Front ( ) ; el != nil ; el = el . Next ( ) {
p := el . Value . ( * replyMatcher )
if p . from == r . from && p . ptype == r . ptype && p . ip . Equal ( r . ip ) {
if p . from == r . from && p . ptype == r . data . kind ( ) && p . ip . Equal ( r . ip ) {
ok , requestDone := p . callback ( r . data )
matched = matched || ok
// Remove the matcher if callback indicates that all replies have been received.
if requestDone {
p . reply = r . data
p . errc <- nil
plist . Remove ( el )
}
@ -635,8 +714,8 @@ func init() {
}
}
func ( t * UDPv4 ) send ( toaddr * net . UDPAddr , toid enode . ID , ptype byte , req packetV4 ) ( [ ] byte , error ) {
packet , hash , err := t . encode ( t . priv , ptype , req )
func ( t * UDPv4 ) send ( toaddr * net . UDPAddr , toid enode . ID , req packetV4 ) ( [ ] byte , error ) {
packet , hash , err := t . encode ( t . priv , req )
if err != nil {
return hash , err
}
@ -649,18 +728,19 @@ func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []
return err
}
func ( t * UDPv4 ) encode ( priv * ecdsa . PrivateKey , ptype byte , req interface { } ) ( packet , hash [ ] byte , err error ) {
func ( t * UDPv4 ) encode ( priv * ecdsa . PrivateKey , req packetV4 ) ( packet , hash [ ] byte , err error ) {
name := req . name ( )
b := new ( bytes . Buffer )
b . Write ( headSpace )
b . WriteByte ( ptype )
b . WriteByte ( req . kind ( ) )
if err := rlp . Encode ( b , req ) ; err != nil {
t . log . Error ( "Can't encode discv4 packet" , "err" , err )
t . log . Error ( fmt . Sprintf ( "Can't encode %s packet" , name ) , "err" , err )
return nil , nil , err
}
packet = b . Bytes ( )
sig , err := crypto . Sign ( crypto . Keccak256 ( packet [ headSize : ] ) , priv )
if err != nil {
t . log . Error ( "Can't sign discv4 packet" , "err" , err )
t . log . Error ( fmt . Sprintf ( "Can't sign %s packet" , name ) , "err" , err )
return nil , nil , err
}
copy ( packet [ macSize : ] , sig )
@ -743,6 +823,10 @@ func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) {
req = new ( findnodeV4 )
case p_neighborsV4 :
req = new ( neighborsV4 )
case p_enrRequestV4 :
req = new ( enrRequestV4 )
case p_enrResponseV4 :
req = new ( enrResponseV4 )
default :
return nil , fromKey , hash , fmt . Errorf ( "unknown type: %d" , ptype )
}
@ -751,7 +835,41 @@ func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) {
return req , fromKey , hash , err
}
// Packet Handlers
// 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
}
// 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 {
rm := t . sendPing ( toid , toaddr , nil )
<- rm . errc
// Wait for them to ping back and process our pong.
time . Sleep ( respTimeout )
}
}
// expired checks whether the given UNIX time stamp is in the past.
func expired ( ts uint64 ) bool {
return time . Unix ( int64 ( ts ) , 0 ) . Before ( time . Now ( ) )
}
func seqFromTail ( tail [ ] rlp . RawValue ) uint64 {
if len ( tail ) == 0 {
return 0
}
var seq uint64
rlp . DecodeBytes ( tail [ 0 ] , & seq )
return seq
}
// PING/v4
func ( req * pingV4 ) name ( ) string { return "PING/v4" }
func ( req * pingV4 ) kind ( ) byte { return p_pingV4 }
func ( req * pingV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if expired ( req . Expiration ) {
@ -767,10 +885,12 @@ func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromK
func ( req * pingV4 ) handle ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , mac [ ] byte ) {
// Reply.
t . send ( from , fromID , p_pongV4 , & pongV4 {
seq , _ := rlp . EncodeToBytes ( t . localNode . Node ( ) . Seq ( ) )
t . send ( from , fromID , & pongV4 {
To : makeEndpoint ( from , req . From . TCP ) ,
ReplyTok : mac ,
Expiration : uint64 ( time . Now ( ) . Add ( expiration ) . Unix ( ) ) ,
Rest : [ ] rlp . RawValue { seq } ,
} )
// Ping back if our last pong on file is too far in the past.
@ -788,13 +908,16 @@ func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by
t . localNode . UDPEndpointStatement ( from , & net . UDPAddr { IP : req . To . IP , Port : int ( req . To . UDP ) } )
}
func ( req * pingV4 ) name ( ) string { return "PING/v4" }
// PONG/v4
func ( req * pongV4 ) name ( ) string { return "PONG/v4" }
func ( req * pongV4 ) kind ( ) byte { return p_pongV4 }
func ( req * pongV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if expired ( req . Expiration ) {
return errExpired
}
if ! t . handleReply ( fromID , from . IP , p_pongV4 , req ) {
if ! t . handleReply ( fromID , from . IP , req ) {
return errUnsolicitedReply
}
return nil
@ -805,13 +928,16 @@ func (req *pongV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by
t . db . UpdateLastPongReceived ( fromID , from . IP , time . Now ( ) )
}
func ( req * pongV4 ) name ( ) string { return "PONG/v4" }
// FINDNODE/v4
func ( req * findnodeV4 ) name ( ) string { return "FINDNODE/v4" }
func ( req * findnodeV4 ) kind ( ) byte { return p_findnodeV4 }
func ( req * findnodeV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if expired ( req . Expiration ) {
return errExpired
}
if time . Since ( t . db . LastPongReceive d( fromID , from . IP ) ) > bondExpiration {
if ! t . checkBon d( fromID , from . IP ) {
// 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
@ -839,23 +965,26 @@ func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac
p . Nodes = append ( p . Nodes , nodeToRPC ( n ) )
}
if len ( p . Nodes ) == maxNeighbors {
t . send ( from , fromID , p_neighborsV4 , & p )
t . send ( from , fromID , & p )
p . Nodes = p . Nodes [ : 0 ]
sent = true
}
}
if len ( p . Nodes ) > 0 || ! sent {
t . send ( from , fromID , p_neighborsV4 , & p )
t . send ( from , fromID , & p )
}
}
func ( req * findnodeV4 ) name ( ) string { return "FINDNODE/v4" }
// NEIGHBORS/v4
func ( req * neighborsV4 ) name ( ) string { return "NEIGHBORS/v4" }
func ( req * neighborsV4 ) kind ( ) byte { return p_neighborsV4 }
func ( req * neighborsV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if expired ( req . Expiration ) {
return errExpired
}
if ! t . handleReply ( fromID , from . IP , p_neighborsV4 , req ) {
if ! t . handleReply ( fromID , from . IP , req ) {
return errUnsolicitedReply
}
return nil
@ -864,8 +993,39 @@ func (req *neighborsV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID,
func ( req * neighborsV4 ) handle ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , mac [ ] byte ) {
}
func ( req * neighborsV4 ) name ( ) string { return "NEIGHBORS/v4" }
// ENRREQUEST/v4
func expired ( ts uint64 ) bool {
return time . Unix ( int64 ( ts ) , 0 ) . Before ( time . Now ( ) )
func ( req * enrRequestV4 ) name ( ) string { return "ENRREQUEST/v4" }
func ( req * enrRequestV4 ) kind ( ) byte { return p_enrRequestV4 }
func ( req * enrRequestV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if expired ( req . Expiration ) {
return errExpired
}
if ! t . checkBond ( fromID , from . IP ) {
return errUnknownNode
}
return nil
}
func ( req * enrRequestV4 ) handle ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , mac [ ] byte ) {
t . send ( from , fromID , & enrResponseV4 {
ReplyTok : mac ,
Record : * t . localNode . Node ( ) . Record ( ) ,
} )
}
// ENRRESPONSE/v4
func ( req * enrResponseV4 ) name ( ) string { return "ENRRESPONSE/v4" }
func ( req * enrResponseV4 ) kind ( ) byte { return p_enrResponseV4 }
func ( req * enrResponseV4 ) preverify ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , fromKey encPubkey ) error {
if ! t . handleReply ( fromID , from . IP , req ) {
return errUnsolicitedReply
}
return nil
}
func ( req * enrResponseV4 ) handle ( t * UDPv4 , from * net . UDPAddr , fromID enode . ID , mac [ ] byte ) {
}