Check 5 min intervals

This commit is contained in:
Zoltán Papp 2025-06-23 19:51:41 +02:00
parent 9307e7e0ea
commit 546538f570
2 changed files with 130 additions and 30 deletions

View File

@ -30,9 +30,10 @@ const (
handshakeMaxInterval = 3 * time.Minute handshakeMaxInterval = 3 * time.Minute
checkInterval = 1 * time.Minute checkInterval = 1 * time.Minute
historySize = 5 * time.Minute
DefaultInactivityThreshold = 5 * time.Minute DefaultInactivityThreshold = 15 * time.Minute
MinimumInactivityThreshold = 3 * time.Minute MinimumInactivityThreshold = 5 * time.Minute
recorderEnv = "NB_LAZYCONN_RECORDER_ENABLED" recorderEnv = "NB_LAZYCONN_RECORDER_ENABLED"
) )
@ -42,11 +43,12 @@ type WgInterface interface {
} }
type peerHistory struct { type peerHistory struct {
lastRxBytes int64 // last received bytes lastRxBytes int64 // last received bytes
bytesHistory *list.List // linked list of int64 bytesHistory *list.List // linked list of int64
historySize int historySize int
summarizedBytes int64 summarizedBytes int64
log *log.Entry inactivityCounter int // counter to track inactivity
log *log.Entry
} }
func newPeerHistory(log *log.Entry, historySize int) *peerHistory { func newPeerHistory(log *log.Entry, historySize int) *peerHistory {
@ -92,6 +94,8 @@ type Manager struct {
maxBytesPerPeriod int64 maxBytesPerPeriod int64
historySize int // Size of the history buffer for each peer, used to track received bytes over time historySize int // Size of the history buffer for each peer, used to track received bytes over time
recorder *Recorder recorder *Recorder
thresholdOfInactivity int // Number of consecutive checks with low activity to consider a peer inactive
} }
func NewManager(iface WgInterface, configuredThreshold *time.Duration) *Manager { func NewManager(iface WgInterface, configuredThreshold *time.Duration) *Manager {
@ -101,14 +105,15 @@ func NewManager(iface WgInterface, configuredThreshold *time.Duration) *Manager
log.Warnf("invalid inactivity threshold configured: %v, using default: %v", err, DefaultInactivityThreshold) log.Warnf("invalid inactivity threshold configured: %v, using default: %v", err, DefaultInactivityThreshold)
} }
expectedMaxBytes := calculateExpectedMaxBytes(inactivityThreshold) expectedMaxBytes := calculateExpectedMaxBytes()
log.Infof("receive less than %d bytes per %v, will be considered inactive", expectedMaxBytes, inactivityThreshold) log.Infof("receive less than %d bytes per %v, will be considered inactive", expectedMaxBytes, inactivityThreshold)
return &Manager{ return &Manager{
InactivePeersChan: make(chan []string, 1), InactivePeersChan: make(chan []string, 1),
iface: iface, iface: iface,
interestedPeers: make(map[string]*peerHistory), interestedPeers: make(map[string]*peerHistory),
historySize: calculateHistorySize(inactivityThreshold), historySize: int(historySize.Minutes()),
maxBytesPerPeriod: expectedMaxBytes, maxBytesPerPeriod: expectedMaxBytes,
thresholdOfInactivity: int(math.Ceil(inactivityThreshold.Minutes() / checkInterval.Minutes())),
} }
} }
@ -199,16 +204,22 @@ func (m *Manager) checkStats(now time.Time) ([]string, error) {
// not enough history to determine inactivity // not enough history to determine inactivity
if history.bytesHistory.Len() < m.historySize { if history.bytesHistory.Len() < m.historySize {
history.log.Tracef("not enough history to determine inactivity, current history size: %d, required: %d", history.bytesHistory.Len(), m.historySize) history.log.Debugf("not enough history to determine inactivity, current history size: %d, required: %d", history.bytesHistory.Len(), m.historySize)
continue continue
} }
history.log.Tracef("summarized Bytes: %d", history.summarizedBytes)
if history.summarizedBytes <= m.maxBytesPerPeriod { if history.summarizedBytes <= m.maxBytesPerPeriod {
idlePeers = append(idlePeers, peer) history.inactivityCounter++
history.log.Tracef("peer is inactive, summarizedBytes: %d, maxBytesPerPeriod: %d, %v", history.summarizedBytes, m.maxBytesPerPeriod, history.historyString()) history.log.Debugf("peer is inactive, summarizedBytes: %d, maxBytesPerPeriod: %d, %v", history.summarizedBytes, m.maxBytesPerPeriod, history.historyString())
} else { } else {
history.log.Tracef("peer is active, summarizedBytes: %d, maxBytesPerPeriod: %d, %v", history.summarizedBytes, m.maxBytesPerPeriod, history.historyString()) history.inactivityCounter = 0 // reset inactivity counter if activity is detected
history.log.Debugf("peer is active, summarizedBytes: %d, maxBytesPerPeriod: %d, %v", history.summarizedBytes, m.maxBytesPerPeriod, history.historyString())
}
if history.inactivityCounter >= m.thresholdOfInactivity {
history.log.Infof("peer is inactive for %d consecutive checks, marking as idle (limit %d) ", history.inactivityCounter, m.thresholdOfInactivity)
idlePeers = append(idlePeers, peer)
history.inactivityCounter = 0 // reset inactivity counter after marking as idle
} }
} }
@ -225,23 +236,14 @@ func validateInactivityThreshold(configuredThreshold *time.Duration) (time.Durat
return *configuredThreshold, nil return *configuredThreshold, nil
} }
// calculateHistorySize calculates the number of history entries needed based on the inactivity threshold. func calculateExpectedMaxBytes() int64 {
func calculateHistorySize(inactivityThreshold time.Duration) int {
return int(math.Ceil(inactivityThreshold.Minutes() / checkInterval.Minutes()))
}
func calculateExpectedMaxBytes(duration time.Duration) int64 {
// Calculate number of keep-alive packets expected // Calculate number of keep-alive packets expected
keepAliveCount := int64(duration.Seconds() / keepAliveInterval.Seconds()) keepAliveCount := int64(historySize.Seconds() / keepAliveInterval.Seconds())
keepAliveBytes := keepAliveCount * keepAliveBytes keepAliveBytes := keepAliveCount * keepAliveBytes
// Calculate potential handshake packets (conservative estimate) // Calculate potential handshake packets (conservative estimate)
handshakeCount := int64(duration.Minutes() / handshakeMaxInterval.Minutes()) handshakeCount := int64(historySize.Minutes() / handshakeMaxInterval.Minutes())
if handshakeCount == 0 && duration >= handshakeMaxInterval {
handshakeCount = 1
}
handshakeBytes := handshakeCount * (handshakeInitBytes + keepAliveBytes) // handshake + extra bytes handshakeBytes := handshakeCount * (handshakeInitBytes + keepAliveBytes) // handshake + extra bytes
// todo: fine tune this value, add some overhead for unexpected lag
return keepAliveBytes + handshakeBytes return keepAliveBytes + handshakeBytes
} }

