EC2でマルチキャスト(IPフラグメント対応)

なんかこの話題が続き過ぎな気もしていますが、EC2でマルチキャストの続きです。

というのも、MTUを超えるサイズのパケットを↑の方法で擬似マルチキャストすると、1台のインスタンスにしか届かないよー、というフィードバックを頂いたのでした。

  • MTUを超えるとダメになる –> すなわちIPフラグメント周りで何か起きている
  • 擬似マルチキャストでは同じIPパケットを何度も投げている

ということから当たりをつけるとするとやはりIPパケットのID周りかなと思って、パケットIDを変えながら送り出すというようなことをやってみました。

(IPパケットにはそれぞれIDがついていて、分割されたときには各フラグメントのIDを見ながら再結合するようになっています。)

このコードで試すと、MTU越えなパケットで、フラグメントされたとしても期待通り複数ホストで受信できる事が分かりました。

 

変更分は単純で、パケットIDを複製する度に1つずつずらしているのと、それに伴ってチェックサムを再計算しているくらいです。

ずらし方は一定なので、パケットが分割されてもフラグメント同士は同一のIDを持ち続けるので関係性は保たれつつ、複製ごとに異なるIDを持つというわけです。

(こんなずらし方で直近のパケットとあたる可能性が十分低いのかは要チェック)

 

IPレイヤではパケットのIDを見比べてフラグメントされたパケットを再結合するので、複数同じIDのフラグメントが通過すると、どうやら下のネットワークレイヤでこの再結合がうまくいかなくなるようです。

これがフラグメントなしだと通るけど、フラグメントされると通らない原因と言えそうです。

試しながら気づいたこととしては、送信側と受信側でフラグメントの切れ目が違うので、恐らく下のネットワークで再結合と再分割を行なっているようです。

IPヘッダをいじるとチェックサムを再計算しないといけないのがいやですね。

出来ればL2だけで済ませたかった。。。

 

そして実はユーザランドのプログラムに頼らずにカーネルだけで処理を完結する方法を編み出していたのですが、この問題に対応する為にもうちょっといくつか試さないといけなそうです。

その方法についてはまた近いうちに。

EC2-VPCでVRRPを動かしてみた

これまで何度かフレームコピーと送信先MACアドレスの付け替えで擬似的にVPCサブネット内でブロードキャストを実現する話を書いてきました。

今日はそれの応用編ということで、いつもマルチキャストが使えないからと素通りされがちだったけど、本当はみんな大好きなVRRPを動かしてみたという話を書きたいと思います。

VRRPとは?

VRRP (Virtual Router Redundancy Protocol)とは、仮想IPアドレスを使ったL3ノードの冗長化のためのプロトコルです。ゲートウェイルータやロードバランサなど、複数ノードのトラヒック(すいません、IEICEとかに論文を書いていたときのくせで)が通過するIPノードを冗長化するために利用されたりします。

ある仮想IPアドレス(Virtual IP、略してVIP)でサービスを行うノード同士でVRRPグループを構成し、グループ内のノードはMasterとBackupの関係を構築します。 MasterとなったノードはVIP宛のパケットを受信したり転送したりするとともに、VRRPグループ内のノードにIPマルチキャストでハートビートを送ります。何らかの障害でMasterからのハートビートが途絶えた場合はBackupノードがVIPを引き継いで何事もなかったかのようにシステムを継続動作させるというわけです。

そんなわけでマルチキャストが前提なプロトコルなものですから、IPマルチキャスト/ブロードキャストが使えないことで知られるEC2環境では使えないものとされることが多かったかと思います。 冗長化が当然な可用性要件が厳しいシステムであっても、です。

そこに多少のオーバーヘッドはあるとはいえ、Ethernetブロードキャストが通るようにしてVRRPを動かすことが出来れば システム設計の際の選択肢を増やすことが出来ます。 なので早速やってみましょう。

検証システムの構成とVRRPの動作確認

検証用のシステム構成としては下図のようにしました。 単一のVPCサブネットの中にLVS + Keepalivedを動かしたインスタンスを2台、その背後に静的なファイルを返すWebサーバ(nginx)を並べています。 前段のLVSサーバ達には擬似ブロードキャストの設定をしておいて、IPマルチキャストでお互いコミュニケーション可能にした上で、KeepalivedでVRRPグループを組みます。

