Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Performance
- **Roughly double bssh-server single-connection SFTP write throughput by fixing channel fragmentation and the serial write path** (#187). Three independent bottlenecks identified in the issue's code audit are addressed. First, `build_russh_config` now advertises a 65535-byte `maximum_packet_size` (the russh cap; library default 32768) and an 8 MiB `window_size` (library default 2 MiB), both configurable via `server.maximum_packet_size` / `server.window_size` (env: `BSSH_MAX_PACKET_SIZE` / `BSSH_WINDOW_SIZE`), so a 256 KiB SFTP write is no longer chopped into 8 CHANNEL_DATA packets that each pay the full cipher/copy/scheduling cost. Second, the SFTP server loop in `bssh-russh-sftp` is restructured from a strict read-process-write-flush cycle into a reader task with a bounded read-ahead queue plus an in-order processor: responses are flushed once per burst instead of once per request, and consecutive queued `SSH_FXP_WRITE` requests to the same handle at sequential offsets are coalesced into a single handler call (bounded by `max_write_coalesce_len`, default 256 KiB) while every merged request id still receives its own status reply, preserving SFTP response ordering and error semantics. The bssh write/read handlers additionally track each open file's cursor and elide the per-chunk `seek` for sequential transfers (append handles are exempt since O_APPEND ignores the cursor). Third, the workspace gains a tuned `[profile.release]` (`lto = "fat"`, `codegen-units = 1`) and documented `RUSTFLAGS` target-CPU guidance for self-builds. Local before/after benchmark (loopback, OpenSSH sftp client, 1 GiB random file, 3-run averages, 20-core Linux box): upload 71 MiB/s to 437 MiB/s (6.2x), download 73 MiB/s to 282 MiB/s (3.9x); an ablation with the code changes but library-default channel sizing lands at 333 / 291 MiB/s, attributing most of the gain to the pipelined write path and the rest of the upload gain to the larger packets. Numbers on the reporter's NIC-limited Xeon will differ; the methodology is documented in the PR. Remaining plan items (per-packet copies inside russh, AES-CTR internals) live in upstream russh since #212 removed the fork and are out of scope here.

### Added
- **Make server-side SSH compression configurable instead of hard-disabled** (#220). The #215 workaround forced the server to advertise only `none` compression for every deployment, with no way for operators to opt back in. A new `server.compression` option (YAML `server.compression: true`, env `BSSH_COMPRESSION`, builder `ServerConfigBuilder::compression`) now controls whether `build_russh_config` advertises russh's default compression list (`none`, `zlib`, `zlib@openssh.com`) or only `none`. The default stays off, so behavior is unchanged unless explicitly enabled, and enabling it logs a warning: russh's delayed-zlib (`zlib@openssh.com`) transport still desyncs a few packets after compression activates post-auth (reproduced on russh 0.61.1 and 0.62.1), dropping clients that negotiate it mid-session. Both settings are covered by unit tests, and the option plus the desync caveat are documented in the man page and config docs.

Expand Down
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,14 @@ path = "src/bin/bssh_server.rs"
name = "bssh-keygen"
path = "src/bin/bssh_keygen.rs"


# Tuned release profile (issue #187): fat LTO plus a single codegen unit let
# the compiler inline across the russh framing / cipher / SFTP hot path,
# which per-packet overhead makes worthwhile for bulk SFTP/SCP transfers.
# For an additional boost on known deployment hardware, build with a pinned
# target CPU, e.g.:
# RUSTFLAGS="-C target-cpu=x86-64-v3" cargo build --release # AVX2-class servers
# RUSTFLAGS="-C target-cpu=native" cargo build --release # this machine only
[profile.release]
lto = "fat"
codegen-units = 1
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,16 @@ Read [ARCHITECTURE](ARCHITECTURE.md) documentation for more information.
cargo build
```

Release builds use a tuned profile (`lto = "fat"`, `codegen-units = 1`) so the SSH framing, cipher, and SFTP hot paths inline across crates. For maximum throughput on known hardware, additionally pin the target CPU:

```bash
# Portable AVX2-class server build (Haswell/Zen and newer)
RUSTFLAGS="-C target-cpu=x86-64-v3" cargo build --release

# Optimized only for the build machine itself
RUSTFLAGS="-C target-cpu=native" cargo build --release
```

### Testing
```bash
cargo test
Expand Down
Loading