FortiGateの検証中に非対称ルーティングを発見した話

ネットワーク

はじめに

自宅に構築したFortiGate検証環境でCLIコマンドの練習をしていたところ、フロートレースで不審な挙動に気づきました。調べてみると、非対称ルーティングが発生しており、FortiGateのセッションテーブルに大量の無駄なエントリが生成・破棄を繰り返していたことが分かりました。

しかも厄介なことに、通信自体は成立していました。
ユーザー視点では問題なくつながっているため、diagnose debug flow を見ていなければ見逃していたと思います。

この記事では、発見から原因特定、解消までの流れを、検証ログとともに振り返ります。


環境と背景

CLIコマンドの練習中に気づいた

もともとの目的は、FortiGateのCLIに慣れることでした。
diagnose sys session listdiagnose debug flow といったコマンドを実際に叩き、出力の読み方を身につける練習をしていました。

今回の検証環境は以下のような構成です。

  • 自宅端末(10.0.0.3)
  • VPS(VPN接続先 10.0.0.1)
  • 自宅VPNサーバ(192.168.10.25)
  • 各検証サーバ
  • FortiGate:内部ネットワーク 192.168.10.0/24 とWAN側の間に配置
  • RDP接続先サーバ:192.168.10.21

通信のイメージとしては次のような流れです。

自宅端末(10.0.0.3)

VPS(VPN 10.0.0.1)

自宅VPNサーバ(192.168.10.25)

各検証サーバ(例:192.168.10.21)

フロートレースで違和感に気づく

diagnose debug flow でパケットの流れを追跡していたところ、RDP関連のパケットに不審な挙動が見つかりました。

id=20085 trace_id=172 func=print_pkt_detail line=5727
msg="vd-root:0 received a packet(proto=17, 192.168.10.21:3389->10.0.0.1:51450) from LAN-Agi."id=20085 trace_id=172 func=init_ip_session_common line=5898
msg="allocate a new session-002f7549"id=20085 trace_id=172 func=vf_ip_route_input_common line=2621
msg="find a route: flag=05000000 gw-192.168.10.25 via LAN-Agi"

ここで気になったポイントは3つあります。

FortiGateが見ているのは戻り方向のパケットだけ

FortiGateが受け取っているのは、192.168.10.21:3389 → 10.0.0.1 という戻り方向のパケットでした。

allocate a new session が毎回出ている

パケットが来るたびに新しいセッションが生成されています。
通常であれば、最初の通信で作られたセッションに後続パケットが紐づくはずです。

同じLAN側へ戻している

転送先が gw-192.168.10.25 via LAN-Agi となっており、入ってきたインターフェースと同じLAN側へ戻している、いわゆるヘアピンでUターンするような動きになっていました。


TCPは既存セッションに乗るのに、UDPだけ毎回新規セッションになる

TCPのパケットについては、Find an existing session で既存セッションにマッチしていました。
一方で、UDP(RDPがパフォーマンス向上のために併用する通信)は、毎回新規セッションが生成されていました。(UDPのプロトコル番号は17です。)

trace_id=172 → allocate a new session-002f7549
trace_id=173 → allocate a new session-002f754a
trace_id=176 → allocate a new session-002f754c
trace_id=177 → allocate a new session-002f754d
trace_id=180 → allocate a new session-002f754e
trace_id=181 → allocate a new session-002f754f
...(以降も延々と続く)

この時点で、「これは単なる一時的な通信ではなく、経路そのものに問題があるのではないか」と考えました。


原因は非対称ルーティングだった

調べてみると、行きと帰りで経路が異なる非対称ルーティングが発生していました。

行きの経路(FortiGateを通らない)

自宅端末(10.0.0.1)
→ VPS(VPN)
→ 自宅VPNサーバ(192.168.10.25)
→ RDP先(192.168.10.21)

行きのパケットは、自宅VPNサーバからRDP先へ同一セグメント内で直接到達していたため、FortiGateを経由していませんでした。

戻りの経路(FortiGateを通る)

RDP先(192.168.10.21)
→ FortiGate(192.168.10.254)
→ 自宅VPNサーバ(192.168.10.25)
→ VPS(VPN)
→ 自宅端末(10.0.0.1)

RDP先サーバのデフォルトゲートウェイがFortiGateを向いていたため、10.0.0.1 宛の戻りパケットはFortiGateに送られていました。
FortiGateはルーティングテーブルに従い、192.168.10.25 をネクストホップとしてLAN側に転送していました。

つまり、

  • 行きはFortiGateを通らない
  • 戻りだけFortiGateを通る

という、典型的な非対称ルーティングになっていました。


なぜセッションが大量に生成・破棄されるのか

FortiGateはステートフルファイアウォールです。
通常は、通信開始時のSYNなどを見てセッションを作成し、戻りパケットをそのセッションに紐づけて処理します。

しかし今回は、FortiGateは行きのパケットを見ていません。

その状態で戻りのUDPパケットだけが到着すると、FortiGateは以下のような動作を繰り返します。

  1. 戻りのUDPパケットが到着する
  2. 該当するセッションがないため、新規セッションを生成する
  3. ファイアウォールポリシーに合致するため、そのまま転送する
  4. 片方向しかトラフィックがないため、短時間でセッションがタイムアウト・破棄される
  5. 次のUDPパケットが到着すると、再び新規セッションを生成する

