I am currently doing experiments with the "Block connection without VPN" feature of AOSP. while I have witnessed similar behavior (possibly the same) with other phones and OSes (samsung s23 ULTRA), I thought I would post it here as someone might be interested in checking this problem.
My setup is:
- A private DNS and some services scattered around subnets in my local networks.
- A Wireguard VPN server on this network
- Pixel Pro XL or 9a with 2026012800 GraheneOS release. However, I don't think the device nor the release is that relevant as long as it is Android 13-15 (at least).
- A phone configured with a Wireguard client to access my services:
- With my private DNS.
- With allowed IPs containing my different subnets including the DNS one. It only contains my network's private IPs.
With this setup, the phone can communicate with my services including with their hostnames thanks to the DNS. However, it goes bad when "Block connection without VPN" is enabled. InetAddress cannot perform hostnames resolution anymore. I tried with a test application to execute a ping and it worked as expected (did it also with adb shell as my phone user). It is specifically InetAddress that can't resolve hostnames, the packet never leaves the tun0 interface and probably gets dropped by new iptable rules enforced when activating "Block connection without VPN". It gets weirder when I realized that by deactivating split tunneling and allowing all traffic to go through the VPN everything worked again (0.0.0.0/0). At first I thought Inetaddress was leaking something and trying to resolve with a public DNS but I couldn't find any DNS packets outside the one for my private DNS, I used tcpump to check that out. Of course, with split tunneling I don't see any DNS packets anymore which is what leads me to think something in the kernel drops my packets before they go out in the network layer. I first checked "netd" and DnsResolver" components and saw that app netId and mark and interface netId and mark where coherent each time with and without split tunneling. So I don't think the problem comes from them. I have found out that ping doesn't use the same route as InetAddress to resolve a host. InetAddress uses directly netd and DnsResolver to resolve the IP bound to a hostname. I went specifically throught netd NetworkController.getNetworkForDnsLocked() and DnsProxyListener::GetAddrInfoHandler::run() and subsequent functions (that is how I found out coherency on which interface was used). However ping doesn't seem to use directly (definitely not sure) those function to resolve an IP. It however ends doing a reverse DNS check by going through DnsProxyListener::GetHosByAddrHandler::run(). This at least explains why one behaves different from the other.
What I haven't check:
- I haven't tested another VPN.
- I haven't found out what ping does differently.
- I haven't checked kernel code (eBPF or code that uses iptable rules or any other thing that could explain this behavior).
- I haven't checked the whole chain of command when "Block connection without VPN" is enabled.
The current conclusion is that I have no idea if it is a real bug coming from "Block connection without VPN" feature or a misuse/miss configuration on my part.