MasterインスタンスにはSecondary IPアドレスとしてVIPを割り当て、そのVIPに対してElastic IP(EC2で利用できる固定グローバル化IPアドレス)を割り当てました。 これにより、平常時は割り当てたElastic IPに対してパケットを送ると、図の通りにリクエストが流れて行きます。

VRRP-normal

ここでMasterとなっているLVSインスタンスが停止すると、フェイルオーバが起きて、Backupが新たにMasterとなります。 実際にMasterのインスタンスを停止させると、期待通りBackupがMasterに昇格することがログなどで確認できました。

EC2-VPC特有の設定

一般的なEthernetの場合は、VRRPでMasterに昇格した時点でVIPとMACアドレスの対応の更新がGratuitous ARPリクエストによってサブネット内に周知されるので、特に何もせずともVIP宛のパケットが新マスターに送られるようになります。(それ以降に参加したノードは通常通りARPリクエストを送って、新マスターがそれに応答することで更新後のVIP-MACアドレスの対応関係を知ることになります)

しかし、EC2-VPCではARPを元にIPアドレスとMACアドレスを対応付けているわけではないので、単にLVS + Keepalivedの設定をいつも通りしただけでは、VIPの付け替えが起こらず、フェイルオーバした時点でシステムとしては応答しなくなります。 VIPを新マスターのネットワークインターフェースとの紐付けを明示的にVPCに対して設定する必要があります。

フェイルオーバ時の目指すべき状態を図示すると次の図のようになります。

VRRP-failover

上記のようにVIPとネットワークインターフェースのマッピングを変えるのはAWSのAPIをたたくことで可能です。 例えば下記のようなスクリプトを新マスターで実行することで、VIPとネットワークインターフェースのマッピングを更新して、VIP宛のパケットを新マスターに向けることができます。

上記のような内容のスクリプトをVRRPでマスターに昇格した時点で実行するように設定しておけば、期待通りフェイルオーバしてVIP宛のパケットを新マスターが受け取るようになります。 Keepalivedの場合の設定例がこちら(末尾のnotify_masterでMaster昇格時の実行スクリプトを指定している)。

vrrp_instance VI_1 {
  state BACKUP
  interface eth1
  virtual_router_id 51
  priority 100
  advert_int 1

  authentication {
    auth_type PASS
    auth_pass 1111
  }

  virtual_ipaddress {
    172.31.24.1 dev eth0
  }

  notify_master /etc/keepalived/assign_vip.sh
}

実際にこれでフェイルオーバを試してみました。 実行すると、APIでVPCの設定を書き換えるリクエストを投げて、それが反映されるまでというタイムラグがあるので、ARPを使った実ネットワークでのフェイルオーバに比べればダウンタイムが長くなってしまいますが、数秒といった程度で障害検知からフェイルオーバまで出来るのは魅力的かと思います。

まとめ

というわけで、無事EC2-VPCでVRRPが動いて、フェイルオーバが出来ることまで確認できました。

ロードバランサやファイアウォールなど、VRRPでの冗長化が前提となっているソフトウェアをAWSで使う際にはこんな方法も検討してみてはいかがでしょうか?

ほんとはこの構成のCloudFormationテンプレートを作ろうかと思っていたのですが、時間の都合でそこまでたどり着いてません。ご要望あれば作りますのでお寄せください(笑)

VPCサブネットでNeighbour Discovery

以前、EC2でブロードキャストができるようにするという記事を書きました。
EC2-VPCならL2のアドレスを書き換えてL3以上にタッチせずにフレームの宛先を変えられるので、それを利用して受信ノード数分フレームをコピーしてユニキャストで送るというトリックでEthernetブロードキャストを動かし、結果としてサブネット内のIPマルチキャスト・ブロードキャストを実現するという話です。

今更ながら、図に起こしたので貼っておきます。
ec2broadcast

となると、Ethernetブロードキャストをするには、まず同一サブネット内の他インスタンス(厳密に言うとそれにアタッチされたENI)のMACアドレスのリストが必要です。
通常のネットワークであれば、all IPホストグループ(224.0.0.1やff02::1など)にpingでも打てば良さそうなものですが、そもそもそのマルチキャストができないのだから本末転倒です。

