こんな夜中にOpenFlowでネットワークをプログラミング!

第12回Trema編】動画放送局をIPマルチキャストで作ってみよう!

はじめに

グー⁠ーッモーニング、ベトナー⁠ーー⁠ーーム!

『グッドモーニング、ベトナム』⁠バリー・レヴィンソン)

今回はOpenFlowでミニ放送局気分を味わってみましょう! 動画を配信するしくみの1つにマルチキャストがあります。これはNTT東/西の「ひかりTV」などで実際に使われているしくみで、動画などのデータを多くの視聴者に対して効率的に配信できるという特徴があります。このマルチキャストを使った動画配信をTremaで実現してみます。

観たい人にだけ届けるマルチキャスト

マルチキャストを使った動画配信の動作を簡単に説明しておきましょう図1⁠。動画ストリーミングサーバが配信を始めると、マルチキャストネットワークが視聴者全員にパケットをコピーして転送します。ここでもし、動画を観ない人も含めた全員に動画を届けてしまうと、ネットワークの負荷が非常に大きくなってしまいます。そこでマルチキャストでは、動画を観る視聴者にだけパケットを配信します。

図1 マルチキャストネットワーク
図1 マルチキャストネットワーク

マルチキャストを実現するには、今まで扱ってきたレイヤ2[1]よりも上位のレイヤの情報を使う必要があります。たとえば、テレビのチャンネルに相当するグループアドレスはIPヘッダ(レイヤ3)の中に含まれています。それぞれの視聴者に、観たいチャンネルの動画を届けるためには、このグループアドレスに基づいてパケット転送を行う必要があります。

このように、マルチキャストでは扱うレイヤが増えるため、これまで本連載で紹介してきた内容よりも少し踏み込んだOpenFlowプログラミングが必要となります。もちろん、Tremaでは各レイヤの情報をとても簡単に扱うことができます。

それでは、マルチキャストの詳しいしくみから順に見ていきましょう。

マルチキャストの動作マルチキャスト転送を実現するために、OpenFlowスイッチはどのように動作すればよいでしょうか?

説明を簡単にするため、スイッチ1台の構成を考えましょう図2)。ストリーミングサーバが動画パケットをスイッチに送ると、スイッチは受け取ったパケットをコピーして、受信ホストがつながっているすべてのポートに出力します。

図2 スイッチ1台のマルチキャスト
図2 スイッチ1台のマルチキャスト

では、受信ホストの存在をスイッチはどのようにして知るのでしょうか? 受信ホストは、受信開始時にIGMP(Internet Group Management Protocol)パケットを出して「どの動画を観たいか」をスイッチに伝えます。スイッチは、このIGMPパケットを受け取ったポートに対してのみパケットを送信します。

ポイントは、IGMPを送ったホストの管理方法と、IGMPを送ったホストにだけパケットを届けるしくみです。では、OpenFlowでの実現方法を見ていきましょう。

OpenFlowでのマルチキャスト

ここでも説明を簡単にするため、スイッチ1台の小さなネットワークに、動画を送信するストリーミングサーバ1台と受信するホスト3台を接続した場合を考えます。

視聴者にだけパケットを送る

まずは、視聴者にだけパケットを送るしくみです図3⁠。マルチキャストでは、送信者であるストリーミングサーバはパケットの宛先フィールドにグループアドレス(たとえば239.192.0.1)を含めます。これは「どの視聴者グループに動画を届けるか」を示すグループ名の一種と考えてください。

図3 視聴者にだけパケットを送る
図3 視聴者にだけパケットを送る

コントローラは、packet_inで送られてきたパケットのグループアドレスを参照し、そのグループに参加しているホスト(視聴者)を調べます。そして、視聴者が接続しているポートにパケットを出力するフローをflow_modメッセージでスイッチに書き込みます。これで、以降のパケットは視聴者にだけ転送されます。

グループへの参加と脱退

ホストがグループに参加/脱退するためにはIGMPパケットを使います。ホストがIGMPパケットを送ると、これがpacket_inとしてコントローラに上がります。コントローラは、これがグループへの参加(MEMBERSHIP_REPORT)であるか脱退(LEAVE_GROUP)であるかを判別し、内部的に持っているグループ情報を更新します。このグループ情報は、定期的にフローを更新する際に使います図4⁠。

図4 グループへの参加
図4 グループへの参加

実装

では、さっそくTremaで実装してみましょう。このためのコントローラは、グループを管理するクラス(MFCクラス)とコントローラ本体であるSimpleMulticastクラスから構成されます。

MFCクラス

MFC(Multicast Forwarding Cache)クラスは、グループの一覧と、グループごとにどのホストが接続しているか(ホストが接続しているポート番号で識別します)を管理しますリスト1⁠。

リスト1 MFCクラス(mfc.rb)
require "set"

class MFC
  def initialize
    @db = Hash.new do | hash, key | ← グループの一覧を初期化
      hash[ key ] = Set.new
    end
  end

  def learn group, port ← グループに受信者接続ポートを登録
    members( group ).add( port )
  end

  def remove group, port ← グループからポートを削除
    members( group ).delete( port )
  end

  def members group ← グループの受信ホストが接続するすべてのポートを検索
    @db[ group.to_i ]
  end
en

SimpleMulticastクラス

SimpleMulticastクラスは、コントローラの本体ですリスト2⁠。

リスト2 SimpleMulticastクラス(simple-multicast.rb)
require "mfc"

