0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Arduino] LANセグメントチェッカー

0
Last updated at Posted at 2026-03-10

はじめに

ArduinoにLANケーブルをつないで、ネットワークセグメントを調査するツールをつくる

Arduinoにはイーサネットシールドという後付け追加基盤がある
RJ45ポートが付くことで、LANケーブルを使ったネットワーク開発ができるようになる代物だ。

Arduinoとシールドを合わせたらラズパイの中古価格を超えるので
ラズパイでいいじゃん?という話もあるかもしれない
ただ、ラズパイはOSがあることが逆に邪魔だったりするので
電源を入れてさくっと動かすような単一目的のツールなら、Arduinoのほうがいい場面もあると思う。

Geminiと会話しながら開発したので、所々でGemini口調になります

この記事では、大きく「ハードウェア構築」と「Arduinoで動くプログラム開発」の2セクションに分けて進める。

作るものの概要

  • ArduinoにLANケーブルを指す
  • そのLANが何のセグメントかわかる
    • なぜわかるか?LEDが光るから

image.png

ハードウェアの話

まずは ArduinoとそれにまつわるLEDなどの結線

部品リスト

だいたいのリスト
ArduinoとPCはUSB A-Bケーブルでつなぐ

品目 備考
Arduino UNO 5,000円くらい
Arduino ETHERNET shield2 5,000円くらい
発光ダイオード セグメントを色ごとに識別するために何色か用意
シフトレジスタ 74HC595,3個つかう
抵抗 1000オーム
ユニバーサル基盤 なんでもいいが取付穴の寸法注意
配線材料 適用に。Arduinoにさすピンも必要
M3スペーサ Arduino用
M2.5スペーサ ユニバーサル基盤用
はんだ、はんだ吸い取り フレックスあるとなおよし
はんだごて 先っちょ細めで
ドライバ M2.5とM3のねじをしめるので#1で
片刃ニッパ 先っちょ細めで
ワイヤストリッパ 手が痛くなるのでいいものがよい
テスタ はんだ付けしたらこまめにテストを
ピンセット 細かい作業するので
基盤固定台 あったほうがストレスない、基盤は動く
電池ボックス 単三電池6個用、DCジャック付き
2Pトグルスイッチ Arduinoの電源ONOFF用
LANケーブル CAT5Eくらい。いいもの買うとシールドにささらない

ユニバーサル基盤の取付穴とArduino取付穴のサイズが違うのがややこしい

🛠 結線

Arduino ↔ シフトレジスタ(制御信号)

データ信号は「数珠つなぎ」、クロックとラッチは「枝分かれ(スター型)」で配線

信号の種類 Arduinoピン 1枚目 (IC1) 2枚目 (IC2) 3枚目 (IC3) 備考
データ (DS) D2 14番ピンへ 1枚目の9番から 2枚目の9番から 9番ピン(Q7S)でバトンタッチ
ラッチ (ST_CP) D3 12番ピンへ 12番ピンへ 12番ピンへ 全IC共通(並列)
クロック (SH_CP) D4 11番ピンへ 11番ピンへ 11番ピンへ 全IC共通(並列)
電源 (VCC) 5V 16番ピン 16番ピン 16番ピン 各ICの横にパスコン(0.1μF)推奨
グランド (GND) GND 8番ピン 8番ピン 8番ピン 太い線で一気に貫通させる

注意点: 10番ピン(MR)はすべて5Vへ、13番ピン(OE)はすべてGNDへ接続し、動作を安定させています。

シフトレジスタ ↔ 21個のLED(出力)

21個のLEDを、3枚のICで「7個ずつ」制御する構成です。各ICの8番目(Q7)は使用せず
シフトレジスタのピン配置的にQ7だけが反対側に位置しているので
配線の都合上使わないことにした

LED番号 担当IC ビット位置 (i) 出力名 備考
LED 1〜7 1枚目 0 〜 6 Q0 〜 Q6
(不使用) 7 Q7
--- --- --- --- ---
LED 8〜14 2枚目 8 〜 14 Q0 〜 Q6
(不使用) 15 Q7
--- --- --- --- ---
LED 15〜21 3枚目 16 〜 22 Q0 〜 Q6
(不使用) 23 Q7

ソフトウェアプログラムの話

続いて、Arduinoに書き込むプログラムのはなし

Arduino IDEを入手する

Arduinoで開発するには何はともあれ必要

Arduino用ライブラリを入手

GitHubからライブラリを入手する
EthernetICMPフォルダをC:\Users\UserName\OneDrive\ドキュメント\Arduino\librariesに置く

上は実はフォークしたリポジトリで、親は以下のようだけど、気づいたのが今更なので差分はわからない

