15 releases (7 breaking)
Uses new Rust 2024
| 0.8.0 | May 4, 2026 |
|---|---|
| 0.6.1 | Mar 19, 2026 |
| 0.5.1 | Dec 14, 2025 |
| 0.4.2 | Nov 30, 2025 |
| 0.1.0 | Mar 28, 2024 |
#136 in Asynchronous
7,335 downloads per month
Used in 2 crates
150KB
2.5K
SLoC
mikrotik-rs
A Rust client for the MikroTik RouterOS API.
Send commands, stream responses, and manage multiple concurrent operations against MikroTik routers with a type-safe, channel-based API.
Highlights
- Sans-IO protocol core —
#![no_std]-compatible, runtime-agnostic protocol implementation - Tokio adapter — high-level async client with background actor, per-command channels, and TLS support
- Embassy adapter — embedded-friendly async client, transport-agnostic over
embedded-io-async - Typestate command builder — the compiler enforces correct command construction order
- Compile-time command validation — the
command!macro validates RouterOS command paths at compile time - Zero-copy protocol parsing — words are parsed lazily from the receive buffer with byte-level dispatch
- Concurrent command execution — each command is tagged with a UUID v4 for response demultiplexing
- Automatic lifecycle management — dropping a response receiver cancels the command on the router
- No
unsafecode —unsafe_code = "forbid"is enforced at the workspace level
Installation
cargo add mikrotik-rs
Quick start
use mikrotik_rs::{MikrotikDevice, CommandBuilder, Event};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let device = MikrotikDevice::connect("192.168.88.1:8728", "admin", Some("password")).await?;
let cmd = CommandBuilder::new()
.command("/system/resource/print")
.build();
let mut rx = device.send_command(cmd).await?;
while let Some(event) = rx.recv().await {
match event {
Event::Reply { response, .. } => println!("{:?}", response.attributes),
Event::Done { .. } => break,
Event::Trap { response, .. } => eprintln!("error: {}", response.message),
other => println!("{other:?}"),
}
}
Ok(())
}
MikrotikDevice is cheaply Clone-able — share it across tasks, spawn concurrent commands, and let
each one stream responses independently.
Feature flags
| Feature | Default | Description |
|---|---|---|
tokio |
yes | Enables the Tokio async adapter and MikrotikDevice client |
embassy |
no | Enables the Embassy embedded async adapter |
tokio-tls |
no | Enables TLS support via tokio-rustls |
# Plaintext (default)
mikrotik-rs = "0.7"
# TLS for API-SSL (port 8729) — bring your own crypto provider
mikrotik-rs = { version = "0.7", features = ["tokio-tls"] }
rustls = { version = "0.23", features = ["ring"] } # or "aws-lc-rs"
# Embassy embedded adapter (no_std)
mikrotik-rs = { version = "0.7", default-features = false, features = ["embassy"] }
# Protocol types only (no runtime)
mikrotik-rs = { version = "0.7", default-features = false }
Usage
Building commands
The CommandBuilder uses a typestate pattern: you must call .command() before .attribute() or .build().
The compiler rejects incorrect ordering.
use mikrotik_rs::CommandBuilder;
let cmd = CommandBuilder::new()
.command("/interface/ethernet/monitor")
.attribute("numbers", Some("0,1"))
.attribute("once", None)
.build();
The command! macro
For static command paths, the command! macro validates the path at compile time:
use mikrotik_rs::command;
let cmd = command!("/interface/print");
let cmd = command!(
"/interface/ethernet/monitor",
numbers = "0,1",
once
);
// These fail at compile time:
// command!("invalid//command"); // no empty segments
// command!("no-leading-slash"); // must start with '/'
Handling responses
Every call to send_command returns an mpsc::Receiver<Event> scoped to that command:
use mikrotik_rs::Event;
let mut rx = device.send_command(cmd).await?;
while let Some(event) = rx.recv().await {
match event {
Event::Reply { response, .. } => {
println!("attributes: {:?}", response.attributes);
}
Event::Done { .. } => {
println!("command completed");
}
Event::Trap { response, .. } => {
eprintln!("trap: {} (category: {:?})", response.message, response.category);
}
Event::Fatal { reason } => {
eprintln!("fatal: {reason}");
}
Event::Empty { .. } => {
// RouterOS 7.18+: command had no data to return
}
}
}
TLS connections
With the tokio-tls feature enabled, use the builder for TLS:
// Accept self-signed certs (typical for MikroTik routers)
let device = MikrotikDevice::builder("192.168.88.1:8729")
.credentials("admin", Some("password"))
.tls_insecure()
.connect()
.await?;
// Or with a custom rustls ClientConfig
let device = MikrotikDevice::builder("192.168.88.1:8729")
.credentials("admin", Some("password"))
.tls_config(my_config, server_name)
.connect()
.await?;
Streaming responses
Commands that produce continuous output stream results through the same channel:
let monitor = CommandBuilder::new()
.command("/interface/monitor-traffic")
.attribute("interface", Some("ether1"))
.build();
let mut rx = device.send_command(monitor).await?;
while let Some(event) = rx.recv().await {
println!("{event:?}");
}
// Dropping `rx` automatically sends /cancel to the router
Queries
Filter results using RouterOS query operations:
use mikrotik_rs::{CommandBuilder, QueryOperator};
let cmd = CommandBuilder::new()
.command("/interface/print")
.query_equal("type", "ether")
.query_equal("running", "true")
.query_operations([QueryOperator::And].into_iter())
.build();
| Method | Wire format | Description |
|---|---|---|
query_equal(k, v) |
?k=v |
Property equals value |
query_gt(k, v) |
?>k=v |
Property greater than value |
query_lt(k, v) |
?<k=v |
Property less than value |
query_is_present(k) |
?k |
Property exists |
query_not_present(k) |
?-k |
Property does not exist |
query_operations(ops) |
?#... |
Boolean stack operations (And, Or, Not, Dot) |
Workspace
The library is split into focused crates:
| Crate | Purpose |
|---|---|
mikrotik-proto |
Sans-IO protocol core (#![no_std]) — wire format, commands, responses, connection state machine |
mikrotik-tokio |
Tokio async adapter — background actor, per-command channels, TLS |
mikrotik-embassy |
Embassy embedded async adapter — transport-agnostic over embedded-io-async |
mikrotik-rs |
Convenience re-exports from all crates |
┌─────────────────────────────────────────────────────────────────┐
│ mikrotik-rs │
│ (re-exports from all) │
├──────────────────────────┬──────────────────────────────────────┤
│ mikrotik-tokio │ mikrotik-embassy │
│ (Tokio async adapter) │ (Embassy embedded adapter) │
├──────────────────────────┴──────────────────────────────────────┤
│ mikrotik-proto │
│ Sans-IO protocol core (#![no_std]) │
└─────────────────────────────────────────────────────────────────┘
Examples
The repository includes runnable examples in examples/.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request.
License
Licensed under the GNU Affero General Public License v3.0.
For commercial licensing options (use without AGPL obligations), contact the project maintainer.
Disclaimer
This project is not affiliated with MikroTik. It is an independent, community-developed library.
Dependencies
~1–13MB
~118K SLoC