class SimpleMulticast ← MFCオブジェクトを用意
  end

  def packet_in datapath_id, message
    if message.igmp? ← (1)packet_inしたパケットがIGMPか?
      handle_igmp message
    else
      members = @mfc.members( message.ipv4_daddr ) ← グループのメンバー一覧
      flow_mod datapath_id, members, message
      packet_out datapath_id, members, message
    end
  end

  private ← 以下プライベートメソッド

  def handle_igmp message ← (2)IGMPパケット受信時の処理
    group = message.igmp_group ← IGMPパケット中のグループアドレスの値
    port = message.in_port

    if message.igmp_v2_membership_report?
      @mfc.learn group, port ← グループへ参加
    elsif message.igmp_v2_leave_group?
      @mfc.remove group, port ← グループを脱退
    end
  end

  def flow_mod datapath_id, members, message ← (3)フローを設定
    send_flow_mod_add(
      datapath_id,
      :match => ExactMatch.from( message ),
      :actions => output_actions( members ), ← 視聴者のポートだけに出力
      :hard_timeout => 5 ← flow_modの有効期限は5秒
    )
  end

  def packet_out datapath_id, members, message ← packet_inパケットの転送
    send_packet_out(
      datapath_id,
      :packet_in => message,
      :actions => output_actions( members ) ← 視聴者のいるすべてのポートに出力
    )
  end

  def output_actions members ← 視聴者のいるポートだけに出力するアクション
    members.collect do | each |
      ActionOutput.new( :port => each )
    end
  end
end

packet_in ハンドラ(リスト2(1⁠⁠)ではigmp?メソッドを使い、パケットがIGMPであるかを判定しています。IGMPであった場合、handle_igmpメソッド(リスト2(2⁠⁠)で中身を見て、グループの参加/脱退を行います。このように、Trema::PacketInクラスはパケットの種別や各フィールドを参照するための便利なメソッドをたくさん持っており[2]⁠、パケットの詳細を知らなくても簡単に各レイヤの情報を取り出すことができます。

flow_modメソッド(リスト2(3⁠⁠)では、視聴者だけにパケットを転送するためのフローを設定します。output_actionsメソッドを使って、視聴者の接続するポートに出力するためのアクションの配列を作り、send_flow_mod_addの:actionに指定します。

グループの情報は刻々と変化しますので、フローの有効期限は5秒としています。フローが消えると再びマルチキャストパケットがpacket_inでコントローラまで上がってきますので、その時点でのグループ情報を使って新しくフローを張りなおします。

実行してみよう

さっそく動かしてみましょう! 構成としてストリーミングサーバ1台、受信ホスト2台に加え、コントローラを動作させるホストの合計4台を使います図5⁠。

図5 動作構成
図5 動作構成

まず、TremaとOpenFlowスイッチの準備をしましょう。リスト3のネットワーク設定ファイルを用意します。

リスト3 マルチキャスト用スイッチ1台をeth0、eth1、eth2に接続(network.conf)
vswitch ( "msw" ) {
  datapath_id "0xabc"
}

link "msw", "eth0"
link "msw", "eth1"
link "msw", "eth2"

今回作成したsimple-multicast.rb、mfc.rbとコンフィグレーションファイルを、tremaコマンドと同じディレクトリに置いてください。次のように今回作成したコントローラを実行します。

% ./trema run ./simple-multicast.rb -c ./network.conf

マルチキャストの送受信を行うアプリケーションとしては、VLC[3]を使います。ストリーミングサーバと受信ホストの両方に、次のようにVLCをインストールしてください。

% sudo apt-get install vlc

それでは、ストリーミングサーバの設定をします。前もって、配信したい動画ファイルを携帯やデジカメで撮影しておいてください(movie.mpg⁠⁠。次のコマンドで、マルチキャストを使った動画のストリーミングを開始します[4]⁠。

% cvlc ./movie.mpg --sout udp://239.192.0.1:60000 
--miface eth0 --loop --mtu 1472 -d

次に、2台の受信ホストで動画視聴の準備をしましょう。まず、次のコマンドでIGMP Version 2[5]を使うように各ホストの設定を変更しておいてください。

% sudo sysctl -w net.ipv4.conf.all.force_igmp_version=2

次に、VLCを起動し、メニューから「ネットワークストリームを開く」を選択します。ダイアログボックスに、⁠udp://@239.192.0.1:60000」と入力してください図6⁠。

図6 マルチキャスト受信の設定
図6 マルチキャスト受信の設定

以上を行うと、2台の受信ホストで、図7のように動画が同じタイミングで再生されるはずです。うまく再生ができたでしょうか?

図7 マルチキャストで配信された動画の表示

図7 マルチキャストで配信された動画の表示 図7 マルチキャストで配信された動画の表示

まとめ

今回は、マルチキャスト転送を実現するコントローラを作りました。学んだことは、次の2点です。

  • packet_inメッセージの中のパケットの各フィールドの値を参照する方法を学びました。Trema::PacketInクラスに用意されているigmp_typeなどのアクセサメソッドを使うことで、いろいろなレイヤの値を簡単に取り出すことができます。
  • マルチキャスト転送を実現するアクションの書き方を学びました。複数のアクションを指定することで、複数のポートに複製したパケットの出力ができます。

次回は、いよいよ最終回です。Tremaについてより深く知ってもらうために、Tremaの中身、アーキテクチャについて紹介します。

おすすめ記事

記事・ニュース一覧