mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
calculate WG peer connection status
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
PeerStatusUnknown = "unknown"
|
||||
PeerStatusUp = "up"
|
||||
PeerStatusDown = "down"
|
||||
)
|
||||
|
||||
type peer struct {
|
||||
config PeerConfig
|
||||
lastEndpointChangeTime time.Time
|
||||
lastHandshakeTime time.Time
|
||||
receiveBytes int64
|
||||
transmitBytes int64
|
||||
status string
|
||||
}
|
||||
|
||||
func newPeer(config PeerConfig) peer {
|
||||
p := peer{
|
||||
config: config,
|
||||
status: PeerStatusUnknown,
|
||||
}
|
||||
if p.config.Endpoint != nil {
|
||||
p.lastEndpointChangeTime = time.Now()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *peer) updateConfig(config PeerConfig) {
|
||||
if p.config.Endpoint != config.Endpoint {
|
||||
p.lastEndpointChangeTime = time.Now()
|
||||
p.status = PeerStatusUnknown
|
||||
}
|
||||
p.config = config
|
||||
}
|
||||
|
||||
func (p *peer) updateFromWireGuard(wgPeer wgtypes.Peer) {
|
||||
p.lastHandshakeTime = wgPeer.LastHandshakeTime
|
||||
p.receiveBytes = wgPeer.ReceiveBytes
|
||||
p.transmitBytes = wgPeer.TransmitBytes
|
||||
p.calculateStatus()
|
||||
}
|
||||
|
||||
// Peer status calculation is based on Talos Kubespan implementation:
|
||||
// https://github.com/siderolabs/talos/blob/v1.8.0/internal/app/machined/pkg/adapters/kubespan/peer_status.go
|
||||
|
||||
// endpointConnectionTimeout is time to wait for initial handshake when the endpoint is just set.
|
||||
const endpointConnectionTimeout = 15 * time.Second
|
||||
|
||||
// peerDownInterval is the time since last handshake when established peer is considered to be down.
|
||||
//
|
||||
// WG whitepaper defines a downed peer as being:
|
||||
// Handshake Timeout (180s) + Rekey Timeout (5s) + Rekey Attempt Timeout (90s)
|
||||
//
|
||||
// This interval is applied when the link is already established.
|
||||
const peerDownInterval = (180 + 5 + 90) * time.Second
|
||||
|
||||
// calculateStatus updates the peer's connection status based on other field values.
|
||||
//
|
||||
// Goal: endpoint is ultimately down if we haven't seen handshake for more than peerDownInterval,
|
||||
// but as the endpoints get updated we want faster feedback, so we start checking more aggressively
|
||||
// that the handshake happened within endpointConnectionTimeout since last endpoint change.
|
||||
//
|
||||
// Timeline:
|
||||
//
|
||||
// ---------------------------------------------------------------------->
|
||||
// ^ ^ ^
|
||||
// | | |
|
||||
// T0 T0+endpointConnectionTimeout T0+peerDownInterval
|
||||
//
|
||||
// Where T0 = lastEndpointChangeTime
|
||||
//
|
||||
// The question is where is LastHandshakeTimeout vs. those points above:
|
||||
//
|
||||
// - if we're past (T0+peerDownInterval), simply check that time since last handshake < peerDownInterval
|
||||
// - if we're between (T0+endpointConnectionTimeout) and (T0+peerDownInterval), and there's no handshake
|
||||
// after the endpoint change, assume that the endpoint is down
|
||||
// - if we're between (T0) and (T0+endpointConnectionTimeout), and there's no handshake since the endpoint change,
|
||||
// consider the state to be unknown
|
||||
func (p *peer) calculateStatus() {
|
||||
sinceLastHandshake := time.Since(p.lastHandshakeTime)
|
||||
sinceEndpointChange := time.Since(p.lastEndpointChangeTime)
|
||||
|
||||
switch {
|
||||
case sinceEndpointChange > peerDownInterval: // past T0+peerDownInterval
|
||||
// If we got handshake in the last peerDownInterval, endpoint is up.
|
||||
if sinceLastHandshake < peerDownInterval {
|
||||
p.status = PeerStatusUp
|
||||
} else {
|
||||
p.status = PeerStatusDown
|
||||
}
|
||||
case sinceEndpointChange < endpointConnectionTimeout: // between (T0) and (T0+endpointConnectionTimeout)
|
||||
// Endpoint got recently updated, consider no handshake as 'unknown'.
|
||||
if p.lastHandshakeTime.After(p.lastEndpointChangeTime) {
|
||||
p.status = PeerStatusUp
|
||||
} else {
|
||||
p.status = PeerStatusUnknown
|
||||
}
|
||||
default: // otherwise, we're between (T0+endpointConnectionTimeout) and (T0+peerDownInterval)
|
||||
// If we haven't had the handshake yet, consider the endpoint to be down.
|
||||
if p.lastHandshakeTime.After(p.lastEndpointChangeTime) {
|
||||
p.status = PeerStatusUp
|
||||
} else {
|
||||
p.status = PeerStatusDown
|
||||
}
|
||||
}
|
||||
|
||||
if p.status == PeerStatusDown && p.config.Endpoint == nil {
|
||||
// No endpoint, so unknown.
|
||||
p.status = PeerStatusUnknown
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user