diff --git a/banman/store.go b/banman/store.go index 2eedfd349..76a4703ee 100644 --- a/banman/store.go +++ b/banman/store.go @@ -66,6 +66,9 @@ type Store interface { // Status returns the ban status for a given IP network. Status(*net.IPNet) (Status, error) + + // UnbanIPNet removes the ban imposed on the specified peer. + UnbanIPNet(ipNet *net.IPNet) error } // NewStore returns a Store backed by a database. @@ -136,6 +139,38 @@ func (s *banStore) BanIPNet(ipNet *net.IPNet, reason Reason, duration time.Durat }) } +// UnbanIPNet removes a ban record for the IP network within the store. +func (s *banStore) UnbanIPNet(ipNet *net.IPNet) error { + err := walletdb.Update(s.db, func(tx walletdb.ReadWriteTx) error { + banStore := tx.ReadWriteBucket(banStoreBucket) + if banStore == nil { + return ErrCorruptedStore + } + + banIndex := banStore.NestedReadWriteBucket(banBucket) + if banIndex == nil { + return ErrCorruptedStore + } + + reasonIndex := banStore.NestedReadWriteBucket(reasonBucket) + if reasonIndex == nil { + return ErrCorruptedStore + } + + var ipNetBuf bytes.Buffer + if err := encodeIPNet(&ipNetBuf, ipNet); err != nil { + return fmt.Errorf("unable to encode %v: %v", ipNet, + err) + } + + k := ipNetBuf.Bytes() + + return removeBannedIPNet(banIndex, reasonIndex, k) + }) + + return err +} + // addBannedIPNet adds an entry to the ban store for the given IP network. func addBannedIPNet(banIndex, reasonIndex walletdb.ReadWriteBucket, ipNetKey []byte, reason Reason, duration time.Duration) error { diff --git a/banman/store_test.go b/banman/store_test.go index e4b5f8c40..70ba49888 100644 --- a/banman/store_test.go +++ b/banman/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" "github.com/lightninglabs/neutrino/banman" + "github.com/stretchr/testify/require" ) // createTestBanStore creates a test Store backed by a boltdb instance. @@ -123,4 +124,10 @@ func TestBanStore(t *testing.T) { // We'll query for second IP network again as it should now be unknown // to the BanStore. We should expect not to find anything regarding it. checkBanStore(ipNet2, false, 0, 0) + + // Test UnbanIPNet. + require.NoError(t, banStore.UnbanIPNet(ipNet1)) + + // We would now check that ipNet1 is indeed unbanned. + checkBanStore(ipNet1, false, 0, 0) } diff --git a/neutrino.go b/neutrino.go index 7ee45edd0..387db8b10 100644 --- a/neutrino.go +++ b/neutrino.go @@ -1081,6 +1081,24 @@ func (s *ChainService) BanPeer(addr string, reason banman.Reason) error { return s.banStore.BanIPNet(ipNet, reason, BanDuration) } +// UnbanPeer connects and unbans a previously banned peer. +func (s *ChainService) UnbanPeer(addr string, parmanent bool) error { + log.Infof("UnBanning peer %v", addr) + + ipNet, err := banman.ParseIPNet(addr, nil) + if err != nil { + return fmt.Errorf("unable to parse IP network for peer %v: %v", + addr, err) + } + + err = s.banStore.UnbanIPNet(ipNet) + if err != nil { + return fmt.Errorf("unable to unban peer: %v", err) + } + + return s.ConnectNode(addr, parmanent) +} + // IsBanned returns true if the peer is banned, and false otherwise. func (s *ChainService) IsBanned(addr string) bool { ipNet, err := banman.ParseIPNet(addr, nil)