fix(route): empty site list now means a full tunnel, not all-direct — DIRECT_ALL no longer leaves the proxy carrying nothing
This commit is contained in:
parent
f2b234341d
commit
55dada7772
2 changed files with 19 additions and 0 deletions
|
|
@ -101,6 +101,11 @@ enum class RouteDecision { PROXY, DIRECT }
|
||||||
* - [RoutingMode.PROXY_ALL]: everything proxied; matched sites go DIRECT.
|
* - [RoutingMode.PROXY_ALL]: everything proxied; matched sites go DIRECT.
|
||||||
* - [RoutingMode.DIRECT_ALL]: everything direct; matched sites go through the PROXY.
|
* - [RoutingMode.DIRECT_ALL]: everything direct; matched sites go through the PROXY.
|
||||||
*
|
*
|
||||||
|
* With **no rules at all** the split is inert and everything is proxied (a full tunnel) —
|
||||||
|
* this deliberately overrides DIRECT_ALL, whose literal "everything direct" would otherwise
|
||||||
|
* leave a connected proxy carrying nothing while the user still sees a green "proxy confirmed".
|
||||||
|
* The split only takes effect once there is at least one site to match against.
|
||||||
|
*
|
||||||
* Only domain destinations can match — IP-literal / IPv6 / non-DNS flows have no domain
|
* Only domain destinations can match — IP-literal / IPv6 / non-DNS flows have no domain
|
||||||
* to test, so they always take the global default (the documented coverage gap).
|
* to test, so they always take the global default (the documented coverage gap).
|
||||||
*/
|
*/
|
||||||
|
|
@ -109,6 +114,8 @@ class SiteRouter(
|
||||||
private val matcher: SiteMatcher,
|
private val matcher: SiteMatcher,
|
||||||
) {
|
) {
|
||||||
fun decide(host: String, isDomain: Boolean): RouteDecision {
|
fun decide(host: String, isDomain: Boolean): RouteDecision {
|
||||||
|
// No rules ⇒ full tunnel, never the degenerate all-direct of an empty DIRECT_ALL.
|
||||||
|
if (matcher.isEmpty) return RouteDecision.PROXY
|
||||||
val matched = isDomain && matcher.matches(host)
|
val matched = isDomain && matcher.matches(host)
|
||||||
return when (mode) {
|
return when (mode) {
|
||||||
RoutingMode.PROXY_ALL -> if (matched) RouteDecision.DIRECT else RouteDecision.PROXY
|
RoutingMode.PROXY_ALL -> if (matched) RouteDecision.DIRECT else RouteDecision.PROXY
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,18 @@ class SiteRoutingTest {
|
||||||
assertEquals(RouteDecision.DIRECT, r.decide("93.184.216.34", isDomain = false))
|
assertEquals(RouteDecision.DIRECT, r.decide("93.184.216.34", isDomain = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun directAll_emptyList_fallsBackToFullTunnel() {
|
||||||
|
// The footgun guard: DIRECT_ALL with no rules must NOT send everything direct (a
|
||||||
|
// connected proxy carrying nothing). With no rules the split is inert = full tunnel.
|
||||||
|
val r = SiteRouter(RoutingMode.DIRECT_ALL, SiteMatcher.EMPTY)
|
||||||
|
assertFalse(r.hasRules)
|
||||||
|
assertEquals(RouteDecision.PROXY, r.decide("anything.com", isDomain = true))
|
||||||
|
assertEquals(RouteDecision.PROXY, r.decide("93.184.216.34", isDomain = false))
|
||||||
|
// Same via build() with no entries.
|
||||||
|
val b = SiteRouter.build(RoutingMode.DIRECT_ALL, emptySet(), GeositeSource.EMPTY)
|
||||||
|
assertEquals(RouteDecision.PROXY, b.decide("anything.com", isDomain = true))
|
||||||
|
}
|
||||||
|
|
||||||
// --- SiteRouter.build (entries -> rules) ---
|
// --- SiteRouter.build (entries -> rules) ---
|
||||||
|
|
||||||
@Test fun build_mixesPlainAndGeositeEntries() {
|
@Test fun build_mixesPlainAndGeositeEntries() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue