github.com/ethereum-optimism/optimism@v1.7.2/op-node/p2p/gating/expiry.go (about)

     1  package gating
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  
     7  	"github.com/libp2p/go-libp2p/core/network"
     8  	"github.com/libp2p/go-libp2p/core/peer"
     9  	"github.com/multiformats/go-multiaddr"
    10  	manet "github.com/multiformats/go-multiaddr/net"
    11  
    12  	"github.com/ethereum/go-ethereum/log"
    13  
    14  	"github.com/ethereum-optimism/optimism/op-node/p2p/store"
    15  	"github.com/ethereum-optimism/optimism/op-service/clock"
    16  )
    17  
    18  type UnbanMetrics interface {
    19  	RecordPeerUnban()
    20  	RecordIPUnban()
    21  }
    22  
    23  //go:generate mockery --name ExpiryStore --output mocks/ --with-expecter=true
    24  type ExpiryStore interface {
    25  	store.IPBanStore
    26  	store.PeerBanStore
    27  }
    28  
    29  // ExpiryConnectionGater enhances a BlockingConnectionGater by implementing ban-expiration
    30  type ExpiryConnectionGater struct {
    31  	BlockingConnectionGater
    32  	store ExpiryStore
    33  	log   log.Logger
    34  	clock clock.Clock
    35  	m     UnbanMetrics
    36  }
    37  
    38  func AddBanExpiry(gater BlockingConnectionGater, store ExpiryStore, log log.Logger, clock clock.Clock, m UnbanMetrics) *ExpiryConnectionGater {
    39  	return &ExpiryConnectionGater{
    40  		BlockingConnectionGater: gater,
    41  		store:                   store,
    42  		log:                     log,
    43  		clock:                   clock,
    44  		m:                       m,
    45  	}
    46  }
    47  
    48  func (g *ExpiryConnectionGater) UnblockPeer(p peer.ID) error {
    49  	if err := g.BlockingConnectionGater.UnblockPeer(p); err != nil {
    50  		log.Warn("failed to unblock peer from underlying gater", "method", "UnblockPeer", "peer_id", p, "err", err)
    51  		return err
    52  	}
    53  	if err := g.store.SetPeerBanExpiration(p, time.Time{}); err != nil {
    54  		log.Warn("failed to unblock peer from expiry gater", "method", "UnblockPeer", "peer_id", p, "err", err)
    55  		return err
    56  	}
    57  	g.m.RecordPeerUnban()
    58  	return nil
    59  }
    60  
    61  func (g *ExpiryConnectionGater) peerBanExpiryCheck(p peer.ID) (allow bool) {
    62  	// if the peer is blocked, check if it's time to unblock
    63  	expiry, err := g.store.GetPeerBanExpiration(p)
    64  	if errors.Is(err, store.UnknownBanErr) {
    65  		return true // peer is allowed if it has not been banned
    66  	}
    67  	if err != nil {
    68  		g.log.Warn("failed to load peer-ban expiry time", "method", "peerBanExpiryCheck", "peer_id", p, "err", err)
    69  		return false
    70  	}
    71  	if g.clock.Now().Before(expiry) {
    72  		return false
    73  	}
    74  	g.log.Info("peer-ban expired, unbanning peer", "peer_id", p, "expiry", expiry)
    75  	if err := g.store.SetPeerBanExpiration(p, time.Time{}); err != nil {
    76  		g.log.Warn("failed to unban peer", "method", "peerBanExpiryCheck", "peer_id", p, "err", err)
    77  		return false // if we ignored the error, then the inner connection-gater would drop them
    78  	}
    79  	g.m.RecordPeerUnban()
    80  	return true
    81  }
    82  
    83  func (g *ExpiryConnectionGater) addrBanExpiryCheck(ma multiaddr.Multiaddr) (allow bool) {
    84  	ip, err := manet.ToIP(ma)
    85  	if err != nil {
    86  		g.log.Error("tried to check multi-addr with bad IP", "method", "addrBanExpiryCheck", "addr", ma)
    87  		return false
    88  	}
    89  	// if just the IP is blocked, check if it's time to unblock
    90  	expiry, err := g.store.GetIPBanExpiration(ip)
    91  	if errors.Is(err, store.UnknownBanErr) {
    92  		return true // IP is allowed if it has not been banned
    93  	}
    94  	if err != nil {
    95  		g.log.Warn("failed to load IP-ban expiry time", "method", "addrBanExpiryCheck", "ip", ip, "err", err)
    96  		return false
    97  	}
    98  	if g.clock.Now().Before(expiry) {
    99  		return false
   100  	}
   101  	g.log.Info("IP-ban expired, unbanning IP", "method", "addrBanExpiryCheck", "ip", ip, "expiry", expiry)
   102  	if err := g.store.SetIPBanExpiration(ip, time.Time{}); err != nil {
   103  		g.log.Warn("failed to unban IP", "method", "addrBanExpiryCheck", "ip", ip, "err", err)
   104  		return false // if we ignored the error, then the inner connection-gater would drop them
   105  	}
   106  	g.m.RecordIPUnban()
   107  	return true
   108  }
   109  
   110  func (g *ExpiryConnectionGater) InterceptPeerDial(p peer.ID) (allow bool) {
   111  	if !g.BlockingConnectionGater.InterceptPeerDial(p) {
   112  		return false
   113  	}
   114  	peerBan := g.peerBanExpiryCheck(p)
   115  	if !peerBan {
   116  		log.Warn("peer is temporarily banned", "method", "InterceptPeerDial", "peer_id", p)
   117  	}
   118  	return peerBan
   119  }
   120  
   121  func (g *ExpiryConnectionGater) InterceptAddrDial(id peer.ID, ma multiaddr.Multiaddr) (allow bool) {
   122  	if !g.BlockingConnectionGater.InterceptAddrDial(id, ma) {
   123  		return false
   124  	}
   125  	peerBan := g.peerBanExpiryCheck(id)
   126  	if !peerBan {
   127  		log.Warn("peer id is temporarily banned", "method", "InterceptAddrDial", "peer_id", id, "multi_addr", ma)
   128  		return false
   129  	}
   130  	addrBan := g.addrBanExpiryCheck(ma)
   131  	if !addrBan {
   132  		log.Warn("peer address is temporarily banned", "method", "InterceptAddrDial", "peer_id", id, "multi_addr", ma)
   133  		return false
   134  	}
   135  	return true
   136  }
   137  
   138  func (g *ExpiryConnectionGater) InterceptAccept(mas network.ConnMultiaddrs) (allow bool) {
   139  	if !g.BlockingConnectionGater.InterceptAccept(mas) {
   140  		return false
   141  	}
   142  	addrBan := g.addrBanExpiryCheck(mas.RemoteMultiaddr())
   143  	if !addrBan {
   144  		log.Warn("peer address is temporarily banned", "method", "InterceptAccept", "multi_addr", mas.RemoteMultiaddr())
   145  	}
   146  	return addrBan
   147  }
   148  
   149  func (g *ExpiryConnectionGater) InterceptSecured(direction network.Direction, id peer.ID, mas network.ConnMultiaddrs) (allow bool) {
   150  	// Outbound dials are always accepted: the dial intercepts handle it before the connection is made.
   151  	if direction == network.DirOutbound {
   152  		return true
   153  	}
   154  	if !g.BlockingConnectionGater.InterceptSecured(direction, id, mas) {
   155  		return false
   156  	}
   157  	// InterceptSecured is called after InterceptAccept, we already checked the addrs.
   158  	// This leaves just the peer-ID expiry to check on inbound connections.
   159  	peerBan := g.peerBanExpiryCheck(id)
   160  	if !peerBan {
   161  		log.Warn("peer id is temporarily banned", "method", "InterceptSecured", "peer_id", id, "multi_addr", mas.RemoteMultiaddr())
   162  	}
   163  	return peerBan
   164  }