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]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3e7512c1772a70c004d9557850137e4dd82545ec2e16381b4694ff4a0c5a6819"
|
||||
inputs-digest = "2133b0035a81c856475302a127bc26d30217a30d1e41708d3604f2de82e1ab31"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -1,10 +1,12 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/btcsuite/go-socks/socks"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
)
|
||||
|
||||
// addressType specifies the network protocol and version that should be used
|
||||
@ -21,38 +23,75 @@ const (
|
||||
// v2OnionAddr denotes a version 2 Tor onion service address.
|
||||
v2OnionAddr addressType = 2
|
||||
|
||||
// v3OnionAddr denotes a version 3 Tor (prop224) onion service addresses.
|
||||
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
|
||||
v3OnionAddr addressType = 3
|
||||
)
|
||||
|
||||
// encodeTCPAddr serializes a TCP address into its compact raw bytes
|
||||
// representation.
|
||||
func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
|
||||
var scratch [16]byte
|
||||
var (
|
||||
addrType byte
|
||||
ip []byte
|
||||
)
|
||||
|
||||
if addr.IP.To4() != nil {
|
||||
scratch[0] = uint8(tcp4Addr)
|
||||
if _, err := w.Write(scratch[:1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(scratch[:4], addr.IP.To4())
|
||||
if _, err := w.Write(scratch[:4]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addrType = byte(tcp4Addr)
|
||||
ip = addr.IP.To4()
|
||||
} else {
|
||||
scratch[0] = uint8(tcp6Addr)
|
||||
if _, err := w.Write(scratch[:1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(scratch[:], addr.IP.To16())
|
||||
if _, err := w.Write(scratch[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
addrType = byte(tcp6Addr)
|
||||
ip = addr.IP.To16()
|
||||
}
|
||||
|
||||
byteOrder.PutUint16(scratch[:2], uint16(addr.Port))
|
||||
if _, err := w.Write(scratch[:2]); err != nil {
|
||||
if _, err := w.Write([]byte{addrType}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(ip); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
byteOrder.PutUint16(port[:], uint16(addr.Port))
|
||||
if _, err := w.Write(port[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeOnionAddr serializes an onion address into its compact raw bytes
|
||||
// representation.
|
||||
func encodeOnionAddr(w io.Writer, addr *tor.OnionAddr) error {
|
||||
var suffixIndex int
|
||||
switch len(addr.OnionService) {
|
||||
case tor.V2Len:
|
||||
if _, err := w.Write([]byte{byte(v2OnionAddr)}); err != nil {
|
||||
return err
|
||||
}
|
||||
suffixIndex = tor.V2Len - tor.OnionSuffixLen
|
||||
case tor.V3Len:
|
||||
if _, err := w.Write([]byte{byte(v3OnionAddr)}); err != nil {
|
||||
return err
|
||||
}
|
||||
suffixIndex = tor.V3Len - tor.OnionSuffixLen
|
||||
default:
|
||||
return errors.New("unknown onion service length")
|
||||
}
|
||||
|
||||
host, err := tor.Base32Encoding.DecodeString(
|
||||
addr.OnionService[:suffixIndex],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(host); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
byteOrder.PutUint16(port[:], uint16(addr.Port))
|
||||
if _, err := w.Write(port[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -60,42 +99,84 @@ func encodeTCPAddr(w io.Writer, addr *net.TCPAddr) error {
|
||||
}
|
||||
|
||||
// deserializeAddr reads the serialized raw representation of an address and
|
||||
// deserializes it into the actual address, to avoid performing address
|
||||
// resolution in the database module
|
||||
// deserializes it into the actual address. This allows us to avoid address
|
||||
// resolution within the channeldb package.
|
||||
func deserializeAddr(r io.Reader) (net.Addr, error) {
|
||||
var scratch [8]byte
|
||||
var address net.Addr
|
||||
|
||||
if _, err := r.Read(scratch[:1]); err != nil {
|
||||
var addrType [1]byte
|
||||
if _, err := r.Read(addrType[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): also add onion addrs
|
||||
switch addressType(scratch[0]) {
|
||||
var address net.Addr
|
||||
switch addressType(addrType[0]) {
|
||||
case tcp4Addr:
|
||||
addr := &net.TCPAddr{}
|
||||
var ip [4]byte
|
||||
if _, err := r.Read(ip[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr.IP = (net.IP)(ip[:])
|
||||
if _, err := r.Read(scratch[:2]); err != nil {
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr.Port = int(byteOrder.Uint16(scratch[:2]))
|
||||
address = addr
|
||||
|
||||
address = &net.TCPAddr{
|
||||
IP: net.IP(ip[:]),
|
||||
Port: int(binary.BigEndian.Uint16(port[:])),
|
||||
}
|
||||
case tcp6Addr:
|
||||
addr := &net.TCPAddr{}
|
||||
var ip [16]byte
|
||||
if _, err := r.Read(ip[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr.IP = (net.IP)(ip[:])
|
||||
if _, err := r.Read(scratch[:2]); err != nil {
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr.Port = int(byteOrder.Uint16(scratch[:2]))
|
||||
address = addr
|
||||
|
||||
address = &net.TCPAddr{
|
||||
IP: net.IP(ip[:]),
|
||||
Port: int(binary.BigEndian.Uint16(port[:])),
|
||||
}
|
||||
case v2OnionAddr:
|
||||
var h [tor.V2DecodedLen]byte
|
||||
if _, err := r.Read(h[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var p [2]byte
|
||||
if _, err := r.Read(p[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(h[:])
|
||||
onionService += tor.OnionSuffix
|
||||
port := int(binary.BigEndian.Uint16(p[:]))
|
||||
|
||||
address = &tor.OnionAddr{
|
||||
OnionService: onionService,
|
||||
Port: port,
|
||||
}
|
||||
case v3OnionAddr:
|
||||
var h [tor.V3DecodedLen]byte
|
||||
if _, err := r.Read(h[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var p [2]byte
|
||||
if _, err := r.Read(p[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(h[:])
|
||||
onionService += tor.OnionSuffix
|
||||
port := int(binary.BigEndian.Uint16(p[:]))
|
||||
|
||||
address = &tor.OnionAddr{
|
||||
OnionService: onionService,
|
||||
Port: port,
|
||||
}
|
||||
default:
|
||||
return nil, ErrUnknownAddressType
|
||||
}
|
||||
@ -103,34 +184,14 @@ func deserializeAddr(r io.Reader) (net.Addr, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// serializeAddr serializes an address into a raw byte representation so it
|
||||
// can be deserialized without requiring address resolution
|
||||
// serializeAddr serializes an address into its raw bytes representation so that
|
||||
// it can be deserialized without requiring address resolution.
|
||||
func serializeAddr(w io.Writer, address net.Addr) error {
|
||||
|
||||
switch addr := address.(type) {
|
||||
case *net.TCPAddr:
|
||||
return encodeTCPAddr(w, addr)
|
||||
|
||||
// If this is a proxied address (due to the connection being
|
||||
// established over a SOCKs proxy, then we'll convert it into its
|
||||
// corresponding TCP address.
|
||||
case *socks.ProxiedAddr:
|
||||
// If we can't parse the host as an IP (though we should be
|
||||
// able to at this point), then we'll skip this address all
|
||||
// together.
|
||||
//
|
||||
// TODO(roasbeef): would be nice to be able to store hosts
|
||||
// though...
|
||||
ip := net.ParseIP(addr.Host)
|
||||
if ip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tcpAddr := &net.TCPAddr{
|
||||
IP: ip,
|
||||
Port: addr.Port,
|
||||
}
|
||||
return encodeTCPAddr(w, tcpAddr)
|
||||
case *tor.OnionAddr:
|
||||
return encodeOnionAddr(w, addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
51
channeldb/addr_test.go
Normal file
51
channeldb/addr_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
)
|
||||
|
||||
// TestAddrSerialization tests that the serialization method used by channeldb
|
||||
// for net.Addr's works as intended.
|
||||
func TestAddrSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testAddrs := []net.Addr{
|
||||
&net.TCPAddr{
|
||||
IP: net.ParseIP("192.168.1.1"),
|
||||
Port: 12345,
|
||||
},
|
||||
&net.TCPAddr{
|
||||
IP: net.ParseIP("2001:0db8:0000:0000:0000:ff00:0042:8329"),
|
||||
Port: 65535,
|
||||
},
|
||||
&tor.OnionAddr{
|
||||
OnionService: "3g2upl4pq6kufc4m.onion",
|
||||
Port: 9735,
|
||||
},
|
||||
&tor.OnionAddr{
|
||||
OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion",
|
||||
Port: 80,
|
||||
},
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
for _, expectedAddr := range testAddrs {
|
||||
if err := serializeAddr(&b, expectedAddr); err != nil {
|
||||
t.Fatalf("unable to serialize address: %v", err)
|
||||
}
|
||||
|
||||
addr, err := deserializeAddr(&b)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to deserialize address: %v", err)
|
||||
}
|
||||
|
||||
if addr.String() != expectedAddr.String() {
|
||||
t.Fatalf("expected address %v after serialization, "+
|
||||
"got %v", addr, expectedAddr)
|
||||
}
|
||||
}
|
||||
}
|
@ -54,8 +54,6 @@ type LinkNode struct {
|
||||
// Addresses is a list of IP address in which either we were able to
|
||||
// reach the node over in the past, OR we received an incoming
|
||||
// authenticated connection for the stored identity public key.
|
||||
//
|
||||
// TODO(roasbeef): also need to support hidden service addrs
|
||||
Addresses []net.Addr
|
||||
|
||||
db *DB
|
||||
@ -85,7 +83,7 @@ func (l *LinkNode) UpdateLastSeen(lastSeen time.Time) error {
|
||||
|
||||
// AddAddress appends the specified TCP address to the list of known addresses
|
||||
// this node is/was known to be reachable at.
|
||||
func (l *LinkNode) AddAddress(addr *net.TCPAddr) error {
|
||||
func (l *LinkNode) AddAddress(addr net.Addr) error {
|
||||
for _, a := range l.Addresses {
|
||||
if a.String() == addr.String() {
|
||||
return nil
|
||||
|
196
config.go
196
config.go
@ -5,6 +5,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -22,7 +23,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/brontide"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/torsvc"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcutil"
|
||||
)
|
||||
@ -50,6 +51,12 @@ const (
|
||||
defaultMaxLogFiles = 3
|
||||
defaultMaxLogFileSize = 10
|
||||
|
||||
defaultTorSOCKSPort = 9050
|
||||
defaultTorDNSHost = "soa.nodes.lightning.directory"
|
||||
defaultTorDNSPort = 53
|
||||
defaultTorControlPort = 9051
|
||||
defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
|
||||
|
||||
defaultBroadcastDelta = 10
|
||||
|
||||
// minTimeLockDelta is the minimum timelock we require for incoming
|
||||
@ -81,6 +88,11 @@ var (
|
||||
|
||||
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
|
||||
defaultLitecoindDir = btcutil.AppDataDir("litecoin", false)
|
||||
|
||||
defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
|
||||
defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
|
||||
defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
|
||||
defaultTorV2PrivateKeyPath = filepath.Join(defaultLndDir, defaultTorV2PrivateKeyFilename)
|
||||
)
|
||||
|
||||
type chainConfig struct {
|
||||
@ -136,9 +148,14 @@ type autoPilotConfig struct {
|
||||
}
|
||||
|
||||
type torConfig struct {
|
||||
Socks string `long:"socks" description:"The port that Tor's exposed SOCKS5 proxy is listening on. Using Tor allows outbound-only connections (listening will be disabled) -- NOTE port must be between 1024 and 65535"`
|
||||
DNS string `long:"dns" description:"The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
|
||||
StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
|
||||
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"`
|
||||
SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"`
|
||||
DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
|
||||
StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
|
||||
Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"`
|
||||
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
|
||||
V2PrivateKeyPath string `long:"v2privatekeypath" description:"The path to the private key of the onion service being created"`
|
||||
V3 bool `long:"v3" description:"Use a v3 onion service to listen for inbound connections"`
|
||||
}
|
||||
|
||||
// config defines the configuration options for lnd.
|
||||
@ -188,7 +205,7 @@ type config struct {
|
||||
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
|
||||
LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"`
|
||||
|
||||
Autopilot *autoPilotConfig `group:"autopilot" namespace:"autopilot"`
|
||||
Autopilot *autoPilotConfig `group:"Autopilot" namespace:"autopilot"`
|
||||
|
||||
Tor *torConfig `group:"Tor" namespace:"tor"`
|
||||
|
||||
@ -206,7 +223,7 @@ type config struct {
|
||||
|
||||
NoChanUpdates bool `long:"nochanupdates" description:"If specified, lnd will not request real-time channel updates from connected peers. This option should be used by routing nodes to save bandwidth."`
|
||||
|
||||
net torsvc.Net
|
||||
net tor.Net
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using a config file and command
|
||||
@ -275,6 +292,13 @@ func loadConfig() (*config, error) {
|
||||
Alias: defaultAlias,
|
||||
Color: defaultColor,
|
||||
MinChanSize: int64(minChanFundingSize),
|
||||
Tor: &torConfig{
|
||||
SOCKS: defaultTorSOCKS,
|
||||
DNS: defaultTorDNS,
|
||||
Control: defaultTorControl,
|
||||
V2PrivateKeyPath: defaultTorV2PrivateKeyPath,
|
||||
},
|
||||
net: &tor.ClearNet{},
|
||||
}
|
||||
|
||||
// Pre-parse the command line options to pick up an alternative config
|
||||
@ -305,6 +329,7 @@ func loadConfig() (*config, error) {
|
||||
defaultCfg.InvoiceMacPath = filepath.Join(lndDir, defaultInvoiceMacFilename)
|
||||
defaultCfg.ReadMacPath = filepath.Join(lndDir, defaultReadMacFilename)
|
||||
defaultCfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
|
||||
defaultCfg.Tor.V2PrivateKeyPath = filepath.Join(lndDir, defaultTorV2PrivateKeyFilename)
|
||||
}
|
||||
|
||||
// Create the lnd directory if it doesn't already exist.
|
||||
@ -354,53 +379,83 @@ func loadConfig() (*config, error) {
|
||||
cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir)
|
||||
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
|
||||
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir)
|
||||
cfg.Tor.V2PrivateKeyPath = cleanAndExpandPath(cfg.Tor.V2PrivateKeyPath)
|
||||
|
||||
// Setup dial and DNS resolution functions depending on the specified
|
||||
// options. The default is to use the standard golang "net" package
|
||||
// functions. When Tor's proxy is specified, the dial function is set to
|
||||
// the proxy specific dial function and the DNS resolution functions use
|
||||
// Tor.
|
||||
cfg.net = &torsvc.RegularNet{}
|
||||
if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" {
|
||||
// Validate Tor port number
|
||||
torport, err := strconv.Atoi(cfg.Tor.Socks)
|
||||
if err != nil || torport < 1024 || torport > 65535 {
|
||||
str := "%s: The tor socks5 port must be between 1024 and 65535"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If ExternalIPs is set, throw an error since we cannot
|
||||
// listen for incoming connections via Tor's SOCKS5 proxy.
|
||||
if len(cfg.ExternalIPs) != 0 {
|
||||
str := "%s: Cannot set externalip flag with proxy flag - " +
|
||||
"cannot listen for incoming connections via Tor's " +
|
||||
"socks5 proxy"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.net = &torsvc.TorProxyNet{
|
||||
TorDNS: cfg.Tor.DNS,
|
||||
TorSocks: cfg.Tor.Socks,
|
||||
StreamIsolation: cfg.Tor.StreamIsolation,
|
||||
}
|
||||
|
||||
// If we are using Tor, since we only want connections routed
|
||||
// through Tor, listening is disabled.
|
||||
cfg.DisableListen = true
|
||||
|
||||
} else if cfg.Tor.Socks != "" || cfg.Tor.DNS != "" {
|
||||
// Both TorSocks and TorDNS must be set.
|
||||
str := "%s: Both the tor.socks and the tor.dns flags must be set" +
|
||||
"to properly route connections and avoid DNS leaks while" +
|
||||
"using Tor"
|
||||
// Ensure that the user didn't attempt to specify negative values for
|
||||
// any of the autopilot params.
|
||||
if cfg.Autopilot.MaxChannels < 0 {
|
||||
str := "%s: autopilot.maxchannels must be non-negative"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return nil, err
|
||||
}
|
||||
if cfg.Autopilot.Allocation < 0 {
|
||||
str := "%s: autopilot.allocation must be non-negative"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return nil, err
|
||||
}
|
||||
if cfg.Autopilot.MinChannelSize < 0 {
|
||||
str := "%s: autopilot.minchansize must be non-negative"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return nil, err
|
||||
}
|
||||
if cfg.Autopilot.MaxChannelSize < 0 {
|
||||
str := "%s: autopilot.maxchansize must be non-negative"
|
||||
err := fmt.Errorf(str, funcName)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure that the specified values for the min and max channel size
|
||||
// don't are within the bounds of the normal chan size constraints.
|
||||
if cfg.Autopilot.MinChannelSize < int64(minChanFundingSize) {
|
||||
cfg.Autopilot.MinChannelSize = int64(minChanFundingSize)
|
||||
}
|
||||
if cfg.Autopilot.MaxChannelSize > int64(maxFundingAmount) {
|
||||
cfg.Autopilot.MaxChannelSize = int64(maxFundingAmount)
|
||||
}
|
||||
|
||||
// Validate the Tor config parameters.
|
||||
cfg.Tor.SOCKS = normalizeAddress(
|
||||
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),
|
||||
)
|
||||
cfg.Tor.DNS = normalizeAddress(
|
||||
cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort),
|
||||
)
|
||||
cfg.Tor.Control = normalizeAddress(
|
||||
cfg.Tor.Control, strconv.Itoa(defaultTorControlPort),
|
||||
)
|
||||
switch {
|
||||
case cfg.Tor.V2 && cfg.Tor.V3:
|
||||
return nil, errors.New("either tor.v2 or tor.v3 can be set, " +
|
||||
"but not both")
|
||||
case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3):
|
||||
return nil, errors.New("listening must be enabled when " +
|
||||
"enabling inbound connections over Tor")
|
||||
case cfg.Tor.Active && (!cfg.Tor.V2 || !cfg.Tor.V3):
|
||||
// If an onion service version wasn't selected, we'll assume the
|
||||
// user is only interested in outbound connections over Tor.
|
||||
// Therefore, we'll disable listening in order to avoid
|
||||
// inadvertent leaks.
|
||||
cfg.DisableListen = true
|
||||
}
|
||||
|
||||
// Set up the network-related functions that will be used throughout
|
||||
// the daemon. We use the standard Go "net" package functions by
|
||||
// default. If we should be proxying all traffic through Tor, then
|
||||
// we'll use the Tor proxy specific functions in order to avoid leaking
|
||||
// our real information.
|
||||
if cfg.Tor.Active {
|
||||
cfg.net = &tor.ProxyNet{
|
||||
SOCKS: cfg.Tor.SOCKS,
|
||||
DNS: cfg.Tor.DNS,
|
||||
StreamIsolation: cfg.Tor.StreamIsolation,
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the active chain configuration and its parameters.
|
||||
switch {
|
||||
// At this moment, multiple active chains are not supported.
|
||||
case cfg.Litecoin.Active && cfg.Bitcoin.Active:
|
||||
@ -734,6 +789,23 @@ func loadConfig() (*config, error) {
|
||||
cfg.Listeners = normalizeAddresses(cfg.Listeners,
|
||||
strconv.Itoa(defaultPeerPort))
|
||||
|
||||
// Finally, ensure that we are only listening on localhost if Tor
|
||||
// inbound support is enabled.
|
||||
if cfg.Tor.V2 || cfg.Tor.V3 {
|
||||
for _, addr := range cfg.Listeners {
|
||||
// Due to the addresses being normalized above, we can
|
||||
// skip checking the error.
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
if host == "localhost" || host == "127.0.0.1" {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, errors.New("lnd must *only* be listening " +
|
||||
"on localhost when running with Tor inbound " +
|
||||
"support enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// Warn about missing config file only after all other configuration is
|
||||
// done. This prevents the warning on help messages and invalid
|
||||
// options. Note this should go directly before the return.
|
||||
@ -1113,15 +1185,7 @@ func normalizeAddresses(addrs []string, defaultPort string) []string {
|
||||
result := make([]string, 0, len(addrs))
|
||||
seen := map[string]struct{}{}
|
||||
for _, addr := range addrs {
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
// If the address is an integer, then we assume it is *only* a
|
||||
// port and default to binding to that port on localhost
|
||||
if _, err := strconv.Atoi(addr); err == nil {
|
||||
addr = net.JoinHostPort("localhost", addr)
|
||||
} else {
|
||||
addr = net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
}
|
||||
addr = normalizeAddress(addr, defaultPort)
|
||||
if _, ok := seen[addr]; !ok {
|
||||
result = append(result, addr)
|
||||
seen[addr] = struct{}{}
|
||||
@ -1130,6 +1194,24 @@ func normalizeAddresses(addrs []string, defaultPort string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// normalizeAddress normalizes an address by either setting a missing host to
|
||||
// localhost or missing port to the default port.
|
||||
func normalizeAddress(addr, defaultPort string) string {
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
// If the address is an integer, then we assume it is *only* a
|
||||
// port and default to binding to that port on localhost.
|
||||
if _, err := strconv.Atoi(addr); err == nil {
|
||||
return net.JoinHostPort("localhost", addr)
|
||||
}
|
||||
|
||||
// Otherwise, the address only contains the host so we'll use
|
||||
// the default port.
|
||||
return net.JoinHostPort(addr, defaultPort)
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// enforceSafeAuthentication enforces "safe" authentication taking into account
|
||||
// the interfaces that the RPC servers are listening on, and if macaroons are
|
||||
// activated or not. To project users from using dangerous config combinations,
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcutil/bech32"
|
||||
@ -164,12 +165,12 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
||||
// If we haven't yet reached our limit, then
|
||||
// we'll copy over the details of this node
|
||||
// into the set of addresses to be returned.
|
||||
tcpAddr, ok := nodeAddr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
// If this isn't a valid TCP address,
|
||||
// then we'll ignore it as currently
|
||||
// we'll only attempt to connect out to
|
||||
// TCP peers.
|
||||
switch nodeAddr.(type) {
|
||||
case *net.TCPAddr, *tor.OnionAddr:
|
||||
default:
|
||||
// If this isn't a valid address
|
||||
// supported by the protocol, then we'll
|
||||
// skip this node.
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -178,7 +179,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
||||
// error.
|
||||
a = append(a, &lnwire.NetAddress{
|
||||
IdentityKey: node.PubKey(),
|
||||
Address: tcpAddr,
|
||||
Address: nodeAddr,
|
||||
})
|
||||
}
|
||||
|
||||
@ -246,9 +247,8 @@ type DNSSeedBootstrapper struct {
|
||||
// in the tuple is a special A record that we'll query in order to
|
||||
// receive the IP address of the current authoritative DNS server for
|
||||
// the network seed.
|
||||
dnsSeeds [][2]string
|
||||
lookupHost func(string) ([]string, error)
|
||||
lookupSRV func(string, string, string) (string, []*net.SRV, error)
|
||||
dnsSeeds [][2]string
|
||||
net tor.Net
|
||||
}
|
||||
|
||||
// A compile time assertion to ensure that DNSSeedBootstrapper meets the
|
||||
@ -262,14 +262,8 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
|
||||
// used as a fallback for manual TCP resolution in the case of an error
|
||||
// receiving the UDP response. The second host should return a single A record
|
||||
// with the IP address of the authoritative name server.
|
||||
func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string, error),
|
||||
lookupSRV func(string, string, string) (string, []*net.SRV, error)) (
|
||||
NetworkPeerBootstrapper, error) {
|
||||
return &DNSSeedBootstrapper{
|
||||
dnsSeeds: seeds,
|
||||
lookupHost: lookupHost,
|
||||
lookupSRV: lookupSRV,
|
||||
}, nil
|
||||
func NewDNSSeedBootstrapper(seeds [][2]string, net tor.Net) NetworkPeerBootstrapper {
|
||||
return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net}
|
||||
}
|
||||
|
||||
// fallBackSRVLookup attempts to manually query for SRV records we need to
|
||||
@ -280,12 +274,14 @@ func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string
|
||||
// the records we return are currently too large for a class of resolvers,
|
||||
// causing them to be filtered out. The targetEndPoint is the original end
|
||||
// point that was meant to be hit.
|
||||
func fallBackSRVLookup(soaShim string, targetEndPoint string) ([]*net.SRV, error) {
|
||||
func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string,
|
||||
targetEndPoint string) ([]*net.SRV, error) {
|
||||
|
||||
log.Tracef("Attempting to query fallback DNS seed")
|
||||
|
||||
// First, we'll lookup the IP address of the server that will act as
|
||||
// our shim.
|
||||
addrs, err := net.LookupHost(soaShim)
|
||||
addrs, err := d.net.LookupHost(soaShim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -293,7 +289,7 @@ func fallBackSRVLookup(soaShim string, targetEndPoint string) ([]*net.SRV, error
|
||||
// Once we have the IP address, we'll establish a TCP connection using
|
||||
// port 53.
|
||||
dnsServer := net.JoinHostPort(addrs[0], "53")
|
||||
conn, err := net.Dial("tcp", dnsServer)
|
||||
conn, err := d.net.Dial("tcp", dnsServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -356,10 +352,12 @@ search:
|
||||
// keys of nodes. We use the lndLookupSRV function for
|
||||
// this task.
|
||||
primarySeed := dnsSeedTuple[0]
|
||||
_, addrs, err := d.lookupSRV("nodes", "tcp", primarySeed)
|
||||
_, addrs, err := d.net.LookupSRV("nodes", "tcp", primarySeed)
|
||||
if err != nil {
|
||||
log.Tracef("Unable to lookup SRV records via " +
|
||||
"primary seed, falling back to secondary")
|
||||
log.Tracef("Unable to lookup SRV records via "+
|
||||
"primary seed: %v", err)
|
||||
|
||||
log.Trace("Falling back to secondary")
|
||||
|
||||
// If the host of the secondary seed is blank,
|
||||
// then we'll bail here as we can't proceed.
|
||||
@ -371,7 +369,7 @@ search:
|
||||
// the primary seed, we'll fallback to the
|
||||
// secondary seed before concluding failure.
|
||||
soaShim := dnsSeedTuple[1]
|
||||
addrs, err = fallBackSRVLookup(
|
||||
addrs, err = d.fallBackSRVLookup(
|
||||
soaShim, primarySeed,
|
||||
)
|
||||
if err != nil {
|
||||
@ -397,7 +395,7 @@ search:
|
||||
// key. We use the lndLookup function for this
|
||||
// task.
|
||||
bechNodeHost := nodeSrv.Target
|
||||
addrs, err := d.lookupHost(bechNodeHost)
|
||||
addrs, err := d.net.LookupHost(bechNodeHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,24 +1,29 @@
|
||||
# Table of Contents
|
||||
1. [Overview](#Overview)
|
||||
2. [Outbound Connections Only](#outbound-connections-only)
|
||||
1. [Overview](#overview)
|
||||
2. [Getting Started](#getting-started)
|
||||
3. [Tor Stream Isolation](#tor-stream-isolation)
|
||||
4. [Listening for Inbound Connections](#listening-for-inbound-connections)
|
||||
1. [v2 Onion Services](#v2-onion-services)
|
||||
2. [v3 Onion Services](#v3-onion-services)
|
||||
|
||||
## 1. Overview
|
||||
## Overview
|
||||
|
||||
`lnd` currently has _partial_ support for using Lightning over
|
||||
`lnd` currently has complete support for using Lightning over
|
||||
[Tor](https://www.torproject.org/). Usage of Lightning over Tor is valuable as
|
||||
routing nodes no longer need to potentially expose their location via their
|
||||
advertised IP address. Additionally, leaf nodes can also protect their location
|
||||
by using Tor for anonymous networking to establish connections.
|
||||
by using Tor for anonymous networking to establish connections.
|
||||
|
||||
At the time of the writing of this documentation, `lnd` only supports usage of
|
||||
Tor for establishing _outbound_ connections. In the near future, support for
|
||||
full [Onion Service](https://www.torproject.org/docs/onion-services.html.en)
|
||||
usage will be added as well. Support for both `v2` and `v3` onion services are
|
||||
planned. With widespread usage of Onion Services within the network, concerns
|
||||
about the difficulty of proper NAT traversal are alleviated, as usage of Onion
|
||||
Services allows nodes to accept inbound connections even if they're behind a
|
||||
NAT.
|
||||
With widespread usage of Onion Services within the network, concerns about the
|
||||
difficulty of proper NAT traversal are alleviated, as usage of Onion Services
|
||||
allows nodes to accept inbound connections even if they're behind a NAT.
|
||||
|
||||
At the time of writing this documentation, `lnd` supports both types of onion
|
||||
services: v2 and v3. However, only v2 onion services can automatically be
|
||||
created and set up by `lnd` until Tor Control support for v3 onion services is
|
||||
implemented in the stable release of the Tor daemon. v3 onion services can be
|
||||
used as long as they are set up manually. We'll cover the steps on how to do
|
||||
these things below.
|
||||
|
||||
Before following the remainder of this documentation, you should ensure that
|
||||
you already have Tor installed locally. Official instructions to install the
|
||||
@ -26,29 +31,19 @@ latest release of Tor can be found
|
||||
[here](https://www.torproject.org/docs/tor-doc-unix.html.en).
|
||||
|
||||
**NOTE**: This documentation covers how to ensure that `lnd`'s _Lightning
|
||||
protocol traffic_ is tunnled over Tor. Users will need to take care that if
|
||||
they're running using a Bitcoin full-node, then that is also configured to
|
||||
proxy all trafic over Tor. If using the `neutrino` backend for `lnd`, then it
|
||||
will automatically also default to Tor usage if active within `lnd`.
|
||||
protocol traffic_ is tunneled over Tor. Users must ensure that when also running
|
||||
a Bitcoin full-node, that it is also proxying all traffic over Tor. If using the
|
||||
`neutrino` backend for `lnd`, then it will automatically also default to Tor
|
||||
usage if active within `lnd`.
|
||||
|
||||
|
||||
## 2. Outbound Connections Only
|
||||
|
||||
Currenty, `lnd` only supports purely _outbound_ Tor usage. In this mode, `lnd`
|
||||
_won't_ listen at all, and will only be able to establish outbound connections.
|
||||
_All_ protocol traffic will be tunneled over Tor. Additionally, we'll also
|
||||
force any DNS requests over Tor such that we don't leak our IP address to the
|
||||
clear net.
|
||||
|
||||
The remainder of this tutorial assumes one already has the `tor` daemon
|
||||
installed locally.
|
||||
## Getting Started
|
||||
|
||||
First, you'll want to run `tor` locally before starting up `lnd`. Depending on
|
||||
how you installed Tor, you'll find the configuration file at
|
||||
`/usr/local/etc/tor/torrc`. Here's an example configuration file that we'll be
|
||||
using for the remainder of the tutorial:
|
||||
```
|
||||
SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections.
|
||||
SOCKSPort 9050
|
||||
Log notice stdout
|
||||
ControlPort 9051
|
||||
CookieAuthentication 1
|
||||
@ -84,28 +79,49 @@ At this point, we can now start `lnd` with the relevant arguments:
|
||||
<snip>
|
||||
|
||||
Tor:
|
||||
--tor.socks= The port that Tor's exposed SOCKS5 proxy is listening on. Using Tor allows outbound-only connections (listening will be disabled) -- NOTE port must be between 1024 and 65535
|
||||
--tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled
|
||||
--tor.active Allow outbound and inbound connections to be routed through Tor
|
||||
--tor.socks= The port that Tor's exposed SOCKS5 proxy is listening on -- NOTE port must be between 1024 and 65535 (default: 9050)
|
||||
--tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled (default: soa.nodes.lightning.directory:53)
|
||||
--tor.streamisolation Enable Tor stream isolation by randomizing user credentials for each connection.
|
||||
--tor.controlport= The port that Tor is listening on for Tor control connections -- NOTE port must be between 1024 and 65535 (default: 9051)
|
||||
--tor.v2 Automatically set up a v2 onion service to listen for inbound connections
|
||||
--tor.v3 Use a v3 onion service to listen for inbound connections
|
||||
--tor.privatekeypath= The path to the private key of the onion service being created (default: /Users/user/Library/Application Support/Lnd/onion_private_key)
|
||||
```
|
||||
|
||||
The `--tor.socks` argument should point to the interface that the `Tor` daemon
|
||||
is listening on to proxy connections. The `--tor.dns` flag is required in order
|
||||
to be able to properly automatically bootstrap a set of peer connections. The
|
||||
`tor` daemon doesn't currently support proxying `SRV` queries over Tor. So
|
||||
instead, we need to connect directly to the authoritative DNS server over TCP,
|
||||
in order query for `SRV` records that we can use to bootstrap our connections.
|
||||
As of the time this documentation was written, for Bitcoin's Testnet, clients
|
||||
should point to `nodes.lightning.directory`.
|
||||
There are a couple things here, so let's dissect them. The `--tor.active` flag
|
||||
allows `lnd` to route all outbound and inbound connections through Tor.
|
||||
|
||||
Finally, we'll start `lnd` with the proper arguments:
|
||||
```
|
||||
⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory
|
||||
Outbound connections are possible with the use of the `--tor.socks` and
|
||||
`--tor.dns` arguments. The `--tor.socks` argument should point to the interface
|
||||
that the `Tor` daemon is listening on to proxy connections. The `--tor.dns` flag
|
||||
is required in order to be able to properly automatically bootstrap a set of
|
||||
peer connections. The `tor` daemon doesn't currently support proxying `SRV`
|
||||
queries over Tor. So instead, we need to connect directly to the authoritative
|
||||
DNS server over TCP, in order query for `SRV` records that we can use to
|
||||
bootstrap our connections.
|
||||
|
||||
Inbound connections are possible due to `lnd` automatically creating a v2 onion
|
||||
service. A path to save the onion service's private key can be specified with
|
||||
the `--tor.privatekeypath` flag. A v3 onion service can also be used, but it
|
||||
must be created manually. We'll expand on how this works in [Listening for
|
||||
Inbound Connections](#listening-for-inbound-connections).
|
||||
|
||||
Most of these arguments have defaults, so as long as they apply to you, routing
|
||||
all outbound and inbound connections through Tor can simply be done with:
|
||||
```shell
|
||||
⛰ ./lnd --tor.active --tor.v2
|
||||
```
|
||||
|
||||
With the above arguments, `lnd` will proxy _all_ network traffic over Tor!
|
||||
Outbound support only can also be used with:
|
||||
```shell
|
||||
⛰ ./lnd --tor.active
|
||||
```
|
||||
|
||||
This will allow you to make all outgoing connections over Tor, but still allow
|
||||
regular (clearnet) incoming connections.
|
||||
|
||||
## 3. Tor Stream Isolation
|
||||
## Tor Stream Isolation
|
||||
|
||||
Our support for Tor also has an additional privacy enhancing modified: stream
|
||||
isolation. Usage of this mode means that Tor will always use _new circuit_ for
|
||||
@ -116,5 +132,56 @@ circuit.
|
||||
Activating stream isolation is very straightforward, we only require the
|
||||
specification of an additional argument:
|
||||
```
|
||||
⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory --tor.streamisolation
|
||||
⛰ ./lnd --tor.active --tor.streamisolation
|
||||
```
|
||||
|
||||
## Listening for Inbound Connections
|
||||
|
||||
In order to listen for inbound connections through Tor, an onion service must be
|
||||
created. There are two types of onion services: v2 and v3.
|
||||
|
||||
### v2 Onion Services
|
||||
|
||||
v2 onion services can be created automatically by `lnd` and are currently the
|
||||
default. To do so, run `lnd` with the following arguments:
|
||||
```
|
||||
⛰ ./lnd --tor.active --tor.v2
|
||||
```
|
||||
|
||||
This will automatically create a hidden service for your node to use to listen
|
||||
for inbound connections and advertise itself to the network. The onion service's
|
||||
private key is saved to a file named `onion_private_key` in `lnd`'s base
|
||||
directory. This will allow `lnd` to recreate the same hidden service upon
|
||||
restart. If you wish to generate a new onion service, you can simply delete this
|
||||
file. The path to this private key file can also be modified with the
|
||||
`--tor.privatekeypath` argument.
|
||||
|
||||
### v3 Onion Services
|
||||
|
||||
v3 onion services are the latest generation of onion services and they provide a
|
||||
number of advantages over the legacy v2 onion services. To learn more about
|
||||
these benefits, see [Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions).
|
||||
|
||||
Unfortunately, at the time of writing this, v3 onion service support is still
|
||||
at an alpha level in the Tor daemon, so we're unable to automatically set them
|
||||
up within `lnd` unlike with v2 onion services. However, they can still be run
|
||||
manually! To do so, append the following lines to the torrc sample from above:
|
||||
```
|
||||
HiddenServiceDir PATH_TO_HIDDEN_SERVICE
|
||||
HiddenServiceVersion 3
|
||||
HiddenServicePort PORT_ONION_SERVICE_LISTENS_ON ADDRESS_LND_LISTENS_ON
|
||||
```
|
||||
|
||||
If needed, instructions on how to set up a v3 onion service manually can be
|
||||
found [here](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions#Howtosetupyourownprop224service).
|
||||
|
||||
Once the v3 onion service is set up, `lnd` is able to use it to listen for
|
||||
inbound connections. You'll also need the onion service's hostname in order to
|
||||
advertise your node to the network. To do so, run `lnd` with the following
|
||||
arguments:
|
||||
```
|
||||
⛰ ./lnd --tor.active --tor.v3 --externalip=ONION_SERVICE_HOSTNAME
|
||||
```
|
||||
|
||||
Once v3 onion service support is stable, `lnd` will be updated to also
|
||||
automatically set up v3 onion services.
|
||||
|
7
lnd.go
7
lnd.go
@ -304,11 +304,10 @@ func lndMain() error {
|
||||
}
|
||||
idPrivKey.Curve = btcec.S256()
|
||||
|
||||
if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" {
|
||||
if cfg.Tor.Active {
|
||||
srvrLog.Infof("Proxying all network traffic via Tor "+
|
||||
"(stream_isolation=%v)! NOTE: If running with a full-node "+
|
||||
"backend, ensure that is proxying over Tor as well",
|
||||
cfg.Tor.StreamIsolation)
|
||||
"(stream_isolation=%v)! NOTE: Ensure the backend node "+
|
||||
"is proxying over Tor as well", cfg.Tor.StreamIsolation)
|
||||
}
|
||||
|
||||
// Set up the core server which will listen for incoming peer
|
||||
|
114
lnwire/lnwire.go
114
lnwire/lnwire.go
@ -11,6 +11,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
@ -43,8 +44,7 @@ const (
|
||||
// v2OnionAddr denotes a version 2 Tor onion service address.
|
||||
v2OnionAddr addressType = 3
|
||||
|
||||
// v3OnionAddr denotes a version 3 Tor (prop224) onion service
|
||||
// addresses
|
||||
// v3OnionAddr denotes a version 3 Tor (prop224) onion service address.
|
||||
v3OnionAddr addressType = 4
|
||||
)
|
||||
|
||||
@ -56,16 +56,12 @@ func (a addressType) AddrLen() uint16 {
|
||||
return 0
|
||||
case tcp4Addr:
|
||||
return 6
|
||||
|
||||
case tcp6Addr:
|
||||
return 18
|
||||
|
||||
case v2OnionAddr:
|
||||
return 12
|
||||
|
||||
case v3OnionAddr:
|
||||
return 37
|
||||
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
@ -300,7 +296,6 @@ func writeElement(w io.Writer, element interface{}) error {
|
||||
return fmt.Errorf("cannot write nil TCPAddr")
|
||||
}
|
||||
|
||||
// TODO(roasbeef): account for onion types too
|
||||
if e.IP.To4() != nil {
|
||||
var descriptor [1]byte
|
||||
descriptor[0] = uint8(tcp4Addr)
|
||||
@ -331,6 +326,45 @@ func writeElement(w io.Writer, element interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case *tor.OnionAddr:
|
||||
if e == nil {
|
||||
return errors.New("cannot write nil onion address")
|
||||
}
|
||||
|
||||
var suffixIndex int
|
||||
switch len(e.OnionService) {
|
||||
case tor.V2Len:
|
||||
descriptor := []byte{byte(v2OnionAddr)}
|
||||
if _, err := w.Write(descriptor); err != nil {
|
||||
return err
|
||||
}
|
||||
suffixIndex = tor.V2Len - tor.OnionSuffixLen
|
||||
case tor.V3Len:
|
||||
descriptor := []byte{byte(v3OnionAddr)}
|
||||
if _, err := w.Write(descriptor); err != nil {
|
||||
return err
|
||||
}
|
||||
suffixIndex = tor.V3Len - tor.OnionSuffixLen
|
||||
default:
|
||||
return errors.New("unknown onion service length")
|
||||
}
|
||||
|
||||
host, err := tor.Base32Encoding.DecodeString(
|
||||
e.OnionService[:suffixIndex],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(host); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
binary.BigEndian.PutUint16(port[:], uint16(e.Port))
|
||||
if _, err := w.Write(port[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case []net.Addr:
|
||||
// First, we'll encode all the addresses into an intermediate
|
||||
// buffer. We need to do this in order to compute the total
|
||||
@ -632,6 +666,7 @@ func readElement(r io.Reader, element interface{}) error {
|
||||
addresses []net.Addr
|
||||
addrBytesRead uint16
|
||||
)
|
||||
|
||||
for addrBytesRead < addrsLen {
|
||||
var descriptor [1]byte
|
||||
if _, err = io.ReadFull(addrBuf, descriptor[:]); err != nil {
|
||||
@ -640,52 +675,87 @@ func readElement(r io.Reader, element interface{}) error {
|
||||
|
||||
addrBytesRead++
|
||||
|
||||
address := &net.TCPAddr{}
|
||||
aType := addressType(descriptor[0])
|
||||
switch aType {
|
||||
|
||||
var address net.Addr
|
||||
switch aType := addressType(descriptor[0]); aType {
|
||||
case noAddr:
|
||||
addrBytesRead += aType.AddrLen()
|
||||
continue
|
||||
|
||||
case tcp4Addr:
|
||||
var ip [4]byte
|
||||
if _, err = io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
address.IP = (net.IP)(ip[:])
|
||||
|
||||
var port [2]byte
|
||||
if _, err = io.ReadFull(addrBuf, port[:]); err != nil {
|
||||
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
address.Port = int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
address = &net.TCPAddr{
|
||||
IP: net.IP(ip[:]),
|
||||
Port: int(binary.BigEndian.Uint16(port[:])),
|
||||
}
|
||||
addrBytesRead += aType.AddrLen()
|
||||
|
||||
case tcp6Addr:
|
||||
var ip [16]byte
|
||||
if _, err = io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||
if _, err := io.ReadFull(addrBuf, ip[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
address.IP = (net.IP)(ip[:])
|
||||
|
||||
var port [2]byte
|
||||
if _, err = io.ReadFull(addrBuf, port[:]); err != nil {
|
||||
if _, err := io.ReadFull(addrBuf, port[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
address.Port = int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
address = &net.TCPAddr{
|
||||
IP: net.IP(ip[:]),
|
||||
Port: int(binary.BigEndian.Uint16(port[:])),
|
||||
}
|
||||
addrBytesRead += aType.AddrLen()
|
||||
|
||||
case v2OnionAddr:
|
||||
var h [tor.V2DecodedLen]byte
|
||||
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p [2]byte
|
||||
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(h[:])
|
||||
onionService += tor.OnionSuffix
|
||||
port := int(binary.BigEndian.Uint16(p[:]))
|
||||
|
||||
address = &tor.OnionAddr{
|
||||
OnionService: onionService,
|
||||
Port: port,
|
||||
}
|
||||
addrBytesRead += aType.AddrLen()
|
||||
continue
|
||||
|
||||
case v3OnionAddr:
|
||||
var h [tor.V3DecodedLen]byte
|
||||
if _, err := io.ReadFull(addrBuf, h[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p [2]byte
|
||||
if _, err := io.ReadFull(addrBuf, p[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(h[:])
|
||||
onionService += tor.OnionSuffix
|
||||
port := int(binary.BigEndian.Uint16(p[:]))
|
||||
|
||||
address = &tor.OnionAddr{
|
||||
OnionService: onionService,
|
||||
Port: port,
|
||||
}
|
||||
addrBytesRead += aType.AddrLen()
|
||||
continue
|
||||
|
||||
default:
|
||||
return &ErrUnknownAddrType{aType}
|
||||
|
@ -2,6 +2,7 @@ package lnwire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"image/color"
|
||||
"math"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
@ -37,11 +39,6 @@ var (
|
||||
}
|
||||
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
|
||||
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
|
||||
|
||||
// TODO(roasbeef): randomly generate from three types of addrs
|
||||
a1 = &net.TCPAddr{IP: (net.IP)([]byte{0x7f, 0x0, 0x0, 0x1}), Port: 8333}
|
||||
a2, _ = net.ResolveTCPAddr("tcp", "[2001:db8:85a3:0:0:8a2e:370:7334]:80")
|
||||
testAddrs = []net.Addr{a1, a2}
|
||||
)
|
||||
|
||||
func randPubKey() (*btcec.PublicKey, error) {
|
||||
@ -76,6 +73,100 @@ func randRawFeatureVector(r *rand.Rand) *RawFeatureVector {
|
||||
return featureVec
|
||||
}
|
||||
|
||||
func randTCP4Addr(r *rand.Rand) (*net.TCPAddr, error) {
|
||||
var ip [4]byte
|
||||
if _, err := r.Read(ip[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrIP := net.IP(ip[:])
|
||||
addrPort := int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
|
||||
}
|
||||
|
||||
func randTCP6Addr(r *rand.Rand) (*net.TCPAddr, error) {
|
||||
var ip [16]byte
|
||||
if _, err := r.Read(ip[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrIP := net.IP(ip[:])
|
||||
addrPort := int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
|
||||
}
|
||||
|
||||
func randV2OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
|
||||
var serviceID [tor.V2DecodedLen]byte
|
||||
if _, err := r.Read(serviceID[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
|
||||
onionService += tor.OnionSuffix
|
||||
addrPort := int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
|
||||
}
|
||||
|
||||
func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
|
||||
var serviceID [tor.V3DecodedLen]byte
|
||||
if _, err := r.Read(serviceID[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var port [2]byte
|
||||
if _, err := r.Read(port[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
|
||||
onionService += tor.OnionSuffix
|
||||
addrPort := int(binary.BigEndian.Uint16(port[:]))
|
||||
|
||||
return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
|
||||
}
|
||||
|
||||
func randAddrs(r *rand.Rand) ([]net.Addr, error) {
|
||||
tcp4Addr, err := randTCP4Addr(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcp6Addr, err := randTCP6Addr(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v2OnionAddr, err := randV2OnionAddr(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v3OnionAddr, err := randV3OnionAddr(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []net.Addr{tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr}, nil
|
||||
}
|
||||
|
||||
func TestMaxOutPointIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -465,8 +556,6 @@ func TestLightningWireProtocol(t *testing.T) {
|
||||
G: uint8(r.Int31()),
|
||||
B: uint8(r.Int31()),
|
||||
},
|
||||
// TODO(roasbeef): proper gen rand addrs
|
||||
Addresses: testAddrs,
|
||||
}
|
||||
req.Signature, err = NewSigFromSignature(testSig)
|
||||
if err != nil {
|
||||
@ -480,6 +569,11 @@ func TestLightningWireProtocol(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
req.Addresses, err = randAddrs(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate addresses: %v", err)
|
||||
}
|
||||
|
||||
v[0] = reflect.ValueOf(req)
|
||||
},
|
||||
MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) {
|
||||
|
17
pilot.go
17
pilot.go
@ -7,6 +7,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/autopilot"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
@ -49,18 +50,12 @@ func (c *chanController) OpenChannel(target *btcec.PublicKey,
|
||||
// advertised IP addresses, or have made a connection.
|
||||
var connected bool
|
||||
for _, addr := range addrs {
|
||||
// If the address doesn't already have a port, then
|
||||
// we'll assume the current default port.
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return fmt.Errorf("TCP address required instead "+
|
||||
"have %T", addr)
|
||||
switch addr.(type) {
|
||||
case *net.TCPAddr, *tor.OnionAddr:
|
||||
lnAddr.Address = addr
|
||||
default:
|
||||
return fmt.Errorf("unknown address type %T", addr)
|
||||
}
|
||||
if tcpAddr.Port == 0 {
|
||||
tcpAddr.Port = defaultPeerPort
|
||||
}
|
||||
|
||||
lnAddr.Address = tcpAddr
|
||||
|
||||
// TODO(roasbeef): make perm connection in server after
|
||||
// chan open?
|
||||
|
17
rpcserver.go
17
rpcserver.go
@ -9,8 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -635,25 +633,14 @@ func (r *rpcServer) ConnectPeer(ctx context.Context,
|
||||
return nil, fmt.Errorf("cannot make connection to self")
|
||||
}
|
||||
|
||||
// If the address doesn't already have a port, we'll assume the current
|
||||
// default port.
|
||||
var addr string
|
||||
_, _, err = net.SplitHostPort(in.Addr.Host)
|
||||
if err != nil {
|
||||
addr = net.JoinHostPort(in.Addr.Host, strconv.Itoa(defaultPeerPort))
|
||||
} else {
|
||||
addr = in.Addr.Host
|
||||
}
|
||||
|
||||
// We use ResolveTCPAddr here in case we wish to resolve hosts over Tor.
|
||||
host, err := cfg.net.ResolveTCPAddr("tcp", addr)
|
||||
addr, err := parseAddr(in.Addr.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerAddr := &lnwire.NetAddress{
|
||||
IdentityKey: pubKey,
|
||||
Address: host,
|
||||
Address: addr,
|
||||
ChainNet: activeNetParams.Net,
|
||||
}
|
||||
|
||||
|
239
server.go
239
server.go
@ -28,6 +28,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/connmgr"
|
||||
@ -73,6 +74,16 @@ type server struct {
|
||||
// long-term identity private key.
|
||||
lightningID [32]byte
|
||||
|
||||
// listenAddrs is the list of addresses the server is currently
|
||||
// listening on.
|
||||
listenAddrs []string
|
||||
|
||||
// torController is a client that will communicate with a locally
|
||||
// running Tor server. This client will handle initiating and
|
||||
// authenticating the connection to the Tor server, automatically
|
||||
// creating and setting up onion services, etc.
|
||||
torController *tor.Controller
|
||||
|
||||
mu sync.RWMutex
|
||||
peersByPub map[string]*peer
|
||||
|
||||
@ -139,6 +150,42 @@ type server struct {
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// parseAddr parses an address from its string format to a net.Addr.
|
||||
func parseAddr(address string) (net.Addr, error) {
|
||||
var (
|
||||
host string
|
||||
port int
|
||||
)
|
||||
|
||||
// Split the address into its host and port components.
|
||||
h, p, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
// If a port wasn't specified, we'll assume the address only
|
||||
// contains the host so we'll use the default port.
|
||||
host = address
|
||||
port = defaultPeerPort
|
||||
} else {
|
||||
// Otherwise, we'll note both the host and ports.
|
||||
host = h
|
||||
portNum, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port = portNum
|
||||
}
|
||||
|
||||
if tor.IsOnionHost(host) {
|
||||
return &tor.OnionAddr{OnionService: host, Port: port}, nil
|
||||
}
|
||||
|
||||
// If the host is part of a TCP address, we'll use the network
|
||||
// specific ResolveTCPAddr function in order to resolve these
|
||||
// addresses over Tor in order to prevent leaking your real IP
|
||||
// address.
|
||||
hostPort := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
return cfg.net.ResolveTCPAddr("tcp", hostPort)
|
||||
}
|
||||
|
||||
// newServer creates a new instance of the server which is to listen using the
|
||||
// passed listener address.
|
||||
func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
||||
@ -178,6 +225,8 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
||||
identityPriv: privKey,
|
||||
nodeSigner: newNodeSigner(privKey),
|
||||
|
||||
listenAddrs: listenAddrs,
|
||||
|
||||
// TODO(roasbeef): derive proper onion key based on rotation
|
||||
// schedule
|
||||
sphinx: htlcswitch.NewOnionProcessor(sphinxRouter),
|
||||
@ -251,25 +300,25 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
|
||||
}
|
||||
|
||||
// If external IP addresses have been specified, add those to the list
|
||||
// of this server's addresses. We need to use the cfg.net.ResolveTCPAddr
|
||||
// function in case we wish to resolve hosts over Tor since domains
|
||||
// CAN be passed into the ExternalIPs configuration option.
|
||||
// of this server's addresses.
|
||||
selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs))
|
||||
for _, ip := range cfg.ExternalIPs {
|
||||
var addr string
|
||||
_, _, err = net.SplitHostPort(ip)
|
||||
if err != nil {
|
||||
addr = net.JoinHostPort(ip, strconv.Itoa(defaultPeerPort))
|
||||
} else {
|
||||
addr = ip
|
||||
}
|
||||
|
||||
lnAddr, err := cfg.net.ResolveTCPAddr("tcp", addr)
|
||||
addr, err := parseAddr(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selfAddrs = append(selfAddrs, lnAddr)
|
||||
selfAddrs = append(selfAddrs, addr)
|
||||
}
|
||||
|
||||
// If we were requested to route connections through Tor and to
|
||||
// automatically create an onion service, we'll initiate our Tor
|
||||
// controller and establish a connection to the Tor server.
|
||||
//
|
||||
// NOTE: v3 onion services cannot be created automatically yet. In the
|
||||
// future, this will be expanded to do so.
|
||||
if cfg.Tor.Active && cfg.Tor.V2 {
|
||||
s.torController = tor.NewController(cfg.Tor.Control)
|
||||
}
|
||||
|
||||
chanGraph := chanDB.ChannelGraph()
|
||||
@ -561,6 +610,12 @@ func (s *server) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.torController != nil {
|
||||
if err := s.initTorController(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Start the notification server. This is used so channel management
|
||||
// goroutines can be notified when a funding transaction reaches a
|
||||
// sufficient number of confirmations, or when the input for the
|
||||
@ -632,6 +687,10 @@ func (s *server) Stop() error {
|
||||
|
||||
close(s.quit)
|
||||
|
||||
if s.torController != nil {
|
||||
s.torController.Stop()
|
||||
}
|
||||
|
||||
// Shutdown the wallet, funding manager, and the rpc server.
|
||||
s.cc.chainNotifier.Stop()
|
||||
s.chanRouter.Stop()
|
||||
@ -693,15 +752,9 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e
|
||||
srvrLog.Infof("Creating DNS peer bootstrapper with "+
|
||||
"seeds: %v", dnsSeeds)
|
||||
|
||||
dnsBootStrapper, err := discovery.NewDNSSeedBootstrapper(
|
||||
dnsSeeds,
|
||||
cfg.net.LookupHost,
|
||||
cfg.net.LookupSRV,
|
||||
dnsBootStrapper := discovery.NewDNSSeedBootstrapper(
|
||||
dnsSeeds, cfg.net,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bootStrappers = append(bootStrappers, dnsBootStrapper)
|
||||
}
|
||||
}
|
||||
@ -861,6 +914,49 @@ func (s *server) peerBootstrapper(numTargetPeers uint32,
|
||||
}
|
||||
}
|
||||
|
||||
// initTorController initiliazes the Tor controller backed by lnd and
|
||||
// automatically sets up a v2 onion service in order to listen for inbound
|
||||
// connections over Tor.
|
||||
func (s *server) initTorController() error {
|
||||
if err := s.torController.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the different ports the server is listening on. The onion
|
||||
// service's virtual port will map to these ports and one will be picked
|
||||
// at random when the onion service is being accessed.
|
||||
listenPorts := make(map[int]struct{})
|
||||
for _, listenAddr := range s.listenAddrs {
|
||||
// At this point, the listen addresses should have already been
|
||||
// normalized, so it's safe to ignore the errors.
|
||||
_, portStr, _ := net.SplitHostPort(listenAddr)
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
listenPorts[port] = struct{}{}
|
||||
}
|
||||
|
||||
// Once the port mapping has been set, we can go ahead and automatically
|
||||
// create our onion service. The service's private key will be saved to
|
||||
// disk in order to regain access to this service when restarting `lnd`.
|
||||
virtToTargPorts := tor.VirtToTargPorts{defaultPeerPort: listenPorts}
|
||||
onionServiceAddrs, err := s.torController.AddOnionV2(
|
||||
cfg.Tor.V2PrivateKeyPath, virtToTargPorts,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that the onion service has been created, we'll add the different
|
||||
// onion addresses it can be reached at to our list of advertised
|
||||
// addresses.
|
||||
for _, addr := range onionServiceAddrs {
|
||||
s.currentNodeAnn.Addresses = append(
|
||||
s.currentNodeAnn.Addresses, addr,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// genNodeAnnouncement generates and returns the current fully signed node
|
||||
// announcement. If refresh is true, then the time stamp of the announcement
|
||||
// will be updated in order to ensure it propagates through the network.
|
||||
@ -921,17 +1017,7 @@ func (s *server) establishPersistentConnections() error {
|
||||
return err
|
||||
}
|
||||
for _, node := range linkNodes {
|
||||
for _, address := range node.Addresses {
|
||||
switch addr := address.(type) {
|
||||
case *net.TCPAddr:
|
||||
if addr.Port == 0 {
|
||||
addr.Port = defaultPeerPort
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
pubStr := string(node.IdentityPub.SerializeCompressed())
|
||||
|
||||
nodeAddrs := &nodeAddresses{
|
||||
pubKey: node.IdentityPub,
|
||||
addresses: node.Addresses,
|
||||
@ -964,17 +1050,29 @@ func (s *server) establishPersistentConnections() error {
|
||||
linkNodeAddrs, ok := nodeAddrsMap[pubStr]
|
||||
if ok {
|
||||
for _, lnAddress := range linkNodeAddrs.addresses {
|
||||
lnAddrTCP, ok := lnAddress.(*net.TCPAddr)
|
||||
if !ok {
|
||||
var addrHost string
|
||||
switch addr := lnAddress.(type) {
|
||||
case *net.TCPAddr:
|
||||
addrHost = addr.IP.String()
|
||||
case *tor.OnionAddr:
|
||||
addrHost = addr.OnionService
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
var addrMatched bool
|
||||
for _, polAddress := range policy.Node.Addresses {
|
||||
polTCPAddr, ok := polAddress.(*net.TCPAddr)
|
||||
if ok && polTCPAddr.IP.Equal(lnAddrTCP.IP) {
|
||||
addrMatched = true
|
||||
addrs = append(addrs, polTCPAddr)
|
||||
switch addr := polAddress.(type) {
|
||||
case *net.TCPAddr:
|
||||
if addr.IP.String() == addrHost {
|
||||
addrMatched = true
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
case *tor.OnionAddr:
|
||||
if addr.OnionService == addrHost {
|
||||
addrMatched = true
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !addrMatched {
|
||||
@ -983,9 +1081,9 @@ func (s *server) establishPersistentConnections() error {
|
||||
}
|
||||
} else {
|
||||
for _, addr := range policy.Node.Addresses {
|
||||
polTCPAddr, ok := addr.(*net.TCPAddr)
|
||||
if ok {
|
||||
addrs = append(addrs, polTCPAddr)
|
||||
switch addr.(type) {
|
||||
case *net.TCPAddr, *tor.OnionAddr:
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1447,17 +1545,18 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
||||
addr := conn.RemoteAddr()
|
||||
pubKey := brontideConn.RemotePub()
|
||||
|
||||
// We'll ensure that we locate the proper port to use within the peer's
|
||||
// address for reconnecting purposes.
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok && !inbound {
|
||||
targetPort := s.fetchNodeAdvertisedPort(pubKey, tcpAddr)
|
||||
|
||||
// Once we have the correct port, we'll make a new copy of the
|
||||
// address so we don't modify the underlying pointer directly.
|
||||
addr = &net.TCPAddr{
|
||||
IP: tcpAddr.IP,
|
||||
Port: targetPort,
|
||||
Zone: tcpAddr.Zone,
|
||||
// We'll ensure that we locate an advertised address to use within the
|
||||
// peer's address for reconnection purposes.
|
||||
//
|
||||
// TODO: leave the address field empty if there aren't any?
|
||||
if !inbound {
|
||||
advertisedAddr, err := s.fetchNodeAdvertisedAddr(pubKey)
|
||||
if err != nil {
|
||||
srvrLog.Errorf("Unable to retrieve advertised address "+
|
||||
"for node %x: %v", pubKey.SerializeCompressed(),
|
||||
err)
|
||||
} else {
|
||||
addr = advertisedAddr
|
||||
}
|
||||
}
|
||||
|
||||
@ -2136,42 +2235,16 @@ func computeNextBackoff(currBackoff time.Duration) time.Duration {
|
||||
return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2)
|
||||
}
|
||||
|
||||
// fetchNodeAdvertisedPort attempts to fetch the advertised port of the target
|
||||
// node. If a port isn't found, then the default port will be used.
|
||||
func (s *server) fetchNodeAdvertisedPort(pub *btcec.PublicKey,
|
||||
targetAddr *net.TCPAddr) int {
|
||||
|
||||
// If the target port is already the default peer port, then we'll
|
||||
// return that.
|
||||
if targetAddr.Port == defaultPeerPort {
|
||||
return defaultPeerPort
|
||||
}
|
||||
|
||||
// fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node.
|
||||
func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) {
|
||||
node, err := s.chanDB.ChannelGraph().FetchLightningNode(pub)
|
||||
|
||||
// If the node wasn't found, then we'll just return the current default
|
||||
// port.
|
||||
if err != nil {
|
||||
return defaultPeerPort
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Otherwise, we'll attempt to find a matching advertised IP, and will
|
||||
// then use the port for that.
|
||||
for _, addr := range node.Addresses {
|
||||
// We'll only examine an address if it's a TCP address.
|
||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this is the matching IP, then we'll return the port that
|
||||
// it has been advertised with.
|
||||
if tcpAddr.IP.Equal(targetAddr.IP) {
|
||||
return tcpAddr.Port
|
||||
}
|
||||
if len(node.Addresses) == 0 {
|
||||
return nil, errors.New("no advertised addresses found")
|
||||
}
|
||||
|
||||
// If we couldn't find a matching IP, then we'll just return the
|
||||
// default port.
|
||||
return defaultPeerPort
|
||||
return node.Addresses[0], nil
|
||||
}
|
||||
|
21
tor/README.md
Normal file
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