View File

@ -108,4 +108,102 @@ var scenarios = []scenario{
{when: 750 * time.Second, RxBytes: 32}, {when: 750 * time.Second, RxBytes: 32},
}, },
}, },
{
// Rosenpass
ExpectedInactive: true,
Data: []rxHistory{
{when: 0 * time.Second, RxBytes: 1200},
{when: 0 * time.Second, RxBytes: 1200},
{when: 0 * time.Second, RxBytes: 128},
{when: 0 * time.Second, RxBytes: 1200},
{when: 0 * time.Second, RxBytes: 128},
{when: 0 * time.Second, RxBytes: 2},
{when: 35 * time.Second, RxBytes: 32},
{when: 60 * time.Second, RxBytes: 32},
{when: 85 * time.Second, RxBytes: 32},
{when: 110 * time.Second, RxBytes: 32},
{when: 120 * time.Second, RxBytes: 1152},
{when: 120 * time.Second, RxBytes: 92},
{when: 120 * time.Second, RxBytes: 240},
{when: 130 * time.Second, RxBytes: 1200},
{when: 130 * time.Second, RxBytes: 32},
{when: 130 * time.Second, RxBytes: 1200},
{when: 130 * time.Second, RxBytes: 128},
{when: 165 * time.Second, RxBytes: 32},
{when: 190 * time.Second, RxBytes: 32},
{when: 215 * time.Second, RxBytes: 32},
{when: 240 * time.Second, RxBytes: 92},
{when: 240 * time.Second, RxBytes: 1200},
{when: 240 * time.Second, RxBytes: 128},
{when: 260 * time.Second, RxBytes: 1200},
{when: 260 * time.Second, RxBytes: 1200},
{when: 260 * time.Second, RxBytes: 128},
{when: 320 * time.Second, RxBytes: 32},
{when: 345 * time.Second, RxBytes: 32},
{when: 370 * time.Second, RxBytes: 92},
{when: 370 * time.Second, RxBytes: 1200},
{when: 370 * time.Second, RxBytes: 128},
{when: 390 * time.Second, RxBytes: 1200},
{when: 390 * time.Second, RxBytes: 128},
{when: 450 * time.Second, RxBytes: 32},
{when: 475 * time.Second, RxBytes: 32},
{when: 500 * time.Second, RxBytes: 92},
{when: 500 * time.Second, RxBytes: 1200},
{when: 500 * time.Second, RxBytes: 128},
{when: 520 * time.Second, RxBytes: 1200},
{when: 520 * time.Second, RxBytes: 128},
},
},
{
ExpectedInactive: true,
Data: []rxHistory{
{when: 0 * time.Second, RxBytes: 1152},
{when: 0 * time.Second, RxBytes: 1152},
{when: 0 * time.Second, RxBytes: 240},
{when: 0 * time.Second, RxBytes: 1152},
{when: 1 * time.Second, RxBytes: 240},
{when: 1 * time.Second, RxBytes: 2},
{when: 11 * time.Second, RxBytes: 32},
{when: 121 * time.Second, RxBytes: 1200},
{when: 121 * time.Second, RxBytes: 148},
{when: 121 * time.Second, RxBytes: 32},
{when: 121 * time.Second, RxBytes: 128},
{when: 131 * time.Second, RxBytes: 1152},
{when: 131 * time.Second, RxBytes: 1152},
{when: 131 * time.Second, RxBytes: 240},
{when: 141 * time.Second, RxBytes: 32},
{when: 191 * time.Second, RxBytes: 32},
{when: 241 * time.Second, RxBytes: 1152},
{when: 241 * time.Second, RxBytes: 148},
{when: 241 * time.Second, RxBytes: 32},
{when: 241 * time.Second, RxBytes: 240},
{when: 251 * time.Second, RxBytes: 32},
{when: 261 * time.Second, RxBytes: 1152},
{when: 261 * time.Second, RxBytes: 1152},
{when: 261 * time.Second, RxBytes: 240},
{when: 271 * time.Second, RxBytes: 32},
{when: 296 * time.Second, RxBytes: 32},
{when: 371 * time.Second, RxBytes: 1152},
{when: 371 * time.Second, RxBytes: 148},
{when: 371 * time.Second, RxBytes: 32},
{when: 371 * time.Second, RxBytes: 240},
{when: 381 * time.Second, RxBytes: 32},
{when: 391 * time.Second, RxBytes: 1152},
{when: 391 * time.Second, RxBytes: 240},
{when: 401 * time.Second, RxBytes: 32},
{when: 426 * time.Second, RxBytes: 32},
{when: 501 * time.Second, RxBytes: 1152},
{when: 501 * time.Second, RxBytes: 148},
{when: 501 * time.Second, RxBytes: 32},
{when: 501 * time.Second, RxBytes: 240},
{when: 511 * time.Second, RxBytes: 32},
{when: 521 * time.Second, RxBytes: 1152},
{when: 521 * time.Second, RxBytes: 240},
{when: 531 * time.Second, RxBytes: 32},
{when: 631 * time.Second, RxBytes: 1152},
{when: 631 * time.Second, RxBytes: 148},
{when: 631 * time.Second, RxBytes: 32},
{when: 631 * time.Second, RxBytes: 240},
},
},
} }