$npx -y skills add tikoci/routeros-skills --skill routeros-mac-telnetMAC-Telnet protocol (MikroTik Layer-2 terminal/exec over UDP 20561) wire format, session handshake, and MD5 + MTWEI (EC-SRP) authentication. Use when: implementing or debugging a MAC-Telnet client/server, reaching a RouterOS device by MAC address without IP, parsing MAC-Telnet pa
| 1 | # MAC-Telnet — MikroTik Layer-2 Terminal Protocol |
| 2 | |
| 3 | MAC-Telnet lets you open an interactive RouterOS terminal (or run commands) |
| 4 | addressing a device by its **MAC address** — you do not need to know or route to |
| 5 | the target's IP. It works across a Layer-2 broadcast domain even when the target |
| 6 | has no IP, a wrong IP, or an IP you cannot route to, which is why WISPs and |
| 7 | provisioning tools rely on it. WinBox's "MAC Telnet" and the `tools/mac-telnet` |
| 8 | CLI use this protocol family. |
| 9 | |
| 10 | ## Why This Matters for Agents |
| 11 | |
| 12 | - It is the only way to get a shell on a freshly-unboxed or misconfigured |
| 13 | RouterOS device that has no reachable IP. |
| 14 | - The protocol carries its **own 6+6-byte source/destination MAC addressing |
| 15 | inside every packet**, independent of the outer UDP/L2 delivery. This trips up |
| 16 | implementers who assume normal UDP semantics. |
| 17 | - Authentication has **two incompatible modes** — classic **MD5** and modern |
| 18 | **MTWEI (EC-SRP over Curve25519)**. Current RouterOS 7.x defaults to MTWEI; |
| 19 | getting the mode-detection wrong is the single most common failure. |
| 20 | - It is simple enough to implement from scratch for the MD5 path; MTWEI needs an |
| 21 | elliptic-curve library. |
| 22 | |
| 23 | ## Protocol Basics |
| 24 | |
| 25 | | Property | Value | |
| 26 | |----------|-------| |
| 27 | | Transport | UDP | |
| 28 | | Port | **20561** (server listens; client may use an ephemeral or matching source port) | |
| 29 | | Addressing | In-packet 6-byte src + 6-byte dst MAC (the real addressing); outer UDP/IP is just transport | |
| 30 | | Delivery | Layer-2 — broadcast or unicast Ethernet frame to the target MAC; does not cross routers | |
| 31 | | Reliability | Application-level: byte-counter ACKs + timed retransmission (UDP gives none) | |
| 32 | | Session | Stateful handshake → authenticated → raw terminal stream → teardown | |
| 33 | | Auth | MD5 (legacy) **or** MTWEI / EC-SRP (current RouterOS 7.x default) | |
| 34 | | Max packet | 1500 bytes (`MT_PACKET_LEN`) | |
| 35 | |
| 36 | MAC-Telnet (UDP 20561) is a **sibling of MNDP** (UDP 5678) from the same |
| 37 | MikroTik L2 toolset: MNDP discovers the device and its MAC; MAC-Telnet then |
| 38 | connects to that MAC. See the `routeros-mndp` skill for discovery. They are |
| 39 | otherwise independent wire formats — MNDP is a one-shot TLV announcement with no |
| 40 | session, auth, control-block magic, or `00 15` client-type. |
| 41 | |
| 42 | ## Packet Header (22 bytes) |
| 43 | |
| 44 | Every SESSIONSTART / DATA / ACK / END session packet starts with a fixed |
| 45 | 22-byte header. **The session-key and client-type fields swap position by |
| 46 | direction** — this is the #1 footgun. (PING/PONG belong to the MAC-Ping tool and |
| 47 | use an 18-byte variant; see *Packet Types*.) |
| 48 | |
| 49 | Client → server layout: |
| 50 | |
| 51 | ```text |
| 52 | Offset Len Field |
| 53 | 0 1 version = 0x01 (always 1) |
| 54 | 1 1 packet type (see table below) |
| 55 | 2 6 source MAC (the client's in-protocol address) |
| 56 | 8 6 destination MAC (the target device) |
| 57 | 14 2 session key (uint16, BIG-endian) ← client direction |
| 58 | 16 2 client type = 00 15 ← client direction |
| 59 | 18 4 counter (uint32, BIG-endian) |
| 60 | ``` |
| 61 | |
| 62 | Server → client layout is identical **except** the two middle fields swap: |
| 63 | |
| 64 | ```text |
| 65 | 14 2 client type = 00 15 ← server direction |
| 66 | 16 2 session key (uint16, BIG-endian) ← server direction |
| 67 | ``` |
| 68 | |
| 69 | So a parser must know the direction (or which MAC is "ours") to read the session |
| 70 | key from the right offset. Reference: `init_packet()` in MAC-Telnet `protocol.c` |
| 71 | keys both fields on `mt_direction_fromserver`. |
| 72 | |
| 73 | - **Version** is always `1`. Reject other values as not-this-protocol. |
| 74 | - **Client type `00 15`** is a fixed constant identifying a MAC-Telnet/WinBox |
| 75 | client. Treat it as a magic constant; its internal meaning is not |
| 76 | documented by MikroTik. |
| 77 | - **Counter** is **cumulative payload bytes**, not a packet index — see below. |
| 78 | |
| 79 | ## Packet Types (`enum mt_ptype`) |
| 80 | |
| 81 | | Value | Name | Direction | Purpose | |
| 82 | |-------|------|-----------|---------| |
| 83 | | 0 | SESSIONSTART | client → server | Open a session (header only, no payload) | |
| 84 | | 1 | DATA | both | Carries control blocks and/or terminal bytes; must be ACKed | |
| 85 | | 2 | ACK | both | Acknowledges received bytes (header only; counter = bytes seen) | |
| 86 | | 4 | PING | either | **MAC-Ping** liveness/latency probe — *not* the session keepalive (see below) | |
| 87 | | 5 | PONG | either | Reply to PING, echoing the PING's payload | |
| 88 | | 255 | END | either | Tear down the session | |
| 89 | |
| 90 | Note the gap: there is **no type 3**; PING/PONG are 4/5. |
| 91 | |
| 92 | **PING/PONG are the separate MAC-Ping tool, not session keepalive.** In the |
| 93 | canonical implementat |