「Wi-Fiには繋がっているのに、なぜかインターネットにアクセスできない」
「スマートホームデバイスを数十台増やしたあたりから、ネットワークが頻繁に瞬断するようになった」
こうした家庭内ネットワークのトラブルシューティングに挑むとき、多くのエンジニアがまず物理層や暗号化規格(WPA3など)を疑いがちです。しかし、数々の修羅場をくぐり抜けてきた私たちシニアエンジニアから見れば、トラブルの真の主犯はもっと上位のレイヤー、すなわち「IPアドレスの動的割り当て」に潜んでいることが珍しくありません。
その心臓部を担うのが DHCP(Dynamic Host Configuration Protocol) です。
普段は空気のように存在を意識させないプロトコルですが、その挙動は非常に緻密で、一歩間違えればネットワーク全体を麻痺させる破壊力を秘めています。今回は、普段Web APIの設計やクラウドインフラの構築に携わっているエンジニアのあなたに向けて、DHCPの内部挙動、RFC 2131に基づく4段階ハンドシェイクの真実、そして実務で役立つ設定とデバッグ手法を、パケットの動きが見えるレベルまで深く、泥臭く解説します。
—
1. DHCP 4段階ハンドシェイク(DORA)の解剖学
DHCPの基本は、クライアントとサーバー間で行われる4つのメッセージ交換です。それぞれの頭文字をとって「DORAプロセス」と呼ばれます。
「知っているよ、Discover、Offer、Request、Ackだろう?」と思うかもしれません。しかし、それぞれのパケットが「なぜブロードキャストなのか、あるいはユニキャストなのか」まで、自信を持って説明できるでしょうか。
まずは、そのシーケンスをパケットレベルで可視化してみましょう。
クライアント (ポート 68) DHCPサーバー (ポート 67)
| |
| ------ 1. DHCPDISCOVER (ブロードキャスト) ----> |
| |
| <----- 2. DHCPOFFER (ユニ/ブロードキャスト) --- |
| |
| ------ 3. DHCPREQUEST (ブロードキャスト) -----> |
| |
| <----- 4. DHCPACK (ユニ/ブロードキャスト) ----- |
| |
① DHCPDISCOVER(探査)
- 送信元ポート:
68/ 宛先ポート:67(UDP) - 送信元IP:
0.0.0.0/ 宛先IP:255.255.255.255(リミテッド・ブロードキャスト)
ネットワークに参加したばかりのクライアントは、自分のIPアドレスすら持っていません。そのため、送信元IPを 0.0.0.0 とし、同一セグメント内のすべての機器に向けて「誰かIPアドレスをくれませんか?」と叫びます(ブロードキャスト)。
② DHCPOFFER(提案)
- 送信元IP: サーバーのIPアドレス(例:
192.168.11.1) - 宛先IP:
255.255.255.255(またはクライアントのMACアドレス宛てのユニキャスト)
DISCOVERを受け取ったDHCPサーバーは、自身のIPプールから未使用のアドレス(例: 192.168.11.50)を確保し、「このアドレスはどうですか?」と提案します。
この際、クライアントはまだIPアドレスを正式に保持していないため、サーバーは宛先IPをブロードキャストで返す仕様(または、L2ヘッダーのMACアドレスを直接指定するユニキャスト。これは実装に依存します)になっています。
③ DHCPREQUEST(要求)―― なぜここでも「ブロードキャスト」なのか?
- 送信元IP:
0.0.0.0/ 宛先IP:255.255.255.255
ここが設計の極めて美しいポイントです。クライアントは、提案されたIPアドレスを採用することを決めたら、DHCPREQUESTを送信します。このパケットはブロードキャストで送られます。
なぜユニキャストではないのでしょうか?
それは、「ネットワーク上に複数のDHCPサーバーが存在する可能性」を考慮しているからです。クライアントは、最初に届いたDHCPOFFERを採用します。その際、ブロードキャストで「私はサーバーAさんから提案された 192.168.11.50 を使います!」と宣言することで、不採用となったサーバーBさんに対して「あなたの提案は辞退しますので、確保していたIPアドレスをプールに戻してください」とスマートに通知しているのです。
④ DHCPACK(確認応答)
- 送信元IP: サーバーのIPアドレス / 宛先IP:
255.255.255.255(またはユニキャスト)
サーバーは要求を確認し、最終的な合意として DHCPACK を送信します。このパケットの中に、IPアドレスのほか、サブネットマスク、デフォルトゲートウェイ、DNSサーバー、そしてリース時間(Lease Time)といった、クライアントがネットワークで生きていくためのすべてのパラメータが内包されています。
—
2. リース時間(Lease Time)とタイマーの設計思想
DHCPから払い出されるIPアドレスは「買い取り」ではなく「レンタル」です。このレンタル期間を規定するのが Lease Time(RFC 2131 Option 51)です。
実務において、このリース時間をどう設計するかは、ネットワークの安定性を左右する極めて重要な意志決定です。
T1とT2:自動リリースのメカニズム
クライアントは、リース期間が切れて突然通信が途絶えるのを防ぐため、バックグラウンドで段階的に更新(リニューアル)を試みます。ここには2つの重要なタイマーが存在します。
1. T1タイマー(Renewal Timer): リース時間の 50% が経過した時点で起動。
- クライアントは、IPを払い出してくれたDHCPサーバーに対してユニキャストで
DHCPREQUESTを送信し、リースの延長を申請します。通常はここでサーバーからDHCPACKが戻り、リース期間がリセットされます。
2. T2タイマー(Rebinding Timer): リース時間の 87.5%(7/8) が経過してもT1が成功しなかった場合に起動。
- もともとのDHCPサーバーがダウンしている可能性があるため、クライアントはブロードキャストで
DHCPREQUESTを送信し、ネットワーク上の「他の任意のDHCPサーバー」に対して、現在のIPアドレスの使用継続を懇願します。
現場におけるリース時間の設計方針
| ユースケース | 推奨リース時間 | 設計の意図 |
| :— | :— | :— |
| 一般的なオフィス / 自宅 | 8時間 ~ 24時間 (28800~86400秒) | 端末の入れ替わりが穏やかであり、DHCPパケットによるトラフィックの無駄を抑える。 |
| カフェ / 公共Wi-Fi | 30分 ~ 1時間 (1800~3600秒) | 滞在者が次々と入れ替わるため、長すぎるリース時間はIPアドレスの枯渇(Address Exhaustion)を招く。 |
| スマートホーム(IoT高密度環境)| 12時間 ~ 24時間 | 多数のデバイスが常時接続されるため、短すぎるとルーターのCPU負荷が上がり、長すぎると「MACアドレスランダム化」機能によるゴースト端末でIPが枯渇する。 |
> シニアからのTips: 近年のiOSやAndroid、Windows 10/11には、セキュリティ機能としてWi-Fi接続時にMACアドレスをランダムに変更する「プライベートWi-Fiアドレス」機能が標準搭載されています。
> これにより、同じスマートフォンであっても、接続し直すたびに「新しいデバイス」として認識され、DHCPサーバーのIPアドレスが瞬く間に食い潰される現象が発生します。IoTデバイスが100台規模になるモダンなスマートホームやオフィスでは、リース時間を長すぎる設定(例:1週間など)にすることは自殺行為です。
—
3. 実践:Linux DHCPサーバー(dhcpd)の堅牢な設定例
家庭用ルーターの多くは内部でDnsmasqやISC DHCP Serverを動かしています。ここでは、実務でプライベートな検証環境やエッジネットワークを構築する際によく使われる dhcpd.conf の設定例を示します。
スマートホームデバイスのように、「常に同じIPアドレスで通信してほしい機器(スマートカメラやHome Assistantサーバーなど)」を固定割り当てしつつ、動的プールを安全に管理するための実践的なテンプレートです。
# /etc/dhcp/dhcpd.conf の設定例
# ネットワーク全体のデフォルト設定
option domain-name "home.internal";
option domain-name-servers 1.1.1.1, 8.8.8.8; # DNSサーバーの指定(Cloudflare, Google)
# リース時間の設定(単位:秒)
default-lease-time 43200; # デフォルトは12時間(スマートホームの churn 対策)
max-lease-time 86400; # 最大でも24時間で強制回収
# このDHCPサーバーがこのセグメントにおける絶対的な権限を持つことを宣言
authoritative;
# サブネットの定義(192.168.11.0/24)
subnet 192.168.11.0 netmask 255.255.255.0 {
range 192.168.11.50 192.168.11.150; # 動的IPの払い出しプール(101個分)
option routers 192.168.11.1; # デフォルトゲートウェイの指定
option subnet-mask 255.255.255.0; # サブネットマスク
option broadcast-address 192.168.11.255; # ブロードキャストアドレス
}
# 特定のスマートホームデバイス(例:スマートカメラ)へのIP固定バインド
host smart_camera_01 {
hardware ethernet 00:11:22:33:44:55; # デバイスの物理MACアドレス
fixed-address 192.168.11.200; # 動的プール外の固定IPを割り当てる
}
—
4. パケットレベルのデバッグ:PythonによるDHCP監視スクリプト
「DHCPがうまく動いていない気がするが、ルーターの管理画面からは何も見えない」
そんなときこそ、エンジニアとしての本領発揮です。ネットワークを流れるパケットを直接キャプチャしてデバッグしましょう。
Pythonの強力なパケット操作ライブラリ scapy を使えば、ネットワーク上のDHCPパケット(特に DHCPDISCOVER や DHCPOFFER)をリアルタイムで監視する簡易アナライザーを数十行で記述できます。
依存ライブラリのインストール
事前に以下のコマンドで scapy をインストールしておきます。
pip install scapy
DHCPパケット監視スクリプト (dhcp_sniffer.py)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from scapy.all import sniff, DHCP, BOOTP, IP
def handle_dhcp_packet(packet):
"""
キャプチャしたDHCPパケットを解析し、コンソールに出力するコールバック関数
"""
# パケットにDHCPレイヤーが含まれているか確認
if packet.haslayer(DHCP):
# BOOTPレイヤーからクライアントのMACアドレスを取得
mac_addr = packet[BOOTP].chaddr[:6].hex(":")
# DHCPオプションからメッセージタイプ(Discover, Offer等)を抽出
options = packet[DHCP].options
message_type = None
for opt in options:
if isinstance(opt, tuple) and opt[0] == 'message-type':
# メッセージタイプは数値で格納されているため、対応する文字列表現にマッピング
dhcp_types = {
1: "DISCOVER",
2: "OFFER",
3: "REQUEST",
4: "DECLINE",
5: "ACK",
6: "NAK",
7: "RELEASE",
8: "INFORM"
}
message_type = dhcp_types.get(opt[1], f"UNKNOWN ({opt[1]})")
break
# 解析結果を人間に読みやすいフォーマットで出力
print(f"[+] DHCP Message Detected!")
print(f" Type: {message_type}")
print(f" Client MAC: {mac_addr}")
if packet.haslayer(IP):
print(f" Source IP: {packet[IP].src}")
print(f" Dest IP: {packet[IP].dst}")
# 提案されたIPアドレス(yiaddr: Your IP Address)がある場合
yiaddr = packet[BOOTP].yiaddr
if yiaddr and yiaddr != "0.0.0.0":
print(f" Offered IP: {yiaddr}")
print("-" * 40)
def main():
print("[*] Starting DHCP Sniffer... Press Ctrl+C to stop.")
# UDPポート67および68のトラフィックのみをフィルターしてキャプチャ開始
# ※ 実行には管理者権限(sudo)が必要です
try:
sniff(filter="udp port 67 or port 68", prn=handle_dhcp_packet, store=0)
except PermissionError:
print("[-] Error: Root privileges (sudo) are required to capture raw packets.", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
このスクリプトで何が分かるのか?
このスクリプトをローカルマシンで起動した状態で、スマートフォンのWi-Fiを一旦オフにしてオンに戻してみてください。
コンソールに DISCOVER -> OFFER -> REQUEST -> ACK の一連の流れが綺麗に表示されるはずです。
もし DISCOVER ばかりが連打されていて OFFER が戻ってきていない場合、それはDHCPサーバー(ルーター)が沈んでいるか、IPプールが枯渇しているシグナルです。実務の障害対応において、この「何が届いていて、何が届いていないか」を一次情報(パケット)で確認するスキルは、解決への最短ルートとなります。
—
5. シニアエンジニアからのアドバイス:スマートホーム時代のネットワーク設計
最後に、現場でIoTやスマートホームネットワークを設計するあなたに、これまでのトラブル対応から得た知見を共有します。
1. メッシュWi-Fi環境での「マルチキャスト・ブロードキャストの不通」に気をつけろ
現代の家庭で広く普及しているメッシュWi-Fiですが、一部の安価な製品や古いファームウェアでは、サテライト(子機)とメインルーター間でのブロードキャストパケットの転送が不安定になるバグを抱えていることがあります。
「子機のWi-Fiに繋いだデバイスだけが、DHCPでのIP取得に失敗する」という怪現象に遭遇したら、ブロードキャストが中継を阻害されている可能性を疑ってください。
2. 「野良DHCP(Rogue DHCP)」の存在を警戒せよ
オフィスの検証環境や、ギークな家庭内LANでよくあるのが、開発者が検証用に立ち上げた仮想マシンやルーターのDHCPサーバー機能が有効なままメインネットワークに接続され、本番ルーターと「IPアドレスの奪い合い(早い者勝ち)」を始めるトラブルです。
先ほどのPythonスクリプトで、身に覚えのないIPアドレスから DHCPOFFER が飛んできていないかを監視することで、この手のトラブルは一瞬で看破できます。
ネットワークのトラブルは、一見すると複雑怪奇に見えますが、プロトコルの仕様を正しく理解し、パケットを観察すれば、必ずロジカルな答えに辿り着きます。この記事が、あなたのネットワークをより堅牢に、そしてトラブルシューティングをよりスマートにする一助となれば幸いです。
コメント