Skip to content

Commit 5d853fc

Browse files
chore: support adding dns hosts to tailnet.Conn (#15419)
Relates to #14718. The remaining changes (regarding the Tailscale DNS service) will need to be made on `coder/tailscale`.
1 parent e5661c2 commit 5d853fc

File tree

3 files changed

+223
-6
lines changed

3 files changed

+223
-6
lines changed

tailnet/configmaps.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"maps"
89
"net/netip"
10+
"slices"
911
"sync"
1012
"time"
1113

@@ -14,9 +16,11 @@ import (
1416
"tailscale.com/ipn/ipnstate"
1517
"tailscale.com/net/dns"
1618
"tailscale.com/tailcfg"
19+
"tailscale.com/types/dnstype"
1720
"tailscale.com/types/ipproto"
1821
"tailscale.com/types/key"
1922
"tailscale.com/types/netmap"
23+
"tailscale.com/util/dnsname"
2024
"tailscale.com/wgengine"
2125
"tailscale.com/wgengine/filter"
2226
"tailscale.com/wgengine/router"
@@ -30,6 +34,10 @@ import (
3034

3135
const lostTimeout = 15 * time.Minute
3236

37+
// CoderDNSSuffix is the default DNS suffix that we append to Coder DNS
38+
// records.
39+
const CoderDNSSuffix = "coder."
40+
3341
// engineConfigurable is the subset of wgengine.Engine that we use for configuration.
3442
//
3543
// This allows us to test configuration code without faking the whole interface.
@@ -63,6 +71,7 @@ type configMaps struct {
6371

6472
engine engineConfigurable
6573
static netmap.NetworkMap
74+
hosts map[dnsname.FQDN][]netip.Addr
6675
peers map[uuid.UUID]*peerLifecycle
6776
addresses []netip.Prefix
6877
derpMap *tailcfg.DERPMap
@@ -79,6 +88,7 @@ func newConfigMaps(logger slog.Logger, engine engineConfigurable, nodeID tailcfg
7988
phased: phased{Cond: *(sync.NewCond(&sync.Mutex{}))},
8089
logger: logger,
8190
engine: engine,
91+
hosts: make(map[dnsname.FQDN][]netip.Addr),
8292
static: netmap.NetworkMap{
8393
SelfNode: &tailcfg.Node{
8494
ID: nodeID,
@@ -153,10 +163,11 @@ func (c *configMaps) configLoop() {
153163
}
154164
if c.netmapDirty {
155165
nm := c.netMapLocked()
166+
hosts := c.hostsLocked()
156167
actions = append(actions, func() {
157168
c.logger.Debug(context.Background(), "updating engine network map", slog.F("network_map", nm))
158169
c.engine.SetNetworkMap(nm)
159-
c.reconfig(nm)
170+
c.reconfig(nm, hosts)
160171
})
161172
}
162173
if c.filterDirty {
@@ -212,6 +223,11 @@ func (c *configMaps) netMapLocked() *netmap.NetworkMap {
212223
return nm
213224
}
214225

226+
// hostsLocked returns the current DNS hosts mapping. c.L must be held.
227+
func (c *configMaps) hostsLocked() map[dnsname.FQDN][]netip.Addr {
228+
return maps.Clone(c.hosts)
229+
}
230+
215231
// peerConfigLocked returns the set of peer nodes we have. c.L must be held.
216232
func (c *configMaps) peerConfigLocked() []*tailcfg.Node {
217233
out := make([]*tailcfg.Node, 0, len(c.peers))
@@ -261,6 +277,37 @@ func (c *configMaps) setAddresses(ips []netip.Prefix) {
261277
c.Broadcast()
262278
}
263279

280+
func (c *configMaps) addHosts(hosts map[dnsname.FQDN][]netip.Addr) {
281+
c.L.Lock()
282+
defer c.L.Unlock()
283+
for name, addrs := range hosts {
284+
c.hosts[name] = slices.Clone(addrs)
285+
}
286+
c.netmapDirty = true
287+
c.Broadcast()
288+
}
289+
290+
func (c *configMaps) setHosts(hosts map[dnsname.FQDN][]netip.Addr) {
291+
c.L.Lock()
292+
defer c.L.Unlock()
293+
c.hosts = make(map[dnsname.FQDN][]netip.Addr)
294+
for name, addrs := range hosts {
295+
c.hosts[name] = slices.Clone(addrs)
296+
}
297+
c.netmapDirty = true
298+
c.Broadcast()
299+
}
300+
301+
func (c *configMaps) removeHosts(names []dnsname.FQDN) {
302+
c.L.Lock()
303+
defer c.L.Unlock()
304+
for _, name := range names {
305+
delete(c.hosts, name)
306+
}
307+
c.netmapDirty = true
308+
c.Broadcast()
309+
}
310+
264311
// setBlockEndpoints sets whether we should block configuring endpoints we learn
265312
// from peers. It triggers a configuration of the engine if the value changes.
266313
// nolint: revive
@@ -305,7 +352,15 @@ func (c *configMaps) derpMapLocked() *tailcfg.DERPMap {
305352
// reconfig computes the correct wireguard config and calls the engine.Reconfig
306353
// with the config we have. It is not intended for this to be called outside of
307354
// the updateLoop()
308-
func (c *configMaps) reconfig(nm *netmap.NetworkMap) {
355+
func (c *configMaps) reconfig(nm *netmap.NetworkMap, hosts map[dnsname.FQDN][]netip.Addr) {
356+
dnsCfg := &dns.Config{}
357+
if len(hosts) > 0 {
358+
dnsCfg.Hosts = hosts
359+
dnsCfg.OnlyIPv6 = true
360+
dnsCfg.Routes = map[dnsname.FQDN][]*dnstype.Resolver{
361+
CoderDNSSuffix: nil,
362+
}
363+
}
309364
cfg, err := nmcfg.WGCfg(nm, Logger(c.logger.Named("net.wgconfig")), netmap.AllowSingleHosts, "")
310365
if err != nil {
311366
// WGCfg never returns an error at the time this code was written. If it starts, returning
@@ -314,8 +369,11 @@ func (c *configMaps) reconfig(nm *netmap.NetworkMap) {
314369
return
315370
}
316371

317-
rc := &router.Config{LocalAddrs: nm.Addresses}
318-
err = c.engine.Reconfig(cfg, rc, &dns.Config{}, &tailcfg.Debug{})
372+
rc := &router.Config{
373+
LocalAddrs: nm.Addresses,
374+
Routes: []netip.Prefix{CoderServicePrefix.AsNetip()},
375+
}
376+
err = c.engine.Reconfig(cfg, rc, dnsCfg, &tailcfg.Debug{})
319377
if err != nil {
320378
if errors.Is(err, wgengine.ErrNoChanges) {
321379
return

tailnet/configmaps_internal_test.go

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import (
1010
"github.com/google/uuid"
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
13+
"golang.org/x/exp/maps"
1314
"tailscale.com/ipn/ipnstate"
1415
"tailscale.com/net/dns"
1516
"tailscale.com/tailcfg"
17+
"tailscale.com/types/dnstype"
1618
"tailscale.com/types/key"
1719
"tailscale.com/types/netmap"
20+
"tailscale.com/util/dnsname"
1821
"tailscale.com/wgengine/filter"
1922
"tailscale.com/wgengine/router"
2023
"tailscale.com/wgengine/wgcfg"
@@ -1157,6 +1160,127 @@ func TestConfigMaps_updatePeers_nonexist(t *testing.T) {
11571160
}
11581161
}
11591162

1163+
func TestConfigMaps_addRemoveHosts(t *testing.T) {
1164+
t.Parallel()
1165+
1166+
ctx := testutil.Context(t, testutil.WaitShort)
1167+
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
1168+
fEng := newFakeEngineConfigurable()
1169+
nodePrivateKey := key.NewNode()
1170+
nodeID := tailcfg.NodeID(5)
1171+
discoKey := key.NewDisco()
1172+
uut := newConfigMaps(logger, fEng, nodeID, nodePrivateKey, discoKey.Public())
1173+
defer uut.close()
1174+
1175+
addr1 := CoderServicePrefix.AddrFromUUID(uuid.New())
1176+
addr2 := CoderServicePrefix.AddrFromUUID(uuid.New())
1177+
addr3 := CoderServicePrefix.AddrFromUUID(uuid.New())
1178+
addr4 := CoderServicePrefix.AddrFromUUID(uuid.New())
1179+
1180+
// WHEN: we add two hosts
1181+
uut.addHosts(map[dnsname.FQDN][]netip.Addr{
1182+
"agent.myws.me.coder.": {
1183+
addr1,
1184+
},
1185+
"dev.main.me.coder.": {
1186+
addr2,
1187+
addr3,
1188+
},
1189+
})
1190+
1191+
// THEN: the engine is reconfigured with those same hosts
1192+
_ = testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap)
1193+
req := testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
1194+
require.Equal(t, req.dnsCfg, &dns.Config{
1195+
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
1196+
CoderDNSSuffix: nil,
1197+
},
1198+
Hosts: map[dnsname.FQDN][]netip.Addr{
1199+
"agent.myws.me.coder.": {
1200+
addr1,
1201+
},
1202+
"dev.main.me.coder.": {
1203+
addr2,
1204+
addr3,
1205+
},
1206+
},
1207+
OnlyIPv6: true,
1208+
})
1209+
1210+
// WHEN: we add a new host
1211+
newHost := map[dnsname.FQDN][]netip.Addr{
1212+
"agent2.myws.me.coder.": {
1213+
addr4,
1214+
},
1215+
}
1216+
uut.addHosts(newHost)
1217+
1218+
// THEN: the engine is reconfigured with both the old and new hosts
1219+
_ = testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap)
1220+
req = testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
1221+
require.Equal(t, req.dnsCfg, &dns.Config{
1222+
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
1223+
CoderDNSSuffix: nil,
1224+
},
1225+
Hosts: map[dnsname.FQDN][]netip.Addr{
1226+
"agent.myws.me.coder.": {
1227+
addr1,
1228+
},
1229+
"dev.main.me.coder.": {
1230+
addr2,
1231+
addr3,
1232+
},
1233+
"agent2.myws.me.coder.": {
1234+
addr4,
1235+
},
1236+
},
1237+
OnlyIPv6: true,
1238+
})
1239+
1240+
// WHEN: We replace the hosts with a new set
1241+
uut.setHosts(map[dnsname.FQDN][]netip.Addr{
1242+
"newagent.myws.me.coder.": {
1243+
addr4,
1244+
},
1245+
"newagent2.main.me.coder.": {
1246+
addr1,
1247+
},
1248+
})
1249+
1250+
// THEN: The engine is reconfigured with only the new hosts
1251+
_ = testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap)
1252+
req = testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
1253+
require.Equal(t, req.dnsCfg, &dns.Config{
1254+
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
1255+
CoderDNSSuffix: nil,
1256+
},
1257+
Hosts: map[dnsname.FQDN][]netip.Addr{
1258+
"newagent.myws.me.coder.": {
1259+
addr4,
1260+
},
1261+
"newagent2.main.me.coder.": {
1262+
addr1,
1263+
},
1264+
},
1265+
OnlyIPv6: true,
1266+
})
1267+
1268+
// WHEN: we remove all the hosts, and a bad host
1269+
uut.removeHosts(append(maps.Keys(req.dnsCfg.Hosts), "badhostname"))
1270+
_ = testutil.RequireRecvCtx(ctx, t, fEng.setNetworkMap)
1271+
req = testutil.RequireRecvCtx(ctx, t, fEng.reconfig)
1272+
1273+
// THEN: the engine is reconfigured with an empty config
1274+
require.Equal(t, req.dnsCfg, &dns.Config{})
1275+
1276+
done := make(chan struct{})
1277+
go func() {
1278+
defer close(done)
1279+
uut.close()
1280+
}()
1281+
_ = testutil.RequireRecvCtx(ctx, t, done)
1282+
}
1283+
11601284
func newTestNode(id int) *Node {
11611285
return &Node{
11621286
ID: tailcfg.NodeID(id),
@@ -1199,6 +1323,7 @@ func requireNeverConfigures(ctx context.Context, t *testing.T, uut *phased) {
11991323
type reconfigCall struct {
12001324
wg *wgcfg.Config
12011325
router *router.Config
1326+
dnsCfg *dns.Config
12021327
}
12031328

12041329
var _ engineConfigurable = &fakeEngineConfigurable{}
@@ -1235,8 +1360,8 @@ func (f fakeEngineConfigurable) SetNetworkMap(networkMap *netmap.NetworkMap) {
12351360
f.setNetworkMap <- networkMap
12361361
}
12371362

1238-
func (f fakeEngineConfigurable) Reconfig(wg *wgcfg.Config, r *router.Config, _ *dns.Config, _ *tailcfg.Debug) error {
1239-
f.reconfig <- reconfigCall{wg: wg, router: r}
1363+
func (f fakeEngineConfigurable) Reconfig(wg *wgcfg.Config, r *router.Config, dnsCfg *dns.Config, _ *tailcfg.Debug) error {
1364+
f.reconfig <- reconfigCall{wg: wg, router: r, dnsCfg: dnsCfg}
12401365
return nil
12411366
}
12421367

tailnet/conn.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
tslogger "tailscale.com/types/logger"
3434
"tailscale.com/types/netlogtype"
3535
"tailscale.com/types/netmap"
36+
"tailscale.com/util/dnsname"
3637
"tailscale.com/wgengine"
3738
"tailscale.com/wgengine/capture"
3839
"tailscale.com/wgengine/magicsock"
@@ -290,6 +291,7 @@ func NewConn(options *Options) (conn *Conn, err error) {
290291
configMaps: cfgMaps,
291292
nodeUpdater: nodeUp,
292293
telemetrySink: options.TelemetrySink,
294+
dnsConfigurator: options.DNSConfigurator,
293295
telemetryStore: telemetryStore,
294296
createdAt: time.Now(),
295297
watchCtx: ctx,
@@ -379,6 +381,12 @@ func (p ServicePrefix) RandomPrefix() netip.Prefix {
379381
return netip.PrefixFrom(p.RandomAddr(), 128)
380382
}
381383

384+
func (p ServicePrefix) AsNetip() netip.Prefix {
385+
out := [16]byte{}
386+
copy(out[:], p[:])
387+
return netip.PrefixFrom(netip.AddrFrom16(out), 48)
388+
}
389+
382390
// Conn is an actively listening Wireguard connection.
383391
type Conn struct {
384392
// Unique ID used for telemetry.
@@ -396,6 +404,7 @@ type Conn struct {
396404
wireguardMonitor *netmon.Monitor
397405
wireguardRouter *router.Config
398406
wireguardEngine wgengine.Engine
407+
dnsConfigurator dns.OSConfigurator
399408
listeners map[listenKey]*listener
400409
clientType proto.TelemetryEvent_ClientType
401410
createdAt time.Time
@@ -442,6 +451,31 @@ func (c *Conn) SetAddresses(ips []netip.Prefix) error {
442451
return nil
443452
}
444453

454+
func (c *Conn) AddDNSHosts(hosts map[dnsname.FQDN][]netip.Addr) error {
455+
if c.dnsConfigurator == nil {
456+
return xerrors.New("no DNSConfigurator set")
457+
}
458+
c.configMaps.addHosts(hosts)
459+
return nil
460+
}
461+
462+
func (c *Conn) RemoveDNSHosts(names []dnsname.FQDN) error {
463+
if c.dnsConfigurator == nil {
464+
return xerrors.New("no DNSConfigurator set")
465+
}
466+
c.configMaps.removeHosts(names)
467+
return nil
468+
}
469+
470+
// SetDNSHosts replaces the map of DNS hosts for the connection.
471+
func (c *Conn) SetDNSHosts(hosts map[dnsname.FQDN][]netip.Addr) error {
472+
if c.dnsConfigurator == nil {
473+
return xerrors.New("no DNSConfigurator set")
474+
}
475+
c.configMaps.setHosts(hosts)
476+
return nil
477+
}
478+
445479
func (c *Conn) SetNodeCallback(callback func(node *Node)) {
446480
c.nodeUpdater.setCallback(callback)
447481
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy