commit
665488ff4a
@ -0,0 +1,360 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package com.tailscale.ipn;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.DhcpInfo;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Tailscale DNS Config retrieval
|
||||
//
|
||||
// Tailscale's DNS support can either override the local DNS servers with a set of servers
|
||||
// configured in the admin panel, or supplement the local DNS servers with additional
|
||||
// servers for specific domains like example.com.beta.tailscale.net. In the non-override mode,
|
||||
// we need to retrieve the current set of DNS servers from the platform. These will typically
|
||||
// be the DNS servers received from DHCP.
|
||||
//
|
||||
// Importantly, after the Tailscale VPN comes up it will set a DNS server of 100.100.100.100
|
||||
// but we still want to retrieve the underlying DNS servers received from DHCP. If we roam
|
||||
// from Wi-Fi to LTE, we want the DNS servers received from LTE.
|
||||
//
|
||||
// --------------------- Android 7 and later -----------------------------------------
|
||||
//
|
||||
// ## getDnsConfigFromLinkProperties
|
||||
// Android provides a getAllNetworks interface in the ConnectivityManager. We walk through
|
||||
// each interface to pick the most appropriate one.
|
||||
// - If there is an Ethernet interface active we use that.
|
||||
// - If Wi-Fi is active we use that.
|
||||
// - If LTE is active we use that.
|
||||
// - We never use a VPN's DNS servers. That VPN is likely us. Even if not us, Android
|
||||
// only allows one VPN at a time so a different VPN's DNS servers won't be available
|
||||
// once Tailscale comes up.
|
||||
//
|
||||
// getAllNetworks() is used as the sole mechanism for retrieving the DNS config with
|
||||
// Android 7 and later.
|
||||
//
|
||||
// --------------------- Releases older than Android 7 -------------------------------
|
||||
//
|
||||
// We support Tailscale back to Android 5. Android versions 5 and 6 supply a getAllNetworks()
|
||||
// implementation but it always returns an empty list.
|
||||
//
|
||||
// ## getDnsConfigFromLinkProperties with getActiveNetwork
|
||||
// ConnectivityManager also supports a getActiveNetwork() routine, which Android 5 and 6 do
|
||||
// return a value for. If Tailscale isn't up yet and we can get the Wi-Fi/LTE/etc DNS
|
||||
// config using getActiveNetwork(), we use that.
|
||||
//
|
||||
// Once Tailscale is up, getActiveNetwork() returns tailscale0 with DNS server 100.100.100.100
|
||||
// and that isn't useful. So we try two other mechanisms:
|
||||
//
|
||||
// ## getDnsServersFromSystemProperties
|
||||
// Android versions prior to 8 let us retrieve the actual system DNS servers from properties.
|
||||
// Later Android versions removed the properties and only return an empty string.
|
||||
//
|
||||
// We check the net.dns1 - net.dns4 DNS servers. If Tailscale is up the DNS server will be
|
||||
// 100.100.100.100, which isn't useful, but if we get something different we'll use that.
|
||||
//
|
||||
// getDnsServersFromSystemProperties can only retrieve the IPv4 or IPv6 addresses of the
|
||||
// configured DNS servers. We also want to know the DNS Search Domains configured, but
|
||||
// we have no way to retrieve this using these interfaces. We return an empty list of
|
||||
// search domains. Sorry.
|
||||
//
|
||||
// ## getDnsServersFromNetworkInfo
|
||||
// ConnectivityManager supports an older API called getActiveNetworkInfo to return the
|
||||
// active network interface. It doesn't handle VPNs, so the interface will always be Wi-Fi
|
||||
// or Cellular even if Tailscale is up.
|
||||
//
|
||||
// For Wi-Fi interfaces we retrieve the DHCP response from the WifiManager. For Cellular
|
||||
// interfaces we check for properties populated by most of the radio drivers.
|
||||
//
|
||||
// getDnsServersFromNetworkInfo does not have a way to retrieve the DNS Search Domains,
|
||||
// so we return an empty list. Additionally, these interfaces are so old that they only
|
||||
// support IPv4. We can't retrieve IPv6 DNS server addresses this way.
|
||||
|
||||
public class DnsConfig {
|
||||
private Context ctx;
|
||||
|
||||
public DnsConfig(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
// getDnsConfigAsString returns the current DNS configuration as a multiline string:
|
||||
// line[0] DNS server addresses separated by spaces
|
||||
// line[1] search domains separated by spaces
|
||||
//
|
||||
// For example:
|
||||
// 8.8.8.8 8.8.4.4
|
||||
// example.com
|
||||
//
|
||||
// an empty string means the current DNS configuration could not be retrieved.
|
||||
String getDnsConfigAsString() {
|
||||
String s = getDnsConfigFromLinkProperties();
|
||||
if (!s.trim().isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
// If ConnectivityManager.getAllNetworks() works, it is the
|
||||
// authoritative mechanism and we rely on it. The other methods
|
||||
// which follow involve more compromises.
|
||||
return "";
|
||||
}
|
||||
|
||||
s = getDnsServersFromSystemProperties();
|
||||
if (!s.trim().isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
return getDnsServersFromNetworkInfo();
|
||||
}
|
||||
|
||||
// getDnsConfigFromLinkProperties finds the DNS servers for each Network interface
|
||||
// returned by ConnectivityManager getAllNetworks().LinkProperties, and return the
|
||||
// one that (heuristically) would be the primary DNS servers.
|
||||
//
|
||||
// on a Nexus 4 with Android 5.1 on wifi: 2602:248:7b4a:ff60::1 10.1.10.1
|
||||
// on a Nexus 7 with Android 6.0 on wifi: 2602:248:7b4a:ff60::1 10.1.10.1
|
||||
// on a Pixel 3a with Android 12.0 on wifi: 2602:248:7b4a:ff60::1 10.1.10.1\nlocaldomain
|
||||
// on a Pixel 3a with Android 12.0 on LTE: fd00:976a::9 fd00:976a::10
|
||||
//
|
||||
// One odd behavior noted on Pixel3a with Android 12:
|
||||
// With Wi-Fi already connected, starting Tailscale returned DNS servers 2602:248:7b4a:ff60::1 10.1.10.1
|
||||
// Turning off Wi-Fi and connecting LTE returned DNS servers fd00:976a::9 fd00:976a::10.
|
||||
// Turning Wi-Fi back on return DNS servers: 10.1.10.1. The IPv6 DNS server is gone.
|
||||
// This appears to be the ConnectivityManager behavior, not something we are doing.
|
||||
//
|
||||
// This implementation can work through Android 12 (SDK 30). In SDK 31 the
|
||||
// getAllNetworks() method is deprecated and we'll need to implement a
|
||||
// android.net.ConnectivityManager.NetworkCallback instead to monitor
|
||||
// link changes and track which DNS server to use.
|
||||
String getDnsConfigFromLinkProperties() {
|
||||
ConnectivityManager cMgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cMgr == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Network[] networks = cMgr.getAllNetworks();
|
||||
if (networks == null) {
|
||||
// Android 6 and before often returns an empty list, but we
|
||||
// can try again with just the active network.
|
||||
//
|
||||
// Once Tailscale is connected, the active network will be Tailscale
|
||||
// which will have 100.100.100.100 for its DNS server. We reject
|
||||
// TYPE_VPN in getPreferabilityForNetwork, so it won't be returned.
|
||||
Network active = cMgr.getActiveNetwork();
|
||||
if (active == null) {
|
||||
return "";
|
||||
}
|
||||
networks = new Network[]{active};
|
||||
}
|
||||
|
||||
// getPreferabilityForNetwork returns an index into dnsConfigs from 0-3.
|
||||
String[] dnsConfigs = new String[]{"", "", "", ""};
|
||||
for (Network network : networks) {
|
||||
int idx = getPreferabilityForNetwork(cMgr, network);
|
||||
if ((idx < 0) || (idx > 3)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LinkProperties linkProp = cMgr.getLinkProperties(network);
|
||||
NetworkCapabilities nc = cMgr.getNetworkCapabilities(network);
|
||||
List<InetAddress> dnsList = linkProp.getDnsServers();
|
||||
StringBuilder sb = new StringBuilder("");
|
||||
for (InetAddress ip : dnsList) {
|
||||
sb.append(ip.getHostAddress() + " ");
|
||||
}
|
||||
|
||||
String d = linkProp.getDomains();
|
||||
if (d != null) {
|
||||
sb.append("\n");
|
||||
sb.append(d);
|
||||
}
|
||||
|
||||
dnsConfigs[idx] = sb.toString();
|
||||
}
|
||||
|
||||
// return the lowest index DNS config which exists. If an Ethernet config
|
||||
// was found, return it. Otherwise if Wi-fi was found, return it. Etc.
|
||||
for (String s : dnsConfigs) {
|
||||
if (!s.trim().isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// getDnsServersFromSystemProperties returns DNS servers found in system properties.
|
||||
// On Android versions prior to Android 8, we can directly query the DNS
|
||||
// servers the system is using. More recent Android releases return empty strings.
|
||||
//
|
||||
// Once Tailscale is connected these properties will return 100.100.100.100, which we
|
||||
// suppress.
|
||||
//
|
||||
// on a Nexus 4 with Android 5.1 on wifi: 2602:248:7b4a:ff60::1 10.1.10.1
|
||||
// on a Nexus 7 with Android 6.0 on wifi: 2602:248:7b4a:ff60::1 10.1.10.1
|
||||
// on a Pixel 3a with Android 12.0 on wifi:
|
||||
// on a Pixel 3a with Android 12.0 on LTE:
|
||||
//
|
||||
// The list of DNS search domains does not appear to be available in system properties.
|
||||
String getDnsServersFromSystemProperties() {
|
||||
try {
|
||||
Class SystemProperties = Class.forName("android.os.SystemProperties");
|
||||
Method method = SystemProperties.getMethod("get", String.class);
|
||||
List<String> servers = new ArrayList<String>();
|
||||
for (String name : new String[]{"net.dns1", "net.dns2", "net.dns3", "net.dns4"}) {
|
||||
String value = (String) method.invoke(null, name);
|
||||
if (value != null && !value.isEmpty() &&
|
||||
!value.equals("100.100.100.100") &&
|
||||
!servers.contains(value)) {
|
||||
servers.add(value);
|
||||
}
|
||||
}
|
||||
return String.join(" ", servers);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String intToInetString(int hostAddress) {
|
||||
return String.format("%d.%d.%d.%d",
|
||||
(byte)(0xff & hostAddress),
|
||||
(byte)(0xff & (hostAddress >> 8)),
|
||||
(byte)(0xff & (hostAddress >> 16)),
|
||||
(byte)(0xff & (hostAddress >> 24)));
|
||||
}
|
||||
|
||||
// getDnsServersFromNetworkInfo retrieves DNS servers using ConnectivityManager
|
||||
// getActiveNetworkInfo() plus interface-specific mechanisms to retrieve the DNS servers.
|
||||
// Only IPv4 DNS servers are supported by this mechanism, neither the WifiManager nor the
|
||||
// interface-specific dns properties appear to populate IPv6 DNS server addresses.
|
||||
//
|
||||
// on a Nexus 4 with Android 5.1 on wifi: 10.1.10.1
|
||||
// on a Nexus 7 with Android 6.0 on wifi: 10.1.10.1
|
||||
// on a Pixel-3a with Android 12.0 on wifi: 10.1.10.1
|
||||
// on a Pixel-3a with Android 12.0 on LTE:
|
||||
//
|
||||
// The list of DNS search domains is not available in this way.
|
||||
String getDnsServersFromNetworkInfo() {
|
||||
ConnectivityManager cMgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cMgr == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
NetworkInfo info = cMgr.getActiveNetworkInfo();
|
||||
if (info == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Class SystemProperties;
|
||||
Method method;
|
||||
|
||||
try {
|
||||
SystemProperties = Class.forName("android.os.SystemProperties");
|
||||
method = SystemProperties.getMethod("get", String.class);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> servers = new ArrayList<String>();
|
||||
|
||||
switch(info.getType()) {
|
||||
case ConnectivityManager.TYPE_WIFI:
|
||||
case ConnectivityManager.TYPE_WIMAX:
|
||||
for (String name : new String[]{
|
||||
"net.wifi0.dns1", "net.wifi0.dns2", "net.wifi0.dns3", "net.wifi0.dns4",
|
||||
"net.wlan0.dns1", "net.wlan0.dns2", "net.wlan0.dns3", "net.wlan0.dns4",
|
||||
"net.eth0.dns1", "net.eth0.dns2", "net.eth0.dns3", "net.eth0.dns4",
|
||||
"dhcp.wlan0.dns1", "dhcp.wlan0.dns2", "dhcp.wlan0.dns3", "dhcp.wlan0.dns4",
|
||||
"dhcp.tiwlan0.dns1", "dhcp.tiwlan0.dns2", "dhcp.tiwlan0.dns3", "dhcp.tiwlan0.dns4"}) {
|
||||
try {
|
||||
String value = (String) method.invoke(null, name);
|
||||
if (value != null && !value.isEmpty() && !servers.contains(value)) {
|
||||
servers.add(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
WifiManager wMgr = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
|
||||
if (wMgr != null) {
|
||||
DhcpInfo dhcp = wMgr.getDhcpInfo();
|
||||
if (dhcp.dns1 != 0) {
|
||||
String value = intToInetString(dhcp.dns1);
|
||||
if (value != null && !value.isEmpty() && !servers.contains(value)) {
|
||||
servers.add(value);
|
||||
}
|
||||
}
|
||||
if (dhcp.dns2 != 0) {
|
||||
String value = intToInetString(dhcp.dns2);
|
||||
if (value != null && !value.isEmpty() && !servers.contains(value)) {
|
||||
servers.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.join(" ", servers);
|
||||
case ConnectivityManager.TYPE_MOBILE:
|
||||
case ConnectivityManager.TYPE_MOBILE_HIPRI:
|
||||
for (String name : new String[]{
|
||||
"net.rmnet0.dns1", "net.rmnet0.dns2", "net.rmnet0.dns3", "net.rmnet0.dns4",
|
||||
"net.rmnet1.dns1", "net.rmnet1.dns2", "net.rmnet1.dns3", "net.rmnet1.dns4",
|
||||
"net.rmnet2.dns1", "net.rmnet2.dns2", "net.rmnet2.dns3", "net.rmnet2.dns4",
|
||||
"net.rmnet3.dns1", "net.rmnet3.dns2", "net.rmnet3.dns3", "net.rmnet3.dns4",
|
||||
"net.rmnet4.dns1", "net.rmnet4.dns2", "net.rmnet4.dns3", "net.rmnet4.dns4",
|
||||
"net.rmnet5.dns1", "net.rmnet5.dns2", "net.rmnet5.dns3", "net.rmnet5.dns4",
|
||||
"net.rmnet6.dns1", "net.rmnet6.dns2", "net.rmnet6.dns3", "net.rmnet6.dns4",
|
||||
"net.rmnet7.dns1", "net.rmnet7.dns2", "net.rmnet7.dns3", "net.rmnet7.dns4",
|
||||
"net.pdp0.dns1", "net.pdp0.dns2", "net.pdp0.dns3", "net.pdp0.dns4",
|
||||
"net.pdpbr0.dns1", "net.pdpbr0.dns2", "net.pdpbr0.dns3", "net.pdpbr0.dns4"}) {
|
||||
try {
|
||||
String value = (String) method.invoke(null, name);
|
||||
if (value != null && !value.isEmpty() && !servers.contains(value)) {
|
||||
servers.add(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// getPreferabilityForNetwork is a utility routine which implements a priority for
|
||||
// different types of network transport, used in a heuristic to pick DNS servers to use.
|
||||
int getPreferabilityForNetwork(ConnectivityManager cMgr, Network network) {
|
||||
NetworkCapabilities nc = cMgr.getNetworkCapabilities(network);
|
||||
|
||||
if (nc == null) {
|
||||
return -1;
|
||||
}
|
||||
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
|
||||
// tun0 has both VPN and WIFI set, have to check VPN first and return.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
||||
return 0;
|
||||
} else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
return 1;
|
||||
} else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue