github.com/btcsuite/btcd@v0.24.0/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:8333")
   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  }