Skip to content

Conversation

DCjanus
Copy link
Contributor

@DCjanus DCjanus commented Sep 27, 2025

Close #821

host.rs is the core of this change and contains the implementation of host port exposure via SSH reverse tunnels. The file is fairly large and assumes familiarity with the background, so here is a top-down walkthrough to lower the reviewer’s cognitive load, plus an explanation of how tcpip_forward and server_channel_open_forwarded_tcpip work together in the forwarding flow.

HostPortExposure

  • Manages the lifecycle of host port exposure: owns the SSH sidecar container, the SSH handle, and the cancellation token.
  • setup orchestrates the flow in four stages—build plan → launch sidecar → establish SSH → register ports—highlighting the control path and failure handling.
  • shutdown/Drop perform cleanup: trigger the cancellation token and, when running inside Tokio, disconnect the SSH session gracefully.

HostExposurePlan

  • Immutable execution plan that records the requested ports, one-off password, network settings, SSH image version, and retry policy. Every subsequent step reads from this plan only.

Preparation stage

  • prepare_host_exposure handles port deduplication, rejects reserved ports, disallows incompatible network modes and reusable containers, and generates the random password, ensuring the plan has valid inputs.

Sidecar launch

  • spawn_sshd_sidecar boots the ephemeral SSHD based on testcontainers/sshd:1.3.0, exposes port 22, injects the single-use password, and attaches to the user network if present.

SSH session setup

  • connect_with_retry performs an exponential backoff TCP connect to the sidecar and enables TCP_NODELAY.
  • establish_ssh_connection selects IPv4/IPv6 host-port resolution, creates the russh client, authenticates with the generated password, and wires up the handler.

Port registration and callbacks

  • register_requested_ports invokes tcpip_forward for each host port so the SSHD listens on the exact requested value; it fails fast if the server attempts to allocate a different port.
  • tcpip_forward is the SSH remote port forwarding request, effectively mirroring the first half of ssh -R <port>:localhost:<port>: the client asks the server to listen on that port and tunnel any incoming connection back over SSH.
  • server_channel_open_forwarded_tcpip is the russh callback triggered when the SSH server accepts a new connection on a forwarded port. The handler dials localhost:<remote_port> on the host, then bridges the SSH channel and the local TCP stream with copy_bidirectional, completing the second half of the ssh -R behavior. In other words, the full forwarding chain is “declare the listener via tcpip_forward → handle incoming traffic in the callback by wiring up the data path.”

Tunnel handling and teardown

  • HostExposeHandler keeps a CancellationToken so every forwarding task can be cancelled consistently; start_forward_connection spawns the async proxy, while forward_connection pumps data in both directions and shuts streams down cleanly.
  • HostExposeError wraps russh and framework errors and converts them back into TestcontainersError, producing actionable diagnostics.

Reviewing in this order lets you understand the high-level contract first, then drill into port validation, sidecar management, SSH session establishment, forwarding callbacks, and resource cleanup without getting lost in the details.

Copy link

netlify bot commented Sep 27, 2025

Deploy Preview for testcontainers-rust ready!

Name Link
🔨 Latest commit 1bd86f7
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-rust/deploys/68d98639af6a5f0008200e5c
😎 Deploy Preview https://deploy-preview-846--testcontainers-rust.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

memchr = "2.7.2"
parse-display = "0.9.0"
pin-project-lite = "0.2.14"
russh = { version = "0.54.3", default-features = false, features = ["ring", "rsa"] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep it under a feature - looks like a good candidate.
Isolated functionality and we can enable feature by default later.

It's common request to be able to use testcontainers without ring or aws-lc-rs.

@DCjanus DCjanus marked this pull request as draft September 28, 2025 16:50
@DCjanus DCjanus marked this pull request as ready for review September 28, 2025 19:40
@DCjanus
Copy link
Contributor Author

DCjanus commented Sep 28, 2025

Hi @DDtKey, thanks again for the earlier review—your feedback made it much easier to tighten things up. Since that round I’ve:

  • Wrapped the host port exposure implementation behind its feature gate and turned russh into an optional dependency. Because russh insists on exactly one TLS backend feature (ring or aws-lc-rs) being active, the
    new feature now effectively requires enabling precisely one of those; otherwise the crate will refuse to compile.
  • Guarded every runtime touchpoint (async container lifecycle, request wiring, runner, and the integration tests) with #[cfg(feature = "host-port-exposure")], so default builds stay free of the SSH tunnel code
    paths.
  • Updated CI to run both the “feature disabled” matrix and the “feature enabled with ring/aws-lc-rs” permutations. That coverage makes sure we catch the compile-time constraint above whenever someone mixes
    feature flags.
  • Added a networking guide that explains how to enable host port exposure, calls out the TLS backend requirement, and gives code samples, and then routed the page through MkDocs so users can actually find it.

@DCjanus
Copy link
Contributor Author

DCjanus commented Sep 28, 2025

It seems the failing test is unrelated to this patch and may just be flaky. The same CI job passes on my fork, so I don’t have a repro tied to these changes.

Since I don’t have permissions to retry the job here, I may have to wait for someone who does to rerun it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[feature request] Support for Host Port Exposure (host.testcontainers.internal)
2 participants