Merge pull request #1159 from wpaulino/onion-service-support

multi: add onion services support
This commit is contained in:
Olaoluwa Osuntokun 2018-06-04 21:46:51 -07:00 committed by GitHub
commit d98d4523e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1694 additions and 623 deletions

2
Gopkg.lock generated

@ -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

@ -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

51
channeldb/addr_test.go Normal file

@ -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)
}
}
}

@ -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

196
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,

@ -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"
@ -164,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
}
@ -178,7 +179,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
// error.
a = append(a, &lnwire.NetAddress{
IdentityKey: node.PubKey(),
Address: tcpAddr,
Address: nodeAddr,
})
}
@ -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
}

@ -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:
<snip>
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.

7
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

@ -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}

@ -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) {

@ -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?

@ -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,
}

239
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"
@ -73,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
@ -139,6 +150,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,
@ -178,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),
@ -251,25 +300,25 @@ 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)
}
// 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()
@ -561,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
@ -632,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()
@ -693,15 +752,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)
}
}
@ -861,6 +914,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.
@ -921,17 +1017,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,
@ -964,17 +1050,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 {
@ -983,9 +1081,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)
}
}
}
@ -1447,17 +1545,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
}
}
@ -2136,42 +2235,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
}

21
tor/README.md Normal file

@ -0,0 +1,21 @@
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.
* 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
```bash
$ go get -u github.com/lightningnetwork/lnd/tor
```

448
tor/controller.go Normal file

@ -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
}

104
tor/net.go Normal file

@ -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)
}

63
tor/onionaddr.go Normal file

@ -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"
}

243
tor/tor.go Normal file

@ -0,0 +1,243 @@
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",
}
)
// 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) {
// 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
}
// 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
}

@ -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
```

@ -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)
}

@ -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)
}

@ -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
}