|
| 1 | +# MasterDnsVPN integration — deferral note and design |
| 2 | + |
| 3 | +Date: 2026-06-15 |
| 4 | +Status: **Deferred** (blocked on upstream Android socket protection) |
| 5 | + |
| 6 | +## Goal |
| 7 | +Add `masterking32/MasterDnsVPN` (a DNS-tunneling VPN that carries TCP/UDP over DNS |
| 8 | +queries) as a client profile in NekoBox, using the same bundled-binary sidecar pattern |
| 9 | +that now powers Mieru. |
| 10 | + |
| 11 | +## Why it is deferred |
| 12 | +MasterDnsVPN has **no Android `VpnService.protect()` mechanism**. Its client |
| 13 | +(`cmd/client`, Go 1.25, TOML config) sends its tunnel carrier as DNS queries to |
| 14 | +configured resolver IPs (e.g. `8.8.8.8:53`). When NekoBox's `VpnService` is active and |
| 15 | +captures all traffic into the TUN, those upstream DNS sockets would be captured by the VPN |
| 16 | +and loop back into sing-box instead of egressing on the real underlying network — breaking |
| 17 | +the tunnel. |
| 18 | + |
| 19 | +Repo inspection (2026-06-15) found no protect-path env, no `SO_MARK`/fwmark/`Dialer.Control` |
| 20 | +socket-control hook, and no Android-specific code. By contrast, Mieru integrates cleanly |
| 21 | +because upstream Mieru honors `MIERU_PROTECT_PATH` and lets NekoBox protect its upstream |
| 22 | +sockets. |
| 23 | + |
| 24 | +## Architecture analysis (confirmed with an external model review) |
| 25 | +- **sing-box `direct`/bypass routing rule for the resolver IPs — does NOT work.** A sing-box |
| 26 | + route rule only affects sing-box's own outbound sockets. The MasterDnsVPN sidecar is a |
| 27 | + separate process; its sockets are not sing-box's, so a `direct` rule cannot keep them off |
| 28 | + the VPN. At best it would hairpin captured sidecar packets back through sing-box, which is |
| 29 | + fragile and semantically wrong for a DNS tunnel (sing-box may treat the carrier DNS as |
| 30 | + ordinary DNS and rewrite/route it). |
| 31 | +- **Android `VpnService.Builder.excludeRoute()` (API 33+) — works by destination but is the |
| 32 | + wrong tool.** It would exclude *all* traffic to the resolver IPs for the whole covered UID |
| 33 | + (not just MasterDnsVPN's DNS carrier), is IP-prefix based (not per-socket/session), can't |
| 34 | + follow dynamic resolver changes without rebuilding the VPN, is awkward pre-API 33, and |
| 35 | + interacts badly with per-app/lockdown VPN expectations. Leak-prone; not production-grade. |
| 36 | +- **Per-socket `VpnService.protect(fd)` is the only reliable way** to keep a separate |
| 37 | + sidecar's upstream sockets off the VPN. Raw `SO_MARK` from app code is gated by |
| 38 | + Android/netd permission checks and is not a substitute. |
| 39 | + |
| 40 | +## Recommended sequence (when work resumes) |
| 41 | + |
| 42 | +### Step 1 — Ship Proxy-mode-only sidecar (interim, usable) |
| 43 | +Full feature scaffolding, gated to NekoBox proxy mode (no TUN, so no VPN loop): |
| 44 | +- `MasterDnsVpnBean` (new `ProxyEntity.TYPE_*`, `TypeMap`, `needExternal()=true`). |
| 45 | +- Fields: `domains`, `dataEncryptionMethod`, `encryptionKey`, `resolvers`, |
| 46 | + `resolverBalancingStrategy`, `localSocksPort`, `socks5Auth`/`user`/`pass`, |
| 47 | + `protocolType` (SOCKS5), plus an `advancedToml` override for the long tail of knobs. |
| 48 | +- TOML generator writing `client_config.toml` (mirror `client_config.toml.simple`) + |
| 49 | + a resolvers file. |
| 50 | +- Process runner via the existing `GuardedProcessPool` (like Mieru), local SOCKS5 on |
| 51 | + `127.0.0.1:<allocated port>`; sing-box `socks` outbound -> that port. |
| 52 | +- Bundled binaries: cross-compile `cmd/client` for all 4 ABIs into |
| 53 | + `app/executableSo/<abi>/libmasterdnsvpn.so`, built in CI (mirror `buildScript/lib/mieru.sh` |
| 54 | + and the per-workflow Mieru job). Note: MasterDnsVPN is `go 1.25.0`; the build job would |
| 55 | + need a Go that satisfies that (CI currently pins 1.24.9 for libcore — use a separate Go |
| 56 | + setup for this binary or revisit the toolchain). |
| 57 | +- In VPN mode, disable the profile with a clear message: |
| 58 | + "MasterDnsVPN requires Android socket protection for VPN mode. Proxy mode is supported; |
| 59 | + VPN mode will be enabled after upstream resolver sockets can be protected." |
| 60 | +- Security defaults: `LISTEN_IP=127.0.0.1`, SOCKS5 auth on with random user/pass, never log |
| 61 | + `ENCRYPTION_KEY` or SOCKS creds, bind loopback only. |
| 62 | + |
| 63 | +### Step 2 — Upstream a socket-protect hook to MasterDnsVPN (fork/PR) |
| 64 | +- Add a `MASTERDNSVPN_PROTECT_PATH` env (unset = normal desktop behavior). |
| 65 | +- Wire every upstream resolver socket through `net.Dialer.ControlContext` (connected |
| 66 | + TCP/UDP) and `net.ListenConfig.Control` (unconnected UDP `PacketConn`), protecting the fd |
| 67 | + *after creation, before connect/bind*. |
| 68 | +- Audit ALL upstream socket creation sites: UDP query, TCP fallback, IPv4+IPv6, resolver |
| 69 | + health checks, MTU/probe, validation, any future DoH/DoT. Do NOT protect the local SOCKS |
| 70 | + listener. |
| 71 | +- Fail fast in VPN mode if protect fails. |
| 72 | + |
| 73 | +### Step 3 — NekoBox protect bridge (reuse Mieru's) |
| 74 | +- NekoBox creates a private unix domain socket under app-private storage, starts the protect |
| 75 | + server before the sidecar, launches the client with `MASTERDNSVPN_PROTECT_PATH`. |
| 76 | +- The client passes the real fd via `SCM_RIGHTS`; NekoBox calls `VpnService.protect(fd)` and |
| 77 | + returns a status byte. (Pass the actual fd, not a numeric string.) |
| 78 | + |
| 79 | +### Step 4 — Enable VPN mode; remove any interim workaround |
| 80 | +- App -> TUN -> sing-box -> SOCKS outbound -> 127.0.0.1:client -> protected resolver sockets. |
| 81 | +- No sing-box "direct resolver IP" rule needed — the carrier DNS never enters sing-box. |
| 82 | + |
| 83 | +### Step 5 — Verification that matters |
| 84 | +- Full-route VPN (`0.0.0.0/0` + `::/0`), no excludeRoute, no sing-box direct rule: |
| 85 | + MasterDnsVPN works AND the TUN shows no `sidecar -> resolver:53` packets; protect-server |
| 86 | + logs show every resolver socket protected. |
| 87 | +- Matrix: UDP + TCP fallback, multiple resolvers, failover/health checks, IPv6 resolvers, |
| 88 | + Wi-Fi<->cellular switch, VPN restart, always-on/lockdown, target Android versions. |
| 89 | + |
| 90 | +## Why not do it all now |
| 91 | +- VPN-mode support (the headline use case) depends on an upstream code change that doesn't |
| 92 | + exist yet; shipping a sing-box-routing workaround would be leak-prone and architecturally |
| 93 | + wrong. |
| 94 | +- The proxy-mode-only interim is a real option (Step 1) but was deferred together with the |
| 95 | + rest pending a decision to invest in the upstream protect hook. This note captures the full |
| 96 | + design so the work can start cleanly later. |
0 commit comments