PHPackages                             redrodrigo/router-os-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Queues &amp; Workers](/categories/queues)
4. /
5. redrodrigo/router-os-sdk

ActiveLibrary[Queues &amp; Workers](/categories/queues)

redrodrigo/router-os-sdk
========================

Async-capable Mikrotik RouterOS API client for PHP (Fiber-based, optional Swoole transport, Laravel/Hyperf integrations)

v0.7.0(1mo ago)132MITPHPPHP ^8.1CI passing

Since Jul 10Pushed 1mo agoCompare

[ Source](https://github.com/tecrodrigocastro/router-os-sdk)[ Packagist](https://packagist.org/packages/redrodrigo/router-os-sdk)[ Docs](https://github.com/tecrodrigocastro/router-os-sdk)[ RSS](/packages/redrodrigo-router-os-sdk/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (4)Versions (12)Used By (0)

router-os-sdk
=============

[](#router-os-sdk)

[![Tests](https://github.com/tecrodrigocastro/router-os-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/tecrodrigocastro/router-os-sdk/actions/workflows/tests.yml)[![Packagist Version](https://camo.githubusercontent.com/318c5a7f6c56d6b441f52b46fbe10915fdaf419bc297a609d91624da34e80225/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726564726f647269676f2f726f757465722d6f732d73646b2e737667)](https://packagist.org/packages/redrodrigo/router-os-sdk)[![License](https://camo.githubusercontent.com/e46035bd4ec12958f22657221a04fe52cf250743abd28a93bb0887095580076a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f746563726f647269676f63617374726f2f726f757465722d6f732d73646b2e737667)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/03539faa0e6cc64cba5ad72ad3bce57b3f084e11c68bcd9394a42936200593fa/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f726564726f647269676f2f726f757465722d6f732d73646b2e737667)](composer.json)

Async-capable PHP client for the **Mikrotik RouterOS binary API** — connect to a router over TCP/TLS, run commands, and consume multiple real-time streams (`/listen`, `=interval=N`) concurrently on a single connection.

Why
---

[](#why)

RouterOS's own API multiplexes commands and streams over one TCP connection using a `.tag` field, but no existing PHP client actually used that tag to route responses — every one of them supports exactly one command in flight at a time. This SDK adds that: a real tag-multiplexing dispatcher built on native PHP 8.1+ Fibers, alongside a battle-tested wire protocol implementation and the handful of RouterOS quirks (7.18+ empty replies, multi-block responses on some wireless APs, interval-stream semantics) that only show up once you push a client hard in production.

Features
--------

[](#features)

- **Real concurrency** — run a one-shot command and several `/listen` or `=interval=N` streams at the same time on one socket, not one at a time.
- **TCP and TLS** (RouterOS API-SSL, port 8729).
- **Modern and legacy login** — `=name=`/`=password=` (RouterOS ≥ 6.43) and the MD5 challenge-response scheme for older firmware.
- **Fluent query builder** for filters (`where()`), attributes (`equal()`), and RouterOS's `?#` operations.
- **No hard runtime dependencies** — the concurrency core runs on native PHP Fibers; a Swoole coroutine transport is planned as an opt-in upgrade, not a requirement.
- **RouterOS protocol quirks handled out of the box**: `!empty` replies (RouterOS 7.18+), unknown/expired tag packets, multi-block `!done`responses on some wireless APs, and the `!done`-as-cycle-boundary semantics of `=interval=N` streams.
- **Resilience**: `ManagedClient` reconnects with exponential backoff for long-running processes; the Laravel `RouterOsManager` auto-heals a dead connection on next use and fails fast (no full `connect_timeout` wait) if the router recently failed.
- **ISP/telecom toolkit** (`RouterOS\Sdk\Isp\*`) — PPPoE secrets and active sessions, PPP profiles, address-list blocking, simple queues and queue trees, idempotent firewall rules, and `Customer::suspend()`/`activate()`: a unified customer-lifecycle operation that touches all of the above in one call, each action isolated (one failing doesn't stop the others).
- **Native WireGuard support** (`RouterOS\Sdk\Vpn\WireGuard`) — configure RouterOS 7's built-in WireGuard interface and peers directly, no separate VPN server needed.
- **Bootstrap script generators** for a router with no connectivity yet: `WireGuardBootstrapScript` (dial-home VPN tunnel) and `RadiusBootstrapScript` (register FreeRADIUS as the PPP AAA backend) — pure string templating, print-and-paste into a fresh router's terminal.
- **Connection diagnostics** (`RouterOS\Sdk\Diagnostics\ConnectionProbe`) — classifies any connect/command failure into a small stable vocabulary (`connected`/`auth_failed`/`timeout`/`tls_failed`/`unreachable`) for a "test connection" UI, instead of every caller pattern-matching exception messages itself. Never throws.

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1

Install
-------

[](#install)

```
composer require redrodrigo/router-os-sdk
```

Quick start
-----------

[](#quick-start)

See the [`examples/`](examples/) directory for runnable scripts against a real router (basic usage, streaming, and the concurrent `write()`+`listen()`proof), plus a Laravel usage pattern.

```
use RouterOS\Sdk\Client;

$client = Client::connect([
    'host' => '192.168.88.1',
    'user' => 'admin',
    'pass' => 'secret',
    'tls'  => true, // port defaults to 8729 when true, 8728 otherwise
]);

// One-shot command
$interfaces = $client->write('/interface/print');

// Event-driven stream — one payload per change
$arpChannel = $client->listen('/ip/arp/listen');
while ($row = $arpChannel->wait()) {
    // e.g. ['address' => '10.0.0.5', 'mac-address' => '...']
}

// =interval=N push stream, for print commands with no /listen variant
$resources = $client->interval('/system/resource/print', 2);
while ($cycle = $resources->wait()) {
    // full snapshot every 2 seconds
}

$client->close();
```

### Query builder

[](#query-builder)

```
use RouterOS\Sdk\Query;

$query = new Query('/interface/print');
$query->where('disabled', 'false')
      ->where('running', '=', 'true');

$running = $client->query($query);
```

### Find/act helpers

[](#findact-helpers)

Most RouterOS resources follow the same `add`/`print`/`set`/`remove`convention under one path (e.g. `/ppp/secret/{add,print,set,remove}`). These four `Client` methods cover the common "find by filter, then act on `.id`" pattern without writing it out by hand each time:

```
$secret = $client->findOne('/ppp/secret', ['name' => 'joao']);      // first match, or null
$secrets = $client->findWhere('/ppp/secret', ['service' => 'pppoe']); // all matches
$client->removeWhere('/ppp/secret', ['name' => 'joao']);              // find + /remove each match
$client->setWhere('/ppp/secret', ['name' => 'joao'], ['profile' => 'vip']); // find + /set each match
```

### ISP toolkit

[](#isp-toolkit)

`RouterOS\Sdk\Isp\*` are small convenience wrappers (built entirely on the find/act helpers above — no protocol-level code of their own) for the operations a PPPoE-based ISP panel needs most: provisioning a customer, suspending them for non-payment, and shaping their bandwidth.

```
// PPPoE secrets + active sessions
$client->pppSecrets()->create('joao', 'senha123', profile: 'default');
$client->pppSecrets()->isOnline('joao');
$client->pppSecrets()->kill('joao'); // drop the session so it reconnects with fresh RADIUS/profile attrs
$client->pppSecrets()->remove('joao');

// Firewall address-list, e.g. blocking delinquent customers
$morosos = $client->addressList('morosos');
$morosos->block('10.0.0.5', comment: 'Contract #123'); // idempotent — no duplicate entry on retry
$morosos->isBlocked('10.0.0.5');
$morosos->unblock('10.0.0.5');

// Bandwidth shaping — flat (SimpleQueue) or hierarchical (QueueTree)
$client->simpleQueue()->create('joao', '10.0.0.5/32', '20M/20M');
$client->simpleQueue()->setMaxLimit('joao', '5M/5M');
$client->queueTree()->create('joao', parent: 'total-download', packetMark: 'joao-mark', maxLimit: '20M');

// PPP profiles (rate-limit templates secrets reference)
$client->pppProfiles()->create('vip', rateLimit: '50M/50M');

// Idempotent firewall rule installation, keyed by comment
$client->firewall()->ensureRule('filter', [
    'chain'            => 'forward',
    'src-address-list' => 'morosos',
    'action'           => 'drop',
], comment: 'block-morosos'); // no-op if a rule with this comment already exists

// Unified suspend/activate: each action (address/PPP/queue) runs
// independently, so one failing (e.g. the queue doesn't exist) doesn't
// stop the others — you get back exactly what succeeded and what failed.
$result = $client->customer('joao')->suspend(address: '10.0.0.5', pppUser: 'joao', queueName: 'joao');
$result->succeeded; // e.g. ['address_list', 'ppp_disabled']
$result->failed;    // e.g. ['queue_disabled' => 'no such item']
$client->customer('joao')->activate(address: '10.0.0.5', pppUser: 'joao', queueName: 'joao');
```

See `examples/isp-toolkit.php` for a runnable version.

`RouterOS\Sdk\Isp\RadiusBootstrapScript::generate()` produces a `.rsc` script that registers a FreeRADIUS server as the PPP AAA backend on a fresh router (`/radius add` + `/ppp aaa set use-radius=yes`) — a router needs this before credentials written to `radcheck`/`radreply` have any effect. Pure string templating, no connection needed — see `examples/radius-bootstrap.php`.

### VPN (WireGuard)

[](#vpn-wireguard)

`RouterOS\Sdk\Vpn\WireGuard` configures RouterOS 7's native WireGuard support — no separate VPN server product needed for the common "router dials home to a central hub" pattern:

```
use RouterOS\Sdk\Vpn\WireGuard;

$wg = $client->wireGuard('to-hq');
$wg->createInterface(listenPort: 51820); // RouterOS generates a keypair if none given
$wg->addPeer(
    publicKey: 'base64-hub-public-key',
    allowedAddress: '10.200.0.2/32',
    endpointHost: 'vpn.example.com',
    endpointPort: 51820,
);
```

If you need a keypair *before* the router has one (e.g. registering it as a peer on a hub ahead of time), `WireGuard::generateKeypair()` produces a RouterOS-compatible one (requires `ext-sodium`, bundled with PHP but not always enabled — falls back to a clear exception telling you to enable it or generate keys another way, e.g. the `wg genkey`/`wg pubkey` CLI tools).

For a router with no connectivity yet, `WireGuardBootstrapScript::generate()`produces a `.rsc` script a field technician can paste into its terminal on-site — see `examples/wireguard-bootstrap.php` (needs no router to run, it's pure string generation).

### Diagnostics

[](#diagnostics)

Every panel that embeds this SDK ends up writing its own "test connection" button, and every one of them needs to turn a raw exception into something a non-technical user can be shown. `RouterOS\Sdk\Diagnostics\ConnectionProbe`does that classification once, as part of the SDK, instead of each caller pattern-matching exception messages itself:

```
use RouterOS\Sdk\Diagnostics\ConnectionProbe;

$result = ConnectionProbe::probe([
    'host' => '192.168.88.1',
    'user' => 'admin',
    'pass' => 'secret',
    'tls'  => true,
]);

$result->status;      // ConnectionStatus::Connected|AuthFailed|Timeout|TlsFailed|Unreachable
$result->isConnected(); // bool
$result->identity;    // router identity (from /system/identity/print), or null
```

`probe()` never throws — a bad host, a refused connection, a failed TLS handshake, or wrong credentials all come back as the same `ProbeResult`shape, just with a different `status`. It connects, authenticates, runs a harmless read-only command (configurable via the second argument; defaults to `/system/identity/print`), and always closes the connection afterward.

If you already have a connected `Client` (e.g. reusing one from a pool) and just want the classification without the connect/close lifecycle, `ConnectionProbe::probeClient($client)` does the same thing against it without touching its lifecycle.

`ConnectionStatus` is intentionally generic — it describes what happened at the RouterOS-connection level, not anything about your application's own domain. A caller with app-specific states (e.g. "peer not configured yet") maps those on top of `ConnectionStatus` itself, rather than this SDK trying to guess them.

### Laravel

[](#laravel)

The `ServiceProvider`/`Facade` are auto-discovered — just install the package and publish the config:

```
php artisan vendor:publish --provider="RouterOS\Sdk\Integrations\Laravel\ServiceProvider" --tag=config
```

`config/router-os.php` follows the same `default` + `connections` shape as `database.php`, so a second router is just another entry away. Then:

```
use RouterOS\Sdk\Integrations\Laravel\Facade as RouterOs;

$interfaces = RouterOs::write('/interface/print');            // default connection
$interfaces = RouterOs::connection('secondary')->write(...);  // named connection
```

`RouterOsManager` (what the facade resolves to) auto-heals: a connection that goes dead (`Client::isClosed()`) is rebuilt on the *next* call, which matters for long-lived processes (queue workers, Octane) — but it never silently retries the command that actually failed, since that could double-execute a non-idempotent one (e.g. `/ip/address/add`) if the command reached the router and only the reply was lost. If the router is genuinely unreachable, further calls fail immediately (no full `connect_timeout` wait) for `reconnectCooldownSeconds` (default 5) after a failure, instead of every job/request paying the full timeout again.

If your routers aren't known statically at boot — e.g. one row per customer/site in a database, rather than a fixed `config/router-os.php`list — register them at runtime instead:

```
use RouterOS\Sdk\Integrations\Laravel\RouterOsManager;

app(RouterOsManager::class)->registerConnection("equipment-{$equipment->id}", [
    'host' => $equipment->ip_address,
    'user' => $equipment->api_user,
    'pass' => $equipment->api_pass,
    'port' => $equipment->api_port,
]);

RouterOs::connection("equipment-{$equipment->id}")->write(...);
```

### Resilience: reconnecting for long-running processes

[](#resilience-reconnecting-for-long-running-processes)

For a daemon-style script or Artisan command that's meant to run forever (not a request-scoped web/queue context — see the Laravel section above for that), `ManagedClient` is the PHP equivalent of MikroDash's Node `ROS`class `connectLoop()`: connect, hand a working `Client` to your setup code, and if the connection dies, reconnect with exponential backoff and hand over a fresh one again.

```
use RouterOS\Sdk\ManagedClient;

$managed = new ManagedClient($config);

$managed->onConnected(function ($client) {
    $arp = $client->listen('/ip/arp/listen');
    while (true) {
        $row = $arp->wait(); // throws when the connection dies, ending this cycle
        // handle $row
    }
});

$managed->onDisconnected(function () {
    // e.g. log it — a reconnect (with backoff) is about to be attempted
});

$managed->run(); // blocks until $managed->stop() is called
```

`ManagedClient` doesn't try to be a generic scheduler — it only notices a connection cycle ended (the callback returned, or threw) and reconnects. For concurrent work inside a cycle (`write()` + `listen()` at once), pass a `Reactor` to its constructor and drive your own Fiber + `Reactor::tick()`loop inside the callback, same as `examples/concurrent-reactor.php`. See `examples/managed-client.php` for a runnable version.

### Concurrency

[](#concurrency)

`write()` and `listen()`/`interval()` already work concurrently against each other out of the box (see `tests/ConnectionTest.php`). For that concurrency to hold against a *real* socket — not just in unit tests against the in-memory test double — pass a shared `RouterOS\Sdk\Io\Reactor`to `Client::connect()` and drive it:

```
use RouterOS\Sdk\Client;
use RouterOS\Sdk\Io\Reactor;

$reactor = new Reactor();
$client  = Client::connect($config, $reactor);
```

See `Io/Reactor.php`'s docblock and `tests/Io/ReactorConcurrencyTest.php`for the pattern — nothing drives the loop automatically in plain PHP, so whoever wants several concurrent operations to progress needs to tick it. Under Hyperf/Swoole or Laravel Octane, a planned `SwooleTransport` will make this automatic instead.

Testing
-------

[](#testing)

```
composer install
composer test
```

143 tests, including a real end-to-end test over a loopback TCP socket and a genuine two-Fiber concurrency test against a real socket.

Roadmap
-------

[](#roadmap)

- `SwooleTransport` + auto-detection, for coroutine-native concurrency under Hyperf and Laravel Octane (Swoole mode) with no manual `Reactor`driving required.
- Hyperf integration (`ConfigProvider` + coroutine connection pool).

Contributions on any of the above are welcome.

Credits
-------

[](#credits)

Built on ideas and code from two prior projects:

- [`evilfreelancer/routeros-api-php`](https://github.com/EvilFreelancer/routeros-api-php)— the RouterOS wire protocol (length-prefix codec, query builder, login) this SDK's `Protocol/` and `Query`/`Config`/`Auth` layers are adapted from.
- MikroDash, a Node.js RouterOS dashboard whose production-hardened tag-multiplexing model and RouterOS quirk fixes shaped `Connection`'s design.

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance94

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

11

Last Release

31d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/dc2af815fc5af896db9bae89605d17f9251e53c31ba58fb2391034da2ab7ddf0?d=identicon)[redrodrigo](/maintainers/redrodrigo)

---

Top Contributors

[![tecrodrigocastro](https://avatars.githubusercontent.com/u/31713982?v=4)](https://github.com/tecrodrigocastro "tecrodrigocastro (29 commits)")

---

Tags

asynclaravelswoolehyperfFibersrouterosmikrotiksocket-client

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/redrodrigo-router-os-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/redrodrigo-router-os-sdk/health.svg)](https://phpackages.com/packages/redrodrigo-router-os-sdk)
```

###  Alternatives

[evilfreelancer/routeros-api-php

Modern Mikrotik RouterOS API PHP client for your applications (with Laravel support)

505282.5k7](/packages/evilfreelancer-routeros-api-php)[toin0u/geotools-laravel

Geo-related tools PHP library for Laravel 4 &amp; 5

240390.0k1](/packages/toin0u-geotools-laravel)[easyswoole/wechat

Coroutine safety WeChat library

8243.3k13](/packages/easyswoole-wechat)[tochka-developers/queue-promises

Promises for Laravel queue jobs

1912.3k](/packages/tochka-developers-queue-promises)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
