github.com/lbryio/lbcd@v0.22.119/connmgr/connmanager_test.go (about) 1 // Copyright (c) 2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package connmgr 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "sync/atomic" 13 "testing" 14 "time" 15 ) 16 17 func init() { 18 // Override the max retry duration when running tests. 19 maxRetryDuration = 2 * time.Millisecond 20 } 21 22 // mockAddr mocks a network address 23 type mockAddr struct { 24 net, address string 25 } 26 27 func (m mockAddr) Network() string { return m.net } 28 func (m mockAddr) String() string { return m.address } 29 30 // mockConn mocks a network connection by implementing the net.Conn interface. 31 type mockConn struct { 32 io.Reader 33 io.Writer 34 io.Closer 35 36 // local network, address for the connection. 37 lnet, laddr string 38 39 // remote network, address for the connection. 40 rAddr net.Addr 41 } 42 43 // LocalAddr returns the local address for the connection. 44 func (c mockConn) LocalAddr() net.Addr { 45 return &mockAddr{c.lnet, c.laddr} 46 } 47 48 // RemoteAddr returns the remote address for the connection. 49 func (c mockConn) RemoteAddr() net.Addr { 50 return &mockAddr{c.rAddr.Network(), c.rAddr.String()} 51 } 52 53 // Close handles closing the connection. 54 func (c mockConn) Close() error { 55 return nil 56 } 57 58 func (c mockConn) SetDeadline(t time.Time) error { return nil } 59 func (c mockConn) SetReadDeadline(t time.Time) error { return nil } 60 func (c mockConn) SetWriteDeadline(t time.Time) error { return nil } 61 62 // mockDialer mocks the net.Dial interface by returning a mock connection to 63 // the given address. 64 func mockDialer(addr net.Addr) (net.Conn, error) { 65 r, w := io.Pipe() 66 c := &mockConn{rAddr: addr} 67 c.Reader = r 68 c.Writer = w 69 return c, nil 70 } 71 72 // TestNewConfig tests that new ConnManager config is validated as expected. 73 func TestNewConfig(t *testing.T) { 74 _, err := New(&Config{}) 75 if err == nil { 76 t.Fatalf("New expected error: 'Dial can't be nil', got nil") 77 } 78 _, err = New(&Config{ 79 Dial: mockDialer, 80 }) 81 if err != nil { 82 t.Fatalf("New unexpected error: %v", err) 83 } 84 } 85 86 // TestStartStop tests that the connection manager starts and stops as 87 // expected. 88 func TestStartStop(t *testing.T) { 89 connected := make(chan *ConnReq) 90 disconnected := make(chan *ConnReq) 91 cmgr, err := New(&Config{ 92 TargetOutbound: 1, 93 GetNewAddress: func() (net.Addr, error) { 94 return &net.TCPAddr{ 95 IP: net.ParseIP("127.0.0.1"), 96 Port: 18555, 97 }, nil 98 }, 99 Dial: mockDialer, 100 OnConnection: func(c *ConnReq, conn net.Conn) { 101 connected <- c 102 }, 103 OnDisconnection: func(c *ConnReq) { 104 disconnected <- c 105 }, 106 }) 107 if err != nil { 108 t.Fatalf("New error: %v", err) 109 } 110 cmgr.Start() 111 gotConnReq := <-connected 112 cmgr.Stop() 113 // already stopped 114 cmgr.Stop() 115 // ignored 116 cr := &ConnReq{ 117 Addr: &net.TCPAddr{ 118 IP: net.ParseIP("127.0.0.1"), 119 Port: 18555, 120 }, 121 Permanent: true, 122 } 123 cmgr.Connect(cr) 124 if cr.ID() != 0 { 125 t.Fatalf("start/stop: got id: %v, want: 0", cr.ID()) 126 } 127 cmgr.Disconnect(gotConnReq.ID()) 128 cmgr.Remove(gotConnReq.ID()) 129 select { 130 case <-disconnected: 131 t.Fatalf("start/stop: unexpected disconnection") 132 case <-time.Tick(10 * time.Millisecond): 133 break 134 } 135 } 136 137 // TestConnectMode tests that the connection manager works in the connect mode. 138 // 139 // In connect mode, automatic connections are disabled, so we test that 140 // requests using Connect are handled and that no other connections are made. 141 func TestConnectMode(t *testing.T) { 142 connected := make(chan *ConnReq) 143 cmgr, err := New(&Config{ 144 TargetOutbound: 2, 145 Dial: mockDialer, 146 OnConnection: func(c *ConnReq, conn net.Conn) { 147 connected <- c 148 }, 149 }) 150 if err != nil { 151 t.Fatalf("New error: %v", err) 152 } 153 cr := &ConnReq{ 154 Addr: &net.TCPAddr{ 155 IP: net.ParseIP("127.0.0.1"), 156 Port: 18555, 157 }, 158 Permanent: true, 159 } 160 cmgr.Start() 161 cmgr.Connect(cr) 162 gotConnReq := <-connected 163 wantID := cr.ID() 164 gotID := gotConnReq.ID() 165 if gotID != wantID { 166 t.Fatalf("connect mode: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID) 167 } 168 gotState := cr.State() 169 wantState := ConnEstablished 170 if gotState != wantState { 171 t.Fatalf("connect mode: %v - want state %v, got state %v", cr.Addr, wantState, gotState) 172 } 173 select { 174 case c := <-connected: 175 t.Fatalf("connect mode: got unexpected connection - %v", c.Addr) 176 case <-time.After(time.Millisecond): 177 break 178 } 179 cmgr.Stop() 180 } 181 182 // TestTargetOutbound tests the target number of outbound connections. 183 // 184 // We wait until all connections are established, then test they there are the 185 // only connections made. 186 func TestTargetOutbound(t *testing.T) { 187 targetOutbound := uint32(10) 188 connected := make(chan *ConnReq) 189 cmgr, err := New(&Config{ 190 TargetOutbound: targetOutbound, 191 Dial: mockDialer, 192 GetNewAddress: func() (net.Addr, error) { 193 return &net.TCPAddr{ 194 IP: net.ParseIP("127.0.0.1"), 195 Port: 18555, 196 }, nil 197 }, 198 OnConnection: func(c *ConnReq, conn net.Conn) { 199 connected <- c 200 }, 201 }) 202 if err != nil { 203 t.Fatalf("New error: %v", err) 204 } 205 cmgr.Start() 206 for i := uint32(0); i < targetOutbound; i++ { 207 <-connected 208 } 209 210 select { 211 case c := <-connected: 212 t.Fatalf("target outbound: got unexpected connection - %v", c.Addr) 213 case <-time.After(time.Millisecond): 214 break 215 } 216 cmgr.Stop() 217 } 218 219 // TestRetryPermanent tests that permanent connection requests are retried. 220 // 221 // We make a permanent connection request using Connect, disconnect it using 222 // Disconnect and we wait for it to be connected back. 223 func TestRetryPermanent(t *testing.T) { 224 connected := make(chan *ConnReq) 225 disconnected := make(chan *ConnReq) 226 cmgr, err := New(&Config{ 227 RetryDuration: time.Millisecond, 228 TargetOutbound: 1, 229 Dial: mockDialer, 230 OnConnection: func(c *ConnReq, conn net.Conn) { 231 connected <- c 232 }, 233 OnDisconnection: func(c *ConnReq) { 234 disconnected <- c 235 }, 236 }) 237 if err != nil { 238 t.Fatalf("New error: %v", err) 239 } 240 241 cr := &ConnReq{ 242 Addr: &net.TCPAddr{ 243 IP: net.ParseIP("127.0.0.1"), 244 Port: 18555, 245 }, 246 Permanent: true, 247 } 248 go cmgr.Connect(cr) 249 cmgr.Start() 250 gotConnReq := <-connected 251 wantID := cr.ID() 252 gotID := gotConnReq.ID() 253 if gotID != wantID { 254 t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID) 255 } 256 gotState := cr.State() 257 wantState := ConnEstablished 258 if gotState != wantState { 259 t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState) 260 } 261 262 cmgr.Disconnect(cr.ID()) 263 gotConnReq = <-disconnected 264 wantID = cr.ID() 265 gotID = gotConnReq.ID() 266 if gotID != wantID { 267 t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID) 268 } 269 gotState = cr.State() 270 wantState = ConnPending 271 if gotState != wantState { 272 t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState) 273 } 274 275 gotConnReq = <-connected 276 wantID = cr.ID() 277 gotID = gotConnReq.ID() 278 if gotID != wantID { 279 t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID) 280 } 281 gotState = cr.State() 282 wantState = ConnEstablished 283 if gotState != wantState { 284 t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState) 285 } 286 287 cmgr.Remove(cr.ID()) 288 gotConnReq = <-disconnected 289 wantID = cr.ID() 290 gotID = gotConnReq.ID() 291 if gotID != wantID { 292 t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID) 293 } 294 gotState = cr.State() 295 wantState = ConnDisconnected 296 if gotState != wantState { 297 t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState) 298 } 299 cmgr.Stop() 300 } 301 302 // TestMaxRetryDuration tests the maximum retry duration. 303 // 304 // We have a timed dialer which initially returns err but after RetryDuration 305 // hits maxRetryDuration returns a mock conn. 306 func TestMaxRetryDuration(t *testing.T) { 307 networkUp := make(chan struct{}) 308 time.AfterFunc(5*time.Millisecond, func() { 309 close(networkUp) 310 }) 311 timedDialer := func(addr net.Addr) (net.Conn, error) { 312 select { 313 case <-networkUp: 314 return mockDialer(addr) 315 default: 316 return nil, errors.New("network down") 317 } 318 } 319 320 connected := make(chan *ConnReq) 321 cmgr, err := New(&Config{ 322 RetryDuration: time.Millisecond, 323 TargetOutbound: 1, 324 Dial: timedDialer, 325 OnConnection: func(c *ConnReq, conn net.Conn) { 326 connected <- c 327 }, 328 }) 329 if err != nil { 330 t.Fatalf("New error: %v", err) 331 } 332 333 cr := &ConnReq{ 334 Addr: &net.TCPAddr{ 335 IP: net.ParseIP("127.0.0.1"), 336 Port: 18555, 337 }, 338 Permanent: true, 339 } 340 go cmgr.Connect(cr) 341 cmgr.Start() 342 // retry in 1ms 343 // retry in 2ms - max retry duration reached 344 // retry in 2ms - timedDialer returns mockDial 345 select { 346 case <-connected: 347 case <-time.Tick(100 * time.Millisecond): 348 t.Fatalf("max retry duration: connection timeout") 349 } 350 } 351 352 // TestNetworkFailure tests that the connection manager handles a network 353 // failure gracefully. 354 func TestNetworkFailure(t *testing.T) { 355 var dials uint32 356 errDialer := func(net net.Addr) (net.Conn, error) { 357 atomic.AddUint32(&dials, 1) 358 return nil, errors.New("network down") 359 } 360 cmgr, err := New(&Config{ 361 TargetOutbound: 5, 362 RetryDuration: 5 * time.Millisecond, 363 Dial: errDialer, 364 GetNewAddress: func() (net.Addr, error) { 365 return &net.TCPAddr{ 366 IP: net.ParseIP("127.0.0.1"), 367 Port: 18555, 368 }, nil 369 }, 370 OnConnection: func(c *ConnReq, conn net.Conn) { 371 t.Fatalf("network failure: got unexpected connection - %v", c.Addr) 372 }, 373 }) 374 if err != nil { 375 t.Fatalf("New error: %v", err) 376 } 377 cmgr.Start() 378 time.AfterFunc(10*time.Millisecond, cmgr.Stop) 379 cmgr.Wait() 380 wantMaxDials := uint32(75) 381 if atomic.LoadUint32(&dials) > wantMaxDials { 382 t.Fatalf("network failure: unexpected number of dials - got %v, want < %v", 383 atomic.LoadUint32(&dials), wantMaxDials) 384 } 385 } 386 387 // TestStopFailed tests that failed connections are ignored after connmgr is 388 // stopped. 389 // 390 // We have a dailer which sets the stop flag on the conn manager and returns an 391 // err so that the handler assumes that the conn manager is stopped and ignores 392 // the failure. 393 func TestStopFailed(t *testing.T) { 394 done := make(chan struct{}, 1) 395 waitDialer := func(addr net.Addr) (net.Conn, error) { 396 done <- struct{}{} 397 time.Sleep(time.Millisecond) 398 return nil, errors.New("network down") 399 } 400 cmgr, err := New(&Config{ 401 Dial: waitDialer, 402 }) 403 if err != nil { 404 t.Fatalf("New error: %v", err) 405 } 406 cmgr.Start() 407 go func() { 408 <-done 409 atomic.StoreInt32(&cmgr.stop, 1) 410 time.Sleep(2 * time.Millisecond) 411 atomic.StoreInt32(&cmgr.stop, 0) 412 cmgr.Stop() 413 }() 414 cr := &ConnReq{ 415 Addr: &net.TCPAddr{ 416 IP: net.ParseIP("127.0.0.1"), 417 Port: 18555, 418 }, 419 Permanent: true, 420 } 421 go cmgr.Connect(cr) 422 cmgr.Wait() 423 } 424 425 // TestRemovePendingConnection tests that it's possible to cancel a pending 426 // connection, removing its internal state from the ConnMgr. 427 func TestRemovePendingConnection(t *testing.T) { 428 // Create a ConnMgr instance with an instance of a dialer that'll never 429 // succeed. 430 wait := make(chan struct{}) 431 indefiniteDialer := func(addr net.Addr) (net.Conn, error) { 432 <-wait 433 return nil, fmt.Errorf("error") 434 } 435 cmgr, err := New(&Config{ 436 Dial: indefiniteDialer, 437 }) 438 if err != nil { 439 t.Fatalf("New error: %v", err) 440 } 441 cmgr.Start() 442 443 // Establish a connection request to a random IP we've chosen. 444 cr := &ConnReq{ 445 Addr: &net.TCPAddr{ 446 IP: net.ParseIP("127.0.0.1"), 447 Port: 18555, 448 }, 449 Permanent: true, 450 } 451 go cmgr.Connect(cr) 452 453 time.Sleep(10 * time.Millisecond) 454 455 if cr.State() != ConnPending { 456 t.Fatalf("pending request hasn't been registered, status: %v", 457 cr.State()) 458 } 459 460 // The request launched above will actually never be able to establish 461 // a connection. So we'll cancel it _before_ it's able to be completed. 462 cmgr.Remove(cr.ID()) 463 464 time.Sleep(10 * time.Millisecond) 465 466 // Now examine the status of the connection request, it should read a 467 // status of ConnCanceled. 468 if cr.State() != ConnCanceled { 469 t.Fatalf("request wasn't canceled, status is: %v", cr.State()) 470 } 471 472 close(wait) 473 cmgr.Stop() 474 } 475 476 // TestCancelIgnoreDelayedConnection tests that a canceled connection request will 477 // not execute the on connection callback, even if an outstanding retry 478 // succeeds. 479 func TestCancelIgnoreDelayedConnection(t *testing.T) { 480 retryTimeout := 10 * time.Millisecond 481 482 // Setup a dialer that will continue to return an error until the 483 // connect chan is signaled, the dial attempt immediately after will 484 // succeed in returning a connection. 485 connect := make(chan struct{}) 486 failingDialer := func(addr net.Addr) (net.Conn, error) { 487 select { 488 case <-connect: 489 return mockDialer(addr) 490 default: 491 } 492 493 return nil, fmt.Errorf("error") 494 } 495 496 connected := make(chan *ConnReq) 497 cmgr, err := New(&Config{ 498 Dial: failingDialer, 499 RetryDuration: retryTimeout, 500 OnConnection: func(c *ConnReq, conn net.Conn) { 501 connected <- c 502 }, 503 }) 504 if err != nil { 505 t.Fatalf("New error: %v", err) 506 } 507 cmgr.Start() 508 defer cmgr.Stop() 509 510 // Establish a connection request to a random IP we've chosen. 511 cr := &ConnReq{ 512 Addr: &net.TCPAddr{ 513 IP: net.ParseIP("127.0.0.1"), 514 Port: 18555, 515 }, 516 } 517 cmgr.Connect(cr) 518 519 // Allow for the first retry timeout to elapse. 520 time.Sleep(2 * retryTimeout) 521 522 // Connection be marked as failed, even after reattempting to 523 // connect. 524 if cr.State() != ConnFailing { 525 t.Fatalf("failing request should have status failed, status: %v", 526 cr.State()) 527 } 528 529 // Remove the connection, and then immediately allow the next connection 530 // to succeed. 531 cmgr.Remove(cr.ID()) 532 close(connect) 533 534 // Allow the connection manager to process the removal. 535 time.Sleep(5 * time.Millisecond) 536 537 // Now examine the status of the connection request, it should read a 538 // status of canceled. 539 if cr.State() != ConnCanceled { 540 t.Fatalf("request wasn't canceled, status is: %v", cr.State()) 541 } 542 543 // Finally, the connection manager should not signal the on-connection 544 // callback, since we explicitly canceled this request. We give a 545 // generous window to ensure the connection manager's lienar backoff is 546 // allowed to properly elapse. 547 select { 548 case <-connected: 549 t.Fatalf("on-connect should not be called for canceled req") 550 case <-time.After(5 * retryTimeout): 551 } 552 553 } 554 555 // mockListener implements the net.Listener interface and is used to test 556 // code that deals with net.Listeners without having to actually make any real 557 // connections. 558 type mockListener struct { 559 localAddr string 560 provideConn chan net.Conn 561 } 562 563 // Accept returns a mock connection when it receives a signal via the Connect 564 // function. 565 // 566 // This is part of the net.Listener interface. 567 func (m *mockListener) Accept() (net.Conn, error) { 568 for conn := range m.provideConn { 569 return conn, nil 570 } 571 return nil, errors.New("network connection closed") 572 } 573 574 // Close closes the mock listener which will cause any blocked Accept 575 // operations to be unblocked and return errors. 576 // 577 // This is part of the net.Listener interface. 578 func (m *mockListener) Close() error { 579 close(m.provideConn) 580 return nil 581 } 582 583 // Addr returns the address the mock listener was configured with. 584 // 585 // This is part of the net.Listener interface. 586 func (m *mockListener) Addr() net.Addr { 587 return &mockAddr{"tcp", m.localAddr} 588 } 589 590 // Connect fakes a connection to the mock listener from the provided remote 591 // address. It will cause the Accept function to return a mock connection 592 // configured with the provided remote address and the local address for the 593 // mock listener. 594 func (m *mockListener) Connect(ip string, port int) { 595 m.provideConn <- &mockConn{ 596 laddr: m.localAddr, 597 lnet: "tcp", 598 rAddr: &net.TCPAddr{ 599 IP: net.ParseIP(ip), 600 Port: port, 601 }, 602 } 603 } 604 605 // newMockListener returns a new mock listener for the provided local address 606 // and port. No ports are actually opened. 607 func newMockListener(localAddr string) *mockListener { 608 return &mockListener{ 609 localAddr: localAddr, 610 provideConn: make(chan net.Conn), 611 } 612 } 613 614 // TestListeners ensures providing listeners to the connection manager along 615 // with an accept callback works properly. 616 func TestListeners(t *testing.T) { 617 // Setup a connection manager with a couple of mock listeners that 618 // notify a channel when they receive mock connections. 619 receivedConns := make(chan net.Conn) 620 listener1 := newMockListener("127.0.0.1:9246") 621 listener2 := newMockListener("127.0.0.1:9333") 622 listeners := []net.Listener{listener1, listener2} 623 cmgr, err := New(&Config{ 624 Listeners: listeners, 625 OnAccept: func(conn net.Conn) { 626 receivedConns <- conn 627 }, 628 Dial: mockDialer, 629 }) 630 if err != nil { 631 t.Fatalf("New error: %v", err) 632 } 633 cmgr.Start() 634 635 // Fake a couple of mock connections to each of the listeners. 636 go func() { 637 for i, listener := range listeners { 638 l := listener.(*mockListener) 639 l.Connect("127.0.0.1", 10000+i*2) 640 l.Connect("127.0.0.1", 10000+i*2+1) 641 } 642 }() 643 644 // Tally the receive connections to ensure the expected number are 645 // received. Also, fail the test after a timeout so it will not hang 646 // forever should the test not work. 647 expectedNumConns := len(listeners) * 2 648 var numConns int 649 out: 650 for { 651 select { 652 case <-receivedConns: 653 numConns++ 654 if numConns == expectedNumConns { 655 break out 656 } 657 658 case <-time.After(time.Millisecond * 50): 659 t.Fatalf("Timeout waiting for %d expected connections", 660 expectedNumConns) 661 } 662 } 663 664 cmgr.Stop() 665 cmgr.Wait() 666 }