Skip to content

Excluding an app from TrackerControl should pass traffic through, not leave the VPN #732

Description

@kasnder

"Exclude from TrackerControl" currently means "leave the VPN" (apply = false
addDisallowedApplication, ServiceSinkhole.java:1778). That is a kernel-level
bypass, and it is not what the toggle is supposed to mean.

Consequences:

  • It is incompatible with always-on VPN + "block connections without VPN":
    under lockdown an excluded app gets no network, not the raw network.
  • Toggling it requires rebuilding the tun, so it cannot change at runtime.
  • It collapses two independent choices — is this app filtered and is this app
    in the tunnel
    — in exactly the way Route apps around the remote VPN without leaving TrackerControl #730 has just separated is this app
    filtered
    from is this app routed through the remote VPN.

What we actually want from "exclude" is a passthrough: the app stays in the
tun, its packets are relayed unchanged, and no block decision or recording is
made for it.

Scope of the change

Once a packet is in the tun there is no bypass — it can only leave through a
protected socket, i.e. handle_tcp / handle_udp. So passthrough cannot skip
the relay. It skips the per-session work: the create_packet +
is_address_allowed JNI upcall, the DB write, the SNI reassembly, the logging.
The per-packet copy stays, so throughput and battery land roughly where filtered
apps already are. The win is semantic (lockdown-compatible, runtime-toggleable),
not performance.

The mechanism largely exists already: tracker_protect = false is passthrough
with monitoring — in the tun, seen, not blocked. The missing state is "in the
tun, relayed, not recorded". That is one more verdict at the same fork in
handle_ip() (ip.c:344) that #730 turned into route_decide(), and can follow
the same shape: a sorted UID array pushed over JNI, an I/O-free filter_decide(),
mirrored by a JVM-testable Java class.

Open questions (design, not plumbing)

  • System apps. !includeSystem && rule.system uses the same
    addDisallowedApplication path. Converting that set to userspace passthrough
    would drag all system traffic through the relay — a real regression. Argues for
    keeping the kernel exclusion available as a separate, clearly-labelled escape
    hatch rather than replacing it.
  • DNS. Port 53 skips the UID lookup entirely today, so a passthrough app's
    queries would still be parsed and attributed unless we pay the fwd53-style
    cost described in Route apps around the remote VPN without leaving TrackerControl #730. Since attribution is global anyway, "still monitored"
    is probably right — but then "excluded" no longer means invisible, and
    shouldTrackApp (ServiceSinkhole.java:2431) has to change to match.
  • UID on packet 2+. Same uid == -1 trap as Route apps around the remote VPN without leaving TrackerControl #730. Easier here: stamp the
    verdict on the session at creation, since existing sessions already
    short-circuit to allowed = 1.
  • Configurability. This is a third orthogonal axis on top of Route apps around the remote VPN without leaving TrackerControl #730's two.
    Given the simplicity-over-configurability rule, it should fold into the
    existing per-app control as a tri-state rather than becoming a new switch.

Proposal

Make passthrough the default meaning of the per-app exclusion toggle, and keep
addDisallowedApplication as a separate escape hatch for apps that genuinely
break inside a userspace stack (VoIP, captive portals).

Follows on from #730.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions