Himansh Raj

NetGuard: A No-Root Android Firewall

· 4 min read

NetGuard: A No-Root Android Firewall

I built NetGuard, a no-root Android firewall written in Kotlin. It blocks apps and domains from reaching the network — no root, no custom ROM, no system-level hacks. The whole thing hinges on one idea: if you control DNS, you control what apps can actually reach.

I wanted to understand how firewalling is even possible on an unrooted phone, and building it end to end was the only way to really find out.

Why I made it

On a normal Android phone you don't get root, so you can't just install netfilter rules like you would on Linux. That constraint is the interesting part. Android exposes a VpnService API meant for VPN clients, but you can repurpose it: instead of tunneling traffic to a remote server, you tunnel it to yourself and inspect it locally.

That reframing is the core of the project — build a firewall out of a VPN that never leaves the device.

How it works

NetGuard leans on DNS interception rather than trying to proxy every packet. When an app wants to reach a server, it almost always asks DNS for an IP first. Intercept that question and you can decide the answer.

The flow looks like this:

  • An app makes a DNS query, which Android sends to the configured DNS server.
  • NetGuard routes only the DNS server's IP through a local TUN interface — everything else bypasses the tunnel entirely.
  • It reads the DNS packet, parses the domain name out of the RFC 1035 wire format, and resolves which app sent it via a UID lookup.
  • Then it decides:
    • App blocked? Return 0.0.0.0 — the app gets a dead address and no network.
    • Domain blocked? Return 0.0.0.0 — that domain is unreachable.
    • Allowed? Forward to the upstream DNS server and return the real IP.

The design choice I like most: only DNS traffic goes through the tunnel. Regular traffic flows over the real network untouched, so there's no throughput penalty for everything else.

What it can do

The feature set that came out of this:

  • Per-app blocking — toggle any app's network access; blocked apps get their DNS answered as 0.0.0.0.
  • Per-domain blocking with wildcards — block youtube.com, or *.youtube.com to catch every subdomain across every app.
  • Real-time traffic logs — a live feed of which app asked for which domain, tagged BLOCKED or ALLOWED.
  • Aggregate statistics — per-app and per-domain counts that persist across log clears.
  • Auto-start on boot — optionally bring the firewall up when the device starts.

Under the hood it's a fairly standard modern Android app: Kotlin, Jetpack Compose + Material 3 for the UI, MVVM with Hilt for structure, and Room with reactive Flow queries for storage. A DomainTrie handles fast wildcard domain lookups, and a priority-ordered rule engine decides each query.

What I learned

  1. Constraints shape the design. No root meant I couldn't filter at the packet level cheaply, so DNS became the leverage point. The limitation led to a cleaner, lighter approach than full proxying.
  2. DNS is a firewall's soft underbelly. You don't need to inspect every byte an app sends — you just need to control the name resolution it depends on first.
  3. Selective routing is the performance trick. By only routing DNS server IPs through the tunnel, the app avoids touching the traffic it doesn't care about.

There's plenty I haven't done yet — connection-level filtering via SOCKS5 and TLS SNI inspection are scaffolded but not wired up, so today's blocking is honestly DNS-level, not connection-level. That's a real limitation worth naming: an app that hardcodes an IP and skips DNS would slip through.

If you want to look at the code, here it is: NetGuard on GitHub.