DNS settings stay after disconnecting from WireGuard mode on Mac

On MacOS, when the client is connected to the server in WireGuard mode and disconnects, sometimes tainted DNS configuration remains on the client’s host. Depending on the configuration that remains, this can prevent the host from resolving any DNS query while disconnected from the VPN. Connecting in OpenVPN mode does not suffer from the same issue.

Here’s how I reproduce the issue and what I see in scutil and networksetup output.

How to reproduce

I’ve done my best to document a method for reproducing the issue we are seeing.
The method doesn’t work every time, I suspect because the root cause of the
issue is a race condition.

0. Initial state

Disconnected from VPN. Connected to WiFi. All client settings are at the default
(all toggles are off).

$ networksetup -getdnsservers Wi-Fi
There aren't any DNS Servers set on Wi-Fi.
$ sudo scutil --dns
DNS configuration

resolver #1
  search domain[0] : home
  nameserver[0] : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
  nameserver[1] : 192.168.1.1
  if_index : 14 (en0)
  flags    : Request A records, Request AAAA records
  reach    : 0x00020002 (Reachable,Directly Reachable Address)

Removed irrelevant resolvers (.local and .arpa domains).

DNS configuration (for scoped queries)

resolver #1
  search domain[0] : home
  nameserver[0] : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
  nameserver[1] : 192.168.1.1
  if_index : 14 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00020002 (Reachable,Directly Reachable Address)
$ sudo scutil
> open
> show State:/Network/Global/DNS
<dictionary> {
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
    1 : 192.168.1.1
  }
  __CONFIGURATION_ID__ : Default: 0
  __FLAGS__ : 6
  __IF_INDEX__ : 14
  __ORDER__ : 0
}
show State:/Network/Global/IPv4
<dictionary> {
  PrimaryInterface : en0
  PrimaryService : 1692E22E-ADBB-4FB7-8349-72BE1809C202
  Router : 192.168.1.1
}
> show State:/Network/Service/1692E22E-ADBB-4FB7-8349-72BE1809C202/DNS
<dictionary> {
  DomainName : home
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 192.168.1.1
    1 : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
  }
}
> list .*Pritunl.*
  no keys.
> quit

1. Connected to VPN

Connected to VPN in WireGuard mode. Connected to WiFi. DNS resolution works as expected.

$ networksetup -getdnsservers Wi-Fi
10.254.0.7
10.254.1.9
10.254.2.12
8.8.8.8
$ sudo scutil --dns
DNS configuration

resolver #1
  search domain[0] : home
  nameserver[0] : 10.254.0.7
  nameserver[1] : 10.254.1.9
  nameserver[2] : 10.254.2.12
  nameserver[3] : 8.8.8.8
  flags    : Request A records, Request AAAA records
  reach    : 0x00000002 (Reachable)

Removed irrelevant resolvers (.local and .arpa domains).

DNS configuration (for scoped queries)

resolver #1
  search domain[0] : home
  nameserver[0] : 10.254.0.7
  nameserver[1] : 10.254.1.9
  nameserver[2] : 10.254.2.12
  nameserver[3] : 8.8.8.8
  if_index : 14 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00000002 (Reachable)
$ sudo scutil
> open
> show State:/Network/Global/DNS
<dictionary> {
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 10.254.0.7
    1 : 10.254.1.9
    2 : 10.254.2.12
    3 : 8.8.8.8
  }
  __CONFIGURATION_ID__ : Default: 0
  __FLAGS__ : 6
  __ORDER__ : 0
}
> show State:/Network/Service/1692E22E-ADBB-4FB7-8349-72BE1809C202/DNS
<dictionary> {
  DomainName : home
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 192.168.1.1
    1 : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
  }
}
> show Setup:/Network/Service/1692E22E-ADBB-4FB7-8349-72BE1809C202/DNS
<dictionary> {
  ServerAddresses : <array> {
    0 : 10.254.0.7
    1 : 10.254.1.9
    2 : 10.254.2.12
    3 : 8.8.8.8
  }
}
> list .*Pritunl.*
  no keys.
> quit

2. Disconnected from VPN uncleanly

We noticed the issue happened often when users closed their laptop and went home. We found that we could reproduce the issue by toggling WiFi and the VPN connection in quick succession. We eventually end up in a state where the client is disconnected from the VPN, but the VPN’s DNS configuration remains.

The Pritunl service attempts to restore DNS (watch DNS is enabled on the client) to what it was before but fails. It looks like this is because the State:/Network/Global/IPv4 scutil key does not exist when the host is disconnected from WiFi. The service tries twice and then falls back to “Reseting networking”, which doesn’t solve the issue.

At this point, DNS resolution is broken. How hard it’s broken depends on the DNS servers that were configured by the VPN. If they’re not reachable, DNS resolution will fail. If they’re reachable, DNS resolution may work.

$ networksetup -getdnsservers Wi-Fi
10.254.0.7
10.254.1.9
10.254.2.12
8.8.8.8
$ sudo scutil --dns
DNS configuration

resolver #1
  search domain[0] : home
  nameserver[0] : 10.254.0.7
  nameserver[1] : 10.254.1.9
  nameserver[2] : 10.254.2.12
  nameserver[3] : 8.8.8.8
  flags    : Request A records, Request AAAA records
  reach    : 0x00000002 (Reachable)

Removed irrelevant resolvers (.local and .arpa domains).

DNS configuration (for scoped queries)

resolver #1
  search domain[0] : home
  nameserver[0] : 10.254.0.7
  nameserver[1] : 10.254.1.9
  nameserver[2] : 10.254.2.12
  nameserver[3] : 8.8.8.8
  if_index : 14 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00000002 (Reachable)
$ sudo scutil
> open
> show State:/Network/Global/DNS
<dictionary> {
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 10.254.0.7
    1 : 10.254.1.9
    2 : 10.254.2.12
    3 : 8.8.8.8
  }
  __CONFIGURATION_ID__ : Default: 0
  __FLAGS__ : 6
  __ORDER__ : 0
}
> show State:/Network/Service/1692E22E-ADBB-4FB7-8349-72BE1809C202/DNS
<dictionary> {
  DomainName : home
  SearchDomains : <array> {
    0 : home
  }
  ServerAddresses : <array> {
    0 : 192.168.1.1
    1 : 2a01:cb00:534:c600:d6f8:29ff:fe44:8bf0
  }
}
> show Setup:/Network/Service/1692E22E-ADBB-4FB7-8349-72BE1809C202/DNS
<dictionary> {
  ServerAddresses : <array> {
    0 : 10.254.0.7
    1 : 10.254.1.9
    2 : 10.254.2.12
    3 : 8.8.8.8
  }
}
> list .*Pritunl.*
  no keys.
> quit

Investigation

I did my best to investigate the issue. Here is what I found.

The Pritunl service’s logs:

[2023-12-04 13:14:18][INFO] ▶ profile: Disconnecting ◆ profile_id="bf4fc47f6dd108b5"
[2023-12-04 13:14:18][INFO] ▶ profile: Disconnected ◆ profile_id="bf4fc47f6dd108b5"
[2023-12-04 13:14:20][INFO] ▶ utils: Restore DNS
[2023-12-04 13:14:43][ERRO] ▶ utils: Failed to find primary service from scutil ◆ output="No such key"
[2023-12-04 13:14:43][WARN] ▶ watch: Failed to restore DNS
utils: Failed to find primary service from scutil
ORIGINAL STACK TRACE:
github.com/pritunl/pritunl-client-electron/service/utils.GetScutilService
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/utils/utils.go:343 +0x102689de0
github.com/pritunl/pritunl-client-electron/service/utils.RestoreScutilDns
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/utils/utils.go:365 +0x102689f23
github.com/pritunl/pritunl-client-electron/service/watch.dnsWatch
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/watch/watch.go:162 +0x10276b317
runtime.goexit
	/opt/homebrew/Cellar/go/1.21.3/libexec/src/runtime/asm_arm64.s:1197 +0x102238593
[2023-12-04 13:14:48][INFO] ▶ utils: Restore DNS
[2023-12-04 13:15:11][ERRO] ▶ utils: Failed to find primary service from scutil ◆ output="No such key"
[2023-12-04 13:15:11][ERRO] ▶ watch: Failed to restore DNS, resetting network
utils: Failed to find primary service from scutil
ORIGINAL STACK TRACE:
github.com/pritunl/pritunl-client-electron/service/utils.GetScutilService
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/utils/utils.go:343 +0x102689de0
github.com/pritunl/pritunl-client-electron/service/utils.RestoreScutilDns
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/utils/utils.go:365 +0x102689f23
github.com/pritunl/pritunl-client-electron/service/watch.dnsWatch
	/Users/apple/go/src/github.com/pritunl/pritunl-client-electron/service/watch/watch.go:162 +0x10276b317
runtime.goexit
	/opt/homebrew/Cellar/go/1.21.3/libexec/src/runtime/asm_arm64.s:1197 +0x102238593
[2023-12-04 13:15:11][INFO] ▶ utils: Reseting networking

Shutting Pritunl down, uninstalling it, and rebooting the host do not resolve the issue.

Using the client’s “Reset networking” button resets the DNS configuration to how it was before the issue happened. This lead me to the client’s utils.ClearDns function. The function uses networksetup to clear DNS from all network devices. I discovered that when the client disconnects from the VPN uncleanly, some network services keep the DNS configuration set by the client:

# Only a sample of the network services on the host.
$ networksetup -getdnsservers Wi-Fi
10.254.0.7
10.254.1.9
10.254.2.12
8.8.8.8
$ networksetup -getdnsservers "USB 10/100 LAN"
10.254.0.7
10.254.1.9
10.254.2.12
8.8.8.8
$ networksetup -getdnsservers "Thunderbolt Bridge"
There aren't any DNS Servers set on Thunderbolt Bridge.

When the VPN disconnects, it usually resets the DNS servers of all network services. When it does not, the DNS servers left behind lead to the corrupt DNS configuration we observe.

There seems to be a race condition somewhere. I tried to look at the client code to understand where, but I couldn’t figure out how the client handled DNS differently for OpenVPN and WireGuard modes.

Once the issue has happened, Pritunl can interract with the tainted DNS configuration when using other modes or profiles. This leads to very weird network behavior. For instance, if connected to another profile in OpenVPN mode, Pritunl’s “DNS Watch” feature will fight with the system and the host will use the corrupt DNS settings for a few seconds before Pritunl changes them back. Pritunl will also store the corrupt DNS settings in its Restore scutil key, so it will put the settings back when the user disconnects.

May be related to [DNS setting stays on WiFi after disconnect Mac Client](DNS setting stays on WiFi after disconnect Mac Client).

The newer releases of the client have an option to use the same DNS code as the OpenVPN connections. This is done by enabling the WireGuard DNS watch option in the advanced settings.