EthernetICMP.hを編集する

55行目のコメントアウトを外す
今回は複数のセグメントをまとめてチェックするので
非同期で ping を送る必要がある
そうしないと1つのセグメントがタイムアウトになるまで待っていないといけない

EthernetICMP.h
// ICMPPING_ASYNCH_ENABLE -- define this to enable asynch operations
#define ICMPPING_ASYNCH_ENABLE

さらに革新的(革新的な)ライブラリをGET

レイヤ3のICMPよりさらに低レベルなレイヤ2を扱えるライブラリを発見

とりあえず両方のコードをGithubからダウンロードして、ローカルに配置する
こうすることでArduinoのスケッチで参照できるようになる

ドキュメント\Arduino>tree

C:.
└─libraries
    ├─EthernetICMP
    │  └─examples
    │      ├─Ping
    │      ├─PingAsync
    │      └─PingDHCP
    └─W5500MacRaw
        └─sendeth

開発方針

目的は

  1. 各LANセグメントにArduinoをつなぐ
  2. 自身がつながっているセグメントが何のセグメントであるかがわかる

とする。

Arduino自身のIP設定のために Ethernet.begin(mac, ip); が必要
LANセグメントが多くなると、この begin呼び出しがLANセグメント数に比例する

ping を打って返りを待つ場合、タイムアウトの時間分待たされる
セグメントが増えれば待ち時間も比例する

完成したコード
#include "w5500.h"

// ===== ネットワーク設定 =====
byte myMac[] = {0xA8, 0x61, 0x04, 0xAE, 0x23, 0x11};

// ===== シフトレジスタ設定 =====
const int dataPin = 2;
const int latchPin = 3;
const int clockPin = 4;

Wiznet5500 w5500;

// ===== ターゲットIPとLEDの対応表(プレフィックス長を追加) =====
struct TargetInfo {
  byte ip[4];
  uint8_t prefix; // サブネットマスク(/24 や /23)
  uint8_t bitPos;
};

TargetInfo targets[] = {
  // --- SR3 (左ブロック : QB〜QH = 17〜23) ---
  {{192, 168, 1, 254},   24, 17}, // SR3-QB
  {{192, 168, 2, 254},   24, 18}, // SR3-QC
  {{192, 168, 3, 254},   24, 19}, // SR3-QD
  {{192, 168, 10, 254},  23, 20}, // SR3-QE
  {{192, 168, 12, 254},  23, 21}, // SR3-QF
  {{192, 168, 100, 254}, 24, 22}, // SR3-QG
  {{192, 168, 200, 254}, 24, 23}, // SR3-QH
  
  // --- SR2 (中央ブロック : QB〜QH = 9〜15) ---
  {{172, 16, 1, 254},    24, 9},  // SR2-QB
  {{172, 16, 2, 254},    24, 10}, // SR2-QC
  {{172, 16, 10, 254},   24, 11}, // SR2-QD
  {{172, 20, 1, 254},    24, 12}, // SR2-QE
  {{172, 20, 2, 254},    24, 13}, // SR2-QF
  {{172, 31, 1, 254},    24, 14}, // SR2-QG
  {{172, 31, 100, 254},  24, 15}, // SR2-QH
  
  // --- SR1 (右ブロック : QB〜QF = 1〜5) ---
  {{10, 0, 1, 254},      24, 1},  // SR1-QB
  {{10, 0, 2, 254},      24, 2},  // SR1-QC
  {{10, 10, 10, 254},    24, 3},  // SR1-QD
  {{10, 20, 30, 254},    24, 4},  // SR1-QE
  {{10, 100, 0, 254},    24, 5}   // SR1-QF
};

const int NUM_TARGETS = sizeof(targets) / sizeof(targets[0]);
int currentScanIndex = 0; 

// ===== ヘルスチェック用のLED順番定義 =====
const uint8_t testLedOrder[] = {
  17, 18, 19, 20, 21, 22, 23, // SR3
  9, 10, 11, 12, 13, 14, 15,  // SR2
  1, 2, 3, 4, 5, 6, 7         // SR1
};
const int numTestLeds = sizeof(testLedOrder) / sizeof(testLedOrder[0]);

// タイマーと状態管理
unsigned long lastArpTime = 0;
const unsigned long SCAN_INTERVAL = 200; // 200ms間隔スキャン
unsigned long lastReplyTime = 0;
const unsigned long TIMEOUT_INTERVAL = 10000; // 10秒で未接続扱い

bool isConnected = false;
uint8_t connectedBitPos = 0; 
const uint8_t LED_DISCONNECTED = 7;  // SR1-QH (右下)
bool lastBlinkState = false;

