Skip to content

Commit 871cc05

Browse files
chore: add a dns.OSConfigurator implementation that uses the CoderVPN protocol (#15342)
Closes #14733.
1 parent 076399b commit 871cc05

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

tailnet/conn.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"tailscale.com/envknob"
2323
"tailscale.com/ipn/ipnstate"
2424
"tailscale.com/net/connstats"
25+
"tailscale.com/net/dns"
2526
"tailscale.com/net/netmon"
2627
"tailscale.com/net/netns"
2728
"tailscale.com/net/tsdial"
@@ -106,6 +107,9 @@ type Options struct {
106107
ClientType proto.TelemetryEvent_ClientType
107108
// TelemetrySink is optional.
108109
TelemetrySink TelemetrySink
110+
// DNSConfigurator is optional, and is passed to the underlying wireguard
111+
// engine.
112+
DNSConfigurator dns.OSConfigurator
109113
}
110114

111115
// TelemetrySink allows tailnet.Conn to send network telemetry to the Coder
@@ -178,6 +182,7 @@ func NewConn(options *Options) (conn *Conn, err error) {
178182
Dialer: dialer,
179183
ListenPort: options.ListenPort,
180184
SetSubsystem: sys.Set,
185+
DNS: options.DNSConfigurator,
181186
})
182187
if err != nil {
183188
return nil, xerrors.Errorf("create wgengine: %w", err)

vpn/dns.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package vpn
2+
3+
import "tailscale.com/net/dns"
4+
5+
func NewDNSConfigurator(t *Tunnel) dns.OSConfigurator {
6+
return &dnsManager{tunnel: t}
7+
}
8+
9+
type dnsManager struct {
10+
tunnel *Tunnel
11+
}
12+
13+
func (v *dnsManager) SetDNS(cfg dns.OSConfig) error {
14+
settings := convertDNSConfig(cfg)
15+
return v.tunnel.ApplyNetworkSettings(v.tunnel.ctx, &NetworkSettingsRequest{
16+
DnsSettings: settings,
17+
})
18+
}
19+
20+
func (*dnsManager) GetBaseConfig() (dns.OSConfig, error) {
21+
// Tailscale calls this function to blend the OS's DNS configuration with
22+
// it's own, so this is only called if `SupportsSplitDNS` returns false.
23+
return dns.OSConfig{}, dns.ErrGetBaseConfigNotSupported
24+
}
25+
26+
func (*dnsManager) SupportsSplitDNS() bool {
27+
// macOS & Windows 10+ support split DNS, so we'll assume all CoderVPN
28+
// clients do too.
29+
return true
30+
}
31+
32+
// Close implements dns.OSConfigurator.
33+
func (*dnsManager) Close() error {
34+
// There's no cleanup that we need to initiate from within the dylib.
35+
return nil
36+
}
37+
38+
func convertDNSConfig(cfg dns.OSConfig) *NetworkSettingsRequest_DNSSettings {
39+
servers := make([]string, 0, len(cfg.Nameservers))
40+
for _, ns := range cfg.Nameservers {
41+
servers = append(servers, ns.String())
42+
}
43+
searchDomains := make([]string, 0, len(cfg.SearchDomains))
44+
for _, domain := range cfg.SearchDomains {
45+
searchDomains = append(searchDomains, domain.WithoutTrailingDot())
46+
}
47+
matchDomains := make([]string, 0, len(cfg.MatchDomains))
48+
for _, domain := range cfg.MatchDomains {
49+
matchDomains = append(matchDomains, domain.WithoutTrailingDot())
50+
}
51+
return &NetworkSettingsRequest_DNSSettings{
52+
Servers: servers,
53+
SearchDomains: searchDomains,
54+
DomainName: "coder",
55+
MatchDomains: matchDomains,
56+
MatchDomainsNoSearch: false,
57+
}
58+
}

vpn/dns_internal_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package vpn
2+
3+
import (
4+
"net/netip"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
"tailscale.com/net/dns"
9+
"tailscale.com/util/dnsname"
10+
)
11+
12+
func TestConvertDNSConfig(t *testing.T) {
13+
t.Parallel()
14+
15+
tests := []struct {
16+
name string
17+
input dns.OSConfig
18+
expected *NetworkSettingsRequest_DNSSettings
19+
}{
20+
{
21+
name: "Basic",
22+
input: dns.OSConfig{
23+
Nameservers: []netip.Addr{
24+
netip.MustParseAddr("1.1.1.1"),
25+
netip.MustParseAddr("8.8.8.8"),
26+
},
27+
SearchDomains: []dnsname.FQDN{
28+
"example.com.",
29+
"test.local.",
30+
},
31+
MatchDomains: []dnsname.FQDN{
32+
"internal.domain.",
33+
},
34+
},
35+
expected: &NetworkSettingsRequest_DNSSettings{
36+
Servers: []string{"1.1.1.1", "8.8.8.8"},
37+
SearchDomains: []string{"example.com", "test.local"},
38+
DomainName: "coder",
39+
MatchDomains: []string{"internal.domain"},
40+
MatchDomainsNoSearch: false,
41+
},
42+
},
43+
{
44+
name: "Empty",
45+
input: dns.OSConfig{
46+
Nameservers: []netip.Addr{},
47+
SearchDomains: []dnsname.FQDN{},
48+
MatchDomains: []dnsname.FQDN{},
49+
},
50+
expected: &NetworkSettingsRequest_DNSSettings{
51+
Servers: []string{},
52+
SearchDomains: []string{},
53+
DomainName: "coder",
54+
MatchDomains: []string{},
55+
MatchDomainsNoSearch: false,
56+
},
57+
},
58+
}
59+
60+
//nolint:paralleltest // outdated rule
61+
for _, tt := range tests {
62+
t.Run(tt.name, func(t *testing.T) {
63+
t.Parallel()
64+
65+
result := convertDNSConfig(tt.input)
66+
require.Equal(t, tt.expected.Servers, result.Servers)
67+
require.Equal(t, tt.expected.SearchDomains, result.SearchDomains)
68+
require.Equal(t, tt.expected.DomainName, result.DomainName)
69+
require.Equal(t, tt.expected.MatchDomains, result.MatchDomains)
70+
require.Equal(t, tt.expected.MatchDomainsNoSearch, result.MatchDomainsNoSearch)
71+
})
72+
}
73+
}

0 commit comments

Comments
 (0)