Skip to content

t13801206/esp32-loopchk

Repository files navigation

esp32-loopchk

「未知のポートを繋ぐ前に、ループの有無を確かめたい」
ネットワーク管理者なら誰もが感じる、あの緊張感を解消するためのツール。

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-hlUdp トレイト(udp_bind / udp_send_to / udp_recv_from)による UDP ブロードキャスト
  • APIPA アドレス帯(169.254.0.0/16)の採用による既存 IP 体系への無干渉
  • PHY リンク監視(PHYCFGR レジスタ)によるリンク断検知とループフラグ自動リセット
  • ループ検知のラッチ(フラグ維持)——瞬時のパケットを逃さず、物理リンク断まで警告を継続
  • ソケット健全性チェック(sn_sr)と自動再バインド(udp_bind)による自己復旧

ソケット自動復旧(Rebind)について

実環境検証で、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
  1. Unit A(送信側) が 1 秒ごとに 255.255.255.255:8888 宛てに UDP ブロードキャストを送信します。
    ペイロードはマジックバイト CHECK_LOOP(10 バイト)。

  2. Unit B(受信側) は UDP ポート 8888 で受信待機します。

  3. ループが存在しない正常状態 → Unit A の送信フレームは Unit B に届きません(独立セグメント)。

  4. ループが存在する場合 → ブロードキャストフレームが折り返してきて Unit B が CHECK_LOOP を受信。
    即座に赤 LED 点灯(ループ警告)。ケーブルを抜いてリンクが断するまで警告を維持します。

APIPA(Link-Local)を使う理由

169.254.0.0/16 は IANA が Link-Local 用に予約したアドレス帯です。
IP 設計が不明な現場でも既存ホストとのアドレス競合を起こさず、L2 セグメントさえ繋がっていればブロードキャストが届きます。


ハードウェア構成

ESP32 ピンアサイン

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-halSpiDeviceDriver が内部 Mutex で排他制御するため、マルチスレッド安全です。

SPI 周波数の考え方

本実装では SPI を 20MHzsrc/main.rsSPI_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 赤 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 トレイト、Eui48AddrIpv4Addr、PHY 設定
embuild 0.33 ビルドスクリプト(ESP-IDF 自動連携)

Rust ツールチェーン: esp チャンネル(Xtensa 対応 fork、1.82 系)— rust-toolchain.toml で固定済み。


ビルドと書き込み

cargo run

ターゲット: xtensa-esp32-espidfrust-toolchain.toml および .cargo/config.toml で指定済み)

開発ビルド(dev)と本番ビルド(release)

cargo run はデフォルトで dev プロファイル[profile.dev])を使います。
日常のデバッグは cargo run で問題ありません。

最終評価・本番投入時は、最適化された release プロファイル を使ってください。

# 書き込み + 実行(release)
cargo run --release

# バイナリのみ作成(release)
cargo build --release

このプロジェクトでは Cargo.toml にて releaseopt-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 を参照してください。

About

EPS32とW5500を使ったループ検知のRustプロジェクトです

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors