# ISP360 MikroTik Integration Specification

**Version:** 1.0  
**Date:** 2026-06-13  
**Prepared by:** Polakis Technologies  

Companion: [DATABASE_DESIGN.md](DATABASE_DESIGN.md) | [RBAC_SPEC.md](RBAC_SPEC.md) | [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md)

---

## 1. Overview

ISP360 manages MikroTik RouterOS devices at scale (10,000+ routers) via the **RouterOS API** (binary API over TCP). All router communication is abstracted through two core classes:

| Class | File | Responsibility |
|-------|------|----------------|
| `MikrotikAPI` | `includes/MikrotikAPI.php` | Low-level RouterOS API connection, sentence read/write |
| `RouterManager` | `includes/RouterManager.php` | High-level ISP operations, queue, sync, provisioning |

### Design Principles

- **Never store plaintext API passwords** — encrypted in `router_credentials`
- **All router ops are tenant-scoped** — `tenant_id` on every query
- **Long-running commands go through `router_commands` queue** — never block UI
- **Idempotent provisioning** — upsert by username/MAC, not blind insert
- **Health polling via cron** — not on every page load

### Supported RouterOS

| Version | Support |
|---------|---------|
| RouterOS 6.x | Full |
| RouterOS 7.x | Full (primary target) |
| SwOS | Not supported |

### Connection Modes

| Mode | Port | Default | Use |
|------|------|---------|-----|
| API | 8728 | Plain TCP | Lab/legacy only |
| API-SSL | 8729 | TLS | **Production default** |
| SSH | 22 | Fallback for backup/export | Secondary |

---

## 2. Architecture

```mermaid
flowchart TB
    subgraph ui [Admin UI]
        RouterEdit["router_edit.php"]
        RouterView["router_view.php"]
        RouterCmd["router_commands.php"]
    end

    subgraph ajax [AJAX Layer]
        SaveRouter["ajax/save_router.php"]
        TestConn["ajax/test_router_connection.php"]
        QueueCmd["ajax/queue_router_command.php"]
    end

    subgraph core [Core Classes]
        RouterMgr["RouterManager.php"]
        MikroAPI["MikrotikAPI.php"]
        Encrypt["Encryption.php"]
        RadiusMgr["RadiusManager.php"]
    end

    subgraph data [Database]
        Routers["routers"]
        Credentials["router_credentials"]
        Commands["router_commands"]
        Status["router_status"]
        NAS["nas"]
    end

    subgraph cron [Background]
        HealthCron["cron/router_health.php"]
        CmdProc["cron/router_command_processor.php"]
        BackupCron["cron/router_backup.php"]
    end

    subgraph device [MikroTik RouterOS]
        APISSL["API-SSL :8729"]
        Hotspot["/ip/hotspot"]
        PPPoE["/ppp/secret"]
        Queue["/queue/tree"]
    end

    RouterEdit --> SaveRouter
    RouterView --> TestConn
    SaveRouter --> RouterMgr
    TestConn --> RouterMgr
    QueueCmd --> RouterMgr
    RouterMgr --> MikroAPI
    RouterMgr --> Encrypt
    RouterMgr --> Routers
    RouterMgr --> Credentials
    RouterMgr --> Commands
    MikroAPI --> APISSL
    HealthCron --> RouterMgr
    CmdProc --> RouterMgr
    RouterMgr --> RadiusMgr
    RadiusMgr --> NAS
```

---

## 3. Database Tables

| Table | Integration Role |
|-------|------------------|
| `routers` | Router registry (IP, port, SSL, status) |
| `router_credentials` | Encrypted API username/password |
| `router_commands` | Async command queue |
| `router_status` | Latest health snapshot |
| `router_metrics` | Time-series telemetry |
| `router_backups` | Config backup files |
| `router_queues` | Queue tree definitions (synced) |
| `router_firewall_rules` | Firewall rule mirror |
| `router_sync_status` | Per-module sync state |
| `nas` | FreeRADIUS NAS entry per router |
| `pppoe_accounts` | Source of truth for PPPoE secrets |
| `hotspot_users` | Source of truth for hotspot users |

### `router_credentials` Schema Usage

```
credential_type: 'api' | 'ssh' | 'snmp'
username:        API login user (not admin if avoidable)
password_enc:    AES-256-GCM encrypted password
encryption_key_id: tenant key version for rotation
```

---

## 4. Class: `MikrotikAPI`

**File:** `includes/MikrotikAPI.php`

Low-level RouterOS binary API client. No business logic. No direct DB access.

### 4.1 Constructor

```php
class MikrotikAPI
{
    private $socket;
    private string $host;
    private int $port;
    private bool $ssl;
    private int $timeout;
    private bool $connected = false;

    /**
     * @param string $host      Router IP or hostname
     * @param int    $port      8728 (plain) or 8729 (SSL)
     * @param bool   $ssl       Enable TLS
     * @param int    $timeout   Socket timeout seconds (default 10)
     */
    public function __construct(
        string $host,
        int $port = 8729,
        bool $ssl = true,
        int $timeout = 10
    );
}
```

### 4.2 Connection Methods

```php
/** Connect socket. Throws MikrotikConnectionException on failure. */
public function connect(): void;

/** Login with RouterOS credentials. Throws MikrotikAuthException on failure. */
public function login(string $username, string $password): bool;

/** Graceful disconnect. */
public function disconnect(): void;

/** Returns true if socket is alive. */
public function isConnected(): bool;
```

### 4.3 API Communication

```php
/**
 * Execute RouterOS command and return parsed response.
 *
 * @param string $command  e.g. '/system/resource/print'
 * @param array  $params   ['.proplist' => 'uptime,cpu-load', '?name' => 'queue1']
 * @return array           Parsed sentence array
 * @throws MikrotikApiException
 */
public function comm(string $command, array $params = []): array;

/**
 * Write-only command (no response expected beyond !done).
 * Used for add/set/remove operations.
 */
public function commWrite(string $command, array $params = []): void;

/**
 * Read all sentences until !done or !trap.
 */
public function readResponse(): array;
```

### 4.4 Binary API Protocol Notes

RouterOS API uses **length-prefixed words** terminated by null byte:

```
Login sequence:
  → /login
  ← !done =ret=...challenge...
  → /login =name=user =response=00{md5(chr(0)+pass+challenge)}

RouterOS 6.43+:
  → /login =name=user =password=pass
  ← !done
```

**Trap handling:** Any `!trap` sentence must throw `MikrotikApiException` with `=message` attribute.

### 4.5 Exception Classes

```php
class MikrotikException extends Exception {}
class MikrotikConnectionException extends MikrotikException {}
class MikrotikAuthException extends MikrotikException {}
class MikrotikApiException extends MikrotikException
{
    public function getTrapMessage(): string;
    public function getTrapCategory(): string;
}
class MikrotikTimeoutException extends MikrotikConnectionException {}
```

---

## 5. Class: `RouterManager`

**File:** `includes/RouterManager.php`

High-level orchestrator. All admin UI and cron jobs call this class — never `MikrotikAPI` directly.

### 5.1 Constructor

```php
class RouterManager
{
    private PDO $pdo;
    private int $tenant_id;

    public function __construct(PDO $pdo, int $tenant_id);

    /** Factory: load router by ID with tenant check. */
    public static function forRouter(PDO $pdo, int $tenant_id, int $router_id): self;

    /** Get connected MikrotikAPI instance for a router. */
    private function getApi(int $router_id): MikrotikAPI;
}
```

### 5.2 Router Lifecycle

```php
/** Register new router + credentials + NAS entry. */
public function addRouter(array $data, array $credentials, int $user_id): int;

/** Update router details. Re-register NAS if IP changes. */
public function updateRouter(int $router_id, array $data, int $user_id): bool;

/** Soft-delete router. Disable NAS. Queue cleanup command. */
public function deleteRouter(int $router_id, int $user_id): bool;

/** Test API connection. Returns identity + resource info. */
public function testConnection(int $router_id): array;

/** Discover RouterOS identity, version, board-name. */
public function discoverRouter(string $ip, int $port, bool $ssl, string $user, string $pass): array;
```

### 5.3 Health & Status

