A good software design principle is that of least surprise. Software should do what one can reasonably expect it to do in response to user actions and any configuration that has been made.

Another good design principle is to fail safely (or securely). If for some reason a program cannot perform a requested action, it should put itself, or the system, into a known-safe state. That state probably won’t be that which the user was seeking to achieve, but it should be one that does not cause the user to unexpectedly do anything dangerous or which might endanger the system further.

When used with VPN connections (both OpenVPN and Wireguard), the Linux NetworkManager tool unfortunately comes up horribly short in both areas.

In short: absent special configuration, DNS queries (and responses) can quite easily leak outside of the VPN tunnel to the DNS resolver provided by whatever network you are on. These DNS queries can allow whoever operates that DNS resolver to see the host names you are connecting to.

If you are on a trusted network, such as at home, while surprising, this is probably not a major issue.

If you are on an untrusted network, such as a public network at a café, an airport, or a hotel, where you might want to use a general-purpose VPN for traffic confidentiality, this can be a much larger issue.

By default, when connecting to a VPN, NetworkManager will combine the DNS servers provided by that network (either through fixed configuration or obtained dynamically via DHCP) with whatever resolver configuration existed previously, likely obtained when connecting to the network you are already on and connecting to the VPN through.

Consequently, if an attacker can disrupt the VPN traffic at the right moment, they can cause DNS requests to be made to the lower-priority DNS resolver: that on the local network, outside the VPN.

Additionally, if you mistype a host name, then it is possible that the search suffix can lead to a slight loss of anonymity against the operator of the DNS resolver that happens to be used.

Worse, there is no way to configure this behavior through the NetworkManager GUI.

Fortunately, it’s easy to configure through nmcli. Open a terminal window, and check the current settings:

$ nmcli con show "vpn connection name" | grep ipv.\.dns-priority

This will most likely show two lines of output, similar to:

ipv4.dns-priority:     50
ipv6.dns-priority:     50

(The exact value for both of these can vary.)

The actual encoding of the priority value is a little peculiar. In particular:

  • Lower values are considered higher priority
  • Negative values exclude configurations with higher values
  • DNS configurations obtained through networks with the same priority value are combined

The value itself is a signed 32-bit integer, so the valid range is -2147483647 through +2147483647.

To force only the DNS servers obtained through this particular connection to be used when this connection is active, use nmcli to modify the connection to set both of these to the largest negative value possible: -2147483647. This ensures that no more negative priority value can exist on a different connection, causing the DNS configuration for this particular connection to always have priority.

$ nmcli con modify "vpn connection name" +ipv4.dns-priority -2147483647
$ nmcli con modify "vpn connection name" +ipv6.dns-priority -2147483647

For OpenVPN connections, after modifying the connection in this manner, you will need to provide the VPN user’s password (not your local login password) when connecting to it the next time.

Note that if you have multiple connections with the same value for the respective dns-priority properties, and connect to those networks simultaneously, the configurations are combined. Therefore, you do not want to set this on any potentially untrusted network that you might be connected to at the same time as the VPN connection.

Having made the configuration change, connect and disconnect the VPN connection repeatedly and observe the effect on the system name resolver configuration in /etc/resolv.conf:

$ watch cat /etc/resolv.conf

If everything is working as intended, you will see the set of search and nameserver directives being replaced as you connect and disconnect the VPN connection, instead of amended.