Enable use of a different proxy for .onion addresses.

This implements --onion (and --onionuser/--onionpass) that enable a
different proxy to be used to connect to .onion addresses. If no main
proxy is supplied then no proxy will be used for non-onion addresses.

Additionally we add --noonion that blocks connection attempts to .onion
addresses entirely (and avoids using tor for proxy dns lookups).

the --tor option has been supersceded and thus removed.

Closes #47
This commit is contained in:
Owain G. Ainsworth 2013-12-17 15:10:59 +00:00
parent 34657d43d9
commit dd7c910e86
7 changed files with 105 additions and 87 deletions

View file

@ -774,13 +774,7 @@ func hostToNetAddress(host string, port uint16, services btcwire.ServiceFlag) (*
prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} prefix := []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43}
ip = net.IP(append(prefix, data...)) ip = net.IP(append(prefix, data...))
} else if ip = net.ParseIP(host); ip == nil { } else if ip = net.ParseIP(host); ip == nil {
var err error ips, err := BtcdLookup(host)
var ips []net.IP
if cfg.Proxy != "" {
ips, err = torLookupIP(host, cfg.Proxy)
} else {
ips, err = net.LookupIP(host)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -5,12 +5,14 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"github.com/conformal/btcdb" "github.com/conformal/btcdb"
_ "github.com/conformal/btcdb/ldb" _ "github.com/conformal/btcdb/ldb"
"github.com/conformal/btcutil" "github.com/conformal/btcutil"
"github.com/conformal/btcwire" "github.com/conformal/btcwire"
"github.com/conformal/go-flags" "github.com/conformal/go-flags"
"github.com/conformal/go-socks"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -71,7 +73,10 @@ type config struct {
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"` Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"` ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"` ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
UseTor bool `long:"tor" description:"Specifies the proxy server used is a Tor node"` OnionProxy string `long:"onion" description:"Connect to tor hidden services via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
OnionProxyUser string `long:"onionuser" description:"Username for onion proxy server"`
OnionProxyPass string `long:"onionpass" default-mask:"-" description:"Password for onion proxy server"`
NoOnion bool `long:"noonion" description:"Disable connecting to tor hidden services"`
TestNet3 bool `long:"testnet" description:"Use the test network"` TestNet3 bool `long:"testnet" description:"Use the test network"`
RegressionTest bool `long:"regtest" description:"Use the regression test network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"`
DisableCheckpoints bool `long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing."` DisableCheckpoints bool `long:"nocheckpoints" description:"Disable built-in checkpoints. Don't do this unless you know what you're doing."`
@ -80,6 +85,10 @@ type config struct {
CpuProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"` CpuProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"` DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"` Upnp bool `long:"upnp" description:"Use UPnP to map our listening port outside of NAT"`
onionlookup func(string) ([]net.IP, error)
lookup func(string) ([]net.IP, error)
oniondial func(string, string) (net.Conn, error)
dial func(string, string) (net.Conn, error)
} }
// serviceOptions defines the configuration options for btcd as a service on // serviceOptions defines the configuration options for btcd as a service on
@ -422,15 +431,6 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err return nil, nil, err
} }
// --tor requires --proxy to be set.
if cfg.UseTor && cfg.Proxy == "" {
str := "%s: the --tor option requires --proxy to be set"
err := fmt.Errorf(str, "loadConfig")
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}
// --proxy or --connect without --listen disables listening. // --proxy or --connect without --listen disables listening.
if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) && if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
len(cfg.Listeners) == 0 { len(cfg.Listeners) == 0 {
@ -494,5 +494,60 @@ func loadConfig() (*config, []string, error) {
btcdLog.Warnf("%v", configFileError) btcdLog.Warnf("%v", configFileError)
} }
cfg.dial = net.Dial
cfg.lookup = net.LookupIP
if cfg.Proxy != "" {
proxy := &socks.Proxy{
Addr: cfg.Proxy,
Username: cfg.ProxyUser,
Password: cfg.ProxyPass,
}
cfg.dial = proxy.Dial
if !cfg.NoOnion {
cfg.lookup = func(host string) ([]net.IP, error) {
return torLookupIP(host, cfg.Proxy)
}
}
}
if cfg.OnionProxy != "" {
cfg.oniondial = func(a, b string) (net.Conn, error) {
proxy := &socks.Proxy{
Addr: cfg.OnionProxy,
Username: cfg.OnionProxyUser,
Password: cfg.OnionProxyPass,
}
return proxy.Dial(a, b)
}
cfg.onionlookup = func(host string) ([]net.IP, error) {
return torLookupIP(host, cfg.OnionProxy)
}
} else {
cfg.oniondial = cfg.dial
cfg.onionlookup = cfg.lookup
}
if cfg.NoOnion {
cfg.oniondial = func(a, b string) (net.Conn, error) {
return nil, errors.New("tor has been disabled")
}
cfg.onionlookup = func(a string) ([]net.IP, error) {
return nil, errors.New("tor has been disabled")
}
}
return &cfg, remainingArgs, nil return &cfg, remainingArgs, nil
} }
func BtcdDial(network, address string) (net.Conn, error) {
if strings.HasSuffix(address, ".onion") {
return cfg.oniondial(network, address)
}
return cfg.dial(network, address)
}
func BtcdLookup(host string) ([]net.IP, error) {
if strings.HasSuffix(host, ".onion") {
return cfg.onionlookup(host)
}
return cfg.lookup(host)
}

