Architecture and Design
Architectural Principles
Tornado VPN is organized as a local-service mesh on a single host:
- Process supervision and lifecycle control are centralized in
MASTER_service.py. - Service-to-service calls use Unix domain sockets in
/run/tornado/*.sock. - External API ingress is isolated to two FastAPI apps (
admin-dashboardandclient-connect). - Runtime state is split between Redis (ephemeral/live) and PostgreSQL (durable/history).
Control Plane vs Data Plane
flowchart TB
subgraph ControlPlane[Control Plane]
Master[MASTER_service]
ApiMgr[api_service]
OsSvc[os_service]
UserSvc[user_service]
AuthSvc[auth_service]
SessionSvc[session_service]
Ipam[ipam_service]
KeyRot[key_rotator]
BootKeys[bootstrap_keys]
LogSvc[log_manage]
end
subgraph DataPlane[Data Plane]
WG0[WireGuard wg0\n10.8.0.0/24]
WG1[WireGuard wg1\n10.9.0.0/24]
TorMgr[tor_manager]
TorProc[Tor Process]
NFT[nftables / iptables]
end
subgraph Interfaces[External Interfaces]
ClientAPI[Client API :4605]
AdminAPI[Admin API :8000 via NGINX]
Client[Desktop Clients]
Admin[Admin Browser]
end
Redis[(Redis)]
PG[(PostgreSQL)]
Client --> ClientAPI
Admin --> AdminAPI
ClientAPI --> AuthSvc
ClientAPI --> SessionSvc
ClientAPI --> Ipam
ClientAPI --> UserSvc
ClientAPI --> WG0
ClientAPI --> WG1
AdminAPI --> ApiMgr
AdminAPI --> OsSvc
AdminAPI --> UserSvc
AdminAPI --> TorMgr
AdminAPI --> KeyRot
AdminAPI --> LogSvc
SessionSvc --> Redis
Ipam --> Redis
AuthSvc --> Redis
AdminAPI --> Redis
AuthSvc --> PG
UserSvc --> PG
SessionSvc --> PG
WG0 --> NFT
WG1 --> TorMgr
TorMgr --> TorProc
Supervisor Model
MASTER_service.py loads services.json, drops privileges per service user, creates child processes, and performs liveness checks via each service socket ping action. On failed heartbeat or crash, restart is scheduled.
sequenceDiagram
participant Master as MASTER_service
participant Svc as Child Service
participant Sock as /run/tornado/*.sock
Master->>Svc: spawn process
loop every 10s
Master->>Sock: {"action":"ping"}
Sock-->>Master: {"status":"pong"}
end
Note over Master,Svc: If process exits or ping fails -> restart
Master->>Svc: terminate + restart
Session Lifecycle
session_service.py owns session state transitions and finalization.
stateDiagram-v2
[*] --> Online: create_session
Online --> Online: heartbeat
Online --> Offline: heartbeat key expires
Offline --> Online: heartbeat recovered
Online --> Closed: close_session
Offline --> Closed: hard_ttl expired
Closed --> [*]
Authentication and Connection Flow
sequenceDiagram
participant C as Client App
participant API as client-connect API
participant Auth as auth_service
participant WG as wg_manager
participant IPAM as ipam_service
participant Sess as session_service
C->>API: GET /auth/pubkey
C->>API: POST /auth/login (encrypted payload)
API->>Auth: login via UDS
Auth-->>API: tokens + device_id
API-->>C: access/refresh tokens
C->>API: POST /vpn/initiate (JWT + public_key)
API->>WG: add_peer
WG->>IPAM: allocate vpn_ip + tor_ip
WG->>Sess: create_session (async task)
WG-->>API: vpn_ip, tor_ip, server pubkeys, TTLs
API-->>C: connection bundle
Key and Secret Rotation Topology
flowchart LR
KR["key_rotator"]
Keys["/opt/tornado/keys/jwt"]
Overlap["/opt/tornado/keys/jwt/overlap"]
Auth["auth_service"]
ClientAPI["client-connect API"]
AdminAPI["admin-dashboard API"]
Env["/opt/tornado/.env (ADMIN_SECRET)"]
KR --> Keys
KR --> Overlap
KR --> Env
KR -->|SIGHUP via pid files| Auth
KR -->|SIGHUP via pid files| AdminAPI
Auth -->|reload_keys| Keys
ClientAPI -->|verify token| Keys
Data Stores and Ownership
- Redis session keys:
vpn:session:* - Redis heartbeat sentinels:
vpn:session:*:hb - Redis IP pools:
vpn:ipam:pool:vpn,vpn:ipam:pool:tor - Redis live event channel:
vpn:live_events - Redis user event channel:
vpn:user_events - Redis revocation keys:
revoked_jti:* - PostgreSQL tables:
users,auth_sessions,wg_sessions,vpn_session_history
Operational Ports and Endpoints
- Admin API bind:
127.0.0.1:8000(proxied by NGINX) - Client API bind:
0.0.0.0:4605 - WireGuard listener ports:
51820(wg0) and51821(wg1) - Tor defaults: TransPort
9040, DNSPort9053, control9051, maintenance9041
Design Constraints and Tradeoffs
- UDS-first service communication reduces network exposure but keeps services host-coupled.
- Redis keyspace event dependence requires
notify-keyspace-events Exto be configured. wg_managerandsession_servicerun as root for network and interface operations.- Key rotation uses file-based primitives and signal-driven reload; correctness depends on consistent pid file management.