Traversio

Forwarding

Choose between raw channels, local forwarding, dynamic SOCKS forwarding, remote forwarding, streamlocal forwarding, connection proxies, and ProxyJump.

Traversio's forwarding and routing APIs fall into two families:

  • SSH forwarding, which creates SSH channels after authentication and carries TCP or Unix-socket traffic
  • connection routing, which changes how Traversio reaches the SSH server before the final SSH connection is established

Most API choices come from three questions:

  1. who opens the stream
  2. where the listener lives
  3. whether the endpoint is TCP or a Unix domain socket path

The Main Dimensions

QuestionValuesMeaning
Who opens the stream?Swift code, a local tool, or a remote processThis decides whether you need a raw channel or a listener
Where is the listener?Local machine, SSH server side, or external proxyThis decides local, dynamic, remote, or connection-proxy shape
What kind of endpoint?TCP host:port or Unix socket pathThis decides TCP/IP forwarding or OpenSSH streamlocal forwarding

SSH Forwarding Modes

ModeListener locationTarget selectionSSH channel/requestTraversio APIExample
Direct TCP/IP channelSwift code opens one stream directlyFixed TCP target per channeldirect-tcpipopenDirectTCPIPChannel(...)Swift sends one HTTP request to 127.0.0.1:8080 from the server's network view
Local port forwardingYour local machineFixed remote TCP target per forwarding scopedirect-tcpip per accepted local connectionwithLocalPortForwarding(...)Local browser opens 127.0.0.1:8080, Traversio reaches remote 127.0.0.1:3000
Dynamic SOCKS forwardingYour local machine as a SOCKS serverEach local SOCKS request chooses the targetdirect-tcpip per SOCKS CONNECTwithDynamicPortForwarding(...)A browser or curl reaches many internal services through one local SOCKS endpoint
Remote TCP listenerSSH server sideYour Swift code handles each incoming streamtcpip-forward plus forwarded-tcpipwithRemotePortForwardListener(...)Remote clients connect to 127.0.0.1:8080 on the SSH host, Swift decides how to handle each channel
Remote TCP bridge helperSSH server sideFixed local TCP endpoint per forwarding scopetcpip-forward plus forwarded-tcpipwithRemotePortForwarding(...)Remote clients connect to the SSH host, Traversio bridges traffic to local 127.0.0.1:3000
Direct streamlocal channelSwift code opens one stream directlyFixed remote Unix socket path per channel[email protected]openDirectStreamLocalChannel(...)Swift talks to remote /var/run/docker.sock or /run/postgresql/.s.PGSQL.5432
Remote streamlocal listenerSSH server side as a Unix socket pathYour Swift code handles each incoming stream[email protected] plus [email protected]withRemoteStreamLocalForwardListener(...)Remote processes connect to /tmp/traversio.sock, Swift accepts the channel

Connection Routing Features

These APIs affect how Traversio reaches an SSH server. They run before shell, exec, SFTP, or forwarding work starts.

FeatureRoute shapeTraversio APIExample
Connection proxyApp -> external SOCKS5 or HTTP CONNECT proxy -> SSH serverSSHClientConfiguration.connectionProxyThe first TCP connection goes through a corporate SOCKS5 or HTTP CONNECT proxy
ProxyJumpApp -> SSH jump host -> final SSH serverSSHClientConfiguration.proxyJumpHostsTraversio SSHes to bastion.example.com, opens direct-tcpip to db-admin.internal:22, then starts the final SSH handshake

Mental Models

Direct TCP/IP channel

+-----------------+      +------------+      +----------------+
| Your Swift code | ---> | SSH server | ---> | Remote service |
+-----------------+      +------------+      +----------------+

Direct TCP/IP is the raw primitive.

  • your Swift code opens the stream directly
  • the target is fixed when you call the API
  • callers own the protocol bytes

Use this when your own code wants to speak the target protocol itself. Typical examples are:

  • send one HTTP request to an internal service and parse the raw bytes
  • talk to a custom binary protocol
  • build your own higher-level forwarding behavior on top of one raw channel

Direct streamlocal channels follow the same raw-channel model for Unix domain sockets on the SSH server side.

Direct streamlocal channel

+-----------------+      +------------+      +--------------------------+
| Your Swift code | ---> | SSH server | ---> | Remote Unix socket path |
+-----------------+      +------------+      +--------------------------+

Use this when the remote target is a socket path such as /run/postgresql/.s.PGSQL.5432 and your Swift code should speak the protocol bytes directly.

Local port forwarding

