|
@@ -35,6 +35,7 @@ import (
|
|
|
"github.com/cespare/xxhash"
|
|
"github.com/cespare/xxhash"
|
|
|
lrucache "github.com/cognusion/go-cache-lru"
|
|
lrucache "github.com/cognusion/go-cache-lru"
|
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/fxamacker/cbor/v2"
|
|
|
|
|
+ "golang.org/x/time/rate"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
const (
|
|
@@ -52,6 +53,9 @@ const (
|
|
|
brokerPendingServerReportsTTL = 60 * time.Second
|
|
brokerPendingServerReportsTTL = 60 * time.Second
|
|
|
brokerPendingServerReportsMaxSize = 100000
|
|
brokerPendingServerReportsMaxSize = 100000
|
|
|
brokerMetricName = "inproxy_broker"
|
|
brokerMetricName = "inproxy_broker"
|
|
|
|
|
+
|
|
|
|
|
+ brokerRateLimiterReapHistoryFrequencySeconds = 300
|
|
|
|
|
+ brokerRateLimiterMaxCacheEntries = 1000000
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// LookupGeoIP is a callback for providing GeoIP lookup service.
|
|
// LookupGeoIP is a callback for providing GeoIP lookup service.
|
|
@@ -100,15 +104,18 @@ type Broker struct {
|
|
|
commonCompartmentsMutex sync.Mutex
|
|
commonCompartmentsMutex sync.Mutex
|
|
|
commonCompartments *consistent.Consistent
|
|
commonCompartments *consistent.Consistent
|
|
|
|
|
|
|
|
- proxyAnnounceTimeout int64
|
|
|
|
|
- clientOfferTimeout int64
|
|
|
|
|
- clientOfferPersonalTimeout int64
|
|
|
|
|
- pendingServerReportsTTL int64
|
|
|
|
|
|
|
+ proxyAnnounceTimeout atomic.Int64
|
|
|
|
|
+ clientOfferTimeout atomic.Int64
|
|
|
|
|
+ clientOfferPersonalTimeout atomic.Int64
|
|
|
|
|
+ pendingServerReportsTTL atomic.Int64
|
|
|
maxRequestTimeouts atomic.Value
|
|
maxRequestTimeouts atomic.Value
|
|
|
- maxCompartmentIDs int64
|
|
|
|
|
|
|
+ maxCompartmentIDs atomic.Int64
|
|
|
|
|
|
|
|
enableProxyQualityMutex sync.Mutex
|
|
enableProxyQualityMutex sync.Mutex
|
|
|
enableProxyQuality atomic.Bool
|
|
enableProxyQuality atomic.Bool
|
|
|
|
|
+
|
|
|
|
|
+ dslRequestRateLimiters *lrucache.Cache
|
|
|
|
|
+ dslRequestRateLimitParams atomic.Value
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// BrokerConfig specifies the configuration for a Broker.
|
|
// BrokerConfig specifies the configuration for a Broker.
|
|
@@ -226,6 +233,10 @@ type BrokerConfig struct {
|
|
|
MatcherOfferRateLimitQuantity int
|
|
MatcherOfferRateLimitQuantity int
|
|
|
MatcherOfferRateLimitInterval time.Duration
|
|
MatcherOfferRateLimitInterval time.Duration
|
|
|
|
|
|
|
|
|
|
+ // DSL request relay rate limit configuration.
|
|
|
|
|
+ DSLRequestRateLimitQuantity int
|
|
|
|
|
+ DSLRequestRateLimitInterval time.Duration
|
|
|
|
|
+
|
|
|
// MaxCompartmentIDs specifies the maximum number of compartment IDs that
|
|
// MaxCompartmentIDs specifies the maximum number of compartment IDs that
|
|
|
// can be included, per list, in one request. If 0, the value
|
|
// can be included, per list, in one request. If 0, the value
|
|
|
// MaxCompartmentIDs is used.
|
|
// MaxCompartmentIDs is used.
|
|
@@ -306,12 +317,10 @@ func NewBroker(config *BrokerConfig) (*Broker, error) {
|
|
|
|
|
|
|
|
proxyQualityState: proxyQuality,
|
|
proxyQualityState: proxyQuality,
|
|
|
|
|
|
|
|
- proxyAnnounceTimeout: int64(config.ProxyAnnounceTimeout),
|
|
|
|
|
- clientOfferTimeout: int64(config.ClientOfferTimeout),
|
|
|
|
|
- clientOfferPersonalTimeout: int64(config.ClientOfferPersonalTimeout),
|
|
|
|
|
- pendingServerReportsTTL: int64(config.PendingServerReportsTTL),
|
|
|
|
|
-
|
|
|
|
|
- maxCompartmentIDs: int64(common.ValueOrDefault(config.MaxCompartmentIDs, MaxCompartmentIDs)),
|
|
|
|
|
|
|
+ dslRequestRateLimiters: lrucache.NewWithLRU(
|
|
|
|
|
+ 0,
|
|
|
|
|
+ time.Duration(brokerRateLimiterReapHistoryFrequencySeconds)*time.Second,
|
|
|
|
|
+ brokerRateLimiterMaxCacheEntries),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
b.pendingServerReports = lrucache.NewWithLRU(
|
|
b.pendingServerReports = lrucache.NewWithLRU(
|
|
@@ -319,6 +328,20 @@ func NewBroker(config *BrokerConfig) (*Broker, error) {
|
|
|
1*time.Minute,
|
|
1*time.Minute,
|
|
|
brokerPendingServerReportsMaxSize)
|
|
brokerPendingServerReportsMaxSize)
|
|
|
|
|
|
|
|
|
|
+ b.proxyAnnounceTimeout.Store(int64(config.ProxyAnnounceTimeout))
|
|
|
|
|
+ b.clientOfferTimeout.Store(int64(config.ClientOfferTimeout))
|
|
|
|
|
+ b.clientOfferPersonalTimeout.Store(int64(config.ClientOfferPersonalTimeout))
|
|
|
|
|
+ b.pendingServerReportsTTL.Store(int64(config.PendingServerReportsTTL))
|
|
|
|
|
+
|
|
|
|
|
+ b.maxCompartmentIDs.Store(
|
|
|
|
|
+ int64(common.ValueOrDefault(config.MaxCompartmentIDs, MaxCompartmentIDs)))
|
|
|
|
|
+
|
|
|
|
|
+ b.dslRequestRateLimitParams.Store(
|
|
|
|
|
+ &brokerRateLimitParams{
|
|
|
|
|
+ quantity: config.DSLRequestRateLimitQuantity,
|
|
|
|
|
+ interval: config.DSLRequestRateLimitInterval,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
if len(config.CommonCompartmentIDs) > 0 {
|
|
if len(config.CommonCompartmentIDs) > 0 {
|
|
|
err = b.initializeCommonCompartmentIDHashing(config.CommonCompartmentIDs)
|
|
err = b.initializeCommonCompartmentIDHashing(config.CommonCompartmentIDs)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -365,10 +388,10 @@ func (b *Broker) SetTimeouts(
|
|
|
pendingServerReportsTTL time.Duration,
|
|
pendingServerReportsTTL time.Duration,
|
|
|
maxRequestTimeouts map[string]time.Duration) {
|
|
maxRequestTimeouts map[string]time.Duration) {
|
|
|
|
|
|
|
|
- atomic.StoreInt64(&b.proxyAnnounceTimeout, int64(proxyAnnounceTimeout))
|
|
|
|
|
- atomic.StoreInt64(&b.clientOfferTimeout, int64(clientOfferTimeout))
|
|
|
|
|
- atomic.StoreInt64(&b.clientOfferPersonalTimeout, int64(clientOfferPersonalTimeout))
|
|
|
|
|
- atomic.StoreInt64(&b.pendingServerReportsTTL, int64(pendingServerReportsTTL))
|
|
|
|
|
|
|
+ b.proxyAnnounceTimeout.Store(int64(proxyAnnounceTimeout))
|
|
|
|
|
+ b.clientOfferTimeout.Store(int64(clientOfferTimeout))
|
|
|
|
|
+ b.clientOfferPersonalTimeout.Store(int64(clientOfferPersonalTimeout))
|
|
|
|
|
+ b.pendingServerReportsTTL.Store(int64(pendingServerReportsTTL))
|
|
|
b.maxRequestTimeouts.Store(maxRequestTimeouts)
|
|
b.maxRequestTimeouts.Store(maxRequestTimeouts)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -383,7 +406,9 @@ func (b *Broker) SetLimits(
|
|
|
matcherOfferLimitEntryCount int,
|
|
matcherOfferLimitEntryCount int,
|
|
|
matcherOfferRateLimitQuantity int,
|
|
matcherOfferRateLimitQuantity int,
|
|
|
matcherOfferRateLimitInterval time.Duration,
|
|
matcherOfferRateLimitInterval time.Duration,
|
|
|
- maxCompartmentIDs int) {
|
|
|
|
|
|
|
+ maxCompartmentIDs int,
|
|
|
|
|
+ dslRequestRateLimitQuantity int,
|
|
|
|
|
+ dslRequestRateLimitInterval time.Duration) {
|
|
|
|
|
|
|
|
b.matcher.SetLimits(
|
|
b.matcher.SetLimits(
|
|
|
matcherAnnouncementLimitEntryCount,
|
|
matcherAnnouncementLimitEntryCount,
|
|
@@ -394,9 +419,14 @@ func (b *Broker) SetLimits(
|
|
|
matcherOfferRateLimitQuantity,
|
|
matcherOfferRateLimitQuantity,
|
|
|
matcherOfferRateLimitInterval)
|
|
matcherOfferRateLimitInterval)
|
|
|
|
|
|
|
|
- atomic.StoreInt64(
|
|
|
|
|
- &b.maxCompartmentIDs,
|
|
|
|
|
|
|
+ b.maxCompartmentIDs.Store(
|
|
|
int64(common.ValueOrDefault(maxCompartmentIDs, MaxCompartmentIDs)))
|
|
int64(common.ValueOrDefault(maxCompartmentIDs, MaxCompartmentIDs)))
|
|
|
|
|
+
|
|
|
|
|
+ b.dslRequestRateLimitParams.Store(
|
|
|
|
|
+ &brokerRateLimitParams{
|
|
|
|
|
+ quantity: dslRequestRateLimitQuantity,
|
|
|
|
|
+ interval: dslRequestRateLimitInterval,
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func (b *Broker) SetProxyQualityParameters(
|
|
func (b *Broker) SetProxyQualityParameters(
|
|
@@ -666,7 +696,7 @@ func (b *Broker) handleProxyAnnounce(
|
|
|
|
|
|
|
|
var apiParams common.APIParameters
|
|
var apiParams common.APIParameters
|
|
|
apiParams, logFields, err = announceRequest.ValidateAndGetParametersAndLogFields(
|
|
apiParams, logFields, err = announceRequest.ValidateAndGetParametersAndLogFields(
|
|
|
- int(atomic.LoadInt64(&b.maxCompartmentIDs)),
|
|
|
|
|
|
|
+ int(b.maxCompartmentIDs.Load()),
|
|
|
b.config.APIParameterValidator,
|
|
b.config.APIParameterValidator,
|
|
|
b.config.APIParameterLogFieldFormatter,
|
|
b.config.APIParameterLogFieldFormatter,
|
|
|
geoIPData)
|
|
geoIPData)
|
|
@@ -807,7 +837,7 @@ func (b *Broker) handleProxyAnnounce(
|
|
|
// Await client offer.
|
|
// Await client offer.
|
|
|
|
|
|
|
|
timeout := common.ValueOrDefault(
|
|
timeout := common.ValueOrDefault(
|
|
|
- time.Duration(atomic.LoadInt64(&b.proxyAnnounceTimeout)),
|
|
|
|
|
|
|
+ time.Duration(b.proxyAnnounceTimeout.Load()),
|
|
|
brokerProxyAnnounceTimeout)
|
|
brokerProxyAnnounceTimeout)
|
|
|
|
|
|
|
|
// Adjust the timeout to respect any shorter maximum request timeouts for
|
|
// Adjust the timeout to respect any shorter maximum request timeouts for
|
|
@@ -1038,7 +1068,7 @@ func (b *Broker) handleClientOffer(
|
|
|
|
|
|
|
|
var filteredSDP []byte
|
|
var filteredSDP []byte
|
|
|
filteredSDP, logFields, err = offerRequest.ValidateAndGetLogFields(
|
|
filteredSDP, logFields, err = offerRequest.ValidateAndGetLogFields(
|
|
|
- int(atomic.LoadInt64(&b.maxCompartmentIDs)),
|
|
|
|
|
|
|
+ int(b.maxCompartmentIDs.Load()),
|
|
|
b.config.LookupGeoIP,
|
|
b.config.LookupGeoIP,
|
|
|
b.config.APIParameterValidator,
|
|
b.config.APIParameterValidator,
|
|
|
b.config.APIParameterLogFieldFormatter,
|
|
b.config.APIParameterLogFieldFormatter,
|
|
@@ -1111,9 +1141,9 @@ func (b *Broker) handleClientOffer(
|
|
|
// resulting broker rotation.
|
|
// resulting broker rotation.
|
|
|
var timeout time.Duration
|
|
var timeout time.Duration
|
|
|
if hasPersonalCompartmentIDs {
|
|
if hasPersonalCompartmentIDs {
|
|
|
- timeout = time.Duration(atomic.LoadInt64(&b.clientOfferPersonalTimeout))
|
|
|
|
|
|
|
+ timeout = time.Duration(b.clientOfferPersonalTimeout.Load())
|
|
|
} else {
|
|
} else {
|
|
|
- timeout = time.Duration(atomic.LoadInt64(&b.clientOfferTimeout))
|
|
|
|
|
|
|
+ timeout = time.Duration(b.clientOfferTimeout.Load())
|
|
|
}
|
|
}
|
|
|
timeout = common.ValueOrDefault(timeout, brokerClientOfferTimeout)
|
|
timeout = common.ValueOrDefault(timeout, brokerClientOfferTimeout)
|
|
|
|
|
|
|
@@ -1680,6 +1710,26 @@ func (b *Broker) handleClientDSL(
|
|
|
}
|
|
}
|
|
|
}()
|
|
}()
|
|
|
|
|
|
|
|
|
|
+ // Rate limit the number of relayed DSL requests. The DSL backend has its
|
|
|
|
|
+ // own rate limit enforcement, but avoiding excess requests here saves on
|
|
|
|
|
+ // resources consumed between the relay and backend.
|
|
|
|
|
+ //
|
|
|
|
|
+ // Unlike the announce/offer rate limit cases, there's no "limited" error
|
|
|
|
|
+ // flag returned to the client in this case, since this rate limiter is
|
|
|
|
|
+ // purely for abuse prevention and is expected to be configured with
|
|
|
|
|
+ // limits that won't be exceeded by legitimate clients.
|
|
|
|
|
+
|
|
|
|
|
+ rateLimitParams := b.dslRequestRateLimitParams.Load().(*brokerRateLimitParams)
|
|
|
|
|
+ err := brokerRateLimit(
|
|
|
|
|
+ b.dslRequestRateLimiters,
|
|
|
|
|
+ clientIP,
|
|
|
|
|
+ rateLimitParams.quantity,
|
|
|
|
|
+ rateLimitParams.interval)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, errors.Trace(err)
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
dslRequest, err := UnmarshalClientDSLRequest(requestPayload)
|
|
dslRequest, err := UnmarshalClientDSLRequest(requestPayload)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, errors.Trace(err)
|
|
return nil, errors.Trace(err)
|
|
@@ -1790,7 +1840,7 @@ func (b *Broker) initiateRelayedServerReport(
|
|
|
serverReport: serverReport,
|
|
serverReport: serverReport,
|
|
|
roundTrip: roundTrip,
|
|
roundTrip: roundTrip,
|
|
|
},
|
|
},
|
|
|
- time.Duration(atomic.LoadInt64(&b.pendingServerReportsTTL)))
|
|
|
|
|
|
|
+ time.Duration(b.pendingServerReportsTTL.Load()))
|
|
|
|
|
|
|
|
return relayPacket, nil
|
|
return relayPacket, nil
|
|
|
}
|
|
}
|
|
@@ -2038,3 +2088,37 @@ func (b *Broker) selectCommonCompartmentID(proxyID ID) (ID, error) {
|
|
|
|
|
|
|
|
return compartmentID, nil
|
|
return compartmentID, nil
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+type brokerRateLimitParams struct {
|
|
|
|
|
+ quantity int
|
|
|
|
|
+ interval time.Duration
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func brokerRateLimit(
|
|
|
|
|
+ rateLimiters *lrucache.Cache,
|
|
|
|
|
+ limitIP string,
|
|
|
|
|
+ quantity int,
|
|
|
|
|
+ interval time.Duration) error {
|
|
|
|
|
+
|
|
|
|
|
+ if quantity <= 0 || interval <= 0 {
|
|
|
|
|
+ return nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var rateLimiter *rate.Limiter
|
|
|
|
|
+
|
|
|
|
|
+ entry, ok := rateLimiters.Get(limitIP)
|
|
|
|
|
+ if ok {
|
|
|
|
|
+ rateLimiter = entry.(*rate.Limiter)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ limit := float64(quantity) / interval.Seconds()
|
|
|
|
|
+ rateLimiter = rate.NewLimiter(rate.Limit(limit), quantity)
|
|
|
|
|
+ rateLimiters.Set(
|
|
|
|
|
+ limitIP, rateLimiter, interval)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if !rateLimiter.Allow() {
|
|
|
|
|
+ return errors.TraceNew("rate exceeded for IP")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|