ふたひねりきかせたOpenFlowの利用事例

ということでOpenFlow、なんですが、仮想化・抽象化されたネットワークにおけるサービスの提供といった、いわゆるSDNの手法として導入されるケースが一般的かと思います。 しかし本稿でご紹介するのはそうした事例とは全く異なるもので、OpenFlowならではのクリーンなAPIとハードウェアスイッチの処理能力を生かした、効果的なDDoSアタック防御の手法です。
以下の内容は、ネットワークエンジニア向け情報サイト、Packet Pushersのコミュニティブログに寄稿した3本の記事をさくらのナレッジ向けに書き直したものです。

お題目

大規模DDoSアタックが発生すると、さくらインターネットのネットワーク網(以下:当社ネットワーク)への複数の入口から攻撃パケットが一気に流入してきます。

こうした大量のパケット流入によるネットワーク帯域輻輳を防ぐために、以前から当社ではd/RTBHという手法で対応していますが、これは簡単に説明すると、例えば10.0.0.1を宛先IPアドレスとするDDoS攻撃を検知したら、当社の網内へ10.0.0.1/32宛のBGP経路を広報するというものです。その際、このBGP経路に対してある決まったパス属性を付与することによって、10.0.0.1/32へのネクストホップをnullにセットし、かつこの経路を当社の外部へは広報しない、というルールが適用されます。この仕組みによって、10.0.0.1宛のパケットは当社のネットワーク網の入口にいるすべてのBGPルータ(ボーダルータ)でドロップされるため、網内での帯域輻輳を防ぐことができます。この手法では、攻撃ターゲット以外のIPアドレスでの通信については輻輳による疎通障害から救済できますが、このIPアドレスと外部ネットワークとの通信は完全にNGとなります。

いっぽう、当社では2010年末ころからDDoSアタック検知の仕組みを大きく改善したことにより、攻撃のターゲットや規模だけでなく攻撃パケットの送信元やタイプなどをすぐに特定できるようになりました。これらの情報を元に、攻撃パケットのみをドロップすることができれば、攻撃のターゲットとなってしまったIPアドレスにおける通信も救済できることになります。

ではどのような手法で実装するのが最適か、既知のやり方もふくめて比較検討しました。詳細は割愛しますが、稼働中の機器やネットワーク構成へのインパクト、オペミスのリスク、導入・運用コスト、等をできるだけ抑えつつ、安定運用が可能であることが要件となります。

OpenFlow1.0を使うという解

攻撃のターゲット宛トラフィックをいったんOpenFlowスイッチへ転送し、そこでDDoSパケットをドロップしつつ正常なトラフィックは宛先IPアドレスをつけかえてからiBGP網を通過させてiDCまで転送します。そしてiDCに到達したパケットをOpenFlowスイッチで元の宛先IPアドレスにつけ戻し、通常のルーティングでお客様機器に届ける、という処理を実装します。
バックボーン網に接続されたOpenFlowスイッチは、転送されたパケットをドロップするか、またはパケットの宛先IPアドレスを書き換えて着信したポートから網側に送り返すか、のいずれかです。
OpenFlowスイッチにおけるパケット処理はすべて、モニタリング用アプリケーションからのリクエストを受け取るOpenFlowコントローラがプッシュするスタティックなフローで制御しますので、スイッチが生成するpacket_inに基づく処理はありません。

ddos_cleaning_flowmod_j

上のイラストを例にして以下の説明をすすめますが、実際のネットワーク構成と比べると簡略化されていることをお断りしておきます。

OpenFlow対応スイッチの配置と事前設定

当社のバックボーンネットワークで稼働するすべてのルータ(ボーダ、集約、iDCコア)とOpenFlow対応スイッチを10Gインターフェイス×1で接続します。

すべてのBGPルータ(ボーダ、集約)に対して、接続インターフェイスに同一のIPアドレスを設定します。

interface ethernet x/x
  ip address 192.0.2.1/30

また、対向のL2アドレス解決のためスタティックARPを登録します。

arp 192.0.2.2 0000.0000.0001 ethernet x/x

この192.0.2.2がRTBHで広報するBGP経路のネクストホップとなります。
また、ここで登録するMACアドレスは適当でかまいません。着信フレームの宛先MACがなんであれ、スイッチは登録されているフローに基づいて処理するだけだからです。

つぎに、(BGPプロセスが起動していない)各iDCのコアルータに対して、スイッチとの接続インターフェイスにそれぞれ個別のL3アドレスを設定します。
例えばIDC-AのRT31に対しては、以下のアドレスを割り当てるとします。

interface ethernet x/x
  ip address 10.3.0.1/30

対向のL2アドレス解決のためスタティックARPを登録しますが、ここでもMACアドレスはなんでも構いません。

arp 10.3.0.2 0000.0000.0001 ethernet x/x

もう一点、各iDC毎に、宛先IPアドレスつけかえ用のプールアドレスを確保して、OpenFlowスイッチ向けにスタティックルートを追加します。
たとえば、IDC-Aにプールアドレスとして10.3.3.0/28を確保して、RT31へ以下の設定を追加します。

