Fix native-v1 connection break, server panic, and resource leaks
The native-v1 work changed the UDP wire format (HEADER_LEN 42->58) without a
version bump, so a current client silently timed out against the older
still-deployed server. The server just needed redeploying; alongside that,
fix the robustness bugs surfaced while restoring the connection:
- Server: clamp terminal size to >=1 at every PTY/parser resize site. A client
reporting a 0x0 terminal made vt100::Parser::set_size panic ("attempt to
subtract with overflow") and took down the whole single-threaded daemon.
- Client: treat size() == Ok((0,0)) like a missing size and fall back to 80x24
instead of sending 0x0 to the server.
- Reap abandoned sessions and their shells. PtyHandle now retains the child and
kills it on drop, and the cleanup task removes clientless, non-prewarmed
sessions after the grace period. Previously the shell leaked forever (one
zsh per abandoned session); prewarmed sessions still stay hot.
- Evict half-finished native handshakes (pending_native) after 30s so an
unauthenticated ClientHello flood can't grow the map without bound.
- Drop the per-client, per-packet vt100 Screen clone on the output hot path and
remove the write-only last_screen field.
- Guard load_or_create_server_secret against a <32-byte secret file (was an
index-out-of-range panic at startup).
- Reconcile stream flow-control credit (client and server) when StreamData hits
a missing writer, so a closed/unknown stream can't wedge the peer's window.
Adds a unit test for session reaping. cargo fmt + cargo test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6c14d669b8
commit
2a6f28d529
+23
-2
@@ -1,5 +1,5 @@
|
||||
use anyhow::{Context, Result};
|
||||
use portable_pty::{CommandBuilder, MasterPty, NativePtySystem, PtySize, PtySystem};
|
||||
use portable_pty::{Child, CommandBuilder, MasterPty, NativePtySystem, PtySize, PtySystem};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -8,6 +8,7 @@ use tokio::sync::mpsc;
|
||||
|
||||
pub struct PtyHandle {
|
||||
writer: Arc<Mutex<Box<dyn Write + Send>>>,
|
||||
child: Mutex<Box<dyn Child + Send + Sync>>,
|
||||
_master: Box<dyn MasterPty + Send>,
|
||||
}
|
||||
|
||||
@@ -28,6 +29,25 @@ impl PtyHandle {
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Terminate the shell process backing this PTY and reap it.
|
||||
///
|
||||
/// Without this the child shell outlives the session: dropping the master
|
||||
/// alone is not guaranteed to take the process down, and the `Child` handle
|
||||
/// used to be discarded at spawn time, which leaked one shell per
|
||||
/// abandoned session.
|
||||
pub fn kill(&self) {
|
||||
if let Ok(mut child) = self.child.lock() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PtyHandle {
|
||||
fn drop(&mut self) {
|
||||
self.kill();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -68,7 +88,7 @@ pub fn spawn_pty_session(
|
||||
} else if let Some(parent) = Path::new(shell).parent() {
|
||||
cmd.env("PWD", parent.as_os_str());
|
||||
}
|
||||
let _child = pair.slave.spawn_command(cmd).context("spawn shell")?;
|
||||
let child = pair.slave.spawn_command(cmd).context("spawn shell")?;
|
||||
drop(pair.slave);
|
||||
|
||||
let writer = pair.master.take_writer().context("take pty writer")?;
|
||||
@@ -110,6 +130,7 @@ pub fn spawn_pty_session(
|
||||
|
||||
Ok(PtyHandle {
|
||||
writer: Arc::new(Mutex::new(writer)),
|
||||
child: Mutex::new(child),
|
||||
_master: pair.master,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user