Skip to content

Secure DNS is silently bypassed when the WireGuard config has no DNS line #734

Description

@kasnder

Summary

With remote VPN (WireGuard) enabled and a .conf that carries no DNS = line, Secure DNS (DoH) reports itself as active, the local DoH proxy runs, and the port-53 forward is installed — but not one query ever reaches it. Queries are handed to the WireGuard tunnel addressed to the public fallback resolver instead.

The setting is on, the UI says nothing is wrong, and the user's chosen resolver (e.g. a NextDNS profile) is silently unused.

Root cause

handle_ip diverts to WireGuard before the DNS redirect can be applied. The redirect is a property of the userspace NAT path, and a tunnelled packet never gets there:

  1. prepareForwarding installs mapForward[53] → 127.0.0.1:5353 when doh_enabled && !hasActiveWireGuardDns(prefs)ServiceSinkhole.java:2153.
  2. isAddressAllowed duly returns new Allowed("127.0.0.1", 5353) for the query.
  3. Back in handle_ip, the WireGuard fork runs on the packet's original destination: int wg_dest = !is_local_dest(version, daddr) || is_dns;ip.c:487. is_dns is true, so wg_dest is true unconditionally.
  4. write_wireguard_packet(...) succeeds and handle_ip returns. handle_udp never runs, so the redirect is discarded.

The query leaves through the tunnel to whatever resolver getBuilder programmed — the public fallback, since the config named none.

Why this matters beyond the obvious

This is not a DNS leak: the query does go through the tunnel. The failure is that a security feature the user explicitly enabled is inert while claiming to be on.

It also means the analysis in #684 is not quite right. That issue was closed on the reasoning that lifting the DoH pause would send the DoH request "out over the plain underlying network with the device's real IP — same as it does today when WG has no DNS = line". In fact DoH does not function at all in that configuration; nothing is resolved over DoH, so there is no such request to leak. Worth re-reading #684 with that corrected, since the reporter accepted the outcome on the strength of it.

Scope

  • WireGuard enabled and doh_enabled and the config carries no DNS = line. With a DNS = line, updateDnsProxyState stops the proxy deliberately and the behaviour is documented in summary_doh_enabled; that case is fine.
  • Present on master. Not introduced by Route apps around the remote VPN without leaving TrackerControl #730 — that PR preserves the same branch (is_dns && !dns_direct ⇒ tunnel).

Direction

Either honour the redirect before the hijack (rewrite the destination, then let the routing fork see the rewritten local address, which is_local_dest would then keep off the tunnel), or — if DoH-alongside-WireGuard is not wanted at all — extend the updateDnsProxyState pause to cover the no-DNS = case so the UI stops claiming Secure DNS is active. The second is a one-line honesty fix; the first is the feature #684 actually asked for.

Root cause is shared with the two sibling issues linked below: handle_ip hands the packet to WireGuard before the DNS and session machinery runs, so everything that machinery provides disappears whenever the tunnel is on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions