$npx -y skills add tikoci/routeros-skills --skill routeros-mndpMNDP (MikroTik Neighbor Discovery Protocol) wire format, behavior, and RouterOS /ip/neighbor integration. Use when: implementing MNDP discovery, parsing MNDP packets, working with /ip/neighbor, understanding WinBox device discovery, debugging why a router doesn't appear in neighb
| 1 | # MNDP — MikroTik Neighbor Discovery Protocol |
| 2 | |
| 3 | MNDP is the UDP broadcast/multicast protocol that RouterOS uses for automatic device |
| 4 | discovery on the local network. It is the same protocol WinBox uses to find routers. |
| 5 | Every RouterOS device participates by default. |
| 6 | |
| 7 | ## Why This Matters for Agents |
| 8 | |
| 9 | - **`/ip/neighbor`** on RouterOS is the CLI/REST surface for MNDP results |
| 10 | - WinBox's "Neighbors" tab is an MNDP listener |
| 11 | - Any agent implementing device discovery for MikroTik equipment needs MNDP |
| 12 | - The protocol is simple enough to implement from scratch — no library needed |
| 13 | |
| 14 | ## Protocol Basics |
| 15 | |
| 16 | | Property | Value | |
| 17 | |----------|-------| |
| 18 | | Transport | UDP | |
| 19 | | Port | 5678 | |
| 20 | | IPv4 | Broadcast to 255.255.255.255:5678 | |
| 21 | | IPv6 | Multicast to ff02::1 (all-nodes link-local) | |
| 22 | | Direction | Bidirectional — same port for send and receive | |
| 23 | | Authentication | None — read-only discovery, no credentials | |
| 24 | | Scope | Layer 2 broadcast domain (does not cross routers) | |
| 25 | |
| 26 | ## How Discovery Works |
| 27 | |
| 28 | 1. **Listener sends a refresh packet** — a small UDP datagram to 255.255.255.255:5678 (MAC-Telnet sends a minimal 4-byte zeroed header; a 9-byte form also works — see below) |
| 29 | 2. **All RouterOS devices on the LAN reply** — each sends a TLV-encoded announcement with identity, version, board, MAC, IP, uptime, etc. |
| 30 | 3. **Replies arrive asynchronously** — devices respond within milliseconds to seconds depending on network conditions |
| 31 | 4. **RouterOS devices also announce periodically** (~60s cycle) without being prompted — passive listening works but is slow to populate |
| 32 | |
| 33 | ### Refresh Packet (Discovery Request) |
| 34 | |
| 35 | A short packet that triggers immediate replies from all RouterOS devices on the broadcast |
| 36 | domain. The minimal form is just a zeroed 4-byte header — this is exactly what MAC-Telnet |
| 37 | sends (`unsigned int message = 0;`): |
| 38 | |
| 39 | ```text |
| 40 | Offset Length Value Field |
| 41 | 0 1 0x00 header byte 0 (version per MAC-Telnet; "unknown" per Wireshark) |
| 42 | 1 1 0x00 header byte 1 (ttl per MAC-Telnet) |
| 43 | 2 2 0x0000 seqno / checksum |
| 44 | ``` |
| 45 | |
| 46 | As raw bytes (minimal): `00 00 00 00` |
| 47 | |
| 48 | Some implementations append an explicit refresh TLV (type 6, length 0). The complete |
| 49 | header + empty-TLV form is 8 bytes (`00 00 00 00 00 06 00 00`); some implementations add |
| 50 | a trailing zero byte (non-semantic padding), giving 9 bytes. RouterOS accepts all forms: |
| 51 | |
| 52 | ```text |
| 53 | Offset Length Value Field |
| 54 | 0 4 00 00 00 00 header (2 header bytes + 2-byte seqno) |
| 55 | 4 2 0x0006 TLV type = 6 (refresh) ← big-endian |
| 56 | 6 2 0x0000 TLV length = 0 |
| 57 | 8 1 0x00 optional trailing pad (non-semantic) |
| 58 | ``` |
| 59 | |
| 60 | As raw bytes (9-byte form): `00 00 00 00 00 06 00 00 00` |
| 61 | |
| 62 | ### Response Packet |
| 63 | |
| 64 | ```text |
| 65 | Offset Length Field |
| 66 | 0 2 header bytes (version + ttl per MAC-Telnet; "unknown" per Wireshark) |
| 67 | 2 2 sequence number (big-endian; per-device counter) |
| 68 | 4+ TLV[] zero or more TLV records (see below) |
| 69 | ``` |
| 70 | |
| 71 | The first 4 bytes are a fixed header; parsers skip the first 2 bytes and read the |
| 72 | sequence number as a big-endian uint16, then iterate TLVs from offset 4. |
| 73 | |
| 74 | ## TLV Format |
| 75 | |
| 76 | Each TLV (Type-Length-Value) record in the response: |
| 77 | |
| 78 | ```text |
| 79 | Offset Length Field |
| 80 | 0 2 type (big-endian uint16) |
| 81 | 2 2 length (big-endian uint16) — byte count of value |
| 82 | 4 N value (raw bytes — interpretation depends on type) |
| 83 | ``` |
| 84 | |
| 85 | **Byte order:** TLV type and length are **big-endian** (network byte order — `ntohs`). |
| 86 | This is confirmed by the canonical reference implementations: MAC-Telnet's `protocol.c` |
| 87 | (`type = ntohs(type); len = ntohs(len);`) and the official Wireshark MNDP dissector |
| 88 | (`packet-mndp.c`, which reads both with `ENC_BIG_ENDIAN`). |
| 89 | |
| 90 | **Value encoding varies by type and is the most common footgun:** string TLVs are UTF-8, |
| 91 | IPv4/IPv6/MAC are raw network-order bytes, but **TLV 10 (uptime) is a little-endian |
| 92 | uint32** — the *only* little-endian value in the entire protocol. Everything else |
| 93 | multi-byte is big-endian/raw. |
| 94 | |
| 95 | ### TLV Type Reference |
| 96 | |
| 97 | | Type | Hex | Name | Value Format | Notes | |
| 98 | |------|--------|----------------|-------------|-------| |
| 99 | | 1 | 0x0001 | MAC Address | 6 raw bytes (network order) | Per-interface MAC, not chassis MAC | |
| 100 | | 5 | 0x0005 | Identity | UTF-8 string | Hostname — same across all interfaces | |
| 101 | | 7 | 0x0007 | Version | UTF-8 string | e.g. `7.18 (stable)`, `7.22rc1` | |
| 102 | | 8 | 0x0008 | Platform | UTF-8 string | Usually `MikroTik` | |
| 103 | | 10 | 0x000a | Uptime | 4 bytes **LE** uint32 | Seconds since boot — the on |