mcpField
R
routeros-mndp

routeros-mndp

github.com/tikoci開発8346 回インストール5 日前に更新
リポジトリ

routeros-mndp について

routeros-mndpは、MikroTik RouterOSの近隣探索プロトコルMNDPを解説する技術スキルです。エージェントや開発者がLAN探索やデバイス通知の解析を実装し、プロトコルの挙動とセキュリティ上の注意点を把握できます。

主な機能

  1. 01MNDPパケットとTLV形式の解説
  2. 02デバイス識別情報とインターフェース項目
  3. 03IPv4ブロードキャストとIPv6マルチキャスト
  4. 04応答時間と複数インターフェースの挙動
  5. 05ソケットと解析の手引き

活用シーン

  1. 01LAN上のMikroTikデバイス探索を実装
  2. 02RouterOSの近隣通知を解析
  3. 03信頼できないインターフェースでのMNDP公開を確認

MCP 対応

RouterOSドキュメントの検索には、routeros_searchやrouteros_get_pageを提供するrosetta MCPサーバーを案内しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tikoci/routeros-skills --skill routeros-mndp

インストール後、Claude Code で/routeros-mndpを入力して読み込まれたか確認します。

詳細

MNDP — MikroTik Neighbor Discovery Protocol

MNDP は、RouterOS がローカルネットワーク上のデバイスを自動検出するために使用する UDP ブロードキャスト/マルチキャストプロトコルです。WinBox がルーターの検出に使用するものと同じプロトコルです。すべての RouterOS デバイスは、デフォルトでこのプロトコルに参加します。

エージェントにとって重要な理由

  • RouterOS の /ip/neighbor は、MNDP の結果を取得するための CLI/REST インターフェースです
  • WinBox の「Neighbors」タブは、MNDP リスナーです
  • MikroTik 機器のデバイス検出を実装するエージェントには、MNDP が必要です
  • このプロトコルは一から実装できるほどシンプルで、ライブラリは必要ありません

プロトコルの基本

プロパティ値
トランスポートUDP
ポート5678
IPv4255.255.255.255:5678 へのブロードキャスト
IPv6ff02::1(全ノードのリンクローカル)へのマルチキャスト
方向双方向 — 送受信に同じポートを使用
認証なし — 読み取り専用の検出で、認証情報は不要
範囲レイヤー 2 のブロードキャストドメイン(ルーターを越えません)

検出の仕組み

  1. リスナーがリフレッシュパケットを送信します — 255.255.255.255:5678 宛ての小さな UDP データグラムです(MAC-Telnet は最小サイズの 4 バイトのゼロ化ヘッダーを送信します。9 バイト形式も使用できます。以下を参照してください)
  2. LAN 上のすべての RouterOS デバイスが応答します — 各デバイスは、識別情報、バージョン、ボード、MAC、IP、稼働時間などを含む TLV エンコードされたアナウンスを送信します
  3. 応答は非同期に届きます — ネットワークの状態に応じて、デバイスは数ミリ秒から数秒以内に応答します
  4. 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(Type-Length-Value)レコード:

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 タイプのリファレンス
タイプHex名前値の形式備考
10x0001MAC アドレス6 バイトの生データ(ネットワークバイトオーダー)インターフェースごとの MAC アドレスであり、シャーシの MAC アドレスではありません
50x0005IDUTF-8 文字列ホスト名 — すべてのインターフェースで共通です
70x0007バージョンUTF-8 文字列例:7.18 (stable)、7.22rc1
80x0008プラットフォームUTF-8 文字列通常は MikroTik です
100x000a稼働時間4 バイトの LE uint32起動後の秒数 — リトルエンディアンの値はこれだけです
110x000bソフトウェア IDUTF-8 文字列ライセンス識別子
120x000cボードUTF-8 文字列例:RB4011iGS+5HacQ2HnD、CHR
140x000e展開1 バイトファームウェアの圧縮フラグ(IPv6 の有無を示すフラグとして扱うパーサーもあります)
150x000fIPv6 アドレス16 バイトの生データリンクローカルまたはグローバル IPv6
160x0010インターフェース名UTF-8 文字列ルーター上の送信インターフェース(例:ether1)
170x0011IPv4 アドレス4 バイトの生データ(ネットワークバイトオーダー)送信インターフェースの IP