+----------------------+      +----------------+      +------------+      +---------------------+
| Browser / local tool | ---> | Local listener | ---> | SSH server | ---> | Fixed remote target |
+----------------------+      +----------------+      +------------+      +---------------------+

Local port forwarding creates a normal TCP listener on your machine.

  • local tools connect to that local port
  • Traversio forwards everything to one fixed remote target
  • the target is chosen when the forward starts

This is the usual answer for:

  • "the service runs on the server, but I want to open it locally"
  • "I want my browser to visit a remote-only HTTP service"
  • "I want a local SQL client to reach one remote database host"
Dynamic SOCKS forwarding

+----------------------+      +---------------------+      +------------+      +----------------------+
| Browser / curl / DB  | ---> | Local SOCKS listener| ---> | SSH server | ---> | Target chosen by the |
| tool                 |      |                     |      |            |      | SOCKS client request |
+----------------------+      +---------------------+      +------------+      +----------------------+

Dynamic forwarding creates a SOCKS proxy on your machine.

  • local tools connect to the SOCKS listener
  • the final target is selected by each SOCKS request
  • one SOCKS endpoint can reach many different remote services

Use this when one local tool needs flexible routing, for example:

  • a browser accessing several internal sites
  • curl switching between multiple internal HTTP endpoints
  • a database tool connecting to different hosts through one SSH-backed SOCKS proxy
Remote port forwarding

+---------------+      +-----------------+      +------------+      +--------------------+
| Remote client | ---> | Remote listener | ---> | SSH server | ---> | Your local service |
+---------------+      +-----------------+      +------------+      +--------------------+

Remote port forwarding reverses the direction.

  • the listener is created on the SSH server side
  • remote clients connect there
  • Traversio sends that traffic back to your local service

This is the usual answer for:

  • "my app is running locally, but the remote side needs to reach it"
  • "I want to expose a local dev HTTP server through the SSH host"
  • "I want a machine behind the SSH server to connect back to my laptop service"

Remote streamlocal forwarding uses the same reverse direction with a Unix domain socket path on the SSH server side. Use withRemoteStreamLocalForwardListener(...) when the remote side should connect to a socket path and your Swift code wants to handle each incoming stream directly.

Remote TCP Has Two API Levels

Traversio exposes a raw listener and a bridge helper for remote TCP forwarding:

  • withRemotePortForwardListener(...) gives you each incoming SSHForwardedTCPIPChannel. Use it when Swift should inspect origin metadata, choose routing per accepted connection, or speak the channel bytes directly.
  • withRemotePortForwarding(...) accepts incoming remote connections and bridges each one to a fixed local TCP endpoint. Use it when the whole forwarding scope maps to one local service.

One Concrete Example Per Mode

Direct TCP/IP channel

Scenario: an HTTP server is already running on 127.0.0.1:8080 on the remote side, and your Swift code wants to send one request and parse the raw response itself.

Your code opens one direct-tcpip channel and speaks the target protocol directly.

Direct streamlocal channel

Scenario: a Docker daemon or PostgreSQL server on the SSH host listens on a Unix domain socket such as /var/run/docker.sock or /run/postgresql/.s.PGSQL.5432, and your Swift code wants to talk to that socket directly.

Your code opens one [email protected] channel to that socket path and owns the protocol bytes.

Local port forwarding

Scenario: an HTTP server is running on the server side, for example 127.0.0.1:3000, and you want to access it from your laptop at http://127.0.0.1:8080 for debugging.

This is the classic "remote service, local access" tunnel.

Dynamic SOCKS forwarding

Scenario: you want one local SOCKS proxy, then let curl, a browser, or a database GUI decide whether the next connection should go to 127.0.0.1:8080, db.internal:5432, or some other remote-only service.

The local client chooses the target per connection through SOCKS.

Remote TCP listener

Scenario: the SSH server should listen on 127.0.0.1:8080, and your Swift code wants to accept each incoming connection as a raw SSHForwardedTCPIPChannel.

Use withRemotePortForwardListener(...) and handle each accepted channel in Swift.

Remote TCP bridge helper

Scenario: your local machine is running a dev HTTP server on 127.0.0.1:3000, and a remote machine should open http://127.0.0.1:8080 on the SSH server side to reach your local app.

Use withRemotePortForwarding(...) to bridge accepted remote connections back to that local service.

Remote streamlocal listener

Scenario: a remote process expects to connect to a Unix socket path such as /tmp/traversio-agent.sock, and your Swift code wants to handle each accepted stream.

