lnd+server+watchtower: allow Standalone access to the tor controller

This commit lets the watchtower automatically create hidden services
by giving it a pointer to a TorController. The server was also slightly
refactored so that it was not the sole owner of the TorController.
This commit is contained in:
nsa 2020-03-17 10:58:36 -04:00
parent f1fd5e86c0
commit ada0b78dfc
4 changed files with 107 additions and 28 deletions

44
lnd.go

@ -45,6 +45,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/walletunlocker"
"github.com/lightningnetwork/lnd/watchtower"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
@ -473,6 +474,28 @@ func Main(lisCfg ListenerCfg) error {
defer towerClientDB.Close()
}
// If tor is active and either v2 or v3 onion services have been specified,
// make a tor controller and pass it into both the watchtower server and
// the regular lnd server.
var torController *tor.Controller
if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) {
torController = tor.NewController(
cfg.Tor.Control, cfg.Tor.TargetIPAddress, cfg.Tor.Password,
)
// Start the tor controller before giving it to any other subsystems.
if err := torController.Start(); err != nil {
err := fmt.Errorf("unable to initialize tor controller: %v", err)
ltndLog.Error(err)
return err
}
defer func() {
if err := torController.Stop(); err != nil {
ltndLog.Errorf("error stopping tor controller: %v", err)
}
}()
}
var tower *watchtower.Standalone
if cfg.Watchtower.Active {
// Segment the watchtower directory by chain and network.
@ -506,7 +529,7 @@ func Main(lisCfg ListenerCfg) error {
return err
}
wtConfig, err := cfg.Watchtower.Apply(&watchtower.Config{
wtCfg := &watchtower.Config{
BlockFetcher: activeChainControl.chainIO,
DB: towerDB,
EpochRegistrar: activeChainControl.chainNotifier,
@ -519,7 +542,23 @@ func Main(lisCfg ListenerCfg) error {
NodePrivKey: towerPrivKey,
PublishTx: activeChainControl.wallet.PublishTransaction,
ChainHash: *activeNetParams.GenesisHash,
}, lncfg.NormalizeAddresses)
}
// If there is a tor controller (user wants auto hidden services), then
// store a pointer in the watchtower config.
if torController != nil {
wtCfg.TorController = torController
wtCfg.WatchtowerKeyPath = cfg.Tor.WatchtowerKeyPath
switch {
case cfg.Tor.V2:
wtCfg.Type = tor.V2
case cfg.Tor.V3:
wtCfg.Type = tor.V3
}
}
wtConfig, err := cfg.Watchtower.Apply(wtCfg, lncfg.NormalizeAddresses)
if err != nil {
err := fmt.Errorf("Unable to configure watchtower: %v",
err)
@ -543,6 +582,7 @@ func Main(lisCfg ListenerCfg) error {
server, err := newServer(
cfg.Listeners, chanDB, towerClientDB, activeChainControl,
idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor,
torController,
)
if err != nil {
err := fmt.Errorf("Unable to create server: %v", err)

@ -322,7 +322,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
towerClientDB *wtdb.ClientDB, cc *chainControl,
privKey *btcec.PrivateKey,
chansToRestore walletunlocker.ChannelsToRecover,
chanPredicate chanacceptor.ChannelAcceptor) (*server, error) {
chanPredicate chanacceptor.ChannelAcceptor,
torController *tor.Controller) (*server, error) {
var err error
@ -428,6 +429,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
// schedule
sphinx: hop.NewOnionProcessor(sphinxRouter),
torController: torController,
persistentPeers: make(map[string]bool),
persistentPeersBackoff: make(map[string]time.Duration),
persistentConnReqs: make(map[string][]*connmgr.ConnReq),
@ -598,16 +601,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
selfAddrs = append(selfAddrs, ip)
}
// If we were requested to route connections through Tor and to
// automatically create an onion service, we'll initiate our Tor
// controller and establish a connection to the Tor server.
if cfg.Tor.Active && (cfg.Tor.V2 || cfg.Tor.V3) {
s.torController = tor.NewController(
cfg.Tor.Control, cfg.Tor.TargetIPAddress,
cfg.Tor.Password,
)
}
chanGraph := chanDB.ChannelGraph()
// We'll now reconstruct a node announcement based on our current
@ -1251,7 +1244,7 @@ func (s *server) Start() error {
var startErr error
s.start.Do(func() {
if s.torController != nil {
if err := s.initTorController(); err != nil {
if err := s.createNewHiddenService(); err != nil {
startErr = err
return
}
@ -1442,10 +1435,6 @@ func (s *server) Stop() error {
close(s.quit)
if s.torController != nil {
s.torController.Stop()
}
// Shutdown the wallet, funding manager, and the rpc server.
s.chanStatusMgr.Stop()
s.cc.chainNotifier.Stop()
@ -1965,14 +1954,9 @@ func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{},
}
}
// initTorController initiliazes the Tor controller backed by lnd and
// automatically sets up a v2 onion service in order to listen for inbound
// connections over Tor.
func (s *server) initTorController() error {
if err := s.torController.Start(); err != nil {
return err
}
// createNewHiddenService automatically sets up a v2 or v3 onion service in
// order to listen for inbound connections over Tor.
func (s *server) createNewHiddenService() error {
// Determine the different ports the server is listening on. The onion
// service's virtual port will map to these ports and one will be picked
// at random when the onion service is being accessed.

@ -34,8 +34,8 @@ var (
)
// Config defines the resources and parameters used to configure a Watchtower.
// All nil-able elements with the Config must be set in order for the Watchtower
// to function properly.
// All nil-able elements besides tor-related ones must be set in order for the
// Watchtower to function properly.
type Config struct {
// ChainHash identifies the chain that the watchtower will be monitoring
// for breaches and that will be advertised in the server's Init message
@ -89,4 +89,16 @@ type Config struct {
// message from the other end, if the connection has stopped buffering
// the server's replies.
WriteTimeout time.Duration
// TorController allows the watchtower to optionally setup an onion hidden
// service.
TorController *tor.Controller
// WatchtowerKeyPath allows the watchtower to specify where the private key
// for a watchtower hidden service should be stored.
WatchtowerKeyPath string
// Type specifies the hidden service type (V2 or V3) that the watchtower
// will create.
Type tor.OnionType
}

@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
@ -112,6 +113,15 @@ func (w *Standalone) Start() error {
log.Infof("Starting watchtower")
// If a tor controller exists in the config, then automatically create a
// hidden service for the watchtower to accept inbound connections from.
if w.cfg.TorController != nil {
log.Infof("Creating watchtower hidden service")
if err := w.createNewHiddenService(); err != nil {
return err
}
}
if err := w.lookout.Start(); err != nil {
return err
}
@ -142,6 +152,39 @@ func (w *Standalone) Stop() error {
return nil
}
// createNewHiddenService automatically sets up a v2 or v3 onion service in
// order to listen for inbound connections over Tor.
func (w *Standalone) createNewHiddenService() error {
// Get all the ports the watchtower is listening on. These will be used to
// map the hidden service's virtual port.
listenPorts := make([]int, 0, len(w.listeners))
for _, listener := range w.listeners {
port := listener.Addr().(*net.TCPAddr).Port
listenPorts = append(listenPorts, port)
}
// Once we've created the port mapping, we can automatically create the
// hidden service. The service's private key will be saved on disk in order
// to persistently have access to this hidden service across restarts.
onionCfg := tor.AddOnionConfig{
VirtualPort: DefaultPeerPort,
TargetPorts: listenPorts,
Store: tor.NewOnionFile(w.cfg.WatchtowerKeyPath, 0600),
Type: w.cfg.Type,
}
addr, err := w.cfg.TorController.AddOnion(onionCfg)
if err != nil {
return err
}
// Append this address to ExternalIPs so that it will be exposed in
// tower info calls.
w.cfg.ExternalIPs = append(w.cfg.ExternalIPs, addr)
return nil
}
// PubKey returns the public key for the watchtower used to authentication and
// encrypt traffic with clients.
//