はじめに
ArduinoにLANケーブルをつないで、ネットワークセグメントを調査するツールをつくる
Arduinoにはイーサネットシールドという後付け追加基盤がある
RJ45ポートが付くことで、LANケーブルを使ったネットワーク開発ができるようになる代物だ。
Arduinoとシールドを合わせたらラズパイの中古価格を超えるので
ラズパイでいいじゃん?という話もあるかもしれない
ただ、ラズパイはOSがあることが逆に邪魔だったりするので
電源を入れてさくっと動かすような単一目的のツールなら、Arduinoのほうがいい場面もあると思う。
Geminiと会話しながら開発したので、所々でGemini口調になります
この記事では、大きく「ハードウェア構築」と「Arduinoで動くプログラム開発」の2セクションに分けて進める。
作るものの概要
- ArduinoにLANケーブルを指す
- そのLANが何のセグメントかわかる
- なぜわかるか?LEDが光るから
ハードウェアの話
まずは 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つのセグメントがタイムアウトになるまで待っていないといけない
// 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
開発方針
目的は
- 各LANセグメントにArduinoをつなぐ
- 自身がつながっているセグメントが何のセグメントであるかがわかる
とする。
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ブロードキャスト問い合わせが飛び交っている様子がうかがえる
おわりに
もともとは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言語と市販部品のみの構成なので、他の技術者でも容易に保守・改修が可能。 |
参考

