macula_dist_relay_client (macula v3.13.0)

View Source

Client for the Macula dist relay (macula-io/macula-dist-relay).

Maintains a persistent QUIC connection to a dedicated dist relay and exposes a simple API for establishing point-to-point tunnels between Erlang nodes. Each tunnel is backed by a raw QUIC stream — no framing, no pub/sub, no application-level encryption.

This replaces the pub/sub bridge approach in macula_dist_relay (the SDK's previous dist-over-mesh implementation) which forced dist bytes through the station's MessagePack/handler pipeline.

Protocol

Control frames on stream 0 (length-prefixed MessagePack):

     node  relay    identify{node_name}
     relay  node    identified{status}
     node  relay    tunnel_request{target}
     relay  node    tunnel_ok{tunnel_id} | tunnel_error{reason}
     relay  node    tunnel_notify{tunnel_id, source}
     node  relay    tunnel_close{tunnel_id}

Tunnel data streams (stream 1+) carry the raw dist wire bytes after a 32-byte tunnel_id prefix (written by the relay on stream open) that lets the client match new_stream events to pending tunnels.

Lifecycle

1. start_link(RelayUrl, NodeName) connects + sends identify 2. Await identified reply 3. request_tunnel(TargetNode) blocks until tunnel_ok + tunnel stream arrive; returns {ok, ConnRef, StreamRef} for use as the dist Socket in macula_dist 4. Incoming tunnels: tunnel_notify arrives on control, then a new_stream event; client reads 32-byte prefix, matches to the notified tunnel, hands the stream to net_kernel via the standard {accept, _, Socket, Family, Driver} protocol.

Not yet implemented (Phase 2 MVP)

- net_kernel handoff for incoming tunnels (needs macula_dist integration on the accept side) - automatic reconnect on relay disconnect - multiple relay failover

Summary

Functions

Request a tunnel to TargetNode. Blocks until the tunnel stream is ready or the request fails/times out. Returns a {ConnRef, StreamRef} tuple suitable for use as the dist Socket in macula_dist.

Tell the client which process to deliver {accept, ...} messages to when inbound tunnels arrive. Called by macula_dist:accept/1 with the net_kernel pid (self() at that call site).

Start the client with a locally-registered name so macula_dist can find it without being passed the pid. Only one dist_relay_client per node makes sense — a node connects to exactly one dist relay for its cluster traffic.

Locate the registered client, if any.

Functions

close_tunnel(Client, TunnelId)

-spec close_tunnel(pid(), binary()) -> ok.

handle_call(Msg, From, State)

handle_cast(Msg, State)

handle_info(Info, State)

init(_)

request_tunnel(Client, TargetNode)

-spec request_tunnel(pid(), binary()) -> {ok, reference(), reference()} | {error, term()}.

Request a tunnel to TargetNode. Blocks until the tunnel stream is ready or the request fails/times out. Returns a {ConnRef, StreamRef} tuple suitable for use as the dist Socket in macula_dist.

set_kernel(Client, Kernel)

-spec set_kernel(pid(), pid()) -> ok.

Tell the client which process to deliver {accept, ...} messages to when inbound tunnels arrive. Called by macula_dist:accept/1 with the net_kernel pid (self() at that call site).

start_link(RelayUrl, NodeName)

-spec start_link(binary() | string(), binary()) -> {ok, pid()} | {error, term()}.

start_link(RelayUrl, NodeName, Opts)

-spec start_link(binary() | string(), binary(), map()) -> {ok, pid()} | {error, term()}.

Start the client with a locally-registered name so macula_dist can find it without being passed the pid. Only one dist_relay_client per node makes sense — a node connects to exactly one dist relay for its cluster traffic.

status(Client)

-spec status(pid()) -> map().

terminate(Reason, State)

whereis_client()

-spec whereis_client() -> pid() | undefined.

Locate the registered client, if any.