Runtime Management
xrat manages the lifecycle of local proxy processes (Xray or V2Ray), providing automatic config generation, process spawning, health monitoring, and graceful shutdown.
Connect Flow
When you run xrat connect <id>:
- Load config β Fetch the config from the database by ID
- Generate runtime config β Create Xray JSON with local inbounds
- Spawn process β Launch Xray/V2Ray as a child process
- Wait for readiness β Poll the SOCKS port until it accepts connections
- Persist session β Insert a
runtime_sessionsrecord with statusrunning - Return result β Print connection details (ports, PID, config info)
Runtime Config Generation
xrat generates a complete Xray config with:
- Inbounds: SOCKS5, HTTP, Shadowsocks (as configured in config.toml)
- Outbound: Single outbound to the proxy node
- Logging: Configurable log level and file paths
- Stream settings: TLS, WebSocket, gRPC, TCP header obfuscation
Example generated config:
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "socks-in",
"port": 1080,
"listen": "0.0.0.0",
"protocol": "socks",
"settings": { "udp": true }
},
{
"tag": "http-in",
"port": 8080,
"listen": "0.0.0.0",
"protocol": "http"
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "example.com",
"port": 443,
"users": [{ "id": "uuid-123", "encryption": "none" }]
}
]
},
"stream_settings": {
"network": "ws",
"security": "tls",
"tls_settings": { "server_name": "cdn.example.com" },
"ws_settings": {
"path": "/ray",
"headers": { "Host": "cdn.example.com" }
}
}
}
]
}
Process Spawning
xrat spawns the proxy process with:
- Config file: Written to
<runtime_dir>/session-<id>.json - Stdout: Redirected to
<runtime_dir>/session-<id>.out.log - Stderr: Redirected to
<runtime_dir>/session-<id>.err.log - Detached mode: Process continues running after CLI exits (when using daemon)
Readiness Check
After spawning, xrat polls the SOCKS port every 100ms until:
- Port accepts TCP connections β success
- Process exits β error
- Timeout (default 10s) β error, process is killed
Session State
Each runtime session has a status:
| Status | Description |
|---|---|
starting | Process spawned, waiting for port readiness |
running | Port is ready, proxy is active |
stopping | Graceful shutdown in progress |
stopped | Process terminated cleanly |
failed | Process exited unexpectedly or startup failed |
State Transitions
starting β running β stopping β stopped
β β
failed failed
Session Record
Persisted to runtime_sessions table:
| Field | Description |
|---|---|
id | Session ID (primary key) |
config_id | Foreign key to configs table |
status | Current status |
process_id | OS process ID (PID) |
socks_host, socks_port | SOCKS inbound address |
http_host, http_port | HTTP inbound address |
shadowsocks_host, shadowsocks_port | Shadowsocks inbound address |
failure_reason | Error message (if failed) |
owner_kind | cli or daemon |
owner_instance_id | Daemon instance ID (if daemon-owned) |
started_at, stopped_at | Timestamps |
Disconnect Flow
When you run xrat disconnect:
- Load active session β Find the latest
runningsession - Send SIGTERM β Request graceful shutdown
- Wait for exit β Poll process status every 100ms (up to 5s)
- Send SIGKILL β Force kill if still running after timeout
- Update session β Set status to
stoppedorfailed - Cleanup β Remove temporary config files (if configured)
Graceful Shutdown
xrat attempts graceful shutdown:
#![allow(unused)]
fn main() {
terminate_process_gracefully(pid, Duration::from_secs(5))
}
- Check if process is running
- Send SIGTERM
- Poll every 100ms for up to 5 seconds
- If still running, send SIGKILL
- Return outcome:
Terminated,Killed, orNotRunning
Status Check
When you run xrat status:
- Load active session β Find the latest session (any status)
- Check PID liveness β Verify process is still running
- Check inbound health β Test TCP reachability of SOCKS/HTTP/Shadowsocks ports
- Return snapshot β Print status with config details and health
Health Check
For each inbound port:
| Status | Description |
|---|---|
reachable | TCP connection succeeded |
unreachable | TCP connection failed |
not_checked | Inbound is disabled or port is 0 |
Session Replacement
When replace_active_session = true in config.toml:
xrat connect 99
If a session is already running:
- Disconnect the old session (graceful shutdown)
- Connect the new session
- Atomic operation from the userβs perspective
This is useful for switching proxies without manual disconnect.
Reattach on Daemon Restart
When the daemon starts, it reconciles stale sessions:
- Find stale sessions β Query for
runningsessions with nostopped_at - Check PID liveness β For each stale session, check if PID is still running
- Verify process identity β Compare
/proc/<pid>/cmdlinewith expected binary path - Reattach or mark failed:
- PID alive + cmdline matches β reattach (keep as
running) - PID alive + cmdline mismatch β mark as
failed(different process reused PID) - PID dead β mark as
failed
- PID alive + cmdline matches β reattach (keep as
Reattach Validation
xrat validates that the PID still belongs to the expected proxy process:
#![allow(unused)]
fn main() {
fn validate_reattach(pid: i64, expected_binary: &Path) -> bool {
let cmdline = read_proc_cmdline(pid);
cmdline.contains(expected_binary.to_str().unwrap())
}
}
This prevents reattaching to a different process that happens to have the same PID.
Inbound Configuration
Configure local inbounds in config.toml:
SOCKS5
[runtime.socks]
enabled = true
host = "0.0.0.0"
port = 1080
udp = true
auth = { enabled = true, username = "xrat", password = { env = "XRAT_SOCKS_PASSWORD" } }
| Field | Description |
|---|---|
enabled | Enable SOCKS inbound |
host | Bind address |
port | Bind port |
udp | Enable UDP support |
auth | Optional username/password authentication |
HTTP
[runtime.http]
enabled = false
host = "0.0.0.0"
port = 8080
Shadowsocks
[runtime.shadowsocks]
enabled = false
host = "0.0.0.0"
port = 1081
method = "aes-128-gcm"
password = { env = "XRAT_SHADOWSOCKS_PASSWORD" }
network = "tcp,udp"
Sniffing
Enable traffic sniffing for better routing:
[runtime.sniffing]
enabled = true
dest_override = ["http", "tls", "quic"]
route_only = true
metadata_only = false
domains_excluded = []
ips_excluded = []
Logging
Configure proxy process logging:
[runtime.log]
enabled = true
mask = "none" # "quarter" | "half" | "full" | "none"
dir = "logs"
dns_log = false
level = "warning" # "debug" | "info" | "warning" | "error"
keep = true
| Field | Description |
|---|---|
enabled | Enable logging to files |
mask | Mask IP addresses in logs |
dir | Log directory (relative to config dir or absolute) |
dns_log | Enable DNS query logging |
level | Log level |
keep | Keep log files after session stops |
Engine Selection
Choose the proxy engine in config.toml:
[runtime]
engine = "xray" # "xray" | "v2ray" | "sing-box"
| Engine | Binary | Protocols |
|---|---|---|
xray | xray | All except Hysteria2 |
v2ray | v2ray | VLESS, VMess, Shadowsocks, Trojan, HTTP, SOCKS5 |
sing-box | sing-box | Parse-time/runtime-config preview only; managed process lifecycle is not yet sing-box parity |
Related
connectCLI β command reference- Daemon and IPC β daemon-managed sessions
- Auto-Rotation β automatic proxy switching
- Config Generation β how configs are generated