Trema/MyRoutingSwitch(5), Topology Change and Re-route(2)

はじめに

前回はトポロジの変化と、現状の MyRoutingSwitch が行う経路制御処理の基礎について、でした。今回は現状の動作の問題点と対応方針についてみていきます。いまはリンクダウンに対応できることを目標として考えます。

問題点

トポロジ変化に対応したスイッチのフローエントリ書換処理がない

コントローラでトポロジ変化をキャッチできても、何もしなければスイッチ持っているフローエントリは変化しません。あたりまえですが…。トポロジ変化を認識すると、コントローラ内部に保持しているトポロジ情報は更新され、新しく入ってきたパケット(packet-in)については変更後のトポロジ情報で経路を計算します(Observer Pattern)。問題は既にスイッチに投入しているエントリで、放っておけば既にダウンしてしまったリンクに対して packet-out するわけです。

つまり、トポロジ変化が発生しても、スイッチが持っている既存のフローエントリをコントローラがどうにかしなければ、既に使えなくなったポートにパケットを送ろうとして、通信が途切れてしまう…と。そりゃそうだ。しばらく通信が途切れて問題のフローエントリが age out してくれれば良いですが、通信を続けようとしてパケットを出すとむしろ通信できない状況が継続してしまいます。

ということで、制御(コントローラ実装)上、簡単そうな解決策はこんな感じでしょうか。

  • packet-in によって生成するフローエントリはハードタイムアウトさせる。
  • トポロジ変化をキャッチしたら環境内のすべてのスイッチのフローテーブルをクリアしてしまう。

単純な分、そこそこ確実に動きそうではあります。が、環境に流れている既存の通信への影響や、packet-in 処理効率といった面からはよろしくはなさそうです。規模(通信量/フロー数やスイッチ数)が大きくなればなるほど問題でしょう。(何より作る上で面白くないし。)

不要フローエントリ削除処理の導入

残っているフローエントリについて、直接悪さをしているのは、既にダウンしてしまったポート(N)に対して actions=output:N してしまっているエントリなわけです。なので、次の解決策として、

  • port_status でキャッチしたポート(リンクダウンしたポート)に output しているフローを削除する

ことを考えてみます。

どのスイッチのどのポートが落ちたかというのは port_status イベントハンドラにわたされる DataPath ID および PortStatus オブジェクトでわかります。あとはそれを元に send_flow_mod_delete するだけ。

send_flow_mod_delete(
  dpid,
  :out_port => port
)

ただそんな単純でもなかったですね…。やっぱり問題はあります。

中間経路でのイベント発生とフロー書換の問題

トポロジ変化(リンクダウン)をキャッチして、そのポートに output しているエントリを消すことを考えます。もう一度 2-hosts/4-switches 直列構成で考えてみます。ただし、障害に対して代替経路がないといけないので sw2-sw3 の間に sw5 を追加しておきます。パスが増えるので、トポロジ変化前(リンクダウン発生前)は sw5 を経由しません。

リンクダウン発生

既に host(a)-host(b) が双方向で通信している状態で、sw2-sw3 間のリンクダウンが起きたとします。


  • [1] リンクダウン発生: sw2,sw3 は port_status message をコントローラに送信する。
  • [2] コントローラは受け取った情報を元に flow mod delete を sw2,sw3 に送付する。sw2,sw3 は受信した flow mod に従って、リンクダウンしたポートに output しているフローエントリを削除する。
リンクダウン後のパケット送付(片方向)

トポロジ変化を補足した後で host(a) からパケットが送られます。


  • [3] host(a) が host(b) 宛てのパケットを送出する。
  • [4] sw1 はマッチするフローエントリを持っているので、それに従ってパケットを送出(output:2)する。
  • [5] sw2 はマッチするフローエントリがないので packet-in を発生させる。
  • [6] Controller は、パケットが入ってきた位置を arp table に記録する。宛先(host(b)) の位置がわかる(arp tableにある)ので、host(a)→host(b) への経最短路を計算する。
    • このとき「host(a)の位置」が 「packet-in が発生した場所」になってしまいます。
  • [7] Controller は得られた経路の中間スイッチそれぞれにフローエントリを送る(flow mod)。また末尾(sw4)については packet-out を同時に行う。
  • [8] sw4 は packet-out メッセージを受け取って、host(a) が送付したパケットを host(b) 向けに出力する。
