Skip to content

WinBox Native connection

Daniel Frantík edited this page Jul 20, 2026 · 16 revisions

WinBox Native (M2) Connection ⚠️ Alpha-preview

⚠️ Alpha-preview — ships in v4.0.0-alpha. This transport is the newest and least battle-tested in tik4net. The API/field mapping is reconstructed from the WinBox .jg catalog 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())
{
    // ...
}

Why a native mode at all?

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 mapping problem: API names ↔ WinBox keys

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:

  1. 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.
  2. 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.

The three names of one thing

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
Loading

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

How tik4net solves it

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.

When the mapping can't be resolved

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 pathTikNoSuchCommandException ("no M2 handler mapping for path … — add a PathAlias for the WinBox menu labels, a PathOverride for a raw handler, or use a WinboxCli connection")
  • unknown fieldWinboxFieldResolutionException ("cannot resolve API field … — add a FieldOverride or use a WinboxCli connection")

You resolve these per session with the mappings below.

Session mappings

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:

  1. PathOverride — taken at face value (also bypasses interface subtype filtering)
  2. direct .jg hit — the menu label already equals the API leaf
  3. PathAlias — your text alias, resolved against the live .jg map
  4. the built-in shipped alias dictionary
  5. UseGuiNames retry — 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.

Addressing entirely by GUI labels

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();

Turning it on and off per command

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 strict

What 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 names

Because 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, a FieldOverride, or a PathAlias for 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 reports Crud | Listen | SafeMode and no RawCommand, so CreateRawCommand on it throws TikConnectionCapabilityNotSupportedException (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 .jg catalogs WinBox caches under %APPDATA%\MikroTik\WinBox\<version>\*.jg (and the copy tik4net caches under CatalogCachePath) are plain JavaScript object literals: a window's path:[a,b] is the handler, and a field's id:'s10006' encodes the key (0x10006) and type (s=string). Match the window/field by its label to read off the numbers.

Catalog cache location

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%\tik4net

Files 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.

Coverage — what native maps today

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/resource or /system/health is read through the correct window for the device (on x86/CHR /system/health is 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-ids and firewall port/DSCP/PCP ranges (dst-port / src-port / dscp / pcp) are encoded as the M2 u32 array 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. firewall protocol), 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 guiding WinboxFieldResolutionException rather 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; native get / set / remove on 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/health on 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. health state / state-after-reboot). Read those over the API.
  • Exotic tables / verbs the built-in text bridge doesn't cover — add a session mapping (PathAlias first, 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 / ApiSsl for those. See the capability matrix.

Error handling

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

Capability

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); // true

ExecuteAsync / 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. /ping takes address where WinBox calls it Ping To). tik4net ships these aliases, but an exotic monitor may still need a FieldOverride(...); the thrown exception names the field and key to add.

Native vs. WinBox CLI — which to use?

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.

Acknowledgements

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.

See also

Clone this wiki locally