Control protocol 1
Status: normative contract for runtime/control_socket.* and the status
replies of yume and yumed. yume --status, yume --accept-route,
yumed --status and the desktop GUI are its clients. It is a
local interface between processes of one user, not a network protocol.
Purpose
A running yume or yumed answers questions about its state and takes a few
requests on a UNIX socket. The GUI is a separate, unprivileged program that
talks to the running yume only through this socket, so it can crash or be
replaced without touching the tunnel. The rules below let newer programs add
fields and requests without breaking an older GUI, and let a newer GUI see
what an older program lacks.
What each part does
- yume-gui
- Owns the display and kit selection. It links no YUME library and sends no traffic through the tunnel itself.
src/gui/tunnel.cppsrc/gui/control_client.cpp - yume
- Owns credentials, adapters, session and the control socket. The same user can ask status, follow messages and request stop.
src/runtime/native_cli.cppsrc/runtime/control_socket.cpp
Text version
+-----------------+ +-----------------+
| yume-gui | | yume |
| window | | tunnel process |
+-----------------+ +-----------------+
| |
| run with --validate |
|---------------------------------->|
| |
| validation result |
|<----------------------------------|
| |
| start detached, socket path |
|---------------------------------->|
| |
| .--|
| listen, connect, serve socket | |
| '->|
| |
| status and messages |
|---------------------------------->|
| |
| state and printed lines |
|<----------------------------------|
| |
|--. |
| | close window, client runs |
|<-' |
| |
|--. |
| | reopen window, find client |
|<-' |
| |
| Disconnect: stop |
|---------------------------------->|
| |
| acknowledge stop |
|<----------------------------------|
| |
| .--|
| drain and exit | |
| '->|
| |
|--. |
| | process ended: stopped |
|<-' |
| |
The socket
The configuration’s control.socket names the socket’s path, and
yume --control-socket names another for one run, so a program that starts
yume chooses where to find it. The directory that holds it must belong to
the running user and be closed to writes by group and others. The socket is mode 0600, and a connection from another user
is closed before it is read. The server serves at most 4 connections at once,
and later ones wait. Each ends after one reply, or after 2 seconds without a
complete request.
A connection carries one request and one reply:
- The client sends one JSON object and a newline, at most 512 bytes.
- The server sends one JSON object and a newline, under 64 KiB, and closes the connection.
Replies are UTF-8. Text from a peer or the system that is not valid UTF-8 is replaced, so a reply is always one JSON line.
Versions and extension
Every request and reply carries "control": 1. The number changes only for a
change an older client cannot survive, which needs a new version of this
document. Within protocol 1:
- Fields and requests are only added. An existing field keeps its name, type and meaning, and an existing request keeps its fields and its reply.
- A request is closed: it holds exactly the fields its definition lists.
- A reply is open: a client ignores fields it does not know and does not rely on their order.
- Every status reply lists, in
requests, the requests that program takes. A client sends only a listed request. Any other getsunknown_request. - A field described as present when something holds is absent otherwise. A
field that can be
nullis always present.
A new request, such as a command the GUI needs later, gets a name, a closed list of fields and a reply that names its result in a field of its own. A request that changes anything says so in its definition here. No request keeps its connection open: a client that follows a state asks for status again.
Errors
A request that fails gets an error reply:
{"control": 1, "error": "no route proposal has that id", "code": "not_found"}
A client acts on code. The text is for people and may change.
| Code | Meaning |
|---|---|
malformed |
The request is not a JSON object, is too long, or lacks or adds a field |
unsupported |
control is missing or not 1 |
unknown_request |
This program does not take that request |
not_found |
The request names something that does not exist, such as a proposal id |
failed |
The program could not carry out a well-formed request |
unavailable |
The program could not produce its status |
Requests
status, taken by both programs, changes nothing:
{"control": 1, "request": "status"}
accept-route, taken by a yume whose configuration has a circuits
section, accepts the shorter route a status reply proposes, by its id, a
string of at most 64 characters. It changes which routes the client uses. A
proposal is described in cluster 1:
{"control": 1, "request": "accept-route", "id": "00112233aabbccdd"}
{"control": 1, "accepted": "00112233aabbccdd"}
An id that no current proposal has gets not_found, so an answer to a
proposal the user has not seen accepts nothing.
messages, taken by both programs, changes nothing. It returns the lines
the program has printed, numbered from 1 in order, after the number after:
{"control": 1, "request": "messages", "after": 0}
{"control": 1, "instance": "5f0c9a3e11d2b746", "messages": [{"seq": 1, "time": "2026-09-30T12:00:00.250Z", "text": "session authenticated"}], "missed": 0, "more": false}
A program keeps its latest 256 lines, each cut to 512 bytes. The reply holds
the kept lines numbered above after, oldest first, each with its seq, its
time in UTC and its text, as many as fit in 63 KiB. missed counts the
lines numbered above after that the program no longer keeps, and more is
true when kept lines were left out, so the client asks again after the last
seq it received. instance is the same for the life of the program and
changes when it starts again, when numbering restarts at 1. The lines are the
ones the program writes to its standard error, and they hold no key,
credential or payload.
stop, taken by yume, changes the program’s state: it answers, then stops
the program as SIGTERM does, closing its sessions and listeners and
removing the TUN networking it owns. A program that started yume stops it
this way:
{"control": 1, "request": "stop"}
{"control": 1, "stopping": true}
The program stops also when the client leaves before reading the answer, since the request itself was valid.
The status of yume
| Field | Value |
|---|---|
control, requests |
1, and the requests this program takes |
program, version |
"yume", and the product version |
state |
idle, connecting, connected, waiting between attempts, or closed |
server |
host and port of the server the kit names |
server_identity |
When connected: the server’s verified composite fingerprint |
connected_ms |
When connected: how long the session has been up |
retry_ms |
When waiting: the delay before the next attempt |
sessions |
Sessions authenticated since the program started |
failed_attempts |
Failed attempts since the last authenticated session |
last_failure |
null, or the latest failure’s code and message |
traffic |
payload_bytes_sent, payload_bytes_received, record_bytes_sent and record_bytes_received, summed over every session |
socks5 |
The SOCKS5 listeners, as address:port |
forwards |
The forward listeners, as address:port or a socket path |
posture |
The fixed composition and the tuning limits in use, below |
circuits |
null, or the circuits below when the configuration has a circuits section |
posture holds what this build and configuration run, for a display that
states the posture rather than offering switches: the transport version
(YTP/1), its suite, the captured evidence_profile the outer layers
follow, the security provider and the crypto_backend library, as
yume --version prints them, and limits, the configuration’s
max_queued_bytes, max_epoch_bytes, credit_returns_per_window and
idle_epoch_rotation, which the tuning presets set. When connected it also
holds epoch_bytes, the session’s key epoch: the smaller of this client’s
and the server’s max_epoch_bytes.
circuits holds hops, min_hops, accepted_hops (0 unless a shorter route
was accepted), current_hops (the length new streams take, 0 while none can
be used), plain_http (refuse or allow), serial (the routes view’s, 0
before one is verified), stopped (null, or why circuits stopped), routes
and proposal. Each route has its nodes by name, the entry first, its
age_ms and its open streams. proposal is null, or a shorter route’s
id, hops, nodes, the view’s serial, its cost, what the user
gives_up in plain English, and latency_ms when every hop has been
measured. cost names what is given up. Its values are these, and a value
is added only with a new kind of shorter route:
cost |
Route | What is given up |
|---|---|---|
entry_sees_all |
1 hop | The entry server sees both who the user is and every site they visit, as a direct connection does |
entry_exit_adjacent |
2 hops | The entry is the exit’s neighbour, so a party that runs or watches both can link the user to the sites they visit |
A client may show its own words for a cost it knows, such as a
translation, and shows gives_up for any other value.
The status of yumed
| Field | Value |
|---|---|
control, requests |
1, and the requests this program takes |
program, version |
"yumed", and the product version |
listeners |
The listening endpoints, as address:port |
client_sessions |
Client sessions open now |
loops |
The sessions open on each event loop, the first loop, which also accepts every connection, first |
cluster |
null, or the cluster below when the configuration names one |
cluster holds the list’s id (the operator’s fingerprint), serial,
not_after in UTC, whether it has expired, this server’s name as self,
its links and its circuits. Each link names the peer and its identity,
the outbound link this server keeps (its state, sessions,
failed_attempts, last_failure, and connected_ms or retry_ms as for
yume), the inbound sessions the peer holds here (sessions, and
connected_ms when there is one) and the circuits it carries in and
out. circuits counts whether this server is an exit, circuits open,
those it is the entry of, those it relayed, exit_streams, failed
circuits and those refused, as a total and by bound (client_circuits,
circuit_rate, handshakes, streams). No count names both neighbours of a
circuit.
Limits
- Replies hold no key, credential, admission secret or payload. They do name server identities, cluster node names, local listener addresses and the program’s printed lines, which the socket’s permissions keep to the user who runs the program.
- Any process of that user can send
accept-routeandstop. That user could edit the configuration or end the process anyway. - The socket is not a stable interface for other users or other hosts, and the C ABI does not serve it.