ip route 10.3.3.0/28 10.3.0.2

この経路を網内へ広報するため、iDCコアルータではスタティックルートをOSPFへ再配布する必要があります。

既存の機器への設定はこれでおしまいです。設定したのは、インターフェイスのIPアドレスとスタティックARP、そしてiDCコアルータにスタティックルートひとつ、それだけです。

スイッチ側で必要となる設定はほとんどありません。 OpenFlowコントローラのIPアドレスと、接続インターフェイスでOpenFlowを有効にするのと、フェイルモードをセキュアにセットするだけです。コントローラはFloodLightを使いますが、こちらもほとんど素のままです。

そして、コントローラとの接続が確立したスイッチに対して、あらかじめ「デフォルトドロップ」として作用する低プライオリティ(10000)のフローをプッシュしておき、packet_inメッセージが生成されないようにします。

事前の仕込みはここまでです。
では、実際にDDoSが発生した時のオペレーションとアプリが行う処理をみていきます。

DDoSアタック発生時のオペレーション

当社ネットワーク網内の10.0.0.1宛に攻撃トラフィックが流入してきたことを検知したアプリからアラートを受信したら、アプリのUIを使ってRTBHを実行します。
実際の経路制御処理としては、ネクストホップ192.0.2.2という属性を付与した10.0.0.1/32へのBGP経路を生成してルートリフレクタ(RR)からBGPルータ(RT11とRT21)へアップデートすることになります。
この制御によって、宛先10.0.0.1のパケットを受信したRT11とRT21は、ネクストホップ192.0.2.2へのコネクテッド経路を参照してOpenFlowスイッチに転送します。

この時点で、OpenFlowスイッチのフローテーブルには「デフォルトドロップ」のエントリがひとつだけあるので、宛先10.0.0.1のパケットはすべて、このフローにマッチしてドロップされます。このなかには、DDoSだけでなくお客様機器宛の正当な通信のパケットも含まれています。

ここでアプリによってただちに、BGPルータ(RT11とRT21)に接続されたOpenFlowスイッチに対して、DDoSパケット送信元からのパケットをドロップする最大プライオリティ(32767)のフローを追加します。各フローのマッチ条件として、送信元IPアドレスと宛先IPアドレスの両方を設定します。したがって、各スイッチに追加するフローエントリ数はDDoSパケット送信元IPアドレスの合計数となります。

この時点で、DDoSパケットは送信元IPアドレスごとにプライオリティ32767のフローにマッチしてドロップされますし、また、正当な通信のパケットもプライオリティ10000のフローによりドロップされます。

さらにアプリが以下の処理を実行します。

  1. 攻撃のターゲット(10.0.0.1)となっているお客様機器は、データセンタIDC-Aに収容されていることを特定
  2. IDC-A用に事前に確保したプールアドレス10.3.3.0/28から未使用の10.3.3.1を割り当て
  3. IDC-Aのコアルータ(RT31)に接続されたOpenFlowスイッチに対して、宛先IPアドレス10.3.3.1をマッチ条件にして、プライオリティ32766のフローを追加
  4. actions=mod_dl_src:00:00:00:00:00:01,mod_dl_dst:yy:yy:yy:yy:yy:yy, mod_nw_dst:10.0.0.1,IN_PORT
  5. BGPルータ(RT11とRT21)に接続されたOpenFlowスイッチに対して、宛先IPアドレス10.0.0.1をマッチ条件にして、プライオリティ32766のフローを追加
  6. actions=mod_dl_src:00:00:00:00:00:01,mod_dl_dst:xx:xx:xx:xx:xx:xx, mod_nw_dst:10.3.3.1,IN_PORT

ここまでの処理が実行されると、当社のBGPメッシュに到達した10.0.0.1宛のパケットは、

  • DDoS攻撃トラフィックのみOpenFlowスイッチでドロップ、
  • その他の正当なトラフィックは、1回目に経由するOpenFlowスイッチでいったん10.3.3.1宛となり、2回目に経由するOpenFlowスイッチで元の10.0.0.1宛となってIDC-Aのお客様機器まで無事到達、

という具合に制御されます。経路を2回ひねってお客様機器までパケットを届ける、というかんじです。

ハードウェアスイッチのOpenFlow対応状況

OpenFlowを使ったトラフィックの制御については、Open vSwitchを使ってすぐに挙動を確認できるので助かります。実際にOpen vSwitchと検証機材を組み合わせて、上記の制御方法をテストしてみたところ、想定通りに動作することを確認できました。

そこで、OpenFlow1.0対応の(とある)ハードウェアスイッチをベンダから借り受けて試してみましたが、残念ながら勝手がちがいました。とりあえずテスト環境をセットアップし、トラフィック流してフローを投入してみましたところ、パケットジェネレータ→BGPルータ→OpenFlowスイッチと転送されたパケットが、BGPルータまで戻ってきません。スイッチのテーブルエントリをダンプしてみると、BGPルータへ戻すフローのマッチカウントはアップするのですが、同時にBGPルータとの接続インターフェイスで、出力エラーカウントもアップします。

そこで、コントローラとスイッチのTCPセッションをキャプチャしてみたところ、スイッチがコントローラへ送出するFeatures Replyパケットから、宛先IPアドレスを書き換えるアクションは非対応であることが分かりました。

        Capabilities: 0x00000087
            .... .... .... .... .... .... .... ...1 =   Flow statistics: Yes (1)
            .... .... .... .... .... .... .... ..1. =   Table statistics: Yes (1)
            .... .... .... .... .... .... .... .1.. =   Port statistics: Yes (1)
            .... .... .... .... .... .... .... 0... =   802.11d spanning tree: No (0)
            .... .... .... .... .... .... ...0 .... =   Reserved: No (0)
            .... .... .... .... .... .... ..0. .... =   Can reassemble IP fragments: No (0)
            .... .... .... .... .... .... .0.. .... =   Queue statistics: No (0)
            .... .... .... .... .... .... 1... .... =   Match IP addresses in ARP pkts: Yes (1)
        Actions: 0x0000003f
            .... .... .... .... .... .... .... ...1 =   Output to switch port: Yes (1)
            .... .... .... .... .... .... .... ..1. =   Set the 802.1q VLAN id: Yes (1)
            .... .... .... .... .... .... .... .1.. =   Set the 802.1q priority: Yes (1)
            .... .... .... .... .... .... .... 1... =   Strip the 802.1q header: Yes (1)
            .... .... .... .... .... .... ...1 .... =   Ethernet source address: Yes (1)
            .... .... .... .... .... .... ..1. .... =   Ethernet destination address: Yes (1)
            .... .... .... .... .... .... .0.. .... =   IP source address: No (0)
            .... .... .... .... .... .... 0... .... =   IP destination address: No (0)
            .... .... .... .... .... ...0 .... .... =   Set IP TOS bits: No (0)
            .... .... .... .... .... ..0. .... .... =   TCP/UDP source: No (0)
            .... .... .... .... .... .0.. .... .... =   TCP/UDP destination: No (0)
            .... .... .... .... .... 0... .... .... =   Enqueue port queue: No (0)

一方、Open vSwitchとコントローラとのセッションを再度実行してパケットをキャプチャすると、確かにこのビットは立ってました。

じつは、ここでようやくOpenFlow1.0の仕様をあらためて確認するまで、パケットヘッダを書き換えるアクションについては、プロトコル上はオプション扱いであることを知らなかったのです。

さらにいくつかのスイッチでテストしてみたりベンダへの確認や調査を通じて、L3アドレス書き換えアクションをハードウェア処理するチップは、この時点ではまだ出回っていなさそうに見受けられました。チップベンダが製品化するにあたってSDN需要が前提になるのであれば、ヘッダ書き換えについては、MACやVLAN IDのサポートに限定されるということもあり得ると考えました。

せっかくうまくいくかと期待した手法ですが、残念ながらいったんあきらめ、代替案での動作検証にとりかかりました。

Centec V330との出会い

ところが、冒頭で紹介したPacket Pushersのコミュニティブログで、L3アドレス書き換えをサポートするOpenFlow1.0対応スイッチの提供ベンダを呼びかけたところ、期待薄だったにもかかわらず応答がありました!

Centec Networks社から、同社製チップで製品化した V330 の紹介を受けたのです。とはいえ、同社製品はこれまでまったく使用実績がないため、手探りでの情報交換からスタートし、ほどなく国内代理店の株式会社トーメンアドバンストテクノロジーより検証機材を借り受けてから、前回のテスト環境を使ってただちに動作確認を行いました。

その結果、V330が2度の宛先IPアドレス書き換えを10Gワイヤレートかつ30usecという実用レベルの遅延で処理できることが分かりました。また、フローエントリも最大2560まで登録できるため、今回の目的には十分といえます。エージングテストの経過も問題なく、マネジメント機能を含めたスイッチとしての基本性能も実用レベルに達していることを確認できました。なお、この検証については、Packet Pushersに寄稿した3本目のブログ記事ですこし詳しく紹介しています。

当社では現在、このV330を各拠点に配置し、実際に本稿で紹介した手法を使ってDDoSアタック対策を高度化するための準備をすすめています。

最後に...

以上、当社のネットワークで実際に準備をすすめている、ちょっとかわったOpenFlowの使い方について、ハードウェアスイッチ選定のいきさつも交えて紹介させていただきました。ベンダによるOpenFlowへの今後の対応については、バージョン1.3がターゲットになっていくという見通しもあるようです。また、最近の動向としてベンダ主導でOpenDayLightというプロジェクトも立ち上がっており、まだまだ1年先の状況ですらまったく予測がつかないところです。

とはいえ、SDNの実装ということに限らず、この事例のように局所的にハマりどころがあれば、コスト面やAPIでのメリットを生かせるケースもあるのでは、と個人的には感じたりもしています。本稿をご覧いただいたネットワーク技術者の皆様はいかがお考えでしょうか?ご意見お待ちしております。