关于 routeros-mndp
routeros-mndp 是介绍 MikroTik RouterOS 邻居发现协议 MNDP 的技术技能,帮助 Agent 和开发者实现局域网发现、解析设备通告,并了解协议行为与安全注意事项。
主要功能
- 01说明 MNDP 数据包与 TLV 格式
- 02列出设备身份及接口字段
- 03介绍 IPv4 广播与 IPv6 多播
- 04记录响应时间和多接口行为
- 05提供套接字与解析指南
适用场景
- 01实现局域网内的 MikroTik 设备发现
- 02解析 RouterOS 邻居通告
- 03检查不可信接口上的 MNDP 暴露
MCP 相关
该技能推荐使用 rosetta MCP 服务查询 RouterOS 文档,包括 routeros_search 和 routeros_get_page 工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tikoci/ routeros-skills --skill routeros-mndp安装后在 Claude Code 中输入/routeros-mndp确认已加载。
详细介绍
MNDP — MikroTik 邻居发现协议
MNDP 是 RouterOS 用于在本地网络中自动发现设备的 UDP 广播/组播协议。WinBox 使用的也是同一种协议来查找路由器。 每台 RouterOS 设备默认都会参与发现。
为什么这对 Agent 很重要
- RouterOS 上的
/ip/neighbor是查看 MNDP 结果的 CLI/REST 接口 - WinBox 的“Neighbors”选项卡是 MNDP 监听器
- 任何为 MikroTik 设备实现发现功能的 Agent 都需要支持 MNDP
- 该协议足够简单,可以从头实现,无需使用库
协议基础
| 属性 | 值 |
|---|---|
| 传输方式 | UDP |
| 端口 | 5678 |
| IPv4 | 向 255.255.255.255:5678 广播 |
| IPv6 | 向 ff02::1(所有节点链路本地地址)组播 |
| 方向 | 双向——发送和接收使用同一端口 |
| 身份验证 | 无——只读发现,无需凭据 |
| 范围 | 二层广播域(不会穿过路由器) |
发现工作原理
- 监听器发送刷新数据包——向 255.255.255.255:5678 发送一个简短的 UDP 数据报(MAC-Telnet 会发送最简的 4 字节全零报头;9 字节格式也有效——见下文)
- 局域网内所有 RouterOS 设备都会回复——每台设备都会发送一个以 TLV 编码的通告,其中包含身份、版本、板卡、MAC、IP、运行时间等信息
- 回复以异步方式到达——设备会在数毫秒到数秒内响应,具体取决于网络状况
- RouterOS 设备也会定期通告(约每 60 秒一次),无需触发——被动监听也可行,但发现信息填充较慢
刷新数据包(发现请求)
这个简短的数据包会触发广播域内所有 RouterOS 设备立即回复。最简格式仅包含一个全零的 4 字节报头——这正是 MAC-Telnet 发送的内容(unsigned int message = 0;):
Offset Length Value Field
0 1 0x00 header byte 0 (version per MAC-Telnet; "unknown" per Wireshark)
1 1 0x00 header byte 1 (ttl per MAC-Telnet)
2 2 0x0000 seqno / checksum
原始字节(最简格式):00 00 00 00
某些实现会附加一个明确的刷新 TLV(类型为 6,长度为 0)。完整的报头加空 TLV 格式为 8 字节(00 00 00 00 00 06 00 00);有些实现还会添加一个尾随零字节(无语义的填充),使其长度达到 9 字节。RouterOS 接受所有格式:
Offset Length Value Field
0 4 00 00 00 00 header (2 header bytes + 2-byte seqno)
4 2 0x0006 TLV type = 6 (refresh) ← big-endian
6 2 0x0000 TLV length = 0
8 1 0x00 optional trailing pad (non-semantic)
原始字节(9 字节格式):00 00 00 00 00 06 00 00 00
响应数据包
Offset Length Field
0 2 header bytes (version + ttl per MAC-Telnet; "unknown" per Wireshark)
2 2 sequence number (big-endian; per-device counter)
4+ TLV[] zero or more TLV records (see below)
前 4 个字节是固定报头;解析器会跳过前 2 个字节,将序列号读取为大端 uint16,然后从偏移量 4 开始遍历 TLV。
TLV 格式
响应中的每条 TLV(类型-长度-值)记录:
Offset Length Field
0 2 type (big-endian uint16)
2 2 length (big-endian uint16) — byte count of value
4 N value (raw bytes — interpretation depends on type)
字节序:TLV 类型和长度采用大端序(网络字节序——ntohs)。
这已由权威参考实现证实:MAC-Telnet 的 protocol.c
(type = ntohs(type); len = ntohs(len);)以及官方 Wireshark MNDP 解析器
(packet-mndp.c,其中两者均使用 ENC_BIG_ENDIAN 读取)。
**值的编码因类型而异,这是最常见的易错点:**字符串 TLV 使用 UTF-8, IPv4/IPv6/MAC 使用原始网络序字节,但 TLV 10(运行时间)采用小端 uint32——这是整个协议中唯一采用小端序的值。其他所有多字节值均采用大端序/原始格式。
TLV 类型参考
| Type | Hex | Name | Value Format | Notes |
|---|---|---|---|---|
| 1 | 0x0001 | MAC 地址 | 6 个原始字节(网络字节序) | 每个接口的 MAC,而非机箱 MAC |
| 5 | 0x0005 | 标识 | UTF-8 字符串 | 主机名——所有接口相同 |
| 7 | 0x0007 | 版本 | UTF-8 字符串 | 例如 7.18 (stable)、7.22rc1 |
| 8 | 0x0008 | 平台 | UTF-8 字符串 | 通常为 MikroTik |
| 10 | 0x000a | 运行时间 | 4 字节 LE uint32 | 自启动以来的秒数——唯一的小端值 |
| 11 | 0x000b | 软件 ID | UTF-8 字符串 | 许可证标识符 |
| 12 | 0x000c | 主板 | UTF-8 字符串 | 例如 RB4011iGS+5HacQ2HnD、CHR |
| 14 | 0x000e | 解包 | 1 字节 | 固件压缩标志(某些解析器将其视为 IPv6 存在标志) |
| 15 | 0x000f | IPv6 地址 | 16 个原始字节 | 链路本地或全局 IPv6 |
| 16 | 0x0010 | 接口名称 | UTF-8 字符串 | 路由器上发送数据的接口(例如 ether1) |
| 17 | 0x0011 | IPv4 地址 | 4 个原始字节(网络字节序) | 发送接口的 IP |
**权威来源:**这些类型 ID 与规范 MAC-Telnet protocol.c
(MT_MNDPTYPE_* 枚举)和 Wireshark MNDP 解剖器值表完全一致。请特别注意,运行时间的类型为 10,软件 ID 的类型为 11,主板的类型为 12——常见错误是将它们的编号各加 1(运行时间=11/主板=13),这会导致真实 RouterOS 数据包被静默地错误解析。
**打包 TLV(类型 2、3、9):**与 /ip 打包/压缩相关,可以安全跳过。
多接口行为
具有 N 个活动接口的 RouterOS 设备会发送 N 条独立的 MNDP 通告——每个接口一条。每条通告都包含:
- 不同的 MAC 地址(该接口自身的 MAC)
- 不同的接口名称(TLV 16)
- 不同的 IP 地址(如果已分配)
- 相同的标识(主机名)
这是预期行为,并非错误。显示结果时,请按标识分组,避免同一台路由器重复显示 N 次。如果标识仍为出厂默认值(MikroTik),请使用 MAC 地址来区分设备。
时序和可靠性
| 场景 | 预期响应时间 |
|---|---|
| 本地 LAN(有线) | 1-3 秒 |
| WiFi/网络拥塞 | 3-10 秒 |
| ZeroTier/隧道覆盖网络 | 5-20 秒 |
| 卫星/高延迟网络 | 10-30 秒 |
**最佳实践:**在监听窗口期间多次发送刷新数据包(每隔 5 秒一次,与 WinBox 行为一致)。因丢包、WiFi 省电模式或隧道中继延迟而错过首次广播的设备,会响应后续刷新。
**切勿将未发现设备解读为离线。**较短的扫描窗口只能获得部分结果。在得出设备无法访问的结论之前,请延长超时时间。
RouterOS /ip/neighbor
/ip/neighbor 是 RouterOS 侧的 MNDP(以及 CDP/LLDP)发现结果视图。它显示路由器从直接连接网络上的其他设备收到的信息。
# Print discovered neighbors
/ip/neighbor/print
# Columns: interface, address, mac-address, identity, platform, version, board
/ip/neighbor/discovery-settings
控制哪些接口参与邻居发现:
# 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 访问
# List neighbors via REST
curl -u admin: http://<router-ip>/rest/ip/neighbor
# Response is JSON array with the same fields as CLI print
安全注意事项
- MNDP 没有身份验证——广播域中的任何设备都可以发现路由器
- 在不可信接口(面向公网的接口、访客网络)上禁用 MNDP
- RouterOS 默认在所有接口上启用发现——请检查并限制
- MNDP 会泄露:标识(主机名)、版本、主板型号、IP、MAC、运行时间
- 这些信息对攻击者有用——请像对待开放的 SNMP community 一样对待 MNDP
套接字实现说明
端口共享(SO_REUSEPORT)
MNDP 发送和接收使用相同的端口(5678)。如果另一个进程(例如 WinBox)已绑定 UDP/5678,你的监听器需要使用 SO_REUSEPORT 才能共存:
// 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));
平台说明:SO_REUSEPORT 在 macOS 和 Linux 上可用。在 Windows 上,单独使用 SO_REUSEADDR 可能就足够了。在 Bun 中,reusePort: true 要求 Bun >= 1.3.11(较早版本会在 macOS 上静默忽略此设置)。
自回显过滤
当你向 255.255.255.255:5678 发送广播时,操作系统会将副本发送回你自己的套接字。过滤掉来自你自己 IP 地址的数据包,以免将自己的刷新请求误认为邻居响应。回环的数据包不包含 MNDP TLV(它是你的 9 字节刷新请求,而不是设备通告)。
IPv6 多播
IPv6 MNDP 使用 ff02::1(所有节点多播)。这种方式不如 IPv4 广播常用,但也受支持:
sock.addMembership("ff02::1");
IPv6 链路本地地址包含一个区域 ID(例如 fe80::1%en0),用于标识接收接口——这是 IPv4 不具备的实用拓扑信息。
解析示例(TypeScript)
function parseMndpResponse(buf: Buffer): Record<string, string | number> {
let offset = 4; // skip 2-byte header (version, ttl) + 2-byte sequence
const fields: Record<string, string | number> = {};
while (offset + 4 <= buf.length) {
const tlvType = buf.readUInt16BE(offset); // big-endian
const tlvLen = buf.readUInt16BE(offset + 2); // big-endian
offset += 4;
if (offset + tlvLen > buf.length) break; // malformed / truncated
const value = buf.subarray(offset, offset + tlvLen);
switch (tlvType) {
case 1: // MAC Address — 6 raw bytes
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: // Uptime — 4 bytes LITTLE-ENDIAN uint32 (the only LE value)
if (tlvLen === 4) fields.uptime = value.readUInt32LE(0);
break;
case 11: fields.softwareId = value.toString("utf8"); break;
case 12: fields.board = value.toString("utf8"); break;
case 15: // IPv6 — 16 bytes
if (tlvLen === 16) fields.ipv6 = formatIpv6(value);
break;
case 16: fields.interfaceName = value.toString("utf8"); break;
case 17: // IPv4 — 4 raw bytes
if (tlvLen === 4) fields.ipv4 = `${value[0]}.${value[1]}.${value[2]}.${value[3]}`;
break;
}
offset += tlvLen;
}
return fields;
}
参考实现
| 语言 | 源码 | 说明 |
|---|---|---|
| C | github.com/haakonnessjoen/MAC-Telnet(protocol.c、protocol.h) | 权威基准。 TLV 枚举 + ntohs 大端序 TLV + le32toh 运行时间 |
| C(解析器) | Wireshark epan/dissectors/packet-mndp.c | 官方协议解析器——确认类型 ID 和 ENC_BIG_ENDIAN TLV |
| Go | github.com/middelink/mikrotik-fwupdate | 辅助协议交叉核验 |
| Elixir | hex.pm mndp package | 确认 TLV 类型 ID |
| Swift | 开源 macOS 实现 | 确认 MAC 地址采用原始字节编码 |
**已验证:**此技能中的 TLV 类型 ID 和字节序已与 MAC-Telnet 的 protocol.c 和 Wireshark 解析器交叉核验——两者均确认 TLV 类型和长度采用大端序,而运行时间(类型 10)是唯一的小端序值。
相关技能
- RouterOS CLI/REST 基础知识:参见
routeros-fundamentals技能 /console/inspect命令树:参见routeros-command-tree技能- 抓包和 TZSP 流式传输:参见
routeros-sniffer技能 - QEMU CHR 设置(无需硬件即可测试):参见
routeros-qemu-chr技能
相关 MCP 工具
- 查询 RouterOS 文档:使用
rosettaMCP 服务工具(routeros_search、routeros_get_page)