66"io"
77"maps"
88"math"
9+ "net/netip"
910"strings"
1011"sync"
1112"time"
@@ -15,6 +16,7 @@ import (
1516"storj.io/drpc"
1617"storj.io/drpc/drpcerr"
1718"tailscale.com/tailcfg"
19+ "tailscale.com/util/dnsname"
1820
1921"cdr.dev/slog"
2022"github.com/coder/coder/v2/codersdk"
@@ -104,6 +106,12 @@ type WorkspaceUpdatesController interface {
104106New (WorkspaceUpdatesClient )CloserWaiter
105107}
106108
109+ // DNSHostsSetter is something that you can set a mapping of DNS names to IPs on. It's the subset
110+ // of the tailnet.Conn that we use to configure DNS records.
111+ type DNSHostsSetter interface {
112+ SetDNSHosts (hosts map [dnsname.FQDN ][]netip.Addr )error
113+ }
114+
107115// ControlProtocolClients represents an abstract interface to the tailnet control plane via a set
108116// of protocol clients. The Closer should close all the clients (e.g. by closing the underlying
109117// connection).
@@ -835,8 +843,9 @@ func (r *basicResumeTokenRefresher) refresh() {
835843}
836844
837845type tunnelAllWorkspaceUpdatesController struct {
838- coordCtrl * TunnelSrcCoordController
839- logger slog.Logger
846+ coordCtrl * TunnelSrcCoordController
847+ dnsHostSetter DNSHostsSetter
848+ logger slog.Logger
840849}
841850
842851type workspace struct {
@@ -845,30 +854,48 @@ type workspace struct {
845854agents map [uuid.UUID ]agent
846855}
847856
857+ // addAllDNSNames adds names for all of its agents to the given map of names
858+ func (w workspace )addAllDNSNames (names map [dnsname.FQDN ][]netip.Addr )error {
859+ for _ ,a := range w .agents {
860+ // TODO: technically, DNS labels cannot start with numbers, but the rules are often not
861+ // strictly enforced.
862+ // TODO: support <agent>.<workspace>.<username>.coder
863+ fqdn ,err := dnsname .ToFQDN (fmt .Sprintf ("%s.%s.me.coder." ,a .name ,w .name ))
864+ if err != nil {
865+ return err
866+ }
867+ names [fqdn ]= []netip.Addr {CoderServicePrefix .AddrFromUUID (a .id )}
868+ }
869+ // TODO: Possibly support <workspace>.coder. alias if there is only one agent
870+ return nil
871+ }
872+
848873type agent struct {
849874id uuid.UUID
850875name string
851876}
852877
853878func (t * tunnelAllWorkspaceUpdatesController )New (client WorkspaceUpdatesClient )CloserWaiter {
854879updater := & tunnelUpdater {
855- client :client ,
856- errChan :make (chan error ,1 ),
857- logger :t .logger ,
858- coordCtrl :t .coordCtrl ,
859- recvLoopDone :make (chan struct {}),
860- workspaces :make (map [uuid.UUID ]* workspace ),
880+ client :client ,
881+ errChan :make (chan error ,1 ),
882+ logger :t .logger ,
883+ coordCtrl :t .coordCtrl ,
884+ dnsHostsSetter :t .dnsHostSetter ,
885+ recvLoopDone :make (chan struct {}),
886+ workspaces :make (map [uuid.UUID ]* workspace ),
861887}
862888go updater .recvLoop ()
863889return updater
864890}
865891
866892type tunnelUpdater struct {
867- errChan chan error
868- logger slog.Logger
869- client WorkspaceUpdatesClient
870- coordCtrl * TunnelSrcCoordController
871- recvLoopDone chan struct {}
893+ errChan chan error
894+ logger slog.Logger
895+ client WorkspaceUpdatesClient
896+ coordCtrl * TunnelSrcCoordController
897+ dnsHostsSetter DNSHostsSetter
898+ recvLoopDone chan struct {}
872899
873900// don't need the mutex since only manipulated by the recvLoop
874901workspaces map [uuid.UUID ]* workspace
@@ -991,6 +1018,16 @@ func (t *tunnelUpdater) handleUpdate(update *proto.WorkspaceUpdate) error {
9911018}
9921019allAgents := t .allAgentIDs ()
9931020t .coordCtrl .SyncDestinations (allAgents )
1021+ if t .dnsHostsSetter != nil {
1022+ t .logger .Debug (context .Background (),"updating dns hosts" )
1023+ dnsNames := t .allDNSNames ()
1024+ err := t .dnsHostsSetter .SetDNSHosts (dnsNames )
1025+ if err != nil {
1026+ return xerrors .Errorf ("failed to set DNS hosts: %w" ,err )
1027+ }
1028+ }else {
1029+ t .logger .Debug (context .Background (),"skipping setting DNS names because we have no setter" )
1030+ }
9941031return nil
9951032}
9961033
@@ -1035,10 +1072,30 @@ func (t *tunnelUpdater) allAgentIDs() []uuid.UUID {
10351072return out
10361073}
10371074
1075+ func (t * tunnelUpdater )allDNSNames ()map [dnsname.FQDN ][]netip.Addr {
1076+ names := make (map [dnsname.FQDN ][]netip.Addr )
1077+ for _ ,w := range t .workspaces {
1078+ err := w .addAllDNSNames (names )
1079+ if err != nil {
1080+ // This should never happen in production, because converting the FQDN only fails
1081+ // if names are too long, and we put strict length limits on agent, workspace, and user
1082+ // names.
1083+ t .logger .Critical (context .Background (),
1084+ "failed to include DNS name(s)" ,
1085+ slog .F ("workspace_id" ,w .id ),
1086+ slog .Error (err ))
1087+ }
1088+ }
1089+ return names
1090+ }
1091+
1092+ // NewTunnelAllWorkspaceUpdatesController creates a WorkspaceUpdatesController that creates tunnels
1093+ // (via the TunnelSrcCoordController) to all agents received over the WorkspaceUpdates RPC. If a
1094+ // DNSHostSetter is provided, it also programs DNS hosts based on the agent and workspace names.
10381095func NewTunnelAllWorkspaceUpdatesController (
1039- logger slog.Logger ,c * TunnelSrcCoordController ,
1096+ logger slog.Logger ,c * TunnelSrcCoordController ,d DNSHostsSetter ,
10401097)WorkspaceUpdatesController {
1041- return & tunnelAllWorkspaceUpdatesController {logger :logger ,coordCtrl :c }
1098+ return & tunnelAllWorkspaceUpdatesController {logger :logger ,coordCtrl :c , dnsHostSetter : d }
10421099}
10431100
10441101// NewController creates a new Controller without running it