Traversio

Dynamic Port Forwarding

Run a local SOCKS proxy and let each local client choose the remote target per connection.

Dynamic forwarding is the answer to this question:

"I want one local proxy, then let my browser, curl, or another client decide which remote service to reach."

This is similar to ssh -D.

Mental Model

browser / curl / DB tool
    -> local SOCKS listener
        -> SSH tunnel
            -> target chosen by the SOCKS client for each connection

The final target comes from the local SOCKS request, so the forwarding target is chosen per connection.

Example

import Traversio

@available(macOS 26.0, iOS 26.0, *)
func withSOCKSTunnel(configuration: SSHClientConfiguration) async throws {
    try await SSHClient.withConnection(configuration: configuration) { connection in
        try await connection.withDynamicPortForwarding(
            localHost: "127.0.0.1",
            localPort: 0
        ) { forward in
            print("SOCKS proxy ready on \(forward.localHost):\(forward.localPort)")

            // While this scope stays open, point local tools at the SOCKS endpoint.
            // Example:
            // curl --socks5-hostname 127.0.0.1:\(forward.localPort) http://127.0.0.1:8080/health
        }
    }
}

If local tools should authenticate before they can use the SOCKS listener, pass socks5Authentication:

try await connection.withDynamicPortForwarding(
    localHost: "127.0.0.1",
    localPort: 0,
    socks5Authentication: .usernamePassword(
        username: "local-user",
        password: "local-secret"
    )
) { forward in
    print("Authenticated SOCKS proxy ready on \(forward.localHost):\(forward.localPort)")
}

Good Use Cases

Dynamic forwarding is a better fit than fixed local forwarding when:

  • the target host and port vary per connection
  • you want to browse multiple internal sites through one SSH-backed SOCKS endpoint
  • a local tool already supports SOCKS and you do not want to create one fixed tunnel per target

A typical example is:

  • internal HTTP admin UI on one host
  • internal PostgreSQL on another host
  • one local SOCKS endpoint
  • each client picks the destination it needs

How It Differs From Local Port Forwarding

Both modes create something on your machine.

The difference is:

  • local forwarding creates a normal TCP listener for one fixed target
  • dynamic forwarding creates a SOCKS listener and the target is chosen later by each client

If the target is always known up front, fixed local forwarding is usually simpler.

Supported SOCKS Scope

Supported behavior:

  • SOCKS5 with no-auth negotiation
  • SOCKS5 username/password auth through socks5Authentication: .usernamePassword(...)
  • SOCKS4 connect when SOCKS5 auth is not configured
  • SOCKS4a connect when SOCKS5 auth is not configured

Limits:

  • enabling SOCKS5 username/password auth disables SOCKS4 and SOCKS4a so local clients cannot bypass auth
  • no UDP associate path
  • this is a closure-scoped Apple 26+ helper

Local client failures are isolated per connection:

  • a SOCKS handshake/auth rejection closes that local client connection
  • the listener stays available for later local clients in the same forwarding scope

Not The Same As A Connection Proxy

Dynamic forwarding means Traversio becomes a SOCKS server for other local tools after SSH is already connected.

If Traversio itself needs to reach the SSH server through an external SOCKS5 or HTTP proxy first, that is a Connection Proxy, configured with SSHClientConfiguration.connectionProxy.

Validation

As of April 7, 2026:

  • the SOCKS5 dynamic-forward path is live-validated against a real local OpenSSH 9.6 target in both no-auth and username/password modes
  • SOCKS4 and SOCKS4a have deterministic coverage, and their live-validation depth is narrower than the SOCKS5 path

Lifetime And Shutdown

The same practical rules as local forwarding apply:

  • the SOCKS listener exists only inside withDynamicPortForwarding(...)
  • the owning SSHConnection must stay alive
  • shutdown follows a best-effort listener teardown model

On this page