Files
tailscale/feature/conn25/api_test.go
Michael Ben-Ami 2820b5e99d feature/conn25: rename localapi.go to api.go
And rename serveStateGet to serveLocalAPIStateGet to prepare for adding
a c2n handler that is backed by the same methods as the LocalAPI
handler.

Updates tailscale/corp#40125

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-07-22 13:04:41 -04:00

159 lines
5.7 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package conn25
import (
"net/netip"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/types/appctype"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
"tailscale.com/util/must"
)
func TestGetActiveState(t *testing.T) {
mustFQDN := func(s string) dnsname.FQDN { return must.Get(dnsname.ToFQDN(s)) }
// Fixed, arbitrary expiry so the output is comparable.
expires := time.Date(2026, 7, 13, 12, 0, 0, 0, time.UTC)
t.Run("unconfigured", func(t *testing.T) {
c := newConn25(logger.Discard)
if diff := cmp.Diff(appctype.Conn25ActiveState{}, c.GetActiveState()); diff != "" {
t.Fatalf("unconfigured Conn25ActiveState mismatch (-want +got):\n%s", diff)
}
})
t.Run("configured", func(t *testing.T) {
c := newConn25(logger.Discard)
c.config.Store(&config{isConfigured: true})
client := c.client
client.assignments.byDomainDst = map[domainDst]*addrs{}
addClientAssignment := func(app, domain, dst, magic, transit string, flows int) {
as := &addrs{
app: app,
domain: mustFQDN(domain),
dst: netip.MustParseAddr(dst),
magic: netip.MustParseAddr(magic),
transit: netip.MustParseAddr(transit),
activeFlowCount: flows,
expiresAt: expires,
}
client.assignments.byDomainDst[domainDst{domain: as.domain, dst: as.dst}] = as
}
// Two addresses under app1/example.com: should sort by active flow
// count descending (5 before 2).
addClientAssignment("app1", "example.com", "10.0.0.1", "100.64.0.1", "169.254.0.1", 2)
addClientAssignment("app1", "example.com", "10.0.0.2", "100.64.0.2", "169.254.0.2", 5)
// example.com (2 labels) sorts before sub.example.com.
addClientAssignment("app1", "sub.example.com", "10.0.0.3", "100.64.0.3", "169.254.0.3", 1)
// A second app: apps sort by name, so app1 before zebra.
addClientAssignment("zebra", "z.example.org", "10.0.0.4", "100.64.0.4", "169.254.0.4", 1)
// IP addresses should be sorted numerically, not lexigraphically.
c.connector.transitIPs = map[netip.Addr]map[netip.Addr]appAddr{
netip.MustParseAddr("100.64.0.1"): {
netip.MustParseAddr("169.254.0.100"): {app: "app1", addr: netip.MustParseAddr("10.0.0.100")},
netip.MustParseAddr("169.254.0.11"): {app: "app1", addr: netip.MustParseAddr("10.0.0.11")},
},
netip.MustParseAddr("11.0.0.1"): {
netip.MustParseAddr("169.254.0.5"): {app: "zapp", addr: netip.MustParseAddr("10.0.0.5")},
netip.MustParseAddr("169.254.0.6"): {app: "app1", addr: netip.MustParseAddr("10.0.0.6")},
},
}
// Configure the four IP pools with distinct capacities and hand out a
// distinct number of addresses from each.
mustPool := func(prefix string, handOut int) *ippool {
p := newIPPool(mustIPSetFromPrefix(prefix))
for range handOut {
if _, err := p.next(); err != nil {
t.Fatal(err)
}
}
return p
}
client.v4MagicIPPool = mustPool("100.64.0.0/30", 1) // capacity 4
client.v6MagicIPPool = mustPool("fd7a:1::/125", 2) // capacity 8
client.v4TransitIPPool = mustPool("169.254.0.0/28", 3) // capacity 16
client.v6TransitIPPool = mustPool("fd7a:2::/123", 4) // capacity 32
want := appctype.Conn25ActiveState{
Configured: true,
Client: appctype.Conn25ClientState{
Apps: []appctype.Conn25ClientAppState{
{
App: "app1",
Domains: []appctype.Conn25ClientDomainState{
{
Domain: mustFQDN("example.com"),
Addresses: []appctype.Conn25ClientAddressState{
{ActiveFlowCount: 5, DestinationIP: "10.0.0.2", MagicIP: "100.64.0.2", TransitIP: "169.254.0.2", ExpiresAt: expires},
{ActiveFlowCount: 2, DestinationIP: "10.0.0.1", MagicIP: "100.64.0.1", TransitIP: "169.254.0.1", ExpiresAt: expires},
},
},
{
Domain: mustFQDN("sub.example.com"),
Addresses: []appctype.Conn25ClientAddressState{
{ActiveFlowCount: 1, DestinationIP: "10.0.0.3", MagicIP: "100.64.0.3", TransitIP: "169.254.0.3", ExpiresAt: expires},
},
},
},
},
{
App: "zebra",
Domains: []appctype.Conn25ClientDomainState{
{
Domain: mustFQDN("z.example.org"),
Addresses: []appctype.Conn25ClientAddressState{
{ActiveFlowCount: 1, DestinationIP: "10.0.0.4", MagicIP: "100.64.0.4", TransitIP: "169.254.0.4", ExpiresAt: expires},
},
},
},
},
},
IPPoolStats: appctype.Conn25ClientIPPoolStats{
IPv4MagicIPsInUse: 1,
IPv4MagicIPsCapacity: 4,
IPv6MagicIPsInUse: 2,
IPv6MagicIPsCapacity: 8,
IPv4TransitIPsInUse: 3,
IPv4TransitIPsCapacity: 16,
IPv6TransitIPsInUse: 4,
IPv6TransitIPsCapacity: 32,
},
},
Connector: appctype.Conn25ConnectorState{
Peers: []appctype.Conn25ConnectorPeerState{
{
ClientIP: "11.0.0.1",
Apps: []appctype.Conn25ConnectorAppState{
{App: "app1", Addresses: []appctype.Conn25ConnectorAddressState{{DestinationIP: "10.0.0.6", TransitIP: "169.254.0.6"}}},
{App: "zapp", Addresses: []appctype.Conn25ConnectorAddressState{{DestinationIP: "10.0.0.5", TransitIP: "169.254.0.5"}}},
},
},
{
ClientIP: "100.64.0.1",
Apps: []appctype.Conn25ConnectorAppState{
{App: "app1", Addresses: []appctype.Conn25ConnectorAddressState{
{DestinationIP: "10.0.0.11", TransitIP: "169.254.0.11"},
{DestinationIP: "10.0.0.100", TransitIP: "169.254.0.100"},
}},
},
},
},
},
}
got := c.GetActiveState()
if diff := cmp.Diff(want, got, cmpopts.EquateApproxTime(0)); diff != "" {
t.Fatalf("Conn25ActiveState mismatch (-want +got):\n%s", diff)
}
})
}