View file

@ -7,7 +7,6 @@ package main
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"net" "net"
) )
@ -41,29 +40,9 @@ var (
} }
) )
// try individual DNS server return list of strings for responses. // torLookupIP uses Tor to resolve DNS via the SOCKS extension they provide for
func doDNSLookup(host, proxy string) ([]net.IP, error) { // resolution over the Tor network. Tor itself doesnt support ipv6 so this
var err error // doesn't either.
var addrs []net.IP
if proxy != "" {
addrs, err = torLookupIP(host, proxy)
} else {
addrs, err = net.LookupIP(host)
}
if err != nil {
return nil, err
}
return addrs, nil
}
// Use Tor to resolve DNS.
/*
TODO:
* this function must be documented internally
* this function does not handle IPv6
*/
func torLookupIP(host, proxy string) ([]net.IP, error) { func torLookupIP(host, proxy string) ([]net.IP, error) {
conn, err := net.Dial("tcp", proxy) conn, err := net.Dial("tcp", proxy)
if err != nil { if err != nil {
@ -149,17 +128,12 @@ func torLookupIP(host, proxy string) ([]net.IP, error) {
// resolution. If any errors occur then the seeder that errored will not have // resolution. If any errors occur then the seeder that errored will not have
// any hosts in the list. Therefore if all hosts failed an empty slice of // any hosts in the list. Therefore if all hosts failed an empty slice of
// strings will be returned. // strings will be returned.
func dnsDiscover(seeder string, proxy string) []net.IP { func dnsDiscover(seeder string) []net.IP {
discLog.Debugf("Fetching list of seeds from %v", seeder) discLog.Debugf("Fetching list of seeds from %v", seeder)
peers, err := doDNSLookup(seeder, proxy) peers, err := BtcdLookup(seeder)
if err != nil { if err != nil {
seederPlusProxy := seeder
if proxy != "" {
seederPlusProxy = fmt.Sprintf("%s (proxy %s)",
seeder, proxy)
}
discLog.Debugf("Unable to fetch dns seeds from %s: %v", discLog.Debugf("Unable to fetch dns seeds from %s: %v",
seederPlusProxy, err) seeder, err)
return []net.IP{} return []net.IP{}
} }

27
doc.go
View file

@ -20,8 +20,7 @@ this location.
Usage: Usage:
btcd [OPTIONS] btcd [OPTIONS]
The flags are: Application Options:
-h, --help Show this help message
-V, --version Display version information and exit -V, --version Display version information and exit
-C, --configfile= Path to configuration file -C, --configfile= Path to configuration file
-b, --datadir= Directory to store data -b, --datadir= Directory to store data
@ -33,35 +32,45 @@ The flags are:
interfaces via --listen interfaces via --listen
--listen= Add an interface/port to listen for connections (default --listen= Add an interface/port to listen for connections (default
all interfaces port: 8333, testnet: 18333) all interfaces port: 8333, testnet: 18333)
--maxpeers= Max number of inbound and outbound peers --maxpeers= Max number of inbound and outbound peers (125)
--banduration= How long to ban misbehaving peers. Valid time units are --banduration= How long to ban misbehaving peers. Valid time units are
{s, m, h}. Minimum 1 second {s, m, h}. Minimum 1 second (24h0m0s)
-u, --rpcuser= Username for RPC connections -u, --rpcuser= Username for RPC connections
-P, --rpcpass= Password for RPC connections -P, --rpcpass= Password for RPC connections
--rpclisten= Add an interface/port to listen for RPC connections --rpclisten= Add an interface/port to listen for RPC connections
(default localhost port: 8334, testnet: 18334) (default port: 8334, testnet: 18334)
--rpccert= File containing the certificate file --rpccert= File containing the certificate file
--rpckey= File containing the certificate key --rpckey= File containing the certificate key
--norpc Disable built-in RPC server -- NOTE: The RPC server is --norpc Disable built-in RPC server -- NOTE: The RPC server is
disabled by default if no rpcuser/rpcpass is specified disabled by default if no rpcuser/rpcpass is specified
--nodnsseed Disable DNS seeding for peers --nodnsseed Disable DNS seeding for peers
--externalip=
--proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050) --proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)
--proxyuser= Username for proxy server --proxyuser= Username for proxy server
--proxypass= Password for proxy server --proxypass= Password for proxy server
--onion= Connect to tor hidden services via SOCKS5 proxy (eg.
127.0.0.1:9050)
--onionuser= Username for onion proxy server
--onionpass= Password for onion proxy server
--noonion Disable connecting to tor hidden services
--tor Specifies the proxy server used is a Tor node --tor Specifies the proxy server used is a Tor node
--testnet Use the test network --testnet Use the test network
--regtest Use the regression test network --regtest Use the regression test network
--nocheckpoints Disable built-in checkpoints. Don't do this unless you --nocheckpoints Disable built-in checkpoints. Don't do this unless you
know what you're doing. know what you're doing.
--dbtype= Database backend to use for the Block Chain --dbtype= Database backend to use for the Block Chain (leveldb)
--profile= Enable HTTP profiling on given port -- NOTE port must be --profile= Enable HTTP profiling on given port -- NOTE port must be
between 1024 and 65536 between 1024 and 65536 (6060)
--cpuprofile= Write CPU profile to the specified file --cpuprofile= Write CPU profile to the specified file
-d, --debuglevel: Logging level for all subsystems {trace, debug, info, -d, --debuglevel= Logging level for all subsystems {trace, debug, info,
warn, error, critical} -- You may also specify warn, error, critical} -- You may also specify
<subsystem>=<level>,<subsystem2>=<level>,... to set the <subsystem>=<level>,<subsystem2>=<level>,... to set the
log level for individual subsystems -- Use show to list log level for individual subsystems -- Use show to list
available subsystems available subsystems (info)
--upnp Use UPnP to map our listening port outside of NAT
Help Options:
-h, --help Show this help message
*/ */
package main package main

23
peer.go
View file

@ -1544,31 +1544,16 @@ func newOutboundPeer(s *server, addr string, persistent bool) *peer {
} }
go func() { go func() {
// Select which dial method to call depending on whether or
// not a proxy is configured. Also, add proxy information to
// logged address if needed.
dial := net.Dial
faddr := addr
if cfg.Proxy != "" {
proxy := &socks.Proxy{
Addr: cfg.Proxy,
Username: cfg.ProxyUser,
Password: cfg.ProxyPass,
}
dial = proxy.Dial
faddr = fmt.Sprintf("%s via proxy %s", addr, cfg.Proxy)
}
// Attempt to connect to the peer. If the connection fails and // Attempt to connect to the peer. If the connection fails and
// this is a persistent connection, retry after the retry // this is a persistent connection, retry after the retry
// interval. // interval.
for atomic.LoadInt32(&p.disconnect) == 0 { for atomic.LoadInt32(&p.disconnect) == 0 {
srvrLog.Debugf("Attempting to connect to %s", faddr) srvrLog.Debugf("Attempting to connect to %s", addr)
conn, err := dial("tcp", addr) conn, err := BtcdDial("tcp", addr)
if err != nil { if err != nil {
p.retryCount += 1 p.retryCount += 1
srvrLog.Debugf("Failed to connect to %s: %v", srvrLog.Debugf("Failed to connect to %s: %v",
faddr, err) addr, err)
if !persistent { if !persistent {
p.server.donePeers <- p p.server.donePeers <- p
return return
@ -1576,7 +1561,7 @@ func newOutboundPeer(s *server, addr string, persistent bool) *peer {
scaledInterval := connectionRetryInterval.Nanoseconds() * p.retryCount / 2 scaledInterval := connectionRetryInterval.Nanoseconds() * p.retryCount / 2
scaledDuration := time.Duration(scaledInterval) scaledDuration := time.Duration(scaledInterval)
srvrLog.Debugf("Retrying connection to %s in "+ srvrLog.Debugf("Retrying connection to %s in "+
"%s", faddr, scaledDuration) "%s", addr, scaledDuration)
time.Sleep(scaledDuration) time.Sleep(scaledDuration)
continue continue
} }

View file

@ -28,11 +28,16 @@
; proxyuser= ; proxyuser=
; proxypass= ; proxypass=
; The SOCKS5 proxy above is Tor (https://www.torproject.org). ; The SOCKS5 proxy above is assumed to be Tor (https://www.torproject.org).
; Although not required if the proxy set is indeed Tor, setting this option ; If the proxy is not tor the the following my be used to prevent using
; improves anonymity by sending DNS queries over the Tor network (during DNS ; tor specific SOCKS queries to lookup addresses (this increases anonymity when
; seed lookup). This stops your IP from being leaked via DNS. ; tor is used by preventing your IP being leaked via DNS).
; tor=1 ;noonion=1
; Use an alternative proxy to connect to .onion addresses. The proxy is assumed
; to be a Tor node. Non .onion addresses will be contacted with the main proxy
; or without a proxy if none is set.
; onion=127.0.0.1:9051
; ****************************************************************************** ; ******************************************************************************
; Summary of 'addpeer' versus 'connect'. ; Summary of 'addpeer' versus 'connect'.

View file

@ -407,12 +407,8 @@ func (s *server) seedFromDNS() {
return return
} }
proxy := ""
if cfg.Proxy != "" && cfg.UseTor {
proxy = cfg.Proxy
}
for _, seeder := range activeNetParams.dnsSeeds { for _, seeder := range activeNetParams.dnsSeeds {
seedpeers := dnsDiscover(seeder, proxy) seedpeers := dnsDiscover(seeder)
if len(seedpeers) == 0 { if len(seedpeers) == 0 {
continue continue
} }