From 5c9cec0064eae57e58d4bdcd9ee466f0019ecad0 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sat, 9 Oct 2021 10:04:42 -0700 Subject: [PATCH] retrieve current DNS servers. Add getDnsConfigAsString() to retrieve the current DNS configuration from the Android platform. This implements several mechanisms to retrieve DNS information, suitable for different Android versions: Android 7 and later use ConnectivityManager getAllNetworks(), then iterate over each network to retrieve DNS servers and search domains using the LinkProperties. Android 6 and earlier can only retrieve the currently active interface using ConnectivityManager getActiveNetwork(), but have two additional fallback options which leverage the system properties available in older Android releases. -------- Also changed how LinkChange notification works, switching from the older BroadcastReceiver of a ConnectivityManager Intent to the newer ConnectivityManager.registerNetworkCallback. We need this because the onAvailable event is too early, we get notified that LTE is up before its DNS servers have been set. We need to wait for the onLinkPropertiesChanged event instead, which is only available with registerNetworkCallback. Fixes https://github.com/tailscale/tailscale/issues/2116 Updates https://github.com/tailscale/tailscale/issues/988 Signed-off-by: Denton Gentry --- android/src/main/AndroidManifest.xml | 2 + .../src/main/java/com/tailscale/ipn/App.java | 23 +- .../java/com/tailscale/ipn/DnsConfig.java | 360 ++++++++++++++++++ cmd/tailscale/backend.go | 116 +++++- cmd/tailscale/main.go | 2 +- go.mod | 2 +- go.sum | 14 + 7 files changed, 504 insertions(+), 15 deletions(-) create mode 100644 android/src/main/java/com/tailscale/ipn/DnsConfig.java diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 81619d8..0640d06 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + diff --git a/android/src/main/java/com/tailscale/ipn/App.java b/android/src/main/java/com/tailscale/ipn/App.java index 7a472d5..796b376 100644 --- a/android/src/main/java/com/tailscale/ipn/App.java +++ b/android/src/main/java/com/tailscale/ipn/App.java @@ -24,6 +24,9 @@ import android.content.pm.Signature; import android.provider.MediaStore; import android.provider.Settings; import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkRequest; import android.net.Uri; import android.net.VpnService; import android.view.View; @@ -77,6 +80,9 @@ public class App extends Application { private final static Handler mainHandler = new Handler(Looper.getMainLooper()); + public DnsConfig dns = new DnsConfig(this); + public DnsConfig getDnsConfigObj() { return this.dns; } + @Override public void onCreate() { super.onCreate(); // Load and initialize the Go library. @@ -90,13 +96,18 @@ public class App extends Application { } private void registerNetworkCallback() { - BroadcastReceiver connectivityChanged = new BroadcastReceiver() { - @Override public void onReceive(Context ctx, Intent intent) { - boolean noconn = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - onConnectivityChanged(!noconn); + ConnectivityManager cMgr = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); + cMgr.registerNetworkCallback(new NetworkRequest.Builder().build(), new ConnectivityManager.NetworkCallback() { + @Override + public void onLost(Network network) { + onConnectivityChanged(false); + } + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { + onConnectivityChanged(true); } - }; - registerReceiver(connectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + }); } public void startVPN() { diff --git a/android/src/main/java/com/tailscale/ipn/DnsConfig.java b/android/src/main/java/com/tailscale/ipn/DnsConfig.java new file mode 100644 index 0000000..7a9696a --- /dev/null +++ b/android/src/main/java/com/tailscale/ipn/DnsConfig.java @@ -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 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 servers = new ArrayList(); + 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 servers = new ArrayList(); + + 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; + } + } +} diff --git a/cmd/tailscale/backend.go b/cmd/tailscale/backend.go index d9efc30..bced41f 100644 --- a/cmd/tailscale/backend.go +++ b/cmd/tailscale/backend.go @@ -11,6 +11,7 @@ import ( "net/http" "path/filepath" "reflect" + "strings" "time" "github.com/tailscale/tailscale-android/jni" @@ -23,8 +24,10 @@ import ( "tailscale.com/logtail" "tailscale.com/logtail/filch" "tailscale.com/net/dns" + "tailscale.com/net/tsdial" "tailscale.com/smallzstd" "tailscale.com/types/logger" + "tailscale.com/util/dnsname" "tailscale.com/wgengine" "tailscale.com/wgengine/router" ) @@ -41,7 +44,8 @@ type backend struct { // when no nameservers are provided by Tailscale. avoidEmptyDNS bool - jvm *jni.JVM + jvm *jni.JVM + appCtx jni.Object } type settingsFunc func(*router.Config, *dns.OSConfig) error @@ -58,19 +62,27 @@ const ( loginMethodWeb = "web" ) -var fallbackNameservers = []netaddr.IP{netaddr.IPv4(8, 8, 8, 8), netaddr.IPv4(8, 8, 4, 4)} +// googleDnsServers are used on ChromeOS, where an empty VpnBuilder DNS setting results +// in erasing the platform DNS servers. The developer docs say this is not supposed to happen, +// but nonetheless it does. +var googleDnsServers = []netaddr.IP{netaddr.MustParseIP("8.8.8.8"), netaddr.MustParseIP("8.8.4.4"), + netaddr.MustParseIP("2001:4860:4860::8888"), netaddr.MustParseIP("2001:4860:4860::8844"), +} // errVPNNotPrepared is used when VPNService.Builder.establish returns // null, either because the VPNService is not yet prepared or because // VPN status was revoked. var errVPNNotPrepared = errors.New("VPN service not prepared or was revoked") -func newBackend(dataDir string, jvm *jni.JVM, store *stateStore, settings settingsFunc) (*backend, error) { +func newBackend(dataDir string, jvm *jni.JVM, appCtx jni.Object, store *stateStore, + settings settingsFunc) (*backend, error) { + logf := logger.RusagePrefixLog(log.Printf) b := &backend{ jvm: jvm, devices: newTUNDevices(), settings: settings, + appCtx: appCtx, } var logID logtail.PrivateID logID.UnmarshalText([]byte("dead0000dead0000dead0000dead0000dead0000dead0000dead0000dead0000")) @@ -90,19 +102,23 @@ func newBackend(dataDir string, jvm *jni.JVM, store *stateStore, settings settin logID.UnmarshalText([]byte(storedLogID)) } b.SetupLogs(dataDir, logID) + + dialer := new(tsdial.Dialer) cb := &router.CallbackRouter{ - SetBoth: b.setCfg, - SplitDNS: false, // TODO: https://github.com/tailscale/tailscale/issues/1695 + SetBoth: b.setCfg, + SplitDNS: false, + GetBaseConfigFunc: b.getDNSBaseConfig, } engine, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ Tun: b.devices, Router: cb, DNS: cb, + Dialer: dialer, }) if err != nil { return nil, fmt.Errorf("runBackend: NewUserspaceEngine: %v", err) } - local, err := ipnlocal.NewLocalBackend(logf, logID.Public().String(), store, engine) + local, err := ipnlocal.NewLocalBackend(logf, logID.Public().String(), store, dialer, engine) if err != nil { engine.Close() return nil, fmt.Errorf("runBackend: NewLocalBackend: %v", err) @@ -170,7 +186,7 @@ func (b *backend) updateTUN(service jni.Object, rcfg *router.Config, dcfg *dns.O if dcfg != nil { nameservers := dcfg.Nameservers if b.avoidEmptyDNS && len(nameservers) == 0 { - nameservers = fallbackNameservers + nameservers = googleDnsServers } for _, dns := range nameservers { _, err = jni.CallObjectMethod(env, @@ -324,3 +340,89 @@ func (b *backend) SetupLogs(logDir string, logID logtail.PrivateID) { log.Printf("SetupLogs: filch setup failed: %v", filchErr) } } + +// We log the result of each of the DNS configuration discovery mechanisms, as we're +// expecting a long tail of obscure Android devices with interesting behavior. +func (b *backend) logDNSConfigMechanisms() { + err := jni.Do(b.jvm, func(env *jni.Env) error { + cls := jni.GetObjectClass(env, b.appCtx) + m := jni.GetMethodID(env, cls, "getDnsConfigObj", "()Lcom/tailscale/ipn/DnsConfig;") + dns, err := jni.CallObjectMethod(env, b.appCtx, m) + if err != nil { + return fmt.Errorf("getDnsConfigObj JNI: %v", err) + } + dnsCls := jni.GetObjectClass(env, dns) + + for _, impl := range []string{"getDnsConfigFromLinkProperties", + "getDnsServersFromSystemProperties", + "getDnsServersFromNetworkInfo"} { + + m = jni.GetMethodID(env, dnsCls, impl, "()Ljava/lang/String;") + n, err := jni.CallObjectMethod(env, dns, m) + baseConfig := jni.GoString(env, jni.String(n)) + if err != nil { + log.Printf("%s JNI: %v", impl, err) + } else { + oneLine := strings.Replace(baseConfig, "\n", ";", -1) + log.Printf("%s: %s", impl, oneLine) + } + } + return nil + }) + if err != nil { + log.Printf("logDNSConfigMechanisms: %v", err) + } +} + +func (b *backend) getPlatformDNSConfig() string { + var baseConfig string + err := jni.Do(b.jvm, func(env *jni.Env) error { + cls := jni.GetObjectClass(env, b.appCtx) + m := jni.GetMethodID(env, cls, "getDnsConfigObj", "()Lcom/tailscale/ipn/DnsConfig;") + dns, err := jni.CallObjectMethod(env, b.appCtx, m) + if err != nil { + return fmt.Errorf("getDnsConfigObj: %v", err) + } + dnsCls := jni.GetObjectClass(env, dns) + m = jni.GetMethodID(env, dnsCls, "getDnsConfigAsString", "()Ljava/lang/String;") + n, err := jni.CallObjectMethod(env, dns, m) + baseConfig = jni.GoString(env, jni.String(n)) + return err + }) + if err != nil { + log.Printf("getPlatformDNSConfig JNI: %v", err) + return "" + } + return baseConfig +} + +func (b *backend) getDNSBaseConfig() (dns.OSConfig, error) { + b.logDNSConfigMechanisms() + baseConfig := b.getPlatformDNSConfig() + lines := strings.Split(baseConfig, "\n") + if len(lines) == 0 { + return dns.OSConfig{}, nil + } + + config := dns.OSConfig{} + addrs := strings.Trim(lines[0], " \n") + for _, addr := range strings.Split(addrs, " ") { + ip, err := netaddr.ParseIP(addr) + if err == nil { + config.Nameservers = append(config.Nameservers, ip) + } + } + + if len(lines) > 1 { + for _, s := range strings.Split(strings.Trim(lines[1], " \n"), " ") { + domain, err := dnsname.ToFQDN(s) + if err != nil { + log.Printf("getDNSBaseConfig: unable to parse %q: %v", s, err) + continue + } + config.SearchDomains = append(config.SearchDomains, domain) + } + } + + return config, nil +} diff --git a/cmd/tailscale/main.go b/cmd/tailscale/main.go index 72f8c1d..822a3cc 100644 --- a/cmd/tailscale/main.go +++ b/cmd/tailscale/main.go @@ -256,7 +256,7 @@ func (a *App) runBackend() error { } configs := make(chan configPair) configErrs := make(chan error) - b, err := newBackend(appDir, a.jvm, a.store, func(rcfg *router.Config, dcfg *dns.OSConfig) error { + b, err := newBackend(appDir, a.jvm, a.appCtx, a.store, func(rcfg *router.Config, dcfg *dns.OSConfig) error { if rcfg == nil { return nil } diff --git a/go.mod b/go.mod index e114679..e0e0fca 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45 inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 - tailscale.com v1.1.1-0.20211125200034-e24f89efad22 + tailscale.com v1.1.1-0.20211206024951-b3810e3b1b93 ) require ( diff --git a/go.sum b/go.sum index 39cd6bd..e8c6bbc 100644 --- a/go.sum +++ b/go.sum @@ -636,5 +636,19 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237 h1:iAEkCBPbRaflBgZ7o9gjVUuWuvWeV4sytFWg9o+Pj2k= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +tailscale.com v1.1.1-0.20211103210016-0532eb30dbba h1:UJfsplfXBBJb2d2Sj3G+Uwq4cZqNQUOM04K+aWavumM= +tailscale.com v1.1.1-0.20211103210016-0532eb30dbba/go.mod h1:KpeWAuozkb9G2RharSOmrtLh8nfUsJNI9Ml1hUSjk1U= +tailscale.com v1.1.1-0.20211108154433-eccc2ac6ee91 h1:cfV2IJg5dc6BfxzX3W+GAZ/veXgvuoKUxHyuxrXM4iE= +tailscale.com v1.1.1-0.20211108154433-eccc2ac6ee91/go.mod h1:+3SVDKPct8EqzwfYDQ3RpRwwBodKP7kNs49WXoM7wiw= +tailscale.com v1.1.1-0.20211109021509-d6dde5a1aca7 h1:gfDJ/zqR+hPeL3JCOY6akU2VHvmRz+lv3OpwkPgBbFA= +tailscale.com v1.1.1-0.20211109021509-d6dde5a1aca7/go.mod h1:+3SVDKPct8EqzwfYDQ3RpRwwBodKP7kNs49WXoM7wiw= +tailscale.com v1.1.1-0.20211116231549-773af7292b95 h1:gyWNQkqxuUbdumPC5XH5rGRQG1iomkTblYVC8A7L7Jw= +tailscale.com v1.1.1-0.20211116231549-773af7292b95/go.mod h1:XzG4o2vtYFkVvmJWPaTGSaOzqlKSRx2WU+aJbrxaVE0= +tailscale.com v1.1.1-0.20211119125307-221a18f4f07c h1:XVV1hEdNdLT2md2XkTBtD1oFBSPs/A/FcDZrRxbuN9A= +tailscale.com v1.1.1-0.20211119125307-221a18f4f07c/go.mod h1:XzG4o2vtYFkVvmJWPaTGSaOzqlKSRx2WU+aJbrxaVE0= tailscale.com v1.1.1-0.20211125200034-e24f89efad22 h1:BCCsNwOxI+Oo4E7GCWcIrOCfVmyYkQDI/iYBYyj/kpA= tailscale.com v1.1.1-0.20211125200034-e24f89efad22/go.mod h1:i+JH48j9W4tCWOzunNJfrEZcZRQgavPQXO57oh3cwGE= +tailscale.com v1.1.1-0.20211205233917-7f5171030f07 h1:3zeQUManu3redjOkkcvoa5nCPD8Q3hZV0B5RvGiNK28= +tailscale.com v1.1.1-0.20211205233917-7f5171030f07/go.mod h1:L3DiIyJXiHO4r8/6y+G2axGMdYAI/VERGMhGftHfP4U= +tailscale.com v1.1.1-0.20211206024951-b3810e3b1b93 h1:7AWRaSMyCYl8/wAruUelHPnFSqK4hSRwXUFHG9lDRbY= +tailscale.com v1.1.1-0.20211206024951-b3810e3b1b93/go.mod h1:L3DiIyJXiHO4r8/6y+G2axGMdYAI/VERGMhGftHfP4U=