複数VLANがある環境で直面する問題
この状況には何度も遭遇したことがある。複数のVLANがある環境を構築 — VLAN 10をサーバー用、VLAN 20をワークステーション用、VLAN 30をIoTデバイス用に設定。DHCPサーバーはVLAN 10に集中配置。VLAN 20のマシンを起動するまでは問題なかったが…いくら待ってもIPアドレスが割り当てられない。
ネットワークインターフェースを再起動し、DHCPサーバーを再起動し、ケーブルを確認しても — 何も変わらない。そこで気づいた:DHCPはブロードキャストに基づいて動作するが、ブロードキャストはルーターやレイヤー3スイッチを越えることができない。VLAN 20とVLAN 10は完全に独立した2つのブロードキャストドメインだ。
なぜDHCPはsubnetをまたげないのか
クライアントが送信するDHCP Discoveryパケットは、アドレス255.255.255.255へのブロードキャストだ。ルーターはブロードキャストを受け取っても無視する — デフォルトではルーターはブロードキャストを転送しない。
具体的には、192.168.20.0/24のクライアントがDHCP Discoverを送信する場合:
Source IP: 0.0.0.0
Destination: 255.255.255.255
Protocol: UDP 68 (client) → UDP 67 (server)
このパケットはVLAN 20のブロードキャストドメイン内にしか存在しない。ルーターはDHCPサーバーが稼働しているVLAN 10へは転送しない。クライアントは約60秒間応答を待ち続け、最終的に169.254.x.x(APIPA)を自己割り当てして、ネットワークは…使えない状態になる。
3つの解決策 — 実際に使うべきはどれか
方法1:各subnetに専用DHCPサーバーを立てる
一見最もシンプルな方法:subnetごとにDHCPサーバーを用意する。しかし実際の運用ではすぐに問題が出る:
- 管理が分散し、どのIPが誰に割り当てられているか把握できない
- 設定変更の際に複数箇所を修正しなければならない
- 問題発生時のデバッグが2倍困難になる
方法2:Proxy ARPまたはunnumberedインターフェース
DHCPサーバーの代わりにルーターでARPをエミュレートするよう設定する。理論上は動作するが、ARPキャッシュのタイムアウトがDHCPリース更新と同期されないため、再現が極めて困難なランダムなエラーが発生する。
方法3:DHCP Relay Agent — RFCに準拠した標準的な解決策
RFC 3046はまさにこの問題を解決するために策定された。DHCP Relay Agentを各subnetに配置し、クライアントからのブロードキャストを受け取ってユニキャストに変換し、集中管理されたDHCPサーバーへ直接送信する。サーバーが応答し、relayがクライアントへ転送する。
動作フロー:
Client (VLAN 20) --[broadcast]--> Relay Agent (192.168.20.1)
Relay Agent ------[unicast]-----> DHCP Server (192.168.10.10)
DHCP Server ------[unicast]-----> Relay Agent
Relay Agent ------[unicast]-----> Client
LinuxへのDHCP Relay Agentのインストールと設定
ステップ1:isc-dhcp-relayのインストール
# Ubuntu/Debian
sudo apt update && sudo apt install isc-dhcp-relay -y
# CentOS/RHEL/Rocky Linux
sudo dnf install dhcp-relay -y
Debian/Ubuntuでは、インストーラーがDHCPサーバーのアドレスとrelayするインターフェースを尋ねてくる。私は通常そのステップをスキップし、設定ファイルで手動設定する方が確実だと感じている。
ステップ2:ネットワークトポロジーの確認
コマンドを実行する前に、どのIP範囲で作業しているかを明確に把握しておく必要がある。私はよくtoolcraft.app/ja/tools/developer/ip-subnet-calculatorを利用している — CIDRを入力するだけでネットワーク範囲、ブロードキャストアドレス、利用可能なホスト数がすぐに分かる。/26や/27を手計算するのはそれほど難しくないが、5〜6個のsubnetが同時にある場合はミスが起きやすい。
本記事のトポロジー例:
VLAN 10 (Server): 192.168.10.0/24 — interface eth0.10 (IP: 192.168.10.1)
VLAN 20 (Workstation): 192.168.20.0/24 — interface eth0.20 (IP: 192.168.20.1)
VLAN 30 (IoT): 192.168.30.0/24 — interface eth0.30 (IP: 192.168.30.1)
DHCP Server: 192.168.10.10
Linux Router/Relay: 3つのVLAN全てにIPを持つ
ステップ3:isc-dhcp-relayの設定
sudo nano /etc/default/isc-dhcp-relay
# DHCPサーバーのアドレス(複数の場合はスペース区切り)
SERVERS="192.168.10.10"
# relayがリッスンするインターフェース(関連する全てのVLAN)
INTERFACES="eth0.10 eth0.20 eth0.30"
# -a: append agent information option (circuit-id)
OPTIONS="-a"
オプション-aは最初に思っていたより重要だ — これによりパケットにどのインターフェースがリクエストを受け取ったかの情報が追加される。DHCPサーバーはその情報を元にクライアントがどのsubnetから来たかを識別し、適切なIPアドレス範囲を割り当てる。
ステップ4:DHCPサーバーでのsubnet宣言
DHCPサーバー側(isc-dhcp-serverを使用)では、各subnetのプールを宣言する必要がある:
sudo nano /etc/dhcp/dhcpd.conf
authoritative;
default-lease-time 86400;
max-lease-time 172800;
# VLAN 10 — サーバーネットワーク、静的IPを使用する場合はプール不要
subnet 192.168.10.0 netmask 255.255.255.0 {
option routers 192.168.10.1;
option domain-name-servers 8.8.8.8, 1.1.1.1;
}
# VLAN 20 — workstations
subnet 192.168.20.0 netmask 255.255.255.0 {
range 192.168.20.100 192.168.20.200;
option routers 192.168.20.1;
option domain-name-servers 8.8.8.8, 1.1.1.1;
option broadcast-address 192.168.20.255;
default-lease-time 43200;
}
# VLAN 30 — IoTデバイス(リース時間を短く設定)
subnet 192.168.30.0 netmask 255.255.255.0 {
range 192.168.30.50 192.168.30.150;
option routers 192.168.30.1;
option domain-name-servers 8.8.8.8;
default-lease-time 3600;
max-lease-time 7200;
}
重要なヒント:DHCPサーバーにはすべてのネットワークに対してsubnet宣言が必要だ — サーバー自身が属するネットワーク(VLAN 10)も含めて。宣言が欠けていると、dhcpdはエラーを報告して起動を拒否する。
ステップ5:サービスの起動
# relay agentマシン(Linuxルーター)で実行
sudo systemctl enable --now isc-dhcp-relay
sudo systemctl status isc-dhcp-relay
# DHCPサーバーで実行
sudo systemctl enable --now isc-dhcp-server
sudo systemctl status isc-dhcp-server
クライアントがIPを取得できない場合のデバッグ
初回のセットアップ時、このデバッグだけで2時間近くかかった。以下は最もよく遭遇するエラーだ:
relay agentがリクエストを受信しているか確認
# リアルタイムでログを確認
sudo journalctl -u isc-dhcp-relay -f
# インターフェース上でパケットを直接キャプチャ
sudo tcpdump -i eth0.20 port 67 or port 68 -n -v
DHCPサーバーが転送されたリクエストを受信しているか確認
sudo journalctl -u isc-dhcp-server -f
# 付与済みのリースを確認
cat /var/lib/dhcp/dhcpd.leases
よくあるエラー
“No subnet declaration for eth0.20” — dhcpd.confにsubnet宣言が不足している。対応するsubnetブロックを追加すれば解決する。
relayは動作しているが転送されない — DHCPサーバーのファイアウォールがブロックしている可能性がある。確認:
# UFW
sudo ufw allow 67/udp
# iptables
sudo iptables -A INPUT -p udp --dport 67 -j ACCEPT
クライアントはIPを取得できるが間違ったsubnetのもの — relay設定でオプション-aが不足している。サーバーはリクエストがどのVLANから来たかを識別できず、最初に見つかったIPを割り当ててしまう。OPTIONSに-aを追加してrelayを再起動する。
LinuxでRelayを使うべき場合とスイッチで使うべき場合
レイヤー3スイッチ(Cisco、Juniper、MikroTik RouterOS)がある環境では、スイッチのVLANインターフェースに直接ip helper-addressを設定する方が望ましい — よりシンプルで障害点が少ない。
# Cisco IOSの例
interface Vlan20
ip address 192.168.20.1 255.255.255.0
ip helper-address 192.168.10.10
しかし、homelab環境、ルーターとして使用するVPS、または純粋なLinuxインフラでは、isc-dhcp-relayは安定して動作し、中規模のproduction環境でも十分に実用的だ。現在、4つのVLANに約60台のデバイスがある小規模なオフィスネットワークでこの方法を使用しており、ここ数ヶ月間何も問題は発生していない。

