mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-11-23 04:28:32 +01:00
tor: add a new response reader for tor controller
This commit adds a new response reader which replaces the old textproto.Reader.ReadResponse. The older reader cannot handle the case when the reply from Tor server contains a data reply line, which uses the symbol "+" to signal such a case.
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
package tor
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"net"
|
||||
"net/textproto"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestParseTorVersion is a series of tests for different version strings that
|
||||
// check the correctness of determining whether they support creating v3 onion
|
||||
@@ -74,3 +80,199 @@ func TestParseTorVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testProxy emulates a Tor daemon and contains the info used for the tor
|
||||
// controller to make connections.
|
||||
type testProxy struct {
|
||||
// server is the proxy listener.
|
||||
server net.Listener
|
||||
|
||||
// serverConn is the established connection from the server side.
|
||||
serverConn net.Conn
|
||||
|
||||
// serverAddr is the tcp address the proxy is listening on.
|
||||
serverAddr string
|
||||
|
||||
// clientConn is the established connection from the client side.
|
||||
clientConn *textproto.Conn
|
||||
}
|
||||
|
||||
// cleanUp is used after each test to properly close the ports/connections.
|
||||
func (tp *testProxy) cleanUp() {
|
||||
// Don't bother cleanning if there's no a server created.
|
||||
if tp.server == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := tp.clientConn.Close(); err != nil {
|
||||
log.Errorf("closing client conn got err: %v", err)
|
||||
}
|
||||
if err := tp.server.Close(); err != nil {
|
||||
log.Errorf("closing proxy server got err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// createTestProxy creates a proxy server to listen on a random address,
|
||||
// creates a server and a client connection, and initializes a testProxy using
|
||||
// these params.
|
||||
func createTestProxy(t *testing.T) *testProxy {
|
||||
// Set up the proxy to listen on given port.
|
||||
//
|
||||
// NOTE: we use a port 0 here to indicate we want a free port selected
|
||||
// by the system.
|
||||
proxy, err := net.Listen("tcp", ":0")
|
||||
require.NoError(t, err, "failed to create proxy")
|
||||
|
||||
t.Logf("created proxy server to listen on address: %v", proxy.Addr())
|
||||
|
||||
// Accept the connection inside a goroutine.
|
||||
serverChan := make(chan net.Conn, 1)
|
||||
go func(result chan net.Conn) {
|
||||
conn, err := proxy.Accept()
|
||||
require.NoError(t, err, "failed to accept")
|
||||
|
||||
result <- conn
|
||||
}(serverChan)
|
||||
|
||||
// Create the connection using tor controller.
|
||||
client, err := textproto.Dial("tcp", proxy.Addr().String())
|
||||
require.NoError(t, err, "failed to create connection")
|
||||
|
||||
tc := &testProxy{
|
||||
server: proxy,
|
||||
serverConn: <-serverChan,
|
||||
serverAddr: proxy.Addr().String(),
|
||||
clientConn: client,
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
// TestReadResponse contructs a series of possible responses returned by Tor
|
||||
// and asserts the readResponse can handle them correctly.
|
||||
func TestReadResponse(t *testing.T) {
|
||||
// Create mock server and client connection.
|
||||
proxy := createTestProxy(t)
|
||||
defer proxy.cleanUp()
|
||||
server := proxy.serverConn
|
||||
|
||||
// Create a dummy tor controller.
|
||||
c := &Controller{conn: proxy.clientConn}
|
||||
|
||||
testCase := []struct {
|
||||
name string
|
||||
serverResp string
|
||||
|
||||
// expectedReply is the reply we expect the readResponse to
|
||||
// return.
|
||||
expectedReply string
|
||||
|
||||
// expectedCode is the code we expect the server to return.
|
||||
expectedCode int
|
||||
|
||||
// returnedCode is the code we expect the readResponse to
|
||||
// return.
|
||||
returnedCode int
|
||||
|
||||
// expectedErr is the error we expect the readResponse to
|
||||
// return.
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
// Test a simple response.
|
||||
name: "succeed on 250",
|
||||
serverResp: "250 OK\n",
|
||||
expectedReply: "OK",
|
||||
expectedCode: 250,
|
||||
returnedCode: 250,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test a mid reply(-) response.
|
||||
name: "succeed on mid reply line",
|
||||
serverResp: "250-field=value\n" +
|
||||
"250 OK\n",
|
||||
expectedReply: "field=value\nOK",
|
||||
expectedCode: 250,
|
||||
returnedCode: 250,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test a data reply(+) response.
|
||||
name: "succeed on data reply line",
|
||||
serverResp: "250+field=\n" +
|
||||
"line1\n" +
|
||||
"line2\n" +
|
||||
".\n" +
|
||||
"250 OK\n",
|
||||
expectedReply: "field=line1,line2\nOK",
|
||||
expectedCode: 250,
|
||||
returnedCode: 250,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test a mixed reply response.
|
||||
name: "succeed on mixed reply line",
|
||||
serverResp: "250-field=value\n" +
|
||||
"250+field=\n" +
|
||||
"line1\n" +
|
||||
"line2\n" +
|
||||
".\n" +
|
||||
"250 OK\n",
|
||||
expectedReply: "field=value\nfield=line1,line2\nOK",
|
||||
expectedCode: 250,
|
||||
returnedCode: 250,
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
// Test unexpected code.
|
||||
name: "fail on codes not matched",
|
||||
serverResp: "250 ERR\n",
|
||||
expectedReply: "ERR",
|
||||
expectedCode: 500,
|
||||
returnedCode: 250,
|
||||
expectedErr: errCodeNotMatch,
|
||||
},
|
||||
{
|
||||
// Test short response error.
|
||||
name: "fail on short response",
|
||||
serverResp: "123\n250 OK\n",
|
||||
expectedReply: "",
|
||||
expectedCode: 250,
|
||||
returnedCode: 0,
|
||||
expectedErr: textproto.ProtocolError(
|
||||
"short line: 123"),
|
||||
},
|
||||
{
|
||||
// Test short response error.
|
||||
name: "fail on invalid response",
|
||||
serverResp: "250?OK\n",
|
||||
expectedReply: "",
|
||||
expectedCode: 250,
|
||||
returnedCode: 250,
|
||||
expectedErr: textproto.ProtocolError(
|
||||
"invalid line: 250?OK"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCase {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
// Let the server mocks a given response.
|
||||
_, err := server.Write([]byte(tc.serverResp))
|
||||
require.NoError(t, err, "server failed to write")
|
||||
|
||||
// Read the response and checks all expectations
|
||||
// satisfied.
|
||||
code, reply, err := c.readResponse(tc.expectedCode)
|
||||
require.Equal(t, tc.expectedErr, err)
|
||||
require.Equal(t, tc.returnedCode, code)
|
||||
require.Equal(t, tc.expectedReply, reply)
|
||||
|
||||
// Check that the read buffer is cleaned.
|
||||
require.Zero(t, c.conn.R.Buffered(),
|
||||
"read buffer not empty")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user