なのでここはEC2に特化したネットワーク情報の取得方法を取ることにしましょう。
AWS上の操作や必要な情報の取得はAPIを通じて可能です。
サブネットに関する情報ももちろん取得可能。

例えば下記のようなスクリプトを動かせば、同一サブネットに接続されて、インスタンスにアタッチされているネットワークインターフェース(ENI)のリストを取得することができます。(Linux前提ですが)
これの出力を先日の擬似ブロードキャストを実現するスクリプトの引数として与えるようなスクリプトを用意すればMACアドレスを調べてわざわざ渡す必要なくブロードキャストが実現できます。

上記スクリプトは、

  1. 第一引数で受け取ったインターフェースが属するVPCサブネットのIDを取得
  2. 自分(インスタンス)が属するリージョンを取得
  3. describe-network-interfacesを実行して、VPCサブネットのID、インスタンスにアタッチされているかどうかでフィルタ

という感じのことをしています。

実際に使うときにはこれをたくさんのホストが実行するのは非効率なので、API取得結果をキャッシュするような仕組みを別途用意しておくのが吉かと思います。
例えばdescribe-network-interfacesの結果をDynamoDBに入れておいて、各インスタンスはそこに対して自分のサブネットの情報をQueryするとか。

Stereo Mixがない! — Virtual Audio Streamingを使って何とか事なきを得る 

昔からSound Mixerを見れば必ず当たり前のようにあったものが、Windows 7の入った手元のLenovo X220に見当たらない事に気づきました。

Stereo Mixがない!!

あるのが当たり前だったので、動画を再生してその音声をキャプチャして~、なんて普通にStereo Mixをインプットに指定して録音ソフトを動かせばいいじゃないと思ってました。

でもそれがないんです。

 

とはいえ、なきゃないで巷にはいくらでもソリューションがあるだろうと思って探してみたものの、Google様に導かれて見つかるのは同じように驚き困り果てる人々の姿のみ。

どうやらWindows 7からオーディオ周りのAPIが変わって、Stereo Mixのサポートをやめたサウンドカードが多いんだとか。

(すべてのサウンドカードでダメというわけではないようです。場合によっては古いドライバを入れればStereo Mixを有効化出来る場合もあるんだとか。)

 

結局手持ちのPCに適用出来る方法が簡単に見つかりそうにはなかったので、Virtual Audio Streaming というソフトウェアを試すことにしました。

Virtual Audio Streamingは正にこのWindows 7のStereo Mixがない問題を解決してくれるソフトウェアで、インストールすると仮想のオーディオ再生デバイスと、オーディオ録音デバイスが作られます。

この仮想デバイスを通すことで、PCから出る任意のサウンドをキャプチャ出来るという仕組みです。

Unixのパイプで入出力つないでいく感じをイメージすると近いですかね。

virtualaudiostreaming

 

典型的な利用例としては:

  • 録音したい音声を出力するソフトウェアの再生デバイスとしてVirtual Audio Streamingの仮想再生デバイスを設定
  • 音声を録音するソフトウェアの録音元デバイスとしてVirtual Audio Streamingの仮想録音デバイスを設定

という感じです。

とはいえ、特定のデバイスを指定できるソフトウェアばかりではないかと思うので、そういう場合にはWindowsのデフォルトの再生デバイスとしてVirtual Audio Streamingのデバイスを設定するのが簡単かと思います。

下記はそのような設定の例。

virtualOutputdevice

録音側も設定できなければデフォルトに設定してしまうことも可能です。

virtualInputdevice

これでひとまず任意の音声出力の録音が出来るようになりました。

なんかWindowsって出来る事がバージョンが上がるにつれて減っていくような。。。

 

Couchbaseの起動からクラスタ設定まで

CouchbaseはCouchDBの作者であるDamien Katz氏が始めたCouchOne社と、Membaseを提供するMembase社が合併して出来たCouchbase社が提供するオープンソースのNoSQL DBです。

てっきりCouchDBの延長線上だと思っていたのですが、実際には新たに起こされた実装で、CouchDBの中でパフォーマンスのネックとなっていた部分をCやC++で書き直したり、Membaseとの統合でIn-memory DBと同等の応答性能を提供したり、非常に運用しやすいピアベースのクラスタリングをサポートしたりと、非常にモダンなNoSQL DBとのこと。

知ったきっかけはラスベガスで行われたAWS re:Inventの展示ブースだったのですが、その後日本国内で開かれたCouchconfに参加して同社のエンジニアと話したりする中でどんどん親しみが湧いてきて、早速試してみたくなりました。

日本での知名度はまだまだあまり高くなさそうだし、日本語の情報もまだまだ少なそうなので、知名度向上の一助になればという思いも含め、書いています。

インストールについて

Couchbaseのインストールですが、特に難しいことは無いと思います。

本家のページでdebパッケージやrpmパッケージが提供されてるし、Downloadページに行けばMac OS向けのダウンロードリンクが表示されたりするので楽々感たっぷり。

でもせっかくなのでクラスタリング等試したいですし、それを考えるとパッケージ入れたノードをいくつも作るのはめんどくさいですし、どのポートを開けないと動かないとかとりあえず試すだけなのに考えるのはいやです。

そこでありそうだな〜と思いつつ、AWS Marketplaceでおもむろにcouchbaseとたたいてみました。

ああ、あるある。

無料でインストール済みのEC2インスタンスが1-clickで立ち上げられるというわけです。

せっかくなのでこれで立ち上げましょう。

AWS Marketplace からの起動

Screen Shot 2013-03-24 at 7.02.21 PM

一番上のCommunity Editionならライセンス費はかからない模様。

それを選んで進みます。

Screen Shot 2013-03-24 at 7.04.15 PM

Continueをクリックして次の画面へ。

Screen Shot 2013-03-24 at 7.06.05 PM

リージョンにTokyo、インスタンスタイプにm1.mediumを選んで”Launch with 1-Click”。
(できればt1.microで行きたかったのですが、どうもt1.microだとサーバは立ち上がるものの、セットアップ途中からスペック不足らしく、まともに動作しません。m1.smallが選べればちょうどいいのですが。Couchbase folks, why don’t you enable m1.small instead of t1.micro which cannot run Couchbase well? The next choice is m1.medium and that is kind of expensive to just give it a try…)

これだけでセットアップ済みのCouchbaseノードが立ち上がります。

嬉しいのはセキュリティグループ(EC2インスタンスに設定するファイアウォールのテンプレートのようなもの)もCouchbaseにあわせて最初から設定されているので、必要なポート番号について悩む必要もなく、ポート全通しのような暴挙に出る必要もなく、サーバが立ち上げられます。(昔は1つソフトウェアを試すだけで大変でしたが、いい時代になったものですね。。)

下記のような画面が出れば完了です。

Screen Shot 2013-03-24 at 7.13.26 PM

上記画面にはこの後のインストラクションなどが書いてあってとっても親切。

でもまあ読むよりも試したいという方も多いと思いますので(自分含め)とりあえず真ん中くらいの”Your Software”と書いたリンクをクリックしましょう。

そして出てくる下記のような画面で、”Access Software”を選びます。

Screen Shot 2013-03-24 at 7.14.24 PM

すると何と言うことでしょう!

既にCouchbaseのWebコンソールの中です!

繰り返しになりますが、いい時代になったものですねぇ。

Couchbaseのコンソールでサンプルデータの投入と初めてのクラスタ設定

 

Screen Shot 2013-03-24 at 7.16.28 PM

 

早速ログインしてみましょう。

Username: Administrator

Password: <起動したEC2インスタンスのID>

パスワードはわかりにくいので要注意です。

起動したEC2インスタンスのIDを調べて入力して下さい。1つ手前のページに出ているi-XXXXXXXというやつがそれです。

わからなければAWSのマネージメントコンソールで確認しましょう。

Screen Shot 2013-03-24 at 11.52.48 PM

 

ログインすればもうそこはCouchbasenの管理用GUI。

このコンソールを使えばデータ用のバケットを作成したり、クラスタ内の各ノードのステータスを管理したり、Viewを管理したり、クラスタにノードを追加したり、何でも出来ます。

リージョンを越えて地球の裏側のノードにレプリケーションを行うことだって出来ちゃいます(Cross Data Center Replication, XDCR)。

いろいろクエリを試す為のサンプルバケットを追加することもコンソールから簡単に出来ます。

Settings –> Sample Buckets と選び、beer-sampleやgamesim-sampleにチェックを入れて、Createを押せばサンプルデータが読み込まれたバケットが出来上がります。

Screen Shot 2013-03-24 at 11.50.48 PM

