chanfitness: add flap count based rate limiting
To prevent flapping peers from endlessly dos-ing us with online and offline events, we rate limit the number of events we will store per period using their flap rate to determine how often we will add their events to our in memory list of online events. Since we are tracking online events, we need to track the aggregate change over the rate limited period, otherwise we will lose track of a peer's current state. For example, if we store an online event, then do not store the subsequent offline event, we will believe that the peer is online when they actually aren't. To address this, we "stage" a single event which keeps track of all the events that occurred while we were rate limiting the peer. At the end of the rate limting period, we will store the last state for that peer, thereby ensureing that we maintain our record of their most recent state.
This commit is contained in:
4 files changed
+232
-5
No files matched your search
@@ -43,9 +43,31 @@ type peerLog struct {
|
||||
// online stores whether the peer is currently online.
|
||||
online bool
|
||||
|
||||
// onlineEvents is a log of timestamped events observed for the peer.
|
||||
// onlineEvents is a log of timestamped events observed for the peer
|
||||
// that we have committed to allocating memory to.
|
||||
onlineEvents []*event
|
||||
|
||||
// stagedEvent represents an event that is pending addition to the
|
||||
// events list. It has not yet been added because we rate limit the
|
||||
// frequency that we store events at. We need to store this value
|
||||
// in the log (rather than just ignore events) so that we can flush the
|
||||
// aggregate outcome to our event log once the rate limiting period has
|
||||
// ended.
|
||||
//
|
||||
// Take the following example:
|
||||
// - Peer online event recorded
|
||||
// - Peer offline event, not recorded due to rate limit
|
||||
// - No more events, we incorrectly believe our peer to be online
|
||||
// Instead of skipping events, we stage the most recent event during the
|
||||
// rate limited period so that we know what happened (on aggregate)
|
||||
// while we were rate limiting events.
|
||||
//
|
||||
// Note that we currently only store offline/online events so we can
|
||||
// use this field to track our online state. With the addition of other
|
||||
// event types, we need to only stage online/offline events, or split
|
||||
// them out.
|
||||
stagedEvent *event
|
||||
|
||||
// flapCount is the number of times this peer has been observed as
|
||||
// going offline.
|
||||
flapCount int
|
||||
@@ -105,7 +127,8 @@ func (p *peerLog) onlineEvent(online bool) {
|
||||
p.addEvent(online, eventTime)
|
||||
}
|
||||
|
||||
// addEvent records an online or offline event in our event log.
|
||||
// addEvent records an online or offline event in our event log. and increments
|
||||
// the peer's flap count.
|
||||
func (p *peerLog) addEvent(online bool, time time.Time) {
|
||||
eventType := peerOnlineEvent
|
||||
if !online {
|
||||
@@ -117,7 +140,26 @@ func (p *peerLog) addEvent(online bool, time time.Time) {
|
||||
eventType: eventType,
|
||||
}
|
||||
|
||||
p.onlineEvents = append(p.onlineEvents, event)
|
||||
// If we have no staged events, we can just stage this event and return.
|
||||
if p.stagedEvent == nil {
|
||||
p.stagedEvent = event
|
||||
return
|
||||
}
|
||||
|
||||
// We get the amount of time we require between events according to
|
||||
// peer flap count.
|
||||
aggregation := getRateLimit(p.flapCount)
|
||||
nextRecordTime := p.stagedEvent.timestamp.Add(aggregation)
|
||||
flushEvent := nextRecordTime.Before(event.timestamp)
|
||||
|
||||
// If enough time has passed since our last staged event, we add our
|
||||
// event to our in-memory list.
|
||||
if flushEvent {
|
||||
p.onlineEvents = append(p.onlineEvents, p.stagedEvent)
|
||||
}
|
||||
|
||||
// Finally, we replace our staged event with the new event we received.
|
||||
p.stagedEvent = event
|
||||
}
|
||||
|
||||
// addChannel adds a channel to our log. If we have not tracked any online
|
||||
@@ -160,6 +202,7 @@ func (p *peerLog) removeChannel(channelPoint wire.OutPoint) error {
|
||||
// TODO(carla): this could be done on a per channel basis.
|
||||
if p.channelCount() == 0 {
|
||||
p.onlineEvents = nil
|
||||
p.stagedEvent = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -197,6 +240,18 @@ func (p *peerLog) getFlapCount() (int, *time.Time) {
|
||||
return p.flapCount, p.lastFlap
|
||||
}
|
||||
|
||||
// listEvents returns all of the events that our event log has tracked,
|
||||
// including events that are staged for addition to our set of events but have
|
||||
// not yet been committed to (because we rate limit and store only the aggregate
|
||||
// outcome over a period).
|
||||
func (p *peerLog) listEvents() []*event {
|
||||
if p.stagedEvent == nil {
|
||||
return p.onlineEvents
|
||||
}
|
||||
|
||||
return append(p.onlineEvents, p.stagedEvent)
|
||||
}
|
||||
|
||||
// onlinePeriod represents a period of time over which a peer was online.
|
||||
type onlinePeriod struct {
|
||||
start, end time.Time
|
||||
@@ -211,8 +266,10 @@ type onlinePeriod struct {
|
||||
// to be ordered by ascending timestamp, and can tolerate multiple consecutive
|
||||
// online or offline events.
|
||||
func (p *peerLog) getOnlinePeriods() []*onlinePeriod {
|
||||
events := p.listEvents()
|
||||
|
||||
// Return early if there are no events, there are no online periods.
|
||||
if len(p.onlineEvents) == 0 {
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,7 +288,7 @@ func (p *peerLog) getOnlinePeriods() []*onlinePeriod {
|
||||
// the online event and the present is not tracked. The type of the most
|
||||
// recent event is tracked using the offline bool so that we can add a
|
||||
// final online period if necessary.
|
||||
for _, event := range p.onlineEvents {
|
||||
for _, event := range events {
|
||||
switch event.eventType {
|
||||
case peerOnlineEvent:
|
||||
// If our previous event is nil, we just set it and
|
||||
|
||||
Reference in new issue
Block a user