feature/conn25: use new pool nodeattr

We have been reading the pool config from the app nodeattr, but it is
global config, not per app, so it needs to be its own thing.

Updates tailscale/corp#39999

Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
Fran Bull
2026-05-29 08:29:34 -07:00
committed by franbull
parent 412c812d76
commit 3d5102090f
3 changed files with 143 additions and 83 deletions
+31 -27
View File
@@ -547,6 +547,7 @@ type ConnectorTransitIPResponse struct {
} }
const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental" const AppConnectorsExperimentalAttrName = "tailscale.com/app-connectors-experimental"
const AppConnectorsExperimentalIPPoolsAttrName = "tailscale.com/app-connectors-experimental-ippools"
// ipSets wraps all the IPSets the config needs. // ipSets wraps all the IPSets the config needs.
type ipSets struct { type ipSets struct {
@@ -586,6 +587,14 @@ func configFromNodeView(n tailcfg.NodeView) (*config, error) {
if len(apps) == 0 { if len(apps) == 0 {
return &config{}, nil return &config{}, nil
} }
poolsSlice, err := tailcfg.UnmarshalNodeCapViewJSON[appctype.Conn25PoolsAttr](n.CapMap(), AppConnectorsExperimentalIPPoolsAttrName)
if err != nil {
return &config{}, err
}
if len(poolsSlice) != 1 {
return &config{}, errors.New("must be one conn25 pools nodeattr")
}
pools := poolsSlice[0]
selfTags := set.SetOf(n.Tags().AsSlice()) selfTags := set.SetOf(n.Tags().AsSlice())
cfg := &config{ cfg := &config{
isConfigured: true, isConfigured: true,
@@ -620,34 +629,29 @@ func configFromNodeView(n tailcfg.NodeView) (*config, error) {
} }
// TODO(fran) 2026-03-18 we don't yet have a proper way to communicate the v4Mipp, err := ipSetFromIPRanges(pools.V4MagicIPPool)
// global IP pool config. For now just take it from the first app. if err != nil {
if len(apps) != 0 { return &config{}, err
app := apps[0]
v4Mipp, err := ipSetFromIPRanges(app.V4MagicIPPool)
if err != nil {
return &config{}, err
}
v4Tipp, err := ipSetFromIPRanges(app.V4TransitIPPool)
if err != nil {
return &config{}, err
}
v6Mipp, err := ipSetFromIPRanges(app.V6MagicIPPool)
if err != nil {
return &config{}, err
}
v6Tipp, err := ipSetFromIPRanges(app.V6TransitIPPool)
if err != nil {
return &config{}, err
}
ipSets := ipSets{
v4Magic: v4Mipp,
v4Transit: v4Tipp,
v6Magic: v6Mipp,
v6Transit: v6Tipp,
}
cfg.ipSets = ipSets
} }
v4Tipp, err := ipSetFromIPRanges(pools.V4TransitIPPool)
if err != nil {
return &config{}, err
}
v6Mipp, err := ipSetFromIPRanges(pools.V6MagicIPPool)
if err != nil {
return &config{}, err
}
v6Tipp, err := ipSetFromIPRanges(pools.V6TransitIPPool)
if err != nil {
return &config{}, err
}
ipSets := ipSets{
v4Magic: v4Mipp,
v4Transit: v4Tipp,
v6Magic: v6Mipp,
v6Transit: v6Tipp,
}
cfg.ipSets = ipSets
return cfg, nil return cfg, nil
} }
+108 -55
View File
@@ -458,6 +458,9 @@ func TestReconfig(t *testing.T) {
tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{ tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): []tailcfg.RawMessage{
tailcfg.RawMessage(rawCfg), tailcfg.RawMessage(rawCfg),
}, },
tailcfg.NodeCapability(AppConnectorsExperimentalIPPoolsAttrName): []tailcfg.RawMessage{
tailcfg.RawMessage("{}"),
},
} }
c := newConn25(logger.Discard) c := newConn25(logger.Discard)
@@ -485,11 +488,26 @@ func TestReconfig(t *testing.T) {
} }
} }
func getRawMessages[T any](t *testing.T, attrs []T) []tailcfg.RawMessage {
t.Helper()
toRet := make([]tailcfg.RawMessage, 0, len(attrs))
for _, attr := range attrs {
bs, err := json.Marshal(attr)
if err != nil {
t.Fatalf("unexpected error in test setup: %v", err)
}
toRet = append(toRet, tailcfg.RawMessage(bs))
}
return toRet
}
func TestConfigFromNodeView(t *testing.T) { func TestConfigFromNodeView(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
name string name string
rawCfg string rawAppCfg string
cfg []appctype.Conn25Attr appCfg []appctype.Conn25Attr
rawPoolsCfg string
poolsCfg []appctype.Conn25PoolsAttr
tags []string tags []string
wantErr bool wantErr bool
wantAppsByDomain map[dnsname.FQDN][]string wantAppsByDomain map[dnsname.FQDN][]string
@@ -497,17 +515,27 @@ func TestConfigFromNodeView(t *testing.T) {
wantSelfAppNames set.Set[string] wantSelfAppNames set.Set[string]
}{ }{
{ {
name: "bad-config", name: "bad-app-config",
rawCfg: `bad`, rawAppCfg: `bad`,
wantErr: true, rawPoolsCfg: "{}",
wantErr: true,
},
{
name: "bad-pool-config",
appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}},
},
rawPoolsCfg: "bad",
wantErr: true,
}, },
{ {
name: "simple", name: "simple",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}}, {Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}},
{Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}}, {Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}},
}, },
tags: []string{"tag:one"}, rawPoolsCfg: "{}",
tags: []string{"tag:one"},
wantAppsByDomain: map[dnsname.FQDN][]string{ wantAppsByDomain: map[dnsname.FQDN][]string{
"a.example.com.": {"one"}, "a.example.com.": {"one"},
"b.example.com.": {"two"}, "b.example.com.": {"two"},
@@ -517,13 +545,14 @@ func TestConfigFromNodeView(t *testing.T) {
}, },
{ {
name: "more-complex-with-connector-self-domains", name: "more-complex-with-connector-self-domains",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"1.a.example.com", "1.b.example.com"}, Connectors: []string{"tag:one", "tag:onea"}}, {Name: "one", Domains: []string{"1.a.example.com", "1.b.example.com"}, Connectors: []string{"tag:one", "tag:onea"}},
{Name: "two", Domains: []string{"2.b.example.com", "2.c.example.com"}, Connectors: []string{"tag:two", "tag:twoa"}}, {Name: "two", Domains: []string{"2.b.example.com", "2.c.example.com"}, Connectors: []string{"tag:two", "tag:twoa"}},
{Name: "three", Domains: []string{"1.b.example.com", "1.c.example.com"}, Connectors: []string{}}, {Name: "three", Domains: []string{"1.b.example.com", "1.c.example.com"}, Connectors: []string{}},
{Name: "four", Domains: []string{"4.b.example.com", "4.d.example.com"}, Connectors: []string{"tag:four"}}, {Name: "four", Domains: []string{"4.b.example.com", "4.d.example.com"}, Connectors: []string{"tag:four"}},
}, },
tags: []string{"tag:onea", "tag:four", "tag:unrelated"}, rawPoolsCfg: "{}",
tags: []string{"tag:onea", "tag:four", "tag:unrelated"},
wantAppsByDomain: map[dnsname.FQDN][]string{ wantAppsByDomain: map[dnsname.FQDN][]string{
"1.a.example.com.": {"one"}, "1.a.example.com.": {"one"},
"1.b.example.com.": {"one", "three"}, "1.b.example.com.": {"one", "three"},
@@ -538,11 +567,12 @@ func TestConfigFromNodeView(t *testing.T) {
}, },
{ {
name: "eligible-connector-no-matching-tag-no-self-domains", name: "eligible-connector-no-matching-tag-no-self-domains",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}}, {Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}},
{Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}}, {Name: "two", Domains: []string{"b.example.com"}, Connectors: []string{"tag:two"}},
}, },
tags: []string{"tag:unrelated"}, rawPoolsCfg: "{}",
tags: []string{"tag:unrelated"},
wantAppsByDomain: map[dnsname.FQDN][]string{ wantAppsByDomain: map[dnsname.FQDN][]string{
"a.example.com.": {"one"}, "a.example.com.": {"one"},
"b.example.com.": {"two"}, "b.example.com.": {"two"},
@@ -550,11 +580,12 @@ func TestConfigFromNodeView(t *testing.T) {
wantAppsByWCDomain: map[dnsname.FQDN][]string{}}, wantAppsByWCDomain: map[dnsname.FQDN][]string{}},
{ {
name: "wildcard-collapse-and-deduplication", name: "wildcard-collapse-and-deduplication",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"*.example.com", "example.com"}, Connectors: []string{"tag:one"}}, {Name: "one", Domains: []string{"*.example.com", "example.com"}, Connectors: []string{"tag:one"}},
{Name: "two", Domains: []string{"example.com", "sub.example.com"}, Connectors: []string{"tag:two"}}, {Name: "two", Domains: []string{"example.com", "sub.example.com"}, Connectors: []string{"tag:two"}},
}, },
tags: []string{"tag:one", "tag:two"}, rawPoolsCfg: "{}",
tags: []string{"tag:one", "tag:two"},
wantAppsByDomain: map[dnsname.FQDN][]string{ wantAppsByDomain: map[dnsname.FQDN][]string{
"example.com.": {"one", "two"}, "example.com.": {"one", "two"},
"sub.example.com.": {"two"}, "sub.example.com.": {"two"},
@@ -569,10 +600,11 @@ func TestConfigFromNodeView(t *testing.T) {
// domain and the app name must appear exactly once in appNamesByDomain, // domain and the app name must appear exactly once in appNamesByDomain,
// not once per case variant. // not once per case variant.
name: "case-variant-exact-domains-deduplicated-within-app", name: "case-variant-exact-domains-deduplicated-within-app",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"EXAMPLE.com", "example.COM", "Example.COM"}, Connectors: []string{"tag:one"}}, {Name: "one", Domains: []string{"EXAMPLE.com", "example.COM", "Example.COM"}, Connectors: []string{"tag:one"}},
}, },
tags: []string{"tag:one"}, rawPoolsCfg: "{}",
tags: []string{"tag:one"},
wantAppsByDomain: map[dnsname.FQDN][]string{ wantAppsByDomain: map[dnsname.FQDN][]string{
"example.com.": {"one"}, "example.com.": {"one"},
}, },
@@ -583,29 +615,39 @@ func TestConfigFromNodeView(t *testing.T) {
// Same as above but for wildcard domains: *.EXAMPLE.com and *.example.COM // Same as above but for wildcard domains: *.EXAMPLE.com and *.example.COM
// must collapse to a single entry in appNamesByWCDomain. // must collapse to a single entry in appNamesByWCDomain.
name: "case-variant-wildcard-domains-deduplicated-within-app", name: "case-variant-wildcard-domains-deduplicated-within-app",
cfg: []appctype.Conn25Attr{ appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"*.EXAMPLE.com", "*.example.COM"}, Connectors: []string{"tag:one"}}, {Name: "one", Domains: []string{"*.EXAMPLE.com", "*.example.COM"}, Connectors: []string{"tag:one"}},
}, },
rawPoolsCfg: "{}",
tags: []string{"tag:one"}, tags: []string{"tag:one"},
wantAppsByDomain: map[dnsname.FQDN][]string{}, wantAppsByDomain: map[dnsname.FQDN][]string{},
wantAppsByWCDomain: map[dnsname.FQDN][]string{"example.com.": {"one"}}, wantAppsByWCDomain: map[dnsname.FQDN][]string{"example.com.": {"one"}},
wantSelfAppNames: set.SetOf([]string{"one"}), wantSelfAppNames: set.SetOf([]string{"one"}),
}, },
{
name: "too-many-pools",
appCfg: []appctype.Conn25Attr{
{Name: "one", Domains: []string{"a.example.com"}, Connectors: []string{"tag:one"}},
},
poolsCfg: []appctype.Conn25PoolsAttr{
{},
{},
},
wantErr: true,
},
} { } {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
cfg := []tailcfg.RawMessage{tailcfg.RawMessage(tt.rawCfg)} appCfg := []tailcfg.RawMessage{tailcfg.RawMessage(tt.rawAppCfg)}
if tt.cfg != nil { if tt.appCfg != nil {
cfg = []tailcfg.RawMessage{} appCfg = getRawMessages(t, tt.appCfg)
for _, attr := range tt.cfg { }
bs, err := json.Marshal(attr) poolsCfg := []tailcfg.RawMessage{tailcfg.RawMessage(tt.rawPoolsCfg)}
if err != nil { if tt.poolsCfg != nil {
t.Fatalf("unexpected error in test setup: %v", err) poolsCfg = getRawMessages(t, tt.poolsCfg)
}
cfg = append(cfg, tailcfg.RawMessage(bs))
}
} }
capMap := tailcfg.NodeCapMap{ capMap := tailcfg.NodeCapMap{
tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): appCfg,
tailcfg.NodeCapability(AppConnectorsExperimentalIPPoolsAttrName): poolsCfg,
} }
sn := (&tailcfg.Node{ sn := (&tailcfg.Node{
CapMap: capMap, CapMap: capMap,
@@ -639,6 +681,7 @@ func TestGetAppsForDomainName(t *testing.T) {
{Name: "four", Domains: []string{"a.sub.example.com"}, Connectors: []string{"tag:four"}}, {Name: "four", Domains: []string{"a.sub.example.com"}, Connectors: []string{"tag:four"}},
{Name: "self-routed", Domains: []string{"*.wildcard.com", "exact-match.com"}, Connectors: []string{"tag:self-routed"}}, {Name: "self-routed", Domains: []string{"*.wildcard.com", "exact-match.com"}, Connectors: []string{"tag:self-routed"}},
}, },
appctype.Conn25PoolsAttr{},
[]string{"tag:self-routed"}, []string{"tag:self-routed"},
) )
@@ -732,7 +775,7 @@ func TestGetAppsForDomainName(t *testing.T) {
} }
} }
func makeSelfNode(t *testing.T, attrs []appctype.Conn25Attr, tags []string) tailcfg.NodeView { func makeSelfNode(t *testing.T, attrs []appctype.Conn25Attr, pools appctype.Conn25PoolsAttr, tags []string) tailcfg.NodeView {
t.Helper() t.Helper()
cfg := make([]tailcfg.RawMessage, 0, len(attrs)) cfg := make([]tailcfg.RawMessage, 0, len(attrs))
for i, attr := range attrs { for i, attr := range attrs {
@@ -742,8 +785,13 @@ func makeSelfNode(t *testing.T, attrs []appctype.Conn25Attr, tags []string) tail
} }
cfg = append(cfg, tailcfg.RawMessage(bs)) cfg = append(cfg, tailcfg.RawMessage(bs))
} }
poolsBytes, err := json.Marshal(pools)
if err != nil {
t.Fatalf("unexpected error marshaling pools in test setup: %v", err)
}
capMap := tailcfg.NodeCapMap{ capMap := tailcfg.NodeCapMap{
tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg, tailcfg.NodeCapability(AppConnectorsExperimentalAttrName): cfg,
tailcfg.NodeCapability(AppConnectorsExperimentalIPPoolsAttrName): {tailcfg.RawMessage(poolsBytes)},
} }
return (&tailcfg.Node{ return (&tailcfg.Node{
@@ -1095,14 +1143,15 @@ func TestMapDNSResponseAssignsAddrs(t *testing.T) {
dnsResp = makeV6DNSResponse(t, tt.domain, tt.v6Addrs) dnsResp = makeV6DNSResponse(t, tt.domain, tt.v6Addrs)
} }
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{
Name: "app1", Name: "app1",
Connectors: []string{"tag:woo"}, Connectors: []string{"tag:woo"},
Domains: tt.appDomains, Domains: tt.appDomains,
}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10"), v4RangeFrom("20", "30")}, V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10"), v4RangeFrom("20", "30")},
V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10"), v6RangeFrom("20", "30")}, V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10"), v6RangeFrom("20", "30")},
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")}, V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")},
V6TransitIPPool: []netipx.IPRange{v6RangeFrom("40", "50")}, V6TransitIPPool: []netipx.IPRange{v6RangeFrom("40", "50")},
}}, tt.selfTags) }, tt.selfTags)
c := newConn25(logger.Discard) c := newConn25(logger.Discard)
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
@@ -1128,14 +1177,15 @@ func TestMapDNSResponseSetsExpiryBasedOnTTL(t *testing.T) {
domainName := configuredDomain + "." domainName := configuredDomain + "."
dnsMessageName := dnsmessage.MustNewName(domainName) dnsMessageName := dnsmessage.MustNewName(domainName)
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{
Name: "app1", Name: "app1",
Connectors: []string{"tag:woo"}, Connectors: []string{"tag:woo"},
Domains: []string{configuredDomain}, Domains: []string{configuredDomain},
}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")}, V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")},
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")}, V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")},
V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6812:100"), netip.MustParseAddr("2606:4700::6812:1ff"))}, V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6812:100"), netip.MustParseAddr("2606:4700::6812:1ff"))},
V6TransitIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6813:100"), netip.MustParseAddr("2606:4700::6813:1ff"))}, V6TransitIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6813:100"), netip.MustParseAddr("2606:4700::6813:1ff"))},
}}, nil) }, nil)
c := newConn25(logger.Discard) c := newConn25(logger.Discard)
clock := tstest.NewClock(tstest.ClockOpts{Start: time.Now()}) clock := tstest.NewClock(tstest.ClockOpts{Start: time.Now()})
@@ -1410,7 +1460,7 @@ func TestAddressAssignmentIsHandled(t *testing.T) {
Name: "app1", Name: "app1",
Connectors: []string{"tag:woo"}, Connectors: []string{"tag:woo"},
Domains: []string{"example.com"}, Domains: []string{"example.com"},
}}, []string{}) }}, appctype.Conn25PoolsAttr{}, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
ext.conn25.reconfig(cfg) ext.conn25.reconfig(cfg)
@@ -1484,14 +1534,15 @@ func TestMapDNSResponseRewritesResponses(t *testing.T) {
domainName := configuredDomain + "." domainName := configuredDomain + "."
dnsMessageName := dnsmessage.MustNewName(domainName) dnsMessageName := dnsmessage.MustNewName(domainName)
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{
Name: "app1", Name: "app1",
Connectors: []string{"tag:connector"}, Connectors: []string{"tag:connector"},
Domains: []string{configuredDomain}, Domains: []string{configuredDomain},
}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")}, V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")},
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")}, V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")},
V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6812:100"), netip.MustParseAddr("2606:4700::6812:1ff"))}, V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6812:100"), netip.MustParseAddr("2606:4700::6812:1ff"))},
V6TransitIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6813:100"), netip.MustParseAddr("2606:4700::6813:1ff"))}, V6TransitIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("2606:4700::6813:100"), netip.MustParseAddr("2606:4700::6813:1ff"))},
}}, []string{}) }, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
@@ -2011,7 +2062,7 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
Connectors: []string{"tag:hoo"}, Connectors: []string{"tag:hoo"},
Domains: []string{"hoo.example.com"}, Domains: []string{"hoo.example.com"},
}, },
}, []string{}) }, appctype.Conn25PoolsAttr{}, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
ext.conn25.reconfig(cfg) ext.conn25.reconfig(cfg)
@@ -2187,10 +2238,10 @@ func TestTransitIPConnMapping(t *testing.T) {
} }
func TestClientTransitIPForMagicIP(t *testing.T) { func TestClientTransitIPForMagicIP(t *testing.T) {
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{Name: "app1"}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")}, // 100.64.0.0 - 100.64.0.10 V4MagicIPPool: []netipx.IPRange{v4RangeFrom("0", "10")}, // 100.64.0.0 - 100.64.0.10
V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10")}, V6MagicIPPool: []netipx.IPRange{v6RangeFrom("0", "10")},
}}, []string{}) }, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
mappedMip := netip.MustParseAddr("100.64.0.0") mappedMip := netip.MustParseAddr("100.64.0.0")
@@ -2278,9 +2329,9 @@ func TestClientTransitIPForMagicIP(t *testing.T) {
} }
func TestConnectorRealIPForTransitIPConnection(t *testing.T) { func TestConnectorRealIPForTransitIPConnection(t *testing.T) {
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{Name: "app1"}}, appctype.Conn25PoolsAttr{
V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")}, // 100.64.0.40 - 100.64.0.50 V4TransitIPPool: []netipx.IPRange{v4RangeFrom("40", "50")}, // 100.64.0.40 - 100.64.0.50
}}, []string{}) }, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
mappedSrc := netip.MustParseAddr("100.0.0.1") mappedSrc := netip.MustParseAddr("100.0.0.1")
@@ -2421,12 +2472,13 @@ func TestConnectorPacketFilterAllow(t *testing.T) {
func TestGetMagicRange(t *testing.T) { func TestGetMagicRange(t *testing.T) {
sn := makeSelfNode(t, []appctype.Conn25Attr{{ sn := makeSelfNode(t, []appctype.Conn25Attr{{
Name: "app1", Name: "app1",
Connectors: []string{"tag:woo"}, Connectors: []string{"tag:woo"},
Domains: []string{"example.com"}, Domains: []string{"example.com"},
}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.3"))}, V4MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("0.0.0.1"), netip.MustParseAddr("0.0.0.3"))},
V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("::1"), netip.MustParseAddr("::3"))}, V6MagicIPPool: []netipx.IPRange{netipx.IPRangeFrom(netip.MustParseAddr("::1"), netip.MustParseAddr("::3"))},
}}, []string{}) }, []string{})
cfg := mustConfig(t, sn) cfg := mustConfig(t, sn)
c := newConn25(t.Logf) c := newConn25(t.Logf)
c.reconfig(cfg) c.reconfig(cfg)
@@ -2479,14 +2531,15 @@ func TestReconfigDoesNotReissueInUseAddresses(t *testing.T) {
afterRangeV6 := mustRange("::4", "::7") afterRangeV6 := mustRange("::4", "::7")
makeNodeFromMagicRange := func(v4, v6 netipx.IPRange) tailcfg.NodeView { makeNodeFromMagicRange := func(v4, v6 netipx.IPRange) tailcfg.NodeView {
return makeSelfNode(t, []appctype.Conn25Attr{{ return makeSelfNode(t, []appctype.Conn25Attr{{
Name: appName, Name: appName,
Connectors: []string{"tag:woo"}, Connectors: []string{"tag:woo"},
Domains: []string{"example.com"}, Domains: []string{"example.com"},
}}, appctype.Conn25PoolsAttr{
V4MagicIPPool: []netipx.IPRange{v4}, V4MagicIPPool: []netipx.IPRange{v4},
V6MagicIPPool: []netipx.IPRange{v6}, V6MagicIPPool: []netipx.IPRange{v6},
V4TransitIPPool: []netipx.IPRange{mustRange("169.254.0.0", "169.254.0.10")}, V4TransitIPPool: []netipx.IPRange{mustRange("169.254.0.0", "169.254.0.10")},
V6TransitIPPool: []netipx.IPRange{mustRange("fd7a:115c:a1e0:a99c:0200::", "fd7a:115c:a1e0:a99c:0200::10")}, V6TransitIPPool: []netipx.IPRange{mustRange("fd7a:115c:a1e0:a99c:0200::", "fd7a:115c:a1e0:a99c:0200::10")},
}}, []string{}) }, []string{})
} }
domain := must.Get(dnsname.ToFQDN("example.com.")) domain := must.Get(dnsname.ToFQDN("example.com."))
+4 -1
View File
@@ -104,7 +104,10 @@ type Conn25Attr struct {
// Connectors enumerates the app connectors which service these domains. // Connectors enumerates the app connectors which service these domains.
// These can either be "*" to match any advertising connector, or a // These can either be "*" to match any advertising connector, or a
// tag of the form tag:<tag-name>. // tag of the form tag:<tag-name>.
Connectors []string `json:"connectors,omitempty"` Connectors []string `json:"connectors,omitempty"`
}
type Conn25PoolsAttr struct {
V4MagicIPPool []netipx.IPRange `json:"v4MagicIPPool,omitempty"` V4MagicIPPool []netipx.IPRange `json:"v4MagicIPPool,omitempty"`
V4TransitIPPool []netipx.IPRange `json:"v4TransitIPPool,omitempty"` V4TransitIPPool []netipx.IPRange `json:"v4TransitIPPool,omitempty"`
V6MagicIPPool []netipx.IPRange `json:"v6MagicIPPool,omitempty"` V6MagicIPPool []netipx.IPRange `json:"v6MagicIPPool,omitempty"`