docs: document domain routing, fake-IP data path, and its invariants/limits
This commit is contained in:
parent
ef165b6b9b
commit
4e419146ca
2 changed files with 66 additions and 4 deletions
|
|
@ -76,8 +76,9 @@ file top.
|
||||||
- **`ui/`** — `MainScreen` hosts a 4-tab `HorizontalPager`: `ConnectionScreen`, `ServersScreen`,
|
- **`ui/`** — `MainScreen` hosts a 4-tab `HorizontalPager`: `ConnectionScreen`, `ServersScreen`,
|
||||||
`AppsScreen`, `SettingsScreen`. `MainViewModel` bridges UI ↔ `ConfigStore`/`VpnState`.
|
`AppsScreen`, `SettingsScreen`. `MainViewModel` bridges UI ↔ `ConfigStore`/`VpnState`.
|
||||||
- **native** — `tun2socks.c` is the JNI bridge (start/stop, hands the tun fd + YAML config to
|
- **native** — `tun2socks.c` is the JNI bridge (start/stop, hands the tun fd + YAML config to
|
||||||
hev). `hev-socks5-tunnel/` is the vendored engine (includes `hev-mapped-dns.c`, a fake-DNS
|
hev). `hev-socks5-tunnel/` is the vendored engine; its `hev-mapped-dns.c` fake-DNS facility is
|
||||||
facility worth knowing about for any domain-aware routing work).
|
what makes domain routing possible and is enabled via a `mapdns:` config block (see Domain
|
||||||
|
routing below).
|
||||||
|
|
||||||
## Per-app routing (model for any future per-target filtering)
|
## Per-app routing (model for any future per-target filtering)
|
||||||
|
|
||||||
|
|
@ -90,7 +91,54 @@ file top.
|
||||||
|
|
||||||
This is an **OS-level filter keyed by app UID**. It is the model the "Apps" tab exposes. Note
|
This is an **OS-level filter keyed by app UID**. It is the model the "Apps" tab exposes. Note
|
||||||
that anything keyed by *destination* (domain/IP) is NOT an OS feature and cannot reuse this —
|
that anything keyed by *destination* (domain/IP) is NOT an OS feature and cannot reuse this —
|
||||||
it has to be decided in the data path (the local SOCKS5 server sees the destination host).
|
it has to be decided in the data path (the local SOCKS5 server sees the destination host). That
|
||||||
|
is exactly how the "Сайты" tab works, below.
|
||||||
|
|
||||||
|
## Domain routing ("Сайты" tab)
|
||||||
|
|
||||||
|
`AppSettings.routedSites: Set<String>` + `routingMode: RoutingMode` add a **destination-keyed**
|
||||||
|
proxy-vs-direct split *on top of* the per-app filter — orthogonal layers: per-app decides which
|
||||||
|
apps enter the tun; domain routing decides, within that traffic, where each destination goes.
|
||||||
|
|
||||||
|
The data path is IP-only by the time it reaches hev (the OS already resolved the domain), so
|
||||||
|
domains are recovered with hev's **mapped-DNS / fake-IP** facility, enabled only when
|
||||||
|
`routedSites` is non-empty:
|
||||||
|
|
||||||
|
1. `buildEngineConfig()` emits a `mapdns:` block (synthetic resolver `198.18.0.2:53`, fake-IP pool
|
||||||
|
`198.19.0.0/16`, `cache-size 10000`); `establish()` points the tun's DNS at `198.18.0.2`.
|
||||||
|
2. hev intercepts A-queries to that resolver, answers with a fake IP from the pool, and on the
|
||||||
|
resulting TCP connection **reverse-maps the fake IP back to the original domain**, sending it
|
||||||
|
upstream as a SOCKS5 *domain* CONNECT (`hev-mapped-dns.c` + `misc/hev-utils.c`).
|
||||||
|
3. `LocalSocks5Server` now sees the real domain in `dest.host`; `core/route/SiteRouter` picks
|
||||||
|
PROXY (the existing upstream path) or DIRECT (a `protect()`-ed socket dialed straight to the
|
||||||
|
host on the underlying non-VPN network, via `ProxyVpnService` as `DirectDialer`) — both reuse
|
||||||
|
the same half-close relay.
|
||||||
|
|
||||||
|
Entries are explicit suffix domains (`example.com` ⇒ it + all subdomains) or geosite categories
|
||||||
|
(`geosite:netflix`). The full MIT v2fly `geosite.dat` (`assets/geosite/`, ~1500 categories,
|
||||||
|
refresh via `tools/fetch-geosite.sh`) is parsed lazily by `core/route/GeositeIndex` (a small
|
||||||
|
hand-rolled protobuf reader, no dependency) into a process-shared index used both by the Сайты
|
||||||
|
search (live category/domain suggestions) and by `SiteRouter` at tunnel build. `RoutingMode` is
|
||||||
|
the global default and the list
|
||||||
|
is the exceptions: `PROXY_ALL` ⇒ listed go direct; `DIRECT_ALL` ⇒ listed go through the proxy.
|
||||||
|
|
||||||
|
**Known limitation — DNS that bypasses the fake resolver isn't split.** hev only diverts UDP whose
|
||||||
|
destination is exactly the advertised resolver `198.18.0.2:53`. An app that hardcodes its own
|
||||||
|
resolver (e.g. `8.8.8.8`), or uses DoH/DoT/QUIC, never hits the fake DNS, so its flows arrive as
|
||||||
|
IP literals and take the **global default**. Under `DIRECT_ALL` that means a site the user added
|
||||||
|
to go *through the proxy* can still exit direct if its app resolves on its own. Closing this would
|
||||||
|
require hijacking all `:53` (and ideally DoH) in the engine — a possible follow-up. (Also: synthesized
|
||||||
|
answers carry TTL=1 and only A records; AAAA/HTTPS-SVCB get an empty answer, which is fine while v6
|
||||||
|
is suppressed but strips HTTP/3 hints.)
|
||||||
|
|
||||||
|
**Known limitation — UDP is not domain-routed.** Only `handleConnect` (TCP) consults the router;
|
||||||
|
`handleUdpAssociate` always sends UDP through the proxy (and an HTTP(S) upstream, which has no UDP
|
||||||
|
relay, drops it). So QUIC / HTTP-3 is proxied regardless of mode — under `DIRECT_ALL` a "direct"
|
||||||
|
site's QUIC still traverses the proxy. TCP is the routed path; UDP direct-routing is a possible
|
||||||
|
follow-up (it needs a protected per-destination UDP relay, mindful of the §3 anti-spoof invariant).
|
||||||
|
|
||||||
|
Mapped-DNS is **IPv4-only**, so while `routedSites` is non-empty the tun is forced IPv4-only (v6
|
||||||
|
suppressed regardless of `settings.ipv6`) to stop dual-stack apps bypassing the split over v6.
|
||||||
|
|
||||||
## State & status
|
## State & status
|
||||||
|
|
||||||
|
|
@ -100,7 +148,7 @@ it has to be decided in the data path (the local SOCKS5 server sees the destinat
|
||||||
applied config id, not the merely-selected server.
|
applied config id, not the merely-selected server.
|
||||||
- Settings take effect only at `establish()` / `buildEngineConfig()` time. `MainViewModel`
|
- Settings take effect only at `establish()` / `buildEngineConfig()` time. `MainViewModel`
|
||||||
force-restarts a running tunnel (`reconnectIfRunning()` → `ACTION_RESTART`) when the active
|
force-restarts a running tunnel (`reconnectIfRunning()` → `ACTION_RESTART`) when the active
|
||||||
server or the app filter changes.
|
server, the app filter, or the site list / routing mode changes.
|
||||||
|
|
||||||
### Status & journal
|
### Status & journal
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,20 @@ against the code and the harness before touching.
|
||||||
write and recovers from it; only a genuine `SerializationException`/`IllegalArgumentException`
|
write and recovers from it; only a genuine `SerializationException`/`IllegalArgumentException`
|
||||||
counts as corruption (let `Error`/OOM propagate so a transient failure doesn't wipe servers).
|
counts as corruption (let `Error`/OOM propagate so a transient failure doesn't wipe servers).
|
||||||
9. **MTU 8500 is the baseline** (not a tuned value); the engine config and tun must agree.
|
9. **MTU 8500 is the baseline** (not a tuned value); the engine config and tun must agree.
|
||||||
|
10. **Domain routing recovers domains via hev mapped-DNS (fake-IP); the DIRECT path must bypass
|
||||||
|
it.** When `routedSites` is non-empty, `buildEngineConfig()` enables a `mapdns:` block and the
|
||||||
|
tun DNS is pointed at the synthetic resolver `198.18.0.2`. The fake-IP pool is `198.19.0.0/16`
|
||||||
|
— it clears the `198.18.0.1/32` tun address and is deliberately NOT in the `isUnroutableRelay()`
|
||||||
|
CGNAT/private set (those fake IPs become domains before reaching any upstream, so that relay
|
||||||
|
path is never involved). `DirectDialer` resolves on the **underlying non-VPN network** and
|
||||||
|
`protect()`s the socket: resolving via the default resolver would return a fake IP and loop the
|
||||||
|
"direct" flow back into the tun. The direct dialer also requires the underlying network to carry
|
||||||
|
`NET_CAPABILITY_INTERNET` — a device can expose an IMS/MMTEL cellular network (NOT_VPN, but no
|
||||||
|
INTERNET) that can't resolve, which silently broke every direct dial until filtered out
|
||||||
|
(device-confirmed). Mapped-DNS is IPv4-only, so v6 is force-suppressed while sites are active —
|
||||||
|
don't "restore" it. Keep the fake-IP cache sized to the whole pool (`MAPDNS_CACHE_SIZE` ==
|
||||||
|
`~netmask`): the LRU evicts by the *cache* limit, not the pool, and an evicted-but-still-referenced
|
||||||
|
fake IP reverse-maps to nothing and dead-ends.
|
||||||
|
|
||||||
## The "no universal core" decision
|
## The "no universal core" decision
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue