YUME Internet freedom by FixCraft
On this page

YUME operations

This page covers provisioning, deployment and maintenance after the first local connection in the quick start. Implementation status lists the covered service checks and remaining distribution, real-network and release qualification.

Provision a kit

yume-setup init creates one server tree and one client bundle in a new directory. It never overwrites an existing path.

yume-setup init --host vpn.example.net --port 443 \
  --output "$PWD/yume-kit" --client-name laptop
yume-doctor --config yume-kit/server/yumed.json
yume-doctor --config yume-kit/client/yume.json

The native guide lists every generated file and its purpose. Move the client bundle to its machine over an authenticated channel. Remove offline CA material from the server host, and never commit a kit, capture or diagnostic artifact.

Each device gets its own composite identity and access PSK. Issue another client bundle from the server tree, then reload the daemon so it reads the extended store:

yume-setup add-client --server yume-kit/server --host vpn.example.net \
  --output "$PWD/tablet" --client-name tablet --max-sessions 2

add-client writes a new bundle, the client’s public key and PSK under the store’s authorized/ directory, and a replacement authorized-keys.json that grants the kit’s standard tcp and udp services. The store is replaced last, but a failure while flushing a directory after that replacement returns exit status 3. The store and the files it names remain visible, but directory durability was not confirmed. Validate the server tree before reloading it. Files it adds to the server tree keep the store’s owner. --host must be the name the server certificate was issued for. --max-sessions and --weight are optional, and init accepts both, as well as --max-egress-mbps for the server and --preset for the kit’s tuning (the configuration reference lists the presets).

To revoke a device, remove it and reload. Its sessions end at once:

yume-setup remove-client --server /etc/yume --client-name tablet
systemctl reload yumed

remove-client replaces the store atomically, then deletes the client’s public key and PSK. It refuses to remove the last client, because the daemon requires at least one.

Join servers into a cluster

Servers of one operator can form a cluster: each checks a server list the operator signs and keeps an authenticated link to every other server, and circuits run over the links through two or three servers. A yume client with configured circuits builds its routes over those links. Keep the operator directory, which holds the signing key, off the servers, and run the commands on the operator’s copies of the server directories:

yume-setup cluster-init --output "$PWD/operator"
yume-setup cluster-add --cluster operator --server north/server \
  --name north --host north.example.net
yume-setup cluster-add --cluster operator --server south/server \
  --name south --host south.example.net --exit
yume-setup cluster-sign --cluster operator --days 30

cluster-add writes a PSK for each direction between the new server and each earlier one into their credentials/cluster/ directories, and adds a cluster section to the new server’s yumed.json. With --exit the server carries circuits’ streams to destinations through its direct_tcp adapter’s destination policy. cluster-sign signs the next list and the routes view that clients choose routes from, and copies both to every server. A server whose host does not resolve when you sign needs --address at cluster-add. add-client --circuits gives a new client a circuits section: its SOCKS5 connections then leave through circuits of three servers, with that server as the entry. When no route of three hops can be built, the client refuses connections until you accept its proposal: yume --config PATH --status shows it, with what the shorter route gives up, and yume --config PATH --accept-route ID accepts it. Set circuits.min_hops to approve a shorter route in advance on a machine nobody watches. Deploy each server’s credentials/cluster/ directory (and a new member’s yumed.json) with its files owned by the daemon’s account and mode 0600, restart a new member and reload the others. Sign again before not_after, 30 days by default: a server whose list has expired closes its links, and it refuses to start or reload until it has a newer list. cluster-remove --cluster operator --name south takes a server out; sign and deploy afterwards. With control.socket set, yumed --config PATH --status shows each link. Cluster 1 gives the formats and limits.

The generated cluster configuration uses cluster-state.json beside yumed.json. For a packaged server under /etc/yume, set its cluster.state file to /var/lib/yume/cluster-state.json, because the unit makes /etc/yume read-only.

Run the daemon as a service

The Debian yume-daemon package installs /usr/bin/yumed, a credential-free bootstrap /etc/yume/yumed.json and a hardened, disabled yumed.service that runs as the locked yume account. Install only the kit’s server/ contents under /etc/yume, keeping its credentials/ and cover-site/ layout. Private files must be owned by yume with mode 0600, and credentials/ should be mode 0700. Then validate as the service account and start the service:

runuser -u yume -- yume-doctor --config /etc/yume/yumed.json
runuser -u yume -- yumed --config /etc/yume/yumed.json --validate
systemctl start yumed.service

The unit repeats validation before every start, grants only CAP_NET_BIND_SERVICE for port 443, and reads /etc/yume without being able to modify it. Enable it at boot only once the deployment is ready. The static cover site is served in-process; no separate HTTP backend runs. If a proxy sits in front of yumed, use TCP passthrough; terminating TLS breaks admission, which is bound to the TLS connection. The package’s README.Debian holds the complete checklist.

Client identities and limits

credentials/authorized-keys.json lists the client identities the daemon accepts. Each entry names a composite identity with its SHA-256 fingerprint, its access PSK file, and the configured services it may open. An entry may add max_sessions, an integer from 1 to 1024. When a new session of that identity would exceed it, the daemon ends the identity’s oldest session. A client that reconnects after a network change therefore replaces its own half-open session instead of being refused. Without the field an identity is bounded only by the daemon’s session capacity below. admin-keys.json is a separate store of second-factor identities and grants no traffic.

The server’s limits.max_egress_mbps, from 1 to 1000000 megabits per second, caps the stream payload the daemon relays. Each byte counts once, in either direction and for every service. Protocol framing and control messages do not count. Clients share the rate by weight. A busy client gets its entry’s weight, a number from 0.1 to 100 that defaults to 1, divided by the total weight of the clients sending at that moment, and a client that stops sending leaves its share to the others. When another client starts sending, transfers already scheduled keep their earlier share, so the total can exceed the rate until they have started. Without the field the daemon does not limit the rate and weights have no effect.

SIGHUP, or systemctl reload yumed with the packaged unit, makes the daemon read both stores and its composite and ML-KEM keys again without dropping other clients. Later sessions authenticate against the new stores. Established sessions use the new grants for their next stream and a changed weight for their next transfer. Sessions of a removed identity, or beyond a lowered max_sessions, end at once. An invalid store is refused and the previous credentials stay in force. The daemon logs the outcome either way. TLS certificate and admission key changes still need a restart, and a changed admission key is refused. The schema-1 limits object bounds each session’s frames, streams, queued bytes, pending opens, rekey work, control messages and packets; the configuration reference owns those fields.

Capacity

One yumed holds up to 1024 client sessions. The connections their direct routes open to destinations share one pool of up to 16384, of which up to 1024 may be connecting at once. Each session and connection takes a descriptor, so the daemon raises its soft open-file limit to the hard one at start, keeps 512 descriptors for itself, gives sessions at most half of the rest and the pool what remains. The packaged unit’s LimitNOFILE=65536 allows the full sizes, and a limit of 1024 leaves 256 sessions and 256 connections. One authenticated identity may reserve at most half of the provider’s pending and active route limits, rounded up, across its sessions and TCP/UDP services. The reservation is released when an open fails, is canceled or its channel closes. At a circuit exit the counted identity is the previous hop.

Sessions spread over endpoint.event_loops threads, by default one for each CPU the daemon may run on, up to 16, and each session stays on one of them, so one session moves at what one core encrypts and several share the cores. A unit that pins the daemon with CPUAffinity= also sets how many loops it runs by default. A cluster member or a server with a packet adapter runs one loop. yumed --status shows the sessions on each loop. An idle connection holds no read buffer. A session streaming to a slower client can hold its limits.max_queued_bytes of outbound data, and up to twice that, at least 8 MiB, of records it received, so plan memory for the sessions that stream at the same time, not for every session, and bound the process as below.

For hard process ceilings, use systemd rather than in-process settings:

# systemctl edit yumed.service
[Service]
CPUQuota=800%
MemoryHigh=768M
MemoryMax=1G
TasksMax=1024

Keep MemoryMax above expected session, socket, TLS and library memory. Too tight a ceiling lets the kernel terminate the daemon instead of the daemon refusing work.

TCP autotuning on long paths

Native YUME leaves remote TCP socket buffers to the kernel’s autotuning. On Linux, a low receive or send ceiling can constrain fast and max on long paths. Compare checked transfers with ss -tinm at both endpoints: snd_wnd shows the peer’s advertised receive window. If it stops growing at the current ceiling, raise the third value of net.ipv4.tcp_rmem and net.ipv4.tcp_wmem, preserving their minimum and initial values, and measure again. Choose ceilings within the host’s memory budget and expected concurrent connections; larger allowed buffers can consume more memory under load.

Keep TCP autotuning enabled. Do not force fixed SO_RCVBUF or SO_SNDBUF values: those requests disable the corresponding autotuning. The net.core.rmem_max and net.core.wmem_max ceilings govern explicit socket buffer requests; changing those alone does not raise TCP’s autotuning maxima. A loss-free emulated path can establish a ceiling effect, but a production setting needs measurements on that host and path.

Forward a local port

A client forward adapter gives a program without SOCKS5 support one fixed path through the tunnel. Add it to the client’s adapters:

{
  "kind": "forward",
  "service": "tcp",
  "listen_address": "127.0.0.1",
  "listen_port": 2222,
  "destination": {"host": "git.example.net", "port": 22}
}

Every connection to 127.0.0.1:2222 becomes a stream to git.example.net port 22, which the server’s direct_tcp destinations must permit. To keep the listener away from other local users, use listen_path with a socket path in a directory only this user can write, such as $XDG_RUNTIME_DIR/yume/git.sock, instead of an address and port. yume-doctor checks the adapter, and yume reports a directory with the wrong owner or mode at startup.

Run a module

A server module adapter hands one stream service’s streams to a program that systemd runs from the packaged yume-module@.socket and yume-module@.service templates, as modules describes. Enable the module’s socket before starting yumed, which refuses a missing socket. Each module runs as a dynamic user of its own in its own control group, so yumed’s limits do not count it, and it cannot read the daemon’s credentials. Its messages and exits are in its own journal, as with journalctl -u yume-module@echo.service.

Name resolution

The client resolves a server host that is a name, and the daemon resolves the destination names that clients request through direct TCP/UDP adapters and SOCKS5. Both use the system resolver configuration, including /etc/hosts and NSS modules, in a separate helper process. It appears as a yume-resolver child after the first lookup. The helper holds no connections, keeps no capabilities, and runs each lookup on its own thread, up to 64 at once. Later lookups wait for a free thread, up to 1024 of them, instead of failing their connections. Stopping yume or yumed kills it, so a stalled DNS server or NSS module never delays shutdown. A numeric host or endpoint.connect_address needs no lookup. A client with endpoint.socks5_proxy looks nothing up either: it connects to the proxy’s numeric address and the proxy resolves the server’s name. A client with a managed TUN adapter needs a numeric first hop, the proxy or else the server, so that the route to it can be excluded before the tunnel changes routing. Every address a destination name resolves to must be permitted before the daemon connects.

Release verification

A Linux x86-64 release publishes yume-amd64-linux.tar.xz, holding yume, yume-setup, yume-doctor, the license, third-party notices, quick start and a manifest.json, and the standalone daemon yumed-amd64-linux. Each artifact has a .sha256 file, and the release adds SHA256SUMS.txt and release-manifest.json. Both artifacts, the aggregate checksums and the manifest require detached .sig files.

The release workflow verifies the annotated tag and its commit against the tracked public key in third_party/prebuilt/yume-release-key.asc, pinned to primary fingerprint 967278FF6FA436F504CBB0058A1588B5E2598DB1. The tag must point directly to the checked-out commit. Publication requires the matching GPG_PRIVATE_KEY secret, GPG_PASSPHRASE when needed, and approval of the release gates.

Verify the signing key’s primary fingerprint through an authenticated source before checking downloaded signatures:

sha256sum -c SHA256SUMS.txt
gpg --verify yumed-amd64-linux.sig yumed-amd64-linux
gpg --verify release-manifest.json.sig release-manifest.json

scripts/release_integrity.py signs the artifacts and aggregate checksums before generating the manifest, then signs the manifest itself. The inventory lists artifacts under files and aggregate checksums under checksums, with their signature filenames. Its own signature stays outside the inventory. Missing or inconsistent checksums, inventory entries or trusted signatures stop publication.

Before uploading, the helper rechecks local signatures, the remote tag object and every existing asset name. Any matching name stops the upload. Uploads never replace assets. A failed multi-file upload can leave a partial set and requires operator review before another attempt.

Public endpoints

Do not advertise a public endpoint until its users can detect replacement or downgrade. Publish at least the hostname and port, the daemon release, the TLS certificate identity, the server’s composite identity fingerprint, and the services and fair-use limits it offers. Whether an operator keeps logs is a policy claim that clients cannot verify remotely.

Troubleshooting

--validate checks configuration, credentials, destination policy with its egress lists, and module sockets without opening a network connection. yume-doctor also checks the cover site and TLS material. Both programs exit with 0 after a requested stop, 1 for a runtime failure, and 2 for a usage or configuration error.

If a client cannot connect:

  • confirm TCP reachability to the daemon’s port from the client network;
  • confirm no HTTP proxy terminates TLS in front of yumed;
  • check that the client’s endpoint.host matches the server certificate and that client and server come from the same kit, since the admission key and TLS trust are per deployment;
  • check that the client’s identity is listed in authorized-keys.json with the services it opens;
  • check that the cover site still contains the browser profile’s assets, which yume-doctor verifies.

If a session connects but a destination fails, the SOCKS5 reply reports a refusal or route failure without its detail. Through circuits the reply comes before the exit has answered, so such a failure closes the connection after a success reply instead. Check that the destination, and every address its name resolves to on the daemon host, lies inside the direct adapter’s configured networks and outside its deny lists, and that the client’s identity is granted that service. The daemon does not log individual opens. It reads egress lists only when it starts, so restart it after a list file changes.