はじめに
グーーッモーニング、ベトナーーーーーム!
『グッドモーニング、ベトナム』( バリー・レヴィンソン)
今回はOpenFlowでミニ放送局気分を味わってみましょう! 動画を配信するしくみの1つにマルチキャストがあります。これはNTT東/西の「ひかりTV」などで実際に使われているしくみで、動画などのデータを多くの視聴者に対して効率的に配信できるという特徴があります。このマルチキャストを使った動画配信をTremaで実現してみます。
観たい人にだけ届けるマルチキャスト
マルチキャストを使った動画配信の動作を簡単に説明しておきましょう(図1 ) 。動画ストリーミングサーバが配信を始めると、マルチキャストネットワークが視聴者全員にパケットをコピーして転送します。ここでもし、動画を観ない人も含めた全員に動画を届けてしまうと、ネットワークの負荷が非常に大きくなってしまいます。そこでマルチキャストでは、動画を観る視聴者にだけパケットを配信します。
図1 マルチキャストネットワーク
マルチキャストを実現するには、今まで扱ってきたレイヤ2[1] よりも上位のレイヤの情報を使う必要があります。たとえば、テレビのチャンネルに相当するグループアドレスはIPヘッダ(レイヤ3)の中に含まれています。それぞれの視聴者に、観たいチャンネルの動画を届けるためには、このグループアドレスに基づいてパケット転送を行う必要があります。
このように、マルチキャストでは扱うレイヤが増えるため、これまで本連載で紹介してきた内容よりも少し踏み込んだOpenFlowプログラミングが必要となります。もちろん、Tremaでは各レイヤの情報をとても簡単に扱うことができます。
それでは、マルチキャストの詳しいしくみから順に見ていきましょう。
マルチキャストの動作マルチキャスト転送を実現するために、OpenFlowスイッチはどのように動作すればよいでしょうか?
説明を簡単にするため、スイッチ1台の構成を考えましょう(図2 )。ストリーミングサーバが動画パケットをスイッチに送ると、スイッチは受け取ったパケットをコピーして、受信ホストがつながっているすべてのポートに出力します。
図2 スイッチ1台のマルチキャスト
では、受信ホストの存在をスイッチはどのようにして知るのでしょうか? 受信ホストは、受信開始時にIGMP(Internet Group Management Protocol)パケットを出して「どの動画を観たいか」をスイッチに伝えます。スイッチは、このIGMPパケットを受け取ったポートに対してのみパケットを送信します。
ポイントは、IGMPを送ったホストの管理方法と、IGMPを送ったホストにだけパケットを届けるしくみです。では、OpenFlowでの実現方法を見ていきましょう。
OpenFlowでのマルチキャスト
ここでも説明を簡単にするため、スイッチ1台の小さなネットワークに、動画を送信するストリーミングサーバ1台と受信するホスト3台を接続した場合を考えます。
視聴者にだけパケットを送る
まずは、視聴者にだけパケットを送るしくみです(図3 ) 。マルチキャストでは、送信者であるストリーミングサーバはパケットの宛先フィールドにグループアドレス(たとえば239.192.0.1)を含めます。これは「どの視聴者グループに動画を届けるか」を示すグループ名の一種と考えてください。
図3 視聴者にだけパケットを送る
コントローラは、packet_inで送られてきたパケットのグループアドレスを参照し、そのグループに参加しているホスト(視聴者)を調べます。そして、視聴者が接続しているポートにパケットを出力するフローをflow_modメッセージでスイッチに書き込みます。これで、以降のパケットは視聴者にだけ転送されます。
グループへの参加と脱退
ホストがグループに参加/脱退するためにはIGMPパケットを使います。ホストがIGMPパケットを送ると、これがpacket_inとしてコントローラに上がります。コントローラは、これがグループへの参加(MEMBERSHIP_REPORT)であるか脱退(LEAVE_GROUP)であるかを判別し、内部的に持っているグループ情報を更新します。このグループ情報は、定期的にフローを更新する際に使います(図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?
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
group = message.igmp_group
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
send_flow_mod_add(
datapath_id,
:match => ExactMatch.from( message ),
:actions => output_actions( members ),
:hard_timeout => 5
)
end
def packet_out datapath_id, members, message
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でコントローラまで上がってきますので、その時点でのグループ情報を使って新しくフローを張りなおします。
[2] どのようなメソッドが用意されているかは、「 Trema RubyAPIドキュメント 」中のTrema::PacketInクラスの項を参照してください。このドキュメントは、「 trema ruby」コマンドを実行することでも表示できます。
実行してみよう
さっそく動かしてみましょう! 構成としてストリーミングサーバ1台、受信ホスト2台に加え、コントローラを動作させるホストの合計4台を使います(図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 マルチキャスト受信の設定
以上を行うと、2台の受信ホストで、図7 のように動画が同じタイミングで再生されるはずです。うまく再生ができたでしょうか?
図7 マルチキャストで配信された動画の表示
まとめ
今回は、マルチキャスト転送を実現するコントローラを作りました。学んだことは、次の2点です。
packet_inメッセージの中のパケットの各フィールドの値を参照する方法を学びました。Trema::PacketInクラスに用意されているigmp_typeなどのアクセサメソッドを使うことで、いろいろなレイヤの値を簡単に取り出すことができます。
マルチキャスト転送を実現するアクションの書き方を学びました。複数のアクションを指定することで、複数のポートに複製したパケットの出力ができます。
次回は、いよいよ最終回です。Tremaについてより深く知ってもらうために、Tremaの中身、アーキテクチャについて紹介します。
[友太郎の質問]もっと大きなネットワークでマルチキャストしたい!
Q.「ぼくの撮った動画をもっとたくさんの人に観てもらいたいな! 今回はスイッチ1つだけだったけど、もっとたくさんのスイッチを使ったマルチキャストはどうするの?」
A.今回紹介したSimpleMulticastでは、スイッチを1台しか扱えません。しかし、実際に大勢のユーザに動画を配信するネットワークは、多くのスイッチで構成されています。このようなネットワークでマルチキャスト転送をするためには、たとえば図A のようにパケットが流れるよう、フローを設定しなければいけません。
このフロー設定のためには、ストリーミングサーバがつながっているスイッチAから、受信ホストがつながっているスイッチDまでの最短パスを計算します。その結果、A→B→Dが最短パスとわかるので、このパス上の各スイッチにフローを設定します。受信ホストはスイッチCにもつながっているので、こちらにも同様に最短パスの計算とフロー設定を行います。
最短パスを計算するためには、スイッチどうしがどのように接続しているかを知る必要があります。そのためのモジュールとして、Trema Apps中にTopologyモジュールが用意されています。また、最短パスの計算には、Trema Apps中にあるRouting Switchに同梱されているlibpathresolverというライブラリが参考になるでしょう。
図A 複数スイッチによるマルチキャスト