Relay Client
link-relay-client is the one outbound, authenticated connection from
this machine to the relay, and the Service interface the tunnel and
remote plugins implement to answer its requests. It knows nothing about
ports or models: a plugin hands it a Service. Its wire is the
Protocol.
The connection
run exposes a service through the relay: it dials out, so no inbound
port is needed, keeps the one connection alive, and turns every request
stream the relay sends into a call to the service. It calls on_ready
each time a connection is made, and returns only if the relay rejects
the agent, such as for a bad token or an unsupported version.
Reconnecting
If the connection drops, it reconnects with backoff, at most 30 seconds apart, and asks for the same tunnel name, so the public URL survives. A connection's DNS, TCP, TLS and WebSocket upgrade take 10 seconds at most together, and each of a relay's addresses gets a 250 ms head start before the next is tried (RFC 8305).
AgentConfig
What a connection asks for: the relay's URL, its token and a
TokenSource asked for a new one before a reconnect when it's near
expiry or was refused, the app name, the client's version, the
tunnel's password (basic_auth), the visitors it lets in (allow), a
custom domain, and whether it waits as a standby. The agent refuses to
serve if the relay doesn't confirm it enforces the password or the
visitors, and stops if the relay doesn't confirm the domain.
Connected
What the relay said when the connection was made: the public URL, a
newer client release if the relay knows of one, the operator's notice,
whether the relay requires a password or lets in only allow's
visitors, and where it serves the custom domain.
How much it serves at once
It serves up to 512 of the relay's requests at once. It writes up to 64 messages before a flush, passing them to the socket every 16 KiB, so on a slow uplink a small response isn't held behind a batch's worth of others.
The tunnel's Service interface
A plugin implements Service to answer what the relay sends: call
takes an http::Request<Body> and answers an http::Response<Body>,
and websocket, which answers 501 unless implemented, takes a visitor's
WebSocket.
pub trait Service: Send + Sync + 'static {
fn call(&self, req: http::Request<Body>) -> ResponseFuture<'_>; // -> Result<http::Response<Body>, BoxError>
fn websocket(&self, req: http::Request<()>) -> WsFuture<'_> { /* 501 by default */ }
}Requests and bodies
link-relay-client turns each relay stream into an http::Request and
streams the returned body back chunk by chunk. The request body streams
in as the visitor uploads it (read_body collects it); reading slowly
is fine, since flow control makes the upload wait. A service never sees
stream ids, the relay, or its WebSocket, and an Err becomes a 502 for
the visitor.
Visitors' WebSockets
For a visitor's WebSocket, websocket returns WsUpgrade::Accepted
with a WsSocket, a pair of message connections, or a normal response.
websocket::connect bridges to a local ws:// server.
exposes_an_in_process_service in tests/e2e is the smallest example.
What else it asks the relay
accounts asks a relay which account service it takes logins from
(GET /_link/accounts), and the audience it takes relay tokens for:
where lnk auth login signs this machine in. status asks it which of
your tunnels are connected, from every machine, for lnk tunnel list.
share is the relay's share API, for lnk bucket link create and a share's
files for lnk bucket pull --link <url>.