feature/conn25,types/appctype: serve active Conn25 state over localapi
At /v0/conn25-state. State includes whether the node is configured for Connectors 2025, as well as client-specific and connector-specific state, if the node is acting in those contexts. Client-specific state includes the reserved Magic IPs and Transit IPs on the client that have not been returned to their IP pools, and their associated apps, domains, real destination IPs, and active flow counts. We also report IP pool utilization: the number of magic and transit IPs in use versus each pool's capacity, split by IP family. Connector-specific state includes a peer list of clients that have registered Transit IPs with the connector, and the apps are real destination IPs the Transit IPs map to. Updates tailscale/corp#40125 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
This commit is contained in:
committed by
mzbenami
parent
71e5a98404
commit
b2de420e3d
@@ -5,6 +5,7 @@ package conn25
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
@@ -197,3 +198,85 @@ func TestIPPoolReconfig(t *testing.T) {
|
||||
ipp.returnAddr(netip.MustParseAddr("192.168.0.9"))
|
||||
expectAddrNext(t, ipp, "192.168.0.9")
|
||||
}
|
||||
|
||||
func TestIPPoolCapacity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prefix string
|
||||
want int64
|
||||
}{
|
||||
{"ipv4-slash-30", "100.64.0.0/30", 4},
|
||||
{"ipv4-slash-24", "100.64.0.0/24", 256},
|
||||
{"ipv4-single", "100.64.0.1/32", 1},
|
||||
{"ipv6-slash-120", "fd7a::/120", 256},
|
||||
// 2^62 is the largest power of two below math.MaxInt64; not clamped.
|
||||
{"ipv6-slash-66-not-clamped", "fd7a::/66", 1 << 62},
|
||||
// 2^63 overflows int64, so it clamps.
|
||||
{"ipv6-slash-65-clamps", "fd7a::/65", math.MaxInt64},
|
||||
{"ipv6-slash-64-clamps", "fd7a::/64", math.MaxInt64},
|
||||
{"ipv6-default-clamps", "::/0", math.MaxInt64},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ipp := newIPPool(mustIPSetFromPrefix(tt.prefix))
|
||||
if got := ipp.capacity(); got != tt.want {
|
||||
t.Errorf("capacity(%s) = %d, want %d", tt.prefix, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("multi-prefix-sum-overflows-clamps", func(t *testing.T) {
|
||||
b := &netipx.IPSetBuilder{}
|
||||
b.AddPrefix(netip.MustParsePrefix("fd7a::/66")) // 2^62 +
|
||||
b.AddPrefix(netip.MustParsePrefix("fd7a:0:0:0:8000::/66")) // 2^62 = 2^63 (1 more than MaxInt64)
|
||||
set, err := b.IPSet()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := newIPPool(set).capacity(); got != math.MaxInt64 {
|
||||
t.Errorf("capacity() = %d, want %d", got, int64(math.MaxInt64))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil-and-uninitialized", func(t *testing.T) {
|
||||
var nilPool *ippool
|
||||
if got := nilPool.capacity(); got != 0 {
|
||||
t.Errorf("nil pool capacity() = %d, want 0", got)
|
||||
}
|
||||
// newIPPool(nil) returns a non-nil pool with a nil ipSet.
|
||||
if got := newIPPool(nil).capacity(); got != 0 {
|
||||
t.Errorf("uninitialized pool capacity() = %d, want 0", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIPPoolInUseCount(t *testing.T) {
|
||||
t.Run("counts-handed-out", func(t *testing.T) {
|
||||
ipp := newIPPool(mustIPSetFromPrefix("100.64.0.0/29")) // 8 addresses
|
||||
if got := ipp.inUseCount(); got != 0 {
|
||||
t.Fatalf("fresh pool inUseCount() = %d, want 0", got)
|
||||
}
|
||||
a1 := must.Get(ipp.next())
|
||||
must.Get(ipp.next())
|
||||
if got := ipp.inUseCount(); got != 2 {
|
||||
t.Fatalf("after 2 next() inUseCount() = %d, want 2", got)
|
||||
}
|
||||
if err := ipp.returnAddr(a1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := ipp.inUseCount(); got != 1 {
|
||||
t.Fatalf("after returnAddr inUseCount() = %d, want 1", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil-and-uninitialized", func(t *testing.T) {
|
||||
var nilPool *ippool
|
||||
if got := nilPool.inUseCount(); got != 0 {
|
||||
t.Errorf("nil pool inUseCount() = %d, want 0", got)
|
||||
}
|
||||
// newIPPool(nil) returns a non-nil pool with a nil inUse set.
|
||||
if got := newIPPool(nil).inUseCount(); got != 0 {
|
||||
t.Errorf("uninitialized pool inUseCount() = %d, want 0", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user