Skip to content

OpenWRT Cluster

The OpenWRT cluster provides the VPN infrastructure for cbintel. It consists of 18 OpenWRT devices: 1 master router, 1 gateway, and 16 workers.

Hardware Overview

Role Count IP Range Purpose
Master 1 17.0.0.1 HAProxy, LuCI RPC
Gateway 1 17.0.0.2 Network gateway
Workers 16 17.0.0.10-25 VPN tunnels, proxies

Network Architecture

graph TB
    subgraph "Host Server"
        CLUSTER_API[Cluster API :9002]
        LUCI_CLIENT[LuCI RPC Client]
        WG[WireGuard Tunnels<br/>wg01-wg16]
    end

    subgraph "Master Router 17.0.0.1"
        HAPROXY[HAProxy<br/>Ports 8890-8999]
        LUCI[LuCI JSON-RPC]
    end

    subgraph "Workers 17.0.0.10-25"
        W1[Worker 1<br/>TinyProxy :3128<br/>OpenVPN tun0]
        W2[Worker 2<br/>TinyProxy :3128<br/>OpenVPN tun0]
        W16[Worker 16<br/>TinyProxy :3128<br/>OpenVPN tun0]
    end

    subgraph "VPN Providers"
        PROTON[ProtonVPN<br/>12,909 profiles<br/>127 countries]
    end

    CLUSTER_API --> LUCI_CLIENT --> LUCI
    WG --> W1 & W2 & W16
    HAPROXY --> W1 & W2 & W16
    W1 & W2 & W16 --> PROTON

Worker Components

Each worker runs:

Component Port Purpose
TinyProxy 3128 HTTP proxy server
OpenVPN tun0 VPN tunnel interface
Dropbear 22 SSH access
LuCI 80 Web admin interface

LuCI RPC Interface

The LuCI JSON-RPC interface enables programmatic device control.

Authentication

from cbintel.cluster.clients.luci_rpc import LuCIClient

# Connect to a worker
client = LuCIClient("17.0.0.10", username="root", password="...")
await client.authenticate()

Available Methods

Method Purpose
system.info Get system status
luci.admin.uci.get_all Read UCI configuration
luci.admin.uci.set Write UCI configuration
luci.admin.uci.commit Apply UCI changes
sys.exec Execute shell command

Example: Get System Info

# Get worker system information
info = await client.call("system.info")
print(f"Uptime: {info['uptime']}s")
print(f"Load: {info['load']}")
print(f"Memory: {info['memory']['free']} / {info['memory']['total']}")

Example: Control VPN

# Stop VPN service
await client.call("sys.exec", "service openvpn stop")

# Upload new profile
await client.upload("/etc/openvpn/client.ovpn", profile_content)

# Start VPN service
await client.call("sys.exec", "service openvpn start")

# Check tunnel status
result = await client.call("sys.exec", "ifconfig tun0")

HAProxy Configuration

The master router runs HAProxy to load-balance across workers.

Port Allocation

8890-8899: Reserved for banks
8890: Bank 1 (e.g., California)
8891: Bank 2 (e.g., New York)
...

Configuration Example

frontend california_frontend
    bind *:8890
    default_backend california_backend

backend california_backend
    balance roundrobin
    server worker1 17.0.0.10:3128 check
    server worker2 17.0.0.11:3128 check
    server worker3 17.0.0.12:3128 check

Dynamic Configuration

Banks are configured dynamically via the Cluster API:

from cbintel.cluster.services.bank_service import BankService

service = BankService()

# Create bank - updates HAProxy config
bank = await service.create_bank(
    name="California",
    workers=[1, 2, 3],
    filter="us:ca"
)
print(f"Endpoint: {bank.endpoint}")  # http://17.0.0.1:8890

WireGuard Tunnels

Each worker has a dedicated WireGuard tunnel from the host.

Tunnel Configuration

Tunnel Worker Tunnel IP
wg01 17.0.0.10 10.200.1.2
wg02 17.0.0.11 10.200.2.2
... ... ...
wg16 17.0.0.25 10.200.16.2

Host Configuration

# /etc/wireguard/wg01.conf
[Interface]
Address = 10.200.1.1/24
PrivateKey = <host_private_key>

