Files
tailscale/feature/conn25/api_test.go
Michael Ben-Ami 49aa9c859c feature/conn25: store tokens from DNS response and pass them back in
TransitIPRequest to connector

Parse the tokens from the HTTP headers in the response. Determine which
node responded to the DNS request from the RequestURL passed in as
metadata to (*extension).mapDNSResponse. Store the token and connector
node alongside the TransitIP in the addrs struct. Then populate the new
Token field in TransitIPRequest with the stored token.

Updates tailscale/corp#46169

Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-10-31 15:22:34 -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),
connectorAddr: connectorAddr{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)
}
})
}