Marketplace routeros-mndp

MNDP (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 neighbor lists, or when the user mentions MNDP, neighbor discovery, WinBox discovery, or /ip/neighbor.

install
source · Clone the upstream repo
git clone https://github.com/aiskillstore/marketplace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/tikoci/routeros-mndp" ~/.claude/skills/aiskillstore-marketplace-routeros-mndp && rm -rf "$T"
manifest: skills/tikoci/routeros-mndp/SKILL.md
source content

MNDP — MikroTik Neighbor Discovery Protocol

MNDP is the UDP broadcast/multicast protocol that RouterOS uses for automatic device discovery on the local network. It is the same protocol WinBox uses to find routers. Every RouterOS device participates by default.

Why This Matters for Agents

  • /ip/neighbor
    on RouterOS is the CLI/REST surface for MNDP results
  • WinBox's "Neighbors" tab is an MNDP listener
  • Any agent implementing device discovery for MikroTik equipment needs MNDP
  • The protocol is simple enough to implement from scratch — no library needed

Protocol Basics

PropertyValue
TransportUDP
Port5678
IPv4Broadcast to 255.255.255.255:5678
IPv6Multicast to ff02::1 (all-nodes link-local)
DirectionBidirectional — same port for send and receive
AuthenticationNone — read-only discovery, no credentials
ScopeLayer 2 broadcast domain (does not cross routers)

How Discovery Works

  1. Listener sends a refresh packet — a 9-byte UDP datagram to 255.255.255.255:5678
  2. All RouterOS devices on the LAN reply — each sends a TLV-encoded announcement with identity, version, board, MAC, IP, uptime, etc.
  3. Replies arrive asynchronously — devices respond within milliseconds to seconds depending on network conditions
  4. RouterOS devices also announce periodically (~60s cycle) without being prompted — passive listening works but is slow to populate

Refresh Packet (Discovery Request)

A 9-byte packet that triggers immediate replies from all RouterOS devices on the broadcast domain:

Offset  Length  Value       Field
0       2       0x0000      type (MNDP)
2       2       0x0000      sequence number (0 for request)
4       2       0x0000      TLV type (none)
6       2       0x0000      TLV length (none)
8       1       0x00        padding

As raw bytes:

00 00 00 00 00 00 00 00 00

Response Packet

Offset  Length  Field
0       2       type = 0x0000 (MNDP)
2       2       sequence number (monotonically increasing per device)
4+      TLV[]   zero or more TLV records (see below)

TLV Format

Each TLV (Type-Length-Value) record in the response:

Offset  Length  Field
0       2       type   (little-endian uint16)
2       2       length (little-endian uint16) — byte count of value
4       N       value  (raw bytes — interpretation depends on type)

Byte order: TLV type and length are little-endian. Value encoding varies by type.

TLV Type Reference

TypeHexNameValue FormatNotes
10x0001MAC Address6 bytes, big-endianPer-interface MAC, not chassis MAC
50x0005IdentityUTF-8 stringHostname — same across all interfaces
70x0007VersionUTF-8 stringe.g.
7.18 (stable)
,
7.22rc1
80x0008PlatformUTF-8 stringUsually
MikroTik
100x000aBoardUTF-8 stringe.g.
RB4011iGS+5HacQ2HnD
,
CHR
110x000bUptime4 bytes LE uint32Seconds since boot
120x000cSoftware IDUTF-8 stringLicense identifier
130x000dBoard (alt)UTF-8 stringSome firmware uses type 13 instead of 10
140x000eUnpack1 byteFirmware compression flag
150x000fIPv6 Address16 bytesLink-local or global IPv6
160x0010Interface NameUTF-8 stringSending interface on the router (e.g.
ether1
)
170x0011IPv4 Address4 bytes, big-endianIP of the sending interface

Board name: Some firmware versions use TLV type 10, others use type 13. Parsers should handle both — prefer type 10 if both are present.

Multi-Interface Behavior

A RouterOS device with N active interfaces sends N separate MNDP announcements — one per interface. Each announcement has:

  • A different MAC address (the interface's own MAC)
  • A different interface name (TLV 16)
  • A different IP address (if assigned)
  • The same identity (hostname)

This is expected behavior, not a bug. When displaying results, group by identity to avoid showing the same router N times. Use MAC address to disambiguate when the identity is the factory default (

MikroTik
).

Timing and Reliability

ScenarioExpected Response Time
Local LAN (wired)1-3 seconds
WiFi / congested network3-10 seconds
ZeroTier / tunnel overlay5-20 seconds
Satellite / high-latency10-30 seconds

Best practice: Send multiple refresh packets during the listen window (every 5 seconds, matching WinBox behavior). Devices that miss the first broadcast due to packet loss, WiFi power-save, or tunnel relay latency will respond to subsequent refreshes.

Never interpret missing devices as offline. A short scan window produces partial results. Increase the timeout before concluding a device is unreachable.

RouterOS /ip/neighbor

/ip/neighbor
is the RouterOS-side view of MNDP (and CDP/LLDP) discovery results. It shows what the router has heard from other devices on its directly-connected networks.

# Print discovered neighbors
/ip/neighbor/print

# Columns: interface, address, mac-address, identity, platform, version, board

/ip/neighbor/discovery-settings

Controls which interfaces participate in neighbor discovery:

# Show current discovery settings
/ip/neighbor/discovery-settings/print

# Disable MNDP on a specific interface (security hardening)
/interface/list/member/add list=no-mndp interface=ether1
/ip/neighbor/discovery-settings/set discover-interface-list=!no-mndp

# Supported protocols (can be combined)
# cdp — Cisco Discovery Protocol
# lldp — Link Layer Discovery Protocol
# mndp — MikroTik Neighbor Discovery Protocol
/ip/neighbor/discovery-settings/set protocol=mndp,lldp

REST API Access

# List neighbors via REST
curl -u admin: http://<router-ip>/rest/ip/neighbor

# Response is JSON array with the same fields as CLI print

Security Considerations

  • MNDP has no authentication — any device on the broadcast domain can discover routers
  • Disable MNDP on untrusted interfaces (public-facing, guest networks)
  • RouterOS defaults to discovery on all interfaces — review and restrict
  • MNDP reveals: identity (hostname), version, board model, IPs, MACs, uptime
  • This information is useful for attackers — treat MNDP like an open SNMP community

Socket Implementation Notes

Port Sharing (SO_REUSEPORT)

MNDP uses the same port (5678) for both sending and receiving. If another process (e.g., WinBox) already has UDP/5678 bound, your listener needs

SO_REUSEPORT
to coexist:

// Node.js / Bun dgram
import { createSocket } from "node:dgram";
const sock = createSocket({ type: "udp4", reuseAddr: true, reusePort: true });
sock.bind(5678, "0.0.0.0", () => {
  sock.setBroadcast(true);
});
// C / POSIX
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

Platform note:

SO_REUSEPORT
works on macOS and Linux. On Windows,
SO_REUSEADDR
alone may be sufficient. In Bun,
reusePort: true
requires Bun >= 1.3.11 (earlier versions silently ignored it on macOS).

Self-Echo Filtering

When you send a broadcast to 255.255.255.255:5678, the OS delivers a copy back to your own socket. Filter out packets from your own IP addresses to avoid processing your own refresh as a neighbor response. The looped-back packet will have no MNDP TLVs (it's your 9-byte refresh, not a device announcement).

IPv6 Multicast

IPv6 MNDP uses

ff02::1
(all-nodes multicast). Less commonly used than IPv4 broadcast but supported:

sock.addMembership("ff02::1");

IPv6 link-local addresses include a zone ID (e.g.,

fe80::1%en0
) that identifies the receiving interface — useful topology information not available in IPv4.

Parsing Example (TypeScript)

function parseMndpResponse(buf: Buffer): Record<string, string | number> {
  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
  let offset = 4; // skip 2-byte type + 2-byte sequence
  const fields: Record<string, string | number> = {};

  while (offset + 4 <= buf.length) {
    const tlvType = view.getUint16(offset, true);     // little-endian
    const tlvLen  = view.getUint16(offset + 2, true);  // little-endian
    offset += 4;

    if (offset + tlvLen > buf.length) break; // malformed
    const value = buf.subarray(offset, offset + tlvLen);

    switch (tlvType) {
      case 1:  // MAC Address — 6 bytes big-endian
        fields.macAddress = [...value].map(b => b.toString(16).padStart(2, "0")).join(":");
        break;
      case 5:  fields.identity = value.toString("utf8"); break;
      case 7:  fields.version = value.toString("utf8"); break;
      case 8:  fields.platform = value.toString("utf8"); break;
      case 10: // Board (primary)
      case 13: // Board (alternate — some firmware)
        if (!fields.board) fields.board = value.toString("utf8");
        break;
      case 11: // Uptime — 4 bytes LE uint32 (seconds)
        fields.uptime = view.getUint32(offset - tlvLen, true);
        break;
      case 12: fields.softwareId = value.toString("utf8"); break;
      case 15: // IPv6 — 16 bytes
        fields.ipv6 = formatIpv6(value);
        break;
      case 16: fields.interfaceName = value.toString("utf8"); break;
      case 17: // IPv4 — 4 bytes big-endian
        fields.ipv4 = `${value[0]}.${value[1]}.${value[2]}.${value[3]}`;
        break;
    }
    offset += tlvLen;
  }
  return fields;
}

Reference Implementations

LanguageSourceNotes
Gogithub.com/middelink/mikrotik-fwupdateUsed as protocol ground truth during original research
Elixirhex.pm mndp packageConfirms TLV type IDs
CVarious open-source MNDP clientsDirect setsockopt for REUSEPORT
SwiftOpen-source macOS implementationsConfirms big-endian MAC encoding

Related Skills

  • For RouterOS CLI/REST basics: see
    routeros-fundamentals
    skill
  • For the
    /console/inspect
    command tree: see
    routeros-command-tree
    skill
  • For packet capture and TZSP streaming: see
    routeros-sniffer
    skill
  • For QEMU CHR setup (testing without hardware): see
    routeros-qemu-chr
    skill

Related MCP Tools

  • For RouterOS docs lookup: use the
    rosetta
    MCP server tools (
    routeros_search
    ,
    routeros_get_page
    )