mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 06:21:40 +01:00
tor: add GETINFO method to check liveness of onion service
This commit is contained in:
parent
7daffcbba8
commit
2800c4364e
2 changed files with 159 additions and 0 deletions
72
tor/cmd_info.go
Normal file
72
tor/cmd_info.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package tor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrServiceNotCreated is used when we want to query info on an onion
|
||||
// service while it's not been created yet.
|
||||
ErrServiceNotCreated = errors.New("onion service hasn't been created")
|
||||
|
||||
// ErrServiceIDUnmatch is used when the serviceID the controller has
|
||||
// doesn't match the serviceID the Tor daemon has.
|
||||
ErrServiceIDUnmatch = errors.New("onion serviceIDs not match")
|
||||
|
||||
// ErrNoServiceFound is used when the Tor daemon replies no active
|
||||
// onion services found for the current control connection while we
|
||||
// expect one.
|
||||
ErrNoServiceFound = errors.New("no active service found")
|
||||
)
|
||||
|
||||
// CheckOnionService checks that the onion service created by the controller
|
||||
// is active. It queries the Tor daemon using the endpoint "onions/current" to
|
||||
// get the current onion service and checks that service ID matches the
|
||||
// activeServiceID.
|
||||
func (c *Controller) CheckOnionService() error {
|
||||
// Check that we have a hidden service created.
|
||||
if c.activeServiceID == "" {
|
||||
return ErrServiceNotCreated
|
||||
}
|
||||
|
||||
// Fetch the onion services that live in current control connection.
|
||||
cmd := "GETINFO onions/current"
|
||||
code, reply, err := c.sendCommand(cmd)
|
||||
|
||||
// Exit early if we got an error or Tor daemon didn't respond success.
|
||||
// TODO(yy): unify the usage of err and code so we could rely on a
|
||||
// single source to change our state.
|
||||
if err != nil || code != success {
|
||||
log.Debugf("query service:%v got err:%v, reply:%v",
|
||||
c.activeServiceID, err, reply)
|
||||
|
||||
return fmt.Errorf("%w: %v", err, reply)
|
||||
}
|
||||
|
||||
// Parse the reply, which should have the following format,
|
||||
// onions/current=serviceID
|
||||
// After parsing, we get a map as,
|
||||
// [onion/current: serviceID]
|
||||
//
|
||||
// NOTE: our current tor controller does NOT support multiple onion
|
||||
// services to be created at the same time, thus we expect the reply to
|
||||
// only contain one serviceID. If multiple serviceIDs are returned, we
|
||||
// would expected the reply to have the following format,
|
||||
// onions/current=serviceID1, serviceID2, serviceID3,...
|
||||
// Thus a new parser is need to parse that reply.
|
||||
resp := parseTorReply(reply)
|
||||
serviceID, ok := resp["onions/current"]
|
||||
if !ok {
|
||||
return ErrNoServiceFound
|
||||
}
|
||||
|
||||
// Check that our active service is indeed the service acknowledged by
|
||||
// Tor daemon.
|
||||
if c.activeServiceID != serviceID {
|
||||
return fmt.Errorf("%w: controller has: %v, Tor daemon has: %v",
|
||||
ErrServiceIDUnmatch, c.activeServiceID, serviceID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
87
tor/cmd_info_test.go
Normal file
87
tor/cmd_info_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package tor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckOnionServiceFailOnServiceNotCreated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a dummy tor controller.
|
||||
c := &Controller{}
|
||||
|
||||
// Check that CheckOnionService returns an error when the service
|
||||
// hasn't been created.
|
||||
require.Equal(t, ErrServiceNotCreated, c.CheckOnionService())
|
||||
}
|
||||
|
||||
func TestCheckOnionServiceSucceed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create mock server and client connection.
|
||||
proxy := createTestProxy(t)
|
||||
defer proxy.cleanUp()
|
||||
server := proxy.serverConn
|
||||
|
||||
// Assign a fake service ID to the controller.
|
||||
c := &Controller{conn: proxy.clientConn, activeServiceID: "fakeID"}
|
||||
|
||||
// Test a successful response.
|
||||
serverResp := "250-onions/current=fakeID\n250 OK\n"
|
||||
|
||||
// Let the server mocks a given response.
|
||||
_, err := server.Write([]byte(serverResp))
|
||||
require.NoError(t, err, "server failed to write")
|
||||
|
||||
// For a successful response, we expect no error.
|
||||
require.NoError(t, c.CheckOnionService())
|
||||
}
|
||||
|
||||
func TestCheckOnionServiceFailOnServiceIDNotMatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create mock server and client connection.
|
||||
proxy := createTestProxy(t)
|
||||
defer proxy.cleanUp()
|
||||
server := proxy.serverConn
|
||||
|
||||
// Assign a fake service ID to the controller.
|
||||
c := &Controller{conn: proxy.clientConn, activeServiceID: "fakeID"}
|
||||
|
||||
// Mock a response with a different serviceID.
|
||||
serverResp := "250-onions/current=unmatchedID\n250 OK\n"
|
||||
|
||||
// Let the server mocks a given response.
|
||||
_, err := server.Write([]byte(serverResp))
|
||||
require.NoError(t, err, "server failed to write")
|
||||
|
||||
// Check the error returned from GetServiceInfo is expected.
|
||||
require.ErrorIs(t, c.CheckOnionService(), ErrServiceIDUnmatch)
|
||||
}
|
||||
|
||||
func TestCheckOnionServiceFailOnClosedConnection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create mock server and client connection.
|
||||
proxy := createTestProxy(t)
|
||||
defer proxy.cleanUp()
|
||||
server := proxy.serverConn
|
||||
|
||||
// Assign a fake service ID to the controller.
|
||||
c := &Controller{conn: proxy.clientConn, activeServiceID: "fakeID"}
|
||||
|
||||
// Close the connection from the server side.
|
||||
require.NoError(t, server.Close(), "server failed to close conn")
|
||||
|
||||
// Check the error returned from GetServiceInfo is expected.
|
||||
err := c.CheckOnionService()
|
||||
eof := errors.Is(err, io.EOF)
|
||||
reset := errors.Is(err, syscall.ECONNRESET)
|
||||
require.Truef(t, eof || reset,
|
||||
"must of EOF or RESET error, instead got: %v", err)
|
||||
}
|
Loading…
Add table
Reference in a new issue