github.com/jeffallen/go-ethereum@v1.1.4-0.20150910155051-571d3236c49c/p2p/discover/udp_test.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "io" 26 logpkg "log" 27 "math/rand" 28 "net" 29 "os" 30 "path/filepath" 31 "reflect" 32 "runtime" 33 "sync" 34 "testing" 35 "time" 36 37 "github.com/ethereum/go-ethereum/crypto" 38 "github.com/ethereum/go-ethereum/logger" 39 ) 40 41 func init() { 42 logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, logpkg.LstdFlags, logger.ErrorLevel)) 43 } 44 45 // shared test variables 46 var ( 47 futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) 48 testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} 49 testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} 50 testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} 51 testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} 52 ) 53 54 type udpTest struct { 55 t *testing.T 56 pipe *dgramPipe 57 table *Table 58 udp *udp 59 sent [][]byte 60 localkey, remotekey *ecdsa.PrivateKey 61 remoteaddr *net.UDPAddr 62 } 63 64 func newUDPTest(t *testing.T) *udpTest { 65 test := &udpTest{ 66 t: t, 67 pipe: newpipe(), 68 localkey: newkey(), 69 remotekey: newkey(), 70 remoteaddr: &net.UDPAddr{IP: net.IP{1, 2, 3, 4}, Port: 30303}, 71 } 72 test.table, test.udp = newUDP(test.localkey, test.pipe, nil, "") 73 return test 74 } 75 76 // handles a packet as if it had been sent to the transport. 77 func (test *udpTest) packetIn(wantError error, ptype byte, data packet) error { 78 enc, err := encodePacket(test.remotekey, ptype, data) 79 if err != nil { 80 return test.errorf("packet (%d) encode error: %v", err) 81 } 82 test.sent = append(test.sent, enc) 83 if err = test.udp.handlePacket(test.remoteaddr, enc); err != wantError { 84 return test.errorf("error mismatch: got %q, want %q", err, wantError) 85 } 86 return nil 87 } 88 89 // waits for a packet to be sent by the transport. 90 // validate should have type func(*udpTest, X) error, where X is a packet type. 91 func (test *udpTest) waitPacketOut(validate interface{}) error { 92 dgram := test.pipe.waitPacketOut() 93 p, _, _, err := decodePacket(dgram) 94 if err != nil { 95 return test.errorf("sent packet decode error: %v", err) 96 } 97 fn := reflect.ValueOf(validate) 98 exptype := fn.Type().In(0) 99 if reflect.TypeOf(p) != exptype { 100 return test.errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) 101 } 102 fn.Call([]reflect.Value{reflect.ValueOf(p)}) 103 return nil 104 } 105 106 func (test *udpTest) errorf(format string, args ...interface{}) error { 107 _, file, line, ok := runtime.Caller(2) // errorf + waitPacketOut 108 if ok { 109 file = filepath.Base(file) 110 } else { 111 file = "???" 112 line = 1 113 } 114 err := fmt.Errorf(format, args...) 115 fmt.Printf("\t%s:%d: %v\n", file, line, err) 116 test.t.Fail() 117 return err 118 } 119 120 func TestUDP_packetErrors(t *testing.T) { 121 test := newUDPTest(t) 122 defer test.table.Close() 123 124 test.packetIn(errExpired, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version}) 125 test.packetIn(errBadVersion, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: 99, Expiration: futureExp}) 126 test.packetIn(errUnsolicitedReply, pongPacket, &pong{ReplyTok: []byte{}, Expiration: futureExp}) 127 test.packetIn(errUnknownNode, findnodePacket, &findnode{Expiration: futureExp}) 128 test.packetIn(errUnsolicitedReply, neighborsPacket, &neighbors{Expiration: futureExp}) 129 } 130 131 func TestUDP_pingTimeout(t *testing.T) { 132 t.Parallel() 133 test := newUDPTest(t) 134 defer test.table.Close() 135 136 toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} 137 toid := NodeID{1, 2, 3, 4} 138 if err := test.udp.ping(toid, toaddr); err != errTimeout { 139 t.Error("expected timeout error, got", err) 140 } 141 } 142 143 func TestUDP_responseTimeouts(t *testing.T) { 144 t.Parallel() 145 test := newUDPTest(t) 146 defer test.table.Close() 147 148 rand.Seed(time.Now().UnixNano()) 149 randomDuration := func(max time.Duration) time.Duration { 150 return time.Duration(rand.Int63n(int64(max))) 151 } 152 153 var ( 154 nReqs = 200 155 nTimeouts = 0 // number of requests with ptype > 128 156 nilErr = make(chan error, nReqs) // for requests that get a reply 157 timeoutErr = make(chan error, nReqs) // for requests that time out 158 ) 159 for i := 0; i < nReqs; i++ { 160 // Create a matcher for a random request in udp.loop. Requests 161 // with ptype <= 128 will not get a reply and should time out. 162 // For all other requests, a reply is scheduled to arrive 163 // within the timeout window. 164 p := &pending{ 165 ptype: byte(rand.Intn(255)), 166 callback: func(interface{}) bool { return true }, 167 } 168 binary.BigEndian.PutUint64(p.from[:], uint64(i)) 169 if p.ptype <= 128 { 170 p.errc = timeoutErr 171 nTimeouts++ 172 } else { 173 p.errc = nilErr 174 time.AfterFunc(randomDuration(60*time.Millisecond), func() { 175 if !test.udp.handleReply(p.from, p.ptype, nil) { 176 t.Logf("not matched: %v", p) 177 } 178 }) 179 } 180 test.udp.addpending <- p 181 time.Sleep(randomDuration(30 * time.Millisecond)) 182 } 183 184 // Check that all timeouts were delivered and that the rest got nil errors. 185 // The replies must be delivered. 186 var ( 187 recvDeadline = time.After(20 * time.Second) 188 nTimeoutsRecv, nNil = 0, 0 189 ) 190 for i := 0; i < nReqs; i++ { 191 select { 192 case err := <-timeoutErr: 193 if err != errTimeout { 194 t.Fatalf("got non-timeout error on timeoutErr %d: %v", i, err) 195 } 196 nTimeoutsRecv++ 197 case err := <-nilErr: 198 if err != nil { 199 t.Fatalf("got non-nil error on nilErr %d: %v", i, err) 200 } 201 nNil++ 202 case <-recvDeadline: 203 t.Fatalf("exceeded recv deadline") 204 } 205 } 206 if nTimeoutsRecv != nTimeouts { 207 t.Errorf("wrong number of timeout errors received: got %d, want %d", nTimeoutsRecv, nTimeouts) 208 } 209 if nNil != nReqs-nTimeouts { 210 t.Errorf("wrong number of successful replies: got %d, want %d", nNil, nReqs-nTimeouts) 211 } 212 } 213 214 func TestUDP_findnodeTimeout(t *testing.T) { 215 t.Parallel() 216 test := newUDPTest(t) 217 defer test.table.Close() 218 219 toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} 220 toid := NodeID{1, 2, 3, 4} 221 target := NodeID{4, 5, 6, 7} 222 result, err := test.udp.findnode(toid, toaddr, target) 223 if err != errTimeout { 224 t.Error("expected timeout error, got", err) 225 } 226 if len(result) > 0 { 227 t.Error("expected empty result, got", result) 228 } 229 } 230 231 func TestUDP_findnode(t *testing.T) { 232 test := newUDPTest(t) 233 defer test.table.Close() 234 235 // put a few nodes into the table. their exact 236 // distribution shouldn't matter much, altough we need to 237 // take care not to overflow any bucket. 238 targetHash := crypto.Sha3Hash(testTarget[:]) 239 nodes := &nodesByDistance{target: targetHash} 240 for i := 0; i < bucketSize; i++ { 241 nodes.push(nodeAtDistance(test.table.self.sha, i+2), bucketSize) 242 } 243 test.table.stuff(nodes.entries) 244 245 // ensure there's a bond with the test node, 246 // findnode won't be accepted otherwise. 247 test.table.db.updateNode(newNode( 248 PubkeyID(&test.remotekey.PublicKey), 249 test.remoteaddr.IP, 250 uint16(test.remoteaddr.Port), 251 99, 252 )) 253 // check that closest neighbors are returned. 254 test.packetIn(nil, findnodePacket, &findnode{Target: testTarget, Expiration: futureExp}) 255 expected := test.table.closest(targetHash, bucketSize) 256 257 waitNeighbors := func(want []*Node) { 258 test.waitPacketOut(func(p *neighbors) { 259 if len(p.Nodes) != len(want) { 260 t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) 261 } 262 for i := range p.Nodes { 263 if p.Nodes[i].ID != want[i].ID { 264 t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, p.Nodes[i], expected.entries[i]) 265 } 266 } 267 }) 268 } 269 waitNeighbors(expected.entries[:maxNeighbors]) 270 waitNeighbors(expected.entries[maxNeighbors:]) 271 } 272 273 func TestUDP_findnodeMultiReply(t *testing.T) { 274 test := newUDPTest(t) 275 defer test.table.Close() 276 277 // queue a pending findnode request 278 resultc, errc := make(chan []*Node), make(chan error) 279 go func() { 280 rid := PubkeyID(&test.remotekey.PublicKey) 281 ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) 282 if err != nil && len(ns) == 0 { 283 errc <- err 284 } else { 285 resultc <- ns 286 } 287 }() 288 289 // wait for the findnode to be sent. 290 // after it is sent, the transport is waiting for a reply 291 test.waitPacketOut(func(p *findnode) { 292 if p.Target != testTarget { 293 t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) 294 } 295 }) 296 297 // send the reply as two packets. 298 list := []*Node{ 299 MustParseNode("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"), 300 MustParseNode("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"), 301 MustParseNode("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"), 302 MustParseNode("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303"), 303 } 304 rpclist := make([]rpcNode, len(list)) 305 for i := range list { 306 rpclist[i] = nodeToRPC(list[i]) 307 } 308 test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[:2]}) 309 test.packetIn(nil, neighborsPacket, &neighbors{Expiration: futureExp, Nodes: rpclist[2:]}) 310 311 // check that the sent neighbors are all returned by findnode 312 select { 313 case result := <-resultc: 314 if !reflect.DeepEqual(result, list) { 315 t.Errorf("neighbors mismatch:\n got: %v\n want: %v", result, list) 316 } 317 case err := <-errc: 318 t.Errorf("findnode error: %v", err) 319 case <-time.After(5 * time.Second): 320 t.Error("findnode did not return within 5 seconds") 321 } 322 } 323 324 func TestUDP_successfulPing(t *testing.T) { 325 test := newUDPTest(t) 326 added := make(chan *Node, 1) 327 test.table.nodeAddedHook = func(n *Node) { added <- n } 328 defer test.table.Close() 329 330 // The remote side sends a ping packet to initiate the exchange. 331 go test.packetIn(nil, pingPacket, &ping{From: testRemote, To: testLocalAnnounced, Version: Version, Expiration: futureExp}) 332 333 // the ping is replied to. 334 test.waitPacketOut(func(p *pong) { 335 pinghash := test.sent[0][:macSize] 336 if !bytes.Equal(p.ReplyTok, pinghash) { 337 t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) 338 } 339 wantTo := rpcEndpoint{ 340 // The mirrored UDP address is the UDP packet sender 341 IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), 342 // The mirrored TCP port is the one from the ping packet 343 TCP: testRemote.TCP, 344 } 345 if !reflect.DeepEqual(p.To, wantTo) { 346 t.Errorf("got pong.To %v, want %v", p.To, wantTo) 347 } 348 }) 349 350 // remote is unknown, the table pings back. 351 test.waitPacketOut(func(p *ping) error { 352 if !reflect.DeepEqual(p.From, test.udp.ourEndpoint) { 353 t.Errorf("got ping.From %v, want %v", p.From, test.udp.ourEndpoint) 354 } 355 wantTo := rpcEndpoint{ 356 // The mirrored UDP address is the UDP packet sender. 357 IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), 358 TCP: 0, 359 } 360 if !reflect.DeepEqual(p.To, wantTo) { 361 t.Errorf("got ping.To %v, want %v", p.To, wantTo) 362 } 363 return nil 364 }) 365 test.packetIn(nil, pongPacket, &pong{Expiration: futureExp}) 366 367 // the node should be added to the table shortly after getting the 368 // pong packet. 369 select { 370 case n := <-added: 371 rid := PubkeyID(&test.remotekey.PublicKey) 372 if n.ID != rid { 373 t.Errorf("node has wrong ID: got %v, want %v", n.ID, rid) 374 } 375 if !bytes.Equal(n.IP, test.remoteaddr.IP) { 376 t.Errorf("node has wrong IP: got %v, want: %v", n.IP, test.remoteaddr.IP) 377 } 378 if int(n.UDP) != test.remoteaddr.Port { 379 t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP, test.remoteaddr.Port) 380 } 381 if n.TCP != testRemote.TCP { 382 t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP, testRemote.TCP) 383 } 384 case <-time.After(2 * time.Second): 385 t.Errorf("node was not added within 2 seconds") 386 } 387 } 388 389 // dgramPipe is a fake UDP socket. It queues all sent datagrams. 390 type dgramPipe struct { 391 mu *sync.Mutex 392 cond *sync.Cond 393 closing chan struct{} 394 closed bool 395 queue [][]byte 396 } 397 398 func newpipe() *dgramPipe { 399 mu := new(sync.Mutex) 400 return &dgramPipe{ 401 closing: make(chan struct{}), 402 cond: &sync.Cond{L: mu}, 403 mu: mu, 404 } 405 } 406 407 // WriteToUDP queues a datagram. 408 func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) { 409 msg := make([]byte, len(b)) 410 copy(msg, b) 411 c.mu.Lock() 412 defer c.mu.Unlock() 413 if c.closed { 414 return 0, errors.New("closed") 415 } 416 c.queue = append(c.queue, msg) 417 c.cond.Signal() 418 return len(b), nil 419 } 420 421 // ReadFromUDP just hangs until the pipe is closed. 422 func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { 423 <-c.closing 424 return 0, nil, io.EOF 425 } 426 427 func (c *dgramPipe) Close() error { 428 c.mu.Lock() 429 defer c.mu.Unlock() 430 if !c.closed { 431 close(c.closing) 432 c.closed = true 433 } 434 return nil 435 } 436 437 func (c *dgramPipe) LocalAddr() net.Addr { 438 return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} 439 } 440 441 func (c *dgramPipe) waitPacketOut() []byte { 442 c.mu.Lock() 443 defer c.mu.Unlock() 444 for len(c.queue) == 0 { 445 c.cond.Wait() 446 } 447 p := c.queue[0] 448 copy(c.queue, c.queue[1:]) 449 c.queue = c.queue[:len(c.queue)-1] 450 return p 451 }