From c1a4ea94dd648c9126efe2a4c5c12e7a6c465709 Mon Sep 17 00:00:00 2001 From: Jeongkyu Shin Date: Tue, 1 Sep 2026 17:33:23 +0900 Subject: [PATCH] docs: make user-facing guidance self-contained Replace issue, pull request, and historical-version references in active CLI help, runtime warnings, manpages, README, current release notes, and public Rust documentation with durable behavior-focused guidance. Add help-rendering regression checks for all three binaries so internal tracking references cannot silently return. Validation includes the full workspace test suite with localhost SSH authentication, all 16 actual help paths, product-code Clippy with warnings denied, formatting, and diff checks. --- CHANGELOG.md | 58 ++++++++++----------- README.md | 69 ++++++++++--------------- crates/bssh-russh-sftp/README.md | 2 +- debian/changelog | 57 ++++++++++---------- docs/architecture/interactive-mode.md | 2 +- docs/architecture/ssh-client.md | 2 +- docs/architecture/ssh-config-parser.md | 2 +- docs/architecture/ssh-jump-hosts.md | 6 +-- docs/man/bssh-server.8 | 6 +-- docs/openssh-regress.md | 4 +- docs/security.md | 2 +- src/bin/bssh_keygen.rs | 8 +++ src/bin/bssh_server.rs | 8 +++ src/cli/bssh.rs | 21 +++++++- src/forwarding/spec.rs | 4 +- src/jump/chain.rs | 7 ++- src/jump/chain/auth.rs | 2 +- src/jump/chain/tunnel.rs | 3 +- src/pty/session/constants.rs | 6 +-- src/pty/session/input.rs | 4 +- src/server/config/loader.rs | 2 +- src/server/config/mod.rs | 15 +++--- src/server/config/types.rs | 14 +++-- src/server/mod.rs | 7 +-- src/ssh/client/command.rs | 2 +- src/ssh/client/connection.rs | 1 - src/ssh/control/config.rs | 2 +- src/ssh/tokio_client/channel_manager.rs | 4 +- 28 files changed, 166 insertions(+), 154 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbf1d5a..6f263663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,47 +10,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Moved single-destination mode onto an OpenSSH-compatible command-line and runtime contract measured against the pinned OpenSSH `V_10_3_P1` regression suite. ### Added -- **Write diagnostics to a dedicated log with `-E`** (#289). Attached and separate path forms append tracing, warnings, and terminal errors to a private file while remote command stderr remains on file descriptor 2. -- **Dump the effective SSH configuration with `-G`** (#310). Resolution includes CLI precedence, ordered `Host`, `Match`, and `Include` processing, token expansion, forwarding settings, and bounded `Match exec` evaluation. -- **Apply OpenSSH session and transport options** (#302, #303, #311). Runtime support now covers algorithm lists, retries, TCP keepalive, environment requests, local and remote commands, TTY and subsystem policy, null stdin, and `-W` stdio forwarding. -- **Reuse authenticated connections through bssh control sockets** (#312, #313). `-M`, `-O check|forward|cancel|exit|stop|proxy`, `ControlPath`, and `ControlPersist` support passenger sessions, forwarding changes, background masters, and bounded same-user control handlers. -- **Forward the local authentication agent and display server authentication banners** (#313). Agent forwarding is session-scoped and follows OpenSSH's `-A` meaning. +- **Write diagnostics to a dedicated log with `-E`**. Attached and separate path forms append tracing, warnings, and terminal errors to a private file while remote command stderr remains on file descriptor 2. +- **Dump the effective SSH configuration with `-G`**. Resolution includes CLI precedence, ordered `Host`, `Match`, and `Include` processing, token expansion, forwarding settings, and bounded `Match exec` evaluation. +- **Apply OpenSSH session and transport options**. Runtime support now covers algorithm lists, retries, TCP keepalive, environment requests, local and remote commands, TTY and subsystem policy, null stdin, and `-W` stdio forwarding. +- **Reuse authenticated connections through bssh control sockets**. `-M`, `-O check|forward|cancel|exit|stop|proxy`, `ControlPath`, and `ControlPersist` support passenger sessions, forwarding changes, background masters, and bounded same-user control handlers. +- **Forward the local authentication agent and display server authentication banners**. Agent forwarding is session-scoped and follows OpenSSH's `-A` meaning. ### Security -- **Honor every configured user and global known-hosts store** (#290). Verification, `accept-new` recording, path expansion, revoked keys, malformed files, and multi-file matching now use the resolved `UserKnownHostsFile` and `GlobalKnownHostsFile` policy. -- **Use `HostKeyAlias` as the trust identity without changing the network target** (#292). Direct, interactive, forwarding, and jump-host paths share the aliased lookup and recording behavior. -- **Enforce host-key policy and verified key rotation** (#304). `CheckHostIP`, `HashKnownHosts`, `KnownHostsCommand`, DNSSEC-aware SSHFP checks, `VerifyHostKeyDNS`, and proof-backed `UpdateHostKeys` are active at runtime. -- **Enforce authentication policy and user certificates** (#306). Identity selection, accepted algorithms, authentication order, batch mode, password prompt limits, and certificate-to-key pairing now affect live authentication. -- **Escape control characters at SSH configuration diagnostic boundaries** (#309). Paths, patterns, keywords, cache errors, include errors, validation errors, and tracing output cannot inject terminal or log lines. +- **Honor every configured user and global known-hosts store**. Verification, `accept-new` recording, path expansion, revoked keys, malformed files, and multi-file matching now use the resolved `UserKnownHostsFile` and `GlobalKnownHostsFile` policy. +- **Use `HostKeyAlias` as the trust identity without changing the network target**. Direct, interactive, forwarding, and jump-host paths share the aliased lookup and recording behavior. +- **Enforce host-key policy and verified key rotation**. `CheckHostIP`, `HashKnownHosts`, `KnownHostsCommand`, DNSSEC-aware SSHFP checks, `VerifyHostKeyDNS`, and proof-backed `UpdateHostKeys` are active at runtime. +- **Enforce authentication policy and user certificates**. Identity selection, accepted algorithms, authentication order, batch mode, password prompt limits, and certificate-to-key pairing now affect live authentication. +- **Escape control characters at SSH configuration diagnostic boundaries**. Paths, patterns, keywords, cache errors, include errors, validation errors, and tracing output cannot inject terminal or log lines. ### Fixed -- **Preserve exact stdout and stderr bytes for one SSH destination** (#291). Single-host execution no longer adds prefixes, banners, summaries, ANSI styling, UTF-8 conversion, synthesized newlines, or forwarding status messages. -- **Execute `ProxyCommand` instead of silently connecting directly** (#293). Managed child transports implement OpenSSH proxy precedence, bounded stderr capture, token expansion, exit diagnostics, and deterministic cleanup. -- **Replace opaque SSH I/O errors with stage-specific diagnostics** (#294). DNS, TCP, negotiation, host-key, authentication, channel, command, and timeout failures retain their causes and use SSH-compatible exit status 255. -- **Apply configured forwarding directives to live connections** (#305). Local, remote, and dynamic forwards honor ordering, clear semantics, failure policy, allocated remote ports, connection limits, and deterministic teardown. -- **Honor source binding and IPQoS for each target and jump hop** (#307). `BindAddress`, `BindInterface`, address-family matching, and traffic class are applied before TCP establishment without losing the original host alias policy. -- **Enforce byte and time based `RekeyLimit` policies** (#308). Client and server transports count directional payload bytes, rekey after authentication, and reset their limits after each completed key installation. -- **Stop silently accepting unused SSH configuration keywords** (#309). The registry classifies all 91 accepted spellings as 51 runtime-supported and 40 explicitly unimplemented entries that emit one source-aware warning. +- **Preserve exact stdout and stderr bytes for one SSH destination**. Single-host execution no longer adds prefixes, banners, summaries, ANSI styling, UTF-8 conversion, synthesized newlines, or forwarding status messages. +- **Execute `ProxyCommand` instead of silently connecting directly**. Managed child transports implement OpenSSH proxy precedence, bounded stderr capture, token expansion, exit diagnostics, and deterministic cleanup. +- **Replace opaque SSH I/O errors with stage-specific diagnostics**. DNS, TCP, negotiation, host-key, authentication, channel, command, and timeout failures retain their causes and use SSH-compatible exit status 255. +- **Apply configured forwarding directives to live connections**. Local, remote, and dynamic forwards honor ordering, clear semantics, failure policy, allocated remote ports, connection limits, and deterministic teardown. +- **Honor source binding and IPQoS for each target and jump hop**. `BindAddress`, `BindInterface`, address-family matching, and traffic class are applied before TCP establishment without losing the original host alias policy. +- **Enforce byte and time based `RekeyLimit` policies**. Client and server transports count directional payload bytes, rekey after authentication, and reset their limits after each completed key installation. +- **Stop silently accepting unused SSH configuration keywords**. The registry classifies all 91 accepted spellings as 51 runtime-supported and 40 explicitly unimplemented entries that emit one source-aware warning. ### CI/CD -- **Gate compatibility with the pinned OpenSSH regression suite** (#288). Linux and macOS CI validate an audited client-test inventory, compare candidate failures with the reference client, bound timeouts and logs, and enforce committed pass floors. -- **Raise the measured compatibility score to the epic gate** (#313). The final harness records 60 passes out of 66 eligible tests on macOS and 61 out of 69 on Ubuntu. +- **Gate compatibility with the pinned OpenSSH regression suite**. Linux and macOS CI validate an audited client-test inventory, compare candidate failures with the reference client, bound timeouts and logs, and enforce committed pass floors. +- **Raise the measured compatibility score to the epic gate**. The final harness records 60 passes out of 66 eligible tests on macOS and 61 out of 69 on Ubuntu. ### Dependencies -- **Add an internal bssh-russh 0.63.1 workspace member** (#304). The fork adds bounded host-key proof support, post-authentication automatic rekey, compatible key-exchange diagnostics, and default exclusion of legacy SSH-RSA/SHA-1 host signatures. -- **Add DNSSEC-capable SSHFP resolution and cryptographic proof dependencies** (#304). The root package now directly uses hickory-resolver, SHA-1, SHA-2, HMAC, signature, and Base64 support required by host-key verification and rotation. +- **Add an internal bssh-russh 0.63.1 workspace member**. The fork adds bounded host-key proof support, post-authentication automatic rekey, compatible key-exchange diagnostics, and default exclusion of legacy SSH-RSA/SHA-1 host signatures. +- **Add DNSSEC-capable SSHFP resolution and cryptographic proof dependencies**. The root package now directly uses hickory-resolver, SHA-1, SHA-2, HMAC, signature, and Base64 support required by host-key verification and rotation. ### Breaking Changes -- **Restore OpenSSH meanings for seven colliding short flags** (#313). Native bssh 2.x scripts must replace the former bssh meanings as follows: `-N` with `--no-prefix`, `-f` with `--filter`, `-C` with `--cluster`, `-A` with `--use-agent`, `-S` with `--sudo-password`, `-k` with `--fail-fast`, and `-b` with `--batch`. The letters now mean no remote command, background after authentication, compression, agent forwarding, control path, disabled GSSAPI credential delegation, and source bind address respectively. pdsh meanings remain available in pdsh compatibility mode. -- **Treat `-A` as agent forwarding rather than local agent authentication** (#313). Update 2.x scripts to `--use-agent` before running them with 3.0 because an unchanged `-A` expands the remote host's access to the local agent. -- **Change one-destination execution to the SSH-compatible raw output path** (#291). Use `-H host` to retain bssh's multi-host banner, prefixes, progress UI, forwarding status, and summary for a single host. -- **Write `-V` version output to stderr instead of stdout** (#291). This matches OpenSSH and can require redirection changes in scripts. +- **Restore OpenSSH meanings for seven colliding short flags**. Native bssh 2.x scripts must replace the former bssh meanings as follows: `-N` with `--no-prefix`, `-f` with `--filter`, `-C` with `--cluster`, `-A` with `--use-agent`, `-S` with `--sudo-password`, `-k` with `--fail-fast`, and `-b` with `--batch`. The letters now mean no remote command, background after authentication, compression, agent forwarding, control path, disabled GSSAPI credential delegation, and source bind address respectively. pdsh meanings remain available in pdsh compatibility mode. +- **Treat `-A` as agent forwarding rather than local agent authentication**. Update 2.x scripts to `--use-agent` before running them with 3.0 because an unchanged `-A` expands the remote host's access to the local agent. +- **Change one-destination execution to the SSH-compatible raw output path**. Use `-H host` to retain bssh's multi-host banner, prefixes, progress UI, forwarding status, and summary for a single host. +- **Write `-V` version output to stderr instead of stdout**. This matches OpenSSH and can require redirection changes in scripts. ### Known Issues -- **Six OpenSSH regression candidates still fail on both CI platforms** (#314): `agent-restrict`, `dynamic-forward`, `hostkey-agent`, `multiplex`, `percent`, and `proxyjump`. -- **Two additional candidates fail only on Ubuntu** (#314): `rekey` and legacy remote-to-remote `scp3`; the reference client also fails those cases in the macOS fixture. -- **Forty recognized SSH configuration spellings remain unimplemented** (#281, #309). They emit a source-aware warning instead of being silently discarded. -- **Control sockets use a private bssh protocol rather than OpenSSH mux v4** (#312, #314). `-O conninfo` and remaining OpenSSH control operations are not supported. +- **Some OpenSSH interoperability paths remain incomplete**. Restricted-agent forwarding, agent-backed host certificates, some dynamic-forwarding forms, multiplexing interoperability, percent-token expansion, and ProxyJump edge cases are not yet fully compatible. +- **Ubuntu validation also has gaps around rekeying and legacy remote-to-remote `scp -3`**. The reference client fails the corresponding macOS fixtures as well. +- **Forty recognized SSH configuration spellings remain unimplemented**. They emit a source-aware warning instead of being silently discarded. +- **Control sockets use a private bssh protocol rather than OpenSSH mux v4**. `-O conninfo` and remaining OpenSSH control operations are not supported. ## [2.4.3] - 2026-08-24 diff --git a/README.md b/README.md index cf7e6703..15b3bb8d 100644 --- a/README.md +++ b/README.md @@ -12,19 +12,19 @@ A high-performance SSH client with **SSH-compatible syntax** for both single-hos ## Recent Updates -- **v3.0.0 (2026/09/01):** Made single-destination sessions byte-transparent, applied SSH configuration at runtime, added OpenSSH configuration, session, forwarding, and multiplexing options, and restored seven colliding short flags to their OpenSSH meanings. The pinned OpenSSH suite passes 60 tests on macOS and 61 on Ubuntu (#275, #313). -- **v2.4.3 (2026/08/24):** Fix bssh-to-bssh SFTP downloads above 255 KiB, upgrade to russh 0.63.1 with two security fixes and host-certificate refusal, sync bssh-russh-sftp 2.4.0, and repair release automation for API-key notarization and Homebrew updates (#268, #269, #270, #272, #273). -- **v2.4.2 (2026/08/14):** Republish the macOS binaries with a Developer ID signature and Apple notarization. The v2.4.1 and earlier macOS builds are killed on launch and deleted by macOS because their signing certificate was revoked. No source changes (#264). -- **v2.4.1 (2026/08/03):** Close the four host key and forwarding known issues from v2.4.0: `accept-new` no longer disables verification without a home directory or an unusable known_hosts file, first-use recording is serialized across processes, `@cert-authority` lines can be rejected, forwarding targets keep their hostnames for the server to resolve, SOCKS4 honors `-4`/`-6`, and SOCKS5 accepts IPv6 destination literals (#242, #243, #255, #256, #257). -- **v2.4.0 (2026/08/03):** Implement real TOFU verification for the default `accept-new` host key mode, make `bssh ping` report a 0/1/255 exit code, wire up the `-4`/`-6` flags and ssh_config `AddressFamily` across connect, forwarding, and jump chains, resolve ssh_config settings per target host, and accept bracketed IPv6 host literals (#238, #239, #245, #246, #248, #249, #251). -- **v2.3.1 (2026/07/29):** Restore keyboard protocols leaked by disconnected PTY applications, preserve an outer TUI's keyboard state, raise the MSRV and Debian/Launchpad toolchain to Rust 1.96, and make Homebrew formula updates safer (#231, #232, #233, #235, #237). -- **v2.3.0 (2026/07/18):** Roughly double bssh-server SFTP write throughput, fix the paramiko prefetch deadlock (with TCP_NODELAY), make `sftp.root`/`scp.root` chroot usable, make server SSH compression configurable, and drop the vendored `bssh-russh` fork for upstream russh 0.62.1 (#187, #212, #214, #215, #227). -- **v2.2.3 (2026/05/25):** Sync both internal russh forks to upstream stable and patch RUSTSEC-2026-0009 (a `time` stack-exhaustion DoS), raising the minimum supported Rust to 1.88 (#207, #208). -- **v2.2.2 (2026/05/25):** Keep idle SSH sessions alive by lowering the default `--server-alive-interval` to 30s and leaving the client inactivity timeout disabled (#206). -- **v2.2.1 (2026/05/19):** Dependency upgrade pass and russh fork sync picking up both halves of CVE-2026-46673, plus a bssh-russh dev-deps fix so its inline tests compile (#203, #204, #205). -- **v2.2.0 (2026/05/18):** Collect `--password` once and share it across parallel tasks, add `BSSH_PASSWORD`, resolve all cargo-audit findings, and drop five redundant dependencies (#198, #199, #200, #201). -- **v2.1.4 (2026/05/10):** Stream SFTP transfers in 255 KiB chunks (~160x lower memory, ~11x faster 1 GiB upload) and pipeline up to 64 concurrent requests (#195, #196, #197). -- **v2.1.3 (2026/04/30):** Fix SCP/SFTP path doubling and chroot config, vendor `russh-sftp` with a serde_bytes perf fix (+29% upload), and forward-port unreleased upstream russh fixes (#186). +- **v3.0.0 (2026/09/01):** Made single-destination sessions byte-transparent, applied SSH configuration at runtime, added OpenSSH configuration, session, forwarding, and multiplexing options, and restored seven colliding short flags to their OpenSSH meanings. The pinned OpenSSH suite passes 60 tests on macOS and 61 on Ubuntu. +- **v2.4.3 (2026/08/24):** Fix bssh-to-bssh SFTP downloads above 255 KiB, upgrade to russh 0.63.1 with two security fixes and host-certificate refusal, sync bssh-russh-sftp 2.4.0, and repair release automation for API-key notarization and Homebrew updates. +- **v2.4.2 (2026/08/14):** Republish the macOS binaries with a Developer ID signature and Apple notarization. The v2.4.1 and earlier macOS builds are killed on launch and deleted by macOS because their signing certificate was revoked. No source changes. +- **v2.4.1 (2026/08/03):** Close the four host key and forwarding known issues from v2.4.0: `accept-new` no longer disables verification without a home directory or an unusable known_hosts file, first-use recording is serialized across processes, `@cert-authority` lines can be rejected, forwarding targets keep their hostnames for the server to resolve, SOCKS4 honors `-4`/`-6`, and SOCKS5 accepts IPv6 destination literals. +- **v2.4.0 (2026/08/03):** Implement real TOFU verification for the default `accept-new` host key mode, make `bssh ping` report a 0/1/255 exit code, wire up the `-4`/`-6` flags and ssh_config `AddressFamily` across connect, forwarding, and jump chains, resolve ssh_config settings per target host, and accept bracketed IPv6 host literals. +- **v2.3.1 (2026/07/29):** Restore keyboard protocols leaked by disconnected PTY applications, preserve an outer TUI's keyboard state, raise the MSRV and Debian/Launchpad toolchain to Rust 1.96, and make Homebrew formula updates safer. +- **v2.3.0 (2026/07/18):** Roughly double bssh-server SFTP write throughput, fix the paramiko prefetch deadlock (with TCP_NODELAY), make `sftp.root`/`scp.root` chroot usable, make server SSH compression configurable, and drop the vendored `bssh-russh` fork for upstream russh 0.62.1. +- **v2.2.3 (2026/05/25):** Sync both internal russh forks to upstream stable and patch RUSTSEC-2026-0009 (a `time` stack-exhaustion DoS), raising the minimum supported Rust to 1.88. +- **v2.2.2 (2026/05/25):** Keep idle SSH sessions alive by lowering the default `--server-alive-interval` to 30s and leaving the client inactivity timeout disabled. +- **v2.2.1 (2026/05/19):** Dependency upgrade pass and russh fork sync picking up both halves of CVE-2026-46673, plus a bssh-russh dev-deps fix so its inline tests compile. +- **v2.2.0 (2026/05/18):** Collect `--password` once and share it across parallel tasks, add `BSSH_PASSWORD`, resolve all cargo-audit findings, and drop five redundant dependencies. +- **v2.1.4 (2026/05/10):** Stream SFTP transfers in 255 KiB chunks (~160x lower memory, ~11x faster 1 GiB upload) and pipeline up to 64 concurrent requests. +- **v2.1.3 (2026/04/30):** Fix SCP/SFTP path doubling and chroot config, vendor `russh-sftp` with a serde_bytes perf fix (+29% upload), and forward-port unreleased upstream russh fixes. _See [CHANGELOG.md](./CHANGELOG.md) for the complete version history._ @@ -51,7 +51,7 @@ _See [CHANGELOG.md](./CHANGELOG.md) for the complete version history._ ## Platform Support - **Linux and macOS**: Fully supported, including SSH agent authentication (`--use-agent`), PTY-based interactive sessions, and every CLI feature documented here. -- **Windows (native)**: Not currently supported as a client. The `bssh` crate does not build for a Windows target today because of unconditional Unix-only dependencies (`nix`, `signal-hook`, `libc`) and un-gated PTY/agent code; there is no Windows CI job or release artifact. Use **WSL2** to run `bssh` on Windows in the meantime. See [#213](https://github.com/lablup/bssh/issues/213) for the current status and the known blockers to native Windows client support. +- **Windows (native)**: Not currently supported as a client. The `bssh` crate does not build for a Windows target today because of unconditional Unix-only dependencies (`nix`, `signal-hook`, `libc`) and un-gated PTY/agent code; there is no Windows CI job or release artifact. Use **WSL2** to run `bssh` on Windows. ## Installation @@ -551,40 +551,27 @@ bssh --cluster production upload local.txt /tmp/ bssh -H "host1,host2" download /etc/hosts ./backups/ ``` -## Breaking Changes in v1.2.0 +## Exit Code Strategies -**⚠️ Exit Code Behavior Changed**: v1.2.0 now returns the main rank's exit code by default (matching MPI standard tools like mpirun/srun/mpiexec). +By default, bssh returns the main rank's exit code, matching MPI-oriented +tools such as `mpirun`, `srun`, and `mpiexec`. Choose an explicit strategy +when a workflow must account for every node. -### What Changed +| Strategy | Behavior | Use case | +|----------|----------|----------| +| Default | Return the main rank's actual exit code | MPI workloads and CI/CD | +| `--require-all-success` | Return 0 only when every node succeeds; otherwise return 1 | Health checks | +| `--check-all-nodes` | Return the main rank's code, or 1 if the main rank succeeds but another node fails | Cluster validation | -| Version | Behavior | Use Case | -|---------|----------|----------| -| **v1.0-v1.1** | Returns 0 if all succeed, 1 if any fails | Health checks | -| **v1.2.0+** (default) | Returns main rank's actual exit code | MPI workloads, CI/CD | - -### Migration Guide - -**MPI Workloads** - ✅ No changes needed: -```bash -# Now returns actual exit codes: 0, 139 (SIGSEGV), 137 (OOM), etc. -bssh exec "mpirun ./simulation" -``` - -**Health Checks** - Add `--require-all-success` flag: +### Examples ```bash -# v1.0-v1.1 -bssh exec "health-check" +# Preserve the main rank's actual exit status. +bssh -H "node[1-8]" "mpirun ./simulation" -# v1.2.0+ (preserve old behavior) -bssh --require-all-success exec "health-check" +# Fail the health check if any node fails. +bssh --require-all-success -H "node[1-8]" "health-check" ``` -### Available Strategies - -- **Default**: Return main rank's exit code (MPI standard) -- **`--require-all-success`**: Return 0 only if all nodes succeed -- **`--check-all-nodes`**: Return main rank code, or 1 if main OK but others failed - See [examples/mpi_exit_code.sh](examples/mpi_exit_code.sh) and [examples/health_check.sh](examples/health_check.sh) for detailed examples. ## Authentication diff --git a/crates/bssh-russh-sftp/README.md b/crates/bssh-russh-sftp/README.md index 95496b5a..9278408f 100644 --- a/crates/bssh-russh-sftp/README.md +++ b/crates/bssh-russh-sftp/README.md @@ -17,7 +17,7 @@ Re-applied on sync from `patches/pipelined-file-io.patch`. ### Server: request read-ahead and write coalescing (`src/server/mod.rs`) -The serial request loop is replaced by a byte-bounded intake queue plus a processor, adding two `server::Config` knobs: `max_buffered_request_bytes` (default 8 MiB) and `max_write_coalesce_len` (default 256 KiB). Read-ahead keeps the transport decrypting requests while the handler is blocked on file I/O, and consecutive `SSH_FXP_WRITE`s to the same handle at sequential offsets are merged into one handler call while each request id still gets its own status reply. The unbounded-in-count, bounded-in-bytes intake is deliberate: stalling intake can deadlock against the russh session loop waiting on channel window (see issue lablup/bssh#227, paramiko's unbounded READ prefetch). +The serial request loop is replaced by a byte-bounded intake queue plus a processor, adding two `server::Config` knobs: `max_buffered_request_bytes` (default 8 MiB) and `max_write_coalesce_len` (default 256 KiB). Read-ahead keeps the transport decrypting requests while the handler is blocked on file I/O, and consecutive `SSH_FXP_WRITE`s to the same handle at sequential offsets are merged into one handler call while each request id still gets its own status reply. The unbounded-in-count, bounded-in-bytes intake is deliberate: stalling intake can deadlock when unbounded client read-ahead fills the russh channel window. Re-applied on sync from `patches/server-readahead-write-coalescing.patch`. diff --git a/debian/changelog b/debian/changelog index 54af744f..cd88636c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,63 +2,60 @@ bssh (3.0.0-1~jammy1) jammy; urgency=medium * v3.0.0 ### Added - - Add -E diagnostic logs and -G effective SSH configuration output - (#289, #310). + - Add -E diagnostic logs and -G effective SSH configuration output. - Apply algorithm, environment, command, TTY, subsystem, null-stdin, - and stdio-forwarding policies at runtime (#302, #303, #311). + and stdio-forwarding policies at runtime. - Add same-user connection multiplexing, forwarding control, - background masters, and ControlPersist (#312, #313). - - Add session-scoped SSH agent forwarding and authentication banners - (#313). + background masters, and ControlPersist. + - Add session-scoped SSH agent forwarding and authentication banners. ### Security - Honor configured user and global known-hosts stores and HostKeyAlias - across every connection path (#290, #292). + across every connection path. - Enforce DNSSEC SSHFP checks, KnownHostsCommand, hashed host names, - and proof-backed host-key rotation (#304). + and proof-backed host-key rotation. - Enforce identity, certificate, algorithm, authentication order, - batch mode, and password prompt policies (#306). - - Escape control characters in SSH configuration diagnostics (#309). + batch mode, and password prompt policies. + - Escape control characters in SSH configuration diagnostics. ### Fixed - Preserve exact stdout and stderr bytes for one SSH destination - without banners, prefixes, styling, summaries, or added newlines - (#291). + without banners, prefixes, styling, summaries, or added newlines. - Execute ProxyCommand transports instead of silently connecting - directly (#293). + directly. - Report typed SSH connection stages and causes instead of opaque I/O - errors (#294). + errors. - Apply local, remote, and dynamic forwarding directives to live - connections (#305). + connections. - Honor BindAddress, BindInterface, and IPQoS for targets and jump - hops (#307). - - Enforce byte and time based RekeyLimit policies (#308). + hops. + - Enforce byte and time based RekeyLimit policies. - Warn once for each accepted but unimplemented SSH configuration - keyword instead of silently discarding it (#309). + keyword instead of silently discarding it. ### CI/CD - Gate Linux and macOS compatibility with the pinned OpenSSH 10.3 - regression suite (#288). - - Record 60 eligible passes on macOS and 61 on Ubuntu (#313). + regression suite. + - Record 60 eligible passes on macOS and 61 on Ubuntu. ### Dependencies - Add the internal bssh-russh 0.63.1 workspace member and DNSSEC, - hashing, signing, and proof dependencies for host-key rotation - (#304). + hashing, signing, and proof dependencies for host-key rotation. ### Breaking Changes - Restore OpenSSH meanings for -N, -f, -C, -A, -S, -k, and -b. Native bssh 2.x scripts must use --no-prefix, --filter, --cluster, --use-agent, --sudo-password, --fail-fast, and --batch for their - former meanings (#313). + former meanings. - Treat -A as remote agent forwarding. Existing scripts that meant local agent authentication must change to --use-agent before use - with 3.0 (#313). + with 3.0. - Route one-destination execution through the raw SSH output path; - use -H host to retain the multi-host user interface (#291). - - Write -V output to stderr instead of stdout (#291). + use -H host to retain the multi-host user interface. + - Write -V output to stderr instead of stdout. ### Known Issues - - Track six cross-platform and two Ubuntu-only OpenSSH regression - candidate failures in #314. + - Keep restricted-agent forwarding, agent-backed host certificates, + some dynamic forwarding, multiplexing interoperability, percent-token + expansion, ProxyJump, rekeying, and legacy scp -3 limitations explicit. - Keep 40 recognized but unimplemented SSH configuration spellings - explicit and source-warned under #281 and #309. + explicit and source-warned. - Use a private bssh control protocol rather than OpenSSH mux v4; - conninfo and remaining control operations are tracked in #314. + conninfo and remaining control operations are unsupported. -- Jeongkyu Shin Tue, 01 Sep 2026 12:59:35 +0900 diff --git a/docs/architecture/interactive-mode.md b/docs/architecture/interactive-mode.md index ae393d72..0ca63a6d 100644 --- a/docs/architecture/interactive-mode.md +++ b/docs/architecture/interactive-mode.md @@ -197,7 +197,7 @@ Before starting the PTY input task, bssh uses a DA-delimited query transaction o Teardown resets leaked stacks on both buffers and restores both captured Kitty values plus `modifyOtherKeys`. It also tracks fragmented remote `1049h`/`1049l` transitions, but commits a transition only after the bytes survive response filtering and `write_all` succeeds. Its streaming parser skips 7-bit OSC, DCS, APC, PM, and SOS payloads through fragmented terminators—BEL is accepted only for OSC compatibility, while every string type accepts `ESC \` ST—and accepts only validated private CSI parameters, preventing embedded escape-looking data from changing cleanup state. Raw 8-bit C1 controls are not interpreted because their byte values can also occur as UTF-8 continuation bytes. If bssh began on main and the connection dies in a remote-owned 1049 alternate screen, cleanup uses `1049l` to restore the remote-saved cursor before restoring both screens' keyboard state. The reverse correction is intentionally non-destructive: if bssh began inside an outer alternate screen and the remote reset 1049, cleanup selects the original buffer with mode 47 instead of forcing `1049h`. Generic terminal protocols expose only one cursor-save slot, and `1049h` clears the alternate buffer, so the 1049 mode bit, saved cursor, and existing outer contents cannot all be reconstructed exactly in that case. Keyboard state and screen contents take priority over claiming an impossible exact mode-bit restoration. -If a DA transaction or the screen-state query is unsupported, malformed, or times out, cleanup falls back to the ordinary-shell baseline from #234 rather than claiming partial preservation. The cleanup snapshot is published with poison recovery and reused by normal, forced, and repeated cleanup paths. A single-owner RAII token covers raw and deferred-raw guards alike so they cannot race the process-global snapshot, while a deferred guard that never enters raw mode performs no terminal cleanup when dropped. +If a DA transaction or the screen-state query is unsupported, malformed, or times out, cleanup falls back to the ordinary-shell baseline rather than claiming partial preservation. The cleanup snapshot is published with poison recovery and reused by normal, forced, and repeated cleanup paths. A single-owner RAII token covers raw and deferred-raw guards alike so they cannot race the process-global snapshot, while a deferred guard that never enters raw mode performs no terminal cleanup when dropped. To validate outer-TUI preservation in Ghostty and Kitty or WezTerm: diff --git a/docs/architecture/ssh-client.md b/docs/architecture/ssh-client.md index 8c86c593..0630ed25 100644 --- a/docs/architecture/ssh-client.md +++ b/docs/architecture/ssh-client.md @@ -618,7 +618,7 @@ let auth_method = auth_ctx.determine_method?; - Uses `zeroize` crate to clear passwords and passphrases from memory - Secure passphrase prompts via `rpassword` crate - No credential caching or storage -- Platform-specific handling (SSH agent not supported on Windows; native Windows client execution is not currently supported at all, see the README's [Platform Support](../../README.md#platform-support) section and [#213](https://github.com/lablup/bssh/issues/213)) +- Platform-specific handling (SSH agent not supported on Windows; native Windows client execution is not currently supported, as described in the README's [Platform Support](../../README.md#platform-support) section) **Code Reduction:** - Eliminated ~130 lines of duplicated authentication logic diff --git a/docs/architecture/ssh-config-parser.md b/docs/architecture/ssh-config-parser.md index b652e306..e9a24cdb 100644 --- a/docs/architecture/ssh-config-parser.md +++ b/docs/architecture/ssh-config-parser.md @@ -207,7 +207,7 @@ bssh supports 40+ SSH configuration directives organized into categories: - `ConnectTimeout` - Connection timeout in seconds - `ServerAliveInterval` - Keepalive interval - `ServerAliveCountMax` - Keepalive retry count -- `Compression` - Advertise transport compression to the server (yes/no, default: no). `yes` offers eager `zlib` ahead of `none`; `no`/unset offers only `none`. `zlib@openssh.com` is never advertised regardless of this setting, because russh's delayed-zlib codec desyncs the flate2 stream a few packets after activation (see the server-side fix in #215); wiring is in `SshConnectionConfig::to_russh_config` (`src/ssh/tokio_client/connection.rs`). +- `Compression` - Advertise transport compression to the server (yes/no, default: no). `yes` offers eager `zlib` ahead of `none`; `no`/unset offers only `none`. `zlib@openssh.com` is never advertised because russh's delayed-zlib codec desyncs the flate2 stream a few packets after activation; wiring is in `SshConnectionConfig::to_russh_config` (`src/ssh/tokio_client/connection.rs`). **Authentication Options:** - `IdentityFile` - SSH private key file (multiple allowed) diff --git a/docs/architecture/ssh-jump-hosts.md b/docs/architecture/ssh-jump-hosts.md index 4772c4b4..19a1b9be 100644 --- a/docs/architecture/ssh-jump-hosts.md +++ b/docs/architecture/ssh-jump-hosts.md @@ -330,7 +330,7 @@ pub fn get_max_jump_hosts -> usize { - No compilation warnings (after clippy allows) - Successfully handles multi-hop scenarios -### SSH Config ProxyJump Integration (Issue #117 - Implemented) +### SSH Config ProxyJump Integration **Implementation:** `src/executor/connection_manager.rs`, `src/app/initialization.rs` @@ -395,7 +395,7 @@ bssh -H db.internal "uptime" # Uses db-gateway.example.com - Each operation establishes fresh tunnel - **Rationale:** russh session limitations prevent connection reuse -### YAML Configuration File Support (Issue #115 - Implemented) +### YAML Configuration File Support **Implementation:** `src/config/types.rs`, `src/config/resolver.rs` @@ -440,7 +440,7 @@ clusters: 2. SSH config `ProxyJump` directive 3. YAML config (node → cluster → global) -### Per-Jump-Host SSH Key Configuration (Issue #167 - Implemented) +### Per-Jump-Host SSH Key Configuration **Implementation:** `src/config/types.rs`, `src/jump/chain/auth.rs`, `src/jump/parser/host.rs` diff --git a/docs/man/bssh-server.8 b/docs/man/bssh-server.8 index 31426fa3..9be6e0dd 100644 --- a/docs/man/bssh-server.8 +++ b/docs/man/bssh-server.8 @@ -189,9 +189,9 @@ compression (\fBzlib\fR, \fBzlib@openssh.com\fR). It defaults to \fBfalse\fR, advertising only \fBnone\fR so clients use the uncompressed transport, because the bundled russh library's delayed-zlib (\fBzlib@openssh.com\fR) transport currently desyncs a few packets after compression activates, -dropping clients that negotiate it (Cyberduck, \fBsftp -C\fR) mid-session; -see https://github.com/lablup/bssh/issues/215. Enable it only if the -upstream russh bug is fixed or your clients never negotiate compression. +dropping clients that negotiate it (Cyberduck, \fBsftp -C\fR) mid-session. +Enable it only after verifying compression interoperability with every +deployed client. .TP .B auth Authentication methods and settings (methods, publickey, password) diff --git a/docs/openssh-regress.md b/docs/openssh-regress.md index 3467b9c4..32a7c4e9 100644 --- a/docs/openssh-regress.md +++ b/docs/openssh-regress.md @@ -32,7 +32,7 @@ An upstream `SKIPPED:` result is reported as `skip` and excluded from the eligib The score is `pass / (pass + fail)`. CI compares both the pass count and the environment-valid result count with platform-specific floors in `baseline.json`, uploads the generated JSON table even on failure, and fails when either count drops below its floor. Durations are milliseconds, and `first_failure_line` is the first diagnostic line selected from the captured combined harness output; per-test logs are streamed under `target/openssh-regress/logs/` and capped at 16 MiB each. -The complete #275 implementation run recorded in `tests/openssh-regress/results.json` measured 60 pass, nine fail, four environmental, and six upstream skips on Linux: 60/69 eligible tests. The `agent`, `banner`, `connect-uri`, and `portnum` suites account for the final four passes over the preceding 56/69 measurement. Both Linux and macOS now enforce the epic's 60-pass floor; CI supplies the platform-specific verification on each pull request. +The complete baseline run recorded in `tests/openssh-regress/results.json` measured 60 pass, nine fail, four environmental, and six upstream skips on Linux: 60/69 eligible tests. The `agent`, `banner`, `connect-uri`, and `portnum` suites account for the final four passes over the preceding 56/69 measurement. Both Linux and macOS enforce a 60-pass floor; CI supplies the platform-specific verification on each pull request. The eligible floors remain 67 on Linux and 65 on macOS. The current Linux run produced 69 eligible results from 79 runnable rows after six upstream skips and four environmental results. The lower eligible floors preserve bounded tolerance for platform-specific environmental failures while still rejecting a collapsed all-environmental run. Focused `--test` runs report verdicts without enforcing full-suite floors. @@ -40,4 +40,4 @@ The eligible floors remain 67 on Linux and 65 on macOS. The current Linux run pr The pinned manifest contains 79 runnable client tests and 11 permanent candidate skips, preserving a 90-test candidate inventory. Another 25 server-only or otherwise out-of-scope rows remain explicitly excluded, accounting for all 115 shell tests in the pinned tree. `forwarding` remains runnable because it covers local, remote, and standard-input forwarding; the out-of-scope X11 and tun-device features do not justify skipping it. `allow-deny-users` is excluded as a pure sshd configuration test, while `sftp-chroot` and `reconfigure` remain reasoned permanent sshd-side skips. -The historical measurement in issue #275 reported 116 shell tests and listed `pubkey-priority` as an environmental failure. The exact `V_10_3_P1` tree contains 115 shell tests and its `LTESTS` inventory has no `pubkey-priority`. The committed `results.json` now records the complete current 79-row run, while every run validates `selection.tsv` against the pinned tree and rejects invented or stale test names. +An earlier planning measurement reported 116 shell tests and listed `pubkey-priority` as an environmental failure. The exact `V_10_3_P1` tree contains 115 shell tests and its `LTESTS` inventory has no `pubkey-priority`. The committed `results.json` records the complete current 79-row run, while every run validates `selection.tsv` against the pinned tree and rejects invented or stale test names. diff --git a/docs/security.md b/docs/security.md index 6d7285f0..26d6f563 100644 --- a/docs/security.md +++ b/docs/security.md @@ -258,7 +258,7 @@ way: SFTP symlink target is itself re-anchored under `root`, so a created link can never point at the host filesystem. -When `root` is unset (default since v2.1.3, per #186), the handler runs +When `root` is unset (the default), the handler runs without chroot. Absolute paths are honored verbatim and relative paths resolve from the user's home directory, matching OpenSSH `sftp-server`. This is the recommended default for Backend.AI session containers and any diff --git a/src/bin/bssh_keygen.rs b/src/bin/bssh_keygen.rs index bb70cd11..2339d632 100644 --- a/src/bin/bssh_keygen.rs +++ b/src/bin/bssh_keygen.rs @@ -200,6 +200,14 @@ mod tests { fn test_cli_parsing() { // Verify CLI structure is valid Cli::command().debug_assert(); + + let help = Cli::command().render_long_help().to_string(); + for internal_reference in ["issue #", "PR #", "/issues/", "/pull/"] { + assert!( + !help.contains(internal_reference), + "help contains internal tracking reference {internal_reference:?}" + ); + } } #[test] diff --git a/src/bin/bssh_server.rs b/src/bin/bssh_server.rs index 2ff6ad29..cfedf189 100644 --- a/src/bin/bssh_server.rs +++ b/src/bin/bssh_server.rs @@ -520,6 +520,14 @@ mod tests { // Verify CLI structure is valid Cli::command().debug_assert(); + + let help = Cli::command().render_long_help().to_string(); + for internal_reference in ["issue #", "PR #", "/issues/", "/pull/"] { + assert!( + !help.contains(internal_reference), + "help contains internal tracking reference {internal_reference:?}" + ); + } } #[test] diff --git a/src/cli/bssh.rs b/src/cli/bssh.rs index 1c8d1bab..3a8f217a 100644 --- a/src/cli/bssh.rs +++ b/src/cli/bssh.rs @@ -229,7 +229,7 @@ pub struct Cli { #[arg( long, - help = "Require all nodes to succeed (v1.0-v1.1 behavior)\nDefault: return main rank's exit code (v1.2+)\nUseful for health checks and monitoring where all nodes must be operational" + help = "Require every node to succeed\nReturns 0 only when all nodes succeed; otherwise returns 1\nUseful for health checks and monitoring where all nodes must be operational" )] pub require_all_success: bool, @@ -1115,6 +1115,25 @@ fn migration_long_takes_value(name: &str) -> bool { #[cfg(test)] mod tests { use super::*; + use clap::CommandFactory; + + #[test] + fn help_uses_current_user_facing_terms() { + let help = Cli::command().render_long_help().to_string(); + + for internal_reference in ["issue #", "PR #", "/issues/", "/pull/"] { + assert!( + !help.contains(internal_reference), + "help contains internal tracking reference {internal_reference:?}" + ); + } + for historical_version in ["v1.0-v1.1", "v1.2+"] { + assert!( + !help.contains(historical_version), + "help describes current behavior using historical version {historical_version:?}" + ); + } + } #[test] fn identity_flag_is_repeatable_and_preserves_order() { diff --git a/src/forwarding/spec.rs b/src/forwarding/spec.rs index 105b6555..a06422a5 100644 --- a/src/forwarding/spec.rs +++ b/src/forwarding/spec.rs @@ -146,8 +146,8 @@ impl ForwardingSpec { /// /// Unlike `-L` and `-D`, the bind address here names a listener on the /// *remote* side, which the server creates and which the local `-4`/`-6` - /// flags therefore do not govern. The implicit default stays IPv4 - /// loopback; see issue #246 for the decision. + /// flags therefore do not govern. The implicit default remains IPv4 + /// loopback for compatibility with OpenSSH listener behavior. pub fn parse_remote(spec: &str) -> Result { let spec = Self::normalize_stream_spec(spec)?; let parts = Self::split_stream_fields(&spec)?; diff --git a/src/jump/chain.rs b/src/jump/chain.rs index 30183cde..a263256c 100644 --- a/src/jump/chain.rs +++ b/src/jump/chain.rs @@ -80,7 +80,7 @@ pub struct JumpHostChain { /// Pre-collected SSH password (from the dispatcher's single up-front prompt). /// When `use_password` is set on a per-call basis, this is consumed by every /// jump-host auth step instead of prompting per-call, which would otherwise - /// race N parallel auth tasks into N separate prompts. See issue #200. + /// produce one prompt per concurrent authentication task. ssh_password: Option>, } @@ -126,7 +126,7 @@ impl JumpHostChain { /// Provide the pre-collected SSH password (collected once up-front by the /// dispatcher). When `--password` is used together with `-J `, every /// jump-host auth step consumes this shared secret instead of prompting - /// per-call. See issue #200. + /// per-call. pub fn with_ssh_password(mut self, password: Option>) -> Self { self.ssh_password = password; self @@ -500,8 +500,7 @@ impl Drop for JumpHostChain { /// /// `hop` is the 1-based position of `jump_host` within the full chain (i.e. /// already offset past the first jump host). The host is interpolated only -/// once; see issue #238 for the readability defect this replaced, where the -/// host name was duplicated as both the subject and a trailing echo. +/// once so the rendered error chain does not duplicate the host name. fn intermediate_jump_hop_context(jump_host: &JumpHost, hop: usize) -> String { format!("Failed to connect to jump host {jump_host} (hop {hop})") } diff --git a/src/jump/chain/auth.rs b/src/jump/chain/auth.rs index 2ecc2bb4..11cbf5a8 100644 --- a/src/jump/chain/auth.rs +++ b/src/jump/chain/auth.rs @@ -29,7 +29,7 @@ use std::sync::Arc; /// /// When `use_password` is `true`, the `pre_collected_password` argument MUST /// carry the password the dispatcher collected once up-front via -/// `prompt_password()` (issue #200). Per-call password prompts here would race +/// `prompt_password()`. Per-call password prompts here would race /// across parallel jump-host auth tasks and produce N prompts for N nodes — /// the very bug `--password` was supposed to fix. pub(super) async fn determine_auth_method( diff --git a/src/jump/chain/tunnel.rs b/src/jump/chain/tunnel.rs index 22fac499..19b68a76 100644 --- a/src/jump/chain/tunnel.rs +++ b/src/jump/chain/tunnel.rs @@ -439,8 +439,7 @@ mod tests { assert_eq!(addr.to_string(), "127.0.0.1:22"); } - /// Regression test for the chain-duplication defect issue #238 established - /// a convention against: `resolve_handler_address`'s own error must not + /// Regression coverage ensures that `resolve_handler_address`'s own error does not /// restate `host:port`, since every call site already wraps it with a /// `.with_context()` that names the host and port. #[test] diff --git a/src/pty/session/constants.rs b/src/pty/session/constants.rs index 234c52a8..fe228f5a 100644 --- a/src/pty/session/constants.rs +++ b/src/pty/session/constants.rs @@ -14,9 +14,9 @@ //! Terminal constants and key sequence definitions //! -//! NOTE: Many key sequence constants are currently unused since we switched to -//! raw byte passthrough (see issue #87), but are kept for reference and potential -//! future debugging use. +//! NOTE: Many key sequence constants are currently unused because PTY input now +//! uses raw byte passthrough, but they are kept for reference and potential future +//! debugging use. // Allow dead code for unused key sequence constants #![allow(dead_code)] diff --git a/src/pty/session/input.rs b/src/pty/session/input.rs index f3429926..b2437f3a 100644 --- a/src/pty/session/input.rs +++ b/src/pty/session/input.rs @@ -14,8 +14,8 @@ //! Input event handling for PTY sessions //! -//! NOTE: This module is currently unused since we switched to raw byte passthrough -//! (see issue #87), but is kept for reference, testing, and potential future use. +//! NOTE: This module is currently unused because PTY input now uses raw byte +//! passthrough, but it is kept for reference, testing, and potential future use. // Allow dead code for the entire module #![allow(dead_code)] diff --git a/src/server/config/loader.rs b/src/server/config/loader.rs index 458979b5..a1b5349d 100644 --- a/src/server/config/loader.rs +++ b/src/server/config/loader.rs @@ -55,7 +55,7 @@ use std::path::{Path, PathBuf}; /// - `BSSH_HOST_KEY` - Comma-separated host key paths /// - `BSSH_MAX_CONNECTIONS` - Maximum concurrent connections /// - `BSSH_KEEPALIVE_INTERVAL` - Keepalive interval in seconds -/// - `BSSH_COMPRESSION` - Advertise SSH transport compression ("true"/"false"; default false, see issue #215) +/// - `BSSH_COMPRESSION` - Advertise SSH transport compression ("true"/"false"; default false because delayed-zlib interoperability is limited) /// - `BSSH_MAX_PACKET_SIZE` - Maximum SSH channel packet size in bytes (default 65535, clamped to at most 65535) /// - `BSSH_WINDOW_SIZE` - SSH channel flow-control window size in bytes (default 8388608) /// - `BSSH_AUTH_METHODS` - Comma-separated auth methods (e.g., "publickey,password") diff --git a/src/server/config/mod.rs b/src/server/config/mod.rs index 6f021cb5..7ba878ac 100644 --- a/src/server/config/mod.rs +++ b/src/server/config/mod.rs @@ -224,10 +224,8 @@ pub struct ServerConfig { /// fall back to the uncompressed transport. This is the safe default: /// russh's delayed-zlib (`zlib@openssh.com`) transport desyncs a few /// packets after compression activates post-auth, dropping clients that - /// negotiate it (Cyberduck, `sftp -C`) mid-session. See - /// . Enable only if the - /// upstream russh bug is fixed or your clients never negotiate - /// compression. + /// negotiate it (Cyberduck, `sftp -C`) mid-session. Enable only after + /// verifying compression interoperability with every deployed client. #[serde(default)] pub compression: bool, @@ -235,9 +233,8 @@ pub struct ServerConfig { /// /// Larger packets amortize per-packet cipher and copy overhead; russh's /// library default of 32768 fragments a 256 KiB SFTP write into 8 - /// CHANNEL_DATA packets (see - /// ). Clamped to at most - /// 65535, which russh requires. Default: 65535. + /// CHANNEL_DATA packets. Clamped to at most 65535, which russh requires. + /// Default: 65535. #[serde(default = "default_maximum_packet_size")] pub maximum_packet_size: u32, @@ -667,8 +664,8 @@ impl ServerConfigBuilder { /// /// Disabled by default: russh's delayed-zlib (`zlib@openssh.com`) /// transport desyncs mid-session, so the server advertises only `none` - /// unless this is explicitly enabled (see - /// ). + /// unless this is explicitly enabled. Test interoperability with every + /// deployed client before enabling it. pub fn compression(mut self, enabled: bool) -> Self { self.config.compression = enabled; self diff --git a/src/server/config/types.rs b/src/server/config/types.rs index db993a22..1faa22ac 100644 --- a/src/server/config/types.rs +++ b/src/server/config/types.rs @@ -131,11 +131,9 @@ pub struct ServerSettings { /// /// **Caveat:** russh's delayed-zlib (`zlib@openssh.com`) transport /// currently desyncs a few packets after compression activates post-auth, - /// dropping clients that negotiate it (Cyberduck, `sftp -C`) mid-session - /// (reproduced on russh 0.61.1 and 0.62.1; see - /// ). Enable this only if you - /// have verified the underlying russh bug is fixed or your clients never - /// negotiate compression. + /// dropping clients that negotiate it (Cyberduck, `sftp -C`) mid-session. + /// Enable this only after verifying compression interoperability with + /// every deployed client. /// /// Default: false #[serde(default)] @@ -146,9 +144,9 @@ pub struct ServerSettings { /// Larger packets amortize the per-packet cipher, copy, and scheduling /// overhead of the SSH transport: with the russh library default of /// 32768, a 256 KiB SFTP write is fragmented into 8 CHANNEL_DATA packets, - /// roughly halving single-connection SFTP throughput on slower CPUs (see - /// ). Values above 65535 are - /// clamped because russh rejects packets larger than a TCP frame. + /// roughly halving single-connection SFTP throughput on slower CPUs. + /// Values above 65535 are clamped because russh rejects packets larger + /// than a TCP frame. /// /// Default: 65535 #[serde(default = "default_maximum_packet_size")] diff --git a/src/server/mod.rs b/src/server/mod.rs index 9144baf3..20c8c537 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -207,7 +207,8 @@ impl BsshServer { tracing::warn!( "SSH transport compression enabled; russh's delayed-zlib \ (zlib@openssh.com) desync may drop clients that negotiate \ - compression mid-session (see issue #215)" + compression mid-session; enable it only after testing every \ + deployed client" ); russh::Preferred::DEFAULT } else { @@ -447,8 +448,8 @@ impl russh::server::Server for BsshServerRunner { } /// Whether a session error is an ordinary client-side disconnect (abrupt -/// client exit, network cut) rather than a server fault. These used to be -/// logged at ERROR, which was noise (issue #227). russh's `Error::IO` is +/// client exit, network cut) rather than a server fault. Logging these expected +/// disconnects at ERROR would create operational noise. russh's `Error::IO` is /// `#[error(transparent)]`, which forwards `source()` past the contained /// io::Error, so the io::Error never appears in the anyhow chain on its own; /// it has to be matched through `russh::Error` as well. diff --git a/src/ssh/client/command.rs b/src/ssh/client/command.rs index 4098fc91..881cbd2f 100644 --- a/src/ssh/client/command.rs +++ b/src/ssh/client/command.rs @@ -169,7 +169,7 @@ impl SshClient { /// password (when `--password` is used). Callers in the `download` /// glob-resolution path MUST forward `FileTransferParams::ssh_password` /// here — otherwise the user is prompted twice (once by the dispatcher, - /// once by this connection). See issue #200. + /// once by this connection). #[allow(clippy::too_many_arguments)] pub async fn connect_and_execute_with_host_check( &mut self, diff --git a/src/ssh/client/connection.rs b/src/ssh/client/connection.rs index 04f0f703..b4c8c1ff 100644 --- a/src/ssh/client/connection.rs +++ b/src/ssh/client/connection.rs @@ -70,7 +70,6 @@ fn client_jump_spec<'a>( /// `Display` already says everything the context layer would, and the /// messages it does return add remediation guidance without echoing the /// cause and carry no trailing period (which would render as `".: "`). -/// See issue #238. fn connect_error_message(e: &crate::ssh::tokio_client::Error) -> Option { match e { crate::ssh::tokio_client::Error::KeyAuthFailed => { diff --git a/src/ssh/control/config.rs b/src/ssh/control/config.rs index d5893e2c..173fb2dd 100644 --- a/src/ssh/control/config.rs +++ b/src/ssh/control/config.rs @@ -138,7 +138,7 @@ impl fmt::Display for ControlPersist { } } -/// Commands accepted by OpenSSH-compatible `-O` handling in issue #286. +/// Commands accepted by OpenSSH-compatible `-O` control handling. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[non_exhaustive] #[serde(rename_all = "snake_case")] diff --git a/src/ssh/tokio_client/channel_manager.rs b/src/ssh/tokio_client/channel_manager.rs index 1ce2da66..f29a2bce 100644 --- a/src/ssh/tokio_client/channel_manager.rs +++ b/src/ssh/tokio_client/channel_manager.rs @@ -836,8 +836,8 @@ impl Client { /// 1. Requesting PTY with proper terminal modes via `channel.request_pty()` /// 2. Requesting shell via `channel.request_shell()` /// - /// This change fixes issue #40: PTY should be requested once with proper terminal - /// modes by PtySession::initialize() rather than twice with empty modes. + /// PTY allocation is requested once with the terminal modes supplied by + /// `PtySession::initialize()`, rather than twice with an empty first request. pub async fn request_interactive_shell( &self, _term_type: &str,