github.com/ethereum/go-ethereum@v1.16.1/p2p/discover/v4_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 crand "crypto/rand" 23 "encoding/binary" 24 "errors" 25 "fmt" 26 "io" 27 "math/rand" 28 "net" 29 "net/netip" 30 "reflect" 31 "sync" 32 "testing" 33 "time" 34 35 "github.com/ethereum/go-ethereum/internal/testlog" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/p2p/discover/v4wire" 38 "github.com/ethereum/go-ethereum/p2p/enode" 39 "github.com/ethereum/go-ethereum/p2p/enr" 40 ) 41 42 // shared test variables 43 var ( 44 futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) 45 testTarget = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} 46 testRemote = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} 47 testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} 48 testLocal = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} 49 ) 50 51 type udpTest struct { 52 t *testing.T 53 pipe *dgramPipe 54 table *Table 55 db *enode.DB 56 udp *UDPv4 57 sent [][]byte 58 localkey, remotekey *ecdsa.PrivateKey 59 remoteaddr netip.AddrPort 60 } 61 62 func newUDPTest(t *testing.T) *udpTest { 63 test := &udpTest{ 64 t: t, 65 pipe: newpipe(), 66 localkey: newkey(), 67 remotekey: newkey(), 68 remoteaddr: netip.MustParseAddrPort("10.0.1.99:30303"), 69 } 70 71 test.db, _ = enode.OpenDB("") 72 ln := enode.NewLocalNode(test.db, test.localkey) 73 test.udp, _ = ListenV4(test.pipe, ln, Config{ 74 PrivateKey: test.localkey, 75 Log: testlog.Logger(t, log.LvlTrace), 76 }) 77 test.table = test.udp.tab 78 // Wait for initial refresh so the table doesn't send unexpected findnode. 79 <-test.table.initDone 80 return test 81 } 82 83 func (test *udpTest) close() { 84 test.udp.Close() 85 test.db.Close() 86 } 87 88 // handles a packet as if it had been sent to the transport. 89 func (test *udpTest) packetIn(wantError error, data v4wire.Packet) { 90 test.t.Helper() 91 92 test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data) 93 } 94 95 // handles a packet as if it had been sent to the transport by the key/endpoint. 96 func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr netip.AddrPort, data v4wire.Packet) { 97 test.t.Helper() 98 99 enc, _, err := v4wire.Encode(key, data) 100 if err != nil { 101 test.t.Errorf("%s encode error: %v", data.Name(), err) 102 } 103 test.sent = append(test.sent, enc) 104 if err = test.udp.handlePacket(addr, enc); err != wantError { 105 test.t.Errorf("error mismatch: got %q, want %q", err, wantError) 106 } 107 } 108 109 // waits for a packet to be sent by the transport. 110 // validate should have type func(X, netip.AddrPort, []byte), where X is a packet type. 111 func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { 112 test.t.Helper() 113 114 dgram, err := test.pipe.receive() 115 if err == errClosed { 116 return true 117 } else if err != nil { 118 test.t.Error("packet receive error:", err) 119 return false 120 } 121 p, _, hash, err := v4wire.Decode(dgram.data) 122 if err != nil { 123 test.t.Errorf("sent packet decode error: %v", err) 124 return false 125 } 126 fn := reflect.ValueOf(validate) 127 exptype := fn.Type().In(0) 128 if !reflect.TypeOf(p).AssignableTo(exptype) { 129 test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) 130 return false 131 } 132 fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(dgram.to), reflect.ValueOf(hash)}) 133 return false 134 } 135 136 func TestUDPv4_packetErrors(t *testing.T) { 137 test := newUDPTest(t) 138 defer test.close() 139 140 test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4}) 141 test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp}) 142 test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp}) 143 test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp}) 144 } 145 146 func TestUDPv4_pingTimeout(t *testing.T) { 147 t.Parallel() 148 test := newUDPTest(t) 149 defer test.close() 150 151 key := newkey() 152 toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222} 153 node := enode.NewV4(&key.PublicKey, toaddr.IP, 0, toaddr.Port) 154 if _, err := test.udp.ping(node); err != errTimeout { 155 t.Error("expected timeout error, got", err) 156 } 157 } 158 159 type testPacket byte 160 161 func (req testPacket) Kind() byte { return byte(req) } 162 func (req testPacket) Name() string { return "" } 163 164 func TestUDPv4_responseTimeouts(t *testing.T) { 165 t.Parallel() 166 test := newUDPTest(t) 167 defer test.close() 168 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(60*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(30 * 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 := netip.AddrPortFrom(netip.MustParseAddr("1.2.3.4"), 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 := enode.NewV4(&key.PublicKey, ip, 0, 2000) 266 // Ensure half of table content isn't verified live yet. 267 if i > numCandidates/2 { 268 live[n.ID()] = true 269 } 270 test.table.addFoundNode(n, live[n.ID()]) 271 nodes.push(n, numCandidates) 272 } 273 274 // ensure there's a bond with the test node, 275 // findnode won't be accepted otherwise. 276 remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID() 277 test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.Addr(), time.Now()) 278 279 // check that closest neighbors are returned. 280 expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true) 281 test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp}) 282 waitNeighbors := func(want []*enode.Node) { 283 test.waitPacketOut(func(p *v4wire.Neighbors, to netip.AddrPort, hash []byte) { 284 if len(p.Nodes) != len(want) { 285 t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), len(want)) 286 return 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.Addr(), time.Now()) 313 314 // queue a pending findnode request 315 resultc, errc := make(chan []*enode.Node, 1), make(chan error, 1) 316 go func() { 317 rid := v4wire.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 netip.AddrPort, 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 := []*enode.Node{ 336 enode.MustParse("enode://ba85011c70bcc5c04d8607d3a0ed29aa6179c092cbdda10d5d32684fb33ed01bd94f588ca8f91ac48318087dcb02eaf36773a7a453f0eedd6742af668097b29c@10.0.1.16:30303?discport=30304"), 337 enode.MustParse("enode://81fa361d25f157cd421c60dcc28d8dac5ef6a89476633339c5df30287474520caca09627da18543d9079b5b288698b542d56167aa5c09111e55acdbbdf2ef799@10.0.1.16:30303"), 338 enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17"), 339 enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@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, netip.AddrPort, []byte) {}) 372 test.waitPacketOut(func(*v4wire.Ping, netip.AddrPort, []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, netip.AddrPort, []byte) {}) 383 384 test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) { 385 wrongAddr := netip.MustParseAddrPort("33.44.1.2: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 *tableNode, 1) 397 test.table.nodeAddedHook = func(b *bucket, n *tableNode) { 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 netip.AddrPort, 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 // The mirrored UDP address is the UDP packet sender. 410 // The mirrored TCP port is the one from the ping packet. 411 wantTo := v4wire.NewEndpoint(test.remoteaddr, testRemote.TCP) 412 if !reflect.DeepEqual(p.To, wantTo) { 413 t.Errorf("got pong.To %v, want %v", p.To, wantTo) 414 } 415 }) 416 417 // Remote is unknown, the table pings back. 418 test.waitPacketOut(func(p *v4wire.Ping, to netip.AddrPort, hash []byte) { 419 wantFrom := test.udp.ourEndpoint() 420 wantFrom.IP = net.IP{} 421 if !reflect.DeepEqual(p.From, wantFrom) { 422 t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint()) 423 } 424 // The mirrored UDP address is the UDP packet sender. 425 wantTo := v4wire.NewEndpoint(test.remoteaddr, 0) 426 if !reflect.DeepEqual(p.To, wantTo) { 427 t.Errorf("got ping.To %v, want %v", p.To, wantTo) 428 } 429 test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp}) 430 }) 431 432 // The node should be added to the table shortly after getting the 433 // pong packet. 434 select { 435 case n := <-added: 436 rid := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID() 437 if n.ID() != rid { 438 t.Errorf("node has wrong ID: got %v, want %v", n.ID(), rid) 439 } 440 if n.IPAddr() != test.remoteaddr.Addr() { 441 t.Errorf("node has wrong IP: got %v, want: %v", n.IPAddr(), test.remoteaddr.Addr()) 442 } 443 if n.UDP() != int(test.remoteaddr.Port()) { 444 t.Errorf("node has wrong UDP port: got %v, want: %v", n.UDP(), test.remoteaddr.Port()) 445 } 446 if n.TCP() != int(testRemote.TCP) { 447 t.Errorf("node has wrong TCP port: got %v, want: %v", n.TCP(), testRemote.TCP) 448 } 449 case <-time.After(2 * time.Second): 450 t.Errorf("node was not added within 2 seconds") 451 } 452 } 453 454 // This test checks that EIP-868 requests work. 455 func TestUDPv4_EIP868(t *testing.T) { 456 test := newUDPTest(t) 457 defer test.close() 458 459 test.udp.localNode.Set(enr.WithEntry("foo", "bar")) 460 wantNode := test.udp.localNode.Node() 461 462 // ENR requests aren't allowed before endpoint proof. 463 test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp}) 464 465 // Perform endpoint proof and check for sequence number in packet tail. 466 test.packetIn(nil, &v4wire.Ping{Expiration: futureExp}) 467 test.waitPacketOut(func(p *v4wire.Pong, addr netip.AddrPort, hash []byte) { 468 if p.ENRSeq != wantNode.Seq() { 469 t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq()) 470 } 471 }) 472 test.waitPacketOut(func(p *v4wire.Ping, addr netip.AddrPort, hash []byte) { 473 if p.ENRSeq != wantNode.Seq() { 474 t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq()) 475 } 476 test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) 477 }) 478 479 // Request should work now. 480 test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp}) 481 test.waitPacketOut(func(p *v4wire.ENRResponse, addr netip.AddrPort, hash []byte) { 482 n, err := enode.New(enode.ValidSchemes, &p.Record) 483 if err != nil { 484 t.Fatalf("invalid record: %v", err) 485 } 486 if !reflect.DeepEqual(n, wantNode) { 487 t.Fatalf("wrong node in ENRResponse: %v", n) 488 } 489 }) 490 } 491 492 // This test verifies that a small network of nodes can boot up into a healthy state. 493 func TestUDPv4_smallNetConvergence(t *testing.T) { 494 t.Parallel() 495 496 // Start the network. 497 nodes := make([]*UDPv4, 4) 498 for i := range nodes { 499 var cfg Config 500 if i > 0 { 501 bn := nodes[0].Self() 502 cfg.Bootnodes = []*enode.Node{bn} 503 } 504 nodes[i] = startLocalhostV4(t, cfg) 505 defer nodes[i].Close() 506 } 507 508 // Run through the iterator on all nodes until 509 // they have all found each other. 510 status := make(chan error, len(nodes)) 511 for i := range nodes { 512 node := nodes[i] 513 go func() { 514 found := make(map[enode.ID]bool, len(nodes)) 515 it := node.RandomNodes() 516 for it.Next() { 517 found[it.Node().ID()] = true 518 if len(found) == len(nodes) { 519 status <- nil 520 return 521 } 522 } 523 status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString()) 524 }() 525 } 526 527 // Wait for all status reports. 528 timeout := time.NewTimer(30 * time.Second) 529 defer timeout.Stop() 530 for received := 0; received < len(nodes); { 531 select { 532 case <-timeout.C: 533 for _, node := range nodes { 534 node.Close() 535 } 536 case err := <-status: 537 received++ 538 if err != nil { 539 t.Error("ERROR:", err) 540 return 541 } 542 } 543 } 544 } 545 546 func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { 547 t.Helper() 548 549 cfg.PrivateKey = newkey() 550 db, _ := enode.OpenDB("") 551 ln := enode.NewLocalNode(db, cfg.PrivateKey) 552 553 // Prefix logs with node ID. 554 lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) 555 cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix) 556 557 // Listen. 558 socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) 559 if err != nil { 560 t.Fatal(err) 561 } 562 realaddr := socket.LocalAddr().(*net.UDPAddr) 563 ln.SetStaticIP(realaddr.IP) 564 ln.SetFallbackUDP(realaddr.Port) 565 udp, err := ListenV4(socket, ln, cfg) 566 if err != nil { 567 t.Fatal(err) 568 } 569 return udp 570 } 571 572 // dgramPipe is a fake UDP socket. It queues all sent datagrams. 573 type dgramPipe struct { 574 mu *sync.Mutex 575 cond *sync.Cond 576 closing chan struct{} 577 closed bool 578 queue []dgram 579 } 580 581 type dgram struct { 582 to netip.AddrPort 583 data []byte 584 } 585 586 func newpipe() *dgramPipe { 587 mu := new(sync.Mutex) 588 return &dgramPipe{ 589 closing: make(chan struct{}), 590 cond: &sync.Cond{L: mu}, 591 mu: mu, 592 } 593 } 594 595 // WriteToUDPAddrPort queues a datagram. 596 func (c *dgramPipe) WriteToUDPAddrPort(b []byte, to netip.AddrPort) (n int, err error) { 597 msg := make([]byte, len(b)) 598 copy(msg, b) 599 c.mu.Lock() 600 defer c.mu.Unlock() 601 if c.closed { 602 return 0, errors.New("closed") 603 } 604 c.queue = append(c.queue, dgram{to, b}) 605 c.cond.Signal() 606 return len(b), nil 607 } 608 609 // ReadFromUDPAddrPort just hangs until the pipe is closed. 610 func (c *dgramPipe) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) { 611 <-c.closing 612 return 0, netip.AddrPort{}, io.EOF 613 } 614 615 func (c *dgramPipe) Close() error { 616 c.mu.Lock() 617 defer c.mu.Unlock() 618 if !c.closed { 619 close(c.closing) 620 c.closed = true 621 } 622 c.cond.Broadcast() 623 return nil 624 } 625 626 func (c *dgramPipe) LocalAddr() net.Addr { 627 return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} 628 } 629 630 func (c *dgramPipe) receive() (dgram, error) { 631 c.mu.Lock() 632 defer c.mu.Unlock() 633 634 var timedOut bool 635 timer := time.AfterFunc(3*time.Second, func() { 636 c.mu.Lock() 637 timedOut = true 638 c.mu.Unlock() 639 c.cond.Broadcast() 640 }) 641 defer timer.Stop() 642 643 for len(c.queue) == 0 && !c.closed && !timedOut { 644 c.cond.Wait() 645 } 646 if c.closed { 647 return dgram{}, errClosed 648 } 649 if timedOut { 650 return dgram{}, errTimeout 651 } 652 p := c.queue[0] 653 copy(c.queue, c.queue[1:]) 654 c.queue = c.queue[:len(c.queue)-1] 655 return p, nil 656 }