Expand native auth and benchmark gates

This commit is contained in:
DuProcess
2026-06-18 09:29:06 -04:00
parent 2835da76b0
commit 25d9a6aefa
13 changed files with 1036 additions and 178 deletions
+334 -1
View File
@@ -4,6 +4,7 @@ use std::net::{TcpListener, TcpStream, UdpSocket};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
@@ -234,6 +235,37 @@ fn start_echo_server() -> u16 {
port
}
fn start_slow_sink_server() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
thread::spawn(move || {
if let Ok((mut stream, _)) = listener.accept() {
let mut buf = [0u8; 8192];
loop {
match stream.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(_) => thread::sleep(Duration::from_millis(20)),
}
}
}
});
port
}
fn connect_with_retry_no_child(port: u16, timeout: Duration) -> TcpStream {
let deadline = std::time::Instant::now() + timeout;
loop {
match TcpStream::connect(("127.0.0.1", port)) {
Ok(stream) => return stream,
Err(err) if std::time::Instant::now() < deadline => {
let _ = err;
thread::sleep(Duration::from_millis(50));
}
Err(err) => panic!("connect to local forward 127.0.0.1:{port}: {err}"),
}
}
}
fn write_native_client_auth(dir: &tempfile::TempDir, config_path: &std::path::Path) {
write_native_client_auth_with_options(dir, config_path, "");
}
@@ -414,6 +446,15 @@ fn direct_attach(
config: &std::path::Path,
port: u16,
mode: &str,
) -> (std::net::UdpSocket, dosh::auth::BootstrapResponse, AttachOk) {
direct_attach_session(config, port, mode, "default")
}
fn direct_attach_session(
config: &std::path::Path,
port: u16,
mode: &str,
session: &str,
) -> (std::net::UdpSocket, dosh::auth::BootstrapResponse, AttachOk) {
let config = load_server_config(Some(config.to_path_buf())).unwrap();
let secret = load_or_create_server_secret(&config).unwrap();
@@ -421,7 +462,7 @@ fn direct_attach(
&config,
&secret,
"tester".to_string(),
"default".to_string(),
session.to_string(),
mode.to_string(),
(80, 24),
crypto::random_12(),
@@ -782,6 +823,106 @@ fn native_local_forward_background_smoke() {
assert_eq!(&buf[..n], b"dosh-background-forward-ping");
}
#[test]
fn native_local_forward_bulk_load_does_not_delay_interactive_terminal() {
use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
let dir = tempfile::tempdir().unwrap();
let port = free_udp_port();
let config = write_server_config(&dir, port);
write_native_client_auth(&dir, &config);
let sink_port = start_slow_sink_server();
let local_port = free_tcp_port();
let mut server = start_server(&dir, &config);
let pty = NativePtySystem::default();
let pair = pty
.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})
.unwrap();
let mut reader = pair.master.try_clone_reader().unwrap();
let mut writer = pair.master.take_writer().unwrap();
let client_bin = env!("CARGO_BIN_EXE_dosh-client");
let mut cmd = CommandBuilder::new(client_bin);
let forward_arg = format!("{local_port}:127.0.0.1:{sink_port}");
let dosh_port = port.to_string();
cmd.args([
"--auth",
"native",
"--no-cache",
"-L",
&forward_arg,
"--session",
"load-session",
"--dosh-host",
"127.0.0.1",
"--dosh-port",
&dosh_port,
"local",
]);
cmd.env("HOME", dir.path().to_string_lossy().to_string());
let mut child = pair.slave.spawn_command(cmd).unwrap();
drop(pair.slave);
let mut forward_stream = connect_with_retry_no_child(local_port, Duration::from_secs(5));
let bulk_writer = thread::spawn(move || {
let chunk = vec![b'x'; 8192];
for _ in 0..128 {
if forward_stream.write_all(&chunk).is_err() {
break;
}
}
});
let (seen_tx, seen_rx) = mpsc::channel();
let reader_handle = thread::spawn(move || {
let mut buf = [0u8; 4096];
let mut output = Vec::new();
loop {
match reader.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => {
output.extend_from_slice(&buf[..n]);
if output
.windows(b"DOSH_LOAD_TERMINAL_OK".len())
.any(|w| w == b"DOSH_LOAD_TERMINAL_OK")
{
let _ = seen_tx.send(String::from_utf8_lossy(&output).to_string());
break;
}
}
}
}
});
thread::sleep(Duration::from_millis(250));
writer
.write_all(b"printf DOSH_LOAD_TERMINAL_OK\\n\r")
.unwrap();
writer.flush().unwrap();
let seen = seen_rx.recv_timeout(Duration::from_secs(3));
let _ = child.kill();
let _ = child.wait();
drop(writer);
drop(pair.master);
let _ = reader_handle.join();
let _ = bulk_writer.join();
let _ = server.kill();
let _ = server.wait();
assert!(
seen.is_ok(),
"interactive terminal output was delayed behind local-forward bulk load"
);
}
#[test]
fn native_remote_forward_echo_smoke() {
let dir = tempfile::tempdir().unwrap();
@@ -1180,6 +1321,67 @@ fn live_output_forwards_terminal_control_sequences() {
);
}
#[test]
fn tui_control_sequences_survive_transport_verbatim() {
let dir = tempfile::tempdir().unwrap();
let port = free_udp_port();
let config = write_server_config(&dir, port);
let mut server = start_server(&dir, &config);
let (socket, bootstrap, ok) = direct_attach(&config, port, "read-write");
let sequences = concat!(
"\x1b[?1049h", // alternate screen on
"\x1b[?2004h", // bracketed paste on
"\x1b[?1000h", // mouse tracking on
"\x1b[?1006h", // SGR mouse encoding on
"\x1b[?25l", // cursor hidden
"\x1b[12;34H", // absolute cursor movement
"DOSH_TUI_VERBATIM",
"\x1b[?25h",
"\x1b[?1006l",
"\x1b[?1000l",
"\x1b[?2004l",
"\x1b[?1049l"
);
let input = Input {
bytes: format!("printf '{sequences}'\n").into_bytes(),
};
send_encrypted(
&socket,
port,
PacketKind::Input,
ok.client_id,
2,
0,
&bootstrap.session_key,
&protocol::to_body(&input).unwrap(),
);
let text = collect_frame_text(&socket, &bootstrap.session_key, 2000);
let _ = server.kill();
let _ = server.wait();
for needle in [
"\x1b[?1049h",
"\x1b[?2004h",
"\x1b[?1000h",
"\x1b[?1006h",
"\x1b[?25l",
"\x1b[12;34H",
"DOSH_TUI_VERBATIM",
"\x1b[?25h",
"\x1b[?1006l",
"\x1b[?1000l",
"\x1b[?2004l",
"\x1b[?1049l",
] {
assert!(
text.contains(needle),
"expected TUI control sequence {needle:?} to survive verbatim, got {text:?}"
);
}
}
#[test]
fn resume_snapshot_preserves_alternate_screen_mode() {
let dir = tempfile::tempdir().unwrap();
@@ -1983,3 +2185,134 @@ fn session_survives_server_restart_same_shell_and_screen() {
"restored attach snapshot must repaint the pre-restart screen, got {snapshot:?}"
);
}
#[test]
fn multiple_persistent_named_sessions_survive_restart_independently() {
let dir = tempfile::tempdir().unwrap();
let sessions_dir = dir.path().join("sessions");
let port = free_udp_port();
let config = write_persistent_server_config(&dir, port);
let mut server = start_server(&dir, &config);
let (work_socket, work_bootstrap, work_ok) =
direct_attach_session(&config, port, "read-write", "work");
let (logs_socket, logs_bootstrap, logs_ok) =
direct_attach_session(&config, port, "read-write", "logs");
let work_key = work_bootstrap.session_key;
let logs_key = logs_bootstrap.session_key;
type_line(
&work_socket,
port,
&work_ok,
&work_key,
2,
"export DOSH_SLOT=work_slot\n",
);
type_line(
&logs_socket,
port,
&logs_ok,
&logs_key,
2,
"export DOSH_SLOT=logs_slot\n",
);
type_line(
&work_socket,
port,
&work_ok,
&work_key,
3,
"printf 'WORK_SCREEN_MARKER\\n'\n",
);
type_line(
&logs_socket,
port,
&logs_ok,
&logs_key,
3,
"printf 'LOGS_SCREEN_MARKER\\n'\n",
);
let work_pre = collect_frame_text(&work_socket, &work_key, 1500);
let logs_pre = collect_frame_text(&logs_socket, &logs_key, 1500);
assert!(
work_pre.contains("WORK_SCREEN_MARKER"),
"work marker missing before restart: {work_pre:?}"
);
assert!(
logs_pre.contains("LOGS_SCREEN_MARKER"),
"logs marker missing before restart: {logs_pre:?}"
);
thread::sleep(Duration::from_secs(3));
let work_pid_before = holder_shell_pid(&sessions_dir, "work");
let logs_pid_before = holder_shell_pid(&sessions_dir, "logs");
assert!(work_pid_before.is_some(), "work holder pid missing");
assert!(logs_pid_before.is_some(), "logs holder pid missing");
assert_ne!(
work_pid_before, logs_pid_before,
"named sessions must have independent holder shells"
);
let _ = server.kill();
let _ = server.wait();
thread::sleep(Duration::from_millis(300));
let mut server = start_server(&dir, &config);
let (work_socket2, work_bootstrap2, work_ok2) =
direct_attach_session(&config, port, "read-write", "work");
let (logs_socket2, logs_bootstrap2, logs_ok2) =
direct_attach_session(&config, port, "read-write", "logs");
let work_key2 = work_bootstrap2.session_key;
let logs_key2 = logs_bootstrap2.session_key;
let work_snapshot = String::from_utf8_lossy(&work_ok2.snapshot).to_string();
let logs_snapshot = String::from_utf8_lossy(&logs_ok2.snapshot).to_string();
type_line(
&work_socket2,
port,
&work_ok2,
&work_key2,
2,
"printf 'WORK_SLOT=%s\\n' \"$DOSH_SLOT\"\n",
);
type_line(
&logs_socket2,
port,
&logs_ok2,
&logs_key2,
2,
"printf 'LOGS_SLOT=%s\\n' \"$DOSH_SLOT\"\n",
);
let work_post = collect_frame_text(&work_socket2, &work_key2, 2000);
let logs_post = collect_frame_text(&logs_socket2, &logs_key2, 2000);
let work_pid_after = holder_shell_pid(&sessions_dir, "work");
let logs_pid_after = holder_shell_pid(&sessions_dir, "logs");
let _ = server.kill();
let _ = server.wait();
kill_holder(&sessions_dir, "work");
kill_holder(&sessions_dir, "logs");
assert_eq!(work_pid_after, work_pid_before);
assert_eq!(logs_pid_after, logs_pid_before);
assert!(
work_snapshot.contains("WORK_SCREEN_MARKER")
&& !work_snapshot.contains("LOGS_SCREEN_MARKER"),
"work snapshot should restore only work screen, got {work_snapshot:?}"
);
assert!(
logs_snapshot.contains("LOGS_SCREEN_MARKER")
&& !logs_snapshot.contains("WORK_SCREEN_MARKER"),
"logs snapshot should restore only logs screen, got {logs_snapshot:?}"
);
assert!(
work_post.contains("WORK_SLOT=work_slot") && !work_post.contains("logs_slot"),
"work state crossed sessions or disappeared: {work_post:?}"
);
assert!(
logs_post.contains("LOGS_SLOT=logs_slot") && !logs_post.contains("work_slot"),
"logs state crossed sessions or disappeared: {logs_post:?}"
);
}