-
Notifications
You must be signed in to change notification settings - Fork 0
How It Works
etr uses SSH only for the initial bootstrap — to start the server and exchange credentials securely. All subsequent terminal I/O flows over a persistent QUIC connection that survives network interruptions.
1. etr SSHes to the remote host and starts etrs
2. etrs generates an ephemeral self-signed TLS certificate,
binds a random QUIC port, prints "PORT <n> CERT <fingerprint>",
and forks into the background
3. etr reads the port and certificate fingerprint from SSH stdout; SSH closes
4. etr opens a QUIC connection to the server, pinning the received certificate
5. etr sends SessionOpen (session ID + passkey) on the control stream;
etrs responds with SessionAccept
6. Terminal I/O flows on the PTY stream; port-forwards on additional streams
7. On clean disconnect, etrs exits immediately
QUIC runs over UDP, so like raw UDP it isn't tied to a specific network path — etr can reconnect from a new IP or port without the OS discarding connection state. Unlike raw UDP, QUIC provides:
- TLS 1.3 built in — all data is encrypted with no custom crypto needed
- Reliable ordered delivery per stream — no dropped or reordered bytes reach the application
- Multiplexed streams — PTY and each port-forward run independently; a stalled forward can't block the terminal
- Congestion control — built in via QUIC's loss detection
mosh is the inspiration for the bootstrap model. etr differs in a few ways:
- etr uses a proper ordered stream with replay and acknowledgement, not mosh's state-sync model
- etr uses QUIC, which gives reliable delivery, TLS 1.3, and multiplexing for free
- etr supports port forwarding (
-L)
etr runs:
ssh -p <port> <host> etrs
and writes SESSION_ID/PASSKEY/TERM to the SSH process's stdin. etrs generates an ephemeral self-signed TLS certificate, binds a QUIC port, and prints PORT <n> CERT <cert_der_hex> to stdout, then forks:
- Parent process: exits immediately → SSH sees the command return and closes cleanly
-
Child process: calls
setsid(), redirects stdio to/dev/null, writes logs to~/.local/state/etr/etrs.log, and runs the session
etr reads the port and certificate from SSH stdout, then opens a QUIC connection pinning exactly that certificate. No CA or PKI is involved — this is analogous to SSH host-key trust.
The passkey (a random 32-character string generated fresh each session) is exchanged over the SSH-encrypted bootstrap channel. Only a holder of the correct passkey can authenticate a SessionOpen message. The TLS certificate, also transmitted over SSH, prevents man-in-the-middle attacks on the QUIC connection. Together these provide the same security properties as SSH host-key pinning, without requiring the server to be pre-configured.
The client sends a heartbeat every 5 seconds. If 15 seconds pass without any packet:
-
etr's per-connection tasks see QUIC errors and return -
etropens a new QUIC connection to the same server address -
etrsends a newSessionOpenwith the samesession_idandpasskey, plus its currentlast_received_seqwatermarks - The server matches on
session_id+passkey, sendsSessionAcceptwith its own watermarks - Both sides replay any unacknowledged PTY data (seq > peer's watermark)
The server holds session state (shell process, PTY, stream history) for 30 minutes after the last packet. The reconnecting client may come from a different IP or port.
Port-forward streams are not replayed on reconnect — they are re-opened fresh by the client.
Every QUIC stream opened by the client begins with a 1-byte tag:
| Tag | Stream | Content |
|---|---|---|
0x01 |
Control |
SessionOpen / SessionAccept, then Heartbeat / TerminalResize / Disconnect
|
0x02 |
PTY | Sequence-numbered raw chunks (terminal output server→client, stdin client→server) |
0x03 |
Forward |
StreamOpen header then raw bytes (TCP) or UdpDatagram envelopes (UDP) |
See PROTOCOL.md for the full wire format.
| Process | Log location |
|---|---|
etr (client) |
~/.local/state/etr/etr.log (when running interactively with -v) |
etrs (server) |
~/.local/state/etr/etrs.log (on the remote host) |