github.com/decred/dcrlnd@v0.7.6/tor/cmd_info.go (about)

     1  package tor
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  )
     8  
     9  var (
    10  	// ErrServiceNotCreated is used when we want to query info on an onion
    11  	// service while it's not been created yet.
    12  	ErrServiceNotCreated = errors.New("onion service hasn't been created")
    13  
    14  	// ErrServiceIDMismatch is used when the serviceID the controller has
    15  	// doesn't match the serviceID the Tor daemon has.
    16  	ErrServiceIDMismatch = errors.New("onion serviceIDs don't match")
    17  
    18  	// ErrNoServiceFound is used when the Tor daemon replies no active
    19  	// onion services found for the current control connection while we
    20  	// expect one.
    21  	ErrNoServiceFound = errors.New("no active service found")
    22  )
    23  
    24  // CheckOnionService checks that the onion service created by the controller
    25  // is active. It queries the Tor daemon using the endpoint "onions/current" to
    26  // get the current onion service and checks that service ID matches the
    27  // activeServiceID.
    28  func (c *Controller) CheckOnionService() error {
    29  	// Check that we have a hidden service created.
    30  	if c.activeServiceID == "" {
    31  		return ErrServiceNotCreated
    32  	}
    33  
    34  	// Fetch the onion services that live in current control connection.
    35  	cmd := "GETINFO onions/current"
    36  	code, reply, err := c.sendCommand(cmd)
    37  
    38  	// Exit early if we got an error or Tor daemon didn't respond success.
    39  	// TODO(yy): unify the usage of err and code so we could rely on a
    40  	// single source to change our state.
    41  	if err != nil || code != success {
    42  		log.Debugf("query service:%v got err:%v, reply:%v",
    43  			c.activeServiceID, err, reply)
    44  
    45  		return fmt.Errorf("%w: %v", err, reply)
    46  	}
    47  
    48  	// Parse the reply, which should have the following format,
    49  	//      onions/current=serviceID
    50  	// After parsing, we get a map as,
    51  	// 	[onion/current: serviceID]
    52  	//
    53  	// NOTE: our current tor controller does NOT support multiple onion
    54  	// services to be created at the same time, thus we expect the reply to
    55  	// only contain one serviceID. If multiple serviceIDs are returned, we
    56  	// would expected the reply to have the following format,
    57  	//      onions/current=serviceID1, serviceID2, serviceID3,...
    58  	// Thus a new parser is need to parse that reply.
    59  	resp := parseTorReply(reply)
    60  	serviceID, ok := resp["onions/current"]
    61  	if !ok {
    62  		return ErrNoServiceFound
    63  	}
    64  
    65  	// Check that our active service is indeed the service acknowledged by
    66  	// Tor daemon. The controller is only aware of a single service but the
    67  	// Tor daemon might have multiple services registered (for example for
    68  	// the watchtower as well as the node p2p connections). So we just want
    69  	// to check that our current controller's ID is contained in the list of
    70  	// registered services.
    71  	if !strings.Contains(serviceID, c.activeServiceID) {
    72  		return fmt.Errorf("%w: controller has: %v, Tor daemon has: %v",
    73  			ErrServiceIDMismatch, c.activeServiceID, serviceID)
    74  	}
    75  
    76  	return nil
    77  }