試しにもう1つ同じようにインスタンスを追加して、そのインスタンスと2ノードでクラスタを組んでみましょう。

まずは同じ手順でもう1つインスタンスを起動。

Screen Shot 2013-03-24 at 11.48.40 PM

Couchbaseのコンソールに戻り、”Server Nodes”のタブに入り、”Add Servers”のボタンを押します。

クラスタに足すべきノードのホスト名あるいはIPアドレスと、Username, Passwordを聞かれるので入力します。

(クラスタを組むにはTCP 21100番ポートと通信出来る必要があるので、セキュリティグループを調整しておきましょう)

Screen Shot 2013-03-25 at 12.03.25 AM

 

これだけで何と2台のノードを含むクラスタの設定が完了です。

Screen Shot 2013-03-25 at 12.13.22 AM

後はRebalanceが行われれば2台で負荷を分散し合う形になります。

下記の図はRebalancingを手動でキックした際の様子。

Screen Shot 2013-03-25 at 12.18.37 AM

なお、Couchbaseのクラスタのノード間にはマスタ/スレーブの関係はなく、すべてのノードが同等のピアになっています。

そのため、どのノードにログインしてもクラスタのマネージメントが出来ます。

クライアントからはライブラリにクラスタのノードを渡しておけば、後はうまくやってくれるというわけです。

すばらしい!

ドキュメント指向のDBとして利用したり、Memcachedプロトコルもサポートしているので、キャッシュの置き換えとして利用したり、いろいろ使えそうですね!

 

EC2でブロードキャスト/マルチキャスト

EC2は非常に自由度が高いので、好みのOSを使って自由にアプリケーションやミドルウェアを利用可能ですが、レイヤが下がれば下がるほど、物理サーバとは環境が違うことを実感する場面が出てきます。

その1つがIPブロードキャスト、IPマルチキャストが使えないことかと思います。

(そうなんです。ユニキャストのみなんです。VPCでサブネットを区切ったとしても、その中でブロードキャストやマルチキャストが出来るわけではないんです。結構驚かれる方も多いところです)

クラウド環境という物理的なリソースを共有する環境でブロードキャストが出来てしまうと、他のユーザに影響を与える可能性が出てくるのである意味出来ないのは納得です。

そのため、ブロードキャストやマルチキャストを使ったDiscoveryプロトコルや、Keepaliveプロトコル等々はそのままでは使えません。

多くの方はそれを考慮して使うソリューションを選んでいるかと思いますが、ちょっと工夫すればメッセージング程度の利用には耐えるようなブロードキャストをEC2でも用いることが出来ます。

それが本日のお題です。

そもそもIPブロードキャストとIPマルチキャストって

IPのブロードキャストやマルチキャストは特別なIPアドレス宛に送られたパケットをサブネット全体あるいは指定されたマルチキャストグループ宛に送られますが、L2がEthernetの場合は、IPブロードキャスト・IPマルチキャスト共に、Ethernetのブロードキャストフレームに包まれて送信されます。

すなわち、IPブロードキャストもIPマルチキャストも、Ethernetの上では同じ扱いです。

で、このEthernetのブロードキャストのフレームはEC2では送信されないというのがどちらも使えない理由です。

「でもユニキャストは送れるんだから、送りたい相手に全部順番に送りつければいいんじゃない?」

まさにその通り。それをやってしまって、IPブロードキャスト・IPマルチキャストをEC2上で出来るようにしてしまおうというわけです。

L2でごにょごにょする

前のエントリ「コピー&リダイレクトパターン(仮)実装編」でちょっと触れましたが、EC2-Classic (VPCの外でEC2を使うことをこう呼ぶようになりました)だと、L2はブラックボックスになっていて、L2のアドレスを使って宛先制御という概念がありません。

arp -aとかたたいてみると、見慣れない出力が返ってくることに気づくと思います。

ec2classic-arp

fe:ff:ff:ff:ff:ffという普通じゃないMACアドレス宛にすべてのフレームが送られる形となり、下でハイパーバイザーによってL3のヘッダを見ながらルーティングがされていくものと思われます。

ところが、EC2-VPC(VPC内でEC2を使うことをこう呼ぶようになりました)の場合、ENI (Elastic Network Interface)があることからも推測される通り、この辺りの仕様が違っていて、ENIにつくMACアドレスを使ってパケットのスイッチングが仮想的になされるようになっています。

arp -aの出力を見てみると、そこには見慣れた光景が広がっています。

ec2-vpc

と、いうことは、L2のアドレスを使ってごにょごにょすることで、パケットの宛先をコントロール出来るわけです。

すなわち、ebtablesでEthernetレベルのNATをしたり、DSR (Direct Server Return)なロードバランサを作ったり、うまくやればVRRPみたいなのも動くかも、という感じです。

本題のブロードキャストについても、パケットを複製して、同一サブネット上のアドレスに対してそれぞれ送りつけるようなコードを書けば出来る、という話になります。

というわけでやってみた

こんな感じのコードを書いてみました。

上記のコードは、標準入力からMACアドレスのリストを受け取り、Ethernetのブロードキャストフレーム(最初のバイトの1ビット目が1)をキャプチャしたら、それを受け取ったMACアドレスのリスト分複製して、宛先をリスト中のそれぞれのMACアドレスに書き換えながら送信するというものです。

たったこれだけのコードでEthernetのブロードキャストを動作させられます。

実際にこのコードを動作させながら、ブロードキャストpingを送ってみた結果がこちら。
Screen Shot 2013-03-24 at 6.29.34 PM

実際にはAWS SDKを使いながら、同一サブネット上のENIのリストを取得して、そのMACアドレスのリストに対して、上記のようなコードを実行すれば特に手で設定することなく利用可能です。さらに、そんなコードをsupervisordで管理しながら実行すれば結構実用的になるんじゃないかと思ってます。もちろん、宛先分パケットを送り直すので、大量のブロードキャストメッセージが発生するユースケースでは無理が出てくるでしょうが、メッセージングに用いるだけならいいんじゃないかと。

そのうちこれを使いながら何か動かしたよ〜という記事を書こうと思います。

コピー&リダイレクトパターン(仮)実装編

先日「EC2でパケットをミラーリングしたい時ー」に書いたEC2インスタンスでパケットをキャプチャして別のコレクションポイントに送る構成を実装する方法について書いてみたいと思います。

インバウンドのパケットだけコピー&リダイレクト出来ればいい場合

まず、EC2インスタンスに入ってくるインバウンドのパケットだけをミラー&リダイレクトするのでしたら話は簡単ですので、この場合について書いておきます。

この場合であれば、iptablesのteeというターゲットを使えば簡単に実行出来ます。

例えば、下記のようにすればTCPの80番ポートで受信されるパケットのコピーを作って指定したIPアドレスに宛先を付け替えて送り出すことが出来ます。


sudo iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TEE --gateway <IP of HOST B>

ちなみに、インバウンドのパケットだけで良ければと註釈したのにはわけがあります。

次の図を見ればわかるかと思いますが、teeを使った場合、宛先IPアドレスを書き換えてしまうので、アウトバウンドのパケットにこれを適用すると、リクエスト元のIPアドレスがパケットから失われてしまい、アクセス解析等を行いたい場合には使えない内容になってしまうのです。
(黄色がクライアントのアドレスだとすると、それがコレクタには届かない)

iptables-tee

両方向のパケットを宛先と送信元含めてキャプチャしたい場合はteeでは役不足となります。

別の方法としては、パケットのコピーを作った後、L2で宛先を書き換えてコレクタに送るという手があるかと思います。

この場合、上記の問題は起きませんが、コレクタとキャプチャポイントが同一サブネットにいる必要があること、という条件が付きます。

サブネットが異なれば当然L2フレームを届けることは出来ませんので。

また、EC2-Classicではそもそも出来ません。

なぜならL2はインスタンスからは見えないブラックボックスとなっている為です。

はarpコマンドの出力例。fe:ff:ff:ff:ff:ffというアドレスがプロキシーARPのようになっていて、そこにすべてのパケットが送られるようになっているのですね。

ec2classic-arp

L3でトンネルしてコレクションポイントに集める

 サブネット越えもしつつ、送信先・送信元アドレスを残したままパケットを別の宛先に送りたいとすると、やはりトンネルしかなくなってきます。

とはいえ、オーバーヘッドは最小限にしたいですし、出来れば特別なソフトウェアを使うのも避けたい、カーネルに処理をさせたい、等々考えると自ずと選択肢は狭まってきます。