void setup() {
  pinMode(dataPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);

  Serial.begin(9600);
  while (!Serial);
  delay(1000);

  Serial.println(F("--- Network Segment Auto-Detector ---"));
  Serial.println(F("--- Running LED Health Check ---"));

  // ヘルスチェックアニメーション
  for (int i = 0; i < numTestLeds; i++) {
    updateLEDs(1UL << testLedOrder[i]);
    delay(50); 
  }

  uint32_t allLeds = 0;
  for (int i = 0; i < numTestLeds; i++) {
    allLeds |= (1UL << testLedOrder[i]);
  }
  updateLEDs(allLeds);
  delay(500); 

  updateLEDs(0);
  delay(500);

  // W5500 初期化
  if (w5500.begin(myMac)) {
    Serial.println(F("W5500 Initialized."));
  } else {
    Serial.println(F("Initialization FAILED."));
    while(1);
  }
}

void loop() {
  // 1. スキャンパトロール (200msごと)
  if (millis() - lastArpTime >= SCAN_INTERVAL) {
    lastArpTime = millis();
    
    // TargetInfo構造体ごと関数に渡して偽装IPを作らせる
    sendArpRequest(targets[currentScanIndex]);
    
    currentScanIndex++;
    if (currentScanIndex >= NUM_TARGETS) {
      currentScanIndex = 0;
    }
  }

  // 2. パケット受信
  uint8_t rxBuffer[128];
  int packetSize = w5500.readFrame(rxBuffer, sizeof(rxBuffer));
  if (packetSize > 0) {
    checkArpReply(rxBuffer, packetSize);
  }

  // 3. タイムアウト判定
  if (isConnected && (millis() - lastReplyTime >= TIMEOUT_INTERVAL)) {
    isConnected = false;
    Serial.println(F("--- Connection Timeout! ---"));
  }

  // 4. LED点滅ロジック
  bool currentBlinkState = (millis() / 500) % 2 == 0;
  
  if (currentBlinkState != lastBlinkState) {
    lastBlinkState = currentBlinkState;
    
    if (currentBlinkState) {
      if (isConnected) {
        updateLEDs(1UL << connectedBitPos);
      } else {
        updateLEDs(1UL << LED_DISCONNECTED);
      }
    } else {
      updateLEDs(0);
    }
  }
}

// ===== LED送信用関数 =====
void updateLEDs(uint32_t dataToShift) {
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, (dataToShift >> 16) & 0xFF); // SR3(左)
  shiftOut(dataPin, clockPin, MSBFIRST, (dataToShift >> 8)  & 0xFF); // SR2(中)
  shiftOut(dataPin, clockPin, MSBFIRST, (dataToShift >> 0)  & 0xFF); // SR1(右)
  digitalWrite(latchPin, HIGH);
}

// ===== ネットワーク関連の関数 =====
void sendArpRequest(TargetInfo target) {
  uint8_t packet[60]; 
  memset(packet, 0, sizeof(packet));

  memset(packet, 0xFF, 6);
  memcpy(packet + 6, myMac, 6);
  packet[12] = 0x08; packet[13] = 0x06;

  packet[14] = 0x00; packet[15] = 0x01;
  packet[16] = 0x08; packet[17] = 0x00;
  packet[18] = 0x06;
  packet[19] = 0x04;
  packet[20] = 0x00; packet[21] = 0x01;

  // ==========================================
  // ★ 送信元IPの偽装
  // ==========================================
  byte spoofedIp[4];
  spoofedIp[0] = target.ip[0];
  spoofedIp[1] = target.ip[1];
  
  if (target.prefix == 23) {
    // /23 の場合は第3オクテットの最下位ビットを0にして偶数にする
    spoofedIp[2] = target.ip[2] & 0xFE; 
  } else {
    // /24 の場合はそのまま
    spoofedIp[2] = target.ip[2];
  }
  spoofedIp[3] = 239; // 第4オクテットは .239 で固定

  memcpy(packet + 22, myMac, 6);
  memcpy(packet + 28, spoofedIp, 4); // 名乗るIPを偽装IPに差し替え!
  memcpy(packet + 38, target.ip, 4); 

  w5500.sendFrame(packet, sizeof(packet));
}

void checkArpReply(uint8_t* packet, int size) {
  if (size < 42) return; 
  if (packet[12] != 0x08 || packet[13] != 0x06) return;
  if (packet[20] != 0x00 || packet[21] != 0x02) return;

  byte senderIp[4];
  memcpy(senderIp, packet + 28, 4);

  for (int i = 0; i < NUM_TARGETS; i++) {
    if (memcmp(senderIp, targets[i].ip, 4) == 0) {
      
      lastReplyTime = millis();
      
      if (!isConnected || connectedBitPos != targets[i].bitPos) {
        isConnected = true;
        connectedBitPos = targets[i].bitPos;
        
        Serial.print(F(">>> Connected! IP: "));
        Serial.print(senderIp[0]); Serial.print(".");
        Serial.print(senderIp[1]); Serial.print(".");
        Serial.print(senderIp[2]); Serial.print(".");
        Serial.println(senderIp[3]);

        updateLEDs(1UL << connectedBitPos);
        lastBlinkState = true; 
      }
      break; 
    }
  }
}

