diff --git a/config.go b/config.go index b7a8219b..1192be4f 100644 --- a/config.go +++ b/config.go @@ -103,6 +103,7 @@ type config struct { DisableBanning bool `long:"nobanning" description:"Disable banning of misbehaving peers"` BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"` BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."` + Whitelists []string `long:"whitelist" description:"Add an IP network or IP that will not be banned. (eg. 192.168.1.0/24 or ::1)"` RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"` RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` RPCLimitUser string `long:"rpclimituser" description:"Username for limited RPC connections"` @@ -163,6 +164,7 @@ type config struct { addCheckpoints []chaincfg.Checkpoint miningAddrs []btcutil.Address minRelayTxFee btcutil.Amount + whitelists []*net.IPNet } // serviceOptions defines the configuration options for the daemon as a service on @@ -630,6 +632,38 @@ func loadConfig() (*config, []string, error) { return nil, nil, err } + // Validate any given whitelisted IP addresses and networks. + if len(cfg.Whitelists) > 0 { + var ip net.IP + cfg.whitelists = make([]*net.IPNet, 0, len(cfg.Whitelists)) + + for _, addr := range cfg.Whitelists { + _, ipnet, err := net.ParseCIDR(addr) + if err != nil { + ip = net.ParseIP(addr) + if ip == nil { + str := "%s: The whitelist value of '%s' is invalid" + err = fmt.Errorf(str, funcName, addr) + fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, usageMessage) + return nil, nil, err + } + var bits int + if ip.To4() == nil { + // IPv6 + bits = 128 + } else { + bits = 32 + } + ipnet = &net.IPNet{ + IP: ip, + Mask: net.CIDRMask(bits, bits), + } + } + cfg.whitelists = append(cfg.whitelists, ipnet) + } + } + // --addPeer and --connect do not mix. if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 { str := "%s: the --addpeer and --connect options can not be " + diff --git a/doc.go b/doc.go index 90da387a..631568ff 100644 --- a/doc.go +++ b/doc.go @@ -35,10 +35,12 @@ Application Options: (default all interfaces port: 8333, testnet: 18333) --maxpeers= Max number of inbound and outbound peers (125) --nobanning Disable banning of misbehaving peers - --banthreshold= Maximum allowed ban score before disconnecting and - banning misbehaving peers. --banduration= How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second (24h0m0s) + --banthreshold= Maximum allowed ban score before disconnecting and + banning misbehaving peers. + --whitelist= Add an IP network or IP that will not be banned. + (eg. 192.168.1.0/24 or ::1) -u, --rpcuser= Username for RPC connections -P, --rpcpass= Password for RPC connections --rpclimituser= Username for limited RPC connections diff --git a/sample-btcd.conf b/sample-btcd.conf index 3ac9aab4..ead8d06a 100644 --- a/sample-btcd.conf +++ b/sample-btcd.conf @@ -114,6 +114,13 @@ ; banduration=24h ; banduration=11h30m15s +; Add whitelisted IP networks and IPs. Connected peers whose IP matches a +; whitelist will not have their ban score increased. +; whitelist=127.0.0.1 +; whitelist=::1 +; whitelist=192.168.0.0/24 +; whitelist=fd00::/16 + ; Disable DNS seeding for peers. By default, when btcd starts, it will use ; DNS to query for available peers to connect with. ; nodnsseed=1 diff --git a/server.go b/server.go index 70d85b2b..a865e247 100644 --- a/server.go +++ b/server.go @@ -223,6 +223,7 @@ type serverPeer struct { relayMtx sync.Mutex disableRelayTx bool sentAddrs bool + isWhitelisted bool filter *bloom.Filter knownAddresses map[string]struct{} banScore connmgr.DynamicBanScore @@ -315,6 +316,11 @@ func (sp *serverPeer) addBanScore(persistent, transient uint32, reason string) { if cfg.DisableBanning { return } + if sp.isWhitelisted { + peerLog.Debugf("Misbehaving whitelisted peer %s: %s", sp, reason) + return + } + warnThreshold := cfg.BanThreshold >> 1 if transient == 0 && persistent == 0 { // The score is not being increased, but a warning message is still @@ -1590,6 +1596,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config { // for disconnection. func (s *server) inboundPeerConnected(conn net.Conn) { sp := newServerPeer(s, false) + sp.isWhitelisted = isWhitelisted(conn.RemoteAddr()) sp.Peer = peer.NewInboundPeer(newPeerConfig(sp)) sp.AssociateConnection(conn) go s.peerDoneHandler(sp) @@ -1609,6 +1616,7 @@ func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) { } sp.Peer = p sp.connReq = c + sp.isWhitelisted = isWhitelisted(conn.RemoteAddr()) sp.AssociateConnection(conn) go s.peerDoneHandler(sp) s.addrManager.Attempt(sp.NA()) @@ -2599,6 +2607,32 @@ func dynamicTickDuration(remaining time.Duration) time.Duration { return time.Hour } +// isWhitelisted returns whether the IP address is included in the whitelisted +// networks and IPs. +func isWhitelisted(addr net.Addr) bool { + if len(cfg.whitelists) == 0 { + return false + } + + host, _, err := net.SplitHostPort(addr.String()) + if err != nil { + srvrLog.Warnf("Unable to SplitHostPort on '%s': %v", addr, err) + return false + } + ip := net.ParseIP(host) + if ip == nil { + srvrLog.Warnf("Unable to parse IP '%s'", addr) + return false + } + + for _, ipnet := range cfg.whitelists { + if ipnet.Contains(ip) { + return true + } + } + return false +} + // checkpointSorter implements sort.Interface to allow a slice of checkpoints to // be sorted. type checkpointSorter []chaincfg.Checkpoint