2017-03-27 14:15:02 +02:00
|
|
|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
2017-01-12 08:40:01 +01:00
|
|
|
// released under the MIT license
|
|
|
|
|
2017-10-09 23:37:13 +02:00
|
|
|
package connection_limits
|
2017-01-12 08:40:01 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ThrottleDetails holds the connection-throttling details for a subnet/IP.
|
|
|
|
type ThrottleDetails struct {
|
2019-01-01 22:45:37 +01:00
|
|
|
Start time.Time
|
|
|
|
Count int
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenericThrottle allows enforcing limits of the form
|
|
|
|
// "at most X events per time window of duration Y"
|
|
|
|
type GenericThrottle struct {
|
|
|
|
ThrottleDetails // variable state: what events have been seen
|
|
|
|
// these are constant after creation:
|
|
|
|
Duration time.Duration // window length to consider
|
|
|
|
Limit int // number of events allowed per window
|
|
|
|
}
|
|
|
|
|
|
|
|
// Touch checks whether an additional event is allowed:
|
|
|
|
// it either denies it (by returning false) or allows it (by returning true)
|
|
|
|
// and records it
|
|
|
|
func (g *GenericThrottle) Touch() (throttled bool, remainingTime time.Duration) {
|
2019-05-12 09:12:50 +02:00
|
|
|
return g.touch(time.Now().UTC())
|
2019-01-01 22:45:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GenericThrottle) touch(now time.Time) (throttled bool, remainingTime time.Duration) {
|
|
|
|
if g.Limit == 0 {
|
|
|
|
return // limit of 0 disables throttling
|
|
|
|
}
|
|
|
|
|
|
|
|
elapsed := now.Sub(g.Start)
|
|
|
|
if elapsed > g.Duration {
|
|
|
|
// reset window, record the operation
|
|
|
|
g.Start = now
|
|
|
|
g.Count = 1
|
|
|
|
return false, 0
|
|
|
|
} else if g.Count >= g.Limit {
|
|
|
|
// we are throttled
|
|
|
|
return true, g.Start.Add(g.Duration).Sub(now)
|
|
|
|
} else {
|
|
|
|
// we are not throttled, record the operation
|
|
|
|
g.Count += 1
|
|
|
|
return false, 0
|
|
|
|
}
|
2017-01-12 08:40:01 +01:00
|
|
|
}
|