Library lifecycle and callbacks
On this page
- Create the context and pool
- Create and attach frontends
- Fixed TCP port forwarding
- Callback and memory rules
- Commands and keepalives
- Stop and destroy
- Source references
Include <OuterSSH/OuterSSH.h>. Public functions use outerssh_; outersshpriv_, reactor structures, and OTDispatch implementation APIs are internal.
Create the context and pool
outerssh_context_create() returns a context or NULL. Pools created with it, and their frontends, share a serial control queue; each SSH connection has a separate reactor queue. The HTTP frontend additionally owns a queue for its local HTTP connections.
On Apple platforms, outerssh_context_create_with_dispatch_queue(queue) accepts an application’s serial Dispatch queue. It retains the queue. Keep work on it short; do not suspend it or block it waiting for an OuterSSH callback. Scheduling work there does not wait for previously requested network operations to finish. There is no corresponding public custom-queue API on Linux or Windows.
outerssh_session_pool_create(context, &configuration, &error) creates one server/account pool. The application supplies a hostname, SSH port, username, authentication, optional agent path, optional host-key validator, and optional bridge-binary loader. Creation copies configuration strings and authentication bytes. It does not perform the SSH connection synchronously.
The library does not resolve OpenSSH configuration aliases or load a private key file from a filename on the application’s behalf. Supply the resolved connection fields and key contents. The standalone CLI has its own OpenSSH configuration support.
Authentication and host identity
| Authentication kind | Inputs and behavior |
|---|---|
OUTERSSH_SESSION_AUTHENTICATION_PRIVATE_KEY |
Private-key bytes, optional public-key bytes and passphrase. An explicitly configured agent may be tried before the supplied key. |
OUTERSSH_SESSION_AUTHENTICATION_PASSWORD |
Password bytes. The implementation can fall back to keyboard-interactive using the supplied password; it is not a general UI callback for arbitrary MFA prompts. |
OUTERSSH_SESSION_AUTHENTICATION_AGENT |
Explicit agent socket/pipe path. No fallback to a private key. |
A non-NULL password with zero length is an empty password. NULL with zero length means absent. Private-key passphrases cannot contain embedded NUL bytes.
The optional OuterSSHProxyHostKeyValidator runs before login authentication, on the control queue. It receives the server’s public key bytes, key type, and a completion/context. Call the completion exactly once with domain == OUTERSSH_PROXY_ERROR_NONE to accept the key, or a populated error to reject it. The completion can run on any queue, including synchronously inside the validator. OuterSSH copies error text before the completion returns.
For a trust prompt, dispatch UI work and return promptly; invoke the completion when the user answers. Host-key bytes and the validator context remain valid until the completion is called. Do not block the control queue or use those borrowed values after completing. The authentication timeout starts after approval; the server may still disconnect while a decision is pending.
Complete outstanding decisions even when the navigation is cancelled or pool destruction has been requested. Pool destruction waits for their completions; a late approval cannot revive a cancelled connection. Applications should dismiss pending prompts and complete them with a rejection during shutdown rather than waiting for pool destruction first.
Omitting the validator accepts the host key. OuterSSH does not automatically maintain known_hosts or show a trust prompt. That policy belongs to the application.
Create and attach frontends
Pass the session pool to outerssh_http_proxy_server_create. The proxy inherits its control queue. Set rewrite_request_authorities and rewrite_loopback_urls_in_html independently; zero-initialization leaves both false. Supply HTTP proxy credentials through endpoint, or NULL for unauthenticated HTTP. realm is an optional NUL-terminated string.
The following is a configuration excerpt, not a complete startup program. user, password, their lengths, pool, and error are supplied by the host:
OuterSSHProxyEndpointValue credentials = {
.username = user,
.username_length = user_length,
.password = password,
.password_length = password_length,
};
OuterSSHHTTPProxyServerConfiguration configuration = {
.endpoint = &credentials,
.rewrite_request_authorities = true,
.rewrite_loopback_urls_in_html = true,
};
OuterSSHHTTPProxyServer *http =
outerssh_http_proxy_server_create(pool, &configuration, &error);
Check creation failure, then call outerssh_http_proxy_server_start. Port 0 chooses an available port. The endpoint callback reports the actual port and credentials; its pointers are borrowed.
The proxy APIs are declared in HTTPProxyServer.h and SOCKSProxyServer.h; shared endpoint values and callbacks live in ProxyTypes.h.
For SOCKS, create an OuterSSHSOCKSProxyServer with outerssh_socks_proxy_server_create(pool, &configuration, &error), then call outerssh_socks_proxy_server_start. A zero-initialized SOCKS configuration requests password authentication with generated credentials. This differs from HTTP: HTTP’s NULL endpoint means no authentication, not generated credentials.
Each frontend uses one fixed pool for its lifetime. Multiple frontends may share that pool; each owns its credentials and streams. Keep the pool alive until frontend teardown as described below.
Fixed TCP port forwarding
PortForward.h exposes OuterSSHPortForward, independently of the proxy-server APIs. Create one with outerssh_port_forward_create(pool, host, host_length, remote_port, &error), then call outerssh_port_forward_start(forward, local_port, completion, context). The destination bytes are copied; no terminator is required. The pool is fixed at creation; no separate registration is needed.
A local port of 0 chooses an available port. The start callback receives that port and an error, without proxy credentials. Connect the browser directly to http://127.0.0.1:<local-port>/; every connection reaches the fixed destination through the pool. The remote server resolves the destination hostname. Traffic has no proxy negotiation or HTTP rewriting.
outerssh_port_forward_stop closes the listener and schedules active streams for closure. Start may be called again after stopping or after startup fails. outerssh_port_forward_destroy finishes teardown; keep the pool alive until its destroy completion runs. Install outerssh_port_forward_set_connection_failure_handler to receive individual forwarding failures.
Callback and memory rules
Public operations may be submitted from any queue while their objects remain usable. Do not assume that a callback means “main thread,” or that every callback is deferred.
| Callback | Execution and lifetime |
|---|---|
| Pool operation completion; SOCKS start/stop/lease completion; port-forward start/stop completion | Exactly once. Normally the control queue; validation or allocation failure may complete synchronously on the caller’s queue. |
| Frontend connection-failure and sudo-recovery handlers | Control queue. |
| HTTP start completion | HTTP frontend’s own queue; failure to allocate the start operation completes synchronously on the caller’s queue. |
| HTTP destroy completion | HTTP frontend’s own queue. |
| Socket-availability handler | Control queue; may run many times during a watch. |
| Host-key validator | Control queue; accepts an immediate or deferred completion from any queue. |
| Packaged-binary loader and its byte-release callback | Control queue. |
| Pool, SOCKS, and port-forward destroy completion | Exactly once when supplied, asynchronously on the control queue after teardown. |
Initialize a callback context fully before submitting an operation. A completion may consume/free it before the call returns. Copy borrowed results before posting them to UI code.
Most strings are const char * plus a separate UTF-8 byte count, excluding a terminator. Do not substitute character counts or read bytes[length]. Swift uses string.utf8.count; C++ uses the size of its UTF-8 string. The address helpers produce terminated output while returning a length that excludes the terminator.
| Value | Ownership |
|---|---|
| Pool configuration strings, authentication, public command/input, socket paths and sudo passwords | Copied before the corresponding public call returns. |
| Configuration callback contexts | Transferred to the pool only on successful creation. On creation failure the caller still owns them. |
| Operation context | Caller-defined storage; normally released by its completion. No generic operation-context destructor is supplied. |
| Command result, stdout/stderr, proxy endpoint, error text, availability path | Borrowed for the callback. Copy before retaining or crossing queues. |
| Handler context with a supplied destructor | Released on replacement or object destruction; registration failure can invoke its destructor synchronously. |
| Packaged binary | Remains valid until its separate release callback; see the socket contract. |
OuterSSHProxyError contains domain/codes and borrowed message/detail pointers with lengths. Assigning the struct does not extend its text’s lifetime. outerssh_proxy_error_copy(error, &storage) makes an owned copy; free storage after its final use. Test error.domain, not whether the message is non-NULL. Creation errors have process-lifetime text where their public headers specify it.
The remaining public C-string exceptions include HTTP realm and printf-style format strings. Those arguments do require termination.
Commands and keepalives
outerssh_session_pool_execute_command works without any proxy. It copies command and input before returning; its completion receives borrowed stdout, stderr, exit status, and an error. Check both the OuterSSH error and the remote exit status. Output is collected rather than exposed as a streaming callback; the implementation imposes output and execution limits. There is no public per-command cancellation handle.
outerssh_session_pool_poke requests keepalive checks for existing connections, for example after application activity or wake. It is not an instruction to navigate or install an application.
Stop and destroy
SOCKS stop closes the listener, clears leases, cancels pending connections, and schedules closure of its active streams. Its completion does not mean every SSH reactor has drained. The pool remains usable. Call start to restart through the same pool; it is idempotent on the current port and fails for a different nonzero port while active. HTTP has no public stop/restart API: start it once and destroy it once.
SOCKS lease IDs are 16 bytes. Acquiring the same ID is idempotent; releasing the last lease stops the server; releasing an unknown ID does nothing. HTTP has no matching public lease API. Outer Loop’s mobile HTTP leases are adapter bookkeeping.
For an application using both HTTP and SOCKS:
- Stop browser activity and finish submitting public calls from other queues. Cancel application prompts and settle their recovery completions.
- Request HTTP frontend destruction and wait for its completion. Its shutdown begins on a different queue.
- Submit SOCKS server destruction, if present, then pool destruction. These submissions are ordered on the shared control queue; SOCKS destruction need not finish before the pool-destroy call is submitted.
- Use the destroy completions if the application must know that local sockets, dispatch sources, operations, and owned callback contexts have been released.
If the pool also has fixed port forwards, destroy them and wait for their completions before destroying the pool.
If several frontends share the pool, destroy all of them first. Destroying a frontend does not destroy its pool. After calling destroy, do not use that object again, even from an earlier operation’s completion. Pending operations still complete; destruction must wait for outstanding recovery completions.
Pool destruction attempts remote executable cleanup, but this is best effort and has no error result. Local application work posted by callbacks can outlive library destruction; the host owns that work’s lifetime.
outerssh_context_destroy is synchronous and separate. Once child creation is finished, the context may be destroyed: pools and frontends retain their queue independently. Keeping a context or queue alive does not keep a pool or frontend alive.