```php
/** Poll router health. Updates router_status + routers.status. */
public function pollHealth(int $router_id): array;

/** Poll all active routers for tenant (cron). */
public function pollAllHealth(): int;

/**
 * Returns health data:
 * - cpu_load, memory_used_percent, uptime_seconds
 * - active_pppoe, active_hotspot, bandwidth_in/out
 * - identity, version, board_name
 */
```

### 5.4 Backup & Restore

```php
/** Queue backup command. Returns command_id. */
public function queueBackup(int $router_id, string $type = 'manual', int $user_id = null): int;

/** Execute backup: /export file=isp360-backup-{timestamp} */
public function executeBackup(int $router_id): string; // returns file_path

/** Queue restore from router_backups record. */
public function queueRestore(int $router_id, int $backup_id, int $user_id): int;
```

### 5.5 Queue Management

```php
/** List queue trees from router. */
public function getQueues(int $router_id): array;

/** Create/update simple queue or queue tree entry. */
public function upsertQueue(int $router_id, array $queueData): string; // returns .id

/** Remove queue by name. */
public function removeQueue(int $router_id, string $queueName): bool;

/** Apply bandwidth profile template to customer. */
public function applyBandwidthProfile(int $router_id, string $target, int $profile_id): bool;
```

### 5.6 Hotspot Management

```php
/** Create hotspot user on router + DB sync. */
public function createHotspotUser(int $router_id, array $userData): string;

/** Update hotspot user profile/rate-limit. */
public function updateHotspotUser(int $router_id, string $username, array $data): bool;

/** Remove hotspot user from router. */
public function removeHotspotUser(int $router_id, string $username): bool;

/** List active hotspot sessions. */
public function getHotspotActiveSessions(int $router_id): array;

/** Disconnect hotspot session by MAC or user. */
public function disconnectHotspotSession(int $router_id, string $mac_or_user): bool;
```

**RouterOS paths:**
- Users: `/ip/hotspot/user/add|set|remove|print`
- Active: `/ip/hotspot/active/print`
- Remove session: `/ip/hotspot/active/remove`

### 5.7 PPPoE Management

```php
/** Create PPPoE secret on router + radcheck sync. */
public function createPppoeSecret(int $router_id, array $accountData): string;

/** Update PPPoE secret (password, profile, service). */
public function updatePppoeSecret(int $router_id, string $username, array $data): bool;

/** Disable/suspend PPPoE secret. */
public function suspendPppoeSecret(int $router_id, string $username): bool;

/** Remove PPPoE secret. */
public function removePppoeSecret(int $router_id, string $username): bool;

/** List active PPPoE sessions. */
public function getPppoeActiveSessions(int $router_id): array;

/** Disconnect active PPPoE session. */
public function disconnectPppoeSession(int $router_id, string $session_id): bool;
```

**RouterOS paths:**
- Secrets: `/ppp/secret/add|set|remove|print`
- Active: `/ppp/active/print`
- Disconnect: `/ppp/active/remove`

### 5.8 Firewall Management

```php
/** List firewall filter rules. */
public function getFirewallRules(int $router_id, string $chain = 'forward'): array;

/** Add firewall rule. */
public function addFirewallRule(int $router_id, array $rule): string;

/** Remove firewall rule by .id. */
public function removeFirewallRule(int $router_id, string $rule_id): bool;
```

**RouterOS path:** `/ip/firewall/filter/add|set|remove|print`

### 5.9 Command Queue

```php
/** Enqueue command for async execution. */
public function queueCommand(
    int $router_id,
    string $command_type,
    string $command_text,
    int $priority = 5,
    ?string $correlation_id = null,
    int $user_id = null
): int;

/** Process next queued command for router (cron). */
public function processNextCommand(int $router_id): ?array;

/** Process all pending commands for tenant (cron). */
public function processCommandQueue(int $limit = 50): int;
```

---

## 6. Credential Encryption

**File:** `includes/Encryption.php`

| Parameter | Value |
|-----------|-------|
| Algorithm | AES-256-GCM |
| Key source | `platform_settings` or per-tenant `tenant_settings` key `encryption_key` |
| IV | Random 12 bytes per encryption |
| Storage format | `base64(iv + tag + ciphertext)` |

