Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e4c3c63

Browse files
committed
chore: support adding dns hosts to tailnet.Conn
1 parent f7cbf5d commit e4c3c63

File tree

3 files changed

+223
-6
lines changed

3 files changed

+223
-6
lines changed

tailnet/configmaps.go

+62-4
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

+127-2
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.UUID{1})
1176+
addr2 := CoderServicePrefix.AddrFromUUID(uuid.UUID{2})
1177+
addr3 := CoderServicePrefix.AddrFromUUID(uuid.UUID{3})
1178+
addr4 := CoderServicePrefix.AddrFromUUID(uuid.UUID{4})
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

+34
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)