[Peer]
PublicKey = <worker1_public_key>
AllowedIPs = 10.200.1.2/32
Endpoint = 17.0.0.10:51820

Device Registry

The Device Registry tracks all 18 devices with cached metadata.

Registry Model

@dataclass
class DeviceRegistryEntry:
    id: int                    # 1-18
    hostname: str              # lazarus-worker-01
    lan_ip: str               # 17.0.0.10
    wg_interface: str         # wg01
    wg_tunnel_ip: str         # 10.200.1.2
    wg_public_key: str        # WireGuard public key
    vpn_profile: str          # Current VPN profile
    vpn_country: str          # VPN country
    vpn_region: str           # VPN region
    vpn_provider: str         # ProtonVPN
    external_ip: str          # Current exit IP
    status: str               # online/offline
    last_seen: datetime       # Last status check
    uptime: int               # Uptime in seconds
    load_avg: float           # System load
    memory_free_mb: float     # Free memory
    ping_latency_ms: float    # Network latency
    download_speed_mbps: float # Download speed
    upload_speed_mbps: float  # Upload speed
    tags: list[str]           # Custom tags
    notes: str                # Custom notes

Registry Operations

from cbintel.cluster.services.device_service import DeviceService

service = DeviceService()

# Ping a device
result = await service.ping_device(1)
print(f"Latency: {result.avg_ms}ms")

# Get external IP with geolocation
ip_info = await service.get_external_ip(1)
print(f"Exit: {ip_info.ip} ({ip_info.country}, {ip_info.city})")

# Run speedtest
speed = await service.run_speedtest(1)
print(f"Down: {speed.download_mbps} Mbps")

# Execute command
result = await service.execute_command(1, "uptime")
print(result.stdout)

# Get system info
info = await service.get_system_info(1)
print(f"Load: {info.load_avg}")

VPN Profile Management

Profile Location

/path/to/profiles/intl-ovpn/
├── us-ca-001.protonvpn.udp.ovpn
├── us-ca-002.protonvpn.udp.ovpn
├── us-ny-001.protonvpn.udp.ovpn
├── de-fra-001.protonvpn.udp.ovpn
└── ... (~12,909 profiles)

Profile Selection

Profiles are selected based on geographic filters:

Filter Description Example Profiles
us Any US server us--.ovpn
us:ca California us-ca-*.ovpn
us:ca:tor California + Tor us-ca-*-tor.ovpn
de Germany de-*.ovpn
uk United Kingdom uk-*.ovpn

Profile Assignment

from cbintel.cluster.services.worker_service import WorkerService

service = WorkerService()

# Start VPN with specific profile
await service.start_vpn(
    worker_number=1,
    profile="us-ca-324.protonvpn.udp.ovpn"
)

# Stop VPN
await service.stop_vpn(1)

# Restart with new profile
await service.restart_vpn(1, "us-ny-123.protonvpn.udp.ovpn")

Monitoring

Health Checks

from cbintel.cluster.services.device_service import DeviceService

service = DeviceService()

# Ping all devices
results = await service.ping_all_devices()
for device_id, result in results.items():
    if result.success:
        print(f"Device {device_id}: {result.avg_ms}ms")
    else:
        print(f"Device {device_id}: UNREACHABLE")

# Refresh device status
await service.refresh_device(1)

# Refresh all devices
await service.refresh_all_devices()

Status Endpoints

Endpoint Description
GET /api/v1/devices/ List all devices
GET /api/v1/devices/{id}/status Device system info
POST /api/v1/devices/{id}/ping Ping device
POST /api/v1/devices/ping-all Ping all devices

Troubleshooting

Device Unreachable

  1. Check WireGuard tunnel status: wg show wg01
  2. Ping worker directly: ping 17.0.0.10
  3. Check SSH access: ssh root@17.0.0.10
  4. Verify VPN status on device: ifconfig tun0

VPN Not Connecting

  1. Check profile validity
  2. Verify credentials in profile
  3. Check DNS resolution on worker
  4. Review OpenVPN logs: /var/log/openvpn.log

HAProxy Issues

  1. Check HAProxy status: systemctl status haproxy
  2. View config: /etc/haproxy/haproxy.cfg
  3. Check backend health: /api/v1/cluster/health