```php
// Store credential
$encrypted = Encryption::encrypt($password, $tenant_id);
// INSERT router_credentials (password_enc, encryption_key_id)

// Retrieve for API login
$password = Encryption::decrypt($row['password_enc'], $tenant_id);
$api->login($row['username'], $password);
```

**Rules:**
- Passwords never logged, never returned in AJAX responses
- `test_connection` returns success/failure only — not credentials
- API user should be dedicated `isp360-api` with minimal policy (not `admin`)

### Recommended RouterOS API User Policy

```
/user group add name=isp360 policy=read,write,test,api,!ftp,!reboot
/user add name=isp360-api group=isp360 password={strong}
```

---

## 7. Workflows

### 7.1 Add Router

```mermaid
sequenceDiagram
    participant UI as router_edit.php
    participant AJAX as save_router.php
    participant RM as RouterManager
    participant DB as MySQL
    participant API as MikrotikAPI
    participant R as RouterOS

    UI->>AJAX: POST router data + credentials
    AJAX->>AJAX: permission_check routers.create
    AJAX->>RM: addRouter(data, credentials)
    RM->>DB: INSERT routers status=pending
    RM->>DB: INSERT router_credentials encrypted
    RM->>API: connect + login
    API->>R: /system/identity/print
    API->>R: /system/resource/print
    R-->>API: identity + version
    RM->>DB: UPDATE routers firmware_version, status=online
    RM->>DB: INSERT nas router_id, nasname=ip, secret
    RM->>DB: INSERT router_sync_status
    RM->>RM: queueCommand backup initial
    RM->>DB: audit_log router.created
    AJAX-->>UI: success redirect router_view.php
```

**Steps:**
1. Validate `router_code` unique per tenant
2. Encrypt and store credentials
3. Test connection before saving (or save as `pending` if test fails)
4. Register NAS entry for FreeRADIUS
5. Queue initial backup
6. Set `router_sync_status` modules: hotspot, pppoe, queues = `pending`

### 7.2 Test Connection

```mermaid
sequenceDiagram
    participant UI as router_edit.php
    participant AJAX as test_router_connection.php
    participant RM as RouterManager
    participant API as MikrotikAPI
    participant R as RouterOS

    UI->>AJAX: POST router_id or inline credentials
    AJAX->>AJAX: permission_check routers.view
    AJAX->>RM: testConnection(router_id)
    RM->>DB: SELECT router + decrypt credentials
    RM->>API: connect SSL port 8729 timeout 10s
    API->>R: /login
    API->>R: /system/identity/print
    API->>R: /system/resource/print
    API->>R: /system/routerboard/print
    R-->>API: identity, cpu, uptime, board
    RM->>DB: UPDATE router_status
    AJAX-->>UI: JSON identity, version, cpu, memory, uptime
```

**Response JSON:**
```json
{
  "success": true,
  "identity": "ISP-POP-Nairobi-01",
  "version": "7.14.2",
  "board": "RB4011iGS+",
  "cpu_load": 12.5,
  "memory_percent": 45.2,
  "uptime": "5w3d12h"
}
```

### 7.3 Backup Router

```mermaid
sequenceDiagram
    participant UI as router_backup.php
    participant AJAX as create_router_backup.php
    participant RM as RouterManager
    participant Q as router_commands
    participant CRON as router_command_processor
    participant API as MikrotikAPI
    participant R as RouterOS

    UI->>AJAX: Request backup
    AJAX->>RM: queueBackup(router_id, manual)
    RM->>Q: INSERT status=queued command_type=backup
    AJAX-->>UI: command_id queued
    CRON->>RM: processNextCommand(router_id)
    RM->>API: connect
    API->>R: /export file=isp360-bak-{ts}
    API->>R: /file/print ?name=isp360-bak-{ts}
    RM->>RM: FTP/SCP download file from router
    RM->>RM: Store uploads/backups/{tenant}/{router}/
    RM->>DB: INSERT router_backups
    RM->>Q: UPDATE status=completed
```

**Backup formats:**
| Format | Command | Use |
|--------|---------|-----|
| export | `/export` | Human-readable .rsc (default) |
| binary | `/system/backup/save` | Full binary backup |
| rsc | `/export compact` | Compact script |

