Restart-survivable sessions change the core model (per-session holder
processes + SCM_RIGHTS fd passing). Keep them OFF by default so deploying
the build can't flip the daily driver's session model unproven; enable with
persist_sessions = true after stress-testing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make dosh terminal sessions survive a dosh-server restart
(crash/upgrade/systemctl restart): a reattaching client lands on the
SAME shell with screen state intact, instead of a fresh one.
Design: when persist_sessions is on (new config, default true) each
terminal session's shell runs in a small detached per-session holder
process (the dosh-server binary re-exec'd as `dosh-server hold`). The
holder double-forks + setsid into its own session, opens the PTY, spawns
the shell as ITS child, and serves the PTY master fd over a per-session
Unix socket via SCM_RIGHTS. The server adopts that fd to build a PtyHandle
that DETACHES (does not kill the shell) on drop, so a server exit leaves
holder + shell alive. On startup the server scans the runtime dir under
sessions_dir/run, reconnects to each live holder, receives the master fd
again, restores the persisted vt100 screen, and rebuilds the Session.
Screen/scrollback (server-memory only) is mirrored to disk atomically:
byte-throttled on the output hot path plus a 2s periodic flush, and
restored on re-adoption so reattach repaints. Truly-abandoned persistent
sessions are still reaped per the existing grace logic by asking the
holder to shut down. Anything in the persistent path failing degrades
gracefully to the original in-process shell.
PtyHandle gains an Owned (kill-on-drop, unchanged default) vs Adopted
(detach-on-drop) backing; resize on an adopted master uses TIOCSWINSZ.
No wire-format change, so protocol::VERSION stays 3. New deps: libc
(direct). New config: persist_sessions (default true); existing
integration tests pin it false to keep exercising the non-persistent
path unchanged.
Adds tests/integration_smoke.rs::session_survives_server_restart_same_
shell_and_screen: attaches, sets `cd /tmp; export MARK=...`, paints a
screen marker, KILLs the server, restarts it on the same config, reattaches
and asserts same shell pid + MARK + PWD + restored screen. Plus persist.rs
unit tests (name round-trip, screen save/load, dead-holder scan cleanup,
SCM_RIGHTS fd round-trip). cargo fmt --check and cargo test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Item 1: mosh-style disconnect status line. When UDP packets stop arriving,
paint a single reverse-video cyan "[dosh] last contact Ns ago — reconnecting…"
on the terminal's bottom row using save/restore cursor (ESC7/ESC8) so the app
cursor never moves and full-screen TUIs are not corrupted. Cleared the instant a
packet resumes and refreshed on the status timer tick. Configurable via
client.toml `disconnect_status` (default on) and `DOSH_DISCONNECT_STATUS` env
override. State machine and rendered escape sequences are pure and unit-tested.
Item 2: re-evaluate the prediction display policy on the select-loop status tick
via `Predictor::refresh_policy`, so a latency spike flips speculation on (or off
on recovery) promptly instead of waiting for the next keystroke. Repaints only
when the on-screen result would change, so idle ticks are no-ops.
Tests: 8 new client unit tests (status state machine + overlay bytes, predictor
spike/recovery/idle). fmt + full test suite green. No wire-format change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track A protocol hardening for native-v1.
1. Protocol VERSION discipline (protocol.rs, native.rs, dosh-server.rs,
dosh-client.rs): bump protocol::VERSION to 2 and NATIVE_PROTOCOL_VERSION to 2.
Foreign wire-version datagrams now get a clear named "protocol version
mismatch - upgrade dosh" reject from the server instead of a silent timeout
(peek_foreign_wire_version + VERSION_MISMATCH_REASON). The native handshake's
plaintext protocol_version is also checked server-side before any crypto via
check_native_protocol_version, returning a typed ProtocolVersionMismatch.
2. Transport rekey (spec section 11/9): server rotates a client's traffic key
after rekey_after_packets OR rekey_after_secs (config knobs). Rotated keys are
derived independently of the handshake keys from the current key plus fresh
server CSPRNG material shipped confidentially in an AEAD Rekey packet
(derive_rekey_session_key). The previous epoch's key is retained briefly so
in-flight pre-rekey packets still decrypt (matched by session_key_id), and
stale-epoch packets are ignored, not fatal. Client handles Rekey/RekeyAck.
3. Connection migration (spec section 11): every authenticated, replay-accepted
packet now migrates client.endpoint to the new source address (input, resize,
ping, ack, resume, stream*, rekey-ack), not just resume. Ping now verifies its
AEAD tag before acting so migration cannot be spoofed.
4. Native auth rate limiting: per-source-IP token bucket
(native_auth_rate_limit_per_minute) enforced in handle_native_client_hello
BEFORE any X25519/Ed25519 work; over-limit hellos get a non-crypto reject.
5. Speed: replaced O(sessions x clients) per-packet linear scans with an O(1)
HashMap<conn_id, session_name> index in ServerState, kept in sync on every
client insert/remove (attach handlers, detach, remove_client, cleanup reap,
session-exit drain).
New config keys (ServerConfig, with defaults): rekey_after_packets = 100000,
rekey_after_secs = 3600.
Tests: version-mismatch reject (no hang), rate-limit flood reject, rekey
round-trip end-to-end + key-derivation unit tests, source-address migration,
client-index sync + cleanup purge. fmt and full test suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the basic printable-only predictor with a clean-room speculative
local-echo engine modeled on the Mosh paper's prediction overlay design
(no GPL code copied or translated; original Rust written from the
algorithm).
Model:
- Per-line cell model with a local cursor; printable keystrokes echo
immediately, dimmed (and underlined on a very slow link) via
save/restore cursor so full-screen TUIs are never corrupted.
- Backspace and in-line Left/Right arrow prediction (CSI and SS3).
- Epoch grouping per keystroke burst; anything unmodelable (CR/LF,
escapes, control bytes, wide chars, right margin, paste-sized bursts)
ends the epoch safely rather than mispredicting.
- Frame-sequenced confirmation: an authoritative server frame supersedes
and erases the overlay (dosh has no client-side emulator, so it cannot
do Mosh's per-cell content match; frame arrival is the confirmation).
- Glitch detection: contradicting output (e.g. alternate-screen entry)
discards the epoch.
- SRTT-based display policy with hysteresis and a latency-spike override;
modes off / experimental(adaptive, default) / always via new
client config `predict_mode` and `DOSH_PREDICT_MODE` env override.
Improvements over the prior predictor: in-line arrow motion, dim+flag
styling, exact-width erase, paste guard, and faster (frame-arrival)
confirmation. Prediction is display-only and never swallows input.
Adds unit tests: printable run, confirmation-clears, glitch-discards,
backspace, line-wrap budget, paste-skip, newline, inline arrows, and the
experimental SRTT threshold gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>