-
Notifications
You must be signed in to change notification settings - Fork 99
WinBox Native connection
⚠️ Alpha-preview — ships inv4.0.0-alpha. This transport is the newest and least battle-tested in tik4net. The API/field mapping is reconstructed from the WinBox.jgcatalog and covers common tables, but exotic tables/verbs may need a session mapping, and details may still change. For production use prefer the API or, on the WinBox channel, the WinBox CLI transport. Bug reports and mapping fixes are welcome.
WinBox Native (TikConnectionType.WinboxNative) connects over the encrypted WinBox channel on TCP
port 8291 — the same port and crypto as the WinBox application — but instead of driving a terminal it
issues structured M2 protocol calls (the binary key–value messages WinBox itself uses). CRUD maps
directly to the native getall / get / set / add / remove / move handlers, so there is no CLI
screen-scraping involved.
Authentication is identical to WinBox CLI: EC-SRP5 + AES-128-CBC (legacy MD5
fallback for RouterOS < 6.43). The difference is purely above the channel: native M2 vs. a mepty
terminal.
using System.Linq;
using tik4net;
using (var conn = ConnectionFactory.OpenConnection(
TikConnectionType.WinboxNative, "192.168.4.1", "admin", ""))
{
var ifaces = conn.LoadAll<tik4net.Objects.Interface.Interface>().ToList();
foreach (var i in ifaces)
conn.CreateCommandAndParameters("/interface/set", ".id", i.Id, "comment", "managed").ExecuteNonQuery();
}Via TikConnectionSetup — the recommended way, because its configure callback runs before the
connection opens, which is exactly where the session mappings below must be set:
var setup = new TikConnectionSetup("192.168.4.1", "admin", "");
using (var conn = setup.CreateWinboxNativeConnection(c =>
{
// WinBox menu: PPP ▸ Secrets ▸ (window) PPP Secret API path: /ppp/secret
c.PathAlias("/ppp/secret", "/ppp/secrets/ppp-secret");
c.CatalogCachePath = @"C:\ProgramData\myapp\winbox-cache";
}))
{
// ...
}
// async
using (var conn = await setup.CreateWinboxNativeConnectionAsync())
{
// ...
}ConnectTimeout can also be set on the TikConnectionSetup instance itself, which flows into the
connection:
var setup = new TikConnectionSetup("192.168.4.1", "admin", "") { ConnectTimeout = TimeSpan.FromSeconds(10) };
using (var conn = setup.CreateWinboxNativeConnection())
{
// ...
}The CLI transports (Telnet / MAC-Telnet / WinBox CLI) parse print as-value text. Native M2 avoids the
terminal entirely: it is faster, has no prompt/echo/paging edge cases, and is closer to how the WinBox GUI
talks to the router. The trade-off is the mapping problem described next.
The MikroTik API addresses things by text: a path like /ip/firewall/filter and field names like
src-address or connection-state. The WinBox M2 protocol addresses the same things by numbers: a
handler array like [20,3] and integer field keys like 0xFE0009. To make the O/R mapper work unchanged,
WinboxNativeConnection must translate API path → handler and API field name → numeric key (and
back) — and that mapping is genuinely awkward for two reasons:
- The numbers are version-volatile. Handler minors and field keys shift between RouterOS releases as menus and fields are added. Hard-coding them would rot.
-
WinBox stores GUI labels, not API names. The catalog calls a window "DNS Static Entry" (API
/ip/dns/static), "Firewall Rule" (API/ip/firewall/filter), "Resources" (API/system/resource). WinBox never stores the API leaf, so the two cannot be derived from each other mechanically in every case.
Every table and field in this transport has three identities, and it helps to keep them apart:
flowchart TB
subgraph see["What a human can see"]
gui["<b>WinBox GUI</b><br/>menu: PPP ▸ Secrets<br/>window title: PPP Secret<br/>field caption: MAC Address"]
api["<b>RouterOS API / CLI</b><br/>path: /ppp/secret<br/>field: mac-address"]
end
subgraph wire["What the M2 wire needs"]
m2["<b>WinBox M2</b><br/>handler: [20,12]<br/>field key: 0xFF0001"]
end
api -->|"label normalizer<br/>lower-case · spaces to '-' · drop '.'"| gui
gui -->|"router's own .jg catalog<br/>(menu tree to handler / key)"| m2
api -.->|"PathAlias · UseGuiNames<br/>text — survives upgrades"| gui
api -.->|"PathOverride · FieldOverride<br/>numbers — re-verify per version"| m2
The GUI label is the stable bridge: it changes rarely, it is printed on your screen, and the router's
own .jg already ties it to the number. That is why every mapping tik4net ships — and every hook it offers
you — is written in labels, with the numbers looked up behind them.
A menu-label path is just the window's breadcrumb plus its record label, lower-cased with spaces as
dashes. This is the spelling PathAlias (below) expects:
| What WinBox shows | Menu-label path | API path |
|---|---|---|
| IP ▸ DNS ▸ Static → window DNS Static Entry | /ip/dns/dns-static-entry |
/ip/dns/static |
| PPP ▸ Secrets → window PPP Secret | /ppp/secrets/ppp-secret |
/ppp/secret |
| System ▸ Resources → window Resources | /system/resources/resources |
/system/resource |
| IP ▸ Firewall ▸ Connections → window Connection | /ip/firewall/connection |
/ip/firewall/connection — identical, the clean case |
The design splits the mapping by stability, so the volatile half is always read live and only the stable (text) half is shipped:
| What | Stability | Source |
|---|---|---|
| Handler number, field key / type / read-only | volatile (changes per version) |
read live from the version-matched .jg catalog the router serves, fetched once over the same WinBox channel and cached |
| API field name ↔ label | stable text | a normalizer (lower-case, spaces→-) over the live labels, + a small built-in alias dictionary for irregular labels |
| API path ↔ handler | stable text → volatile number | the menu tree in the live .jg (handler is always live); a small built-in alias bridges irregular paths to their menu label |
Protocol constants (getall cmd, .id, comment, disabled keys) |
fixed | hard-coded |
So: handler/field numbers are never hard-coded — they come from the router's own .jg for its version.
Only the English text bridge is shipped (a hard-coded dictionary), and it is the thing that carries across
versions. Typed values (IP addresses, MACs, enums, interface references) are encoded/decoded from the .jg
field type — e.g. an IP packs to a u32, an interface reference resolves to/from the target object's name.
If a field name or path can't be resolved (an unusual label, or a table the built-in dictionary doesn't cover), the connection throws a guiding exception rather than guessing:
- unknown path →
TikNoSuchCommandException("no M2 handler mapping for path … — add aPathAliasfor the WinBox menu labels, aPathOverridefor a raw handler, or use a WinboxCli connection") - unknown field →
WinboxFieldResolutionException("cannot resolve API field … — add aFieldOverrideor use a WinboxCli connection")
You resolve these per session with the mappings below.
All of these take priority over the built-in dictionary and must be set before Open. They differ in
one important way — whether you pin text or a number:
var setup = new TikConnectionSetup("192.168.4.1", "admin", "");
using (var conn = setup.CreateWinboxNativeConnection(c =>
{
// 1) PREFERRED — teach it a path by the labels WinBox shows you.
// WinBox menu: PPP ▸ Secrets ▸ (window) PPP Secret
c.PathAlias("/ppp/secret", "/ppp/secrets/ppp-secret");
// 2) Accept GUI spelling for paths and field names ("MAC Address" → mac-address,
// "Dst. Address" → dst-address, "/IP/Firewall/Filter"). Off by default.
c.UseGuiNames = true;
// 3) Escape hatch — pin a field to its numeric M2 key.
c.FieldOverride("/ip/hotspot/user", "mac-address", 0x1);
// 4) Escape hatch — pin a whole path to a raw handler [major, minor].
c.PathOverride("/tool/sniffer", new[] { 27, 101 });
}))
{
// ...
}PathAlias survives router upgrades; PathOverride / FieldOverride do not. An alias pins only the
label path — the handler behind it is still read live from that router's .jg, so a RouterOS release that
renumbers the handler changes nothing for you. The numeric overrides pin a value that can move between
versions, so re-verify them after an upgrade. Reach for them only when the label route can't work: the
window has no entry in the menu tree, or its label is ambiguous.
Resolution order for a path, highest first:
-
PathOverride— taken at face value (also bypasses interface subtype filtering) - direct
.jghit — the menu label already equals the API leaf -
PathAlias— your text alias, resolved against the live.jgmap - the built-in shipped alias dictionary
-
UseGuiNamesretry — the name is pushed through the label normalizer and steps 1–4 run again
Fields resolve the same way (FieldOverride → shipped API alias → universal keys → live .jg → GUI-name
retry). A name that resolves verbatim is never re-normalized, and decoded output always comes back in
canonical API names regardless of how you addressed the request.
With UseGuiNames = true you can drive the connection without knowing a single RouterOS API identifier —
type the path the way the WinBox menu spells it and the field the way its caption reads:
using System;
using System.Linq;
using tik4net;
var setup = new TikConnectionSetup("192.168.4.1", "admin", "");
using var conn = setup.CreateWinboxNativeConnection(c => c.UseGuiNames = true); // set BEFORE Open
// Path spelled as the menu tree shows it: "/IP/Firewall/Filter" → /ip/firewall/filter
var rules = conn.CreateCommand("/IP/Firewall/Filter/print").ExecuteList();
foreach (var row in rules)
Console.WriteLine(row.GetResponseFieldOrDefault("src-address", "(any)")); // ← canonical API names on output
// Field names spelled as the window captions read.
var id = rules.First().GetResponseField(".id");
conn.CreateCommandAndParameters("/IP/Firewall/Filter/set",
".id", id,
"Src._Address", "10.0.0.0/24", // "Src._Address", "Src. Address" and "src-address" all fold the same
"Dst. Address", "192.168.88.0/24")
.ExecuteNonQuery();UseGuiNames is not a connect-time setting — the path and field resolvers are built per operation and
read the flag at that moment, so you can scope it to a single call and keep the rest of the session strict:
var setup = new TikConnectionSetup("192.168.4.1", "admin", "");
using var conn = setup.CreateWinboxNativeConnection(); // strict by default
conn.CreateCommand("/ip/firewall/filter/print").ExecuteList(); // strict API names
conn.UseGuiNames = true; // ← labels accepted from here on
conn.CreateCommandAndParameters("/IP/Firewall/Filter/set", ".id", id, "Src. Address", "10.0.0.0/24")
.ExecuteNonQuery();
conn.UseGuiNames = false; // ← back to strictWhat decides is the flag's value at execute time, not when the command object was created — so this also works:
var cmd = conn.CreateCommand("/IP/Firewall/Filter/print");
conn.UseGuiNames = true;
var rows = cmd.ExecuteList(); // resolved with GUI namesBecause this is mutable connection state, don't toggle it while the same connection is in use from another thread — in that case set it once before first use.
Three things to keep in mind:
-
It is input-only. Decoded rows always come back with canonical API field names (
src-address), never the GUI caption — so the O/R mapper and any existing parsing code keep working unchanged. - Verbatim wins. A name that resolves as-is is never re-normalized, so turning the flag on cannot change the meaning of code that already used API names.
-
It is a convenience, not a second addressing scheme. The normalizer only folds separators (space /
_→-, abbreviation.dropped, case). A label that does not reduce to the API name that way — e.g. "Tx" →tx-byte— needs the built-in alias, aFieldOverride, or aPathAliasfor a whole path.
Note — this is not
CreateRawCommand. Raw pass-through is a CLI feature: it hands your text to the router's command line, so it exists on the API and CLI transports (including WinBox CLI) and its payload is RouterOS CLI syntax, never WinBox labels. WinBox Native speaks binary M2 key–value messages, which have no textual raw form at all — it reportsCrud | Listen | SafeModeand noRawCommand, soCreateRawCommandon it throwsTikConnectionCapabilityNotSupportedException(fail-closed). Label addressing, as above, is the native equivalent: it skips the API-name step, not the mapping. If you need a command the native mapping doesn't cover at all, switch to WinBox CLI and send CLI text.
Tip — finding the numbers. Needed only for the numeric escape hatches. The
.jgcatalogs WinBox caches under%APPDATA%\MikroTik\WinBox\<version>\*.jg(and the copy tik4net caches underCatalogCachePath) are plain JavaScript object literals: a window'spath:[a,b]is the handler, and a field'sid:'s10006'encodes the key (0x10006) and type (s=string). Match the window/field by its label to read off the numbers.
The fetched .jg set is cached per router version so it isn't downloaded on every connect:
var setup = new TikConnectionSetup("192.168.4.1", "admin", "");
using var conn = setup.CreateWinboxNativeConnection(c =>
c.CatalogCachePath = @"C:\ProgramData\myapp\winbox-cache"); // default: %TEMP%\tik4netFiles land in <CatalogCachePath>\<routerVersion>\*.jg. Only the base roteros.jg is required; the
feature plugins (ppp.jg, hotspot.jg, dhcp.jg, …) are optional and only present for packages the router
actually has — calling into a path whose plugin is absent yields TikNoSuchCommandException ("no such
command"), exactly like the router would answer.
The native transport resolves the common configuration surface directly from the router's live .jg
catalog. The list below is the current state of the mapping, so you know what to expect before reaching
for an override.
Resolved out of the box
-
Paths across the common menus (
/interface,/ip,/ipv6,/routing,/system,/tool,/ppp, bridge, CAPsMAN/WiFi, …). Handlers come live from the.jg; irregular GUI labels are bridged by the built-in alias dictionary. -
Singletons, including board-gated ones — the per-board window is selected automatically, so a
singleton like
/system/resourceor/system/healthis read through the correct window for the device (on x86/CHR/system/healthis the singleton sensor window). -
Scalar field types — strings, numbers, booleans (
yes/no), IPv4/IPv6 addresses, MAC addresses, enums, and interface references (resolved to/from the target object's name). -
List / number-range fields — multi-value number ranges such as bridge-VLAN
vlan-idsand firewall port/DSCP/PCP ranges (dst-port/src-port/dscp/pcp) are encoded as the M2u32array WinBox uses ("10,20-30"↔[10,10,20,30]), with!-negation where the field allows it. -
opt/not-wrapped fields — for fields the GUI wraps in a presence/negation flag (e.g. firewallprotocol), the flag is emitted alongside the value, so the router doesn't silently ignore the setting.
Needs an override or another transport (genuine current limits, not bugs)
-
Interface-reference list fields — e.g. bridge-VLAN
tagged/untagged(a list of interface references). These are not yet encodable over native; the resolver throws a guidingWinboxFieldResolutionExceptionrather than silently dropping the value. Set such fields over the API / a CLI transport. -
Creating sub-type interfaces —
/interface add type=bridge|vlan|…is rejected by the router over native ("unsupported device type"). Create the interface over API/CLI; nativeget/set/removeon the already-existing interface works normally. -
Hardware-only / API-only fields — read-only sensor windows are empty on virtual or sensorless
hardware (e.g.
/system/healthon a CHR/VM has no sensors to report), and a few fields are exposed only by the API/CLI and never by the WinBox window (e.g. healthstate/state-after-reboot). Read those over the API. -
Exotic tables / verbs the built-in text bridge doesn't cover — add a
session mapping (
PathAliasfirst, the numeric overrides as a fallback), or fall back to WinBox CLI, which accepts any command the router's CLI does. -
Protocol-model features — no Streaming / RawSentences / Tagging / RawCommand; the M2 model has no
equivalent. Use
Api/ApiSslfor those. See the capability matrix.
Native errors are surfaced with the router's own message and translated to the same exception types as the API/CLI transports, so existing error-handling code keeps working:
| Router result | tik4net exception |
|---|---|
already have such address / already-exists |
TikAlreadyHaveSuchItemException |
object doesn't exist / unresolvable .id
|
TikNoSuchItemException |
| unmapped path / missing plugin | TikNoSuchCommandException |
| other M2 errors |
TikCommandTrapException (message includes the router's error string) — the same generic fallback as the API/CLI/REST transports |
CRUD, Listen and Safe Mode — native reports Crud | Listen | SafeMode, so the callback-based async
APIs work in addition to the synchronous CRUD ones, and SafeModeTake() / SafeModeRelease() are available
via the native M2 toggleSafeMode commands (no in-place SafeModeUnroll() — to roll back, drop the
connection without releasing; see Safe Mode):
bool hasCrud = conn.Supports(TikConnectionCapability.Crud); // true
bool hasListen = conn.Supports(TikConnectionCapability.Listen); // true
bool hasSafeMode = conn.Supports(TikConnectionCapability.SafeMode); // trueExecuteAsync / LoadAsync / LoadListenAsync are supported and cover three shapes:
| Call shape | Example | How native does it |
|---|---|---|
| Streaming monitor |
/tool/torch, /tool/profile, /ping, /interface/monitor-traffic
|
starts a .jg monitor window and polls it every autorefresh ms over the normal M2 channel (WinBox has no server push — webfig polls too); each polled record is pushed to your row callback. /tool/torch is confirmed working here (live, WinboxNative/WinboxNativeMac) — M2 decodes typed fields (bitrate, number, ipaddr) directly, so no text parsing is involved at all. The CLI transports support it too, but via a different, dedicated mechanism (freeze-frame-interval+proplist, since torch's usual as-value snapshot prints nothing) — see Command translation. |
| Async list |
cmd.LoadAsync<Interface>(...) on a /print
|
runs the read on a background thread, emits each row, then completes |
| Change listen | conn.LoadListenAsync<Interface>(onChange, onDeleted, …) |
polls the table and diffs snapshots by .id; a changed row fires onChange, a vanished .id fires onDeleted (config fields only — runtime counters are ignored so it doesn't fire on every tick) |
Cancel a running async command with Cancel() / CancelAndJoin(), or by closing the connection (a close
mid-stream stops cleanly without surfacing an error). TikConnectionCapability.Streaming
(ExecuteListWithDuration) is not reported — use the API transport for that.
Note: a few monitor windows label fields differently from the API (e.g.
/pingtakesaddresswhere WinBox calls it Ping To). tik4net ships these aliases, but an exotic monitor may still need aFieldOverride(...); the thrown exception names the field and key to add.
Both use the same encrypted WinBox channel (TCP 8291, EC-SRP5 + AES). They differ above the channel:
WinBox Native (WinboxNative) |
WinBox CLI (WinboxCli) |
|
|---|---|---|
| How CRUD works | structured M2 calls (no terminal) | drives the RouterOS CLI via the mepty terminal |
| Field mapping | API ↔ M2 keys via the live .jg + dictionary |
none — uses CLI text directly |
| Coverage | broad (what native maps today); exotic tables/verbs may need an override | every command the CLI accepts |
| Speed | no prompt/echo/paging overhead | terminal round-trips |
| Best when | you want clean structured CRUD on common tables | you need a command the native mapping doesn't cover yet, or full CLI parity |
WinBox CLI is the fallback whenever native mapping is incomplete for your
workload: it accepts any command the router's CLI does, at the cost of text parsing. The two are
interchangeable at the ITikConnection level, so switching is a one-line change to the
TikConnectionType / factory call.
The WinBox M2 protocol mapping was reconstructed from the official webfig /jsproxy client
(master*.js, which speaks the same M2 protocol in JSON) and the community reverse-engineering work
referenced on WinBox-CLI-connection.
- WinBox-CLI-connection — terminal-driven WinBox CRUD (the fallback)
- WinBox-Native-MAC-connection — the same structured M2 CRUD over the MAC layer (no IP route needed)
- WinBox-CLI-MAC-connection — WinBox CLI over the MAC layer
- SSL-connection — encrypted binary API connection
-
MCP server — run M2 commands and see the raw M2 trace (
includeRawTrace) from an MCP client - Roadmap-4x — transport roadmap