Skip to content

v4.8.2 — bind native sockets to active Android Network via JNI helper (deferred fix 2 from #56) #57

Description

@emooreatx

Context

Deferred from CIRISVerify#56 (closed by v4.8.1) — that fix shipped two of the three asks:

  1. probe semanticsmobile_http::probe_reachability treats any HTTP response (including 401) as reachable
  2. decouple local checks from network raceself_verification always populates with the locally-computed binary hash, even on a network-unavailable fast-fail
  3. 🟡 bind native sockets to the active Android Network — this issue

Real-device testing on the Galaxy S21U (the agent team's pin to v4.8.1) is reportedly green for #1 + #2 today, which is consistent with the #56 trace evidence (ureq reached the registry, returned 401). But the #56 root-cause analysis flagged a second-order defect: on Android multi-network devices (WiFi + Cellular + VPN simultaneously), native sockets created without an explicit Network binding may pick the wrong network OR fail with ENETUNREACH. The Java/OkHttp layer and JVM-level resolver know about the multi-network state and route correctly; the native Rust + ureq paths do not.

v4.8.1 doesn't fix this structural gap — it's still possible for a future Android scenario (mid-call network switch, captive portal, VPN flap) to surface it. v4.8.2 closes it.

Proposed approach

FFI helper: add ciris_verify_bind_to_active_network() -> bool to the FFI surface. On Android, the implementation calls (via JNI):

  1. ConnectivityManager.getActiveNetwork() → returns a Network handle (or null if no active network).
  2. If non-null: ConnectivityManager.bindProcessToNetwork(network) (or the deprecated setProcessDefaultNetwork, both equivalent at the syscall level — they call android_setprocnetwork(net_handle) from the NDK).
  3. Return true on success, false on no-active-network or JNI failure.

On non-Android targets (Linux, macOS, iOS, Windows), the function is a no-op returning true. iOS doesn't have the multi-network-via-API model — system-level routing handles it.

After this call, EVERY subsequent native socket created in the process — by ureq, reqwest, hickory-dns, anything — goes through the bound Network. No per-call binding required.

Caller responsibility

CIRISAgent (and any other consumer of the verify wheel on Android) calls ciris_verify_bind_to_active_network() ONCE at startup, before the first attestation. The wheel's Python __init__ can call it automatically on Android.

Why FFI helper, not Java layer

The Java layer COULD do this — Android's recommended pattern is "Java binds the process, then native code doesn't care." But putting the binding in the verify FFI means:

  • Every wheel consumer (Python agents, future C consumers, mobile SDKs) gets the binding for free
  • The Network is bound on the same thread that loads the verify .so, removing race windows
  • The bound-Network state is testable from Rust unit tests (mocked on desktop, real on Android instrumentation tests)

Testing requirement

This MUST be tested on a real Android device with multi-network state (WiFi + Cellular simultaneously) before merge. Suggested QA scenario:

  1. Boot device with WiFi + Cellular both up
  2. Run attestation → expect green
  3. Disconnect WiFi mid-attestation → expect either green (race wins on Cellular path) or network_unavailable partial result with self_verification populated (the v4.8.1 fix), NOT a hang or ENETUNREACH cascade
  4. Reconnect WiFi → next attestation should be green

Out of scope

  • Network-change callbacks (re-binding when active Network changes mid-process). For v4.8.2 the binding is set at init time only; if the active Network changes, the next process restart re-binds. Continuous re-binding is a v4.9 candidate if needed.
  • VPN-only / WiFi-Network-only forced routing. The binding picks whatever ConnectivityManager calls "active" — operator preference (force Cellular, force WiFi) is out of scope.

Cross-refs

  • CIRISVerify#56 — original report (closed by v4.8.1, fixes 1+2 of 3)
  • CIRISAgent#862 — agent classifier consumes the structured network_unavailable status (independent fix on agent side)
  • CIRISAgent#843 — startup attestation budget bump kept at 20s as defense-in-depth

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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