github.com/core-coin/go-core/v2@v2.1.9/p2p/discover/v4_udp_test.go (about) 1 // Copyright 2015 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 "bytes" 21 crand "crypto/rand" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "io" 26 "math/rand" 27 "net" 28 "reflect" 29 "sync" 30 "testing" 31 "time" 32 33 "github.com/core-coin/go-core/v2/crypto" 34 "github.com/core-coin/go-core/v2/internal/testlog" 35 "github.com/core-coin/go-core/v2/log" 36 "github.com/core-coin/go-core/v2/p2p/discover/v4wire" 37 "github.com/core-coin/go-core/v2/p2p/enode" 38 "github.com/core-coin/go-core/v2/p2p/enr" 39 ) 40 41 // shared test variables 42 var ( 43 futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) 44 testTarget = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} 45 testRemote = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} 46 testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} 47 testLocal = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} 48 ) 49 50 type udpTest struct { 51 t *testing.T 52 pipe *dgramPipe 53 table *Table 54 db *enode.DB 55 udp *UDPv4 56 sent [][]byte 57 localkey, remotekey *crypto.PrivateKey 58 remoteaddr *net.UDPAddr 59 } 60 61 func newUDPTest(t *testing.T) *udpTest { 62 test := &udpTest{ 63 t: t, 64 pipe: newpipe(), 65 localkey: newkey(), 66 remotekey: newkey(), 67 remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30300}, 68 } 69 70 test.db, _ = enode.OpenDB("") 71 ln := enode.NewLocalNode(test.db, test.localkey) 72 test.udp, _ = ListenV4(test.pipe, ln, Config{ 73 PrivateKey: test.localkey, 74 Log: testlog.Logger(t, log.LvlTrace), 75 }) 76 test.table = test.udp.tab 77 // Wait for initial refresh so the table doesn't send unexpected findnode. 78 <-test.table.initDone 79 return test 80 } 81 82 func (test *udpTest) close() { 83 test.udp.Close() 84 test.db.Close() 85 } 86 87 // handles a packet as if it had been sent to the transport. 88 func (test *udpTest) packetIn(wantError error, data v4wire.Packet) { 89 test.t.Helper() 90 91 test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data) 92 } 93 94 // handles a packet as if it had been sent to the transport by the key/endpoint. 95 func (test *udpTest) packetInFrom(wantError error, key *crypto.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) { 96 test.t.Helper() 97 98 enc, _, err := v4wire.Encode(key, data) 99 if err != nil { 100 test.t.Errorf("%s encode error: %v", data.Name(), err) 101 } 102 test.sent = append(test.sent, enc) 103 if err = test.udp.handlePacket(addr, enc); err != wantError { 104 test.t.Errorf("error mismatch: got %q, want %q", err, wantError) 105 } 106 } 107 108 // waits for a packet to be sent by the transport. 109 // validate should have type func(X, *net.UDPAddr, []byte), where X is a packet type. 110 func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { 111 test.t.Helper() 112 113 dgram, err := test.pipe.receive() 114 if err == errClosed { 115 return true 116 } else if err != nil { 117 test.t.Error("packet receive error:", err) 118 return false 119 } 120 p, _, hash, err := v4wire.Decode(dgram.data) 121 if err != nil { 122 test.t.Errorf("sent packet decode error: %v", err) 123 return false 124 } 125 fn := reflect.ValueOf(validate) 126 exptype := fn.Type().In(0) 127 if !reflect.TypeOf(p).AssignableTo(exptype) { 128 test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) 129 return false 130 } 131 fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(hash)}) 132 return false 133 } 134 135 func TestUDPv4_packetErrors(t *testing.T) { 136 test := newUDPTest(t) 137 defer test.close() 138 139 test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4}) 140 test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp}) 141 test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp}) 142 test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp}) 143 } 144 145 func TestUDPv4_pingTimeout(t *testing.T) { 146 t.Parallel() 147 test := newUDPTest(t) 148 defer test.close() 149 150 key := newkey() 151 toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} 152 node := enode.NewV4(key.PublicKey(), toaddr.IP, 0, toaddr.Port) 153 if _, err := test.udp.ping(node); err != errTimeout { 154 t.Error("expected timeout error, got", err) 155 } 156 } 157 158 type testPacket byte 159 160 func (req testPacket) Kind() byte { return byte(req) } 161 func (req testPacket) Name() string { return "" } 162 163 func TestUDPv4_responseTimeouts(t *testing.T) { 164 t.Parallel() 165 test := newUDPTest(t) 166 defer test.close() 167 168 rand.Seed(time.Now().UnixNano()) 169 randomDuration := func(max time.Duration) time.Duration { 170 return time.Duration(rand.Int63n(int64(max))) 171 } 172 173 var ( 174 nReqs = 200 175 nTimeouts = 0 // number of requests with ptype > 128 176 nilErr = make(chan error, nReqs) // for requests that get a reply 177 timeoutErr = make(chan error, nReqs) // for requests that time out 178 ) 179 for i := 0; i < nReqs; i++ { 180 // Create a matcher for a random request in udp.loop. Requests 181 // with ptype <= 128 will not get a reply and should time out. 182 // For all other requests, a reply is scheduled to arrive 183 // within the timeout window. 184 p := &replyMatcher{ 185 ptype: byte(rand.Intn(255)), 186 callback: func(v4wire.Packet) (bool, bool) { return true, true }, 187 } 188 binary.BigEndian.PutUint64(p.from[:], uint64(i)) 189 if p.ptype <= 128 { 190 p.errc = timeoutErr 191 test.udp.addReplyMatcher <- p 192 nTimeouts++ 193 } else { 194 p.errc = nilErr 195 test.udp.addReplyMatcher <- p 196 time.AfterFunc(randomDuration(100*time.Millisecond), func() { 197 if !test.udp.handleReply(p.from, p.ip, testPacket(p.ptype)) { 198 t.Logf("not matched: %v", p) 199 } 200 }) 201 } 202 time.Sleep(randomDuration(50 * time.Millisecond)) 203 } 204 205 // Check that all timeouts were delivered and that the rest got nil errors. 206 // The replies must be delivered. 207 var ( 208 recvDeadline = time.After(20 * time.Second) 209 nTimeoutsRecv, nNil = 0, 0 210 ) 211 for i := 0; i < nReqs; i++ { 212 select { 213 case err := <-timeoutErr: 214 if err != errTimeout { 215 t.Fatalf("got non-timeout error on timeoutErr %d: %v", i, err) 216 } 217 nTimeoutsRecv++ 218 case err := <-nilErr: 219 if err != nil { 220 t.Fatalf("got non-nil error on nilErr %d: %v", i, err) 221 } 222 nNil++ 223 case <-recvDeadline: 224 t.Fatalf("exceeded recv deadline") 225 } 226 } 227 if nTimeoutsRecv != nTimeouts { 228 t.Errorf("wrong number of timeout errors received: got %d, want %d", nTimeoutsRecv, nTimeouts) 229 } 230 if nNil != nReqs-nTimeouts { 231 t.Errorf("wrong number of successful replies: got %d, want %d", nNil, nReqs-nTimeouts) 232 } 233 } 234 235 func TestUDPv4_findnodeTimeout(t *testing.T) { 236 t.Parallel() 237 test := newUDPTest(t) 238 defer test.close() 239 240 toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} 241 toid := enode.ID{1, 2, 3, 4} 242 target := v4wire.Pubkey{4, 5, 6, 7} 243 result, err := test.udp.findnode(toid, toaddr, target) 244 if err != errTimeout { 245 t.Error("expected timeout error, got", err) 246 } 247 if len(result) > 0 { 248 t.Error("expected empty result, got", result) 249 } 250 } 251 252 func TestUDPv4_findnode(t *testing.T) { 253 test := newUDPTest(t) 254 defer test.close() 255 256 // put a few nodes into the table. their exact 257 // distribution shouldn't matter much, although we need to 258 // take care not to overflow any bucket. 259 nodes := &nodesByDistance{target: testTarget.ID()} 260 live := make(map[enode.ID]bool) 261 numCandidates := 2 * bucketSize 262 for i := 0; i < numCandidates; i++ { 263 key := newkey() 264 ip := net.IP{10, 13, 0, byte(i)} 265 n := wrapNode(enode.NewV4(key.PublicKey(), ip, 0, 2000)) 266 // Ensure half of table content isn't verified live yet. 267 if i > numCandidates/2 { 268 n.livenessChecks = 1 269 live[n.ID()] = true 270 } 271 nodes.push(n, numCandidates) 272 } 273 fillTable(test.table, nodes.entries) 274 275 // ensure there's a bond with the test node, 276 // findnode won't be accepted otherwise. 277 remoteID := v4wire.EncodePubkey(test.remotekey.PublicKey()).ID() 278 test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now()) 279 280 // check that closest neighbors are returned. 281 expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true) 282 test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp}) 283 waitNeighbors := func(want []*node) { 284 test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) { 285 if len(p.Nodes) != len(want) { 286 t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize) 287 } 288 for i, n := range p.Nodes { 289 if n.ID.ID() != want[i].ID() { 290 t.Errorf("result mismatch at %d:\n got: %v\n want: %v", i, n, expected.entries[i]) 291 } 292 if !live[n.ID.ID()] { 293 t.Errorf("result includes dead node %v", n.ID.ID()) 294 } 295 } 296 }) 297 } 298 // Receive replies. 299 want := expected.entries 300 if len(want) > v4wire.MaxNeighbors { 301 waitNeighbors(want[:v4wire.MaxNeighbors]) 302 want = want[v4wire.MaxNeighbors:] 303 } 304 waitNeighbors(want) 305 } 306 307 func TestUDPv4_findnodeMultiReply(t *testing.T) { 308 test := newUDPTest(t) 309 defer test.close() 310 311 rid := enode.PubkeyToIDV4(test.remotekey.PublicKey()) 312 test.table.db.UpdateLastPingReceived(rid, test.remoteaddr.IP, time.Now()) 313 314 // queue a pending findnode request 315 resultc, errc := make(chan []*node), make(chan error) 316 go func() { 317 rid := encodePubkey(test.remotekey.PublicKey()).id() 318 ns, err := test.udp.findnode(rid, test.remoteaddr, testTarget) 319 if err != nil && len(ns) == 0 { 320 errc <- err 321 } else { 322 resultc <- ns 323 } 324 }() 325 326 // wait for the findnode to be sent. 327 // after it is sent, the transport is waiting for a reply 328 test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) { 329 if p.Target != testTarget { 330 t.Errorf("wrong target: got %v, want %v", p.Target, testTarget) 331 } 332 }) 333 334 // send the reply as two packets. 335 list := []*node{ 336 wrapNode(enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd67@10.0.1.16:30303?discport=30304")), 337 wrapNode(enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e5@10.0.1.16:30303")), 338 wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e@10.0.1.36:30301?discport=17")), 339 wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446@10.0.1.16:30303")), 340 } 341 rpclist := make([]v4wire.Node, len(list)) 342 for i := range list { 343 rpclist[i] = nodeToRPC(list[i]) 344 } 345 test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2]}) 346 test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:]}) 347 348 // check that the sent neighbors are all returned by findnode 349 select { 350 case result := <-resultc: 351 want := append(list[:2], list[3:]...) 352 if !reflect.DeepEqual(result, want) { 353 t.Errorf("neighbors mismatch:\n got: %v\n want: %v", result, want) 354 } 355 case err := <-errc: 356 t.Errorf("findnode error: %v", err) 357 case <-time.After(5 * time.Second): 358 t.Error("findnode did not return within 5 seconds") 359 } 360 } 361 362 // This test checks that reply matching of pong verifies the ping hash. 363 func TestUDPv4_pingMatch(t *testing.T) { 364 test := newUDPTest(t) 365 defer test.close() 366 367 randToken := make([]byte, 32) 368 crand.Read(randToken) 369 370 test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) 371 test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) 372 test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {}) 373 test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp}) 374 } 375 376 // This test checks that reply matching of pong verifies the sender IP address. 377 func TestUDPv4_pingMatchIP(t *testing.T) { 378 test := newUDPTest(t) 379 defer test.close() 380 381 test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) 382 test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {}) 383 384 test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { 385 wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000} 386 test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{ 387 ReplyTok: hash, 388 To: testLocalAnnounced, 389 Expiration: futureExp, 390 }) 391 }) 392 } 393 394 func TestUDPv4_successfulPing(t *testing.T) { 395 test := newUDPTest(t) 396 added := make(chan *node, 1) 397 test.table.nodeAddedHook = func(n *node) { added <- n } 398 defer test.close() 399 400 // The remote side sends a ping packet to initiate the exchange. 401 go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp}) 402 403 // The ping is replied to. 404 test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) { 405 pinghash := test.sent[0][:32] 406 if !bytes.Equal(p.ReplyTok, pinghash) { 407 t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash) 408 } 409 wantTo := v4wire.Endpoint{ 410 // The mirrored UDP address is the UDP packet sender 411 IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port), 412 // The mirrored TCP port is the one from the ping packet 413 TCP: testRemote.TCP, 414 } 415 if !reflect.DeepEqual(p.To, wantTo) { 416 t.Errorf("got pong.To %v, want %v", p.To, wantTo) 417 } 418 }) 419 420 // Remote is unknown, the table pings back. 421 test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) { 422 if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) { 423 t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) 424 } 425 wantTo := v4wire.Endpoint{ 426 // The mirrored UDP address is the UDP packet sender. 427 IP: test.remoteaddr.IP, 428 UDP: uint16(test.remoteaddr.Port), 429 TCP: 0, 430 } 431 if !reflect.DeepEqual(p.To, wantTo) { 432 t.Errorf("got ping.To %v, want %v", p.To, wantTo) 433 } 434 test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp}) 435 }) 436 437 // The node should be added to the table shortly after getting the 438 // pong packet. 439 select { 440 case n := <-added: 441 rid := encodePubkey(test.remotekey.PublicKey()).id() 442 if n.ID() != rid { 443 t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid) 444 } 445 if !n.IP().Equal(test.remoteaddr.IP) { 446 t.Errorf("node has wrong IP: got %v, want: %v", n.IP(), test.remoteaddr.IP) 447 } 448 if n.UDP() != test.remoteaddr.Port { 449 t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port) 450 } 451 if n.TCP() != int(testRemote.TCP) { 452 t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP) 453 } 454 case <-time.After(2 * time.Second): 455 t.Errorf("node was not added within 2 seconds") 456 } 457 } 458 459 // This test checks that CIP-868 requests work. 460 func TestUDPv4_CIP868(t *testing.T) { 461 test := newUDPTest(t) 462 defer test.close() 463 464 test.udp.localNode.Set(enr.WithEntry("foo", "bar")) 465 wantNode := test.udp.localNode.Node() 466 467 // ENR requests aren't allowed before endpoint proof. 468 test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp}) 469 470 // Perform endpoint proof and check for sequence number in packet tail. 471 test.packetIn(nil, &v4wire.Ping{Expiration: futureExp}) 472 test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) { 473 if p.ENRSeq() != wantNode.Seq() { 474 t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq(), wantNode.Seq()) 475 } 476 }) 477 test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) { 478 if p.ENRSeq() != wantNode.Seq() { 479 t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq(), wantNode.Seq()) 480 } 481 test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) 482 }) 483 484 // Request should work now. 485 test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp}) 486 test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) { 487 n, err := enode.New(enode.ValidSchemes, &p.Record) 488 if err != nil { 489 t.Fatalf("invalid record: %v", err) 490 } 491 if !reflect.DeepEqual(n, wantNode) { 492 t.Fatalf("wrong node in enrResponse: %v", n) 493 } 494 }) 495 } 496 497 // This test verifies that a small network of nodes can boot up into a healthy state. 498 func TestUDPv4_smallNetConvergence(t *testing.T) { 499 t.Parallel() 500 501 // Start the network. 502 nodes := make([]*UDPv4, 4) 503 for i := range nodes { 504 var cfg Config 505 if i > 0 { 506 bn := nodes[0].Self() 507 cfg.Bootnodes = []*enode.Node{bn} 508 } 509 nodes[i] = startLocalhostV4(t, cfg) 510 defer nodes[i].Close() 511 } 512 513 // Run through the iterator on all nodes until 514 // they have all found each other. 515 status := make(chan error, len(nodes)) 516 for i := range nodes { 517 node := nodes[i] 518 go func() { 519 found := make(map[enode.ID]bool, len(nodes)) 520 it := node.RandomNodes() 521 for it.Next() { 522 found[it.Node().ID()] = true 523 if len(found) == len(nodes) { 524 status <- nil 525 return 526 } 527 } 528 status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString()) 529 }() 530 } 531 532 // Wait for all status reports. 533 timeout := time.NewTimer(30 * time.Second) 534 defer timeout.Stop() 535 for received := 0; received < len(nodes); { 536 select { 537 case <-timeout.C: 538 for _, node := range nodes { 539 node.Close() 540 } 541 case err := <-status: 542 received++ 543 if err != nil { 544 t.Error("ERROR:", err) 545 return 546 } 547 } 548 } 549 } 550 551 func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { 552 t.Helper() 553 554 cfg.PrivateKey = newkey() 555 db, _ := enode.OpenDB("") 556 ln := enode.NewLocalNode(db, cfg.PrivateKey) 557 558 // Prefix logs with node ID. 559 lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) 560 lfmt := log.TerminalFormat(false) 561 cfg.Log = testlog.Logger(t, log.LvlTrace) 562 cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { 563 t.Logf("%s %s", lprefix, lfmt.Format(r)) 564 return nil 565 })) 566 567 // Listen. 568 socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) 569 if err != nil { 570 t.Fatal(err) 571 } 572 realaddr := socket.LocalAddr().(*net.UDPAddr) 573 ln.SetStaticIP(realaddr.IP) 574 ln.SetFallbackUDP(realaddr.Port) 575 udp, err := ListenV4(socket, ln, cfg) 576 if err != nil { 577 t.Fatal(err) 578 } 579 return udp 580 } 581 582 // dgramPipe is a fake UDP socket. It queues all sent datagrams. 583 type dgramPipe struct { 584 mu *sync.Mutex 585 cond *sync.Cond 586 closing chan struct{} 587 closed bool 588 queue []dgram 589 } 590 591 type dgram struct { 592 to net.UDPAddr 593 data []byte 594 } 595 596 func newpipe() *dgramPipe { 597 mu := new(sync.Mutex) 598 return &dgramPipe{ 599 closing: make(chan struct{}), 600 cond: &sync.Cond{L: mu}, 601 mu: mu, 602 } 603 } 604 605 // WriteToUDP queues a datagram. 606 func (c *dgramPipe) WriteToUDP(b []byte, to *net.UDPAddr) (n int, err error) { 607 msg := make([]byte, len(b)) 608 copy(msg, b) 609 c.mu.Lock() 610 defer c.mu.Unlock() 611 if c.closed { 612 return 0, errors.New("closed") 613 } 614 c.queue = append(c.queue, dgram{*to, b}) 615 c.cond.Signal() 616 return len(b), nil 617 } 618 619 // ReadFromUDP just hangs until the pipe is closed. 620 func (c *dgramPipe) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { 621 <-c.closing 622 return 0, nil, io.EOF 623 } 624 625 func (c *dgramPipe) Close() error { 626 c.mu.Lock() 627 defer c.mu.Unlock() 628 if !c.closed { 629 close(c.closing) 630 c.closed = true 631 } 632 c.cond.Broadcast() 633 return nil 634 } 635 636 func (c *dgramPipe) LocalAddr() net.Addr { 637 return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} 638 } 639 640 func (c *dgramPipe) receive() (dgram, error) { 641 c.mu.Lock() 642 defer c.mu.Unlock() 643 644 var timedOut bool 645 timer := time.AfterFunc(3*time.Second, func() { 646 c.mu.Lock() 647 timedOut = true 648 c.mu.Unlock() 649 c.cond.Broadcast() 650 }) 651 defer timer.Stop() 652 653 for len(c.queue) == 0 && !c.closed && !timedOut { 654 c.cond.Wait() 655 } 656 if c.closed { 657 return dgram{}, errClosed 658 } 659 if timedOut { 660 return dgram{}, errTimeout 661 } 662 p := c.queue[0] 663 copy(c.queue, c.queue[1:]) 664 c.queue = c.queue[:len(c.queue)-1] 665 return p, nil 666 }