### 7.4 Queue Management

```mermaid
sequenceDiagram
    participant UI as router_queues.php
    participant AJAX as save_router_queue.php
    participant RM as RouterManager
    participant API as MikrotikAPI
    participant R as RouterOS
    participant DB as MySQL

    UI->>AJAX: POST queue name, max-limit, parent
    AJAX->>RM: upsertQueue(router_id, data)
    RM->>API: /queue/tree/print ?name={name}
    alt Queue exists
        RM->>API: /queue/tree/set .id={id} max-limit=...
    else New queue
        RM->>API: /queue/tree/add name=... max-limit=...
    end
    RM->>DB: UPSERT router_queues
    AJAX-->>UI: success
```

**Queue types:**
- `/queue/simple` — per-IP simple queues (CPE management)
- `/queue/tree` — hierarchical QoS (POP aggregation)

### 7.5 Hotspot User — Create

```mermaid
sequenceDiagram
    participant UI as hotspot_users.php
    participant AJAX as save_hotspot_user.php
    participant RM as RouterManager
    participant API as MikrotikAPI
    participant R as RouterOS
    participant DB as MySQL

    AJAX->>RM: createHotspotUser(router_id, data)
    RM->>DB: INSERT hotspot_users
    RM->>API: /ip/hotspot/user/add
    Note over API,R: name, password, profile, limit-uptime, comment
    API-->>RM: .id
    RM->>DB: UPDATE hotspot_users router_object_id
    RM->>DB: audit_log hotspot.user.created
    AJAX-->>UI: success
```

**Hotspot user params:**
| Param | RouterOS Key | Source |
|-------|--------------|--------|
| Username | `name` | `hotspot_users.username` |
| Password | `password` | `hotspot_users.password_hash` (plain to router only) |
| Profile | `profile` | `hotspot_profiles.profile_name` |
| Uptime limit | `limit-uptime` | `package_limits.time_limit_minutes` |
| Rate limit | via profile | `hotspot_profiles.rate_limit` |

### 7.6 PPPoE Secret — Create / Suspend

```mermaid
sequenceDiagram
    participant UI as pppoe_accounts.php
    participant AJAX as save_pppoe_account.php
    participant RM as RouterManager
    participant RAD as RadiusManager
    participant API as MikrotikAPI
    participant R as RouterOS
    participant DB as MySQL

    AJAX->>RM: createPppoeSecret(router_id, data)
    RM->>DB: INSERT pppoe_accounts
    RM->>RAD: syncRadcheck(username, password)
    RM->>API: /ppp/secret/add
    Note over API,R: name, password, service=pppoe, profile, remote-address
    alt Suspend flow
        RM->>API: /ppp/secret/set disabled=yes
        RM->>RAD: update radcheck Auth-Type := Reject
    end
    RM->>DB: UPDATE pppoe_accounts synced_at
    AJAX-->>UI: success
```

### 7.7 Firewall Rule — Add

```mermaid
sequenceDiagram
    participant UI as router_firewall.php
    participant AJAX as save_firewall_rule.php
    participant RM as RouterManager
    participant API as MikrotikAPI
    participant DB as MySQL

    AJAX->>RM: addFirewallRule(router_id, rule)
    RM->>API: /ip/firewall/filter/add
    Note over API: chain, action, src-address, dst-address, protocol
    RM->>DB: INSERT router_firewall_rules
    AJAX-->>UI: rule .id
```

### 7.8 Health Polling (Cron)

```mermaid
sequenceDiagram
    participant CRON as cron/router_health.php
    participant RM as RouterManager
    participant API as MikrotikAPI
    participant R as RouterOS
    participant DB as MySQL

    CRON->>RM: pollAllHealth()
    loop Each active router
        RM->>API: connect timeout 5s
        alt Connection OK
            API->>R: /system/resource/print
            API->>R: /ppp/active/print count-only
            API->>R: /ip/hotspot/active/print count-only
            API->>R: /interface/print stats
            RM->>DB: UPSERT router_status
            RM->>DB: INSERT router_metrics
            RM->>DB: UPDATE routers status=online last_seen_at
        else Connection fail
            RM->>DB: UPDATE routers status=offline
            RM->>DB: INSERT router_alerts if offline > 5min
        end
    end
```

