From 43cbd5a814a92d24082016d206532c4e038f55be Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 25 Apr 2018 12:00:50 -0400 Subject: [PATCH 01/17] tor: streamline package to better follow the Effective Go guidelines In this commit, we clean up the tor package to better follow the Effective Go guidelines. Most of the changes revolve around naming, where we'd have things like `torsvc.TorDial`. This was simplified to `tor.Dial` along with many others. --- tor/README.md | 15 ++++ tor/net.go | 104 +++++++++++++++++++++++++++ tor/tor.go | 160 +++++++++++++++++++++++++++++++++++++++++ torsvc/README.md | 15 ---- torsvc/interface.go | 24 ------- torsvc/net.go | 76 -------------------- torsvc/torsvc.go | 168 -------------------------------------------- 7 files changed, 279 insertions(+), 283 deletions(-) create mode 100644 tor/README.md create mode 100644 tor/net.go create mode 100644 tor/tor.go delete mode 100644 torsvc/README.md delete mode 100644 torsvc/interface.go delete mode 100644 torsvc/net.go delete mode 100644 torsvc/torsvc.go diff --git a/tor/README.md b/tor/README.md new file mode 100644 index 000000000..a3d84d1d3 --- /dev/null +++ b/tor/README.md @@ -0,0 +1,15 @@ +tor +=== + +The tor package contains utility functions that allow for interacting with the +Tor daemon. So far, supported functions include routing all traffic over Tor's +exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). In the +future more features will be added: automatic setup of v2 hidden service +functionality, control port functionality, and handling manually setup v3 hidden +services. + +## Installation and Updating + +```bash +$ go get -u github.com/lightningnetwork/lnd/tor +``` diff --git a/tor/net.go b/tor/net.go new file mode 100644 index 000000000..febf7227c --- /dev/null +++ b/tor/net.go @@ -0,0 +1,104 @@ +package tor + +import ( + "errors" + "net" +) + +// TODO: this interface and its implementations should ideally be moved +// elsewhere as they are not Tor-specific. + +// Net is an interface housing a Dial function and several DNS functions that +// allows us to abstract the implementations of these functions over different +// networks, e.g. clearnet, Tor net, etc. +type Net interface { + // Dial connects to the address on the named network. + Dial(network, address string) (net.Conn, error) + + // LookupHost performs DNS resolution on a given host and returns its + // addresses. + LookupHost(host string) ([]string, error) + + // LookupSRV tries to resolve an SRV query of the given service, + // protocol, and domain name. + LookupSRV(service, proto, name string) (string, []*net.SRV, error) + + // ResolveTCPAddr resolves TCP addresses. + ResolveTCPAddr(network, address string) (*net.TCPAddr, error) +} + +// ClearNet is an implementation of the Net interface that defines behaviour +// for regular network connections. +type ClearNet struct{} + +// Dial on the regular network uses net.Dial +func (r *ClearNet) Dial(network, address string) (net.Conn, error) { + return net.Dial(network, address) +} + +// LookupHost for regular network uses the net.LookupHost function +func (r *ClearNet) LookupHost(host string) ([]string, error) { + return net.LookupHost(host) +} + +// LookupSRV for regular network uses net.LookupSRV function +func (r *ClearNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { + return net.LookupSRV(service, proto, name) +} + +// ResolveTCPAddr for regular network uses net.ResolveTCPAddr function +func (r *ClearNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { + return net.ResolveTCPAddr(network, address) +} + +// ProxyNet is an implementation of the Net interface that defines behaviour +// for Tor network connections. +type ProxyNet struct { + // SOCKS is the host:port which Tor's exposed SOCKS5 proxy is listening + // on. + SOCKS string + + // DNS is the host:port of the DNS server for Tor to use for SRV + // queries. + DNS string + + // StreamIsolation is a bool that determines if we should force the + // creation of a new circuit for this connection. If true, then this + // means that our traffic may be harder to correlate as each connection + // will now use a distinct circuit. + StreamIsolation bool +} + +// Dial uses the Tor Dial function in order to establish connections through +// Tor. Since Tor only supports TCP connections, only TCP networks are allowed. +func (p *ProxyNet) Dial(network, address string) (net.Conn, error) { + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, errors.New("cannot dial non-tcp network via Tor") + } + return Dial(address, p.SOCKS, p.StreamIsolation) +} + +// LookupHost uses the Tor LookupHost function in order to resolve hosts over +// Tor. +func (p *ProxyNet) LookupHost(host string) ([]string, error) { + return LookupHost(host, p.SOCKS) +} + +// LookupSRV uses the Tor LookupSRV function in order to resolve SRV DNS queries +// over Tor. +func (p *ProxyNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { + return LookupSRV(service, proto, name, p.SOCKS, p.DNS, p.StreamIsolation) +} + +// ResolveTCPAddr uses the Tor ResolveTCPAddr function in order to resolve TCP +// addresses over Tor. +func (p *ProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, errors.New("cannot dial non-tcp network via Tor") + } + return ResolveTCPAddr(address, p.SOCKS) +} diff --git a/tor/tor.go b/tor/tor.go new file mode 100644 index 000000000..68e18680a --- /dev/null +++ b/tor/tor.go @@ -0,0 +1,160 @@ +package tor + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "net" + "strconv" + + "github.com/miekg/dns" + "github.com/roasbeef/btcd/connmgr" + "golang.org/x/net/proxy" +) + +var ( + // dnsCodes maps the DNS response codes to a friendly description. This + // does not include the BADVERS code because of duplicate keys and the + // underlying DNS (miekg/dns) package not using it. For more info, see + // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. + dnsCodes = map[int]string{ + 0: "no error", + 1: "format error", + 2: "server failure", + 3: "non-existent domain", + 4: "not implemented", + 5: "query refused", + 6: "name exists when it should not", + 7: "RR set exists when it should not", + 8: "RR set that should exist does not", + 9: "server not authoritative for zone", + 10: "name not contained in zone", + 16: "TSIG signature failure", + 17: "key not recognized", + 18: "signature out of time window", + 19: "bad TKEY mode", + 20: "duplicate key name", + 21: "algorithm not supported", + 22: "bad truncation", + 23: "bad/missing server cookie", + } +) + +// Dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP +// is supported over Tor. The final argument determines if we should force +// stream isolation for this new connection. If we do, then this means this new +// connection will use a fresh circuit, rather than possibly re-using an +// existing circuit. +func Dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) { + // If we were requested to force stream isolation for this connection, + // we'll populate the authentication credentials with random data as + // Tor will create a new circuit for each set of credentials. + var auth *proxy.Auth + if streamIsolation { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return nil, err + } + + auth = &proxy.Auth{ + User: hex.EncodeToString(b[:8]), + Password: hex.EncodeToString(b[8:]), + } + } + + // Establish the connection through Tor's SOCKS proxy. + dialer, err := proxy.SOCKS5("tcp", socksAddr, auth, proxy.Direct) + if err != nil { + return nil, err + } + + return dialer.Dial("tcp", address) +} + +// LookupHost performs DNS resolution on a given host via Tor's native resolver. +// Only IPv4 addresses are returned. +func LookupHost(host, socksAddr string) ([]string, error) { + ip, err := connmgr.TorLookupIP(host, socksAddr) + if err != nil { + return nil, err + } + + // Only one IPv4 address is returned by the TorLookupIP function. + return []string{ip[0].String()}, nil +} + +// LookupSRV uses Tor's SOCKS proxy to route DNS SRV queries. Tor does not +// natively support SRV queries so we must route all SRV queries through the +// proxy by connecting directly to a DNS server and querying it. The DNS server +// must have TCP resolution enabled for the given port. +func LookupSRV(service, proto, name, socksAddr, dnsServer string, + streamIsolation bool) (string, []*net.SRV, error) { + + // Connect to the DNS server we'll be using to query SRV records. + conn, err := Dial(dnsServer, socksAddr, streamIsolation) + if err != nil { + return "", nil, err + } + + dnsConn := &dns.Conn{Conn: conn} + defer dnsConn.Close() + + // Once connected, we'll construct the SRV request for the host + // following the format _service._proto.name. as described in RFC #2782. + host := fmt.Sprintf("_%s._%s.%s.", service, proto, name) + msg := new(dns.Msg).SetQuestion(host, dns.TypeSRV) + + // Send the request to the DNS server and read its response. + if err := dnsConn.WriteMsg(msg); err != nil { + return "", nil, err + } + resp, err := dnsConn.ReadMsg() + if err != nil { + return "", nil, err + } + + // We'll fail if we were unable to query the DNS server for our record. + if resp.Rcode != dns.RcodeSuccess { + return "", nil, fmt.Errorf("unable to query for SRV records: "+ + "%s", dnsCodes[resp.Rcode]) + } + + // Retrieve the RR(s) of the Answer section. + var rrs []*net.SRV + for _, rr := range resp.Answer { + srv := rr.(*dns.SRV) + rrs = append(rrs, &net.SRV{ + Target: srv.Target, + Port: srv.Port, + Priority: srv.Priority, + Weight: srv.Weight, + }) + } + + return "", rrs, nil +} + +// ResolveTCPAddr uses Tor's proxy to resolve TCP addresses instead of the +// standard system resolver provided in the `net` package. +func ResolveTCPAddr(address, socksAddr string) (*net.TCPAddr, error) { + // Split host:port since the lookup function does not take a port. + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + ip, err := LookupHost(host, socksAddr) + if err != nil { + return nil, err + } + + p, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + + return &net.TCPAddr{ + IP: net.ParseIP(ip[0]), + Port: p, + }, nil +} diff --git a/torsvc/README.md b/torsvc/README.md deleted file mode 100644 index a9d57f965..000000000 --- a/torsvc/README.md +++ /dev/null @@ -1,15 +0,0 @@ -torsvc -========== - -The torsvc package contains utility functions that allow for interacting -with the Tor daemon. So far, supported functions include routing all traffic -over Tor's exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). -In the future more features will be added: automatic setup of v2 hidden service -functionality, control port functionality, and handling manually setup v3 hidden -services. - -## Installation and Updating - -```bash -$ go get -u github.com/lightningnetwork/lnd/torsvc -``` diff --git a/torsvc/interface.go b/torsvc/interface.go deleted file mode 100644 index 451615d95..000000000 --- a/torsvc/interface.go +++ /dev/null @@ -1,24 +0,0 @@ -package torsvc - -import ( - "net" -) - -// Net is an interface housing a Dial function and several DNS functions, to -// abstract the implementation of these functions over both Regular and Tor -type Net interface { - // Dial accepts a network and address and returns a connection to a remote - // peer. - Dial(string, string) (net.Conn, error) - - // LookupHost performs DNS resolution on a given hostname and returns - // addresses of that hostname - LookupHost(string) ([]string, error) - - // LookupSRV allows a service and network to be specified and makes queries - // to a given DNS server for SRV queries. - LookupSRV(string, string, string) (string, []*net.SRV, error) - - // ResolveTCPAddr is a used to resolve publicly advertised TCP addresses. - ResolveTCPAddr(string, string) (*net.TCPAddr, error) -} diff --git a/torsvc/net.go b/torsvc/net.go deleted file mode 100644 index 859189acd..000000000 --- a/torsvc/net.go +++ /dev/null @@ -1,76 +0,0 @@ -package torsvc - -import ( - "fmt" - "net" -) - -// RegularNet is an implementation of the Net interface that defines behaviour -// for Regular network connections -type RegularNet struct{} - -// Dial on the regular network uses net.Dial -func (r *RegularNet) Dial(network, address string) (net.Conn, error) { - return net.Dial(network, address) -} - -// LookupHost for regular network uses the net.LookupHost function -func (r *RegularNet) LookupHost(host string) ([]string, error) { - return net.LookupHost(host) -} - -// LookupSRV for regular network uses net.LookupSRV function -func (r *RegularNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { - return net.LookupSRV(service, proto, name) -} - -// ResolveTCPAddr for regular network uses net.ResolveTCPAddr function -func (r *RegularNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { - return net.ResolveTCPAddr(network, address) -} - -// TorProxyNet is an implementation of the Net interface that defines behaviour -// for Tor network connections -type TorProxyNet struct { - // TorDNS is the IP:PORT of the DNS server for Tor to use for SRV queries - TorDNS string - - // TorSocks is the port which Tor's exposed SOCKS5 proxy is listening on. - // This is used for an outbound-only mode, so the node will not listen for - // incoming connections - TorSocks string - - // StreamIsolation is a bool that determines if we should force the - // creation of a new circuit for this connection. If true, then this - // means that our traffic may be harder to correlate as each connection - // will now use a distinct circuit. - StreamIsolation bool -} - -// Dial on the Tor network uses the torsvc TorDial() function, and requires -// that network specified be tcp because only that is supported -func (t *TorProxyNet) Dial(network, address string) (net.Conn, error) { - if network != "tcp" { - return nil, fmt.Errorf("Cannot dial non-tcp network via Tor") - } - return TorDial(address, t.TorSocks, t.StreamIsolation) -} - -// LookupHost on Tor network uses the torsvc TorLookupHost function. -func (t *TorProxyNet) LookupHost(host string) ([]string, error) { - return TorLookupHost(host, t.TorSocks) -} - -// LookupSRV on Tor network uses the torsvc TorLookupHost function. -func (t *TorProxyNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { - return TorLookupSRV(service, proto, name, t.TorSocks, t.TorDNS) -} - -// ResolveTCPAddr on Tor network uses the towsvc TorResolveTCP function, and -// requires network to be "tcp" because only "tcp" is supported -func (t *TorProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { - if network != "tcp" { - return nil, fmt.Errorf("Cannot dial non-tcp network via Tor") - } - return TorResolveTCP(address, t.TorSocks) -} diff --git a/torsvc/torsvc.go b/torsvc/torsvc.go deleted file mode 100644 index 293693730..000000000 --- a/torsvc/torsvc.go +++ /dev/null @@ -1,168 +0,0 @@ -package torsvc - -import ( - "fmt" - "net" - "strconv" - - "github.com/btcsuite/go-socks/socks" - "github.com/miekg/dns" - "github.com/roasbeef/btcd/connmgr" - "golang.org/x/net/proxy" -) - -const ( - localhost = "127.0.0.1" -) - -var ( - // DNS Message Response Codes, see - // https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml - dnsCodes = map[int]string{ - 0: "No Error", - 1: "Format Error", - 2: "Server Failure", - 3: "Non-Existent Domain", - 4: "Not Implemented", - 5: "Query Refused", - 6: "Name Exists when it should not", - 7: "RR Set Exists when it should not", - 8: "RR Set that should exist does not", - 9: "Server Not Authoritative for zone", - 10: "Name not contained in zone", - // Left out 16: "Bad OPT Version" because of duplicate keys and - // because miekg/dns does not use this message response code. - 16: "TSIG Signature Failure", - 17: "Key not recognized", - 18: "Signature out of time window", - 19: "Bad TKEY Mode", - 20: "Duplicate key name", - 21: "Algorithm not supported", - 22: "Bad Truncation", - 23: "Bad/missing Server Cookie", - } -) - -// TorDial returns a connection to a remote peer via Tor's socks proxy. Only -// TCP is supported over Tor. The final argument determines if we should force -// stream isolation for this new connection. If we do, then this means this new -// connection will use a fresh circuit, rather than possibly re-using an -// existing circuit. -func TorDial(address, socksPort string, streamIsolation bool) (net.Conn, error) { - p := &socks.Proxy{ - Addr: localhost + ":" + socksPort, - TorIsolation: streamIsolation, - } - - return p.Dial("tcp", address) -} - -// TorLookupHost performs DNS resolution on a given hostname via Tor's -// native resolver. Only IPv4 addresses are returned. -func TorLookupHost(host, socksPort string) ([]string, error) { - ip, err := connmgr.TorLookupIP(host, localhost+":"+socksPort) - if err != nil { - return nil, err - } - - var addrs []string - // Only one IPv4 address is returned by the TorLookupIP function. - addrs = append(addrs, ip[0].String()) - return addrs, nil -} - -// TorLookupSRV uses Tor's socks proxy to route DNS SRV queries. Tor does not -// natively support SRV queries so we must route all SRV queries THROUGH the -// Tor proxy and connect directly to a DNS server and query it. -// NOTE: TorLookupSRV uses golang's proxy package since go-socks will cause -// the SRV request to hang. -func TorLookupSRV(service, proto, name, socksPort, dnsServer string) (string, - []*net.SRV, error) { - // _service._proto.name as described in RFC#2782. - host := "_" + service + "._" + proto + "." + name + "." - - // Set up golang's proxy dialer - Tor's socks proxy doesn't support - // authentication. - dialer, err := proxy.SOCKS5( - "tcp", - localhost+":"+socksPort, - nil, - proxy.Direct, - ) - if err != nil { - return "", nil, err - } - - // Dial dnsServer via Tor. dnsServer must have TCP resolution enabled - // for the port we are dialing. - conn, err := dialer.Dial("tcp", dnsServer) - if err != nil { - return "", nil, err - } - - // Construct the actual SRV request. - msg := new(dns.Msg) - msg.SetQuestion(host, dns.TypeSRV) - msg.RecursionDesired = true - - dnsConn := &dns.Conn{Conn: conn} - defer dnsConn.Close() - - // Write the SRV request. - dnsConn.WriteMsg(msg) - - // Read the response. - resp, err := dnsConn.ReadMsg() - if err != nil { - return "", nil, err - } - - // If the message response code was not the success code, fail. - if resp.Rcode != dns.RcodeSuccess { - return "", nil, fmt.Errorf("Unsuccessful SRV request, "+ - "received: %s", dnsCodes[resp.Rcode]) - } - - // Retrieve the RR(s) of the Answer section. - var rrs []*net.SRV - for _, rr := range resp.Answer { - srv := rr.(*dns.SRV) - rrs = append(rrs, &net.SRV{ - Target: srv.Target, - Port: srv.Port, - Priority: srv.Priority, - Weight: srv.Weight, - }) - } - - return "", rrs, nil -} - -// TorResolveTCP uses Tor's proxy to resolve TCP addresses instead of the -// system resolver that ResolveTCPAddr and related functions use. Only TCP -// resolution is supported. -func TorResolveTCP(address, socksPort string) (*net.TCPAddr, error) { - // Split host:port since the lookup function does not take a port. - host, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - - // Look up the host's IP address via Tor. - ip, err := TorLookupHost(host, socksPort) - if err != nil { - return nil, err - } - - // Convert port to an int. - p, err := strconv.Atoi(port) - if err != nil { - return nil, err - } - - // Return a *net.TCPAddr exactly like net.ResolveTCPAddr. - return &net.TCPAddr{ - IP: net.ParseIP(ip[0]), - Port: p, - }, nil -} From eded682f9b2f1f5d0ae36f4af08333dd2ed9eb2d Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 30 May 2018 17:32:51 -0700 Subject: [PATCH 02/17] build: update inputs-digest due to removed btcsuite/go-socks dep --- Gopkg.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gopkg.lock b/Gopkg.lock index dc0664ccf..d91217764 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -359,6 +359,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "3e7512c1772a70c004d9557850137e4dd82545ec2e16381b4694ff4a0c5a6819" + inputs-digest = "2133b0035a81c856475302a127bc26d30217a30d1e41708d3604f2de82e1ab31" solver-name = "gps-cdcl" solver-version = 1 From a07397465be21d010ae5c14709f904c96eac17ec Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 25 Apr 2018 12:25:39 -0400 Subject: [PATCH 03/17] tor: add onion address implementation Co-Authored-By: Eugene --- tor/onionaddr.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tor/onionaddr.go diff --git a/tor/onionaddr.go b/tor/onionaddr.go new file mode 100644 index 000000000..ccc905d7d --- /dev/null +++ b/tor/onionaddr.go @@ -0,0 +1,63 @@ +package tor + +import ( + "encoding/base32" + "net" + "strconv" +) + +const ( + // base32Alphabet is the alphabet used for encoding and decoding v2 and + // v3 onion addresses. + base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567" + + // OnionSuffix is the ".onion" suffix for v2 and v3 onion addresses. + OnionSuffix = ".onion" + + // OnionSuffixLen is the length of the ".onion" suffix. + OnionSuffixLen = len(OnionSuffix) + + // V2DecodedLen is the length of a decoded v2 onion service. + V2DecodedLen = 10 + + // V2Len is the length of a v2 onion service including the ".onion" + // suffix. + V2Len = 22 + + // V3DecodedLen is the length of a decoded v3 onion service. + V3DecodedLen = 35 + + // V3Len is the length of a v2 onion service including the ".onion" + // suffix. + V3Len = 62 +) + +var ( + // Base32Encoding represents the Tor's base32-encoding scheme for v2 and + // v3 onion addresses. + Base32Encoding = base32.NewEncoding(base32Alphabet) +) + +// OnionAddr represents a Tor network end point onion address. +type OnionAddr struct { + // OnionService is the host of the onion address. + OnionService string + + // Port is the port of the onion address. + Port int +} + +// A compile-time check to ensure that OnionAddr implements the net.Addr +// interface. +var _ net.Addr = (*OnionAddr)(nil) + +// String returns the string representation of an onion address. +func (o *OnionAddr) String() string { + return net.JoinHostPort(o.OnionService, strconv.Itoa(o.Port)) +} + +// Network returns the network that this implementation of net.Addr will use. +// In this case, because Tor only allows TCP connections, the network is "tcp". +func (o *OnionAddr) Network() string { + return "tcp" +} From 850f9fbbe99cd332f2fe44b1f1ef05ede3d8693a Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 25 Apr 2018 13:22:47 -0400 Subject: [PATCH 04/17] lnwire: implement serialization of onion addresses Co-Authored-By: Eugene --- lnwire/lnwire.go | 114 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 22 deletions(-) diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 39209a5ce..815a1d8f8 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -11,6 +11,7 @@ import ( "net" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/tor" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" @@ -43,8 +44,7 @@ const ( // v2OnionAddr denotes a version 2 Tor onion service address. v2OnionAddr addressType = 3 - // v3OnionAddr denotes a version 3 Tor (prop224) onion service - // addresses + // v3OnionAddr denotes a version 3 Tor (prop224) onion service address. v3OnionAddr addressType = 4 ) @@ -56,16 +56,12 @@ func (a addressType) AddrLen() uint16 { return 0 case tcp4Addr: return 6 - case tcp6Addr: return 18 - case v2OnionAddr: return 12 - case v3OnionAddr: return 37 - default: return 0 } @@ -300,7 +296,6 @@ func writeElement(w io.Writer, element interface{}) error { return fmt.Errorf("cannot write nil TCPAddr") } - // TODO(roasbeef): account for onion types too if e.IP.To4() != nil { var descriptor [1]byte descriptor[0] = uint8(tcp4Addr) @@ -331,6 +326,45 @@ func writeElement(w io.Writer, element interface{}) error { return err } + case *tor.OnionAddr: + if e == nil { + return errors.New("cannot write nil onion address") + } + + var suffixIndex int + switch len(e.OnionService) { + case tor.V2Len: + descriptor := []byte{byte(v2OnionAddr)} + if _, err := w.Write(descriptor); err != nil { + return err + } + suffixIndex = tor.V2Len - tor.OnionSuffixLen + case tor.V3Len: + descriptor := []byte{byte(v3OnionAddr)} + if _, err := w.Write(descriptor); err != nil { + return err + } + suffixIndex = tor.V3Len - tor.OnionSuffixLen + default: + return errors.New("unknown onion service length") + } + + host, err := tor.Base32Encoding.DecodeString( + e.OnionService[:suffixIndex], + ) + if err != nil { + return err + } + if _, err := w.Write(host); err != nil { + return err + } + + var port [2]byte + binary.BigEndian.PutUint16(port[:], uint16(e.Port)) + if _, err := w.Write(port[:]); err != nil { + return err + } + case []net.Addr: // First, we'll encode all the addresses into an intermediate // buffer. We need to do this in order to compute the total @@ -632,6 +666,7 @@ func readElement(r io.Reader, element interface{}) error { addresses []net.Addr addrBytesRead uint16 ) + for addrBytesRead < addrsLen { var descriptor [1]byte if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil { @@ -640,52 +675,87 @@ func readElement(r io.Reader, element interface{}) error { addrBytesRead++ - address := &net.TCPAddr{} - aType := addressType(descriptor[0]) - switch aType { - + var address net.Addr + switch aType := addressType(descriptor[0]); aType { case noAddr: addrBytesRead += aType.AddrLen() continue case tcp4Addr: var ip [4]byte - if _, err = io.ReadFull(addrBuf, ip[:]); err != nil { + if _, err := io.ReadFull(addrBuf, ip[:]); err != nil { return err } - address.IP = (net.IP)(ip[:]) var port [2]byte - if _, err = io.ReadFull(addrBuf, port[:]); err != nil { + if _, err := io.ReadFull(addrBuf, port[:]); err != nil { return err } - address.Port = int(binary.BigEndian.Uint16(port[:])) - + address = &net.TCPAddr{ + IP: net.IP(ip[:]), + Port: int(binary.BigEndian.Uint16(port[:])), + } addrBytesRead += aType.AddrLen() case tcp6Addr: var ip [16]byte - if _, err = io.ReadFull(addrBuf, ip[:]); err != nil { + if _, err := io.ReadFull(addrBuf, ip[:]); err != nil { return err } - address.IP = (net.IP)(ip[:]) var port [2]byte - if _, err = io.ReadFull(addrBuf, port[:]); err != nil { + if _, err := io.ReadFull(addrBuf, port[:]); err != nil { return err } - address.Port = int(binary.BigEndian.Uint16(port[:])) + address = &net.TCPAddr{ + IP: net.IP(ip[:]), + Port: int(binary.BigEndian.Uint16(port[:])), + } addrBytesRead += aType.AddrLen() case v2OnionAddr: + var h [tor.V2DecodedLen]byte + if _, err := io.ReadFull(addrBuf, h[:]); err != nil { + return err + } + + var p [2]byte + if _, err := io.ReadFull(addrBuf, p[:]); err != nil { + return err + } + + onionService := tor.Base32Encoding.EncodeToString(h[:]) + onionService += tor.OnionSuffix + port := int(binary.BigEndian.Uint16(p[:])) + + address = &tor.OnionAddr{ + OnionService: onionService, + Port: port, + } addrBytesRead += aType.AddrLen() - continue case v3OnionAddr: + var h [tor.V3DecodedLen]byte + if _, err := io.ReadFull(addrBuf, h[:]); err != nil { + return err + } + + var p [2]byte + if _, err := io.ReadFull(addrBuf, p[:]); err != nil { + return err + } + + onionService := tor.Base32Encoding.EncodeToString(h[:]) + onionService += tor.OnionSuffix + port := int(binary.BigEndian.Uint16(p[:])) + + address = &tor.OnionAddr{ + OnionService: onionService, + Port: port, + } addrBytesRead += aType.AddrLen() - continue default: return &ErrUnknownAddrType{aType} From e5987a1ef1463984ade2ad4cb20e8e760d4aea87 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 16:50:36 -0400 Subject: [PATCH 05/17] lnwire: extend tests to randomly generate all types of supported addresses --- lnwire/lnwire_test.go | 108 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index fd214308d..0633e6b0e 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -2,6 +2,7 @@ package lnwire import ( "bytes" + "encoding/binary" "encoding/hex" "image/color" "math" @@ -14,6 +15,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/tor" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" @@ -37,11 +39,6 @@ var ( } _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) - - // TODO(roasbeef): randomly generate from three types of addrs - a1 = &net.TCPAddr{IP: (net.IP)([]byte{0x7f, 0x0, 0x0, 0x1}), Port: 8333} - a2, _ = net.ResolveTCPAddr("tcp", "[2001:db8:85a3:0:0:8a2e:370:7334]:80") - testAddrs = []net.Addr{a1, a2} ) func randPubKey() (*btcec.PublicKey, error) { @@ -76,6 +73,100 @@ func randRawFeatureVector(r *rand.Rand) *RawFeatureVector { return featureVec } +func randTCP4Addr(r *rand.Rand) (*net.TCPAddr, error) { + var ip [4]byte + if _, err := r.Read(ip[:]); err != nil { + return nil, err + } + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { + return nil, err + } + + addrIP := net.IP(ip[:]) + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil +} + +func randTCP6Addr(r *rand.Rand) (*net.TCPAddr, error) { + var ip [16]byte + if _, err := r.Read(ip[:]); err != nil { + return nil, err + } + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { + return nil, err + } + + addrIP := net.IP(ip[:]) + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil +} + +func randV2OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) { + var serviceID [tor.V2DecodedLen]byte + if _, err := r.Read(serviceID[:]); err != nil { + return nil, err + } + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { + return nil, err + } + + onionService := tor.Base32Encoding.EncodeToString(serviceID[:]) + onionService += tor.OnionSuffix + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil +} + +func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) { + var serviceID [tor.V3DecodedLen]byte + if _, err := r.Read(serviceID[:]); err != nil { + return nil, err + } + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { + return nil, err + } + + onionService := tor.Base32Encoding.EncodeToString(serviceID[:]) + onionService += tor.OnionSuffix + addrPort := int(binary.BigEndian.Uint16(port[:])) + + return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil +} + +func randAddrs(r *rand.Rand) ([]net.Addr, error) { + tcp4Addr, err := randTCP4Addr(r) + if err != nil { + return nil, err + } + + tcp6Addr, err := randTCP6Addr(r) + if err != nil { + return nil, err + } + + v2OnionAddr, err := randV2OnionAddr(r) + if err != nil { + return nil, err + } + + v3OnionAddr, err := randV3OnionAddr(r) + if err != nil { + return nil, err + } + + return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr}, nil +} + func TestMaxOutPointIndex(t *testing.T) { t.Parallel() @@ -465,8 +556,6 @@ func TestLightningWireProtocol(t *testing.T) { G: uint8(r.Int31()), B: uint8(r.Int31()), }, - // TODO(roasbeef): proper gen rand addrs - Addresses: testAddrs, } req.Signature, err = NewSigFromSignature(testSig) if err != nil { @@ -480,6 +569,11 @@ func TestLightningWireProtocol(t *testing.T) { return } + req.Addresses, err = randAddrs(r) + if err != nil { + t.Fatalf("unable to generate addresses: %v", err) + } + v[0] = reflect.ValueOf(req) }, MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) { From 3738e68ae218428bcf95314d3c67af3c342ca791 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 25 Apr 2018 14:00:26 -0400 Subject: [PATCH 06/17] channeldb: implement serialization of onion addresses Co-Authored-By: Eugene --- channeldb/addr.go | 193 ++++++++++++++++++++++++++++++---------------- 1 file changed, 127 insertions(+), 66 deletions(-) diff --git a/channeldb/addr.go b/channeldb/addr.go index f97e8a5b4..4d2f578f0 100644 --- a/channeldb/addr.go +++ b/channeldb/addr.go @@ -1,10 +1,12 @@ package channeldb import ( + "encoding/binary" + "errors" "io" "net" - "github.com/btcsuite/go-socks/socks" + "github.com/lightningnetwork/lnd/tor" ) // addressType specifies the network protocol and version that should be used @@ -21,38 +23,75 @@ const ( // v2OnionAddr denotes a version 2 Tor onion service address. v2OnionAddr addressType = 2 - // v3OnionAddr denotes a version 3 Tor (prop224) onion service addresses. + // v3OnionAddr denotes a version 3 Tor (prop224) onion service address. v3OnionAddr addressType = 3 ) +// encodeTCPAddr serializes a TCP address into its compact raw bytes +// representation. func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error { - var scratch [16]byte + var ( + addrType byte + ip []byte + ) if addr.IP.To4() != nil { - scratch[0] = uint8(tcp4Addr) - if _, err := w.Write(scratch[:1]); err != nil { - return err - } - - copy(scratch[:4], addr.IP.To4()) - if _, err := w.Write(scratch[:4]); err != nil { - return err - } - + addrType = byte(tcp4Addr) + ip = addr.IP.To4() } else { - scratch[0] = uint8(tcp6Addr) - if _, err := w.Write(scratch[:1]); err != nil { - return err - } - - copy(scratch[:], addr.IP.To16()) - if _, err := w.Write(scratch[:]); err != nil { - return err - } + addrType = byte(tcp6Addr) + ip = addr.IP.To16() } - byteOrder.PutUint16(scratch[:2], uint16(addr.Port)) - if _, err := w.Write(scratch[:2]); err != nil { + if _, err := w.Write([]byte{addrType}); err != nil { + return err + } + + if _, err := w.Write(ip); err != nil { + return err + } + + var port [2]byte + byteOrder.PutUint16(port[:], uint16(addr.Port)) + if _, err := w.Write(port[:]); err != nil { + return err + } + + return nil +} + +// encodeOnionAddr serializes an onion address into its compact raw bytes +// representation. +func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error { + var suffixIndex int + switch len(addr.OnionService) { + case tor.V2Len: + if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil { + return err + } + suffixIndex = tor.V2Len - tor.OnionSuffixLen + case tor.V3Len: + if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil { + return err + } + suffixIndex = tor.V3Len - tor.OnionSuffixLen + default: + return errors.New("unknown onion service length") + } + + host, err := tor.Base32Encoding.DecodeString( + addr.OnionService[:suffixIndex], + ) + if err != nil { + return err + } + if _, err := w.Write(host); err != nil { + return err + } + + var port [2]byte + byteOrder.PutUint16(port[:], uint16(addr.Port)) + if _, err := w.Write(port[:]); err != nil { return err } @@ -60,42 +99,84 @@ func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error { } // deserializeAddr reads the serialized raw representation of an address and -// deserializes it into the actual address, to avoid performing address -// resolution in the database module +// deserializes it into the actual address. This allows us to avoid address +// resolution within the channeldb package. func deserializeAddr(r io.Reader) (net.Addr, error) { - var scratch [8]byte - var address net.Addr - - if _, err := r.Read(scratch[:1]); err != nil { + var addrType [1]byte + if _, err := r.Read(addrType[:]); err != nil { return nil, err } - // TODO(roasbeef): also add onion addrs - switch addressType(scratch[0]) { + var address net.Addr + switch addressType(addrType[0]) { case tcp4Addr: - addr := &net.TCPAddr{} var ip [4]byte if _, err := r.Read(ip[:]); err != nil { return nil, err } - addr.IP = (net.IP)(ip[:]) - if _, err := r.Read(scratch[:2]); err != nil { + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { return nil, err } - addr.Port = int(byteOrder.Uint16(scratch[:2])) - address = addr + + address = &net.TCPAddr{ + IP: net.IP(ip[:]), + Port: int(binary.BigEndian.Uint16(port[:])), + } case tcp6Addr: - addr := &net.TCPAddr{} var ip [16]byte if _, err := r.Read(ip[:]); err != nil { return nil, err } - addr.IP = (net.IP)(ip[:]) - if _, err := r.Read(scratch[:2]); err != nil { + + var port [2]byte + if _, err := r.Read(port[:]); err != nil { return nil, err } - addr.Port = int(byteOrder.Uint16(scratch[:2])) - address = addr + + address = &net.TCPAddr{ + IP: net.IP(ip[:]), + Port: int(binary.BigEndian.Uint16(port[:])), + } + case v2OnionAddr: + var h [tor.V2DecodedLen]byte + if _, err := r.Read(h[:]); err != nil { + return nil, err + } + + var p [2]byte + if _, err := r.Read(p[:]); err != nil { + return nil, err + } + + onionService := tor.Base32Encoding.EncodeToString(h[:]) + onionService += tor.OnionSuffix + port := int(binary.BigEndian.Uint16(p[:])) + + address = &tor.OnionAddr{ + OnionService: onionService, + Port: port, + } + case v3OnionAddr: + var h [tor.V3DecodedLen]byte + if _, err := r.Read(h[:]); err != nil { + return nil, err + } + + var p [2]byte + if _, err := r.Read(p[:]); err != nil { + return nil, err + } + + onionService := tor.Base32Encoding.EncodeToString(h[:]) + onionService += tor.OnionSuffix + port := int(binary.BigEndian.Uint16(p[:])) + + address = &tor.OnionAddr{ + OnionService: onionService, + Port: port, + } default: return nil, ErrUnknownAddressType } @@ -103,34 +184,14 @@ func deserializeAddr(r io.Reader) (net.Addr, error) { return address, nil } -// serializeAddr serializes an address into a raw byte representation so it -// can be deserialized without requiring address resolution +// serializeAddr serializes an address into its raw bytes representation so that +// it can be deserialized without requiring address resolution. func serializeAddr(w io.Writer, address net.Addr) error { - switch addr := address.(type) { case *net.TCPAddr: return encodeTCPAddr(w, addr) - - // If this is a proxied address (due to the connection being - // established over a SOCKs proxy, then we'll convert it into its - // corresponding TCP address. - case *socks.ProxiedAddr: - // If we can't parse the host as an IP (though we should be - // able to at this point), then we'll skip this address all - // together. - // - // TODO(roasbeef): would be nice to be able to store hosts - // though... - ip := net.ParseIP(addr.Host) - if ip == nil { - return nil - } - - tcpAddr := &net.TCPAddr{ - IP: ip, - Port: addr.Port, - } - return encodeTCPAddr(w, tcpAddr) + case *tor.OnionAddr: + return encodeOnionAddr(w, addr) } return nil From 5f1d2524b8830105405826353f55099f26283c61 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 16:52:41 -0400 Subject: [PATCH 07/17] channeldb: add address serialization tests --- channeldb/addr_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 channeldb/addr_test.go diff --git a/channeldb/addr_test.go b/channeldb/addr_test.go new file mode 100644 index 000000000..2de179d6b --- /dev/null +++ b/channeldb/addr_test.go @@ -0,0 +1,51 @@ +package channeldb + +import ( + "bytes" + "net" + "testing" + + "github.com/lightningnetwork/lnd/tor" +) + +// TestAddrSerialization tests that the serialization method used by channeldb +// for net.Addr's works as intended. +func TestAddrSerialization(t *testing.T) { + t.Parallel() + + testAddrs := []net.Addr{ + &net.TCPAddr{ + IP: net.ParseIP("192.168.1.1"), + Port: 12345, + }, + &net.TCPAddr{ + IP: net.ParseIP("2001:0db8:0000:0000:0000:ff00:0042:8329"), + Port: 65535, + }, + &tor.OnionAddr{ + OnionService: "3g2upl4pq6kufc4m.onion", + Port: 9735, + }, + &tor.OnionAddr{ + OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion", + Port: 80, + }, + } + + var b bytes.Buffer + for _, expectedAddr := range testAddrs { + if err := serializeAddr(&b, expectedAddr); err != nil { + t.Fatalf("unable to serialize address: %v", err) + } + + addr, err := deserializeAddr(&b) + if err != nil { + t.Fatalf("unable to deserialize address: %v", err) + } + + if addr.String() != expectedAddr.String() { + t.Fatalf("expected address %v after serialization, "+ + "got %v", addr, expectedAddr) + } + } +} From 5d29dea21aeaa47e14cfae83faa38bb19029e293 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Mon, 30 Apr 2018 02:58:12 -0400 Subject: [PATCH 08/17] tor: return the connection's actual remote address rather than the proxy's In this commit, we fix an issue where connections made through Tor's SOCKS proxy would result in the remote address being the address of the proxy itself We fix this by using an internal proxyConn struct that sets the correct address at the time of the connection. --- tor/tor.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/tor/tor.go b/tor/tor.go index 68e18680a..e93e3e46a 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -40,12 +40,46 @@ var ( } ) -// Dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP +// proxyConn is a wrapper around net.Conn that allows us to expose the actual +// remote address we're dialing, rather than the proxy's address. +type proxyConn struct { + net.Conn + remoteAddr net.Addr +} + +func (c *proxyConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +// Dial is a wrapper over the non-exported dial function that returns a wrapper +// around net.Conn in order to expose the actual remote address we're dialing, +// rather than the proxy's address. +func Dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) { + conn, err := dial(address, socksAddr, streamIsolation) + if err != nil { + return nil, err + } + + // Now that the connection is established, we'll create our internal + // proxyConn that will serve in populating the correct remote address + // of the connection, rather than using the proxy's address. + remoteAddr, err := ParseAddr(address, socksAddr) + if err != nil { + return nil, err + } + + return &proxyConn{ + Conn: conn, + remoteAddr: remoteAddr, + }, nil +} + +// dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP // is supported over Tor. The final argument determines if we should force // stream isolation for this new connection. If we do, then this means this new // connection will use a fresh circuit, rather than possibly re-using an // existing circuit. -func Dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) { +func dial(address, socksAddr string, streamIsolation bool) (net.Conn, error) { // If we were requested to force stream isolation for this connection, // we'll populate the authentication credentials with random data as // Tor will create a new circuit for each set of credentials. @@ -91,7 +125,7 @@ func LookupSRV(service, proto, name, socksAddr, dnsServer string, streamIsolation bool) (string, []*net.SRV, error) { // Connect to the DNS server we'll be using to query SRV records. - conn, err := Dial(dnsServer, socksAddr, streamIsolation) + conn, err := dial(dnsServer, socksAddr, streamIsolation) if err != nil { return "", nil, err } @@ -158,3 +192,52 @@ func ResolveTCPAddr(address, socksAddr string) (*net.TCPAddr, error) { Port: p, }, nil } + +// ParseAddr parses an address from its string format to a net.Addr. +func ParseAddr(address, socksAddr string) (net.Addr, error) { + host, portStr, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, err + } + + if IsOnionHost(host) { + return &OnionAddr{OnionService: host, Port: port}, nil + } + + return ResolveTCPAddr(address, socksAddr) +} + +// IsOnionHost determines whether a host is part of an onion address. +func IsOnionHost(host string) bool { + // Note the starting index of the onion suffix in the host depending + // on its length. + var suffixIndex int + switch len(host) { + case V2Len: + suffixIndex = V2Len - OnionSuffixLen + case V3Len: + suffixIndex = V3Len - OnionSuffixLen + default: + return false + } + + // Make sure the host ends with the ".onion" suffix. + if host[suffixIndex:] != OnionSuffix { + return false + } + + // We'll now attempt to decode the host without its suffix, as the + // suffix includes invalid characters. This will tell us if the host is + // actually valid if succesful. + host = host[:suffixIndex] + if _, err := Base32Encoding.DecodeString(host); err != nil { + return false + } + + return true +} From d6c2957f3c7e00932d2ac57e80ae8f8e27a7f487 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 26 Apr 2018 02:44:12 -0400 Subject: [PATCH 09/17] tor: add inital tor controller implementation In this commit, we add our inital implementation of a Tor Controller. This commit includes the ability for the controller to automatically signal the Tor daemon to create a v2 onion service. This will be expanded later on to support creating v3 onion services. Before allowing the controller to interact with the Tor daemon, the connection must be authenticated first. This commit includes support for the SAFECOOKIE authentication method as a sane default. Co-Authored-By: Eugene --- tor/README.md | 16 +- tor/controller.go | 448 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 459 insertions(+), 5 deletions(-) create mode 100644 tor/controller.go diff --git a/tor/README.md b/tor/README.md index a3d84d1d3..337f0159a 100644 --- a/tor/README.md +++ b/tor/README.md @@ -2,11 +2,17 @@ tor === The tor package contains utility functions that allow for interacting with the -Tor daemon. So far, supported functions include routing all traffic over Tor's -exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). In the -future more features will be added: automatic setup of v2 hidden service -functionality, control port functionality, and handling manually setup v3 hidden -services. +Tor daemon. So far, supported functions include: + +* Routing all traffic over Tor's exposed SOCKS5 proxy. +* Routing DNS queries over Tor (A, AAAA, SRV). +* Limited Tor Control functionality (synchronous messages only). So far, this +includes: + * Support for SAFECOOKIE authentication only as a sane default. + * Creating v2 onion services. + +In the future, the Tor Control functionality will be extended to support v3 +onion services, asynchronous messages, etc. ## Installation and Updating diff --git a/tor/controller.go b/tor/controller.go new file mode 100644 index 000000000..6a369ff80 --- /dev/null +++ b/tor/controller.go @@ -0,0 +1,448 @@ +package tor + +import ( + "bytes" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io/ioutil" + "net/textproto" + "os" + "strings" + "sync/atomic" +) + +const ( + // success is the Tor Control response code representing a successful + // request. + success = 250 + + // nonceLen is the length of a nonce generated by either the controller + // or the Tor server + nonceLen = 32 + + // cookieLen is the length of the authentication cookie. + cookieLen = 32 + + // ProtocolInfoVersion is the `protocolinfo` version currently supported + // by the Tor server. + ProtocolInfoVersion = 1 +) + +var ( + // serverKey is the key used when computing the HMAC-SHA256 of a message + // from the server. + serverKey = []byte("Tor safe cookie authentication " + + "server-to-controller hash") + + // controllerKey is the key used when computing the HMAC-SHA256 of a + // message from the controller. + controllerKey = []byte("Tor safe cookie authentication " + + "controller-to-server hash") +) + +// Controller is an implementation of the Tor Control protocol. This is used in +// order to communicate with a Tor server. Its only supported method of +// authentication is the SAFECOOKIE method. +// +// NOTE: The connection to the Tor server must be authenticated before +// proceeding to send commands. Otherwise, the connection will be closed. +// +// TODO: +// * if adding support for more commands, extend this with a command queue? +// * place under sub-package? +// * support async replies from the server +type Controller struct { + // started is used atomically in order to prevent multiple calls to + // Start. + started int32 + + // stopped is used atomically in order to prevent multiple calls to + // Stop. + stopped int32 + + // conn is the underlying connection between the controller and the + // Tor server. It provides read and write methods to simplify the + // text-based messages within the connection. + conn *textproto.Conn + + // controlAddr is the host:port the Tor server is listening locally for + // controller connections on. + controlAddr string +} + +// NewController returns a new Tor controller that will be able to interact with +// a Tor server. +func NewController(controlAddr string) *Controller { + return &Controller{controlAddr: controlAddr} +} + +// Start establishes and authenticates the connection between the controller and +// a Tor server. Once done, the controller will be able to send commands and +// expect responses. +func (c *Controller) Start() error { + if !atomic.CompareAndSwapInt32(&c.started, 0, 1) { + return nil + } + + conn, err := textproto.Dial("tcp", c.controlAddr) + if err != nil { + return fmt.Errorf("unable to connect to Tor server: %v", err) + } + + c.conn = conn + + return c.authenticate() +} + +// Stop closes the connection between the controller and the Tor server. +func (c *Controller) Stop() error { + if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { + return nil + } + + return c.conn.Close() +} + +// sendCommand sends a command to the Tor server and returns its response, as a +// single space-delimited string, and code. +func (c *Controller) sendCommand(command string) (int, string, error) { + if err := c.conn.Writer.PrintfLine(command); err != nil { + return 0, "", err + } + + // We'll use ReadResponse as it has built-in support for multi-line + // text protocol responses. + code, reply, err := c.conn.Reader.ReadResponse(success) + if err != nil { + return code, reply, err + } + + return code, reply, nil +} + +// parseTorReply parses the reply from the Tor server after receving a command +// from a controller. This will parse the relevent reply parameters into a map +// of keys and values. +func parseTorReply(reply string) map[string]string { + params := make(map[string]string) + + // Replies can either span single or multiple lines, so we'll default + // to stripping whitespace and newlines in order to retrieve the + // individual contents of it. The -1 indicates that we want this to span + // across all instances of a newline. + contents := strings.Split(strings.Replace(reply, "\n", " ", -1), " ") + for _, content := range contents { + // Each parameter within the reply should be of the form + // "KEY=VALUE". If the parameter doesn't contain "=", then we + // can assume it does not provide any other relevant information + // already known. + keyValue := strings.Split(content, "=") + if len(keyValue) != 2 { + continue + } + + key := keyValue[0] + value := keyValue[1] + params[key] = value + } + + return params +} + +// authenticate authenticates the connection between the controller and the +// Tor server using the SAFECOOKIE authentication method. +func (c *Controller) authenticate() error { + // Before proceeding to authenticate the connection, we'll retrieve + // the authentication cookie of the Tor server. This will be used + // throughout the authentication routine. We do this before as once the + // authentication routine has begun, it is not possible to retrieve it + // mid-way. + cookie, err := c.getAuthCookie() + if err != nil { + return fmt.Errorf("unable to retrieve authentication cookie: "+ + "%v", err) + } + + // Authenticating using the SAFECOOKIE authentication method is a two + // step process. We'll kick off the authentication routine by sending + // the AUTHCHALLENGE command followed by a hex-encoded 32-byte nonce. + clientNonce := make([]byte, nonceLen) + if _, err := rand.Read(clientNonce); err != nil { + return fmt.Errorf("unable to generate client nonce: %v", err) + } + + cmd := fmt.Sprintf("AUTHCHALLENGE SAFECOOKIE %x", clientNonce) + _, reply, err := c.sendCommand(cmd) + if err != nil { + return err + } + + // If successful, the reply from the server should be of the following + // format: + // + // "250 AUTHCHALLENGE" + // SP "SERVERHASH=" ServerHash + // SP "SERVERNONCE=" ServerNonce + // CRLF + // + // We're interested in retrieving the SERVERHASH and SERVERNONCE + // parameters, so we'll parse our reply to do so. + replyParams := parseTorReply(reply) + + // Once retrieved, we'll ensure these values are of proper length when + // decoded. + serverHash, ok := replyParams["SERVERHASH"] + if !ok { + return errors.New("server hash not found in reply") + } + decodedServerHash, err := hex.DecodeString(serverHash) + if err != nil { + return fmt.Errorf("unable to decode server hash: %v", err) + } + if len(decodedServerHash) != sha256.Size { + return errors.New("invalid server hash length") + } + + serverNonce, ok := replyParams["SERVERNONCE"] + if !ok { + return errors.New("server nonce not found in reply") + } + decodedServerNonce, err := hex.DecodeString(serverNonce) + if err != nil { + return fmt.Errorf("unable to decode server nonce: %v", err) + } + if len(decodedServerNonce) != nonceLen { + return errors.New("invalid server nonce length") + } + + // The server hash above was constructed by computing the HMAC-SHA256 + // of the message composed of the cookie, client nonce, and server + // nonce. We'll redo this computation ourselves to ensure the integrity + // and authentication of the message. + hmacMessage := bytes.Join( + [][]byte{cookie, clientNonce, decodedServerNonce}, []byte{}, + ) + computedServerHash := computeHMAC256(serverKey, hmacMessage) + if !hmac.Equal(computedServerHash, decodedServerHash) { + return fmt.Errorf("expected server hash %x, got %x", + decodedServerHash, computedServerHash) + } + + // If the MAC check was successful, we'll proceed with the last step of + // the authentication routine. We'll now send the AUTHENTICATE command + // followed by a hex-encoded client hash constructed by computing the + // HMAC-SHA256 of the same message, but this time using the controller's + // key. + clientHash := computeHMAC256(controllerKey, hmacMessage) + if len(clientHash) != sha256.Size { + return errors.New("invalid client hash length") + } + + cmd = fmt.Sprintf("AUTHENTICATE %x", clientHash) + if _, _, err := c.sendCommand(cmd); err != nil { + return err + } + + return nil +} + +// getAuthCookie retrieves the authentication cookie in bytes from the Tor +// server. Cookie authentication must be enabled for this to work. +func (c *Controller) getAuthCookie() ([]byte, error) { + // Retrieve the authentication methods currently supported by the Tor + // server. + authMethods, cookieFilePath, _, err := c.ProtocolInfo() + if err != nil { + return nil, err + } + + // Ensure that the Tor server supports the SAFECOOKIE authentication + // method. + safeCookieSupport := false + for _, authMethod := range authMethods { + if authMethod == "SAFECOOKIE" { + safeCookieSupport = true + } + } + + if !safeCookieSupport { + return nil, errors.New("the Tor server is currently not " + + "configured for cookie authentication") + } + + // Read the cookie from the file and ensure it has the correct length. + cookie, err := ioutil.ReadFile(cookieFilePath) + if err != nil { + return nil, err + } + + if len(cookie) != cookieLen { + return nil, errors.New("invalid authentication cookie length") + } + + return cookie, nil +} + +// computeHMAC256 computes the HMAC-SHA256 of a key and message. +func computeHMAC256(key, message []byte) []byte { + mac := hmac.New(sha256.New, key) + mac.Write(message) + return mac.Sum(nil) +} + +// ProtocolInfo returns the different authentication methods supported by the +// Tor server and the version of the Tor server. +func (c *Controller) ProtocolInfo() ([]string, string, string, error) { + // We'll start off by sending the "PROTOCOLINFO" command to the Tor + // server. We should receive a reply of the following format: + // + // METHODS=COOKIE,SAFECOOKIE + // COOKIEFILE="/home/user/.tor/control_auth_cookie" + // VERSION Tor="0.3.2.10" + // + // We're interested in retrieving all of these fields, so we'll parse + // our reply to do so. + cmd := fmt.Sprintf("PROTOCOLINFO %d", ProtocolInfoVersion) + _, reply, err := c.sendCommand(cmd) + if err != nil { + return nil, "", "", err + } + + info := parseTorReply(reply) + methods, ok := info["METHODS"] + if !ok { + return nil, "", "", errors.New("auth methods not found in " + + "reply") + } + + cookieFile, ok := info["COOKIEFILE"] + if !ok { + return nil, "", "", errors.New("cookie file path not found " + + "in reply") + } + + version, ok := info["Tor"] + if !ok { + return nil, "", "", errors.New("Tor version not found in reply") + } + + // Finally, we'll clean up the results before returning them. + authMethods := strings.Split(methods, ",") + cookieFilePath := strings.Trim(cookieFile, "\"") + torVersion := strings.Trim(version, "\"") + + return authMethods, cookieFilePath, torVersion, nil +} + +// VirtToTargPorts is a mapping of virtual ports to target ports. When creating +// an onion service, it will be listening externally on each virtual port. Each +// virtual port can then be mapped to one or many target ports internally. When +// accessing the onion service at a specific virtual port, it will forward the +// traffic to a mapped randomly chosen target port. +type VirtToTargPorts = map[int]map[int]struct{} + +// AddOnionV2 creates a new v2 onion service and returns its onion address(es). +// Once created, the new onion service will remain active until the connection +// between the controller and the Tor server is closed. The path to a private +// key can be provided in order to restore a previously created onion service. +// If a file at this path does not exist, a new onion service will be created +// and its private key will be saved to a file at this path. A mapping of +// virtual ports to target ports should also be provided. Each virtual port will +// be the ports where the onion service can be reached at, while the mapped +// target ports will be the ports where the onion service is running locally. +func (c *Controller) AddOnionV2(privateKeyFilename string, + virtToTargPorts VirtToTargPorts) ([]*OnionAddr, error) { + + // We'll start off by checking if the file containing the private key + // exists. If it does not, then we should request the server to create + // a new onion service and return its private key. Otherwise, we'll + // request the server to recreate the onion server from our private key. + var keyParam string + if _, err := os.Stat(privateKeyFilename); os.IsNotExist(err) { + keyParam = "NEW:RSA1024" + } else { + privateKey, err := ioutil.ReadFile(privateKeyFilename) + if err != nil { + return nil, err + } + keyParam = string(privateKey) + } + + // Now, we'll determine the different virtual ports on which this onion + // service will be accessed by. + var portParam string + for virtPort, targPorts := range virtToTargPorts { + // If the virtual port doesn't map to any target ports, we'll + // use the virtual port as the target port. + if len(targPorts) == 0 { + portParam += fmt.Sprintf("Port=%d,%d ", virtPort, + virtPort) + continue + } + + // Otherwise, we'll create a mapping from the virtual port to + // each target port. + for targPort := range targPorts { + portParam += fmt.Sprintf("Port=%d,%d ", virtPort, + targPort) + } + } + + cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam) + _, reply, err := c.sendCommand(cmd) + if err != nil { + return nil, err + } + + // If successful, the reply from the server should be of the following + // format, depending on whether a private key has been requested: + // + // C: ADD_ONION RSA1024:[Blob Redacted] Port=80,8080 + // S: 250-ServiceID=testonion1234567 + // S: 250 OK + // + // C: ADD_ONION NEW:RSA1024 Port=80,8080 + // S: 250-ServiceID=testonion1234567 + // S: 250-PrivateKey=RSA1024:[Blob Redacted] + // S: 250 OK + // + // We're interested in retrieving the service ID, which is the public + // name of the service, and the private key if requested. + replyParams := parseTorReply(reply) + serviceID, ok := replyParams["ServiceID"] + if !ok { + return nil, errors.New("service id not found in reply") + } + + // If a new onion service was created, we'll write its private key to + // disk under strict permissions in the event that it needs to be + // recreated later on. + if privateKey, ok := replyParams["PrivateKey"]; ok { + err := ioutil.WriteFile( + privateKeyFilename, []byte(privateKey), 0600, + ) + if err != nil { + return nil, fmt.Errorf("unable to write private key "+ + "to file: %v", err) + } + } + + // Finally, return the different onion addresses composed of the service + // ID, along with the onion suffix, and the different virtual ports this + // onion service can be reached at. + onionService := serviceID + ".onion" + addrs := make([]*OnionAddr, 0, len(virtToTargPorts)) + for virtPort := range virtToTargPorts { + addr := &OnionAddr{ + OnionService: onionService, + Port: virtPort, + } + addrs = append(addrs, addr) + } + + return addrs, nil +} From 978fc7ba081767e3b8cc9fa81fca0fca61c900c5 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 16:54:35 -0400 Subject: [PATCH 10/17] config+lnd: update tor config to include onion services flags In this commit, we update the set of Tor flags to use sane defaults when not specified. We also include some new flags related to the recent onion services changes. This allows users to easily get set up on Tor by only specifying the tor.active flag. If needed, the defaults can still be overridden. --- config.go | 196 ++++++++++++++++++++++++++++++++++++++---------------- lnd.go | 7 +- 2 files changed, 142 insertions(+), 61 deletions(-) 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 From 3669a40c52d9d140fda1184c3560a8ccecf1e011 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 16:59:19 -0400 Subject: [PATCH 11/17] server: add support to advertise onion addresses In this commit, we allow `lnd` to properly parse onion addresses in order to advertise them to the network when set through the `--externalip` flag. Co-Authored-By: Eugene --- server.go | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/server.go b/server.go index 080f2539d..23bb1adcf 100644 --- a/server.go +++ b/server.go @@ -28,6 +28,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/tor" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/connmgr" @@ -139,6 +140,42 @@ type server struct { wg sync.WaitGroup } +// parseAddr parses an address from its string format to a net.Addr. +func parseAddr(address string) (net.Addr, error) { + var ( + host string + port int + ) + + // Split the address into its host and port components. + h, p, err := net.SplitHostPort(address) + if err != nil { + // If a port wasn't specified, we'll assume the address only + // contains the host so we'll use the default port. + host = address + port = defaultPeerPort + } else { + // Otherwise, we'll note both the host and ports. + host = h + portNum, err := strconv.Atoi(p) + if err != nil { + return nil, err + } + port = portNum + } + + if tor.IsOnionHost(host) { + return &tor.OnionAddr{OnionService: host, Port: port}, nil + } + + // If the host is part of a TCP address, we'll use the network + // specific ResolveTCPAddr function in order to resolve these + // addresses over Tor in order to prevent leaking your real IP + // address. + hostPort := net.JoinHostPort(host, strconv.Itoa(port)) + return cfg.net.ResolveTCPAddr("tcp", hostPort) +} + // newServer creates a new instance of the server which is to listen using the // passed listener address. func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, @@ -251,25 +288,15 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, } // If external IP addresses have been specified, add those to the list - // of this server's addresses. We need to use the cfg.net.ResolveTCPAddr - // function in case we wish to resolve hosts over Tor since domains - // CAN be passed into the ExternalIPs configuration option. + // of this server's addresses. selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs)) for _, ip := range cfg.ExternalIPs { - var addr string - _, _, err = net.SplitHostPort(ip) - if err != nil { - addr = net.JoinHostPort(ip, strconv.Itoa(defaultPeerPort)) - } else { - addr = ip - } - - lnAddr, err := cfg.net.ResolveTCPAddr("tcp", addr) + addr, err := parseAddr(ip) if err != nil { return nil, err } - selfAddrs = append(selfAddrs, lnAddr) + selfAddrs = append(selfAddrs, addr) } chanGraph := chanDB.ChannelGraph() From d6d0c26252efe6096116492e83f30560014bc835 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 16:59:59 -0400 Subject: [PATCH 12/17] rpcserver: add support to connect to onion addresses In this commit, we now allow connections to onion addresses due to recently adding support to properly parse them. Co-Authored-By: Eugene --- rpcserver.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/rpcserver.go b/rpcserver.go index 7aa1a2f9e..33d3ea922 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -9,8 +9,6 @@ import ( "fmt" "io" "math" - "net" - "strconv" "strings" "time" @@ -635,25 +633,14 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, return nil, fmt.Errorf("cannot make connection to self") } - // If the address doesn't already have a port, we'll assume the current - // default port. - var addr string - _, _, err = net.SplitHostPort(in.Addr.Host) - if err != nil { - addr = net.JoinHostPort(in.Addr.Host, strconv.Itoa(defaultPeerPort)) - } else { - addr = in.Addr.Host - } - - // We use ResolveTCPAddr here in case we wish to resolve hosts over Tor. - host, err := cfg.net.ResolveTCPAddr("tcp", addr) + addr, err := parseAddr(in.Addr.Host) if err != nil { return nil, err } peerAddr := &lnwire.NetAddress{ IdentityKey: pubKey, - Address: host, + Address: addr, ChainNet: activeNetParams.Net, } From 3bb1733fa2ee9de20af588932e2c2689b3ac2b1e Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Sun, 29 Apr 2018 00:44:55 -0400 Subject: [PATCH 13/17] discovery+server: use network-specific functions for fallback SRV lookup In this commit, we fix a bug where a fallback SRV lookup would leak information if `lnd` was set to route connections over Tor. We solve this by using the network-specific functions rather than the standard ones found in the `net` package. --- discovery/bootstrapper.go | 36 +++++++++++++++++------------------- server.go | 10 ++-------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index aed159fb9..96695fb61 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -12,6 +12,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tor" "github.com/miekg/dns" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil/bech32" @@ -246,9 +247,8 @@ type DNSSeedBootstrapper struct { // in the tuple is a special A record that we'll query in order to // receive the IP address of the current authoritative DNS server for // the network seed. - dnsSeeds [][2]string - lookupHost func(string) ([]string, error) - lookupSRV func(string, string, string) (string, []*net.SRV, error) + dnsSeeds [][2]string + net tor.Net } // A compile time assertion to ensure that DNSSeedBootstrapper meets the @@ -262,14 +262,8 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) // used as a fallback for manual TCP resolution in the case of an error // receiving the UDP response. The second host should return a single A record // with the IP address of the authoritative name server. -func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string, error), - lookupSRV func(string, string, string) (string, []*net.SRV, error)) ( - NetworkPeerBootstrapper, error) { - return &DNSSeedBootstrapper{ - dnsSeeds: seeds, - lookupHost: lookupHost, - lookupSRV: lookupSRV, - }, nil +func NewDNSSeedBootstrapper(seeds [][2]string, net tor.Net) NetworkPeerBootstrapper { + return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net} } // fallBackSRVLookup attempts to manually query for SRV records we need to @@ -280,12 +274,14 @@ func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string // the records we return are currently too large for a class of resolvers, // causing them to be filtered out. The targetEndPoint is the original end // point that was meant to be hit. -func fallBackSRVLookup(soaShim string, targetEndPoint string) ([]*net.SRV, error) { +func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string, + targetEndPoint string) ([]*net.SRV, error) { + log.Tracef("Attempting to query fallback DNS seed") // First, we'll lookup the IP address of the server that will act as // our shim. - addrs, err := net.LookupHost(soaShim) + addrs, err := d.net.LookupHost(soaShim) if err != nil { return nil, err } @@ -293,7 +289,7 @@ func fallBackSRVLookup(soaShim string, targetEndPoint string) ([]*net.SRV, error // Once we have the IP address, we'll establish a TCP connection using // port 53. dnsServer := net.JoinHostPort(addrs[0], "53") - conn, err := net.Dial("tcp", dnsServer) + conn, err := d.net.Dial("tcp", dnsServer) if err != nil { return nil, err } @@ -356,10 +352,12 @@ search: // keys of nodes. We use the lndLookupSRV function for // this task. primarySeed := dnsSeedTuple[0] - _, addrs, err := d.lookupSRV("nodes", "tcp", primarySeed) + _, addrs, err := d.net.LookupSRV("nodes", "tcp", primarySeed) if err != nil { - log.Tracef("Unable to lookup SRV records via " + - "primary seed, falling back to secondary") + log.Tracef("Unable to lookup SRV records via "+ + "primary seed: %v", err) + + log.Trace("Falling back to secondary") // If the host of the secondary seed is blank, // then we'll bail here as we can't proceed. @@ -371,7 +369,7 @@ search: // the primary seed, we'll fallback to the // secondary seed before concluding failure. soaShim := dnsSeedTuple[1] - addrs, err = fallBackSRVLookup( + addrs, err = d.fallBackSRVLookup( soaShim, primarySeed, ) if err != nil { @@ -397,7 +395,7 @@ search: // key. We use the lndLookup function for this // task. bechNodeHost := nodeSrv.Target - addrs, err := d.lookupHost(bechNodeHost) + addrs, err := d.net.LookupHost(bechNodeHost) if err != nil { return nil, err } diff --git a/server.go b/server.go index 23bb1adcf..0f4d3cd91 100644 --- a/server.go +++ b/server.go @@ -720,15 +720,9 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e srvrLog.Infof("Creating DNS peer bootstrapper with "+ "seeds: %v", dnsSeeds) - dnsBootStrapper, err := discovery.NewDNSSeedBootstrapper( - dnsSeeds, - cfg.net.LookupHost, - cfg.net.LookupSRV, + dnsBootStrapper := discovery.NewDNSSeedBootstrapper( + dnsSeeds, cfg.net, ) - if err != nil { - return nil, err - } - bootStrappers = append(bootStrappers, dnsBootStrapper) } } From 1012f876e183a3e2b508ef1bf24807b3b1db8520 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 17:02:05 -0400 Subject: [PATCH 14/17] server: add automatic creation of v2 onion services In this commit, we allow the daemon to use the recently introduced Tor Controller implementation. This will automatically create a v2 onion service at startup in order to listen for inbound connections over Tor. Co-Authored-By: Eugene --- server.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/server.go b/server.go index 0f4d3cd91..d7f9e7229 100644 --- a/server.go +++ b/server.go @@ -74,6 +74,16 @@ type server struct { // long-term identity private key. lightningID [32]byte + // listenAddrs is the list of addresses the server is currently + // listening on. + listenAddrs []string + + // torController is a client that will communicate with a locally + // running Tor server. This client will handle initiating and + // authenticating the connection to the Tor server, automatically + // creating and setting up onion services, etc. + torController *tor.Controller + mu sync.RWMutex peersByPub map[string]*peer @@ -215,6 +225,8 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, identityPriv: privKey, nodeSigner: newNodeSigner(privKey), + listenAddrs: listenAddrs, + // TODO(roasbeef): derive proper onion key based on rotation // schedule sphinx: htlcswitch.NewOnionProcessor(sphinxRouter), @@ -299,6 +311,16 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, selfAddrs = append(selfAddrs, addr) } + // 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. + // + // NOTE: v3 onion services cannot be created automatically yet. In the + // future, this will be expanded to do so. + if cfg.Tor.Active && cfg.Tor.V2 { + s.torController = tor.NewController(cfg.Tor.Control) + } + chanGraph := chanDB.ChannelGraph() // Parse node color from configuration. @@ -588,6 +610,12 @@ func (s *server) Start() error { return nil } + if s.torController != nil { + if err := s.initTorController(); err != nil { + return err + } + } + // Start the notification server. This is used so channel management // goroutines can be notified when a funding transaction reaches a // sufficient number of confirmations, or when the input for the @@ -659,6 +687,10 @@ 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.cc.chainNotifier.Stop() s.chanRouter.Stop() @@ -881,6 +913,49 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, } } +// 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 + } + + // 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. + listenPorts := make(map[int]struct{}) + for _, listenAddr := range s.listenAddrs { + // At this point, the listen addresses should have already been + // normalized, so it's safe to ignore the errors. + _, portStr, _ := net.SplitHostPort(listenAddr) + port, _ := strconv.Atoi(portStr) + listenPorts[port] = struct{}{} + } + + // Once the port mapping has been set, we can go ahead and automatically + // create our onion service. The service's private key will be saved to + // disk in order to regain access to this service when restarting `lnd`. + virtToTargPorts := tor.VirtToTargPorts{defaultPeerPort: listenPorts} + onionServiceAddrs, err := s.torController.AddOnionV2( + cfg.Tor.V2PrivateKeyPath, virtToTargPorts, + ) + if err != nil { + return err + } + + // Now that the onion service has been created, we'll add the different + // onion addresses it can be reached at to our list of advertised + // addresses. + for _, addr := range onionServiceAddrs { + s.currentNodeAnn.Addresses = append( + s.currentNodeAnn.Addresses, addr, + ) + } + + return nil +} + // genNodeAnnouncement generates and returns the current fully signed node // announcement. If refresh is true, then the time stamp of the announcement // will be updated in order to ensure it propagates through the network. From 5b58d24f78bf35f42502ef9c276279e885d99ac5 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Fri, 27 Apr 2018 17:03:28 -0400 Subject: [PATCH 15/17] docs: update documentation for inbound connections --- docs/configuring_tor.md | 157 ++++++++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 45 deletions(-) diff --git a/docs/configuring_tor.md b/docs/configuring_tor.md index d4395fb1e..eebef2869 100644 --- a/docs/configuring_tor.md +++ b/docs/configuring_tor.md @@ -1,24 +1,29 @@ # Table of Contents -1. [Overview](#Overview) -2. [Outbound Connections Only](#outbound-connections-only) +1. [Overview](#overview) +2. [Getting Started](#getting-started) 3. [Tor Stream Isolation](#tor-stream-isolation) +4. [Listening for Inbound Connections](#listening-for-inbound-connections) + 1. [v2 Onion Services](#v2-onion-services) + 2. [v3 Onion Services](#v3-onion-services) -## 1. Overview +## Overview -`lnd` currently has _partial_ support for using Lightning over +`lnd` currently has complete support for using Lightning over [Tor](https://www.torproject.org/). Usage of Lightning over Tor is valuable as routing nodes no longer need to potentially expose their location via their advertised IP address. Additionally, leaf nodes can also protect their location -by using Tor for anonymous networking to establish connections. +by using Tor for anonymous networking to establish connections. -At the time of the writing of this documentation, `lnd` only supports usage of -Tor for establishing _outbound_ connections. In the near future, support for -full [Onion Service](https://www.torproject.org/docs/onion-services.html.en) -usage will be added as well. Support for both `v2` and `v3` onion services are -planned. With widespread usage of Onion Services within the network, concerns -about the difficulty of proper NAT traversal are alleviated, as usage of Onion -Services allows nodes to accept inbound connections even if they're behind a -NAT. +With widespread usage of Onion Services within the network, concerns about the +difficulty of proper NAT traversal are alleviated, as usage of Onion Services +allows nodes to accept inbound connections even if they're behind a NAT. + +At the time of writing this documentation, `lnd` supports both types of onion +services: v2 and v3. However, only v2 onion services can automatically be +created and set up by `lnd` until Tor Control support for v3 onion services is +implemented in the stable release of the Tor daemon. v3 onion services can be +used as long as they are set up manually. We'll cover the steps on how to do +these things below. Before following the remainder of this documentation, you should ensure that you already have Tor installed locally. Official instructions to install the @@ -26,29 +31,19 @@ latest release of Tor can be found [here](https://www.torproject.org/docs/tor-doc-unix.html.en). **NOTE**: This documentation covers how to ensure that `lnd`'s _Lightning -protocol traffic_ is tunnled over Tor. Users will need to take care that if -they're running using a Bitcoin full-node, then that is also configured to -proxy all trafic over Tor. If using the `neutrino` backend for `lnd`, then it -will automatically also default to Tor usage if active within `lnd`. +protocol traffic_ is tunneled over Tor. Users must ensure that when also running +a Bitcoin full-node, that it is also proxying all traffic over Tor. If using the +`neutrino` backend for `lnd`, then it will automatically also default to Tor +usage if active within `lnd`. - -## 2. Outbound Connections Only - -Currenty, `lnd` only supports purely _outbound_ Tor usage. In this mode, `lnd` -_won't_ listen at all, and will only be able to establish outbound connections. -_All_ protocol traffic will be tunneled over Tor. Additionally, we'll also -force any DNS requests over Tor such that we don't leak our IP address to the -clear net. - -The remainder of this tutorial assumes one already has the `tor` daemon -installed locally. +## Getting Started First, you'll want to run `tor` locally before starting up `lnd`. Depending on how you installed Tor, you'll find the configuration file at `/usr/local/etc/tor/torrc`. Here's an example configuration file that we'll be using for the remainder of the tutorial: ``` -SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections. +SOCKSPort 9050 Log notice stdout ControlPort 9051 CookieAuthentication 1 @@ -84,28 +79,49 @@ At this point, we can now start `lnd` with the relevant arguments: Tor: - --tor.socks= 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 - --tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled + --tor.active Allow outbound and inbound connections to be routed through Tor + --tor.socks= The port that Tor's exposed SOCKS5 proxy is listening on -- NOTE port must be between 1024 and 65535 (default: 9050) + --tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled (default: soa.nodes.lightning.directory:53) + --tor.streamisolation Enable Tor stream isolation by randomizing user credentials for each connection. + --tor.controlport= The port that Tor is listening on for Tor control connections -- NOTE port must be between 1024 and 65535 (default: 9051) + --tor.v2 Automatically set up a v2 onion service to listen for inbound connections + --tor.v3 Use a v3 onion service to listen for inbound connections + --tor.privatekeypath= The path to the private key of the onion service being created (default: /Users/user/Library/Application Support/Lnd/onion_private_key) ``` -The `--tor.socks` argument should point to the interface that the `Tor` daemon -is listening on to proxy connections. The `--tor.dns` flag is required in order -to be able to properly automatically bootstrap a set of peer connections. The -`tor` daemon doesn't currently support proxying `SRV` queries over Tor. So -instead, we need to connect directly to the authoritative DNS server over TCP, -in order query for `SRV` records that we can use to bootstrap our connections. -As of the time this documentation was written, for Bitcoin's Testnet, clients -should point to `nodes.lightning.directory`. +There are a couple things here, so let's dissect them. The `--tor.active` flag +allows `lnd` to route all outbound and inbound connections through Tor. -Finally, we'll start `lnd` with the proper arguments: -``` -⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory +Outbound connections are possible with the use of the `--tor.socks` and +`--tor.dns` arguments. The `--tor.socks` argument should point to the interface +that the `Tor` daemon is listening on to proxy connections. The `--tor.dns` flag +is required in order to be able to properly automatically bootstrap a set of +peer connections. The `tor` daemon doesn't currently support proxying `SRV` +queries over Tor. So instead, we need to connect directly to the authoritative +DNS server over TCP, in order query for `SRV` records that we can use to +bootstrap our connections. + +Inbound connections are possible due to `lnd` automatically creating a v2 onion +service. A path to save the onion service's private key can be specified with +the `--tor.privatekeypath` flag. A v3 onion service can also be used, but it +must be created manually. We'll expand on how this works in [Listening for +Inbound Connections](#listening-for-inbound-connections). + +Most of these arguments have defaults, so as long as they apply to you, routing +all outbound and inbound connections through Tor can simply be done with: +```shell +⛰ ./lnd --tor.active --tor.v2 ``` -With the above arguments, `lnd` will proxy _all_ network traffic over Tor! +Outbound support only can also be used with: +```shell +⛰ ./lnd --tor.active +``` +This will allow you to make all outgoing connections over Tor, but still allow +regular (clearnet) incoming connections. -## 3. Tor Stream Isolation +## Tor Stream Isolation Our support for Tor also has an additional privacy enhancing modified: stream isolation. Usage of this mode means that Tor will always use _new circuit_ for @@ -116,5 +132,56 @@ circuit. Activating stream isolation is very straightforward, we only require the specification of an additional argument: ``` -⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory --tor.streamisolation +⛰ ./lnd --tor.active --tor.streamisolation ``` + +## Listening for Inbound Connections + +In order to listen for inbound connections through Tor, an onion service must be +created. There are two types of onion services: v2 and v3. + +### v2 Onion Services + +v2 onion services can be created automatically by `lnd` and are currently the +default. To do so, run `lnd` with the following arguments: +``` +⛰ ./lnd --tor.active --tor.v2 +``` + +This will automatically create a hidden service for your node to use to listen +for inbound connections and advertise itself to the network. The onion service's +private key is saved to a file named `onion_private_key` in `lnd`'s base +directory. This will allow `lnd` to recreate the same hidden service upon +restart. If you wish to generate a new onion service, you can simply delete this +file. The path to this private key file can also be modified with the +`--tor.privatekeypath` argument. + +### v3 Onion Services + +v3 onion services are the latest generation of onion services and they provide a +number of advantages over the legacy v2 onion services. To learn more about +these benefits, see [Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions). + +Unfortunately, at the time of writing this, v3 onion service support is still +at an alpha level in the Tor daemon, so we're unable to automatically set them +up within `lnd` unlike with v2 onion services. However, they can still be run +manually! To do so, append the following lines to the torrc sample from above: +``` +HiddenServiceDir PATH_TO_HIDDEN_SERVICE +HiddenServiceVersion 3 +HiddenServicePort PORT_ONION_SERVICE_LISTENS_ON ADDRESS_LND_LISTENS_ON +``` + +If needed, instructions on how to set up a v3 onion service manually can be +found [here](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions#Howtosetupyourownprop224service). + +Once the v3 onion service is set up, `lnd` is able to use it to listen for +inbound connections. You'll also need the onion service's hostname in order to +advertise your node to the network. To do so, run `lnd` with the following +arguments: +``` +⛰ ./lnd --tor.active --tor.v3 --externalip=ONION_SERVICE_HOSTNAME +``` + +Once v3 onion service support is stable, `lnd` will be updated to also +automatically set up v3 onion services. From 08f80b6cf35d427bf9e1a39079543cccfb63ae1c Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 2 May 2018 02:30:56 -0400 Subject: [PATCH 16/17] server: use advertised address when reestablishing inbound connections In this commit, we update the way we reestablish inbound connections if we lose connectivity to a node we have an open channel with. Rather than fetching the node's advertised port, we'll fetch one of their advertised addresses instead. This ensure that if the remote node is running behind a proxy, we do not see the proxy's address. --- server.go | 61 ++++++++++++++++--------------------------------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/server.go b/server.go index d7f9e7229..a99893c20 100644 --- a/server.go +++ b/server.go @@ -1542,17 +1542,18 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, addr := conn.RemoteAddr() pubKey := brontideConn.RemotePub() - // We'll ensure that we locate the proper port to use within the peer's - // address for reconnecting purposes. - if tcpAddr, ok := addr.(*net.TCPAddr); ok && !inbound { - targetPort := s.fetchNodeAdvertisedPort(pubKey, tcpAddr) - - // Once we have the correct port, we'll make a new copy of the - // address so we don't modify the underlying pointer directly. - addr = &net.TCPAddr{ - IP: tcpAddr.IP, - Port: targetPort, - Zone: tcpAddr.Zone, + // We'll ensure that we locate an advertised address to use within the + // peer's address for reconnection purposes. + // + // TODO: leave the address field empty if there aren't any? + if !inbound { + advertisedAddr, err := s.fetchNodeAdvertisedAddr(pubKey) + if err != nil { + srvrLog.Errorf("Unable to retrieve advertised address "+ + "for node %x: %v", pubKey.SerializeCompressed(), + err) + } else { + addr = advertisedAddr } } @@ -2231,42 +2232,16 @@ func computeNextBackoff(currBackoff time.Duration) time.Duration { return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2) } -// fetchNodeAdvertisedPort attempts to fetch the advertised port of the target -// node. If a port isn't found, then the default port will be used. -func (s *server) fetchNodeAdvertisedPort(pub *btcec.PublicKey, - targetAddr *net.TCPAddr) int { - - // If the target port is already the default peer port, then we'll - // return that. - if targetAddr.Port == defaultPeerPort { - return defaultPeerPort - } - +// fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node. +func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) { node, err := s.chanDB.ChannelGraph().FetchLightningNode(pub) - - // If the node wasn't found, then we'll just return the current default - // port. if err != nil { - return defaultPeerPort + return nil, err } - // Otherwise, we'll attempt to find a matching advertised IP, and will - // then use the port for that. - for _, addr := range node.Addresses { - // We'll only examine an address if it's a TCP address. - tcpAddr, ok := addr.(*net.TCPAddr) - if !ok { - continue - } - - // If this is the matching IP, then we'll return the port that - // it has been advertised with. - if tcpAddr.IP.Equal(targetAddr.IP) { - return tcpAddr.Port - } + if len(node.Addresses) == 0 { + return nil, errors.New("no advertised addresses found") } - // If we couldn't find a matching IP, then we'll just return the - // default port. - return defaultPeerPort + return node.Addresses[0], nil } From 2e0484be19e1a146895489b1f25cdfce8b23f589 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 2 May 2018 02:33:17 -0400 Subject: [PATCH 17/17] multi: ensure addresses are no longer assumed to be TCP addresses only In this commit, we go through the codebase looking for TCP address assumptions and modifying them to include the recently introduced onion addresses. This enables us to fully support onion addresses within the daemon. --- channeldb/nodes.go | 4 +--- discovery/bootstrapper.go | 14 +++++++------- pilot.go | 17 ++++++----------- server.go | 40 ++++++++++++++++++++------------------- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/channeldb/nodes.go b/channeldb/nodes.go index 10fe69b5e..8c3ff79e6 100644 --- a/channeldb/nodes.go +++ b/channeldb/nodes.go @@ -54,8 +54,6 @@ type LinkNode struct { // Addresses is a list of IP address in which either we were able to // reach the node over in the past, OR we received an incoming // authenticated connection for the stored identity public key. - // - // TODO(roasbeef): also need to support hidden service addrs Addresses []net.Addr db *DB @@ -85,7 +83,7 @@ func (l *LinkNode) UpdateLastSeen(lastSeen time.Time) error { // AddAddress appends the specified TCP address to the list of known addresses // this node is/was known to be reachable at. -func (l *LinkNode) AddAddress(addr *net.TCPAddr) error { +func (l *LinkNode) AddAddress(addr net.Addr) error { for _, a := range l.Addresses { if a.String() == addr.String() { return nil diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index 96695fb61..be192ef18 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -165,12 +165,12 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, // If we haven't yet reached our limit, then // we'll copy over the details of this node // into the set of addresses to be returned. - tcpAddr, ok := nodeAddr.(*net.TCPAddr) - if !ok { - // If this isn't a valid TCP address, - // then we'll ignore it as currently - // we'll only attempt to connect out to - // TCP peers. + switch nodeAddr.(type) { + case *net.TCPAddr, *tor.OnionAddr: + default: + // If this isn't a valid address + // supported by the protocol, then we'll + // skip this node. return nil } @@ -179,7 +179,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, // error. a = append(a, &lnwire.NetAddress{ IdentityKey: node.PubKey(), - Address: tcpAddr, + Address: nodeAddr, }) } diff --git a/pilot.go b/pilot.go index 91cb1285c..2acb76a31 100644 --- a/pilot.go +++ b/pilot.go @@ -7,6 +7,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tor" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" @@ -49,18 +50,12 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey, // advertised IP addresses, or have made a connection. var connected bool for _, addr := range addrs { - // If the address doesn't already have a port, then - // we'll assume the current default port. - tcpAddr, ok := addr.(*net.TCPAddr) - if !ok { - return fmt.Errorf("TCP address required instead "+ - "have %T", addr) + switch addr.(type) { + case *net.TCPAddr, *tor.OnionAddr: + lnAddr.Address = addr + default: + return fmt.Errorf("unknown address type %T", addr) } - if tcpAddr.Port == 0 { - tcpAddr.Port = defaultPeerPort - } - - lnAddr.Address = tcpAddr // TODO(roasbeef): make perm connection in server after // chan open? diff --git a/server.go b/server.go index a99893c20..04fddd0b8 100644 --- a/server.go +++ b/server.go @@ -1016,17 +1016,7 @@ func (s *server) establishPersistentConnections() error { return err } for _, node := range linkNodes { - for _, address := range node.Addresses { - switch addr := address.(type) { - case *net.TCPAddr: - if addr.Port == 0 { - addr.Port = defaultPeerPort - } - } - - } pubStr := string(node.IdentityPub.SerializeCompressed()) - nodeAddrs := &nodeAddresses{ pubKey: node.IdentityPub, addresses: node.Addresses, @@ -1059,17 +1049,29 @@ func (s *server) establishPersistentConnections() error { linkNodeAddrs, ok := nodeAddrsMap[pubStr] if ok { for _, lnAddress := range linkNodeAddrs.addresses { - lnAddrTCP, ok := lnAddress.(*net.TCPAddr) - if !ok { + var addrHost string + switch addr := lnAddress.(type) { + case *net.TCPAddr: + addrHost = addr.IP.String() + case *tor.OnionAddr: + addrHost = addr.OnionService + default: continue } var addrMatched bool for _, polAddress := range policy.Node.Addresses { - polTCPAddr, ok := polAddress.(*net.TCPAddr) - if ok && polTCPAddr.IP.Equal(lnAddrTCP.IP) { - addrMatched = true - addrs = append(addrs, polTCPAddr) + switch addr := polAddress.(type) { + case *net.TCPAddr: + if addr.IP.String() == addrHost { + addrMatched = true + addrs = append(addrs, addr) + } + case *tor.OnionAddr: + if addr.OnionService == addrHost { + addrMatched = true + addrs = append(addrs, addr) + } } } if !addrMatched { @@ -1078,9 +1080,9 @@ func (s *server) establishPersistentConnections() error { } } else { for _, addr := range policy.Node.Addresses { - polTCPAddr, ok := addr.(*net.TCPAddr) - if ok { - addrs = append(addrs, polTCPAddr) + switch addr.(type) { + case *net.TCPAddr, *tor.OnionAddr: + addrs = append(addrs, addr) } } }