diff --git a/config.go b/config.go index 5b0779c3..949f2503 100644 --- a/config.go +++ b/config.go @@ -166,20 +166,20 @@ type torConfig struct { type config struct { ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` - LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc."` - ConfigFile string `long:"C" long:"configfile" description:"Path to configuration file"` - DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"` - TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"` - TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"` - TLSExtraIP string `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"` - TLSExtraDomain string `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate"` - NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication"` - AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"` - ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"` - InvoiceMacPath string `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"` - LogDir string `long:"logdir" description:"Directory to log output."` - MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"` - MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"` + LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc."` + ConfigFile string `long:"C" long:"configfile" description:"Path to configuration file"` + DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"` + TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"` + TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"` + TLSExtraIP string `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"` + TLSExtraDomain string `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate"` + NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication"` + AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"` + ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"` + InvoiceMacPath string `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"` + LogDir string `long:"logdir" description:"Directory to log output."` + MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"` + MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"` // We'll parse these 'raw' string arguments into real net.Addrs in the // loadConfig function. We need to expose the 'raw' strings so the @@ -193,8 +193,8 @@ type config struct { RESTListeners []net.Addr Listeners []net.Addr ExternalIPs []net.Addr - DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"` - NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"` + DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"` + NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"` DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` @@ -431,25 +431,31 @@ func loadConfig() (*config, error) { // Validate the Tor config parameters. socks, err := lncfg.ParseAddressString( cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err } cfg.Tor.SOCKS = socks.String() + dns, err := lncfg.ParseAddressString( cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err } cfg.Tor.DNS = dns.String() + control, err := lncfg.ParseAddressString( cfg.Tor.Control, strconv.Itoa(defaultTorControlPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err } cfg.Tor.Control = control.String() + switch { case cfg.Tor.V2 && cfg.Tor.V3: return nil, errors.New("either tor.v2 or tor.v3 can be set, " + @@ -817,6 +823,7 @@ func loadConfig() (*config, error) { // duplicate addresses. cfg.RPCListeners, err = lncfg.NormalizeAddresses( cfg.RawRPCListeners, strconv.Itoa(defaultRPCPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err @@ -826,6 +833,7 @@ func loadConfig() (*config, error) { // duplicate addresses. cfg.RESTListeners, err = lncfg.NormalizeAddresses( cfg.RawRESTListeners, strconv.Itoa(defaultRESTPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err @@ -835,6 +843,7 @@ func loadConfig() (*config, error) { // duplicate addresses. cfg.Listeners, err = lncfg.NormalizeAddresses( cfg.RawListeners, strconv.Itoa(defaultPeerPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err @@ -844,6 +853,7 @@ func loadConfig() (*config, error) { // duplicate addresses. cfg.ExternalIPs, err = lncfg.NormalizeAddresses( cfg.RawExternalIPs, strconv.Itoa(defaultPeerPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err @@ -854,7 +864,7 @@ func loadConfig() (*config, error) { // that. for _, p2pListener := range cfg.Listeners { if lncfg.IsUnix(p2pListener) { - err := fmt.Errorf("unix socket addresses cannot be " + + err := fmt.Errorf("unix socket addresses cannot be "+ "used for the p2p connection listener: %s", p2pListener) return nil, err @@ -865,10 +875,7 @@ func loadConfig() (*config, error) { // 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.String()) - if host == "localhost" || host == "127.0.0.1" { + if lncfg.IsLoopback(addr.String()) { continue } diff --git a/lncfg/address.go b/lncfg/address.go index c3c418e4..fb4deb77 100644 --- a/lncfg/address.go +++ b/lncfg/address.go @@ -1,34 +1,44 @@ package lncfg import ( - "time" - "net" - "fmt" "crypto/tls" + "fmt" + "net" + "strconv" "strings" + "time" + + "github.com/lightningnetwork/lnd/tor" ) var ( loopBackAddrs = []string{"localhost", "127.0.0.1", "[::1]"} ) +type tcpResolver = func(network, addr string) (*net.TCPAddr, error) + // NormalizeAddresses returns a new slice with all the passed addresses // normalized with the given default port and all duplicates removed. -func NormalizeAddresses(addrs []string, - defaultPort string) ([]net.Addr, error) { +func NormalizeAddresses(addrs []string, defaultPort string, + tcpResolver tcpResolver) ([]net.Addr, error) { + result := make([]net.Addr, 0, len(addrs)) seen := map[string]struct{}{} - for _, strAddr := range addrs { - addr, err := ParseAddressString(strAddr, defaultPort) + + for _, addr := range addrs { + parsedAddr, err := ParseAddressString( + addr, defaultPort, tcpResolver, + ) if err != nil { return nil, err } - if _, ok := seen[addr.String()]; !ok { - result = append(result, addr) - seen[addr.String()] = struct{}{} + if _, ok := seen[parsedAddr.String()]; !ok { + result = append(result, parsedAddr) + seen[parsedAddr.String()] = struct{}{} } } + return result, nil } @@ -42,7 +52,7 @@ func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error { // on. If it's a localhost address, we'll skip it, otherwise, we'll // return an error if macaroons are inactive. for _, addr := range addrs { - if IsLoopback(addr) || IsUnix(addr) { + if IsLoopback(addr.String()) || IsUnix(addr) { continue } @@ -57,23 +67,21 @@ func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error { return nil } -// ListenOnAddress creates a listener that listens on the given -// address. +// ListenOnAddress creates a listener that listens on the given address. func ListenOnAddress(addr net.Addr) (net.Listener, error) { return net.Listen(addr.Network(), addr.String()) } -// TlsListenOnAddress creates a TLS listener that listens on the given -// address. +// TlsListenOnAddress creates a TLS listener that listens on the given address. func TlsListenOnAddress(addr net.Addr, config *tls.Config) (net.Listener, error) { return tls.Listen(addr.Network(), addr.String(), config) } // IsLoopback returns true if an address describes a loopback interface. -func IsLoopback(addr net.Addr) bool { +func IsLoopback(addr string) bool { for _, loopback := range loopBackAddrs { - if strings.Contains(addr.String(), loopback) { + if strings.Contains(addr, loopback) { return true } } @@ -81,20 +89,23 @@ func IsLoopback(addr net.Addr) bool { return false } -// isUnix returns true if an address describes an Unix socket address. +// IsUnix returns true if an address describes an Unix socket address. func IsUnix(addr net.Addr) bool { return strings.HasPrefix(addr.Network(), "unix") } // ParseAddressString converts an address in string format to a net.Addr that is // compatible with lnd. UDP is not supported because lnd needs reliable -// connections. -func ParseAddressString(strAddress string, - defaultPort string) (net.Addr, error) { +// connections. We accept a custom function to resolve any TCP addresses so +// that caller is able control exactly how resolution is performed. +func ParseAddressString(strAddress string, defaultPort string, + tcpResolver tcpResolver) (net.Addr, error) { + var parsedNetwork, parsedAddr string - // Addresses can either be in network://address:port format or only - // address:port. We want to support both. + // Addresses can either be in network://address:port format, + // network:address:port, address:port, or just port. We want to support + // all possible types. if strings.Contains(strAddress, "://") { parts := strings.Split(strAddress, "://") parsedNetwork, parsedAddr = parts[0], parts[1] @@ -109,53 +120,95 @@ func ParseAddressString(strAddress string, switch parsedNetwork { case "unix", "unixpacket": return net.ResolveUnixAddr(parsedNetwork, parsedAddr) + case "tcp", "tcp4", "tcp6": - return net.ResolveTCPAddr(parsedNetwork, - verifyPort(parsedAddr, defaultPort)) + return tcpResolver( + parsedNetwork, verifyPort(parsedAddr, defaultPort), + ) + case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram": return nil, fmt.Errorf("only TCP or unix socket "+ "addresses are supported: %s", parsedAddr) + default: - // There was no network specified, just try to parse as host - // and port. - return net.ResolveTCPAddr( - "tcp", verifyPort(strAddress, defaultPort), - ) + // We'll now possibly apply the default port, use the local + // host short circuit, or parse out an all interfaces listen. + addrWithPort := verifyPort(strAddress, defaultPort) + rawHost, rawPort, _ := net.SplitHostPort(addrWithPort) + + // If we reach this point, then we'll check to see if we have + // an onion addresses, if so, we can directly pass the raw + // address and port to create the proper address. + if tor.IsOnionHost(rawHost) { + portNum, err := strconv.Atoi(rawPort) + if err != nil { + return nil, err + } + + return &tor.OnionAddr{ + OnionService: rawHost, + Port: portNum, + }, nil + } + + // Otherwise, we'll attempt the resolve the host. The Tor + // resolver is unable to resolve local addresses, so we'll use + // the system resolver instead. + if rawHost == "" || IsLoopback(rawHost) { + return net.ResolveTCPAddr("tcp", addrWithPort) + } + + return tcpResolver("tcp", addrWithPort) } } -// verifyPort makes sure that an address string has both a host and a port. -// If there is no port found, the default port is appended. -func verifyPort(strAddress string, defaultPort string) string { - host, port, err := net.SplitHostPort(strAddress) +// verifyPort makes sure that an address string has both a host and a port. If +// there is no port found, the default port is appended. If the address is just +// a port, then we'll assume that the user is using the short cut to specify a +// localhost:port address. +func verifyPort(address string, defaultPort string) string { + host, port, err := net.SplitHostPort(address) if err != nil { - // If we already have an IPv6 address with brackets, don't use - // the JoinHostPort function, since it will always add a pair - // of brackets too. - if strings.HasPrefix(strAddress, "[") { - strAddress = strAddress + ":" + defaultPort - } else { - strAddress = net.JoinHostPort(strAddress, defaultPort) + // If the address itself is just an integer, then we'll assume + // that we're mapping this directly to a localhost:port pair. + // This ensures we maintain the legacy behavior. + if _, err := strconv.Atoi(address); err == nil { + return net.JoinHostPort("localhost", address) } - } else if host == "" && port == "" { - // The string ':' is parsed as valid empty host and empty port. - // But in that case, we want the default port to be applied too. - strAddress = ":" + defaultPort + + // Otherwise, we'll assume that the address just failed to + // attach its own port, so we'll use the default port. In the + // case of IPv6 addresses, if the host is already surrounded by + // brackets, then we'll avoid using the JoinHostPort function, + // since it will always add a pair of brackets. + if strings.HasPrefix(address, "[") { + return address + ":" + defaultPort + } + return net.JoinHostPort(address, defaultPort) } - return strAddress + // In the case that both the host and port are empty, we'll use the + // default port. + if host == "" && port == "" { + return ":" + defaultPort + } + + return address } // ClientAddressDialer creates a gRPC dialer that can also dial unix socket // addresses instead of just TCP addresses. -func ClientAddressDialer(defaultPort string) func(string, - time.Duration) (net.Conn, error) { +func ClientAddressDialer(defaultPort string) func(string, time.Duration) (net.Conn, error) { return func(addr string, timeout time.Duration) (net.Conn, error) { - parsedAddr, err := ParseAddressString(addr, defaultPort) + parsedAddr, err := ParseAddressString( + addr, defaultPort, net.ResolveTCPAddr, + ) if err != nil { return nil, err } - return net.DialTimeout(parsedAddr.Network(), - parsedAddr.String(), timeout) + + return net.DialTimeout( + parsedAddr.Network(), parsedAddr.String(), timeout, + ) } } diff --git a/lncfg/address_test.go b/lncfg/address_test.go index 8453a46a..92ed488d 100644 --- a/lncfg/address_test.go +++ b/lncfg/address_test.go @@ -2,7 +2,10 @@ package lncfg -import "testing" +import ( + "net" + "testing" +) // addressTest defines a test vector for an address that contains the non- // normalized input and the expected normalized output. @@ -34,12 +37,40 @@ var ( {"localhost", "tcp", "127.0.0.1:1234", true, false}, {"unix:///tmp/lnd.sock", "unix", "/tmp/lnd.sock", false, true}, {"unix:/tmp/lnd.sock", "unix", "/tmp/lnd.sock", false, true}, + {"123", "tcp", "127.0.0.1:123", true, false}, + { + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion", + "tcp", + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion:1234", + false, + false, + }, + { + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion:9735", + "tcp", + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion:9735", + false, + false, + }, + { + "3g2upl4pq6kufc4m.onion", + "tcp", + "3g2upl4pq6kufc4m.onion:1234", + false, + false, + }, + { + "3g2upl4pq6kufc4m.onion:9735", + "tcp", + "3g2upl4pq6kufc4m.onion:9735", + false, + false, + }, } invalidTestVectors = []string{ "some string", "://", - "12.12.12", - "123", + "12.12.12.12.12", } ) @@ -47,42 +78,41 @@ var ( // normalized correctly. func TestAddresses(t *testing.T) { // First, test all correct addresses. - for _, testVector := range addressTestVectors { + for i, testVector := range addressTestVectors { addr := []string{testVector.address} - normalized, err := NormalizeAddresses(addr, defaultTestPort) + normalized, err := NormalizeAddresses( + addr, defaultTestPort, net.ResolveTCPAddr, + ) if err != nil { - t.Fatalf("unable to normalize address %s: %v", - testVector.address, err) + t.Fatalf("#%v: unable to normalize address %s: %v", + i, testVector.address, err) } netAddr := normalized[0] if err != nil { - t.Fatalf("unable to split normalized address: %v", err) + t.Fatalf("#%v: unable to split normalized address: %v", i, err) } if netAddr.Network() != testVector.expectedNetwork || netAddr.String() != testVector.expectedAddress { - t.Fatalf( - "mismatched address: expected %s://%s, got "+ - "%s://%s", - testVector.expectedNetwork, + t.Fatalf("#%v: mismatched address: expected %s://%s, "+ + "got %s://%s", + i, testVector.expectedNetwork, testVector.expectedAddress, netAddr.Network(), netAddr.String(), ) } - isAddrLoopback := IsLoopback(normalized[0]) + isAddrLoopback := IsLoopback(normalized[0].String()) if testVector.isLoopback != isAddrLoopback { - t.Fatalf( - "mismatched loopback detection: expected "+ - "%v, got %v for addr %s", - testVector.isLoopback, isAddrLoopback, + t.Fatalf("#%v: mismatched loopback detection: expected "+ + "%v, got %v for addr %s", + i, testVector.isLoopback, isAddrLoopback, testVector.address, ) } isAddrUnix := IsUnix(normalized[0]) if testVector.isUnix != isAddrUnix { - t.Fatalf( - "mismatched unix detection: expected "+ - "%v, got %v for addr %s", - testVector.isUnix, isAddrUnix, + t.Fatalf("#%v: mismatched unix detection: expected "+ + "%v, got %v for addr %s", + i, testVector.isUnix, isAddrUnix, testVector.address, ) } @@ -91,8 +121,11 @@ func TestAddresses(t *testing.T) { // Finally, test invalid inputs to see if they are handled correctly. for _, testVector := range invalidTestVectors { addr := []string{testVector} - _, err := NormalizeAddresses(addr, defaultTestPort) + _, err := NormalizeAddresses( + addr, defaultTestPort, net.ResolveTCPAddr, + ) if err == nil { + t.Fatalf("expected error when parsing %v", testVector) } } diff --git a/server.go b/server.go index ebdd6e27..739d6d3e 100644 --- a/server.go +++ b/server.go @@ -399,12 +399,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl, // of this server's addresses. externalIPs, err := lncfg.NormalizeAddresses( externalIpStrings, strconv.Itoa(defaultPeerPort), + cfg.net.ResolveTCPAddr, ) if err != nil { return nil, err } selfAddrs := make([]net.Addr, 0, len(externalIPs)) - for _, ip := range cfg.ExternalIPs { + for _, ip := range externalIPs { selfAddrs = append(selfAddrs, ip) }