Support --listen.

This allows the provision of address/port pairs to be listened on instead
of just providing the port. e.g.:
btcd --listen 1.2.3.4:4321 --listen 127.0.0.01 --listen [::1]:5432

When --proxy and --connect are used, we disable listening *unless* any --listen
arguments have been provided, when we will listen on those addresses as
requested.

Initial code by davec, integration by myself.

Closes #33

allow listens to fail, but warn. error if all failed

fmt
This commit is contained in:
Owain G. Ainsworth 2013-09-19 10:46:33 -05:00
parent 3108b94401
commit 6116a6cb02
4 changed files with 115 additions and 44 deletions

View file

@ -89,10 +89,11 @@ func btcdMain() error {
}) })
// Create server and start it. // Create server and start it.
listenAddr := net.JoinHostPort("", cfg.Port) server, err := newServer(cfg.Listeners, db, activeNetParams.btcnet)
server, err := newServer(listenAddr, db, activeNetParams.btcnet)
if err != nil { if err != nil {
log.Errorf("Unable to start server on %v: %v", listenAddr, err) // TODO(oga) this logging could do with some beautifying.
log.Errorf("Unable to start server on %v: %v",
cfg.Listeners, err)
return err return err
} }
server.Start() server.Start()

View file

@ -34,6 +34,7 @@ var (
btcdHomeDir = btcutil.AppDataDir("btcd", false) btcdHomeDir = btcutil.AppDataDir("btcd", false)
defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename) defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename)
defaultDataDir = filepath.Join(btcdHomeDir, defaultDataDirname) defaultDataDir = filepath.Join(btcdHomeDir, defaultDataDirname)
defaultListener = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
knownDbTypes = btcdb.SupportedDBs() knownDbTypes = btcdb.SupportedDBs()
) )
@ -47,7 +48,7 @@ type config struct {
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"` AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"` ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
DisableListen bool `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect option is used or if the --proxy option is used without the --tor option"` DisableListen bool `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect option is used or if the --proxy option is used without the --tor option"`
Port string `short:"p" long:"port" description:"Listen for connections on this port (default: 8333, testnet: 18333)"` Listeners []string `long:"listen" description:"Listen for connections on this interface/port (default all interfaces port: 8333, testnet: 18333)"`
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"` MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"` BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"`
RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"` RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"`
@ -112,16 +113,6 @@ func validDbType(dbType string) bool {
return false return false
} }
// normalizePeerAddress returns addr with the default peer port appended if
// there is not already a port specified.
func normalizePeerAddress(addr string) string {
_, _, err := net.SplitHostPort(addr)
if err != nil {
return net.JoinHostPort(addr, activeNetParams.peerPort)
}
return addr
}
// removeDuplicateAddresses returns a new slice with all duplicate entries in // removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed. // addrs removed.
func removeDuplicateAddresses(addrs []string) []string { func removeDuplicateAddresses(addrs []string) []string {
@ -136,15 +127,36 @@ func removeDuplicateAddresses(addrs []string) []string {
return result return result
} }
// normalizeAndRemoveDuplicateAddresses return a new slice with all the passed // normalizeAddress returns addr with the passed default port appended if
// addresses normalized and duplicates removed. // there is not already a port specified.
func normalizeAndRemoveDuplicateAddresses(addrs []string) []string { func normalizeAddress(addr, defaultPort string) string {
for i, addr := range addrs { _, _, err := net.SplitHostPort(addr)
addrs[i] = normalizePeerAddress(addr) if err != nil {
return net.JoinHostPort(addr, defaultPort)
} }
addrs = removeDuplicateAddresses(addrs) return addr
}
return addrs // 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)
}
return removeDuplicateAddresses(addrs)
}
// cleanListenAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanListenAddresses(addrs []string) []string {
return normalizeAddresses(addrs, activeNetParams.listenPort)
}
// cleanPeerAddresses returns a new slice with all the passed peer addresses
// normalized and duplicates removed.
func cleanPeerAddresses(addrs []string) []string {
return normalizeAddresses(addrs, activeNetParams.peerPort)
} }
// updateConfigWithActiveParams update the passed config with parameters // updateConfigWithActiveParams update the passed config with parameters
@ -152,9 +164,10 @@ func normalizeAndRemoveDuplicateAddresses(addrs []string) []string {
// object are the default so options specified by the user on the command line // object are the default so options specified by the user on the command line
// are not overridden. // are not overridden.
func updateConfigWithActiveParams(cfg *config) { func updateConfigWithActiveParams(cfg *config) {
if cfg.Port == netParams(defaultBtcnet).listenPort { // Even though there should only be one default, a duplicate might
cfg.Port = activeNetParams.listenPort // have been specified via the config file or CLI options. So, make
} // sure to update all default entries rather than only the first one
// found. Duplicates are removed later.
if cfg.RPCPort == netParams(defaultBtcnet).rpcPort { if cfg.RPCPort == netParams(defaultBtcnet).rpcPort {
cfg.RPCPort = activeNetParams.rpcPort cfg.RPCPort = activeNetParams.rpcPort
@ -187,7 +200,6 @@ func loadConfig() (*config, []string, error) {
// Default config. // Default config.
cfg := config{ cfg := config{
DebugLevel: defaultLogLevel, DebugLevel: defaultLogLevel,
Port: netParams(defaultBtcnet).listenPort,
RPCPort: netParams(defaultBtcnet).rpcPort, RPCPort: netParams(defaultBtcnet).rpcPort,
MaxPeers: defaultMaxPeers, MaxPeers: defaultMaxPeers,
BanDuration: defaultBanDuration, BanDuration: defaultBanDuration,
@ -329,15 +341,24 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err return nil, nil, err
} }
// --proxy without --tor means no listening. // --proxy or --connect without --listen disables listening.
if cfg.Proxy != "" && !cfg.UseTor { if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
len(cfg.Listeners) == 0 {
cfg.DisableListen = true cfg.DisableListen = true
} }
// Connect means no seeding or listening. // Connect means no DNS seeding.
if len(cfg.ConnectPeers) > 0 { if len(cfg.ConnectPeers) > 0 {
cfg.DisableDNSSeed = true cfg.DisableDNSSeed = true
cfg.DisableListen = true }
// Add the default listener if none were specified. The default
// listener is all addresses on the listen port for the network
// we are to connect to.
if len(cfg.Listeners) == 0 {
cfg.Listeners = []string{
net.JoinHostPort("", activeNetParams.listenPort),
}
} }
// The RPC server is disabled if no username or password is provided. // The RPC server is disabled if no username or password is provided.
@ -345,10 +366,14 @@ func loadConfig() (*config, []string, error) {
cfg.DisableRPC = true cfg.DisableRPC = true
} }
// Add default port to all listner addresses if needed and remove
// duplicate addresses.
cfg.Listeners = cleanListenAddresses(cfg.Listeners)
// Add default port to all added peer addresses if needed and remove // Add default port to all added peer addresses if needed and remove
// duplicate addresses. // duplicate addresses.
cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers) cfg.AddPeers = cleanPeerAddresses(cfg.AddPeers)
cfg.ConnectPeers = normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers) cfg.ConnectPeers = cleanPeerAddresses(cfg.ConnectPeers)
return &cfg, remainingArgs, nil return &cfg, remainingArgs, nil
} }

View file

@ -503,7 +503,7 @@ func handleAddNode(s *rpcServer, cmd btcjson.Cmd,
walletNotification chan []byte) (interface{}, error) { walletNotification chan []byte) (interface{}, error) {
c := cmd.(*btcjson.AddNodeCmd) c := cmd.(*btcjson.AddNodeCmd)
addr := normalizePeerAddress(c.Addr) addr := normalizeAddress(c.Addr, activeNetParams.peerPort)
var err error var err error
switch c.SubCmd { switch c.SubCmd {
case "add": case "add":

View file

@ -11,6 +11,7 @@ import (
"github.com/conformal/btcdb" "github.com/conformal/btcdb"
"github.com/conformal/btcwire" "github.com/conformal/btcwire"
"net" "net"
"runtime"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -765,30 +766,74 @@ func (s *server) ScheduleShutdown(duration time.Duration) {
}() }()
} }
func parseListeners(addrs []string) ([]string, []string, error) {
ipv4ListenAddrs := make([]string, 0, len(cfg.Listeners)*2)
ipv6ListenAddrs := make([]string, 0, len(cfg.Listeners)*2)
for _, addr := range cfg.Listeners {
host, _, err := net.SplitHostPort(addr)
if err != nil {
// shouldn't happen, should have already been 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, err
}
if ip.To4() == nil {
ipv6ListenAddrs = append(ipv6ListenAddrs, addr)
} else {
ipv4ListenAddrs = append(ipv4ListenAddrs, addr)
}
}
return ipv4ListenAddrs, ipv6ListenAddrs, nil
}
// newServer returns a new btcd server configured to listen on addr for the // newServer returns a new btcd server configured to listen on addr for the
// bitcoin network type specified in btcnet. Use start to begin accepting // bitcoin network type specified in btcnet. Use start to begin accepting
// connections from peers. // connections from peers.
func newServer(addr string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, error) { func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*server, error) {
nonce, err := btcwire.RandomUint64() nonce, err := btcwire.RandomUint64()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var listeners []net.Listener ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs)
listeners := make([]net.Listener, 0,
len(ipv6ListenAddrs)+len(ipv4ListenAddrs))
if !cfg.DisableListen { if !cfg.DisableListen {
// IPv4 listener. for _, addr := range ipv4ListenAddrs {
listener4, err := net.Listen("tcp4", addr) listener, err := net.Listen("tcp4", addr)
if err != nil { if err != nil {
return nil, err log.Warnf("SRVR: Can't listen on %s: %v", addr,
err)
continue
}
listeners = append(listeners, listener)
} }
listeners = append(listeners, listener4)
// IPv6 listener. for _, addr := range ipv6ListenAddrs {
listener6, err := net.Listen("tcp6", addr) listener, err := net.Listen("tcp6", addr)
if err != nil { if err != nil {
return nil, err log.Warnf("SRVR: Can't listen on %s: %v", addr,
err)
continue
}
listeners = append(listeners, listener)
}
if len(listeners) == 0 {
return nil, errors.New("SRVR: No valid listen address")
} }
listeners = append(listeners, listener6)
} }
s := server{ s := server{