Skip to content

Commit 6ad54fe

Browse files
committed
appc,ipn/ipnlocal: add App Connector domain configuration from mapcap
The AppConnector is now configured by the mapcap from the control plane. Updates tailscale/corp#15437 Signed-off-by: James Tucker <james@tailscale.com>
1 parent e9de59a commit 6ad54fe

File tree

5 files changed

+101
-5
lines changed

5 files changed

+101
-5
lines changed

appc/embedded.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"strings"
1212
"sync"
1313

14+
xmaps "golang.org/x/exp/maps"
1415
"golang.org/x/net/dns/dnsmessage"
1516
"tailscale.com/types/logger"
17+
"tailscale.com/types/views"
1618
)
1719

1820
/*
@@ -70,6 +72,15 @@ func (e *EmbeddedAppConnector) UpdateDomains(domains []string) {
7072
d = strings.ToLower(d)
7173
e.domains[d] = old[d]
7274
}
75+
e.logf("handling domains: %v", xmaps.Keys(e.domains))
76+
}
77+
78+
// Domains returns the currently configured domain list.
79+
func (e *EmbeddedAppConnector) Domains() views.Slice[string] {
80+
e.mu.Lock()
81+
defer e.mu.Unlock()
82+
83+
return views.SliceOf(xmaps.Keys(e.domains))
7384
}
7485

7586
// ObserveDNSResponse is a callback invoked by the DNS resolver when a DNS

appc/embedded_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
func TestUpdateDomains(t *testing.T) {
1717
a := NewEmbeddedAppConnector(t.Logf, nil)
1818
a.UpdateDomains([]string{"example.com"})
19-
if got, want := xmaps.Keys(a.domains), []string{"example.com"}; !slices.Equal(got, want) {
19+
if got, want := a.Domains().AsSlice(), []string{"example.com"}; !slices.Equal(got, want) {
2020
t.Errorf("got %v; want %v", got, want)
2121
}
2222

cmd/tailscaled/depaware.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
321321
tailscale.com/tstime/mono from tailscale.com/net/tstun+
322322
tailscale.com/tstime/rate from tailscale.com/wgengine/filter+
323323
tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled
324-
tailscale.com/types/appctype from tailscale.com/appc
324+
tailscale.com/types/appctype from tailscale.com/appc+
325325
tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+
326326
tailscale.com/types/empty from tailscale.com/ipn+
327327
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled

ipn/ipnlocal/local.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"tailscale.com/tka"
6868
"tailscale.com/tsd"
6969
"tailscale.com/tstime"
70+
"tailscale.com/types/appctype"
7071
"tailscale.com/types/dnstype"
7172
"tailscale.com/types/empty"
7273
"tailscale.com/types/key"
@@ -3233,6 +3234,49 @@ func (b *LocalBackend) blockEngineUpdates(block bool) {
32333234
b.mu.Unlock()
32343235
}
32353236

3237+
// reconfigAppConnectorLocked updates the app connector state based on the
3238+
// current network map and preferences.
3239+
// b.mu must be held.
3240+
func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs ipn.PrefsView) {
3241+
const appConnectorCapName = "tailscale.com/app-connectors"
3242+
3243+
if !prefs.AppConnector().Advertise {
3244+
b.appConnector = nil
3245+
return
3246+
}
3247+
3248+
if b.appConnector == nil {
3249+
b.appConnector = appc.NewEmbeddedAppConnector(b.logf, b)
3250+
}
3251+
if nm == nil {
3252+
return
3253+
}
3254+
3255+
// TODO(raggi): rework the view infrastructure so the large deep clone is no
3256+
// longer required
3257+
sn := nm.SelfNode.AsStruct()
3258+
attrs, err := tailcfg.UnmarshalNodeCapJSON[appctype.AppConnectorAttr](sn.CapMap, appConnectorCapName)
3259+
if err != nil {
3260+
b.logf("[unexpected] error parsing app connector mapcap: %v", err)
3261+
return
3262+
}
3263+
3264+
var domains []string
3265+
for _, attr := range attrs {
3266+
// Geometric cost, assumes that the number of advertised tags is small
3267+
if !nm.SelfNode.Tags().ContainsFunc(func(tag string) bool {
3268+
return slices.Contains(attr.Connectors, tag)
3269+
}) {
3270+
continue
3271+
}
3272+
3273+
domains = append(domains, attr.Domains...)
3274+
}
3275+
slices.Sort(domains)
3276+
slices.Compact(domains)
3277+
b.appConnector.UpdateDomains(domains)
3278+
}
3279+
32363280
// authReconfig pushes a new configuration into wgengine, if engine
32373281
// updates are not currently blocked, based on the cached netmap and
32383282
// user prefs.
@@ -3246,9 +3290,7 @@ func (b *LocalBackend) authReconfig() {
32463290
dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID())
32473291
dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS())
32483292
// If the current node is an app connector, ensure the app connector machine is started
3249-
if prefs.AppConnector().Advertise && b.appConnector == nil {
3250-
b.appConnector = appc.NewEmbeddedAppConnector(b.logf, b)
3251-
}
3293+
b.reconfigAppConnectorLocked(nm, prefs)
32523294
b.mu.Unlock()
32533295

32543296
if blocked {

ipn/ipnlocal/local_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,49 @@ func TestObserveDNSResponse(t *testing.T) {
11871187
}
11881188
}
11891189

1190+
func TestReconfigureAppConnector(t *testing.T) {
1191+
b := newTestBackend(t)
1192+
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
1193+
if b.appConnector != nil {
1194+
t.Fatal("unexpected app connector")
1195+
}
1196+
1197+
b.EditPrefs(&ipn.MaskedPrefs{
1198+
Prefs: ipn.Prefs{
1199+
AppConnector: ipn.AppConnectorPrefs{
1200+
Advertise: true,
1201+
},
1202+
},
1203+
AppConnectorSet: true,
1204+
})
1205+
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
1206+
if b.appConnector == nil {
1207+
t.Fatal("expected app connector")
1208+
}
1209+
1210+
appCfg := `{
1211+
"name": "example",
1212+
"domains": ["example.com"],
1213+
"connectors": ["tag:example"]
1214+
}`
1215+
1216+
b.netMap.SelfNode = (&tailcfg.Node{
1217+
Name: "example.ts.net",
1218+
Tags: []string{"tag:example"},
1219+
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
1220+
"tailscale.com/app-connectors": {tailcfg.RawMessage(appCfg)},
1221+
}),
1222+
}).View()
1223+
1224+
b.reconfigAppConnectorLocked(b.netMap, b.pm.prefs)
1225+
1226+
want := []string{"example.com"}
1227+
if !slices.Equal(b.appConnector.Domains().AsSlice(), want) {
1228+
t.Fatalf("got domains %v, want %v", b.appConnector.Domains(), want)
1229+
}
1230+
1231+
}
1232+
11901233
func resolversEqual(t *testing.T, a, b []*dnstype.Resolver) bool {
11911234
if a == nil && b == nil {
11921235
return true

0 commit comments

Comments
 (0)