この繰り返しが、フロートレース上の allocate a new session の連発として現れていました。


それでも通信は成立していた理由

今回ややこしかったのは、通信そのものは成立していたことです。

IP通信そのものはステートレスな考え方で成り立っているため、行きと帰りの経路が異なっていても、パケットが最終的に届けば通信は成立します。
今回は、FortiGateのポリシー上もパケットは許可されていたため、戻りパケットはそのまま転送されていました。

つまり、

  • FortiGateのセッション管理としては異常
  • でもパケット転送自体はできている
  • そのためユーザーからは問題が見えにくい

という状態でした。

「通っているから問題ない」と思い込みやすい、非常に気づきにくいトラブルだったと感じます。


NPUオフロードも失敗していた

さらに確認すると、セッションの異常だけでなく、NPU(Network Processing Unit)へのオフロードにも失敗していました。

NPUとは

NPUは、FortiGateに搭載されているネットワーク処理専用のハードウェアプロセッサ(ASIC)です。

FortiGateのパケット処理には大きく2つの経路があります。

  • slow path
    セッション確立前の最初の数パケットをCPUで処理する
  • fast path
    セッションが正常に確立された後、NPUへ引き渡して高速処理する

正常な通信であれば、最初だけCPUで処理し、その後はNPUにオフロードされることで、CPU負荷を抑えつつ高いスループットを維持できます。

非対称ルーティング時のNPU状態

問題のあったセッションを diagnose sys session list で確認すると、以下のような状態になっていました。

npu_state=00000000
npu info: flag=0x00/0x00, offload=0/0
no_ofld_reason:
ofld_fail_reason(kernel, drv): none/not-established, none(0)/none(0)

注目すべき点は以下です。

  • offload=0/0
    → NPUオフロードされていない
  • not-established
    → セッションが正常確立とみなされていない

FortiGateは行きのパケットを見ていないため、セッションを正しく確立できず、NPUへ引き渡す条件を満たしていませんでした。
その結果、すべてのパケットがCPUで処理される slow path のままになっていました。


小規模では見えにくいが、大規模では危険

今回の自宅環境では、RDP1本程度の通信だったため、体感上の大きな問題はありませんでした。
しかし企業環境で、数十〜数百ユーザーが同時にRDPやTeams通話などUDPを多用する通信を行っていた場合は、かなり危険だと思います。

想定される影響は次のとおりです。

  • セッションテーブルの無駄な消費
  • CPU負荷の増大
  • NPUオフロード失敗によるスループット低下
  • FortiGate配下全体の通信性能悪化

つまり、表面的には通信できていても、内部ではじわじわとファイアウォールに負荷をかけ続ける状態になり得ます。


静的ルートを追加して解消した

解消策として、RDP先サーバに対して、10.0.0.0/24 宛のパケットを VPNサーバ(192.168.10.25)へ直接送る静的ルート を追加しました。

これにより、戻りパケットがFortiGateを経由せず、VPNサーバへ直接返るようになります。
つまり、行きも戻りも同じ経路を通る対称ルーティング に修正しました。


解消後のフロートレース

非対称ルーティングを解消した別サーバ(192.168.10.22)のフロートレースでは、正常な対称ルーティングの動作が確認できました。

trace_id=211 func=print_pkt_detail
msg="received a packet(proto=6, 192.168.10.22:59071->172.202.64.10:443)
from LAN-Agi. flag [S]"trace_id=211 func=init_ip_session_common
msg="allocate a new session-002f7d91"trace_id=211 func=fw_forward_handler
msg="Allowed by Policy-3: SNAT"trace_id=212 func=print_pkt_detail
msg="received a packet(proto=6, 172.202.64.10:443->192.168.200.254:59071)
from wan. flag [S.]"trace_id=212 func=resolve_ip_tuple_fast
msg="Find an existing session, id-002f7d91, reply direction"

非対称ルーティング時との違いは明確です。

  • allocate a new session は最初のSYNで1回だけ
  • 以降のパケットは Find an existing session で同一セッションにマッチ
  • NPUオフロードも成功している

これが本来の正常な挙動です。


解消前後の比較

項目非対称ルーティング(解消前)対称ルーティング(解消後)
SYNをFWが見るかいいえはい
セッション生成パケットごとに新規生成最初の1回だけ
NPUオフロード失敗(not-established)成功
パケット処理すべてCPU(slow path)NPUで高速処理(fast path)
FW負荷高い低い

まとめ

非対称ルーティングは、「通るけど問題がある」状態を作ります。

IP通信の観点だけで見れば、行きと帰りの経路が異なっていても通信自体は成立することがあります。
しかし、その途中にステートフルファイアウォールがいる場合、片方向のパケットしか見えないことでセッション管理が破綻し、今回のように大量のセッション生成・破棄が発生します。

さらに、セッションが正常に確立されないことでNPUオフロードにも失敗し、本来ハードウェアで高速処理できるはずの通信が、すべてCPU処理に落ちてしまいます。

といったことを学びました。

今回はCLIの練習から偶然気づいた事象でしたが、実務でも十分起こりうる、非常に学びの大きい検証でした。



参考文献