igmpproxyを使ったLinux上のIGMPプロキシ:ルーターとファイアウォールを経由してIPTVマルチキャストストリームをルーティングする

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

6ヶ月前、プロバイダーのIPTVサービスがルーターの直接LANポート以外では一切動かない状態に悩まされていました。Wi-Fiもスイッチポートも、どこでも動かない。原因はマルチキャストにありました。IGMPメンバーシップレポートがISPのアップストリームインターフェイスと内部LAN間の境界を越えられていなかったのです。最初はpimd、次にsmcrouteを試しましたが、最終的にigmpproxyが最もクリーンな解決策でした。以来、一度も再起動することなく稼働し続けています。

このガイドでは、最小限の動作設定からファイアウォールルール、マルチVLANルーティングまで、私が実際に辿った手順をそのまま解説します。

クイックスタート:5分でigmpproxyを動かす

igmpproxyのインストール

Debian/Ubuntuの場合:

sudo apt update && sudo apt install igmpproxy -y

RHEL/Rocky/AlmaLinuxの場合:

sudo dnf install igmpproxy -y

カーネルでIPマルチキャストルーティングを有効化する

カーネルにマルチキャストルーティングサポートが読み込まれていないと、igmpproxyは正常に機能しません:

sudo modprobe ip_mr_route
echo "net.ipv4.conf.all.mc_forwarding = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

最小限の /etc/igmpproxy.conf

eth0がアップストリーム(WAN/ISP)インターフェイス、eth1が内部LANと仮定します:

##
## igmpproxy.conf — 最小限の動作設定
##

phyint eth0 upstream  ratelimit 0  threshold 1
    altnet 0.0.0.0/0

phyint eth1 downstream  ratelimit 0  threshold 1

サービスを起動して有効化します:

sudo systemctl enable --now igmpproxy
sudo systemctl status igmpproxy

アップストリームからのマルチキャストストリームが、IGMPジョインリクエストを送信するeth1上のすべてのデバイスに流れるようになります。次のコマンドで確認しましょう:

sudo tcpdump -i eth1 'udp and dst net 224.0.0.0/4' -n -c 20

LANインターフェイスにUDPパケットが届いていれば、プロキシが正常に動作しています。これが5分でできる基本的なセットアップです。以降では、動作するプロトタイプをより堅牢な本番環境向けの設定に仕上げていきます。

詳細解説:igmpproxyの仕組み

IGMPとマルチキャストルーティングの基礎

マルチキャストはユニキャストとは動作が異なります。1つの送信元が1つのストリームを送信し、ネットワークはIGMP(Internet Group Management Protocol)を通じてマルチキャストグループに明示的に参加したルーターとホストにのみコピーを配信します。IPTVデコーダーは239.1.1.1のようなグループアドレスに対してIGMP Joinを送信し、アップストリームルーターはそのJoinを受信してリクエスト元のセグメントにトラフィックをルーティングする必要があります。

ここでLinuxのルーティングが問題になります。Linuxルーターはデフォルトでインターフェイス間のIGMPメンバーシップレポートを転送しません。igmpproxyはこのギャップを埋めます。ダウンストリームインターフェイスでIGMPレポートを監視し、アップストリームに向けて対応するIGMP Joinを生成し、カーネルにマルチキャストルートを設定します。

実際のネットワークトポロジー

ISPネットワーク(マルチキャストソース)
        │
       eth0  ← アップストリーム(WAN)— ISPからマルチキャストを受信
  [igmpproxyが稼働するLinuxルーター]
       eth1  ← ダウンストリーム(LAN)— IPTVデコーダーにマルチキャストを配信
        │
   ホームLAN(IPTV STB、スマートTV)

igmpproxyはカーネルのマルチキャストルーティングソケット(MRT_ADD_MFC)内の転送テーブルを維持管理します。eth1上のクライアントがIGMP Membership Reportを送信すると、igmpproxyがそれを受信し、eth0経由でアップストリームにIGMP Joinを送信し、次にeth0からeth1への一致するマルチキャストパケットの転送をカーネルに設定します。

altnetディレクティブ — なぜ重要なのか

アップストリームインターフェイス下のaltnetディレクティブは、どのソースアドレスからのマルチキャストを受け入れるかをigmpproxyに伝えます。多くのISPはプライベートアドレス範囲からIPTVストリームを送信します。10.x.x.xや172.x.x.xが一般的です。0.0.0.0/0を使うとすべてを受け入れるため、初期テスト時は問題ありません。本番環境では範囲を絞ることで、ISP側から届く関係のないトラフィックのマルチキャストをigmpproxyが意図せず転送するのを防げます:

phyint eth0 upstream  ratelimit 0  threshold 1
    altnet 10.0.0.0/8
    altnet 172.16.0.0/12

応用設定

マルチキャスト用ファイアウォールルール(iptables / nftables)

Linuxのファイアウォールはデフォルトでマルチキャストトラフィックをブロックします。以下のルールで最もよくあるギャップをカバーします:

iptables:

# 関連するすべてのインターフェイスでIGMP(プロトコル2)を許可
sudo iptables -A INPUT -p igmp -j ACCEPT
sudo iptables -A OUTPUT -p igmp -j ACCEPT
sudo iptables -A FORWARD -p igmp -j ACCEPT

# マルチキャストUDPの転送を許可(224.0.0.0/4はすべてのマルチキャストグループをカバー)
sudo iptables -A FORWARD -d 224.0.0.0/4 -j ACCEPT
sudo iptables -A FORWARD -s 224.0.0.0/4 -j ACCEPT

