diff --git a/docs/DRs/20260616_rssh_routed_egress.md b/docs/DRs/20260616_rssh_routed_egress.md new file mode 100644 index 0000000..aaa5442 --- /dev/null +++ b/docs/DRs/20260616_rssh_routed_egress.md @@ -0,0 +1,426 @@ +# RSSH routed egress (dev-only) — design review (DR) + + +| Field | Value | +|---|---| +| Author | Anton Afanasyeu | +| Revision | R0 | +| Creation date | 2026-06-16 | +| Last modification date | 2026-06-16 | +| Co-authored | Cursor Agent (project assistant) | +| Severity | low | +| State | postponed | +| Document type | DR | +| Pre-requisite to | Finish WireGuard remote-access E2E ([REMOTE_ACCESS_VALIDATION.md](../REMOTE_ACCESS_VALIDATION.md), [VPN_DEMO_GAPS_20260613.md](../VPN_DEMO_GAPS_20260613.md)) | + + +\newpage +\newpage + +--- + +**Document type:** DR (Design Review) +**Source draft:** [docs/drafts/20260616_rssh_routed_egress.txt](../drafts/20260616_rssh_routed_egress.txt) +**PDF:** [20260616_rssh_routed_egress.pdf](20260616_rssh_routed_egress.pdf) · Regenerate: `bash scripts/build-all-docs-pdf.sh` +**Status:** Draft for PO review — **no implementation** until WireGuard track is closed +**Scope:** Developer settings only; optional future path; does **not** replace alpha RSSH (reverse tunnel, no VPN) + +**Related:** [REMOTE_ACCESS_IMPL.md](../REMOTE_ACCESS_IMPL.md) · [20260602_REVERSE_SSH_proposals_summary.md](../20260602_REVERSE_SSH_proposals_summary.md) · [ROADMAP.md](../ROADMAP.md) (RSSH alpha) + +**Documentation index:** [README.md](../README.md) + +--- + +--- + +--- + +## Table of contents + + +- [1. Executive summary](#1-executive-summary) +- [2. Problem statement](#2-problem-statement) +- [3. Vocabulary and scope](#3-vocabulary-and-scope) +- [4. Current state](#4-current-state) +- [5. Goals and non-goals](#5-goals-and-non-goals) +- [6. Architecture options](#6-architecture-options) +- [7. DR recommendation](#7-dr-recommendation) +- [8. Reference design (if pursued later)](#8-reference-design-if-pursued-later) +- [9. Developer settings UX](#9-developer-settings-ux) +- [10. Backend and bastion requirements](#10-backend-and-bastion-requirements) +- [11. App components (sketch)](#11-app-components-sketch) +- [12. Third-party and licenses](#12-third-party-and-licenses) +- [13. Risks](#13-risks) +- [14. Effort estimate (order of magnitude)](#14-effort-estimate-order-of-magnitude) +- [15. Task dependency graph](#15-task-dependency-graph) +- [16. Open questions (PO)](#16-open-questions-po) +- [17. Changelog](#17-changelog) + + +--- + +## 1. Executive summary + +**Question:** Can we add a developer-only, system-wide VPN-style path that routes part or all device traffic through the **RSSH** (reverse SSH) session? + +**Short answer:** **Yes, technically** — but it requires a **new data plane** (`VpnService` + IP-to-SOCKS or TUN-over-SSH), not an extension of today’s RSSH `-R` forward. It **reintroduces VPN permission and the status-bar key**, and overlaps **WireGuard full-tunnel**, which already implements route/app scope in the app. + +**DR decision (R0):** + +| Item | Decision | +|------|----------| +| **Timing** | **Postponed** until WireGuard remote-access work is finished and validated E2E | +| **Alpha RSSH** | Unchanged — outbound reverse SSH for operator → device; **no** device routing | +| **Near-term routing** | Use **WireGuard** + `RemoteAccessVpnRouting` (hub-only or full-tunnel) | +| **If revived** | Dev-only sub-mode **“RSSH + route traffic (experimental)”** via **SSH dynamic forward (-D) + tun2socks + existing route/app prefs** | +| **SPEC** | Do **not** write implementation SPEC until PO confirms after WG closure | + +--- + +## 2. Problem statement + +Developers may want: + +1. **Operator access** to the device (shell/SFTP) — **RSSH today**. +2. **Device traffic** to egress via the hub (e.g. Minsk NAT) or reach hub-private IPs — **WireGuard today** (split or full tunnel). +3. In some networks, **UDP WireGuard** may be blocked while **TCP 443 SSH** works — motivation to explore routing **over RSSH**. + +Without a clear DR, it is easy to assume `vpn-route-scope` / `vpn-app-scope` apply to RSSH; they currently apply to **WireGuard only** ([REMOTE_ACCESS_IMPL.md](../REMOTE_ACCESS_IMPL.md)). + +--- + +## 3. Vocabulary and scope + +| Term | Meaning here | +|------|----------------| +| **RSSH** | Device-initiated reverse SSH; `-R bastion:port → device:8022` for operator access | +| **Routed egress** | Device IP traffic (some or all) sent through bastion/hub NAT to the Internet or private nets | +| **VpnService** | Android API for app-provided VPN; required for system-wide or per-app routing **from our app** | +| **Route scope** | Hub-only (`172.200.2.1/32`) vs full-tunnel (`0.0.0.0/0`) — `RemoteAccessVpnRouting` | +| **App scope** | All apps vs this app only — `IncludedApplications` / `addAllowedApplication` | + +**Not in scope for this DR:** Commercial VPN product (F4), always-on VPN in Android Settings, routing for non-developer builds. + +--- + +## 4. Current state + +```mermaid +flowchart LR + subgraph phone [Device] + RA[RemoteAccessService] + WG[WireGuard :vpn] + RSSH[ReverseSshTunnelBridge] + LOCAL[RsshLocalSshServer :8022] + end + subgraph be [BE / bastion] + WG0[wg0 UDP 45340] + SSHD[sshd TCP 443] + end + RA -->|mode wireguard| WG + WG -->|UDP| WG0 + RA -->|mode rssh| RSSH + RSSH -->|TCP -R| SSHD + SSHD -->|operator| LOCAL +``` + +| Mode | Transport | Device routing | VPN icon | +|------|-----------|----------------|----------| +| **WireGuard** | UDP → `wg0` | Yes (hub-only or full) | Yes | +| **RSSH** | TCP → sshd | **No** | **No** | + +Existing prefs (`dev_remote_access_vpn_route_scope`, `dev_remote_access_vpn_app_scope`) are sent on heartbeat for WG; RSSH ignores them. + +--- + +## 5. Goals and non-goals + +### Goals (if implemented later) + +| ID | Goal | +|----|------| +| G1 | Optional **dev-only** routing of device traffic via **TCP 443** SSH path | +| G2 | Reuse **route scope** and **app scope** semantics from `RemoteAccessVpnRouting` | +| G3 | Network self-test shows bastion, bind ports, egress expectation (like WG `vpn-ip`) | +| G4 | Clear separation from **alpha RSSH** (reverse access only) | + +### Non-goals + +| ID | Non-goal | +|----|----------| +| NG1 | Replace WireGuard for production/lab routing | +| NG2 | Alpha release blocker | +| NG3 | Avoid `VpnService.prepare()` — routed mode **will** need it | +| NG4 | SSH `PermitTunnel` / `ssh -w` as v1 (too heavy for Alpine bastion v1) | +| NG5 | Third-party “VPN provider” in Android Settings (no such API; we use `VpnService`) | + +--- + +## 6. Architecture options + +### 6.1 Option A — WireGuard full-tunnel (existing) + +**Use when:** Hub egress or full WAN via Minsk NAT is needed **now**. + +- Dev settings → WireGuard → route scope **All traffic** → reconnect session. +- BE `wg0` PostUp MASQUERADE already documented. + +| Pros | Cons | +|------|------| +| Implemented | UDP DNAT + VPN consent | +| Kernel/userspace WG | Not RSSH transport | + +**Verdict:** **Default for routing** until WG track is done. + +### 6.2 Option B — RSSH + SSH `-D` (SOCKS) + `VpnService` + tun2socks + +**Use when:** PO explicitly needs **TCP-only** routing and accepts VPN icon + extra stack. + +```mermaid +flowchart TB + subgraph device [Device] + TUN[VpnService TUN] + T2S[tun2socks userspace] + SOCKS[127.0.0.1:SOCKS] + JSch[JSch session] + end + subgraph bastion [Bastion / BE] + SSD[sshd] + NAT[NAT egress eth0] + end + TUN --> T2S --> SOCKS --> JSch --> SSD --> NAT +``` + +1. Extend `ReverseSshTunnelBridge`: keep `-R` for operator; add **`-D 127.0.0.1:`** on same session. +2. New **RsshRoutedVpnService** (or branch in `:vpn`) establishes TUN with routes from `RemoteAccessVpnRouting`. +3. **tun2socks** forwards IP packets → local SOCKS → SSH stream → bastion → Internet. + +| Pros | Cons | +|------|------| +| TCP 443 friendly | VPN permission + icon | +| Reuses SSH session | tun2socks dep + licenses | +| Reuses route/app prefs | TCP-over-TCP, MTU, battery | +| | Bastion egress policy + capacity | +| | JSch dynamic forwarding QA | + +**Verdict:** **Reference design if PO revives** routed RSSH. + +### 6.3 Option C — SSH layer-3 `tun` (`ssh -w`) + +Both ends need TUN, `PermitTunnel`, root/capabilities on bastion, custom Android client. + +**Verdict:** **Reject** for dev-only v1 (operational cost). + +### 6.4 Option D — Hybrid (proposal doc) + +**WG for packets + RSSH (or SSH over WG) for operator files.** + +**Verdict:** Valid **long-term**; does not require RSSH alone to carry IP routing. + +### 6.5 Comparison matrix + +| Criterion | A WG | B RSSH+SOCKS | C ssh -w | D hybrid | +|-----------|------|--------------|----------|----------| +| Ready today | Yes | No | No | Partial | +| TCP 443 only | No | Yes | Yes | Mixed | +| VPN icon | Yes | Yes | Yes | Yes | +| Alpha RSSH purity | N/A | Weakens if default | Weakens | OK | +| Impl effort | Done | High | Very high | Medium | + +--- + +## 7. DR recommendation + +1. **Close WireGuard E2E** (peer reconcile, self-test `vpn-ip`, hub→phone diagnostics, full-tunnel egress check) — **current priority**. +2. **Keep alpha RSSH** as reverse tunnel only ([ROADMAP.md](../ROADMAP.md)). +3. **Park Option B** in this DR; PO reviews §16 and decides **go / no-go** after WG. +4. If **go:** spawn SPEC `rssh_routed_egress` (dev-only feature flag), not a change to core RSSH connect payload for alpha. + +--- + +## 8. Reference design (if pursued later) + +### 8.1 Control plane + +Unchanged: `heartbeat.php` `type: ra`, `tunnel: ssh_reverse`, credentials for bastion + `-R` port. + +Optional dev flag in heartbeat meta: `rssh_route_experimental: true` (telemetry only; no BE requirement for v0). + +### 8.2 Data plane + +| Step | Component | +|------|-----------| +| 1 | `ReverseSshTunnelBridge` connects; `-R` as today | +| 2 | Same session: `session.setPortForwardingD("127.0.0.1", socksPort)` (JSch) | +| 3 | `RsshRoutedVpnEngine` starts TUN via `VpnService` | +| 4 | tun2socks reads TUN, connects to `127.0.0.1:socksPort` | +| 5 | Bastion sshd forwards; **iptables MASQUERADE** on bastion for egress | + +### 8.3 Routing semantics + +Reuse `RemoteAccessVpnRouting.resolveAllowedIps()`: + +| Route scope | TUN routes | +|-------------|------------| +| **HUB_ONLY** | `172.200.2.1/32` (or configured hub CIDR) | +| **FULL_TUNNEL** | `0.0.0.0/0`, `::/0` | + +Reuse `appendInterfaceExtras()` for **THIS_APP_ONLY**. + +**Note:** Hub reachability over RSSH may need **explicit route to hub IP via TUN** or SOCKS; validate that hub-only does not assume WG interface addresses. + +### 8.4 Failure modes + +| Symptom | Likely cause | +|---------|----------------| +| SOCKS up, no Internet | Bastion NAT / forwarding disabled | +| Tunnel up, wrong egress IP | Route scope hub-only (expected) | +| Intermittent stalls | TCP-over-TCP congestion | +| VPN works, operator SFTP dead | `-R` forward dropped; session limits | + +--- + +## 9. Developer settings UX + +Proposed (not implemented): + +``` +Remote access mode: [ Disabled | WireGuard | RSSH ] + +— When WireGuard — + VPN route scope: Hub only | All traffic + VPN app scope: All apps | This app only + +— When RSSH — + [ ] Route device traffic via bastion (experimental) + (shows route/app scope only when checked) + Hint: Reverse tunnel for operator access is always on in RSSH mode. + Routing requires VPN permission (same as WireGuard). +``` + +Network self-test (RSSH + experimental): `rssh-bastion`, `rssh-remote-bind`, `rssh-socks`, `wan-egress-via-vpn`, `rssh-connected`. + +--- + +## 10. Backend and bastion requirements + +| Requirement | Notes | +|-------------|--------| +| `AllowTcpForwarding yes` | Already for `-R` | +| Dynamic forwarding | Confirm sshd `GatewayPorts` / no restrictive `Match` block for `-D` | +| Egress NAT | iptables/nft MASQUERADE on bastion (similar to `wg0` PostUp) | +| Capacity | Routed dev traffic adds bandwidth; separate from §12.1 RSSH SSH counts in scaling DR | +| Audit | Log experimental flag; no change to alpha session schema required | + +No new nginx HTTP paths; optional stream unchanged. + +--- + +## 11. App components (sketch) + +| Component | Process | Role | +|-----------|---------|------| +| `ReverseSshTunnelBridge` | main | SSH session; `-R` + `-D` | +| `RsshRoutedVpnService` | `:vpn` | TUN + lifecycle | +| `RsshTun2SocksEngine` | `:vpn` | Packet relay | +| `RemoteAccessVpnRouting` | shared | Route/app scope | +| `DevVpnStatusProbe` | main | Diagnostics | + +Do **not** fold into `WireGuardVpnEngine`; separate engine behind common `VpnServiceClient` interface if refactored later. + +--- + +## 12. Third-party and licenses + +| Candidate | License | Action if adopted | +|-----------|---------|-------------------| +| **badvpn** / **tun2socks** | Various | Evaluate; add to `third-party/`, mobile + BE license files | +| **JSch** | BSD | Already in app; verify `-D` support | +| **Apache MINA SSHD** | Apache-2.0 | Local server only today | + +Update [COMMERCIAL.md](../COMMERCIAL.md) and `app/src/main/assets/licenses/` before merge. + +--- + +## 13. Risks + +| Risk | Mitigation | +|------|------------| +| Duplicates WG | PO go/no-go; default to WG | +| Alpha scope creep | Dev-only flag; off in release if needed | +| VPN consent UX regression | Document in FR; separate from “no VPN” RSSH story | +| Bastion abuse (open egress) | Dev whitelist + session TTL + rate limits | +| Maintenance burden | Two routing stacks | + +--- + +## 14. Effort estimate (order of magnitude) + +| Phase | Work | Duration (indicative) | +|-------|------|------------------------| +| P0 | WG E2E closure | **Current sprint** (PO priority) | +| P1 | Spike: JSch `-D` + manual SOCKS browser test | 2–3 d | +| P2 | tun2socks + `VpnService` + route prefs | 5–8 d | +| P3 | Bastion NAT + self-test + docs | 2–3 d | +| P4 | Unit/integration tests, soak | 3–5 d | + +**Total if pursued:** ~3–4 weeks **after** WG done — not parallelized with WG closure unless PO overrides. + +--- + +## 15. Task dependency graph + +Legend: **blocked by** upstream task. + +```mermaid +flowchart TD + WG1["1. WG peer reconcile on BE deploy"] + WG2["2. WG E2E phone ↔ hub"] + WG3["3. WG full-tunnel egress validation"] + PO["4. PO review this DR"] + GO{"5. Go routed RSSH?"} + SPIKE["6. JSch -D spike"] + SPEC["7. SPEC rssh_routed_egress"] + IMPL["8. Implement dev experimental mode"] + + WG1 --> WG2 --> WG3 --> PO + PO --> GO + GO -->|no| PARK["Park — use WG only"] + GO -->|yes| SPIKE --> SPEC --> IMPL +``` + +| Priority | Task | Owner | State | +|----------|------|-------|-------| +| **1** | Finish WG (peer sync, ping/handshake, self-test, full-tunnel) | Dev/PO | In progress | +| **2** | Review DR R0 | PO | Pending | +| **3** | RSSH routed egress | — | **Postponed** | + +--- + +## 16. Open questions (PO) + +| # | Question | Default if silent | +|---|----------|-------------------| +| Q1 | After WG E2E, is TCP-only routing still a requirement? | **No** — stay WG-only for routing | +| Q2 | Accept VPN icon for an experimental RSSH routing mode? | Required for Option B | +| Q3 | Should hub-only over RSSH target `172.200.2.1` without WG? | Clarify use case | +| Q4 | Bastion egress NAT for dev traffic — allowed on prod `be-vpn`? | Lab-only | +| Q5 | Single SSH session for `-R` + `-D` or two sessions? | Prefer **one** session | + +--- + +## 17. Changelog + +| Rev | Date | Change | +|-----|------|--------| +| R0 | 2026-06-16 | Initial DR from PO question; postponed pending WG closure | + +--- + +## Related docs + +- [REMOTE_ACCESS_IMPL.md](../REMOTE_ACCESS_IMPL.md) — WG vs RSSH modes +- [REMOTE_ACCESS_VALIDATION.md](../REMOTE_ACCESS_VALIDATION.md) — CLI/mobile WG checks +- [VPN_DEMO_GAPS_20260613.md](../VPN_DEMO_GAPS_20260613.md) — WG demo gaps +- [20260602_REVERSE_SSH_proposals_summary.md](../20260602_REVERSE_SSH_proposals_summary.md) — original transport comparison +- [specs/20100612_1_scaling.md](../specs/20100612_1_scaling.md) — `be-vpn`, RSSH capacity