例えばここで取り上げるIP-in-IPは上記のようなポイントを考慮すると1つよい選択肢だろうと思います。

その名の通り、IPパケットを別のIPパケットのペイロードとして送る仕組みです(RFC1853)。

これを使えば、次の図のようにうまいことコレクションポイントにパケットを集めることが出来ますし、キャプチャポイントにもコレクションポイントにも特別なソフトウェアは必要ありません。

また、一方向の転送しかしないことを活かして、複数あるキャプチャポイント側のトンネルエンドポイントにすべて同じIPアドレスを指定すれば、コレクションポイント側では単一のトンネルインターフェースで全パケットを受け付けることが出来ます。(キャプチャポイントごとにトンネルインターフェースを作る必要がない)

ip-in-ip

tcのmirred(mirror & redirect)アクションを使えば特定のパケットを指定したインターフェースに再送出来るので、トンネルインターフェースを作った後はその設定を追加すればいけるはず。
設定例は次のような感じ。

キャプチャポイント側(Webサーバなど)の設定


#トンネルインターフェースを作成

ip tunnel add ipip0 mode ipip remote 192.168.1.10 local 192.168.1.20 ttl 64

#受信したパケットをトンネルインターフェースに流す設定
tc qdisc add dev eth0 ingress

tc filter add dev eth0 parent ffff: prio 10 protocol ip u32 match ip protocol 6 0xff match ip dport 80 0xffff flowid 10:1 action mirred egress mirror dev ipip0

#送信するパケットをトンネルインターフェースに流す設定

tc qdisc add dev eth0 root handle 10: prio

tc filter add dev eth0 parent 10: prio 10 protocol ip u32 match ip protocol 6 0xff match ip sport 80 0xffff flowid 10:1 action mirred egress mirror dev ipip0

コレクションポイント側の設定


ip tunnel add ipip0 mode ipip remote 192.168.1.20 local 192.168.1.10 ttl 64

上記設定をしていざ動かすと、、、なんとOSごとハングアップ orz…

どうも送信するパケットをトンネルインターフェースに流そうとすると逝ってしまわれるようで、受信したパケットのみを流す(上記設定の前半)分に関しては特に問題なし。
また、トンネルインターフェースでないネットワークインターフェース(eth1やlo)に流すと、受信・送信パケットともに問題なく流れます。

EC2インスタンスでも物理PCでも試したところ同じ現象が起きるので、どうやらLinuxカーネルが原因っぽい。

パケットがループしているということも考えたけど、それにしては受信側に1つもパケットが流れてこない不思議。

とりあえずトンネルインターフェースから出るパケットが再びeth0を通らないような設定にするとOSがハングアップしないことを確認。

ひとまずこれでパケットをキャプチャ出来るのでしてみることに。

そして見つけたのがこれだ、1, 2, 3.

capture

こちら、IP-in-IPのトンネルインターフェースにtcpdumpを走らせたところなんですが、本来パケット先頭にあるはずのIPヘッダが、赤い四角のところから始まっているのです。

そしてその手前には14 octetsの不可思議なゴミが。

14 octets,,, 14 octets, おー、なんと!Ethernetのヘッダじゃないですか!

よく見ればご丁寧にMACアドレスらしきバイト列の後にEthernet的に次に続くのはIPヘッダと書いてくれてます。(パケットキャプチャを見慣れているとHex文字列が読めてくる不思議)

つまり、実際に送信されるパケットは| IP | Ethernet | IP | 元のペイロード |という状態になっているということ。

わかったはいいけど、なぜ付いているのかは謎のまま。

どうやらtcを使ってingress以外のルールでトンネルインターフェースに出したときにのみ生じる現象のようで。

おそらくカーネルのバグですかね。

OSがハングアップしたのも、このなぜかIPヘッダの後ろにEthernetヘッダが続くパケットを処理出来ずにカーネルが刺さったのが原因かと。

カーネルのデバッグまで出来たら時間を見つけてやりたいところですが、一旦コンセプトを確認する為に、簡単なコードを書いて確認することにしました。

これで無事にコレクションポイントでIP-in-IPのインターフェースでキャプチャを行うことで、期待通り複数のキャプチャポイントでキャプチャしたパケットを集めることが出来ることを確認出来ました。
出来ればtc周りのカーネルのコードを見て問題を直してやりたいと思います。
今日はこの辺で。