controller_test.go
1 package tor 2 3 import ( 4 "fmt" 5 "net" 6 "net/textproto" 7 "os" 8 "path/filepath" 9 "strconv" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/require" 15 ) 16 17 // TestParseTorVersion is a series of tests for different version strings that 18 // check the correctness of determining whether they support creating v3 onion 19 // services through Tor control's port. 20 func TestParseTorVersion(t *testing.T) { 21 t.Parallel() 22 23 tests := []struct { 24 version string 25 valid bool 26 }{ 27 { 28 version: "0.3.3.6", 29 valid: true, 30 }, 31 { 32 version: "0.3.3.7", 33 valid: true, 34 }, 35 { 36 version: "0.3.4.6", 37 valid: true, 38 }, 39 { 40 version: "0.4.3.6", 41 valid: true, 42 }, 43 { 44 version: "0.4.0.5", 45 valid: true, 46 }, 47 { 48 version: "1.3.3.6", 49 valid: true, 50 }, 51 { 52 version: "0.3.3.6-rc", 53 valid: true, 54 }, 55 { 56 version: "0.3.3.7-rc", 57 valid: true, 58 }, 59 { 60 version: "0.3.3.5-rc", 61 valid: false, 62 }, 63 { 64 version: "0.3.3.5", 65 valid: false, 66 }, 67 { 68 version: "0.3.2.6", 69 valid: false, 70 }, 71 { 72 version: "0.1.3.6", 73 valid: false, 74 }, 75 { 76 version: "0.0.6.3", 77 valid: false, 78 }, 79 } 80 81 for i, test := range tests { 82 err := supportsV3(test.version) 83 if test.valid != (err == nil) { 84 t.Fatalf("test %d with version string %v failed: %v", i, 85 test.version, err) 86 } 87 } 88 } 89 90 // testProxy emulates a Tor daemon and contains the info used for the tor 91 // controller to make connections. 92 type testProxy struct { 93 // server is the proxy listener. 94 server net.Listener 95 96 // serverConn is the established connection from the server side. 97 serverConn net.Conn 98 99 // serverAddr is the tcp address the proxy is listening on. 100 serverAddr string 101 102 // clientConn is the established connection from the client side. 103 clientConn *textproto.Conn 104 } 105 106 // cleanUp is used after each test to properly close the ports/connections. 107 func (tp *testProxy) cleanUp() { 108 // Don't bother cleaning if there's no a server created. 109 if tp.server == nil { 110 return 111 } 112 113 if err := tp.clientConn.Close(); err != nil { 114 log.Errorf("closing client conn got err: %v", err) 115 } 116 if err := tp.server.Close(); err != nil { 117 log.Errorf("closing proxy server got err: %v", err) 118 } 119 } 120 121 // createTestProxy creates a proxy server to listen on a random address, 122 // creates a server and a client connection, and initializes a testProxy using 123 // these params. 124 func createTestProxy(t *testing.T) *testProxy { 125 // Set up the proxy to listen on a unique port. 126 addr := fmt.Sprintf(":%d", nextAvailablePort()) 127 proxy, err := net.Listen("tcp", addr) 128 require.NoError(t, err, "failed to create proxy") 129 130 t.Logf("created proxy server to listen on address: %v", proxy.Addr()) 131 132 // Accept the connection inside a goroutine. 133 serverChan := make(chan net.Conn, 1) 134 go func(result chan net.Conn) { 135 conn, err := proxy.Accept() 136 require.NoError(t, err, "failed to accept") 137 138 result <- conn 139 }(serverChan) 140 141 // Create the connection using tor controller. 142 client, err := textproto.Dial("tcp", proxy.Addr().String()) 143 require.NoError(t, err, "failed to create connection") 144 145 tc := &testProxy{ 146 server: proxy, 147 serverConn: <-serverChan, 148 serverAddr: proxy.Addr().String(), 149 clientConn: client, 150 } 151 152 t.Logf("server listening on %v, client listening on %v", 153 tc.serverConn.LocalAddr(), tc.serverConn.RemoteAddr()) 154 155 return tc 156 } 157 158 // TestReadResponse constructs a series of possible responses returned by Tor 159 // and asserts the readResponse can handle them correctly. 160 func TestReadResponse(t *testing.T) { 161 // Create mock server and client connection. 162 proxy := createTestProxy(t) 163 t.Cleanup(proxy.cleanUp) 164 server := proxy.serverConn 165 166 // Create a dummy tor controller. 167 c := &Controller{conn: proxy.clientConn} 168 169 testCase := []struct { 170 name string 171 serverResp string 172 173 // expectedReply is the reply we expect the readResponse to 174 // return. 175 expectedReply string 176 177 // expectedCode is the code we expect the server to return. 178 expectedCode int 179 180 // returnedCode is the code we expect the readResponse to 181 // return. 182 returnedCode int 183 184 // expectedErr is the error we expect the readResponse to 185 // return. 186 expectedErr error 187 }{ 188 { 189 // Test a simple response. 190 name: "succeed on 250", 191 serverResp: "250 OK\n", 192 expectedReply: "OK", 193 expectedCode: 250, 194 returnedCode: 250, 195 expectedErr: nil, 196 }, 197 { 198 // Test a mid reply(-) response. 199 name: "succeed on mid reply line", 200 serverResp: "250-field=value\n" + 201 "250 OK\n", 202 expectedReply: "field=value\nOK", 203 expectedCode: 250, 204 returnedCode: 250, 205 expectedErr: nil, 206 }, 207 { 208 // Test a data reply(+) response. 209 name: "succeed on data reply line", 210 serverResp: "250+field=\n" + 211 "line1\n" + 212 "line2\n" + 213 ".\n" + 214 "250 OK\n", 215 expectedReply: "field=line1,line2\nOK", 216 expectedCode: 250, 217 returnedCode: 250, 218 expectedErr: nil, 219 }, 220 { 221 // Test a mixed reply response. 222 name: "succeed on mixed reply line", 223 serverResp: "250-field=value\n" + 224 "250+field=\n" + 225 "line1\n" + 226 "line2\n" + 227 ".\n" + 228 "250 OK\n", 229 expectedReply: "field=value\nfield=line1,line2\nOK", 230 expectedCode: 250, 231 returnedCode: 250, 232 expectedErr: nil, 233 }, 234 { 235 // Test unexpected code. 236 name: "fail on codes not matched", 237 serverResp: "250 ERR\n", 238 expectedReply: "ERR", 239 expectedCode: 500, 240 returnedCode: 250, 241 expectedErr: errCodeNotMatch, 242 }, 243 { 244 // Test short response error. 245 name: "fail on short response", 246 serverResp: "123\n250 OK\n", 247 expectedReply: "", 248 expectedCode: 250, 249 returnedCode: 0, 250 expectedErr: textproto.ProtocolError( 251 "short line: 123"), 252 }, 253 { 254 // Test short response error. 255 name: "fail on invalid response", 256 serverResp: "250?OK\n", 257 expectedReply: "", 258 expectedCode: 250, 259 returnedCode: 250, 260 expectedErr: textproto.ProtocolError( 261 "invalid line: 250?OK"), 262 }, 263 } 264 265 for _, tc := range testCase { 266 tc := tc 267 t.Run(tc.name, func(t *testing.T) { 268 // Let the server mocks a given response. 269 _, err := server.Write([]byte(tc.serverResp)) 270 require.NoError(t, err, "server failed to write") 271 272 // Read the response and checks all expectations 273 // satisfied. 274 code, reply, err := c.readResponse(tc.expectedCode) 275 require.Equal(t, tc.expectedErr, err) 276 require.Equal(t, tc.returnedCode, code) 277 require.Equal(t, tc.expectedReply, reply) 278 279 // Check that the read buffer is cleaned. 280 require.Zero(t, c.conn.R.Buffered(), 281 "read buffer not empty") 282 }) 283 } 284 } 285 286 // TestReconnectTCMustBeRunning checks that the tor controller must be running 287 // while calling Reconnect. 288 func TestReconnectTCMustBeRunning(t *testing.T) { 289 // Create a dummy controller. 290 c := &Controller{} 291 292 // Reconnect should fail because the TC is not started. 293 require.Equal(t, errTCNotStarted, c.Reconnect()) 294 295 // Set the started flag. 296 c.started = 1 297 298 // Set the stopped flag so the TC is stopped. 299 c.stopped = 1 300 301 // Reconnect should fail because the TC is stopped. 302 require.Equal(t, errTCStopped, c.Reconnect()) 303 } 304 305 // TestReconnectSucceed tests a reconnection will succeed when the tor 306 // controller is up and running. 307 func TestReconnectSucceed(t *testing.T) { 308 // Create mock server and client connection. 309 proxy := createTestProxy(t) 310 t.Cleanup(proxy.cleanUp) 311 312 // Create a tor controller and mark the controller as started. 313 c := &Controller{ 314 conn: proxy.clientConn, 315 started: 1, 316 controlAddr: proxy.serverAddr, 317 } 318 319 // Close the old conn before reconnection. 320 require.NoError(t, proxy.serverConn.Close()) 321 322 // Accept the connection inside a goroutine. When the client makes a 323 // reconnection, the messages flow is, 324 // 1. the client sends the command PROTOCOLINFO to the server. 325 // 2. the server responds with its protocol version. 326 // 3. the client reads the response and sends the command AUTHENTICATE 327 // to the server 328 // 4. the server responds with the authentication info. 329 // 330 // From the server's PoV, We need to mock two reads and two writes 331 // inside the connection, 332 // 1. read the command PROTOCOLINFO sent from the client. 333 // 2. write protocol info so the client can read it. 334 // 3. read the command AUTHENTICATE sent from the client. 335 // 4. write auth challenge so the client can read it. 336 go func() { 337 // Accept the new connection. 338 server, err := proxy.server.Accept() 339 require.NoError(t, err, "failed to accept") 340 341 t.Logf("server listening on %v, client listening on %v", 342 server.LocalAddr(), server.RemoteAddr()) 343 344 // Read the protocol command from the client. 345 buf := make([]byte, 65535) 346 _, err = server.Read(buf) 347 require.NoError(t, err) 348 349 // Write the protocol info. 350 resp := "250-PROTOCOLINFO 1\n" + 351 "250-AUTH METHODS=NULL\n" + 352 "250 OK\n" 353 _, err = server.Write([]byte(resp)) 354 require.NoErrorf(t, err, "failed to write protocol info") 355 356 // Read the auth info from the client. 357 _, err = server.Read(buf) 358 require.NoError(t, err) 359 360 // Write the auth challenge. 361 resp = "250 AUTHCHALLENGE SERVERHASH=fake\n" 362 _, err = server.Write([]byte(resp)) 363 require.NoErrorf(t, err, "failed to write auth challenge") 364 }() 365 366 // Reconnect should succeed. 367 require.NoError(t, c.Reconnect()) 368 369 // Check that the old connection is closed. 370 _, err := proxy.clientConn.ReadLine() 371 require.Contains(t, err.Error(), "use of closed network connection") 372 373 // Check that the connection has been updated. 374 require.NotEqual(t, proxy.clientConn, c.conn) 375 } 376 377 // TestParseTorReply tests that Tor replies are parsed correctly. 378 func TestParseTorReply(t *testing.T) { 379 testCase := []struct { 380 reply string 381 expectedParams map[string]string 382 }{ 383 { 384 // Test a regular reply. 385 reply: `VERSION Tor="0.4.7.8"`, 386 expectedParams: map[string]string{ 387 "Tor": "0.4.7.8", 388 }, 389 }, 390 { 391 // Test a reply with multiple values, one of them 392 // containing spaces. 393 reply: `AUTH METHODS=COOKIE,SAFECOOKIE,HASHEDPASSWORD` + 394 ` COOKIEFILE="/path/with/spaces/Tor Browser/c` + 395 `ontrol_auth_cookie"`, 396 expectedParams: map[string]string{ 397 "METHODS": "COOKIE,SAFECOOKIE,HASHEDPASSWORD", 398 "COOKIEFILE": "/path/with/spaces/Tor Browser/" + 399 "control_auth_cookie", 400 }, 401 }, 402 { 403 // Test a multiline reply. 404 reply: "ServiceID=id\r\nOK", 405 expectedParams: map[string]string{"ServiceID": "id"}, 406 }, 407 { 408 // Test a reply with invalid parameters. 409 reply: "AUTH =invalid", 410 expectedParams: map[string]string{}, 411 }, 412 { 413 // Test escaping arbitrary characters. 414 reply: `PARAM="esca\ped \"doub\lequotes\""`, 415 expectedParams: map[string]string{ 416 `PARAM`: `escaped "doublequotes"`, 417 }, 418 }, 419 { 420 // Test escaping backslashes. Each single backslash 421 // should be removed, each double backslash replaced 422 // with a single one. Note that the single backslash 423 // before the space escapes the space character, so 424 // there's two spaces in a row. 425 reply: `PARAM="escaped \\ \ \\\\"`, 426 expectedParams: map[string]string{ 427 `PARAM`: `escaped \ \\`, 428 }, 429 }, 430 } 431 432 for _, tc := range testCase { 433 params := parseTorReply(tc.reply) 434 require.Equal(t, tc.expectedParams, params) 435 } 436 } 437 438 const ( 439 // listenerFormat is the format string that is used to generate local 440 // listener addresses. 441 listenerFormat = "127.0.0.1:%d" 442 443 // defaultNodePort is the start of the range for listening ports of 444 // harness nodes. Ports are monotonically increasing starting from this 445 // number and are determined by the results of NextAvailablePort(). 446 defaultNodePort int = 10000 447 448 // uniquePortFile is the name of the file that is used to store the 449 // last port that was used by a node. This is used to make sure that 450 // the same port is not used by multiple nodes at the same time. The 451 // file is located in the temp directory of a system. 452 uniquePortFile = "rpctest-port" 453 ) 454 455 var ( 456 // portFileMutex is a mutex that is used to make sure that the port file 457 // is not accessed by multiple goroutines of the same process at the 458 // same time. This is used in conjunction with the lock file to make 459 // sure that the port file is not accessed by multiple processes at the 460 // same time either. So the lock file is to guard between processes and 461 // the mutex is to guard between goroutines of the same process. 462 portFileMutex sync.Mutex 463 ) 464 465 // nextAvailablePort returns the first port that is available for listening by a 466 // new node, using a lock file to make sure concurrent access for parallel tasks 467 // on the same system don't re-use the same port. 468 // 469 // NOTE: This is a copy of `lntest/port`. Since `lnd/tor` is a submodule, it 470 // cannot import the port module `lntest/port` so we need to re-define it here. 471 func nextAvailablePort() int { 472 portFileMutex.Lock() 473 defer portFileMutex.Unlock() 474 475 lockFile := filepath.Join(os.TempDir(), uniquePortFile+".lock") 476 timeout := time.After(10 * time.Second) 477 478 var ( 479 lockFileHandle *os.File 480 err error 481 ) 482 for { 483 // Attempt to acquire the lock file. If it already exists, wait 484 // for a bit and retry. 485 lockFileHandle, err = os.OpenFile( 486 lockFile, os.O_CREATE|os.O_EXCL, 0600, 487 ) 488 if err == nil { 489 // Lock acquired. 490 break 491 } 492 493 // Wait for a bit and retry. 494 select { 495 case <-timeout: 496 panic("timeout waiting for lock file") 497 case <-time.After(10 * time.Millisecond): 498 } 499 } 500 501 // Release the lock file when we're done. 502 defer func() { 503 // Always close file first, Windows won't allow us to remove it 504 // otherwise. 505 _ = lockFileHandle.Close() 506 err := os.Remove(lockFile) 507 if err != nil { 508 panic(fmt.Errorf("couldn't remove lock file: %w", err)) 509 } 510 }() 511 512 portFile := filepath.Join(os.TempDir(), uniquePortFile) 513 port, err := os.ReadFile(portFile) 514 if err != nil { 515 if !os.IsNotExist(err) { 516 panic(fmt.Errorf("error reading port file: %w", err)) 517 } 518 port = []byte(strconv.Itoa(defaultNodePort)) 519 } 520 521 lastPort, err := strconv.Atoi(string(port)) 522 if err != nil { 523 panic(fmt.Errorf("error parsing port: %w", err)) 524 } 525 526 // We take the next one. 527 lastPort++ 528 for lastPort < 65535 { 529 // If there are no errors while attempting to listen on this 530 // port, close the socket and return it as available. While it 531 // could be the case that some other process picks up this port 532 // between the time the socket is closed, and it's reopened in 533 // the harness node, in practice in CI servers this seems much 534 // less likely than simply some other process already being 535 // bound at the start of the tests. 536 addr := fmt.Sprintf(listenerFormat, lastPort) 537 l, err := net.Listen("tcp4", addr) 538 if err == nil { 539 err := l.Close() 540 if err == nil { 541 err := os.WriteFile( 542 portFile, 543 []byte(strconv.Itoa(lastPort)), 0600, 544 ) 545 if err != nil { 546 panic(fmt.Errorf("error updating "+ 547 "port file: %w", err)) 548 } 549 550 return lastPort 551 } 552 } 553 lastPort++ 554 555 // Start from the beginning if we reached the end of the port 556 // range. We need to do this because the lock file now is 557 // persistent across runs on the same machine during the same 558 // boot/uptime cycle. So in order to make this work on 559 // developer's machines, we need to reset the port to the 560 // default value when we reach the end of the range. 561 if lastPort == 65535 { 562 lastPort = defaultNodePort 563 } 564 } 565 566 // No ports available? Must be a mistake. 567 panic("no ports available for listening") 568 }