Copy btcd RPC listening behavior.

This change copies the listening behavior of btcd by replacing the
--serverport option with --listen.  By default, btcwallet will only
listen for localhost connections, but with this change it will be
possible to add listeners for remote connections.

This was added due to finding a bug with updateConfigWithActiveParams.
After consulting the btcd source code, the bug was fixed by replacing
the function (as it was no longer needed) when the new listening code
was introduced.

While here, mask out the password flag from being shown in the help
message.
This commit is contained in:
Josh Rickmar 2013-12-05 17:20:52 -05:00
parent 52525dd302
commit 8669129917
5 changed files with 127 additions and 62 deletions

View file

@ -186,14 +186,6 @@ func (store *AccountStore) CreateEncryptedWallet(name, desc string, passphrase [
return ErrAcctExists
}
// Decide which Bitcoin network must be used.
var net btcwire.BitcoinNet
if cfg.MainNet {
net = btcwire.MainNet
} else {
net = btcwire.TestNet3
}
// Get current block's height and hash.
bs, err := GetCurBlock()
if err != nil {
@ -201,7 +193,7 @@ func (store *AccountStore) CreateEncryptedWallet(name, desc string, passphrase [
}
// Create new wallet in memory.
wlt, err := wallet.NewWallet(name, desc, passphrase, net, &bs)
wlt, err := wallet.NewWallet(name, desc, passphrase, cfg.Net(), &bs)
if err != nil {
return err
}

2
cmd.go
View file

@ -225,7 +225,7 @@ func main() {
go DirtyAccountSyncer()
go func() {
s, err := newServer()
s, err := newServer(cfg.SvrListeners)
if err != nil {
log.Errorf("Unable to create HTTP server: %v", err)
os.Exit(1)

View file

@ -44,22 +44,22 @@ var (
)
type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
CAFile string `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
Connect string `short:"c" long:"connect" description:"Server and port of btcd instance to connect to"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
SvrPort string `short:"p" long:"serverport" description:"Port to serve frontend websocket connections on (default: 18332, mainnet: 8332)"`
DataDir string `short:"D" long:"datadir" description:"Directory to store wallets and transactions"`
Username string `short:"u" long:"username" description:"Username for btcd authorization"`
Password string `short:"P" long:"password" description:"Password for btcd authorization"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
MainNet bool `long:"mainnet" description:"*DISABLED* Use the main Bitcoin network (default testnet3)"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
CAFile string `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
Connect string `short:"c" long:"connect" description:"Server and port of btcd instance to connect to"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
SvrListeners []string `long:"listen" description:"Listen for RPC/websocket connections on this interface/port (default no listening. default port: 18332, mainnet: 8332)"`
DataDir string `short:"D" long:"datadir" description:"Directory to store wallets and transactions"`
Username string `short:"u" long:"username" description:"Username for btcd authorization"`
Password string `short:"P" long:"password" default-mask:"-" description:"Password for btcd authorization"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
MainNet bool `long:"mainnet" description:"*DISABLED* Use the main Bitcoin network (default testnet3)"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
}
// cleanAndExpandPath expands environement variables and leading ~ in the
@ -76,18 +76,28 @@ func cleanAndExpandPath(path string) string {
return filepath.Clean(os.ExpandEnv(path))
}
// updateConfigWithActiveParams update the passed config with parameters
// from the active net params if the relevant options in the passed config
// object are the default so options specified by the user on the command line
// are not overridden.
func updateConfigWithActiveParams(cfg *config) {
if cfg.Connect == netParams(defaultBtcNet).connect {
cfg.Connect = activeNetParams.connect
// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
result := make([]string, 0)
seen := map[string]bool{}
for _, val := range addrs {
if _, ok := seen[val]; !ok {
result = append(result, val)
seen[val] = true
}
}
return result
}
// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
for i, addr := range addrs {
addrs[i] = normalizeAddress(addr, defaultPort)
}
if cfg.SvrPort == netParams(defaultBtcNet).svrPort {
cfg.SvrPort = activeNetParams.svrPort
}
return removeDuplicateAddresses(addrs)
}
// filesExists reports whether the named file or directory exists.
@ -129,7 +139,6 @@ func loadConfig() (*config, []string, error) {
CAFile: defaultCAFile,
ConfigFile: defaultConfigFile,
Connect: netParams(defaultBtcNet).connect,
SvrPort: netParams(defaultBtcNet).svrPort,
DataDir: defaultDataDir,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
@ -189,14 +198,10 @@ func loadConfig() (*config, []string, error) {
log.Warnf("%v", configFileError)
}
// TODO(jrick): Enable mainnet support again when ready.
cfg.MainNet = false
// Choose the active network params based on the mainnet net flag.
if cfg.MainNet {
activeNetParams = netParams(btcwire.MainNet)
//activeNetParams = netParams(btcwire.MainNet)
}
updateConfigWithActiveParams(&cfg)
// Validate debug log level
if !validLogLevel(cfg.DebugLevel) {
@ -210,6 +215,28 @@ func loadConfig() (*config, []string, error) {
// Add default port to connect flag if missing.
cfg.Connect = normalizeAddress(cfg.Connect, activeNetParams.btcdPort)
if len(cfg.SvrListeners) == 0 {
addrs, err := net.LookupHost("localhost")
if err != nil {
return nil, nil, err
}
cfg.SvrListeners = make([]string, 0, len(addrs))
for _, addr := range addrs {
addr = net.JoinHostPort(addr, activeNetParams.svrPort)
cfg.SvrListeners = append(cfg.SvrListeners, addr)
}
}
// Add default port to all listener addresses if needed and remove
// duplicate addresses.
cfg.SvrListeners = normalizeAddresses(cfg.SvrListeners,
activeNetParams.svrPort)
// Add default port to all rpc listener addresses if needed and remove
// duplicate addresses.
cfg.SvrListeners = normalizeAddresses(cfg.SvrListeners,
activeNetParams.svrPort)
// Expand environment variable and leading ~ for filepaths.
cfg.CAFile = cleanAndExpandPath(cfg.CAFile)

View file

@ -11,5 +11,5 @@
; username=
; password=
; Port to serve websocket connections for wallet frontends.
; serverport=18332
; Listen for RPC/websocket connections on the following ports/interfaces:
; listen=localhost:18332

View file

@ -41,6 +41,7 @@ import (
"net"
"net/http"
"os"
"runtime"
"sync"
"time"
)
@ -101,19 +102,57 @@ var (
// server holds the items the RPC server may need to access (auth,
// config, shutdown, etc.)
type server struct {
port string
wg sync.WaitGroup
listeners []net.Listener
authsha [sha256.Size]byte
}
// parseListeners splits the list of listen addresses passed in addrs into
// IPv4 and IPv6 slices and returns them. This allows easy creation of the
// listeners on the correct interface "tcp4" and "tcp6". It also properly
// detects addresses which apply to "all interfaces" and adds the address to
// both slices.
func parseListeners(addrs []string) ([]string, []string, error) {
ipv4ListenAddrs := make([]string, 0, len(addrs)*2)
ipv6ListenAddrs := make([]string, 0, len(addrs)*2)
for _, addr := range addrs {
host, _, err := net.SplitHostPort(addr)
if err != nil {
// Shouldn't happen due to already being normalized.
return nil, nil, err
}
// Empty host or host of * on plan9 is both IPv4 and IPv6.
if host == "" || (host == "*" && runtime.GOOS == "plan9") {
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
continue
}
// Parse the IP.
ip := net.ParseIP(host)
if ip == nil {
return nil, nil, fmt.Errorf("'%s' is not a valid IP "+
"address", host)
}
// To4 returns nil when the IP is not an IPv4 address, so use
// this determine the address type.
if ip.To4() == nil {
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
} else {
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
}
}
return ipv4ListenAddrs, ipv6ListenAddrs, nil
}
// newServer returns a new instance of the server struct.
func newServer() (*server, error) {
func newServer(listenAddrs []string) (*server, error) {
login := cfg.Username + ":" + cfg.Password
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
s := server{
authsha: sha256.Sum256([]byte(auth)),
port: cfg.SvrPort,
}
// Check for existence of cert file and key file
@ -133,24 +172,31 @@ func newServer() (*server, error) {
Certificates: []tls.Certificate{keypair},
}
// IPv4 listener.
var listeners []net.Listener
listenAddr4 := net.JoinHostPort("127.0.0.1", s.port)
listener4, err := tls.Listen("tcp4", listenAddr4, &tlsConfig)
if err != nil {
log.Errorf("RPCS: Couldn't create listener: %v", err)
return nil, err
ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs)
listeners := make([]net.Listener, 0,
len(ipv6ListenAddrs)+len(ipv4ListenAddrs))
for _, addr := range ipv4ListenAddrs {
listener, err := tls.Listen("tcp4", addr, &tlsConfig)
if err != nil {
log.Warnf("RPCS: Can't listen on %s: %v", addr,
err)
continue
}
listeners = append(listeners, listener)
}
listeners = append(listeners, listener4)
// IPv6 listener.
listenAddr6 := net.JoinHostPort("::1", s.port)
listener6, err := tls.Listen("tcp6", listenAddr6, &tlsConfig)
if err != nil {
log.Errorf("RPCS: Couldn't create listener: %v", err)
return nil, err
for _, addr := range ipv6ListenAddrs {
listener, err := tls.Listen("tcp6", addr, &tlsConfig)
if err != nil {
log.Warnf("RPCS: Can't listen on %s: %v", addr,
err)
continue
}
listeners = append(listeners, listener)
}
if len(listeners) == 0 {
return nil, errors.New("RPCS: No valid listen address")
}
listeners = append(listeners, listener6)
s.listeners = listeners