package rcmgr
import (
"math"
"net/netip"
"slices"
"sync"
"time"
"github.com/libp2p/go-libp2p/x/rate"
)
type ConnLimitPerSubnet struct {
PrefixLength int
ConnCount int
}
type NetworkPrefixLimit struct {
Network netip .Prefix
ConnCount int
}
var defaultMaxConcurrentConns = 8
var defaultIP4Limit = ConnLimitPerSubnet {
ConnCount : defaultMaxConcurrentConns ,
PrefixLength : 32 ,
}
var defaultIP6Limits = []ConnLimitPerSubnet {
{
ConnCount : defaultMaxConcurrentConns ,
PrefixLength : 56 ,
},
{
ConnCount : 8 * defaultMaxConcurrentConns ,
PrefixLength : 48 ,
},
}
var DefaultNetworkPrefixLimitV4 = sortNetworkPrefixes ([]NetworkPrefixLimit {
{
Network : netip .MustParsePrefix ("127.0.0.0/8" ),
ConnCount : math .MaxInt ,
},
})
var DefaultNetworkPrefixLimitV6 = sortNetworkPrefixes ([]NetworkPrefixLimit {
{
Network : netip .MustParsePrefix ("::1/128" ),
ConnCount : math .MaxInt ,
},
})
func sortNetworkPrefixes(limits []NetworkPrefixLimit ) []NetworkPrefixLimit {
slices .SortStableFunc (limits , func (a , b NetworkPrefixLimit ) int {
return b .Network .Bits () - a .Network .Bits ()
})
return limits
}
func WithNetworkPrefixLimit (ipv4 []NetworkPrefixLimit , ipv6 []NetworkPrefixLimit ) Option {
return func (rm *resourceManager ) error {
if ipv4 != nil {
rm .connLimiter .networkPrefixLimitV4 = sortNetworkPrefixes (ipv4 )
}
if ipv6 != nil {
rm .connLimiter .networkPrefixLimitV6 = sortNetworkPrefixes (ipv6 )
}
return nil
}
}
func WithLimitPerSubnet (ipv4 []ConnLimitPerSubnet , ipv6 []ConnLimitPerSubnet ) Option {
return func (rm *resourceManager ) error {
if ipv4 != nil {
rm .connLimiter .connLimitPerSubnetV4 = ipv4
}
if ipv6 != nil {
rm .connLimiter .connLimitPerSubnetV6 = ipv6
}
return nil
}
}
type connLimiter struct {
mu sync .Mutex
networkPrefixLimitV4 []NetworkPrefixLimit
networkPrefixLimitV6 []NetworkPrefixLimit
connsPerNetworkPrefixV4 []int
connsPerNetworkPrefixV6 []int
connLimitPerSubnetV4 []ConnLimitPerSubnet
connLimitPerSubnetV6 []ConnLimitPerSubnet
ip4connsPerLimit []map [netip .Prefix ]int
ip6connsPerLimit []map [netip .Prefix ]int
}
func newConnLimiter() *connLimiter {
return &connLimiter {
networkPrefixLimitV4 : DefaultNetworkPrefixLimitV4 ,
networkPrefixLimitV6 : DefaultNetworkPrefixLimitV6 ,
connLimitPerSubnetV4 : []ConnLimitPerSubnet {defaultIP4Limit },
connLimitPerSubnetV6 : defaultIP6Limits ,
}
}
func (cl *connLimiter ) addNetworkPrefixLimit (isIP6 bool , npLimit NetworkPrefixLimit ) {
cl .mu .Lock ()
defer cl .mu .Unlock ()
if isIP6 {
cl .networkPrefixLimitV6 = append (cl .networkPrefixLimitV6 , npLimit )
cl .networkPrefixLimitV6 = sortNetworkPrefixes (cl .networkPrefixLimitV6 )
} else {
cl .networkPrefixLimitV4 = append (cl .networkPrefixLimitV4 , npLimit )
cl .networkPrefixLimitV4 = sortNetworkPrefixes (cl .networkPrefixLimitV4 )
}
}
func (cl *connLimiter ) addConn (ip netip .Addr ) bool {
cl .mu .Lock ()
defer cl .mu .Unlock ()
networkPrefixLimits := cl .networkPrefixLimitV4
connsPerNetworkPrefix := cl .connsPerNetworkPrefixV4
limits := cl .connLimitPerSubnetV4
connsPerLimit := cl .ip4connsPerLimit
isIP6 := ip .Is6 ()
if isIP6 {
networkPrefixLimits = cl .networkPrefixLimitV6
connsPerNetworkPrefix = cl .connsPerNetworkPrefixV6
limits = cl .connLimitPerSubnetV6
connsPerLimit = cl .ip6connsPerLimit
}
if len (connsPerNetworkPrefix ) == 0 && len (networkPrefixLimits ) > 0 {
connsPerNetworkPrefix = make ([]int , len (networkPrefixLimits ))
if isIP6 {
cl .connsPerNetworkPrefixV6 = connsPerNetworkPrefix
} else {
cl .connsPerNetworkPrefixV4 = connsPerNetworkPrefix
}
}
for i , limit := range networkPrefixLimits {
if limit .Network .Contains (ip ) {
if connsPerNetworkPrefix [i ]+1 > limit .ConnCount {
return false
}
connsPerNetworkPrefix [i ]++
return true
}
}
if len (connsPerLimit ) == 0 && len (limits ) > 0 {
connsPerLimit = make ([]map [netip .Prefix ]int , len (limits ))
if isIP6 {
cl .ip6connsPerLimit = connsPerLimit
} else {
cl .ip4connsPerLimit = connsPerLimit
}
}
for i , limit := range limits {
prefix , err := ip .Prefix (limit .PrefixLength )
if err != nil {
return false
}
counts , ok := connsPerLimit [i ][prefix ]
if !ok {
if connsPerLimit [i ] == nil {
connsPerLimit [i ] = make (map [netip .Prefix ]int )
}
connsPerLimit [i ][prefix ] = 0
}
if counts +1 > limit .ConnCount {
return false
}
}
for i , limit := range limits {
prefix , _ := ip .Prefix (limit .PrefixLength )
connsPerLimit [i ][prefix ]++
}
return true
}
func (cl *connLimiter ) rmConn (ip netip .Addr ) {
cl .mu .Lock ()
defer cl .mu .Unlock ()
networkPrefixLimits := cl .networkPrefixLimitV4
connsPerNetworkPrefix := cl .connsPerNetworkPrefixV4
limits := cl .connLimitPerSubnetV4
connsPerLimit := cl .ip4connsPerLimit
isIP6 := ip .Is6 ()
if isIP6 {
networkPrefixLimits = cl .networkPrefixLimitV6
connsPerNetworkPrefix = cl .connsPerNetworkPrefixV6
limits = cl .connLimitPerSubnetV6
connsPerLimit = cl .ip6connsPerLimit
}
if len (connsPerNetworkPrefix ) == 0 && len (networkPrefixLimits ) > 0 {
connsPerNetworkPrefix = make ([]int , len (networkPrefixLimits ))
if isIP6 {
cl .connsPerNetworkPrefixV6 = connsPerNetworkPrefix
} else {
cl .connsPerNetworkPrefixV4 = connsPerNetworkPrefix
}
}
for i , limit := range networkPrefixLimits {
if limit .Network .Contains (ip ) {
count := connsPerNetworkPrefix [i ]
if count <= 0 {
log .Errorf ("unexpected conn count for ip %s. Was this not added with addConn first?" , ip )
return
}
connsPerNetworkPrefix [i ]--
return
}
}
if len (connsPerLimit ) == 0 && len (limits ) > 0 {
connsPerLimit = make ([]map [netip .Prefix ]int , len (limits ))
if isIP6 {
cl .ip6connsPerLimit = connsPerLimit
} else {
cl .ip4connsPerLimit = connsPerLimit
}
}
for i , limit := range limits {
prefix , err := ip .Prefix (limit .PrefixLength )
if err != nil {
log .Errorf ("unexpected error getting prefix: %v" , err )
continue
}
counts , ok := connsPerLimit [i ][prefix ]
if !ok || counts == 0 {
log .Errorf ("unexpected conn count for %s ok=%v count=%v" , prefix , ok , counts )
continue
}
connsPerLimit [i ][prefix ]--
if connsPerLimit [i ][prefix ] <= 0 {
delete (connsPerLimit [i ], prefix )
}
}
}
const handshakeDuration = 5 * time .Second
const sourceAddressRPS = float64 (1.0 *time .Second ) / (2 * float64 (handshakeDuration ))
func newVerifySourceAddressRateLimiter(cl *connLimiter ) *rate .Limiter {
networkPrefixLimits := make ([]rate .PrefixLimit , 0 , len (cl .networkPrefixLimitV4 )+len (cl .networkPrefixLimitV6 ))
for _ , l := range cl .networkPrefixLimitV4 {
networkPrefixLimits = append (networkPrefixLimits , rate .PrefixLimit {
Prefix : l .Network ,
Limit : rate .Limit {RPS : sourceAddressRPS , Burst : l .ConnCount / 2 },
})
}
for _ , l := range cl .networkPrefixLimitV6 {
networkPrefixLimits = append (networkPrefixLimits , rate .PrefixLimit {
Prefix : l .Network ,
Limit : rate .Limit {RPS : sourceAddressRPS , Burst : l .ConnCount / 2 },
})
}
ipv4SubnetLimits := make ([]rate .SubnetLimit , 0 , len (cl .connLimitPerSubnetV4 ))
for _ , l := range cl .connLimitPerSubnetV4 {
ipv4SubnetLimits = append (ipv4SubnetLimits , rate .SubnetLimit {
PrefixLength : l .PrefixLength ,
Limit : rate .Limit {RPS : sourceAddressRPS , Burst : l .ConnCount / 2 },
})
}
ipv6SubnetLimits := make ([]rate .SubnetLimit , 0 , len (cl .connLimitPerSubnetV6 ))
for _ , l := range cl .connLimitPerSubnetV6 {
ipv6SubnetLimits = append (ipv6SubnetLimits , rate .SubnetLimit {
PrefixLength : l .PrefixLength ,
Limit : rate .Limit {RPS : sourceAddressRPS , Burst : l .ConnCount / 2 },
})
}
return &rate .Limiter {
NetworkPrefixLimits : networkPrefixLimits ,
SubnetRateLimiter : rate .SubnetLimiter {
IPv4SubnetLimits : ipv4SubnetLimits ,
IPv6SubnetLimits : ipv6SubnetLimits ,
GracePeriod : 1 * time .Minute ,
},
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .