github.com/decred/dcrlnd@v0.7.6/tor/controller_test.go (about)

     1  package tor
     2  
     3  import (
     4  	"net"
     5  	"net/textproto"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  // TestParseTorVersion is a series of tests for different version strings that
    12  // check the correctness of determining whether they support creating v3 onion
    13  // services through Tor control's port.
    14  func TestParseTorVersion(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	tests := []struct {
    18  		version string
    19  		valid   bool
    20  	}{
    21  		{
    22  			version: "0.3.3.6",
    23  			valid:   true,
    24  		},
    25  		{
    26  			version: "0.3.3.7",
    27  			valid:   true,
    28  		},
    29  		{
    30  			version: "0.3.4.6",
    31  			valid:   true,
    32  		},
    33  		{
    34  			version: "0.4.3.6",
    35  			valid:   true,
    36  		},
    37  		{
    38  			version: "0.4.0.5",
    39  			valid:   true,
    40  		},
    41  		{
    42  			version: "1.3.3.6",
    43  			valid:   true,
    44  		},
    45  		{
    46  			version: "0.3.3.6-rc",
    47  			valid:   true,
    48  		},
    49  		{
    50  			version: "0.3.3.7-rc",
    51  			valid:   true,
    52  		},
    53  		{
    54  			version: "0.3.3.5-rc",
    55  			valid:   false,
    56  		},
    57  		{
    58  			version: "0.3.3.5",
    59  			valid:   false,
    60  		},
    61  		{
    62  			version: "0.3.2.6",
    63  			valid:   false,
    64  		},
    65  		{
    66  			version: "0.1.3.6",
    67  			valid:   false,
    68  		},
    69  		{
    70  			version: "0.0.6.3",
    71  			valid:   false,
    72  		},
    73  	}
    74  
    75  	for i, test := range tests {
    76  		err := supportsV3(test.version)
    77  		if test.valid != (err == nil) {
    78  			t.Fatalf("test %d with version string %v failed: %v", i,
    79  				test.version, err)
    80  		}
    81  	}
    82  }
    83  
    84  // testProxy emulates a Tor daemon and contains the info used for the tor
    85  // controller to make connections.
    86  type testProxy struct {
    87  	// server is the proxy listener.
    88  	server net.Listener
    89  
    90  	// serverConn is the established connection from the server side.
    91  	serverConn net.Conn
    92  
    93  	// serverAddr is the tcp address the proxy is listening on.
    94  	serverAddr string
    95  
    96  	// clientConn is the established connection from the client side.
    97  	clientConn *textproto.Conn
    98  }
    99  
   100  // cleanUp is used after each test to properly close the ports/connections.
   101  func (tp *testProxy) cleanUp() {
   102  	// Don't bother cleanning if there's no a server created.
   103  	if tp.server == nil {
   104  		return
   105  	}
   106  
   107  	if err := tp.clientConn.Close(); err != nil {
   108  		log.Errorf("closing client conn got err: %v", err)
   109  	}
   110  	if err := tp.server.Close(); err != nil {
   111  		log.Errorf("closing proxy server got err: %v", err)
   112  	}
   113  }
   114  
   115  // createTestProxy creates a proxy server to listen on a random address,
   116  // creates a server and a client connection, and initializes a testProxy using
   117  // these params.
   118  func createTestProxy(t *testing.T) *testProxy {
   119  	// Set up the proxy to listen on given port.
   120  	//
   121  	// NOTE: we use a port 0 here to indicate we want a free port selected
   122  	// by the system.
   123  	proxy, err := net.Listen("tcp", ":0")
   124  	require.NoError(t, err, "failed to create proxy")
   125  
   126  	t.Logf("created proxy server to listen on address: %v", proxy.Addr())
   127  
   128  	// Accept the connection inside a goroutine.
   129  	serverChan := make(chan net.Conn, 1)
   130  	go func(result chan net.Conn) {
   131  		conn, err := proxy.Accept()
   132  		require.NoError(t, err, "failed to accept")
   133  
   134  		result <- conn
   135  	}(serverChan)
   136  
   137  	// Create the connection using tor controller.
   138  	client, err := textproto.Dial("tcp", proxy.Addr().String())
   139  	require.NoError(t, err, "failed to create connection")
   140  
   141  	tc := &testProxy{
   142  		server:     proxy,
   143  		serverConn: <-serverChan,
   144  		serverAddr: proxy.Addr().String(),
   145  		clientConn: client,
   146  	}
   147  
   148  	return tc
   149  }
   150  
   151  // TestReadResponse contructs a series of possible responses returned by Tor
   152  // and asserts the readResponse can handle them correctly.
   153  func TestReadResponse(t *testing.T) {
   154  	// Create mock server and client connection.
   155  	proxy := createTestProxy(t)
   156  	defer proxy.cleanUp()
   157  	server := proxy.serverConn
   158  
   159  	// Create a dummy tor controller.
   160  	c := &Controller{conn: proxy.clientConn}
   161  
   162  	testCase := []struct {
   163  		name       string
   164  		serverResp string
   165  
   166  		// expectedReply is the reply we expect the readResponse to
   167  		// return.
   168  		expectedReply string
   169  
   170  		// expectedCode is the code we expect the server to return.
   171  		expectedCode int
   172  
   173  		// returnedCode is the code we expect the readResponse to
   174  		// return.
   175  		returnedCode int
   176  
   177  		// expectedErr is the error we expect the readResponse to
   178  		// return.
   179  		expectedErr error
   180  	}{
   181  		{
   182  			// Test a simple response.
   183  			name:          "succeed on 250",
   184  			serverResp:    "250 OK\n",
   185  			expectedReply: "OK",
   186  			expectedCode:  250,
   187  			returnedCode:  250,
   188  			expectedErr:   nil,
   189  		},
   190  		{
   191  			// Test a mid reply(-) response.
   192  			name: "succeed on mid reply line",
   193  			serverResp: "250-field=value\n" +
   194  				"250 OK\n",
   195  			expectedReply: "field=value\nOK",
   196  			expectedCode:  250,
   197  			returnedCode:  250,
   198  			expectedErr:   nil,
   199  		},
   200  		{
   201  			// Test a data reply(+) response.
   202  			name: "succeed on data reply line",
   203  			serverResp: "250+field=\n" +
   204  				"line1\n" +
   205  				"line2\n" +
   206  				".\n" +
   207  				"250 OK\n",
   208  			expectedReply: "field=line1,line2\nOK",
   209  			expectedCode:  250,
   210  			returnedCode:  250,
   211  			expectedErr:   nil,
   212  		},
   213  		{
   214  			// Test a mixed reply response.
   215  			name: "succeed on mixed reply line",
   216  			serverResp: "250-field=value\n" +
   217  				"250+field=\n" +
   218  				"line1\n" +
   219  				"line2\n" +
   220  				".\n" +
   221  				"250 OK\n",
   222  			expectedReply: "field=value\nfield=line1,line2\nOK",
   223  			expectedCode:  250,
   224  			returnedCode:  250,
   225  			expectedErr:   nil,
   226  		},
   227  		{
   228  			// Test unexpected code.
   229  			name:          "fail on codes not matched",
   230  			serverResp:    "250 ERR\n",
   231  			expectedReply: "ERR",
   232  			expectedCode:  500,
   233  			returnedCode:  250,
   234  			expectedErr:   errCodeNotMatch,
   235  		},
   236  		{
   237  			// Test short response error.
   238  			name:          "fail on short response",
   239  			serverResp:    "123\n250 OK\n",
   240  			expectedReply: "",
   241  			expectedCode:  250,
   242  			returnedCode:  0,
   243  			expectedErr: textproto.ProtocolError(
   244  				"short line: 123"),
   245  		},
   246  		{
   247  			// Test short response error.
   248  			name:          "fail on invalid response",
   249  			serverResp:    "250?OK\n",
   250  			expectedReply: "",
   251  			expectedCode:  250,
   252  			returnedCode:  250,
   253  			expectedErr: textproto.ProtocolError(
   254  				"invalid line: 250?OK"),
   255  		},
   256  	}
   257  
   258  	for _, tc := range testCase {
   259  		tc := tc
   260  		t.Run(tc.name, func(t *testing.T) {
   261  
   262  			// Let the server mocks a given response.
   263  			_, err := server.Write([]byte(tc.serverResp))
   264  			require.NoError(t, err, "server failed to write")
   265  
   266  			// Read the response and checks all expectations
   267  			// satisfied.
   268  			code, reply, err := c.readResponse(tc.expectedCode)
   269  			require.Equal(t, tc.expectedErr, err)
   270  			require.Equal(t, tc.returnedCode, code)
   271  			require.Equal(t, tc.expectedReply, reply)
   272  
   273  			// Check that the read buffer is cleaned.
   274  			require.Zero(t, c.conn.R.Buffered(),
   275  				"read buffer not empty")
   276  		})
   277  	}
   278  }
   279  
   280  // TestReconnectTCMustBeRunning checks that the tor controller must be running
   281  // while calling Reconnect.
   282  func TestReconnectTCMustBeRunning(t *testing.T) {
   283  	// Create a dummy controller.
   284  	c := &Controller{}
   285  
   286  	// Reconnect should fail because the TC is not started.
   287  	require.Equal(t, errTCNotStarted, c.Reconnect())
   288  
   289  	// Set the started flag.
   290  	c.started = 1
   291  
   292  	// Set the stopped flag so the TC is stopped.
   293  	c.stopped = 1
   294  
   295  	// Reconnect should fail because the TC is stopped.
   296  	require.Equal(t, errTCStopped, c.Reconnect())
   297  }
   298  
   299  // TestReconnectSucceed tests a reconnection will succeed when the tor
   300  // controller is up and running.
   301  func TestReconnectSucceed(t *testing.T) {
   302  	// Create mock server and client connection.
   303  	proxy := createTestProxy(t)
   304  	defer proxy.cleanUp()
   305  
   306  	// Create a tor controller and mark the controller as started.
   307  	c := &Controller{
   308  		conn:        proxy.clientConn,
   309  		started:     1,
   310  		controlAddr: proxy.serverAddr,
   311  	}
   312  
   313  	// Accept the connection inside a goroutine. We will also write some
   314  	// data so that the reconnection can succeed. We will mock three writes
   315  	// and two reads inside our proxy server,
   316  	//   - write protocol info
   317  	//   - read auth info
   318  	//   - write auth challenge
   319  	//   - read auth challenge
   320  	//   - write OK
   321  	go func() {
   322  		// Accept the new connection.
   323  		server, err := proxy.server.Accept()
   324  		require.NoError(t, err, "failed to accept")
   325  
   326  		// Write the protocol info.
   327  		resp := "250-PROTOCOLINFO 1\n" +
   328  			"250-AUTH METHODS=NULL\n" +
   329  			"250 OK\n"
   330  		_, err = server.Write([]byte(resp))
   331  		require.NoErrorf(t, err, "failed to write protocol info")
   332  
   333  		// Read the auth info from the client.
   334  		buf := make([]byte, 65535)
   335  		_, err = server.Read(buf)
   336  		require.NoError(t, err)
   337  
   338  		// Write the auth challenge.
   339  		resp = "250 AUTHCHALLENGE SERVERHASH=fake\n"
   340  		_, err = server.Write([]byte(resp))
   341  		require.NoErrorf(t, err, "failed to write auth challenge")
   342  
   343  		// Read the auth challenge resp from the client.
   344  		_, err = server.Read(buf)
   345  		require.NoError(t, err)
   346  
   347  		// Write OK resp.
   348  		resp = "250 OK\n"
   349  		_, err = server.Write([]byte(resp))
   350  		require.NoErrorf(t, err, "failed to write response auth")
   351  	}()
   352  
   353  	// Reconnect should succeed.
   354  	require.NoError(t, c.Reconnect())
   355  
   356  	// Check that the old connection is closed.
   357  	_, err := proxy.clientConn.ReadLine()
   358  	require.Contains(t, err.Error(), "use of closed network connection")
   359  
   360  	// Check that the connection has been updated.
   361  	require.NotEqual(t, proxy.clientConn, c.conn)
   362  }