Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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>:

  1. Load config β€” Fetch the config from the database by ID
  2. Generate runtime config β€” Create Xray JSON with local inbounds
  3. Spawn process β€” Launch Xray/V2Ray as a child process
  4. Wait for readiness β€” Poll the SOCKS port until it accepts connections
  5. Persist session β€” Insert a runtime_sessions record with status running
  6. 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:

StatusDescription
startingProcess spawned, waiting for port readiness
runningPort is ready, proxy is active
stoppingGraceful shutdown in progress
stoppedProcess terminated cleanly
failedProcess exited unexpectedly or startup failed

State Transitions

starting β†’ running β†’ stopping β†’ stopped
   ↓                      ↓
 failed                failed

Session Record

Persisted to runtime_sessions table:

FieldDescription
idSession ID (primary key)
config_idForeign key to configs table
statusCurrent status
process_idOS process ID (PID)
socks_host, socks_portSOCKS inbound address
http_host, http_portHTTP inbound address
shadowsocks_host, shadowsocks_portShadowsocks inbound address
failure_reasonError message (if failed)
owner_kindcli or daemon
owner_instance_idDaemon instance ID (if daemon-owned)
started_at, stopped_atTimestamps

Disconnect Flow

When you run xrat disconnect:

  1. Load active session β€” Find the latest running session
  2. Send SIGTERM β€” Request graceful shutdown
  3. Wait for exit β€” Poll process status every 100ms (up to 5s)
  4. Send SIGKILL β€” Force kill if still running after timeout
  5. Update session β€” Set status to stopped or failed
  6. 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))
}
  1. Check if process is running
  2. Send SIGTERM
  3. Poll every 100ms for up to 5 seconds
  4. If still running, send SIGKILL
  5. Return outcome: Terminated, Killed, or NotRunning

Status Check

When you run xrat status:

  1. Load active session β€” Find the latest session (any status)
  2. Check PID liveness β€” Verify process is still running
  3. Check inbound health β€” Test TCP reachability of SOCKS/HTTP/Shadowsocks ports
  4. Return snapshot β€” Print status with config details and health

Health Check

For each inbound port:

StatusDescription
reachableTCP connection succeeded
unreachableTCP connection failed
not_checkedInbound 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:

  1. Disconnect the old session (graceful shutdown)
  2. Connect the new session
  3. 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:

  1. Find stale sessions β€” Query for running sessions with no stopped_at
  2. Check PID liveness β€” For each stale session, check if PID is still running
  3. Verify process identity β€” Compare /proc/<pid>/cmdline with expected binary path
  4. 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

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" } }
FieldDescription
enabledEnable SOCKS inbound
hostBind address
portBind port
udpEnable UDP support
authOptional 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
FieldDescription
enabledEnable logging to files
maskMask IP addresses in logs
dirLog directory (relative to config dir or absolute)
dns_logEnable DNS query logging
levelLog level
keepKeep log files after session stops

Engine Selection

Choose the proxy engine in config.toml:

[runtime]
engine = "xray"  # "xray" | "v2ray" | "sing-box"
EngineBinaryProtocols
xrayxrayAll except Hysteria2
v2rayv2rayVLESS, VMess, Shadowsocks, Trojan, HTTP, SOCKS5
sing-boxsing-boxParse-time/runtime-config preview only; managed process lifecycle is not yet sing-box parity