信頼できる情報源: これらのタイプ ID は、正規の MAC-Telnet protocol.c (MT_MNDPTYPE_* enum)および Wireshark の MNDP ディセクターの値テーブルと完全に一致します。特に、稼働時間はタイプ 10、ソフトウェア ID はタイプ 11、ボードはタイプ 12 です。 これらを 1 つずつずらしてしまう(稼働時間=11/ボード=13)のはよくある間違いで、実際の RouterOS パケットを気付かないまま誤解析します。

パッキング TLV(タイプ 2、3、9): /ip のパッキング/圧縮に関連します。安全にスキップできます。

複数インターフェースの動作

N 個のアクティブなインターフェースを持つ RouterOS デバイスは、インターフェースごとに 1 つ、合計 N 個の個別の MNDP アナウンスを送信します。各アナウンスには次の情報が含まれます。

  • 異なる MAC アドレス(そのインターフェース固有の MAC アドレス)
  • 異なるインターフェース名(TLV 16)
  • 異なる IP アドレス(割り当てられている場合)
  • 同じ ID(ホスト名)

これは想定された動作であり、バグではありません。結果を表示する際は、同じルーターが N 回表示されないよう、ID でグループ化してください。ID が工場出荷時のデフォルト(MikroTik)の場合は、MAC アドレスを使って区別してください。

タイミングと信頼性

シナリオ想定される応答時間
ローカル LAN(有線)1~3 秒
Wi-Fi /混雑したネットワーク3~10 秒
ZeroTier /トンネルオーバーレイ5~20 秒
衛星通信/高遅延10~30 秒

ベストプラクティス: リッスン時間中に、5 秒ごとに複数回リフレッシュパケットを送信してください(WinBox の動作に合わせます)。パケット損失、Wi-Fi の省電力機能、またはトンネルリレーの遅延により最初のブロードキャストを受信できなかったデバイスも、その後のリフレッシュに応答します。

デバイスが見つからなくても、オフラインと判断してはいけません。 短いスキャン時間では結果が不完全になります。デバイスに到達できないと結論付ける前に、タイムアウトを延長してください。

RouterOS /ip/neighbor

/ip/neighbor は、MNDP(および CDP/LLDP)の検出結果を RouterOS 側から確認するビューです。ルーターに直接接続されたネットワーク上の他のデバイスから、ルーターが受信した情報が表示されます。

# 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 からは、ID(ホスト名)、バージョン、ボードモデル、IP アドレス、MAC アドレス、稼働時間が分かります
  • この情報は攻撃者にとって有用です — MNDP は公開された SNMP コミュニティと同様に扱ってください

ソケット実装に関する注意事項

ポート共有(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 にブロードキャストを送信すると、OS はそのコピーを自身のソケットに返します。自身の 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;
}

リファレンス実装

言語ソース備考
Cgithub.com/haakonnessjoen/MAC-Telnet(protocol.c、protocol.h)正準となる基準実装です。 TLV の列挙型、ntohs によるビッグエンディアン TLV、le32toh による uptime
C(ディセクター)Wireshark epan/dissectors/packet-mndp.c公式プロトコルディセクターです。タイプ ID と ENC_BIG_ENDIAN TLV を確認できます
Gogithub.com/middelink/mikrotik-fwupdateプロトコルの照合に使用した補助実装
Elixirhex.pm mndp packageTLV のタイプ ID を確認できます
Swiftオープンソースの macOS 実装生バイトによる MAC エンコードを確認できます

検証済み: このスキルの TLV タイプ ID とバイトオーダーは、MAC-Telnet の protocol.c および Wireshark のディセクターと照合済みです。両方とも、TLV のタイプと長さはビッグエンディアンであり、uptime(タイプ 10)は唯一のリトルエンディアン値であることを確認しています。

関連スキル

  • RouterOS の CLI/REST の基本については、routeros-fundamentals スキルを参照してください
  • /console/inspect コマンドツリーについては、routeros-command-tree スキルを参照してください
  • パケットキャプチャと TZSP ストリーミングについては、routeros-sniffer スキルを参照してください
  • QEMU CHR のセットアップ(ハードウェアを使わないテスト)については、routeros-qemu-chr スキルを参照してください

関連 MCP ツール

  • RouterOS ドキュメントの検索には、rosetta MCP サーバーのツール(routeros_search、routeros_get_page)を使用してください

コメント