リンクダウン後のパケット送付(逆方向)

host(b) は host(a) に応答します。


  • [9] host(b) は host(a) に向けてパケットを送出する。
  • [10] sw4 はマッチするフローエントリを持っているので、それに従ってパケットを送出(output:1)する。
  • [11] sw3 はマッチするフローエントリがないので packet-in を発生させる。
  • [12] Controller は、パケットが入ってきた位置を arp table に記録する。宛先(host(b)) の位置がわかる(arp tableにある)ので、host(b)→host(a) への経最短路を計算する。
  • [13] Controller は得られた経路の中間スイッチそれぞれにフローエントリを送る(flow mod)。また末尾(sw2)については packet-out を同時に行う。
    • [12][13]で、arp table 上は、host(a),host(b) が sw2/sw3 にあるように見えてしまうことに注意。
  • [14] sw4 は packet-out メッセージを受け取って、host(b) が送付したパケットを host(a) 向けに出力する。
    • "末尾" に対するフローエントリはマッチルールが異なるので別なエントリが追加される。
  • [15] sw1 はマッチするフローエントリを持っているので、それに従ってパケットを送出(output:1)する。

まとめ

中間経路でのイベント発生とフロー書換の問題が結構面倒です。それなりに動いちゃうんですけどね。トラブルが起こって、エントリが空白になった区間だけフローエントリを入れ直しましょう、その両端は今まで通り流しましょう、なので。マッチしないエントリはエイジアウトさせるようにしておけばいいし。

ただ、arp table と実際の接続位置のミスマッチはよろしくない。認識されている位置が異なるということは、たとえば sw5 に host(c) がいて host(c)→host(a) をした場合には、この arp entry がある限り、host(c)-sw5-sw2-host(a) として経路計算されてしまうということになります。(トポロジが単純なので sw2→sw1→host(a) はちゃんとパケット投げられてしまうのですが…。) 通常想像する動きと実際の制御が異なっているのは、少なくともトラブルシュートとかやる上ではとても良くない。過去の状態変化履歴まで見てトラブルシュートとか…ヤバイでしょう。

さて。そういう問題を含めてどうすれば良いかというのを考えるわけです。トポロジ変化がある状況では、「packet-in が発生する場所」=「endpoint node が接続されている場所」ではないということです。暗黙の仮定が崩れている。であれば、

  • スイッチ間リンクで発生した packet-in では arp table を更新しない

ということにしてしまえばいい*1。packet-in 即 arp table update をやめると、arp table は「endpoint node が接続されている位置」を保持する物に固定できます。これも今更な話ですが、なぜ arp table を持つのかというと「誰がどこにいるのか」を記録しておかないと最終的な投げ先がわからないからです。そこが崩れないようにする。

これができれば次の話が成立します。

  • packet-in が発生した場合、endpoint node が接続されているスイッチ間のすべての経路を再計算しなおす。

経路中間で packet-in が上がった場合でも、arp table (endpoint の接続位置)を元に始点・終点を求めて間のフローエントリを全部書き直します。end-to-end の経路を全部描き直すことで、変なエントリが残ったり、トポロジ状態の履歴に応じて変なフローが入ったりすることを防止します。

…じゃないかなーと考えられるわけですが、それが本当に上手くいくのかどうか実際に実装して試していきます。が、その前に、上で説明した動作を実際にテストしてみます。

*1:ここでいう "スイッチ間リンク" はコントローラが定期的に送出するスイッチ間 LLDP が通っているリンクのことです。