**Cron schedule:** `*/5 * * * *` (every 5 minutes)

---

## 8. Command Queue

### Queue Processing Rules

| Rule | Value |
|------|-------|
| Max concurrent per router | 1 |
| Default priority | 5 (1=highest, 10=lowest) |
| Default max_retries | 3 |
| Retry backoff | 30s, 120s, 600s |
| Command timeout | 300 seconds |
| Stale lock recovery | Reset `running` > 10 min to `failed` |

### Command Types

| Type | Processor | Example |
|------|-----------|---------|
| `api` | Direct MikrotikAPI comm | `/interface/print` |
| `script` | Run RouterOS script | Custom provisioning |
| `backup` | executeBackup() | /export |
| `restore` | Import .rsc file | Post-maintenance |
| `sync` | Module sync | Hotspot user sync |
| `reboot` | `/system/reboot` | Scheduled maintenance |
| `custom` | Tenant-defined | Advanced use |

### Priority Levels

| Priority | Use case |
|----------|----------|
| 1 | Disconnect session (COA urgent) |
| 3 | User suspend/unsuspend |
| 5 | Normal provisioning |
| 7 | Backup |
| 9 | Bulk sync |

### Cron: `cron/router_command_processor.php`

```
Schedule: * * * * * (every minute)
Limit: 50 commands per run
Order: priority ASC, created_at ASC
```

---

## 9. Error Handling & Retry Policy

### Exception → User Message Mapping

| Exception | HTTP | User Message |
|-----------|------|--------------|
| `MikrotikConnectionException` | 503 | Router unreachable. Check IP and firewall. |
| `MikrotikAuthException` | 401 | API authentication failed. Check credentials. |
| `MikrotikTimeoutException` | 504 | Router response timed out. |
| `MikrotikApiException` | 500 | RouterOS error: {trap message} |
| Tenant mismatch | 403 | Access denied |

### Retry Policy

```php
// In processNextCommand():
if ($command failed && retry_count < max_retries) {
    UPDATE router_commands SET
        status = 'queued',
        retry_count = retry_count + 1,
        scheduled_at = NOW() + backoff_seconds(retry_count)
}
// backoff: 30, 120, 600 seconds
```

### Circuit Breaker

If router has **5 consecutive failed commands** within 15 minutes:
- Set `routers.status = 'degraded'`
- Insert `router_alerts` severity=high
- Skip non-priority commands until manual ack or successful health poll

### Logging

All API errors logged to `router_logs`:
```
tenant_id, router_id, log_level, log_type, message, context_json
```

---

## 10. RouterOS API Reference (ISP360 Operations)

### System / Health

| Operation | Command |
|-----------|---------|
| Identity | `/system/identity/print` |
| Resources | `/system/resource/print` |
| Routerboard | `/system/routerboard/print` |
| Reboot | `/system/reboot` |
| Clock | `/system/clock/print` |

### Interfaces

| Operation | Command |
|-----------|---------|
| List | `/interface/print` |
| Stats | `/interface/print stats` |
| Enable | `/interface/enable .id={id}` |
| Disable | `/interface/disable .id={id}` |

### PPPoE

| Operation | Command |
|-----------|---------|
| Add secret | `/ppp/secret/add name={u} password={p} service=pppoe profile={prof}` |
| Disable | `/ppp/secret/set .id={id} disabled=yes` |
| Active sessions | `/ppp/active/print` |
| Disconnect | `/ppp/active/remove .id={id}` |

### Hotspot

| Operation | Command |
|-----------|---------|
| Add user | `/ip/hotspot/user/add name={u} password={p} profile={prof}` |
| Active | `/ip/hotspot/active/print` |
| Disconnect | `/ip/hotspot/active/remove .id={id}` |
| Profiles | `/ip/hotspot/user/profile/print` |

### Queues

| Operation | Command |
|-----------|---------|
| Simple add | `/queue/simple/add name={n} target={ip} max-limit={dl}/{ul}` |
| Tree add | `/queue/tree/add name={n} parent={p} max-limit={dl}/{ul}` |

### Firewall

