diff --git a/btcd.go b/btcd.go index e6bb53ff..664d4072 100644 --- a/btcd.go +++ b/btcd.go @@ -89,10 +89,11 @@ func btcdMain() error { }) // Create server and start it. - listenAddr := net.JoinHostPort("", cfg.Port) - server, err := newServer(listenAddr, db, activeNetParams.btcnet) + server, err := newServer(cfg.Listeners, db, activeNetParams.btcnet) 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 } server.Start() diff --git a/config.go b/config.go index 819cda36..d4bf3d89 100644 --- a/config.go +++ b/config.go @@ -34,6 +34,7 @@ var ( btcdHomeDir = btcutil.AppDataDir("btcd", false) defaultConfigFile = filepath.Join(btcdHomeDir, defaultConfigFilename) defaultDataDir = filepath.Join(btcdHomeDir, defaultDataDirname) + defaultListener = net.JoinHostPort("", netParams(defaultBtcnet).listenPort) 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"` 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"` - 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"` 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"` @@ -112,16 +113,6 @@ func validDbType(dbType string) bool { 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 // addrs removed. func removeDuplicateAddresses(addrs []string) []string { @@ -136,15 +127,36 @@ func removeDuplicateAddresses(addrs []string) []string { return result } -// normalizeAndRemoveDuplicateAddresses return a new slice with all the passed -// addresses normalized and duplicates removed. -func normalizeAndRemoveDuplicateAddresses(addrs []string) []string { - for i, addr := range addrs { - addrs[i] = normalizePeerAddress(addr) +// normalizeAddress returns addr with the passed default port appended if +// there is not already a port specified. +func normalizeAddress(addr, defaultPort string) string { + _, _, err := net.SplitHostPort(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 @@ -152,9 +164,10 @@ func normalizeAndRemoveDuplicateAddresses(addrs []string) []string { // object are the default so options specified by the user on the command line // are not overridden. func updateConfigWithActiveParams(cfg *config) { - if cfg.Port == netParams(defaultBtcnet).listenPort { - cfg.Port = activeNetParams.listenPort - } + // Even though there should only be one default, a duplicate might + // 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 { cfg.RPCPort = activeNetParams.rpcPort @@ -187,7 +200,6 @@ func loadConfig() (*config, []string, error) { // Default config. cfg := config{ DebugLevel: defaultLogLevel, - Port: netParams(defaultBtcnet).listenPort, RPCPort: netParams(defaultBtcnet).rpcPort, MaxPeers: defaultMaxPeers, BanDuration: defaultBanDuration, @@ -329,15 +341,24 @@ func loadConfig() (*config, []string, error) { return nil, nil, err } - // --proxy without --tor means no listening. - if cfg.Proxy != "" && !cfg.UseTor { + // --proxy or --connect without --listen disables listening. + if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) && + len(cfg.Listeners) == 0 { cfg.DisableListen = true } - // Connect means no seeding or listening. + // Connect means no DNS seeding. if len(cfg.ConnectPeers) > 0 { 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. @@ -345,10 +366,14 @@ func loadConfig() (*config, []string, error) { 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 // duplicate addresses. - cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers) - cfg.ConnectPeers = normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers) + cfg.AddPeers = cleanPeerAddresses(cfg.AddPeers) + cfg.ConnectPeers = cleanPeerAddresses(cfg.ConnectPeers) return &cfg, remainingArgs, nil } diff --git a/rpcserver.go b/rpcserver.go index c7a36f56..e3d4f2f8 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -503,7 +503,7 @@ func handleAddNode(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) { c := cmd.(*btcjson.AddNodeCmd) - addr := normalizePeerAddress(c.Addr) + addr := normalizeAddress(c.Addr, activeNetParams.peerPort) var err error switch c.SubCmd { case "add": diff --git a/server.go b/server.go index f4c6ffc7..1e87be70 100644 --- a/server.go +++ b/server.go @@ -11,6 +11,7 @@ import ( "github.com/conformal/btcdb" "github.com/conformal/btcwire" "net" + "runtime" "strconv" "sync" "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 // bitcoin network type specified in btcnet. Use start to begin accepting // 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() if err != nil { return nil, err } - var listeners []net.Listener + ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs) + listeners := make([]net.Listener, 0, + len(ipv6ListenAddrs)+len(ipv4ListenAddrs)) if !cfg.DisableListen { - // IPv4 listener. - listener4, err := net.Listen("tcp4", addr) - if err != nil { - return nil, err + for _, addr := range ipv4ListenAddrs { + listener, err := net.Listen("tcp4", addr) + if err != nil { + log.Warnf("SRVR: Can't listen on %s: %v", addr, + err) + continue + } + listeners = append(listeners, listener) } - listeners = append(listeners, listener4) - // IPv6 listener. - listener6, err := net.Listen("tcp6", addr) - if err != nil { - return nil, err + for _, addr := range ipv6ListenAddrs { + listener, err := net.Listen("tcp6", addr) + if err != nil { + 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{