Use withRemoteStreamLocalForwardListener(...). Traversio asks the SSH server to create the socket listener and yields incoming SSHForwardedStreamLocalChannel values.

Connection proxy

Scenario: your app can reach proxy.corp.example.com:8080, and that proxy can reach ssh.internal.example.com:22.

Use SSHClientConfiguration.connectionProxy so the first TCP connection goes through SOCKS5 or HTTP CONNECT before SSH identification exchange starts.

ProxyJump

Scenario: you cannot SSH to db-admin.internal directly, but you can SSH to bastion.example.com, and that bastion can reach the internal host.

Use SSHClientConfiguration.proxyJumpHosts so Traversio first authenticates to the bastion, opens direct-tcpip through it, and starts the final SSH handshake inside that channel.

Choosing The API

Use this rule first:

  • if your local machine gets the listener, think local or dynamic
  • if the SSH server side gets the listener, think remote
  • if Swift opens one raw stream itself, think direct
  • if the SSH server is only reachable through an external proxy service, think connectionProxy
  • if you are solving "how do I reach the SSH server at all", think ProxyJump

Another practical shortcut:

  • fixed target and local browser/tool connects to a local port: withLocalPortForwarding(...)
  • variable target and local browser/tool speaks SOCKS: withDynamicPortForwarding(...)
  • remote TCP clients should reach one fixed local service: withRemotePortForwarding(...)
  • remote TCP clients should be handled by Swift code: withRemotePortForwardListener(...)
  • remote side must reach your Swift code through a Unix socket path: withRemoteStreamLocalForwardListener(...)
  • your Swift code alone needs one raw stream: openDirectTCPIPChannel(...) or openDirectStreamLocalChannel(...)

ProxyJump And Connection Proxies

These two features solve different routing problems:

  • ProxyJump means: first SSH to hop A, then open the next SSH hop through A
  • an HTTP or SOCKS connection proxy means: before SSH starts, tunnel the outer TCP connection through an external proxy service

Traversio supports both models:

  • SSHClientConfiguration.connectionProxy
  • SSHClientConfiguration.proxyJumpHosts

Supported Forwarding Surface

Public APIs in the library:

  • SSHConnection.openDirectTCPIPChannel(...)
  • SSHConnection.openDirectStreamLocalChannel(...)
  • SSHConnection.withLocalPortForwarding(...)
  • SSHConnection.withDynamicPortForwarding(...)
  • SSHConnection.withRemotePortForwardListener(...)
  • SSHConnection.withRemoteStreamLocalForwardListener(...)
  • SSHConnection.withRemotePortForwarding(...)
  • SSHClientConfiguration.connectionProxy
  • SSHClientConfiguration.proxyJumpHosts

Validation summary:

  • direct raw forwarding, local port forwarding, dynamic forwarding, raw remote TCP listeners, and fixed remote bridging have live validation across the documented server families
  • direct streamlocal and remote streamlocal listener paths are validated on OpenSSH streamlocal servers
  • connection proxies are validated for SOCKS5 no-auth, SOCKS5 username/password, HTTP CONNECT, HTTP CONNECT Basic auth, and one combined connectionProxy + proxyJumpHosts route
  • ProxyJump is validated through a real jump-host connection and a nested final-target SSH handshake
  • repeated long-running validation covers forwarding together with shell, SFTP, proxy routing, and rekey-heavy workloads

Forwarding limits to keep in mind:

  • local, dynamic, and remote forwarding are closure-scoped helpers
  • Apple 26+ systems prefer the modern transport and listener backends automatically, and older supported releases use the compatibility backends behind the same public APIs
  • dynamic forwarding supports SOCKS5 with no-auth or username/password auth, plus SOCKS4 and SOCKS4a when SOCKS5 auth is not configured
  • connection proxies cover SOCKS5 and HTTP CONNECT on the outermost TCP connection
  • remote forwarding targets one fixed local endpoint per helper scope, and accepted remote connections stay isolated per connection
  • one accepted local, dynamic, remote TCP, or remote streamlocal channel failing stays scoped to that accepted connection; it does not close unrelated shell, exec, SFTP, or forwarding work on the same SSHConnection
  • local listener shutdown is best-effort: after scope exit Traversio stops bridging data and closes late accepted connections as soon as possible, but it does not promise the port is unconnectable at the exact instant the closure returns
  • remote listener shutdown sends the matching cancel request before scope exit; if the server rejects cancellation, Traversio closes the parent SSHConnection so the remote listener does not remain active on the server
  • streamlocal forwarding uses the OpenSSH streamlocal extensions in the current release line

Use the page below that matches your scenario:

On this page