「未知のポートを繋ぐ前に、ループの有無を確かめたい」
ネットワーク管理者なら誰もが感じる、あの緊張感を解消するためのツール。
ESP32 + W5500 ×2 で構成される ループ未然検知セグメントチェッカー の Rust 実装です。
対象セグメントに持ち込んでポートに差すだけで、ループが発生するかどうかを工事前に 100% 判定します。
L2 スイッチのループは発生した瞬間からブロードキャストストームを引き起こし、数秒でネットワーク全体を麻痺させます。
STP(スパニングツリー)が無効な環境、古い HUB が混在する現場、誤配線が起きやすい工事中の環境では特に致命的です。
本デバイスのゴールは「嵐が起きてから対処する」ではなく、「嵐を起こす前に止める」ことです。
| フェーズ | 内容 |
|---|---|
| Phase 1 | Arduino + Ethernet.h による ARP チェッカー(L3 層の導通確認) |
| Phase 2 | Rust + smoltcp + MACRAW による W5500 制御の PoC |
| Phase 3 | 本実装: w5500-hl の高レベル UDP API を採用し、MACRAW の複雑さを排除 |
ARP チェッカーで「持ち込み検査」の有効性を確認した後、DHCP も IP 設計も不明なカオスな現場でも使えるよう、L2 的アプローチへ刷新しました。
w5500-hlのUdpトレイト(udp_bind/udp_send_to/udp_recv_from)による UDP ブロードキャスト- APIPA アドレス帯(169.254.0.0/16)の採用による既存 IP 体系への無干渉
- PHY リンク監視(
PHYCFGRレジスタ)によるリンク断検知とループフラグ自動リセット - ループ検知のラッチ(フラグ維持)——瞬時のパケットを逃さず、物理リンク断まで警告を継続
- ソケット健全性チェック(
sn_sr)と自動再バインド(udp_bind)による自己復旧
実環境検証で、L2 スイッチ側の遮断やリンク揺れを契機に、W5500 の UDP ソケット状態が Closed へ遷移するケースを確認しました。
本実装では、送信・受信の直前にソケット状態を確認し、Udp 以外の場合はその場で udp_bind を再実行します。
sn_sr == Udp: そのまま送受信sn_sr != Udp: 即時再バインドを試行- 再バインド成功時:
INFO - 再バインド失敗時:
WARN
この自己復旧により、リンク復帰後に受信が止まり続ける状態からの自動回復を狙います。
| 手法 | 採用しなかった理由 |
|---|---|
| MACRAW + smoltcp | Phase 2 で試験済み。「ストームが起きた後でも動く堅牢性」は本ツールの要件外。未然検知に特化するため、w5500-hl の高レベル UDP API で十分と判断 |
| DHCP の利用 | 未知のセグメントで既存 IP 体系を汚染・競合させるリスクを排除するため APIPA を採用 |
| STP 検出・制御 | 本ツールはループ存在の有無を判定するだけの安全装置であり、ネットワーク制御は行わない設計とした |
C 言語(Arduino Ethernet.h) |
Ethernet.h はグローバルな状態管理で 2 枚の W5500 を安全に制御できない。Rust の型安全な SPI 共有(SpiDeviceDriver + 内部 Mutex)により、誤動作のない堅牢なシステムを実現した |
⚠️ 使用上の注意: 本ツールはループ「未然」検知用です。すでにブロードキャストストームが発生している環境では、受信処理が影響を受ける場合があります。
┌──────────────────────────────┐
│ スイッチ / HUB │
└─────────┬──────────┬─────────┘
│ │
[Unit A] [Unit B]
169.254.1.1 169.254.1.2
port 8887 port 8888
│ │
│ UDP Bcast │
└─────────→┘
"CHECK_LOOP"
dst: 255.255.255.255:8888
-
Unit A(送信側) が 1 秒ごとに
255.255.255.255:8888宛てに UDP ブロードキャストを送信します。
ペイロードはマジックバイトCHECK_LOOP(10 バイト)。 -
Unit B(受信側) は UDP ポート 8888 で受信待機します。
-
ループが存在しない正常状態 → Unit A の送信フレームは Unit B に届きません(独立セグメント)。
-
ループが存在する場合 → ブロードキャストフレームが折り返してきて Unit B が
CHECK_LOOPを受信。
即座に赤 LED 点灯(ループ警告)。ケーブルを抜いてリンクが断するまで警告を維持します。
169.254.0.0/16 は IANA が Link-Local 用に予約したアドレス帯です。
IP 設計が不明な現場でも既存ホストとのアドレス競合を起こさず、L2 セグメントさえ繋がっていればブロードキャストが届きます。
| ESP32 ピン | 信号 | 役割 |
|---|---|---|
| GPIO17 | RST | W5500 共通ハードウェアリセット(Low 有効) |
| GPIO18 | SCLK | SPI クロック(2 枚共有) |
| GPIO23 | MOSI | SPI データ出力(2 枚共有) |
| GPIO19 | MISO | SPI データ入力(2 枚共有) |
| GPIO5 | CS-A | Chip Select — Unit A(W5500 #1) |
| GPIO21 | CS-B | Chip Select — Unit B(W5500 #2) |
| GPIO2 | LED 緑 | 正常状態表示 |
| GPIO4 | LED 赤 | ループ検知警告 |
SPI バスは 2 つの W5500 で共有します。esp-idf-hal の SpiDeviceDriver が内部 Mutex で排他制御するため、マルチスレッド安全です。
本実装では SPI を 20MHz(src/main.rs の SPI_BAUDRATE_HZ)に設定しています。
初期の 4MHz 設定は動作確認には十分ですが、ESP32 + W5500 構成としては低めで、送受信レイテンシと CPU 待ち時間の観点で余裕を残します。
- 推奨初期値: 20MHz
- 配線が長い・ノイズが強い環境: 12MHz まで下げて安定性を優先
- 短配線で安定している環境: 26MHz 以上も検証可能(エラーログ増加時は戻す)
実運用では、send/recv エラー率とリンク安定性を観測しながら段階的に最適化するのが安全です。
| Unit A | Unit B | |
|---|---|---|
| IP アドレス | 169.254.1.1 | 169.254.1.2 |
| サブネット | 255.255.0.0 | 255.255.0.0 |
| MAC | 00:08:DC:01:00:01 | 00:08:DC:01:00:02 |
| UDP ソケット | 送信専用(port 8887) | 受信専用(port 8888) |
| ブロードキャスト送信先 | 255.255.255.255:8888 | — |
| 状態 | 緑 LED | 赤 LED |
|---|---|---|
| リンクアップ・ループなし(正常) | 点灯 | 消灯 |
| ループ検知 | 消灯 | 点灯(ラッチ維持) |
| リンクアップ待ち | 点滅(両方交互 500ms) | 点滅(両方交互 500ms) |
| クレート | バージョン | 用途 |
|---|---|---|
log |
0.4 | Rust 標準ログファサード。log::info! / warn! / debug! マクロを提供 |
esp-idf-hal |
0.46 | SPI・GPIO ドライバ(ESP-IDF HAL)。SpiDeviceDriver による型安全なバス共有 |
esp-idf-svc |
0.52.1 | ESP-IDF サービス統合・ロガー |
w5500-hl |
0.12 (feature: eh1) |
W5500 高レベルドライバ。Udp トレイトで bind/send_to/recv_from を提供 |
w5500-ll |
0.13(w5500-hl 依存) | W5500 レジスタ操作。Registers トレイト、Eui48Addr、Ipv4Addr、PHY 設定 |
embuild |
0.33 | ビルドスクリプト(ESP-IDF 自動連携) |
Rust ツールチェーン: esp チャンネル(Xtensa 対応 fork、1.82 系)— rust-toolchain.toml で固定済み。
cargo runターゲット: xtensa-esp32-espidf(rust-toolchain.toml および .cargo/config.toml で指定済み)
cargo run はデフォルトで dev プロファイル([profile.dev])を使います。
日常のデバッグは cargo run で問題ありません。
最終評価・本番投入時は、最適化された release プロファイル を使ってください。
# 書き込み + 実行(release)
cargo run --release
# バイナリのみ作成(release)
cargo build --releaseこのプロジェクトでは Cargo.toml にて release を opt-level = "s"(サイズ最適化)で定義しています。
性能優先で再調整する場合は opt-level = 2 または 3 を検討してください。
I (312) esp32_loopchk: === ループ未然検知セグメントチェッカー 起動 ===
I (318) esp32_loopchk: W5500 A OK IP=169.254.1.1 ver=0x04
I (324) esp32_loopchk: W5500 B OK IP=169.254.1.2 ver=0x04
I (330) esp32_loopchk: Unit A bind port=8887, Unit B bind port=8888
W (1412) esp32_loopchk: ⚠ ループ検知! 送信元: 169.254.1.1:8887
INFO: 起動完了、W5500 認識成功、リンクアップ、ループ検知、再バインド成功などの状態変化WARN: リンクダウン、送受信エラー、再バインド失敗など注意すべき異常DEBUG: 定常監視中の送信成功や非MAGIC受信など、通常運用では不要な詳細
LICENSE を参照してください。