トンネリングせずにEC2でMulticast/Broadcast
AWSソリューションアーキテクトの安川 (@thekentiest)です。
前の荒木 (@ar1)の投稿で、トンネル技術を使えばいろんなEC2-ClassicやEC2-VPCでサポートしていないプロトコルでも利用可能であるという話がありました。その投稿でも触れられていたとおり、トンネリングを使えばIP Multicast/BroadcastもEC2インスタンス間でやりとりする(しているかのように上位レイヤのアプリケーションに思わせる)ことができるので、トンネリングを使ってIP Multicast/Broadcastを前提としたアプリケーションやミドルウェアを動かす例も多く見られます。
トンネリングは汎用的で、様々なプロトコルを動作させる目的で使える点は優れています。しかし、通信のオーバーヘッドが伴うのはもとより、特定のノード間でトンネリングをする設定をしないといけないので、その部分が煩雑であったり、トンネルの設定の不備で通信が出来ないなんて事も起こりえます。また、IP Multicast/Broadcastに注目してみると、サーバ側での用途の多くは動的に追加・削除されるノードのディスカバリや疎通確認、メッセージング等だと思うので、IP Multicast/Broadcastのためにトンネルを張るというのは、本来何がしたかったのかという話になることも多いのではないでしょうか。
ここではIP Multicast/Broadcastに特化した方法ですが、EC2上でトンネリングよりも少ないオーバーヘッドとコンフィグでIP Multicast/Broadcastを前提としたソフトウェアを動かす方法をご紹介します。
IP Multicast/BroadcastとEC2で動かない背景
そもそもIP Broadcast / Multicastとは、特別なIPアドレス宛に送られたパケットを、同サブネット全体 / 指定されたマルチキャストグループ宛に送信するというIPレイヤ (L3)の仕組みです。ただし、それが実際にどのように送出されるかは、その下のデータリンクレイヤ (L2)に任せられます。Ethernetの場合は、IP Broadcast / MulticastはEthernetのBroadcastフレームに包まれて送信されます。
すなわち、IPブロードキャストもIPマルチキャストも、Ethernetの上では同じ扱いです。そして、このEthernetのブロードキャストのフレームはEC2では送信されないというのがどちらも使えない理由です。
トンネリングなしでIP Broadcast / Multicastパケットを届ける方法
ここまで読んで、こんな素朴な疑問をお持ちにはならないでしょうか?
「でもユニキャストは送れるんだから、送りたい相手に全部順番に送りつければいいんじゃない?」
その通りです。ここで紹介する方法は、VPC内限定ですが、まさにその通りのことを実現してしまうという内容です。
EC2-Classicでは、L2はブラックボックスになっていて、L2のアドレスを使って宛先制御ということは出来ません。しかし、ENI (Elastic Network Interface)にMACアドレスがあることからお気づきになる通り、VPCではL2でMACアドレスを使ったスイッチングが行われます。
実際、EC2-Classicでarp -aとかたたいてみると、見慣れない出力が返ってくることに気づくと思います。
ところが、VPCに配置されたインスタンスで同じことを行うと、そこには見慣れた光景が広がっています。
ということは、L2で使われるMACアドレスを書き換えることで、L3以上のレイヤに影響を与えずにパケットの宛先をコントロール出来るわけです。
実際にこんな感じのコードを書いて試してみました。
#!/usr/bin/ruby require 'rubygems' require 'packetfu' dev = ARGV[0] mac=`ip link show #{dev} | awk '/ether/ {print $2}'` ARGV.shift dests = ARGV cap = PacketFu::Capture.new( :timeout => 4000, :iface => dev, :start => true, :filter => "ether src #{mac} and ether[0] & 1 = 1") loop do cap.stream.each do |pkt| frame = PacketFu::IPPacket.parse(pkt) dests.each do |dest| frame.eth_daddr = dest frame.to_w(dev) end end end
上記のコードは、標準入力からMACアドレスのリストを受け取り、Ethernetのブロードキャストフレーム(最初のバイトの1ビット目が1)をキャプチャしたら、それを受け取ったMACアドレスのリスト分複製して、宛先をリスト中のそれぞれのMACアドレスに書き換えながら送信するというものです。(上記実装だとIPパケットがフラグメントされた際に問題がおきます。その対策も入れたコードはこちら)
上記のコードは仮説検証のためのもので、効率や実運用に耐えるための考察などを行っていませんが、同様の処理を実現すればEthernetのブロードキャストを動作させられるという確認ができました。すなわち、IP Multicast / BroadcastのパケットをIPアドレスを維持したまま同一VPC Subnet内のインスタンスに届けることが出来るわけです。
実際にこのコードを動作させながら、ブロードキャストpingを送ってみた結果がこちら。
実用を考えると、同一サブネット内のENIのMACアドレスのリストを取得する必要がありますが、これはAWS SDKやCLIを使ってAPIコールをすれば取得できるので、インスタンス追加・削除時にリフレッシュするようなロジックを用意すればよいかと思います。また、上記のコードはパケットキャプチャベースの実装のため、ユーザランドへのパケットコピーが発生したり、プロセスの死活監視が必要になったりしますが、カーネルで同じような処理を実施すればそういった心配はなくなります。(Linux tcを使って同様のパケット操作をする設定例がこちら)
「EC2ではIP Broadcast / Multicastが使えないから〜は出来ない」という文章を見聞きした時にワークアラウンドとして思い出すきっかけになれば幸いです。
おわりに
荒木の投稿でも予告がありましたが、今週土曜日にせまったJAWS DAYS 2014にて、このテクニックをご紹介しつつ、実際にMulticastを利用するアプリケーションを動作させるデモなどを予定しています。ご興味のある方はTrack3: AWS Technical Deep Diveの「マルチキャストがないならユニキャストすればいいじゃない/ほしいプロトコルはカプセルすればいいじゃない」をお楽しみに!
コメント