Merge pull request #1159 from wpaulino/onion-service-support
multi: add onion services support
This commit is contained in:
commit
d98d4523e1
2
Gopkg.lock
generated
2
Gopkg.lock
generated
@ -359,6 +359,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "3e7512c1772a70c004d9557850137e4dd82545ec2e16381b4694ff4a0c5a6819"
|
inputs-digest = "2133b0035a81c856475302a127bc26d30217a30d1e41708d3604f2de82e1ab31"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package channeldb
|
package channeldb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/btcsuite/go-socks/socks"
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addressType specifies the network protocol and version that should be used
|
// 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 denotes a version 2 Tor onion service address.
|
||||||
v2OnionAddr addressType = 2
|
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
|
v3OnionAddr addressType = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// encodeTCPAddr serializes a TCP address into its compact raw bytes
|
||||||
|
// representation.
|
||||||
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
|
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
|
||||||
var scratch [16]byte
|
var (
|
||||||
|
addrType byte
|
||||||
|
ip []byte
|
||||||
|
)
|
||||||
|
|
||||||
if addr.IP.To4() != nil {
|
if addr.IP.To4() != nil {
|
||||||
scratch[0] = uint8(tcp4Addr)
|
addrType = byte(tcp4Addr)
|
||||||
if _, err := w.Write(scratch[:1]); err != nil {
|
ip = addr.IP.To4()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(scratch[:4], addr.IP.To4())
|
|
||||||
if _, err := w.Write(scratch[:4]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
scratch[0] = uint8(tcp6Addr)
|
addrType = byte(tcp6Addr)
|
||||||
if _, err := w.Write(scratch[:1]); err != nil {
|
ip = addr.IP.To16()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write([]byte{addrType}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(scratch[:], addr.IP.To16())
|
if _, err := w.Write(ip); err != nil {
|
||||||
if _, err := w.Write(scratch[:]); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var port [2]byte
|
||||||
|
byteOrder.PutUint16(port[:], uint16(addr.Port))
|
||||||
|
if _, err := w.Write(port[:]); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
byteOrder.PutUint16(scratch[:2], uint16(addr.Port))
|
return nil
|
||||||
if _, err := w.Write(scratch[:2]); err != 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
|
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
|
// deserializeAddr reads the serialized raw representation of an address and
|
||||||
// deserializes it into the actual address, to avoid performing address
|
// deserializes it into the actual address. This allows us to avoid address
|
||||||
// resolution in the database module
|
// resolution within the channeldb package.
|
||||||
func deserializeAddr(r io.Reader) (net.Addr, error) {
|
func deserializeAddr(r io.Reader) (net.Addr, error) {
|
||||||
var scratch [8]byte
|
var addrType [1]byte
|
||||||
var address net.Addr
|
if _, err := r.Read(addrType[:]); err != nil {
|
||||||
|
|
||||||
if _, err := r.Read(scratch[:1]); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): also add onion addrs
|
var address net.Addr
|
||||||
switch addressType(scratch[0]) {
|
switch addressType(addrType[0]) {
|
||||||
case tcp4Addr:
|
case tcp4Addr:
|
||||||
addr := &net.TCPAddr{}
|
|
||||||
var ip [4]byte
|
var ip [4]byte
|
||||||
if _, err := r.Read(ip[:]); err != nil {
|
if _, err := r.Read(ip[:]); err != nil {
|
||||||
return nil, err
|
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
|
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:
|
case tcp6Addr:
|
||||||
addr := &net.TCPAddr{}
|
|
||||||
var ip [16]byte
|
var ip [16]byte
|
||||||
if _, err := r.Read(ip[:]); err != nil {
|
if _, err := r.Read(ip[:]); err != nil {
|
||||||
return nil, err
|
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
|
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:
|
default:
|
||||||
return nil, ErrUnknownAddressType
|
return nil, ErrUnknownAddressType
|
||||||
}
|
}
|
||||||
@ -103,34 +184,14 @@ func deserializeAddr(r io.Reader) (net.Addr, error) {
|
|||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// serializeAddr serializes an address into a raw byte representation so it
|
// serializeAddr serializes an address into its raw bytes representation so that
|
||||||
// can be deserialized without requiring address resolution
|
// it can be deserialized without requiring address resolution.
|
||||||
func serializeAddr(w io.Writer, address net.Addr) error {
|
func serializeAddr(w io.Writer, address net.Addr) error {
|
||||||
|
|
||||||
switch addr := address.(type) {
|
switch addr := address.(type) {
|
||||||
case *net.TCPAddr:
|
case *net.TCPAddr:
|
||||||
return encodeTCPAddr(w, addr)
|
return encodeTCPAddr(w, addr)
|
||||||
|
case *tor.OnionAddr:
|
||||||
// If this is a proxied address (due to the connection being
|
return encodeOnionAddr(w, addr)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
51
channeldb/addr_test.go
Normal file
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
|
// 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
|
// reach the node over in the past, OR we received an incoming
|
||||||
// authenticated connection for the stored identity public key.
|
// authenticated connection for the stored identity public key.
|
||||||
//
|
|
||||||
// TODO(roasbeef): also need to support hidden service addrs
|
|
||||||
Addresses []net.Addr
|
Addresses []net.Addr
|
||||||
|
|
||||||
db *DB
|
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
|
// AddAddress appends the specified TCP address to the list of known addresses
|
||||||
// this node is/was known to be reachable at.
|
// 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 {
|
for _, a := range l.Addresses {
|
||||||
if a.String() == addr.String() {
|
if a.String() == addr.String() {
|
||||||
return nil
|
return nil
|
||||||
|
178
config.go
178
config.go
@ -5,6 +5,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -22,7 +23,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/brontide"
|
"github.com/lightningnetwork/lnd/brontide"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/torsvc"
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
@ -50,6 +51,12 @@ const (
|
|||||||
defaultMaxLogFiles = 3
|
defaultMaxLogFiles = 3
|
||||||
defaultMaxLogFileSize = 10
|
defaultMaxLogFileSize = 10
|
||||||
|
|
||||||
|
defaultTorSOCKSPort = 9050
|
||||||
|
defaultTorDNSHost = "soa.nodes.lightning.directory"
|
||||||
|
defaultTorDNSPort = 53
|
||||||
|
defaultTorControlPort = 9051
|
||||||
|
defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
|
||||||
|
|
||||||
defaultBroadcastDelta = 10
|
defaultBroadcastDelta = 10
|
||||||
|
|
||||||
// minTimeLockDelta is the minimum timelock we require for incoming
|
// minTimeLockDelta is the minimum timelock we require for incoming
|
||||||
@ -81,6 +88,11 @@ var (
|
|||||||
|
|
||||||
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
|
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
|
||||||
defaultLitecoindDir = btcutil.AppDataDir("litecoin", 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 {
|
type chainConfig struct {
|
||||||
@ -136,9 +148,14 @@ type autoPilotConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type torConfig 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"`
|
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"`
|
||||||
DNS string `long:"dns" description:"The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
|
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."`
|
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.
|
// config defines the configuration options for lnd.
|
||||||
@ -188,7 +205,7 @@ type config struct {
|
|||||||
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
|
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
|
||||||
LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"`
|
LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"`
|
||||||
|
|
||||||
Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"`
|
Autopilot *autoPilotConfig `group:"Autopilot" namespace:"autopilot"`
|
||||||
|
|
||||||
Tor *torConfig `group:"Tor" namespace:"tor"`
|
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."`
|
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
|
// loadConfig initializes and parses the config using a config file and command
|
||||||
@ -275,6 +292,13 @@ func loadConfig() (*config, error) {
|
|||||||
Alias: defaultAlias,
|
Alias: defaultAlias,
|
||||||
Color: defaultColor,
|
Color: defaultColor,
|
||||||
MinChanSize: int64(minChanFundingSize),
|
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
|
// 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.InvoiceMacPath = filepath.Join(lndDir, defaultInvoiceMacFilename)
|
||||||
defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename)
|
defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename)
|
||||||
defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
|
defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
|
||||||
|
defaultCfg.Tor.V2PrivateKeyPath = filepath.Join(lndDir, defaultTorV2PrivateKeyFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the lnd directory if it doesn't already exist.
|
// 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.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir)
|
||||||
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
|
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
|
||||||
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.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
|
// Ensure that the user didn't attempt to specify negative values for
|
||||||
// options. The default is to use the standard golang "net" package
|
// any of the autopilot params.
|
||||||
// functions. When Tor's proxy is specified, the dial function is set to
|
if cfg.Autopilot.MaxChannels < 0 {
|
||||||
// the proxy specific dial function and the DNS resolution functions use
|
str := "%s: autopilot.maxchannels must be non-negative"
|
||||||
// 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)
|
err := fmt.Errorf(str, funcName)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
fmt.Fprintln(os.Stderr, usageMessage)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if cfg.Autopilot.Allocation < 0 {
|
||||||
// If ExternalIPs is set, throw an error since we cannot
|
str := "%s: autopilot.allocation must be non-negative"
|
||||||
// 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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.net = &torsvc.TorProxyNet{
|
// Ensure that the specified values for the min and max channel size
|
||||||
TorDNS: cfg.Tor.DNS,
|
// don't are within the bounds of the normal chan size constraints.
|
||||||
TorSocks: cfg.Tor.Socks,
|
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,
|
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"
|
|
||||||
err := fmt.Errorf(str, funcName)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the active chain configuration and its parameters.
|
||||||
switch {
|
switch {
|
||||||
// At this moment, multiple active chains are not supported.
|
// At this moment, multiple active chains are not supported.
|
||||||
case cfg.Litecoin.Active && cfg.Bitcoin.Active:
|
case cfg.Litecoin.Active && cfg.Bitcoin.Active:
|
||||||
@ -734,6 +789,23 @@ func loadConfig() (*config, error) {
|
|||||||
cfg.Listeners = normalizeAddresses(cfg.Listeners,
|
cfg.Listeners = normalizeAddresses(cfg.Listeners,
|
||||||
strconv.Itoa(defaultPeerPort))
|
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
|
// Warn about missing config file only after all other configuration is
|
||||||
// done. This prevents the warning on help messages and invalid
|
// done. This prevents the warning on help messages and invalid
|
||||||
// options. Note this should go directly before the return.
|
// 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))
|
result := make([]string, 0, len(addrs))
|
||||||
seen := map[string]struct{}{}
|
seen := map[string]struct{}{}
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
addr = normalizeAddress(addr, defaultPort)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := seen[addr]; !ok {
|
if _, ok := seen[addr]; !ok {
|
||||||
result = append(result, addr)
|
result = append(result, addr)
|
||||||
seen[addr] = struct{}{}
|
seen[addr] = struct{}{}
|
||||||
@ -1130,6 +1194,24 @@ func normalizeAddresses(addrs []string, defaultPort string) []string {
|
|||||||
return result
|
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
|
// enforceSafeAuthentication enforces "safe" authentication taking into account
|
||||||
// the interfaces that the RPC servers are listening on, and if macaroons are
|
// the interfaces that the RPC servers are listening on, and if macaroons are
|
||||||
// activated or not. To project users from using dangerous config combinations,
|
// activated or not. To project users from using dangerous config combinations,
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/autopilot"
|
"github.com/lightningnetwork/lnd/autopilot"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcutil/bech32"
|
"github.com/roasbeef/btcutil/bech32"
|
||||||
@ -164,12 +165,12 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
|||||||
// If we haven't yet reached our limit, then
|
// If we haven't yet reached our limit, then
|
||||||
// we'll copy over the details of this node
|
// we'll copy over the details of this node
|
||||||
// into the set of addresses to be returned.
|
// into the set of addresses to be returned.
|
||||||
tcpAddr, ok := nodeAddr.(*net.TCPAddr)
|
switch nodeAddr.(type) {
|
||||||
if !ok {
|
case *net.TCPAddr, *tor.OnionAddr:
|
||||||
// If this isn't a valid TCP address,
|
default:
|
||||||
// then we'll ignore it as currently
|
// If this isn't a valid address
|
||||||
// we'll only attempt to connect out to
|
// supported by the protocol, then we'll
|
||||||
// TCP peers.
|
// skip this node.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
|||||||
// error.
|
// error.
|
||||||
a = append(a, &lnwire.NetAddress{
|
a = append(a, &lnwire.NetAddress{
|
||||||
IdentityKey: node.PubKey(),
|
IdentityKey: node.PubKey(),
|
||||||
Address: tcpAddr,
|
Address: nodeAddr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,8 +248,7 @@ type DNSSeedBootstrapper struct {
|
|||||||
// receive the IP address of the current authoritative DNS server for
|
// receive the IP address of the current authoritative DNS server for
|
||||||
// the network seed.
|
// the network seed.
|
||||||
dnsSeeds [][2]string
|
dnsSeeds [][2]string
|
||||||
lookupHost func(string) ([]string, error)
|
net tor.Net
|
||||||
lookupSRV func(string, string, string) (string, []*net.SRV, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time assertion to ensure that DNSSeedBootstrapper meets the
|
// 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
|
// 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
|
// receiving the UDP response. The second host should return a single A record
|
||||||
// with the IP address of the authoritative name server.
|
// with the IP address of the authoritative name server.
|
||||||
func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string, error),
|
func NewDNSSeedBootstrapper(seeds [][2]string, net tor.Net) NetworkPeerBootstrapper {
|
||||||
lookupSRV func(string, string, string) (string, []*net.SRV, error)) (
|
return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net}
|
||||||
NetworkPeerBootstrapper, error) {
|
|
||||||
return &DNSSeedBootstrapper{
|
|
||||||
dnsSeeds: seeds,
|
|
||||||
lookupHost: lookupHost,
|
|
||||||
lookupSRV: lookupSRV,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallBackSRVLookup attempts to manually query for SRV records we need to
|
// 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,
|
// 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
|
// causing them to be filtered out. The targetEndPoint is the original end
|
||||||
// point that was meant to be hit.
|
// 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")
|
log.Tracef("Attempting to query fallback DNS seed")
|
||||||
|
|
||||||
// First, we'll lookup the IP address of the server that will act as
|
// First, we'll lookup the IP address of the server that will act as
|
||||||
// our shim.
|
// our shim.
|
||||||
addrs, err := net.LookupHost(soaShim)
|
addrs, err := d.net.LookupHost(soaShim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Once we have the IP address, we'll establish a TCP connection using
|
||||||
// port 53.
|
// port 53.
|
||||||
dnsServer := net.JoinHostPort(addrs[0], "53")
|
dnsServer := net.JoinHostPort(addrs[0], "53")
|
||||||
conn, err := net.Dial("tcp", dnsServer)
|
conn, err := d.net.Dial("tcp", dnsServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -356,10 +352,12 @@ search:
|
|||||||
// keys of nodes. We use the lndLookupSRV function for
|
// keys of nodes. We use the lndLookupSRV function for
|
||||||
// this task.
|
// this task.
|
||||||
primarySeed := dnsSeedTuple[0]
|
primarySeed := dnsSeedTuple[0]
|
||||||
_, addrs, err := d.lookupSRV("nodes", "tcp", primarySeed)
|
_, addrs, err := d.net.LookupSRV("nodes", "tcp", primarySeed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("Unable to lookup SRV records via "+
|
log.Tracef("Unable to lookup SRV records via "+
|
||||||
"primary seed, falling back to secondary")
|
"primary seed: %v", err)
|
||||||
|
|
||||||
|
log.Trace("Falling back to secondary")
|
||||||
|
|
||||||
// If the host of the secondary seed is blank,
|
// If the host of the secondary seed is blank,
|
||||||
// then we'll bail here as we can't proceed.
|
// then we'll bail here as we can't proceed.
|
||||||
@ -371,7 +369,7 @@ search:
|
|||||||
// the primary seed, we'll fallback to the
|
// the primary seed, we'll fallback to the
|
||||||
// secondary seed before concluding failure.
|
// secondary seed before concluding failure.
|
||||||
soaShim := dnsSeedTuple[1]
|
soaShim := dnsSeedTuple[1]
|
||||||
addrs, err = fallBackSRVLookup(
|
addrs, err = d.fallBackSRVLookup(
|
||||||
soaShim, primarySeed,
|
soaShim, primarySeed,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -397,7 +395,7 @@ search:
|
|||||||
// key. We use the lndLookup function for this
|
// key. We use the lndLookup function for this
|
||||||
// task.
|
// task.
|
||||||
bechNodeHost := nodeSrv.Target
|
bechNodeHost := nodeSrv.Target
|
||||||
addrs, err := d.lookupHost(bechNodeHost)
|
addrs, err := d.net.LookupHost(bechNodeHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
# Table of Contents
|
# Table of Contents
|
||||||
1. [Overview](#Overview)
|
1. [Overview](#overview)
|
||||||
2. [Outbound Connections Only](#outbound-connections-only)
|
2. [Getting Started](#getting-started)
|
||||||
3. [Tor Stream Isolation](#tor-stream-isolation)
|
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
|
[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
|
routing nodes no longer need to potentially expose their location via their
|
||||||
advertised IP address. Additionally, leaf nodes can also protect their location
|
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
|
With widespread usage of Onion Services within the network, concerns about the
|
||||||
Tor for establishing _outbound_ connections. In the near future, support for
|
difficulty of proper NAT traversal are alleviated, as usage of Onion Services
|
||||||
full [Onion Service](https://www.torproject.org/docs/onion-services.html.en)
|
allows nodes to accept inbound connections even if they're behind a NAT.
|
||||||
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
|
At the time of writing this documentation, `lnd` supports both types of onion
|
||||||
about the difficulty of proper NAT traversal are alleviated, as usage of Onion
|
services: v2 and v3. However, only v2 onion services can automatically be
|
||||||
Services allows nodes to accept inbound connections even if they're behind a
|
created and set up by `lnd` until Tor Control support for v3 onion services is
|
||||||
NAT.
|
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
|
Before following the remainder of this documentation, you should ensure that
|
||||||
you already have Tor installed locally. Official instructions to install the
|
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).
|
[here](https://www.torproject.org/docs/tor-doc-unix.html.en).
|
||||||
|
|
||||||
**NOTE**: This documentation covers how to ensure that `lnd`'s _Lightning
|
**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
|
protocol traffic_ is tunneled over Tor. Users must ensure that when also running
|
||||||
they're running using a Bitcoin full-node, then that is also configured to
|
a Bitcoin full-node, that it is also proxying all traffic over Tor. If using the
|
||||||
proxy all trafic over Tor. If using the `neutrino` backend for `lnd`, then it
|
`neutrino` backend for `lnd`, then it will automatically also default to Tor
|
||||||
will automatically also default to Tor usage if active within `lnd`.
|
usage if active within `lnd`.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
## 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.
|
|
||||||
|
|
||||||
First, you'll want to run `tor` locally before starting up `lnd`. Depending on
|
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
|
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
|
`/usr/local/etc/tor/torrc`. Here's an example configuration file that we'll be
|
||||||
using for the remainder of the tutorial:
|
using for the remainder of the tutorial:
|
||||||
```
|
```
|
||||||
SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections.
|
SOCKSPort 9050
|
||||||
Log notice stdout
|
Log notice stdout
|
||||||
ControlPort 9051
|
ControlPort 9051
|
||||||
CookieAuthentication 1
|
CookieAuthentication 1
|
||||||
@ -84,28 +79,49 @@ At this point, we can now start `lnd` with the relevant arguments:
|
|||||||
<snip>
|
<snip>
|
||||||
|
|
||||||
Tor:
|
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.active Allow outbound and inbound connections to be routed through Tor
|
||||||
--tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled
|
--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
|
There are a couple things here, so let's dissect them. The `--tor.active` flag
|
||||||
is listening on to proxy connections. The `--tor.dns` flag is required in order
|
allows `lnd` to route all outbound and inbound connections through Tor.
|
||||||
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`.
|
|
||||||
|
|
||||||
Finally, we'll start `lnd` with the proper arguments:
|
Outbound connections are possible with the use of the `--tor.socks` and
|
||||||
```
|
`--tor.dns` arguments. The `--tor.socks` argument should point to the interface
|
||||||
⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory
|
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
|
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
|
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
|
Activating stream isolation is very straightforward, we only require the
|
||||||
specification of an additional argument:
|
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
7
lnd.go
@ -304,11 +304,10 @@ func lndMain() error {
|
|||||||
}
|
}
|
||||||
idPrivKey.Curve = btcec.S256()
|
idPrivKey.Curve = btcec.S256()
|
||||||
|
|
||||||
if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" {
|
if cfg.Tor.Active {
|
||||||
srvrLog.Infof("Proxying all network traffic via Tor "+
|
srvrLog.Infof("Proxying all network traffic via Tor "+
|
||||||
"(stream_isolation=%v)! NOTE: If running with a full-node "+
|
"(stream_isolation=%v)! NOTE: Ensure the backend node "+
|
||||||
"backend, ensure that is proxying over Tor as well",
|
"is proxying over Tor as well", cfg.Tor.StreamIsolation)
|
||||||
cfg.Tor.StreamIsolation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the core server which will listen for incoming peer
|
// Set up the core server which will listen for incoming peer
|
||||||
|
114
lnwire/lnwire.go
114
lnwire/lnwire.go
@ -11,6 +11,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
@ -43,8 +44,7 @@ const (
|
|||||||
// v2OnionAddr denotes a version 2 Tor onion service address.
|
// v2OnionAddr denotes a version 2 Tor onion service address.
|
||||||
v2OnionAddr addressType = 3
|
v2OnionAddr addressType = 3
|
||||||
|
|
||||||
// v3OnionAddr denotes a version 3 Tor (prop224) onion service
|
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
|
||||||
// addresses
|
|
||||||
v3OnionAddr addressType = 4
|
v3OnionAddr addressType = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,16 +56,12 @@ func (a addressType) AddrLen() uint16 {
|
|||||||
return 0
|
return 0
|
||||||
case tcp4Addr:
|
case tcp4Addr:
|
||||||
return 6
|
return 6
|
||||||
|
|
||||||
case tcp6Addr:
|
case tcp6Addr:
|
||||||
return 18
|
return 18
|
||||||
|
|
||||||
case v2OnionAddr:
|
case v2OnionAddr:
|
||||||
return 12
|
return 12
|
||||||
|
|
||||||
case v3OnionAddr:
|
case v3OnionAddr:
|
||||||
return 37
|
return 37
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -300,7 +296,6 @@ func writeElement(w io.Writer, element interface{}) error {
|
|||||||
return fmt.Errorf("cannot write nil TCPAddr")
|
return fmt.Errorf("cannot write nil TCPAddr")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): account for onion types too
|
|
||||||
if e.IP.To4() != nil {
|
if e.IP.To4() != nil {
|
||||||
var descriptor [1]byte
|
var descriptor [1]byte
|
||||||
descriptor[0] = uint8(tcp4Addr)
|
descriptor[0] = uint8(tcp4Addr)
|
||||||
@ -331,6 +326,45 @@ func writeElement(w io.Writer, element interface{}) error {
|
|||||||
return err
|
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:
|
case []net.Addr:
|
||||||
// First, we'll encode all the addresses into an intermediate
|
// First, we'll encode all the addresses into an intermediate
|
||||||
// buffer. We need to do this in order to compute the total
|
// 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
|
addresses []net.Addr
|
||||||
addrBytesRead uint16
|
addrBytesRead uint16
|
||||||
)
|
)
|
||||||
|
|
||||||
for addrBytesRead < addrsLen {
|
for addrBytesRead < addrsLen {
|
||||||
var descriptor [1]byte
|
var descriptor [1]byte
|
||||||
if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil {
|
if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil {
|
||||||
@ -640,52 +675,87 @@ func readElement(r io.Reader, element interface{}) error {
|
|||||||
|
|
||||||
addrBytesRead++
|
addrBytesRead++
|
||||||
|
|
||||||
address := &net.TCPAddr{}
|
var address net.Addr
|
||||||
aType := addressType(descriptor[0])
|
switch aType := addressType(descriptor[0]); aType {
|
||||||
switch aType {
|
|
||||||
|
|
||||||
case noAddr:
|
case noAddr:
|
||||||
addrBytesRead += aType.AddrLen()
|
addrBytesRead += aType.AddrLen()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case tcp4Addr:
|
case tcp4Addr:
|
||||||
var ip [4]byte
|
var ip [4]byte
|
||||||
if _, err = io.ReadFull(addrBuf, ip[:]); err != nil {
|
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
address.IP = (net.IP)(ip[:])
|
|
||||||
|
|
||||||
var port [2]byte
|
var port [2]byte
|
||||||
if _, err = io.ReadFull(addrBuf, port[:]); err != nil {
|
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
|
||||||
return err
|
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()
|
addrBytesRead += aType.AddrLen()
|
||||||
|
|
||||||
case tcp6Addr:
|
case tcp6Addr:
|
||||||
var ip [16]byte
|
var ip [16]byte
|
||||||
if _, err = io.ReadFull(addrBuf, ip[:]); err != nil {
|
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
address.IP = (net.IP)(ip[:])
|
|
||||||
|
|
||||||
var port [2]byte
|
var port [2]byte
|
||||||
if _, err = io.ReadFull(addrBuf, port[:]); err != nil {
|
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
|
||||||
return err
|
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()
|
addrBytesRead += aType.AddrLen()
|
||||||
|
|
||||||
case v2OnionAddr:
|
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()
|
addrBytesRead += aType.AddrLen()
|
||||||
continue
|
|
||||||
|
|
||||||
case v3OnionAddr:
|
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()
|
addrBytesRead += aType.AddrLen()
|
||||||
continue
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return &ErrUnknownAddrType{aType}
|
return &ErrUnknownAddrType{aType}
|
||||||
|
@ -2,6 +2,7 @@ package lnwire
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
@ -37,11 +39,6 @@ var (
|
|||||||
}
|
}
|
||||||
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
|
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
|
||||||
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 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) {
|
func randPubKey() (*btcec.PublicKey, error) {
|
||||||
@ -76,6 +73,100 @@ func randRawFeatureVector(r *rand.Rand) *RawFeatureVector {
|
|||||||
return featureVec
|
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) {
|
func TestMaxOutPointIndex(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -465,8 +556,6 @@ func TestLightningWireProtocol(t *testing.T) {
|
|||||||
G: uint8(r.Int31()),
|
G: uint8(r.Int31()),
|
||||||
B: uint8(r.Int31()),
|
B: uint8(r.Int31()),
|
||||||
},
|
},
|
||||||
// TODO(roasbeef): proper gen rand addrs
|
|
||||||
Addresses: testAddrs,
|
|
||||||
}
|
}
|
||||||
req.Signature, err = NewSigFromSignature(testSig)
|
req.Signature, err = NewSigFromSignature(testSig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -480,6 +569,11 @@ func TestLightningWireProtocol(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Addresses, err = randAddrs(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate addresses: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
v[0] = reflect.ValueOf(req)
|
v[0] = reflect.ValueOf(req)
|
||||||
},
|
},
|
||||||
MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) {
|
MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) {
|
||||||
|
17
pilot.go
17
pilot.go
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/autopilot"
|
"github.com/lightningnetwork/lnd/autopilot"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/roasbeef/btcutil"
|
||||||
@ -49,18 +50,12 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
|
|||||||
// advertised IP addresses, or have made a connection.
|
// advertised IP addresses, or have made a connection.
|
||||||
var connected bool
|
var connected bool
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
// If the address doesn't already have a port, then
|
switch addr.(type) {
|
||||||
// we'll assume the current default port.
|
case *net.TCPAddr, *tor.OnionAddr:
|
||||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
lnAddr.Address = addr
|
||||||
if !ok {
|
default:
|
||||||
return fmt.Errorf("TCP address required instead "+
|
return fmt.Errorf("unknown address type %T", addr)
|
||||||
"have %T", addr)
|
|
||||||
}
|
}
|
||||||
if tcpAddr.Port == 0 {
|
|
||||||
tcpAddr.Port = defaultPeerPort
|
|
||||||
}
|
|
||||||
|
|
||||||
lnAddr.Address = tcpAddr
|
|
||||||
|
|
||||||
// TODO(roasbeef): make perm connection in server after
|
// TODO(roasbeef): make perm connection in server after
|
||||||
// chan open?
|
// chan open?
|
||||||
|
17
rpcserver.go
17
rpcserver.go
@ -9,8 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -635,25 +633,14 @@ func (r *rpcServer) ConnectPeer(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("cannot make connection to self")
|
return nil, fmt.Errorf("cannot make connection to self")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the address doesn't already have a port, we'll assume the current
|
addr, err := parseAddr(in.Addr.Host)
|
||||||
// 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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
peerAddr := &lnwire.NetAddress{
|
peerAddr := &lnwire.NetAddress{
|
||||||
IdentityKey: pubKey,
|
IdentityKey: pubKey,
|
||||||
Address: host,
|
Address: addr,
|
||||||
ChainNet: activeNetParams.Net,
|
ChainNet: activeNetParams.Net,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
237
server.go
237
server.go
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/roasbeef/btcd/btcec"
|
"github.com/roasbeef/btcd/btcec"
|
||||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
"github.com/roasbeef/btcd/connmgr"
|
"github.com/roasbeef/btcd/connmgr"
|
||||||
@ -73,6 +74,16 @@ type server struct {
|
|||||||
// long-term identity private key.
|
// long-term identity private key.
|
||||||
lightningID [32]byte
|
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
|
mu sync.RWMutex
|
||||||
peersByPub map[string]*peer
|
peersByPub map[string]*peer
|
||||||
|
|
||||||
@ -139,6 +150,42 @@ type server struct {
|
|||||||
wg sync.WaitGroup
|
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
|
// newServer creates a new instance of the server which is to listen using the
|
||||||
// passed listener address.
|
// passed listener address.
|
||||||
func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
||||||
@ -178,6 +225,8 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
identityPriv: privKey,
|
identityPriv: privKey,
|
||||||
nodeSigner: newNodeSigner(privKey),
|
nodeSigner: newNodeSigner(privKey),
|
||||||
|
|
||||||
|
listenAddrs: listenAddrs,
|
||||||
|
|
||||||
// TODO(roasbeef): derive proper onion key based on rotation
|
// TODO(roasbeef): derive proper onion key based on rotation
|
||||||
// schedule
|
// schedule
|
||||||
sphinx: htlcswitch.NewOnionProcessor(sphinxRouter),
|
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
|
// 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
|
// of this server's addresses.
|
||||||
// function in case we wish to resolve hosts over Tor since domains
|
|
||||||
// CAN be passed into the ExternalIPs configuration option.
|
|
||||||
selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs))
|
selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs))
|
||||||
for _, ip := range cfg.ExternalIPs {
|
for _, ip := range cfg.ExternalIPs {
|
||||||
var addr string
|
addr, err := parseAddr(ip)
|
||||||
_, _, err = net.SplitHostPort(ip)
|
|
||||||
if err != nil {
|
|
||||||
addr = net.JoinHostPort(ip, strconv.Itoa(defaultPeerPort))
|
|
||||||
} else {
|
|
||||||
addr = ip
|
|
||||||
}
|
|
||||||
|
|
||||||
lnAddr, err := cfg.net.ResolveTCPAddr("tcp", addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
chanGraph := chanDB.ChannelGraph()
|
||||||
@ -561,6 +610,12 @@ func (s *server) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.torController != nil {
|
||||||
|
if err := s.initTorController(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start the notification server. This is used so channel management
|
// Start the notification server. This is used so channel management
|
||||||
// goroutines can be notified when a funding transaction reaches a
|
// goroutines can be notified when a funding transaction reaches a
|
||||||
// sufficient number of confirmations, or when the input for the
|
// sufficient number of confirmations, or when the input for the
|
||||||
@ -632,6 +687,10 @@ func (s *server) Stop() error {
|
|||||||
|
|
||||||
close(s.quit)
|
close(s.quit)
|
||||||
|
|
||||||
|
if s.torController != nil {
|
||||||
|
s.torController.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
// Shutdown the wallet, funding manager, and the rpc server.
|
// Shutdown the wallet, funding manager, and the rpc server.
|
||||||
s.cc.chainNotifier.Stop()
|
s.cc.chainNotifier.Stop()
|
||||||
s.chanRouter.Stop()
|
s.chanRouter.Stop()
|
||||||
@ -693,15 +752,9 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e
|
|||||||
srvrLog.Infof("Creating DNS peer bootstrapper with "+
|
srvrLog.Infof("Creating DNS peer bootstrapper with "+
|
||||||
"seeds: %v", dnsSeeds)
|
"seeds: %v", dnsSeeds)
|
||||||
|
|
||||||
dnsBootStrapper, err := discovery.NewDNSSeedBootstrapper(
|
dnsBootStrapper := discovery.NewDNSSeedBootstrapper(
|
||||||
dnsSeeds,
|
dnsSeeds, cfg.net,
|
||||||
cfg.net.LookupHost,
|
|
||||||
cfg.net.LookupSRV,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bootStrappers = append(bootStrappers, dnsBootStrapper)
|
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
|
// genNodeAnnouncement generates and returns the current fully signed node
|
||||||
// announcement. If refresh is true, then the time stamp of the announcement
|
// announcement. If refresh is true, then the time stamp of the announcement
|
||||||
// will be updated in order to ensure it propagates through the network.
|
// will be updated in order to ensure it propagates through the network.
|
||||||
@ -921,17 +1017,7 @@ func (s *server) establishPersistentConnections() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, node := range linkNodes {
|
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())
|
pubStr := string(node.IdentityPub.SerializeCompressed())
|
||||||
|
|
||||||
nodeAddrs := &nodeAddresses{
|
nodeAddrs := &nodeAddresses{
|
||||||
pubKey: node.IdentityPub,
|
pubKey: node.IdentityPub,
|
||||||
addresses: node.Addresses,
|
addresses: node.Addresses,
|
||||||
@ -964,17 +1050,29 @@ func (s *server) establishPersistentConnections() error {
|
|||||||
linkNodeAddrs, ok := nodeAddrsMap[pubStr]
|
linkNodeAddrs, ok := nodeAddrsMap[pubStr]
|
||||||
if ok {
|
if ok {
|
||||||
for _, lnAddress := range linkNodeAddrs.addresses {
|
for _, lnAddress := range linkNodeAddrs.addresses {
|
||||||
lnAddrTCP, ok := lnAddress.(*net.TCPAddr)
|
var addrHost string
|
||||||
if !ok {
|
switch addr := lnAddress.(type) {
|
||||||
|
case *net.TCPAddr:
|
||||||
|
addrHost = addr.IP.String()
|
||||||
|
case *tor.OnionAddr:
|
||||||
|
addrHost = addr.OnionService
|
||||||
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var addrMatched bool
|
var addrMatched bool
|
||||||
for _, polAddress := range policy.Node.Addresses {
|
for _, polAddress := range policy.Node.Addresses {
|
||||||
polTCPAddr, ok := polAddress.(*net.TCPAddr)
|
switch addr := polAddress.(type) {
|
||||||
if ok && polTCPAddr.IP.Equal(lnAddrTCP.IP) {
|
case *net.TCPAddr:
|
||||||
|
if addr.IP.String() == addrHost {
|
||||||
addrMatched = true
|
addrMatched = true
|
||||||
addrs = append(addrs, polTCPAddr)
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
case *tor.OnionAddr:
|
||||||
|
if addr.OnionService == addrHost {
|
||||||
|
addrMatched = true
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !addrMatched {
|
if !addrMatched {
|
||||||
@ -983,9 +1081,9 @@ func (s *server) establishPersistentConnections() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, addr := range policy.Node.Addresses {
|
for _, addr := range policy.Node.Addresses {
|
||||||
polTCPAddr, ok := addr.(*net.TCPAddr)
|
switch addr.(type) {
|
||||||
if ok {
|
case *net.TCPAddr, *tor.OnionAddr:
|
||||||
addrs = append(addrs, polTCPAddr)
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1447,17 +1545,18 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
addr := conn.RemoteAddr()
|
addr := conn.RemoteAddr()
|
||||||
pubKey := brontideConn.RemotePub()
|
pubKey := brontideConn.RemotePub()
|
||||||
|
|
||||||
// We'll ensure that we locate the proper port to use within the peer's
|
// We'll ensure that we locate an advertised address to use within the
|
||||||
// address for reconnecting purposes.
|
// peer's address for reconnection purposes.
|
||||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok && !inbound {
|
//
|
||||||
targetPort := s.fetchNodeAdvertisedPort(pubKey, tcpAddr)
|
// TODO: leave the address field empty if there aren't any?
|
||||||
|
if !inbound {
|
||||||
// Once we have the correct port, we'll make a new copy of the
|
advertisedAddr, err := s.fetchNodeAdvertisedAddr(pubKey)
|
||||||
// address so we don't modify the underlying pointer directly.
|
if err != nil {
|
||||||
addr = &net.TCPAddr{
|
srvrLog.Errorf("Unable to retrieve advertised address "+
|
||||||
IP: tcpAddr.IP,
|
"for node %x: %v", pubKey.SerializeCompressed(),
|
||||||
Port: targetPort,
|
err)
|
||||||
Zone: tcpAddr.Zone,
|
} else {
|
||||||
|
addr = advertisedAddr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2136,42 +2235,16 @@ func computeNextBackoff(currBackoff time.Duration) time.Duration {
|
|||||||
return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2)
|
return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchNodeAdvertisedPort attempts to fetch the advertised port of the target
|
// fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node.
|
||||||
// node. If a port isn't found, then the default port will be used.
|
func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
node, err := s.chanDB.ChannelGraph().FetchLightningNode(pub)
|
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 {
|
if err != nil {
|
||||||
return defaultPeerPort
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we'll attempt to find a matching advertised IP, and will
|
if len(node.Addresses) == 0 {
|
||||||
// then use the port for that.
|
return nil, errors.New("no advertised addresses found")
|
||||||
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
|
return node.Addresses[0], nil
|
||||||
// it has been advertised with.
|
|
||||||
if tcpAddr.IP.Equal(targetAddr.IP) {
|
|
||||||
return tcpAddr.Port
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we couldn't find a matching IP, then we'll just return the
|
|
||||||
// default port.
|
|
||||||
return defaultPeerPort
|
|
||||||
}
|
}
|
||||||
|
21
tor/README.md
Normal file
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
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
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
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
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)
|
|
||||||
}
|
|
168
torsvc/torsvc.go
168
torsvc/torsvc.go
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user