From a7dfea267c076deecbc84d96ac135c8de7a896c4 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Sat, 25 Jul 2020 14:08:25 +0200 Subject: [PATCH] cmd/tailscale: fallback back to Google DNS on ChromeOS Contrary to the VpnService.Builder documentation, ChromeOS doesn't automatically fall back to the underlying network nameservers when none are provided. Updates tailscale/tailscale#431 Signed-off-by: Elias Naur --- .../src/main/java/com/tailscale/ipn/App.java | 4 ++++ cmd/tailscale/backend.go | 13 +++++++++++- cmd/tailscale/main.go | 21 +++++++++++++++++++ go.mod | 1 + jni/gojni.h | 1 + jni/jni.c | 4 ++++ jni/jni.go | 5 +++++ 7 files changed, 48 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/tailscale/ipn/App.java b/android/src/main/java/com/tailscale/ipn/App.java index 6c73645..8f7982b 100644 --- a/android/src/main/java/com/tailscale/ipn/App.java +++ b/android/src/main/java/com/tailscale/ipn/App.java @@ -139,5 +139,9 @@ public class App extends Application { ft.commitNow(); } + boolean isChromeOS() { + return getPackageManager().hasSystemFeature("android.hardware.type.pc"); + } + private static native void onConnectivityChanged(boolean connected); } diff --git a/cmd/tailscale/backend.go b/cmd/tailscale/backend.go index b73835c..e434713 100644 --- a/cmd/tailscale/backend.go +++ b/cmd/tailscale/backend.go @@ -17,6 +17,7 @@ import ( "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "golang.org/x/sys/unix" + "inet.af/netaddr" "tailscale.com/ipn" "tailscale.com/logtail" "tailscale.com/logtail/filch" @@ -35,6 +36,10 @@ type backend struct { settings func(*router.Config) error lastCfg *router.Config + // avoidEmptyDNS controls whether to use fallback nameservers + // when no nameservers are provided by Tailscale. + avoidEmptyDNS bool + jvm jni.JVM } @@ -54,6 +59,8 @@ const ( loginMethodWeb = "web" ) +var fallbackNameservers = []netaddr.IP{netaddr.IPv4(8, 8, 8, 8), netaddr.IPv4(8, 8, 4, 4)} + // errVPNNotPrepared is used when VPNService.Builder.establish returns // null, either because the VPNService is not yet prepared or because // VPN status was revoked. @@ -171,7 +178,11 @@ func (b *backend) updateTUN(service jni.Object, cfg *router.Config) error { // builder.addDnsServer addDnsServer := jni.GetMethodID(env, bcls, "addDnsServer", "(Ljava/lang/String;)Landroid/net/VpnService$Builder;") - for _, dns := range cfg.Nameservers { + nameservers := cfg.Nameservers + if b.avoidEmptyDNS && len(nameservers) == 0 { + nameservers = fallbackNameservers + } + for _, dns := range nameservers { _, err = jni.CallObjectMethod(env, builder, addDnsServer, diff --git a/cmd/tailscale/main.go b/cmd/tailscale/main.go index 64f4de5..4af3fa4 100644 --- a/cmd/tailscale/main.go +++ b/cmd/tailscale/main.go @@ -145,6 +145,12 @@ func (a *App) runBackend() error { return err } defer b.CloseTUNs() + + // Contrary to the documentation for VpnService.Builder.addDnsServer, + // ChromeOS doesn't fall back to the underlying network nameservers if + // we don't provide any. + b.avoidEmptyDNS = a.isChromeOS() + var timer *time.Timer var alarmChan <-chan time.Time alarm := func(t *time.Timer) { @@ -300,6 +306,21 @@ func (a *App) runBackend() error { } } +func (a *App) isChromeOS() bool { + var chromeOS bool + err := jni.Do(a.jvm, func(env jni.Env) error { + cls := jni.GetObjectClass(env, a.appCtx) + m := jni.GetMethodID(env, cls, "isChromeOS", "()Z") + b, err := jni.CallBooleanMethod(env, a.appCtx, m) + chromeOS = b + return err + }) + if err != nil { + panic(err) + } + return chromeOS +} + // hostname builds a hostname from android.os.Build fields, in place of a // useless os.Hostname(). func (a *App) hostname() string { diff --git a/go.mod b/go.mod index fad3aed..ef57b1c 100644 --- a/go.mod +++ b/go.mod @@ -11,5 +11,6 @@ require ( golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200501052902-10377860bb8e + inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c tailscale.com v0.100.1-0.20200724211229-0a42b0a72666 ) diff --git a/jni/gojni.h b/jni/gojni.h index 6895305..ba8b1ef 100644 --- a/jni/gojni.h +++ b/jni/gojni.h @@ -17,6 +17,7 @@ __attribute__ ((visibility ("hidden"))) jint _jni_CallStaticIntMethodA(JNIEnv *e __attribute__ ((visibility ("hidden"))) jobject _jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args); __attribute__ ((visibility ("hidden"))) void _jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args); __attribute__ ((visibility ("hidden"))) jobject _jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args); +__attribute__ ((visibility ("hidden"))) jboolean _jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args); __attribute__ ((visibility ("hidden"))) jint _jni_CallIntMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args); __attribute__ ((visibility ("hidden"))) void _jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args); __attribute__ ((visibility ("hidden"))) jbyteArray _jni_NewByteArray(JNIEnv *env, jsize length); diff --git a/jni/jni.c b/jni/jni.c index 45b1d27..22021ab 100644 --- a/jni/jni.c +++ b/jni/jni.c @@ -76,6 +76,10 @@ jobject _jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalu return (*env)->CallObjectMethodA(env, obj, method, args); } +jboolean _jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { + return (*env)->CallBooleanMethodA(env, obj, method, args); +} + jint _jni_CallIntMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { return (*env)->CallIntMethodA(env, obj, method, args); } diff --git a/jni/jni.go b/jni/jni.go index b772555..acc8211 100644 --- a/jni/jni.go +++ b/jni/jni.go @@ -117,6 +117,11 @@ func CallObjectMethod(e Env, obj Object, method MethodID, args ...Value) (Object return Object(res), exception(e) } +func CallBooleanMethod(e Env, obj Object, method MethodID, args ...Value) (bool, error) { + res := C._jni_CallBooleanMethodA(e.env, C.jobject(obj), C.jmethodID(method), varArgs(args)) + return res == C.JNI_TRUE, exception(e) +} + func CallIntMethod(e Env, obj Object, method MethodID, args ...Value) (int32, error) { res := C._jni_CallIntMethodA(e.env, C.jobject(obj), C.jmethodID(method), varArgs(args)) return int32(res), exception(e)