mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-16 03:11:12 +02:00
Fixes #4420 No functional changes, just moving packages around. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
159 lines
5.8 KiB
Go
159 lines
5.8 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package kubespan
|
|
|
|
import (
|
|
"time"
|
|
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
"inet.af/netaddr"
|
|
|
|
"github.com/talos-systems/talos/pkg/machinery/resources/kubespan"
|
|
)
|
|
|
|
// PeerStatusSpec adapter provides Wiregard integration and state management.
|
|
//
|
|
//nolint:revive,golint
|
|
func PeerStatusSpec(r *kubespan.PeerStatusSpec) peerStatus {
|
|
return peerStatus{
|
|
PeerStatusSpec: r,
|
|
}
|
|
}
|
|
|
|
type peerStatus struct {
|
|
*kubespan.PeerStatusSpec
|
|
}
|
|
|
|
// 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
|
|
|
|
// EndpointConnectionTimeout is time to wait for initial handshake when the endpoint is just set.
|
|
const EndpointConnectionTimeout = 15 * time.Second
|
|
|
|
// CalculateState updates connection state based on other fields 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 = LastEndpontChange
|
|
//
|
|
// 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 (a peerStatus) CalculateState() {
|
|
sinceLastHandshake := time.Since(a.PeerStatusSpec.LastHandshakeTime)
|
|
sinceEndpointChange := time.Since(a.PeerStatusSpec.LastEndpointChange)
|
|
|
|
a.CalculateStateWithDurations(sinceLastHandshake, sinceEndpointChange)
|
|
}
|
|
|
|
// CalculateStateWithDurations calculates the state based on the time since events.
|
|
func (a peerStatus) CalculateStateWithDurations(sinceLastHandshake, sinceEndpointChange time.Duration) {
|
|
switch {
|
|
case sinceEndpointChange > PeerDownInterval: // past T0+peerDownInterval
|
|
// if we got handshake in the last peerDownInterval, endpoint is up
|
|
if sinceLastHandshake < PeerDownInterval {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUp
|
|
} else {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateDown
|
|
}
|
|
case sinceEndpointChange < EndpointConnectionTimeout: // between (T0) and (T0+endpointConnectionTimeout)
|
|
// endpoint got recently updated, consider no handshake as 'unknown'
|
|
if a.PeerStatusSpec.LastHandshakeTime.After(a.PeerStatusSpec.LastEndpointChange) {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUp
|
|
} else {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUnknown
|
|
}
|
|
|
|
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 a.PeerStatusSpec.LastHandshakeTime.After(a.PeerStatusSpec.LastEndpointChange) {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUp
|
|
} else {
|
|
a.PeerStatusSpec.State = kubespan.PeerStateDown
|
|
}
|
|
}
|
|
|
|
if a.PeerStatusSpec.State == kubespan.PeerStateDown && a.PeerStatusSpec.LastUsedEndpoint.IsZero() {
|
|
// no endpoint, so unknown
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUnknown
|
|
}
|
|
}
|
|
|
|
// UpdateFromWireguard updates fields from wgtypes information.
|
|
func (a peerStatus) UpdateFromWireguard(peer wgtypes.Peer) {
|
|
if peer.Endpoint != nil {
|
|
a.PeerStatusSpec.Endpoint, _ = netaddr.FromStdAddr(peer.Endpoint.IP, peer.Endpoint.Port, "")
|
|
} else {
|
|
a.PeerStatusSpec.Endpoint = netaddr.IPPort{}
|
|
}
|
|
|
|
a.PeerStatusSpec.LastHandshakeTime = peer.LastHandshakeTime
|
|
a.PeerStatusSpec.TransmitBytes = peer.TransmitBytes
|
|
a.PeerStatusSpec.ReceiveBytes = peer.ReceiveBytes
|
|
}
|
|
|
|
// UpdateEndpoint updates the endpoint information and last update timestamp.
|
|
func (a peerStatus) UpdateEndpoint(endpoint netaddr.IPPort) {
|
|
a.PeerStatusSpec.Endpoint = endpoint
|
|
a.PeerStatusSpec.LastUsedEndpoint = endpoint
|
|
a.PeerStatusSpec.LastEndpointChange = time.Now()
|
|
a.PeerStatusSpec.State = kubespan.PeerStateUnknown
|
|
}
|
|
|
|
// ShouldChangeEndpoint tells whether endpoint should be updated.
|
|
func (a peerStatus) ShouldChangeEndpoint() bool {
|
|
return a.PeerStatusSpec.State == kubespan.PeerStateDown || a.PeerStatusSpec.LastUsedEndpoint.IsZero()
|
|
}
|
|
|
|
// PickNewEndpoint picks new endpoint given the state and list of available endpoints.
|
|
//
|
|
// If returned newEndpoint is zero value, no new endpoint is available.
|
|
func (a peerStatus) PickNewEndpoint(endpoints []netaddr.IPPort) (newEndpoint netaddr.IPPort) {
|
|
if len(endpoints) == 0 {
|
|
return
|
|
}
|
|
|
|
if a.PeerStatusSpec.LastUsedEndpoint.IsZero() {
|
|
// first time setting the endpoint
|
|
newEndpoint = endpoints[0]
|
|
} else {
|
|
// find the next endpoint after LastUsedEndpoint and use it
|
|
idx := -1
|
|
|
|
for i := range endpoints {
|
|
if endpoints[i] == a.PeerStatusSpec.LastUsedEndpoint {
|
|
idx = i
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// special case: if the peer has just a single endpoint, we can't rotate
|
|
if !(len(endpoints) == 1 && idx == 0 && a.PeerStatusSpec.Endpoint == a.PeerStatusSpec.LastUsedEndpoint) {
|
|
newEndpoint = endpoints[(idx+1)%len(endpoints)]
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|