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 }