@ -1,10 +1,18 @@
package discover
import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"
"io"
logpkg "log"
"net"
"os"
"path"
"reflect"
"runtime"
"sync"
"testing"
"time"
@ -15,197 +23,317 @@ func init() {
logger . AddLogSystem ( logger . NewStdLogSystem ( os . Stdout , logpkg . LstdFlags , logger . ErrorLevel ) )
}
func TestUDP_ping ( t * testing . T ) {
t . Parallel ( )
n1 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
n2 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
defer n1 . Close ( )
defer n2 . Close ( )
type udpTest struct {
t * testing . T
pipe * dgramPipe
table * Table
udp * udp
sent [ ] [ ] byte
localkey , remotekey * ecdsa . PrivateKey
remoteaddr * net . UDPAddr
}
if err := n1 . net . ping ( n2 . self ) ; err != nil {
t . Fatalf ( "ping error: %v" , err )
func newUDPTest ( t * testing . T ) * udpTest {
test := & udpTest {
t : t ,
pipe : newpipe ( ) ,
localkey : newkey ( ) ,
remotekey : newkey ( ) ,
remoteaddr : & net . UDPAddr { IP : net . IP { 1 , 2 , 3 , 4 } , Port : 30303 } ,
}
if find ( n2 , n1 . self . ID ) == nil {
t . Errorf ( "node 2 does not contain id of node 1" )
test . table , test . udp = newUDP ( test . localkey , test . pipe , nil )
return test
}
// handles a packet as if it had been sent to the transport.
func ( test * udpTest ) packetIn ( wantError error , ptype byte , data packet ) error {
enc , err := encodePacket ( test . remotekey , ptype , data )
if err != nil {
return test . errorf ( "packet (%d) encode error: %v" , err )
}
if e := find ( n1 , n2 . self . ID ) ; e != nil {
t . Errorf ( "node 1 does contains id of node 2: %v" , e )
test . sent = append ( test . sent , enc )
err = data . handle ( test . udp , test . remoteaddr , PubkeyID ( & test . remotekey . PublicKey ) , enc [ : macSize ] )
if err != wantError {
return test . errorf ( "error mismatch: got %q, want %q" , err , wantError )
}
return nil
}
func find ( tab * Table , id NodeID ) * Node {
for _ , b := range tab . buckets {
for _ , e := range b . entries {
if e . ID == id {
return e
}
}
// waits for a packet to be sent by the transport.
// validate should have type func(*udpTest, X) error, where X is a packet type.
func ( test * udpTest ) waitPacketOut ( validate interface { } ) error {
dgram := test . pipe . waitPacketOut ( )
p , _ , _ , err := decodePacket ( dgram )
if err != nil {
return test . errorf ( "sent packet decode error: %v" , err )
}
fn := reflect . ValueOf ( validate )
exptype := fn . Type ( ) . In ( 0 )
if reflect . TypeOf ( p ) != exptype {
return test . errorf ( "sent packet type mismatch, got: %v, want: %v" , reflect . TypeOf ( p ) , exptype )
}
fn . Call ( [ ] reflect . Value { reflect . ValueOf ( p ) } )
return nil
}
func TestUDP_findnode ( t * testing . T ) {
func ( test * udpTest ) errorf ( format string , args ... interface { } ) error {
_ , file , line , ok := runtime . Caller ( 2 ) // errorf + waitPacketOut
if ok {
file = path . Base ( file )
} else {
file = "???"
line = 1
}
err := fmt . Errorf ( format , args ... )
fmt . Printf ( "\t%s:%d: %v\n" , file , line , err )
test . t . Fail ( )
return err
}
// shared test variables
var (
futureExp = uint64 ( time . Now ( ) . Add ( 10 * time . Hour ) . Unix ( ) )
testTarget = MustHexID ( "01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" )
)
func TestUDP_packetErrors ( t * testing . T ) {
test := newUDPTest ( t )
defer test . table . Close ( )
test . packetIn ( errExpired , pingPacket , & ping { IP : "foo" , Port : 99 , Version : Version } )
test . packetIn ( errBadVersion , pingPacket , & ping { IP : "foo" , Port : 99 , Version : 99 , Expiration : futureExp } )
test . packetIn ( errUnsolicitedReply , pongPacket , & pong { ReplyTok : [ ] byte { } , Expiration : futureExp } )
test . packetIn ( errUnknownNode , findnodePacket , & findnode { Expiration : futureExp } )
test . packetIn ( errUnsolicitedReply , neighborsPacket , & neighbors { Expiration : futureExp } )
}
func TestUDP_pingTimeout ( t * testing . T ) {
t . Parallel ( )
test := newUDPTest ( t )
defer test . table . Close ( )
toaddr := & net . UDPAddr { IP : net . ParseIP ( "1.2.3.4" ) , Port : 2222 }
toid := NodeID { 1 , 2 , 3 , 4 }
if err := test . udp . ping ( toid , toaddr ) ; err != errTimeout {
t . Error ( "expected timeout error, got" , err )
}
}
func TestUDP_findnodeTimeout ( t * testing . T ) {
t . Parallel ( )
test := newUDPTest ( t )
defer test . table . Close ( )
n1 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
n2 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
defer n1 . Close ( )
defer n2 . Close ( )
toaddr := & net . UDPAddr { IP : net . ParseIP ( "1.2.3.4" ) , Port : 2222 }
toid := NodeID { 1 , 2 , 3 , 4 }
target := NodeID { 4 , 5 , 6 , 7 }
result , err := test . udp . findnode ( toid , toaddr , target )
if err != errTimeout {
t . Error ( "expected timeout error, got" , err )
}
if len ( result ) > 0 {
t . Error ( "expected empty result, got" , result )
}
}
// put a few nodes into n2. the exact distribution shouldn't
// matter much, altough we need to take care not to overflow
// any bucket.
target := randomID ( n1 . self . ID , 100 )
func TestUDP_findnode ( t * testing . T ) {
test := newUDPTest ( t )
defer test . table . Close ( )
// put a few nodes into the table. their exact
// distribution shouldn't matter much, altough we need to
// take care not to overflow any bucket.
target := testTarget
nodes := & nodesByDistance { target : target }
for i := 0 ; i < bucketSize ; i ++ {
n2 . add ( [ ] * Node { & Node {
nodes . push ( & Node {
IP : net . IP { 1 , 2 , 3 , byte ( i ) } ,
DiscPort : i + 2 ,
TCPPort : i + 2 ,
ID : randomID ( n2 . self . ID , i + 2 ) ,
} } )
ID : randomID ( test . table . self . ID , i + 2 ) ,
} , bucketSize )
}
n2 . add ( nodes . entries )
n2 . bumpOrAdd ( n1 . self . ID , & net . UDPAddr { IP : n1 . self . IP , Port : n1 . self . DiscPort } )
expected := n2 . closest ( target , bucketSize )
test . table . add ( nodes . entries )
// ensure there's a bond with the test node,
// findnode won't be accepted otherwise.
test . table . db . add ( PubkeyID ( & test . remotekey . PublicKey ) , test . remoteaddr , 99 )
err := runUDP ( 10 , func ( ) error {
result , _ := n1 . net . findnode ( n2 . self , target )
if len ( result ) != bucketSize {
return fmt . Errorf ( "wrong number of results: got %d, want %d" , len ( result ) , bucketSize )
// check that closest neighbors are returned.
test . packetIn ( nil , findnodePacket , & findnode { Target : testTarget , Expiration : futureExp } )
test . waitPacketOut ( func ( p * neighbors ) {
expected := test . table . closest ( testTarget , bucketSize )
if len ( p . Nodes ) != bucketSize {
t . Errorf ( "wrong number of results: got %d, want %d" , len ( p . Nodes ) , bucketSize )
}
for i := range result {
if result [ i ] . ID != expected . entries [ i ] . ID {
return fmt . Errorf ( "result mismatch at %d:\n got: %v\n want: %v" , i , result [ i ] , expected . entries [ i ] )
for i := range p . Nodes {
if p . Nodes [ i ] . ID != expected . entries [ i ] . ID {
t . Errorf ( "result mismatch at %d:\n got: %v\n want: %v" , i , p . Nodes [ i ] , expected . entries [ i ] )
}
}
return nil
} )
if err != nil {
t . Error ( err )
}
}
func TestUDP_replytimeout ( t * testing . T ) {
t . Parallel ( )
func TestUDP_findnodeMultiReply ( t * testing . T ) {
test := newUDPTest ( t )
defer test . table . Close ( )
// reserve a port so we don't talk to an existing service by accident
addr , _ := net . ResolveUDPAddr ( "udp" , "127.0.0.1:0" )
fd , err := net . ListenUDP ( "udp" , addr )
if err != nil {
t . Fatal ( err )
}
defer fd . Close ( )
// queue a pending findnode request
resultc , errc := make ( chan [ ] * Node ) , make ( chan error )
go func ( ) {
rid := PubkeyID ( & test . remotekey . PublicKey )
ns , err := test . udp . findnode ( rid , test . remoteaddr , testTarget )
if err != nil && len ( ns ) == 0 {
errc <- err
} else {
resultc <- ns
}
} ( )
n1 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
defer n1 . Close ( )
n2 := n1 . bumpOrAdd ( randomID ( n1 . self . ID , 10 ) , fd . LocalAddr ( ) . ( * net . UDPAddr ) )
// wait for the findnode to be sent.
// after it is sent, the transport is waiting for a reply
test . waitPacketOut ( func ( p * findnode ) {
if p . Target != testTarget {
t . Errorf ( "wrong target: got %v, want %v" , p . Target , testTarget )
}
} )
if err := n1 . net . ping ( n2 ) ; err != errTimeout {
t . Error ( "expected timeout error, got" , err )
// send the reply as two packets.
list := [ ] * Node {
MustParseNode ( "enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303" ) ,
MustParseNode ( "enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303" ) ,
MustParseNode ( "enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301" ) ,
MustParseNode ( "enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303" ) ,
}
test . packetIn ( nil , neighborsPacket , & neighbors { Expiration : futureExp , Nodes : list [ : 2 ] } )
test . packetIn ( nil , neighborsPacket , & neighbors { Expiration : futureExp , Nodes : list [ 2 : ] } )
if result , err := n1 . net . findnode ( n2 , n1 . self . ID ) ; err != errTimeout {
t . Error ( "expected timeout error, got" , err )
} else if len ( result ) > 0 {
t . Error ( "expected empty result, got" , result )
// check that the sent neighbors are all returned by findnode
select {
case result := <- resultc :
if ! reflect . DeepEqual ( result , list ) {
t . Errorf ( "neighbors mismatch:\n got: %v\n want: %v" , result , list )
}
case err := <- errc :
t . Errorf ( "findnode error: %v" , err )
case <- time . After ( 5 * time . Second ) :
t . Error ( "findnode did not return within 5 seconds" )
}
}
func TestUDP_findnodeMultiReply ( t * testing . T ) {
t . Parallel ( )
func TestUDP_successfulPing ( t * testing . T ) {
test := newUDPTest ( t )
defer test . table . Close ( )
n1 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
n2 , _ := ListenUDP ( newkey ( ) , "127.0.0.1:0" , nil )
udp2 := n2 . net . ( * udp )
defer n1 . Close ( )
defer n2 . Close ( )
err := runUDP ( 10 , func ( ) error {
nodes := make ( [ ] * Node , bucketSize )
for i := range nodes {
nodes [ i ] = & Node {
IP : net . IP { 1 , 2 , 3 , 4 } ,
DiscPort : i + 1 ,
TCPPort : i + 1 ,
ID : randomID ( n2 . self . ID , i + 1 ) ,
}
}
done := make ( chan struct { } )
go func ( ) {
test . packetIn ( nil , pingPacket , & ping { IP : "foo" , Port : 99 , Version : Version , Expiration : futureExp } )
close ( done )
} ( )
// ask N2 for neighbors. it will send an empty reply back.
// the request will wait for up to bucketSize replies.
resultc := make ( chan [ ] * Node )
errc := make ( chan error )
go func ( ) {
ns , err := n1 . net . findnode ( n2 . self , n1 . self . ID )
if err != nil {
errc <- err
} else {
resultc <- ns
}
} ( )
// send a few more neighbors packets to N1.
// it should collect those.
for end := 0 ; end < len ( nodes ) ; {
off := end
if end = end + 5 ; end > len ( nodes ) {
end = len ( nodes )
}
udp2 . send ( n1 . self , neighborsPacket , neighbors {
Nodes : nodes [ off : end ] ,
Expiration : uint64 ( time . Now ( ) . Add ( 10 * time . Second ) . Unix ( ) ) ,
} )
// the ping is replied to.
test . waitPacketOut ( func ( p * pong ) {
pinghash := test . sent [ 0 ] [ : macSize ]
if ! bytes . Equal ( p . ReplyTok , pinghash ) {
t . Errorf ( "got ReplyTok %x, want %x" , p . ReplyTok , pinghash )
}
} )
// check that they are all returned. we cannot just check for
// equality because they might not be returned in the order they
// were sent.
var result [ ] * Node
select {
case result = <- resultc :
case err := <- errc :
return err
}
if hasDuplicates ( result ) {
return fmt . Errorf ( "result slice contains duplicates" )
}
if len ( result ) != len ( nodes ) {
return fmt . Errorf ( "wrong number of nodes returned: got %d, want %d" , len ( result ) , len ( nodes ) )
}
matched := make ( map [ NodeID ] bool )
for _ , n := range result {
for _ , expn := range nodes {
if n . ID == expn . ID { // && bytes.Equal(n.Addr.IP, expn.Addr.IP) && n.Addr.Port == expn.Addr.Port {
matched [ n . ID ] = true
}
// remote is unknown, the table pings back.
test . waitPacketOut ( func ( p * ping ) error { return nil } )
test . packetIn ( nil , pongPacket , & pong { Expiration : futureExp } )
// ping should return shortly after getting the pong packet.
<- done
// check that the node was added.
rid := PubkeyID ( & test . remotekey . PublicKey )
rnode := find ( test . table , rid )
if rnode == nil {
t . Fatalf ( "node %v not found in table" , rid )
}
if ! bytes . Equal ( rnode . IP , test . remoteaddr . IP ) {
t . Errorf ( "node has wrong IP: got %v, want: %v" , rnode . IP , test . remoteaddr . IP )
}
if rnode . DiscPort != test . remoteaddr . Port {
t . Errorf ( "node has wrong Port: got %v, want: %v" , rnode . DiscPort , test . remoteaddr . Port )
}
if rnode . TCPPort != 99 {
t . Errorf ( "node has wrong Port: got %v, want: %v" , rnode . TCPPort , 99 )
}
}
func find ( tab * Table , id NodeID ) * Node {
for _ , b := range tab . buckets {
for _ , e := range b . entries {
if e . ID == id {
return e
}
}
if len ( matched ) != len ( nodes ) {
return fmt . Errorf ( "wrong number of matching nodes: got %d, want %d" , len ( matched ) , len ( nodes ) )
}
return nil
} )
if err != nil {
t . Error ( err )
}
return nil
}
// runUDP runs a test n times and returns an error if the test failed
// in all n runs. This is necessary because UDP is unreliable even for
// connections on the local machine, causing test failures.
func runUDP ( n int , test func ( ) error ) error {
errcount := 0
errors := ""
for i := 0 ; i < n ; i ++ {
if err := test ( ) ; err != nil {
errors += fmt . Sprintf ( "\n#%d: %v" , i , err )
errcount ++
}
// dgramPipe is a fake UDP socket. It queues all sent datagrams.
type dgramPipe struct {
mu * sync . Mutex
cond * sync . Cond
closing chan struct { }
closed bool
queue [ ] [ ] byte
}
func newpipe ( ) * dgramPipe {
mu := new ( sync . Mutex )
return & dgramPipe {
closing : make ( chan struct { } ) ,
cond : & sync . Cond { L : mu } ,
mu : mu ,
}
if errcount == n {
return fmt . Errorf ( "failed on all %d iterations:%s" , n , errors )
}
// WriteToUDP queues a datagram.
func ( c * dgramPipe ) WriteToUDP ( b [ ] byte , to * net . UDPAddr ) ( n int , err error ) {
msg := make ( [ ] byte , len ( b ) )
copy ( msg , b )
c . mu . Lock ( )
defer c . mu . Unlock ( )
if c . closed {
return 0 , errors . New ( "closed" )
}
c . queue = append ( c . queue , msg )
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 ) {
<- c . closing
return 0 , nil , io . EOF
}
func ( c * dgramPipe ) Close ( ) error {
c . mu . Lock ( )
defer c . mu . Unlock ( )
if ! c . closed {
close ( c . closing )
c . closed = true
}
return nil
}
func ( c * dgramPipe ) LocalAddr ( ) net . Addr {
return & net . UDPAddr { }
}
func ( c * dgramPipe ) waitPacketOut ( ) [ ] byte {
c . mu . Lock ( )
defer c . mu . Unlock ( )
for len ( c . queue ) == 0 {
c . cond . Wait ( )
}
p := c . queue [ 0 ]
copy ( c . queue , c . queue [ 1 : ] )
c . queue = c . queue [ : len ( c . queue ) - 1 ]
return p
}