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 }