RouterOS API
Elixir client for MikroTik RouterOS binary API. Supports both plain TCP (port 8728) and TLS (port 8729) connections.
Features
- ✅ Plain TCP connections (port 8728)
- ✅ TLS/SSL connections (port 8729) with self-signed certificate support
- ✅ RouterOS 7.x authentication (plain text)
- ✅ RouterOS 6.x authentication (MD5 challenge-response fallback)
- ✅ Response parsing to Elixir maps with type coercion
- ✅ Synchronous command execution
-
✅ Streaming API for live monitoring (e.g.,
/interface/monitor-traffic) - ✅ Query filters support
- ✅ Connection pooling with NimblePool
- ✅ Telemetry integration for monitoring
- ✅ Helper functions for common operations
- ✅ Type-safe with Dialyzer
- ✅ Comprehensive test coverage
Installation
Add routeros_api to your list of dependencies in mix.exs:
def deps do
[
{:routeros_api, "~> 0.3.0"}
]
endOr from GitHub:
def deps do
[
{:routeros_api, github: "jlbyh2o/routeros_api"}
]
endQuick Start
Basic Connection (Plain TCP)
# Connect to router
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
port: 8728,
username: "admin",
password: "password"
})
# Execute a command
{:ok, interfaces} = RouterosApi.command(conn, ["/interface/print"])
# Result is a list of maps
[
%{
"name" => "ether1",
"type" => "ether",
"disabled" => false,
"running" => true
},
...
]
# Disconnect when done
RouterosApi.disconnect(conn)Secure Connection (TLS)
# Auto-detect TLS from port 8729
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
port: 8729, # TLS port - auto-detected
username: "admin",
password: "password",
ssl_opts: [verify: :verify_none] # For self-signed certificates
})
# Or explicit TLS with certificate verification
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
port: 8729,
username: "admin",
password: "password",
ssl: true,
ssl_opts: [
verify: :verify_peer,
cacertfile: "/path/to/ca.pem"
]
})Self-Signed Certificates
For lab/testing environments with self-signed certificates:
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
port: 8729,
username: "admin",
password: "password",
ssl_opts: [
verify: :verify_none # Disables certificate verification
]
})Note:verify: :verify_none should only be used in lab/testing environments. For production, use proper certificates and verify: :verify_peer.
Usage Examples
List IP Addresses
{:ok, addresses} = RouterosApi.command(conn, ["/ip/address/print"])Add IP Address
{:ok, _} = RouterosApi.command(conn, [
"/ip/address/add",
"=address=192.168.88.2/24",
"=interface=bridge"
])Query with Filters
{:ok, [interface]} = RouterosApi.command(conn, [
"/interface/print",
"?name=ether1"
])Error Handling
case RouterosApi.command(conn, ["/interface/print"]) do
{:ok, data} ->
IO.inspect(data)
{:error, %RouterosApi.Error{type: :trap, message: msg}} ->
IO.puts("RouterOS error: #{msg}")
{:error, %RouterosApi.Error{type: :fatal}} ->
IO.puts("Fatal error - connection lost")
{:error, reason} ->
IO.puts("Network error: #{inspect(reason)}")
endConfiguration
Connection Options
All connection options:
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1", # Required: Router hostname or IP
port: 8728, # Optional: Port (default: 8728 for TCP, 8729 for TLS)
username: "admin", # Required: RouterOS username
password: "password", # Required: RouterOS password
timeout: 5000, # Optional: Connection timeout in ms (default: 5000)
ssl: false, # Optional: Force TLS (auto-detected from port)
ssl_opts: [] # Optional: SSL options (e.g., verify: :verify_none)
})Connection Pooling
For production use with multiple concurrent requests, use connection pooling:
# In your application.ex
def start(_type, _args) do
children = [
{RouterosApi.Pool, [
name: :main_router,
host: "192.168.88.1",
port: 8729, # Optional: Use TLS
username: "admin",
password: "password",
pool_size: 10, # Number of connections in pool
ssl_opts: [verify: :verify_none] # For self-signed certs
]}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
# In your code - use pool name instead of connection PID
{:ok, interfaces} = RouterosApi.command(:main_router, ["/interface/print"])
{:ok, resource} = RouterosApi.Helpers.get_system_resource(:main_router)Benefits of pooling:
- Handles concurrent requests efficiently
- Automatic connection health checks
- Connection recovery on failures
- Supervised connections
Telemetry
The library emits telemetry events for monitoring:
Connection Events:
[:routeros_api, :connection, :start]- Connection attempt started[:routeros_api, :connection, :stop]- Connection successful[:routeros_api, :connection, :exception]- Connection failed
Command Events:
[:routeros_api, :command, :start]- Command execution started[:routeros_api, :command, :stop]- Command completed successfully[:routeros_api, :command, :exception]- Command failed
Pool Events:
[:routeros_api, :pool, :checkout]- Connection checked out from pool[:routeros_api, :pool, :checkin]- Connection returned to pool
Example telemetry handler:
:telemetry.attach_many(
"routeros-api-handler",
[
[:routeros_api, :command, :stop],
[:routeros_api, :command, :exception]
],
&MyApp.Telemetry.handle_event/4,
nil
)
defmodule MyApp.Telemetry do
require Logger
def handle_event([:routeros_api, :command, :stop], measurements, metadata, _config) do
if measurements.duration > 1_000_000_000 do
Logger.warning("Slow RouterOS command: #{metadata.command} (#{measurements.duration}ns)")
end
end
def handle_event([:routeros_api, :command, :exception], _measurements, metadata, _config) do
Logger.error("RouterOS command failed: #{metadata.command} - #{inspect(metadata.reason)}")
end
endHelper Functions
The library provides convenient helper functions for common operations:
alias RouterosApi.Helpers
# List all interfaces
{:ok, interfaces} = Helpers.list_interfaces(conn)
# Get specific interface
{:ok, interface} = Helpers.get_interface(conn, "ether1")
# List IP addresses
{:ok, addresses} = Helpers.list_ip_addresses(conn)
# Add IP address
{:ok, _} = Helpers.add_ip_address(conn, "192.168.1.1/24", "ether1")
# Get system information
{:ok, resource} = Helpers.get_system_resource(conn)
# Get/set router identity
{:ok, identity} = Helpers.get_identity(conn)
{:ok, _} = Helpers.set_identity(conn, "MyRouter")
# List firewall rules
{:ok, rules} = Helpers.list_firewall_rules(conn)
# List DHCP leases
{:ok, leases} = Helpers.list_dhcp_leases(conn)All helpers work with both direct connections and connection pools.
Streaming API (Live Monitoring)
For commands that produce continuous output (like /interface/monitor-traffic), use the streaming API:
# Start a dedicated streaming connection
{:ok, stream_conn} = RouterosApi.Stream.connect(%{
host: "192.168.88.1",
username: "admin",
password: "password"
})
# Create a stream for interface traffic monitoring
{:ok, stream} = RouterosApi.Stream.monitor(stream_conn, [
"/interface/monitor-traffic",
"=interface=ether1"
])
# Consume the stream - each item is a data map
stream
|> Stream.take(10) # Take 10 samples
|> Enum.each(fn data ->
rx = data["rx-bits-per-second"]
tx = data["tx-bits-per-second"]
IO.puts("RX: #{rx} bps, TX: #{tx} bps")
end)
# Disconnect when done
RouterosApi.Stream.disconnect(stream_conn)Time-Limited Monitoring
RouterOS supports duration-limited monitoring:
{:ok, stream} = RouterosApi.Stream.monitor(stream_conn, [
"/interface/monitor-traffic",
"=interface=ether1",
"=duration=30s" # Router stops after 30 seconds
])
# Collect all samples
samples = Enum.to_list(stream)Stream Transformations
Streams work with all standard Elixir Stream functions:
{:ok, stream} = RouterosApi.Stream.monitor(stream_conn, [
"/interface/monitor-traffic",
"=interface=ether1"
])
# Calculate 5-sample moving average
stream
|> Stream.chunk_every(5)
|> Stream.map(fn samples ->
avg = samples
|> Enum.map(&String.to_integer(&1["rx-bits-per-second"]))
|> Enum.sum()
|> div(5)
%{average_rx_bps: avg}
end)
|> Stream.take(10)
|> Enum.each(&IO.inspect/1)Streaming Limitations
- Not compatible with connection pools - Streams require dedicated connections
- One stream per connection - Use sequential streams or multiple connections
- No automatic reconnection - Connection loss terminates the stream
Stream Telemetry Events
[:routeros_api, :stream, :connect, :start|:stop|:exception]- Connection lifecycle[:routeros_api, :stream, :start]- Stream started[:routeros_api, :stream, :stop]- Stream completed/cancelled
Authentication
The library automatically handles authentication for different RouterOS versions:
RouterOS 7.x and 6.43+ (Plain Text)
Modern RouterOS versions use plain text authentication:
# The library automatically detects and uses plain text auth
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
username: "admin",
password: "password"
})RouterOS pre-6.43 (MD5 Challenge-Response)
Older RouterOS versions use MD5 challenge-response authentication. The library automatically falls back to this method if plain text authentication fails:
# Same code works - automatic fallback
{:ok, conn} = RouterosApi.connect(%{
host: "192.168.88.1",
username: "admin",
password: "password"
})Note: The authentication method is automatically detected and handled. You don't need to specify which method to use.
Troubleshooting
Connection Issues
"Connection refused"
-
Ensure the API service is enabled on the router:
/ip service print - Check that the correct port is being used (8728 for TCP, 8729 for TLS)
- Verify firewall rules allow connections to the API port
"Authentication failed"
- Verify username and password are correct
- Check that the user has API access permissions
- For RouterOS 7.x, ensure you're using the correct authentication method (automatic)
SSL/TLS Certificate Errors
-
For self-signed certificates, use
ssl_opts: [verify: :verify_none] -
For production, use proper certificates and
verify: :verify_peer -
Ensure the API-SSL service is enabled:
/ip service print
Performance
Slow Commands
- Use connection pooling for concurrent requests
- Monitor with telemetry events
- Check network latency to the router
Connection Timeouts
-
Increase timeout:
timeout: 10_000(10 seconds) - Check router CPU usage
- Verify network connectivity
Documentation
Full documentation is available at https://hexdocs.pm/routeros_api.
Testing
The library includes comprehensive test coverage:
# Run unit tests only
mix test
# Run all tests including integration tests
mix test --include integration --include ssl_integration
# Run with coverage
mix test --cover
# Run Dialyzer type checking
mix dialyzer
# Run code quality checks
mix credo --strictTest Coverage:
- 109 tests (all passing)
- Unit tests for all modules
- Integration tests with real RouterOS device
- SSL/TLS integration tests
- Connection pooling tests
- Telemetry tests
- Helper function tests
Compatibility
Tested with:
- Elixir 1.14 - 1.18
- OTP 25 - 28
- RouterOS 7.12.1 (stable)
- RouterOS 6.x (MD5 auth fallback)
Supported RouterOS versions:
- RouterOS 7.x (plain text authentication)
- RouterOS 6.43+ (plain text authentication)
- RouterOS pre-6.43 (MD5 authentication fallback)
Development
Quality Tools
The project uses several tools to maintain code quality:
- Dialyzer - Static type checking
- Credo - Code quality and consistency
- ExDoc - Documentation generation
- GitHub Actions - Automated CI/CD
Running Quality Checks
# Format code
mix format
# Check formatting
mix format --check-formatted
# Run all quality checks
mix test && mix dialyzer && mix credoContributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE for details.
Acknowledgments
This project was inspired by the original erotik Erlang library.