| Operation | Command |
|-----------|---------|
| Add filter | `/ip/firewall/filter/add chain=forward action=accept src-address={ip}` |
| List | `/ip/firewall/filter/print` |
| Remove | `/ip/firewall/filter/remove .id={id}` |

### Backup

| Operation | Command |
|-----------|---------|
| Export | `/export file={name}` |
| Binary backup | `/system/backup/save name={name}` |
| List files | `/file/print` |

---

## 11. AJAX Endpoint Mapping

| Endpoint | RouterManager Method | Permission |
|----------|---------------------|------------|
| `ajax/save_router.php` | `addRouter()` / `updateRouter()` | `routers.create` / `routers.edit` |
| `ajax/test_router_connection.php` | `testConnection()` / `discoverRouter()` | `routers.view` |
| `ajax/delete_router.php` | `deleteRouter()` | `routers.delete` |
| `ajax/queue_router_command.php` | `queueCommand()` | `routers.command` |
| `ajax/create_router_backup.php` | `queueBackup()` | `routers.backup` |
| `ajax/restore_router_backup.php` | `queueRestore()` | `routers.backup` |
| `ajax/save_router_queue.php` | `upsertQueue()` | `routers.edit` |
| `ajax/save_firewall_rule.php` | `addFirewallRule()` | `routers.edit` |
| `ajax/save_hotspot_user.php` | `createHotspotUser()` | `hotspot.users` |
| `ajax/suspend_hotspot_user.php` | `removeHotspotUser()` | `hotspot.users` |
| `ajax/save_pppoe_account.php` | `createPppoeSecret()` | `pppoe.create` |
| `ajax/suspend_pppoe_account.php` | `suspendPppoeSecret()` | `pppoe.suspend` |
| `ajax/disconnect_pppoe_session.php` | `disconnectPppoeSession()` | `pppoe.sessions` |
| `ajax/disconnect_hotspot_session.php` | `disconnectHotspotSession()` | `hotspot.sessions` |
| `ajax/refresh_router_status.php` | `pollHealth()` | `routers.view` |
| `ajax/run_router_discovery.php` | `discoverRouter()` | `routers.create` |

---

## 12. Cron Jobs

| File | Schedule | Method |
|------|----------|--------|
| `cron/router_health.php` | `*/5 * * * *` | `pollAllHealth()` |
| `cron/router_command_processor.php` | `* * * * *` | `processCommandQueue(50)` |
| `cron/router_backup.php` | `0 2 * * *` | `queueBackup()` for `auto_backup=1` |

---

## 13. Security Requirements

| Requirement | Implementation |
|-------------|----------------|
| Credential encryption | AES-256-GCM via `Encryption.php` |
| Tenant isolation | All queries filter `tenant_id` |
| Least privilege API user | Dedicated `isp360-api` RouterOS group |
| TLS mandatory in production | `use_ssl=1`, port 8729 |
| No credential in logs | Redact in `router_logs` |
| Command injection prevention | Whitelist `command_type`; escape API params |
| Rate limit test connection | Max 10/min per router per user |
| Audit all write ops | `audit_log()` on create/update/delete/command |

### Network Requirements

| Direction | Port | Purpose |
|-----------|------|---------|
| ISP360 server → Router | 8729/TCP | API-SSL |
| ISP360 server → Router | 8728/TCP | API plain (dev only) |
| Router → FreeRADIUS | 1812/1813 UDP | Auth/accounting (separate spec) |

---

## 14. Implementation Checklist

- [ ] Implement `includes/MikrotikAPI.php` — binary API protocol
- [ ] Implement `includes/RouterManager.php` — all methods above
- [ ] Implement `includes/Encryption.php` — credential encrypt/decrypt
- [ ] Wire `ajax/save_router.php` + `ajax/test_router_connection.php`
- [ ] Implement `cron/router_health.php`
- [ ] Implement `cron/router_command_processor.php`
- [ ] NAS auto-registration on router add (with RadiusManager)
- [ ] Unit tests with RouterOS CHR in lab

---

## Change Log

| Version | Date | Change |
|---------|------|--------|
| 1.0 | 2026-06-13 | Initial MikroTik integration spec — MikrotikAPI + RouterManager, 8 workflows |
