Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions android/app/src/main/kotlin/com/anywherelan/awl/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ data class AppConfig(

val dnsJson = root.optJSONObject("dns")
val dnsConfig = DnsConfig(
disableDNS = dnsJson?.optBoolean("disableDNS", false) ?: false,
upstreamDNSAddress = dnsJson?.optString("upstreamDNSAddress", "") ?: ""
)

Expand Down Expand Up @@ -68,12 +69,18 @@ data class VPNGatewayConfig(

// DnsConfig mirrors DNSConfig in github.com/anywherelan/awl/config.
//
// `disableDNS` — DNS is off entirely (also the field kill switch for the
// interceptor): establishTun then does not call addDnsServer with the awl
// DNS IP and the Go side does not install the interceptor.
//
// `upstreamDNSAddress` — the public resolver (host:port) the Go side forwards
// non-.awl queries to. On Android there is no in-process awl resolver
// (binding :53 needs root; see the TODO in the Go DNSService), so we instead
// point VpnService at this resolver directly via addDnsServer when full-tunnel
// (gateway client) mode is on, so DNS does not leak around the tunnel. The
// port is stripped — VpnService.addDnsServer takes a bare IP.
// non-.awl queries to. Normally the device talks to the in-tunnel awl DNS
// IP (Anywherelan.dnsServerIP) and the Go resolver does the forwarding;
// this address is used by the host directly only when DNS is disabled but
// full-tunnel (gateway client) mode is on — then VpnService pins it via
// addDnsServer so queries do not leak around the tunnel. The port is
// stripped — VpnService.addDnsServer takes a bare IP.
data class DnsConfig(
val disableDNS: Boolean,
val upstreamDNSAddress: String,
)
22 changes: 18 additions & 4 deletions android/app/src/main/kotlin/com/anywherelan/awl/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,25 @@ class MainActivity : FlutterActivity() {
if (config.vpnGateway.clientEnabled) {
builder.addRoute("0.0.0.0", 0)
builder.addRoute("::", 0)
}

// Full-tunnel mode: pin DNS to the configured upstream so queries go
// through the TUN (and the exit node) instead of leaking to the
// device's own resolver. The Go DNSService cannot take over DNS on
// Android (no awl :53 listener without root), so the host owns this.
// The Go core owns an in-tunnel DNS IP (netstack interceptor in the
// TUN read path): .awl names are answered locally, everything else is
// forwarded to the configured upstream — in gateway client mode
// through the tunnel, so DNS does not leak. The IP is inside the awl
// subnet that addAddress already routes on-link, so no extra addRoute
// is needed. An empty string means DNS is off or the Go side could
// not set the interceptor up (e.g. no free IP in the subnet).
val dnsServerIP = if (!config.dns.disableDNS) Anywherelan.dnsServerIP() else ""
if (dnsServerIP.isNotEmpty()) {
builder.addDnsServer(dnsServerIP)
// TODO: builder.addSearchDomain("awl") — would resolve bare
// short names (e.g. "mypeer") without the .awl suffix.
} else if (config.vpnGateway.clientEnabled) {
// No interceptor (DNS disabled — the kill switch — or the Go side
// failed to set it up) while full-tunnel mode is on: pin DNS to
// the configured upstream so queries go through the TUN (and the
// exit node) instead of leaking to the device's own resolver.
val dnsHost = hostFromAddress(config.dns.upstreamDNSAddress)
if (dnsHost.isNotEmpty()) {
builder.addDnsServer(dnsHost)
Expand Down