diff --git a/config.go b/config.go index 4cfbb8488..b81ebcdae 100644 --- a/config.go +++ b/config.go @@ -5,6 +5,7 @@ package main import ( + "errors" "fmt" "io/ioutil" "net" @@ -22,7 +23,7 @@ import ( "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/torsvc" + "github.com/lightningnetwork/lnd/tor" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil" ) @@ -50,6 +51,12 @@ const ( defaultMaxLogFiles = 3 defaultMaxLogFileSize = 10 + defaultTorSOCKSPort = 9050 + defaultTorDNSHost = "soa.nodes.lightning.directory" + defaultTorDNSPort = 53 + defaultTorControlPort = 9051 + defaultTorV2PrivateKeyFilename = "v2_onion_private_key" + defaultBroadcastDelta = 10 // minTimeLockDelta is the minimum timelock we require for incoming @@ -81,6 +88,11 @@ var ( defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false) defaultLitecoindDir = btcutil.AppDataDir("litecoin", false) + + defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort)) + defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort)) + defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort)) + defaultTorV2PrivateKeyPath = filepath.Join(defaultLndDir, defaultTorV2PrivateKeyFilename) ) type chainConfig struct { @@ -136,9 +148,14 @@ type autoPilotConfig struct { } type torConfig struct { - Socks string `long:"socks" description:"The port that Tor's exposed SOCKS5 proxy is listening on. Using Tor allows outbound-only connections (listening will be disabled) -- NOTE port must be between 1024 and 65535"` - DNS string `long:"dns" description:"The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` - StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` + Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"` + SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"` + DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` + StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` + Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"` + V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"` + V2PrivateKeyPath string `long:"v2privatekeypath" description:"The path to the private key of the onion service being created"` + V3 bool `long:"v3" description:"Use a v3 onion service to listen for inbound connections"` } // config defines the configuration options for lnd. @@ -188,7 +205,7 @@ type config struct { LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"` LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"` - Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"` + Autopilot *autoPilotConfig `group:"Autopilot" namespace:"autopilot"` Tor *torConfig `group:"Tor" namespace:"tor"` @@ -206,7 +223,7 @@ type config struct { NoChanUpdates bool `long:"nochanupdates" description:"If specified, lnd will not request real-time channel updates from connected peers. This option should be used by routing nodes to save bandwidth."` - net torsvc.Net + net tor.Net } // loadConfig initializes and parses the config using a config file and command @@ -275,6 +292,13 @@ func loadConfig() (*config, error) { Alias: defaultAlias, Color: defaultColor, MinChanSize: int64(minChanFundingSize), + Tor: &torConfig{ + SOCKS: defaultTorSOCKS, + DNS: defaultTorDNS, + Control: defaultTorControl, + V2PrivateKeyPath: defaultTorV2PrivateKeyPath, + }, + net: &tor.ClearNet{}, } // Pre-parse the command line options to pick up an alternative config @@ -305,6 +329,7 @@ func loadConfig() (*config, error) { defaultCfg.InvoiceMacPath = filepath.Join(lndDir, defaultInvoiceMacFilename) defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename) defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname) + defaultCfg.Tor.V2PrivateKeyPath = filepath.Join(lndDir, defaultTorV2PrivateKeyFilename) } // Create the lnd directory if it doesn't already exist. @@ -354,53 +379,83 @@ func loadConfig() (*config, error) { cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir) cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir) cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir) + cfg.Tor.V2PrivateKeyPath = cleanAndExpandPath(cfg.Tor.V2PrivateKeyPath) - // Setup dial and DNS resolution functions depending on the specified - // options. The default is to use the standard golang "net" package - // functions. When Tor's proxy is specified, the dial function is set to - // the proxy specific dial function and the DNS resolution functions use - // Tor. - cfg.net = &torsvc.RegularNet{} - if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" { - // Validate Tor port number - torport, err := strconv.Atoi(cfg.Tor.Socks) - if err != nil || torport < 1024 || torport > 65535 { - str := "%s: The tor socks5 port must be between 1024 and 65535" - err := fmt.Errorf(str, funcName) - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, usageMessage) - return nil, err - } - - // If ExternalIPs is set, throw an error since we cannot - // listen for incoming connections via Tor's SOCKS5 proxy. - if len(cfg.ExternalIPs) != 0 { - str := "%s: Cannot set externalip flag with proxy flag - " + - "cannot listen for incoming connections via Tor's " + - "socks5 proxy" - err := fmt.Errorf(str, funcName) - return nil, err - } - - cfg.net = &torsvc.TorProxyNet{ - TorDNS: cfg.Tor.DNS, - TorSocks: cfg.Tor.Socks, - StreamIsolation: cfg.Tor.StreamIsolation, - } - - // If we are using Tor, since we only want connections routed - // through Tor, listening is disabled. - cfg.DisableListen = true - - } else if cfg.Tor.Socks != "" || cfg.Tor.DNS != "" { - // Both TorSocks and TorDNS must be set. - str := "%s: Both the tor.socks and the tor.dns flags must be set" + - "to properly route connections and avoid DNS leaks while" + - "using Tor" + // Ensure that the user didn't attempt to specify negative values for + // any of the autopilot params. + if cfg.Autopilot.MaxChannels < 0 { + str := "%s: autopilot.maxchannels must be non-negative" err := fmt.Errorf(str, funcName) + fmt.Fprintln(os.Stderr, err) + return nil, err + } + if cfg.Autopilot.Allocation < 0 { + str := "%s: autopilot.allocation must be non-negative" + err := fmt.Errorf(str, funcName) + fmt.Fprintln(os.Stderr, err) + return nil, err + } + if cfg.Autopilot.MinChannelSize < 0 { + str := "%s: autopilot.minchansize must be non-negative" + err := fmt.Errorf(str, funcName) + fmt.Fprintln(os.Stderr, err) + return nil, err + } + if cfg.Autopilot.MaxChannelSize < 0 { + str := "%s: autopilot.maxchansize must be non-negative" + err := fmt.Errorf(str, funcName) + fmt.Fprintln(os.Stderr, err) return nil, err } + // Ensure that the specified values for the min and max channel size + // don't are within the bounds of the normal chan size constraints. + if cfg.Autopilot.MinChannelSize < int64(minChanFundingSize) { + cfg.Autopilot.MinChannelSize = int64(minChanFundingSize) + } + if cfg.Autopilot.MaxChannelSize > int64(maxFundingAmount) { + cfg.Autopilot.MaxChannelSize = int64(maxFundingAmount) + } + + // Validate the Tor config parameters. + cfg.Tor.SOCKS = normalizeAddress( + cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort), + ) + cfg.Tor.DNS = normalizeAddress( + cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort), + ) + cfg.Tor.Control = normalizeAddress( + cfg.Tor.Control, strconv.Itoa(defaultTorControlPort), + ) + switch { + case cfg.Tor.V2 && cfg.Tor.V3: + return nil, errors.New("either tor.v2 or tor.v3 can be set, " + + "but not both") + case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3): + return nil, errors.New("listening must be enabled when " + + "enabling inbound connections over Tor") + case cfg.Tor.Active && (!cfg.Tor.V2 || !cfg.Tor.V3): + // If an onion service version wasn't selected, we'll assume the + // user is only interested in outbound connections over Tor. + // Therefore, we'll disable listening in order to avoid + // inadvertent leaks. + cfg.DisableListen = true + } + + // Set up the network-related functions that will be used throughout + // the daemon. We use the standard Go "net" package functions by + // default. If we should be proxying all traffic through Tor, then + // we'll use the Tor proxy specific functions in order to avoid leaking + // our real information. + if cfg.Tor.Active { + cfg.net = &tor.ProxyNet{ + SOCKS: cfg.Tor.SOCKS, + DNS: cfg.Tor.DNS, + StreamIsolation: cfg.Tor.StreamIsolation, + } + } + + // Determine the active chain configuration and its parameters. switch { // At this moment, multiple active chains are not supported. case cfg.Litecoin.Active && cfg.Bitcoin.Active: @@ -734,6 +789,23 @@ func loadConfig() (*config, error) { cfg.Listeners = normalizeAddresses(cfg.Listeners, strconv.Itoa(defaultPeerPort)) + // Finally, ensure that we are only listening on localhost if Tor + // inbound support is enabled. + if cfg.Tor.V2 || cfg.Tor.V3 { + for _, addr := range cfg.Listeners { + // Due to the addresses being normalized above, we can + // skip checking the error. + host, _, _ := net.SplitHostPort(addr) + if host == "localhost" || host == "127.0.0.1" { + continue + } + + return nil, errors.New("lnd must *only* be listening " + + "on localhost when running with Tor inbound " + + "support enabled") + } + } + // Warn about missing config file only after all other configuration is // done. This prevents the warning on help messages and invalid // options. Note this should go directly before the return. @@ -1113,15 +1185,7 @@ func normalizeAddresses(addrs []string, defaultPort string) []string { result := make([]string, 0, len(addrs)) seen := map[string]struct{}{} for _, addr := range addrs { - if _, _, err := net.SplitHostPort(addr); err != nil { - // If the address is an integer, then we assume it is *only* a - // port and default to binding to that port on localhost - if _, err := strconv.Atoi(addr); err == nil { - addr = net.JoinHostPort("localhost", addr) - } else { - addr = net.JoinHostPort(addr, defaultPort) - } - } + addr = normalizeAddress(addr, defaultPort) if _, ok := seen[addr]; !ok { result = append(result, addr) seen[addr] = struct{}{} @@ -1130,6 +1194,24 @@ func normalizeAddresses(addrs []string, defaultPort string) []string { return result } +// normalizeAddress normalizes an address by either setting a missing host to +// localhost or missing port to the default port. +func normalizeAddress(addr, defaultPort string) string { + if _, _, err := net.SplitHostPort(addr); err != nil { + // If the address is an integer, then we assume it is *only* a + // port and default to binding to that port on localhost. + if _, err := strconv.Atoi(addr); err == nil { + return net.JoinHostPort("localhost", addr) + } + + // Otherwise, the address only contains the host so we'll use + // the default port. + return net.JoinHostPort(addr, defaultPort) + } + + return addr +} + // enforceSafeAuthentication enforces "safe" authentication taking into account // the interfaces that the RPC servers are listening on, and if macaroons are // activated or not. To project users from using dangerous config combinations, diff --git a/lnd.go b/lnd.go index cfec4683f..edae743a1 100644 --- a/lnd.go +++ b/lnd.go @@ -304,11 +304,10 @@ func lndMain() error { } idPrivKey.Curve = btcec.S256() - if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" { + if cfg.Tor.Active { srvrLog.Infof("Proxying all network traffic via Tor "+ - "(stream_isolation=%v)! NOTE: If running with a full-node "+ - "backend, ensure that is proxying over Tor as well", - cfg.Tor.StreamIsolation) + "(stream_isolation=%v)! NOTE: Ensure the backend node "+ + "is proxying over Tor as well", cfg.Tor.StreamIsolation) } // Set up the core server which will listen for incoming peer