fps

Client Profiles And Carrier Setup

FPS client setup is centered on generated profiles rather than hand-written key material.

Profiles configure the current Linux TUN VPN adapter on top of the reusable FPS covert datagram core. The client UUID authenticates the carrier datagram transport; the server-assigned TUN lease is adapter metadata, not a separate identity mechanism.

Current Behavior

This is adequate for CLI/Docker beta operators. It is not a consumer installer: administrators still prepare the server config, allowlist one UUID per device and choose the routing or proxy policy.

For a copyable end-to-end Docker flow, use public-beta-quickstart.md. For revocation and rotation procedures, use rotation.md.

Supported Onboarding Shapes

FPS supports two equivalent profile transports:

Both are generated by FPS binaries. Shell/Python scripts remain for Docker simulations and Linux routing examples, not for defining the product profile schema.

Profile Contents

A generated client profile contains only information the client cannot safely infer:

It does not contain:

Routes and DNS are deployment policy. They remain explicit operator commands or Docker entrypoint environment, not silent side effects of importing a profile.

Carrier Hostname Mapping

Production carrier sessions are expected to come from a browser or another ordinary application. The simple supported client-side setup is a local hosts override for the carrier origin hostname:

127.0.0.1 carrier.example.net

Then run fps_client on the local HTTPS carrier port and open the real carrier URL, for example:

https://carrier.example.net/
wss://carrier.example.net/

This preserves the browser-visible origin name, Host and SNI while routing the TCP connection to the local FPS client. It avoids the common certificate and CORS problems caused by opening https://127.0.0.1/... for a site whose certificate and web policy are written for carrier.example.net.

DNS cannot select a TCP port. If the application insists on default HTTPS, the local FPS client must listen on 127.0.0.1:443 or the user must open an explicit URL with the configured port. Add an ::1 hosts entry only when the local FPS client also listens on IPv6; otherwise IPv6 lookups can bypass the listener.

FPS does not ship a DNS proxy. Use a reviewed hosts-file entry or router DNS override for selected carrier domains.

UUID Sharing Policy

Do not share one client_uuid across multiple devices. The UUID determines the client public key, the persistent server lease key and the assigned TUN IPv4 address. Two machines using the same UUID would therefore contend for the same lease address and can misroute return traffic or collide at the L3/L4 level.

The product policy is one UUID per device/profile. The only supported duplicate policy is replace_old: a newer active instance for the same UUID supersedes older carriers for that UUID. Shared/group UUIDs and multi-device round-robin for one UUID are intentionally not supported.

Implementation detail: fps_client generates a random per-process instance id at startup and sends it only inside encrypted post-auth control metadata. This lets the server distinguish “same client opened another carrier” from “another device reused the same UUID” without adding a config field or exposing the value in logs/status output.

CLI Workflow

Generate a raw client UUID:

CLIENT_UUID="$(fps_client --generate-client-uuid)"

Generate JSON:

fps_server --generate-client-profile \
  --config server.json \
  --client-uuid "$CLIENT_UUID" \
  --server-endpoint fps.example.net:8443 \
  --client-listen 127.0.0.1:7443 \
  --client-status-socket /run/fps/client.status \
  --format json \
  --output client.json

Generate a URI instead:

fps_server --generate-client-profile \
  --config server.json \
  --client-uuid "$CLIENT_UUID" \
  --server-endpoint fps.example.net:8443 \
  --format uri

Decode/import on the client:

fps_client --print-config-from-uri 'fps://v1/...'
fps_client --write-config-from-uri 'fps://v1/...' --output client.json
fps_client --check-config --config client.json

The URI carries URL-safe base64 JSON without padding:

fps://v1/<base64url-json-profile>

A query-string layout would be more readable, but it becomes fragile once optional mobile and platform fields appear. Keeping JSON as the only profile schema makes URI/QR a transport wrapper rather than a second config format. URI import normalizes the decoded JSON and validates the client UUID plus server public key before writing a config file.

Docker Notes

When using Docker bind mounts, direct --output writes from a root-running container create root-owned host files. Prefer host-side redirection or run the container with the host UID/GID:

docker run --rm -v "$PWD/config:/etc/fps:ro" fps:local \
  fps_server --generate-client-profile --config /etc/fps/server.json \
  --client-uuid "$CLIENT_UUID" --server-endpoint fps.example.net:8443 \
  > client.json
chmod 600 client.json

docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/config:/etc/fps" fps:local \
  fps_client --write-config-from-uri 'fps://v1/...' \
  --output /etc/fps/client.json

Do not write --output /tmp/client.json inside a one-shot docker run --rm container unless /tmp is bind-mounted. The file is otherwise created inside the temporary container and is removed with it.

--client-status-socket PATH is explicit because native and Docker deployments use different runtime paths. Docker examples use /run/fps/*.status mounted through a named volume so one-shot status containers can query the daemon.

Generated client profiles use the normal randomized upgrade delay sigma. Set security.zero_rtt.client_upgrade_delay_sigma_ms to 0 manually when a reproducible packet capture or deterministic integration lab requires a fixed upgrade moment.

Android profiles can include app-owned carrier entries under carriers. The first internal Android carrier path is HTTPS GET; max_response_bytes bounds how much response body the app drains from each keep-alive request before treating the carrier as unhealthy. This is an Android runtime guard, not a Linux daemon setting.

Android profiles can also include an inline shaper object using the same compact CDF arrays as Linux configs. Keep mobile profiles self-contained: Android intentionally rejects shaper.profile_file.

Security Notes

Planned UX Work