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 }