Skip to content

Private DNS Discussion #1707

Draft
bitmold wants to merge 2 commits into
masterfrom
private-dns
Draft

Private DNS Discussion #1707
bitmold wants to merge 2 commits into
masterfrom
private-dns

Conversation

@bitmold

@bitmold bitmold commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Private DNS on android has three states we can kind of vaguely reason about:

  • Off
  • "automatic" - the system will use DoT at its own discretion. seemingly prioritizes the user getting their websites loaded over adhering to universal DoT
  • "hostname" - the system says it will force DoT, even if this leads to a broken/poor UX. in this state, we can then obtain the user-specified TLS hostname

Querying This Stuff

We can basically do this on demand whenever we want. IE on the connect screen like @tladesignz had suggested, but could also ask for it anywhere, even in Notifications hypothetically.

I'm 99.9% sure this is mostly what we want:

  • we can learn if it's set
  • possibly display some info about what the user set
  • explain to the user about say leaking a tor onion service URL
  • would be easy to ask for this information when you open a screen, or even when you are building a notification to show the user.
    sealed class PrivateDns {
        object Off : PrivateDns()
        object Opportunistic : PrivateDns()
        data class Strict(val hostname: String) : PrivateDns()
        companion object {
            const val KEY_MODE = "private_dns_mode"
            const val KEY_HOSTNAME = "private_dns_specifier"
            const val HOSTNAME_UNKNOWN = ""
            const val MODE_OFF = "off"
            const val MODE_HOSTNAME = "hostname"
            const val MODE_AUTOMATIC = "automatic"
            fun isPrivateDnsSupported(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
        }
    }

    fun getPrivateDnsConfiguration(context: Context): PrivateDns {
        if (!PrivateDns.isPrivateDnsSupported()) return PrivateDns.Off
        val dnsMode =
            Settings.Secure.getString(context.contentResolver, PrivateDns.KEY_MODE)
                ?: PrivateDns.MODE_OFF
        when (dnsMode) {
            PrivateDns.MODE_OFF -> return PrivateDns.Off
            PrivateDns.MODE_AUTOMATIC -> return PrivateDns.Opportunistic
            PrivateDns.MODE_HOSTNAME -> {
                val hostname = Settings.Secure.getString(
                    context.contentResolver,
                    PrivateDns.KEY_HOSTNAME
                )
                    ?: PrivateDns.HOSTNAME_UNKNOWN
                return PrivateDns.Strict(hostname)
            }
        }
        return PrivateDns.Off

ConnectivityManager and ContentObserver

But you can also subscribe for network events in the app, which SnowflakeProxyService does. The connectivity lets us see when the user moves between networks. Whenever one of these network event fires off, we get the set of DNS resolvers the system has curated for the user at that moment. You don't get any info on if DNS is being resolved or plain text of if it's DoT style though. You also don't get notified which resolver is actually handling your query...

You can also subscribe with ContentObsrevers to get a callback fired whenever those values from above change. Seems like you can masochistically subscribe to both the variable change events, and the network change events, and perhaps make some guesses about what's going on.

This seems complex, also battery draining. and it doesn't give us much value than the code I already wrote above did.

@bitmold bitmold requested review from n8fr8 and tladesignz June 2, 2026 15:18
@bitmold

bitmold commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator Author

i unfortunately need to get on the original GitLab thread and won't be able to do that until later today 🙃

I'm very curious why OnionMasq/TorVPN are working differently...

I'm with cyBerta and concerned about how this rubs against Tor's anonymity promise.

And at the bare minimum I agree with @tladesignz in the immediate term there's UX work to do to convey exactly what is happening to the user. I bet there are a lot of users who have security fatigue, see this thing as "private" and would be upset to learn there's a leak.

I think some users presumably understand exactly what's going on and desire this. But I bet there are many other users who are in the dark, it's not especially clear...

But it gets even more complicated!!

Caution: Android 9 only! These Private DNS settings have no effect when you use a VPN like Nexus/Pixel Wi-Fi Assistant or Google Fi Enhanced Network VPNs, or third-party VPN or DNS changer apps. Those features and apps override Private DNS and do not send DNS-over-TLS queries to Google Public DNS. Most DNS changers send cleartext queries (a few like Intra use other secure DNS protocols) and VPN apps may not secure queries beyond the VPN server. This is "fixed" in Android 10.

  • Based on my cursory understanding I think Google actually has this extremely unclear system that would confuse just about everyone,wherein
    • If you have a VPNService like Orbot, with Private DNS in Strict Mode, then the DNS resolution is leaked outside of the VPN. You also have know way to verify this reliably.
    • But if you have a VPNService in Lockdown Mode and set Private DNS to strict mode, than the system seemingly quietly ignores the Private DNS Strict Mode. Incidentally the method to see if a VPNService is locked down is added in Android 29, the same version of Android where Private DNS started leaking...

FWIW Android apps can open the system setting screen where they can enable lockdown mode by starting this Intent

startActivity(Intent("android.net.vpn.SETTINGS")
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))

@tladesignz

Copy link
Copy Markdown
Collaborator
  • explain to the user about say leaking a tor onion service URL

This typically IS NOT TRUE, as networking stacks are typically implemented to drop DNS queries for onion services early, thanks to Tor Project's lobbying and RFC writing.

Comment thread app/src/main/java/org/torproject/android/util/NetworkUtils.kt
@tladesignz

Copy link
Copy Markdown
Collaborator

As already mentioned in the Gitlab issue:

I suggest adding a warning near the connect button about this. Nothing more.
However, that warning should go away automatically, when users change configuration. Hence, I think we need to hook up to the network change notifications.

To be clear, I think what Google is doing here is correct:

With normal ISPs as well as with normal VPN providers, it's a good idea to hide DNS queries from them. With such a "Secure VPN" setup, users split information about them between two (hopefully) unrelated parties which improves privacy.

This is an edge case in conjunction with Tor as "VPN", where Tor takes care of anonymization already, and does it better than said distribution of information.
We might also want to take steps (Alert box when tapping warning? Or when tapping connect button? ^^) to explain exactly that to the user.

But @cstiens might have better advice here. After all, she is dedicated to reduction and de-complication.

@syphyr

syphyr commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

I am currently using dnscrypt-proxy with Orbot to get a system wide private DNS. Dnscrypt has an option to convert all UDP DNS requests to TCP and query Tor over a socks proxy with a user/pass for isolation between apps. For my setup on cm-14.1, this also requires root in order to use iptables to forward all port 53 requests over to dnscrypt, but apparently newer versions of android do not require that?

Here is my source:
https://github.com/syphyr/dnscrypt_proxy_prebuilt

One benefit of using dnscrypt is that it allows the usage of a domain block list so that I can also use Tor DNS with ad blocking and malware blocking.

@bitmold

bitmold commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator Author

I suggest adding a warning near the connect button about this. Nothing more. However, that warning should go away automatically, when users change configuration. Hence, I think we need to hook up to the network change notifications.

I don't think we need to hook into network configurations for the warning to appear/disappear when the user changes the settings. simply putting the logic to show/hide it in onResume() should suffice. Since if the user closes the app to change the settings, then reopens the app, onResume() is invoked and the text can be updated/removed if necessary

@bitmold

bitmold commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator Author

Whenever the user changes the setting, this updates ConnectFragment without the overhead/drawbacks of hooking into network change callbacks. (Which is typically done from an Activity/Service (which are Contexts)

override fun onResume() {
        super.onResume()
        updatePrivateDnsLabel()
}

private fun updatePrivateDnsLabel() {
        var labelVisibility = View.VISIBLE
        var labelText = ""
        var dialogMsg = ""
        var onLabelClick: () -> Unit = {
            AlertDialog.Builder(requireContext())
                .setTitle("Private DNS") // TODO
                .setMessage(dialogMsg)
                .show()
        }

        val privateDns = NetworkUtils.PrivateDns.getPrivateDnsConfiguration(requireContext())
        when (privateDns) {
            is NetworkUtils.PrivateDns.Off -> {
                labelVisibility = View.GONE
                onLabelClick = {}
            }

            is NetworkUtils.PrivateDns.Opportunistic -> {
                labelText = "Opportunistic Message"
                dialogMsg = "opportunistic explanation"
            }

            is NetworkUtils.PrivateDns.Strict -> {
                labelText = "Strict msg ${privateDns.hostname}"
                dialogMsg = "struct explanation ${privateDns.hostname}"
            }
        }

        binding.tvPrivateDnsStatus.apply {
            visibility = labelVisibility
            text = labelText
            setOnClickListener { onLabelClick() }
        }
    }

I'm going to wait until we can all talk to figure out specifically what text to display to the user on the connect screen + the possible explain more dialog box. But the logic is complete, and can probably be tidied further once the exact implementation details are made clear.

@n8fr8

n8fr8 commented Jun 9, 2026

Copy link
Copy Markdown
Member

@abeluck let's talk about setting up our own DoH server for auditing/testing of what comes through

@bitmold

bitmold commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@abeluck let's talk about setting up our own DoH server for auditing/testing of what comes through

Specifically using this DoH server to test an app using Orbot to resolve .onion domains.

@SkewedZeppelin

SkewedZeppelin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

This typically IS NOT TRUE

quick search via cs.android.com says only some external libraries handle this, the regular android stack doesn't.

I suggest adding a warning near the connect button about this. Nothing more.

I agree with this. maybe a one-time in your face pop-up too.

but users should be able to use private dns if they want so they can have more trustworthy dns responses and dnssec support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants