mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
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>
159 lines
5.7 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|