diff --git a/android/build.gradle b/android/build.gradle index 0befe7d..264223f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -39,6 +39,14 @@ android { targetSdkVersion 34 versionCode 241 versionName "1.73.114-t0970615b1-gab7ab737364" + + // This setting, which defaults to 'true', will cause Tailscale to fall + // back to the Google DNS servers if it cannot determine what the + // operating system's DNS configuration is. + // + // Set it to false either here or in your local.properties file to + // disable this behaviour. + buildConfigField "boolean", "USE_GOOGLE_DNS_FALLBACK", getLocalProperty("tailscale.useGoogleDnsFallback", "true") } compileOptions { @@ -54,6 +62,7 @@ android { jvmTarget = "17" } buildFeatures { + buildConfig true compose true } composeOptions { @@ -66,9 +75,9 @@ android { applicationTest { initWith debug manifestPlaceholders.leanbackRequired = false - buildConfigField "String", "GITHUB_USERNAME", "\"" + getLocalProperty("githubUsername")+"\"" - buildConfigField "String", "GITHUB_PASSWORD", "\"" + getLocalProperty("githubPassword")+"\"" - buildConfigField "String", "GITHUB_2FA_SECRET", "\"" + getLocalProperty("github2FASecret")+"\"" + buildConfigField "String", "GITHUB_USERNAME", "\"" + getLocalProperty("githubUsername", "")+"\"" + buildConfigField "String", "GITHUB_PASSWORD", "\"" + getLocalProperty("githubPassword", "")+"\"" + buildConfigField "String", "GITHUB_2FA_SECRET", "\"" + getLocalProperty("github2FASecret", "")+"\"" } debug { manifestPlaceholders.leanbackRequired = false @@ -156,12 +165,12 @@ dependencies { implementation("androidx.compose.ui:ui-tooling-preview") } -def getLocalProperty(key) { +def getLocalProperty(key, defaultValue) { try { Properties properties = new Properties() properties.load(project.file('local.properties').newDataInputStream()) - return properties.getProperty(key) + return properties.getProperty(key) ?: defaultValue } catch(Throwable ignored) { - return "" + return defaultValue } } diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index 5df2cab..a9430dd 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import libtailscale.BuildConfig as GoBuildConfig import libtailscale.Libtailscale import java.io.File import java.io.IOException @@ -311,6 +312,13 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner { } } + // getBuildConfig implements the libtailscale.AppContext interface. + override fun getBuildConfig(): GoBuildConfig { + var buildConfig = GoBuildConfig() + buildConfig.useGoogleDNSFallback = BuildConfig.USE_GOOGLE_DNS_FALLBACK + return buildConfig + } + fun notifyPolicyChanged() { app.notifyPolicyChanged() } diff --git a/libtailscale/backend.go b/libtailscale/backend.go index 908b7e2..414eeeb 100644 --- a/libtailscale/backend.go +++ b/libtailscale/backend.go @@ -44,6 +44,9 @@ type App struct { // appCtx is a global reference to the com.tailscale.ipn.App instance. appCtx AppContext + // buildConfig is the build configuration for the app. + buildConfig *BuildConfig + store *stateStore policyStore *syspolicyHandler logIDPublicAtomic atomic.Pointer[logid.PublicID] @@ -97,7 +100,8 @@ type backend struct { // when no nameservers are provided by Tailscale. avoidEmptyDNS bool - appCtx AppContext + appCtx AppContext + buildConfig *BuildConfig } type settingsFunc func(*router.Config, *dns.OSConfig) error @@ -262,9 +266,10 @@ func (a *App) newBackend(dataDir, directFileRoot string, appCtx AppContext, stor logf := logger.RusagePrefixLog(log.Printf) b := &backend{ - devices: newTUNDevices(), - settings: settings, - appCtx: appCtx, + devices: newTUNDevices(), + settings: settings, + appCtx: appCtx, + buildConfig: a.buildConfig, } var logID logid.PrivateID logID.UnmarshalText([]byte("dead0000dead0000dead0000dead0000dead0000dead0000dead0000dead0000")) diff --git a/libtailscale/interfaces.go b/libtailscale/interfaces.go index f344baa..11e40e2 100644 --- a/libtailscale/interfaces.go +++ b/libtailscale/interfaces.go @@ -57,6 +57,12 @@ type AppContext interface { // GetSyspolicyStringArrayValue returns the current string array value for the given system policy, // expressed as a JSON string. GetSyspolicyStringArrayJSONValue(key string) (string, error) + + // GetBuildConfig gets the build configuration of the Android app. + // + // The returned BuildConfig should not change during the lifetime of + // the app. + GetBuildConfig() *BuildConfig } // IPNService corresponds to our IPNService in Java. @@ -166,3 +172,11 @@ func RequestVPN(service IPNService) { func ServiceDisconnect(service IPNService) { onDisconnect <- service } + +// BuildConfig is a struct that represents the build configuration of the +// Android application, as set in BuildConfig.java. +type BuildConfig struct { + // UseGoogleDNSFallback is whether to fall back to the Google public + // DNS servers if the platform's DNS servers cannot be determined. + UseGoogleDNSFallback bool +} diff --git a/libtailscale/net.go b/libtailscale/net.go index efa7e8f..c49efd6 100644 --- a/libtailscale/net.go +++ b/libtailscale/net.go @@ -287,7 +287,7 @@ func (b *backend) getDNSBaseConfig() (ret dns.OSConfig, _ error) { // DNS config are lacking, and almost all Android phones use Google // services anyway, so it's a reasonable default: it's an ecosystem the // user has selected by having an Android device. - if len(ret.Nameservers) == 0 && b.appCtx.IsPlayVersion() { + if len(ret.Nameservers) == 0 && b.buildConfig.UseGoogleDNSFallback { log.Printf("getDNSBaseConfig: none found; falling back to Google public DNS") ret.Nameservers = append(ret.Nameservers, googleDNSServers...) } diff --git a/libtailscale/tailscale.go b/libtailscale/tailscale.go index 7370726..5d08cd7 100644 --- a/libtailscale/tailscale.go +++ b/libtailscale/tailscale.go @@ -38,6 +38,11 @@ func newApp(dataDir, directFileRoot string, appCtx AppContext) Application { } a.ready.Add(2) + // Get the build configuration, if any. + if bc := appCtx.GetBuildConfig(); bc != nil { + a.buildConfig = bc + } + a.store = newStateStore(a.appCtx) a.policyStore = &syspolicyHandler{a: a} netmon.RegisterInterfaceGetter(a.getInterfaces)