Skip to content

v2.3.0

Choose a tag to compare

@inureyes inureyes released this 18 Jul 17:26
· 56 commits to main since this release

New Features

  • Server-side SSH compression is now configurable via server.compression (YAML), BSSH_COMPRESSION (env), and ServerConfigBuilder::compression, replacing the hard-disabled behavior from v2.2.x. The default stays off; enabling it logs a warning about the russh delayed-zlib desync (#220).

Improvements

  • Roughly double single-connection SFTP write throughput on bssh-server. build_russh_config now advertises a 65535-byte maximum packet size and an 8 MiB window (both configurable via server.maximum_packet_size/server.window_size and BSSH_MAX_PACKET_SIZE/BSSH_WINDOW_SIZE), the server SFTP loop pipelines writes with sequential coalescing, and sequential handlers elide the per-chunk seek. Local loopback 1 GiB benchmark: upload 71 to 437 MiB/s (6.2x), download 73 to 282 MiB/s (3.9x) (#187, #224).
  • Set TCP_NODELAY on accepted server sockets, removing a ~37 ms delayed-ACK stall per strictly sequential SFTP round trip (#227).
  • Wire the ssh_config Compression yes|no directive into the russh client; it was parsed but silently ignored before. Compression yes advertises eager zlib, unset/no advertises only none, and zlib@openssh.com is never advertised client-side (#219).

Bug Fixes

  • Fix an SFTP session deadlock under paramiko's unbounded READ prefetch that froze every bssh-server download to a paramiko client at its initial 2 MiB channel window. The SFTP intake queue is now byte-bounded (max_buffered_request_bytes, default 8 MiB) instead of count-bounded, so the reader never stops draining the channel. Client-initiated disconnects are no longer logged at ERROR (#227).
  • Make sftp.root/scp.root chroot usable by re-anchoring client paths under the root; cd, get, open, and stat previously failed with "path outside root" and only bare / and readdir worked. SCP is unified with the sftp.root behavior (#214).
  • Confine absolute SFTP symlink targets to the chroot so a chrooted client can no longer create a link that resolves to the host filesystem (#214).
  • Advertise only none SSH compression by default so clients that negotiate zlib@openssh.com (Cyberduck, sftp -C) no longer complete the handshake and then drop mid-session with SshEncoding: length invalid (#215).

CI/CD Improvements

  • Add a CI MSRV job that reads rust-version from Cargo.toml and runs cargo check --workspace --locked on that toolchain, so the declared MSRV can no longer silently drift from what actually builds.

Technical Details

  • Server SFTP write path: the strict read-process-write-flush loop is restructured into a reader task with a byte-bounded intake queue plus an in-order processor. Responses flush once per burst instead of once per request, and consecutive sequential SSH_FXP_WRITE requests to the same handle are coalesced (max_write_coalesce_len, default 256 KiB) while every merged request id still receives its own status reply.
  • Tuned [profile.release] (lto = "fat", codegen-units = 1) with documented RUSTFLAGS="-C target-cpu=..." guidance for self-builds.
  • New tools/bench/ harness: bench.sh (bssh-server vs OpenSSH SFTP throughput, with optional before/after and single-core pinning), profile.sh (perf plus inferno flamegraph of the upload path), and interop/ (sshj and paramiko round-trip checks). Loopback-only and byte-for-byte verified (#228).
  • Unify the auxiliary binaries on the "Broadcast SSH" name in --help/version output, and add an Apache-2.0 NOTICE file (#210).

Dependencies

  • Drop the vendored bssh-russh fork and build against crates.io russh 0.62.1 now that both fork patches are upstream (PTY Handle::data fix in russh 0.62.0, SHA-1 MAC exclusion in 0.60.2). ssh-key is bumped to =0.7.0-rc.11 to match russh 0.62.x, and channel_open_session is adapted to the new 0.62 handler signature (#212).

Breaking Changes

  • The workspace minimum supported Rust version is raised from 1.88 to 1.93. The old 1.88 floor was already inaccurate (rustyline 18 needs File::lock, stabilized in Rust 1.89); 1.93 aligns with the Launchpad PPA and Ubuntu 26.04 toolchains. Building from source now requires rustc 1.93 or newer.

Known Issues

  • Server-side SSH compression stays off by default because russh's delayed-zlib (zlib@openssh.com) transport desyncs a few packets after compression activates post-auth (reproduced on russh 0.61.1 and 0.62.1). Enabling server.compression advertises zlib/zlib@openssh.com and logs a warning; the underlying desync is left to be fixed upstream.
  • Native Windows client execution is not supported. Linux and macOS are fully supported; WSL2 is the recommended path on Windows (see #213).

What's Changed

  • build: standardize Rust toolchain on 1.93 (PPA build, MSRV, CI verification) by @inureyes in #209
  • docs: add Apache-2.0 NOTICE and align tool naming to Broadcast SSH by @inureyes in #210
  • chore(deps): drop bssh-russh fork, use upstream russh 0.62.1 by @Yaminyam in #212
  • fix(sftp): resolve chroot client absolute paths relative to the root (fixes #214) by @Yaminyam in #218
  • fix(server): offer only none SSH compression to avoid zlib@openssh.com desync (fixes #215) by @Yaminyam in #217
  • docs: state native Windows client support is not supported by @inureyes in #221
  • feat(server): make SSH compression configurable instead of hard-disabled by @inureyes in #222
  • fix: wire ssh_config Compression directive into the russh client by @inureyes in #223
  • perf(sftp): raise channel sizing, pipeline server writes, tune release build by @inureyes in #224
  • feat(tools): add SFTP benchmark, flamegraph, and interop harness by @inureyes in #228
  • fix(sftp): fix paramiko prefetch deadlock and enable TCP_NODELAY by @inureyes in #229

Full Changelog: v2.2.3...v2.3.0