net/dns: support global resolvers in macOS tailscaled
The macOS tailscaled DNS configurator only wrote /etc/resolver files, which can express split DNS but not a primary resolver. Teach it to configure a global resolver through the SystemConfiguration dynamic store using scutil when OSConfig has nameservers and no match domains. Let non-sandboxed macOS tailscaled follow Linux split DNS behavior when its OS configurator reports split DNS support. This avoids synthesizing an upstream default route from the machine's base DNS when the netmap did not provide one. Keep iOS and sandboxed macOS app builds on the existing Apple base-config path because those use NetworkExtension DNS settings rather than tailscaled's /etc/resolver configurator. Add tests for switching between split and global DNS, including cleanup of stale Tailscale-managed resolver files, removal of the dynamic-store global DNS key, and preservation of the sandboxed macOS behavior. RELNOTE: tailscaled on macOS now supports configuring global DNS resolvers. Updates #1338 Change-Id: I9b2b61f89750a5529fc0add1cd37b1b9a355db12 Signed-off-by: Maisem Ali <maisem@bold.dev>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
b727675a8b
commit
10672a63f4
+56
-37
@@ -187,14 +187,15 @@ func TestManager(t *testing.T) {
|
||||
// reasonable to make this unsupported as well, in which case
|
||||
// these tests will need tweaking.
|
||||
tests := []struct {
|
||||
name string
|
||||
in Config
|
||||
split bool
|
||||
bs OSConfig
|
||||
os OSConfig
|
||||
knobs *controlknobs.Knobs
|
||||
rs resolver.Config
|
||||
goos string // empty means "linux"
|
||||
name string
|
||||
in Config
|
||||
split bool
|
||||
bs OSConfig
|
||||
os OSConfig
|
||||
knobs *controlknobs.Knobs
|
||||
rs resolver.Config
|
||||
goos string // empty means "linux"
|
||||
sandboxedMacOS bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -450,6 +451,32 @@ func TestManager(t *testing.T) {
|
||||
MatchDomains: fqdns("corp.com"),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Sandboxed macOS app builds use NetworkExtension DNS settings, not
|
||||
// tailscaled's /etc/resolver configurator, so they keep the older
|
||||
// Apple base-config behavior.
|
||||
name: "routes-split-sandboxed-darwin",
|
||||
in: Config{
|
||||
Routes: upstreams("corp.com", "2.2.2.2"),
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
},
|
||||
split: true,
|
||||
bs: OSConfig{
|
||||
Nameservers: mustIPs("8.8.8.8"),
|
||||
SearchDomains: fqdns("coffee.shop"),
|
||||
},
|
||||
os: OSConfig{
|
||||
Nameservers: serviceAddr46,
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf", "coffee.shop"),
|
||||
},
|
||||
rs: resolver.Config{
|
||||
Routes: upstreams(
|
||||
".", "8.8.8.8",
|
||||
"corp.com.", "2.2.2.2"),
|
||||
},
|
||||
goos: "darwin",
|
||||
sandboxedMacOS: true,
|
||||
},
|
||||
{
|
||||
name: "routes-multi",
|
||||
in: Config{
|
||||
@@ -495,25 +522,23 @@ func TestManager(t *testing.T) {
|
||||
goos: "linux",
|
||||
},
|
||||
{
|
||||
// The `routes-multi-split-linux` test case above on Darwin should NOT result in a split
|
||||
// DNS configuration.
|
||||
// Check that MatchDomains is empty. Due to Apple limitations, we cannot set MatchDomains
|
||||
// without those domains also being SearchDomains.
|
||||
name: "routes-multi-does-not-split-on-darwin",
|
||||
// The `routes-multi-split-linux` test case above should match on
|
||||
// macOS, where tailscaled configures split DNS via /etc/resolver.
|
||||
name: "routes-multi-split-darwin",
|
||||
in: Config{
|
||||
Routes: upstreams(
|
||||
"corp.com", "2.2.2.2",
|
||||
"bigco.net", "3.3.3.3"),
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
},
|
||||
split: false,
|
||||
split: true,
|
||||
os: OSConfig{
|
||||
Nameservers: serviceAddr46,
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
MatchDomains: fqdns("bigco.net", "corp.com"),
|
||||
},
|
||||
rs: resolver.Config{
|
||||
Routes: upstreams(
|
||||
".", "",
|
||||
"corp.com.", "2.2.2.2",
|
||||
"bigco.net.", "3.3.3.3"),
|
||||
},
|
||||
@@ -593,10 +618,9 @@ func TestManager(t *testing.T) {
|
||||
goos: "linux",
|
||||
},
|
||||
{
|
||||
// The `magic-split` test case above on Darwin should NOT result in a split DNS configuration.
|
||||
// Check that MatchDomains is empty. Due to Apple limitations, we cannot set MatchDomains
|
||||
// without those domains also being SearchDomains.
|
||||
name: "magic-split-does-not-split-on-darwin",
|
||||
// The `magic-split` test case above should match on macOS, where
|
||||
// tailscaled configures split DNS via /etc/resolver.
|
||||
name: "magic-split-darwin",
|
||||
in: Config{
|
||||
Hosts: hosts(
|
||||
"dave.ts.com.", "1.2.3.4",
|
||||
@@ -604,13 +628,13 @@ func TestManager(t *testing.T) {
|
||||
Routes: upstreams("ts.com", ""),
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
},
|
||||
split: false,
|
||||
split: true,
|
||||
os: OSConfig{
|
||||
Nameservers: serviceAddr46,
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
MatchDomains: fqdns("ts.com"),
|
||||
},
|
||||
rs: resolver.Config{
|
||||
Routes: upstreams(".", ""),
|
||||
Hosts: hosts(
|
||||
"dave.ts.com.", "1.2.3.4",
|
||||
"bradfitz.ts.com.", "2.3.4.5"),
|
||||
@@ -698,11 +722,9 @@ func TestManager(t *testing.T) {
|
||||
goos: "linux",
|
||||
},
|
||||
{
|
||||
// The `routes-magic-split-linux` test case above on Darwin should NOT result in a
|
||||
// split DNS configuration.
|
||||
// Check that MatchDomains is empty. Due to Apple limitations, we cannot set MatchDomains
|
||||
// without those domains also being SearchDomains.
|
||||
name: "routes-magic-does-not-split-on-darwin",
|
||||
// The `routes-magic-split-linux` test case above should match on
|
||||
// macOS, where tailscaled configures split DNS via /etc/resolver.
|
||||
name: "routes-magic-split-darwin",
|
||||
in: Config{
|
||||
Routes: upstreams(
|
||||
"corp.com", "2.2.2.2",
|
||||
@@ -716,12 +738,10 @@ func TestManager(t *testing.T) {
|
||||
os: OSConfig{
|
||||
Nameservers: serviceAddr46,
|
||||
SearchDomains: fqdns("tailscale.com", "universe.tf"),
|
||||
MatchDomains: fqdns("corp.com", "ts.com"),
|
||||
},
|
||||
rs: resolver.Config{
|
||||
Routes: upstreams(
|
||||
".", "",
|
||||
"corp.com.", "2.2.2.2",
|
||||
),
|
||||
Routes: upstreams("corp.com.", "2.2.2.2"),
|
||||
Hosts: hosts(
|
||||
"dave.ts.com.", "1.2.3.4",
|
||||
"bradfitz.ts.com.", "2.3.4.5"),
|
||||
@@ -866,9 +886,9 @@ func TestManager(t *testing.T) {
|
||||
goos: "ios",
|
||||
},
|
||||
{
|
||||
// on darwin, verify that with the same config as in ios-use-split-dns-when-no-custom-resolvers,
|
||||
// MatchDomains are NOT set.
|
||||
name: "darwin-dont-use-split-dns-when-no-custom-resolvers",
|
||||
// macOS should match Linux here. iOS remains special-cased above
|
||||
// for battery-life behavior.
|
||||
name: "darwin-use-split-dns-when-no-custom-resolvers",
|
||||
in: Config{
|
||||
Routes: upstreams("ts.net", "199.247.155.52", "optimistic-display.ts.net", ""),
|
||||
SearchDomains: fqdns("optimistic-display.ts.net"),
|
||||
@@ -877,12 +897,10 @@ func TestManager(t *testing.T) {
|
||||
os: OSConfig{
|
||||
Nameservers: serviceAddr46,
|
||||
SearchDomains: fqdns("optimistic-display.ts.net"),
|
||||
MatchDomains: fqdns("optimistic-display.ts.net", "ts.net"),
|
||||
},
|
||||
rs: resolver.Config{
|
||||
Routes: upstreams(
|
||||
".", "",
|
||||
"ts.net", "199.247.155.52",
|
||||
),
|
||||
Routes: upstreams("ts.net", "199.247.155.52"),
|
||||
LocalDomains: fqdns("optimistic-display.ts.net."),
|
||||
},
|
||||
goos: "darwin",
|
||||
@@ -986,6 +1004,7 @@ func TestManager(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
tstest.Replace(t, &isSandboxedMacOS, func() bool { return test.sandboxedMacOS })
|
||||
f := fakeOSConfigurator{
|
||||
SplitDNS: test.split,
|
||||
BaseConfig: test.bs,
|
||||
|
||||
Reference in New Issue
Block a user