lnd/tor/cmd_info.go
Oliver Gugger 5155ebc405
tor+healthcheck: fix healthcheck for multiple services
Fixes #6013.
This commit fixes the Tor healthcheck that would previously fail if
there were multiple hidden service registered.
In the controller, we only need to know that our service is contained in
the list of active services. But we can't do a string equality check
since there might be multiple services, comma separated.
2021-11-23 09:53:36 +01:00

78 lines
2.7 KiB
Go

package tor
import (
"errors"
"fmt"
"strings"
)
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")
// ErrServiceIDMismatch is used when the serviceID the controller has
// doesn't match the serviceID the Tor daemon has.
ErrServiceIDMismatch = errors.New("onion serviceIDs don't 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. The controller is only aware of a single service but the
// Tor daemon might have multiple services registered (for example for
// the watchtower as well as the node p2p connections). So we just want
// to check that our current controller's ID is contained in the list of
// registered services.
if !strings.Contains(serviceID, c.activeServiceID) {
return fmt.Errorf("%w: controller has: %v, Tor daemon has: %v",
ErrServiceIDMismatch, c.activeServiceID, serviceID)
}
return nil
}