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

はじめに

リンク追加(リンクアップ)に伴うトポロジ変化について。今回は、リンクが増えたときにどういうフロー情報操作が必要になるか? という話を見ていきます。

昨日の夜 master にマージした他ので一応ソースも。

シナリオ

スクリプト前回参照

  • host1-host2, host1-host3 間で双方向に通信
  • リンクダウン→フロー片寄せ→リンクアップ→元に戻るか?
Step.1

初期状態です。host1-host2, host1-host3 間で相互に通信ができる状態(双方向に通信できるようフローエントリが設定されている状態)です。

  • いま、コントローラ内では host1-host2, host1-host3 間双方向の4フローがキャッシュされています(@flowindex が持っています)。この後のステップでもこの4つのフロー情報があります。
    • host1→host2, host2→host1, host1→host3, host3→host1


Step.2

sw2-sw3 間のリンクを落としてみます。使えなくなったフローエントリが削除されます。


Step.3

再度 host1-host2/3 間で双方向に通信を行います。host1-host2 間のフローは上書きされます。host1-host3 のフローはそのまま。


Step.4-5

落としていた sw2-sw3 間のリンクを復旧させます(初期のトポロジに戻します)


  • Controller は、リンクアップを検出した後、キャッシュされている4つのフロー情報を参照して、それぞれ再度経路の再計算とフローエントリの送信を行います。
  • sw5-sw7 には、リンクダウン発生時に追加してあった host1-host2 間のフローエントリ(リンクアップしてトポロジが初期状態に復旧した後は不要なエントリ)が残っています。これが、エントリのエイジアウトと flow removed 処理について邪魔になります。
    • Step.4 でリンクアップした後、環境内のフローはキャッシュされたフロー情報(@flowindex)もとに、再計算・再書き換えされます。Trema を使っていると特に操作しない限り cookie は設定順になります。上の図は実際に試してみたフロー情報を元に書き起こしていますが、1つのリンクアップ操作に対して2ポートのリンクアップイベントが起き、それぞれフローの再計算・再書換が起こるので、sw1-sw4のフローエントリの cookie 値は不連続になっています。
    • このとき、sw1-sw4 にある host1-host2 のフローエントリよりも、sw5-sw7 にある(不要になった)host1-host2 のエントリの方が「古く」「使われないので」先に age out します。
    • Controller には、sw5-sw7 の既に使われていないエントリについて、flow removed が届きます。単純に "host1-host2" というフロー情報だけ見ていま使っている(sw1-sw4設定時の) @flowindex にキャッシュされたフロー情報を消してしまうと、環境内の情報とキャッシュされた情報に不整合が起きます。

対処

"host1-host2" という src/dst 情報だけキャッシュしているだけだと、flow removed が発生したときに、それが今使ってる物なのか既に古くて不要な(無視して良い)物なのかわからないわけです。なので、もう図に書いてあるんですが、フロー情報をキャッシュする場合は、どういう経路か、という情報を併せて持っておく必要があります。上の場合、sw5,sw6,sw7 それぞれから "host1-host2" のフローが消えたというメッセージ(flow removed)が届きますが、いま同じ "host1-host2" のフローについては sw1-sw4 で持っているので無視しよう、という判断ができれば良いわけです。

  • FlowIndex::delete
    • フローエントリ(@flowsの要素: FlowEntry)には、path (経路...src/dst で経由するスイッチのdpidのリスト)を持たせてあります。
    • flow removed が起きた dpid がフローエントリの path に含まれていれば、「今使っている経路」にあるフローエントリが消えたので、キャッシュを消します。そうでなければ古いエントリなので無視します。
    • ここで、フローエントリに持たせているパス情報には末尾(last hop)の情報を含めていません。endpoint が直接接続されている last hop はどういう経路を使っていてもかならず含まれるからです。
  # args are Trema::Mac/Trema::IP object
  def delete dpid, macsa, macda, eth_type, ipv4_saddr, ipv4_daddr
    puts "[FlowIndex::delete] #{dpid.to_hex},#{macsa},#{macda},#{eth_type.to_hex},#{ipv4_saddr},#{ipv4_daddr}"

    flow = FlowEntry.new(
      macsa,
      macda,
      eth_type,
      ipv4_saddr,
      ipv4_daddr,
      nil # dummy
    )
    @flows.delete_if do | each |
      # "path", there is no need to contain last-hop of path.
      # because last-hop is always used by old and new path.
      (each.path.include?(dpid)) and (each == flow)
    end
  end

まとめ

トポロジ変化がある場合、同一のフロー(src/dst)に対して複数の経路を設定する可能性があり、「今使っている経路」で発生したイベントかどうか、というのをチェックする必要があることがわかりました。面倒ですね…。

とまあ、こんな感じでフローを書き換えてやると、トポロジが変わってもそれなりに動いてくれるようです(いくつかテストしてみた限りは)。実際にはスイッチのアップダウンについてもテストをしてみましたが、この辺までの処理が書いてあれば何とかなっている気配。ここから先はもうちょっと実環境に近い状態でテストしなければいけない気がするので、手が出せていません。(というか手元の環境上それをやるのがむずい。)