ceremonyclient/go-libp2p/p2p/protocol/circuitv2/relay/constraints.go
2024-10-12 11:55:17 -07:00

131 lines
3.1 KiB
Go

package relay
import (
"errors"
"sync"
"time"
asnutil "github.com/libp2p/go-libp2p-asn-util"
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
)
var validity = 30 * time.Minute
var (
errTooManyReservations = errors.New("too many reservations")
errTooManyReservationsForPeer = errors.New("too many reservations for peer")
errTooManyReservationsForIP = errors.New("too many peers for IP address")
errTooManyReservationsForASN = errors.New("too many peers for ASN")
)
// constraints implements various reservation constraints
type constraints struct {
rc *Resources
mutex sync.Mutex
total []time.Time
peers map[peer.ID][]time.Time
ips map[string][]time.Time
asns map[uint32][]time.Time
}
// newConstraints creates a new constraints object.
// The methods are *not* thread-safe; an external lock must be held if synchronization
// is required.
func newConstraints(rc *Resources) *constraints {
return &constraints{
rc: rc,
peers: make(map[peer.ID][]time.Time),
ips: make(map[string][]time.Time),
asns: make(map[uint32][]time.Time),
}
}
// AddReservation adds a reservation for a given peer with a given multiaddr.
// If adding this reservation violates IP constraints, an error is returned.
func (c *constraints) AddReservation(p peer.ID, a ma.Multiaddr) error {
c.mutex.Lock()
now := time.Now()
c.cleanup(now)
if len(c.total) >= c.rc.MaxReservations {
c.mutex.Unlock()
return errTooManyReservations
}
ip, err := manet.ToIP(a)
if err != nil {
c.mutex.Unlock()
return errors.New("no IP address associated with peer")
}
peerReservations := c.peers[p]
if len(peerReservations) >= c.rc.MaxReservationsPerPeer {
c.mutex.Unlock()
return errTooManyReservationsForPeer
}
ipReservations := c.ips[ip.String()]
if len(ipReservations) >= c.rc.MaxReservationsPerIP {
c.mutex.Unlock()
return errTooManyReservationsForIP
}
var asnReservations []time.Time
var asn uint32
if ip.To4() == nil {
asn = asnutil.AsnForIPv6(ip)
if asn != 0 {
asnReservations = c.asns[asn]
if len(asnReservations) >= c.rc.MaxReservationsPerASN {
c.mutex.Unlock()
return errTooManyReservationsForASN
}
}
}
expiry := now.Add(validity)
c.total = append(c.total, expiry)
peerReservations = append(peerReservations, expiry)
c.peers[p] = peerReservations
ipReservations = append(ipReservations, expiry)
c.ips[ip.String()] = ipReservations
if asn != 0 {
asnReservations = append(asnReservations, expiry)
c.asns[asn] = asnReservations
}
c.mutex.Unlock()
return nil
}
func (c *constraints) cleanupList(l []time.Time, now time.Time) []time.Time {
var index int
for i, t := range l {
if t.After(now) {
break
}
index = i + 1
}
return l[index:]
}
func (c *constraints) cleanup(now time.Time) {
c.total = c.cleanupList(c.total, now)
for k, peerReservations := range c.peers {
c.peers[k] = c.cleanupList(peerReservations, now)
}
for k, ipReservations := range c.ips {
c.ips[k] = c.cleanupList(ipReservations, now)
}
for k, asnReservations := range c.asns {
c.asns[k] = c.cleanupList(asnReservations, now)
}
}