nftables(モダンなシステム向け):

sudo nft add rule inet filter forward ip daddr 224.0.0.0/4 accept
sudo nft add rule inet filter forward ip protocol igmp accept

最初に痛い目を見た点として、nf_conntrackはマルチキャストをうまく処理できません。FORWARDチェーンにINVALIDステートのドロップルールがある場合、マルチキャストUDPパケットはエラーもログも残さずに消えてしまいます。マルチキャストをconntrackから完全に除外するか、ステートチェックの前に明示的なACCEPTを挿入してください:

# ポジション1に挿入してconntrackステートルールより前に実行
sudo iptables -I FORWARD 1 -d 224.0.0.0/4 -j ACCEPT

複数のダウンストリームインターフェイス(VLAN)

VLANを使用する場合、たとえばeth1.10をメディア用VLAN、eth1.20を一般デバイス用とする場合は、それぞれをダウンストリームエントリとして追加するだけです:

phyint eth0 upstream  ratelimit 0  threshold 1
    altnet 0.0.0.0/0

phyint eth1.10 downstream  ratelimit 0  threshold 1
phyint eth1.20 downstream  ratelimit 0  threshold 1

igmpproxyは、アクティブなIGMPメンバーがいるダウンストリームインターフェイスにストリームを複製します。追加設定は不要で、VLANインターフェイスがカーネルレベルで存在していれば自動的に機能します:

# VLANインターフェイスがまだ存在しない場合は作成
sudo ip link add link eth1 name eth1.10 type vlan id 10
sudo ip link add link eth1 name eth1.20 type vlan id 20
sudo ip link set eth1.10 up
sudo ip link set eth1.20 up

quickleave — チャンネル切り替えの遅延を削減

/etc/igmpproxy.confの先頭にquickleaveを追加すると、IGMPクエリ/レスポンスの完全なサイクルを待たずに、最初のIGMP Leaveを受信した瞬間にigmpproxyが転送を停止します:

quickleave

phyint eth0 upstream  ratelimit 0  threshold 1
    altnet 0.0.0.0/0

phyint eth1 downstream  ratelimit 0  threshold 1

IPTVクライアントが1台の場合、チャンネル切り替えの遅延が3〜5秒から1秒未満に短縮されます。ただし、複数のクライアントが異なるチャンネルを同時に視聴している場合は使用しないでください。1台のクライアントが離脱するだけで、他の全員のストリームが止まってしまいます。

6ヶ月の本番運用で得た実践的なヒント

igmpproxyが起動しない?まずここを確認

初回起動時に最もよく起きるサイレント障害は、マルチキャストルーティングモジュールが読み込まれていないことです。

# モジュールが読み込まれているか確認
lsmod | grep mroute

# 見つからない場合:
sudo modprobe ip_mr_route

# マルチキャスト転送がアクティブか確認
cat /proc/sys/net/ipv4/conf/all/mc_forwarding

マルチキャストルーティングソケットを開けない場合、igmpproxyは即座に終了しますが、systemctl status igmpproxyは常に明確なエラーを表示するとは限りません。完全なジャーナル出力を確認してください:

journalctl -u igmpproxy -n 50

再起動後もモジュールが永続するようにします:

echo "ip_mr_route" | sudo tee /etc/modules-load.d/igmpproxy.conf

tcpdumpで転送パス全体を確認する

何かうまく動かない場合は、tcpdumpで各ホップを個別に確認してください:

# ステップ1:ISPからアップストリームインターフェイスにマルチキャストが届いているか?
sudo tcpdump -i eth0 'udp and dst net 224.0.0.0/4' -n -c 20

# ステップ2:eth0からISPに向けてIGMP Joinが送出されているか?
sudo tcpdump -i eth0 igmp -n

# ステップ3:LANにストリームが届いているか?
sudo tcpdump -i eth1 'udp and dst net 224.0.0.0/4' -n -c 20

3つのステップ、3つの障害点:アップストリームの配信、IGMPシグナリング、またはダウンストリームの転送。どこでチェーンが切れているかがわかれば、何を修正すればよいかが明確になります。

カーネルのマルチキャストルーティングテーブルを直接確認する

# アクティブなマルチキャストルート
cat /proc/net/ip_mr_cache

# カーネルに登録されている仮想インターフェイス
cat /proc/net/ip_mr_vif

ip_mr_cacheにアクティブなエントリがあれば、カーネルがトラフィックを転送していることが確認できます。テーブルが空の場合は、igmpproxyがダウンストリームクライアントからのIGMP Joinを受信できていません。ファイアウォールがIGMPをブロックしているか、クライアントがJoinを送信していないことが主な原因です。

igmpproxy vs smcroute

smcrouteは異なるアプローチを取ります。動的なIGMPプロキシングではなく、静的なマルチキャストルートを使用します。マルチキャストグループのリストが固定されていて既知の場合はシンプルですが、チャンネルごとに手動でルートエントリを追加する必要があります。igmpproxyはすべてのIGMPネゴシエーションを自動的に処理します。新しいIPTVデバイスやチャンネルパッケージを追加しても、設定ファイルを変更する必要はありません。

Linuxルーターを長く運用していると、いつかはマルチキャストの問題に直面します。多くの場合、IPTVがその引き金になります。最初から正しく設定するのは確かに難しいですが、igmpproxyを設定してファイアウォールルールを固めてしまえば、後は放っておけます。6ヶ月間、同じ設定ファイルのままです。私が変更したのは、寝室に新しいメディアプレーヤーを追加した際に2つ目のVLANインターフェイスを追加したことだけです。

Share: