Skip to content

Commit 5f117d1

Browse files
committed
docs: record MasterDnsVPN integration deferral and design
MasterDnsVPN has no Android VpnService.protect mechanism, so its DNS carrier sockets would loop inside NekoBox's VPN. A sing-box direct-route bypass does not protect a separate sidecar process's sockets; per-socket VpnService.protect is the only reliable approach. Deferred pending an upstream socket-protect hook. Documents the analysis and the recommended sequence: ship proxy-mode-only sidecar first, upstream a MASTERDNSVPN_PROTECT_PATH hook (Dialer.Control + SCM_RIGHTS fd passing), reuse NekoBox's Mieru-style protect bridge, then enable VPN mode.
1 parent d5f66df commit 5f117d1

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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

Comments
 (0)