Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/net/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ type HealthCheckSettings struct {

// ReceiveInterval is the minimum interval between received health check packets.
// Accepts either a duration string (e.g. "300ms") or an integer interpreted as milliseconds.
// Defaults to 15s when omitted from the selected health check scope.
// +kubebuilder:validation:XIntOrString
// +optional
ReceiveInterval *intstr.IntOrString `json:"receiveInterval,omitempty"`

// TransmitInterval is the minimum interval between transmitted health check packets.
// Accepts either a duration string (e.g. "300ms") or an integer interpreted as milliseconds.
// Defaults to 15s when omitted from the selected health check scope.
// +kubebuilder:validation:XIntOrString
// +optional
TransmitInterval *intstr.IntOrString `json:"transmitInterval,omitempty"`
Expand Down
4 changes: 2 additions & 2 deletions cmd/kubectl-unbounded/app/net/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ type healthCheckFlags struct {
func (b *healthCheckFlags) addToFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&b.enabled, "health-check-enabled", false, "Enable UDP health probes over tunnels")
cmd.Flags().Int32Var(&b.detectMultiplier, "health-check-detect-multiplier", 0, "Number of missed probes before marking a peer down")
cmd.Flags().StringVar(&b.receiveInterval, "health-check-receive-interval", "", "Min interval between received probes before declaring down, e.g. 300ms")
cmd.Flags().StringVar(&b.transmitInterval, "health-check-transmit-interval", "", "Interval between transmitted health probes, e.g. 300ms")
cmd.Flags().StringVar(&b.receiveInterval, "health-check-receive-interval", "", "Min interval between received probes before declaring down, e.g. 300ms (node default: 15s)")
cmd.Flags().StringVar(&b.transmitInterval, "health-check-transmit-interval", "", "Interval between transmitted health probes, e.g. 300ms (node default: 15s)")
cmd.Flags().Int32Var(&b.tunnelMTU, "tunnel-mtu", 0, "MTU for tunnel interfaces in this scope")
cmd.Flags().StringVar(&b.tunnelProtocol, "tunnel-protocol", "", "Tunnel encapsulation protocol (WireGuard, GENEVE, or Auto)")
_ = cmd.RegisterFlagCompletionFunc("tunnel-protocol", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { //nolint:errcheck
Expand Down
31 changes: 31 additions & 0 deletions cmd/kubectl-unbounded/app/net/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,39 @@ import (
"bytes"
"strings"
"testing"

"github.com/spf13/cobra"
)

func TestHealthCheckFlagsPreserveRuntimeDefaults(t *testing.T) {
cmd := &cobra.Command{}
flags := &healthCheckFlags{}
flags.addToFlags(cmd)
flags.selectedFrom(cmd)

if flags.toObject() != nil {
t.Fatal("omitted health flags must preserve the node's runtime defaults")
}

for _, name := range []string{"health-check-transmit-interval", "health-check-receive-interval"} {
flag := cmd.Flags().Lookup(name)
if flag.DefValue != "" || !strings.Contains(flag.Usage, "15s") {
t.Fatalf("flag %s must document the inherited 15s default without serializing it", name)
}
}

if err := cmd.Flags().Set("health-check-transmit-interval", "60s"); err != nil {
t.Fatal(err)
}

flags.selectedFrom(cmd)

got := flags.toObject()
if len(got) != 1 || got["transmitInterval"] != "60s" {
t.Fatalf("explicit interval or partial settings changed: %v", got)
}
}

func TestCreateSiteUsesSharedSiteAPI(t *testing.T) {
t.Parallel()

Expand Down
28 changes: 19 additions & 9 deletions cmd/unbounded-net-controller/cluster_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,15 +1061,9 @@ func buildConnectivityMatrix(nodes []*NodeStatusResponse, gatewayPools []Gateway

siteNodes[site][name] = true

var allPeers []WireGuardPeerStatus

for _, p := range n.Peers {
if p.PeerType == "site" || p.PeerType == "gateway" {
allPeers = append(allPeers, p)
}
}

nodePeers[name] = allPeers
// Keep the immutable snapshot's slice; filter when reading rather
// than copying every peer, including for scopes above the size limit.
nodePeers[name] = n.Peers

for _, p := range n.Peers {
if p.PeerType == "gateway" && p.Name != "" && p.SiteName == site {
Expand Down Expand Up @@ -1111,6 +1105,10 @@ func buildConnectivityMatrix(nodes []*NodeStatusResponse, gatewayPools []Gateway
}

for _, peer := range nodePeers[srcNode] {
if !isConnectivityMatrixPeer(peer) {
continue
}

tgtNode := peer.Name
if tgtNode == "" || tgtNode == srcNode || !nodeSet[tgtNode] {
continue
Expand Down Expand Up @@ -1153,6 +1151,10 @@ func buildConnectivityMatrix(nodes []*NodeStatusResponse, gatewayPools []Gateway

poolNodeSet[name] = true
for _, peer := range nodePeers[name] {
if !isConnectivityMatrixPeer(peer) {
continue
}

peerName := strings.TrimSpace(peer.Name)
if peerName == "" {
continue
Expand All @@ -1165,6 +1167,10 @@ func buildConnectivityMatrix(nodes []*NodeStatusResponse, gatewayPools []Gateway

for srcNodeName, peers := range nodePeers {
for _, peer := range peers {
if !isConnectivityMatrixPeer(peer) {
continue
}

if strings.TrimSpace(peer.Name) == name {
if _, ok := nodeByName[srcNodeName]; ok {
poolNodeSet[srcNodeName] = true
Expand All @@ -1188,3 +1194,7 @@ func buildConnectivityMatrix(nodes []*NodeStatusResponse, gatewayPools []Gateway

return result
}

func isConnectivityMatrixPeer(peer WireGuardPeerStatus) bool {
return peer.PeerType == "site" || peer.PeerType == "gateway"
}
2 changes: 1 addition & 1 deletion cmd/unbounded-net-controller/cni_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func TestCNIGuardProtoStatusRecovery(t *testing.T) {
if source == "ws" || source == "apiserver-ws" {
var messageType string

messageType, ack = handleProtoWSMessage(health, data, source)
messageType, ack = handleProtoWSBytes(health, data, source)
if messageType != "node_status_ack" {
t.Fatalf("unexpected message type %q: %+v", messageType, ack)
}
Expand Down
59 changes: 59 additions & 0 deletions cmd/unbounded-net-controller/gzip_writer_pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"compress/gzip"
"io"
)

const gzipIdleWriterLimit = 4

type gzipWriterPool struct {
idle chan *gzip.Writer
}

func newGzipWriterPool() *gzipWriterPool {
return &gzipWriterPool{idle: make(chan *gzip.Writer, gzipIdleWriterLimit)}
}

func (p *gzipWriterPool) get(output io.Writer) (*gzip.Writer, error) {
var writer *gzip.Writer

select {
case writer = <-p.idle:
default:
var err error

writer, err = gzip.NewWriterLevel(io.Discard, gzip.BestSpeed)
if err != nil {
return nil, err
}
}

writer.Reset(output)

return writer, nil
}

func (p *gzipWriterPool) put(writer *gzip.Writer, completed bool) {
closed := false

defer func() {
// Detach even if Close panics, and reset before another request can
// acquire the writer. Failed or interrupted responses are not pooled.
writer.Reset(io.Discard)

if !completed || !closed {
return
}

select {
case p.idle <- writer:
default:
}
}()

closed = writer.Close() == nil
}
Loading