Wireshark

同一ソースからARMブロードキャスト問い合わせが飛び交っている様子がうかがえる

image.png

おわりに

もともとはPingを使うつもりだったが、結果的にARPに落ち着いた
Pingでは戻ってくる応答を待つ必要があるし、自身のIPをそのセグメントにする必要がある
一方でARPはターゲット狙い撃ちで問い合わせができる、死活監視が早い

大枠の思想は変わらなかった
LANを指したらヒカルそれだけのシンプルな構成

Gemini Proがなければプログラムは書けなかったし
W5500のGitHubライブラリがなかったら、時間もかかったと思う

2026年4月16日追記

想定Q&A(説明用)

カテゴリー 質問(想定) 回答提案
メリットと仕組み PCで確認すれば十分では? PCを起動する手間を省けるだけでなく、ネットワーク設計者と端末設定者が異なる現場などで、PCや端末が手元にない状況でもセグメントを即座に確認できる。PCレスで作業できることが、人為的なミスを減らし安全性を高めることに繋がる。
メリットと仕組み Ping(ICMP)で判定しているのか? いいえ、ARP(Address Resolution Protocol)を使用。OSのFWで拒否されやすいPingより確実で、L2層でのやり取りのためレスポンスも高速だ。
メリットと仕組み なぜIPアドレスを持っているのか? ARPの仕様上、パケット内の送信元IP(Sender IP)が空だと受信側が応答を返せないため。各セグメントの未使用IPを一時的に名乗る必要がある。
電源と物理的安全 なぜリチウム電池を使わない? 発火・膨張リスクを避け、安全性を最優先した。単三乾電池6本にすることで、安全かつ誰でも保守・交換ができるようにしている。
電源と物理的安全 感電や火災のリスクは? 回路はDC 5V〜9Vの低電圧。製作時にテスターで絶縁チェック済み。ケース内もスペーサーで固定し、物理的な短絡(ショート)を防いでいる。
信頼性 LEDが故障していたら誤判定するのでは? 起動時に全LEDのヘルスチェック・アニメーションを実行する仕様。LED自体の不点灯を事前に目視確認できるため、誤判定のリスクを排除している。
負荷とセキュリティ ネットワークを攻撃しているのでは? 攻撃ではない。200ms(0.2秒)に1回、特定ターゲットへ順次問い合わせを行う設定。秒間300bytes程度の極微量通信であり、負荷は皆無。
負荷とセキュリティ 通信を盗み見していないか? 自分のMACアドレス宛のパケット以外を無視するフィルタ設定。物理的にネットワーク全体のパケットを収集・保存する能力はない。
マルウェア対策 ランサムウェアに感染するリスクは? ゼロ。Windows等のOSがなく、メモリもわずか32KBしかない。数MBある現代のウイルスは物理的に入り切らず、動作する基盤も存在しない。
マルウェア対策 外部にデータを送信したりしないか? 本機はSDカード等の記録領域を一切持たない。データを保存する場所がないため、吸い出しや持ち出しは物理的に不可能。
AIの活用 プログラミングは自分ですべて書いたのか? ChatGPTやMicrosoft CopilotといったAIとのライブコーディング。ネットワーク屋としてのロジックをAIが最適化することで、バグを抑えたコードを短時間で実装した。
AIの活用 部品選定やデザインもAIなのか? ああ。シフトレジスタによる多点制御の構成や、ネーミング、キャラ設定もAIと相談して決めた。AIをハードウェアコンサルタントとして活用した成果。
運用・資産管理 私物の持ち込みではないか? 現在は技術検証(PoC)フェーズ。会社のリソースを無駄にせず、まずは個人機材で有用性を証明する段階。実効性が確認できれば会社資産化を提案したい。
運用・資産管理 セグメントが増えたら対応できるのか? シフトレジスタの数珠つなぎ構造を採用しているため、将来的なセグメント増にもハード・ソフト両面で容易に対応可能。
運用・資産管理 お前がいなくなったら誰が直すんだ? 設計図はMermaid記法、コードはGitで管理済み。標準的なArduino言語と市販部品のみの構成なので、他の技術者でも容易に保守・改修が可能。

参考

糸冬了!!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?