LinuxでDHCP Relay Agentを設定する:複数のSubnetとVLANをまたいだIPアドレス要求の転送方法

Network tutorial - IT technology blog
Network tutorial - IT technology blog

複数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台のデバイスがある小規模なオフィスネットワークでこの方法を使用しており、ここ数ヶ月間何も問題は発生していない。

Share: