Daemon and IPC
xrat includes a long-lived daemon supervisor process that manages proxy sessions, monitors health, and handles auto-rotation. CLI commands communicate with the daemon via Unix domain socket IPC.
Daemon Architecture
The daemon runs as a background process with three main responsibilities:
- IPC Server — Listens for commands from CLI clients
- Health Monitor — Periodically checks proxy liveness
- Rotation Scheduler — Manages automatic proxy switching
Supervisor Event Loop
The daemon runs a tokio::select! loop:
#![allow(unused)]
fn main() {
loop {
tokio::select! {
_ = health_tick.tick() => {
check_proxy_health().await;
}
Some(event) = ipc_rx.recv() => {
handle_ipc_event(event).await;
}
_ = rotation_timer.tick() => {
trigger_rotation().await;
}
}
}
}
IPC Protocol
The daemon uses JSON over Unix domain socket with protocol version 1.
Socket Location
<runtime_dir>/daemon.sock
Default: ~/.config/xrat/runtime/daemon.sock
Connection Flow
- CLI command connects to the Unix socket
- Sends a JSON request
- Receives a JSON response
- Closes the connection
Request Format
{
"protocol_version": 1,
"request": {
"type": "RuntimeConnect",
"payload": {
"config_id": 42
}
}
}
Response Format
{
"protocol_version": 1,
"ok": true,
"code": 200,
"message": "success",
"payload": { ... }
}
Request Types
| Type | Description | Payload |
|---|---|---|
DaemonPing | Check daemon reachability | None |
DaemonShutdown | Request graceful shutdown | None |
RuntimeStatus | Get proxy runtime status | None |
RuntimeConnect | Start a proxy session | { config_id: i64 } |
RuntimeReplace | Atomic disconnect + connect | { trigger, candidate_id } |
RuntimeDisconnect | Stop the active proxy session | None |
ProxyStart | Enable auto-rotation | None |
ProxyStatus | Get rotation status | None |
ProxyStop | Disable auto-rotation | None |
Manual xrat rotate now calls RuntimeReplace with trigger = manual and an
optional candidate_id. There is no separate ProxyRotate request type.
Response Codes
| Code | Description |
|---|---|
200 | Success |
400 | Bad request (invalid payload) |
404 | Not found (no active session) |
409 | Conflict (session already running) |
500 | Internal error |
Daemon Lifecycle
Starting the Daemon
xrat daemon start
- Check if daemon is already running (try connecting to socket)
- Fork a background process
- Create the Unix socket
- Run the supervisor event loop
- Reattach to any stale sessions from previous daemon runs
Stopping the Daemon
xrat daemon stop
- Connect to the daemon socket
- Send
DaemonShutdownrequest - Daemon gracefully terminates:
- Stops the active proxy session (if running)
- Closes the IPC socket
- Exits cleanly
Checking Daemon Status
xrat daemon status
Attempts to connect to the socket and sends a DaemonPing request. Reports
whether the daemon is reachable and the protocol version.
Health Monitoring
The daemon periodically checks the health of the active proxy session.
Health Tick Interval
Every 15 seconds, the daemon:
- Loads the active session from the database
- Checks if the PID is still running
- Tests reachability of the configured local inbounds
- Starts an asynchronous HTTP request through the active SOCKS5 or HTTP proxy
- Applies a current-session result only; stale results from replaced sessions are discarded
Process exit and inbound loss are control-plane failures and trigger recovery
immediately. Proxied HTTP failures are data-plane failures and must occur
runtime.rotation.health_failure_threshold times consecutively. A successful
probe resets the counter. Shadowsocks-only sessions use process and socket
checks because the daemon does not embed a Shadowsocks HTTP client.
Failure Handling
When a health failure reaches its trigger condition:
- Log the failure — Record in daemon logs
- Update session — Persist a specific reason code and per-config cooldown
- Trigger rotation — If
health_trigger_enabled, start rotation - Select safely — Fresh-test enabled candidates outside their cooldown
ProxyStatus reports the configured threshold, consecutive failure count,
whether a probe is in flight, last check time/error, and pending recovery state.
Session Reconciliation
When the daemon starts, it reconciles stale sessions from previous runs.
Stale Session Detection
Query for sessions with:
status = 'running'stopped_at IS NULL
Reconciliation Logic
For each stale session:
- Check PID liveness — Is the process still running?
- Verify process identity — Does the process executable and command line match the expected runtime engine and session config?
- Decision:
- PID alive + match → reattach (keep as
running) - PID alive + mismatch → mark failed (different process reused PID)
- PID dead → mark failed
- PID alive + match → reattach (keep as
Reattach Validation
Process identity is queried through sysinfo rather than reading /proc
directly, so reattach works on Linux, macOS, and BSD:
#![allow(unused)]
fn main() {
fn validate_reattach(pid: i64, expected_binary: &Path) -> bool {
let exe = process_exe_path(pid); // sysinfo Process::exe()
let cmd = process_cmd_args(pid); // sysinfo Process::cmd()
exe_matches(exe, expected_binary) && cmd_contains_session_config(cmd)
}
}
This prevents reattaching to a different process that happens to have the same
PID. On OpenBSD exe() may be unavailable; the command-line check still
applies.
IPC Client
CLI commands use an IPC client to communicate with the daemon.
Connection Retry
The client attempts to connect with retry:
#![allow(unused)]
fn main() {
async fn connect_with_retry(socket_path: &Path, max_attempts: u32) -> Result<UnixStream> {
for attempt in 1..=max_attempts {
match UnixStream::connect(socket_path).await {
Ok(stream) => return Ok(stream),
Err(_) if attempt < max_attempts => {
sleep(Duration::from_millis(100)).await;
}
Err(e) => return Err(e),
}
}
}
}
Error Handling
If the daemon is not running or unreachable:
Error: daemon socket not reachable at /home/user/.config/xrat/runtime/daemon.sock
Hint: start the daemon with 'xrat daemon start'
Daemon Integration
CLI Commands via IPC
When the daemon is running, these commands route through IPC:
| Command | IPC Request |
|---|---|
xrat connect <id> | RuntimeConnect |
xrat disconnect | RuntimeDisconnect |
xrat status | RuntimeStatus |
xrat rotate enable | ProxyStart |
xrat rotate status | ProxyStatus |
xrat rotate now | RuntimeReplace |
xrat rotate disable | ProxyStop |
Daemon Required
Runtime and proxy commands require the daemon IPC path. If the daemon is not
running, xrat connect, xrat status, xrat disconnect, and xrat proxy ...
return a hint to start it:
xrat daemon start
Supervisor State
The daemon maintains internal state:
#![allow(unused)]
fn main() {
struct SupervisorState {
rotation_enabled: bool,
last_rotation_at: Option<DateTime<Utc>>,
last_trigger: Option<RotationTrigger>,
cooldown_until: Option<DateTime<Utc>>,
next_timer_at: Option<DateTime<Utc>>,
instance_id: String,
}
}
Instance ID
Each daemon instance has a unique ID (UUID v4) used to track session ownership:
#![allow(unused)]
fn main() {
let instance_id = Uuid::new_v4().to_string();
}
Sessions created by the daemon include:
{
"owner_kind": "daemon",
"owner_instance_id": "550e8400-e29b-41d4-a716-446655440000"
}
Logging
The daemon logs to stderr and optionally to a file:
xrat daemon start 2> daemon.log
Or with systemd:
[Service]
StandardOutput=journal
StandardError=journal
Log Levels
Control verbosity with RUST_LOG:
RUST_LOG=info xrat daemon start
RUST_LOG=debug xrat daemon start
Security Considerations
Socket Permissions
The Unix socket is created with default permissions (usually 0755). On
multi-user systems, consider restricting access:
chmod 700 ~/.config/xrat/runtime/daemon.sock
API Key
The daemon does not enforce authentication for IPC. Security relies on Unix socket permissions and filesystem access control.
For the HTTP API (if enabled), use the key field in config.toml for
authentication.
Troubleshooting
Daemon Not Starting
Symptom: xrat daemon start fails or exits immediately
Check:
- Is a daemon already running?
xrat daemon status - Check logs:
RUST_LOG=debug xrat daemon start - Verify socket directory exists and is writable
IPC Connection Failed
Symptom: CLI commands fail with “daemon socket not reachable”
Check:
- Is the daemon running?
ps aux | grep xrat - Does the socket file exist?
ls -la ~/.config/xrat/runtime/daemon.sock - Check socket permissions
Stale Sessions
Symptom: xrat status shows a session but no proxy is running
Fix:
xrat disconnect
xrat daemon stop
xrat daemon start
The daemon will reconcile stale sessions on startup.
Related
daemonCLI — command reference- Auto-Rotation — rotation scheduling
- Runtime Management — session lifecycle